@intentius/chant 0.39.0 → 0.41.1
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/apply.d.ts +171 -0
- package/dist/apply.d.ts.map +1 -0
- package/dist/cli/build-params-cli.d.ts +24 -0
- package/dist/cli/build-params-cli.d.ts.map +1 -1
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/codegen/naming.d.ts +48 -1
- package/dist/codegen/naming.d.ts.map +1 -1
- package/dist/codegen/validate.d.ts +31 -0
- package/dist/codegen/validate.d.ts.map +1 -1
- package/dist/components/deploy-units.d.ts +49 -0
- package/dist/components/deploy-units.d.ts.map +1 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/apply.test.ts +169 -0
- package/src/apply.ts +249 -0
- package/src/cli/build-params-cli.ts +35 -0
- package/src/cli/commands/doctor.test.ts +45 -0
- package/src/cli/commands/doctor.ts +40 -0
- package/src/cli/handlers/components.ts +29 -23
- package/src/cli/handlers/graph.test.ts +51 -1
- package/src/cli/handlers/graph.ts +16 -1
- package/src/cli/handlers/lifecycle.ts +18 -3
- package/src/codegen/naming.test.ts +129 -0
- package/src/codegen/naming.ts +72 -1
- package/src/codegen/validate.test.ts +86 -0
- package/src/codegen/validate.ts +74 -0
- package/src/components/deploy-units.test.ts +42 -0
- package/src/components/deploy-units.ts +82 -0
- package/src/config.test.ts +53 -0
- package/src/config.ts +41 -3
- package/src/discovery/index.ts +59 -0
- package/src/discovery/params-cjs-warning.test.ts +75 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deploy units — which live unit(s) a component's composition targets (#1495).
|
|
3
|
+
*
|
|
4
|
+
* `chant components status --live` (and `chant graph --live`) need to know
|
|
5
|
+
* what a component deployed in order to observe it. That answer used to be a
|
|
6
|
+
* string literal in the walk — `kind === "cfn-deploy"` — which made the whole
|
|
7
|
+
* chain CloudFormation-shaped: a component whose deploy phase is a server-side
|
|
8
|
+
* apply or a Helm upgrade contributed nothing and was skipped, not
|
|
9
|
+
* "unobserved", simply absent from the result.
|
|
10
|
+
*
|
|
11
|
+
* This module is the seam that fixes the walk (piece 1 of #1495's
|
|
12
|
+
* decomposition): a registry mapping a deploy-family step `kind` to the field
|
|
13
|
+
* naming its unit and the lexicon whose `describeStackStatus` can observe it.
|
|
14
|
+
* The registry is data, not a rule about what steps look like — a kind not
|
|
15
|
+
* listed here contributes no unit, exactly as before.
|
|
16
|
+
*/
|
|
17
|
+
import type { Phase } from "./component";
|
|
18
|
+
|
|
19
|
+
/** A deploy-family step kind that names a live unit, and who observes it. */
|
|
20
|
+
export interface DeployUnitRule {
|
|
21
|
+
/** The step `kind` (the capability's registered name). */
|
|
22
|
+
kind: string;
|
|
23
|
+
/** The step field carrying the unit's name (`stack`, `release`). */
|
|
24
|
+
field: string;
|
|
25
|
+
/** The lexicon whose `describeStackStatus` reads this kind of unit. */
|
|
26
|
+
lexicon: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The deploy-family kinds that name a unit. cfn-deploy's unit is its stack;
|
|
31
|
+
* kubectl-apply's is the ownership stack its labels carry (#1495 piece 2);
|
|
32
|
+
* helm-upgrade's is its release (#1495 piece 4). Listing a kind here is safe
|
|
33
|
+
* before its lexicon implements `describeStackStatus` — units whose lexicon
|
|
34
|
+
* has no observer are skipped by the caller, the same absent-observer path as
|
|
35
|
+
* before.
|
|
36
|
+
*/
|
|
37
|
+
export const DEPLOY_UNIT_RULES: readonly DeployUnitRule[] = [
|
|
38
|
+
{ kind: "cfn-deploy", field: "stack", lexicon: "aws" },
|
|
39
|
+
{ kind: "kubectl-apply", field: "stack", lexicon: "k8s" },
|
|
40
|
+
{ kind: "helm-upgrade", field: "release", lexicon: "helm" },
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
/** One resolved unit a component's deploy composition targets. */
|
|
44
|
+
export interface DeployUnit {
|
|
45
|
+
/** The unit's name — a stack, a release. */
|
|
46
|
+
unit: string;
|
|
47
|
+
/** The lexicon that observes this kind of unit. */
|
|
48
|
+
lexicon: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Every distinct deploy unit a component's phases target, in declaration
|
|
53
|
+
* order. A step may itself be a nested `Phase`, so the walk recurses; a
|
|
54
|
+
* resolved component carries the unit as a concrete string. Pure.
|
|
55
|
+
*/
|
|
56
|
+
export function deployUnits(deploy: Phase[]): DeployUnit[] {
|
|
57
|
+
const byKind = new Map(DEPLOY_UNIT_RULES.map((r) => [r.kind, r]));
|
|
58
|
+
const seen = new Set<string>();
|
|
59
|
+
const units: DeployUnit[] = [];
|
|
60
|
+
const walkSteps = (steps: Phase["steps"]): void => {
|
|
61
|
+
for (const step of steps) {
|
|
62
|
+
// A step may itself be a nested Phase (it carries its own `steps`). Step
|
|
63
|
+
// is open-typed (capability inputs), so discriminate structurally.
|
|
64
|
+
const nested = (step as { steps?: unknown }).steps;
|
|
65
|
+
if (Array.isArray(nested)) {
|
|
66
|
+
walkSteps(nested as Phase["steps"]);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const s = step as { kind?: string } & Record<string, unknown>;
|
|
70
|
+
const rule = s.kind ? byKind.get(s.kind) : undefined;
|
|
71
|
+
if (!rule) continue;
|
|
72
|
+
const unit = s[rule.field];
|
|
73
|
+
if (typeof unit !== "string" || unit.length === 0) continue;
|
|
74
|
+
const key = `${rule.lexicon}\0${unit}`;
|
|
75
|
+
if (seen.has(key)) continue;
|
|
76
|
+
seen.add(key);
|
|
77
|
+
units.push({ unit, lexicon: rule.lexicon });
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
for (const phase of deploy) walkSteps(phase.steps);
|
|
81
|
+
return units;
|
|
82
|
+
}
|
package/src/config.test.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, test, expect, beforeEach, afterEach } from "vitest";
|
|
2
2
|
import {
|
|
3
3
|
loadChantConfig,
|
|
4
|
+
loadChantConfigUpward,
|
|
4
5
|
DEFAULT_CHANT_CONFIG,
|
|
5
6
|
resolveAutoReleaseDisabled,
|
|
6
7
|
resolveFoldEnabled,
|
|
@@ -137,6 +138,58 @@ describe("loadChantConfig", () => {
|
|
|
137
138
|
});
|
|
138
139
|
});
|
|
139
140
|
|
|
141
|
+
// #1502 — the upward walk skips lint-scoping fragments. A `src/chant.config.json`
|
|
142
|
+
// holding only `extends`/`rules` (the examples/ convention) must not shadow the
|
|
143
|
+
// project config above it, or `chant build src` silently loses `ownership`/
|
|
144
|
+
// `buildParams` — the exact fallback #1117's walk exists to prevent.
|
|
145
|
+
describe("loadChantConfigUpward (#1502 — lint fragments do not end the walk)", () => {
|
|
146
|
+
const SRC = join(TEST_DIR, "src");
|
|
147
|
+
|
|
148
|
+
test("walks past a lint-only src/chant.config.json to the project config", async () => {
|
|
149
|
+
mkdirSync(SRC, { recursive: true });
|
|
150
|
+
writeFileSync(
|
|
151
|
+
join(SRC, "chant.config.json"),
|
|
152
|
+
JSON.stringify({ extends: ["@intentius/chant/lint/presets/strict"], rules: { COR001: "off" } }),
|
|
153
|
+
);
|
|
154
|
+
writeFileSync(
|
|
155
|
+
join(TEST_DIR, "chant.config.json"),
|
|
156
|
+
JSON.stringify({ ownership: { stack: "billing", env: "prod" } }),
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const result = await loadChantConfigUpward(SRC);
|
|
160
|
+
expect(result.config.ownership).toEqual({ stack: "billing", env: "prod" });
|
|
161
|
+
expect(result.configPath).toBe(join(TEST_DIR, "chant.config.json"));
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("a src/chant.config.json declaring any project-level key still wins in place", async () => {
|
|
165
|
+
mkdirSync(SRC, { recursive: true });
|
|
166
|
+
writeFileSync(
|
|
167
|
+
join(SRC, "chant.config.json"),
|
|
168
|
+
JSON.stringify({ ownership: { stack: "nested" }, rules: { COR001: "off" } }),
|
|
169
|
+
);
|
|
170
|
+
writeFileSync(
|
|
171
|
+
join(TEST_DIR, "chant.config.json"),
|
|
172
|
+
JSON.stringify({ ownership: { stack: "root" } }),
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const result = await loadChantConfigUpward(SRC);
|
|
176
|
+
expect(result.config.ownership?.stack).toBe("nested");
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("a fragment-only project resolves to the default config at the boundary", async () => {
|
|
180
|
+
mkdirSync(SRC, { recursive: true });
|
|
181
|
+
writeFileSync(join(TEST_DIR, "package.json"), JSON.stringify({ name: "boundary" }));
|
|
182
|
+
writeFileSync(
|
|
183
|
+
join(SRC, "chant.config.json"),
|
|
184
|
+
JSON.stringify({ extends: ["@intentius/chant/lint/presets/strict"] }),
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
const result = await loadChantConfigUpward(SRC);
|
|
188
|
+
expect(result.config).toEqual(DEFAULT_CHANT_CONFIG);
|
|
189
|
+
expect(result.configPath).toBeUndefined();
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
140
193
|
// #1166 — `environments` accepts either a bare name (unchanged) or
|
|
141
194
|
// `{ name, endpoint }`, so a declared environment can be self-sufficient for
|
|
142
195
|
// `--live` reads without an ambient AWS_ENDPOINT_URL export.
|
package/src/config.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { existsSync } from "fs";
|
|
2
|
-
import { join } from "path";
|
|
1
|
+
import { existsSync, readFileSync } from "fs";
|
|
2
|
+
import { dirname, join } from "path";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import type { LintConfig } from "./lint/config";
|
|
5
5
|
import type { OwnershipMarker } from "./ownership";
|
|
@@ -384,12 +384,50 @@ export async function loadChantConfig(dir: string): Promise<ResolvedConfig> {
|
|
|
384
384
|
* same walk `chant lint`/`chant graph` already used ({@link findProjectConfig},
|
|
385
385
|
* shared with `./lint/config.ts`'s `findProjectRoot`) — one config-discovery
|
|
386
386
|
* contract for the whole CLI.
|
|
387
|
+
*
|
|
388
|
+
* chant #1502 — a lint-scoping fragment does not end the walk. The convention
|
|
389
|
+
* of a `src/chant.config.json` holding only `extends`/`rules` (cc-aws-canonical
|
|
390
|
+
* and most of examples/) sits BETWEEN the build directory and the real
|
|
391
|
+
* `chant.config.ts`, and stopping there re-introduced the exact silent
|
|
392
|
+
* fallback this walk exists to prevent: `chant build src` resolved the
|
|
393
|
+
* fragment, found no `ownership`, and built unstamped manifests that every
|
|
394
|
+
* owned-scoped live read then withheld. A fragment is skipped, not merged —
|
|
395
|
+
* lint resolution keeps its own nearest-wins walk untouched, and a JSON
|
|
396
|
+
* config declaring any project-level key still wins where it stands.
|
|
387
397
|
*/
|
|
388
398
|
export async function loadChantConfigUpward(startDir: string): Promise<ResolvedConfig> {
|
|
389
|
-
|
|
399
|
+
let { dir, configPath } = findProjectConfig(startDir);
|
|
400
|
+
while (configPath && isLintOnlyFragment(configPath)) {
|
|
401
|
+
const parent = dirname(dir);
|
|
402
|
+
if (parent === dir) break;
|
|
403
|
+
({ dir, configPath } = findProjectConfig(parent));
|
|
404
|
+
}
|
|
390
405
|
return loadChantConfig(dir);
|
|
391
406
|
}
|
|
392
407
|
|
|
408
|
+
/**
|
|
409
|
+
* The top-level keys of `./lint/config.ts`'s `LintConfigSchema` (plus the
|
|
410
|
+
* `$schema` editor convention). A `chant.config.json` whose keys all come from
|
|
411
|
+
* this set is a lint-scoping fragment, not a project config — see
|
|
412
|
+
* {@link loadChantConfigUpward}. `chant.config.ts` is never a fragment: it is
|
|
413
|
+
* project-authored code, and inspecting it would mean evaluating it.
|
|
414
|
+
*/
|
|
415
|
+
const LINT_FRAGMENT_KEYS = new Set(["$schema", "extends", "rules", "overrides", "plugins", "policies"]);
|
|
416
|
+
|
|
417
|
+
function isLintOnlyFragment(configPath: string): boolean {
|
|
418
|
+
if (!configPath.endsWith("chant.config.json")) return false;
|
|
419
|
+
try {
|
|
420
|
+
const parsed = JSON.parse(readFileSync(configPath, "utf-8")) as unknown;
|
|
421
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return false;
|
|
422
|
+
const keys = Object.keys(parsed);
|
|
423
|
+
return keys.length > 0 && keys.every((k) => LINT_FRAGMENT_KEYS.has(k));
|
|
424
|
+
} catch {
|
|
425
|
+
// Unreadable/unparseable JSON: let loadChantConfig surface the real error
|
|
426
|
+
// in place rather than silently walking past it.
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
393
431
|
/**
|
|
394
432
|
* Resolve the ownership marker to stamp from project config, or undefined when
|
|
395
433
|
* ownership marking is off (no `stack`, or `enabled: false`).
|
package/src/discovery/index.ts
CHANGED
|
@@ -11,6 +11,64 @@ import { getProvenance } from "../provenance";
|
|
|
11
11
|
import type { BuildParamProvenance } from "../provenance";
|
|
12
12
|
import { buildParamValues } from "../build-params";
|
|
13
13
|
import { setBuildParams } from "../params";
|
|
14
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
15
|
+
import { dirname, join, parse } from "node:path";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Warn when resolved build parameters cannot reach project source (#1421).
|
|
19
|
+
*
|
|
20
|
+
* chant's core is ESM. When the project is CommonJS — `"type": "commonjs"`, or
|
|
21
|
+
* no `type` field — tsx loads project source through the CommonJS transform, so
|
|
22
|
+
* the project's `require` of `params.ts` and core's `import` of it produce two
|
|
23
|
+
* separate module records. {@link setBuildParams} mutates one object in place;
|
|
24
|
+
* project source reads the other, and sees `{}`.
|
|
25
|
+
*
|
|
26
|
+
* The failure is silence. chant prints `[param] tier = "prod" (cli)` and then
|
|
27
|
+
* emits the graph for the default branch. `chant graph` is always affected
|
|
28
|
+
* because it always takes the run path; `chant build --no-fold` likewise. Plain
|
|
29
|
+
* `chant build` usually escapes because folding substitutes parameters
|
|
30
|
+
* statically — but a file that falls back to run inside a folded build is wrong
|
|
31
|
+
* the same way, which is why this warns regardless of `fold`.
|
|
32
|
+
*
|
|
33
|
+
* Only fires when parameters were actually resolved, so a CJS project that uses
|
|
34
|
+
* none is never nagged. `chant doctor`'s `package-type-module` check is the
|
|
35
|
+
* ambient version of the same advice.
|
|
36
|
+
*
|
|
37
|
+
* Best-effort and never throws: a project whose `package.json` cannot be found
|
|
38
|
+
* or parsed gets no warning rather than a failed build.
|
|
39
|
+
*/
|
|
40
|
+
function warnIfParamsCannotReachProject(path: string, values: Record<string, unknown>): void {
|
|
41
|
+
if (Object.keys(values).length === 0) return;
|
|
42
|
+
try {
|
|
43
|
+
const pkgPath = findPackageJsonUpward(path);
|
|
44
|
+
if (!pkgPath) return;
|
|
45
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8")) as { type?: string };
|
|
46
|
+
if (pkg.type === "module") return;
|
|
47
|
+
const found = pkg.type ? `"type": "${pkg.type}"` : "no `type` field";
|
|
48
|
+
const names = Object.keys(values).sort().join(", ");
|
|
49
|
+
console.error(
|
|
50
|
+
`warning: ${pkgPath} has ${found}, but chant is ESM — build parameters (${names}) ` +
|
|
51
|
+
`will read as empty in project source on the run path, so declarations conditioned ` +
|
|
52
|
+
`on them take their default branch. Set "type": "module". (chant #1421)`,
|
|
53
|
+
);
|
|
54
|
+
} catch {
|
|
55
|
+
// Unreadable or unparseable package.json — say nothing rather than fail.
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Nearest `package.json` at or above `startDir`, or undefined. */
|
|
60
|
+
function findPackageJsonUpward(startDir: string): string | undefined {
|
|
61
|
+
let dir = startDir;
|
|
62
|
+
const { root } = parse(dir);
|
|
63
|
+
for (;;) {
|
|
64
|
+
const candidate = join(dir, "package.json");
|
|
65
|
+
if (existsSync(candidate)) return candidate;
|
|
66
|
+
if (dir === root) return undefined;
|
|
67
|
+
const parent = dirname(dir);
|
|
68
|
+
if (parent === dir) return undefined;
|
|
69
|
+
dir = parent;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
14
72
|
|
|
15
73
|
/**
|
|
16
74
|
* Per-file fold-vs-run outcome (chant #1022, epic #1019), populated only
|
|
@@ -150,6 +208,7 @@ export async function discover(path: string, options?: DiscoveryOptions): Promis
|
|
|
150
208
|
// `--watch`) never leaks into a build that supplied none.
|
|
151
209
|
const buildParamValuesMap = buildParamValues(options?.buildParams ?? []);
|
|
152
210
|
setBuildParams(buildParamValuesMap);
|
|
211
|
+
warnIfParamsCannotReachProject(path, buildParamValuesMap);
|
|
153
212
|
|
|
154
213
|
// Step 1: Scan for TypeScript files
|
|
155
214
|
const files = await findInfraFiles(path);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { describe, test, expect, vi, afterEach } from "vitest";
|
|
2
|
+
import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { discover } from "./index";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* #1421 — chant's core is ESM. A CommonJS project's `require` of `params.ts`
|
|
9
|
+
* and core's `import` of it are two module records, so `setBuildParams`'s
|
|
10
|
+
* in-place mutation never reaches project source: it reads `{}` and every
|
|
11
|
+
* declaration conditioned on a parameter takes its default branch, silently.
|
|
12
|
+
*
|
|
13
|
+
* The fix is to stop it being silent. These assert the warning fires exactly
|
|
14
|
+
* when the hazard exists and stays quiet otherwise.
|
|
15
|
+
*/
|
|
16
|
+
describe("build params that cannot reach a CommonJS project (#1421)", () => {
|
|
17
|
+
const dirs: string[] = [];
|
|
18
|
+
const project = (type: string | undefined): string => {
|
|
19
|
+
const dir = mkdtempSync(join(tmpdir(), "chant-1421-"));
|
|
20
|
+
dirs.push(dir);
|
|
21
|
+
writeFileSync(
|
|
22
|
+
join(dir, "package.json"),
|
|
23
|
+
JSON.stringify(type === undefined ? { name: "p" } : { name: "p", type }),
|
|
24
|
+
);
|
|
25
|
+
mkdirSync(join(dir, "src"));
|
|
26
|
+
writeFileSync(join(dir, "src", "main.ts"), "export const x = 1;\n");
|
|
27
|
+
return dir;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
for (const d of dirs.splice(0)) rmSync(d, { recursive: true, force: true });
|
|
32
|
+
vi.restoreAllMocks();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const warnings = async (dir: string, params: Array<{ name: string; value: unknown }>): Promise<string[]> => {
|
|
36
|
+
const seen: string[] = [];
|
|
37
|
+
vi.spyOn(console, "error").mockImplementation((...a: unknown[]) => void seen.push(a.join(" ")));
|
|
38
|
+
await discover(join(dir, "src"), {
|
|
39
|
+
buildParams: params as never,
|
|
40
|
+
});
|
|
41
|
+
return seen.filter((s) => s.includes("#1421"));
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
test('warns for "type": "commonjs" when parameters were resolved', async () => {
|
|
45
|
+
const out = await warnings(project("commonjs"), [{ name: "tier", value: "prod" }]);
|
|
46
|
+
expect(out).toHaveLength(1);
|
|
47
|
+
expect(out[0]).toMatch(/"type": "commonjs"/);
|
|
48
|
+
expect(out[0]).toMatch(/tier/);
|
|
49
|
+
expect(out[0]).toMatch(/Set "type": "module"/);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// The sneakier half: no `type` field at all is also CommonJS.
|
|
53
|
+
test("warns when package.json declares no type at all", async () => {
|
|
54
|
+
const out = await warnings(project(undefined), [{ name: "tier", value: "prod" }]);
|
|
55
|
+
expect(out).toHaveLength(1);
|
|
56
|
+
expect(out[0]).toMatch(/no `type` field/);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('stays quiet for "type": "module"', async () => {
|
|
60
|
+
expect(await warnings(project("module"), [{ name: "tier", value: "prod" }])).toEqual([]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// A CJS project using no parameters is not at risk, and must not be nagged.
|
|
64
|
+
test("stays quiet when no parameters were resolved", async () => {
|
|
65
|
+
expect(await warnings(project("commonjs"), [])).toEqual([]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("names every resolved parameter, sorted", async () => {
|
|
69
|
+
const out = await warnings(project("commonjs"), [
|
|
70
|
+
{ name: "zone", value: "b" },
|
|
71
|
+
{ name: "tier", value: "prod" },
|
|
72
|
+
]);
|
|
73
|
+
expect(out[0]).toMatch(/\(tier, zone\)/);
|
|
74
|
+
});
|
|
75
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -49,6 +49,7 @@ export * from "./import/parser";
|
|
|
49
49
|
export * from "./import/generator";
|
|
50
50
|
export * from "./lexicon";
|
|
51
51
|
export * from "./observation";
|
|
52
|
+
export * from "./apply";
|
|
52
53
|
export * from "./deep-observation";
|
|
53
54
|
export * from "./owner-chain";
|
|
54
55
|
export * from "./lexicon-integrity";
|