@canonical/pragma-cli 0.30.0 → 0.31.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 +64 -35
- package/dist/pragma +0 -0
- package/package.json +15 -13
- package/src/completions/constants.ts +7 -0
- package/src/completions/handleQuery.test.ts +32 -0
- package/src/completions/handleQuery.ts +20 -1
- package/src/completions/queryCompletions.test.ts +5 -0
- package/src/completions/queryCompletions.ts +17 -3
- package/src/completions/querySocket.test.ts +31 -0
- package/src/completions/querySocket.ts +36 -9
- package/src/config/parseConfigValues.ts +2 -0
- package/src/config/readConfigLayers.test.ts +2 -0
- package/src/config/readConfigLayers.ts +3 -0
- package/src/config/types.ts +7 -0
- package/src/constants.ts +3 -1
- package/src/domains/block/commands/sample.ts +2 -1
- package/src/domains/block/formatters/list.test.ts +3 -3
- package/src/domains/block/formatters/lookup.test.ts +5 -5
- package/src/domains/block/orchestration/enrichBlocks.ts +6 -3
- package/src/domains/block/stories.ts +9 -3
- package/src/domains/config/commands/channel.test.ts +16 -0
- package/src/domains/config/commands/channel.ts +14 -1
- package/src/domains/config/commands/framework.test.ts +14 -0
- package/src/domains/config/commands/framework.ts +13 -1
- package/src/domains/config/commands/tier.test.ts +7 -0
- package/src/domains/config/commands/tier.ts +14 -1
- package/src/domains/config/operations/validateTier.test.ts +9 -0
- package/src/domains/config/operations/validateTier.ts +10 -2
- package/src/domains/create/generatorPack/bundled.ts +49 -0
- package/src/domains/create/generatorPack/compileGeneratorPack.test.ts +159 -0
- package/src/domains/create/generatorPack/compileGeneratorPack.ts +312 -0
- package/src/domains/create/index.ts +13 -7
- package/src/domains/create/mcp/specs.ts +6 -161
- package/src/domains/create/renderGeneratorUi.test.ts +103 -0
- package/src/domains/create/renderGeneratorUi.ts +65 -0
- package/src/domains/doctor/formatters/doctor.test.ts +50 -2
- package/src/domains/doctor/formatters/doctor.ts +90 -38
- package/src/domains/doctor/operations/checks/checkMcpCommands.ts +9 -6
- package/src/domains/doctor/operations/checks/checkPackageRefs.ts +30 -12
- package/src/domains/doctor/operations/checks.test.ts +6 -4
- package/src/domains/doctor/operations/types.ts +23 -1
- package/src/domains/graph/commands/inspect.test.ts +2 -2
- package/src/domains/graph/commands/query.test.ts +5 -3
- package/src/domains/graph/formatters/inspect.ts +7 -7
- package/src/domains/graph/formatters/query.test.ts +87 -0
- package/src/domains/graph/formatters/query.ts +42 -5
- package/src/domains/graph/operations/inspectUri.test.ts +5 -5
- package/src/domains/graphql/commands/serve.test.ts +12 -6
- package/src/domains/graphql/commands/serve.ts +16 -6
- package/src/domains/graphql/helpers/parsePrefixes.test.ts +3 -3
- package/src/domains/graphql/helpers/parsePrefixes.ts +3 -3
- package/src/domains/graphql/helpers/resolveSourceFiles.test.ts +9 -0
- package/src/domains/graphql/helpers/resolveSourceFiles.ts +24 -3
- package/src/domains/graphql/operations/compileSchema.test.ts +2 -2
- package/src/domains/graphql/operations/createSchemaServer.test.ts +2 -2
- package/src/domains/graphql/operations/ensureSchemaArtifact.test.ts +167 -0
- package/src/domains/graphql/operations/ensureSchemaArtifact.ts +178 -0
- package/src/domains/graphql/operations/index.ts +5 -0
- package/src/domains/info/formatters/upgrade.test.ts +7 -4
- package/src/domains/info/formatters/upgrade.ts +3 -1
- package/src/domains/info/operations/collectEntityCounts.test.ts +156 -0
- package/src/domains/info/operations/collectEntityCounts.ts +123 -0
- package/src/domains/info/operations/index.ts +1 -0
- package/src/domains/info/types.ts +13 -0
- package/src/domains/llm/capabilities.test.ts +23 -3
- package/src/domains/llm/commands/capabilities.ts +12 -3
- package/src/domains/llm/commands/llm.ts +5 -1
- package/src/domains/llm/data/commandReference.ts +20 -11
- package/src/domains/llm/data/decisionTrees.ts +10 -5
- package/src/domains/llm/data/toolCatalog.ts +49 -18
- package/src/domains/llm/formatters/orientation.ts +5 -1
- package/src/domains/llm/llmCommand.test.ts +11 -3
- package/src/domains/llm/mcp/specs.ts +3 -4
- package/src/domains/llm/operations/collectContext.test.ts +64 -0
- package/src/domains/llm/operations/collectContext.ts +7 -17
- package/src/domains/modifier/commands/sample.ts +2 -1
- package/src/domains/modifier/operations/lookup.ts +5 -3
- package/src/domains/modifier/stories.ts +9 -0
- package/src/domains/ontology/commands/list.test.ts +5 -3
- package/src/domains/ontology/commands/show.test.ts +85 -9
- package/src/domains/ontology/formatters/index.ts +1 -0
- package/src/domains/ontology/formatters/list.ts +37 -12
- package/src/domains/ontology/formatters/show.ts +358 -67
- package/src/domains/ontology/helpers/buildClassTree.ts +89 -0
- package/src/domains/ontology/helpers/completeClassNames.ts +43 -0
- package/src/domains/ontology/helpers/expandOntologyIris.ts +79 -0
- package/src/domains/ontology/helpers/index.ts +14 -0
- package/src/domains/ontology/helpers/queryClasses.ts +32 -16
- package/src/domains/ontology/helpers/queryProperties.ts +39 -6
- package/src/domains/ontology/helpers/queryTboxFacts.ts +160 -0
- package/src/domains/ontology/operations/listOntologies.test.ts +80 -3
- package/src/domains/ontology/operations/listOntologies.ts +126 -30
- package/src/domains/ontology/operations/showOntology.test.ts +131 -12
- package/src/domains/ontology/operations/showOntology.ts +289 -7
- package/src/domains/ontology/operations/showOntologyRaw.test.ts +4 -2
- package/src/domains/ontology/stories.ts +53 -8
- package/src/domains/refs/operations/updateRefs.ts +0 -1
- package/src/domains/setup/commands/index.ts +1 -0
- package/src/domains/setup/commands/setup.ts +70 -0
- package/src/domains/setup/index.ts +7 -3
- package/src/domains/setup/operations/setupLsp.test.ts +3 -2
- package/src/domains/setup/operations/setupLsp.ts +7 -2
- package/src/domains/shared/bootStore.bareCore.test.ts +63 -0
- package/src/domains/shared/bootStore.test.ts +82 -0
- package/src/domains/shared/bootStore.ts +56 -20
- package/src/domains/shared/emptyErrorRecovery.test.ts +23 -26
- package/src/domains/shared/extractLocalName.test.ts +3 -3
- package/src/domains/shared/formatters.ts +23 -2
- package/src/domains/shared/listTiers.ts +40 -0
- package/src/domains/shared/loaders/bundledLoader.ts +48 -0
- package/src/domains/shared/loaders/gitLoader.ts +1 -2
- package/src/domains/shared/loaders/localLoader.ts +2 -18
- package/src/domains/shared/loaders/readPackageDir.test.ts +58 -0
- package/src/domains/shared/loaders/readPackageDir.ts +46 -8
- package/src/domains/shared/lookupMany.test.ts +15 -0
- package/src/domains/shared/lookupMany.ts +22 -6
- package/src/domains/shared/namespaces.ts +13 -5
- package/src/domains/shared/parseSampleCount.test.ts +47 -0
- package/src/domains/shared/parseSampleCount.ts +45 -0
- package/src/domains/shared/prefixes.test.ts +211 -0
- package/src/domains/shared/prefixes.ts +182 -15
- package/src/domains/shared/renderers.ts +5 -5
- package/src/domains/shared/resolveLookupExitCode.test.ts +48 -0
- package/src/domains/shared/resolveLookupExitCode.ts +42 -0
- package/src/domains/shared/semanticPackage.test.ts +89 -0
- package/src/domains/shared/semanticPackage.ts +17 -2
- package/src/domains/shared/stories/compileLookupCommand.ts +3 -1
- package/src/domains/shared/stories/compileLookupTool.ts +1 -1
- package/src/domains/shared/stories/pack/applyPackFilters.test.ts +49 -0
- package/src/domains/shared/stories/pack/applyPackFilters.ts +46 -11
- package/src/domains/shared/stories/pack/applyPackSearch.test.ts +57 -0
- package/src/domains/shared/stories/pack/applyPackSearch.ts +41 -0
- package/src/domains/shared/stories/pack/buildLookupQuery.test.ts +30 -0
- package/src/domains/shared/stories/pack/buildLookupQuery.ts +75 -1
- package/src/domains/shared/stories/pack/bundled/index.ts +23 -0
- package/src/domains/shared/stories/pack/bundled/standardPack.ts +164 -0
- package/src/domains/shared/stories/pack/bundled/tierPack.ts +50 -0
- package/src/domains/shared/stories/pack/collectPackStories.test.ts +305 -7
- package/src/domains/shared/stories/pack/collectPackStories.ts +90 -8
- package/src/domains/shared/stories/pack/compilePackCommands.ts +17 -7
- package/src/domains/shared/stories/pack/compilePackStories.test.ts +607 -2
- package/src/domains/shared/stories/pack/compilePackStories.ts +544 -73
- package/src/domains/shared/stories/pack/compilePackToolSpecs.ts +20 -8
- package/src/domains/shared/stories/pack/index.ts +14 -0
- package/src/domains/shared/stories/pack/reservedVerbs.test.ts +213 -0
- package/src/domains/shared/stories/pack/reservedVerbs.ts +170 -0
- package/src/domains/shared/stories/pack/standardParity.test.ts +293 -220
- package/src/domains/shared/stories/pack/types.ts +123 -3
- package/src/domains/shared/stories/pack/validateStoryPackDefinition.test.ts +502 -0
- package/src/domains/shared/stories/pack/validateStoryPackDefinition.ts +475 -24
- package/src/domains/shared/stories/types.ts +9 -1
- package/src/domains/shared/types/index.ts +5 -9
- package/src/domains/shared/types/ontology.ts +119 -24
- package/src/domains/shared/types/standard.ts +9 -52
- package/src/domains/token/commands/lookup.test.ts +8 -0
- package/src/domains/token/commands/lookup.ts +12 -0
- package/src/domains/token/commands/sample.ts +2 -1
- package/src/domains/token/featureFlag.ts +17 -0
- package/src/domains/token/index.ts +5 -0
- package/src/domains/token/mcp/specs.ts +13 -1
- package/src/domains/trace/commands/resolveTraceLimit.test.ts +31 -0
- package/src/domains/trace/commands/show.ts +30 -4
- package/src/index.ts +1 -13
- package/src/mcp/registerResources.test.ts +2 -2
- package/src/mcp/resources/pickPrimaryType.test.ts +5 -5
- package/src/mcp/tools/index.ts +18 -10
- package/src/mcp/tools/registerTools.test.ts +106 -30
- package/src/pipeline/collectCommands.test.ts +151 -1
- package/src/pipeline/collectCommands.ts +34 -19
- package/src/pipeline/createProgram.test.ts +5 -1
- package/src/pipeline/createProgram.ts +2 -2
- package/src/pipeline/firstRun.test.ts +80 -0
- package/src/pipeline/firstRun.ts +99 -0
- package/src/pipeline/parseGlobalFlags.test.ts +102 -1
- package/src/pipeline/parseGlobalFlags.ts +98 -9
- package/src/pipeline/resolveCommandKind.test.ts +12 -0
- package/src/pipeline/resolveCommandKind.ts +4 -0
- package/src/pipeline/rootHelp.test.ts +87 -0
- package/src/pipeline/rootHelp.ts +265 -0
- package/src/pipeline/runCli.orchestration.test.ts +28 -12
- package/src/pipeline/runCli.test.ts +17 -4
- package/src/pipeline/runCli.ts +162 -19
- package/src/testing/cli.ts +30 -2
- package/src/testing/dsFixtures.ts +21 -4
- package/src/testing/index.ts +1 -4
- package/src/testing/integration/__snapshots__/executor-golden.test.ts.snap +4 -4
- package/src/testing/integration/cli-user-stories.test.ts +63 -0
- package/src/testing/integration/cross-binary-byte-equality.test.ts +2 -2
- package/src/testing/integration/edge-cases.test.ts +62 -50
- package/src/testing/integration/executor-execution.test.ts +6 -4
- package/src/testing/integration/executor-golden.test.ts +2 -2
- package/src/testing/integration/mcp-surface.test.ts +40 -15
- package/src/testing/integration/parity.test.ts +119 -70
- package/src/testing/setupXdgIsolation.ts +16 -5
- package/src/testing/standardParityFixtures.ts +26 -83
- package/src/testing/store.ts +4 -4
- package/src/domains/create/commands/component.test.ts +0 -166
- package/src/domains/create/commands/component.ts +0 -123
- package/src/domains/create/commands/index.ts +0 -3
- package/src/domains/create/commands/package.test.ts +0 -35
- package/src/domains/create/commands/package.ts +0 -21
- package/src/domains/create/generators.ts +0 -8
- package/src/domains/standard/commands/categories.test.ts +0 -70
- package/src/domains/standard/commands/categories.ts +0 -48
- package/src/domains/standard/commands/index.ts +0 -6
- package/src/domains/standard/commands/list.test.ts +0 -105
- package/src/domains/standard/commands/list.ts +0 -11
- package/src/domains/standard/commands/lookup.test.ts +0 -135
- package/src/domains/standard/commands/lookup.ts +0 -14
- package/src/domains/standard/commands/sample.ts +0 -57
- package/src/domains/standard/formatters/categories.test.ts +0 -44
- package/src/domains/standard/formatters/categories.ts +0 -38
- package/src/domains/standard/formatters/index.ts +0 -10
- package/src/domains/standard/formatters/list.test.ts +0 -169
- package/src/domains/standard/formatters/list.ts +0 -184
- package/src/domains/standard/formatters/lookup.test.ts +0 -95
- package/src/domains/standard/formatters/lookup.ts +0 -101
- package/src/domains/standard/formatters/sample.ts +0 -15
- package/src/domains/standard/formatters/types.ts +0 -31
- package/src/domains/standard/index.ts +0 -40
- package/src/domains/standard/mcp/index.ts +0 -2
- package/src/domains/standard/mcp/specs.ts +0 -89
- package/src/domains/standard/operations/categories.test.ts +0 -61
- package/src/domains/standard/operations/categories.ts +0 -38
- package/src/domains/standard/operations/index.ts +0 -6
- package/src/domains/standard/operations/list.test.ts +0 -75
- package/src/domains/standard/operations/list.ts +0 -64
- package/src/domains/standard/operations/lookup.test.ts +0 -58
- package/src/domains/standard/operations/lookup.ts +0 -151
- package/src/domains/standard/operations/sample.ts +0 -35
- package/src/domains/standard/orchestration/buildStandardFilters.ts +0 -11
- package/src/domains/standard/orchestration/index.ts +0 -4
- package/src/domains/standard/orchestration/resolveStandardList.ts +0 -45
- package/src/domains/standard/orchestration/resolveStandardLookup.ts +0 -33
- package/src/domains/standard/orchestration/standardEmptyError.test.ts +0 -33
- package/src/domains/standard/orchestration/standardEmptyError.ts +0 -29
- package/src/domains/standard/standardConfig.ts +0 -42
- package/src/domains/standard/stories.test.ts +0 -24
- package/src/domains/standard/stories.ts +0 -151
- package/src/domains/standard/types.ts +0 -15
- package/src/domains/tier/commands/index.ts +0 -3
- package/src/domains/tier/commands/list.test.ts +0 -67
- package/src/domains/tier/commands/list.ts +0 -9
- package/src/domains/tier/formatters/index.ts +0 -3
- package/src/domains/tier/formatters/list.test.ts +0 -33
- package/src/domains/tier/formatters/list.ts +0 -45
- package/src/domains/tier/index.ts +0 -17
- package/src/domains/tier/mcp/index.ts +0 -2
- package/src/domains/tier/mcp/specs.ts +0 -12
- package/src/domains/tier/operations/index.ts +0 -3
- package/src/domains/tier/operations/list.test.ts +0 -32
- package/src/domains/tier/operations/list.ts +0 -33
- package/src/domains/tier/stories.test.ts +0 -22
- package/src/domains/tier/stories.ts +0 -52
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wires the top-level `pragma setup` command.
|
|
3
|
+
*
|
|
4
|
+
* `setup` is one level more general than `create <thing>`: it configures the
|
|
5
|
+
* whole local environment (completions, LSP, MCP). Unlike `create`, its steps
|
|
6
|
+
* perform *read* effects whose results drive control flow — detecting AI
|
|
7
|
+
* harnesses, checking what is already installed — so it must run for real and
|
|
8
|
+
* report the real outcome. It therefore executes through {@link runSetupTask}
|
|
9
|
+
* (real execution, streamed output) rather than the generator preview-then-
|
|
10
|
+
* execute path, which would render a dry-run preview whose stubbed reads report
|
|
11
|
+
* the wrong state (e.g. "no harnesses detected").
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { CommandDefinition, CommandResult } from "@canonical/cli-core";
|
|
15
|
+
import runSetupTask from "../helpers/runSetupTask.js";
|
|
16
|
+
import setupAll from "../operations/setupAll.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Build the top-level `pragma setup` command definition.
|
|
20
|
+
*
|
|
21
|
+
* @returns The command definition for `pragma setup`.
|
|
22
|
+
* @note Impure
|
|
23
|
+
*/
|
|
24
|
+
export default function buildSetupCommand(): CommandDefinition {
|
|
25
|
+
return {
|
|
26
|
+
path: ["setup"],
|
|
27
|
+
description:
|
|
28
|
+
"Configure this project's environment: completions, LSP, and MCP",
|
|
29
|
+
parameters: [
|
|
30
|
+
{
|
|
31
|
+
name: "dryRun",
|
|
32
|
+
description: "Preview changes without writing",
|
|
33
|
+
type: "boolean",
|
|
34
|
+
default: false,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "undo",
|
|
38
|
+
description: "Reverse a previous setup",
|
|
39
|
+
type: "boolean",
|
|
40
|
+
default: false,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "yes",
|
|
44
|
+
description: "Skip all confirmation prompts",
|
|
45
|
+
type: "boolean",
|
|
46
|
+
default: false,
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
meta: {
|
|
50
|
+
examples: [
|
|
51
|
+
"pragma setup",
|
|
52
|
+
"pragma setup --yes",
|
|
53
|
+
"pragma setup --dry-run",
|
|
54
|
+
"pragma setup --undo",
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
execute: async (
|
|
58
|
+
params: Record<string, unknown>,
|
|
59
|
+
ctx,
|
|
60
|
+
): Promise<CommandResult> =>
|
|
61
|
+
runSetupTask(setupAll(ctx.cwd), {
|
|
62
|
+
dryRun: params.dryRun === true,
|
|
63
|
+
undo: params.undo === true,
|
|
64
|
+
yes: params.yes === true,
|
|
65
|
+
verbose: ctx.globalFlags.verbose,
|
|
66
|
+
llm: ctx.globalFlags.llm,
|
|
67
|
+
format: ctx.globalFlags.format,
|
|
68
|
+
}),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -2,21 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
import type { CommandDefinition } from "@canonical/cli-core";
|
|
4
4
|
import {
|
|
5
|
-
allCommand,
|
|
6
5
|
completionsCommand,
|
|
7
6
|
lspCommand,
|
|
8
7
|
mcpCommand,
|
|
8
|
+
setupCommand,
|
|
9
9
|
skillsCommand,
|
|
10
10
|
} from "./commands/index.js";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Return all setup command definitions.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
15
|
+
* The top-level `pragma setup` is a summon-generator-backed command (replacing
|
|
16
|
+
* the former `setup all`); the granular `setup <step>` verbs remain for
|
|
17
|
+
* targeted, scriptable configuration of a single concern.
|
|
18
|
+
*
|
|
19
|
+
* @returns An array of CommandDefinitions for the setup command and its verbs.
|
|
16
20
|
*/
|
|
17
21
|
export function commands(): CommandDefinition[] {
|
|
18
22
|
return [
|
|
19
|
-
|
|
23
|
+
setupCommand(),
|
|
20
24
|
lspCommand,
|
|
21
25
|
mcpCommand,
|
|
22
26
|
completionsCommand,
|
|
@@ -18,8 +18,9 @@ describe("setupLsp", () => {
|
|
|
18
18
|
it("logs info messages before and after exec", () => {
|
|
19
19
|
const result = dryRun(setupLsp("/project"));
|
|
20
20
|
const logs = result.effects.filter((e) => e._tag === "Log");
|
|
21
|
-
expect(logs.some((l) => l.message.includes("
|
|
22
|
-
|
|
21
|
+
expect(logs.some((l) => l.message.includes("Ensuring"))).toBe(true);
|
|
22
|
+
// Reports an idempotent "up to date" outcome, not a fresh install claim.
|
|
23
|
+
expect(logs.some((l) => l.message.includes("up to date"))).toBe(true);
|
|
23
24
|
});
|
|
24
25
|
|
|
25
26
|
it("collects effects without execution", () => {
|
|
@@ -10,8 +10,13 @@ import { $, exec, gen, info, type Task } from "@canonical/task";
|
|
|
10
10
|
*/
|
|
11
11
|
export default function setupLsp(root: string): Task<void> {
|
|
12
12
|
return gen(function* () {
|
|
13
|
-
yield* $(
|
|
13
|
+
yield* $(
|
|
14
|
+
info("Ensuring the Terrazzo LSP VS Code extension is installed..."),
|
|
15
|
+
);
|
|
16
|
+
// The extension installer is idempotent — it installs if missing and is a
|
|
17
|
+
// no-op if already present — so this reports "ensured", not a fresh install
|
|
18
|
+
// (pragma does not inspect VS Code's extension state to tell them apart).
|
|
14
19
|
yield* $(exec("bunx", ["@canonical/terrazzo-lsp-extension"], root));
|
|
15
|
-
yield* $(info("✓ Terrazzo LSP extension installed."));
|
|
20
|
+
yield* $(info("✓ Terrazzo LSP extension is installed (up to date)."));
|
|
16
21
|
});
|
|
17
22
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bare-core boot — pragma must serve the generic `graph`, `graphql`, and
|
|
3
|
+
* `ontology` surfaces with zero semantic packages loaded. This pins the P0
|
|
4
|
+
* invariant that the core no longer depends on the design-system packages:
|
|
5
|
+
* a store booted with no packages resolves generic queries and each
|
|
6
|
+
* generic operation returns cleanly instead of crashing.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Store } from "@canonical/ke";
|
|
10
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
11
|
+
import { EX_NAMESPACE, GRAPHQL_CLEAN_TTL } from "#testing";
|
|
12
|
+
import inspectUri from "../graph/operations/inspectUri.js";
|
|
13
|
+
import compileSchema from "../graphql/operations/compileSchema.js";
|
|
14
|
+
import listOntologies from "../ontology/operations/listOntologies.js";
|
|
15
|
+
import { bootStore } from "./bootStore.js";
|
|
16
|
+
import { PREFIX_MAP, resolvePrefixes } from "./prefixes.js";
|
|
17
|
+
|
|
18
|
+
let store: Store;
|
|
19
|
+
|
|
20
|
+
beforeAll(async () => {
|
|
21
|
+
// The `sources` override boots with zero resolved packages (bare core).
|
|
22
|
+
const boot = await bootStore({ sources: [] });
|
|
23
|
+
store = boot.store;
|
|
24
|
+
expect(boot.packages).toEqual([]);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterAll(() => store?.dispose());
|
|
28
|
+
|
|
29
|
+
describe("bare-core boot", () => {
|
|
30
|
+
it("carries the generic core prefixes on the store", () => {
|
|
31
|
+
expect(store.prefixes.rdf).toBe(PREFIX_MAP.rdf);
|
|
32
|
+
expect(store.prefixes.owl).toBe(PREFIX_MAP.owl);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("serves generic SPARQL without any packages loaded", async () => {
|
|
36
|
+
const result = await store.query(
|
|
37
|
+
"SELECT (COUNT(*) AS ?n) WHERE { ?s ?p ?o }",
|
|
38
|
+
);
|
|
39
|
+
expect(result.type).toBe("select");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("serves `ontology` — listOntologies returns cleanly, not a crash", async () => {
|
|
43
|
+
await expect(listOntologies(store)).resolves.toEqual([]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("serves `graph` — inspecting an unknown URI reports not-found, not a crash", async () => {
|
|
47
|
+
await expect(
|
|
48
|
+
inspectUri(store, "https://example.org/nothing"),
|
|
49
|
+
).rejects.toMatchObject({ code: "ENTITY_NOT_FOUND" });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("serves `graphql` — compiles a generic ontology with core-only prefixes", async () => {
|
|
53
|
+
const outcome = await compileSchema({
|
|
54
|
+
sources: [
|
|
55
|
+
{ content: GRAPHQL_CLEAN_TTL, format: "turtle", path: "x.ttl" },
|
|
56
|
+
],
|
|
57
|
+
// Package-style prefix layered over the bare core — no DS packages.
|
|
58
|
+
prefixes: resolvePrefixes([{ prefixes: { ex: EX_NAMESPACE } }]),
|
|
59
|
+
});
|
|
60
|
+
expect(outcome.status).toBe("ok");
|
|
61
|
+
expect(outcome.compiled?.sdl).toContain("type Thing");
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { createStore } from "@canonical/ke";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { createGraphLoaderPlugin } from "./bootStore.js";
|
|
4
|
+
|
|
5
|
+
const PREFIXES = { ds: "https://ds.canonical.com/" };
|
|
6
|
+
|
|
7
|
+
const goodGraph = (path: string, subject: string) => ({
|
|
8
|
+
path,
|
|
9
|
+
content: `@prefix ds: <https://ds.canonical.com/>.\nds:${subject} a ds:Component.\n`,
|
|
10
|
+
format: "turtle" as const,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
/** Mirrors the malformed `ds:hasSubcomponent ds:global..` shipped by upstream. */
|
|
14
|
+
const badGraph = (path: string) => ({
|
|
15
|
+
path,
|
|
16
|
+
content:
|
|
17
|
+
"@prefix ds: <https://ds.canonical.com/>.\nds:global.component.broken a ds:Component;\n ds:hasSubcomponent ds:global...\n",
|
|
18
|
+
format: "turtle" as const,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
async function countTriples(
|
|
22
|
+
graphs: readonly ReturnType<typeof goodGraph>[],
|
|
23
|
+
): Promise<number> {
|
|
24
|
+
const store = await createStore({
|
|
25
|
+
sources: [],
|
|
26
|
+
prefixes: PREFIXES,
|
|
27
|
+
plugins: [createGraphLoaderPlugin(graphs)],
|
|
28
|
+
});
|
|
29
|
+
// Always dispose the WASM-backed store so it can't leak across tests.
|
|
30
|
+
try {
|
|
31
|
+
const result = await store.query(
|
|
32
|
+
"SELECT (COUNT(*) AS ?n) WHERE { ?s ?p ?o }",
|
|
33
|
+
);
|
|
34
|
+
return Number(
|
|
35
|
+
result.type === "select" ? (result.bindings[0]?.n ?? "0") : "0",
|
|
36
|
+
);
|
|
37
|
+
} finally {
|
|
38
|
+
store.dispose();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let stderrSpy: ReturnType<typeof vi.spyOn>;
|
|
43
|
+
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
stderrSpy = vi.spyOn(process.stderr, "write").mockImplementation(() => true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
afterEach(() => {
|
|
49
|
+
stderrSpy.mockRestore();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const stderrText = (): string =>
|
|
53
|
+
stderrSpy.mock.calls.map((call) => String(call[0])).join("");
|
|
54
|
+
|
|
55
|
+
describe("createGraphLoaderPlugin", () => {
|
|
56
|
+
it("loads every graph when all are well-formed", async () => {
|
|
57
|
+
const count = await countTriples([
|
|
58
|
+
goodGraph("a.ttl", "global.component.a"),
|
|
59
|
+
goodGraph("b.ttl", "global.component.b"),
|
|
60
|
+
]);
|
|
61
|
+
expect(count).toBe(2);
|
|
62
|
+
expect(stderrText()).toBe("");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("skips a malformed graph but still boots and loads the good ones", async () => {
|
|
66
|
+
const count = await countTriples([
|
|
67
|
+
goodGraph("a.ttl", "global.component.a"),
|
|
68
|
+
badGraph("data/global/component/broken.ttl"),
|
|
69
|
+
goodGraph("b.ttl", "global.component.b"),
|
|
70
|
+
]);
|
|
71
|
+
// The two good graphs load; the malformed one is skipped, not fatal.
|
|
72
|
+
expect(count).toBe(2);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("warns with the offending file path and parse error", async () => {
|
|
76
|
+
await countTriples([badGraph("data/global/component/broken.ttl")]);
|
|
77
|
+
const text = stderrText();
|
|
78
|
+
expect(text).toContain("skipping malformed graph");
|
|
79
|
+
expect(text).toContain("data/global/component/broken.ttl");
|
|
80
|
+
expect(text).toContain("Check the TTL syntax");
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -23,8 +23,8 @@ import {
|
|
|
23
23
|
createLocalLoader,
|
|
24
24
|
} from "./loaders/index.js";
|
|
25
25
|
import { DEFAULT_PACKAGES } from "./packages.js";
|
|
26
|
-
import {
|
|
27
|
-
import type { SemanticPackage } from "./semanticPackage.js";
|
|
26
|
+
import { resolvePrefixes } from "./prefixes.js";
|
|
27
|
+
import type { GraphContent, SemanticPackage } from "./semanticPackage.js";
|
|
28
28
|
import { resolveSemanticPackages } from "./semanticPackage.js";
|
|
29
29
|
|
|
30
30
|
export interface BootStoreOptions {
|
|
@@ -61,13 +61,14 @@ export interface BootResult {
|
|
|
61
61
|
export async function bootStore(
|
|
62
62
|
options: BootStoreOptions = {},
|
|
63
63
|
): Promise<BootResult> {
|
|
64
|
-
const prefixes = { ...PREFIX_MAP, ...options.prefixes };
|
|
65
64
|
try {
|
|
66
|
-
// Testing/programmatic override — use explicit sources
|
|
65
|
+
// Testing/programmatic override — use explicit sources. No packages are
|
|
66
|
+
// resolved, so the prefix map is the core base plus the transitional DS
|
|
67
|
+
// fallback and any caller override.
|
|
67
68
|
if (options.sources) {
|
|
68
69
|
const store = await createStore({
|
|
69
70
|
sources: options.sources,
|
|
70
|
-
prefixes,
|
|
71
|
+
prefixes: resolvePrefixes([], options.prefixes),
|
|
71
72
|
cache: options.cache,
|
|
72
73
|
cwd: options.cwd,
|
|
73
74
|
});
|
|
@@ -90,19 +91,11 @@ export async function bootStore(
|
|
|
90
91
|
// Collect all graph content from resolved packages
|
|
91
92
|
const allGraphs = packages.flatMap((pkg) => pkg.graphs);
|
|
92
93
|
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
for (const graph of allGraphs) {
|
|
99
|
-
ctx.load(graph.content, { format: graph.format });
|
|
100
|
-
}
|
|
101
|
-
},
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
// biome-ignore lint: Plugin generic variance requires explicit unknown
|
|
105
|
-
const plugins: Plugin<any>[] = [graphLoaderPlugin];
|
|
94
|
+
// `Plugin<T>` is invariant in its API type and createStore expects
|
|
95
|
+
// `Plugin<void>[]`, so heterogeneous plugins (graph loader + trace) can only
|
|
96
|
+
// share an array via `any`. `unknown` does not satisfy `Plugin<void>`.
|
|
97
|
+
// biome-ignore lint/suspicious/noExplicitAny: intentional — see above
|
|
98
|
+
const plugins: Plugin<any>[] = [createGraphLoaderPlugin(allGraphs)];
|
|
106
99
|
const traceEnabled =
|
|
107
100
|
process.env.PRAGMA_TRACE === "1" || options.trace === true;
|
|
108
101
|
if (traceEnabled) {
|
|
@@ -111,9 +104,12 @@ export async function bootStore(
|
|
|
111
104
|
plugins.push(createTracePlugin({ traceDir: traceDir() }));
|
|
112
105
|
}
|
|
113
106
|
|
|
107
|
+
// Merge every resolved package's declared prefixes (and the transitional
|
|
108
|
+
// DS fallback) over the generic core, then config overrides — so the
|
|
109
|
+
// store and every query see the packages' own namespaces.
|
|
114
110
|
const store = await createStore({
|
|
115
111
|
sources: [],
|
|
116
|
-
prefixes,
|
|
112
|
+
prefixes: resolvePrefixes(packages, options.prefixes),
|
|
117
113
|
cache: options.cache,
|
|
118
114
|
cwd: options.cwd,
|
|
119
115
|
plugins,
|
|
@@ -127,9 +123,49 @@ export async function bootStore(
|
|
|
127
123
|
{
|
|
128
124
|
recovery: {
|
|
129
125
|
message:
|
|
130
|
-
"
|
|
126
|
+
"If this is a parser error, check the TTL syntax of the source graphs " +
|
|
127
|
+
"(the message above names the line and column). Otherwise ensure design " +
|
|
128
|
+
"system packages are installed: bun add -D @canonical/design-system " +
|
|
129
|
+
"@canonical/code-standards @canonical/anatomy-dsl",
|
|
131
130
|
},
|
|
132
131
|
},
|
|
133
132
|
);
|
|
134
133
|
}
|
|
135
134
|
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Build the ke plugin that loads resolved graph content into the store.
|
|
138
|
+
*
|
|
139
|
+
* Each graph is loaded independently inside its own try/catch: a single
|
|
140
|
+
* malformed TTL file (e.g. invalid Turtle emitted by an upstream authoring
|
|
141
|
+
* tool) must not abort the entire boot and take the whole CLI down with it.
|
|
142
|
+
* Failed graphs are skipped and surfaced as warnings, mirroring the
|
|
143
|
+
* resilience the story loader already provides ("one bad file cannot break
|
|
144
|
+
* boot"). A store built from N-1 good graphs is far more useful than a store
|
|
145
|
+
* that refuses to boot because of one bad graph.
|
|
146
|
+
*
|
|
147
|
+
* @note Impure — writes warnings to stderr on parse failure.
|
|
148
|
+
*/
|
|
149
|
+
export function createGraphLoaderPlugin(
|
|
150
|
+
graphs: readonly GraphContent[],
|
|
151
|
+
): Plugin {
|
|
152
|
+
return definePlugin({
|
|
153
|
+
name: "pragma-graph-loader",
|
|
154
|
+
onReady(ctx) {
|
|
155
|
+
for (const graph of graphs) {
|
|
156
|
+
try {
|
|
157
|
+
ctx.load(graph.content, { format: graph.format });
|
|
158
|
+
} catch (error) {
|
|
159
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
160
|
+
// Name the offending file and the parse error, and point at the fix.
|
|
161
|
+
// The store still boots from the remaining graphs, so this is a
|
|
162
|
+
// warning, not a fatal error.
|
|
163
|
+
process.stderr.write(
|
|
164
|
+
`Warning: skipping malformed graph "${graph.path}" — ${reason}. ` +
|
|
165
|
+
"Check the TTL syntax of this source file; the remaining graphs still load.\n",
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
}
|
|
@@ -4,8 +4,7 @@ import blockSpecs from "../block/mcp/specs.js";
|
|
|
4
4
|
import blockEmptyError from "../block/orchestration/blockEmptyError.js";
|
|
5
5
|
import modifierSpecs from "../modifier/mcp/specs.js";
|
|
6
6
|
import modifierEmptyError from "../modifier/orchestration/modifierEmptyError.js";
|
|
7
|
-
import
|
|
8
|
-
import standardEmptyError from "../standard/orchestration/standardEmptyError.js";
|
|
7
|
+
import { TOKEN_READ_SURFACE_ENABLED } from "../token/featureFlag.js";
|
|
9
8
|
import tokenSpecs from "../token/mcp/specs.js";
|
|
10
9
|
import tokenEmptyError from "../token/orchestration/tokenEmptyError.js";
|
|
11
10
|
import type { FilterConfig } from "./types/index.js";
|
|
@@ -40,36 +39,34 @@ const cases: ReadonlyArray<{
|
|
|
40
39
|
toolNames: new Set(blockSpecs.map((s) => s.name)),
|
|
41
40
|
storeEmpty: false,
|
|
42
41
|
},
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
42
|
+
// The token read surface (and with it the paths that raise
|
|
43
|
+
// tokenEmptyError) is gated behind the token feature flag; its recovery
|
|
44
|
+
// hints can only point at registered tools while the surface is live.
|
|
45
|
+
...(TOKEN_READ_SURFACE_ENABLED
|
|
46
|
+
? [
|
|
47
|
+
{
|
|
48
|
+
domain: "token (category active)",
|
|
49
|
+
error: tokenEmptyError("color"),
|
|
50
|
+
toolNames: new Set(tokenSpecs.map((s) => s.name)),
|
|
51
|
+
storeEmpty: false,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
domain: "token (store-empty)",
|
|
55
|
+
error: tokenEmptyError(),
|
|
56
|
+
toolNames: new Set(tokenSpecs.map((s) => s.name)),
|
|
57
|
+
storeEmpty: true,
|
|
58
|
+
},
|
|
59
|
+
]
|
|
60
|
+
: []),
|
|
55
61
|
{
|
|
56
62
|
domain: "modifier (store-empty)",
|
|
57
63
|
error: modifierEmptyError(),
|
|
58
64
|
toolNames: new Set(modifierSpecs.map((s) => s.name)),
|
|
59
65
|
storeEmpty: true,
|
|
60
66
|
},
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
toolNames: new Set(standardSpecs.map((s) => s.name)),
|
|
65
|
-
storeEmpty: false,
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
domain: "standard (store-empty)",
|
|
69
|
-
error: standardEmptyError({}),
|
|
70
|
-
toolNames: new Set(standardSpecs.map((s) => s.name)),
|
|
71
|
-
storeEmpty: true,
|
|
72
|
-
},
|
|
67
|
+
// `standard` left this contract with its domain: the bundled pack has no
|
|
68
|
+
// emptyError hook (a pinned PARITY_GAP), so an empty standard list renders
|
|
69
|
+
// zero rows instead of an EMPTY_RESULTS recovery.
|
|
73
70
|
];
|
|
74
71
|
|
|
75
72
|
describe("empty-error recovery contract (cross-domain)", () => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
import extractLocalName from "./extractLocalName.js";
|
|
3
|
-
import {
|
|
3
|
+
import { DEFAULT_PREFIX_MAP } from "./prefixes.js";
|
|
4
4
|
|
|
5
5
|
describe("extractLocalName", () => {
|
|
6
6
|
it("extracts after hash", () => {
|
|
@@ -8,7 +8,7 @@ describe("extractLocalName", () => {
|
|
|
8
8
|
});
|
|
9
9
|
|
|
10
10
|
it("extracts after last slash", () => {
|
|
11
|
-
expect(extractLocalName(`${
|
|
11
|
+
expect(extractLocalName(`${DEFAULT_PREFIX_MAP.ds}global`)).toBe("global");
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
it("returns full string when no separator", () => {
|
|
@@ -16,6 +16,6 @@ describe("extractLocalName", () => {
|
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
it("handles nested path", () => {
|
|
19
|
-
expect(extractLocalName(`${
|
|
19
|
+
expect(extractLocalName(`${DEFAULT_PREFIX_MAP.ds}apps/lxd`)).toBe("lxd");
|
|
20
20
|
});
|
|
21
21
|
});
|
|
@@ -70,13 +70,34 @@ export function renderLookupResults<TResult, TInput>(
|
|
|
70
70
|
|
|
71
71
|
const parts = result.results.map((item) => formatOne(mapResult(item)));
|
|
72
72
|
if (result.errors.length > 0) {
|
|
73
|
+
const heading = ctx.globalFlags.llm ? "**Errors:**" : "Errors:";
|
|
73
74
|
parts.push(
|
|
74
75
|
[
|
|
75
|
-
|
|
76
|
-
...result.errors.
|
|
76
|
+
heading,
|
|
77
|
+
...result.errors.flatMap((error) => formatErrorLines(error)),
|
|
77
78
|
].join("\n"),
|
|
78
79
|
);
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
return parts.join("\n\n");
|
|
82
83
|
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Render one lookup error as display lines: the query and message, plus any
|
|
87
|
+
* fuzzy-match suggestions carried on the error (which the JSON mode surfaces
|
|
88
|
+
* but the text modes previously dropped).
|
|
89
|
+
*
|
|
90
|
+
* @param error - A single lookup error entry.
|
|
91
|
+
* @returns One or more display lines for this error.
|
|
92
|
+
*/
|
|
93
|
+
function formatErrorLines(error: {
|
|
94
|
+
query: string;
|
|
95
|
+
message: string;
|
|
96
|
+
suggestions?: readonly string[];
|
|
97
|
+
}): string[] {
|
|
98
|
+
const lines = [`- ${error.query}: ${error.message}`];
|
|
99
|
+
if (error.suggestions && error.suggestions.length > 0) {
|
|
100
|
+
lines.push(` Did you mean: ${error.suggestions.join(", ")}?`);
|
|
101
|
+
}
|
|
102
|
+
return lines;
|
|
103
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List the tiers defined in the loaded ontology, ordered by name.
|
|
3
|
+
*
|
|
4
|
+
* Shared operation: the `tier list` CLI/MCP surface is served by the bundled
|
|
5
|
+
* `tier` story pack, but `config tier` validation still needs the tier set
|
|
6
|
+
* programmatically, and this stays part of the package's public API. Kept in
|
|
7
|
+
* the shared layer so it has no dependency on any DS-specific domain module.
|
|
8
|
+
*
|
|
9
|
+
* @param store - ke store to query.
|
|
10
|
+
* @returns Array of tier entries, empty when none exist.
|
|
11
|
+
* @note Impure — queries the ke store.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { Store, URI } from "@canonical/ke";
|
|
15
|
+
import { buildQuery } from "./buildQuery.js";
|
|
16
|
+
import { P } from "./prefixes.js";
|
|
17
|
+
import type { TierEntry } from "./types/index.js";
|
|
18
|
+
|
|
19
|
+
export async function listTiers(store: Store): Promise<TierEntry[]> {
|
|
20
|
+
const result = await store.query(
|
|
21
|
+
buildQuery(`
|
|
22
|
+
SELECT ?tier ?name
|
|
23
|
+
WHERE {
|
|
24
|
+
?tier a ${P.ds}Tier ;
|
|
25
|
+
${P.ds}name ?name .
|
|
26
|
+
}
|
|
27
|
+
ORDER BY ?name
|
|
28
|
+
`),
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
if (result.type !== "select") return [];
|
|
32
|
+
|
|
33
|
+
return result.bindings.map((b) => ({
|
|
34
|
+
uri: (b.tier ?? "") as URI,
|
|
35
|
+
path: b.name ?? "",
|
|
36
|
+
depth: 0,
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default listTiers;
|
|
@@ -96,12 +96,14 @@ export default function createBundledLoader(): PackageLoader {
|
|
|
96
96
|
if (graphs.length === 0 && stories.length === 0) return undefined;
|
|
97
97
|
|
|
98
98
|
const version = await readEmbeddedVersion(blobs);
|
|
99
|
+
const prefixes = await readEmbeddedPrefixes(blobs);
|
|
99
100
|
|
|
100
101
|
cached = {
|
|
101
102
|
name: "(bundled)",
|
|
102
103
|
version,
|
|
103
104
|
source: "bundled",
|
|
104
105
|
graphs,
|
|
106
|
+
...(prefixes ? { prefixes } : {}),
|
|
105
107
|
skills: [],
|
|
106
108
|
stories,
|
|
107
109
|
};
|
|
@@ -179,6 +181,52 @@ async function readEmbeddedStories(
|
|
|
179
181
|
// Version reading
|
|
180
182
|
// ---------------------------------------------------------------------------
|
|
181
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Aggregate `pragma.prefixes` declared across embedded package manifests.
|
|
186
|
+
*
|
|
187
|
+
* The bundled loader collapses every embedded package into one aggregate
|
|
188
|
+
* SemanticPackage, so their prefix declarations are merged here (later
|
|
189
|
+
* manifests win on key collisions). Only string→string entries are kept;
|
|
190
|
+
* malformed shapes are ignored so one bad manifest cannot break boot.
|
|
191
|
+
*
|
|
192
|
+
* @returns The merged prefix map, or `undefined` when none is declared.
|
|
193
|
+
*/
|
|
194
|
+
async function readEmbeddedPrefixes(
|
|
195
|
+
blobs: ReadonlyArray<Blob & { name: string }>,
|
|
196
|
+
): Promise<Readonly<Record<string, string>> | undefined> {
|
|
197
|
+
const prefixes: Record<string, string> = {};
|
|
198
|
+
|
|
199
|
+
for (const blob of blobs) {
|
|
200
|
+
if (!isPackageManifest(blob.name)) continue;
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
const parsed = JSON.parse(await blob.text()) as { pragma?: unknown };
|
|
204
|
+
const pragma = parsed.pragma;
|
|
205
|
+
if (typeof pragma !== "object" || pragma === null) continue;
|
|
206
|
+
const raw = (pragma as { prefixes?: unknown }).prefixes;
|
|
207
|
+
if (typeof raw !== "object" || raw === null) continue;
|
|
208
|
+
for (const [name, namespace] of Object.entries(raw)) {
|
|
209
|
+
if (typeof namespace !== "string") continue;
|
|
210
|
+
// These collapse into one aggregate package, so a cross-manifest
|
|
211
|
+
// collision would never reach resolvePrefixes' warning — surface it
|
|
212
|
+
// here, naming the manifest, instead of silently last-winning.
|
|
213
|
+
if (name in prefixes && prefixes[name] !== namespace) {
|
|
214
|
+
warn(
|
|
215
|
+
`embedded manifest "${blob.name}" overrides prefix "${name}" ` +
|
|
216
|
+
`(was <${prefixes[name]}>, now <${namespace}>)`,
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
prefixes[name] = namespace;
|
|
220
|
+
}
|
|
221
|
+
} catch (error) {
|
|
222
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
223
|
+
warn(`skipping prefixes in embedded manifest "${blob.name}": ${reason}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return Object.keys(prefixes).length > 0 ? prefixes : undefined;
|
|
228
|
+
}
|
|
229
|
+
|
|
182
230
|
async function readEmbeddedVersion(
|
|
183
231
|
blobs: ReadonlyArray<Blob & { name: string }>,
|
|
184
232
|
): Promise<string> {
|
|
@@ -24,8 +24,7 @@ export default function createGitLoader(): PackageLoader {
|
|
|
24
24
|
const dir = gitCacheDir(ref.pkg, ref.ref);
|
|
25
25
|
if (!existsSync(dir)) return undefined;
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
return { name: ref.pkg, version, source: "git", graphs, skills, stories };
|
|
27
|
+
return { name: ref.pkg, source: "git", ...readPackageDir(dir) };
|
|
29
28
|
},
|
|
30
29
|
};
|
|
31
30
|
}
|