fix(cli): env-aware config, pull clobber guard, strict overlays, 409 hint
Four review fixes on the `pic` surface: - `config --effective` gains `--env`: it now resolves through the same overlay as `plan`/`apply`, so the masked report targets the app those commands act on instead of silently reading the base slug's secrets. - `pull` gains `--force` and refuses to overwrite an existing `picloud.toml` (and colliding `scripts/*.rhai`) without it — fail-fast before any network call or write, mirroring `init`. - Overlays (`ManifestOverlay`/`OverlayApp`) get `deny_unknown_fields`: a `[[scripts]]`/`[[routes]]`/`[[triggers]]` table or a typo'd key in an overlay now errors loudly instead of being silently dropped. +unit test. - `apply` 409 (StateMoved — the only 409 the endpoint emits) surfaces an actionable hint (re-run `pic plan`, or `--force`) instead of a bare `HTTP 409`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -112,7 +112,13 @@ fn overlay_path(base_path: &Path, env: &str) -> std::path::PathBuf {
|
||||
|
||||
/// A sparse per-environment overlay (`picloud.<env>.toml`). Only the fields
|
||||
/// that vary per environment today — slug/name and secret names.
|
||||
///
|
||||
/// `deny_unknown_fields`: an overlay can carry *only* `[app]` and `[secrets]`.
|
||||
/// Scripts/routes/triggers belong in the shared base manifest, so a
|
||||
/// `[[scripts]]`/`[[routes]]`/`[[triggers]]` table (or a typo'd key) in an
|
||||
/// overlay is a mistake — error loudly rather than silently dropping it.
|
||||
#[derive(Debug, Clone, Default, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ManifestOverlay {
|
||||
#[serde(default)]
|
||||
pub app: OverlayApp,
|
||||
@@ -121,6 +127,7 @@ pub struct ManifestOverlay {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct OverlayApp {
|
||||
#[serde(default)]
|
||||
pub slug: Option<String>,
|
||||
@@ -461,6 +468,26 @@ mod tests {
|
||||
assert_eq!(m.scripts.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overlay_rejects_non_overlay_tables() {
|
||||
// An overlay carries only [app]/[secrets]. Scripts/routes/triggers
|
||||
// belong in the shared base, so a `[[scripts]]` table (or a typo'd
|
||||
// key) in an overlay must error loudly, not be silently dropped.
|
||||
let err = toml::from_str::<ManifestOverlay>(
|
||||
"[app]\nslug = \"blog-staging\"\n\n\
|
||||
[[scripts]]\nname = \"hello\"\nfile = \"scripts/hello.rhai\"\n",
|
||||
)
|
||||
.expect_err("overlay with [[scripts]] must be rejected");
|
||||
assert!(
|
||||
err.to_string().contains("scripts") || err.to_string().contains("unknown"),
|
||||
"error should point at the offending table: {err}"
|
||||
);
|
||||
|
||||
// Typo'd key inside [app] is likewise rejected.
|
||||
toml::from_str::<ManifestOverlay>("[app]\nslag = \"oops\"\n")
|
||||
.expect_err("overlay with a typo'd [app] key must be rejected");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overlay_path_derivation() {
|
||||
assert_eq!(
|
||||
|
||||
Reference in New Issue
Block a user