blokctl 0.4.0 → 0.6.2
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/dist/commands/check/index.d.ts +2 -0
- package/dist/commands/check/index.js +39 -0
- package/dist/commands/create/project.js +32 -27
- package/dist/commands/create/utils/Examples.d.ts +2 -2
- package/dist/commands/create/utils/Examples.js +28 -31
- package/dist/commands/dev/index.js +40 -57
- package/dist/commands/generate/GenerationAnalytics.js +2 -1
- package/dist/commands/generate/e2e/NodeGenerator.e2e.test.js +1 -1
- package/dist/commands/generate/e2e/TriggerGenerator.e2e.test.js +1 -1
- package/dist/commands/generate/e2e/WorkflowGenerator.e2e.test.js +1 -1
- package/dist/index.js +14 -1
- package/dist/services/local-token-manager.js +1 -1
- package/dist/services/runtime-detector.d.ts +1 -0
- package/dist/services/runtime-detector.js +12 -0
- package/dist/services/runtime-setup.d.ts +12 -1
- package/dist/services/runtime-setup.js +28 -0
- package/dist/services/semver-utils.d.ts +18 -0
- package/dist/services/semver-utils.js +82 -0
- package/dist/services/semver-utils.test.d.ts +1 -0
- package/dist/services/semver-utils.test.js +233 -0
- package/dist/studio-dist/assets/{icons-N5J4OhGx.js → icons-D-BBts99.js} +110 -65
- package/dist/studio-dist/assets/index-BD8_9YPN.js +42 -0
- package/dist/studio-dist/assets/index-D4Bc9-mb.css +1 -0
- package/dist/studio-dist/index.html +3 -3
- package/package.json +2 -2
- package/dist/studio-dist/assets/index-D6hOoOID.css +0 -1
- package/dist/studio-dist/assets/index-D_CdNmTc.js +0 -42
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import util from "node:util";
|
|
|
5
5
|
import * as p from "@clack/prompts";
|
|
6
6
|
import fsExtra from "fs-extra";
|
|
7
7
|
import color from "picocolors";
|
|
8
|
+
import { checkProject } from "./commands/check/index.js";
|
|
8
9
|
import { createNode } from "./commands/create/node.js";
|
|
9
10
|
import { createProject } from "./commands/create/project.js";
|
|
10
11
|
import { createWorkflow } from "./commands/create/workflow.js";
|
|
@@ -165,10 +166,22 @@ async function main() {
|
|
|
165
166
|
create.addCommand(node);
|
|
166
167
|
create.addCommand(workflow);
|
|
167
168
|
program.addCommand(create);
|
|
169
|
+
program
|
|
170
|
+
.command("check")
|
|
171
|
+
.description("Validate runtime version requirements")
|
|
172
|
+
.action(async (options) => {
|
|
173
|
+
await analytics.trackCommandExecution({
|
|
174
|
+
command: "check",
|
|
175
|
+
args: options,
|
|
176
|
+
execution: async () => {
|
|
177
|
+
await checkProject(options);
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
});
|
|
168
181
|
program
|
|
169
182
|
.command("dev")
|
|
170
183
|
.description("Start the development server")
|
|
171
|
-
.option("--
|
|
184
|
+
.option("--skip-version-check", "Skip runtime version validation")
|
|
172
185
|
.action(async (options) => {
|
|
173
186
|
await analytics.trackCommandExecution({
|
|
174
187
|
command: "dev",
|
|
@@ -13,7 +13,7 @@ class LocalTokenManager {
|
|
|
13
13
|
ensureStorage() {
|
|
14
14
|
try {
|
|
15
15
|
if (!fs.existsSync(this.storageDir)) {
|
|
16
|
-
fs.mkdirSync(this.storageDir, { mode: 0o700 });
|
|
16
|
+
fs.mkdirSync(this.storageDir, { mode: 0o700, recursive: true });
|
|
17
17
|
}
|
|
18
18
|
else {
|
|
19
19
|
fs.chmodSync(this.storageDir, 0o700);
|
|
@@ -22,5 +22,6 @@ export interface RuntimeInfo {
|
|
|
22
22
|
}
|
|
23
23
|
export declare function detectRr(): string | null;
|
|
24
24
|
export declare function detectRuntimes(): Promise<RuntimeInfo[]>;
|
|
25
|
+
export declare function detectRuntimeVersion(kind: string): Promise<string | undefined>;
|
|
25
26
|
export declare function getRuntimeDefinition(kind: string): Omit<RuntimeInfo, "available" | "version"> | undefined;
|
|
26
27
|
export declare function getAllRuntimeDefinitions(): Omit<RuntimeInfo, "available" | "version">[];
|
|
@@ -192,6 +192,18 @@ export async function detectRuntimes() {
|
|
|
192
192
|
}
|
|
193
193
|
return results;
|
|
194
194
|
}
|
|
195
|
+
export async function detectRuntimeVersion(kind) {
|
|
196
|
+
const def = RUNTIME_DEFINITIONS.find((r) => r.kind === kind);
|
|
197
|
+
if (!def)
|
|
198
|
+
return undefined;
|
|
199
|
+
for (const cmd of def.commands) {
|
|
200
|
+
const output = await tryExec(cmd);
|
|
201
|
+
if (output) {
|
|
202
|
+
return parseVersion(output, def.kind);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
195
207
|
export function getRuntimeDefinition(kind) {
|
|
196
208
|
return RUNTIME_DEFINITIONS.find((r) => r.kind === kind);
|
|
197
209
|
}
|
|
@@ -12,7 +12,9 @@ export interface RuntimeConfig {
|
|
|
12
12
|
cwd: string;
|
|
13
13
|
kind: string;
|
|
14
14
|
label: string;
|
|
15
|
-
|
|
15
|
+
version?: string;
|
|
16
|
+
requiredVersion?: string;
|
|
17
|
+
transport?: "grpc";
|
|
16
18
|
}
|
|
17
19
|
export interface TriggerConfig {
|
|
18
20
|
kind: string;
|
|
@@ -31,6 +33,15 @@ export declare function writeProjectConfig(projectDir: string, runtimeConfigs: R
|
|
|
31
33
|
export declare function readProjectConfig(projectDir: string): ProjectConfig | null;
|
|
32
34
|
export declare function generateRuntimeEnvVars(runtimeConfigs: RuntimeConfig[]): string;
|
|
33
35
|
export declare function generateSupervisordConfig(runtimeConfigs: RuntimeConfig[]): string;
|
|
36
|
+
export interface RuntimeValidationResult {
|
|
37
|
+
kind: string;
|
|
38
|
+
label: string;
|
|
39
|
+
required: string;
|
|
40
|
+
found: string | undefined;
|
|
41
|
+
satisfied: boolean;
|
|
42
|
+
message: string;
|
|
43
|
+
}
|
|
44
|
+
export declare function validateProjectRuntimes(projectDir: string): Promise<RuntimeValidationResult[]>;
|
|
34
45
|
export declare function getTriggerPort(triggerKind: string): number;
|
|
35
46
|
export declare function getTriggerLabel(triggerKind: string): string;
|
|
36
47
|
export declare function createTriggerConfig(triggerKind: string): TriggerConfig;
|
|
@@ -2,6 +2,8 @@ import child_process from "node:child_process";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import util from "node:util";
|
|
4
4
|
import fsExtra from "fs-extra";
|
|
5
|
+
import { detectRuntimeVersion } from "./runtime-detector.js";
|
|
6
|
+
import { computeDefaultConstraint, formatVersionMismatch, formatVersionSuccess, satisfiesConstraint, } from "./semver-utils.js";
|
|
5
7
|
const exec = util.promisify(child_process.exec);
|
|
6
8
|
export async function setupRuntime(runtime, githubRepoLocal, projectDir, spinner) {
|
|
7
9
|
const sdkSourcePath = path.join(githubRepoLocal, "sdks", runtime.sdkDir);
|
|
@@ -49,6 +51,8 @@ export async function setupRuntime(runtime, githubRepoLocal, projectDir, spinner
|
|
|
49
51
|
cwd: path.relative(projectDir, blokctlRuntimeDir),
|
|
50
52
|
kind: runtime.kind,
|
|
51
53
|
label: runtime.label,
|
|
54
|
+
version: runtime.version,
|
|
55
|
+
requiredVersion: runtime.version ? computeDefaultConstraint(runtime.version) : undefined,
|
|
52
56
|
transport: "grpc",
|
|
53
57
|
};
|
|
54
58
|
}
|
|
@@ -194,6 +198,30 @@ stdout_logfile=/var/log/${rc.kind}.out.log
|
|
|
194
198
|
}
|
|
195
199
|
return config;
|
|
196
200
|
}
|
|
201
|
+
export async function validateProjectRuntimes(projectDir) {
|
|
202
|
+
const config = readProjectConfig(projectDir);
|
|
203
|
+
if (!config?.runtimes)
|
|
204
|
+
return [];
|
|
205
|
+
const results = [];
|
|
206
|
+
for (const [kind, rc] of Object.entries(config.runtimes)) {
|
|
207
|
+
if (!rc.requiredVersion)
|
|
208
|
+
continue;
|
|
209
|
+
const currentVersion = await detectRuntimeVersion(kind);
|
|
210
|
+
const satisfied = currentVersion ? satisfiesConstraint(currentVersion, rc.requiredVersion) : false;
|
|
211
|
+
const message = satisfied
|
|
212
|
+
? formatVersionSuccess(rc.label, currentVersion, rc.requiredVersion)
|
|
213
|
+
: formatVersionMismatch(rc.label, currentVersion, rc.requiredVersion);
|
|
214
|
+
results.push({
|
|
215
|
+
kind,
|
|
216
|
+
label: rc.label,
|
|
217
|
+
required: rc.requiredVersion,
|
|
218
|
+
found: currentVersion,
|
|
219
|
+
satisfied,
|
|
220
|
+
message,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
return results;
|
|
224
|
+
}
|
|
197
225
|
const TRIGGER_PORTS = {
|
|
198
226
|
http: 4000,
|
|
199
227
|
sse: 4001,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface SemverParts {
|
|
2
|
+
major: number;
|
|
3
|
+
minor: number;
|
|
4
|
+
patch: number;
|
|
5
|
+
}
|
|
6
|
+
export interface ParsedConstraint {
|
|
7
|
+
operator: ">=" | "^" | "~" | "=" | "";
|
|
8
|
+
version: string;
|
|
9
|
+
parts: SemverParts;
|
|
10
|
+
}
|
|
11
|
+
export declare function parseSemver(version: string): SemverParts;
|
|
12
|
+
export declare function compareSemver(a: string, b: string): number;
|
|
13
|
+
export declare function semverGte(a: SemverParts, b: SemverParts): boolean;
|
|
14
|
+
export declare function parseConstraint(constraint: string): ParsedConstraint;
|
|
15
|
+
export declare function satisfiesConstraint(version: string, constraint: string): boolean;
|
|
16
|
+
export declare function computeDefaultConstraint(version: string): string;
|
|
17
|
+
export declare function formatVersionMismatch(runtime: string, found: string | undefined, required: string, installHint?: string): string;
|
|
18
|
+
export declare function formatVersionSuccess(runtime: string, found: string, required: string): string;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export function parseSemver(version) {
|
|
2
|
+
const cleaned = version.trim();
|
|
3
|
+
const parts = cleaned.split(".").map(Number);
|
|
4
|
+
return {
|
|
5
|
+
major: parts[0] || 0,
|
|
6
|
+
minor: parts[1] || 0,
|
|
7
|
+
patch: parts[2] || 0,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export function compareSemver(a, b) {
|
|
11
|
+
const pa = parseSemver(a);
|
|
12
|
+
const pb = parseSemver(b);
|
|
13
|
+
if (pa.major !== pb.major)
|
|
14
|
+
return pa.major - pb.major;
|
|
15
|
+
if (pa.minor !== pb.minor)
|
|
16
|
+
return pa.minor - pb.minor;
|
|
17
|
+
return pa.patch - pb.patch;
|
|
18
|
+
}
|
|
19
|
+
export function semverGte(a, b) {
|
|
20
|
+
if (a.major !== b.major)
|
|
21
|
+
return a.major > b.major;
|
|
22
|
+
if (a.minor !== b.minor)
|
|
23
|
+
return a.minor > b.minor;
|
|
24
|
+
return a.patch >= b.patch;
|
|
25
|
+
}
|
|
26
|
+
export function parseConstraint(constraint) {
|
|
27
|
+
const trimmed = constraint.trim();
|
|
28
|
+
if (trimmed.startsWith(">=")) {
|
|
29
|
+
const ver = trimmed.slice(2);
|
|
30
|
+
return { operator: ">=", version: ver, parts: parseSemver(ver) };
|
|
31
|
+
}
|
|
32
|
+
if (trimmed.startsWith("^")) {
|
|
33
|
+
const ver = trimmed.slice(1);
|
|
34
|
+
return { operator: "^", version: ver, parts: parseSemver(ver) };
|
|
35
|
+
}
|
|
36
|
+
if (trimmed.startsWith("~")) {
|
|
37
|
+
const ver = trimmed.slice(1);
|
|
38
|
+
return { operator: "~", version: ver, parts: parseSemver(ver) };
|
|
39
|
+
}
|
|
40
|
+
const ver = trimmed.startsWith("=") ? trimmed.slice(1) : trimmed;
|
|
41
|
+
return { operator: trimmed.startsWith("=") ? "=" : "", version: ver, parts: parseSemver(ver) };
|
|
42
|
+
}
|
|
43
|
+
export function satisfiesConstraint(version, constraint) {
|
|
44
|
+
const actual = parseSemver(version);
|
|
45
|
+
const parsed = parseConstraint(constraint);
|
|
46
|
+
const base = parsed.parts;
|
|
47
|
+
switch (parsed.operator) {
|
|
48
|
+
case ">=":
|
|
49
|
+
return semverGte(actual, base);
|
|
50
|
+
case "^": {
|
|
51
|
+
if (base.major !== 0) {
|
|
52
|
+
return actual.major === base.major && semverGte(actual, base);
|
|
53
|
+
}
|
|
54
|
+
if (base.minor !== 0) {
|
|
55
|
+
return actual.major === 0 && actual.minor === base.minor && actual.patch >= base.patch;
|
|
56
|
+
}
|
|
57
|
+
return actual.major === 0 && actual.minor === 0 && actual.patch === base.patch;
|
|
58
|
+
}
|
|
59
|
+
case "~":
|
|
60
|
+
return actual.major === base.major && actual.minor === base.minor && actual.patch >= base.patch;
|
|
61
|
+
case "=":
|
|
62
|
+
case "":
|
|
63
|
+
return actual.major === base.major && actual.minor === base.minor && actual.patch === base.patch;
|
|
64
|
+
default:
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export function computeDefaultConstraint(version) {
|
|
69
|
+
const parts = parseSemver(version);
|
|
70
|
+
return `>=${parts.major}.${parts.minor}.0`;
|
|
71
|
+
}
|
|
72
|
+
export function formatVersionMismatch(runtime, found, required, installHint) {
|
|
73
|
+
const lines = [` x ${runtime}`, ` Required: ${required}`, ` Found: ${found || "not installed"}`];
|
|
74
|
+
if (installHint) {
|
|
75
|
+
lines.push(` Fix: ${installHint}`);
|
|
76
|
+
}
|
|
77
|
+
lines.push(" Or update constraint in .blok/config.json");
|
|
78
|
+
return lines.join("\n");
|
|
79
|
+
}
|
|
80
|
+
export function formatVersionSuccess(runtime, found, required) {
|
|
81
|
+
return ` ✓ ${runtime} ${found} (requires ${required})`;
|
|
82
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { compareSemver, computeDefaultConstraint, formatVersionMismatch, formatVersionSuccess, parseConstraint, parseSemver, satisfiesConstraint, semverGte, } from "./semver-utils.js";
|
|
3
|
+
describe("parseSemver", () => {
|
|
4
|
+
it("parses a full semver string", () => {
|
|
5
|
+
expect(parseSemver("3.12.0")).toEqual({ major: 3, minor: 12, patch: 0 });
|
|
6
|
+
});
|
|
7
|
+
it("parses a two-part version", () => {
|
|
8
|
+
expect(parseSemver("1.22")).toEqual({ major: 1, minor: 22, patch: 0 });
|
|
9
|
+
});
|
|
10
|
+
it("parses a single-part version", () => {
|
|
11
|
+
expect(parseSemver("17")).toEqual({ major: 17, minor: 0, patch: 0 });
|
|
12
|
+
});
|
|
13
|
+
it("handles 0.x versions", () => {
|
|
14
|
+
expect(parseSemver("0.2.3")).toEqual({ major: 0, minor: 2, patch: 3 });
|
|
15
|
+
expect(parseSemver("0.0.3")).toEqual({ major: 0, minor: 0, patch: 3 });
|
|
16
|
+
});
|
|
17
|
+
it("handles large version numbers", () => {
|
|
18
|
+
expect(parseSemver("17.0.11")).toEqual({ major: 17, minor: 0, patch: 11 });
|
|
19
|
+
});
|
|
20
|
+
it("trims whitespace", () => {
|
|
21
|
+
expect(parseSemver(" 3.12.0 ")).toEqual({ major: 3, minor: 12, patch: 0 });
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
describe("compareSemver", () => {
|
|
25
|
+
it("returns 0 for equal versions", () => {
|
|
26
|
+
expect(compareSemver("1.2.3", "1.2.3")).toBe(0);
|
|
27
|
+
});
|
|
28
|
+
it("returns positive when a > b (major)", () => {
|
|
29
|
+
expect(compareSemver("2.0.0", "1.9.9")).toBeGreaterThan(0);
|
|
30
|
+
});
|
|
31
|
+
it("returns positive when a > b (minor)", () => {
|
|
32
|
+
expect(compareSemver("1.3.0", "1.2.9")).toBeGreaterThan(0);
|
|
33
|
+
});
|
|
34
|
+
it("returns positive when a > b (patch)", () => {
|
|
35
|
+
expect(compareSemver("1.2.4", "1.2.3")).toBeGreaterThan(0);
|
|
36
|
+
});
|
|
37
|
+
it("returns negative when a < b", () => {
|
|
38
|
+
expect(compareSemver("1.2.3", "1.2.4")).toBeLessThan(0);
|
|
39
|
+
});
|
|
40
|
+
it("treats missing patch as 0", () => {
|
|
41
|
+
expect(compareSemver("1.22", "1.22.0")).toBe(0);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
describe("semverGte", () => {
|
|
45
|
+
it("returns true for equal versions", () => {
|
|
46
|
+
expect(semverGte({ major: 3, minor: 10, patch: 0 }, { major: 3, minor: 10, patch: 0 })).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
it("returns true when major is greater", () => {
|
|
49
|
+
expect(semverGte({ major: 4, minor: 0, patch: 0 }, { major: 3, minor: 10, patch: 0 })).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
it("returns true when minor is greater", () => {
|
|
52
|
+
expect(semverGte({ major: 3, minor: 11, patch: 0 }, { major: 3, minor: 10, patch: 0 })).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
it("returns true when patch is greater", () => {
|
|
55
|
+
expect(semverGte({ major: 3, minor: 10, patch: 1 }, { major: 3, minor: 10, patch: 0 })).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
it("returns false when less than", () => {
|
|
58
|
+
expect(semverGte({ major: 3, minor: 9, patch: 7 }, { major: 3, minor: 10, patch: 0 })).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
describe("parseConstraint", () => {
|
|
62
|
+
it("parses >= operator", () => {
|
|
63
|
+
const result = parseConstraint(">=3.10.0");
|
|
64
|
+
expect(result.operator).toBe(">=");
|
|
65
|
+
expect(result.version).toBe("3.10.0");
|
|
66
|
+
expect(result.parts).toEqual({ major: 3, minor: 10, patch: 0 });
|
|
67
|
+
});
|
|
68
|
+
it("parses ^ operator", () => {
|
|
69
|
+
const result = parseConstraint("^1.22.0");
|
|
70
|
+
expect(result.operator).toBe("^");
|
|
71
|
+
expect(result.version).toBe("1.22.0");
|
|
72
|
+
});
|
|
73
|
+
it("parses ~ operator", () => {
|
|
74
|
+
const result = parseConstraint("~1.22.0");
|
|
75
|
+
expect(result.operator).toBe("~");
|
|
76
|
+
expect(result.version).toBe("1.22.0");
|
|
77
|
+
});
|
|
78
|
+
it("parses exact version (no operator)", () => {
|
|
79
|
+
const result = parseConstraint("3.12.0");
|
|
80
|
+
expect(result.operator).toBe("");
|
|
81
|
+
expect(result.version).toBe("3.12.0");
|
|
82
|
+
});
|
|
83
|
+
it("parses = operator", () => {
|
|
84
|
+
const result = parseConstraint("=3.12.0");
|
|
85
|
+
expect(result.operator).toBe("=");
|
|
86
|
+
expect(result.version).toBe("3.12.0");
|
|
87
|
+
});
|
|
88
|
+
it("trims whitespace", () => {
|
|
89
|
+
const result = parseConstraint(" >=3.10.0 ");
|
|
90
|
+
expect(result.operator).toBe(">=");
|
|
91
|
+
expect(result.version).toBe("3.10.0");
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
describe("satisfiesConstraint", () => {
|
|
95
|
+
describe(">= operator", () => {
|
|
96
|
+
it("satisfies when equal", () => {
|
|
97
|
+
expect(satisfiesConstraint("3.10.0", ">=3.10.0")).toBe(true);
|
|
98
|
+
});
|
|
99
|
+
it("satisfies when greater (patch)", () => {
|
|
100
|
+
expect(satisfiesConstraint("3.10.1", ">=3.10.0")).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
it("satisfies when greater (minor)", () => {
|
|
103
|
+
expect(satisfiesConstraint("3.11.0", ">=3.10.0")).toBe(true);
|
|
104
|
+
});
|
|
105
|
+
it("satisfies when greater (major)", () => {
|
|
106
|
+
expect(satisfiesConstraint("4.0.0", ">=3.10.0")).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
it("fails when less than (minor)", () => {
|
|
109
|
+
expect(satisfiesConstraint("3.9.7", ">=3.10.0")).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
it("fails when less than (major)", () => {
|
|
112
|
+
expect(satisfiesConstraint("2.99.99", ">=3.10.0")).toBe(false);
|
|
113
|
+
});
|
|
114
|
+
it("Python 3.12.0 satisfies >=3.10.0", () => {
|
|
115
|
+
expect(satisfiesConstraint("3.12.0", ">=3.10.0")).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
it("Python 3.9.7 does not satisfy >=3.10.0", () => {
|
|
118
|
+
expect(satisfiesConstraint("3.9.7", ">=3.10.0")).toBe(false);
|
|
119
|
+
});
|
|
120
|
+
it("Go 1.22.5 satisfies >=1.21.0", () => {
|
|
121
|
+
expect(satisfiesConstraint("1.22.5", ">=1.21.0")).toBe(true);
|
|
122
|
+
});
|
|
123
|
+
it("Java 17.0.11 satisfies >=17.0.0", () => {
|
|
124
|
+
expect(satisfiesConstraint("17.0.11", ">=17.0.0")).toBe(true);
|
|
125
|
+
});
|
|
126
|
+
it("Java 11.0.2 does not satisfy >=17.0.0", () => {
|
|
127
|
+
expect(satisfiesConstraint("11.0.2", ">=17.0.0")).toBe(false);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
describe("^ (caret) operator", () => {
|
|
131
|
+
it("satisfies when equal", () => {
|
|
132
|
+
expect(satisfiesConstraint("1.22.0", "^1.22.0")).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
it("satisfies when patch is higher", () => {
|
|
135
|
+
expect(satisfiesConstraint("1.22.5", "^1.22.0")).toBe(true);
|
|
136
|
+
});
|
|
137
|
+
it("satisfies when minor is higher", () => {
|
|
138
|
+
expect(satisfiesConstraint("1.23.0", "^1.22.0")).toBe(true);
|
|
139
|
+
});
|
|
140
|
+
it("fails when major is different", () => {
|
|
141
|
+
expect(satisfiesConstraint("2.0.0", "^1.22.0")).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
it("fails when version is lower", () => {
|
|
144
|
+
expect(satisfiesConstraint("1.21.9", "^1.22.0")).toBe(false);
|
|
145
|
+
});
|
|
146
|
+
it("^0.2.3 requires minor match", () => {
|
|
147
|
+
expect(satisfiesConstraint("0.2.3", "^0.2.3")).toBe(true);
|
|
148
|
+
expect(satisfiesConstraint("0.2.4", "^0.2.3")).toBe(true);
|
|
149
|
+
expect(satisfiesConstraint("0.3.0", "^0.2.3")).toBe(false);
|
|
150
|
+
});
|
|
151
|
+
it("^0.0.3 requires exact patch match", () => {
|
|
152
|
+
expect(satisfiesConstraint("0.0.3", "^0.0.3")).toBe(true);
|
|
153
|
+
expect(satisfiesConstraint("0.0.4", "^0.0.3")).toBe(false);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
describe("~ (tilde) operator", () => {
|
|
157
|
+
it("satisfies when equal", () => {
|
|
158
|
+
expect(satisfiesConstraint("1.22.0", "~1.22.0")).toBe(true);
|
|
159
|
+
});
|
|
160
|
+
it("satisfies when patch is higher", () => {
|
|
161
|
+
expect(satisfiesConstraint("1.22.5", "~1.22.0")).toBe(true);
|
|
162
|
+
});
|
|
163
|
+
it("fails when minor is different", () => {
|
|
164
|
+
expect(satisfiesConstraint("1.23.0", "~1.22.0")).toBe(false);
|
|
165
|
+
});
|
|
166
|
+
it("fails when major is different", () => {
|
|
167
|
+
expect(satisfiesConstraint("2.22.0", "~1.22.0")).toBe(false);
|
|
168
|
+
});
|
|
169
|
+
it("fails when patch is lower", () => {
|
|
170
|
+
expect(satisfiesConstraint("1.22.0", "~1.22.3")).toBe(false);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
describe("exact version", () => {
|
|
174
|
+
it("satisfies when exact match", () => {
|
|
175
|
+
expect(satisfiesConstraint("3.12.0", "3.12.0")).toBe(true);
|
|
176
|
+
});
|
|
177
|
+
it("fails when patch differs", () => {
|
|
178
|
+
expect(satisfiesConstraint("3.12.1", "3.12.0")).toBe(false);
|
|
179
|
+
});
|
|
180
|
+
it("fails when minor differs", () => {
|
|
181
|
+
expect(satisfiesConstraint("3.13.0", "3.12.0")).toBe(false);
|
|
182
|
+
});
|
|
183
|
+
it("works with = prefix", () => {
|
|
184
|
+
expect(satisfiesConstraint("3.12.0", "=3.12.0")).toBe(true);
|
|
185
|
+
expect(satisfiesConstraint("3.12.1", "=3.12.0")).toBe(false);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
describe("computeDefaultConstraint", () => {
|
|
190
|
+
it("computes >=major.minor.0 from full version", () => {
|
|
191
|
+
expect(computeDefaultConstraint("3.12.4")).toBe(">=3.12.0");
|
|
192
|
+
});
|
|
193
|
+
it("computes >=major.minor.0 when patch is already 0", () => {
|
|
194
|
+
expect(computeDefaultConstraint("1.22.0")).toBe(">=1.22.0");
|
|
195
|
+
});
|
|
196
|
+
it("handles two-part version", () => {
|
|
197
|
+
expect(computeDefaultConstraint("1.22")).toBe(">=1.22.0");
|
|
198
|
+
});
|
|
199
|
+
it("handles large major version (Java)", () => {
|
|
200
|
+
expect(computeDefaultConstraint("17.0.11")).toBe(">=17.0.0");
|
|
201
|
+
});
|
|
202
|
+
it("handles 0.x versions", () => {
|
|
203
|
+
expect(computeDefaultConstraint("0.2.3")).toBe(">=0.2.0");
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
describe("formatVersionMismatch", () => {
|
|
207
|
+
it("formats a mismatch with install hint", () => {
|
|
208
|
+
const result = formatVersionMismatch("Python 3", "3.9.7", ">=3.10.0", "Install Python 3.10+: https://python.org/downloads/");
|
|
209
|
+
expect(result).toContain("x Python 3");
|
|
210
|
+
expect(result).toContain("Required: >=3.10.0");
|
|
211
|
+
expect(result).toContain("Found: 3.9.7");
|
|
212
|
+
expect(result).toContain("Fix: Install Python 3.10+");
|
|
213
|
+
expect(result).toContain("Or update constraint in .blok/config.json");
|
|
214
|
+
});
|
|
215
|
+
it("formats a mismatch when not installed", () => {
|
|
216
|
+
const result = formatVersionMismatch("Go", undefined, ">=1.22.0", "Install Go: https://go.dev/dl/");
|
|
217
|
+
expect(result).toContain("Found: not installed");
|
|
218
|
+
});
|
|
219
|
+
it("formats without install hint", () => {
|
|
220
|
+
const result = formatVersionMismatch("Rust", "1.70.0", ">=1.75.0");
|
|
221
|
+
expect(result).not.toContain("Fix:");
|
|
222
|
+
expect(result).toContain("Or update constraint");
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
describe("formatVersionSuccess", () => {
|
|
226
|
+
it("formats a success message", () => {
|
|
227
|
+
const result = formatVersionSuccess("Python 3", "3.12.0", ">=3.10.0");
|
|
228
|
+
expect(result).toContain("✓");
|
|
229
|
+
expect(result).toContain("Python 3");
|
|
230
|
+
expect(result).toContain("3.12.0");
|
|
231
|
+
expect(result).toContain("requires >=3.10.0");
|
|
232
|
+
});
|
|
233
|
+
});
|