feat(cli): declare app vars in the manifest + plan/apply them

Adds a `[vars]` block to `picloud.toml` (key → TOML value), reconciled by
`pic plan`/`pic apply` alongside scripts/routes/triggers/secrets. Unlike
`[secrets]` (name-only), var values live inline since they aren't secret.
The overlay (`picloud.<env>.toml`) may carry `[vars]`; overlay keys win
per key (the env-specific value of an env-agnostic default).

`build_bundle` serializes the vars to JSON for the wire (TOML round-trips
via serde); `pic plan` shows a `var` row group and `pic apply` reports
`vars +c ~u -d`. The `PlanDto`/`ApplyReportDto` gain the matching fields.
`pic pull` carries an empty `[vars]` for now (export lands next). Adds a
round-trip + overlay-precedence unit test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-25 07:40:36 +02:00
parent e517f6dc8c
commit 34dcae98a2
6 changed files with 72 additions and 8 deletions

View File

@@ -75,6 +75,13 @@ pub async fn run(
.field(
"triggers",
format!("+{} -{}", report.triggers_created, report.triggers_deleted),
)
.field(
"vars",
format!(
"+{} ~{} -{}",
report.vars_created, report.vars_updated, report.vars_deleted
),
);
for w in &report.warnings {
block.field("warning", w.clone());

View File

@@ -137,6 +137,7 @@ fn scaffold_manifest(slug: &str, name: &str) -> Manifest {
}],
triggers: crate::manifest::ManifestTriggers::default(),
secrets: crate::manifest::ManifestSecrets::default(),
vars: std::collections::BTreeMap::new(),
}
}

View File

@@ -95,11 +95,22 @@ pub fn build_bundle(manifest: &Manifest, base_dir: &Path) -> Result<Value> {
triggers.push(tagged("queue", s)?);
}
// Vars: key → JSON value. TOML values round-trip to JSON via serde so the
// wire shape matches the server's `serde_json::Value`.
let mut vars = Map::new();
for (k, v) in &manifest.vars {
vars.insert(
k.clone(),
serde_json::to_value(v).with_context(|| format!("encoding var `{k}`"))?,
);
}
Ok(json!({
"scripts": scripts,
"routes": routes,
"triggers": triggers,
"secrets": manifest.secrets.names,
"vars": vars,
}))
}
@@ -114,11 +125,12 @@ fn tagged(kind: &str, spec: impl Serialize) -> Result<Value> {
fn render(plan: &PlanDto, mode: OutputMode) {
let mut table = Table::new(["kind", "op", "resource", "detail"]);
let groups: [(&str, &Vec<ChangeDto>); 4] = [
let groups: [(&str, &Vec<ChangeDto>); 5] = [
("script", &plan.scripts),
("route", &plan.routes),
("trigger", &plan.triggers),
("secret", &plan.secrets),
("var", &plan.vars),
];
for (kind, changes) in groups {
for c in changes {

View File

@@ -204,6 +204,7 @@ pub async fn run(app_ident: &str, dir: &Path, force: bool, mode: OutputMode) ->
secrets: ManifestSecrets {
names: secrets.iter().map(|s| s.name.clone()).collect(),
},
vars: std::collections::BTreeMap::new(),
};
std::fs::write(&manifest_path, manifest.to_toml()?)