@deksden-com/dd-flow-cli 0.2.0 → 0.3.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +76 -5
  3. package/dist/build-info.json +10 -6
  4. package/dist/cli/help.js +165 -20
  5. package/dist/cli/run-cli.js +427 -17
  6. package/dist/schemas/compatibility.schema.json +105 -0
  7. package/dist/schemas/engine-manifest.schema.json +61 -0
  8. package/dist/schemas/flow-guidance.schema.json +90 -0
  9. package/dist/schemas/flow-run-index.schema.json +30 -4
  10. package/dist/schemas/global-dashboard-data.schema.json +126 -0
  11. package/dist/schemas/mb-sdlc-review-report.schema.json +242 -0
  12. package/dist/schemas/mb-upgrade-migration-report.schema.json +93 -0
  13. package/dist/schemas/plan-stage-report.schema.json +83 -0
  14. package/dist/schemas/project-dashboard-data.schema.json +122 -0
  15. package/dist/schemas/project-flow-pack-manifest.schema.json +5 -1
  16. package/dist/schemas/project-summary.schema.json +73 -0
  17. package/dist/schemas/protocol-dashboard-data.schema.json +112 -0
  18. package/dist/schemas/status-report.schema.json +38 -2
  19. package/dist/schemas/version-report.schema.json +22 -0
  20. package/dist/services/build-info.js +26 -3
  21. package/dist/services/canon.js +93 -22
  22. package/dist/services/cleanup.js +45 -1
  23. package/dist/services/cli-operation-classifier.js +104 -0
  24. package/dist/services/compatibility-preflight.js +124 -0
  25. package/dist/services/config.js +31 -0
  26. package/dist/services/dashboard-targets.js +95 -0
  27. package/dist/services/dashboard.js +972 -10
  28. package/dist/services/engines.js +532 -0
  29. package/dist/services/flow-guidance.js +221 -0
  30. package/dist/services/hooks.js +1 -1
  31. package/dist/services/ids.js +106 -0
  32. package/dist/services/lanes.js +333 -1
  33. package/dist/services/merge-queue.js +106 -16
  34. package/dist/services/merge-worker.js +67 -4
  35. package/dist/services/migrations.js +231 -0
  36. package/dist/services/project-summary.js +122 -0
  37. package/dist/services/projects.js +44 -3
  38. package/dist/services/protocol-lifecycle.js +144 -0
  39. package/dist/services/protocols.js +660 -7
  40. package/dist/services/runs.js +98 -21
  41. package/dist/services/schema-validation.js +84 -4
  42. package/dist/services/sessions.js +21 -4
  43. package/dist/services/status.js +199 -1
  44. package/dist/services/version-status.js +59 -9
  45. package/dist/storage/database.js +31 -0
  46. package/dist/storage/paths.js +33 -0
  47. package/package.json +3 -2
@@ -0,0 +1,532 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { spawn } from "node:child_process";
5
+ import { getCliBuildInfo } from "./build-info.js";
6
+ import { AppError } from "../shared/errors.js";
7
+ import { ensureDir, engineStoreRoot, engineVersionRoot, resolveProjectRoot } from "../storage/paths.js";
8
+ import { classifyCliOperation } from "./cli-operation-classifier.js";
9
+ export const engineManifestSchemaId = "dd-flow/engine-manifest@1";
10
+ const routerNativeFamilies = new Set(["engine", "version", "schema"]);
11
+ const sourceFile = fileURLToPath(import.meta.url);
12
+ export function isEngineMode(env) {
13
+ return env.DD_FLOW_ENGINE_MODE === "1";
14
+ }
15
+ export function isRouterNativeCommand(args) {
16
+ const first = args[0];
17
+ if (!first || first === "--version")
18
+ return true;
19
+ if (first === "--help" || first === "-h")
20
+ return true;
21
+ if (args.includes("--help") || args.includes("-h"))
22
+ return true;
23
+ return routerNativeFamilies.has(first);
24
+ }
25
+ export function installCurrentEngine(context, input = {}) {
26
+ const build = getCliBuildInfo();
27
+ const packageRoot = findPackageRoot(path.dirname(sourceFile));
28
+ if (!packageRoot) {
29
+ throw new AppError("engine_install_failed", "Cannot locate package root for current dd-flow engine", 1);
30
+ }
31
+ const target = engineVersionRoot(context.ddFlowHome, build.package_name, build.version);
32
+ const manifestPath = path.join(target, "engine.json");
33
+ const existing = readManifest(manifestPath);
34
+ if (existing && !input.force && manifestHealthy(existing)) {
35
+ return {
36
+ ok: true,
37
+ action: "engine_install",
38
+ changed: false,
39
+ engine: manifestSummary(existing),
40
+ path: target
41
+ };
42
+ }
43
+ const lockDir = `${target}.lock`;
44
+ acquireInstallLock(lockDir);
45
+ try {
46
+ const afterLock = readManifest(manifestPath);
47
+ if (afterLock && !input.force && manifestHealthy(afterLock)) {
48
+ return {
49
+ ok: true,
50
+ action: "engine_install",
51
+ changed: false,
52
+ engine: manifestSummary(afterLock),
53
+ path: target
54
+ };
55
+ }
56
+ const parent = path.dirname(target);
57
+ ensureDir(parent);
58
+ const tmp = path.join(parent, `.${path.basename(target)}.tmp-${process.pid}-${Date.now()}`);
59
+ fs.rmSync(tmp, { recursive: true, force: true });
60
+ ensureDir(tmp);
61
+ copyPackageSnapshot(packageRoot, tmp);
62
+ const manifest = buildManifest(context, build.package_name, build.version, packageRoot, target, tmp);
63
+ fs.writeFileSync(path.join(tmp, "engine.json"), `${JSON.stringify(manifest, null, 2)}\n`);
64
+ fs.rmSync(target, { recursive: true, force: true });
65
+ fs.renameSync(tmp, target);
66
+ const installed = requireManifest(path.join(target, "engine.json"));
67
+ return {
68
+ ok: true,
69
+ action: "engine_install",
70
+ changed: true,
71
+ engine: manifestSummary(installed),
72
+ path: target
73
+ };
74
+ }
75
+ finally {
76
+ fs.rmSync(lockDir, { recursive: true, force: true });
77
+ }
78
+ }
79
+ export function listEngines(context) {
80
+ const root = engineStoreRoot(context.ddFlowHome);
81
+ const engines = readInstalledManifests(root).map(manifestSummary);
82
+ return { ok: true, schema_id: "dd-flow/engine-list@1", engine_store: root, engines };
83
+ }
84
+ export function engineInfo(context, input) {
85
+ const build = getCliBuildInfo();
86
+ const packageName = input.packageName ?? build.package_name;
87
+ const version = input.version ?? build.version;
88
+ const manifest = requireManifest(path.join(engineVersionRoot(context.ddFlowHome, packageName, version), "engine.json"));
89
+ return { ok: true, schema_id: "dd-flow/engine-info@1", engine: manifest };
90
+ }
91
+ export function resolveEngine(context, input = {}) {
92
+ installCurrentEngine(context);
93
+ const selection = selectEngine(context, input);
94
+ if (selection.status === "missing") {
95
+ return { ok: false, error: missingEngineError(selection), selection, exit_code: 1 };
96
+ }
97
+ return { ok: true, schema_id: "dd-flow/engine-resolve@1", selection };
98
+ }
99
+ export function doctorEngines(context, input = {}) {
100
+ installCurrentEngine(context);
101
+ const root = engineStoreRoot(context.ddFlowHome);
102
+ const installed = readInstalledManifests(root);
103
+ const checks = installed.map((manifest) => ({
104
+ engine: manifestSummary(manifest),
105
+ status: manifestHealthy(manifest) ? "ok" : "corrupt",
106
+ diagnostics: manifestDiagnostics(manifest)
107
+ }));
108
+ const selection = selectEngine(context, input);
109
+ return {
110
+ ok: checks.every((check) => check.status === "ok") && selection.status !== "missing",
111
+ schema_id: "dd-flow/engine-doctor@1",
112
+ engine_store: root,
113
+ checks,
114
+ selection,
115
+ exit_code: checks.every((check) => check.status === "ok") && selection.status !== "missing" ? 0 : 1
116
+ };
117
+ }
118
+ export function routeArgsThroughEngine(context, args, io, stdin, env) {
119
+ if (isEngineMode(env) || isRouterNativeCommand(args))
120
+ return Promise.resolve(null);
121
+ installCurrentEngine(context);
122
+ const selection = selectEngine(context, { projectRoot: projectRootFromArgs(args) });
123
+ if (selection.status === "missing") {
124
+ const classification = classifyCliOperation(args, env);
125
+ if (classification.mode === "read_only_diagnostics" || classification.mode === "mb_upgrade") {
126
+ return Promise.resolve(null);
127
+ }
128
+ throw new AppError("missing_engine", "Required dd-flow engine is not installed", 1, missingEngineDetails(selection, classification));
129
+ }
130
+ if (!selection.selected)
131
+ return Promise.resolve(null);
132
+ const current = getCliBuildInfo();
133
+ if (selection.selected.package_name === current.package_name && selection.selected.package_version === current.version) {
134
+ return Promise.resolve({ routed: true, exitCode: 0, inProcess: true });
135
+ }
136
+ const entrypoint = resolveManifestEntrypoint(selection.selected);
137
+ return spawnEngine(selection.selected, entrypoint, args, io, stdin, env, current.version);
138
+ }
139
+ export function engineRoutingMetadata(env) {
140
+ if (env.DD_FLOW_ENGINE_MODE !== "1")
141
+ return null;
142
+ return {
143
+ mode: "engine",
144
+ engine_home: env.DD_FLOW_ENGINE_HOME ?? null,
145
+ routed_from: env.DD_FLOW_ROUTED_FROM ?? null
146
+ };
147
+ }
148
+ export function selectEngine(context, input) {
149
+ const compatibility = readCompatibilityForProject(input.projectRoot);
150
+ const build = getCliBuildInfo();
151
+ const packageName = stringValue(compatibility?.engine?.package_name) ?? build.package_name;
152
+ const requiredRange = stringValue(compatibility?.engine?.version_range) ?? `>=${build.version} <9999.0.0`;
153
+ const recommended = stringValue(compatibility?.engine?.recommended_version) ?? build.version;
154
+ const installHintTemplate = stringValue(compatibility?.engine?.old_engine_install_hint) ?? `npx ${packageName}@<version> engine install`;
155
+ const manifests = readInstalledManifests(engineStoreRoot(context.ddFlowHome))
156
+ .filter((manifest) => manifest.package_name === packageName)
157
+ .filter((manifest) => satisfiesRange(manifest.package_version, requiredRange))
158
+ .filter((manifest) => manifestHealthy(manifest))
159
+ .sort((a, b) => compareSemverStrings(b.package_version, a.package_version));
160
+ const selected = manifests[0] ?? null;
161
+ return {
162
+ status: selected ? "selected" : "missing",
163
+ package_name: packageName,
164
+ required_range: requiredRange,
165
+ recommended_version: recommended,
166
+ selected,
167
+ install_hint: selected ? null : installHintTemplate.replace("<version>", recommended),
168
+ project_root: input.projectRoot ? path.resolve(input.projectRoot) : null,
169
+ memory_bank_version: compatibility?.memory_bank_version ?? null,
170
+ diagnostics: selected ? [] : [`No installed engine satisfies ${packageName} ${requiredRange}`]
171
+ };
172
+ }
173
+ function readCompatibilityForProject(projectRoot) {
174
+ let resolvedProjectRoot = null;
175
+ if (projectRoot) {
176
+ try {
177
+ resolvedProjectRoot = resolveProjectRoot(projectRoot);
178
+ }
179
+ catch {
180
+ return null;
181
+ }
182
+ }
183
+ const roots = resolvedProjectRoot ? [path.join(resolvedProjectRoot, ".memory-bank", "dd-flow", "compatibility.json")] : [];
184
+ for (const file of roots) {
185
+ try {
186
+ return JSON.parse(fs.readFileSync(file, "utf8"));
187
+ }
188
+ catch {
189
+ // Best effort; caller falls back to current package.
190
+ }
191
+ }
192
+ return null;
193
+ }
194
+ function projectRootFromArgs(args) {
195
+ for (let index = 0; index < args.length; index += 1) {
196
+ const arg = args[index];
197
+ if ((arg === "--project-root" || arg === "--root") && args[index + 1])
198
+ return args[index + 1];
199
+ if (arg?.startsWith("--project-root="))
200
+ return arg.slice("--project-root=".length);
201
+ if (arg?.startsWith("--root="))
202
+ return arg.slice("--root=".length);
203
+ }
204
+ return undefined;
205
+ }
206
+ function buildManifest(context, packageName, version, packageRoot, snapshotRoot, checksumRoot) {
207
+ return {
208
+ schema_id: engineManifestSchemaId,
209
+ package_name: packageName,
210
+ package_version: version,
211
+ engine_id: "dd-flow-engine",
212
+ engine_version: version,
213
+ installed_at: context.now(),
214
+ install_source: inferInstallSource(packageRoot, context.ddFlowHome),
215
+ package_root: snapshotRoot,
216
+ snapshot_root: snapshotRoot,
217
+ entrypoint: path.join("dist", "cli.js"),
218
+ supports_memorybank: [`>=0.0.0 <9999.0.0`],
219
+ supports_contracts: {
220
+ compatibility: ["dd-flow/compatibility@1"],
221
+ version: ["dd-flow/version-report@1"],
222
+ status: ["dd-flow/status-report@1"]
223
+ },
224
+ integrity: {
225
+ source: "package_snapshot",
226
+ checksum: fileListChecksum(checksumRoot),
227
+ mode: "file_list"
228
+ }
229
+ };
230
+ }
231
+ function copyPackageSnapshot(packageRoot, target) {
232
+ for (const item of ["dist", "package.json", "README.md", "CHANGELOG.md"]) {
233
+ const from = path.join(packageRoot, item);
234
+ if (!fs.existsSync(from))
235
+ continue;
236
+ const to = path.join(target, item);
237
+ const stat = fs.statSync(from);
238
+ if (stat.isDirectory())
239
+ copyDir(from, to);
240
+ else {
241
+ ensureDir(path.dirname(to));
242
+ fs.copyFileSync(from, to);
243
+ }
244
+ }
245
+ }
246
+ function inferInstallSource(packageRoot, ddFlowHome) {
247
+ const normalized = path.resolve(packageRoot);
248
+ if (normalized.includes(`${path.sep}_npx${path.sep}`) || normalized.includes(`${path.sep}.npm${path.sep}_npx${path.sep}`)) {
249
+ return "npx_package";
250
+ }
251
+ if (isInside(path.resolve(ddFlowHome), normalized))
252
+ return "bundled_package";
253
+ if (hasAncestorFile(normalized, ".git"))
254
+ return "local_development";
255
+ return "bundled_package";
256
+ }
257
+ function hasAncestorFile(start, name) {
258
+ let current = path.resolve(start);
259
+ while (true) {
260
+ if (fs.existsSync(path.join(current, name)))
261
+ return true;
262
+ const parent = path.dirname(current);
263
+ if (parent === current)
264
+ return false;
265
+ current = parent;
266
+ }
267
+ }
268
+ function copyDir(from, to) {
269
+ ensureDir(to);
270
+ for (const entry of fs.readdirSync(from, { withFileTypes: true })) {
271
+ const source = path.join(from, entry.name);
272
+ const target = path.join(to, entry.name);
273
+ if (entry.isDirectory())
274
+ copyDir(source, target);
275
+ else if (entry.isFile())
276
+ fs.copyFileSync(source, target);
277
+ }
278
+ }
279
+ function acquireInstallLock(lockDir) {
280
+ ensureDir(path.dirname(lockDir));
281
+ try {
282
+ fs.mkdirSync(lockDir, { recursive: false });
283
+ }
284
+ catch {
285
+ const stat = fs.existsSync(lockDir) ? fs.statSync(lockDir) : null;
286
+ if (stat && Date.now() - stat.mtimeMs > 120_000) {
287
+ fs.rmSync(lockDir, { recursive: true, force: true });
288
+ fs.mkdirSync(lockDir, { recursive: false });
289
+ return;
290
+ }
291
+ throw new AppError("engine_install_locked", "Another dd-flow engine install is in progress", 1, { lock_dir: lockDir });
292
+ }
293
+ }
294
+ function readInstalledManifests(root) {
295
+ if (!fs.existsSync(root))
296
+ return [];
297
+ const manifests = [];
298
+ for (const packageDir of fs.readdirSync(root)) {
299
+ const packageRoot = path.join(root, packageDir);
300
+ if (!fs.statSync(packageRoot).isDirectory())
301
+ continue;
302
+ for (const versionDir of fs.readdirSync(packageRoot)) {
303
+ const manifest = readManifest(path.join(packageRoot, versionDir, "engine.json"));
304
+ if (manifest)
305
+ manifests.push(manifest);
306
+ }
307
+ }
308
+ return manifests;
309
+ }
310
+ function requireManifest(file) {
311
+ const manifest = readManifest(file);
312
+ if (!manifest)
313
+ throw new AppError("engine_not_found", `Engine manifest not found: ${file}`, 1, { manifest: file });
314
+ return manifest;
315
+ }
316
+ function readManifest(file) {
317
+ try {
318
+ const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
319
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
320
+ return null;
321
+ const record = parsed;
322
+ if (record.schema_id !== engineManifestSchemaId)
323
+ return null;
324
+ return record;
325
+ }
326
+ catch {
327
+ return null;
328
+ }
329
+ }
330
+ function manifestHealthy(manifest) {
331
+ return manifestDiagnostics(manifest).length === 0;
332
+ }
333
+ function manifestDiagnostics(manifest) {
334
+ const diagnostics = [];
335
+ if (manifest.schema_id !== engineManifestSchemaId)
336
+ diagnostics.push("schema_id_mismatch");
337
+ if (!manifest.package_name || !manifest.package_version)
338
+ diagnostics.push("package_identity_missing");
339
+ const snapshotRoot = path.resolve(manifest.snapshot_root || manifest.package_root || "");
340
+ if (!snapshotRoot || !fs.existsSync(snapshotRoot))
341
+ diagnostics.push("snapshot_root_missing");
342
+ const entrypoint = safeResolveManifestEntrypoint(manifest);
343
+ if (!entrypoint)
344
+ diagnostics.push("entrypoint_outside_snapshot");
345
+ else if (!fs.existsSync(entrypoint))
346
+ diagnostics.push("entrypoint_missing");
347
+ return diagnostics;
348
+ }
349
+ function manifestSummary(manifest) {
350
+ return {
351
+ schema_id: manifest.schema_id,
352
+ package_name: manifest.package_name,
353
+ package_version: manifest.package_version,
354
+ engine_version: manifest.engine_version,
355
+ install_source: manifest.install_source,
356
+ entrypoint: manifest.entrypoint,
357
+ snapshot_root: manifest.snapshot_root,
358
+ resolved_entrypoint: safeResolveManifestEntrypoint(manifest),
359
+ installed_at: manifest.installed_at,
360
+ healthy: manifestHealthy(manifest),
361
+ diagnostics: manifestDiagnostics(manifest)
362
+ };
363
+ }
364
+ function resolveManifestEntrypoint(manifest) {
365
+ const resolved = safeResolveManifestEntrypoint(manifest);
366
+ if (!resolved) {
367
+ throw new AppError("unhealthy_engine", "Selected dd-flow engine entrypoint escapes its snapshot", 1, {
368
+ engine: manifestSummary(manifest)
369
+ });
370
+ }
371
+ return resolved;
372
+ }
373
+ function safeResolveManifestEntrypoint(manifest) {
374
+ const snapshotRoot = path.resolve(manifest.snapshot_root || manifest.package_root);
375
+ const candidate = path.isAbsolute(manifest.entrypoint)
376
+ ? path.resolve(manifest.entrypoint)
377
+ : path.resolve(snapshotRoot, manifest.entrypoint);
378
+ if (!isInside(snapshotRoot, candidate))
379
+ return null;
380
+ return candidate;
381
+ }
382
+ function isInside(root, candidate) {
383
+ const relative = path.relative(root, candidate);
384
+ return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
385
+ }
386
+ function spawnEngine(manifest, entrypoint, args, io, stdin, env, routedFromVersion) {
387
+ return new Promise((resolve, reject) => {
388
+ const child = spawn(process.execPath, [entrypoint, ...args], {
389
+ env: {
390
+ ...process.env,
391
+ ...env,
392
+ DD_FLOW_ENGINE_MODE: "1",
393
+ DD_FLOW_ENGINE_HOME: manifest.snapshot_root,
394
+ DD_FLOW_ROUTED_FROM: routedFromVersion
395
+ },
396
+ stdio: ["pipe", "pipe", "pipe"]
397
+ });
398
+ const forwardSignal = (signal) => {
399
+ if (!child.killed)
400
+ child.kill(signal);
401
+ };
402
+ process.once("SIGINT", forwardSignal);
403
+ process.once("SIGTERM", forwardSignal);
404
+ child.stdout.on("data", (chunk) => io.stdout.write(chunk));
405
+ child.stderr.on("data", (chunk) => io.stderr.write(chunk));
406
+ child.on("error", (error) => {
407
+ process.off("SIGINT", forwardSignal);
408
+ process.off("SIGTERM", forwardSignal);
409
+ reject(new AppError("engine_dispatch_failed", `Failed to execute selected dd-flow engine: ${error.message}`, 1, {
410
+ engine: manifestSummary(manifest),
411
+ cause: String(error)
412
+ }));
413
+ });
414
+ child.on("close", (code, signal) => {
415
+ process.off("SIGINT", forwardSignal);
416
+ process.off("SIGTERM", forwardSignal);
417
+ if (signal)
418
+ resolve({ routed: true, exitCode: 128 + signalNumber(signal), inProcess: false });
419
+ else
420
+ resolve({ routed: true, exitCode: code ?? 1, inProcess: false });
421
+ });
422
+ if (stdin)
423
+ stdin.pipe(child.stdin);
424
+ else
425
+ child.stdin.end();
426
+ });
427
+ }
428
+ function signalNumber(signal) {
429
+ if (signal === "SIGINT")
430
+ return 2;
431
+ if (signal === "SIGTERM")
432
+ return 15;
433
+ return 1;
434
+ }
435
+ function missingEngineError(selection, classification = classifyCliOperation(["unknown"])) {
436
+ return {
437
+ code: "missing_engine",
438
+ message: `No installed dd-flow engine satisfies ${selection.package_name} ${selection.required_range ?? ""}`.trim(),
439
+ details: missingEngineDetails(selection, classification)
440
+ };
441
+ }
442
+ function missingEngineDetails(selection, classification) {
443
+ return {
444
+ package_name: selection.package_name,
445
+ required_range: selection.required_range,
446
+ recommended_version: selection.recommended_version,
447
+ install_hint: selection.install_hint,
448
+ project_root: selection.project_root,
449
+ memory_bank_version: selection.memory_bank_version,
450
+ diagnostics: selection.diagnostics,
451
+ compatibility: missingEngineCompatibilityReport(selection, classification)
452
+ };
453
+ }
454
+ function missingEngineCompatibilityReport(selection, classification) {
455
+ const build = getCliBuildInfo();
456
+ return {
457
+ verdict: "incompatible",
458
+ memorybank_version: selection.memory_bank_version,
459
+ router_version: build.version,
460
+ cli_version: build.version,
461
+ engine_version: null,
462
+ engine_resolution: selection.status,
463
+ required_engine_range: selection.required_range,
464
+ recommended_engine_version: selection.recommended_version,
465
+ allowed_modes: ["read_only_diagnostics", "mb_upgrade"],
466
+ operation_mode: classification.mode,
467
+ blocked_operation: classification.mode === "normal_write" ? classification.operation : null,
468
+ install_hint: selection.install_hint,
469
+ project_root: selection.project_root,
470
+ diagnostics: selection.diagnostics
471
+ };
472
+ }
473
+ function findPackageRoot(start) {
474
+ let current = path.resolve(start);
475
+ while (true) {
476
+ if (fs.existsSync(path.join(current, "package.json")))
477
+ return current;
478
+ const parent = path.dirname(current);
479
+ if (parent === current)
480
+ return null;
481
+ current = parent;
482
+ }
483
+ }
484
+ function fileListChecksum(root) {
485
+ const files = [];
486
+ collectFiles(root, root, files);
487
+ return Buffer.from(files.sort().join("\n")).toString("base64url");
488
+ }
489
+ function collectFiles(root, current, files) {
490
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
491
+ if (entry.name === "engine.json")
492
+ continue;
493
+ const full = path.join(current, entry.name);
494
+ if (entry.isDirectory())
495
+ collectFiles(root, full, files);
496
+ else if (entry.isFile())
497
+ files.push(path.relative(root, full));
498
+ }
499
+ }
500
+ function stringValue(value) {
501
+ return typeof value === "string" && value.length > 0 ? value : null;
502
+ }
503
+ function satisfiesRange(version, range) {
504
+ const parts = range.split(/\s+/).filter(Boolean);
505
+ return parts.every((part) => {
506
+ if (part.startsWith(">="))
507
+ return compareSemverStrings(version, part.slice(2)) >= 0;
508
+ if (part.startsWith(">"))
509
+ return compareSemverStrings(version, part.slice(1)) > 0;
510
+ if (part.startsWith("<="))
511
+ return compareSemverStrings(version, part.slice(2)) <= 0;
512
+ if (part.startsWith("<"))
513
+ return compareSemverStrings(version, part.slice(1)) < 0;
514
+ return version === part;
515
+ });
516
+ }
517
+ function compareSemverStrings(left, right) {
518
+ const a = parseSemver(left);
519
+ const b = parseSemver(right);
520
+ if (!a || !b)
521
+ return left.localeCompare(right);
522
+ for (const index of [0, 1, 2]) {
523
+ const diff = a[index] - b[index];
524
+ if (diff !== 0)
525
+ return diff;
526
+ }
527
+ return 0;
528
+ }
529
+ function parseSemver(value) {
530
+ const match = value.match(/^(\d+)\.(\d+)\.(\d+)/);
531
+ return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
532
+ }