@intentius/chant 0.44.14 → 0.46.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.
- package/dist/audit/discover.d.ts +26 -2
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/report-model.d.ts +10 -0
- package/dist/audit/report-model.d.ts.map +1 -1
- package/dist/audit/rules-doc.d.ts.map +1 -1
- package/dist/cli/build-params-cli.d.ts +16 -7
- package/dist/cli/build-params-cli.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts +20 -0
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts +9 -0
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-docs.d.ts +13 -0
- package/dist/cli/commands/check-lexicon-docs.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-examples.d.ts +24 -14
- package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon.d.ts +14 -0
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon/templates/codegen.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon/templates/docs.d.ts +5 -0
- package/dist/cli/commands/init-lexicon/templates/docs.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/lexicon-surface-diff.d.ts +6 -0
- package/dist/cli/commands/lexicon-surface-diff.d.ts.map +1 -1
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/commands/onboard.d.ts +28 -0
- package/dist/cli/commands/onboard.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts +13 -0
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/search.d.ts +27 -0
- package/dist/cli/handlers/search.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +7 -0
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/docs-pages.d.ts +39 -0
- package/dist/codegen/docs-pages.d.ts.map +1 -0
- package/dist/codegen/docs-sections.d.ts.map +1 -1
- package/dist/codegen/docs-sidebar.d.ts +13 -2
- package/dist/codegen/docs-sidebar.d.ts.map +1 -1
- package/dist/codegen/docs-types.d.ts +30 -10
- package/dist/codegen/docs-types.d.ts.map +1 -1
- package/dist/codegen/docs.d.ts +14 -2
- package/dist/codegen/docs.d.ts.map +1 -1
- package/dist/codegen/lexicon-regen.d.ts +11 -0
- package/dist/codegen/lexicon-regen.d.ts.map +1 -1
- package/dist/codegen/surface-snapshot.d.ts +27 -3
- package/dist/codegen/surface-snapshot.d.ts.map +1 -1
- package/dist/codegen/validate.d.ts +21 -3
- package/dist/codegen/validate.d.ts.map +1 -1
- package/dist/components/capability-plugin.d.ts +13 -2
- package/dist/components/capability-plugin.d.ts.map +1 -1
- package/dist/components/driver.d.ts +38 -0
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/starter-plugin.d.ts.map +1 -1
- package/dist/config.d.ts +85 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/env.d.ts +12 -1
- package/dist/env.d.ts.map +1 -1
- package/dist/fold/fold.d.ts +100 -1
- package/dist/fold/fold.d.ts.map +1 -1
- package/dist/graph-ir.d.ts +12 -0
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/graph-ops.d.ts +17 -0
- package/dist/graph-ops.d.ts.map +1 -0
- package/dist/lexicon.d.ts +182 -2
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/change-set.d.ts +23 -2
- package/dist/lifecycle/change-set.d.ts.map +1 -1
- package/dist/lifecycle/deep-observe.d.ts.map +1 -1
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/observe.d.ts +8 -0
- package/dist/lifecycle/observe.d.ts.map +1 -1
- package/dist/lifecycle/replay.d.ts.map +1 -1
- package/dist/lifecycle/snapshot.d.ts.map +1 -1
- package/dist/lifecycle/teardown.d.ts +130 -0
- package/dist/lifecycle/teardown.d.ts.map +1 -0
- package/dist/lifecycle/types.d.ts +7 -0
- package/dist/lifecycle/types.d.ts.map +1 -1
- package/dist/lint/engine.d.ts +6 -2
- package/dist/lint/engine.d.ts.map +1 -1
- package/dist/lint/policy.d.ts.map +1 -1
- package/dist/lint/rule.d.ts +31 -0
- package/dist/lint/rule.d.ts.map +1 -1
- package/dist/lint/rules/cor021-env-literal-name.d.ts +3 -0
- package/dist/lint/rules/cor021-env-literal-name.d.ts.map +1 -0
- package/dist/lint/rules/index.d.ts +2 -1
- package/dist/lint/rules/index.d.ts.map +1 -1
- package/dist/managed-fields.d.ts +33 -21
- package/dist/managed-fields.d.ts.map +1 -1
- package/dist/observation.d.ts +23 -1
- package/dist/observation.d.ts.map +1 -1
- package/dist/op/builders.d.ts +36 -7
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/index.d.ts +1 -1
- package/dist/op/index.d.ts.map +1 -1
- package/dist/testing.d.ts +136 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/yaml.d.ts +0 -8
- package/dist/yaml.d.ts.map +1 -1
- package/package.json +6 -1
- package/src/__snapshots__/okf.test.ts.snap +20 -0
- package/src/audit/discover.test.ts +55 -1
- package/src/audit/discover.ts +70 -2
- package/src/audit/edge-init-safety.test.ts +149 -18
- package/src/audit/report-model.ts +12 -1
- package/src/audit/rules-doc.ts +1 -0
- package/src/build-params.test.ts +52 -0
- package/src/cli/build-params-cli.test.ts +25 -8
- package/src/cli/build-params-cli.ts +37 -23
- package/src/cli/commands/__fixtures__/audit-coverage/.github/workflows/ci.yml +5 -0
- package/src/cli/commands/__fixtures__/audit-coverage/Dockerfile +2 -0
- package/src/cli/commands/__fixtures__/audit-coverage/infra/main.tf +3 -0
- package/src/cli/commands/__fixtures__/audit-coverage/infra/stack.json +6 -0
- package/src/cli/commands/__fixtures__/audit-coverage/k8s/deploy.yaml +12 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/pages/getting-started.mdx +16 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content.config.ts +10 -2
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate-cli.ts +3 -1
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate.ts +2 -1
- package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +2 -1
- package/src/cli/commands/audit.test.ts +83 -1
- package/src/cli/commands/audit.ts +121 -28
- package/src/cli/commands/build.test.ts +275 -5
- package/src/cli/commands/build.ts +86 -14
- package/src/cli/commands/check-lexicon-docs.ts +30 -0
- package/src/cli/commands/check-lexicon-examples.test.ts +73 -7
- package/src/cli/commands/check-lexicon-examples.ts +66 -17
- package/src/cli/commands/check-lexicon.test.ts +46 -2
- package/src/cli/commands/check-lexicon.ts +64 -4
- package/src/cli/commands/init-lexicon/templates/codegen.ts +5 -2
- package/src/cli/commands/init-lexicon/templates/docs.ts +35 -2
- package/src/cli/commands/init-lexicon.test.ts +42 -1
- package/src/cli/commands/init-lexicon.ts +3 -1
- package/src/cli/commands/lexicon-rolling-upgrade.ts +1 -1
- package/src/cli/commands/lexicon-surface-diff.test.ts +1 -1
- package/src/cli/commands/lexicon-surface-diff.ts +10 -1
- package/src/cli/commands/lexicon-surface-diff.update.test.ts +112 -0
- package/src/cli/commands/lint.ts +19 -6
- package/src/cli/commands/onboard.test.ts +319 -225
- package/src/cli/commands/onboard.ts +144 -58
- package/src/cli/handlers/build.test.ts +3 -3
- package/src/cli/handlers/build.ts +2 -0
- package/src/cli/handlers/components.ts +2 -2
- package/src/cli/handlers/graph.test.ts +44 -0
- package/src/cli/handlers/graph.ts +24 -4
- package/src/cli/handlers/lifecycle.test.ts +348 -1
- package/src/cli/handlers/lifecycle.ts +235 -3
- package/src/cli/handlers/run.test.ts +5 -5
- package/src/cli/handlers/run.ts +1 -0
- package/src/cli/handlers/search-live.test.ts +217 -0
- package/src/cli/handlers/search.test.ts +41 -1
- package/src/cli/handlers/search.ts +60 -8
- package/src/cli/main.ts +17 -4
- package/src/cli/registry.ts +7 -0
- package/src/cli/security-boundary.test.ts +2 -1
- package/src/codegen/docs-pages.test.ts +194 -0
- package/src/codegen/docs-pages.ts +138 -0
- package/src/codegen/docs-sections.ts +0 -8
- package/src/codegen/docs-sidebar.ts +50 -37
- package/src/codegen/docs-types.ts +32 -4
- package/src/codegen/docs.ts +78 -40
- package/src/codegen/lexicon-regen.ts +22 -2
- package/src/codegen/rolling-upgrade.test.ts +1 -1
- package/src/codegen/rolling-upgrade.ts +1 -1
- package/src/codegen/surface-snapshot.test.ts +139 -0
- package/src/codegen/surface-snapshot.ts +297 -122
- package/src/codegen/validate.test.ts +49 -3
- package/src/codegen/validate.ts +36 -7
- package/src/components/auto-release.ts +1 -1
- package/src/components/capability-plugin.ts +23 -2
- package/src/components/driver.test.ts +45 -0
- package/src/components/driver.ts +64 -27
- package/src/components/index.ts +2 -0
- package/src/components/starter-plugin.ts +5 -2
- package/src/config.test.ts +40 -0
- package/src/config.ts +159 -8
- package/src/discovery/fold-import.test.ts +427 -2
- package/src/discovery/fold-import.ts +163 -7
- package/src/discovery/sandbox/driver.test.ts +11 -0
- package/src/env.test.ts +35 -1
- package/src/env.ts +17 -3
- package/src/fold/fold.test.ts +41 -0
- package/src/fold/fold.ts +372 -2
- package/src/graph-ir-live.test.ts +42 -0
- package/src/graph-ir.ts +42 -2
- package/src/graph-ops.test.ts +69 -0
- package/src/graph-ops.ts +42 -0
- package/src/lexicon.ts +182 -2
- package/src/lifecycle/change-set.test.ts +41 -0
- package/src/lifecycle/change-set.ts +30 -2
- package/src/lifecycle/deep-observe.ts +4 -0
- package/src/lifecycle/index.ts +1 -0
- package/src/lifecycle/observe.ts +29 -2
- package/src/lifecycle/replay.ts +3 -0
- package/src/lifecycle/snapshot.ts +3 -0
- package/src/lifecycle/teardown.test.ts +537 -0
- package/src/lifecycle/teardown.ts +357 -0
- package/src/lifecycle/types.ts +7 -0
- package/src/lint/engine.ts +7 -1
- package/src/lint/policy.ts +14 -3
- package/src/lint/rule.ts +23 -0
- package/src/lint/rules/cor021-env-literal-name.test.ts +128 -0
- package/src/lint/rules/cor021-env-literal-name.ts +114 -0
- package/src/lint/rules/index.ts +4 -1
- package/src/managed-fields.test.ts +15 -42
- package/src/managed-fields.ts +43 -26
- package/src/observation.test.ts +24 -8
- package/src/observation.ts +48 -4
- package/src/op/builders.ts +40 -7
- package/src/op/index.ts +1 -1
- package/src/ownership.test.ts +63 -1
- package/src/testing.test.ts +261 -0
- package/src/testing.ts +338 -0
- package/src/yaml.test.ts +15 -0
- package/src/yaml.ts +19 -2
|
@@ -44,7 +44,7 @@ import type { Capability } from "./capability";
|
|
|
44
44
|
export interface CapabilityManifest {
|
|
45
45
|
/** Package/plugin name (e.g. "aws", "gcp"), not a capability `kind`. */
|
|
46
46
|
name: string;
|
|
47
|
-
/**
|
|
47
|
+
/** The plugin package's version (semver), read from its `package.json`. */
|
|
48
48
|
version: string;
|
|
49
49
|
/** Minimum/compatible chant core version, checked the same way as `LexiconManifest.chantVersion` (see ../lexicon-manifest.ts's `checkVersionCompatibility`). */
|
|
50
50
|
chantVersion?: string;
|
|
@@ -69,7 +69,13 @@ export interface CapabilityPlugin {
|
|
|
69
69
|
/** Human-readable plugin/package name (e.g. "aws", "gcp"), not a capability `kind`. */
|
|
70
70
|
readonly name: string;
|
|
71
71
|
|
|
72
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* The plugin package's version (semver), read from its `package.json`
|
|
74
|
+
* (chant #1505). Mirrors `LexiconManifest.version`. Informational — nothing
|
|
75
|
+
* gates on it. The built-in plugins expose it as a lazy getter over
|
|
76
|
+
* `ownPackageVersion`, so the read happens on first access and never at
|
|
77
|
+
* module scope.
|
|
78
|
+
*/
|
|
73
79
|
readonly version: string;
|
|
74
80
|
|
|
75
81
|
/** Return every `Capability` this plugin contributes, keyed for registration by its own `kind`. */
|
|
@@ -124,8 +130,23 @@ export function isCapabilityPlugin(value: unknown): value is CapabilityPlugin {
|
|
|
124
130
|
*
|
|
125
131
|
* Returns `"0.0.0"` when no versioned `package.json` is found — a visible
|
|
126
132
|
* sentinel rather than a guess; nothing gates on the field.
|
|
133
|
+
*
|
|
134
|
+
* Call it lazily (from a `get version()` accessor, as the built-in plugins
|
|
135
|
+
* do), not at module scope: the read touches the filesystem, and a plugin
|
|
136
|
+
* module must stay importable where `fs` is absent (workerd, chant #1081).
|
|
137
|
+
* The result is cached per module URL, so the walk happens once.
|
|
127
138
|
*/
|
|
128
139
|
export function ownPackageVersion(moduleUrl: string): string {
|
|
140
|
+
const cached = ownVersionCache.get(moduleUrl);
|
|
141
|
+
if (cached !== undefined) return cached;
|
|
142
|
+
const found = findPackageVersion(moduleUrl);
|
|
143
|
+
ownVersionCache.set(moduleUrl, found);
|
|
144
|
+
return found;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const ownVersionCache = new Map<string, string>();
|
|
148
|
+
|
|
149
|
+
function findPackageVersion(moduleUrl: string): string {
|
|
129
150
|
let dir = dirname(fileURLToPath(moduleUrl));
|
|
130
151
|
for (;;) {
|
|
131
152
|
try {
|
|
@@ -18,6 +18,8 @@ import {
|
|
|
18
18
|
DriverGateUnsupportedError,
|
|
19
19
|
DriverRunFailure,
|
|
20
20
|
UnknownDependencyError,
|
|
21
|
+
accumulateComponentOutputs,
|
|
22
|
+
collectComponentOutputs,
|
|
21
23
|
resolveComponentGraph,
|
|
22
24
|
resolveWiring,
|
|
23
25
|
runComponentDeploy,
|
|
@@ -411,6 +413,49 @@ describe("runComponentDeploy — output wiring", () => {
|
|
|
411
413
|
});
|
|
412
414
|
});
|
|
413
415
|
|
|
416
|
+
describe("collectComponentOutputs / accumulateComponentOutputs — the shared accumulator (#700)", () => {
|
|
417
|
+
// Exported so the durable path's `accumulateComponentOutputs` activity
|
|
418
|
+
// (lexicons/temporal/src/component-op/activities.ts) captures outputs via
|
|
419
|
+
// the same function `runComponentDeploy` does — the accumulation twin of
|
|
420
|
+
// the already-shared `resolveStepInput`.
|
|
421
|
+
const phaseOutputs = {
|
|
422
|
+
Apply: { stackStatus: "CREATE_COMPLETE", outputs: { ClusterArn: "arn:cluster", ApiRepoUri: "repo" } },
|
|
423
|
+
Publish: { uri: "repo@sha256:abc", digest: "sha256:abc" },
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
it("namespaces publish outputs under `publish` and merges stack outputs at the top level", () => {
|
|
427
|
+
expect(collectComponentOutputs(phaseOutputs)).toEqual({
|
|
428
|
+
ClusterArn: "arn:cluster",
|
|
429
|
+
ApiRepoUri: "repo",
|
|
430
|
+
publish: { uri: "repo@sha256:abc", digest: "sha256:abc" },
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
it("returns undefined when no phase output looks like a publish or stack result", () => {
|
|
435
|
+
expect(collectComponentOutputs({ Verify: { ok: true } })).toBeUndefined();
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
it("records under the component name, merging over a seeded entry, and returns the map", () => {
|
|
439
|
+
const seeded = { "shared-alb": { Seeded: "keep" }, other: { x: 1 } };
|
|
440
|
+
const out = accumulateComponentOutputs(seeded, "shared-alb", phaseOutputs);
|
|
441
|
+
expect(out).toBe(seeded);
|
|
442
|
+
expect(out["shared-alb"]).toEqual({
|
|
443
|
+
Seeded: "keep",
|
|
444
|
+
ClusterArn: "arn:cluster",
|
|
445
|
+
ApiRepoUri: "repo",
|
|
446
|
+
publish: { uri: "repo@sha256:abc", digest: "sha256:abc" },
|
|
447
|
+
});
|
|
448
|
+
expect(out.other).toEqual({ x: 1 });
|
|
449
|
+
// Plain data throughout — survives the Temporal activity JSON boundary unchanged.
|
|
450
|
+
expect(JSON.parse(JSON.stringify(out))).toEqual(out);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it("is a no-op when the component exposed nothing", () => {
|
|
454
|
+
const map = { other: { x: 1 } };
|
|
455
|
+
expect(accumulateComponentOutputs(map, "svc", { Verify: { ok: true } })).toEqual({ other: { x: 1 } });
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
|
|
414
459
|
describe("runInterpretDriver — end to end", () => {
|
|
415
460
|
it("runs a producer before its consumer and threads env/vars into every capability call", async () => {
|
|
416
461
|
const registry = new CapabilityRegistry();
|
package/src/components/driver.ts
CHANGED
|
@@ -603,37 +603,77 @@ export async function runComponentDeploy(
|
|
|
603
603
|
return { component: component.name, ok: false, records };
|
|
604
604
|
}
|
|
605
605
|
|
|
606
|
-
// Publish-family outputs
|
|
607
|
-
//
|
|
608
|
-
//
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
}
|
|
606
|
+
// Publish-family and stack outputs become this component's entry in
|
|
607
|
+
// `componentOutputs` — through the same exported accumulator the durable
|
|
608
|
+
// path's `accumulateComponentOutputs` activity calls (#700), so local and
|
|
609
|
+
// Temporal runs can never diverge on what downstream references see.
|
|
610
|
+
accumulateComponentOutputs(componentOutputs, component.name, phaseOutputs);
|
|
611
|
+
|
|
612
|
+
return { component: component.name, ok: true, records };
|
|
613
|
+
}
|
|
613
614
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
615
|
+
/**
|
|
616
|
+
* Collect the cross-component outputs one finished component exposes to its
|
|
617
|
+
* downstream consumers, from every phase output it produced:
|
|
618
|
+
*
|
|
619
|
+
* - publish-family outputs (publish-image / publish-artifact /
|
|
620
|
+
* load-image-on-host all return at least one of uri/digest/key — see
|
|
621
|
+
* ./verbs/publish.ts) are namespaced under `publish`, so downstream
|
|
622
|
+
* `@<name>.publish.*` references resolve;
|
|
623
|
+
* - stack outputs from an apply step (cfn-deploy returns
|
|
624
|
+
* `CfnDeployOutput.outputs`) are merged at the top level, peer to
|
|
625
|
+
* `publish`, so `resolveWiring`'s stackOutput branch —
|
|
626
|
+
* `resolvePath(componentOutputs[stack], name)` — finds each output by
|
|
627
|
+
* name (the cross-stack apply-order integration deferred in #556, closed
|
|
628
|
+
* in #699). The entry is keyed by the component's own name, which by
|
|
629
|
+
* convention is the stack name a `stackOutput` reference targets (see the
|
|
630
|
+
* pilots and composition-and-wiring.mdx).
|
|
631
|
+
*
|
|
632
|
+
* Both halves are generic by output *shape*, not by capability `kind`, keeping
|
|
633
|
+
* the driver free of per-capability branching. Returns `undefined` when the
|
|
634
|
+
* component exposed nothing.
|
|
635
|
+
*
|
|
636
|
+
* Exported, like `resolveStepInput`, so the durable Temporal path
|
|
637
|
+
* (lexicons/temporal/src/component-op/activities.ts) accumulates outputs via
|
|
638
|
+
* this exact function rather than re-deriving it (#700). The resolver and the
|
|
639
|
+
* accumulator are the two halves of one contract; sharing only the resolver
|
|
640
|
+
* is how the cross-stack gap #699 closed locally could reopen durably.
|
|
641
|
+
*/
|
|
642
|
+
export function collectComponentOutputs(
|
|
643
|
+
phaseOutputs: Record<string, Record<string, unknown>>,
|
|
644
|
+
): Record<string, unknown> | undefined {
|
|
645
|
+
const publishOutput = findPublishOutput(phaseOutputs);
|
|
622
646
|
const stackOutputs = findStackOutputs(phaseOutputs);
|
|
623
|
-
if (stackOutputs)
|
|
624
|
-
|
|
625
|
-
|
|
647
|
+
if (!publishOutput && !stackOutputs) return undefined;
|
|
648
|
+
return { ...(stackOutputs ?? {}), ...(publishOutput ? { publish: publishOutput } : {}) };
|
|
649
|
+
}
|
|
626
650
|
|
|
627
|
-
|
|
651
|
+
/**
|
|
652
|
+
* Record a finished component's outputs (`collectComponentOutputs`) into the
|
|
653
|
+
* shared `componentOutputs` map under the component's own name, merging over
|
|
654
|
+
* any seeded entry (`--seed-outputs`, or a durable parent workflow's
|
|
655
|
+
* thread-through). Mutates and returns `componentOutputs`, so a Temporal
|
|
656
|
+
* activity can hand the updated map back to its workflow over the JSON
|
|
657
|
+
* boundary — every value in it is plain activity-result data, so the map is
|
|
658
|
+
* serializable by construction. A no-op when the component exposed nothing.
|
|
659
|
+
*/
|
|
660
|
+
export function accumulateComponentOutputs(
|
|
661
|
+
componentOutputs: Record<string, Record<string, unknown>>,
|
|
662
|
+
componentName: string,
|
|
663
|
+
phaseOutputs: Record<string, Record<string, unknown>>,
|
|
664
|
+
): Record<string, Record<string, unknown>> {
|
|
665
|
+
const collected = collectComponentOutputs(phaseOutputs);
|
|
666
|
+
if (collected) {
|
|
667
|
+
componentOutputs[componentName] = { ...componentOutputs[componentName], ...collected };
|
|
668
|
+
}
|
|
669
|
+
return componentOutputs;
|
|
628
670
|
}
|
|
629
671
|
|
|
630
672
|
/**
|
|
631
673
|
* Find the output of the last step that looks like a publish result (carries
|
|
632
|
-
* `uri`, `digest`, or `key`) across every phase this component ran, so
|
|
633
|
-
*
|
|
634
|
-
*
|
|
635
|
-
* carries one of these fields is eligible, keeping the driver free of
|
|
636
|
-
* per-capability branching.
|
|
674
|
+
* `uri`, `digest`, or `key`) across every phase this component ran, so
|
|
675
|
+
* `collectComponentOutputs` can populate `@<component>.publish.*` for a
|
|
676
|
+
* downstream consumer.
|
|
637
677
|
*/
|
|
638
678
|
function findPublishOutput(
|
|
639
679
|
phaseOutputs: Record<string, Record<string, unknown>>,
|
|
@@ -651,9 +691,6 @@ function findPublishOutput(
|
|
|
651
691
|
* Find the stack `outputs` map produced by an apply-style step (cfn-deploy
|
|
652
692
|
* returns `CfnDeployOutput.outputs`) across every phase this component ran, so
|
|
653
693
|
* a deployed stack's outputs can seed downstream `stackOutput()` resolution.
|
|
654
|
-
* Generic by shape — any output carrying an `outputs` record is eligible —
|
|
655
|
-
* keeping the driver free of per-capability branching, the same way
|
|
656
|
-
* `findPublishOutput` is.
|
|
657
694
|
*/
|
|
658
695
|
function findStackOutputs(
|
|
659
696
|
phaseOutputs: Record<string, Record<string, unknown>>,
|
package/src/components/index.ts
CHANGED
|
@@ -100,8 +100,11 @@ function starterCapabilities(): Array<Capability<never, unknown>> {
|
|
|
100
100
|
export const starterCapabilityPlugin: CapabilityPlugin = {
|
|
101
101
|
name: "starter",
|
|
102
102
|
// The core package's own version (#1505) — lockstep releases bump it, so a
|
|
103
|
-
// literal here would go stale every `just release`.
|
|
104
|
-
|
|
103
|
+
// literal here would go stale every `just release`. A getter, so the
|
|
104
|
+
// package.json read happens on first access rather than at import time.
|
|
105
|
+
get version(): string {
|
|
106
|
+
return ownPackageVersion(import.meta.url);
|
|
107
|
+
},
|
|
105
108
|
capabilities: starterCapabilities,
|
|
106
109
|
families: () => STARTER_VERB_FAMILIES,
|
|
107
110
|
};
|
package/src/config.test.ts
CHANGED
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
environmentName,
|
|
10
10
|
environmentNames,
|
|
11
11
|
environmentEndpoint,
|
|
12
|
+
matchesEnvironmentPattern,
|
|
13
|
+
matchesDeclaredEnvironment,
|
|
12
14
|
} from "./config";
|
|
13
15
|
import { writeFileSync, mkdirSync, rmSync } from "fs";
|
|
14
16
|
import { join } from "path";
|
|
@@ -336,6 +338,44 @@ describe("environmentName / environmentNames / environmentEndpoint (#1166)", ()
|
|
|
336
338
|
expect(environmentEndpoint(environments, "unknown")).toBeUndefined(); // not declared at all
|
|
337
339
|
expect(environmentEndpoint(undefined, "floci")).toBeUndefined();
|
|
338
340
|
});
|
|
341
|
+
|
|
342
|
+
test("environmentEndpoint resolves through a pattern entry, literal entry winning (#1221)", () => {
|
|
343
|
+
const environments = [
|
|
344
|
+
{ name: "pr-special", endpoint: "http://special:1111" },
|
|
345
|
+
{ name: "pr-*", endpoint: "http://preview:4566" },
|
|
346
|
+
];
|
|
347
|
+
expect(environmentEndpoint(environments, "pr-42")).toBe("http://preview:4566");
|
|
348
|
+
expect(environmentEndpoint(environments, "pr-special")).toBe("http://special:1111"); // literal wins over the pattern
|
|
349
|
+
expect(environmentEndpoint(environments, "prod")).toBeUndefined();
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
describe("environment glob patterns (#1221)", () => {
|
|
354
|
+
test("matchesEnvironmentPattern: a * matches any run of characters, anchored", () => {
|
|
355
|
+
expect(matchesEnvironmentPattern("pr-*", "pr-42")).toBe(true);
|
|
356
|
+
expect(matchesEnvironmentPattern("pr-*", "pr-")).toBe(true); // empty run is a run
|
|
357
|
+
expect(matchesEnvironmentPattern("pr-*", "pr")).toBe(false);
|
|
358
|
+
expect(matchesEnvironmentPattern("pr-*", "xpr-42")).toBe(false); // anchored at the start
|
|
359
|
+
expect(matchesEnvironmentPattern("*-preview", "42-preview")).toBe(true);
|
|
360
|
+
expect(matchesEnvironmentPattern("*-preview", "42-preview-old")).toBe(false); // anchored at the end
|
|
361
|
+
expect(matchesEnvironmentPattern("test-*-eu", "test-a-eu")).toBe(true);
|
|
362
|
+
expect(matchesEnvironmentPattern("test-*-eu", "test-a-us")).toBe(false);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
test("matchesEnvironmentPattern without a * is plain equality", () => {
|
|
366
|
+
expect(matchesEnvironmentPattern("prod", "prod")).toBe(true);
|
|
367
|
+
expect(matchesEnvironmentPattern("prod", "prod2")).toBe(false);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
test("matchesDeclaredEnvironment: literal first, then pattern, across entry forms", () => {
|
|
371
|
+
const environments = ["dev", "prod", "pr-*", { name: "test-*", endpoint: "http://localhost:4566" }];
|
|
372
|
+
expect(matchesDeclaredEnvironment(environments, "prod")).toBe(true);
|
|
373
|
+
expect(matchesDeclaredEnvironment(environments, "pr-42")).toBe(true);
|
|
374
|
+
expect(matchesDeclaredEnvironment(environments, "test-suite-a")).toBe(true); // object-form pattern entry
|
|
375
|
+
expect(matchesDeclaredEnvironment(environments, "stage")).toBe(false);
|
|
376
|
+
expect(matchesDeclaredEnvironment(undefined, "prod")).toBe(false);
|
|
377
|
+
expect(matchesDeclaredEnvironment([], "prod")).toBe(false);
|
|
378
|
+
});
|
|
339
379
|
});
|
|
340
380
|
|
|
341
381
|
describe("resolveFoldEnabled (#1134 — fold is the default build path)", () => {
|
package/src/config.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { DEFAULT_SBOM_FORMAT, type SbomFormat } from "./components/verbs/sbom-ge
|
|
|
7
7
|
import type { Severity } from "./components/verbs/vuln-scan";
|
|
8
8
|
import type { VulnPolicy } from "./components/verbs/vuln-gate";
|
|
9
9
|
import type { BuildParamsConfig } from "./build-params";
|
|
10
|
+
import type { BuildParamProvenance } from "./provenance";
|
|
10
11
|
import { findProjectConfig } from "./project-root";
|
|
11
12
|
import { evaluateProjectConfig } from "./config-sandbox";
|
|
12
13
|
|
|
@@ -22,6 +23,12 @@ import { evaluateProjectConfig } from "./config-sandbox";
|
|
|
22
23
|
*/
|
|
23
24
|
export type EnvironmentDeclaration = string | { name: string; endpoint?: string };
|
|
24
25
|
|
|
26
|
+
/**
|
|
27
|
+
* `ownership.env`: a literal environment identity, or a reference to a
|
|
28
|
+
* declared build parameter whose resolved value becomes the identity (#1396).
|
|
29
|
+
*/
|
|
30
|
+
export type OwnershipEnv = string | { param: string };
|
|
31
|
+
|
|
25
32
|
/** The declared name of one `environments` entry, whichever form it takes. */
|
|
26
33
|
export function environmentName(entry: EnvironmentDeclaration): string {
|
|
27
34
|
return typeof entry === "string" ? entry : entry.name;
|
|
@@ -37,6 +44,56 @@ export function environmentNames(environments: EnvironmentDeclaration[] | undefi
|
|
|
37
44
|
return environments?.map(environmentName);
|
|
38
45
|
}
|
|
39
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Whether a declared environment name is a glob pattern rather than a literal
|
|
49
|
+
* (#1221) — it contains at least one `*`.
|
|
50
|
+
*/
|
|
51
|
+
export function isEnvironmentPattern(declaredName: string): boolean {
|
|
52
|
+
return declaredName.includes("*");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Anchored `*`-glob match (#1221): each `*` in `pattern` matches any run of
|
|
57
|
+
* characters (possibly empty); everything else matches itself. Deliberately
|
|
58
|
+
* the whole surface — no `?`, no character classes, no regex. A pattern with
|
|
59
|
+
* no `*` degenerates to string equality.
|
|
60
|
+
*/
|
|
61
|
+
export function matchesEnvironmentPattern(pattern: string, name: string): boolean {
|
|
62
|
+
const parts = pattern.split("*");
|
|
63
|
+
if (parts.length === 1) return pattern === name;
|
|
64
|
+
const first = parts[0];
|
|
65
|
+
const last = parts[parts.length - 1];
|
|
66
|
+
if (!name.startsWith(first)) return false;
|
|
67
|
+
let cursor = first.length;
|
|
68
|
+
for (let i = 1; i < parts.length - 1; i++) {
|
|
69
|
+
const part = parts[i];
|
|
70
|
+
if (part === "") continue;
|
|
71
|
+
const at = name.indexOf(part, cursor);
|
|
72
|
+
if (at === -1) return false;
|
|
73
|
+
cursor = at + part.length;
|
|
74
|
+
}
|
|
75
|
+
return name.length - cursor >= last.length && name.endsWith(last);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Environment-membership test (#1221): is `name` covered by this project's
|
|
80
|
+
* declared `environments`? A literal entry matches by equality; an entry
|
|
81
|
+
* containing `*` matches as a glob pattern (`"pr-*"` covers `pr-42`).
|
|
82
|
+
* Literals are checked first across the whole list, then patterns — so a
|
|
83
|
+
* name that is declared outright never depends on pattern order. `undefined`
|
|
84
|
+
* / empty `environments` returns `false`; callers that treat "no declared
|
|
85
|
+
* environments" as "anything goes" (e.g. {@link unknownEnvError} in
|
|
86
|
+
* `./env.ts`) guard that case themselves.
|
|
87
|
+
*/
|
|
88
|
+
export function matchesDeclaredEnvironment(
|
|
89
|
+
environments: EnvironmentDeclaration[] | undefined,
|
|
90
|
+
name: string,
|
|
91
|
+
): boolean {
|
|
92
|
+
const names = environmentNames(environments) ?? [];
|
|
93
|
+
if (names.includes(name)) return true;
|
|
94
|
+
return names.some((declared) => isEnvironmentPattern(declared) && matchesEnvironmentPattern(declared, name));
|
|
95
|
+
}
|
|
96
|
+
|
|
40
97
|
/**
|
|
41
98
|
* The endpoint `name` declares (chant #1166) — `undefined` for a bare-string
|
|
42
99
|
* entry, an entry with no `endpoint` set, or a name this project doesn't
|
|
@@ -44,9 +101,16 @@ export function environmentNames(environments: EnvironmentDeclaration[] | undefi
|
|
|
44
101
|
* it injects this into the ambient env var each observing lexicon's CLI
|
|
45
102
|
* shell-out reads (e.g. `AWS_ENDPOINT_URL`), unless that var is already set —
|
|
46
103
|
* ambient always wins.
|
|
104
|
+
*
|
|
105
|
+
* A pattern entry (#1221) can carry an endpoint too: `{ name: "pr-*",
|
|
106
|
+
* endpoint }` supplies the endpoint for every `pr-<n>` environment. A literal
|
|
107
|
+
* entry always wins over a pattern — declaring `pr-special` alongside
|
|
108
|
+
* `pr-*` resolves `pr-special` to the literal entry's endpoint (or none).
|
|
47
109
|
*/
|
|
48
110
|
export function environmentEndpoint(environments: EnvironmentDeclaration[] | undefined, name: string): string | undefined {
|
|
49
|
-
const found =
|
|
111
|
+
const found =
|
|
112
|
+
environments?.find((e) => environmentName(e) === name) ??
|
|
113
|
+
environments?.find((e) => isEnvironmentPattern(environmentName(e)) && matchesEnvironmentPattern(environmentName(e), name));
|
|
50
114
|
return found && typeof found !== "string" ? found.endpoint : undefined;
|
|
51
115
|
}
|
|
52
116
|
|
|
@@ -69,7 +133,7 @@ export const ChantConfigSchema = z.object({
|
|
|
69
133
|
lint: z.record(z.string(), z.unknown()).optional(),
|
|
70
134
|
ownership: z.object({
|
|
71
135
|
stack: z.string().min(1).optional(),
|
|
72
|
-
env: z.string().min(1).optional(),
|
|
136
|
+
env: z.union([z.string().min(1), z.object({ param: z.string().min(1) })]).optional(),
|
|
73
137
|
enabled: z.boolean().optional(),
|
|
74
138
|
}).optional(),
|
|
75
139
|
build: z.object({
|
|
@@ -193,8 +257,17 @@ export interface ChantConfig {
|
|
|
193
257
|
ownership?: {
|
|
194
258
|
/** Stack identity stamped onto resources (required to enable stamping). */
|
|
195
259
|
stack?: string;
|
|
196
|
-
/**
|
|
197
|
-
|
|
260
|
+
/**
|
|
261
|
+
* Optional environment identity. Either a literal, or a reference to one
|
|
262
|
+
* of this project's declared `buildParams` (`{ param: "env" }`), in which
|
|
263
|
+
* case the marker takes whatever value that parameter resolves to for the
|
|
264
|
+
* build — the same value `params.env` reads in source (#1396). A
|
|
265
|
+
* reference is the only way to make the marker follow `--param env=prod`:
|
|
266
|
+
* the config is evaluated before build parameters exist, so a literal
|
|
267
|
+
* computed from `process.env` here and a parameter mapped to the same
|
|
268
|
+
* variable are two sources of truth that nothing keeps in step.
|
|
269
|
+
*/
|
|
270
|
+
env?: OwnershipEnv;
|
|
198
271
|
/** Set false to disable stamping even when `stack` is present. */
|
|
199
272
|
enabled?: boolean;
|
|
200
273
|
};
|
|
@@ -490,14 +563,92 @@ function warnIfFragmentShadowsProjectConfig(configPath: string): void {
|
|
|
490
563
|
/** Top-level keys that make a config a *project* config rather than a lint fragment. */
|
|
491
564
|
const PROJECT_CONFIG_KEYS = new Set(Object.keys(ChantConfigSchema.shape).filter((k) => k !== "lint"));
|
|
492
565
|
|
|
566
|
+
/** Whether an `ownership.env` entry is a build-parameter reference rather than a literal. */
|
|
567
|
+
export function isOwnershipParamRef(env: OwnershipEnv | undefined): env is { param: string } {
|
|
568
|
+
return typeof env === "object" && env !== null && typeof env.param === "string";
|
|
569
|
+
}
|
|
570
|
+
|
|
493
571
|
/**
|
|
494
|
-
*
|
|
495
|
-
*
|
|
572
|
+
* The ownership stack identity alone — `ownership.stack` when marking is on
|
|
573
|
+
* (no `stack`, or `enabled: false`, means off). For callers that need the
|
|
574
|
+
* stack and nothing else (a field manager, a prune selector) and so have no
|
|
575
|
+
* build parameters to resolve `env` against.
|
|
496
576
|
*/
|
|
497
|
-
export function
|
|
577
|
+
export function resolveOwnershipStack(config: ChantConfig): string | undefined {
|
|
498
578
|
const o = config.ownership;
|
|
499
579
|
if (!o || !o.stack || o.enabled === false) return undefined;
|
|
500
|
-
return
|
|
580
|
+
return o.stack;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Resolve the `env` half of the ownership marker (#1396). A literal is
|
|
585
|
+
* returned as-is. A `{ param }` reference is looked up in this build's resolved
|
|
586
|
+
* build parameters and must find one: a reference to a parameter that was
|
|
587
|
+
* never declared, or that resolved to no value, throws rather than quietly
|
|
588
|
+
* stamping a marker without an env — an env-less marker is exactly what lets
|
|
589
|
+
* a prod deployment go unseen by `--owned` filtering scoped to prod.
|
|
590
|
+
*/
|
|
591
|
+
export function resolveOwnershipEnv(
|
|
592
|
+
config: ChantConfig,
|
|
593
|
+
buildParams: readonly BuildParamProvenance[] | undefined,
|
|
594
|
+
): string | undefined {
|
|
595
|
+
const env = config.ownership?.env;
|
|
596
|
+
if (!isOwnershipParamRef(env)) return env;
|
|
597
|
+
const name = env.param;
|
|
598
|
+
const declared = config.buildParams?.[name];
|
|
599
|
+
if (!declared) {
|
|
600
|
+
throw new Error(
|
|
601
|
+
`ownership.env references build parameter "${name}", which chant.config.ts's buildParams does not declare`,
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
const resolved = buildParams?.find((p) => p.name === name);
|
|
605
|
+
if (!resolved) {
|
|
606
|
+
throw new Error(
|
|
607
|
+
`ownership.env references build parameter "${name}", which resolved to no value for this build — pass --param ${name}=<value>${declared.env ? `, set ${declared.env}` : ""}, or give it a default`,
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
return String(resolved.value);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Resolve the ownership marker to stamp from project config, or undefined when
|
|
615
|
+
* ownership marking is off (no `stack`, or `enabled: false`). `buildParams` is
|
|
616
|
+
* this build's resolved parameters (`resolveBuildParams`'s provenance), which
|
|
617
|
+
* an `ownership.env: { param }` reference resolves against — see {@link
|
|
618
|
+
* resolveOwnershipEnv}; it throws when the reference cannot be satisfied.
|
|
619
|
+
*/
|
|
620
|
+
export function resolveOwnershipMarker(
|
|
621
|
+
config: ChantConfig,
|
|
622
|
+
buildParams?: readonly BuildParamProvenance[],
|
|
623
|
+
): OwnershipMarker | undefined {
|
|
624
|
+
const stack = resolveOwnershipStack(config);
|
|
625
|
+
if (stack === undefined) return undefined;
|
|
626
|
+
return { stack, env: resolveOwnershipEnv(config, buildParams) };
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* The divergence #1396 is about, made loud when it cannot be removed: a
|
|
631
|
+
* project whose `ownership.env` is a literal while it also declares a build
|
|
632
|
+
* parameter named `env` has two answers to "which environment is this", and
|
|
633
|
+
* the manifest carries both (the ownership marker from config, the labels
|
|
634
|
+
* from the parameter). Returns a warning naming both values when they differ;
|
|
635
|
+
* undefined when there is nothing to say (no literal, no `env` parameter, or
|
|
636
|
+
* the two agree). The fix is the `{ param: "env" }` reference.
|
|
637
|
+
*/
|
|
638
|
+
export function ownershipEnvDisagreement(
|
|
639
|
+
config: ChantConfig,
|
|
640
|
+
buildParams: readonly BuildParamProvenance[] | undefined,
|
|
641
|
+
): string | undefined {
|
|
642
|
+
const literal = config.ownership?.env;
|
|
643
|
+
if (typeof literal !== "string") return undefined;
|
|
644
|
+
if (resolveOwnershipStack(config) === undefined) return undefined;
|
|
645
|
+
const param = buildParams?.find((p) => p.name === "env");
|
|
646
|
+
if (!param || String(param.value) === literal) return undefined;
|
|
647
|
+
return (
|
|
648
|
+
`ownership.env is "${literal}" but the env build parameter resolved to ${JSON.stringify(param.value)} (${param.source}) — ` +
|
|
649
|
+
`the ownership marker and params.env disagree about which environment this build is for. ` +
|
|
650
|
+
`Use ownership: { env: { param: "env" } } so the marker follows the parameter.`
|
|
651
|
+
);
|
|
501
652
|
}
|
|
502
653
|
|
|
503
654
|
/**
|