feat: add workspace backend config schema
This commit is contained in:
parent
52b445e6bc
commit
5a8d18ec27
|
|
@ -1,9 +1,11 @@
|
|||
---
|
||||
title: 'Workspace Backend設定ファイルのスキーマを定義する'
|
||||
state: 'planning'
|
||||
state: 'done'
|
||||
created_at: '2026-07-01T14:41:48Z'
|
||||
updated_at: '2026-07-01T14:43:04Z'
|
||||
updated_at: '2026-07-01T15:48:17Z'
|
||||
assignee: null
|
||||
queued_by: 'yoi ticket'
|
||||
queued_at: '2026-07-01T15:32:38Z'
|
||||
---
|
||||
|
||||
## 背景
|
||||
|
|
|
|||
|
|
@ -20,4 +20,76 @@ Workspace Backend config file schema を定義する Ticket として詳細化
|
|||
- data 本体、workspace identity、Ticket/Objective、Runtime fs-store、transcript、secret 値は config file に入れない。
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- event: intake_summary author: hare at: 2026-07-01T15:32:38Z -->
|
||||
|
||||
## Intake summary
|
||||
|
||||
Marked ready by `yoi ticket state`.
|
||||
|
||||
---
|
||||
|
||||
<!-- event: state_changed author: "yoi ticket" at: 2026-07-01T15:32:38Z from: planning to: ready reason: cli_state field: state -->
|
||||
|
||||
## State changed
|
||||
|
||||
Marked ready by `yoi ticket state`.
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- event: state_changed author: "yoi ticket" at: 2026-07-01T15:32:38Z from: ready to: queued reason: queued field: state -->
|
||||
|
||||
## State changed
|
||||
|
||||
Ticket を `yoi ticket` が queued にしました。
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- event: state_changed author: "yoi ticket" at: 2026-07-01T15:32:39Z from: queued to: inprogress reason: cli_state field: state -->
|
||||
|
||||
## State changed
|
||||
|
||||
State changed to `inprogress`.
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- event: implementation_report author: hare at: 2026-07-01T15:48:09Z -->
|
||||
|
||||
## Implementation report
|
||||
|
||||
Implemented Workspace Backend config file schema and resolver.
|
||||
|
||||
Changes:
|
||||
- Added `workspace_server::config` with `WorkspaceBackendConfigFile` serde TOML schema.
|
||||
- Fixed config path: `<workspace_root>/.yoi/workspace-backend.local.toml`.
|
||||
- Config file absence resolves to defaults.
|
||||
- Unknown TOML fields are rejected via `deny_unknown_fields`.
|
||||
- Relative paths resolve from workspace root; absolute paths are preserved.
|
||||
- Added resolved config boundary for `ServerConfig`, listen address, and database path.
|
||||
- Added data root derivation for `workspace.db` and `embedded-runtime`.
|
||||
- Added remote runtime config schema with `token_ref`, while failing closed until secret ref resolution exists; raw token values are not in schema.
|
||||
- Updated `yoi-workspace-server serve` to load config before applying existing legacy dev CLI overrides.
|
||||
- Added focused tests for defaults, unknown fields, path resolution, data root derivation, secret value rejection, and token_ref fail-closed behavior.
|
||||
|
||||
Validation:
|
||||
- `cargo test -p yoi-workspace-server`
|
||||
- `cargo check -p yoi`
|
||||
- `cargo fmt --check`
|
||||
- `git diff --check`
|
||||
- `nix build .#yoi --no-link`
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- event: state_changed author: "yoi ticket" at: 2026-07-01T15:48:17Z from: inprogress to: done reason: cli_state field: state -->
|
||||
|
||||
## State changed
|
||||
|
||||
State changed to `done`.
|
||||
|
||||
|
||||
---
|
||||
|
|
|
|||
380
crates/workspace-server/src/config.rs
Normal file
380
crates/workspace-server/src/config.rs
Normal file
|
|
@ -0,0 +1,380 @@
|
|||
use std::net::SocketAddr;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{fs, io};
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::hosts::RemoteRuntimeConfig;
|
||||
use crate::identity::WorkspaceIdentity;
|
||||
use crate::server::{AuthConfig, ServerConfig};
|
||||
use crate::{Error, Result};
|
||||
|
||||
pub const WORKSPACE_BACKEND_CONFIG_RELATIVE_PATH: &str = ".yoi/workspace-backend.local.toml";
|
||||
const DEFAULT_LISTEN: &str = "127.0.0.1:8787";
|
||||
const DEFAULT_FRONTEND_URL: &str = "http://127.0.0.1:5173";
|
||||
const DEFAULT_MAX_RECORDS: usize = 200;
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct WorkspaceBackendConfigFile {
|
||||
#[serde(default)]
|
||||
pub server: WorkspaceBackendServerConfig,
|
||||
#[serde(default)]
|
||||
pub data: WorkspaceBackendDataConfig,
|
||||
#[serde(default)]
|
||||
pub limits: WorkspaceBackendLimitsConfig,
|
||||
#[serde(default)]
|
||||
pub runtimes: WorkspaceBackendRuntimesConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct WorkspaceBackendServerConfig {
|
||||
#[serde(default)]
|
||||
pub listen: Option<String>,
|
||||
#[serde(default)]
|
||||
pub frontend_url: Option<String>,
|
||||
#[serde(default)]
|
||||
pub static_assets_dir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct WorkspaceBackendDataConfig {
|
||||
#[serde(default)]
|
||||
pub root: Option<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub workspace_database_path: Option<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub embedded_runtime_store_root: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct WorkspaceBackendLimitsConfig {
|
||||
#[serde(default)]
|
||||
pub max_records: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct WorkspaceBackendRuntimesConfig {
|
||||
#[serde(default)]
|
||||
pub remote: Vec<RemoteRuntimeConfigFile>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct RemoteRuntimeConfigFile {
|
||||
pub id: String,
|
||||
pub endpoint: String,
|
||||
#[serde(default)]
|
||||
pub display_name: Option<String>,
|
||||
#[serde(default)]
|
||||
pub token_ref: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ResolvedWorkspaceBackendConfig {
|
||||
pub server: ServerConfig,
|
||||
pub listen: SocketAddr,
|
||||
pub database_path: PathBuf,
|
||||
}
|
||||
|
||||
impl WorkspaceBackendConfigFile {
|
||||
pub fn path_for_workspace(workspace_root: impl AsRef<Path>) -> PathBuf {
|
||||
workspace_root
|
||||
.as_ref()
|
||||
.join(WORKSPACE_BACKEND_CONFIG_RELATIVE_PATH)
|
||||
}
|
||||
|
||||
pub fn load_for_workspace(workspace_root: impl AsRef<Path>) -> Result<Self> {
|
||||
let path = Self::path_for_workspace(workspace_root);
|
||||
match fs::read_to_string(&path) {
|
||||
Ok(raw) => Self::parse_str(&raw, &path),
|
||||
Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(Self::default()),
|
||||
Err(error) => Err(Error::Io(error)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_str(raw: &str, path: impl AsRef<Path>) -> Result<Self> {
|
||||
toml::from_str(raw).map_err(|error| {
|
||||
Error::Config(format!(
|
||||
"failed to parse workspace backend config `{}`: {error}",
|
||||
path.as_ref().display()
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn resolve(
|
||||
&self,
|
||||
workspace_root: impl AsRef<Path>,
|
||||
identity: WorkspaceIdentity,
|
||||
) -> Result<ResolvedWorkspaceBackendConfig> {
|
||||
let workspace_root = workspace_root.as_ref();
|
||||
let data_root = self
|
||||
.data
|
||||
.root
|
||||
.as_ref()
|
||||
.map(|path| resolve_workspace_path(workspace_root, path))
|
||||
.unwrap_or_else(|| {
|
||||
ServerConfig::default_workspace_backend_data_root(&identity.workspace_id)
|
||||
});
|
||||
let database_path = self
|
||||
.data
|
||||
.workspace_database_path
|
||||
.as_ref()
|
||||
.map(|path| resolve_workspace_path(workspace_root, path))
|
||||
.unwrap_or_else(|| data_root.join("workspace.db"));
|
||||
let embedded_runtime_store_root = self
|
||||
.data
|
||||
.embedded_runtime_store_root
|
||||
.as_ref()
|
||||
.map(|path| resolve_workspace_path(workspace_root, path))
|
||||
.unwrap_or_else(|| data_root.join("embedded-runtime"));
|
||||
let listen = self
|
||||
.server
|
||||
.listen
|
||||
.as_deref()
|
||||
.unwrap_or(DEFAULT_LISTEN)
|
||||
.parse::<SocketAddr>()
|
||||
.map_err(|_| {
|
||||
Error::Config(format!(
|
||||
"invalid workspace backend server.listen `{}`",
|
||||
self.server.listen.as_deref().unwrap_or(DEFAULT_LISTEN)
|
||||
))
|
||||
})?;
|
||||
|
||||
let mut server = ServerConfig::local_dev(workspace_root.to_path_buf(), identity);
|
||||
server.frontend_url = self
|
||||
.server
|
||||
.frontend_url
|
||||
.clone()
|
||||
.unwrap_or_else(|| DEFAULT_FRONTEND_URL.to_string());
|
||||
server.static_assets_dir = self
|
||||
.server
|
||||
.static_assets_dir
|
||||
.as_ref()
|
||||
.map(|path| resolve_workspace_path(workspace_root, path));
|
||||
server.embedded_runtime_store_root = embedded_runtime_store_root;
|
||||
server.max_records = self.limits.max_records.unwrap_or(DEFAULT_MAX_RECORDS);
|
||||
server.remote_runtime_sources = self
|
||||
.runtimes
|
||||
.remote
|
||||
.iter()
|
||||
.map(resolve_remote_runtime)
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
server.auth = AuthConfig::LocalDevToken {
|
||||
token_configured: false,
|
||||
};
|
||||
|
||||
Ok(ResolvedWorkspaceBackendConfig {
|
||||
server,
|
||||
listen,
|
||||
database_path,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ResolvedWorkspaceBackendConfig {
|
||||
pub fn with_database_path(mut self, path: impl Into<PathBuf>) -> Self {
|
||||
self.database_path = path.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_static_assets_dir(mut self, path: Option<PathBuf>) -> Self {
|
||||
self.server.static_assets_dir = path;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_listen(mut self, listen: SocketAddr) -> Self {
|
||||
self.listen = listen;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_remote_runtime(config: &RemoteRuntimeConfigFile) -> Result<RemoteRuntimeConfig> {
|
||||
if let Some(token_ref) = config.token_ref.as_deref() {
|
||||
return Err(Error::Config(format!(
|
||||
"remote runtime `{}` uses token_ref `{token_ref}`, but secret ref resolution is not implemented for workspace backend config yet",
|
||||
config.id
|
||||
)));
|
||||
}
|
||||
Ok(RemoteRuntimeConfig::new(
|
||||
config.id.clone(),
|
||||
config
|
||||
.display_name
|
||||
.clone()
|
||||
.unwrap_or_else(|| config.id.clone()),
|
||||
config.endpoint.clone(),
|
||||
None,
|
||||
))
|
||||
}
|
||||
|
||||
fn resolve_workspace_path(workspace_root: &Path, path: &Path) -> PathBuf {
|
||||
if path.is_absolute() {
|
||||
path.to_path_buf()
|
||||
} else {
|
||||
workspace_root.join(path)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn identity() -> WorkspaceIdentity {
|
||||
WorkspaceIdentity {
|
||||
workspace_id: "018f6a2c-1111-7000-8000-000000000001".to_string(),
|
||||
created_at: "2026-01-01T00:00:00Z".to_string(),
|
||||
display_name: "Workspace".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_config_path_uses_defaults() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let config = WorkspaceBackendConfigFile::load_for_workspace(dir.path()).unwrap();
|
||||
let resolved = config.resolve(dir.path(), identity()).unwrap();
|
||||
|
||||
assert_eq!(resolved.listen, "127.0.0.1:8787".parse().unwrap());
|
||||
assert_eq!(resolved.server.frontend_url, DEFAULT_FRONTEND_URL);
|
||||
assert_eq!(resolved.server.max_records, DEFAULT_MAX_RECORDS);
|
||||
assert!(resolved.database_path.ends_with("workspace.db"));
|
||||
assert!(
|
||||
resolved
|
||||
.server
|
||||
.embedded_runtime_store_root
|
||||
.ends_with("embedded-runtime")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_unknown_fields() {
|
||||
let error = WorkspaceBackendConfigFile::parse_str("[server]\nunknown = true\n", "test")
|
||||
.unwrap_err();
|
||||
assert!(
|
||||
error.to_string().contains("unknown field"),
|
||||
"unexpected error: {error}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolves_relative_paths_against_workspace_root() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let config = WorkspaceBackendConfigFile::parse_str(
|
||||
r#"
|
||||
[server]
|
||||
static_assets_dir = "web/build"
|
||||
|
||||
[data]
|
||||
root = ".yoi/backend-data"
|
||||
workspace_database_path = ".yoi/custom.db"
|
||||
embedded_runtime_store_root = ".yoi/runtime-store"
|
||||
"#,
|
||||
"test",
|
||||
)
|
||||
.unwrap();
|
||||
let resolved = config.resolve(dir.path(), identity()).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
resolved.server.static_assets_dir,
|
||||
Some(dir.path().join("web/build"))
|
||||
);
|
||||
assert_eq!(resolved.database_path, dir.path().join(".yoi/custom.db"));
|
||||
assert_eq!(
|
||||
resolved.server.embedded_runtime_store_root,
|
||||
dir.path().join(".yoi/runtime-store")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn absolute_paths_are_preserved() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let config = WorkspaceBackendConfigFile::parse_str(
|
||||
r#"
|
||||
[data]
|
||||
workspace_database_path = "/tmp/yoi-workspace.db"
|
||||
embedded_runtime_store_root = "/tmp/yoi-runtime"
|
||||
"#,
|
||||
"test",
|
||||
)
|
||||
.unwrap();
|
||||
let resolved = config.resolve(dir.path(), identity()).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
resolved.database_path,
|
||||
PathBuf::from("/tmp/yoi-workspace.db")
|
||||
);
|
||||
assert_eq!(
|
||||
resolved.server.embedded_runtime_store_root,
|
||||
PathBuf::from("/tmp/yoi-runtime")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data_root_derives_database_and_runtime_store_paths() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let config = WorkspaceBackendConfigFile::parse_str(
|
||||
r#"
|
||||
[data]
|
||||
root = ".local-data"
|
||||
"#,
|
||||
"test",
|
||||
)
|
||||
.unwrap();
|
||||
let resolved = config.resolve(dir.path(), identity()).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
resolved.database_path,
|
||||
dir.path().join(".local-data/workspace.db")
|
||||
);
|
||||
assert_eq!(
|
||||
resolved.server.embedded_runtime_store_root,
|
||||
dir.path().join(".local-data/embedded-runtime")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_value_field_is_not_in_schema() {
|
||||
let error = WorkspaceBackendConfigFile::parse_str(
|
||||
r#"
|
||||
[[runtimes.remote]]
|
||||
id = "remote"
|
||||
endpoint = "http://127.0.0.1:8790"
|
||||
token = "secret"
|
||||
"#,
|
||||
"test",
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(
|
||||
error.to_string().contains("unknown field"),
|
||||
"unexpected error: {error}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_ref_fails_closed_until_secret_resolution_exists() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let config = WorkspaceBackendConfigFile::parse_str(
|
||||
r#"
|
||||
[[runtimes.remote]]
|
||||
id = "remote"
|
||||
endpoint = "http://127.0.0.1:8790"
|
||||
token_ref = "local:remote-token"
|
||||
"#,
|
||||
"test",
|
||||
)
|
||||
.unwrap();
|
||||
let error = match config.resolve(dir.path(), identity()) {
|
||||
Ok(_) => panic!("token_ref should fail closed until secret resolution exists"),
|
||||
Err(error) => error,
|
||||
};
|
||||
assert!(
|
||||
error
|
||||
.to_string()
|
||||
.contains("secret ref resolution is not implemented"),
|
||||
"unexpected error: {error}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
//! remain the canonical project records and are read through bounded bridge APIs.
|
||||
|
||||
pub mod companion;
|
||||
pub mod config;
|
||||
pub mod hosts;
|
||||
pub mod identity;
|
||||
pub mod observation;
|
||||
|
|
@ -13,6 +14,10 @@ pub mod repositories;
|
|||
pub mod server;
|
||||
pub mod store;
|
||||
|
||||
pub use config::{
|
||||
ResolvedWorkspaceBackendConfig, WORKSPACE_BACKEND_CONFIG_RELATIVE_PATH,
|
||||
WorkspaceBackendConfigFile,
|
||||
};
|
||||
pub use identity::{WORKSPACE_IDENTITY_RELATIVE_PATH, WorkspaceIdentity};
|
||||
pub use records::{
|
||||
LocalProjectRecordReader, ObjectiveDetail, ObjectiveSummary, TicketDetail, TicketSummary,
|
||||
|
|
@ -38,6 +43,8 @@ pub enum Error {
|
|||
Yaml(#[from] serde_yaml::Error),
|
||||
#[error("invalid project record id `{0}`")]
|
||||
InvalidRecordId(String),
|
||||
#[error("workspace backend config error: {0}")]
|
||||
Config(String),
|
||||
#[error("record `{0}` is missing frontmatter")]
|
||||
MissingFrontmatter(String),
|
||||
#[error("unknown local host `{0}`")]
|
||||
|
|
|
|||
|
|
@ -4,14 +4,16 @@ use std::process::ExitCode;
|
|||
use std::sync::Arc;
|
||||
|
||||
use tokio::net::TcpListener;
|
||||
use yoi_workspace_server::{ServerConfig, SqliteWorkspaceStore, WorkspaceIdentity, serve};
|
||||
use yoi_workspace_server::{
|
||||
SqliteWorkspaceStore, WorkspaceBackendConfigFile, WorkspaceIdentity, serve,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ServeOptions {
|
||||
workspace: PathBuf,
|
||||
db: Option<PathBuf>,
|
||||
frontend: Option<PathBuf>,
|
||||
listen: SocketAddr,
|
||||
listen: Option<SocketAddr>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -65,23 +67,30 @@ async fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||
|
||||
async fn run_serve(options: ServeOptions) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let identity = WorkspaceIdentity::load_or_init(&options.workspace)?;
|
||||
let db = options
|
||||
.db
|
||||
.unwrap_or_else(|| options.workspace.join(".yoi/workspace.db"));
|
||||
if let Some(parent) = db.parent() {
|
||||
let config_file = WorkspaceBackendConfigFile::load_for_workspace(&options.workspace)?;
|
||||
let mut resolved = config_file.resolve(&options.workspace, identity)?;
|
||||
if let Some(db) = options.db {
|
||||
resolved = resolved.with_database_path(db);
|
||||
}
|
||||
if let Some(frontend) = options.frontend {
|
||||
resolved = resolved.with_static_assets_dir(Some(frontend));
|
||||
}
|
||||
if let Some(listen) = options.listen {
|
||||
resolved = resolved.with_listen(listen);
|
||||
}
|
||||
|
||||
if let Some(parent) = resolved.database_path.parent() {
|
||||
tokio::fs::create_dir_all(parent).await?;
|
||||
}
|
||||
|
||||
let store = Arc::new(SqliteWorkspaceStore::open(&db)?);
|
||||
let mut config = ServerConfig::local_dev(&options.workspace, identity);
|
||||
config.static_assets_dir = options.frontend;
|
||||
let listener = TcpListener::bind(options.listen).await?;
|
||||
let store = Arc::new(SqliteWorkspaceStore::open(&resolved.database_path)?);
|
||||
let listener = TcpListener::bind(resolved.listen).await?;
|
||||
eprintln!(
|
||||
"yoi-workspace-server: serving workspace `{}` on http://{}",
|
||||
options.workspace.display(),
|
||||
listener.local_addr()?
|
||||
);
|
||||
serve(config, store, listener).await?;
|
||||
serve(resolved.server, store, listener).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -90,7 +99,7 @@ fn parse_serve_options(args: &[String]) -> Result<ServeOptions, CliError> {
|
|||
.map_err(|error| CliError(format!("failed to resolve current directory: {error}")))?;
|
||||
let mut db = None;
|
||||
let mut frontend = None;
|
||||
let mut listen = "127.0.0.1:8787".parse::<SocketAddr>().unwrap();
|
||||
let mut listen = None;
|
||||
|
||||
let mut index = 0;
|
||||
while index < args.len() {
|
||||
|
|
@ -122,7 +131,7 @@ fn parse_serve_options(args: &[String]) -> Result<ServeOptions, CliError> {
|
|||
let value = args
|
||||
.get(index)
|
||||
.ok_or_else(|| CliError("--listen requires a value".to_string()))?;
|
||||
listen = parse_listen(value)?;
|
||||
listen = Some(parse_listen(value)?);
|
||||
}
|
||||
_ if arg.starts_with("--workspace=") => {
|
||||
workspace = PathBuf::from(value_after_equals(arg, "--workspace")?);
|
||||
|
|
@ -134,7 +143,7 @@ fn parse_serve_options(args: &[String]) -> Result<ServeOptions, CliError> {
|
|||
frontend = Some(PathBuf::from(value_after_equals(arg, "--frontend")?));
|
||||
}
|
||||
_ if arg.starts_with("--listen=") => {
|
||||
listen = parse_listen(value_after_equals(arg, "--listen")?)?;
|
||||
listen = Some(parse_listen(value_after_equals(arg, "--listen")?)?);
|
||||
}
|
||||
_ if arg.starts_with('-') => {
|
||||
return Err(CliError(format!("unknown serve option `{arg}`")));
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ pub struct ServerConfig {
|
|||
pub workspace_display_name: String,
|
||||
pub workspace_created_at: String,
|
||||
pub workspace_root: PathBuf,
|
||||
pub frontend_url: String,
|
||||
pub embedded_runtime_store_root: PathBuf,
|
||||
pub static_assets_dir: Option<PathBuf>,
|
||||
pub auth: AuthConfig,
|
||||
|
|
@ -69,6 +70,7 @@ impl ServerConfig {
|
|||
workspace_display_name: identity.display_name,
|
||||
workspace_created_at: identity.created_at,
|
||||
workspace_root,
|
||||
frontend_url: "http://127.0.0.1:5173".to_string(),
|
||||
embedded_runtime_store_root,
|
||||
static_assets_dir: None,
|
||||
auth: AuthConfig::LocalDevToken {
|
||||
|
|
@ -80,7 +82,7 @@ impl ServerConfig {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn embedded_runtime_store_root_for_data_dir(
|
||||
pub fn workspace_backend_data_root_for_data_dir(
|
||||
data_dir: impl Into<PathBuf>,
|
||||
workspace_id: impl AsRef<str>,
|
||||
) -> PathBuf {
|
||||
|
|
@ -88,22 +90,32 @@ impl ServerConfig {
|
|||
.into()
|
||||
.join("workspace-server")
|
||||
.join(workspace_id.as_ref())
|
||||
.join("embedded-runtime")
|
||||
}
|
||||
|
||||
pub fn default_embedded_runtime_store_root(workspace_id: impl AsRef<str>) -> PathBuf {
|
||||
pub fn default_workspace_backend_data_root(workspace_id: impl AsRef<str>) -> PathBuf {
|
||||
match manifest::paths::data_dir() {
|
||||
Some(data_dir) => {
|
||||
Self::embedded_runtime_store_root_for_data_dir(data_dir, workspace_id.as_ref())
|
||||
Self::workspace_backend_data_root_for_data_dir(data_dir, workspace_id.as_ref())
|
||||
}
|
||||
None => std::env::temp_dir()
|
||||
.join("yoi")
|
||||
.join("workspace-server")
|
||||
.join(workspace_id.as_ref())
|
||||
.join("embedded-runtime"),
|
||||
.join(workspace_id.as_ref()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn embedded_runtime_store_root_for_data_dir(
|
||||
data_dir: impl Into<PathBuf>,
|
||||
workspace_id: impl AsRef<str>,
|
||||
) -> PathBuf {
|
||||
Self::workspace_backend_data_root_for_data_dir(data_dir, workspace_id)
|
||||
.join("embedded-runtime")
|
||||
}
|
||||
|
||||
pub fn default_embedded_runtime_store_root(workspace_id: impl AsRef<str>) -> PathBuf {
|
||||
Self::default_workspace_backend_data_root(workspace_id).join("embedded-runtime")
|
||||
}
|
||||
|
||||
pub fn with_embedded_runtime_store_root(mut self, root: impl Into<PathBuf>) -> Self {
|
||||
self.embedded_runtime_store_root = root.into();
|
||||
self
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user