@intentius/chant 0.44.13 → 0.45.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.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/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.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/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/surface-snapshot.d.ts +27 -3
- package/dist/codegen/surface-snapshot.d.ts.map +1 -1
- package/dist/codegen/validate.d.ts +11 -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 +57 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/discovery/fold-import.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/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/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/types.d.ts +7 -0
- package/dist/lifecycle/types.d.ts.map +1 -1
- package/dist/lint/policy.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/index.d.ts +1 -1
- package/dist/op/index.d.ts.map +1 -1
- package/dist/yaml.d.ts +0 -8
- package/dist/yaml.d.ts.map +1 -1
- package/package.json +1 -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 +144 -5
- package/src/cli/commands/build.ts +66 -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 +1 -1
- package/src/cli/commands/check-lexicon.ts +19 -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 +1 -1
- 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 +22 -2
- package/src/cli/handlers/lifecycle.test.ts +117 -0
- package/src/cli/handlers/lifecycle.ts +16 -0
- 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 +55 -6
- package/src/cli/main.ts +5 -3
- 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 +3 -1
- 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 +16 -3
- package/src/codegen/validate.ts +17 -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 +56 -1
- package/src/config.ts +152 -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/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/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/observe.ts +29 -2
- package/src/lifecycle/replay.ts +3 -0
- package/src/lifecycle/snapshot.ts +3 -0
- package/src/lifecycle/types.ts +7 -0
- package/src/lint/policy.ts +14 -3
- 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-exports.test.ts +60 -0
- package/src/op/index.ts +1 -1
- package/src/ownership.test.ts +63 -1
- package/src/yaml.test.ts +15 -0
- package/src/yaml.ts +19 -2
package/src/observation.ts
CHANGED
|
@@ -112,6 +112,24 @@ export interface ObservationResult {
|
|
|
112
112
|
* namespace, endpoint or region was read, not the one the resource lives in.
|
|
113
113
|
*/
|
|
114
114
|
queried?: Record<string, string>;
|
|
115
|
+
/**
|
|
116
|
+
* Notices about the read as a whole, not about any one entity (#1265) —
|
|
117
|
+
* "the ownership filter could not be applied on this surface" is the
|
|
118
|
+
* canonical one. A note is a property of the environment or the read path,
|
|
119
|
+
* so core says each distinct note once per run, after the answer, however
|
|
120
|
+
* many stacks or lexicons reported it. A lexicon returns it here instead of
|
|
121
|
+
* printing, so the note cannot land ahead of the rows it qualifies.
|
|
122
|
+
*/
|
|
123
|
+
notes?: string[];
|
|
124
|
+
/**
|
|
125
|
+
* What the deployable unit publishes, keyed by the stack that publishes it
|
|
126
|
+
* (#1279) — a CloudFormation stack's outputs, for instance. Stack-level, so it
|
|
127
|
+
* lives here once rather than on every resource: a node's `attributes` are
|
|
128
|
+
* that resource's own properties, and a VPC that carried the stack's
|
|
129
|
+
* `expWebIp` beside no `CidrBlock` of its own was answering the wrong
|
|
130
|
+
* question. Values are already scrubbed of anything that looks secret.
|
|
131
|
+
*/
|
|
132
|
+
stackExports?: Record<string, Record<string, unknown>>;
|
|
115
133
|
}
|
|
116
134
|
|
|
117
135
|
/**
|
|
@@ -126,6 +144,10 @@ export interface NormalizedObservation {
|
|
|
126
144
|
unobserved: Record<string, UnobservedEntity>;
|
|
127
145
|
/** Resolved query address per entity name (#1620). Empty when the lexicon reported none. */
|
|
128
146
|
queried: Record<string, string>;
|
|
147
|
+
/** Run-level notices (#1265), distinct. Empty when the lexicon reported none. */
|
|
148
|
+
notes: string[];
|
|
149
|
+
/** Per-stack exports (#1279), keyed by stack name. Absent when the lexicon reported none. */
|
|
150
|
+
stackExports?: Record<string, Record<string, unknown>>;
|
|
129
151
|
}
|
|
130
152
|
|
|
131
153
|
/** True when `value` is the versioned {@link ObservationResult} envelope. */
|
|
@@ -145,12 +167,16 @@ export function observation(
|
|
|
145
167
|
resources: Record<string, ResourceMetadata>,
|
|
146
168
|
unobserved?: Record<string, UnobservedEntity>,
|
|
147
169
|
queried?: Record<string, string>,
|
|
170
|
+
notes?: string[],
|
|
171
|
+
stackExports?: Record<string, Record<string, unknown>>,
|
|
148
172
|
): ObservationResult {
|
|
149
173
|
return {
|
|
150
174
|
observation: "v1",
|
|
151
175
|
resources,
|
|
152
176
|
...(unobserved && Object.keys(unobserved).length > 0 ? { unobserved } : {}),
|
|
153
177
|
...(queried && Object.keys(queried).length > 0 ? { queried } : {}),
|
|
178
|
+
...(notes && notes.length > 0 ? { notes } : {}),
|
|
179
|
+
...(stackExports && Object.keys(stackExports).length > 0 ? { stackExports } : {}),
|
|
154
180
|
};
|
|
155
181
|
}
|
|
156
182
|
|
|
@@ -161,11 +187,17 @@ export function observation(
|
|
|
161
187
|
* {@link unobservedAll} rather than returning nothing.
|
|
162
188
|
*/
|
|
163
189
|
export function normalizeObservation(value: DescribeResourcesResult | undefined): NormalizedObservation {
|
|
164
|
-
if (!value) return { resources: {}, unobserved: {}, queried: {} };
|
|
190
|
+
if (!value) return { resources: {}, unobserved: {}, queried: {}, notes: [] };
|
|
165
191
|
if (isObservationResult(value)) {
|
|
166
|
-
return {
|
|
192
|
+
return {
|
|
193
|
+
resources: value.resources ?? {},
|
|
194
|
+
unobserved: value.unobserved ?? {},
|
|
195
|
+
queried: value.queried ?? {},
|
|
196
|
+
notes: [...new Set(value.notes ?? [])],
|
|
197
|
+
...(value.stackExports && Object.keys(value.stackExports).length > 0 ? { stackExports: value.stackExports } : {}),
|
|
198
|
+
};
|
|
167
199
|
}
|
|
168
|
-
return { resources: value, unobserved: {}, queried: {} };
|
|
200
|
+
return { resources: value, unobserved: {}, queried: {}, notes: [] };
|
|
169
201
|
}
|
|
170
202
|
|
|
171
203
|
/**
|
|
@@ -205,15 +237,27 @@ export function mergeObservations(parts: Iterable<NormalizedObservation>): Norma
|
|
|
205
237
|
const resources: Record<string, ResourceMetadata> = {};
|
|
206
238
|
const unobserved: Record<string, UnobservedEntity> = {};
|
|
207
239
|
const queried: Record<string, string> = {};
|
|
240
|
+
// A note is about the read, not a stack; four stacks saying the same thing
|
|
241
|
+
// is one note (#1265).
|
|
242
|
+
const notes = new Set<string>();
|
|
243
|
+
const stackExports: Record<string, Record<string, unknown>> = {};
|
|
208
244
|
for (const part of parts) {
|
|
209
245
|
Object.assign(resources, part.resources);
|
|
210
246
|
Object.assign(unobserved, part.unobserved);
|
|
211
247
|
Object.assign(queried, part.queried);
|
|
248
|
+
for (const n of part.notes) notes.add(n);
|
|
249
|
+
Object.assign(stackExports, part.stackExports ?? {});
|
|
212
250
|
}
|
|
213
251
|
// Present wins: a stack that could not be read does not un-observe a resource
|
|
214
252
|
// another stack returned.
|
|
215
253
|
for (const name of Object.keys(resources)) delete unobserved[name];
|
|
216
|
-
return {
|
|
254
|
+
return {
|
|
255
|
+
resources,
|
|
256
|
+
unobserved,
|
|
257
|
+
queried,
|
|
258
|
+
notes: [...notes],
|
|
259
|
+
...(Object.keys(stackExports).length > 0 ? { stackExports } : {}),
|
|
260
|
+
};
|
|
217
261
|
}
|
|
218
262
|
|
|
219
263
|
/** One-line human phrasing of a reason, for CLI output. */
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, test, expect } from "vitest";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import * as opIndex from "./index";
|
|
5
|
+
import * as builders from "./builders";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* chant #1715 — `op/index.ts` re-exports the step builders from `./builders`
|
|
9
|
+
* by name, in a hand-maintained list of forty-odd identifiers. `waitForReady`
|
|
10
|
+
* — the CRD-aware readiness builder #365 added, with its own readiness spec,
|
|
11
|
+
* kstatus default and per-kind overrides — was defined in `builders.ts` and
|
|
12
|
+
* missing from that list, so no project could reach it from the day it landed.
|
|
13
|
+
* The activity behind it had been registered in the k8s lexicon the whole
|
|
14
|
+
* time; nothing noticed because every readiness wait in the repo predates it.
|
|
15
|
+
*
|
|
16
|
+
* The list drifting from the thing it lists is the bug, not the one missing
|
|
17
|
+
* name. Same shape as #1347's assertion over the `LexiconPlugin` optional-member
|
|
18
|
+
* table.
|
|
19
|
+
*/
|
|
20
|
+
describe("op builders are all reachable (#1715)", () => {
|
|
21
|
+
test("every value exported from builders.ts is re-exported from op/index.ts", () => {
|
|
22
|
+
const missing = Object.keys(builders).filter((name) => !(name in opIndex));
|
|
23
|
+
expect(
|
|
24
|
+
missing,
|
|
25
|
+
`builders.ts exports these, and op/index.ts does not re-export them — add them to the ` +
|
|
26
|
+
`export list at the top of op/index.ts, or a project cannot use them:\n ${missing.join("\n ")}`,
|
|
27
|
+
).toEqual([]);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("the temporal lexicon re-exports the same set", () => {
|
|
31
|
+
// The lexicon is what a project actually imports from
|
|
32
|
+
// (`@intentius/chant-lexicon-temporal`), and it re-states the list a third
|
|
33
|
+
// time rather than re-exporting wholesale — so it can drift independently.
|
|
34
|
+
const src = readFileSync(
|
|
35
|
+
join(import.meta.dirname, "../../../../lexicons/temporal/src/index.ts"),
|
|
36
|
+
"utf-8",
|
|
37
|
+
);
|
|
38
|
+
const block = src.match(/export \{([^}]*)\} from "@intentius\/chant\/op";/)?.[1] ?? "";
|
|
39
|
+
const reExported = new Set(
|
|
40
|
+
block.split(",").map((s) => s.trim()).filter(Boolean),
|
|
41
|
+
);
|
|
42
|
+
// The sprites family reaches projects through the fly lexicon, not this
|
|
43
|
+
// one. Named rather than prefix-matched, because `listCheckpoints` is one
|
|
44
|
+
// of them and does not look like one — which this test caught on its
|
|
45
|
+
// first run.
|
|
46
|
+
const FLY_SURFACE = new Set([
|
|
47
|
+
"spriteCreate", "spriteExec", "spriteCheckpoint", "spriteRestore",
|
|
48
|
+
"listCheckpoints", "spriteDestroy", "spriteWriteFile", "spriteReadFile",
|
|
49
|
+
"spriteListDir", "spriteRemove", "spriteApplyNetworkPolicy",
|
|
50
|
+
"spriteApplyServices", "spriteTaskCreate", "spriteTaskRefresh",
|
|
51
|
+
"spriteTaskRelease", "spritesUp", "spritesDown",
|
|
52
|
+
]);
|
|
53
|
+
const expected = Object.keys(builders).filter((n) => !FLY_SURFACE.has(n));
|
|
54
|
+
const missing = expected.filter((name) => !reExported.has(name));
|
|
55
|
+
expect(
|
|
56
|
+
missing,
|
|
57
|
+
`lexicons/temporal/src/index.ts does not re-export these builders:\n ${missing.join("\n ")}`,
|
|
58
|
+
).toEqual([]);
|
|
59
|
+
});
|
|
60
|
+
});
|
package/src/op/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { Op, phase, activity, gate, build, kubectlApply, helmInstall, waitForStack,
|
|
1
|
+
export { Op, phase, activity, gate, build, kubectlApply, helmInstall, waitForStack, waitForReady,
|
|
2
2
|
gitlabPipeline, lifecycleSnapshot, shell, teardown, k3dUp, k3dDown, flociUp, flociDown,
|
|
3
3
|
flociAzUp, flociAzDown, flociGcpUp, flociGcpDown, httpCheck,
|
|
4
4
|
azGroupEnsure, azGroupDelete, azApply, azDelete, awsApply, awsDelete, gcpApply, gcpDelete, policyGate,
|
package/src/ownership.test.ts
CHANGED
|
@@ -9,7 +9,12 @@ import {
|
|
|
9
9
|
OWNERSHIP_MANAGED_BY_VALUE,
|
|
10
10
|
type ChannelKeys,
|
|
11
11
|
} from "./ownership";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
resolveOwnershipMarker,
|
|
14
|
+
resolveOwnershipEnv,
|
|
15
|
+
resolveOwnershipStack,
|
|
16
|
+
ownershipEnvDisagreement,
|
|
17
|
+
} from "./config";
|
|
13
18
|
|
|
14
19
|
// A stand-in for a lexicon-provided convention (e.g. AWS's colon keys), to
|
|
15
20
|
// prove the core helpers are generic over any `ChannelKeys` — the per-provider
|
|
@@ -105,3 +110,60 @@ describe("resolveOwnershipMarker (config opt-in)", () => {
|
|
|
105
110
|
expect(resolveOwnershipMarker({ ownership: { stack: "billing", enabled: false } })).toBeUndefined();
|
|
106
111
|
});
|
|
107
112
|
});
|
|
113
|
+
|
|
114
|
+
describe("ownership.env as a build-parameter reference (#1396)", () => {
|
|
115
|
+
const config = {
|
|
116
|
+
ownership: { stack: "fountain", env: { param: "env" } },
|
|
117
|
+
buildParams: { env: { type: "string" as const, default: "dev", env: "FOUNTAIN_ENV" } },
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
test("takes the resolved parameter value — --param env=prod drives the marker", () => {
|
|
121
|
+
expect(resolveOwnershipMarker(config, [{ name: "env", value: "prod", source: "cli" }])).toEqual({
|
|
122
|
+
stack: "fountain",
|
|
123
|
+
env: "prod",
|
|
124
|
+
});
|
|
125
|
+
expect(resolveOwnershipMarker(config, [{ name: "env", value: "dev", source: "default" }])).toEqual({
|
|
126
|
+
stack: "fountain",
|
|
127
|
+
env: "dev",
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("a reference to an undeclared parameter is an error, not an env-less marker", () => {
|
|
132
|
+
expect(() => resolveOwnershipMarker({ ownership: { stack: "s", env: { param: "env" } } }, [])).toThrow(
|
|
133
|
+
/does not declare/,
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("a reference to a parameter that resolved to nothing is an error", () => {
|
|
138
|
+
expect(() => resolveOwnershipMarker(config, [])).toThrow(
|
|
139
|
+
/resolved to no value.*--param env=<value>.*FOUNTAIN_ENV/,
|
|
140
|
+
);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("the stack-only resolver needs no parameters", () => {
|
|
144
|
+
expect(resolveOwnershipStack(config)).toBe("fountain");
|
|
145
|
+
expect(resolveOwnershipStack({ ownership: { stack: "s", enabled: false } })).toBeUndefined();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("a literal env is returned untouched, with or without parameters", () => {
|
|
149
|
+
expect(resolveOwnershipEnv({ ownership: { stack: "s", env: "prod" } }, undefined)).toBe("prod");
|
|
150
|
+
expect(resolveOwnershipEnv({ ownership: { stack: "s" } }, [])).toBeUndefined();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("a literal env disagreeing with an env parameter is reported", () => {
|
|
154
|
+
const literal = {
|
|
155
|
+
ownership: { stack: "fountain", env: "dev" },
|
|
156
|
+
buildParams: { env: { type: "string" as const, default: "dev" } },
|
|
157
|
+
};
|
|
158
|
+
expect(ownershipEnvDisagreement(literal, [{ name: "env", value: "dev", source: "default" }])).toBeUndefined();
|
|
159
|
+
expect(ownershipEnvDisagreement(literal, [{ name: "env", value: "prod", source: "cli" }])).toMatch(
|
|
160
|
+
/ownership\.env is "dev" but the env build parameter resolved to "prod" \(cli\)/,
|
|
161
|
+
);
|
|
162
|
+
// A reference never disagrees — there is one source.
|
|
163
|
+
expect(ownershipEnvDisagreement(config, [{ name: "env", value: "prod", source: "cli" }])).toBeUndefined();
|
|
164
|
+
// Marking off — nothing is stamped, nothing to disagree with.
|
|
165
|
+
expect(
|
|
166
|
+
ownershipEnvDisagreement({ ownership: { env: "dev" } }, [{ name: "env", value: "prod", source: "cli" }]),
|
|
167
|
+
).toBeUndefined();
|
|
168
|
+
});
|
|
169
|
+
});
|
package/src/yaml.test.ts
CHANGED
|
@@ -10,6 +10,21 @@ describe("emitYAML", () => {
|
|
|
10
10
|
expect(emitYAML(undefined, 0)).toBe("null");
|
|
11
11
|
});
|
|
12
12
|
|
|
13
|
+
test("an undefined object value drops the key, an explicit null keeps it (#1371)", () => {
|
|
14
|
+
// Matches `JSON.stringify`: `{ a: undefined }` is `{}`; an unset optional
|
|
15
|
+
// build parameter passed straight into a resource must not ship as
|
|
16
|
+
// `a: null`.
|
|
17
|
+
expect(emitYAML({ a: undefined, b: 1 }, 0)).toBe("\nb: 1");
|
|
18
|
+
expect(emitYAML({ a: null, b: 1 }, 0)).toBe("\na: null\nb: 1");
|
|
19
|
+
expect(emitYAML({ a: undefined }, 0)).toBe("{}");
|
|
20
|
+
expect(emitYAML({ spec: { baseImageArn: undefined, name: "x" } }, 0)).toBe("\nspec:\n name: x");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("an undefined inside an array-item object drops the key; a bare undefined element is null", () => {
|
|
24
|
+
expect(emitYAML([{ a: undefined, b: 1 }, { c: undefined, d: 2, e: 3 }], 0)).toBe("\n- b: 1\n- d: 2\n e: 3");
|
|
25
|
+
expect(emitYAML([undefined, 1], 0)).toBe("\n- null\n- 1");
|
|
26
|
+
});
|
|
27
|
+
|
|
13
28
|
test("booleans", () => {
|
|
14
29
|
expect(emitYAML(true, 0)).toBe("true");
|
|
15
30
|
expect(emitYAML(false, 0)).toBe("false");
|
package/src/yaml.ts
CHANGED
|
@@ -19,9 +19,26 @@
|
|
|
19
19
|
* with `\n` so the caller can append it after a key.
|
|
20
20
|
* - Tagged values `{ tag, value }` emit `!tag [...]` or `!tag scalar`.
|
|
21
21
|
*/
|
|
22
|
+
/**
|
|
23
|
+
* An object's entries minus those whose value is `undefined`. An `undefined`
|
|
24
|
+
* property is "not supplied" in TypeScript (a declared-but-unset optional
|
|
25
|
+
* build parameter, `{ x: cond ? v : undefined }`), which `JSON.stringify`
|
|
26
|
+
* already omits; the YAML emitter must agree, or the same source ships
|
|
27
|
+
* `key: null` under `-o out.yaml` and no key at all under `-o out.json`. An
|
|
28
|
+
* explicit `null` is a value and is kept (chant #1371).
|
|
29
|
+
*/
|
|
30
|
+
function definedEntries(obj: Record<string, unknown>): [string, unknown][] {
|
|
31
|
+
return Object.entries(obj).filter(([, val]) => val !== undefined);
|
|
32
|
+
}
|
|
33
|
+
|
|
22
34
|
export function emitYAML(value: unknown, indent: number): string {
|
|
23
35
|
const prefix = " ".repeat(indent);
|
|
24
36
|
|
|
37
|
+
// `undefined` only reaches here as a top-level value or an array element —
|
|
38
|
+
// an object entry whose value is `undefined` is dropped below, the same way
|
|
39
|
+
// `JSON.stringify` omits it. A bare `undefined` renders as `null` for the
|
|
40
|
+
// same reason `JSON.stringify([undefined])` is `[null]`: YAML has no way to
|
|
41
|
+
// say "absent" in a sequence slot.
|
|
25
42
|
if (value === null || value === undefined) {
|
|
26
43
|
return "null";
|
|
27
44
|
}
|
|
@@ -74,7 +91,7 @@ export function emitYAML(value: unknown, indent: number): string {
|
|
|
74
91
|
for (const item of value) {
|
|
75
92
|
if (typeof item === "object" && item !== null && !Array.isArray(item)) {
|
|
76
93
|
// Object items in arrays
|
|
77
|
-
const entries =
|
|
94
|
+
const entries = definedEntries(item as Record<string, unknown>);
|
|
78
95
|
if (entries.length > 0) {
|
|
79
96
|
const [firstKey, firstVal] = entries[0];
|
|
80
97
|
const firstEmitted = emitYAML(firstVal, indent + 2);
|
|
@@ -111,7 +128,7 @@ export function emitYAML(value: unknown, indent: number): string {
|
|
|
111
128
|
return `${obj.tag} ${emitYAML(obj.value, indent)}`;
|
|
112
129
|
}
|
|
113
130
|
|
|
114
|
-
const entries =
|
|
131
|
+
const entries = definedEntries(obj);
|
|
115
132
|
if (entries.length === 0) return "{}";
|
|
116
133
|
const lines: string[] = [];
|
|
117
134
|
for (const [key, val] of entries) {
|