@intentius/chant 0.124.44 → 0.124.46
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/capabilities/aws.paths +148 -0
- package/dist/capabilities/base-mappings.json +122 -0
- package/dist/capabilities/catalog.d.ts +89 -0
- package/dist/capabilities/catalog.d.ts.map +1 -0
- package/dist/capabilities/catalog.js +204 -0
- package/dist/capabilities/language.d.ts +103 -0
- package/dist/capabilities/language.d.ts.map +1 -0
- package/dist/capabilities/language.js +335 -0
- package/dist/capabilities/matcher.d.ts +65 -0
- package/dist/capabilities/matcher.d.ts.map +1 -0
- package/dist/capabilities/matcher.js +299 -0
- package/dist/capabilities/view.d.ts +207 -0
- package/dist/capabilities/view.d.ts.map +1 -0
- package/dist/capabilities/view.js +484 -0
- package/dist/ci-identity.d.ts +183 -12
- package/dist/ci-identity.d.ts.map +1 -1
- package/dist/ci-identity.js +362 -53
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/commands/lint.js +1 -0
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.js +17 -1
- package/dist/cli/handlers/declared-graph.d.ts +44 -0
- package/dist/cli/handlers/declared-graph.d.ts.map +1 -0
- package/dist/cli/handlers/declared-graph.js +87 -0
- package/dist/cli/handlers/graph.d.ts +25 -0
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/graph.js +93 -87
- package/dist/cli/handlers/pr-env.d.ts +8 -2
- package/dist/cli/handlers/pr-env.d.ts.map +1 -1
- package/dist/cli/handlers/pr-env.js +127 -225
- package/dist/cli/handlers/pr.d.ts +3 -0
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +1 -0
- package/dist/cli/handlers/search-count.d.ts +89 -0
- package/dist/cli/handlers/search-count.d.ts.map +1 -0
- package/dist/cli/handlers/search-count.js +132 -0
- package/dist/cli/handlers/search.d.ts +358 -6
- package/dist/cli/handlers/search.d.ts.map +1 -1
- package/dist/cli/handlers/search.js +1065 -155
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +23 -3
- package/dist/cli/mcp/server.d.ts.map +1 -1
- package/dist/cli/mcp/server.js +3 -0
- package/dist/cli/mcp/tools/capabilities.d.ts +7 -0
- package/dist/cli/mcp/tools/capabilities.d.ts.map +1 -0
- package/dist/cli/mcp/tools/capabilities.js +27 -0
- package/dist/cli/mcp/workspace-tools.d.ts +3 -1
- package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
- package/dist/cli/mcp/workspace-tools.js +47 -2
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +64 -17
- package/dist/cli/registry.d.ts +22 -0
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +2 -0
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/cli-support.js +4 -2
- package/dist/components/fan-out-support.d.ts +3 -0
- package/dist/components/fan-out-support.d.ts.map +1 -1
- package/dist/components/fan-out-support.js +1 -1
- package/dist/components/pr-env-ports.d.ts +29 -0
- package/dist/components/pr-env-ports.d.ts.map +1 -0
- package/dist/components/pr-env-ports.js +77 -0
- package/dist/components/pr-env-run.d.ts +254 -0
- package/dist/components/pr-env-run.d.ts.map +1 -0
- package/dist/components/pr-env-run.js +376 -0
- package/dist/components/pr-environment.d.ts +91 -15
- package/dist/components/pr-environment.d.ts.map +1 -1
- package/dist/components/pr-environment.js +95 -9
- package/dist/components/pr-pipeline.d.ts +10 -1
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/config-environments.d.ts +92 -0
- package/dist/config-environments.d.ts.map +1 -0
- package/dist/config-environments.js +102 -0
- package/dist/config.d.ts +24 -84
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +9 -96
- package/dist/graph-detail.d.ts.map +1 -1
- package/dist/graph-detail.js +15 -3
- package/dist/graph-effective.d.ts +33 -2
- package/dist/graph-effective.d.ts.map +1 -1
- package/dist/graph-effective.js +215 -68
- package/dist/graph-ir.d.ts +23 -0
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/graph-ir.js +130 -0
- package/dist/graph-sub-template.d.ts +11 -0
- package/dist/graph-sub-template.d.ts.map +1 -0
- package/dist/graph-sub-template.js +19 -0
- package/dist/lexicon.d.ts +9 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/observe.d.ts +28 -0
- package/dist/lifecycle/observe.d.ts.map +1 -1
- package/dist/lifecycle/observe.js +23 -1
- package/dist/lint/rule.d.ts +6 -0
- package/dist/lint/rule.d.ts.map +1 -1
- package/dist/live-endpoint.d.ts +1 -1
- package/dist/live-endpoint.d.ts.map +1 -1
- package/dist/live-endpoint.js +1 -1
- package/dist/observation.d.ts +48 -1
- package/dist/observation.d.ts.map +1 -1
- package/dist/observation.js +21 -5
- package/dist/op/activities/activity-contracts.d.ts +4 -4
- package/dist/op/op-waves.d.ts +17 -0
- package/dist/op/op-waves.d.ts.map +1 -1
- package/dist/op/op-waves.js +23 -1
- package/dist/pr-forge.d.ts +15 -1
- package/dist/pr-forge.d.ts.map +1 -1
- package/dist/pr-forge.js +28 -16
- package/dist/pr-requires.d.ts +5 -1
- package/dist/pr-requires.d.ts.map +1 -1
- package/dist/wave-runtime.mjs +251 -250
- package/dist/workspace/capabilities-contract.d.ts +66 -0
- package/dist/workspace/capabilities-contract.d.ts.map +1 -0
- package/dist/workspace/capabilities-contract.js +107 -0
- package/dist/workspace/capabilities.schema.json +229 -0
- package/dist/workspace/ci-cloud-identity.d.ts +3 -1
- package/dist/workspace/ci-cloud-identity.d.ts.map +1 -1
- package/dist/workspace/ci-cloud-identity.js +16 -1
- package/dist/workspace/ci-run.d.ts +35 -8
- package/dist/workspace/ci-run.d.ts.map +1 -1
- package/dist/workspace/ci-run.js +95 -21
- package/dist/workspace/ci-secrets.d.ts.map +1 -1
- package/dist/workspace/ci-secrets.js +4 -3
- package/dist/workspace/compose-graph.d.ts +9 -0
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/compose-graph.js +3 -1
- package/dist/workspace/conformance/index.d.ts +1 -1
- package/dist/workspace/conformance/index.d.ts.map +1 -1
- package/dist/workspace/conformance/index.js +39 -2
- package/dist/workspace/declaration.d.ts +10 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +17 -1
- package/dist/workspace/declaration.schema.json +36 -1
- package/dist/workspace/graph-cache.d.ts +8 -0
- package/dist/workspace/graph-cache.d.ts.map +1 -1
- package/dist/workspace/graph-cache.js +27 -3
- package/dist/workspace/graph-cli.d.ts +39 -2
- package/dist/workspace/graph-cli.d.ts.map +1 -1
- package/dist/workspace/graph-cli.js +66 -24
- package/dist/workspace/graph.schema.json +148 -0
- package/dist/workspace/kind-readers.d.ts +3 -0
- package/dist/workspace/kind-readers.d.ts.map +1 -1
- package/dist/workspace/kind-readers.js +7 -1
- package/dist/workspace/member-commands.d.ts +10 -1
- package/dist/workspace/member-commands.d.ts.map +1 -1
- package/dist/workspace/member-commands.js +17 -1
- package/dist/workspace/nested-graph.d.ts +2 -0
- package/dist/workspace/nested-graph.d.ts.map +1 -1
- package/dist/workspace/nested-graph.js +32 -1
- package/dist/workspace/reason-codes.d.ts +10 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +12 -0
- package/dist/workspace/search-cli.d.ts +204 -0
- package/dist/workspace/search-cli.d.ts.map +1 -0
- package/dist/workspace/search-cli.js +466 -0
- package/dist/workspace/search-contract.d.ts +56 -0
- package/dist/workspace/search-contract.d.ts.map +1 -0
- package/dist/workspace/search-contract.js +70 -0
- package/dist/workspace/search.schema.json +822 -0
- package/package.json +1 -1
- package/src/__snapshots__/okf.test.ts.snap +10 -0
- package/src/capabilities/aws.paths +148 -0
- package/src/capabilities/base-mappings.json +122 -0
- package/src/capabilities/catalog.ts +230 -0
- package/src/capabilities/language.test.ts +191 -0
- package/src/capabilities/language.ts +373 -0
- package/src/capabilities/matcher.ts +315 -0
- package/src/capabilities/view-compose.test.ts +76 -0
- package/src/capabilities/view.ts +631 -0
- package/src/ci-identity-targets.test.ts +275 -0
- package/src/ci-identity.ts +394 -61
- package/src/cli/commands/lint.ts +1 -0
- package/src/cli/handlers/build.ts +15 -1
- package/src/cli/handlers/declared-graph.ts +99 -0
- package/src/cli/handlers/graph.test.ts +94 -3
- package/src/cli/handlers/graph.ts +123 -96
- package/src/cli/handlers/pr-env.ts +135 -227
- package/src/cli/handlers/pr.ts +4 -0
- package/src/cli/handlers/search-build-roots.test.ts +144 -0
- package/src/cli/handlers/search-count.test.ts +191 -0
- package/src/cli/handlers/search-count.ts +165 -0
- package/src/cli/handlers/search-json.test.ts +333 -0
- package/src/cli/handlers/search-live.test.ts +387 -0
- package/src/cli/handlers/search-paths.test.ts +235 -0
- package/src/cli/handlers/search.ts +1312 -166
- package/src/cli/main.test.ts +28 -3
- package/src/cli/main.ts +23 -3
- package/src/cli/mcp/server.test.ts +3 -3
- package/src/cli/mcp/server.ts +3 -0
- package/src/cli/mcp/tools/capabilities.ts +32 -0
- package/src/cli/mcp/workspace-tools.e2e.test.ts +21 -1
- package/src/cli/mcp/workspace-tools.ts +47 -2
- package/src/cli/parse-args.ts +52 -13
- package/src/cli/registry.ts +22 -0
- package/src/components/cli-support.ts +6 -1
- package/src/components/fan-out-support.ts +4 -1
- package/src/components/pr-env-ports.ts +89 -0
- package/src/components/pr-env-run-bundle.test.ts +54 -0
- package/src/components/pr-env-run.test.ts +290 -0
- package/src/components/pr-env-run.ts +563 -0
- package/src/components/pr-environment.ts +161 -13
- package/src/components/pr-pipeline.ts +10 -1
- package/src/config-environments.ts +133 -0
- package/src/config.ts +37 -124
- package/src/graph-detail.ts +15 -3
- package/src/graph-effective.ts +226 -75
- package/src/graph-ir.test.ts +163 -0
- package/src/graph-ir.ts +147 -0
- package/src/graph-sub-template.ts +22 -0
- package/src/lexicon.ts +10 -0
- package/src/lifecycle/observe.test.ts +27 -1
- package/src/lifecycle/observe.ts +43 -1
- package/src/lint/rule.ts +6 -0
- package/src/live-endpoint.ts +1 -1
- package/src/observation.test.ts +25 -0
- package/src/observation.ts +69 -5
- package/src/op/op-waves.ts +38 -1
- package/src/pr-forge-bundle.test.ts +1 -1
- package/src/pr-forge.ts +44 -17
- package/src/pr-requires.test.ts +43 -6
- package/src/pr-requires.ts +5 -1
- package/src/workspace/capabilities-contract.test.ts +88 -0
- package/src/workspace/capabilities-contract.ts +132 -0
- package/src/workspace/capabilities.schema.json +229 -0
- package/src/workspace/ci-cloud-identity.test.ts +20 -0
- package/src/workspace/ci-cloud-identity.ts +14 -1
- package/src/workspace/ci-run.test.ts +74 -1
- package/src/workspace/ci-run.ts +122 -29
- package/src/workspace/ci-secrets.test.ts +1 -1
- package/src/workspace/ci-secrets.ts +4 -3
- package/src/workspace/compose-graph.ts +10 -1
- package/src/workspace/conformance/conformance.e2e.test.ts +14 -1
- package/src/workspace/conformance/index.ts +31 -2
- package/src/workspace/declaration.schema.json +36 -1
- package/src/workspace/declaration.test.ts +7 -0
- package/src/workspace/declaration.ts +24 -2
- package/src/workspace/graph-cache.ts +27 -3
- package/src/workspace/graph-cli.ts +96 -24
- package/src/workspace/graph.schema.json +148 -0
- package/src/workspace/kind-readers.e2e.test.ts +28 -0
- package/src/workspace/kind-readers.test.ts +1 -1
- package/src/workspace/kind-readers.ts +7 -1
- package/src/workspace/member-commands.ts +22 -1
- package/src/workspace/nested-graph.ts +32 -1
- package/src/workspace/reason-codes.test.ts +7 -0
- package/src/workspace/reason-codes.ts +12 -0
- package/src/workspace/search-cli.e2e.test.ts +305 -0
- package/src/workspace/search-cli.ts +591 -0
- package/src/workspace/search-contract.ts +95 -0
- package/src/workspace/search.schema.json +822 -0
|
@@ -32,14 +32,20 @@
|
|
|
32
32
|
*
|
|
33
33
|
* terragucci's `ephemeral.ts` gives each copy a suffixed Terraform state key.
|
|
34
34
|
* That part is Terraform's own and stays there; chant keys ownership on the
|
|
35
|
-
* marker its lexicons stamp and read back (`../lifecycle/teardown.ts`).
|
|
35
|
+
* marker its lexicons stamp and read back (`../lifecycle/teardown.ts`). A
|
|
36
|
+
* product supplies the rest as adapters (#4330): its plan and apply
|
|
37
|
+
* (`./pr-env-run.ts`), and a teardown for what no lexicon tears down
|
|
38
|
+
* ({@link PrEnvTeardownAdapter}), which reads the marker back from the copy
|
|
39
|
+
* as a lexicon does.
|
|
36
40
|
*/
|
|
37
41
|
|
|
38
|
-
import {
|
|
42
|
+
import type { ChantConfig } from "../config";
|
|
43
|
+
import { environmentEndpoint, environmentName, isEnvironmentPattern, isOwnershipParamRef, matchesDeclaredEnvironment, resolveOwnershipStack, type EnvironmentDeclaration } from "../config-environments";
|
|
39
44
|
import type { OwnershipMarker } from "../ownership";
|
|
40
45
|
import { executeTeardown, planTeardown, type TeardownPlan, type TeardownPlanEntry, type TeardownReport } from "../lifecycle/teardown";
|
|
41
|
-
import type { ObservationLexicon } from "../lexicon";
|
|
46
|
+
import type { ObservationLexicon, TeardownCandidate, TeardownEnumeration, TeardownExecution } from "../lexicon";
|
|
42
47
|
import { applyLiveEndpoint } from "../live-endpoint";
|
|
48
|
+
import type { ComponentEnvironmentDeclaration } from "./component";
|
|
43
49
|
|
|
44
50
|
/** The gate a pull request's environment waits on, under the pull request's op `pr-<n>`. */
|
|
45
51
|
export const PR_ENV_GATE = "pr-env";
|
|
@@ -167,6 +173,35 @@ export function derivePrEnvironment(config: ChantConfig, template: string, pr: n
|
|
|
167
173
|
return { env, template, stack, param: own.param, environments, ...(endpoint ? { endpoint } : {}) };
|
|
168
174
|
}
|
|
169
175
|
|
|
176
|
+
/** The placeholder a template's site URL or domain names the environment with (#4330). */
|
|
177
|
+
export const PR_ENV_SITE_PLACEHOLDER = "{env}";
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* A component's site in `env`, a pull request's environment, carried over
|
|
181
|
+
* from its site in `template` (#4330). An entry the component declares for
|
|
182
|
+
* `env` itself wins. Otherwise the template's runtime and lifecycle remote
|
|
183
|
+
* carry over as they are. Its URL and domain carry over only when they name
|
|
184
|
+
* the environment with `{env}`, which becomes `pr-<n>`: a literal URL or
|
|
185
|
+
* domain is the template's own release, so it is left out rather than
|
|
186
|
+
* pointing pr-<n> at it. Undefined when neither is declared.
|
|
187
|
+
*/
|
|
188
|
+
export function prEnvironmentSite(declared: readonly ComponentEnvironmentDeclaration[] | undefined, template: string, env: string): ComponentEnvironmentDeclaration | undefined {
|
|
189
|
+
const own = declared?.find((d) => d.name === env);
|
|
190
|
+
if (own) return { ...own };
|
|
191
|
+
const from = declared?.find((d) => d.name === template);
|
|
192
|
+
if (!from) return undefined;
|
|
193
|
+
const named = (v: string | undefined): string | undefined => (v && v.includes(PR_ENV_SITE_PLACEHOLDER) ? v.split(PR_ENV_SITE_PLACEHOLDER).join(env) : undefined);
|
|
194
|
+
const url = named(from.url);
|
|
195
|
+
const domain = named(from.domain);
|
|
196
|
+
return {
|
|
197
|
+
name: env,
|
|
198
|
+
...(from.runtime ? { runtime: from.runtime } : {}),
|
|
199
|
+
...(url ? { url } : {}),
|
|
200
|
+
...(domain ? { domain } : {}),
|
|
201
|
+
...(from.lifecycle ? { lifecycle: from.lifecycle } : {}),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
170
205
|
// ── records ────────────────────────────────────────────────────────────────
|
|
171
206
|
|
|
172
207
|
/** One line of `_pr-envs/records.jsonl`: an apply, or a teardown. */
|
|
@@ -186,6 +221,12 @@ export interface PrEnvRecord {
|
|
|
186
221
|
commit: string;
|
|
187
222
|
/** The components applied (up) or the lexicons torn down (down). */
|
|
188
223
|
components: string[];
|
|
224
|
+
/**
|
|
225
|
+
* Up (#4330): where each component's copy lives, by component, as the
|
|
226
|
+
* adapter that applied it says (a Terraform root's suffixed state key).
|
|
227
|
+
* A teardown adapter reads it back from the live environment.
|
|
228
|
+
*/
|
|
229
|
+
locations?: Record<string, string>;
|
|
189
230
|
/** The change-set digest applied; empty for a teardown. */
|
|
190
231
|
planDigest: string;
|
|
191
232
|
/** `incomplete`: a teardown that left something, so the environment stays live and the sweep tries again. */
|
|
@@ -229,6 +270,8 @@ export interface LivePrEnvironment {
|
|
|
229
270
|
commit: string;
|
|
230
271
|
/** Every component an apply of it ever touched. */
|
|
231
272
|
components: string[];
|
|
273
|
+
/** Where each component's copy lives, merged over every up (#4330). */
|
|
274
|
+
locations?: Record<string, string>;
|
|
232
275
|
applied: string;
|
|
233
276
|
expiresAt: string;
|
|
234
277
|
/** Its last teardown left something, so the sweep tries again. */
|
|
@@ -245,6 +288,7 @@ export function livePrEnvironments(records: readonly PrEnvRecord[]): Map<string,
|
|
|
245
288
|
for (const r of records) {
|
|
246
289
|
if (r.kind === "up") {
|
|
247
290
|
const was = live.get(r.env);
|
|
291
|
+
const locations = { ...(was?.locations ?? {}), ...(r.locations ?? {}) };
|
|
248
292
|
live.set(r.env, {
|
|
249
293
|
pr: r.pr,
|
|
250
294
|
env: r.env,
|
|
@@ -253,6 +297,7 @@ export function livePrEnvironments(records: readonly PrEnvRecord[]): Map<string,
|
|
|
253
297
|
marker: { ...r.marker },
|
|
254
298
|
commit: r.commit,
|
|
255
299
|
components: [...new Set([...(was?.components ?? []), ...r.components])],
|
|
300
|
+
...(Object.keys(locations).length > 0 ? { locations } : {}),
|
|
256
301
|
applied: r.timestamp,
|
|
257
302
|
expiresAt: r.expiresAt ?? r.timestamp,
|
|
258
303
|
});
|
|
@@ -302,12 +347,61 @@ export function ownedEntries(plan: TeardownPlan, marker: OwnershipMarker): { pla
|
|
|
302
347
|
return { plan: { ...plan, entries: plan.entries.filter(owns) }, dropped: plan.entries.filter((e) => !owns(e)) };
|
|
303
348
|
}
|
|
304
349
|
|
|
350
|
+
/**
|
|
351
|
+
* A teardown a product supplies for what no lexicon tears down (#4330), such
|
|
352
|
+
* as a Terraform root's copy under a suffixed state key: chant has no
|
|
353
|
+
* Terraform runner, so terragucci supplies this. It takes part in the same
|
|
354
|
+
* two halves a lexicon's teardown does (`../lifecycle/teardown.ts`): `owned`
|
|
355
|
+
* names what the environment owns, each with the marker read back from it,
|
|
356
|
+
* and `teardown` deletes the candidates chant hands back. Every candidate
|
|
357
|
+
* whose marker is not exactly `{ stack, env: "pr-<n>" }` is dropped before
|
|
358
|
+
* `teardown` sees it, and a candidate it says nothing about counts as failed.
|
|
359
|
+
*/
|
|
360
|
+
export interface PrEnvTeardownAdapter {
|
|
361
|
+
/** The name outcomes are attributed to, as a lexicon's are. */
|
|
362
|
+
name: string;
|
|
363
|
+
/**
|
|
364
|
+
* The lexicons this adapter tears down in place of (`terraform`). Their own
|
|
365
|
+
* teardown is left out, so a lexicon with no teardown execution does not
|
|
366
|
+
* leave the environment incomplete when the adapter covers it.
|
|
367
|
+
*/
|
|
368
|
+
lexicons?: string[];
|
|
369
|
+
/** What the environment owns: candidates with their read-back markers, and what could not be read. */
|
|
370
|
+
owned(input: PrEnvTeardownAdapterInput): Promise<TeardownEnumeration>;
|
|
371
|
+
/** Delete the candidates: one outcome per candidate, keyed by name. */
|
|
372
|
+
teardown(input: PrEnvTeardownAdapterInput & { candidates: TeardownCandidate[] }): Promise<TeardownExecution>;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/** What a {@link PrEnvTeardownAdapter} is told about the environment. */
|
|
376
|
+
export interface PrEnvTeardownAdapterInput {
|
|
377
|
+
/** `pr-<n>`. */
|
|
378
|
+
environment: string;
|
|
379
|
+
/** The marker everything is selected on. */
|
|
380
|
+
marker: OwnershipMarker;
|
|
381
|
+
/** The environment as its records say it stands: the commit last applied, its components and their locations. */
|
|
382
|
+
live: LivePrEnvironment;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/** An adapter in the shape `planTeardown` and `executeTeardown` call: a lexicon with only its two teardown halves. */
|
|
386
|
+
function adapterLexicon(adapter: PrEnvTeardownAdapter, live: LivePrEnvironment): ObservationLexicon {
|
|
387
|
+
const shaped: Pick<ObservationLexicon, "name" | "teardownOwned" | "executeTeardown"> = {
|
|
388
|
+
name: adapter.name,
|
|
389
|
+
teardownOwned: (o) => adapter.owned({ environment: o.environment, marker: o.marker, live }),
|
|
390
|
+
executeTeardown: (o) => adapter.teardown({ environment: o.environment, marker: o.marker, live, candidates: o.candidates }),
|
|
391
|
+
};
|
|
392
|
+
return shaped as ObservationLexicon;
|
|
393
|
+
}
|
|
394
|
+
|
|
305
395
|
/** What {@link tearDownPrEnvironment} needs. */
|
|
306
396
|
export interface PrEnvTeardownInput {
|
|
307
397
|
pr: number;
|
|
308
398
|
live: LivePrEnvironment;
|
|
309
|
-
config:
|
|
310
|
-
|
|
399
|
+
/** The project's config, for its environments and stacks. Absent: a product with no chant.config.ts, whose adapters do the teardown. */
|
|
400
|
+
config?: ChantConfig;
|
|
401
|
+
/** The lexicons whose teardown runs. Default none. */
|
|
402
|
+
plugins?: ObservationLexicon[];
|
|
403
|
+
/** Teardowns a product supplies (#4330), run after the lexicons'. */
|
|
404
|
+
adapters?: PrEnvTeardownAdapter[];
|
|
311
405
|
warn?: (message: string) => void;
|
|
312
406
|
}
|
|
313
407
|
|
|
@@ -316,18 +410,30 @@ export interface PrEnvTeardownInput {
|
|
|
316
410
|
* before any read when {@link teardownRefusal} says so; otherwise the live
|
|
317
411
|
* resources under the record's marker are planned, every entry whose marker
|
|
318
412
|
* is not exactly `{ stack, env: "pr-<n>" }` is dropped ({@link ownedEntries}),
|
|
319
|
-
* and only the rest go to each lexicon's teardown. The endpoint
|
|
320
|
-
* the environment was applied at: its own `pr-*` entry's, else the
|
|
413
|
+
* and only the rest go to each lexicon's or adapter's teardown. The endpoint
|
|
414
|
+
* is the one the environment was applied at: its own `pr-*` entry's, else the
|
|
415
|
+
* template's. A lexicon an adapter covers (`lexicons`) is left out.
|
|
321
416
|
*/
|
|
322
417
|
export async function tearDownPrEnvironment(input: PrEnvTeardownInput): Promise<{ refusal: string } | { report: TeardownReport; dropped: number; marker: { stack: string; env: string } }> {
|
|
323
|
-
const { pr, live
|
|
418
|
+
const { pr, live } = input;
|
|
419
|
+
const config = input.config ?? {};
|
|
324
420
|
const refusal = teardownRefusal(pr, live, config.environments);
|
|
325
421
|
if (refusal) return { refusal };
|
|
422
|
+
const adapters = input.adapters ?? [];
|
|
423
|
+
const covered = new Set(adapters.flatMap((a) => a.lexicons ?? []));
|
|
424
|
+
const names = new Set<string>();
|
|
425
|
+
for (const a of adapters) {
|
|
426
|
+
if (names.has(a.name)) return { refusal: `two teardown adapters are named ${a.name}, so their outcomes could not be told apart; nothing was torn down` };
|
|
427
|
+
names.add(a.name);
|
|
428
|
+
}
|
|
429
|
+
const lexicons = (input.plugins ?? []).filter((x) => !covered.has(x.name));
|
|
430
|
+
if (lexicons.some((x) => names.has(x.name))) return { refusal: `a teardown adapter shares its name with a lexicon (${[...names].filter((n) => lexicons.some((x) => x.name === n)).join(", ")}), so their outcomes could not be told apart; nothing was torn down` };
|
|
326
431
|
const env = prEnvironmentName(pr);
|
|
327
432
|
const marker = { stack: live.marker.stack, env };
|
|
328
433
|
const endpoint = environmentEndpoint(config.environments, env) ?? environmentEndpoint(config.environments, live.template);
|
|
329
|
-
const applied = applyLiveEndpoint(endpoint ? [{ name: env, endpoint }] : [], env,
|
|
434
|
+
const applied = applyLiveEndpoint(endpoint ? [{ name: env, endpoint }] : [], env, lexicons.filter((x) => x.teardownOwned || x.describeResources || x.executeTeardown));
|
|
330
435
|
if (applied.notice) input.warn?.(applied.notice);
|
|
436
|
+
const plugins = [...lexicons, ...adapters.map((a) => adapterLexicon(a, live))];
|
|
331
437
|
const deployedStacks = (config.stacks ?? []).map((s) => ({ name: s.name, ...(s.region ? { region: s.region } : {}) }));
|
|
332
438
|
try {
|
|
333
439
|
const planned = await planTeardown({ environment: env, stack: marker.stack, plugins, ...(deployedStacks.length > 0 ? { deployedStacks } : {}) });
|
|
@@ -405,6 +511,7 @@ export function acceptHandedRecord(raw: unknown, check: HandedRecordCheck): { re
|
|
|
405
511
|
marker: { stack: r.marker.stack, env },
|
|
406
512
|
commit: check.head,
|
|
407
513
|
components: [...r.components],
|
|
514
|
+
...(handedLocations(r.locations) ? { locations: handedLocations(r.locations)! } : {}),
|
|
408
515
|
planDigest: typeof r.planDigest === "string" ? r.planDigest : "",
|
|
409
516
|
result: r.result,
|
|
410
517
|
by: check.by,
|
|
@@ -417,6 +524,21 @@ export function acceptHandedRecord(raw: unknown, check: HandedRecordCheck): { re
|
|
|
417
524
|
};
|
|
418
525
|
}
|
|
419
526
|
|
|
527
|
+
/** A handed-on report row's site, kept only as its three strings. */
|
|
528
|
+
function handedSite(raw: unknown): PrEnvReportRow["site"] | undefined {
|
|
529
|
+
if (typeof raw !== "object" || raw === null) return undefined;
|
|
530
|
+
const r = raw as Record<string, unknown>;
|
|
531
|
+
const site = Object.fromEntries((["runtime", "url", "domain"] as const).filter((k) => typeof r[k] === "string").map((k) => [k, r[k] as string]));
|
|
532
|
+
return Object.keys(site).length > 0 ? site : undefined;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/** A handed-on record's locations, kept only as a map of strings to strings. */
|
|
536
|
+
function handedLocations(raw: unknown): Record<string, string> | undefined {
|
|
537
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return undefined;
|
|
538
|
+
const out = Object.fromEntries(Object.entries(raw as Record<string, unknown>).filter((e): e is [string, string] => typeof e[1] === "string"));
|
|
539
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
540
|
+
}
|
|
541
|
+
|
|
420
542
|
/**
|
|
421
543
|
* The report `pr-env up` handed on, checked as data before the post job
|
|
422
544
|
* renders a note from it: an `up` report for this pull request's `pr-<n>`.
|
|
@@ -445,7 +567,16 @@ export function handedPrEnvReport(raw: unknown, check: { pr: number; head: strin
|
|
|
445
567
|
status: r.status,
|
|
446
568
|
...(typeof r.message === "string" ? { message: r.message } : {}),
|
|
447
569
|
...(typeof r.endpoint === "string" ? { endpoint: r.endpoint } : {}),
|
|
448
|
-
components: (Array.isArray(r.components) ? r.components : []).map((c) => ({
|
|
570
|
+
components: (Array.isArray(r.components) ? r.components : []).map((c) => ({
|
|
571
|
+
component: str(c?.component),
|
|
572
|
+
status: str(c?.status),
|
|
573
|
+
create: num(c?.create),
|
|
574
|
+
update: num(c?.update),
|
|
575
|
+
replace: num(c?.replace),
|
|
576
|
+
delete: num(c?.delete),
|
|
577
|
+
...(typeof c?.location === "string" ? { location: c.location } : {}),
|
|
578
|
+
...(handedSite(c?.site) ? { site: handedSite(c?.site)! } : {}),
|
|
579
|
+
})),
|
|
449
580
|
...(Array.isArray(r.approvedBy) ? { approvedBy: r.approvedBy.filter((a): a is string => typeof a === "string") } : {}),
|
|
450
581
|
},
|
|
451
582
|
};
|
|
@@ -453,6 +584,20 @@ export function handedPrEnvReport(raw: unknown, check: { pr: number; head: strin
|
|
|
453
584
|
|
|
454
585
|
// ── the note ───────────────────────────────────────────────────────────────
|
|
455
586
|
|
|
587
|
+
/** One component's row in the report and the note. */
|
|
588
|
+
export interface PrEnvReportRow {
|
|
589
|
+
component: string;
|
|
590
|
+
status: string;
|
|
591
|
+
create: number;
|
|
592
|
+
update: number;
|
|
593
|
+
replace: number;
|
|
594
|
+
delete: number;
|
|
595
|
+
/** Where its copy lives, as the adapter says (#4330). */
|
|
596
|
+
location?: string;
|
|
597
|
+
/** Its site in pr-<n>: the runtime, URL and domain carried over from the template's (#4330, {@link prEnvironmentSite}). */
|
|
598
|
+
site?: { runtime?: string; url?: string; domain?: string };
|
|
599
|
+
}
|
|
600
|
+
|
|
456
601
|
/** What one `pr-env up` or `down` run came to, as `pr-env.json` holds it. */
|
|
457
602
|
export interface PrEnvReport {
|
|
458
603
|
stage: "up" | "down";
|
|
@@ -467,7 +612,7 @@ export interface PrEnvReport {
|
|
|
467
612
|
status: "applied" | "nothing" | "waiting" | "changed" | "plan-failed" | "failed" | "refused" | "destroyed" | "incomplete" | "absent";
|
|
468
613
|
message?: string;
|
|
469
614
|
endpoint?: string;
|
|
470
|
-
components:
|
|
615
|
+
components: PrEnvReportRow[];
|
|
471
616
|
approvedBy?: string[];
|
|
472
617
|
expiresAt?: string;
|
|
473
618
|
outcomes?: PrEnvRecord["outcomes"];
|
|
@@ -505,8 +650,11 @@ export function renderPrEnvNote(r: PrEnvReport, approver = "<you>"): string {
|
|
|
505
650
|
}
|
|
506
651
|
if (r.message) out.push("", `> ${r.message.split("\n").join("\n> ")}`);
|
|
507
652
|
if (r.components.length > 0) {
|
|
508
|
-
|
|
509
|
-
|
|
653
|
+
// #4330: a Where column when a row has a URL or a location, as code: env-up ran the change's code, so the post job does not link it.
|
|
654
|
+
const at = (c: PrEnvReportRow): string | undefined => c.site?.url ?? c.site?.domain ?? c.location;
|
|
655
|
+
const where = r.components.some((c) => at(c));
|
|
656
|
+
out.push("", `| Component | Status | Create | Update | Replace | Delete |${where ? " Where |" : ""}`, `|---|---|---:|---:|---:|---:|${where ? "---|" : ""}`);
|
|
657
|
+
for (const c of r.components) out.push(`| ${code(c.component)} | ${c.status} | ${c.create} | ${c.update} | ${c.replace} | ${c.delete} |${where ? ` ${at(c) ? code(at(c)!) : ""} |` : ""}`);
|
|
510
658
|
}
|
|
511
659
|
if (r.digest) out.push("", `Plan digest: ${code(r.digest)}${r.approvedBy?.length ? `, approved by ${r.approvedBy.map(code).join(", ")}` : ""}`);
|
|
512
660
|
out.push("", `${code(r.env)} is not a release target: releases come from green commits.`);
|
|
@@ -89,7 +89,7 @@ import { EMPTY_TREE_SHA } from "./changed-files";
|
|
|
89
89
|
import { layerWaves } from "../gated-waves";
|
|
90
90
|
import type { DriverComponent } from "./driver";
|
|
91
91
|
import type { GatedWavePipelineOptions } from "./gated-wave-pipeline";
|
|
92
|
-
import type { PipelineCloudIdentity } from "../ci-identity";
|
|
92
|
+
import type { CloudTradeSpec, PipelineCloudIdentity } from "../ci-identity";
|
|
93
93
|
|
|
94
94
|
/** The variable `chant ci run` reads its runner key from (`RUNNER_KEY_ENV` in ../workspace/ci-green.ts). */
|
|
95
95
|
const RUNNER_KEY_ENV = "CHANT_RUNNER_KEY";
|
|
@@ -211,6 +211,15 @@ export interface PrLoopPipelineOptions {
|
|
|
211
211
|
* ({@link prEnvJobs}). Set from `--pr-env` beside `--pr-loop`. Default false.
|
|
212
212
|
*/
|
|
213
213
|
prEnvironment?: boolean;
|
|
214
|
+
/**
|
|
215
|
+
* The cloud identity a pull request environment's jobs take (#4330), from
|
|
216
|
+
* `ci.identity.cloud` (`resolvePrEnvCloudIdentity` in ../ci-identity.ts):
|
|
217
|
+
* the apply identity of a rule that names the pull request environments.
|
|
218
|
+
* env-up, env-down and env-sweep trade their OIDC token for it before the
|
|
219
|
+
* checkout, and env-up's later steps start without the token variables.
|
|
220
|
+
* Absent: they request no OIDC token.
|
|
221
|
+
*/
|
|
222
|
+
prEnvIdentity?: CloudTradeSpec;
|
|
214
223
|
}
|
|
215
224
|
|
|
216
225
|
/** One job's command and the forge values it reads, as environment variables for its step. */
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pure helpers over a loaded config's `environments` and `ownership`:
|
|
3
|
+
* names, patterns, endpoints and the ownership stack. ./config.ts re-exports
|
|
4
|
+
* them. They import nothing at runtime, so ./components/pr-environment.ts and
|
|
5
|
+
* ./live-endpoint.ts use them without bundling the config loader.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ChantConfig } from "./config";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* One project-declared environment (chant #1166). Historically always a bare
|
|
12
|
+
* name (`"floci"`); an entry can now instead carry the endpoint that
|
|
13
|
+
* environment's `--live` reads should target (`{ name: "floci", endpoint:
|
|
14
|
+
* "http://localhost:4566" }`), so a project pointed at a local emulator is
|
|
15
|
+
* self-sufficient — no ambient `AWS_ENDPOINT_URL` export required to avoid
|
|
16
|
+
* silently querying real AWS. See {@link environmentName}, {@link
|
|
17
|
+
* environmentNames}, {@link environmentEndpoint} below, and
|
|
18
|
+
* `./live-endpoint.ts`'s `applyLiveEndpoint`, the CLI-side consumer.
|
|
19
|
+
*
|
|
20
|
+
* The array's order is the member's promotion order (#4030, see
|
|
21
|
+
* `./workspace/promotion-order.ts`), and `canary: true` marks a step as the
|
|
22
|
+
* canary step of that order. The canary is declared and surfaced; chant does
|
|
23
|
+
* not yet act on it.
|
|
24
|
+
*/
|
|
25
|
+
export type EnvironmentDeclaration = string | { name: string; endpoint?: string; canary?: boolean };
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* `ownership.env`: a literal environment identity, or a reference to a
|
|
29
|
+
* declared build parameter whose resolved value becomes the identity (#1396).
|
|
30
|
+
*/
|
|
31
|
+
export type OwnershipEnv = string | { param: string };
|
|
32
|
+
|
|
33
|
+
/** The declared name of one `environments` entry, whichever form it takes. */
|
|
34
|
+
export function environmentName(entry: EnvironmentDeclaration): string {
|
|
35
|
+
return typeof entry === "string" ? entry : entry.name;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Every declared environment's name, in `environments` order. `undefined` in,
|
|
40
|
+
* `undefined` out — mirrors the field itself being optional, so a caller that
|
|
41
|
+
* already writes `config.environments?.something` can keep doing so:
|
|
42
|
+
* `environmentNames(config.environments)?.includes(name)`.
|
|
43
|
+
*/
|
|
44
|
+
export function environmentNames(environments: EnvironmentDeclaration[] | undefined): string[] | undefined {
|
|
45
|
+
return environments?.map(environmentName);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Whether a declared environment name is a glob pattern rather than a literal
|
|
50
|
+
* (#1221) — it contains at least one `*`.
|
|
51
|
+
*/
|
|
52
|
+
export function isEnvironmentPattern(declaredName: string): boolean {
|
|
53
|
+
return declaredName.includes("*");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Anchored `*`-glob match (#1221): each `*` in `pattern` matches any run of
|
|
58
|
+
* characters (possibly empty); everything else matches itself. Deliberately
|
|
59
|
+
* the whole surface — no `?`, no character classes, no regex. A pattern with
|
|
60
|
+
* no `*` degenerates to string equality.
|
|
61
|
+
*/
|
|
62
|
+
export function matchesEnvironmentPattern(pattern: string, name: string): boolean {
|
|
63
|
+
const parts = pattern.split("*");
|
|
64
|
+
if (parts.length === 1) return pattern === name;
|
|
65
|
+
const first = parts[0];
|
|
66
|
+
const last = parts[parts.length - 1];
|
|
67
|
+
if (!name.startsWith(first)) return false;
|
|
68
|
+
let cursor = first.length;
|
|
69
|
+
for (let i = 1; i < parts.length - 1; i++) {
|
|
70
|
+
const part = parts[i];
|
|
71
|
+
if (part === "") continue;
|
|
72
|
+
const at = name.indexOf(part, cursor);
|
|
73
|
+
if (at === -1) return false;
|
|
74
|
+
cursor = at + part.length;
|
|
75
|
+
}
|
|
76
|
+
return name.length - cursor >= last.length && name.endsWith(last);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Environment-membership test (#1221): is `name` covered by this project's
|
|
81
|
+
* declared `environments`? A literal entry matches by equality; an entry
|
|
82
|
+
* containing `*` matches as a glob pattern (`"pr-*"` covers `pr-42`).
|
|
83
|
+
* Literals are checked first across the whole list, then patterns — so a
|
|
84
|
+
* name that is declared outright never depends on pattern order. `undefined`
|
|
85
|
+
* / empty `environments` returns `false`; callers that treat "no declared
|
|
86
|
+
* environments" as "anything goes" (e.g. {@link unknownEnvError} in
|
|
87
|
+
* `./env.ts`) guard that case themselves.
|
|
88
|
+
*/
|
|
89
|
+
export function matchesDeclaredEnvironment(
|
|
90
|
+
environments: EnvironmentDeclaration[] | undefined,
|
|
91
|
+
name: string,
|
|
92
|
+
): boolean {
|
|
93
|
+
const names = environmentNames(environments) ?? [];
|
|
94
|
+
if (names.includes(name)) return true;
|
|
95
|
+
return names.some((declared) => isEnvironmentPattern(declared) && matchesEnvironmentPattern(declared, name));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The endpoint `name` declares (chant #1166) — `undefined` for a bare-string
|
|
100
|
+
* entry, an entry with no `endpoint` set, or a name this project doesn't
|
|
101
|
+
* declare at all. `./live-endpoint.ts`'s `applyLiveEndpoint` is the consumer:
|
|
102
|
+
* it injects this into the ambient env var each observing lexicon's CLI
|
|
103
|
+
* shell-out reads (e.g. `AWS_ENDPOINT_URL`), unless that var is already set —
|
|
104
|
+
* ambient always wins.
|
|
105
|
+
*
|
|
106
|
+
* A pattern entry (#1221) can carry an endpoint too: `{ name: "pr-*",
|
|
107
|
+
* endpoint }` supplies the endpoint for every `pr-<n>` environment. A literal
|
|
108
|
+
* entry always wins over a pattern — declaring `pr-special` alongside
|
|
109
|
+
* `pr-*` resolves `pr-special` to the literal entry's endpoint (or none).
|
|
110
|
+
*/
|
|
111
|
+
export function environmentEndpoint(environments: EnvironmentDeclaration[] | undefined, name: string): string | undefined {
|
|
112
|
+
const found =
|
|
113
|
+
environments?.find((e) => environmentName(e) === name) ??
|
|
114
|
+
environments?.find((e) => isEnvironmentPattern(environmentName(e)) && matchesEnvironmentPattern(environmentName(e), name));
|
|
115
|
+
return found && typeof found !== "string" ? found.endpoint : undefined;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Whether an `ownership.env` entry is a build-parameter reference rather than a literal. */
|
|
119
|
+
export function isOwnershipParamRef(env: OwnershipEnv | undefined): env is { param: string } {
|
|
120
|
+
return typeof env === "object" && env !== null && typeof env.param === "string";
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* The ownership stack identity alone — `ownership.stack` when marking is on
|
|
125
|
+
* (no `stack`, or `enabled: false`, means off). For callers that need the
|
|
126
|
+
* stack and nothing else (a field manager, a prune selector) and so have no
|
|
127
|
+
* build parameters to resolve `env` against.
|
|
128
|
+
*/
|
|
129
|
+
export function resolveOwnershipStack(config: Pick<ChantConfig, "ownership">): string | undefined {
|
|
130
|
+
const o = config.ownership;
|
|
131
|
+
if (!o || !o.stack || o.enabled === false) return undefined;
|
|
132
|
+
return o.stack;
|
|
133
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -14,113 +14,22 @@ import { lexiconNames, pathLexiconRoot, registerLexiconDeclarations, type Lexico
|
|
|
14
14
|
import { decideConfigSchema, type DecideConfig } from "./op/decide-config";
|
|
15
15
|
import { PublishDeclarationSchema, type PublishDeclaration } from "./lifecycle/publish-declaration";
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* `ownership.env`: a literal environment identity, or a reference to a
|
|
36
|
-
* declared build parameter whose resolved value becomes the identity (#1396).
|
|
37
|
-
*/
|
|
38
|
-
export type OwnershipEnv = string | { param: string };
|
|
39
|
-
|
|
40
|
-
/** The declared name of one `environments` entry, whichever form it takes. */
|
|
41
|
-
export function environmentName(entry: EnvironmentDeclaration): string {
|
|
42
|
-
return typeof entry === "string" ? entry : entry.name;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Every declared environment's name, in `environments` order. `undefined` in,
|
|
47
|
-
* `undefined` out — mirrors the field itself being optional, so a caller that
|
|
48
|
-
* already writes `config.environments?.something` can keep doing so:
|
|
49
|
-
* `environmentNames(config.environments)?.includes(name)`.
|
|
50
|
-
*/
|
|
51
|
-
export function environmentNames(environments: EnvironmentDeclaration[] | undefined): string[] | undefined {
|
|
52
|
-
return environments?.map(environmentName);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Whether a declared environment name is a glob pattern rather than a literal
|
|
57
|
-
* (#1221) — it contains at least one `*`.
|
|
58
|
-
*/
|
|
59
|
-
export function isEnvironmentPattern(declaredName: string): boolean {
|
|
60
|
-
return declaredName.includes("*");
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Anchored `*`-glob match (#1221): each `*` in `pattern` matches any run of
|
|
65
|
-
* characters (possibly empty); everything else matches itself. Deliberately
|
|
66
|
-
* the whole surface — no `?`, no character classes, no regex. A pattern with
|
|
67
|
-
* no `*` degenerates to string equality.
|
|
68
|
-
*/
|
|
69
|
-
export function matchesEnvironmentPattern(pattern: string, name: string): boolean {
|
|
70
|
-
const parts = pattern.split("*");
|
|
71
|
-
if (parts.length === 1) return pattern === name;
|
|
72
|
-
const first = parts[0];
|
|
73
|
-
const last = parts[parts.length - 1];
|
|
74
|
-
if (!name.startsWith(first)) return false;
|
|
75
|
-
let cursor = first.length;
|
|
76
|
-
for (let i = 1; i < parts.length - 1; i++) {
|
|
77
|
-
const part = parts[i];
|
|
78
|
-
if (part === "") continue;
|
|
79
|
-
const at = name.indexOf(part, cursor);
|
|
80
|
-
if (at === -1) return false;
|
|
81
|
-
cursor = at + part.length;
|
|
82
|
-
}
|
|
83
|
-
return name.length - cursor >= last.length && name.endsWith(last);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Environment-membership test (#1221): is `name` covered by this project's
|
|
88
|
-
* declared `environments`? A literal entry matches by equality; an entry
|
|
89
|
-
* containing `*` matches as a glob pattern (`"pr-*"` covers `pr-42`).
|
|
90
|
-
* Literals are checked first across the whole list, then patterns — so a
|
|
91
|
-
* name that is declared outright never depends on pattern order. `undefined`
|
|
92
|
-
* / empty `environments` returns `false`; callers that treat "no declared
|
|
93
|
-
* environments" as "anything goes" (e.g. {@link unknownEnvError} in
|
|
94
|
-
* `./env.ts`) guard that case themselves.
|
|
95
|
-
*/
|
|
96
|
-
export function matchesDeclaredEnvironment(
|
|
97
|
-
environments: EnvironmentDeclaration[] | undefined,
|
|
98
|
-
name: string,
|
|
99
|
-
): boolean {
|
|
100
|
-
const names = environmentNames(environments) ?? [];
|
|
101
|
-
if (names.includes(name)) return true;
|
|
102
|
-
return names.some((declared) => isEnvironmentPattern(declared) && matchesEnvironmentPattern(declared, name));
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* The endpoint `name` declares (chant #1166) — `undefined` for a bare-string
|
|
107
|
-
* entry, an entry with no `endpoint` set, or a name this project doesn't
|
|
108
|
-
* declare at all. `./live-endpoint.ts`'s `applyLiveEndpoint` is the consumer:
|
|
109
|
-
* it injects this into the ambient env var each observing lexicon's CLI
|
|
110
|
-
* shell-out reads (e.g. `AWS_ENDPOINT_URL`), unless that var is already set —
|
|
111
|
-
* ambient always wins.
|
|
112
|
-
*
|
|
113
|
-
* A pattern entry (#1221) can carry an endpoint too: `{ name: "pr-*",
|
|
114
|
-
* endpoint }` supplies the endpoint for every `pr-<n>` environment. A literal
|
|
115
|
-
* entry always wins over a pattern — declaring `pr-special` alongside
|
|
116
|
-
* `pr-*` resolves `pr-special` to the literal entry's endpoint (or none).
|
|
117
|
-
*/
|
|
118
|
-
export function environmentEndpoint(environments: EnvironmentDeclaration[] | undefined, name: string): string | undefined {
|
|
119
|
-
const found =
|
|
120
|
-
environments?.find((e) => environmentName(e) === name) ??
|
|
121
|
-
environments?.find((e) => isEnvironmentPattern(environmentName(e)) && matchesEnvironmentPattern(environmentName(e), name));
|
|
122
|
-
return found && typeof found !== "string" ? found.endpoint : undefined;
|
|
123
|
-
}
|
|
17
|
+
// The environment and ownership helpers that read a config already loaded
|
|
18
|
+
// live in ./config-environments.ts, which loads nothing, so a bundle that
|
|
19
|
+
// needs only them stays clear of the loader below (#4356 follow-up).
|
|
20
|
+
export {
|
|
21
|
+
environmentEndpoint,
|
|
22
|
+
environmentName,
|
|
23
|
+
environmentNames,
|
|
24
|
+
isEnvironmentPattern,
|
|
25
|
+
isOwnershipParamRef,
|
|
26
|
+
matchesDeclaredEnvironment,
|
|
27
|
+
matchesEnvironmentPattern,
|
|
28
|
+
resolveOwnershipStack,
|
|
29
|
+
type EnvironmentDeclaration,
|
|
30
|
+
type OwnershipEnv,
|
|
31
|
+
} from "./config-environments";
|
|
32
|
+
import { isOwnershipParamRef, resolveOwnershipStack, type EnvironmentDeclaration, type OwnershipEnv } from "./config-environments";
|
|
124
33
|
|
|
125
34
|
/**
|
|
126
35
|
* Zod schema for ChantConfig validation.
|
|
@@ -157,6 +66,10 @@ export const ChantConfigSchema = z.object({
|
|
|
157
66
|
build: z.object({
|
|
158
67
|
fold: z.boolean().optional(),
|
|
159
68
|
}).optional(),
|
|
69
|
+
// Strict like `run`: a misspelt `lint` would leave the gate on with nothing said.
|
|
70
|
+
graph: z.object({
|
|
71
|
+
lint: z.enum(["gate", "report"]).optional(),
|
|
72
|
+
}).strict().optional(),
|
|
160
73
|
buildParams: z.record(
|
|
161
74
|
z.string(),
|
|
162
75
|
z.object({
|
|
@@ -299,6 +212,23 @@ export interface ChantConfig {
|
|
|
299
212
|
/** Lint configuration (rules, extends, overrides, plugins) */
|
|
300
213
|
lint?: LintConfig;
|
|
301
214
|
|
|
215
|
+
/**
|
|
216
|
+
* How `chant graph` treats lint errors (INTENTIUS/behold#919).
|
|
217
|
+
*
|
|
218
|
+
* `"gate"` (the default) refuses to emit the entity graph for source that
|
|
219
|
+
* fails `chant lint`: the graph stands for source chant will build.
|
|
220
|
+
* `"report"` emits it anyway and carries every lint finding on the IR's
|
|
221
|
+
* `meta.lint`, for a project that only reads source it did not author. The
|
|
222
|
+
* reader project `chant workspace graph` writes for a terraform or choudoufu
|
|
223
|
+
* member sets it, and so does behold's scratch project for a bare Terraform
|
|
224
|
+
* folder: an error-severity finding there describes the estate, and the
|
|
225
|
+
* person needs the picture to look at it. A chant older than the key
|
|
226
|
+
* ignores it and gates as before.
|
|
227
|
+
*/
|
|
228
|
+
graph?: {
|
|
229
|
+
lint?: "gate" | "report";
|
|
230
|
+
};
|
|
231
|
+
|
|
302
232
|
/**
|
|
303
233
|
* Telemetry attribution (#2558, D22): lexicons that declare workloads stamp
|
|
304
234
|
* resource attributes (`chant.workspace`, `chant.member`, `chant.decl`,
|
|
@@ -764,23 +694,6 @@ function warnIfFragmentShadowsProjectConfig(configPath: string): void {
|
|
|
764
694
|
/** Top-level keys that make a config a *project* config rather than a lint fragment. */
|
|
765
695
|
const PROJECT_CONFIG_KEYS = new Set(Object.keys(ChantConfigSchema.shape).filter((k) => k !== "lint"));
|
|
766
696
|
|
|
767
|
-
/** Whether an `ownership.env` entry is a build-parameter reference rather than a literal. */
|
|
768
|
-
export function isOwnershipParamRef(env: OwnershipEnv | undefined): env is { param: string } {
|
|
769
|
-
return typeof env === "object" && env !== null && typeof env.param === "string";
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
/**
|
|
773
|
-
* The ownership stack identity alone — `ownership.stack` when marking is on
|
|
774
|
-
* (no `stack`, or `enabled: false`, means off). For callers that need the
|
|
775
|
-
* stack and nothing else (a field manager, a prune selector) and so have no
|
|
776
|
-
* build parameters to resolve `env` against.
|
|
777
|
-
*/
|
|
778
|
-
export function resolveOwnershipStack(config: ChantConfig): string | undefined {
|
|
779
|
-
const o = config.ownership;
|
|
780
|
-
if (!o || !o.stack || o.enabled === false) return undefined;
|
|
781
|
-
return o.stack;
|
|
782
|
-
}
|
|
783
|
-
|
|
784
697
|
/**
|
|
785
698
|
* Resolve the `env` half of the ownership marker (#1396). A literal is
|
|
786
699
|
* returned as-is. A `{ param }` reference is looked up in this build's resolved
|