@intentius/chant 0.69.1 → 0.70.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../../src/cli/handlers/lifecycle.ts"],"names":[],"mappings":"AA6CA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAKlD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,GAAG,MAAM,CAE9E;AAmCD;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAyH/E;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA4C3E;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA8B/E;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAmL3E;AAmrBD;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAwQ3E;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA2L/E;AAsBD;;GAEG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAiB1E;AAgBD;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAmE7E;AAiCD;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAM9E;AAkBD;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA+C/E"}
1
+ {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../../src/cli/handlers/lifecycle.ts"],"names":[],"mappings":"AA6CA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAKlD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,GAAG,MAAM,CAE9E;AAmCD;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAyH/E;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA4C3E;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA8B/E;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAmL3E;AAmrBD;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAqR3E;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA2L/E;AAsBD;;GAEG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAiB1E;AAgBD;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAmE7E;AAiCD;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAM9E;AAkBD;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CA+C/E"}
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":";AAMA,OAAO,EAAkB,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAwF9E;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CA6VpD;AAgbD;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,EAAE,UAAU,EAkGvC,CAAC;AAgIF;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,CAAC,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAuB5E"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":";AAMA,OAAO,EAAkB,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAwF9E;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CA6VpD;AAqbD;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,EAAE,UAAU,EAkGvC,CAAC;AAgIF;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,CAAC,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAuB5E"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant",
3
- "version": "0.69.1",
3
+ "version": "0.70.0",
4
4
  "description": "Declarative infrastructure-as-code toolkit — TypeScript on Node.js",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",
@@ -916,6 +916,71 @@ describe("runLifecyclePlan", () => {
916
916
  // #2162 — a held property is not an action, so the plan carries it beside
917
917
  // `entries` rather than inside one, attributed to its lexicon the same way
918
918
  // an entry is.
919
+ test("without --deep the pass does not run, and the entries are unchanged (#2405)", async () => {
920
+ // The property that makes `--deep` safe to default off: a held property is
921
+ // never a proposal, so the change set is identical either way and the only
922
+ // thing the flag buys is the HELD section. If that stops being true, this
923
+ // is where it shows.
924
+ const makeBuild = () => {
925
+ const b = makeBuildResult({ k8s: ["web"] });
926
+ b.entities.set("web", {
927
+ lexicon: "k8s",
928
+ entityType: "K8s::Apps::Deployment",
929
+ props: { spec: { replicas: heldElsewhere<number>({ by: "hpa", reason: "the autoscaler owns replicas after the first apply" }) } },
930
+ } as never);
931
+ return b;
932
+ };
933
+ let deepCalls = 0;
934
+ const makePlugins = (): LexiconPlugin[] => {
935
+ const inner = staticDeepObservation({
936
+ web: { type: "K8s::Apps::Deployment", properties: { spec: { replicas: 5 } } },
937
+ });
938
+ return [
939
+ createMockPlugin({
940
+ name: "k8s",
941
+ describeResources: staticObservation({ web: meta({ type: "K8s::Apps::Deployment" }) }),
942
+ // Counting wrapper: the helper's return type is the plugin member's,
943
+ // which is optional, so it is narrowed once here rather than at the
944
+ // call.
945
+ observeResourcesDeep: ((opts: never) => {
946
+ deepCalls += 1;
947
+ return inner!(opts);
948
+ }) as never,
949
+ }),
950
+ ];
951
+ };
952
+
953
+ const run = async (deep: boolean) => {
954
+ stdoutBuf.length = 0;
955
+ buildMock.mockResolvedValue(makeBuild());
956
+ const plugins = makePlugins();
957
+ const exit = await runLifecyclePlan({
958
+ args: makeArgs({ path: "plan", extraPositional: "prod", json: true, ...(deep ? { deep: true } : {}) }),
959
+ plugins,
960
+ serializers: plugins.map((p) => p.serializer),
961
+ } as never);
962
+ expect(exit).toBe(0);
963
+ return JSON.parse(stdoutBuf.join("\n"));
964
+ };
965
+
966
+ deepCalls = 0;
967
+ const shallow = await run(false);
968
+ const callsWithoutDeep = deepCalls;
969
+
970
+ deepCalls = 0;
971
+ const deep = await run(true);
972
+
973
+ // The cost: the pass is not run at all without the flag.
974
+ expect(callsWithoutDeep).toBe(0);
975
+ expect(deepCalls).toBeGreaterThan(0);
976
+
977
+ // The correctness: the same proposals either way, and the held section
978
+ // only when it was asked for.
979
+ expect(shallow.entries).toEqual(deep.entries);
980
+ expect(shallow.held).toBeUndefined();
981
+ expect(deep.held).toBeDefined();
982
+ });
983
+
919
984
  test("--json carries the held set beside entries, attributed to its lexicon", async () => {
920
985
  const build = makeBuildResult({ k8s: ["web"] });
921
986
  build.entities.set("web", {
@@ -934,7 +999,7 @@ describe("runLifecyclePlan", () => {
934
999
  }),
935
1000
  ];
936
1001
  const exit = await runLifecyclePlan({
937
- args: makeArgs({ path: "plan", extraPositional: "prod", json: true }),
1002
+ args: makeArgs({ path: "plan", extraPositional: "prod", json: true, deep: true }),
938
1003
  plugins,
939
1004
  serializers: plugins.map((p) => p.serializer),
940
1005
  });
@@ -971,7 +1036,7 @@ describe("runLifecyclePlan", () => {
971
1036
  }),
972
1037
  ];
973
1038
  const exit = await runLifecyclePlan({
974
- args: makeArgs({ path: "plan", extraPositional: "prod" }),
1039
+ args: makeArgs({ path: "plan", extraPositional: "prod", deep: true }),
975
1040
  plugins,
976
1041
  serializers: plugins.map((p) => p.serializer),
977
1042
  });
@@ -1319,11 +1319,24 @@ export async function runLifecyclePlan(ctx: CommandContext): Promise<number> {
1319
1319
 
1320
1320
  Object.assign(allObservedResources, observed.resources);
1321
1321
 
1322
- // Held properties (#2162): opt-in on the same capability the deep diff
1323
- // path gates on. A held property is never a proposal, so it is
1324
- // computed here (once per lexicon, against this same observation) and
1325
- // carried on the plan beside `entries` rather than folded into one.
1326
- if (plugin.observeResourcesDeep) {
1322
+ // Held properties (#2162), behind `--deep` since #2405.
1323
+ //
1324
+ // A held property is never a proposal: it is computed against this same
1325
+ // observation and carried on the plan beside `entries`, never folded
1326
+ // into one. So the entries a plan proposes are identical whether this
1327
+ // ran or not, and the only thing `--deep` buys is the HELD section.
1328
+ //
1329
+ // It used to run whenever the lexicon could, which made the read's cost
1330
+ // a function of how many resources an estate holds rather than how it is
1331
+ // organised. Measured on 528 resources across 8 stacks: 24 stack-shaped
1332
+ // calls, and 496 from this pass. At ten thousand resources that is
1333
+ // roughly twenty thousand calls against eighty, for a section the caller
1334
+ // did not ask for.
1335
+ //
1336
+ // `--deep` is the flag `lifecycle snapshot` already uses for the same
1337
+ // choice, and `lifecycle diff` still runs the pass unconditionally
1338
+ // because property drift is the question it exists to answer.
1339
+ if (args.deep && plugin.observeResourcesDeep) {
1327
1340
  const deep = await deepDiffForLexicon(plugin, {
1328
1341
  environment,
1329
1342
  buildOutput,
package/src/cli/main.ts CHANGED
@@ -593,6 +593,11 @@ Lifecycle (alias: lc):
593
593
  property-level drift; --update-baseline records
594
594
  what it reports as accepted so it stops alerting)
595
595
  lifecycle plan <env> Typed change set (create/update/delete/adopt) vs live
596
+ --deep: also report properties declared
597
+ heldElsewhere(). Never changes what the plan
598
+ proposes; costs a provider call pair per readable
599
+ resource, so the read's cost becomes a function of
600
+ estate size rather than stack count (#2405)
596
601
  lifecycle affected Stacks a change affects (--base <ref> [--include-dependents])
597
602
  --json: emit the ChangeSet as JSON
598
603
  lifecycle whoami <env> Who chant would act as in each configured lexicon,