feat(cli): env-scoped overlays — --env merges picloud.<env>.toml

The §4.1 base+overlay model for the single-app world: a base `picloud.toml`
holds the shared scripts/routes/triggers; a sparse `picloud.<env>.toml`
carries the per-environment slug (+ extra secret names). `pic plan/apply
--env <E>` merges the overlay onto the base before diffing/applying, so an
environment deploys to its own app ("an environment is an app", §2)
without duplicating the shared definitions.

Merge is sparse: overlay `app.slug`/`name` replace the base's; secret
names union. Scripts/routes/triggers always come from the base. Rich
per-key `vars` resolution stays Phase 3.

Tested: unit tests for the merge + overlay-path derivation; a journey
proving `apply --env staging` deploys to the staging app and leaves the
base app untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-23 20:58:04 +02:00
parent 79153b2063
commit 2ba476aac8
6 changed files with 201 additions and 5 deletions

View File

@@ -200,6 +200,10 @@ struct ApplyArgs {
/// since the last `pic plan`).
#[arg(long)]
force: bool,
/// Merge the `picloud.<env>.toml` overlay (per-env slug + secrets) on
/// top of the base manifest before applying.
#[arg(long)]
env: Option<String>,
}
#[derive(Args)]
@@ -207,6 +211,10 @@ struct PlanArgs {
/// Path to the manifest.
#[arg(long, default_value = "picloud.toml")]
file: PathBuf,
/// Merge the `picloud.<env>.toml` overlay (per-env slug + secrets) on
/// top of the base manifest before diffing.
#[arg(long)]
env: Option<String>,
}
#[derive(Args)]
@@ -1132,9 +1140,17 @@ async fn main() -> ExitCode {
Cmd::Logout => cmds::logout::run().await,
Cmd::Whoami => cmds::whoami::run(mode).await,
Cmd::Apply(args) => {
cmds::apply::run(&args.file, args.prune, args.yes, args.force, mode).await
cmds::apply::run(
&args.file,
args.env.as_deref(),
args.prune,
args.yes,
args.force,
mode,
)
.await
}
Cmd::Plan(args) => cmds::plan::run(&args.file, mode).await,
Cmd::Plan(args) => cmds::plan::run(&args.file, args.env.as_deref(), mode).await,
Cmd::Pull(args) => cmds::pull::run(&args.app, &args.dir, mode).await,
Cmd::Config(args) => cmds::config::run(&args.file, args.effective, mode).await,
Cmd::Init(args) => cmds::init::run(