@avantf/dsh-plugin-base 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +133 -20
- package/{dist → lib}/bootstrap.d.ts +1 -1
- package/{dist → lib}/bootstrap.d.ts.map +1 -1
- package/{dist → lib}/bootstrap.js +45 -8
- package/{dist → lib}/compat.d.ts +10 -0
- package/{dist → lib}/compat.d.ts.map +1 -1
- package/{dist → lib}/compat.js +30 -7
- package/{dist → lib}/conformance.d.ts.map +1 -1
- package/{dist → lib}/conformance.js +5 -0
- package/{dist → lib}/errors.d.ts +3 -1
- package/lib/errors.d.ts.map +1 -0
- package/{dist → lib}/fs.d.ts.map +1 -1
- package/{dist → lib}/fs.js +3 -0
- package/{dist → lib}/index.d.ts +10 -4
- package/lib/index.d.ts.map +1 -0
- package/{dist → lib}/index.js +18 -3
- package/{dist → lib}/interface.d.ts +165 -32
- package/lib/interface.d.ts.map +1 -0
- package/{dist → lib}/interface.js +56 -16
- package/{dist → lib}/internal.d.ts +2 -0
- package/{dist → lib}/internal.d.ts.map +1 -1
- package/{dist → lib}/internal.js +3 -0
- package/{dist → lib}/lint.d.ts.map +1 -1
- package/{dist → lib}/lint.js +42 -2
- package/{dist → lib}/providers/model.d.ts.map +1 -1
- package/{dist → lib}/providers/model.js +100 -17
- package/{dist → lib}/providers/npm.d.ts +10 -1
- package/lib/providers/npm.d.ts.map +1 -0
- package/{dist → lib}/providers/npm.js +6 -2
- package/lib/providers/runtime_deps.d.ts +184 -0
- package/lib/providers/runtime_deps.d.ts.map +1 -0
- package/lib/providers/runtime_deps.js +1214 -0
- package/{dist → lib}/provisioner.d.ts.map +1 -1
- package/{dist → lib}/provisioner.js +55 -33
- package/lib/runtime_deps_helper.d.ts +71 -0
- package/lib/runtime_deps_helper.d.ts.map +1 -0
- package/lib/runtime_deps_helper.js +161 -0
- package/lib/session_notices.d.ts +95 -0
- package/lib/session_notices.d.ts.map +1 -0
- package/lib/session_notices.js +172 -0
- package/{dist → lib}/state.d.ts +8 -1
- package/{dist → lib}/state.d.ts.map +1 -1
- package/{dist → lib}/state.js +20 -4
- package/{dist → lib}/types.d.ts +14 -1
- package/lib/types.d.ts.map +1 -0
- package/package.json +37 -33
- package/dist/cli.d.ts +0 -4
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -37
- package/dist/errors.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/interface.d.ts.map +0 -1
- package/dist/providers/npm.d.ts.map +0 -1
- package/dist/types.d.ts.map +0 -1
- /package/{dist → lib}/artifact.d.ts +0 -0
- /package/{dist → lib}/artifact.d.ts.map +0 -0
- /package/{dist → lib}/artifact.js +0 -0
- /package/{dist → lib}/conformance.d.ts +0 -0
- /package/{dist → lib}/errors.js +0 -0
- /package/{dist → lib}/fs.d.ts +0 -0
- /package/{dist → lib}/integrity.d.ts +0 -0
- /package/{dist → lib}/integrity.d.ts.map +0 -0
- /package/{dist → lib}/integrity.js +0 -0
- /package/{dist → lib}/interface_gate.d.ts +0 -0
- /package/{dist → lib}/interface_gate.d.ts.map +0 -0
- /package/{dist → lib}/interface_gate.js +0 -0
- /package/{dist → lib}/kit/family.d.ts +0 -0
- /package/{dist → lib}/kit/family.d.ts.map +0 -0
- /package/{dist → lib}/kit/family.js +0 -0
- /package/{dist → lib}/kit/index.d.ts +0 -0
- /package/{dist → lib}/kit/index.d.ts.map +0 -0
- /package/{dist → lib}/kit/index.js +0 -0
- /package/{dist → lib}/kit/logger.d.ts +0 -0
- /package/{dist → lib}/kit/logger.d.ts.map +0 -0
- /package/{dist → lib}/kit/logger.js +0 -0
- /package/{dist → lib}/kit/prompt_files.d.ts +0 -0
- /package/{dist → lib}/kit/prompt_files.d.ts.map +0 -0
- /package/{dist → lib}/kit/prompt_files.js +0 -0
- /package/{dist → lib}/kit/typert.d.ts +0 -0
- /package/{dist → lib}/kit/typert.d.ts.map +0 -0
- /package/{dist → lib}/kit/typert.js +0 -0
- /package/{dist → lib}/kit/wellformed.d.ts +0 -0
- /package/{dist → lib}/kit/wellformed.d.ts.map +0 -0
- /package/{dist → lib}/kit/wellformed.js +0 -0
- /package/{dist → lib}/layout.d.ts +0 -0
- /package/{dist → lib}/layout.d.ts.map +0 -0
- /package/{dist → lib}/layout.js +0 -0
- /package/{dist → lib}/lint.d.ts +0 -0
- /package/{dist → lib}/lock.d.ts +0 -0
- /package/{dist → lib}/lock.d.ts.map +0 -0
- /package/{dist → lib}/lock.js +0 -0
- /package/{dist → lib}/manifest.d.ts +0 -0
- /package/{dist → lib}/manifest.d.ts.map +0 -0
- /package/{dist → lib}/manifest.js +0 -0
- /package/{dist → lib}/net.d.ts +0 -0
- /package/{dist → lib}/net.d.ts.map +0 -0
- /package/{dist → lib}/net.js +0 -0
- /package/{dist → lib}/package-name.d.ts +0 -0
- /package/{dist → lib}/package-name.d.ts.map +0 -0
- /package/{dist → lib}/package-name.js +0 -0
- /package/{dist → lib}/preset.d.ts +0 -0
- /package/{dist → lib}/preset.d.ts.map +0 -0
- /package/{dist → lib}/preset.js +0 -0
- /package/{dist → lib}/providers/archive.d.ts +0 -0
- /package/{dist → lib}/providers/archive.d.ts.map +0 -0
- /package/{dist → lib}/providers/archive.js +0 -0
- /package/{dist → lib}/providers/model.d.ts +0 -0
- /package/{dist → lib}/provisioner.d.ts +0 -0
- /package/{dist → lib}/semver.d.ts +0 -0
- /package/{dist → lib}/semver.d.ts.map +0 -0
- /package/{dist → lib}/semver.js +0 -0
- /package/{dist → lib}/tar.d.ts +0 -0
- /package/{dist → lib}/tar.d.ts.map +0 -0
- /package/{dist → lib}/tar.js +0 -0
- /package/{dist → lib}/types.js +0 -0
- /package/{dist → lib}/zip.d.ts +0 -0
- /package/{dist → lib}/zip.d.ts.map +0 -0
- /package/{dist → lib}/zip.js +0 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { createHash } from 'node:crypto';
|
|
6
6
|
import { once } from 'node:events';
|
|
7
|
-
import { createWriteStream } from 'node:fs';
|
|
7
|
+
import { createReadStream, createWriteStream } from 'node:fs';
|
|
8
8
|
import { basename, dirname, join, relative, resolve } from 'node:path';
|
|
9
9
|
import { finished } from 'node:stream/promises';
|
|
10
10
|
import { ProvisionError } from '../errors.js';
|
|
@@ -138,6 +138,19 @@ function flatSidecarDir(home, targetRoot, repo) {
|
|
|
138
138
|
function sha256Hex(bytes) {
|
|
139
139
|
return createHash('sha256').update(bytes).digest('hex');
|
|
140
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* sha256 of a file on disk, STREAMED one chunk at a time: `fs.readFile` would hold a 2 GiB model
|
|
143
|
+
* file entirely in the heap just to hash it, so `verify` used to spike to the file's full size on
|
|
144
|
+
* every required file. The read goes through `node:fs` for the same reason {@link openBlobSpill}'s
|
|
145
|
+
* write does — {@link ProvisionFs} has no streaming API and this change must not move the interface
|
|
146
|
+
* generation; the path has already been vetted by the seam's `stat` before this runs.
|
|
147
|
+
*/
|
|
148
|
+
async function sha256File(path) {
|
|
149
|
+
const hash = createHash('sha256');
|
|
150
|
+
for await (const chunk of createReadStream(path))
|
|
151
|
+
hash.update(chunk);
|
|
152
|
+
return hash.digest('hex');
|
|
153
|
+
}
|
|
141
154
|
function text(value) {
|
|
142
155
|
return new TextDecoder().decode(value);
|
|
143
156
|
}
|
|
@@ -455,8 +468,26 @@ async function writeBlob(fs, blob, bytes) {
|
|
|
455
468
|
return;
|
|
456
469
|
await fs.atomicWrite(blob, bytes);
|
|
457
470
|
}
|
|
458
|
-
/**
|
|
471
|
+
/**
|
|
472
|
+
* A `verify/failed` the provider's own `probe` ALSO reports as a miss.
|
|
473
|
+
*
|
|
474
|
+
* The distinction decides whether a shared completion marker is withdrawn (see `verify` below): a
|
|
475
|
+
* missing, empty or directory required file is already a `found: false` for THIS item, so removing
|
|
476
|
+
* `refs/<revision>` / `record.json` would only force healthy SIBLING items of the same
|
|
477
|
+
* repo+revision to reinstall. A content mismatch (`verifyCorrupt`) is invisible to the probe's
|
|
478
|
+
* existence checks and must withdraw it.
|
|
479
|
+
*/
|
|
480
|
+
class ProbeDetectedVerifyFailure extends ProvisionError {
|
|
481
|
+
constructor(message) {
|
|
482
|
+
super('verify/failed', message);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
/** Throw a `verify/failed` for a problem `probe` already reports as a miss. */
|
|
459
486
|
function verifyFailure(message) {
|
|
487
|
+
throw new ProbeDetectedVerifyFailure(message);
|
|
488
|
+
}
|
|
489
|
+
/** Throw a `verify/failed` only CONTENT inspection can see — the marker must be withdrawn. */
|
|
490
|
+
function verifyCorrupt(message) {
|
|
460
491
|
throw new ProvisionError('verify/failed', message);
|
|
461
492
|
}
|
|
462
493
|
/** Probe the `flat` layout from the sidecar alone: no network, no mutation. */
|
|
@@ -505,27 +536,27 @@ async function verifyHub(ctx, item, spec, resolved) {
|
|
|
505
536
|
verifyFailure(`模型文件是目录 ${file}:${path}`);
|
|
506
537
|
if (info.size === 0)
|
|
507
538
|
verifyFailure(`模型文件为空 ${file}:${path}`);
|
|
508
|
-
const digest =
|
|
539
|
+
const digest = await sha256File(path);
|
|
509
540
|
const link = await ctx.fs.readlink(path);
|
|
510
541
|
if (link !== undefined) {
|
|
511
542
|
// The entry resolves into blobs, so the read above already proved the blob's bytes.
|
|
512
543
|
const target = resolve(dirname(path), link);
|
|
513
544
|
if (!isInside(blobs, target))
|
|
514
|
-
|
|
545
|
+
verifyCorrupt(`模型文件的链接指向 blobs 之外:${path}`);
|
|
515
546
|
const named = basename(target);
|
|
516
547
|
if (!BLOB_PATTERN.test(named))
|
|
517
|
-
|
|
548
|
+
verifyCorrupt(`模型文件的链接目标名不是 sha256:${path}`);
|
|
518
549
|
if (digest !== named)
|
|
519
|
-
|
|
550
|
+
verifyCorrupt(`模型文件内容与 blob 名不符 ${file}:${path}`);
|
|
520
551
|
continue;
|
|
521
552
|
}
|
|
522
553
|
// A real-file fallback carries no expectation; the blob named by its digest must exist intact.
|
|
523
554
|
const blobPath = join(blobs, digest);
|
|
524
555
|
const blobInfo = await ctx.fs.stat(blobPath);
|
|
525
556
|
if (blobInfo === undefined || blobInfo.isDirectory || blobInfo.size === 0)
|
|
526
|
-
|
|
527
|
-
if (
|
|
528
|
-
|
|
557
|
+
verifyCorrupt(`blob 缺失或为空:${blobPath}`);
|
|
558
|
+
if ((await sha256File(blobPath)) !== digest)
|
|
559
|
+
verifyCorrupt(`blob 内容与名字不符:${blobPath}`);
|
|
529
560
|
}
|
|
530
561
|
}
|
|
531
562
|
/** Prove every required `flat` entry matches its recorded digest or the blob its link names. */
|
|
@@ -549,23 +580,62 @@ async function verifyFlat(ctx, item, spec) {
|
|
|
549
580
|
verifyFailure(`模型文件是目录 ${file}:${path}`);
|
|
550
581
|
if (info.size === 0)
|
|
551
582
|
verifyFailure(`模型文件为空 ${file}:${path}`);
|
|
552
|
-
const digest =
|
|
583
|
+
const digest = await sha256File(path);
|
|
553
584
|
const link = await ctx.fs.readlink(path);
|
|
554
585
|
if (link !== undefined) {
|
|
555
586
|
// The entry resolves into the sidecar, so the read above already proved the blob's bytes.
|
|
556
587
|
const target = resolve(dirname(path), link);
|
|
557
588
|
if (!isInside(blobs, target))
|
|
558
|
-
|
|
589
|
+
verifyCorrupt(`模型文件的链接指向 blobs 之外:${path}`);
|
|
559
590
|
const named = basename(target);
|
|
560
591
|
if (!BLOB_PATTERN.test(named))
|
|
561
|
-
|
|
592
|
+
verifyCorrupt(`模型文件的链接目标名不是 sha256:${path}`);
|
|
562
593
|
if (digest !== named)
|
|
563
|
-
|
|
594
|
+
verifyCorrupt(`模型文件内容与 blob 名不符 ${file}:${path}`);
|
|
564
595
|
}
|
|
565
596
|
// A names-only record cannot prove content; a recorded digest always can.
|
|
566
597
|
const expected = digests[file];
|
|
567
598
|
if (expected !== undefined && digest !== expected)
|
|
568
|
-
|
|
599
|
+
verifyCorrupt(`模型文件内容与 record.json 不符 ${file}:${path}`);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Withdraw the completion marker — `refs/<revision>` (hub) or `record.json` (flat) — after a
|
|
604
|
+
* CONTENT failure that `probe` cannot see. The core does not quarantine this kind's `dir`: it is
|
|
605
|
+
* the shared `snapshots/<sha>` / data directory that `refs/<revision>` and every sibling item of
|
|
606
|
+
* the same repo+sha point at, so renaming it would dangle the ref and fail healthy items with the
|
|
607
|
+
* broken one (see `provisioner.ts`'s `ensureOne`). The marker is the only state that says
|
|
608
|
+
* "installed", so dropping it makes the next `probe` a miss and the next `ensure` reinstall,
|
|
609
|
+
* while the snapshot/blobs stay for everyone else.
|
|
610
|
+
*
|
|
611
|
+
* The marker is SHARED by every item of the same repo+revision, which is why `verify` only calls
|
|
612
|
+
* this for corruption (`verifyCorrupt`) and not for a missing/empty file that this item's own probe
|
|
613
|
+
* already reports. The marker is re-read before removal and only dropped when it still names the
|
|
614
|
+
* sha THIS install wrote: a concurrent installer of another revision may already have replaced it,
|
|
615
|
+
* and its marker is not ours to withdraw. Best-effort throughout — the verify failure is the report.
|
|
616
|
+
*/
|
|
617
|
+
async function withdrawMarker(ctx, item, spec, resolved) {
|
|
618
|
+
try {
|
|
619
|
+
if (layoutOf(spec) === FLAT_LAYOUT) {
|
|
620
|
+
const recordPath = join(flatSidecarDir(ctx.home, item.target.root, spec.repo), RECORD_FILE);
|
|
621
|
+
let record;
|
|
622
|
+
try {
|
|
623
|
+
record = decodeRecord(await ctx.fs.readFile(recordPath));
|
|
624
|
+
}
|
|
625
|
+
catch {
|
|
626
|
+
record = undefined;
|
|
627
|
+
}
|
|
628
|
+
if (record !== undefined && record.sha === resolved.version)
|
|
629
|
+
await ctx.fs.rm(recordPath);
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
const refPath = join(hubRoot(ctx.home, item.target.root, spec.repo), 'refs', spec.revision ?? DEFAULT_REVISION);
|
|
633
|
+
const recorded = text(await ctx.fs.readFile(refPath)).trim();
|
|
634
|
+
if (recorded === resolved.version)
|
|
635
|
+
await ctx.fs.rm(refPath);
|
|
636
|
+
}
|
|
637
|
+
catch {
|
|
638
|
+
// A marker that cannot be read or removed changes nothing about the failure being reported.
|
|
569
639
|
}
|
|
570
640
|
}
|
|
571
641
|
/** Build the built-in model-cache provider. */
|
|
@@ -668,9 +738,22 @@ export function modelCacheProvider(options = {}) {
|
|
|
668
738
|
},
|
|
669
739
|
async verify(item, resolved, ctx) {
|
|
670
740
|
const spec = parseSpec(item);
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
741
|
+
try {
|
|
742
|
+
if (layoutOf(spec) === FLAT_LAYOUT)
|
|
743
|
+
return await verifyFlat(ctx, item, spec);
|
|
744
|
+
return await verifyHub(ctx, item, spec, resolved);
|
|
745
|
+
}
|
|
746
|
+
catch (error) {
|
|
747
|
+
// This kind bypasses `publish()`, so the core will not quarantine the failed directory.
|
|
748
|
+
// A CONTENT failure is invisible to `probe`'s existence checks, so the shared marker must
|
|
749
|
+
// go or the broken copy keeps answering present. A failure the probe already detects
|
|
750
|
+
// (`ProbeDetectedVerifyFailure`: missing/empty/directory) must NOT withdraw it:
|
|
751
|
+
// that marker is shared by every item of the same repo+revision, and the failing item is a
|
|
752
|
+
// miss on its own next probe — removing it would only make healthy siblings reinstall.
|
|
753
|
+
if (!(error instanceof ProbeDetectedVerifyFailure))
|
|
754
|
+
await withdrawMarker(ctx, item, spec, resolved);
|
|
755
|
+
throw error;
|
|
756
|
+
}
|
|
674
757
|
},
|
|
675
758
|
};
|
|
676
759
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Provider } from '../types.js';
|
|
1
|
+
import type { Provider, ProviderContext } from '../types.js';
|
|
2
2
|
export declare const NPM_PACKAGE_KIND = "npm-package";
|
|
3
3
|
/** The `spec` of an `npm-package` item. */
|
|
4
4
|
export interface NpmPackageSpec {
|
|
@@ -14,11 +14,20 @@ export interface NpmPackageSpec {
|
|
|
14
14
|
readonly dir?: string;
|
|
15
15
|
}[];
|
|
16
16
|
}
|
|
17
|
+
export declare function packageVersionOf(ctx: ProviderContext, dir: string): Promise<string | undefined>;
|
|
17
18
|
/** Walk up from a resolved entry to the owning package directory.
|
|
18
19
|
*
|
|
19
20
|
* The candidate is checked BEFORE stepping up (the entry usually sits directly in the package), and
|
|
20
21
|
* `dirnameImpl` is the platform `dirname` so the win32 separator path is covered from Linux. */
|
|
21
22
|
export declare function packageDirOf(entry: string, name: string, dirnameImpl?: (path: string) => string): string | undefined;
|
|
23
|
+
/** Resolve a peer directory from this module's tree, falling back to the host CWD with a warning.
|
|
24
|
+
*
|
|
25
|
+
* The order matters: `import.meta.url` is the tree that actually loaded this provider, while the CWD
|
|
26
|
+
* is wherever the host happened to start. A peer resolved from the CWD becomes an ABSOLUTE symlink
|
|
27
|
+
* inside the published version directory (see {@link linkPeers}); it dangles the moment the host
|
|
28
|
+
* starts elsewhere or that tree is removed, and it made "one zod" depend on the launch directory.
|
|
29
|
+
* The CWD stays as a last resort, but never silently. */
|
|
30
|
+
export declare function resolvePeerDir(peer: string, ctx: ProviderContext): string | undefined;
|
|
22
31
|
/** Build the built-in npm provider. */
|
|
23
32
|
export declare function npmPackageProvider(options?: {
|
|
24
33
|
readonly id?: string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"npm.d.ts","sourceRoot":"","sources":["../../src/providers/npm.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAA+B,QAAQ,EAAE,eAAe,EAAsD,MAAM,aAAa,CAAA;AAE7I,eAAO,MAAM,gBAAgB,gBAAgB,CAAA;AAM7C,2CAA2C;AAC3C,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,0EAA0E;IAC1E,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;IACnC,gGAAgG;IAChG,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAC1G;AAuJD,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CASrG;AAeD;;;iGAGiG;AACjG,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAgB,GAAG,MAAM,GAAG,SAAS,CAS7H;AAED;;;;;;0DAM0D;AAC1D,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,eAAe,GAAG,MAAM,GAAG,SAAS,CAyBrF;AA8FD,uCAAuC;AACvC,wBAAgB,kBAAkB,CAAC,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,QAAQ,CAqKnF"}
|
|
@@ -68,6 +68,10 @@ async function download(ctx, urls, integrity) {
|
|
|
68
68
|
bytes = await readCapped(response, DEFAULT_MAX_BYTES);
|
|
69
69
|
}
|
|
70
70
|
catch (error) {
|
|
71
|
+
// Crossing the byte cap is terminal: every mirror serves the same oversized object, so the
|
|
72
|
+
// remaining candidates are not tried (same rule as `providers/model.ts` and `net.ts`).
|
|
73
|
+
if (error instanceof ProvisionError && error.code === 'fetch/too-large')
|
|
74
|
+
throw error;
|
|
71
75
|
problems.push(`${url} → ${error instanceof Error ? error.message : String(error)}`);
|
|
72
76
|
continue;
|
|
73
77
|
}
|
|
@@ -159,7 +163,7 @@ async function findNativeModule(ctx, dir, depth = 0) {
|
|
|
159
163
|
}
|
|
160
164
|
return undefined;
|
|
161
165
|
}
|
|
162
|
-
async function packageVersionOf(ctx, dir) {
|
|
166
|
+
export async function packageVersionOf(ctx, dir) {
|
|
163
167
|
try {
|
|
164
168
|
const raw = decodeJson(await ctx.fs.readFile(join(dir, 'package.json')));
|
|
165
169
|
if (typeof raw !== 'object' || raw === null)
|
|
@@ -207,7 +211,7 @@ export function packageDirOf(entry, name, dirnameImpl = dirname) {
|
|
|
207
211
|
* inside the published version directory (see {@link linkPeers}); it dangles the moment the host
|
|
208
212
|
* starts elsewhere or that tree is removed, and it made "one zod" depend on the launch directory.
|
|
209
213
|
* The CWD stays as a last resort, but never silently. */
|
|
210
|
-
function resolvePeerDir(peer, ctx) {
|
|
214
|
+
export function resolvePeerDir(peer, ctx) {
|
|
211
215
|
const from = (root) => {
|
|
212
216
|
try {
|
|
213
217
|
return dirname(createRequire(root).resolve(`${peer}/package.json`));
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import type { ProvisionFs, ProvisionLock, ProvisionLogger } from '../types.js';
|
|
2
|
+
import type { Provider } from '../types.js';
|
|
3
|
+
/** The kind the runtime-dependency provider claims. */
|
|
4
|
+
export declare const RUNTIME_DEPS_KIND = "runtime-deps";
|
|
5
|
+
/**
|
|
6
|
+
* The canonical `item.target.root` for the shared store. A caller may put the store elsewhere, but
|
|
7
|
+
* then it is a different shared root: the store is shared through the HOME + target.root pair.
|
|
8
|
+
*/
|
|
9
|
+
export declare const RUNTIME_DEPS_DIR = "deps/runtime";
|
|
10
|
+
/** One registry/cache endpoint the attempts can name. */
|
|
11
|
+
export interface DependencySource {
|
|
12
|
+
/** Stable id referenced by an {@link InstallAttempt}. */
|
|
13
|
+
readonly id: string;
|
|
14
|
+
/** Registry URL (`https://registry.example/`); omitted means "no registry". */
|
|
15
|
+
readonly registry?: string;
|
|
16
|
+
/** Manager cache/store directory; with no registry the attempt runs offline. */
|
|
17
|
+
readonly cache?: string;
|
|
18
|
+
}
|
|
19
|
+
/** One install attempt: a source plus the manager/script/env policy for that try. */
|
|
20
|
+
export interface InstallAttempt {
|
|
21
|
+
/** An id from {@link RuntimeDepsSpec.sources}. */
|
|
22
|
+
readonly source: string;
|
|
23
|
+
/** `pnpm` when omitted (this family's package manager). */
|
|
24
|
+
readonly manager?: 'npm' | 'pnpm';
|
|
25
|
+
/** Extra environment for the manager process. */
|
|
26
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
27
|
+
/**
|
|
28
|
+
* Whether lifecycle scripts may run. Default `false`: a self-heal install that runs arbitrary
|
|
29
|
+
* registry lifecycle scripts is an explicit decision, so a caller that needs a native build says so.
|
|
30
|
+
*/
|
|
31
|
+
readonly allowScripts?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/** What a plugin declares for one `runtime-deps` item. Base names no package. */
|
|
34
|
+
export interface RuntimeDepsSpec {
|
|
35
|
+
/** Package name → package.json dependency spec (a range, `file:` path, alias, …). */
|
|
36
|
+
readonly packages: Readonly<Record<string, string>>;
|
|
37
|
+
/** The registry/cache endpoints the attempts reference. */
|
|
38
|
+
readonly sources: readonly DependencySource[];
|
|
39
|
+
/** Tried in order; the first one whose install AND verify succeeds wins. */
|
|
40
|
+
readonly attempts: readonly InstallAttempt[];
|
|
41
|
+
/**
|
|
42
|
+
* Package specifiers every successful root must resolve (`createRequire(...).resolve()`). Must be
|
|
43
|
+
* non-empty (a root that verified nothing is not a verified root), and the set is part of the key:
|
|
44
|
+
* two specs that verify different things never share a root.
|
|
45
|
+
*/
|
|
46
|
+
readonly verify: readonly string[];
|
|
47
|
+
/**
|
|
48
|
+
* Host-provided singletons: name → peer range. They are LINKED from the caller's tree, never
|
|
49
|
+
* installed. The key commits to the name, the range AND the host copy that gets linked (directory +
|
|
50
|
+
* version), so two specs that ask for different ranges — or resolve the same range from different
|
|
51
|
+
* physical copies — never share a root. Every reuse re-reads the version actually linked into the
|
|
52
|
+
* root against the range declared here.
|
|
53
|
+
*/
|
|
54
|
+
readonly peers?: Readonly<Record<string, string>>;
|
|
55
|
+
/** A lockfile whose bytes seed/audit the install and are part of the content key. */
|
|
56
|
+
readonly lockfile?: string;
|
|
57
|
+
}
|
|
58
|
+
/** What one call to the manager runner returns. */
|
|
59
|
+
export interface RuntimeDepsRunResult {
|
|
60
|
+
readonly code: number;
|
|
61
|
+
readonly stdout: string;
|
|
62
|
+
readonly stderr: string;
|
|
63
|
+
}
|
|
64
|
+
/** One manager invocation; injectable so the orchestration is testable without a registry. */
|
|
65
|
+
export interface RuntimeDepsRunInput {
|
|
66
|
+
readonly manager: 'npm' | 'pnpm';
|
|
67
|
+
readonly args: readonly string[];
|
|
68
|
+
readonly cwd: string;
|
|
69
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
70
|
+
readonly timeoutMs?: number;
|
|
71
|
+
readonly signal?: AbortSignal;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Provider options. None of them is a package name: the options only seam the host tree, the
|
|
75
|
+
* process facts the key is built from, and the manager runner (tests).
|
|
76
|
+
*/
|
|
77
|
+
export interface RuntimeDepsProviderOptions {
|
|
78
|
+
/** Provider id; defaults to `@avantf/dsh-plugin-base/runtime-deps`. */
|
|
79
|
+
readonly id?: string;
|
|
80
|
+
/** The manager used by an attempt that does not name one. Default `pnpm`. */
|
|
81
|
+
readonly manager?: 'npm' | 'pnpm';
|
|
82
|
+
/** Explicit peer directories, overriding resolution from the tree that loaded the base. */
|
|
83
|
+
readonly peerDirs?: Readonly<Record<string, string>>;
|
|
84
|
+
/** Per-key lock budget. Default 300 000 ms. */
|
|
85
|
+
readonly lockTimeoutMs?: number;
|
|
86
|
+
/** A lock whose holder is gone is reclaimed after this age. Default 10 000 ms. */
|
|
87
|
+
readonly staleLockMs?: number;
|
|
88
|
+
/** Manager version probe; defaults to `<manager> --version` (cached per process). */
|
|
89
|
+
readonly managerVersion?: (manager: 'npm' | 'pnpm') => string | undefined;
|
|
90
|
+
/** The manager runner; defaults to spawning the manager with `stdio: pipe`. */
|
|
91
|
+
readonly run?: (input: RuntimeDepsRunInput) => Promise<RuntimeDepsRunResult>;
|
|
92
|
+
/** Process facts the content key includes; defaults to `process.platform/arch/versions.modules`. */
|
|
93
|
+
readonly target?: {
|
|
94
|
+
readonly platform?: string;
|
|
95
|
+
readonly arch?: string;
|
|
96
|
+
readonly abi?: string;
|
|
97
|
+
};
|
|
98
|
+
readonly clock?: () => number;
|
|
99
|
+
}
|
|
100
|
+
/** One attempt as recorded in `.runtime.json`. */
|
|
101
|
+
export interface RuntimeDepsAttemptRecord {
|
|
102
|
+
readonly source: string;
|
|
103
|
+
/** The source's registry, REDACTED: a private registry may carry userinfo or a `_authToken`, and this
|
|
104
|
+
* record sits in a store every plugin can read. The host survives (the diagnostic value), the
|
|
105
|
+
* credential does not; the key still commits to the declared value. */
|
|
106
|
+
readonly registry?: string;
|
|
107
|
+
readonly cache?: string;
|
|
108
|
+
readonly manager: 'npm' | 'pnpm';
|
|
109
|
+
/** The env KEYS the attempt set. Values are deliberately not persisted: they can carry tokens. */
|
|
110
|
+
readonly envKeys: readonly string[];
|
|
111
|
+
readonly allowScripts: boolean;
|
|
112
|
+
readonly result: 'ok' | 'failed';
|
|
113
|
+
readonly code?: string;
|
|
114
|
+
readonly detail?: string;
|
|
115
|
+
readonly ms: number;
|
|
116
|
+
}
|
|
117
|
+
/** The `.runtime.json` record: the handle a plugin can read back from the root. */
|
|
118
|
+
export interface RuntimeDepsRecord {
|
|
119
|
+
readonly schemaVersion: number;
|
|
120
|
+
readonly kind: typeof RUNTIME_DEPS_KIND;
|
|
121
|
+
readonly state: 'ready';
|
|
122
|
+
readonly key: string;
|
|
123
|
+
readonly packages: Readonly<Record<string, string>>;
|
|
124
|
+
readonly lockHash: string;
|
|
125
|
+
readonly platform: string;
|
|
126
|
+
readonly arch: string;
|
|
127
|
+
readonly nodeAbi: string;
|
|
128
|
+
readonly attempts: readonly RuntimeDepsAttemptRecord[];
|
|
129
|
+
readonly peers: readonly string[];
|
|
130
|
+
/**
|
|
131
|
+
* Peers whose link fell back to a COPY (`linkOrCopy`: no symlink support on this filesystem).
|
|
132
|
+
* A linked peer's identity is the resolved host directory AND version; a copied peer only records
|
|
133
|
+
* the version it was cloned from, so the root states that boundary here instead of implying a
|
|
134
|
+
* physical link it does not have. Absent in records written before this field existed.
|
|
135
|
+
*/
|
|
136
|
+
readonly copiedPeers?: readonly string[];
|
|
137
|
+
readonly integrity: string;
|
|
138
|
+
readonly installedAt: string;
|
|
139
|
+
}
|
|
140
|
+
/** The shared store root for one item. */
|
|
141
|
+
export declare function runtimeDepsStore(home: string, targetRoot?: string): string;
|
|
142
|
+
/** The per-key lock path; the lock files sit beside the roots, never inside one. */
|
|
143
|
+
export declare function runtimeDepsLockPath(store: string, key: string): string;
|
|
144
|
+
/** Build the built-in shared runtime-dependency provider.
|
|
145
|
+
* @stable */
|
|
146
|
+
export declare function runtimeDepsProvider(options?: RuntimeDepsProviderOptions): Provider;
|
|
147
|
+
/** What one explicit GC pass did. */
|
|
148
|
+
export interface RuntimeDepsGcReport {
|
|
149
|
+
/** Root keys actually removed. */
|
|
150
|
+
readonly removed: readonly string[];
|
|
151
|
+
/** Root keys kept because a recorded plugin references them or the caller listed them. */
|
|
152
|
+
readonly kept: readonly string[];
|
|
153
|
+
/** Root keys not removed (dry-run candidates, a held lock, an unreadable status plane, a failed rm). */
|
|
154
|
+
readonly skipped: readonly string[];
|
|
155
|
+
/** Stale staging trees and dead lock files cleaned up. */
|
|
156
|
+
readonly cleaned: readonly string[];
|
|
157
|
+
}
|
|
158
|
+
export interface RuntimeDepsGcOptions {
|
|
159
|
+
/** The provisioner home (the `<home>` in `<home>/<root>/<key>`). */
|
|
160
|
+
readonly home: string;
|
|
161
|
+
/** The store root below `home`; defaults to {@link RUNTIME_DEPS_DIR}. */
|
|
162
|
+
readonly root?: string;
|
|
163
|
+
/** Extra keys to keep (a caller's own record of the keys it uses). */
|
|
164
|
+
readonly keep?: readonly string[];
|
|
165
|
+
/** Report but do not remove anything. */
|
|
166
|
+
readonly dryRun?: boolean;
|
|
167
|
+
readonly fs?: ProvisionFs;
|
|
168
|
+
readonly lock?: ProvisionLock;
|
|
169
|
+
readonly logger?: ProvisionLogger;
|
|
170
|
+
readonly clock?: () => number;
|
|
171
|
+
/** A lock whose holder pid is gone is removed after this age. Default 10 000 ms. */
|
|
172
|
+
readonly staleMs?: number;
|
|
173
|
+
/** Staging trees older than this are removed. Default 1 hour. */
|
|
174
|
+
readonly stagingGraceMs?: number;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Remove roots no recorded plugin references. EXPLICIT: this is an operator action, never a startup
|
|
178
|
+
* step, because a running plugin may hold a key that has not reached `status.json` yet.
|
|
179
|
+
*
|
|
180
|
+
* A root is kept when its key is in `keep` or in a `runtime-deps` status row's `version` (the
|
|
181
|
+
* content key this provider reports). Roots in use by a live per-key lock are skipped, never stolen.
|
|
182
|
+
*/
|
|
183
|
+
export declare function gcRuntimeDeps(options: RuntimeDepsGcOptions): Promise<RuntimeDepsGcReport>;
|
|
184
|
+
//# sourceMappingURL=runtime_deps.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime_deps.d.ts","sourceRoot":"","sources":["../../src/providers/runtime_deps.ts"],"names":[],"mappings":"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"}
|