@canonical/pragma-cli 0.33.0 → 0.34.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/README.md +7 -7
- package/dist/pragma +0 -0
- package/package.json +18 -15
- package/pragma.conf.ts +664 -0
- package/src/bin.ts +13 -4
- package/src/capabilities/block.parity.test.ts +250 -0
- package/src/capabilities/capabilities/capabilities.render.ts +3 -2
- package/src/capabilities/capabilities/capabilities.verb.ts +31 -13
- package/src/capabilities/capabilities/catalog.ts +18 -10
- package/src/capabilities/capabilities/hints.ts +13 -19
- package/src/capabilities/colophon/collectColophon.ts +34 -19
- package/src/capabilities/colophon/colophon.verb.ts +8 -4
- package/src/capabilities/colophon/types.ts +16 -7
- package/src/capabilities/config/collectConfigShow.test.ts +57 -0
- package/src/capabilities/config/collectConfigShow.ts +41 -4
- package/src/capabilities/config/fields.ts +0 -27
- package/src/capabilities/config/set.verb.ts +7 -3
- package/src/capabilities/config/show.render.test.ts +77 -5
- package/src/capabilities/config/show.render.ts +7 -12
- package/src/capabilities/config/show.verb.ts +4 -3
- package/src/capabilities/config/types.ts +8 -0
- package/src/capabilities/create/byteEquality.test.ts +78 -100
- package/src/capabilities/create/compiledCreate.subprocess.test.ts +52 -15
- package/src/capabilities/create/constants.ts +75 -0
- package/src/capabilities/create/create.mcp.test.ts +2 -2
- package/src/capabilities/create/create.test.ts +162 -15
- package/src/capabilities/create/create.verb.ts +140 -69
- package/src/capabilities/create/index.ts +2 -10
- package/src/capabilities/create/pickGenerator.ts +34 -28
- package/src/capabilities/create/templates.embedded.generated.ts +4 -29
- package/src/capabilities/create/types.ts +10 -6
- package/src/capabilities/{standard/parity.test.ts → distribution.parity.test.ts} +24 -14
- package/src/capabilities/distribution.test.ts +120 -0
- package/src/capabilities/distribution.ts +59 -0
- package/src/capabilities/doctor/checks/checkConfigFile.ts +6 -5
- package/src/capabilities/doctor/checks/checkKeStore.ts +2 -2
- package/src/capabilities/doctor/checks/checkMcpConfigured.ts +7 -6
- package/src/capabilities/doctor/checks/checkPackageRefs.ts +79 -45
- package/src/capabilities/doctor/checks/checkPragmaVersion.ts +3 -2
- package/src/capabilities/doctor/checks/checkShellCompletions.test.ts +61 -11
- package/src/capabilities/doctor/checks/checkShellCompletions.ts +63 -23
- package/src/capabilities/doctor/checks/checkSkillsSymlinked.ts +3 -2
- package/src/capabilities/doctor/doctor.render.test.ts +6 -6
- package/src/capabilities/doctor/doctor.render.ts +2 -1
- package/src/capabilities/doctor/doctor.test.ts +37 -8
- package/src/capabilities/doctor/doctor.verb.ts +6 -2
- package/src/capabilities/doctor/runChecks.ts +5 -5
- package/src/capabilities/doctor/types.ts +1 -1
- package/src/capabilities/emptyRecovery.test.ts +32 -15
- package/src/capabilities/graph/inspect.verb.ts +3 -2
- package/src/capabilities/graph/query.verb.ts +3 -2
- package/src/capabilities/graph/runQuery.ts +8 -5
- package/src/capabilities/index.ts +22 -18
- package/src/capabilities/info/collectInfo.ts +10 -6
- package/src/capabilities/info/info.render.ts +3 -2
- package/src/capabilities/info/info.verb.ts +3 -2
- package/src/capabilities/lazy.test.ts +93 -2
- package/src/capabilities/ontology/verbs.ts +21 -21
- package/src/capabilities/prompt/prompt.test.ts +12 -6
- package/src/capabilities/prompt/verbs.ts +24 -11
- package/src/capabilities/resources/provider.ts +30 -11
- package/src/capabilities/resources/resources.test.ts +85 -29
- package/src/capabilities/setup/operations/setupGenerator.ts +16 -9
- package/src/capabilities/setup/operations/setupMcp.ts +2 -2
- package/src/capabilities/setup/operations/setupSkills.ts +3 -3
- package/src/capabilities/setup/setup.verb.ts +12 -5
- package/src/capabilities/setup/shell.ts +29 -3
- package/src/capabilities/skill/discover.ts +11 -5
- package/src/capabilities/skill/render.ts +4 -5
- package/src/capabilities/skill/verbs.ts +7 -7
- package/src/capabilities/sources/collectStatus.ts +55 -50
- package/src/capabilities/sources/index.ts +1 -1
- package/src/capabilities/sources/installSkills.ts +2 -2
- package/src/capabilities/sources/runUpdate.ts +113 -122
- package/src/capabilities/sources/sources.test.ts +111 -117
- package/src/capabilities/sources/status.render.test.ts +53 -24
- package/src/capabilities/sources/status.render.ts +24 -29
- package/src/capabilities/sources/status.verb.ts +15 -8
- package/src/capabilities/sources/types.ts +18 -25
- package/src/capabilities/sources/update.render.ts +9 -2
- package/src/capabilities/sources/update.verb.ts +12 -23
- package/src/capabilities/surface.test.ts +33 -10
- package/src/capabilities/tier.parity.test.ts +144 -0
- package/src/capabilities/upgrade/upgrade.verb.ts +7 -3
- package/src/constants.test.ts +8 -6
- package/src/constants.ts +47 -21
- package/src/identity.test.ts +333 -0
- package/src/index.ts +2 -2
- package/src/kernel/completion/__snapshots__/emitScripts.test.ts.snap +5 -2
- package/src/kernel/completion/emitScripts.test.ts +1 -1
- package/src/kernel/completion/emitScripts.ts +4 -2
- package/src/kernel/completion/entitySource.test.ts +185 -41
- package/src/kernel/completion/entitySource.ts +117 -107
- package/src/kernel/completion/model.test.ts +1 -1
- package/src/kernel/completion/model.ts +11 -0
- package/src/kernel/completion/safety.test.ts +82 -36
- package/src/kernel/completion/shellDrive.test.ts +592 -0
- package/src/kernel/completion/templates/fish.ts +28 -6
- package/src/kernel/completion/types.ts +3 -3
- package/src/kernel/config/__snapshots__/firstRun.test.ts.snap +1 -1
- package/src/kernel/config/defaults.test.ts +85 -0
- package/src/kernel/config/defaults.ts +12 -23
- package/src/kernel/config/evaluateProjectConfig.ts +2 -3
- package/src/kernel/config/findProjectConfig.ts +4 -3
- package/src/kernel/config/firstRun.ts +12 -5
- package/src/kernel/config/globalConfig.ts +2 -2
- package/src/kernel/config/index.ts +4 -5
- package/src/kernel/config/paths.ts +17 -17
- package/src/kernel/config/readConfig.test.ts +242 -6
- package/src/kernel/config/readConfig.ts +8 -8
- package/src/kernel/config/schema.test.ts +50 -0
- package/src/kernel/config/schema.ts +97 -10
- package/src/kernel/config/types.ts +73 -19
- package/src/kernel/copy.test.ts +453 -0
- package/src/kernel/error/PragmaError.test.ts +1 -1
- package/src/kernel/error/constants.ts +3 -2
- package/src/kernel/error/index.ts +1 -1
- package/src/kernel/error/recovery.test.ts +22 -45
- package/src/kernel/error/recovery.ts +22 -27
- package/src/kernel/error/types.ts +5 -4
- package/src/kernel/packs/collect.test.ts +195 -11
- package/src/kernel/packs/collect.ts +213 -58
- package/src/kernel/packs/compile.test.ts +163 -7
- package/src/kernel/packs/compile.ts +15 -12
- package/src/kernel/packs/disclosure.test.ts +5 -2
- package/src/kernel/packs/graphql/fetchGraphqlLookup.ts +11 -10
- package/src/kernel/packs/graphqlSource.test.ts +6 -3
- package/src/kernel/packs/iri.ts +5 -7
- package/src/kernel/packs/renderPack.ts +5 -3
- package/src/kernel/packs/resolveEntity.ts +21 -17
- package/src/kernel/packs/runBodies.ts +15 -14
- package/src/kernel/packs/schema.ts +96 -10
- package/src/kernel/packs/sparql/runSelect.test.ts +104 -10
- package/src/kernel/packs/sparql/runSelect.ts +73 -34
- package/src/kernel/packs/toolDescription.test.ts +10 -2
- package/src/kernel/packs/types.ts +39 -4
- package/src/kernel/packs/uniqueness.ts +4 -4
- package/src/kernel/project/cli/buildProgram.test.ts +9 -4
- package/src/kernel/project/cli/dispatch.test.ts +67 -0
- package/src/kernel/project/cli/dispatch.ts +34 -12
- package/src/kernel/project/cli/helpFormat.ts +1 -1
- package/src/kernel/project/cli/rootHelp.ts +105 -105
- package/src/kernel/project/mcp/buildServer.ts +11 -6
- package/src/kernel/project/mcp/instructions.ts +11 -6
- package/src/kernel/project/mcp/parity.test.ts +39 -1
- package/src/kernel/project/mcp/prompts/prompts.test.ts +120 -5
- package/src/kernel/project/mcp/prompts/provider.ts +22 -8
- package/src/kernel/project/mcp/prompts/source.ts +68 -67
- package/src/kernel/project/mcp/registerVerb.ts +25 -6
- package/src/kernel/project/mcp/serve.ts +11 -3
- package/src/kernel/project/mcp/toolDescriptions.test.ts +6 -2
- package/src/kernel/render/disclosure.ts +4 -2
- package/src/kernel/render/prefixes.test.ts +36 -0
- package/src/kernel/render/prefixes.ts +40 -13
- package/src/kernel/render/renderers.test.ts +1 -1
- package/src/kernel/runtime/graphpack/build.ts +29 -3
- package/src/kernel/runtime/graphpack/buildIndex.test.ts +46 -0
- package/src/kernel/runtime/graphpack/buildIndex.ts +52 -17
- package/src/kernel/runtime/graphpack/embedded/pack.generated.ts +5 -5
- package/src/kernel/runtime/graphpack/embedded/pack.index.generated.ts +3 -3
- package/src/kernel/runtime/graphpack/embedded/pack.stories.generated.ts +3 -0
- package/src/kernel/runtime/graphpack/embedded.ts +37 -11
- package/src/kernel/runtime/graphpack/graphpack.test.ts +205 -5
- package/src/kernel/runtime/graphpack/hash.ts +3 -2
- package/src/kernel/runtime/graphpack/index.ts +1 -1
- package/src/kernel/runtime/graphpack/manifest.ts +114 -12
- package/src/kernel/runtime/graphpack/read.ts +4 -9
- package/src/kernel/runtime/graphpack/schemas.ts +74 -0
- package/src/kernel/runtime/graphpack/stories.ts +84 -0
- package/src/kernel/runtime/graphpack/types.ts +61 -48
- package/src/kernel/runtime/paths.ts +64 -15
- package/src/kernel/runtime/probe.test.ts +8 -4
- package/src/kernel/runtime/readEntity.ts +6 -5
- package/src/kernel/runtime/refs/gitOps.ts +1 -19
- package/src/kernel/runtime/refs/parseRef.ts +7 -7
- package/src/kernel/runtime/refs/resolve.ts +78 -62
- package/src/kernel/runtime/resolveSources.test.ts +63 -24
- package/src/kernel/runtime/resolveSources.ts +36 -25
- package/src/kernel/runtime/storeReadiness.ts +9 -8
- package/src/kernel/runtime/storeless.test.ts +1 -1
- package/src/kernel/runtime/wasmEmbed.test.ts +22 -19
- package/src/kernel/spec/emitReference.test.ts +3 -3
- package/src/kernel/spec/emitReference.ts +144 -11
- package/src/kernel/spec/emitSurface.ts +9 -5
- package/src/kernel/spec/surfaceConformance.test.ts +2 -2
- package/src/kernel/spec/surfaceConformance.ts +8 -0
- package/src/kernel/spec/types.ts +29 -9
- package/src/kernel/spec/validate.ts +2 -1
- package/src/kernel/vocabulary.test.ts +164 -0
- package/src/kernel/vocabulary.ts +146 -0
- package/src/testing/PARITY_GAPS.ts +8 -2
- package/src/testing/behavioral/README.md +3 -3
- package/src/testing/behavioral/completion.test.ts +19 -6
- package/src/testing/behavioral/errorMatrix.mcp.test.ts +4 -3
- package/src/testing/behavioral/errorMatrix.storeless.test.ts +1 -1
- package/src/testing/behavioral/exitCodes.e2e.test.ts +6 -5
- package/src/testing/behavioral/harness.foundation.test.ts +14 -5
- package/src/testing/behavioral/journeys.cli.test.ts +116 -75
- package/src/testing/behavioral/journeys.defaultPack.test.ts +82 -176
- package/src/testing/behavioral/journeys.firstInstall.test.ts +167 -0
- package/src/testing/behavioral/journeys.mcpLifecycle.mcp.test.ts +9 -6
- package/src/testing/behavioral/journeys.packageStories.test.ts +188 -0
- package/src/testing/behavioral/liveReadSurface.ts +4 -3
- package/src/testing/behavioral/mcpSurface.test.ts +9 -4
- package/src/testing/behavioral/staleCommands.test.ts +10 -0
- package/src/testing/eval/__snapshots__/eval.test.ts.snap +7 -7
- package/src/testing/eval/cases/assembly.ts +13 -6
- package/src/testing/eval/cases/effectful.ts +25 -13
- package/src/testing/eval/cases/readNouns.ts +22 -20
- package/src/testing/eval/cases/stable.ts +5 -1
- package/src/testing/eval/eval.test.ts +7 -1
- package/src/testing/fixtures/fixtureCapability.ts +45 -1
- package/src/testing/fixtures/graph/canonical.ts +6 -3
- package/src/testing/fixtures/graph/defaultPack.ts +6 -5
- package/src/testing/fixtures/graph/owlExport.ts +7 -5
- package/src/testing/helpers/fixtureGraph.ts +33 -16
- package/src/testing/helpers/packRuntime.ts +2 -3
- package/src/testing/helpers/projectCli.ts +2 -1
- package/src/testing/helpers/projectMcp.ts +6 -0
- package/src/testing/perf/budgets.test.ts +4 -0
- package/src/testing/perf/budgets.ts +22 -7
- package/src/testing/perf/globalSetup.ts +121 -6
- package/src/types.ts +2 -2
- package/src/capabilities/block/blockList.test.ts +0 -79
- package/src/capabilities/block/blockList.verb.ts +0 -163
- package/src/capabilities/block/index.ts +0 -25
- package/src/capabilities/block/pack.ts +0 -166
- package/src/capabilities/block/parity.test.ts +0 -187
- package/src/capabilities/block/tierChain.ts +0 -78
- package/src/capabilities/colophon/pragmaColophon.ts +0 -66
- package/src/capabilities/modifier/index.ts +0 -15
- package/src/capabilities/modifier/pack.ts +0 -64
- package/src/capabilities/standard/index.ts +0 -18
- package/src/capabilities/standard/pack.ts +0 -128
- package/src/capabilities/tier/index.ts +0 -19
- package/src/capabilities/tier/lookup.render.test.ts +0 -72
- package/src/capabilities/tier/lookup.render.ts +0 -59
- package/src/capabilities/tier/lookup.test.ts +0 -83
- package/src/capabilities/tier/lookup.verb.ts +0 -44
- package/src/capabilities/tier/pack.ts +0 -28
- package/src/capabilities/tier/runLookup.ts +0 -87
- package/src/capabilities/token/addConfig.render.ts +0 -27
- package/src/capabilities/token/addConfig.test.ts +0 -62
- package/src/capabilities/token/addConfig.verb.ts +0 -40
- package/src/capabilities/token/index.ts +0 -19
- package/src/capabilities/token/pack.ts +0 -58
- package/src/capabilities/token/runAddConfig.ts +0 -95
- package/src/kernel/completion/bashDrive.test.ts +0 -73
- package/src/kernel/runtime/graphpack/embedded/sample.ttl +0 -39
- package/src/kernel/runtime/lock.ts +0 -90
|
@@ -1,29 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PROTECTED — the
|
|
3
|
-
* ways and diffed byte-for-byte (path set + contents, incl.
|
|
4
|
-
* stamp): (1) `pragma create <verb> --yes` through the kernel
|
|
5
|
-
* (2) summon-core `execute` with `autoPrompt`,
|
|
6
|
-
*
|
|
7
|
-
* StampConfig from `createGeneratorStamp` (displayName + version) and
|
|
8
|
-
* SAME `generator.generate` with the SAME answers.
|
|
2
|
+
* PROTECTED — pragma's side of the byte-equality seam contract: every `create`
|
|
3
|
+
* tree is produced TWO ways and diffed byte-for-byte (path set + contents, incl.
|
|
4
|
+
* the "Generated by" stamp): (1) `pragma create <verb> --yes` through the kernel
|
|
5
|
+
* dispatcher, and (2) the REFERENCE — summon-core `execute` with `autoPrompt`,
|
|
6
|
+
* run through `runGeneratorTask` + the shared stamp. They match because both
|
|
7
|
+
* derive the StampConfig from `createGeneratorStamp` (displayName + version) and
|
|
8
|
+
* run the SAME `generator.generate` with the SAME answers.
|
|
9
|
+
*
|
|
10
|
+
* The reference producer, the tree snapshot/diff, and the answer sets are NOT
|
|
11
|
+
* defined here any more: they are `@canonical/summon-core/testing`, the named
|
|
12
|
+
* conformance suite both bins run (L-OPEN-10). This file supplies what only
|
|
13
|
+
* pragma can — its kernel dispatch path, and the mapping from a fixture's
|
|
14
|
+
* logical generator id to the bundled generator plus the CLI param names the
|
|
15
|
+
* `create` verbs take. No new package edge: pragma already depends on
|
|
16
|
+
* summon-core, and never on the summon bin.
|
|
9
17
|
*
|
|
10
18
|
* Five generators are covered: component × {react, svelte, lit}, package, and
|
|
11
19
|
* application. A cross-binary subprocess guard lives in byteEquality.subprocess.test.ts.
|
|
12
20
|
*/
|
|
13
21
|
|
|
14
|
-
import { mkdtempSync
|
|
22
|
+
import { mkdtempSync } from "node:fs";
|
|
15
23
|
import { tmpdir } from "node:os";
|
|
16
24
|
import { join } from "node:path";
|
|
25
|
+
import {
|
|
26
|
+
CONFORMANCE_FIXTURES,
|
|
27
|
+
diffTrees,
|
|
28
|
+
formatTreeDiff,
|
|
29
|
+
isIdentical,
|
|
30
|
+
produceReference,
|
|
31
|
+
snapshotTree,
|
|
32
|
+
type TreeSnapshot,
|
|
33
|
+
} from "@canonical/summon-core/testing";
|
|
17
34
|
import { describe, expect, it } from "vitest";
|
|
18
35
|
import { executeVerb } from "../../kernel/project/cli/dispatch.js";
|
|
19
36
|
import { bootRuntime } from "../../kernel/runtime/boot.js";
|
|
20
37
|
import type { GlobalFlags } from "../../kernel/runtime/types.js";
|
|
21
38
|
import type { VerbSpec } from "../../kernel/spec/types.js";
|
|
22
|
-
import {
|
|
23
|
-
createApplicationVerb,
|
|
24
|
-
createComponentVerb,
|
|
25
|
-
createPackageVerb,
|
|
26
|
-
} from "./create.verb.js";
|
|
39
|
+
import { createVerbs } from "./create.verb.js";
|
|
27
40
|
import type { CreateKind } from "./types.js";
|
|
28
41
|
|
|
29
42
|
const FLAGS: GlobalFlags = {
|
|
@@ -34,39 +47,17 @@ const FLAGS: GlobalFlags = {
|
|
|
34
47
|
};
|
|
35
48
|
const freshCwd = (): string => mkdtempSync(join(tmpdir(), "pragma-golden-"));
|
|
36
49
|
|
|
37
|
-
/** Read a directory tree into a sorted map of relative path → contents. */
|
|
38
|
-
function snapshot(dir: string): Map<string, string> {
|
|
39
|
-
const out = new Map<string, string>();
|
|
40
|
-
const walk = (d: string, base: string): void => {
|
|
41
|
-
for (const entry of readdirSync(d, { withFileTypes: true }).sort((a, b) =>
|
|
42
|
-
a.name.localeCompare(b.name),
|
|
43
|
-
)) {
|
|
44
|
-
const rel = base ? `${base}/${entry.name}` : entry.name;
|
|
45
|
-
if (entry.isDirectory()) walk(join(d, entry.name), rel);
|
|
46
|
-
else out.set(rel, readFileSync(join(d, entry.name), "utf-8"));
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
walk(dir, "");
|
|
50
|
-
return out;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const verbFor: Record<CreateKind, VerbSpec> = {
|
|
54
|
-
component: createComponentVerb as VerbSpec,
|
|
55
|
-
package: createPackageVerb as VerbSpec,
|
|
56
|
-
application: createApplicationVerb as VerbSpec,
|
|
57
|
-
};
|
|
58
|
-
|
|
59
50
|
/** Producer (1): the full pragma kernel path — dispatcher runs the create verb. */
|
|
60
51
|
async function producePragma(
|
|
61
52
|
kind: CreateKind,
|
|
62
53
|
params: Record<string, unknown>,
|
|
63
|
-
): Promise<
|
|
54
|
+
): Promise<TreeSnapshot> {
|
|
64
55
|
const dir = freshCwd();
|
|
65
56
|
const prev = process.cwd();
|
|
66
57
|
process.chdir(dir);
|
|
67
58
|
try {
|
|
68
59
|
const outcome = await executeVerb(
|
|
69
|
-
|
|
60
|
+
createVerbs[kind] as VerbSpec,
|
|
70
61
|
params,
|
|
71
62
|
{ dryRun: false, undo: false, yes: true },
|
|
72
63
|
bootRuntime(FLAGS, dir),
|
|
@@ -75,44 +66,38 @@ async function producePragma(
|
|
|
75
66
|
} finally {
|
|
76
67
|
process.chdir(prev);
|
|
77
68
|
}
|
|
78
|
-
return
|
|
69
|
+
return snapshotTree(dir);
|
|
79
70
|
}
|
|
80
71
|
|
|
81
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* Producer (2): the shared conformance REFERENCE, over pragma's own generator.
|
|
74
|
+
*
|
|
75
|
+
* The fixture names its generator logically; `pickGenerator` is what turns that
|
|
76
|
+
* into the bundled definition. The suite never imports a generator itself —
|
|
77
|
+
* summon-core sits below both bins.
|
|
78
|
+
*/
|
|
82
79
|
async function produceSummon(
|
|
83
80
|
kind: CreateKind,
|
|
84
81
|
framework: string,
|
|
85
|
-
answers: Record<string, unknown
|
|
86
|
-
): Promise<
|
|
82
|
+
answers: Readonly<Record<string, unknown>>,
|
|
83
|
+
): Promise<TreeSnapshot> {
|
|
87
84
|
const { pickGenerator } = await import("./pickGenerator.js");
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
createGeneratorStamp,
|
|
93
|
-
createStampOnEffectStart,
|
|
94
|
-
} = await import("@canonical/summon-core");
|
|
95
|
-
const generator = pickGenerator(kind, { framework });
|
|
96
|
-
const dir = freshCwd();
|
|
97
|
-
await runGeneratorTask(
|
|
98
|
-
execute(generator, { prompt: autoPrompt(answers), params: answers }),
|
|
99
|
-
{
|
|
100
|
-
cwd: dir,
|
|
101
|
-
promptHandler: autoPrompt(answers),
|
|
102
|
-
onEffectStart: createStampOnEffectStart(createGeneratorStamp(generator)),
|
|
103
|
-
onLog: () => {},
|
|
104
|
-
},
|
|
105
|
-
);
|
|
106
|
-
return snapshot(dir);
|
|
85
|
+
return produceReference({
|
|
86
|
+
generator: pickGenerator(kind, { framework }),
|
|
87
|
+
answers,
|
|
88
|
+
});
|
|
107
89
|
}
|
|
108
90
|
|
|
109
|
-
/**
|
|
91
|
+
/**
|
|
92
|
+
* One golden case: the shared fixture, plus what only pragma knows — which
|
|
93
|
+
* bundled generator the fixture's id names, and the CLI param names the create
|
|
94
|
+
* verb takes for the same answers.
|
|
95
|
+
*/
|
|
110
96
|
interface Case {
|
|
111
|
-
readonly
|
|
97
|
+
readonly fixture: string;
|
|
112
98
|
readonly kind: CreateKind;
|
|
113
99
|
readonly framework: string;
|
|
114
100
|
readonly params: Record<string, unknown>;
|
|
115
|
-
readonly answers: Record<string, unknown>;
|
|
116
101
|
}
|
|
117
102
|
|
|
118
103
|
const componentAnswers = {
|
|
@@ -123,7 +108,7 @@ const componentAnswers = {
|
|
|
123
108
|
|
|
124
109
|
const cases: Case[] = [
|
|
125
110
|
{
|
|
126
|
-
|
|
111
|
+
fixture: "component/react",
|
|
127
112
|
kind: "component",
|
|
128
113
|
framework: "react",
|
|
129
114
|
params: {
|
|
@@ -131,10 +116,9 @@ const cases: Case[] = [
|
|
|
131
116
|
componentPath: "src/components/Button",
|
|
132
117
|
...componentAnswers,
|
|
133
118
|
},
|
|
134
|
-
answers: { componentPath: "src/components/Button", ...componentAnswers },
|
|
135
119
|
},
|
|
136
120
|
{
|
|
137
|
-
|
|
121
|
+
fixture: "component/svelte",
|
|
138
122
|
kind: "component",
|
|
139
123
|
framework: "svelte",
|
|
140
124
|
params: {
|
|
@@ -142,10 +126,9 @@ const cases: Case[] = [
|
|
|
142
126
|
componentPath: "src/lib/Button",
|
|
143
127
|
...componentAnswers,
|
|
144
128
|
},
|
|
145
|
-
answers: { componentPath: "src/lib/Button", ...componentAnswers },
|
|
146
129
|
},
|
|
147
130
|
{
|
|
148
|
-
|
|
131
|
+
fixture: "component/lit",
|
|
149
132
|
kind: "component",
|
|
150
133
|
framework: "lit",
|
|
151
134
|
params: {
|
|
@@ -153,10 +136,9 @@ const cases: Case[] = [
|
|
|
153
136
|
componentPath: "src/lib/Button",
|
|
154
137
|
...componentAnswers,
|
|
155
138
|
},
|
|
156
|
-
answers: { componentPath: "src/lib/Button", ...componentAnswers },
|
|
157
139
|
},
|
|
158
140
|
{
|
|
159
|
-
|
|
141
|
+
fixture: "package",
|
|
160
142
|
kind: "package",
|
|
161
143
|
framework: "",
|
|
162
144
|
params: {
|
|
@@ -169,24 +151,15 @@ const cases: Case[] = [
|
|
|
169
151
|
withPrTemplate: false,
|
|
170
152
|
runInstall: false,
|
|
171
153
|
},
|
|
172
|
-
answers: {
|
|
173
|
-
name: "@canonical/my-lib",
|
|
174
|
-
type: "library",
|
|
175
|
-
description: "A library.",
|
|
176
|
-
withReact: false,
|
|
177
|
-
withStorybook: false,
|
|
178
|
-
withCli: false,
|
|
179
|
-
withPrTemplate: false,
|
|
180
|
-
runInstall: false,
|
|
181
|
-
},
|
|
182
154
|
},
|
|
183
155
|
{
|
|
184
|
-
|
|
156
|
+
fixture: "application",
|
|
185
157
|
kind: "application",
|
|
186
158
|
framework: "",
|
|
187
|
-
// The pragma path receives the CLI `--with-X` params (AV-228 B8); the
|
|
188
|
-
//
|
|
189
|
-
// (`withSsr` -> `ssr`, ...) makes both produce the
|
|
159
|
+
// The pragma path receives the CLI `--with-X` params (AV-228 B8); the shared
|
|
160
|
+
// fixture carries the generator's bare prompt names. runCreate's boundary
|
|
161
|
+
// remap (`withSsr` -> `ssr`, ...) makes both produce the identical tree —
|
|
162
|
+
// which is exactly what this case exists to prove.
|
|
190
163
|
params: {
|
|
191
164
|
appPath: "my-app",
|
|
192
165
|
withSsr: true,
|
|
@@ -195,31 +168,36 @@ const cases: Case[] = [
|
|
|
195
168
|
withRelay: false,
|
|
196
169
|
runInstall: false,
|
|
197
170
|
},
|
|
198
|
-
answers: {
|
|
199
|
-
appPath: "my-app",
|
|
200
|
-
ssr: true,
|
|
201
|
-
router: true,
|
|
202
|
-
forms: true,
|
|
203
|
-
relay: false,
|
|
204
|
-
runInstall: false,
|
|
205
|
-
},
|
|
206
171
|
},
|
|
207
172
|
];
|
|
208
173
|
|
|
209
|
-
describe("byte-equality goldens — pragma create
|
|
174
|
+
describe("byte-equality goldens — pragma create \u2261 the conformance reference (PROTECTED)", () => {
|
|
175
|
+
it("covers every shared conformance fixture", () => {
|
|
176
|
+
// The suite's fixture list is the contract; a fixture added there and not
|
|
177
|
+
// wired here would silently go unchecked on pragma's side.
|
|
178
|
+
expect(cases.map((entry) => entry.fixture)).toEqual(
|
|
179
|
+
CONFORMANCE_FIXTURES.map((entry) => entry.name),
|
|
180
|
+
);
|
|
181
|
+
});
|
|
182
|
+
|
|
210
183
|
for (const testCase of cases) {
|
|
211
|
-
it(`${testCase.
|
|
184
|
+
it(`${testCase.fixture}: the kernel path matches the reference byte for byte`, async () => {
|
|
185
|
+
const shared = CONFORMANCE_FIXTURES.find(
|
|
186
|
+
(entry) => entry.name === testCase.fixture,
|
|
187
|
+
);
|
|
188
|
+
if (!shared) throw new Error(`no fixture "${testCase.fixture}"`);
|
|
212
189
|
const pragma = await producePragma(testCase.kind, testCase.params);
|
|
213
|
-
const
|
|
190
|
+
const reference = await produceSummon(
|
|
214
191
|
testCase.kind,
|
|
215
192
|
testCase.framework,
|
|
216
|
-
|
|
193
|
+
shared.answers,
|
|
217
194
|
);
|
|
218
195
|
expect(pragma.size).toBeGreaterThan(0);
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
196
|
+
const diff = diffTrees(pragma, reference);
|
|
197
|
+
expect(
|
|
198
|
+
isIdentical(diff),
|
|
199
|
+
formatTreeDiff(diff, "pragma create", "the conformance reference"),
|
|
200
|
+
).toBe(true);
|
|
223
201
|
});
|
|
224
202
|
}
|
|
225
203
|
});
|
|
@@ -24,7 +24,7 @@ import { mkdtempSync, readdirSync, readFileSync } from "node:fs";
|
|
|
24
24
|
import { tmpdir } from "node:os";
|
|
25
25
|
import { dirname, join, resolve } from "node:path";
|
|
26
26
|
import { fileURLToPath } from "node:url";
|
|
27
|
-
import {
|
|
27
|
+
import { describe, expect, it } from "vitest";
|
|
28
28
|
|
|
29
29
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
30
30
|
const repoRoot = resolve(here, "../../../../../..");
|
|
@@ -33,20 +33,11 @@ const pragmaBin = join(cliNextDir, "src/bin.ts");
|
|
|
33
33
|
const compiledBin = join(cliNextDir, "dist/pragma");
|
|
34
34
|
const freshCwd = (): string => mkdtempSync(join(tmpdir(), "pragma-compiled-"));
|
|
35
35
|
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
beforeAll
|
|
40
|
-
|
|
41
|
-
cwd: cliNextDir,
|
|
42
|
-
stdio: "pipe",
|
|
43
|
-
});
|
|
44
|
-
if (result.status !== 0) {
|
|
45
|
-
throw new Error(
|
|
46
|
-
`failed to build dist/pragma:\n${result.stderr?.toString() ?? ""}`,
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
}, 180_000);
|
|
36
|
+
// The binary is provisioned by `testing/perf/globalSetup.ts`, which rebuilds it
|
|
37
|
+
// whenever it is missing or older than `src/**`, `scripts/**`, `pragma.conf.ts`
|
|
38
|
+
// or `package.json` — so these tests exercise the current bundle + embedded
|
|
39
|
+
// manifest without a second `beforeAll` writing `dist/pragma` in place while
|
|
40
|
+
// another worker's test is spawning it.
|
|
50
41
|
|
|
51
42
|
/** Read a directory tree into a sorted map of relative path → contents. */
|
|
52
43
|
function snapshot(dir: string): Map<string, string> {
|
|
@@ -108,6 +99,52 @@ describe("compiled pragma create component (PROTECTED)", () => {
|
|
|
108
99
|
}
|
|
109
100
|
});
|
|
110
101
|
|
|
102
|
+
/**
|
|
103
|
+
* PROTECTED — the compiled-binary `create` gate.
|
|
104
|
+
*
|
|
105
|
+
* `create component` runs from the binary (the describe above proves it byte for
|
|
106
|
+
* byte) because summon-component routes every template read through
|
|
107
|
+
* `loadTemplateSync` and passes `content:` into `template()`. `create package` /
|
|
108
|
+
* `create application` do NOT: they call `template({ source })`, summon-core
|
|
109
|
+
* falls through to `readFile(options.source)`, and the run dies with `ENOENT …
|
|
110
|
+
* /$bunfs/templates/package.json.ejs` AFTER `mkdir` has already run — a
|
|
111
|
+
* half-made package left on the user's disk. Measured against a real
|
|
112
|
+
* `dist/pragma` with the gate lifted, for both nouns.
|
|
113
|
+
*
|
|
114
|
+
* A `--dry-run` does NOT test this: it exits 0 without reading a template and
|
|
115
|
+
* merely PRINTS `Read file: /$bunfs/templates/package.json.ejs` as a planned
|
|
116
|
+
* effect. That false positive is why the gate has to be pinned by a real run.
|
|
117
|
+
*
|
|
118
|
+
* The `readdirSync(dir)` assertion is the load-bearing one: it fails with the
|
|
119
|
+
* real symptom if the gate is ever lifted without fixing the generators.
|
|
120
|
+
*/
|
|
121
|
+
describe("compiled pragma create gate (PROTECTED)", () => {
|
|
122
|
+
// A LITERAL noun list, deliberately not derived from `create`'s own
|
|
123
|
+
// declaration: changing the gate's input must turn this red rather than
|
|
124
|
+
// silently drop the case.
|
|
125
|
+
for (const kind of ["package", "application"] as const) {
|
|
126
|
+
it(`refuses \`create ${kind}\` and leaves the cwd untouched`, () => {
|
|
127
|
+
const dir = freshCwd();
|
|
128
|
+
const result = spawnSync(compiledBin, ["create", kind, "--yes"], {
|
|
129
|
+
cwd: dir,
|
|
130
|
+
stdio: "pipe",
|
|
131
|
+
encoding: "utf-8",
|
|
132
|
+
});
|
|
133
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
|
|
134
|
+
// 1, not merely non-zero: UNSUPPORTED is a runtime refusal, which
|
|
135
|
+
// `kernel/error/constants.ts` maps to the generic runtime exit.
|
|
136
|
+
expect(result.status).toBe(1);
|
|
137
|
+
expect(output).toContain("not available in the compiled pragma binary");
|
|
138
|
+
// The clean refusal, not the crash it exists to prevent.
|
|
139
|
+
expect(output).not.toMatch(/ENOENT/);
|
|
140
|
+
expect(output).not.toMatch(/Internal error/);
|
|
141
|
+
// And nothing half-made on disk: the generator's `mkdir` effects run
|
|
142
|
+
// BEFORE its first template read, so a lifted gate leaves a stub tree.
|
|
143
|
+
expect(readdirSync(dir)).toEqual([]);
|
|
144
|
+
}, 30_000);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
111
148
|
/**
|
|
112
149
|
* PROTECTED — the compiled-binary READ guard (U1-orig).
|
|
113
150
|
*
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The generator bindings the `create` surface exposes — the one list the
|
|
3
|
+
* surface's mechanical copies derive from: the `CreateKind` union (`types.ts`),
|
|
4
|
+
* the `--framework` enum and the compiled-binary gate (`create.verb.ts`), the
|
|
5
|
+
* generator lookup (`pickGenerator.ts`), and the template roots the bundler
|
|
6
|
+
* embeds (`scripts/build.ts`). Prose is NOT derived: the verb summaries below
|
|
7
|
+
* `createVerbs` and `capabilities/hints.ts` still name the frameworks by hand.
|
|
8
|
+
*
|
|
9
|
+
* This table is the SINGLE authoring point for which generator packages the
|
|
10
|
+
* distribution ships: it binds each `create` noun to the generator-map key it
|
|
11
|
+
* runs, and — for the one generator whose templates the binary carries — to
|
|
12
|
+
* the package `scripts/build.ts` harvests them from. (A parallel `generators`
|
|
13
|
+
* CONFIG field once declared the same packages; it was validated, layered and
|
|
14
|
+
* read by nothing, and was removed under the L-OPEN-1 ruling — the config
|
|
15
|
+
* validator now rejects it loudly.) The binding is hand-written because
|
|
16
|
+
* neither half can be discovered:
|
|
17
|
+
* - surfacing a noun also needs a hand-written prompt mirror, path param and
|
|
18
|
+
* examples in `create.verb.ts`, so the surface is a deliberate SUBSET —
|
|
19
|
+
* `@canonical/summon-application` ships `application/react`, `domain`, `route`
|
|
20
|
+
* and `wrapper`, and `create` exposes one of them;
|
|
21
|
+
* - `bun build --compile` bundles only statically analysable import specifiers,
|
|
22
|
+
* so a shipped binary can never `import(name)` a declared package (measured:
|
|
23
|
+
* `Cannot find module '@canonical/summon-component' from '/$bunfs/root/…'`).
|
|
24
|
+
* `pickGenerator` must import all three statically.
|
|
25
|
+
*
|
|
26
|
+
* `create.test.ts` pins what is checkable: every binding resolves to the
|
|
27
|
+
* generator it names, and the embedded manifest carries only the templates of
|
|
28
|
+
* the binding that reads through it.
|
|
29
|
+
*
|
|
30
|
+
* Deliberately free of any import: `create.verb.ts` reads this on the `--help` /
|
|
31
|
+
* `__complete` fast path (`capabilities/lazy.test.ts`).
|
|
32
|
+
*/
|
|
33
|
+
export const CREATE_GENERATORS = {
|
|
34
|
+
component: {
|
|
35
|
+
/**
|
|
36
|
+
* The declaring package: `scripts/build.ts` harvests its `.ejs` for the
|
|
37
|
+
* binary.
|
|
38
|
+
*/
|
|
39
|
+
name: "@canonical/summon-component",
|
|
40
|
+
/** `--framework <f>` runs `component/<f>`; the FIRST is the enum default. */
|
|
41
|
+
frameworks: ["react", "svelte", "lit"],
|
|
42
|
+
/**
|
|
43
|
+
* Runs from the compiled binary: this generator routes every template read
|
|
44
|
+
* through `loadTemplateSync` and passes `content:` into `template()`, so the
|
|
45
|
+
* embedded manifest serves it. That — not the templates' file extensions —
|
|
46
|
+
* is the property that decides it, and it is a fact about the GENERATOR's
|
|
47
|
+
* source, not about what this build chooses to embed.
|
|
48
|
+
*/
|
|
49
|
+
readsEmbeddedTemplates: true,
|
|
50
|
+
},
|
|
51
|
+
package: {
|
|
52
|
+
/** The generator-map key `create package` runs. */
|
|
53
|
+
key: "package",
|
|
54
|
+
/**
|
|
55
|
+
* NOT runnable from the compiled binary: this generator calls
|
|
56
|
+
* `template({ source })` with no `content:`, so summon-core falls through to
|
|
57
|
+
* `readFile(options.source)`. Measured against a real `dist/pragma` with the
|
|
58
|
+
* gate lifted: `ENOENT: no such file or directory, open
|
|
59
|
+
* '/$bunfs/templates/package.json.ejs'`, after `mkdir` had already created
|
|
60
|
+
* `my-lib/` and `my-lib/src/` — a half-made package left on disk. A
|
|
61
|
+
* `--dry-run` exits 0 without reading a template, so it does NOT test this.
|
|
62
|
+
*/
|
|
63
|
+
readsEmbeddedTemplates: false,
|
|
64
|
+
},
|
|
65
|
+
application: {
|
|
66
|
+
/** The generator-map key `create application` runs. */
|
|
67
|
+
key: "application/react",
|
|
68
|
+
/**
|
|
69
|
+
* NOT runnable from the compiled binary — same cause as `package` (measured:
|
|
70
|
+
* `ENOENT … '/$bunfs/root/templates/package.json.ejs'`, after the app
|
|
71
|
+
* directory was created). It also `copy()`s non-`.ejs` assets.
|
|
72
|
+
*/
|
|
73
|
+
readsEmbeddedTemplates: false,
|
|
74
|
+
},
|
|
75
|
+
} as const;
|
|
@@ -14,7 +14,7 @@ import { bootRuntime } from "../../kernel/runtime/boot.js";
|
|
|
14
14
|
import type { GlobalFlags } from "../../kernel/runtime/types.js";
|
|
15
15
|
import type { VerbSpec } from "../../kernel/spec/types.js";
|
|
16
16
|
import { projectMcp } from "../../testing/helpers/projectMcp.js";
|
|
17
|
-
import {
|
|
17
|
+
import { createVerbs } from "./create.verb.js";
|
|
18
18
|
import { createModule } from "./index.js";
|
|
19
19
|
|
|
20
20
|
const FLAGS: GlobalFlags = {
|
|
@@ -117,7 +117,7 @@ describe("create over MCP (PROTECTED)", () => {
|
|
|
117
117
|
const mcpPlan = (mcpResult.data as { plan: string[] }).plan;
|
|
118
118
|
|
|
119
119
|
const cliOutcome = await executeVerb(
|
|
120
|
-
|
|
120
|
+
createVerbs.component as VerbSpec,
|
|
121
121
|
{
|
|
122
122
|
framework: "react",
|
|
123
123
|
componentPath: "src/components/Button",
|