150 lines
5.2 KiB
Rust
150 lines
5.2 KiB
Rust
use platform_config::ConfigLoader;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::path::PathBuf;
|
|
|
|
/// Inner settings — matches the `ncl_sync` key in the NCL export.
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct NclSyncSettings {
|
|
#[serde(default)]
|
|
pub cache_dir: Option<String>,
|
|
|
|
#[serde(default = "default_idle_timeout")]
|
|
pub idle_timeout_secs: u64,
|
|
|
|
#[serde(default = "default_sync_poll_ms")]
|
|
pub sync_poll_interval_ms: u64,
|
|
|
|
#[serde(default = "default_concurrency")]
|
|
pub warm_concurrency: usize,
|
|
|
|
#[serde(default)]
|
|
pub extra_import_paths: Vec<String>,
|
|
|
|
/// Additional directories to warm alongside the primary workspace.
|
|
/// Useful for `$PROVISIONING/extensions/` and other shared NCL trees that live
|
|
/// outside the active workspace but are frequently read by commands.
|
|
/// Default: empty. Env `NCL_SYNC_EXTRA_WARM` (colon-separated) or config value.
|
|
/// `~` is expanded.
|
|
#[serde(default)]
|
|
pub extra_warm_paths: Vec<String>,
|
|
|
|
/// Filename suffixes that identify non-exportable NCL files (schemas, contracts, lib files).
|
|
/// Files matching these suffixes are skipped during warm-up and watcher events.
|
|
/// Default: `["-schema.ncl", "-defaults.ncl", "-constraints.ncl"]`
|
|
#[serde(default = "default_skip_patterns")]
|
|
pub skip_patterns: Vec<String>,
|
|
|
|
/// Directory basenames that indicate non-exportable NCL files.
|
|
/// Files in any parent directory whose basename matches are skipped.
|
|
/// Default: `["schemas", "defaults", "constraints"]`
|
|
#[serde(default = "default_skip_dirs")]
|
|
pub skip_dirs: Vec<String>,
|
|
|
|
/// NATS-driven event subscriber (opt-in, requires `nats` Cargo feature).
|
|
#[serde(default)]
|
|
pub nats: NclSyncNatsSettings,
|
|
}
|
|
|
|
/// NATS subscription settings for event-driven cache invalidation.
|
|
#[derive(Debug, Default, Serialize, Deserialize)]
|
|
pub struct NclSyncNatsSettings {
|
|
/// When true, connect to NATS and subscribe to `provisioning.workspace.ncl.*`.
|
|
#[serde(default)]
|
|
pub enabled: bool,
|
|
/// NATS URL. Empty → uses platform-nats default (`nats://127.0.0.1:4222`).
|
|
#[serde(default)]
|
|
pub url: String,
|
|
}
|
|
|
|
impl Default for NclSyncSettings {
|
|
fn default() -> Self {
|
|
Self {
|
|
cache_dir: None,
|
|
idle_timeout_secs: default_idle_timeout(),
|
|
sync_poll_interval_ms: default_sync_poll_ms(),
|
|
warm_concurrency: default_concurrency(),
|
|
extra_import_paths: Vec::new(),
|
|
extra_warm_paths: Vec::new(),
|
|
skip_patterns: default_skip_patterns(),
|
|
skip_dirs: default_skip_dirs(),
|
|
nats: NclSyncNatsSettings::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Root config struct — outer key `ncl_sync` mirrors the NCL file shape.
|
|
#[derive(Debug, Default, Serialize, Deserialize)]
|
|
pub struct NclSyncConfig {
|
|
#[serde(default)]
|
|
pub ncl_sync: NclSyncSettings,
|
|
}
|
|
|
|
impl NclSyncConfig {
|
|
pub fn load_or_default() -> Self {
|
|
Self::load().unwrap_or_default()
|
|
}
|
|
|
|
pub fn settings(&self) -> &NclSyncSettings {
|
|
&self.ncl_sync
|
|
}
|
|
|
|
/// Resolve cache dir. Priority:
|
|
/// 1. Explicit `cache_dir` in config file
|
|
/// 2. Workspace-local (`<workspace>/.ncl-cache/`) if workspace provided
|
|
/// 3. Global fallback (`~/.cache/provisioning/config-cache/`)
|
|
pub fn resolved_cache_dir(&self, workspace: Option<&std::path::Path>) -> PathBuf {
|
|
if let Some(dir) = &self.ncl_sync.cache_dir {
|
|
return PathBuf::from(dir);
|
|
}
|
|
crate::manifest::resolve_cache_dir(workspace)
|
|
}
|
|
}
|
|
|
|
impl ConfigLoader for NclSyncConfig {
|
|
fn service_name() -> &'static str {
|
|
"ncl-sync"
|
|
}
|
|
|
|
fn collect_env_overrides() -> serde_json::Value {
|
|
let mut inner = serde_json::Map::new();
|
|
|
|
if let Ok(v) = std::env::var("NCL_SYNC_DIR") {
|
|
inner.insert("cache_dir".into(), serde_json::Value::String(v));
|
|
}
|
|
if let Ok(v) = std::env::var("NCL_SYNC_IDLE_TIMEOUT") {
|
|
if let Ok(n) = v.parse::<u64>() {
|
|
inner.insert("idle_timeout_secs".into(), serde_json::Value::Number(n.into()));
|
|
}
|
|
}
|
|
if let Ok(v) = std::env::var("NCL_SYNC_CONCURRENCY") {
|
|
if let Ok(n) = v.parse::<u64>() {
|
|
inner.insert("warm_concurrency".into(), serde_json::Value::Number(n.into()));
|
|
}
|
|
}
|
|
|
|
// When no env overrides are set, return Null to skip the merge path entirely.
|
|
// Returning {"ncl_sync": {}} triggers a Nickel merge conflict with the contract-
|
|
// annotated record in the NCL config file.
|
|
if inner.is_empty() {
|
|
return serde_json::Value::Null;
|
|
}
|
|
|
|
let mut root = serde_json::Map::new();
|
|
root.insert("ncl_sync".into(), serde_json::Value::Object(inner));
|
|
serde_json::Value::Object(root)
|
|
}
|
|
}
|
|
|
|
fn default_idle_timeout() -> u64 { 600 }
|
|
fn default_sync_poll_ms() -> u64 { 500 }
|
|
fn default_concurrency() -> usize { 4 }
|
|
fn default_skip_patterns() -> Vec<String> {
|
|
vec![
|
|
"-schema.ncl".to_string(),
|
|
"-defaults.ncl".to_string(),
|
|
"-constraints.ncl".to_string(),
|
|
]
|
|
}
|
|
fn default_skip_dirs() -> Vec<String> {
|
|
vec!["schemas".to_string(), "defaults".to_string(), "constraints".to_string()]
|
|
}
|