akm-cli 0.9.3 → 0.9.4
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/CHANGELOG.md +73 -1
- package/README.md +1 -1
- package/SECURITY.md +1 -1
- package/STABILITY.md +1 -1
- package/dist/akm +2 -2
- package/dist/akm-migrate +2 -2
- package/dist/cli.js +5 -5
- package/dist/commands/health/improve-metrics.js +17 -0
- package/dist/commands/health/windows.js +2 -2
- package/dist/commands/health.js +2 -2
- package/dist/commands/improve/preparation.js +8 -1
- package/dist/commands/lint/index.js +3 -7
- package/dist/commands/tasks/tasks-cli.js +28 -3
- package/dist/commands/tasks/tasks.js +29 -1
- package/dist/core/adapter/adapters/akm-adapter.js +21 -14
- package/dist/core/adapter/adapters/akm-lint.js +3 -2
- package/dist/core/adapter/adapters/akm-task-adapter.js +9 -6
- package/dist/core/adapter/adapters/dotenv-adapter.js +13 -11
- package/dist/core/adapter/recognize-match.js +1 -20
- package/dist/core/asset/asset-placement.js +21 -2
- package/dist/core/common.js +21 -1
- package/dist/indexer/lookup/adapter-concept-owner.js +6 -89
- package/dist/indexer/search/db-search.js +6 -0
- package/dist/indexer/walk/matchers.js +0 -22
- package/dist/output/shapes/helpers.js +19 -1
- package/dist/output/shapes/passthrough.js +1 -0
- package/dist/output/text/command-format.js +4 -0
- package/dist/registry/pinned-request-helper.js +2 -2
- package/dist/registry/pinned-transport.js +6 -6
- package/dist/scripts/akm-migrate-node.js +12624 -12555
- package/dist/scripts/akm-migrate.js +12624 -12555
- package/dist/storage/repositories/proposals-repository.js +33 -1
- package/dist/storage/repositories/task-history-repository.js +22 -10
- package/dist/tasks/run/task-history.js +23 -3
- package/dist/tasks/scheduler-sync-preview.js +43 -0
- package/dist/tasks/scheduler-sync.js +1 -0
- package/dist/tasks/source/bounded-document.js +1 -1
- package/dist/tasks/source/parse-task-source.js +77 -11
- package/dist/tasks/source/task-source-v3-frozen.js +428 -0
- package/dist/tasks/source/task-to-v3.js +500 -0
- package/dist/tasks/source/task-to-v4.js +467 -0
- package/docs/reference/cli.md +8 -3
- package/package.json +4 -4
|
@@ -151,10 +151,10 @@ const BUILTIN_PLACEMENT_SPECS = {
|
|
|
151
151
|
isRelevantFile: (fileName) => path.extname(fileName).toLowerCase() === ".yml",
|
|
152
152
|
toCanonicalName: (typeRoot, filePath) => {
|
|
153
153
|
const rel = toPosix(path.relative(typeRoot, filePath));
|
|
154
|
-
return rel.endsWith(".yml") ? rel.slice(0, -4) : rel;
|
|
154
|
+
return rel.toLowerCase().endsWith(".yml") ? rel.slice(0, -4) : rel;
|
|
155
155
|
},
|
|
156
156
|
toAssetPath: (typeRoot, name) => {
|
|
157
|
-
const withExt = name.endsWith(".yml") ? name : `${name}.yml`;
|
|
157
|
+
const withExt = name.toLowerCase().endsWith(".yml") ? name : `${name}.yml`;
|
|
158
158
|
return path.join(typeRoot, withExt);
|
|
159
159
|
},
|
|
160
160
|
},
|
|
@@ -241,3 +241,22 @@ export function assetPathForName(assetType, typeRoot, name) {
|
|
|
241
241
|
throw new Error(`Unknown asset type: "${assetType}"`);
|
|
242
242
|
return spec.toAssetPath(typeRoot, name);
|
|
243
243
|
}
|
|
244
|
+
/**
|
|
245
|
+
* Every physical path spelling that could own `name`, closed-form (#857).
|
|
246
|
+
* `toAssetPath` is a function, so it can only pick ONE spelling — but `env`'s
|
|
247
|
+
* "default" alias is genuinely dual-owned: both `<dir>/.env` and
|
|
248
|
+
* `<dir>/default.env` derive the same canonical name (`toCanonicalName`
|
|
249
|
+
* above), so a physical-owner lookup must consider both without reading
|
|
250
|
+
* either file. Every other placement type has exactly one inverse spelling.
|
|
251
|
+
*/
|
|
252
|
+
export function assetPathCandidatesForName(assetType, typeRoot, name) {
|
|
253
|
+
const primary = assetPathForName(assetType, typeRoot, name);
|
|
254
|
+
if (assetType !== "env")
|
|
255
|
+
return [primary];
|
|
256
|
+
const base = name === "default" ? "" : name.endsWith("/default") ? name.slice(0, -"default".length) : undefined;
|
|
257
|
+
if (base === undefined)
|
|
258
|
+
return [primary];
|
|
259
|
+
const dotForm = path.join(typeRoot, base, ".env");
|
|
260
|
+
const namedForm = path.join(typeRoot, base, "default.env");
|
|
261
|
+
return [...new Set([primary, dotForm, namedForm])];
|
|
262
|
+
}
|
package/dist/core/common.js
CHANGED
|
@@ -5,7 +5,7 @@ import crypto from "node:crypto";
|
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { ConfigError } from "./errors.js";
|
|
8
|
-
import { getConfigPath, getDefaultStashDir } from "./paths.js";
|
|
8
|
+
import { getConfigPath, getDefaultStashDir, getRegistryCacheDir, getRegistryIndexCacheDir } from "./paths.js";
|
|
9
9
|
// ── Constants ───────────────────────────────────────────────────────────────
|
|
10
10
|
// Moved to the platform leaf so paths.ts can use it without a common↔paths
|
|
11
11
|
// cycle (chunk-8 WI-8.6, DoD 11); re-exported here for the existing surface.
|
|
@@ -382,6 +382,26 @@ export function isContainedRelativePath(value) {
|
|
|
382
382
|
export function isWithin(candidate, root) {
|
|
383
383
|
return isContainedResolvedPath(safeRealpath(candidate), safeRealpath(root));
|
|
384
384
|
}
|
|
385
|
+
/**
|
|
386
|
+
* True when `filePath` sits inside akm's OWN resolved registry-cache
|
|
387
|
+
* directories (`<cache>/registry`, `<cache>/registry-index` —
|
|
388
|
+
* {@link getRegistryCacheDir}/{@link getRegistryIndexCacheDir}), the
|
|
389
|
+
* read-only installed-source/registry-index copies that lint (and `--fix`)
|
|
390
|
+
* must never touch.
|
|
391
|
+
*
|
|
392
|
+
* This is the single source of truth for that exclusion — it replaces what
|
|
393
|
+
* used to be three independent unanchored substring checks
|
|
394
|
+
* (`posixPath.includes("/.cache/") || posixPath.includes("/registry/")`).
|
|
395
|
+
* That check matched ANY path merely containing the literal text `.cache` or
|
|
396
|
+
* `registry` anywhere in it — a normal XDG `~/.cache/...` user bundle, or a
|
|
397
|
+
* CI workspace checked out under a `.cache`-named directory, tripped it and
|
|
398
|
+
* silently got zero lint findings. Using {@link isWithin} (realpath +
|
|
399
|
+
* containment, not a string search) fixes that while still excluding the
|
|
400
|
+
* real cache content the check was meant to skip.
|
|
401
|
+
*/
|
|
402
|
+
export function isAkmRegistryCachePath(filePath) {
|
|
403
|
+
return isWithin(filePath, getRegistryCacheDir()) || isWithin(filePath, getRegistryIndexCacheDir());
|
|
404
|
+
}
|
|
385
405
|
/**
|
|
386
406
|
* {@link isWithin} for callers that must not block the event loop (e.g. the
|
|
387
407
|
* workflow exec dispatch path, which runs once per fan-out unit). Same
|
|
@@ -10,10 +10,6 @@ import { canonicalizeWorkflowName } from "../../core/recognition-util.js";
|
|
|
10
10
|
import { resolveUniqueWorkflowSource, workflowNameForConceptId, } from "../../workflows/source-files.js";
|
|
11
11
|
import { buildFileContext } from "../walk/file-context.js";
|
|
12
12
|
const CONTENT_READ_REQUIRED = Symbol("adapter ownership probe requires content");
|
|
13
|
-
const OWNER_SCAN_MAX_DIRECTORIES = 4_096;
|
|
14
|
-
const OWNER_SCAN_MAX_FILES = 16_384;
|
|
15
|
-
const OWNER_SCAN_SKIP_DIRECTORIES = new Set([".git", "node_modules", "bin", ".cache"]);
|
|
16
|
-
const OWNER_SCAN_SKIP_FILES = new Set([".stash.json", ".gitignore", ".gitattributes"]);
|
|
17
13
|
export class AdapterConceptOwnershipError extends UsageError {
|
|
18
14
|
}
|
|
19
15
|
export class AdapterConceptCollisionError extends AdapterConceptOwnershipError {
|
|
@@ -24,13 +20,6 @@ export class AdapterConceptCollisionError extends AdapterConceptOwnershipError {
|
|
|
24
20
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
25
21
|
}
|
|
26
22
|
}
|
|
27
|
-
export class AdapterConceptScanError extends AdapterConceptOwnershipError {
|
|
28
|
-
constructor(adapterId, sourcePath, detail) {
|
|
29
|
-
super(`Adapter "${adapterId}" could not safely enumerate ${sourcePath}: ${detail}.`, "INVALID_FLAG_VALUE");
|
|
30
|
-
this.name = "AdapterConceptScanError";
|
|
31
|
-
Object.setPrototypeOf(this, new.target.prototype);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
23
|
function normalizedConceptId(conceptId) {
|
|
35
24
|
const normalized = conceptId.replaceAll("\\", "/");
|
|
36
25
|
if (!normalized ||
|
|
@@ -128,79 +117,6 @@ function claimsWithoutContent(adapter, component, owner) {
|
|
|
128
117
|
throw error;
|
|
129
118
|
}
|
|
130
119
|
}
|
|
131
|
-
function adapterPathContext(root, authoredPath) {
|
|
132
|
-
const file = buildFileContext(root, authoredPath);
|
|
133
|
-
return {
|
|
134
|
-
absPath: file.absPath,
|
|
135
|
-
relPath: file.relPath,
|
|
136
|
-
ext: file.ext,
|
|
137
|
-
fileName: file.fileName,
|
|
138
|
-
parentDir: file.parentDir,
|
|
139
|
-
parentDirAbs: file.parentDirAbs,
|
|
140
|
-
ancestorDirs: file.ancestorDirs,
|
|
141
|
-
stashRoot: file.stashRoot,
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
function scanRegularAuthoredPaths(sourcePath, realRoot, adapterId) {
|
|
145
|
-
const paths = [];
|
|
146
|
-
const stack = [path.resolve(sourcePath)];
|
|
147
|
-
let directories = 0;
|
|
148
|
-
let files = 0;
|
|
149
|
-
while (stack.length > 0) {
|
|
150
|
-
const current = stack.pop();
|
|
151
|
-
if (!current)
|
|
152
|
-
continue;
|
|
153
|
-
directories++;
|
|
154
|
-
if (directories > OWNER_SCAN_MAX_DIRECTORIES) {
|
|
155
|
-
throw new AdapterConceptScanError(adapterId, sourcePath, `directory limit ${OWNER_SCAN_MAX_DIRECTORIES} exceeded`);
|
|
156
|
-
}
|
|
157
|
-
let entries;
|
|
158
|
-
try {
|
|
159
|
-
entries = fs
|
|
160
|
-
.readdirSync(current, { withFileTypes: true })
|
|
161
|
-
.sort((left, right) => comparePaths(left.name, right.name));
|
|
162
|
-
}
|
|
163
|
-
catch (error) {
|
|
164
|
-
throw new AdapterConceptScanError(adapterId, sourcePath, `cannot read ${current}: ${String(error)}`);
|
|
165
|
-
}
|
|
166
|
-
for (let index = entries.length - 1; index >= 0; index--) {
|
|
167
|
-
const entry = entries[index];
|
|
168
|
-
if (!entry)
|
|
169
|
-
continue;
|
|
170
|
-
const authoredPath = path.join(current, entry.name);
|
|
171
|
-
if (entry.isSymbolicLink())
|
|
172
|
-
continue;
|
|
173
|
-
if (entry.isDirectory()) {
|
|
174
|
-
if (OWNER_SCAN_SKIP_DIRECTORIES.has(entry.name) || entry.name.startsWith("."))
|
|
175
|
-
continue;
|
|
176
|
-
stack.push(authoredPath);
|
|
177
|
-
continue;
|
|
178
|
-
}
|
|
179
|
-
if (!entry.isFile() || OWNER_SCAN_SKIP_FILES.has(entry.name))
|
|
180
|
-
continue;
|
|
181
|
-
files++;
|
|
182
|
-
if (files > OWNER_SCAN_MAX_FILES) {
|
|
183
|
-
throw new AdapterConceptScanError(adapterId, sourcePath, `file limit ${OWNER_SCAN_MAX_FILES} exceeded`);
|
|
184
|
-
}
|
|
185
|
-
let realPath;
|
|
186
|
-
try {
|
|
187
|
-
realPath = fs.realpathSync(authoredPath);
|
|
188
|
-
}
|
|
189
|
-
catch {
|
|
190
|
-
continue;
|
|
191
|
-
}
|
|
192
|
-
if (isWithin(realPath, realRoot))
|
|
193
|
-
paths.push(authoredPath);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
return paths.sort(comparePaths);
|
|
197
|
-
}
|
|
198
|
-
function scannedReadCandidates(adapter, component, sourcePath, realRoot, conceptId) {
|
|
199
|
-
const recognizePathCandidates = adapter.recognizePathCandidates;
|
|
200
|
-
if (!recognizePathCandidates)
|
|
201
|
-
return [];
|
|
202
|
-
return scanRegularAuthoredPaths(sourcePath, realRoot, adapter.id).flatMap((authoredPath) => recognizePathCandidates(component, adapterPathContext(sourcePath, authoredPath)).flatMap((candidateConceptId) => candidateConceptId === conceptId ? [{ path: authoredPath, conceptId: candidateConceptId }] : []));
|
|
203
|
-
}
|
|
204
120
|
/**
|
|
205
121
|
* Resolve one adapter/component's exact physical concept owner without reading
|
|
206
122
|
* authored bytes. Native workflow arbitration is reused verbatim; every other
|
|
@@ -240,11 +156,12 @@ export function resolveAdapterConceptOwner(sourcePath, adapterId, conceptId) {
|
|
|
240
156
|
ownersByIdentity.set(`${path.resolve(workflowOwner.path)}\0${resolutionConceptId}`, workflowOwner);
|
|
241
157
|
}
|
|
242
158
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
159
|
+
// Closed-form candidate set (#857): `readCandidates` inverts the conceptId
|
|
160
|
+
// directly into every physical spelling that could own it (no bundle walk
|
|
161
|
+
// — see each adapter's own doc comment for its candidate classes), and
|
|
162
|
+
// `candidateSpellings` adds case-only siblings via one `readdirSync` of
|
|
163
|
+
// each candidate's own parent directory.
|
|
164
|
+
const spellings = (adapter.readCandidates?.(component, resolutionConceptId) ?? []).flatMap((candidate) => candidateSpellings(candidate, sourcePath, realRoot));
|
|
248
165
|
const inspected = [
|
|
249
166
|
...new Map(spellings.map((candidate) => [`${path.resolve(candidate.path)}\0${candidate.conceptId}`, candidate])).values(),
|
|
250
167
|
]
|
|
@@ -785,6 +785,10 @@ export async function buildDbHit(input) {
|
|
|
785
785
|
...(input.entry.beliefState ? { beliefState: input.entry.beliefState } : {}),
|
|
786
786
|
...(input.entry.currentBeliefRefs ? { currentBeliefRefs: input.entry.currentBeliefRefs } : {}),
|
|
787
787
|
...(graphHit ? { graph: { entities: graphHit.entities, relations: graphHit.relations } } : {}),
|
|
788
|
+
// Which stage of the progressive AND->OR lexical ladder produced this
|
|
789
|
+
// hit. Omitted when the hit has no FTS component (pure-semantic hybrid
|
|
790
|
+
// contribution).
|
|
791
|
+
...(input.lexicalMatch ? { matchStage: input.lexicalMatch } : {}),
|
|
788
792
|
};
|
|
789
793
|
attachDbHitAttribution(hit, input);
|
|
790
794
|
if (input.entry.derivedFrom) {
|
|
@@ -825,6 +829,8 @@ rankingMode, qualityBoost, confidenceBoost, utilityBoosted, graphBoost, lexicalM
|
|
|
825
829
|
];
|
|
826
830
|
if (lexicalMatch === "relaxed")
|
|
827
831
|
reasons.push("lexical recovery after strict query returned no hits");
|
|
832
|
+
if (lexicalMatch === "prefix")
|
|
833
|
+
reasons.push("prefix match after strict query returned no hits");
|
|
828
834
|
const tokens = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
829
835
|
const queryLower = query.toLowerCase().trim();
|
|
830
836
|
const name = entry.name.toLowerCase();
|
|
@@ -270,28 +270,6 @@ function matchResultForFact(fact) {
|
|
|
270
270
|
...(fact.meta ? { meta: fact.meta } : {}),
|
|
271
271
|
};
|
|
272
272
|
}
|
|
273
|
-
/**
|
|
274
|
-
* Every smart-Markdown result possible from path fields alone. The actual
|
|
275
|
-
* classifier above returns only facts from this shared table, so owner
|
|
276
|
-
* discovery can conservatively model a byte request without reading bytes.
|
|
277
|
-
*/
|
|
278
|
-
export function smartMdPathCandidates(ctx) {
|
|
279
|
-
if (ctx.ext !== ".md" || ctx.ancestorDirs.includes("secrets"))
|
|
280
|
-
return [];
|
|
281
|
-
const facts = isTypedDirDocFile(ctx.fileName)
|
|
282
|
-
? [SMART_MD_FACTS.knowledge]
|
|
283
|
-
: [
|
|
284
|
-
SMART_MD_FACTS.workflow,
|
|
285
|
-
SMART_MD_FACTS.toolsAgent,
|
|
286
|
-
SMART_MD_FACTS.command,
|
|
287
|
-
SMART_MD_FACTS.modelAgent,
|
|
288
|
-
SMART_MD_FACTS.knowledge,
|
|
289
|
-
];
|
|
290
|
-
return facts.flatMap((fact) => {
|
|
291
|
-
const result = matchResultForFact(fact);
|
|
292
|
-
return result ? [result] : [];
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
273
|
// ---------------------------------------------------------------------------
|
|
296
274
|
// Public matchers (API unchanged)
|
|
297
275
|
// ---------------------------------------------------------------------------
|
|
@@ -275,7 +275,20 @@ export function shapeSearchHit(hit, detail) {
|
|
|
275
275
|
// visible without forcing callers up to `--detail full`. Optional
|
|
276
276
|
// `quality` (v1 spec §4.2) is also surfaced when present so callers
|
|
277
277
|
// can see why a `proposed` entry showed up under `--include-proposed`.
|
|
278
|
-
|
|
278
|
+
// `matchStage` (issue #856) surfaces which stage of the progressive
|
|
279
|
+
// AND->OR lexical ladder produced the hit; cheap compact enum, worth
|
|
280
|
+
// showing without requiring `--detail full`.
|
|
281
|
+
const shaped = capDescription(pickFields(hit, [
|
|
282
|
+
"type",
|
|
283
|
+
"name",
|
|
284
|
+
"description",
|
|
285
|
+
"action",
|
|
286
|
+
"score",
|
|
287
|
+
"estimatedTokens",
|
|
288
|
+
"warnings",
|
|
289
|
+
"quality",
|
|
290
|
+
"matchStage",
|
|
291
|
+
]), NORMAL_DESCRIPTION_LIMIT);
|
|
279
292
|
if (Array.isArray(hit.keys) && hit.keys.length > 0)
|
|
280
293
|
shaped.keys = hit.keys;
|
|
281
294
|
return shaped;
|
|
@@ -296,6 +309,11 @@ export function shapeSearchHitForAgent(hit) {
|
|
|
296
309
|
"score",
|
|
297
310
|
"estimatedTokens",
|
|
298
311
|
"keys",
|
|
312
|
+
// Issue #856: which stage of the progressive AND->OR lexical ladder
|
|
313
|
+
// produced this hit. Agents need this to gauge how much to trust a
|
|
314
|
+
// hit (a strict-AND match is stronger signal than an OR-fallback
|
|
315
|
+
// recovery match) without going to `--detail full`.
|
|
316
|
+
"matchStage",
|
|
299
317
|
]);
|
|
300
318
|
if (picked.editable !== false)
|
|
301
319
|
delete picked.editHint;
|
|
@@ -270,6 +270,10 @@ export function formatSearchPlain(r, detail) {
|
|
|
270
270
|
// Optional v1 spec §4.2 quality marker (e.g. "curated" / "proposed").
|
|
271
271
|
if (typeof hit.quality === "string" && hit.quality)
|
|
272
272
|
lines.push(` quality: ${hit.quality}`);
|
|
273
|
+
// Issue #856: which stage of the progressive AND->OR lexical ladder
|
|
274
|
+
// produced this hit ("exact" | "prefix" | "relaxed").
|
|
275
|
+
if (typeof hit.matchStage === "string" && hit.matchStage)
|
|
276
|
+
lines.push(` matchStage: ${hit.matchStage}`);
|
|
273
277
|
// Surface optional hit-level warnings (v1 spec §4.2).
|
|
274
278
|
if (Array.isArray(hit.warnings) && hit.warnings.length > 0) {
|
|
275
279
|
lines.push(` warnings: ${hit.warnings.join("; ")}`);
|
|
@@ -117,8 +117,8 @@ export async function nodePinnedRequestHelperMain(readRequest, http, https, isIP
|
|
|
117
117
|
};
|
|
118
118
|
try {
|
|
119
119
|
const nodeMajor = Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10);
|
|
120
|
-
if (!Number.isInteger(nodeMajor) || nodeMajor <
|
|
121
|
-
throw new Error(`The pinned registry helper requires Node.js >=
|
|
120
|
+
if (!Number.isInteger(nodeMajor) || nodeMajor < 22) {
|
|
121
|
+
throw new Error(`The pinned registry helper requires Node.js >= 22; found ${process.versions.node}`);
|
|
122
122
|
}
|
|
123
123
|
const { addressFamily, bodyPresent, headers, initialBody, iterator, method, pinnedAddress, timeoutMs, url } = await readRequest(isIP);
|
|
124
124
|
const hostname = url.hostname.startsWith("[") && url.hostname.endsWith("]") ? url.hostname.slice(1, -1) : url.hostname;
|
|
@@ -233,7 +233,7 @@ async function requestWithNodeHelper(url, address, prepared, init, timeoutMs, ex
|
|
|
233
233
|
}
|
|
234
234
|
function resolveNodeExecutable(override) {
|
|
235
235
|
if (override === null) {
|
|
236
|
-
throw new RegistryPinnedTransportError("Pinned registry networking under Bun requires Node.js >=
|
|
236
|
+
throw new RegistryPinnedTransportError("Pinned registry networking under Bun requires Node.js >= 22 on PATH; no Node executable is available");
|
|
237
237
|
}
|
|
238
238
|
const candidates = override === undefined
|
|
239
239
|
? (process.env.PATH ?? "")
|
|
@@ -254,10 +254,10 @@ function resolveNodeExecutable(override) {
|
|
|
254
254
|
return resolved;
|
|
255
255
|
}
|
|
256
256
|
catch {
|
|
257
|
-
// Try the next PATH entry. The helper itself enforces Node >=
|
|
257
|
+
// Try the next PATH entry. The helper itself enforces Node >= 22.
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
|
-
throw new RegistryPinnedTransportError("Pinned registry networking under Bun requires Node.js >=
|
|
260
|
+
throw new RegistryPinnedTransportError("Pinned registry networking under Bun requires Node.js >= 22 on PATH. Install Node.js or run akm with Node.");
|
|
261
261
|
}
|
|
262
262
|
function isSupportedNodeExecutable(executable) {
|
|
263
263
|
const result = spawnSync(executable, ["--version"], {
|
|
@@ -272,14 +272,14 @@ function isSupportedNodeExecutable(executable) {
|
|
|
272
272
|
return false;
|
|
273
273
|
const match = /^v(\d+)\./.exec(result.stdout.trim());
|
|
274
274
|
const major = match?.[1];
|
|
275
|
-
return major !== undefined && Number.parseInt(major, 10) >=
|
|
275
|
+
return major !== undefined && Number.parseInt(major, 10) >= 22;
|
|
276
276
|
}
|
|
277
277
|
function assertNodeRuntimeVersion() {
|
|
278
278
|
if (process.versions.bun)
|
|
279
279
|
return;
|
|
280
280
|
const major = Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10);
|
|
281
|
-
if (!Number.isInteger(major) || major <
|
|
282
|
-
throw new RegistryPinnedTransportError(`Pinned registry networking requires Node.js >=
|
|
281
|
+
if (!Number.isInteger(major) || major < 22) {
|
|
282
|
+
throw new RegistryPinnedTransportError(`Pinned registry networking requires Node.js >= 22; found ${process.versions.node}`);
|
|
283
283
|
}
|
|
284
284
|
}
|
|
285
285
|
function createHelperArtifacts(ca) {
|