magus-cli 7.6.0 → 7.8.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/package.json +4 -4
- package/src/__tests__/blackbox-parity.test.ts +29 -15
- package/src/__tests__/blackbox-plugin-state.test.ts +25 -6
- package/src/__tests__/blind-780-cli-tools.test.ts +808 -0
- package/src/__tests__/blind-780-deprecated.test.ts +631 -0
- package/src/__tests__/blind-780-failure-reasons.test.ts +1004 -0
- package/src/__tests__/blind-780-plugin-resolution.test.ts +431 -0
- package/src/__tests__/blind-780-review2.test.ts +1203 -0
- package/src/__tests__/cli-update-view.test.ts +3 -3
- package/src/__tests__/deprecated-remove.test.ts +827 -0
- package/src/__tests__/doctor.test.ts +159 -1
- package/src/__tests__/failure-popup-review2.test.tsx +920 -0
- package/src/__tests__/failure-popup.test.tsx +419 -0
- package/src/__tests__/final-fixes-780.test.tsx +724 -0
- package/src/__tests__/format-update-outcome.test.ts +9 -34
- package/src/__tests__/gitignore-profile.test.ts +0 -4
- package/src/__tests__/models-screen-state.test.ts +18 -40
- package/src/__tests__/plugin-resolution.test.ts +52 -13
- package/src/__tests__/plugin-row-text.test.ts +30 -37
- package/src/__tests__/profile-dependencies.test.ts +7 -0
- package/src/__tests__/profile-startup-old-format.test.ts +0 -3
- package/src/__tests__/profile-startup.test.ts +40 -12
- package/src/__tests__/stale-load-repair.test.ts +31 -23
- package/src/__tests__/update-apply.test.ts +13 -29
- package/src/__tests__/update-plan.test.ts +23 -17
- package/src/__tests__/useStaleLoadModal.test.ts +1 -42
- package/src/__tests__/version-unknown-state.test.ts +15 -17
- package/src/cli/doctor.ts +151 -30
- package/src/cli/install.ts +5 -6
- package/src/cli/update-view.ts +58 -15
- package/src/cli/update.ts +27 -5
- package/src/services/catalog-cache-store.ts +8 -6
- package/src/services/claude-cli.ts +106 -19
- package/src/services/claude-settings.ts +84 -36
- package/src/services/gitignore-service.ts +0 -19
- package/src/services/plugin-env-needs.ts +69 -0
- package/src/services/plugin-manager.ts +31 -19
- package/src/services/plugin-resolution.ts +34 -42
- package/src/services/profile-absorb.ts +9 -0
- package/src/services/profile-activate.ts +1 -1
- package/src/services/profile-startup.ts +30 -27
- package/src/services/profile-state.ts +81 -2
- package/src/services/resolver.ts +95 -1
- package/src/services/update-engine.ts +326 -31
- package/src/services/update-plan.ts +293 -33
- package/src/types/index.ts +7 -0
- package/src/ui/App.tsx +186 -123
- package/src/ui/adapters/cliToolsAdapter.ts +203 -1
- package/src/ui/adapters/failureReport.ts +137 -0
- package/src/ui/adapters/pluginsAdapter.ts +299 -28
- package/src/ui/adapters/startupReports.ts +282 -0
- package/src/ui/components/modals/FailureListModal.tsx +231 -0
- package/src/ui/components/modals/MessageModal.tsx +7 -13
- package/src/ui/components/modals/ModalContainer.tsx +10 -5
- package/src/ui/components/modals/index.ts +1 -0
- package/src/ui/hooks/startupQueue.ts +56 -0
- package/src/ui/hooks/useGitignoreModal.ts +31 -31
- package/src/ui/hooks/useProfileStartupModal.ts +127 -122
- package/src/ui/hooks/useStaleLoadModal.ts +33 -53
- package/src/ui/keymap.ts +14 -1
- package/src/ui/renderers/mcpRenderers.tsx +110 -3
- package/src/ui/renderers/pluginRenderers.tsx +77 -6
- package/src/ui/renderers/pluginRowText.ts +25 -25
- package/src/ui/renderers/pluginStatusText.ts +18 -1
- package/src/ui/screens/AliasScreen.tsx +38 -44
- package/src/ui/screens/CliToolsScreen.tsx +65 -51
- package/src/ui/screens/EnvVarsScreen.tsx +21 -6
- package/src/ui/screens/GitignoreScreen.tsx +49 -75
- package/src/ui/screens/McpRegistryScreen.tsx +72 -40
- package/src/ui/screens/McpScreen.tsx +55 -24
- package/src/ui/screens/ModelsScreen.tsx +46 -86
- package/src/ui/screens/PluginsScreen.tsx +316 -245
- package/src/ui/screens/ProfilesScreen.tsx +88 -73
- package/src/ui/screens/SkillsScreen.tsx +83 -80
- package/src/ui/screens/StylesScreen.tsx +211 -275
- package/src/ui/state/AppContext.tsx +72 -7
- package/src/ui/state/DimensionsContext.tsx +3 -22
- package/src/ui/state/reducer.ts +25 -14
- package/src/ui/state/types.ts +61 -31
- package/src/utils/user-path.ts +60 -0
- package/src/__tests__/styles-status-line.test.ts +0 -72
- package/src/__tests__/version-snapshot.test.ts +0 -212
- package/src/services/version-snapshot.ts +0 -189
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* magus 7.8.0 QA spec, section R — which installed copy Claude Code loads.
|
|
3
|
+
*
|
|
4
|
+
* Written blind: from the spec and the exported signatures of
|
|
5
|
+
* services/plugin-resolution.ts only. No function body was read.
|
|
6
|
+
*
|
|
7
|
+
* Coverage map (requirement → describe block):
|
|
8
|
+
* R1 user / managed rows always relevant → "R1"
|
|
9
|
+
* R2 project row at exactly cwd is relevant → "R2"
|
|
10
|
+
* R3 project row with no projectPath never counts → "R3"
|
|
11
|
+
* R4 same-repository rows count, others do not → "R4"
|
|
12
|
+
* R5 cwd in no repository: other folders never → "R5"
|
|
13
|
+
* R6 pickLoadedEntry selection rules → "R6"
|
|
14
|
+
* R7 end to end, outside git → "R7"
|
|
15
|
+
*
|
|
16
|
+
* Real git repositories in the OS temp dir, because "same repository" is a
|
|
17
|
+
* question git answers — a hand-written `.git` would confirm whatever parse it
|
|
18
|
+
* was written from.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
22
|
+
import { execFileSync } from "node:child_process";
|
|
23
|
+
import os from "node:os";
|
|
24
|
+
import path from "node:path";
|
|
25
|
+
import fs from "fs-extra";
|
|
26
|
+
import {
|
|
27
|
+
isRelevantInstallation,
|
|
28
|
+
pickLoadedEntry,
|
|
29
|
+
repoRootForPath,
|
|
30
|
+
} from "../services/plugin-resolution.js";
|
|
31
|
+
import type { InstalledPluginEntry } from "../types/index.js";
|
|
32
|
+
|
|
33
|
+
const GIT_CFG = [
|
|
34
|
+
"-c",
|
|
35
|
+
"user.name=blind",
|
|
36
|
+
"-c",
|
|
37
|
+
"user.email=blind@example.com",
|
|
38
|
+
"-c",
|
|
39
|
+
"commit.gpgsign=false",
|
|
40
|
+
"-c",
|
|
41
|
+
"init.defaultBranch=main",
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
const git = (cwd: string, ...args: string[]) =>
|
|
45
|
+
execFileSync("git", [...GIT_CFG, ...args], { cwd, encoding: "utf8" }).trim();
|
|
46
|
+
|
|
47
|
+
let base: string;
|
|
48
|
+
/** Repository under test. */
|
|
49
|
+
let repo: string;
|
|
50
|
+
/** A folder inside it. */
|
|
51
|
+
let repoSub: string;
|
|
52
|
+
/** A live linked worktree of `repo`. */
|
|
53
|
+
let sibling: string;
|
|
54
|
+
/** A linked worktree of `repo`, OUTSIDE its tree, created and then deleted. */
|
|
55
|
+
let deletedWorktree: string;
|
|
56
|
+
/** A linked worktree of `repo`, INSIDE its tree (Claude Code's own layout), deleted. */
|
|
57
|
+
let deletedNested: string;
|
|
58
|
+
/** A different repository. */
|
|
59
|
+
let otherRepo: string;
|
|
60
|
+
/** Folders in no repository. */
|
|
61
|
+
let plainA: string;
|
|
62
|
+
let plainB: string;
|
|
63
|
+
/** A path in no repository that does not exist. */
|
|
64
|
+
let plainMissing: string;
|
|
65
|
+
/** Install directories that exist on disk. */
|
|
66
|
+
let cacheDir: Record<string, string>;
|
|
67
|
+
/** An install directory that does not exist. */
|
|
68
|
+
let missingCache: string;
|
|
69
|
+
|
|
70
|
+
function row(
|
|
71
|
+
over: Partial<InstalledPluginEntry> & { version: string },
|
|
72
|
+
): InstalledPluginEntry {
|
|
73
|
+
return {
|
|
74
|
+
scope: "project",
|
|
75
|
+
installPath: cacheDir?.[over.version] ?? `/nonexistent/${over.version}`,
|
|
76
|
+
installedAt: "2026-01-01T00:00:00.000Z",
|
|
77
|
+
lastUpdated: "2026-01-01T00:00:00.000Z",
|
|
78
|
+
...over,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function relevant(entry: InstalledPluginEntry, cwd: string): boolean {
|
|
83
|
+
return isRelevantInstallation(entry, cwd, repoRootForPath(cwd));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
beforeAll(async () => {
|
|
87
|
+
base = await fs.realpath(
|
|
88
|
+
await fs.mkdtemp(path.join(os.tmpdir(), "blind-780-resolution-")),
|
|
89
|
+
);
|
|
90
|
+
repo = path.join(base, "repo");
|
|
91
|
+
repoSub = path.join(repo, "packages", "tool");
|
|
92
|
+
sibling = path.join(base, "repo-sibling");
|
|
93
|
+
deletedWorktree = path.join(base, "repo-gone");
|
|
94
|
+
deletedNested = path.join(repo, ".claude", "worktrees", "gone-nested");
|
|
95
|
+
otherRepo = path.join(base, "other");
|
|
96
|
+
plainA = path.join(base, "plain-a");
|
|
97
|
+
plainB = path.join(base, "plain-b");
|
|
98
|
+
plainMissing = path.join(base, "plain-missing");
|
|
99
|
+
missingCache = path.join(base, "cache", "does-not-exist");
|
|
100
|
+
|
|
101
|
+
await fs.ensureDir(repoSub);
|
|
102
|
+
await fs.writeFile(path.join(repoSub, "x.txt"), "x\n");
|
|
103
|
+
git(repo, "init", "-q");
|
|
104
|
+
git(repo, "add", "-A");
|
|
105
|
+
git(repo, "commit", "-qm", "init");
|
|
106
|
+
git(repo, "worktree", "add", "-q", "-b", "sib", sibling);
|
|
107
|
+
git(repo, "worktree", "add", "-q", "-b", "gone", deletedWorktree);
|
|
108
|
+
await fs.remove(deletedWorktree);
|
|
109
|
+
git(repo, "worktree", "add", "-q", "-b", "gone-nested", deletedNested);
|
|
110
|
+
await fs.remove(deletedNested);
|
|
111
|
+
|
|
112
|
+
await fs.ensureDir(otherRepo);
|
|
113
|
+
await fs.writeFile(path.join(otherRepo, "y.txt"), "y\n");
|
|
114
|
+
git(otherRepo, "init", "-q");
|
|
115
|
+
git(otherRepo, "add", "-A");
|
|
116
|
+
git(otherRepo, "commit", "-qm", "init");
|
|
117
|
+
|
|
118
|
+
await fs.ensureDir(plainA);
|
|
119
|
+
await fs.ensureDir(plainB);
|
|
120
|
+
|
|
121
|
+
cacheDir = {};
|
|
122
|
+
for (const v of ["0.0.0", "1.0.0", "2.0.0", "3.0.0"]) {
|
|
123
|
+
const dir = path.join(base, "cache", v);
|
|
124
|
+
await fs.ensureDir(dir);
|
|
125
|
+
cacheDir[v] = dir;
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
afterAll(async () => {
|
|
130
|
+
await fs.remove(base);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe("preconditions of the temp layout", () => {
|
|
134
|
+
test("the plain folders are in no git repository, the repo folders are", () => {
|
|
135
|
+
expect(repoRootForPath(plainA)).toBeNull();
|
|
136
|
+
expect(repoRootForPath(plainB)).toBeNull();
|
|
137
|
+
expect(repoRootForPath(plainMissing)).toBeNull();
|
|
138
|
+
expect(repoRootForPath(repo)).not.toBeNull();
|
|
139
|
+
expect(repoRootForPath(otherRepo)).not.toBeNull();
|
|
140
|
+
expect(fs.existsSync(deletedWorktree)).toBe(false);
|
|
141
|
+
expect(fs.existsSync(deletedNested)).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe("R1 machine-wide rows", () => {
|
|
146
|
+
test("R1 a USER row with no projectPath is relevant inside a repository", () => {
|
|
147
|
+
expect(relevant(row({ scope: "user", version: "1.0.0" }), repo)).toBe(true);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("R1 a USER row is relevant in a folder outside git", () => {
|
|
151
|
+
expect(relevant(row({ scope: "user", version: "1.0.0" }), plainA)).toBe(
|
|
152
|
+
true,
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("R1 a USER row carrying another folder's projectPath is still relevant", () => {
|
|
157
|
+
const entry = row({ scope: "user", version: "1.0.0", projectPath: plainB });
|
|
158
|
+
|
|
159
|
+
expect(relevant(entry, plainA)).toBe(true);
|
|
160
|
+
expect(relevant(entry, repo)).toBe(true);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("R1 a managed row is relevant inside and outside a repository", () => {
|
|
164
|
+
const managed = {
|
|
165
|
+
...row({ version: "1.0.0" }),
|
|
166
|
+
scope: "managed",
|
|
167
|
+
} as unknown as InstalledPluginEntry;
|
|
168
|
+
|
|
169
|
+
expect(relevant(managed, repo)).toBe(true);
|
|
170
|
+
expect(relevant(managed, plainA)).toBe(true);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe("R2 the project row for exactly this folder", () => {
|
|
175
|
+
test("R2 relevant when cwd is a repository root", () => {
|
|
176
|
+
expect(relevant(row({ version: "1.0.0", projectPath: repo }), repo)).toBe(
|
|
177
|
+
true,
|
|
178
|
+
);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("R2 relevant when cwd is a folder outside git", () => {
|
|
182
|
+
expect(
|
|
183
|
+
relevant(row({ version: "1.0.0", projectPath: plainA }), plainA),
|
|
184
|
+
).toBe(true);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("R2 relevant when cwd is a folder outside git that does not exist", () => {
|
|
188
|
+
expect(
|
|
189
|
+
relevant(
|
|
190
|
+
row({ version: "1.0.0", projectPath: plainMissing }),
|
|
191
|
+
plainMissing,
|
|
192
|
+
),
|
|
193
|
+
).toBe(true);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe("R3 a project row with no projectPath", () => {
|
|
198
|
+
test("R3 is not relevant inside a repository", () => {
|
|
199
|
+
expect(relevant(row({ version: "1.0.0" }), repo)).toBe(false);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("R3 is not relevant outside git", () => {
|
|
203
|
+
expect(relevant(row({ version: "1.0.0" }), plainA)).toBe(false);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
describe("R4 cwd inside a git repository", () => {
|
|
208
|
+
test("R4 a row from a subfolder of the same repository is relevant", () => {
|
|
209
|
+
expect(
|
|
210
|
+
relevant(row({ version: "1.0.0", projectPath: repoSub }), repo),
|
|
211
|
+
).toBe(true);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("R4 a row from the repository root is relevant in a subfolder", () => {
|
|
215
|
+
expect(
|
|
216
|
+
relevant(row({ version: "1.0.0", projectPath: repo }), repoSub),
|
|
217
|
+
).toBe(true);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test("R4 a row from a sibling worktree is relevant", () => {
|
|
221
|
+
expect(
|
|
222
|
+
relevant(row({ version: "1.0.0", projectPath: sibling }), repo),
|
|
223
|
+
).toBe(true);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("R4 a row from the main tree is relevant when cwd is the sibling worktree", () => {
|
|
227
|
+
expect(
|
|
228
|
+
relevant(row({ version: "1.0.0", projectPath: repo }), sibling),
|
|
229
|
+
).toBe(true);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("R4 a row from a deleted worktree nested in the repository is relevant", () => {
|
|
233
|
+
expect(
|
|
234
|
+
relevant(row({ version: "1.0.0", projectPath: deletedNested }), repo),
|
|
235
|
+
).toBe(true);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// Spec correction after the blind run: Claude Code's root lookup is a path
|
|
239
|
+
// walk to the nearest `.git` (2.1.284 `Zt`), so a deleted worktree that sat
|
|
240
|
+
// OUTSIDE the repository's folder has no ancestor carrying one and resolves to
|
|
241
|
+
// no repository. The loader does not count it, and neither may magus.
|
|
242
|
+
test("R4 a row from a deleted worktree outside the repository's tree is NOT relevant", () => {
|
|
243
|
+
expect(
|
|
244
|
+
relevant(row({ version: "1.0.0", projectPath: deletedWorktree }), repo),
|
|
245
|
+
).toBe(false);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test("R4 a row from another repository is not relevant", () => {
|
|
249
|
+
expect(
|
|
250
|
+
relevant(row({ version: "1.0.0", projectPath: otherRepo }), repo),
|
|
251
|
+
).toBe(false);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test("R4 a row from a folder outside git is not relevant", () => {
|
|
255
|
+
expect(relevant(row({ version: "1.0.0", projectPath: plainA }), repo)).toBe(
|
|
256
|
+
false,
|
|
257
|
+
);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test("R4 a local-scope row from a sibling worktree is relevant", () => {
|
|
261
|
+
expect(
|
|
262
|
+
relevant(
|
|
263
|
+
row({ scope: "local", version: "1.0.0", projectPath: sibling }),
|
|
264
|
+
repo,
|
|
265
|
+
),
|
|
266
|
+
).toBe(true);
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
describe("R5 cwd in no git repository", () => {
|
|
271
|
+
test("R5 a row from another existing folder outside git is not relevant", () => {
|
|
272
|
+
expect(
|
|
273
|
+
relevant(row({ version: "1.0.0", projectPath: plainB }), plainA),
|
|
274
|
+
).toBe(false);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test("R5 a row from a missing folder outside git is not relevant", () => {
|
|
278
|
+
expect(
|
|
279
|
+
relevant(row({ version: "1.0.0", projectPath: plainMissing }), plainA),
|
|
280
|
+
).toBe(false);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test("R5 a row from a folder inside a repository is not relevant", () => {
|
|
284
|
+
expect(relevant(row({ version: "1.0.0", projectPath: repo }), plainA)).toBe(
|
|
285
|
+
false,
|
|
286
|
+
);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test("R5 a local-scope row from another folder outside git is not relevant", () => {
|
|
290
|
+
expect(
|
|
291
|
+
relevant(
|
|
292
|
+
row({ scope: "local", version: "1.0.0", projectPath: plainB }),
|
|
293
|
+
plainA,
|
|
294
|
+
),
|
|
295
|
+
).toBe(false);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test("R5 holds when a cache is passed and reused across calls", () => {
|
|
299
|
+
const cache = new Map<string, string | null>();
|
|
300
|
+
const root = repoRootForPath(plainA, cache);
|
|
301
|
+
|
|
302
|
+
const results = [plainB, plainMissing, repo].map((p) =>
|
|
303
|
+
isRelevantInstallation(
|
|
304
|
+
row({ version: "1.0.0", projectPath: p }),
|
|
305
|
+
plainA,
|
|
306
|
+
root,
|
|
307
|
+
cache,
|
|
308
|
+
),
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
expect(results).toEqual([false, false, false]);
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
describe("R6 pickLoadedEntry", () => {
|
|
316
|
+
test("R6 returns undefined for undefined or empty input", () => {
|
|
317
|
+
expect(pickLoadedEntry(undefined, repo)).toBeUndefined();
|
|
318
|
+
expect(pickLoadedEntry([], repo)).toBeUndefined();
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test("R6 returns undefined when no entry is relevant", () => {
|
|
322
|
+
const entries = [
|
|
323
|
+
row({ version: "1.0.0", projectPath: otherRepo }),
|
|
324
|
+
row({ version: "2.0.0", projectPath: plainA }),
|
|
325
|
+
row({ version: "3.0.0" }),
|
|
326
|
+
];
|
|
327
|
+
|
|
328
|
+
expect(pickLoadedEntry(entries, repo)).toBeUndefined();
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
test("R6 exactly one relevant entry wins even though its installPath is missing", () => {
|
|
332
|
+
const only = row({
|
|
333
|
+
version: "9.9.9",
|
|
334
|
+
projectPath: repo,
|
|
335
|
+
installPath: missingCache,
|
|
336
|
+
});
|
|
337
|
+
const entries = [
|
|
338
|
+
row({ version: "1.0.0", projectPath: otherRepo }),
|
|
339
|
+
only,
|
|
340
|
+
row({ version: "2.0.0", projectPath: plainA }),
|
|
341
|
+
];
|
|
342
|
+
|
|
343
|
+
expect(pickLoadedEntry(entries, repo)).toBe(only);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
test("R6 several relevant: the first in array order whose installPath exists", () => {
|
|
347
|
+
const entries = [
|
|
348
|
+
row({ version: "0.0.0", projectPath: otherRepo }),
|
|
349
|
+
row({
|
|
350
|
+
version: "9.9.9",
|
|
351
|
+
projectPath: sibling,
|
|
352
|
+
installPath: missingCache,
|
|
353
|
+
}),
|
|
354
|
+
row({ version: "1.0.0", projectPath: deletedNested }),
|
|
355
|
+
row({ version: "3.0.0", projectPath: repo }),
|
|
356
|
+
];
|
|
357
|
+
|
|
358
|
+
expect(pickLoadedEntry(entries, repo)?.version).toBe("1.0.0");
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
test("R6 array order beats version order and scope", () => {
|
|
362
|
+
const entries = [
|
|
363
|
+
row({ version: "1.0.0", projectPath: sibling }),
|
|
364
|
+
row({ scope: "user", version: "3.0.0" }),
|
|
365
|
+
row({ version: "2.0.0", projectPath: repo }),
|
|
366
|
+
];
|
|
367
|
+
|
|
368
|
+
expect(pickLoadedEntry(entries, repo)?.version).toBe("1.0.0");
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
test("R6 none exists: the first relevant entry that has an installPath", () => {
|
|
372
|
+
const entries = [
|
|
373
|
+
row({ version: "0.0.0", projectPath: otherRepo }),
|
|
374
|
+
row({ version: "5.0.0", projectPath: sibling, installPath: "" }),
|
|
375
|
+
row({
|
|
376
|
+
version: "6.0.0",
|
|
377
|
+
projectPath: repo,
|
|
378
|
+
installPath: path.join(missingCache, "a"),
|
|
379
|
+
}),
|
|
380
|
+
row({
|
|
381
|
+
version: "7.0.0",
|
|
382
|
+
projectPath: repoSub,
|
|
383
|
+
installPath: path.join(missingCache, "b"),
|
|
384
|
+
}),
|
|
385
|
+
];
|
|
386
|
+
|
|
387
|
+
expect(pickLoadedEntry(entries, repo)?.version).toBe("6.0.0");
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
describe("R7 end to end, cwd outside git", () => {
|
|
392
|
+
test("R7 own project row first: v2 loads, never the earlier foreign v0", () => {
|
|
393
|
+
const entries = [
|
|
394
|
+
row({ version: "0.0.0", projectPath: plainB }),
|
|
395
|
+
row({ version: "2.0.0", projectPath: plainA }),
|
|
396
|
+
row({ scope: "user", version: "1.0.0" }),
|
|
397
|
+
];
|
|
398
|
+
|
|
399
|
+
expect(pickLoadedEntry(entries, plainA)?.version).toBe("2.0.0");
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
test("R7 USER row first: v1 loads, never the earlier foreign v0", () => {
|
|
403
|
+
const entries = [
|
|
404
|
+
row({ version: "0.0.0", projectPath: plainB }),
|
|
405
|
+
row({ scope: "user", version: "1.0.0" }),
|
|
406
|
+
row({ version: "2.0.0", projectPath: plainA }),
|
|
407
|
+
];
|
|
408
|
+
|
|
409
|
+
expect(pickLoadedEntry(entries, plainA)?.version).toBe("1.0.0");
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
test("R7 the first relevant row missing on disk: the next existing one loads", () => {
|
|
413
|
+
const entries = [
|
|
414
|
+
row({ version: "0.0.0", projectPath: plainB }),
|
|
415
|
+
row({ scope: "user", version: "1.0.0", installPath: missingCache }),
|
|
416
|
+
row({ version: "2.0.0", projectPath: plainA }),
|
|
417
|
+
];
|
|
418
|
+
|
|
419
|
+
expect(pickLoadedEntry(entries, plainA)?.version).toBe("2.0.0");
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
test("R7 a foreign row from a missing outside-git folder is ignored too", () => {
|
|
423
|
+
const entries = [
|
|
424
|
+
row({ version: "0.0.0", projectPath: plainMissing }),
|
|
425
|
+
row({ scope: "user", version: "1.0.0" }),
|
|
426
|
+
row({ version: "2.0.0", projectPath: plainA }),
|
|
427
|
+
];
|
|
428
|
+
|
|
429
|
+
expect(pickLoadedEntry(entries, plainA)?.version).toBe("1.0.0");
|
|
430
|
+
});
|
|
431
|
+
});
|