magus-cli 7.10.5 → 7.11.1
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__/blind/plugin-deps/doctor.test.ts +2 -2
- package/src/__tests__/blind/plugin-deps/hook.test.ts +2 -2
- package/src/__tests__/blind/plugin-deps/read-only.test.ts +1 -1
- package/src/__tests__/blind/plugin-deps/schema.test.ts +6 -6
- package/src/__tests__/blind/requires-min-version/TEST-PLAN.md +54 -0
- package/src/__tests__/blind/requires-min-version/manifests.test.ts +69 -0
- package/src/__tests__/blind/requires-min-version/parse.test.ts +266 -0
- package/src/__tests__/blind/requires-min-version/probe.test.ts +359 -0
- package/src/__tests__/blind/requires-min-version/render.test.ts +252 -0
- package/src/__tests__/blind/requires-min-version/support.ts +83 -0
- package/src/__tests__/blind/requires-min-version/surfaces.test.ts +231 -0
- package/src/__tests__/blind/review-followups/bun-install-path.test.ts +1 -1
- package/src/__tests__/blind/review-followups/ledger-permissions.test.ts +1 -1
- package/src/__tests__/blind/review-followups/ledger-pruning.test.ts +1 -1
- package/src/__tests__/blind/review-followups/path-advice.test.ts +1 -1
- package/src/__tests__/deps-check-hook.test.ts +9 -9
- package/src/__tests__/deps-plan.test.ts +1 -1
- package/src/__tests__/deps-report.test.ts +5 -5
- package/src/__tests__/doctor-json.test.ts +2 -2
- package/src/__tests__/marketplace-refresh.test.ts +1 -1
- package/src/__tests__/plugin-requires.test.ts +2 -2
- package/src/__tests__/requires-probe.test.ts +2 -2
- package/src/__tests__/scope-action-deprecated.test.ts +105 -0
- package/src/cli/deps-view.ts +5 -3
- package/src/cli/router.ts +6 -1
- package/src/services/deps-plan.ts +38 -14
- package/src/services/deps-report.ts +24 -7
- package/src/services/doctor-bins.ts +40 -12
- package/src/services/plugin-manager.ts +5 -1
- package/src/services/resolver.ts +1 -0
- package/src/shared/plugin/hooks/deps-check.ts +7 -3
- package/src/shared/plugin/hooks/lib/requires-probe.ts +257 -33
- package/src/ui/adapters/cliToolsAdapter.ts +6 -1
- package/src/ui/renderers/cliToolRenderers.tsx +7 -1
- package/src/ui/screens/CliToolsScreen.tsx +8 -1
- package/src/ui/screens/PluginsScreen.tsx +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "magus-cli",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.11.1",
|
|
4
4
|
"description": "TUI tool for managing Claude Code plugins, MCPs, and configuration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/main.tsx",
|
|
@@ -65,8 +65,8 @@
|
|
|
65
65
|
"typescript": "^5.6.3"
|
|
66
66
|
},
|
|
67
67
|
"optionalDependencies": {
|
|
68
|
-
"magus-cli-darwin-arm64": "7.
|
|
69
|
-
"magus-cli-darwin-x64": "7.
|
|
70
|
-
"magus-cli-linux-x64": "7.
|
|
68
|
+
"magus-cli-darwin-arm64": "7.11.1",
|
|
69
|
+
"magus-cli-darwin-x64": "7.11.1",
|
|
70
|
+
"magus-cli-linux-x64": "7.11.1"
|
|
71
71
|
}
|
|
72
72
|
}
|
|
@@ -25,7 +25,7 @@ function requirement(
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
function requires(bin: Record<string, unknown>[]): Record<string, unknown> {
|
|
28
|
-
return { bin, schema:
|
|
28
|
+
return { bin, schema: 2 };
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
async function installPlugin(
|
|
@@ -157,7 +157,7 @@ describe("magus doctor black-box contract", () => {
|
|
|
157
157
|
magusOnPath: false,
|
|
158
158
|
pathAdvice: [],
|
|
159
159
|
plugins_needing_update: [],
|
|
160
|
-
schema:
|
|
160
|
+
schema: 2,
|
|
161
161
|
unfixable: [],
|
|
162
162
|
});
|
|
163
163
|
expect(report.platform).toEqual({
|
|
@@ -27,7 +27,7 @@ function requirement(
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
function requires(bin: Record<string, unknown>[]): Record<string, unknown> {
|
|
30
|
-
return { bin, schema:
|
|
30
|
+
return { bin, schema: 2 };
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
describe("registered SessionStart dependency hook", () => {
|
|
@@ -292,7 +292,7 @@ describe("registered SessionStart dependency hook", () => {
|
|
|
292
292
|
const plugin = await makeHookPlugin(world, "invalid", {
|
|
293
293
|
bin: [],
|
|
294
294
|
mystery: true,
|
|
295
|
-
schema:
|
|
295
|
+
schema: 2,
|
|
296
296
|
});
|
|
297
297
|
await linkExecutable(world.bin, "bun", process.execPath);
|
|
298
298
|
const input = sessionInput("invalid-session");
|
|
@@ -13,7 +13,7 @@ function requirement(
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
function manifest(bin: Record<string, unknown>[]): Record<string, unknown> {
|
|
16
|
-
return { requires: { bin, schema:
|
|
16
|
+
return { requires: { bin, schema: 2 } };
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
function invalidText(value: unknown): string {
|
|
@@ -26,7 +26,7 @@ function invalidText(value: unknown): string {
|
|
|
26
26
|
return result.errors.join("\n");
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
describe("requires schema
|
|
29
|
+
describe("requires schema 2", () => {
|
|
30
30
|
// Spec: AS-BUILT.md §1, “brew takes EXACTLY ONE ...” through the
|
|
31
31
|
// renderInstall table define the seven manifest installer kinds.
|
|
32
32
|
test("accepts a complete declaration of every manifest via", () => {
|
|
@@ -78,7 +78,7 @@ describe("requires schema 1", () => {
|
|
|
78
78
|
// level, a requirement, an option) ... error naming the key.”
|
|
79
79
|
test("rejects and locates unknown keys at all three schema levels", () => {
|
|
80
80
|
const top = invalidText({
|
|
81
|
-
requires: { bin: [], mysteryTop: true, schema:
|
|
81
|
+
requires: { bin: [], mysteryTop: true, schema: 2 },
|
|
82
82
|
});
|
|
83
83
|
const req = invalidText(
|
|
84
84
|
manifest([
|
|
@@ -102,7 +102,7 @@ describe("requires schema 1", () => {
|
|
|
102
102
|
expect(option).toContain("mysteryOption");
|
|
103
103
|
});
|
|
104
104
|
|
|
105
|
-
// Spec: AS-BUILT.md §1, “schema greater than
|
|
105
|
+
// Spec: AS-BUILT.md §1, “schema greater than 2 ... smaller ...;
|
|
106
106
|
// missing/non-number → invalid” and “no requires key ... empty.”
|
|
107
107
|
test("classifies schema versions and treats no requires as empty", () => {
|
|
108
108
|
expect(parseRequires({})).toEqual({ ok: true, requirements: [] });
|
|
@@ -112,11 +112,11 @@ describe("requires schema 1", () => {
|
|
|
112
112
|
ok: false,
|
|
113
113
|
});
|
|
114
114
|
}
|
|
115
|
-
expect(parseRequires({ requires: { bin: [], schema:
|
|
115
|
+
expect(parseRequires({ requires: { bin: [], schema: 3 } })).toMatchObject({
|
|
116
116
|
kind: "newer-schema",
|
|
117
117
|
ok: false,
|
|
118
118
|
});
|
|
119
|
-
expect(parseRequires({ requires: { bin: [], schema:
|
|
119
|
+
expect(parseRequires({ requires: { bin: [], schema: 1 } })).toMatchObject({
|
|
120
120
|
kind: "older-schema",
|
|
121
121
|
ok: false,
|
|
122
122
|
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Test Plan: requires schema 2 — minVersion per dependency
|
|
2
|
+
|
|
3
|
+
- **Spec**: docs/plans/2026-10-20-requires-min-version.md
|
|
4
|
+
- **Contract**: ai-docs/plugin-deps-contract/contract/requires-probe.d.ts, contract/doctor-json.d.ts (schema 1; schema-2 additions taken from the spec)
|
|
5
|
+
- **Stack**: bun **Framework**: bun:test **Scenarios**: 109 tests (parameterised)
|
|
6
|
+
|
|
7
|
+
Fake executables are `#!/bin/sh` scripts in a temp dir that print a chosen version and exit
|
|
8
|
+
with a chosen code. The hook tests link the test runner's own bun only to execute the hook's
|
|
9
|
+
TypeScript, as the schema-1 blind suite does; no test reads the real claudish or bun version.
|
|
10
|
+
|
|
11
|
+
## Scenarios (grouped)
|
|
12
|
+
|
|
13
|
+
| Group | Type | Priority | Given / When / Then | REQ |
|
|
14
|
+
|---|---|---|---|---|
|
|
15
|
+
| parse.test.ts schema | unit | critical | REQUIRES_SCHEMA is 2; schema 1 → older-schema; 3 → newer-schema; no requires → empty | 1, 2 |
|
|
16
|
+
| parse.test.ts field | unit | critical | minVersion X.Y.Z ok and kept on the requirement; v10.7.0, 10.7, 10.7.0-beta, 10.7.0.1, 10, "", " 10.7.0", 10.x.0, number → invalid; unknown key invalid and named | 3, 4, 5 |
|
|
17
|
+
| parse.test.ts pins | unit | high | release tag (with/without v) below floor invalid, at/above valid; numeric; non-X.Y.Z tag invalid; vv invalid; no floor → not compared; exact bun version below floor invalid, at/above valid; ranges/tags/no version not compared | 6, 7 |
|
|
18
|
+
| probe.test.ts × {presence, full} | integration | critical | >= floor present; < floor outdated {found,minVersion}; no version → unknown `no version in "tool --version" output`; stderr read; stdout wins; first match; suffix ignored; numeric; non-zero exit/absent → missing; slow → unknown; dependent of outdated is probed; order kept | 8–18 |
|
|
19
|
+
| probe.test.ts no floor | integration | high | presence depth: unfloored self-named check is PATH-only (exit-1 fake present, not run); same fake with a floor runs → missing; full depth unchanged | 12 |
|
|
20
|
+
| render.test.ts renderInstall | unit | critical | bun missing/outdated (@latest, @version), brew formula upgrade, brew cask upgrade --cask; other vias identical across reasons; describeStep of outdated bun | 19–22 |
|
|
21
|
+
| render.test.ts renderFix | unit | high | outdated → banner with `outdated <name> <found> (needs <min>)` and the upgrade line; healthy → null | 23, 24 |
|
|
22
|
+
| manifests.test.ts | unit | critical | every shipped plugin.json with requires is schema 2 and parses; claudish's claudish has minVersion 10.7.0; no other floors | 25, 26 |
|
|
23
|
+
| surfaces.test.ts doctor | e2e (CLI) | high | --json schema 2, outdated dep with found/minVersion, fix text `bun add -g widget@latest`; text doctor exit non-zero; --fix leaving old binary → failed, remedy names found, floor, onPath | 27, 28, 29 |
|
|
24
|
+
| surfaces.test.ts hook | e2e (hook) | high | outdated dep reported once per session with the head text; satisfied floor silent | 30 |
|
|
25
|
+
|
|
26
|
+
## Coverage map
|
|
27
|
+
|
|
28
|
+
| Requirement | Tests | Covered |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| REQ-1 REQUIRES_SCHEMA = 2 | parse | yes |
|
|
31
|
+
| REQ-2 one schema only (1 → older, 3 → newer) | parse | yes |
|
|
32
|
+
| REQ-3 minVersion shape | parse | yes |
|
|
33
|
+
| REQ-4 optional field | parse, probe | yes |
|
|
34
|
+
| REQ-5 allowed keys | parse | yes |
|
|
35
|
+
| REQ-6 release tag vs floor | parse | yes |
|
|
36
|
+
| REQ-7 exact bun version vs floor | parse | yes |
|
|
37
|
+
| REQ-8..11, 13, 14 version reading and comparison | probe (both depths) | yes |
|
|
38
|
+
| REQ-12 floored requirement always runs its check | probe | yes |
|
|
39
|
+
| REQ-15, 16 non-zero/timeout as schema 1 | probe | yes |
|
|
40
|
+
| REQ-17 blockedBy unchanged for outdated | probe | yes |
|
|
41
|
+
| REQ-19..22 renderInstall reason | render | yes |
|
|
42
|
+
| REQ-23, 24 renderFix | render | yes |
|
|
43
|
+
| REQ-25, 26 shipped declarations | manifests | yes |
|
|
44
|
+
| REQ-27 doctor --json schema 2 | surfaces | yes |
|
|
45
|
+
| REQ-28 doctor text exit code | surfaces | yes |
|
|
46
|
+
| REQ-29 executor: still outdated → failed + remedy | surfaces | yes |
|
|
47
|
+
| REQ-30 hook ledger | surfaces | yes |
|
|
48
|
+
|
|
49
|
+
## Known gaps
|
|
50
|
+
|
|
51
|
+
- TUI (`cliToolsAdapter`, `depsAdapter`): no public contract was supplied for these surfaces.
|
|
52
|
+
- Repo gate `scripts/check-plugin-requires.ts --self-test`: outside the contract given.
|
|
53
|
+
- Hook-copy byte identity (`check:hook-copies`): a repo gate, not behaviour of the contract.
|
|
54
|
+
- Rollout messages ("update magus-cli" / "update the plugin"): only the parse kinds are asserted, not doctor's wording.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
import { parseRequires } from "../../../shared/plugin/hooks/lib/requires-probe.js";
|
|
6
|
+
import { REPO_ROOT } from "./support";
|
|
7
|
+
|
|
8
|
+
const PLUGINS = join(REPO_ROOT, "plugins");
|
|
9
|
+
|
|
10
|
+
function manifestPath(plugin: string): string {
|
|
11
|
+
return join(PLUGINS, plugin, ".claude-plugin", "plugin.json");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const declaring = readdirSync(PLUGINS)
|
|
15
|
+
.filter((name) => existsSync(manifestPath(name)))
|
|
16
|
+
.map((name) => ({
|
|
17
|
+
json: JSON.parse(readFileSync(manifestPath(name), "utf8")) as Record<
|
|
18
|
+
string,
|
|
19
|
+
unknown
|
|
20
|
+
>,
|
|
21
|
+
name,
|
|
22
|
+
}))
|
|
23
|
+
.filter(({ json }) => json.requires !== undefined);
|
|
24
|
+
|
|
25
|
+
describe("shipped plugin manifests under schema 2", () => {
|
|
26
|
+
test("REQ-25 at least one shipped manifest declares requires (the scan is not empty)", () => {
|
|
27
|
+
expect(declaring.map(({ name }) => name)).toContain("claudish");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// REQ-25: "Every plugin.json with requires moves to schema: 2."
|
|
31
|
+
for (const { name, json } of declaring) {
|
|
32
|
+
test(`REQ-25 ${name}: requires declares schema 2 and parses ok`, () => {
|
|
33
|
+
expect((json.requires as Record<string, unknown>).schema).toBe(2);
|
|
34
|
+
const result = parseRequires(json);
|
|
35
|
+
if (!result.ok) {
|
|
36
|
+
throw new Error(`${name}: ${result.kind}: ${result.errors.join("; ")}`);
|
|
37
|
+
}
|
|
38
|
+
expect(result.ok).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// REQ-26: claudish gets a minVersion floor; no other requirement gets one. The floor
|
|
43
|
+
// moved from 10.7.0 to 11.1.0 with claudish plugin 2.5.0, which needs SlotRow.recent.
|
|
44
|
+
test("REQ-26 claudish's claudish requirement has minVersion 11.1.0", () => {
|
|
45
|
+
const claudish = declaring.find(({ name }) => name === "claudish");
|
|
46
|
+
if (!claudish) throw new Error("plugins/claudish declares no requires");
|
|
47
|
+
const result = parseRequires(claudish.json);
|
|
48
|
+
if (!result.ok)
|
|
49
|
+
throw new Error(`${result.kind}: ${result.errors.join("; ")}`);
|
|
50
|
+
const req = result.requirements.find((r) => r.name === "claudish") as
|
|
51
|
+
| (Record<string, unknown> & { name: string })
|
|
52
|
+
| undefined;
|
|
53
|
+
expect(req).toBeDefined();
|
|
54
|
+
expect(req?.minVersion).toBe("11.1.0");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("REQ-26 no other shipped requirement declares a floor", () => {
|
|
58
|
+
const floored: string[] = [];
|
|
59
|
+
for (const { name, json } of declaring) {
|
|
60
|
+
const bin = ((json.requires as Record<string, unknown>).bin ??
|
|
61
|
+
[]) as Array<Record<string, unknown>>;
|
|
62
|
+
for (const r of bin) {
|
|
63
|
+
if (r.minVersion !== undefined)
|
|
64
|
+
floored.push(`${name}:${String(r.name)}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
expect(floored).toEqual(["claudish:claudish"]);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
REQUIRES_SCHEMA,
|
|
5
|
+
parseRequires,
|
|
6
|
+
} from "../../../shared/plugin/hooks/lib/requires-probe.js";
|
|
7
|
+
import { manifest, releaseOption, requirement } from "./support";
|
|
8
|
+
|
|
9
|
+
const brew = [{ formula: "tool", via: "brew" }];
|
|
10
|
+
// A via:bun option needs "bun" declared in the same plugin (schema-1 rule),
|
|
11
|
+
// so every bun-option testdata carries this, and an invalid result can only
|
|
12
|
+
// come from the floor comparison.
|
|
13
|
+
const bunDecl = requirement("bun", [{ tool: "bun", via: "bootstrap" }]);
|
|
14
|
+
|
|
15
|
+
function okRequirements(value: unknown) {
|
|
16
|
+
const result = parseRequires(value);
|
|
17
|
+
if (!result.ok) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
`expected ok, got ${result.kind}: ${result.errors.join("; ")}`,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
return result.requirements;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function expectInvalid(value: unknown): string {
|
|
26
|
+
const result = parseRequires(value);
|
|
27
|
+
expect(result.ok).toBe(false);
|
|
28
|
+
if (result.ok) throw new Error("testdata unexpectedly parsed");
|
|
29
|
+
expect(result.kind).toBe("invalid");
|
|
30
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
31
|
+
return result.errors.join("\n");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe("requires schema 2: version and schema number", () => {
|
|
35
|
+
// REQ-1: "any shape change bumps requires.schema, so this is schema 2"
|
|
36
|
+
test("REQ-1 REQUIRES_SCHEMA is 2", () => {
|
|
37
|
+
expect(REQUIRES_SCHEMA).toBe(2);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// REQ-2: "A reader reads exactly one schema: there is no schema-1 reading path"
|
|
41
|
+
test("REQ-2 a schema-1 requires block is older-schema, not parsed", () => {
|
|
42
|
+
expect(
|
|
43
|
+
parseRequires(manifest([requirement("tool", brew)], 1)),
|
|
44
|
+
).toMatchObject({ kind: "older-schema", ok: false });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("REQ-2 a schema-3 requires block is newer-schema", () => {
|
|
48
|
+
expect(
|
|
49
|
+
parseRequires(manifest([requirement("tool", brew)], 3)),
|
|
50
|
+
).toMatchObject({ kind: "newer-schema", ok: false });
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("REQ-2 a manifest without requires still parses as empty", () => {
|
|
54
|
+
expect(parseRequires({ name: "p" })).toEqual({
|
|
55
|
+
ok: true,
|
|
56
|
+
requirements: [],
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe("requires schema 2: the minVersion field", () => {
|
|
62
|
+
// REQ-3: Shape ^\d+\.\d+\.\d+$
|
|
63
|
+
test("REQ-3 accepts minVersion X.Y.Z and keeps it on the requirement", () => {
|
|
64
|
+
const reqs = okRequirements(
|
|
65
|
+
manifest([requirement("tool", brew, { minVersion: "10.7.0" })]),
|
|
66
|
+
);
|
|
67
|
+
expect(reqs).toHaveLength(1);
|
|
68
|
+
expect(reqs[0]).toMatchObject({ minVersion: "10.7.0", name: "tool" });
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// REQ-4: Optional — a requirement without it behaves as in schema 1.
|
|
72
|
+
test("REQ-4 a requirement without minVersion parses ok", () => {
|
|
73
|
+
const reqs = okRequirements(manifest([requirement("tool", brew)]));
|
|
74
|
+
expect(reqs).toHaveLength(1);
|
|
75
|
+
expect(reqs[0].name).toBe("tool");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// REQ-3: "three numeric parts, no v, no prerelease. Anything else is invalid."
|
|
79
|
+
for (const bad of [
|
|
80
|
+
"v10.7.0",
|
|
81
|
+
"10.7",
|
|
82
|
+
"10.7.0-beta",
|
|
83
|
+
"10.7.0.1",
|
|
84
|
+
"10",
|
|
85
|
+
"",
|
|
86
|
+
" 10.7.0",
|
|
87
|
+
"10.x.0",
|
|
88
|
+
]) {
|
|
89
|
+
test(`REQ-3 rejects minVersion ${JSON.stringify(bad)} as invalid`, () => {
|
|
90
|
+
expectInvalid(manifest([requirement("tool", brew, { minVersion: bad })]));
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
test("REQ-3 rejects a numeric (non-string) minVersion as invalid", () => {
|
|
95
|
+
expectInvalid(manifest([requirement("tool", brew, { minVersion: 10.7 })]));
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// REQ-5: allowed keys are name, check, os, minVersion, install.
|
|
99
|
+
test("REQ-5 an unknown requirement key is still invalid and named", () => {
|
|
100
|
+
const errors = expectInvalid(
|
|
101
|
+
manifest([
|
|
102
|
+
requirement("tool", brew, {
|
|
103
|
+
minVersion: "1.0.0",
|
|
104
|
+
minimumVersion: "1.0.0",
|
|
105
|
+
}),
|
|
106
|
+
]),
|
|
107
|
+
);
|
|
108
|
+
expect(errors).toContain("minimumVersion");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("REQ-5 os and minVersion together are allowed", () => {
|
|
112
|
+
const reqs = okRequirements(
|
|
113
|
+
manifest([
|
|
114
|
+
requirement(
|
|
115
|
+
"tool",
|
|
116
|
+
[{ formula: "tool", os: ["darwin"], via: "brew" }],
|
|
117
|
+
{
|
|
118
|
+
minVersion: "1.0.0",
|
|
119
|
+
os: ["darwin"],
|
|
120
|
+
},
|
|
121
|
+
),
|
|
122
|
+
]),
|
|
123
|
+
);
|
|
124
|
+
expect(reqs).toHaveLength(1);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe("requires schema 2: pinned installs against the floor", () => {
|
|
129
|
+
// REQ-6: release tag, one leading v removed, must parse X.Y.Z and be >= minVersion.
|
|
130
|
+
test("REQ-6 a release tag below the floor is invalid", () => {
|
|
131
|
+
expectInvalid(
|
|
132
|
+
manifest([
|
|
133
|
+
requirement("tool", [releaseOption("v10.6.9")], {
|
|
134
|
+
minVersion: "10.7.0",
|
|
135
|
+
}),
|
|
136
|
+
]),
|
|
137
|
+
);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("REQ-6 a release tag below the floor without a v is invalid", () => {
|
|
141
|
+
expectInvalid(
|
|
142
|
+
manifest([
|
|
143
|
+
requirement("tool", [releaseOption("10.6.9")], {
|
|
144
|
+
minVersion: "10.7.0",
|
|
145
|
+
}),
|
|
146
|
+
]),
|
|
147
|
+
);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
for (const tag of ["v10.7.0", "10.7.0", "v10.7.1", "10.8.0", "v11.0.0"]) {
|
|
151
|
+
test(`REQ-6 a release tag ${tag} at or above floor 10.7.0 is valid`, () => {
|
|
152
|
+
expect(
|
|
153
|
+
okRequirements(
|
|
154
|
+
manifest([
|
|
155
|
+
requirement("tool", [releaseOption(tag)], { minVersion: "10.7.0" }),
|
|
156
|
+
]),
|
|
157
|
+
),
|
|
158
|
+
).toHaveLength(1);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
test("REQ-6 tag comparison is numeric: v10.10.0 satisfies 10.9.0", () => {
|
|
163
|
+
expect(
|
|
164
|
+
okRequirements(
|
|
165
|
+
manifest([
|
|
166
|
+
requirement("tool", [releaseOption("v10.10.0")], {
|
|
167
|
+
minVersion: "10.9.0",
|
|
168
|
+
}),
|
|
169
|
+
]),
|
|
170
|
+
),
|
|
171
|
+
).toHaveLength(1);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("REQ-6 tag comparison is numeric: v10.9.0 is below 10.10.0", () => {
|
|
175
|
+
expectInvalid(
|
|
176
|
+
manifest([
|
|
177
|
+
requirement("tool", [releaseOption("v10.9.0")], {
|
|
178
|
+
minVersion: "10.10.0",
|
|
179
|
+
}),
|
|
180
|
+
]),
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("REQ-6 a release tag that is not X.Y.Z is invalid when a floor is set", () => {
|
|
185
|
+
expectInvalid(
|
|
186
|
+
manifest([
|
|
187
|
+
requirement("tool", [releaseOption("v10")], { minVersion: "1.0.0" }),
|
|
188
|
+
]),
|
|
189
|
+
);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("REQ-6 only one leading v is removed: vv10.7.0 is invalid", () => {
|
|
193
|
+
expectInvalid(
|
|
194
|
+
manifest([
|
|
195
|
+
requirement("tool", [releaseOption("vv10.7.0")], {
|
|
196
|
+
minVersion: "10.7.0",
|
|
197
|
+
}),
|
|
198
|
+
]),
|
|
199
|
+
);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("REQ-4 without a floor a release tag is not compared (v1 stays valid)", () => {
|
|
203
|
+
expect(
|
|
204
|
+
okRequirements(manifest([requirement("tool", [releaseOption("v1")])])),
|
|
205
|
+
).toHaveLength(1);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// REQ-7: exact bun version must be >= minVersion; ranges and tags not compared.
|
|
209
|
+
test("REQ-7 an exact bun version below the floor is invalid", () => {
|
|
210
|
+
expectInvalid(
|
|
211
|
+
manifest([
|
|
212
|
+
bunDecl,
|
|
213
|
+
requirement(
|
|
214
|
+
"tool",
|
|
215
|
+
[{ package: "tool", version: "10.6.0", via: "bun" }],
|
|
216
|
+
{
|
|
217
|
+
minVersion: "10.7.0",
|
|
218
|
+
},
|
|
219
|
+
),
|
|
220
|
+
]),
|
|
221
|
+
);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
for (const version of ["10.7.0", "10.12.3"]) {
|
|
225
|
+
test(`REQ-7 an exact bun version ${version} at or above the floor is valid`, () => {
|
|
226
|
+
expect(
|
|
227
|
+
okRequirements(
|
|
228
|
+
manifest([
|
|
229
|
+
bunDecl,
|
|
230
|
+
requirement("tool", [{ package: "tool", version, via: "bun" }], {
|
|
231
|
+
minVersion: "10.7.0",
|
|
232
|
+
}),
|
|
233
|
+
]),
|
|
234
|
+
),
|
|
235
|
+
).toHaveLength(2);
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
for (const version of ["^1.2.0", "latest", "~9.0.0"]) {
|
|
240
|
+
test(`REQ-7 a bun range or tag ${version} is not compared to the floor`, () => {
|
|
241
|
+
expect(
|
|
242
|
+
okRequirements(
|
|
243
|
+
manifest([
|
|
244
|
+
bunDecl,
|
|
245
|
+
requirement("tool", [{ package: "tool", version, via: "bun" }], {
|
|
246
|
+
minVersion: "10.7.0",
|
|
247
|
+
}),
|
|
248
|
+
]),
|
|
249
|
+
),
|
|
250
|
+
).toHaveLength(2);
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
test("REQ-7 a bun option with no version is valid under a floor", () => {
|
|
255
|
+
expect(
|
|
256
|
+
okRequirements(
|
|
257
|
+
manifest([
|
|
258
|
+
bunDecl,
|
|
259
|
+
requirement("tool", [{ package: "tool", via: "bun" }], {
|
|
260
|
+
minVersion: "10.7.0",
|
|
261
|
+
}),
|
|
262
|
+
]),
|
|
263
|
+
),
|
|
264
|
+
).toHaveLength(2);
|
|
265
|
+
});
|
|
266
|
+
});
|