@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
|
@@ -14,13 +14,20 @@ import { join } from "node:path";
|
|
|
14
14
|
import { runTask } from "@canonical/task/node";
|
|
15
15
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
16
16
|
import { VERSION } from "../../constants.js";
|
|
17
|
-
import type {
|
|
17
|
+
import type {
|
|
18
|
+
ConfigLayers,
|
|
19
|
+
PackDeclaration,
|
|
20
|
+
} from "../../kernel/config/types.js";
|
|
18
21
|
import { PragmaError } from "../../kernel/error/PragmaError.js";
|
|
19
22
|
import { executeVerb } from "../../kernel/project/cli/dispatch.js";
|
|
20
23
|
import { bootRuntime } from "../../kernel/runtime/boot.js";
|
|
21
24
|
import { createQueryFacade } from "../../kernel/runtime/facade.js";
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
25
|
+
import { readManifest } from "../../kernel/runtime/graphpack/manifest.js";
|
|
26
|
+
import {
|
|
27
|
+
activePackPath,
|
|
28
|
+
packDir,
|
|
29
|
+
readActivePack,
|
|
30
|
+
} from "../../kernel/runtime/paths.js";
|
|
24
31
|
import {
|
|
25
32
|
detectPrefixClashes,
|
|
26
33
|
resolvePackageJson,
|
|
@@ -58,18 +65,17 @@ const tmp = (prefix: string): string => {
|
|
|
58
65
|
return dir;
|
|
59
66
|
};
|
|
60
67
|
|
|
61
|
-
/** A runtime whose config is the given
|
|
62
|
-
function runtimeFor(cwd: string,
|
|
68
|
+
/** A runtime whose config is the given pack list (no config files needed). */
|
|
69
|
+
function runtimeFor(cwd: string, packs: PackDeclaration[]): PragmaRuntime {
|
|
63
70
|
const layers: ConfigLayers = {
|
|
64
|
-
config: { channel: "normal",
|
|
71
|
+
config: { channel: "normal", packs },
|
|
65
72
|
origins: {
|
|
66
73
|
tier: "default",
|
|
67
74
|
channel: "default",
|
|
68
75
|
detail: "default",
|
|
69
|
-
|
|
76
|
+
packs: "project",
|
|
70
77
|
stories: "default",
|
|
71
78
|
prefixes: "default",
|
|
72
|
-
prompts: "default",
|
|
73
79
|
},
|
|
74
80
|
global: { path: "/nonexistent", exists: false },
|
|
75
81
|
project: { exists: false },
|
|
@@ -101,30 +107,24 @@ function filePackage(): string {
|
|
|
101
107
|
return pkg;
|
|
102
108
|
}
|
|
103
109
|
|
|
104
|
-
describe("sources
|
|
105
|
-
it("file source: builds
|
|
110
|
+
describe("sources update round-trip (PROTECTED)", () => {
|
|
111
|
+
it("file source: builds and points the project at the built pack", async () => {
|
|
106
112
|
const pkg = filePackage();
|
|
107
113
|
const cwd = tmp("pragma-proj-");
|
|
108
114
|
const runtime = runtimeFor(cwd, [
|
|
109
115
|
{ name: "pkg-a", source: `file://${pkg}` },
|
|
110
116
|
]);
|
|
111
117
|
|
|
112
|
-
const result = await runTask(await buildUpdateTask(runtime
|
|
118
|
+
const result = await runTask(await buildUpdateTask(runtime));
|
|
113
119
|
expect(result.contentHash).toMatch(/^[0-9a-f]{64}$/);
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
expect(
|
|
119
|
-
expect(lock?.contentHash).toBe(result.contentHash);
|
|
120
|
-
|
|
121
|
-
const firstBytes = readFileSync(lockPath(cwd), "utf-8");
|
|
122
|
-
// A --frozen re-run reproduces the locked state byte-for-byte.
|
|
123
|
-
await runTask(await buildUpdateTask(runtime, true));
|
|
124
|
-
expect(readFileSync(lockPath(cwd), "utf-8")).toBe(firstBytes);
|
|
120
|
+
expect(result.packs).toEqual([
|
|
121
|
+
{ name: "pkg-a", resolved: pkg, sourceCount: 1, storyCount: 0 },
|
|
122
|
+
]);
|
|
123
|
+
// The pointer the next boot reads names exactly the pack just built.
|
|
124
|
+
expect(readActivePack(cwd)).toBe(result.contentHash);
|
|
125
125
|
});
|
|
126
126
|
|
|
127
|
-
it("git source: resolves
|
|
127
|
+
it("git source: resolves the ref to a commit SHA", async () => {
|
|
128
128
|
const repo = tmp("pragma-repo-");
|
|
129
129
|
const git = (args: string[]) =>
|
|
130
130
|
execFileSync("git", args, {
|
|
@@ -149,34 +149,39 @@ describe("sources lock round-trip (PROTECTED)", () => {
|
|
|
149
149
|
{ name: "pkg-git", source: `git+file://${repo}#main` },
|
|
150
150
|
]);
|
|
151
151
|
|
|
152
|
-
await runTask(await buildUpdateTask(runtime
|
|
153
|
-
const
|
|
154
|
-
expect(
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
152
|
+
const result = await runTask(await buildUpdateTask(runtime));
|
|
153
|
+
const sha = result.packs.at(0)?.resolved;
|
|
154
|
+
expect(sha).toMatch(/^[0-9a-f]{40}$/);
|
|
155
|
+
expect(readActivePack(cwd)).toBe(result.contentHash);
|
|
156
|
+
// The revision reaches the manifest's provenance label, which is the only
|
|
157
|
+
// place `sources status` and `doctor` can read it from now that no lock
|
|
158
|
+
// records it. Same `<name>@<kind>:<resolved>` shape the bundler writes.
|
|
159
|
+
expect(readManifest(packDir(result.contentHash))?.sourceRef).toBe(
|
|
160
|
+
`pkg-git@git:${sha}`,
|
|
161
|
+
);
|
|
159
162
|
});
|
|
160
163
|
|
|
161
|
-
it("a
|
|
162
|
-
// On base
|
|
163
|
-
//
|
|
164
|
-
//
|
|
164
|
+
it("a re-run over an unchanged source leaves the pointer byte-identical (L1)", async () => {
|
|
165
|
+
// On base the lock file carried a `resolvedAt` timestamp, so a no-op re-run
|
|
166
|
+
// rewrote it and dirtied the tree. The pointer holds only the content hash,
|
|
167
|
+
// which is a pure function of the sources — so this is now true by
|
|
168
|
+
// construction. The guard stays because it is the property users cared
|
|
169
|
+
// about: repeating an update changes nothing when nothing changed.
|
|
165
170
|
const pkg = filePackage();
|
|
166
171
|
const cwd = tmp("pragma-proj-");
|
|
167
172
|
const runtime = runtimeFor(cwd, [
|
|
168
173
|
{ name: "pkg-a", source: `file://${pkg}` },
|
|
169
174
|
]);
|
|
170
|
-
await runTask(await buildUpdateTask(runtime
|
|
171
|
-
const firstBytes = readFileSync(
|
|
172
|
-
await runTask(await buildUpdateTask(runtime
|
|
173
|
-
expect(readFileSync(
|
|
175
|
+
await runTask(await buildUpdateTask(runtime));
|
|
176
|
+
const firstBytes = readFileSync(activePackPath(cwd), "utf-8");
|
|
177
|
+
await runTask(await buildUpdateTask(runtime));
|
|
178
|
+
expect(readFileSync(activePackPath(cwd), "utf-8")).toBe(firstBytes);
|
|
174
179
|
});
|
|
175
180
|
|
|
176
181
|
it("follows a symlinked .ttl into the build (L6)", async () => {
|
|
177
182
|
// pnpm / workspace trees symlink their sources; Dirent.isFile is false for a
|
|
178
183
|
// symlink, so on base the only `.ttl` was skipped → 0 sources → the empty
|
|
179
|
-
// build. Following the link ingests it and the build
|
|
184
|
+
// build. Following the link ingests it and the build succeeds.
|
|
180
185
|
const pkg = tmp("pragma-symlink-pkg-");
|
|
181
186
|
mkdirSync(join(pkg, "definitions"), { recursive: true });
|
|
182
187
|
const realTtl = join(pkg, "real.ttl");
|
|
@@ -187,9 +192,9 @@ describe("sources lock round-trip (PROTECTED)", () => {
|
|
|
187
192
|
{ name: "pkg-a", source: `file://${pkg}` },
|
|
188
193
|
]);
|
|
189
194
|
|
|
190
|
-
const result = await runTask(await buildUpdateTask(runtime
|
|
195
|
+
const result = await runTask(await buildUpdateTask(runtime));
|
|
191
196
|
expect(result.contentHash).toMatch(/^[0-9a-f]{64}$/);
|
|
192
|
-
expect(
|
|
197
|
+
expect(readActivePack(cwd)).toBe(result.contentHash);
|
|
193
198
|
});
|
|
194
199
|
|
|
195
200
|
it("does not recurse into a symlinked directory cycle (L6 safety)", async () => {
|
|
@@ -207,21 +212,20 @@ describe("sources lock round-trip (PROTECTED)", () => {
|
|
|
207
212
|
{ name: "pkg-a", source: `file://${pkg}` },
|
|
208
213
|
]);
|
|
209
214
|
|
|
210
|
-
const result = await runTask(await buildUpdateTask(runtime
|
|
215
|
+
const result = await runTask(await buildUpdateTask(runtime));
|
|
211
216
|
expect(result.contentHash).toMatch(/^[0-9a-f]{64}$/);
|
|
212
|
-
expect(
|
|
217
|
+
expect(readActivePack(cwd)).toBe(result.contentHash);
|
|
213
218
|
});
|
|
214
219
|
|
|
215
|
-
it("undo
|
|
220
|
+
it("undo removes the pointer when the project had none before", async () => {
|
|
216
221
|
const pkg = filePackage();
|
|
217
222
|
const cwd = tmp("pragma-proj-");
|
|
218
223
|
const runtime = runtimeFor(cwd, [
|
|
219
224
|
{ name: "pkg-a", source: `file://${pkg}` },
|
|
220
225
|
]);
|
|
221
226
|
const { runUndo } = await import("@canonical/task/node");
|
|
222
|
-
await runUndo(await buildUpdateTask(runtime
|
|
223
|
-
|
|
224
|
-
expect(readLock(cwd)).toBeUndefined();
|
|
227
|
+
await runUndo(await buildUpdateTask(runtime));
|
|
228
|
+
expect(readActivePack(cwd)).toBeUndefined();
|
|
225
229
|
});
|
|
226
230
|
});
|
|
227
231
|
|
|
@@ -237,7 +241,7 @@ describe("sources update — package-declared prefixes (M1)", () => {
|
|
|
237
241
|
{ name: "pkg-a", source: `file://${pkg}` },
|
|
238
242
|
]);
|
|
239
243
|
|
|
240
|
-
const result = await runTask(await buildUpdateTask(runtime
|
|
244
|
+
const result = await runTask(await buildUpdateTask(runtime));
|
|
241
245
|
const index = JSON.parse(
|
|
242
246
|
readFileSync(join(packDir(result.contentHash), "index.json"), "utf-8"),
|
|
243
247
|
) as {
|
|
@@ -267,30 +271,30 @@ describe("sources update — refuses an empty store (A4)", () => {
|
|
|
267
271
|
return pkg;
|
|
268
272
|
}
|
|
269
273
|
|
|
270
|
-
it("a package with no .ttl is refused, and no
|
|
274
|
+
it("a package with no .ttl is refused, and no pointer is written", async () => {
|
|
271
275
|
// On base this builds a 0-triple pack whose empty data.nq fails the
|
|
272
276
|
// completeness gate — so the "successful" update boots to a PERMANENT
|
|
273
|
-
// STORE_UNAVAILABLE loop. The fix refuses before writing the
|
|
277
|
+
// STORE_UNAVAILABLE loop. The fix refuses before writing the pointer.
|
|
274
278
|
const pkg = emptyPackage();
|
|
275
279
|
const cwd = tmp("pragma-proj-");
|
|
276
280
|
const runtime = runtimeFor(cwd, [
|
|
277
281
|
{ name: "pkg-a", source: `file://${pkg}` },
|
|
278
282
|
]);
|
|
279
283
|
|
|
280
|
-
await expect(buildUpdateTask(runtime
|
|
284
|
+
await expect(buildUpdateTask(runtime)).rejects.toMatchObject({
|
|
281
285
|
code: "CONFIG_ERROR",
|
|
282
286
|
});
|
|
283
|
-
// No
|
|
284
|
-
expect(
|
|
287
|
+
// No pointer → the embedded fallback / prior state survives, no boot loop.
|
|
288
|
+
expect(readActivePack(cwd)).toBeUndefined();
|
|
285
289
|
});
|
|
286
290
|
|
|
287
|
-
it("no configured
|
|
291
|
+
it("no configured packs is refused rather than pointing at an empty pack", async () => {
|
|
288
292
|
const cwd = tmp("pragma-proj-");
|
|
289
293
|
const runtime = runtimeFor(cwd, []);
|
|
290
|
-
await expect(buildUpdateTask(runtime
|
|
294
|
+
await expect(buildUpdateTask(runtime)).rejects.toMatchObject({
|
|
291
295
|
code: "CONFIG_ERROR",
|
|
292
296
|
});
|
|
293
|
-
expect(
|
|
297
|
+
expect(readActivePack(cwd)).toBeUndefined();
|
|
294
298
|
});
|
|
295
299
|
|
|
296
300
|
it("a comment-only .ttl (0 triples) is refused too", async () => {
|
|
@@ -305,10 +309,10 @@ describe("sources update — refuses an empty store (A4)", () => {
|
|
|
305
309
|
const runtime = runtimeFor(cwd, [
|
|
306
310
|
{ name: "pkg-a", source: `file://${pkg}` },
|
|
307
311
|
]);
|
|
308
|
-
await expect(buildUpdateTask(runtime
|
|
312
|
+
await expect(buildUpdateTask(runtime)).rejects.toMatchObject({
|
|
309
313
|
code: "CONFIG_ERROR",
|
|
310
314
|
});
|
|
311
|
-
expect(
|
|
315
|
+
expect(readActivePack(cwd)).toBeUndefined();
|
|
312
316
|
});
|
|
313
317
|
});
|
|
314
318
|
|
|
@@ -358,7 +362,7 @@ describe("sources update — conflicting @prefix detection (A5)", () => {
|
|
|
358
362
|
report: (message: string) => reports.push(message),
|
|
359
363
|
};
|
|
360
364
|
|
|
361
|
-
await runTask(await buildUpdateTask(runtime
|
|
365
|
+
await runTask(await buildUpdateTask(runtime));
|
|
362
366
|
expect(
|
|
363
367
|
reports.some(
|
|
364
368
|
(r) => r.includes('Prefix "ex:"') && r.includes("conflicting"),
|
|
@@ -385,11 +389,12 @@ describe("sources update — network-free preview (M2)", () => {
|
|
|
385
389
|
|
|
386
390
|
// A successful plan — the unreachable clone was never attempted.
|
|
387
391
|
expect(outcome.exitCode).toBe(0);
|
|
388
|
-
expect(outcome.stdout).toContain("Resolve and build 1
|
|
392
|
+
expect(outcome.stdout).toContain("Resolve and build 1 pack(s)");
|
|
389
393
|
expect(outcome.stdout).toContain(UNREACHABLE);
|
|
390
|
-
// The
|
|
391
|
-
|
|
392
|
-
expect(
|
|
394
|
+
// The project mutation is previewed, not performed: the plan names the
|
|
395
|
+
// exact pointer file a real run would write, and no pointer landed.
|
|
396
|
+
expect(outcome.stdout).toContain(activePackPath(cwd));
|
|
397
|
+
expect(readActivePack(cwd)).toBeUndefined();
|
|
393
398
|
// The store was never even asked for.
|
|
394
399
|
expect(runtime.store.booted).toBe(false);
|
|
395
400
|
});
|
|
@@ -398,7 +403,7 @@ describe("sources update — network-free preview (M2)", () => {
|
|
|
398
403
|
const cwd = tmp("pragma-proj-");
|
|
399
404
|
writeFileSync(
|
|
400
405
|
join(cwd, "pragma.config.ts"),
|
|
401
|
-
`export default {
|
|
406
|
+
`export default { packs: [{ name: "pkg-remote", source: "${UNREACHABLE}" }] };\n`,
|
|
402
407
|
);
|
|
403
408
|
|
|
404
409
|
const mcp = await projectMcp([sourcesModule], cwd);
|
|
@@ -412,25 +417,8 @@ describe("sources update — network-free preview (M2)", () => {
|
|
|
412
417
|
});
|
|
413
418
|
const plan = (envelope.data as { plan: string[] }).plan;
|
|
414
419
|
expect(plan.some((line) => line.includes(UNREACHABLE))).toBe(true);
|
|
415
|
-
// Plan-first withheld the write — no
|
|
416
|
-
expect(
|
|
417
|
-
});
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
describe("sources update — reproducible-or-fail (m5)", () => {
|
|
421
|
-
it("--frozen refuses a package that has no lock entry", async () => {
|
|
422
|
-
const pkg = filePackage();
|
|
423
|
-
const cwd = tmp("pragma-proj-");
|
|
424
|
-
const runtime = runtimeFor(cwd, [
|
|
425
|
-
{ name: "pkg-a", source: `file://${pkg}` },
|
|
426
|
-
]);
|
|
427
|
-
|
|
428
|
-
// No prior lock → nothing to reproduce → refuse rather than silently
|
|
429
|
-
// advance. The throw is raised during setup, before any Task is returned.
|
|
430
|
-
await expect(buildUpdateTask(runtime, true)).rejects.toMatchObject({
|
|
431
|
-
code: "CONFIG_ERROR",
|
|
432
|
-
});
|
|
433
|
-
expect(readLock(cwd)).toBeUndefined();
|
|
420
|
+
// Plan-first withheld the write — no pointer landed.
|
|
421
|
+
expect(readActivePack(cwd)).toBeUndefined();
|
|
434
422
|
});
|
|
435
423
|
});
|
|
436
424
|
|
|
@@ -498,7 +486,7 @@ describe("sources update — data-failure classification (U6)", () => {
|
|
|
498
486
|
|
|
499
487
|
let caught: unknown;
|
|
500
488
|
try {
|
|
501
|
-
await buildUpdateTask(runtime
|
|
489
|
+
await buildUpdateTask(runtime);
|
|
502
490
|
} catch (error) {
|
|
503
491
|
caught = error;
|
|
504
492
|
}
|
|
@@ -516,8 +504,8 @@ describe("sources update — data-failure classification (U6)", () => {
|
|
|
516
504
|
expect(err.recovery?.message ?? "").not.toContain("report this issue");
|
|
517
505
|
// The recovery points the user at a runnable, useful next step.
|
|
518
506
|
expect(err.recovery?.cli).toBe("pragma sources update --verbose");
|
|
519
|
-
// Nothing was
|
|
520
|
-
expect(
|
|
507
|
+
// Nothing was pointed at on failure.
|
|
508
|
+
expect(readActivePack(cwd)).toBeUndefined();
|
|
521
509
|
});
|
|
522
510
|
|
|
523
511
|
it("classifies a git clone failure, naming the package (not INTERNAL)", async () => {
|
|
@@ -534,7 +522,7 @@ describe("sources update — data-failure classification (U6)", () => {
|
|
|
534
522
|
|
|
535
523
|
let caught: unknown;
|
|
536
524
|
try {
|
|
537
|
-
await buildUpdateTask(runtime
|
|
525
|
+
await buildUpdateTask(runtime);
|
|
538
526
|
} catch (error) {
|
|
539
527
|
caught = error;
|
|
540
528
|
}
|
|
@@ -543,7 +531,7 @@ describe("sources update — data-failure classification (U6)", () => {
|
|
|
543
531
|
expect(err.code).toBe("CONFIG_ERROR");
|
|
544
532
|
expect(err.message).toContain("pkg-remote");
|
|
545
533
|
expect(err.message).not.toContain("Internal error");
|
|
546
|
-
expect(
|
|
534
|
+
expect(readActivePack(cwd)).toBeUndefined();
|
|
547
535
|
});
|
|
548
536
|
|
|
549
537
|
it("names the SPECIFIC bad file among several good ones", async () => {
|
|
@@ -557,12 +545,12 @@ describe("sources update — data-failure classification (U6)", () => {
|
|
|
557
545
|
const cwd = tmp("pragma-proj-");
|
|
558
546
|
const runtime = runtimeFor(cwd, [{ name: "mix", source: `file://${pkg}` }]);
|
|
559
547
|
|
|
560
|
-
await expect(buildUpdateTask(runtime
|
|
548
|
+
await expect(buildUpdateTask(runtime)).rejects.toMatchObject({
|
|
561
549
|
code: "CONFIG_ERROR",
|
|
562
550
|
});
|
|
563
551
|
let caught: unknown;
|
|
564
552
|
try {
|
|
565
|
-
await buildUpdateTask(runtime
|
|
553
|
+
await buildUpdateTask(runtime);
|
|
566
554
|
} catch (error) {
|
|
567
555
|
caught = error;
|
|
568
556
|
}
|
|
@@ -594,9 +582,9 @@ describe("sources update — hidden files and --skip-invalid", () => {
|
|
|
594
582
|
{ name: "pkg-a", source: `file://${pkg}` },
|
|
595
583
|
]);
|
|
596
584
|
|
|
597
|
-
const result = await runTask(await buildUpdateTask(runtime
|
|
585
|
+
const result = await runTask(await buildUpdateTask(runtime));
|
|
598
586
|
expect(result.contentHash).toMatch(/^[0-9a-f]{64}$/);
|
|
599
|
-
expect(
|
|
587
|
+
expect(readActivePack(cwd)).toMatch(/^[0-9a-f]{64}$/);
|
|
600
588
|
});
|
|
601
589
|
|
|
602
590
|
it("without --skip-invalid, one malformed source fails the whole update", async () => {
|
|
@@ -605,10 +593,10 @@ describe("sources update — hidden files and --skip-invalid", () => {
|
|
|
605
593
|
const runtime = runtimeFor(cwd, [
|
|
606
594
|
{ name: "pkg-a", source: `file://${pkg}` },
|
|
607
595
|
]);
|
|
608
|
-
await expect(buildUpdateTask(runtime, false
|
|
596
|
+
await expect(buildUpdateTask(runtime, false)).rejects.toMatchObject({
|
|
609
597
|
code: "CONFIG_ERROR",
|
|
610
598
|
});
|
|
611
|
-
expect(
|
|
599
|
+
expect(readActivePack(cwd)).toBeUndefined();
|
|
612
600
|
});
|
|
613
601
|
|
|
614
602
|
it("with --skip-invalid, drops the bad source, warns loudly, and builds from the rest", async () => {
|
|
@@ -620,10 +608,10 @@ describe("sources update — hidden files and --skip-invalid", () => {
|
|
|
620
608
|
report: (message: string) => reports.push(message),
|
|
621
609
|
};
|
|
622
610
|
|
|
623
|
-
const result = await runTask(await buildUpdateTask(runtime,
|
|
611
|
+
const result = await runTask(await buildUpdateTask(runtime, true));
|
|
624
612
|
// Built from the good widget.ttl, not aborted.
|
|
625
613
|
expect(result.contentHash).toMatch(/^[0-9a-f]{64}$/);
|
|
626
|
-
expect(
|
|
614
|
+
expect(readActivePack(cwd)).toMatch(/^[0-9a-f]{64}$/);
|
|
627
615
|
// Loud per-source warning names the dropped file …
|
|
628
616
|
expect(
|
|
629
617
|
reports.some(
|
|
@@ -643,10 +631,10 @@ describe("sources update — hidden files and --skip-invalid", () => {
|
|
|
643
631
|
...runtimeFor(cwd, [{ name: "pkg-a", source: `file://${pkg}` }]),
|
|
644
632
|
report: () => {},
|
|
645
633
|
};
|
|
646
|
-
await expect(buildUpdateTask(runtime,
|
|
634
|
+
await expect(buildUpdateTask(runtime, true)).rejects.toMatchObject({
|
|
647
635
|
code: "CONFIG_ERROR",
|
|
648
636
|
});
|
|
649
|
-
expect(
|
|
637
|
+
expect(readActivePack(cwd)).toBeUndefined();
|
|
650
638
|
});
|
|
651
639
|
});
|
|
652
640
|
|
|
@@ -740,7 +728,7 @@ describe("sources update — installs package skills (U10)", () => {
|
|
|
740
728
|
{ name: "pkg-a", source: `file://${pkg}` },
|
|
741
729
|
]);
|
|
742
730
|
|
|
743
|
-
await runTask(await buildUpdateTask(runtime
|
|
731
|
+
await runTask(await buildUpdateTask(runtime));
|
|
744
732
|
|
|
745
733
|
// Installed as a symlink into $XDG_DATA_HOME/pragma/skills …
|
|
746
734
|
const linked = join(dataHome, "pragma", "skills", "foo");
|
|
@@ -762,7 +750,7 @@ describe("sources update — installs package skills (U10)", () => {
|
|
|
762
750
|
const runtime = runtimeFor(cwd, [
|
|
763
751
|
{ name: "pkg-a", source: `file://${pkg}` },
|
|
764
752
|
]);
|
|
765
|
-
await runTask(await buildUpdateTask(runtime
|
|
753
|
+
await runTask(await buildUpdateTask(runtime));
|
|
766
754
|
|
|
767
755
|
const shared = discoverSkills(cwd).filter((s) => s.name === "shared");
|
|
768
756
|
expect(shared).toHaveLength(1);
|
|
@@ -776,35 +764,42 @@ describe("sources update — installs package skills (U10)", () => {
|
|
|
776
764
|
{ name: "pkg-a", source: `file://${pkg}` },
|
|
777
765
|
]);
|
|
778
766
|
const { runUndo } = await import("@canonical/task/node");
|
|
779
|
-
await runUndo(await buildUpdateTask(runtime
|
|
767
|
+
await runUndo(await buildUpdateTask(runtime));
|
|
780
768
|
|
|
781
769
|
expect(existsSync(join(dataHome, "pragma", "skills", "bar"))).toBe(false);
|
|
782
770
|
});
|
|
783
771
|
});
|
|
784
772
|
|
|
785
773
|
describe("sources status — entityCount from the manifest (A10)", () => {
|
|
786
|
-
it("reports the count
|
|
787
|
-
|
|
774
|
+
it("reports the count from the manifest even when index.json is unreadable", async () => {
|
|
775
|
+
// A pack of its OWN (unique TTL ⇒ unique content hash), so corrupting its
|
|
776
|
+
// cached index cannot reach any other test's pack in the shared cache.
|
|
777
|
+
const pkg = tmp("pragma-a10-pkg-");
|
|
778
|
+
mkdirSync(join(pkg, "definitions"), { recursive: true });
|
|
779
|
+
writeFileSync(
|
|
780
|
+
join(pkg, "definitions", "widget.ttl"),
|
|
781
|
+
`${TTL}ex:a10only a ex:Widget ; rdfs:label "A10" .\n`,
|
|
782
|
+
);
|
|
788
783
|
const cwd = tmp("pragma-proj-");
|
|
789
784
|
const runtime = runtimeFor(cwd, [
|
|
790
|
-
{ name: "pkg-
|
|
785
|
+
{ name: "pkg-a10", source: `file://${pkg}` },
|
|
791
786
|
]);
|
|
792
|
-
const result = await runTask(await buildUpdateTask(runtime
|
|
787
|
+
const result = await runTask(await buildUpdateTask(runtime));
|
|
793
788
|
|
|
794
|
-
//
|
|
795
|
-
//
|
|
796
|
-
|
|
789
|
+
// Corrupt index.json but keep it non-empty, so the pack still reads as
|
|
790
|
+
// complete: any path that PARSED it to count would now yield null.
|
|
791
|
+
writeFileSync(join(packDir(result.contentHash), "index.json"), "not json");
|
|
797
792
|
|
|
798
793
|
const status = await collectStatus(bootRuntime(flagsJson, cwd));
|
|
799
|
-
//
|
|
800
|
-
expect(status.entityCount).toBe(
|
|
794
|
+
// Two abox individuals (ex:one, ex:a10only); ex:Widget is tbox, not counted.
|
|
795
|
+
expect(status.entityCount).toBe(2);
|
|
801
796
|
});
|
|
802
797
|
});
|
|
803
798
|
|
|
804
799
|
describe("sources status CLI-json == MCP tool (PROTECTED)", () => {
|
|
805
|
-
it("a
|
|
806
|
-
// Both surfaces boot from the SAME cwd + isolated config (default
|
|
807
|
-
//
|
|
800
|
+
it("a fresh install's status is byte-equal on both surfaces", async () => {
|
|
801
|
+
// Both surfaces boot from the SAME cwd + isolated config (default packs,
|
|
802
|
+
// nothing built), so the storeless status must be identical.
|
|
808
803
|
const cwd = tmp("pragma-proj-");
|
|
809
804
|
const cli = await executeVerb(
|
|
810
805
|
statusVerb,
|
|
@@ -820,9 +815,8 @@ describe("sources status CLI-json == MCP tool (PROTECTED)", () => {
|
|
|
820
815
|
|
|
821
816
|
expect(cliEnvelope).toEqual(mcpEnvelope);
|
|
822
817
|
expect(cliEnvelope.ok).toBe(true);
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
);
|
|
826
|
-
expect((cliEnvelope.data as { cached: boolean }).cached).toBe(false);
|
|
818
|
+
// Nothing built here, and the packs are the distribution's own — so the
|
|
819
|
+
// embedded snapshot is what answers reads, and status says exactly that.
|
|
820
|
+
expect((cliEnvelope.data as { store: string }).store).toBe("embedded");
|
|
827
821
|
});
|
|
828
822
|
});
|
|
@@ -3,26 +3,17 @@ import { type RenderStyle, styleFor } from "../../kernel/render/style.js";
|
|
|
3
3
|
import { renderSourcesStatusPlain } from "./status.render.js";
|
|
4
4
|
import type { SourcesStatusData } from "./types.js";
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
/** A project reading from its own built pack, with two configured sources. */
|
|
7
|
+
const BUILT: SourcesStatusData = {
|
|
7
8
|
cwd: "/repo",
|
|
8
|
-
|
|
9
|
+
store: "built",
|
|
9
10
|
contentHash: "abcdef012345aaaa",
|
|
10
|
-
|
|
11
|
+
sourceRef: "ds,extra",
|
|
11
12
|
builtAt: "2026-01-01",
|
|
12
13
|
entityCount: 42,
|
|
13
14
|
sources: [
|
|
14
|
-
{
|
|
15
|
-
|
|
16
|
-
ref: "ds@1",
|
|
17
|
-
resolved: "1111111111112222",
|
|
18
|
-
staleness: "up-to-date",
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
name: "extra",
|
|
22
|
-
ref: "extra@1",
|
|
23
|
-
resolved: null,
|
|
24
|
-
staleness: "config-drift",
|
|
25
|
-
},
|
|
15
|
+
{ name: "ds", ref: "git+https://example.test/ds.git#main" },
|
|
16
|
+
{ name: "extra", ref: "extra@1" },
|
|
26
17
|
],
|
|
27
18
|
};
|
|
28
19
|
|
|
@@ -38,28 +29,66 @@ const TAGGED: RenderStyle = {
|
|
|
38
29
|
|
|
39
30
|
describe("renderSourcesStatusPlain", () => {
|
|
40
31
|
it("is byte-identical to the plain form when the styler is disabled", () => {
|
|
41
|
-
expect(renderSourcesStatusPlain(
|
|
32
|
+
expect(renderSourcesStatusPlain(BUILT, styleFor(false))).toBe(
|
|
42
33
|
[
|
|
43
34
|
"Store: ready",
|
|
44
35
|
" pack: abcdef012345 — 42 entities, built 2026-01-01",
|
|
36
|
+
" from: ds,extra",
|
|
45
37
|
"",
|
|
46
38
|
"Sources:",
|
|
47
|
-
" ds
|
|
48
|
-
" extra
|
|
39
|
+
" ds git+https://example.test/ds.git#main",
|
|
40
|
+
" extra extra@1",
|
|
49
41
|
].join("\n"),
|
|
50
42
|
);
|
|
51
43
|
});
|
|
52
44
|
|
|
53
|
-
it("bolds the heading, aligns names, and
|
|
54
|
-
const out = renderSourcesStatusPlain(
|
|
45
|
+
it("bolds the heading, aligns names, and dims refs on a TTY", () => {
|
|
46
|
+
const out = renderSourcesStatusPlain(BUILT, TAGGED);
|
|
55
47
|
const nameWidth = "extra".length;
|
|
56
48
|
expect(out).toContain("<b>Sources:</b>");
|
|
57
|
-
// up-to-date tints green with a dim resolved suffix; drift tints yellow.
|
|
58
49
|
expect(out).toContain(
|
|
59
|
-
` ${"ds".padEnd(nameWidth)} <
|
|
50
|
+
` ${"ds".padEnd(nameWidth)} <d>git+https://example.test/ds.git#main</d>`,
|
|
60
51
|
);
|
|
61
|
-
expect(out).toContain(
|
|
62
|
-
|
|
52
|
+
expect(out).toContain(` ${"extra".padEnd(nameWidth)} <d>extra@1</d>`);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("names the embedded snapshot AND the update that would replace it", () => {
|
|
56
|
+
// The embedded pack answers reads on a fresh install, but it IS a snapshot —
|
|
57
|
+
// rendering it as "ready" would read as "up to date", which it is not.
|
|
58
|
+
const out = renderSourcesStatusPlain(
|
|
59
|
+
{
|
|
60
|
+
...BUILT,
|
|
61
|
+
store: "embedded",
|
|
62
|
+
sourceRef: "@canonical/design-system@git:abc",
|
|
63
|
+
},
|
|
64
|
+
styleFor(false),
|
|
65
|
+
);
|
|
66
|
+
expect(out).toContain("Store: embedded snapshot");
|
|
67
|
+
expect(out).toContain("pragma sources update");
|
|
68
|
+
expect(out).toContain(" from: @canonical/design-system@git:abc");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("reports an unbuilt store with its recovery and no pack line", () => {
|
|
72
|
+
const out = renderSourcesStatusPlain(
|
|
73
|
+
{
|
|
74
|
+
...BUILT,
|
|
75
|
+
store: "unavailable",
|
|
76
|
+
contentHash: null,
|
|
77
|
+
sourceRef: null,
|
|
78
|
+
builtAt: null,
|
|
79
|
+
entityCount: null,
|
|
80
|
+
},
|
|
81
|
+
styleFor(false),
|
|
82
|
+
);
|
|
83
|
+
expect(out).toContain("Store: not built (run `pragma sources update`)");
|
|
84
|
+
expect(out).not.toContain(" pack: ");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("says so when no packs are configured", () => {
|
|
88
|
+
const out = renderSourcesStatusPlain(
|
|
89
|
+
{ ...BUILT, sources: [] },
|
|
90
|
+
styleFor(false),
|
|
63
91
|
);
|
|
92
|
+
expect(out).toContain(" (none configured)");
|
|
64
93
|
});
|
|
65
94
|
});
|