@intentius/chant 0.44.14 → 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/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.ts +101 -7
- 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/ownership.test.ts +63 -1
- package/src/yaml.test.ts +15 -0
- package/src/yaml.ts +19 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, test, expect, beforeAll, afterAll } from "vitest";
|
|
2
|
-
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
2
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, symlinkSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { checkExamplesBuild } from "./check-lexicon-examples";
|
|
@@ -15,7 +15,10 @@ import { checkExamplesBuild } from "./check-lexicon-examples";
|
|
|
15
15
|
* exactly what each example's own `npm run build` does.
|
|
16
16
|
*
|
|
17
17
|
* These fixtures import the real, already-installed `@intentius/chant-lexicon-aws`
|
|
18
|
-
* package (workspace-linked), so no lexicon plugin needs to be faked.
|
|
18
|
+
* package (workspace-linked), so no lexicon plugin needs to be faked. The
|
|
19
|
+
* temp lexicon dir gets the repo's node_modules linked in so that bare
|
|
20
|
+
* specifier resolves from the fixture the way it does from a real lexicon
|
|
21
|
+
* (Vitest 4's module runner resolves from the importing file, not the root).
|
|
19
22
|
*/
|
|
20
23
|
|
|
21
24
|
function writeLexiconDirWithExample(
|
|
@@ -35,18 +38,43 @@ describe("checkExamplesBuild", () => {
|
|
|
35
38
|
|
|
36
39
|
beforeAll(() => {
|
|
37
40
|
dir = mkdtempSync(join(tmpdir(), "chant-check-lexicon-examples-"));
|
|
41
|
+
symlinkSync(join(__dirname, "..", "..", "..", "..", "..", "node_modules"), join(dir, "node_modules"), "dir");
|
|
38
42
|
});
|
|
39
43
|
afterAll(() => rmSync(dir, { recursive: true, force: true }));
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
// A bucket that satisfies the aws lexicon's error-severity checks (#1400):
|
|
46
|
+
// public access blocked (WAW018) and a TLS-only bucket policy (WAW042).
|
|
47
|
+
const cleanBucketSource = `import { Bucket, PublicAccessBlockConfiguration, S3BucketPolicy, Ref } from "@intentius/chant-lexicon-aws";
|
|
44
48
|
|
|
45
49
|
export const appBucket = new Bucket({
|
|
46
50
|
BucketName: "my-app-bucket",
|
|
51
|
+
PublicAccessBlockConfiguration: new PublicAccessBlockConfiguration({
|
|
52
|
+
BlockPublicAcls: true,
|
|
53
|
+
BlockPublicPolicy: true,
|
|
54
|
+
IgnorePublicAcls: true,
|
|
55
|
+
RestrictPublicBuckets: true,
|
|
56
|
+
}),
|
|
47
57
|
});
|
|
48
|
-
|
|
49
|
-
|
|
58
|
+
|
|
59
|
+
export const appBucketPolicy = new S3BucketPolicy({
|
|
60
|
+
Bucket: Ref(appBucket),
|
|
61
|
+
PolicyDocument: {
|
|
62
|
+
Version: "2012-10-17",
|
|
63
|
+
Statement: [
|
|
64
|
+
{
|
|
65
|
+
Effect: "Deny",
|
|
66
|
+
Principal: "*",
|
|
67
|
+
Action: "s3:*",
|
|
68
|
+
Resource: [appBucket.Arn],
|
|
69
|
+
Condition: { Bool: { "aws:SecureTransport": "false" } },
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
`;
|
|
75
|
+
|
|
76
|
+
test("a clean example builds", async () => {
|
|
77
|
+
writeLexiconDirWithExample(dir, "clean", { "bucket.ts": cleanBucketSource });
|
|
50
78
|
|
|
51
79
|
const results = await checkExamplesBuild(dir);
|
|
52
80
|
const clean = results.find((r) => r.example === "clean");
|
|
@@ -75,6 +103,44 @@ export const dataBucket = new Bucket({
|
|
|
75
103
|
expect(broken?.detail).toMatch(/Duplicate export name "dataBucket" found/);
|
|
76
104
|
});
|
|
77
105
|
|
|
106
|
+
// chant #1400 — a bare bucket serializes fine, which is all #1067 asked,
|
|
107
|
+
// but fails the aws lexicon's own WAW018/WAW042 at error severity. This is
|
|
108
|
+
// the exact shape lambda-api and lambda-s3 shipped in.
|
|
109
|
+
test("an example that fails its own lexicon's post-synth checks at error severity fails (#1400)", async () => {
|
|
110
|
+
writeLexiconDirWithExample(dir, "insecure", {
|
|
111
|
+
"bucket.ts": `import { Bucket } from "@intentius/chant-lexicon-aws";
|
|
112
|
+
|
|
113
|
+
export const plainBucket = new Bucket({
|
|
114
|
+
BucketName: "plain-bucket",
|
|
115
|
+
});
|
|
116
|
+
`,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const results = await checkExamplesBuild(dir);
|
|
120
|
+
const insecure = results.find((r) => r.example === "insecure");
|
|
121
|
+
expect(insecure?.ok).toBe(false);
|
|
122
|
+
expect(insecure?.detail).toMatch(/post-synth error\(s\) from the lexicon's own checks/);
|
|
123
|
+
expect(insecure?.detail).toMatch(/WAW042: \[plainBucket\]/);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("an example's own lint.rules severity config applies to post-synth checks (#1400)", async () => {
|
|
127
|
+
writeLexiconDirWithExample(dir, "suppressed", {
|
|
128
|
+
"bucket.ts": `import { Bucket } from "@intentius/chant-lexicon-aws";
|
|
129
|
+
|
|
130
|
+
export const plainBucket = new Bucket({
|
|
131
|
+
BucketName: "plain-bucket",
|
|
132
|
+
});
|
|
133
|
+
`,
|
|
134
|
+
});
|
|
135
|
+
writeFileSync(
|
|
136
|
+
join(dir, "examples", "suppressed", "chant.config.json"),
|
|
137
|
+
JSON.stringify({ lint: { rules: { WAW018: "warning", WAW042: "off" } } }),
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const results = await checkExamplesBuild(dir);
|
|
141
|
+
expect(results.find((r) => r.example === "suppressed")).toMatchObject({ ok: true });
|
|
142
|
+
});
|
|
143
|
+
|
|
78
144
|
test("an empty src/ directory is skipped, not reported as a failure", async () => {
|
|
79
145
|
writeLexiconDirWithExample(dir, "empty", {});
|
|
80
146
|
const results = await checkExamplesBuild(dir);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* "Every shipped example builds" (chant #1067)
|
|
2
|
+
* "Every shipped example builds" (chant #1067) and "every shipped example
|
|
3
|
+
* passes its own lexicon's post-synth checks" (chant #1400).
|
|
3
4
|
*
|
|
4
5
|
* `check-lexicon.ts`'s existing example checks only count directories
|
|
5
6
|
* ("At least 1 example", "At least 3 examples", "At least 5 examples with
|
|
@@ -12,28 +13,29 @@
|
|
|
12
13
|
* pipeline `chant build` runs and report whether it produced output with no
|
|
13
14
|
* structural error.
|
|
14
15
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* that axis into "does it build" would fail this new check for all of them
|
|
26
|
-
* on its very first run, for defects this issue never set out to fix.
|
|
27
|
-
* Tracked separately; not silently absorbed here.
|
|
16
|
+
* #1067 scoped "builds" to discovery + serialization on purpose, and said
|
|
17
|
+
* so here: three aws examples (lambda-api, lambda-s3, shared-alb) failed
|
|
18
|
+
* WAW042/WAW054 at error severity at the time, for defects that issue never
|
|
19
|
+
* set out to fix. #1400 fixed those three and added the second axis: after
|
|
20
|
+
* serialization, the post-synth checks each loaded plugin ships run against
|
|
21
|
+
* that plugin's own output, exactly as `chant build` runs them (scoped per
|
|
22
|
+
* plugin, `lint.rules` from the example's chant.config applied), and any
|
|
23
|
+
* diagnostic left at `error` severity fails the example. Warnings do not.
|
|
24
|
+
* An example is what a user copies wholesale; it must not teach a pattern
|
|
25
|
+
* the lexicon it demonstrates flags as an error.
|
|
28
26
|
*/
|
|
29
27
|
|
|
30
28
|
import { existsSync, readdirSync } from "fs";
|
|
31
29
|
import { join } from "path";
|
|
32
|
-
import { build } from "../../build";
|
|
30
|
+
import { build, type BuildResult } from "../../build";
|
|
33
31
|
import { findInfraFiles } from "../../discovery/files";
|
|
34
32
|
import { detectLexicons } from "../../detectLexicon";
|
|
35
33
|
import { loadChantConfig } from "../../config";
|
|
36
34
|
import { loadPlugins } from "../plugins";
|
|
35
|
+
import { runPostSynthChecks, type PostSynthDiagnostic } from "../../lint/post-synth";
|
|
36
|
+
import { applyConfiguredSeverity } from "../../lint/config";
|
|
37
|
+
import type { LexiconPlugin } from "../../lexicon";
|
|
38
|
+
import type { SerializerResult } from "../../serializer";
|
|
37
39
|
|
|
38
40
|
export interface ExampleBuildResult {
|
|
39
41
|
example: string;
|
|
@@ -41,6 +43,43 @@ export interface ExampleBuildResult {
|
|
|
41
43
|
detail?: string;
|
|
42
44
|
}
|
|
43
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Run each plugin's own post-synth checks against that plugin's output —
|
|
48
|
+
* the same per-plugin scoping and `lint.rules` severity resolution
|
|
49
|
+
* `cli/commands/build.ts` applies — and return what is left at `error`
|
|
50
|
+
* severity. Project `lint.policies` are not run here: those are the
|
|
51
|
+
* example author's organizational policy, not the lexicon's contract.
|
|
52
|
+
*/
|
|
53
|
+
export function postSynthErrors(
|
|
54
|
+
plugins: LexiconPlugin[],
|
|
55
|
+
result: BuildResult,
|
|
56
|
+
lintRules: Parameters<typeof applyConfiguredSeverity>[1],
|
|
57
|
+
): PostSynthDiagnostic[] {
|
|
58
|
+
const errors: PostSynthDiagnostic[] = [];
|
|
59
|
+
for (const plugin of plugins) {
|
|
60
|
+
if (!plugin.postSynthChecks) continue;
|
|
61
|
+
const checks = plugin.postSynthChecks();
|
|
62
|
+
if (checks.length === 0) continue;
|
|
63
|
+
|
|
64
|
+
const outputKey = plugin.serializer.name;
|
|
65
|
+
const scopedOutputs = new Map<string, string | SerializerResult>();
|
|
66
|
+
const pluginOutput = result.outputs.get(outputKey);
|
|
67
|
+
if (pluginOutput !== undefined) scopedOutputs.set(outputKey, pluginOutput);
|
|
68
|
+
|
|
69
|
+
const diags = runPostSynthChecks(checks, { ...result, outputs: scopedOutputs });
|
|
70
|
+
const { diagnostics } = applyConfiguredSeverity(diags, lintRules);
|
|
71
|
+
for (const diag of diagnostics) {
|
|
72
|
+
if (diag.severity === "error") errors.push(diag);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return errors;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function formatPostSynthError(diag: PostSynthDiagnostic): string {
|
|
79
|
+
const prefix = diag.entity ? `[${diag.entity}] ` : "";
|
|
80
|
+
return `${diag.checkId}: ${prefix}${diag.message}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
44
83
|
/**
|
|
45
84
|
* Build every non-empty example under `<lexiconDir>/examples/*\/src` and
|
|
46
85
|
* report per-example pass/fail. Returns `[]` when the lexicon has no
|
|
@@ -94,7 +133,15 @@ export async function checkExamplesBuild(lexiconDir: string): Promise<ExampleBui
|
|
|
94
133
|
|
|
95
134
|
const structuralErrors = result.errors.map((e) => e.message);
|
|
96
135
|
const producedOutput = result.outputs.size > 0;
|
|
97
|
-
const
|
|
136
|
+
const structurallyOk = structuralErrors.length === 0 && producedOutput;
|
|
137
|
+
|
|
138
|
+
// #1400 — only once the build is structurally sound. A discovery error
|
|
139
|
+
// already explains the failure, and partial output is not the output
|
|
140
|
+
// the checks are meant to see.
|
|
141
|
+
const postSynth = structurallyOk
|
|
142
|
+
? postSynthErrors(plugins, result, exampleConfig.lint?.rules)
|
|
143
|
+
: [];
|
|
144
|
+
const ok = structurallyOk && postSynth.length === 0;
|
|
98
145
|
|
|
99
146
|
results.push({
|
|
100
147
|
example: entry.name,
|
|
@@ -103,7 +150,9 @@ export async function checkExamplesBuild(lexiconDir: string): Promise<ExampleBui
|
|
|
103
150
|
? undefined
|
|
104
151
|
: structuralErrors.length > 0
|
|
105
152
|
? structuralErrors.join("; ")
|
|
106
|
-
:
|
|
153
|
+
: !producedOutput
|
|
154
|
+
? "discovered source but produced no output"
|
|
155
|
+
: `post-synth error(s) from the lexicon's own checks: ${postSynth.map(formatPostSynthError).join("; ")}`,
|
|
107
156
|
});
|
|
108
157
|
} catch (err) {
|
|
109
158
|
results.push({
|
|
@@ -17,7 +17,7 @@ describe("checkLexicon", () => {
|
|
|
17
17
|
test("includes the new #1067 tier-1 checks by name", async () => {
|
|
18
18
|
const result = await checkLexicon(gitlabDir);
|
|
19
19
|
const names = result.items.map((i) => i.name);
|
|
20
|
-
expect(names).toContain("Every shipped example builds");
|
|
20
|
+
expect(names).toContain("Every shipped example builds and passes its own post-synth checks");
|
|
21
21
|
expect(names).toContain("Registered intrinsics are exported by the package");
|
|
22
22
|
expect(names).toContain("Registered intrinsics' isTag matches how they're authored");
|
|
23
23
|
expect(names).toContain("dist/manifest.json declares a chantVersion");
|
|
@@ -2,7 +2,7 @@ import { existsSync, readdirSync, readFileSync } from "fs";
|
|
|
2
2
|
import { join, basename } from "path";
|
|
3
3
|
import { auditIntrinsics } from "./check-lexicon-intrinsics";
|
|
4
4
|
import { checkExamplesBuild } from "./check-lexicon-examples";
|
|
5
|
-
import { auditDocsReachability } from "./check-lexicon-docs";
|
|
5
|
+
import { auditDocsClassification, auditDocsReachability } from "./check-lexicon-docs";
|
|
6
6
|
import { auditMcpNames, lexiconNameFor } from "./check-lexicon-mcp";
|
|
7
7
|
import { loadLexiconFromDir, registers, safeList } from "./check-lexicon-plugin";
|
|
8
8
|
import { RULE_CATALOG } from "../../audit/catalog";
|
|
@@ -254,12 +254,14 @@ export async function checkLexicon(dir: string): Promise<CheckResult> {
|
|
|
254
254
|
// checks only ever counted example directories; none tried to build one,
|
|
255
255
|
// so `lexicons/aws/examples/core-concepts` shipped with a discovery-time
|
|
256
256
|
// "Duplicate export name" error while this tool reported "All tier-1
|
|
257
|
-
// checks passed."
|
|
258
|
-
//
|
|
257
|
+
// checks passed." chant #1400 — and the built output must pass the
|
|
258
|
+
// lexicon's own post-synth checks at error severity; three aws examples
|
|
259
|
+
// taught patterns (no TLS-only bucket policy, mutable ECR tags) the
|
|
260
|
+
// lexicon flags as errors. See ./check-lexicon-examples.ts.
|
|
259
261
|
const exampleBuilds = await checkExamplesBuild(dir);
|
|
260
262
|
const brokenExamples = exampleBuilds.filter((e) => !e.ok);
|
|
261
263
|
items.push({
|
|
262
|
-
name: "Every shipped example builds",
|
|
264
|
+
name: "Every shipped example builds and passes its own post-synth checks",
|
|
263
265
|
tier: 1,
|
|
264
266
|
pass: brokenExamples.length === 0,
|
|
265
267
|
detail:
|
|
@@ -374,6 +376,19 @@ export async function checkLexicon(dir: string): Promise<CheckResult> {
|
|
|
374
376
|
: undefined,
|
|
375
377
|
});
|
|
376
378
|
|
|
379
|
+
// Authored pages name their Diátaxis quadrant; the sidebar is built from
|
|
380
|
+
// it, so an untagged page cannot be placed (#1731).
|
|
381
|
+
const docsClass = auditDocsClassification(dir);
|
|
382
|
+
items.push({
|
|
383
|
+
name: "Every authored doc page has a diataxis quadrant",
|
|
384
|
+
tier: 1,
|
|
385
|
+
pass: !docsClass.hasPages || docsClass.unclassified.length === 0,
|
|
386
|
+
detail:
|
|
387
|
+
docsClass.unclassified.length > 0
|
|
388
|
+
? `${docsClass.unclassified.length} unclassified: ${docsClass.unclassified.join(", ")}`
|
|
389
|
+
: undefined,
|
|
390
|
+
});
|
|
391
|
+
|
|
377
392
|
// ── Tier 2: Recommended ────────────────────────────────────────
|
|
378
393
|
|
|
379
394
|
const pluginContent = readOr(join(dir, "src/plugin.ts"));
|
|
@@ -67,7 +67,8 @@ export async function generate(options?: { verbose?: boolean; force?: boolean })
|
|
|
67
67
|
* Write generated files to the package directory.
|
|
68
68
|
*/
|
|
69
69
|
export function writeGeneratedFiles(result: GenerateResult, pkgDir?: string): void {
|
|
70
|
-
|
|
70
|
+
// This file lives in src/codegen/, so the package root is three levels up.
|
|
71
|
+
const dir = pkgDir ?? dirname(dirname(dirname(fileURLToPath(import.meta.url))));
|
|
71
72
|
writeGeneratedArtifacts({
|
|
72
73
|
baseDir: dir,
|
|
73
74
|
files: {
|
|
@@ -86,7 +87,9 @@ import { generate, writeGeneratedFiles } from "./generate";
|
|
|
86
87
|
import { dirname } from "path";
|
|
87
88
|
import { fileURLToPath } from "url";
|
|
88
89
|
|
|
89
|
-
|
|
90
|
+
// src/codegen/generate-cli.ts -> src/codegen -> src -> package root.
|
|
91
|
+
// Two dirnames land in src/ and the first generate writes src/src/generated/ (#1614).
|
|
92
|
+
const pkgDir = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
|
|
90
93
|
const result = await generate({ verbose: true });
|
|
91
94
|
writeGeneratedFiles(result, pkgDir);
|
|
92
95
|
`;
|
|
@@ -57,16 +57,49 @@ export default defineConfig({
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
export function generateDocsContentConfig(): string {
|
|
60
|
-
return `import { defineCollection } from 'astro:content';
|
|
60
|
+
return `import { defineCollection, z } from 'astro:content';
|
|
61
61
|
import { docsLoader } from '@astrojs/starlight/loaders';
|
|
62
62
|
import { docsSchema } from '@astrojs/starlight/schema';
|
|
63
63
|
|
|
64
64
|
export const collections = {
|
|
65
|
-
docs: defineCollection({
|
|
65
|
+
docs: defineCollection({
|
|
66
|
+
loader: docsLoader(),
|
|
67
|
+
schema: docsSchema({
|
|
68
|
+
extend: z.object({
|
|
69
|
+
// Diátaxis quadrant (https://diataxis.fr), chant #1731.
|
|
70
|
+
diataxis: z.enum(['tutorial', 'how-to', 'reference', 'explanation']).optional(),
|
|
71
|
+
}),
|
|
72
|
+
}),
|
|
73
|
+
}),
|
|
66
74
|
};
|
|
67
75
|
`;
|
|
68
76
|
}
|
|
69
77
|
|
|
78
|
+
/**
|
|
79
|
+
* First authored page. Lives under docs/pages/; the docs pipeline copies it
|
|
80
|
+
* into src/content/docs/ and builds the sidebar from its `diataxis` field.
|
|
81
|
+
*/
|
|
82
|
+
export function generateDocsGettingStartedMdx(name: string): string {
|
|
83
|
+
const displayName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
84
|
+
return `---
|
|
85
|
+
title: Getting Started
|
|
86
|
+
description: First project with the ${displayName} lexicon
|
|
87
|
+
diataxis: tutorial
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
Install the lexicon, declare one resource, and build it.
|
|
91
|
+
|
|
92
|
+
\`\`\`bash
|
|
93
|
+
npm install --save-dev @intentius/chant-lexicon-${name}
|
|
94
|
+
chant build
|
|
95
|
+
\`\`\`
|
|
96
|
+
|
|
97
|
+
Replace this with a walkthrough a new user can follow start to finish. Other
|
|
98
|
+
pages go beside this file in \`docs/pages/\`, each with a \`diataxis\` field
|
|
99
|
+
(\`tutorial\`, \`how-to\`, \`reference\` or \`explanation\`).
|
|
100
|
+
`;
|
|
101
|
+
}
|
|
102
|
+
|
|
70
103
|
export function generateDocsIndexMdx(name: string): string {
|
|
71
104
|
const displayName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
72
105
|
return `---
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, test, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import { existsSync, readFileSync, mkdirSync, writeFileSync, rmSync, readdirSync } from "fs";
|
|
2
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync, rmSync, readdirSync, realpathSync } from "fs";
|
|
3
|
+
import { execFileSync } from "child_process";
|
|
3
4
|
import { join, dirname } from "path";
|
|
4
5
|
import { tmpdir } from "os";
|
|
5
6
|
import { fileURLToPath } from "url";
|
|
@@ -63,6 +64,7 @@ describe("initLexiconCommand", () => {
|
|
|
63
64
|
"docs/astro.config.mjs",
|
|
64
65
|
"docs/src/content.config.ts",
|
|
65
66
|
"docs/src/content/docs/index.mdx",
|
|
67
|
+
"docs/pages/getting-started.mdx",
|
|
66
68
|
"examples/getting-started/package.json",
|
|
67
69
|
"examples/getting-started/src/infra.ts",
|
|
68
70
|
"src/generated/.gitkeep",
|
|
@@ -265,6 +267,45 @@ describe("scaffold content validation", () => {
|
|
|
265
267
|
const content = readFileSync(join(targetDir, "src/lint/rules/index.ts"), "utf-8");
|
|
266
268
|
expect(content).toContain("export const rules");
|
|
267
269
|
});
|
|
270
|
+
|
|
271
|
+
// #1614: generate-cli.ts lives in src/codegen/, so the package root is three
|
|
272
|
+
// dirnames up. Two dirnames is src/, and the first `npm run generate` then
|
|
273
|
+
// writes src/src/generated/. Run the scaffolded CLI for real with generate.ts
|
|
274
|
+
// replaced by a stub that reports where it was told to write.
|
|
275
|
+
test("generate-cli.ts resolves pkgDir to the package root, so generate writes src/generated (#1614)", () => {
|
|
276
|
+
const cliPath = join(targetDir, "src/codegen/generate-cli.ts");
|
|
277
|
+
expect(readFileSync(cliPath, "utf-8")).toContain(
|
|
278
|
+
"dirname(dirname(dirname(fileURLToPath(import.meta.url))))",
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
writeFileSync(
|
|
282
|
+
join(targetDir, "src/codegen/generate.ts"),
|
|
283
|
+
[
|
|
284
|
+
'import { mkdirSync, writeFileSync } from "fs";',
|
|
285
|
+
'import { join } from "path";',
|
|
286
|
+
"export async function generate() { return {}; }",
|
|
287
|
+
"export function writeGeneratedFiles(_result: unknown, pkgDir: string) {",
|
|
288
|
+
' const dir = join(pkgDir, "src/generated");',
|
|
289
|
+
" mkdirSync(dir, { recursive: true });",
|
|
290
|
+
' writeFileSync(join(dir, "lexicon.json"), "{}");',
|
|
291
|
+
" process.stdout.write(pkgDir);",
|
|
292
|
+
"}",
|
|
293
|
+
"",
|
|
294
|
+
].join("\n"),
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
const tsx = join(__dirname, "../../../../../node_modules/.bin/tsx");
|
|
298
|
+
const out = execFileSync(tsx, [cliPath], { cwd: targetDir, encoding: "utf-8" });
|
|
299
|
+
|
|
300
|
+
expect(realpathSync(out.trim())).toBe(realpathSync(targetDir));
|
|
301
|
+
expect(existsSync(join(targetDir, "src/generated/lexicon.json"))).toBe(true);
|
|
302
|
+
expect(existsSync(join(targetDir, "src/src"))).toBe(false);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
test("generate.ts default pkgDir is also the package root (#1614)", () => {
|
|
306
|
+
const content = readFileSync(join(targetDir, "src/codegen/generate.ts"), "utf-8");
|
|
307
|
+
expect(content).toContain("pkgDir ?? dirname(dirname(dirname(fileURLToPath(import.meta.url))))");
|
|
308
|
+
});
|
|
268
309
|
});
|
|
269
310
|
|
|
270
311
|
// ── Fixture snapshot tests ──────────────────────────────────────────
|
|
@@ -10,7 +10,7 @@ import { generateSampleRuleTs, generateLintRulesIndexTs } from "./init-lexicon/t
|
|
|
10
10
|
import { generateLspCompletionsTs, generateLspHoverTs } from "./init-lexicon/templates/lsp";
|
|
11
11
|
import { generatePackageJson, generateTsConfig, generateTsConfigBuild, generateJustfile, generateGitignore, generateReadme, generateSerializerTs, generateValidateTs, generateValidateCliTs } from "./init-lexicon/templates/project";
|
|
12
12
|
import { generatePluginTestTs, generateSerializerTestTs, generateCompletionsTestTs, generateHoverTestTs } from "./init-lexicon/templates/tests";
|
|
13
|
-
import { generateDocsPackageJson, generateDocsTsConfig, generateDocsAstroConfig, generateDocsContentConfig, generateDocsIndexMdx } from "./init-lexicon/templates/docs";
|
|
13
|
+
import { generateDocsPackageJson, generateDocsTsConfig, generateDocsAstroConfig, generateDocsContentConfig, generateDocsIndexMdx, generateDocsGettingStartedMdx } from "./init-lexicon/templates/docs";
|
|
14
14
|
import { generateExamplePackageJson, generateExampleInfraTs } from "./init-lexicon/templates/examples";
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -115,6 +115,7 @@ export async function initLexiconCommand(options: InitLexiconOptions): Promise<I
|
|
|
115
115
|
"docs/src",
|
|
116
116
|
"docs/src/content",
|
|
117
117
|
"docs/src/content/docs",
|
|
118
|
+
"docs/pages",
|
|
118
119
|
"examples/getting-started",
|
|
119
120
|
"examples/getting-started/src",
|
|
120
121
|
];
|
|
@@ -159,6 +160,7 @@ export async function initLexiconCommand(options: InitLexiconOptions): Promise<I
|
|
|
159
160
|
"docs/astro.config.mjs": generateDocsAstroConfig(name),
|
|
160
161
|
"docs/src/content.config.ts": generateDocsContentConfig(),
|
|
161
162
|
"docs/src/content/docs/index.mdx": generateDocsIndexMdx(name),
|
|
163
|
+
"docs/pages/getting-started.mdx": generateDocsGettingStartedMdx(name),
|
|
162
164
|
"examples/getting-started/package.json": generateExamplePackageJson(name),
|
|
163
165
|
"examples/getting-started/src/infra.ts": generateExampleInfraTs(name, names),
|
|
164
166
|
};
|
|
@@ -46,7 +46,7 @@ export async function runRollingUpgrade(
|
|
|
46
46
|
lexicon: "aws",
|
|
47
47
|
hasUpgrade: false,
|
|
48
48
|
severity: "none",
|
|
49
|
-
delta: { added: [], changed: [], removed: [], severity: "none" },
|
|
49
|
+
delta: { added: [], changed: [], removed: [], renamed: [], severity: "none" },
|
|
50
50
|
deltaText: "",
|
|
51
51
|
validationOk: false,
|
|
52
52
|
failures: [{ step: "setup", output: `Lexicon directory not found: ${dir}` }],
|
|
@@ -21,7 +21,7 @@ function result(over: Partial<RegenResult>): RegenResult {
|
|
|
21
21
|
ok: true,
|
|
22
22
|
changed: true,
|
|
23
23
|
severity: "additive",
|
|
24
|
-
delta: { added: [], changed: [], removed: [], severity: "additive" },
|
|
24
|
+
delta: { added: [], changed: [], removed: [], renamed: [], severity: "additive" },
|
|
25
25
|
deltaText: "",
|
|
26
26
|
failures: [],
|
|
27
27
|
freshSnapshot: { surface: "x" } as unknown as RegenResult["freshSnapshot"],
|
|
@@ -62,7 +62,7 @@ export async function runSurfaceDiff(opts: SurfaceDiffOptions): Promise<RegenRes
|
|
|
62
62
|
ok: false,
|
|
63
63
|
changed: false,
|
|
64
64
|
severity: "none",
|
|
65
|
-
delta: { added: [], changed: [], removed: [], severity: "none" },
|
|
65
|
+
delta: { added: [], changed: [], removed: [], renamed: [], severity: "none" },
|
|
66
66
|
deltaText: "",
|
|
67
67
|
failures: [failure],
|
|
68
68
|
freshSnapshot: null,
|