@intentius/chant 0.44.14 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit/discover.d.ts +26 -2
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/report-model.d.ts +10 -0
- package/dist/audit/report-model.d.ts.map +1 -1
- package/dist/audit/rules-doc.d.ts.map +1 -1
- package/dist/cli/build-params-cli.d.ts +16 -7
- package/dist/cli/build-params-cli.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts +20 -0
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts +9 -0
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-docs.d.ts +13 -0
- package/dist/cli/commands/check-lexicon-docs.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon-examples.d.ts +24 -14
- package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -1
- package/dist/cli/commands/check-lexicon.d.ts +14 -0
- package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon/templates/codegen.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon/templates/docs.d.ts +5 -0
- package/dist/cli/commands/init-lexicon/templates/docs.d.ts.map +1 -1
- package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
- package/dist/cli/commands/lexicon-surface-diff.d.ts +6 -0
- package/dist/cli/commands/lexicon-surface-diff.d.ts.map +1 -1
- package/dist/cli/commands/lint.d.ts.map +1 -1
- package/dist/cli/commands/onboard.d.ts +28 -0
- package/dist/cli/commands/onboard.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts +13 -0
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/search.d.ts +27 -0
- package/dist/cli/handlers/search.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +7 -0
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/docs-pages.d.ts +39 -0
- package/dist/codegen/docs-pages.d.ts.map +1 -0
- package/dist/codegen/docs-sections.d.ts.map +1 -1
- package/dist/codegen/docs-sidebar.d.ts +13 -2
- package/dist/codegen/docs-sidebar.d.ts.map +1 -1
- package/dist/codegen/docs-types.d.ts +30 -10
- package/dist/codegen/docs-types.d.ts.map +1 -1
- package/dist/codegen/docs.d.ts +14 -2
- package/dist/codegen/docs.d.ts.map +1 -1
- package/dist/codegen/lexicon-regen.d.ts +11 -0
- package/dist/codegen/lexicon-regen.d.ts.map +1 -1
- package/dist/codegen/surface-snapshot.d.ts +27 -3
- package/dist/codegen/surface-snapshot.d.ts.map +1 -1
- package/dist/codegen/validate.d.ts +21 -3
- package/dist/codegen/validate.d.ts.map +1 -1
- package/dist/components/capability-plugin.d.ts +13 -2
- package/dist/components/capability-plugin.d.ts.map +1 -1
- package/dist/components/driver.d.ts +38 -0
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/starter-plugin.d.ts.map +1 -1
- package/dist/config.d.ts +85 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/env.d.ts +12 -1
- package/dist/env.d.ts.map +1 -1
- package/dist/fold/fold.d.ts +100 -1
- package/dist/fold/fold.d.ts.map +1 -1
- package/dist/graph-ir.d.ts +12 -0
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/graph-ops.d.ts +17 -0
- package/dist/graph-ops.d.ts.map +1 -0
- package/dist/lexicon.d.ts +182 -2
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/change-set.d.ts +23 -2
- package/dist/lifecycle/change-set.d.ts.map +1 -1
- package/dist/lifecycle/deep-observe.d.ts.map +1 -1
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/observe.d.ts +8 -0
- package/dist/lifecycle/observe.d.ts.map +1 -1
- package/dist/lifecycle/replay.d.ts.map +1 -1
- package/dist/lifecycle/snapshot.d.ts.map +1 -1
- package/dist/lifecycle/teardown.d.ts +130 -0
- package/dist/lifecycle/teardown.d.ts.map +1 -0
- package/dist/lifecycle/types.d.ts +7 -0
- package/dist/lifecycle/types.d.ts.map +1 -1
- package/dist/lint/engine.d.ts +6 -2
- package/dist/lint/engine.d.ts.map +1 -1
- package/dist/lint/policy.d.ts.map +1 -1
- package/dist/lint/rule.d.ts +31 -0
- package/dist/lint/rule.d.ts.map +1 -1
- package/dist/lint/rules/cor021-env-literal-name.d.ts +3 -0
- package/dist/lint/rules/cor021-env-literal-name.d.ts.map +1 -0
- package/dist/lint/rules/index.d.ts +2 -1
- package/dist/lint/rules/index.d.ts.map +1 -1
- package/dist/managed-fields.d.ts +33 -21
- package/dist/managed-fields.d.ts.map +1 -1
- package/dist/observation.d.ts +23 -1
- package/dist/observation.d.ts.map +1 -1
- package/dist/op/builders.d.ts +36 -7
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/index.d.ts +1 -1
- package/dist/op/index.d.ts.map +1 -1
- package/dist/testing.d.ts +136 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/yaml.d.ts +0 -8
- package/dist/yaml.d.ts.map +1 -1
- package/package.json +6 -1
- package/src/__snapshots__/okf.test.ts.snap +20 -0
- package/src/audit/discover.test.ts +55 -1
- package/src/audit/discover.ts +70 -2
- package/src/audit/edge-init-safety.test.ts +149 -18
- package/src/audit/report-model.ts +12 -1
- package/src/audit/rules-doc.ts +1 -0
- package/src/build-params.test.ts +52 -0
- package/src/cli/build-params-cli.test.ts +25 -8
- package/src/cli/build-params-cli.ts +37 -23
- package/src/cli/commands/__fixtures__/audit-coverage/.github/workflows/ci.yml +5 -0
- package/src/cli/commands/__fixtures__/audit-coverage/Dockerfile +2 -0
- package/src/cli/commands/__fixtures__/audit-coverage/infra/main.tf +3 -0
- package/src/cli/commands/__fixtures__/audit-coverage/infra/stack.json +6 -0
- package/src/cli/commands/__fixtures__/audit-coverage/k8s/deploy.yaml +12 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/pages/getting-started.mdx +16 -0
- package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content.config.ts +10 -2
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate-cli.ts +3 -1
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/generate.ts +2 -1
- package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +2 -1
- package/src/cli/commands/audit.test.ts +83 -1
- package/src/cli/commands/audit.ts +121 -28
- package/src/cli/commands/build.test.ts +275 -5
- package/src/cli/commands/build.ts +86 -14
- package/src/cli/commands/check-lexicon-docs.ts +30 -0
- package/src/cli/commands/check-lexicon-examples.test.ts +73 -7
- package/src/cli/commands/check-lexicon-examples.ts +66 -17
- package/src/cli/commands/check-lexicon.test.ts +46 -2
- package/src/cli/commands/check-lexicon.ts +64 -4
- package/src/cli/commands/init-lexicon/templates/codegen.ts +5 -2
- package/src/cli/commands/init-lexicon/templates/docs.ts +35 -2
- package/src/cli/commands/init-lexicon.test.ts +42 -1
- package/src/cli/commands/init-lexicon.ts +3 -1
- package/src/cli/commands/lexicon-rolling-upgrade.ts +1 -1
- package/src/cli/commands/lexicon-surface-diff.test.ts +1 -1
- package/src/cli/commands/lexicon-surface-diff.ts +10 -1
- package/src/cli/commands/lexicon-surface-diff.update.test.ts +112 -0
- package/src/cli/commands/lint.ts +19 -6
- package/src/cli/commands/onboard.test.ts +319 -225
- package/src/cli/commands/onboard.ts +144 -58
- package/src/cli/handlers/build.test.ts +3 -3
- package/src/cli/handlers/build.ts +2 -0
- package/src/cli/handlers/components.ts +2 -2
- package/src/cli/handlers/graph.test.ts +44 -0
- package/src/cli/handlers/graph.ts +24 -4
- package/src/cli/handlers/lifecycle.test.ts +348 -1
- package/src/cli/handlers/lifecycle.ts +235 -3
- package/src/cli/handlers/run.test.ts +5 -5
- package/src/cli/handlers/run.ts +1 -0
- package/src/cli/handlers/search-live.test.ts +217 -0
- package/src/cli/handlers/search.test.ts +41 -1
- package/src/cli/handlers/search.ts +60 -8
- package/src/cli/main.ts +17 -4
- package/src/cli/registry.ts +7 -0
- package/src/cli/security-boundary.test.ts +2 -1
- package/src/codegen/docs-pages.test.ts +194 -0
- package/src/codegen/docs-pages.ts +138 -0
- package/src/codegen/docs-sections.ts +0 -8
- package/src/codegen/docs-sidebar.ts +50 -37
- package/src/codegen/docs-types.ts +32 -4
- package/src/codegen/docs.ts +78 -40
- package/src/codegen/lexicon-regen.ts +22 -2
- package/src/codegen/rolling-upgrade.test.ts +1 -1
- package/src/codegen/rolling-upgrade.ts +1 -1
- package/src/codegen/surface-snapshot.test.ts +139 -0
- package/src/codegen/surface-snapshot.ts +297 -122
- package/src/codegen/validate.test.ts +49 -3
- package/src/codegen/validate.ts +36 -7
- package/src/components/auto-release.ts +1 -1
- package/src/components/capability-plugin.ts +23 -2
- package/src/components/driver.test.ts +45 -0
- package/src/components/driver.ts +64 -27
- package/src/components/index.ts +2 -0
- package/src/components/starter-plugin.ts +5 -2
- package/src/config.test.ts +40 -0
- package/src/config.ts +159 -8
- package/src/discovery/fold-import.test.ts +427 -2
- package/src/discovery/fold-import.ts +163 -7
- package/src/discovery/sandbox/driver.test.ts +11 -0
- package/src/env.test.ts +35 -1
- package/src/env.ts +17 -3
- package/src/fold/fold.test.ts +41 -0
- package/src/fold/fold.ts +372 -2
- package/src/graph-ir-live.test.ts +42 -0
- package/src/graph-ir.ts +42 -2
- package/src/graph-ops.test.ts +69 -0
- package/src/graph-ops.ts +42 -0
- package/src/lexicon.ts +182 -2
- package/src/lifecycle/change-set.test.ts +41 -0
- package/src/lifecycle/change-set.ts +30 -2
- package/src/lifecycle/deep-observe.ts +4 -0
- package/src/lifecycle/index.ts +1 -0
- package/src/lifecycle/observe.ts +29 -2
- package/src/lifecycle/replay.ts +3 -0
- package/src/lifecycle/snapshot.ts +3 -0
- package/src/lifecycle/teardown.test.ts +537 -0
- package/src/lifecycle/teardown.ts +357 -0
- package/src/lifecycle/types.ts +7 -0
- package/src/lint/engine.ts +7 -1
- package/src/lint/policy.ts +14 -3
- package/src/lint/rule.ts +23 -0
- package/src/lint/rules/cor021-env-literal-name.test.ts +128 -0
- package/src/lint/rules/cor021-env-literal-name.ts +114 -0
- package/src/lint/rules/index.ts +4 -1
- package/src/managed-fields.test.ts +15 -42
- package/src/managed-fields.ts +43 -26
- package/src/observation.test.ts +24 -8
- package/src/observation.ts +48 -4
- package/src/op/builders.ts +40 -7
- package/src/op/index.ts +1 -1
- package/src/ownership.test.ts +63 -1
- package/src/testing.test.ts +261 -0
- package/src/testing.ts +338 -0
- package/src/yaml.test.ts +15 -0
- package/src/yaml.ts +19 -2
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { build } from "../../build";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
loadChantConfigUpward,
|
|
4
|
+
resolveOwnershipMarker,
|
|
5
|
+
resolveOwnershipEnv,
|
|
6
|
+
ownershipEnvDisagreement,
|
|
7
|
+
isOwnershipParamRef,
|
|
8
|
+
resolveFoldEnabled,
|
|
9
|
+
resolveSandboxEnabled,
|
|
10
|
+
} from "../../config";
|
|
11
|
+
import { unknownEnvError } from "../../env";
|
|
12
|
+
import type { OwnershipMarker } from "../../ownership";
|
|
3
13
|
import { resolveCliBuildParams } from "../build-params-cli";
|
|
4
14
|
import type { Serializer, SerializerResult } from "../../serializer";
|
|
5
15
|
import type { LexiconPlugin } from "../../lexicon";
|
|
@@ -141,10 +151,9 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
|
|
|
141
151
|
// its default.
|
|
142
152
|
const loaded = await loadChantConfigUpward(infraPath);
|
|
143
153
|
const config = loaded.config;
|
|
144
|
-
|
|
154
|
+
// The ownership marker is resolved further down, after build parameters —
|
|
155
|
+
// `ownership.env` may reference one (#1396).
|
|
145
156
|
|
|
146
|
-
// Environment for policy evaluation: explicit --env wins, else ownership.env.
|
|
147
|
-
const env = options.env ?? config.ownership?.env;
|
|
148
157
|
// Project-authored organizational policy checks (lint.policies), run over the
|
|
149
158
|
// resolved resources during build. Resolve paths relative to the config dir.
|
|
150
159
|
const configDir = loaded.configPath ? dirname(loaded.configPath) : infraPath;
|
|
@@ -197,11 +206,13 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
|
|
|
197
206
|
// missing required value, a type/enum mismatch) is reported as a chant
|
|
198
207
|
// build error naming the parameter, never a thrown error from inside user
|
|
199
208
|
// source (which is what loomster's hand-rolled `tierFromEnv()`-style
|
|
200
|
-
// validators did before migrating to this mechanism). Also logs
|
|
201
|
-
// resolved value (`[param] name = value
|
|
209
|
+
// validators did before migrating to this mechanism). Also logs a one-line
|
|
210
|
+
// count on success, or every resolved value (`[param] name = value
|
|
211
|
+
// (source)`) under --verbose (#1424).
|
|
202
212
|
const paramsResolution = resolveCliBuildParams(config.buildParams, {
|
|
203
213
|
cli: options.params,
|
|
204
214
|
paramsFile: options.paramsFile,
|
|
215
|
+
verbose: options.verbose,
|
|
205
216
|
});
|
|
206
217
|
if (!paramsResolution.success) {
|
|
207
218
|
errors.push(...paramsResolution.errors);
|
|
@@ -228,6 +239,45 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
|
|
|
228
239
|
);
|
|
229
240
|
}
|
|
230
241
|
|
|
242
|
+
// #1396 — opt-in ownership marking, resolved AFTER build parameters so
|
|
243
|
+
// `ownership.env: { param: "env" }` takes the value `--param env=prod` (or
|
|
244
|
+
// the declared env mapping, or the default) bound to params.env — one
|
|
245
|
+
// source for the label and the marker. A reference that cannot be
|
|
246
|
+
// satisfied is a build error, not a marker silently stamped without an env.
|
|
247
|
+
let ownership: OwnershipMarker | undefined;
|
|
248
|
+
// Environment for policy evaluation: explicit --env wins, else ownership.env
|
|
249
|
+
// (read even when marking itself is off, so policy can still branch on it).
|
|
250
|
+
let env: string | undefined;
|
|
251
|
+
try {
|
|
252
|
+
ownership = resolveOwnershipMarker(config, paramsResolution.provenance);
|
|
253
|
+
env = options.env ?? resolveOwnershipEnv(config, paramsResolution.provenance);
|
|
254
|
+
} catch (err) {
|
|
255
|
+
errors.push(formatError({ message: err instanceof Error ? err.message : String(err) }));
|
|
256
|
+
return { success: false, resourceCount: 0, fileCount: 0, errors, warnings };
|
|
257
|
+
}
|
|
258
|
+
// A literal `ownership.env` next to an `env` build parameter that resolved
|
|
259
|
+
// to something else is the silent divergence #1396 reports — say so.
|
|
260
|
+
const disagreement = ownershipEnvDisagreement(config, paramsResolution.provenance);
|
|
261
|
+
if (disagreement) warnings.push(formatWarning({ message: disagreement }));
|
|
262
|
+
|
|
263
|
+
// #1221 — dynamic-env legality. `--env` is validated against the declared
|
|
264
|
+
// `environments` in cli/main.ts; a param-bound `ownership.env` supplied via
|
|
265
|
+
// `--param env=<value>` reached here unchecked, so `--param env=pord`
|
|
266
|
+
// stamped a marker for an environment the project never declared. Same
|
|
267
|
+
// check, same site of truth: literal entries match by equality, entries
|
|
268
|
+
// containing `*` (e.g. `"pr-*"`) match as glob patterns, so an unbounded
|
|
269
|
+
// family like per-PR environments is declarable without listing each name.
|
|
270
|
+
if (isOwnershipParamRef(config.ownership?.env)) {
|
|
271
|
+
const dynamicEnvErr = unknownEnvError(env, config.environments);
|
|
272
|
+
if (dynamicEnvErr) {
|
|
273
|
+
errors.push(formatError({
|
|
274
|
+
message: dynamicEnvErr,
|
|
275
|
+
hint: 'Declare it in chant.config `environments` (a "pr-*" pattern entry covers a dynamic family), or pass a declared value.',
|
|
276
|
+
}));
|
|
277
|
+
return { success: false, resourceCount: 0, fileCount: 0, errors, warnings };
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
231
281
|
// #1039 — thread each loaded plugin's registered intrinsics (e.g. AWS's
|
|
232
282
|
// `Sub`) through to the fold path, so a file using a registered intrinsic
|
|
233
283
|
// tagged template folds instead of unconditionally falling back to run.
|
|
@@ -272,14 +322,22 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
|
|
|
272
322
|
});
|
|
273
323
|
|
|
274
324
|
// #1022 — report per-file fold vs run so it's visible what still runs.
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
325
|
+
// #1424 — one line by default; the per-file lines and their reasons are
|
|
326
|
+
// --verbose. A file that ran is the expected outcome for source that
|
|
327
|
+
// computes values in functions, and a dozen "is not foldable" notes read
|
|
328
|
+
// like errors to someone meeting the project for the first time.
|
|
329
|
+
if (fold && result.foldDecisions.length > 0) {
|
|
330
|
+
if (options.verbose) {
|
|
331
|
+
for (const decision of result.foldDecisions) {
|
|
332
|
+
const rel = relative(infraPath, decision.file) || decision.file;
|
|
333
|
+
const detail =
|
|
334
|
+
decision.mode === "fold"
|
|
335
|
+
? `${decision.resourceCount ?? 0} resource(s), no module execution`
|
|
336
|
+
: (decision.reason ?? "fell back to run");
|
|
337
|
+
console.error(formatInfo(`[fold:${decision.mode}] ${rel} — ${detail}`));
|
|
338
|
+
}
|
|
339
|
+
} else {
|
|
340
|
+
console.error(formatInfo(summarizeFoldDecisions(result.foldDecisions)));
|
|
283
341
|
}
|
|
284
342
|
}
|
|
285
343
|
|
|
@@ -587,6 +645,20 @@ export async function buildCommand(options: BuildOptions): Promise<BuildResult>
|
|
|
587
645
|
};
|
|
588
646
|
}
|
|
589
647
|
|
|
648
|
+
/**
|
|
649
|
+
* `fold: 8 files folded, 13 ran (--verbose for reasons)` — the non-verbose
|
|
650
|
+
* report of a fold build's per-file decisions (#1424). The "(--verbose for
|
|
651
|
+
* reasons)" hint appears only when something ran, since that is the only case
|
|
652
|
+
* with a reason to read.
|
|
653
|
+
*/
|
|
654
|
+
export function summarizeFoldDecisions(decisions: readonly { mode: string }[]): string {
|
|
655
|
+
const folded = decisions.filter((d) => d.mode === "fold").length;
|
|
656
|
+
const ran = decisions.length - folded;
|
|
657
|
+
const files = (n: number) => `${n} file${n === 1 ? "" : "s"}`;
|
|
658
|
+
const hint = ran > 0 ? " (--verbose for reasons)" : "";
|
|
659
|
+
return `fold: ${files(folded)} folded, ${ran} ran${hint}`;
|
|
660
|
+
}
|
|
661
|
+
|
|
590
662
|
/**
|
|
591
663
|
* Simple JSON to YAML converter
|
|
592
664
|
*/
|
|
@@ -69,3 +69,33 @@ export function auditDocsReachability(lexiconDir: string): DocsReachability {
|
|
|
69
69
|
|
|
70
70
|
return { hasSite: true, pages, unreachable };
|
|
71
71
|
}
|
|
72
|
+
|
|
73
|
+
export interface DocsClassification {
|
|
74
|
+
/** Whether this lexicon has a docs/pages/ directory at all. */
|
|
75
|
+
hasPages: boolean;
|
|
76
|
+
/** Authored page files under docs/pages/ that lack a valid `diataxis` field. */
|
|
77
|
+
unclassified: string[];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const QUADRANTS = new Set(["tutorial", "how-to", "reference", "explanation"]);
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Audit docs/pages/ for authored pages with no Diátaxis quadrant (#1731).
|
|
84
|
+
*
|
|
85
|
+
* The docs pipeline throws on the same condition, so this is the cheap
|
|
86
|
+
* pre-flight that reports every offender at once instead of the first one.
|
|
87
|
+
*/
|
|
88
|
+
export function auditDocsClassification(lexiconDir: string): DocsClassification {
|
|
89
|
+
const pagesDir = join(lexiconDir, "docs", "pages");
|
|
90
|
+
if (!existsSync(pagesDir)) return { hasPages: false, unclassified: [] };
|
|
91
|
+
const unclassified: string[] = [];
|
|
92
|
+
for (const file of readdirSync(pagesDir).sort()) {
|
|
93
|
+
if (!file.endsWith(".mdx") && !file.endsWith(".md")) continue;
|
|
94
|
+
const text = readFileSync(join(pagesDir, file), "utf-8");
|
|
95
|
+
const end = text.startsWith("---\n") ? text.indexOf("\n---", 4) : -1;
|
|
96
|
+
const fm = end === -1 ? "" : text.slice(4, end);
|
|
97
|
+
const value = fm.match(/^diataxis:\s*(.+)$/m)?.[1].trim();
|
|
98
|
+
if (!value || !QUADRANTS.has(value)) unclassified.push(file);
|
|
99
|
+
}
|
|
100
|
+
return { hasPages: true, unclassified };
|
|
101
|
+
}
|
|
@@ -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({
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { describe, test, expect } from "vitest";
|
|
2
2
|
import { join, dirname } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { checkLexicon } from "./check-lexicon";
|
|
4
|
+
import { checkLexicon, coverageReportCheck } from "./check-lexicon";
|
|
5
|
+
import { loadLexiconFromDir } from "./check-lexicon-plugin";
|
|
6
|
+
import type { LexiconPlugin } from "../../lexicon";
|
|
5
7
|
|
|
6
8
|
// chant #1067 — check-lexicon.ts had zero tests before this issue, despite
|
|
7
9
|
// being the tool meant to gate every lexicon's completeness. This locks in
|
|
@@ -17,7 +19,7 @@ describe("checkLexicon", () => {
|
|
|
17
19
|
test("includes the new #1067 tier-1 checks by name", async () => {
|
|
18
20
|
const result = await checkLexicon(gitlabDir);
|
|
19
21
|
const names = result.items.map((i) => i.name);
|
|
20
|
-
expect(names).toContain("Every shipped example builds");
|
|
22
|
+
expect(names).toContain("Every shipped example builds and passes its own post-synth checks");
|
|
21
23
|
expect(names).toContain("Registered intrinsics are exported by the package");
|
|
22
24
|
expect(names).toContain("Registered intrinsics' isTag matches how they're authored");
|
|
23
25
|
expect(names).toContain("dist/manifest.json declares a chantVersion");
|
|
@@ -32,3 +34,45 @@ describe("checkLexicon", () => {
|
|
|
32
34
|
expect(matches?.pass).toBe(true);
|
|
33
35
|
});
|
|
34
36
|
});
|
|
37
|
+
|
|
38
|
+
// chant #1330 — fountain's spec-coverage gate lived only in its own
|
|
39
|
+
// coverage.test.ts, a convention rather than a check-lexicon contract. These
|
|
40
|
+
// lock in the tier-1 check over the plugin's coverageReport() member: red on
|
|
41
|
+
// an unaccounted kind, vacuous pass without the member, red on a throw, and
|
|
42
|
+
// green against the real fountain lexicon in this repo.
|
|
43
|
+
describe("coverageReportCheck", () => {
|
|
44
|
+
test("fails when the report leaves a kind unaccounted", async () => {
|
|
45
|
+
const plugin = {
|
|
46
|
+
coverageReport: async () => ({ unaccountedKinds: ["SandboxRequest"] }),
|
|
47
|
+
} as unknown as LexiconPlugin;
|
|
48
|
+
const item = await coverageReportCheck(plugin);
|
|
49
|
+
expect(item.tier).toBe(1);
|
|
50
|
+
expect(item.pass).toBe(false);
|
|
51
|
+
expect(item.detail).toContain("SandboxRequest");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("passes vacuously when the plugin has no coverageReport", async () => {
|
|
55
|
+
const item = await coverageReportCheck({} as LexiconPlugin);
|
|
56
|
+
expect(item.pass).toBe(true);
|
|
57
|
+
expect(item.detail).toBeUndefined();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("fails when the report throws", async () => {
|
|
61
|
+
const plugin = {
|
|
62
|
+
coverageReport: async () => {
|
|
63
|
+
throw new Error("snapshot missing");
|
|
64
|
+
},
|
|
65
|
+
} as unknown as LexiconPlugin;
|
|
66
|
+
const item = await coverageReportCheck(plugin);
|
|
67
|
+
expect(item.pass).toBe(false);
|
|
68
|
+
expect(item.detail).toContain("snapshot missing");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("is green on the current fountain lexicon", async () => {
|
|
72
|
+
const loaded = await loadLexiconFromDir(join(repoRoot, "lexicons", "fountain"));
|
|
73
|
+
expect(loaded.plugin).toBeDefined();
|
|
74
|
+
const item = await coverageReportCheck(loaded.plugin);
|
|
75
|
+
expect(item.pass).toBe(true);
|
|
76
|
+
expect(item.detail).toBe("all spec kinds accounted for");
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -2,10 +2,11 @@ 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";
|
|
9
|
+
import type { LexiconPlugin } from "../../lexicon";
|
|
9
10
|
|
|
10
11
|
// ── Types ────────────────────────────────────────────────────────────
|
|
11
12
|
|
|
@@ -66,6 +67,45 @@ function countSubdirs(dir: string): number {
|
|
|
66
67
|
.length;
|
|
67
68
|
}
|
|
68
69
|
|
|
70
|
+
/**
|
|
71
|
+
* #1330 — gate on the plugin's own spec-coverage accounting.
|
|
72
|
+
*
|
|
73
|
+
* fountain's `coverage.test.ts` asserts `unaccountedKinds == []` in CI, but a
|
|
74
|
+
* lexicon-local test is a convention, not a check-lexicon contract — the same
|
|
75
|
+
* class of gap #1342 closed for LSP providers. `coverageReport()` gives core
|
|
76
|
+
* the one fact to gate on: which upstream spec kinds are neither modeled nor
|
|
77
|
+
* on the lexicon's exclusion list. A lexicon without the member passes
|
|
78
|
+
* vacuously, the same conditional shape as the docs-reachability and
|
|
79
|
+
* Diátaxis checks; a report that throws fails, for the same reason `safeList`
|
|
80
|
+
* treats a throw as worse than absence.
|
|
81
|
+
*/
|
|
82
|
+
export async function coverageReportCheck(plugin: LexiconPlugin | undefined): Promise<CheckItem> {
|
|
83
|
+
const report = plugin?.coverageReport;
|
|
84
|
+
const hasReport = typeof report === "function";
|
|
85
|
+
let unaccounted: string[] = [];
|
|
86
|
+
let error: string | undefined;
|
|
87
|
+
if (hasReport) {
|
|
88
|
+
try {
|
|
89
|
+
unaccounted = (await report.call(plugin))?.unaccountedKinds ?? [];
|
|
90
|
+
} catch (e) {
|
|
91
|
+
error = e instanceof Error ? e.message : String(e);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
name: "coverageReport() leaves no spec kind unaccounted",
|
|
96
|
+
tier: 1,
|
|
97
|
+
pass: !hasReport || (error === undefined && unaccounted.length === 0),
|
|
98
|
+
detail:
|
|
99
|
+
error !== undefined
|
|
100
|
+
? `threw: ${error}`
|
|
101
|
+
: unaccounted.length > 0
|
|
102
|
+
? `${unaccounted.length} unaccounted: ${unaccounted.join(", ")}`
|
|
103
|
+
: hasReport
|
|
104
|
+
? "all spec kinds accounted for"
|
|
105
|
+
: undefined,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
69
109
|
// ── Check runner ─────────────────────────────────────────────────────
|
|
70
110
|
|
|
71
111
|
/**
|
|
@@ -254,12 +294,14 @@ export async function checkLexicon(dir: string): Promise<CheckResult> {
|
|
|
254
294
|
// checks only ever counted example directories; none tried to build one,
|
|
255
295
|
// so `lexicons/aws/examples/core-concepts` shipped with a discovery-time
|
|
256
296
|
// "Duplicate export name" error while this tool reported "All tier-1
|
|
257
|
-
// checks passed."
|
|
258
|
-
//
|
|
297
|
+
// checks passed." chant #1400 — and the built output must pass the
|
|
298
|
+
// lexicon's own post-synth checks at error severity; three aws examples
|
|
299
|
+
// taught patterns (no TLS-only bucket policy, mutable ECR tags) the
|
|
300
|
+
// lexicon flags as errors. See ./check-lexicon-examples.ts.
|
|
259
301
|
const exampleBuilds = await checkExamplesBuild(dir);
|
|
260
302
|
const brokenExamples = exampleBuilds.filter((e) => !e.ok);
|
|
261
303
|
items.push({
|
|
262
|
-
name: "Every shipped example builds",
|
|
304
|
+
name: "Every shipped example builds and passes its own post-synth checks",
|
|
263
305
|
tier: 1,
|
|
264
306
|
pass: brokenExamples.length === 0,
|
|
265
307
|
detail:
|
|
@@ -374,6 +416,24 @@ export async function checkLexicon(dir: string): Promise<CheckResult> {
|
|
|
374
416
|
: undefined,
|
|
375
417
|
});
|
|
376
418
|
|
|
419
|
+
// Authored pages name their Diátaxis quadrant; the sidebar is built from
|
|
420
|
+
// it, so an untagged page cannot be placed (#1731).
|
|
421
|
+
const docsClass = auditDocsClassification(dir);
|
|
422
|
+
items.push({
|
|
423
|
+
name: "Every authored doc page has a diataxis quadrant",
|
|
424
|
+
tier: 1,
|
|
425
|
+
pass: !docsClass.hasPages || docsClass.unclassified.length === 0,
|
|
426
|
+
detail:
|
|
427
|
+
docsClass.unclassified.length > 0
|
|
428
|
+
? `${docsClass.unclassified.length} unclassified: ${docsClass.unclassified.join(", ")}`
|
|
429
|
+
: undefined,
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
// #1330 — fountain's spec-coverage gate lived in a lexicon-local vitest
|
|
433
|
+
// assertion, a convention rather than a check-lexicon contract. The plugin
|
|
434
|
+
// now states the fact directly via `coverageReport()`.
|
|
435
|
+
items.push(await coverageReportCheck(plugin));
|
|
436
|
+
|
|
377
437
|
// ── Tier 2: Recommended ────────────────────────────────────────
|
|
378
438
|
|
|
379
439
|
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 `---
|