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
|
@@ -303,7 +303,9 @@ describe("repairStaleLoad — the backup", () => {
|
|
|
303
303
|
const result = await repairStaleLoad(PLUGIN, "0.5.0", here, root);
|
|
304
304
|
|
|
305
305
|
expect(result.ok).toBe(false);
|
|
306
|
-
expect(result.error).
|
|
306
|
+
expect(result.error).toBe(
|
|
307
|
+
"could not save a backup before changing anything, so nothing was changed",
|
|
308
|
+
);
|
|
307
309
|
expect(result.rewritten).toBe(0);
|
|
308
310
|
expect(result.backup).toBeNull();
|
|
309
311
|
expect(await fs.readFile(registryFile, "utf8")).toBe(before);
|
|
@@ -328,7 +330,7 @@ describe("repairStaleLoad — refusals", () => {
|
|
|
328
330
|
const result = await repairStaleLoad(PLUGIN, "0.7.0", here, root);
|
|
329
331
|
|
|
330
332
|
expect(result.ok).toBe(false);
|
|
331
|
-
expect(result.error).
|
|
333
|
+
expect(result.error).toBe("v0.7.0 is not downloaded — run magus install");
|
|
332
334
|
expect(result.backup).toBeNull();
|
|
333
335
|
expect(await fs.readFile(registryFile, "utf8")).toBe(before);
|
|
334
336
|
expect(await backups()).toEqual([]);
|
|
@@ -340,7 +342,7 @@ describe("repairStaleLoad — refusals", () => {
|
|
|
340
342
|
const result = await repairStaleLoad(PLUGIN, "0.5.0", here, root);
|
|
341
343
|
|
|
342
344
|
expect(result.ok).toBe(false);
|
|
343
|
-
expect(result.error).
|
|
345
|
+
expect(result.error).toBe("it is not installed on this machine");
|
|
344
346
|
});
|
|
345
347
|
|
|
346
348
|
test("a string that is not a plugin id is refused", async () => {
|
|
@@ -360,7 +362,9 @@ describe("repairStaleLoad — refusals", () => {
|
|
|
360
362
|
const result = await repairStaleLoad(PLUGIN, "0.5.0", here, root);
|
|
361
363
|
|
|
362
364
|
expect(result.ok).toBe(false);
|
|
363
|
-
expect(result.error).
|
|
365
|
+
expect(result.error).toBe(
|
|
366
|
+
"Claude Code's list of installed plugins cannot be read, so nothing was changed — run magus doctor",
|
|
367
|
+
);
|
|
364
368
|
expect(await fs.readFile(registryFile, "utf8")).toBe("{ not json");
|
|
365
369
|
});
|
|
366
370
|
});
|
|
@@ -400,8 +404,9 @@ describe("repairStaleLoad — scope reach", () => {
|
|
|
400
404
|
const result = await repairStaleLoad(PLUGIN, "0.5.0", here, root);
|
|
401
405
|
|
|
402
406
|
expect(result.ok).toBe(false);
|
|
403
|
-
expect(result.error).
|
|
404
|
-
|
|
407
|
+
expect(result.error).toBe(
|
|
408
|
+
"Claude Code loads the USER install of it here — update it at USER scope",
|
|
409
|
+
);
|
|
405
410
|
expect(result.rewritten).toBe(0);
|
|
406
411
|
expect(await fs.readFile(registryFile, "utf8")).toBe(before);
|
|
407
412
|
expect(await backups()).toEqual([]);
|
|
@@ -457,8 +462,9 @@ describe("repairStaleLoad — scope reach", () => {
|
|
|
457
462
|
const result = await repairStaleLoad(PLUGIN, "0.5.0", here, root);
|
|
458
463
|
|
|
459
464
|
expect(result.ok).toBe(false);
|
|
460
|
-
expect(result.error).
|
|
461
|
-
|
|
465
|
+
expect(result.error).toBe(
|
|
466
|
+
"Claude Code loads the install your administrator manages here — ask them to update it",
|
|
467
|
+
);
|
|
462
468
|
});
|
|
463
469
|
|
|
464
470
|
test("a user-scope row that is not the winner stays where it is", async () => {
|
|
@@ -485,13 +491,11 @@ describe("repairStaleLoad — scope reach", () => {
|
|
|
485
491
|
expect(after[1].installPath).toBe(cacheFor("0.4.0"));
|
|
486
492
|
});
|
|
487
493
|
|
|
488
|
-
test("a rootless row from another directory
|
|
489
|
-
//
|
|
490
|
-
//
|
|
491
|
-
//
|
|
492
|
-
//
|
|
493
|
-
// repository, so the repair reports that it could not fix this rather than
|
|
494
|
-
// editing a stranger's row.
|
|
494
|
+
test("a rootless row from another directory neither competes nor is rewritten", async () => {
|
|
495
|
+
// Claude Code 2.1.284 matches nothing by root in a folder that is in no
|
|
496
|
+
// repository, so the other folder's older row does not load here and this
|
|
497
|
+
// folder's own row does. Nothing to repair, and the stranger's row is left
|
|
498
|
+
// exactly as it was.
|
|
495
499
|
const looseHere = path.join(base, "loose-here");
|
|
496
500
|
const looseOther = path.join(base, "loose-other");
|
|
497
501
|
await fs.ensureDir(looseHere);
|
|
@@ -507,13 +511,13 @@ describe("repairStaleLoad — scope reach", () => {
|
|
|
507
511
|
looseHere,
|
|
508
512
|
repoRootForPath(looseHere),
|
|
509
513
|
),
|
|
510
|
-
).toBe(
|
|
514
|
+
).toBe(false);
|
|
511
515
|
|
|
512
516
|
const result = await repairStaleLoad(PLUGIN, "0.5.0", looseHere, root);
|
|
513
517
|
|
|
514
518
|
expect(result.rewritten).toBe(0);
|
|
515
|
-
expect(result.ok).toBe(
|
|
516
|
-
expect(result.
|
|
519
|
+
expect(result.ok).toBe(true);
|
|
520
|
+
expect(result.loaded).toBe("0.5.0");
|
|
517
521
|
expect((await readRegistry()).plugins[PLUGIN][0].version).toBe("0.3.0");
|
|
518
522
|
});
|
|
519
523
|
});
|
|
@@ -557,7 +561,9 @@ describe("repairStaleLoad — a concurrent write is refused, not clobbered", ()
|
|
|
557
561
|
const result = await repairStaleLoad(PLUGIN, "0.5.0", here, root);
|
|
558
562
|
|
|
559
563
|
expect(result.ok).toBe(false);
|
|
560
|
-
expect(result.error).
|
|
564
|
+
expect(result.error).toBe(
|
|
565
|
+
"another Claude Code session changed its plugins at the same moment, so nothing was changed — run it again",
|
|
566
|
+
);
|
|
561
567
|
expect(result.rewritten).toBe(0);
|
|
562
568
|
} finally {
|
|
563
569
|
spy.mockRestore();
|
|
@@ -694,7 +700,9 @@ describe("repairStaleLoad — the post-write assertion", () => {
|
|
|
694
700
|
expect(result.rewritten).toBe(1);
|
|
695
701
|
expect(result.loaded).toBe("0.9.0");
|
|
696
702
|
expect(result.ok).toBe(false);
|
|
697
|
-
expect(result.error).toContain(
|
|
703
|
+
expect(result.error).toContain(
|
|
704
|
+
"Claude Code still loads v0.9.0 in this folder",
|
|
705
|
+
);
|
|
698
706
|
});
|
|
699
707
|
|
|
700
708
|
test("the assertion reads the file from DISK, not the object it mutated", async () => {
|
|
@@ -1147,7 +1155,7 @@ describe("B4: an update brings the loader forward in the same run", () => {
|
|
|
1147
1155
|
expect(result.ok).toBe(1);
|
|
1148
1156
|
expect(result.failed).toEqual([]);
|
|
1149
1157
|
expect(details).toEqual([
|
|
1150
|
-
"project → 0.5.0 · Claude Code now loads v0.5.0
|
|
1158
|
+
"project → 0.5.0 · Claude Code now loads v0.5.0 in this folder",
|
|
1151
1159
|
]);
|
|
1152
1160
|
expect(await loadedVersionHere(PLUGIN, here, root)).toBe("0.5.0");
|
|
1153
1161
|
// Moved forward, nothing removed.
|
|
@@ -1265,7 +1273,7 @@ describe("7.5.2: Claude Code 2.1.281 updates another project's row of the reposi
|
|
|
1265
1273
|
[here, "0.5.0"],
|
|
1266
1274
|
]);
|
|
1267
1275
|
expect(details).toEqual([
|
|
1268
|
-
|
|
1276
|
+
"project → 0.5.0 · Claude Code updated another folder of this repository instead of this one — this folder now loads v0.5.0 too",
|
|
1269
1277
|
]);
|
|
1270
1278
|
expect(await loadedVersionHere(PLUGIN, here, root)).toBe("0.5.0");
|
|
1271
1279
|
});
|
|
@@ -1335,7 +1343,7 @@ describe("7.5.2: Claude Code 2.1.281 updates another project's row of the reposi
|
|
|
1335
1343
|
?.version,
|
|
1336
1344
|
).toBe("0.4.0");
|
|
1337
1345
|
expect(details[0]).toContain(
|
|
1338
|
-
`no change — the installer exited 0 but ${PLUGIN} is still 0.4.0 (Claude Code updated
|
|
1346
|
+
`no change — the installer exited 0 but ${PLUGIN} is still 0.4.0 (Claude Code updated another folder of this repository instead of this one, and this folder could not follow: v0.6.0 is not downloaded — run magus install)`,
|
|
1339
1347
|
);
|
|
1340
1348
|
});
|
|
1341
1349
|
});
|
|
@@ -26,7 +26,6 @@ import type {
|
|
|
26
26
|
UpdatePlan,
|
|
27
27
|
} from "../services/update-plan.js";
|
|
28
28
|
import type { InstalledPluginEntry, ResolvedBin } from "../types/index.js";
|
|
29
|
-
import { noMoveReasons } from "../ui/adapters/pluginsAdapter.js";
|
|
30
29
|
|
|
31
30
|
const PROJECT = "/tmp/project";
|
|
32
31
|
|
|
@@ -776,25 +775,6 @@ describe("applyPlugins — read-back against beforeVersions", () => {
|
|
|
776
775
|
expect(result.ok).toBe(1);
|
|
777
776
|
expect(result.unchanged).toEqual([]);
|
|
778
777
|
});
|
|
779
|
-
|
|
780
|
-
test("the TUI's collector names the same cause the CLI prints", async () => {
|
|
781
|
-
// The screen used to branch on `failed.length` alone: a no-op update
|
|
782
|
-
// closed with a success and a row that had not moved.
|
|
783
|
-
const { deps } = stubDeps("1.0.0");
|
|
784
|
-
const reasons = noMoveReasons();
|
|
785
|
-
const result = await applyPlugins(
|
|
786
|
-
[item({ beforeVersions: { project: "1.0.0" } })],
|
|
787
|
-
PROJECT,
|
|
788
|
-
deps,
|
|
789
|
-
reasons.reporter,
|
|
790
|
-
);
|
|
791
|
-
expect(result.unchanged).toEqual(["dev@magus"]);
|
|
792
|
-
expect(reasons.lines(result.unchanged)).toBe(
|
|
793
|
-
"dev@magus: the installer exited 0 but dev@magus is still 1.0.0",
|
|
794
|
-
);
|
|
795
|
-
// An item with no recorded cause still gets a line.
|
|
796
|
-
expect(reasons.lines(["other@magus"])).toBe("other@magus");
|
|
797
|
-
});
|
|
798
778
|
});
|
|
799
779
|
|
|
800
780
|
// ── B4: installed must mean LOADED ──────────────────────────────────────────
|
|
@@ -847,8 +827,7 @@ describe("B4: an install or update ends with the loader on the new version", ()
|
|
|
847
827
|
{
|
|
848
828
|
ok: true,
|
|
849
829
|
name: "dev@magus",
|
|
850
|
-
detail:
|
|
851
|
-
"project → 2.0.0 · Claude Code now loads v2.0.0 here (3 older records moved forward)",
|
|
830
|
+
detail: "project → 2.0.0 · Claude Code now loads v2.0.0 in this folder",
|
|
852
831
|
},
|
|
853
832
|
]);
|
|
854
833
|
});
|
|
@@ -876,8 +855,10 @@ describe("B4: an install or update ends with the loader on the new version", ()
|
|
|
876
855
|
{
|
|
877
856
|
ok: false,
|
|
878
857
|
name: "dev@magus",
|
|
858
|
+
// The version still loading IS the reason; the repair's own message
|
|
859
|
+
// said it a second time, in the mechanism's words.
|
|
879
860
|
detail:
|
|
880
|
-
"installed v2.0.0, but Claude Code still loads v1.0.0
|
|
861
|
+
"installed v2.0.0, but Claude Code still loads v1.0.0 in this folder",
|
|
881
862
|
},
|
|
882
863
|
]);
|
|
883
864
|
});
|
|
@@ -917,7 +898,7 @@ describe("B4: an install or update ends with the loader on the new version", ()
|
|
|
917
898
|
await expect(
|
|
918
899
|
updatePluginInScope("dev@magus", "project", PROJECT, deps),
|
|
919
900
|
).rejects.toThrow(
|
|
920
|
-
"installed v2.0.0, but Claude Code still loads v1.0.0
|
|
901
|
+
"installed v2.0.0, but Claude Code still loads v1.0.0 in this folder",
|
|
921
902
|
);
|
|
922
903
|
});
|
|
923
904
|
|
|
@@ -938,7 +919,9 @@ describe("B4: an install or update ends with the loader on the new version", ()
|
|
|
938
919
|
const { deps } = loaderDeps("1.0.0", false);
|
|
939
920
|
await expect(
|
|
940
921
|
updatePluginInScope("dev@magus", "user", PROJECT, deps),
|
|
941
|
-
).rejects.toThrow(
|
|
922
|
+
).rejects.toThrow(
|
|
923
|
+
"installed v2.0.0, but Claude Code still loads v1.0.0 in this folder",
|
|
924
|
+
);
|
|
942
925
|
});
|
|
943
926
|
});
|
|
944
927
|
|
|
@@ -1067,7 +1050,8 @@ describe("7.5.2: an update Claude Code applied to another project's row", () =>
|
|
|
1067
1050
|
{
|
|
1068
1051
|
ok: true,
|
|
1069
1052
|
name: "dev@magus",
|
|
1070
|
-
detail:
|
|
1053
|
+
detail:
|
|
1054
|
+
"project → 8.1.3 · Claude Code updated another folder of this repository instead of this one — this folder now loads v8.1.3 too",
|
|
1071
1055
|
},
|
|
1072
1056
|
]);
|
|
1073
1057
|
});
|
|
@@ -1084,7 +1068,7 @@ describe("7.5.2: an update Claude Code applied to another project's row", () =>
|
|
|
1084
1068
|
// forward by the loader check (B4), and no misdirection is reported.
|
|
1085
1069
|
expect(repaired).toEqual(["8.1.3"]);
|
|
1086
1070
|
expect(rows[0]?.detail).toBe(
|
|
1087
|
-
"project → 8.1.3 · Claude Code now loads v8.1.3
|
|
1071
|
+
"project → 8.1.3 · Claude Code now loads v8.1.3 in this folder",
|
|
1088
1072
|
);
|
|
1089
1073
|
});
|
|
1090
1074
|
|
|
@@ -1097,7 +1081,7 @@ describe("7.5.2: an update Claude Code applied to another project's row", () =>
|
|
|
1097
1081
|
expect(row(HERE).version).toBe("8.0.0");
|
|
1098
1082
|
expect(result.unchanged).toEqual(["dev@magus"]);
|
|
1099
1083
|
expect(rows[0]?.detail).toBe(
|
|
1100
|
-
|
|
1084
|
+
"no change — the installer exited 0 but dev@magus is still 8.0.0 (Claude Code updated another folder of this repository instead of this one, and this folder could not follow: no cache directory for v8.1.3)",
|
|
1101
1085
|
);
|
|
1102
1086
|
});
|
|
1103
1087
|
|
|
@@ -1125,7 +1109,7 @@ describe("7.5.2: an update Claude Code applied to another project's row", () =>
|
|
|
1125
1109
|
expect(repaired).toEqual([]);
|
|
1126
1110
|
expect(result.unchanged).toEqual(["dev@magus"]);
|
|
1127
1111
|
expect(rows[0]?.detail).toBe(
|
|
1128
|
-
|
|
1112
|
+
"no change — the installer exited 0 but dev@magus is still 8.0.0 (Claude Code updated another folder of this repository instead of this one, and this folder could not follow: it went to v8.5.0, not the v8.1.3 this update asked for)",
|
|
1129
1113
|
);
|
|
1130
1114
|
});
|
|
1131
1115
|
|
|
@@ -430,19 +430,23 @@ describe("per-scope decisions — a stale scope is never masked", () => {
|
|
|
430
430
|
// No note: there is nothing for the reader to do about it.
|
|
431
431
|
expect(item.note).toBeUndefined();
|
|
432
432
|
// And it is not confused with the withdrawn case, which is the next test.
|
|
433
|
-
expect(item.reason).not.toBe("
|
|
433
|
+
expect(item.reason).not.toBe("deprecated");
|
|
434
434
|
});
|
|
435
435
|
|
|
436
|
-
test("
|
|
436
|
+
test("a declared plugin ABSENT from every catalog is deprecated", () => {
|
|
437
437
|
// The genuinely withdrawn case: no catalog entry at all, the installed
|
|
438
438
|
// version known only from the registry.
|
|
439
439
|
const [item] = planPluginUpdates(
|
|
440
440
|
{ "withdrawn@magus": "latest" },
|
|
441
441
|
catalog(info({ id: "dev@magus", version: "4.6.1" })),
|
|
442
442
|
);
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
443
|
+
// Its marketplace was read and does not list it: no longer published, so
|
|
444
|
+
// the work is removing it — here from the profile alone, since nothing is
|
|
445
|
+
// installed to uninstall.
|
|
446
|
+
expect(item.action).toBe("remove");
|
|
447
|
+
expect(item.note).toBe("no longer published");
|
|
448
|
+
expect(item.reason).toBe("deprecated");
|
|
449
|
+
expect(item.scopes).toEqual([]);
|
|
446
450
|
});
|
|
447
451
|
});
|
|
448
452
|
|
|
@@ -993,19 +997,20 @@ describe("classifyPlugin — an orphan is never 'current'", () => {
|
|
|
993
997
|
info({ id: "dev@magus", version: "4.7.0" }),
|
|
994
998
|
),
|
|
995
999
|
);
|
|
996
|
-
expect(item.action).toBe("
|
|
1000
|
+
expect(item.action).toBe("remove");
|
|
997
1001
|
expect(item.action).not.toBe("current");
|
|
998
1002
|
expect(item.available).toBeNull();
|
|
999
1003
|
expect(item.target).toBeNull();
|
|
1000
|
-
|
|
1004
|
+
// A removal's scopes are where it is uninstalled: every scope installed here.
|
|
1005
|
+
expect(item.scopes).toEqual(["project"]);
|
|
1001
1006
|
});
|
|
1002
1007
|
|
|
1003
1008
|
// `moved` is GONE, and `movedTo` with it. It was inferred from a bare name
|
|
1004
1009
|
// match: a plugin published to two marketplaces — claudish is — reported
|
|
1005
1010
|
// "moved → magus" when nothing had moved and both copies were installed.
|
|
1006
1011
|
// The inference cannot tell a move from a dual publication, so it is not made
|
|
1007
|
-
// at all. An orphan is `
|
|
1008
|
-
test("an orphan is
|
|
1012
|
+
// at all. An orphan is `deprecated`, which is a fact rather than a guess.
|
|
1013
|
+
test("an orphan is deprecated, and never guesses where it went", () => {
|
|
1009
1014
|
const [item] = planPluginUpdates(
|
|
1010
1015
|
{ "seo@magus": "latest" },
|
|
1011
1016
|
catalog(
|
|
@@ -1014,13 +1019,13 @@ describe("classifyPlugin — an orphan is never 'current'", () => {
|
|
|
1014
1019
|
),
|
|
1015
1020
|
);
|
|
1016
1021
|
expect(item).toMatchObject({
|
|
1017
|
-
action: "
|
|
1018
|
-
reason: "
|
|
1022
|
+
action: "remove",
|
|
1023
|
+
reason: "deprecated",
|
|
1019
1024
|
});
|
|
1020
1025
|
expect(item.note).not.toMatch(/moved/i);
|
|
1021
1026
|
});
|
|
1022
1027
|
|
|
1023
|
-
test("an orphan whose marketplace we DID read is
|
|
1028
|
+
test("an orphan whose marketplace we DID read is deprecated", () => {
|
|
1024
1029
|
const [item] = planPluginUpdates(
|
|
1025
1030
|
{ "seo@magus": "latest" },
|
|
1026
1031
|
catalog(
|
|
@@ -1029,9 +1034,9 @@ describe("classifyPlugin — an orphan is never 'current'", () => {
|
|
|
1029
1034
|
),
|
|
1030
1035
|
);
|
|
1031
1036
|
expect(item).toMatchObject({
|
|
1032
|
-
action: "
|
|
1033
|
-
reason: "
|
|
1034
|
-
note: "
|
|
1037
|
+
action: "remove",
|
|
1038
|
+
reason: "deprecated",
|
|
1039
|
+
note: "no longer published",
|
|
1035
1040
|
});
|
|
1036
1041
|
});
|
|
1037
1042
|
|
|
@@ -1062,7 +1067,7 @@ describe("classifyPlugin — an orphan is never 'current'", () => {
|
|
|
1062
1067
|
expect(item.reason).toBe("marketplace-unread");
|
|
1063
1068
|
});
|
|
1064
1069
|
|
|
1065
|
-
test("an orphan is
|
|
1070
|
+
test("an orphan is deprecated under an exact pin too", () => {
|
|
1066
1071
|
const [item] = planPluginUpdates(
|
|
1067
1072
|
{ "seo@magus": "1.8.0" },
|
|
1068
1073
|
catalog(
|
|
@@ -1070,7 +1075,7 @@ describe("classifyPlugin — an orphan is never 'current'", () => {
|
|
|
1070
1075
|
info({ id: "dev@magus", version: "4.7.0" }),
|
|
1071
1076
|
),
|
|
1072
1077
|
);
|
|
1073
|
-
expect(item.action).toBe("
|
|
1078
|
+
expect(item.action).toBe("remove");
|
|
1074
1079
|
});
|
|
1075
1080
|
});
|
|
1076
1081
|
|
|
@@ -1359,6 +1364,7 @@ describe("countPluginStates", () => {
|
|
|
1359
1364
|
repair: 1,
|
|
1360
1365
|
current: 0,
|
|
1361
1366
|
"stale-load": 0,
|
|
1367
|
+
remove: 0,
|
|
1362
1368
|
unknown: 1,
|
|
1363
1369
|
});
|
|
1364
1370
|
});
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "bun:test";
|
|
2
|
-
import {
|
|
3
|
-
buildStaleLoadModal,
|
|
4
|
-
staleLoadRepairSummary,
|
|
5
|
-
} from "../ui/hooks/useStaleLoadModal";
|
|
2
|
+
import { buildStaleLoadModal } from "../ui/hooks/useStaleLoadModal";
|
|
6
3
|
|
|
7
4
|
/**
|
|
8
5
|
* The startup popup for a broken plugin.
|
|
@@ -137,41 +134,3 @@ describe("buildStaleLoadModal", () => {
|
|
|
137
134
|
});
|
|
138
135
|
});
|
|
139
136
|
});
|
|
140
|
-
|
|
141
|
-
describe("staleLoadRepairSummary", () => {
|
|
142
|
-
it("says what loads NOW for each repaired plugin", () => {
|
|
143
|
-
expect(
|
|
144
|
-
staleLoadRepairSummary([
|
|
145
|
-
{ pluginId: "madbench@magus", target: "0.5.0", ok: true },
|
|
146
|
-
{ pluginId: "dev@magus", target: "7.4.0", ok: true },
|
|
147
|
-
]),
|
|
148
|
-
).toBe("✓ madbench@magus now loads v0.5.0\n✓ dev@magus now loads v7.4.0");
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it("reports a failed repair with its own reason, never as a success", () => {
|
|
152
|
-
expect(
|
|
153
|
-
staleLoadRepairSummary([
|
|
154
|
-
{
|
|
155
|
-
pluginId: "madbench@magus",
|
|
156
|
-
target: "0.5.0",
|
|
157
|
-
ok: false,
|
|
158
|
-
error: "the loader still resolves v0.3.0 here, not v0.5.0",
|
|
159
|
-
},
|
|
160
|
-
]),
|
|
161
|
-
).toBe(
|
|
162
|
-
"✗ madbench@magus — the loader still resolves v0.3.0 here, not v0.5.0",
|
|
163
|
-
);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it("falls back to a plain reason rather than an empty line", () => {
|
|
167
|
-
expect(
|
|
168
|
-
staleLoadRepairSummary([
|
|
169
|
-
{ pluginId: "a@magus", target: "1.0.0", ok: false },
|
|
170
|
-
]),
|
|
171
|
-
).toBe("✗ a@magus — repair did not take effect");
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
it("says so when there was nothing to repair", () => {
|
|
175
|
-
expect(staleLoadRepairSummary([])).toBe("Nothing to repair.");
|
|
176
|
-
});
|
|
177
|
-
});
|
|
@@ -178,7 +178,7 @@ const magusCatalog = (...extra: PluginInfo[]) =>
|
|
|
178
178
|
);
|
|
179
179
|
|
|
180
180
|
describe("planPluginUpdates — a pin its own marketplace does not offer", () => {
|
|
181
|
-
test("declared, not installed, marketplace read, plugin absent →
|
|
181
|
+
test("declared, not installed, marketplace read, plugin absent → remove", () => {
|
|
182
182
|
// The bug: the !installed branch ran before any catalog guard, so
|
|
183
183
|
// `agentdev@magus` (retired) and `seo@magus` (moved to magus-marketing)
|
|
184
184
|
// planned as `install` with the literal pin as target on every run,
|
|
@@ -188,20 +188,20 @@ describe("planPluginUpdates — a pin its own marketplace does not offer", () =>
|
|
|
188
188
|
{ "agentdev@magus": "latest" },
|
|
189
189
|
magusCatalog(),
|
|
190
190
|
);
|
|
191
|
+
// Deprecated now, and the work is dropping the pin from the profile:
|
|
192
|
+
// still never an `install` of something nothing can fetch.
|
|
191
193
|
expect(items).toHaveLength(1);
|
|
192
|
-
expect(items[0]?.action).toBe("
|
|
194
|
+
expect(items[0]?.action).toBe("remove");
|
|
193
195
|
expect(items[0]?.target).toBeNull();
|
|
194
|
-
expect(items[0]?.note).
|
|
195
|
-
|
|
196
|
-
);
|
|
197
|
-
expect(items[0]?.reason).toBe("not-offered");
|
|
196
|
+
expect(items[0]?.note).toBe("no longer published");
|
|
197
|
+
expect(items[0]?.reason).toBe("deprecated");
|
|
198
198
|
});
|
|
199
199
|
|
|
200
|
-
test("an exact pin its marketplace does not offer is also
|
|
200
|
+
test("an exact pin its marketplace does not offer is also deprecated", () => {
|
|
201
201
|
const items = planPluginUpdates({ "gone@magus": "3.0.0" }, magusCatalog());
|
|
202
|
-
expect(items[0]?.action).toBe("
|
|
202
|
+
expect(items[0]?.action).toBe("remove");
|
|
203
203
|
expect(items[0]?.target).toBeNull();
|
|
204
|
-
expect(items[0]?.reason).toBe("
|
|
204
|
+
expect(items[0]?.reason).toBe("deprecated");
|
|
205
205
|
});
|
|
206
206
|
|
|
207
207
|
test("declared, not installed, and the catalog does offer it → install", () => {
|
|
@@ -345,14 +345,14 @@ describe("installed with no comparable catalog version — two different causes"
|
|
|
345
345
|
// The distinction this test exists for is between the two REASONS, and it
|
|
346
346
|
// still holds. What changed is the verdict: declaring no version is not a
|
|
347
347
|
// problem, so the row is `current` and says nothing. Being withdrawn IS a
|
|
348
|
-
// problem, so the next test's row is `
|
|
348
|
+
// problem, so the next test's row is `remove` and says so.
|
|
349
349
|
expect(items[0]?.reason).toBe("no-published-version");
|
|
350
|
-
expect(items[0]?.reason).not.toBe("
|
|
350
|
+
expect(items[0]?.reason).not.toBe("deprecated");
|
|
351
351
|
expect(items[0]?.action).toBe("current");
|
|
352
352
|
expect(items[0]?.note).toBeUndefined();
|
|
353
353
|
});
|
|
354
354
|
|
|
355
|
-
test("absent from the catalog entirely IS
|
|
355
|
+
test("absent from the catalog entirely IS deprecated", () => {
|
|
356
356
|
const items = planPluginUpdates(
|
|
357
357
|
{ "gone@magus": "latest" },
|
|
358
358
|
new Map([
|
|
@@ -366,11 +366,9 @@ describe("installed with no comparable catalog version — two different causes"
|
|
|
366
366
|
],
|
|
367
367
|
]),
|
|
368
368
|
);
|
|
369
|
-
expect(items[0]?.action).toBe("
|
|
370
|
-
expect(items[0]?.note).
|
|
371
|
-
|
|
372
|
-
);
|
|
373
|
-
expect(items[0]?.reason).toBe("not-offered");
|
|
369
|
+
expect(items[0]?.action).toBe("remove");
|
|
370
|
+
expect(items[0]?.note).toBe("no longer published");
|
|
371
|
+
expect(items[0]?.reason).toBe("deprecated");
|
|
374
372
|
});
|
|
375
373
|
});
|
|
376
374
|
|