@microwiseai/snapshot 0.6.0 → 0.7.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/dist/commands/current.d.ts +5 -1
- package/dist/commands/current.d.ts.map +1 -1
- package/dist/commands/current.js +89 -2
- package/dist/commands/current.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +46 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/install.d.ts +41 -0
- package/dist/commands/install.d.ts.map +1 -1
- package/dist/commands/install.js +328 -10
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/publish.d.ts.map +1 -1
- package/dist/commands/publish.js +44 -24
- package/dist/commands/publish.js.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/core-floor.d.ts +49 -0
- package/dist/lib/core-floor.d.ts.map +1 -0
- package/dist/lib/core-floor.js +78 -0
- package/dist/lib/core-floor.js.map +1 -0
- package/dist/lib/install-verify.d.ts +101 -0
- package/dist/lib/install-verify.d.ts.map +1 -0
- package/dist/lib/install-verify.js +143 -0
- package/dist/lib/install-verify.js.map +1 -0
- package/dist/lib/ledger.d.ts +212 -0
- package/dist/lib/ledger.d.ts.map +1 -0
- package/dist/lib/ledger.js +266 -0
- package/dist/lib/ledger.js.map +1 -0
- package/dist/lib/pin-override.d.ts +53 -0
- package/dist/lib/pin-override.d.ts.map +1 -0
- package/dist/lib/pin-override.js +77 -0
- package/dist/lib/pin-override.js.map +1 -0
- package/dist/lib/version-validator.d.ts +9 -0
- package/dist/lib/version-validator.d.ts.map +1 -1
- package/dist/lib/version-validator.js +69 -0
- package/dist/lib/version-validator.js.map +1 -1
- package/package.json +9 -2
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S1 — Post-install verification & convergence (SSOT enforcement).
|
|
3
|
+
*
|
|
4
|
+
* The manifest (snapshot.json) is the single source of truth: it pins every npm
|
|
5
|
+
* package to one exact version. But `npm install -g` is non-deterministic — a
|
|
6
|
+
* later batch/step can silently re-dedupe an earlier explicit pin to a different
|
|
7
|
+
* version on disk (see architecture/beta-pipeline-robustness-2026-08-30 §1 E2/E4).
|
|
8
|
+
*
|
|
9
|
+
* This module answers ONE question fail-loud: after install finishes, does the
|
|
10
|
+
* disk actually match the manifest pins? If not, we report `expected X, got Y`
|
|
11
|
+
* and the caller exits non-zero (no more "green on broken").
|
|
12
|
+
*/
|
|
13
|
+
export interface VersionMismatch {
|
|
14
|
+
/** Package name, e.g. "@ist/sesh". */
|
|
15
|
+
name: string;
|
|
16
|
+
/** The exact version the manifest pinned. */
|
|
17
|
+
expected: string;
|
|
18
|
+
/** The actual version found on disk, or null if the package is missing. */
|
|
19
|
+
got: string | null;
|
|
20
|
+
}
|
|
21
|
+
export interface VerificationResult {
|
|
22
|
+
/** True iff every package in the map matches its pin on disk. */
|
|
23
|
+
ok: boolean;
|
|
24
|
+
/** Every package whose disk version != pin (or is missing). */
|
|
25
|
+
mismatches: VersionMismatch[];
|
|
26
|
+
/** Count of packages whose disk version == pin. */
|
|
27
|
+
verifiedCount: number;
|
|
28
|
+
/** Total packages checked. */
|
|
29
|
+
total: number;
|
|
30
|
+
}
|
|
31
|
+
/** A function that returns the on-disk version of a global npm package, or null. */
|
|
32
|
+
export type VersionReader = (name: string) => string | null;
|
|
33
|
+
/** Test seam: reset the cached global npm root (used by unit tests). */
|
|
34
|
+
export declare function _resetGlobalRootCache(): void;
|
|
35
|
+
/**
|
|
36
|
+
* Read the ACTUAL on-disk version of a globally-installed npm package by reading
|
|
37
|
+
* its installed package.json directly from the global root. This reflects what
|
|
38
|
+
* `require()`/the runtime will actually load — unlike `npm ls` which reports the
|
|
39
|
+
* resolution tree. One `npm root -g` call is amortized across all packages
|
|
40
|
+
* (cached), so this is O(1) process spawns regardless of package count.
|
|
41
|
+
*
|
|
42
|
+
* Returns the version string, or null if the package is not installed on disk.
|
|
43
|
+
*/
|
|
44
|
+
export declare function readInstalledNpmVersion(name: string): string | null;
|
|
45
|
+
/**
|
|
46
|
+
* Verify that every package in `installMap` (name → exact pin) is present on
|
|
47
|
+
* disk at exactly that version.
|
|
48
|
+
*
|
|
49
|
+
* Pure & side-effect free (aside from the injected reader): pass a custom
|
|
50
|
+
* `readVersion` for unit tests; defaults to reading the real global npm tree.
|
|
51
|
+
*/
|
|
52
|
+
export declare function verifyInstallMap(installMap: Record<string, string>, opts?: {
|
|
53
|
+
readVersion?: VersionReader;
|
|
54
|
+
}): VerificationResult;
|
|
55
|
+
/**
|
|
56
|
+
* S1 — convergence. Dependency-injected & pure (no console, no direct npm) so it
|
|
57
|
+
* is fully unit-testable and the integrator (install.ts) owns logging/exit.
|
|
58
|
+
*
|
|
59
|
+
* Given the manifest npm install-map and any FB pin overrides:
|
|
60
|
+
* 1. compute the effective map (pins with overrides applied),
|
|
61
|
+
* 2. verify it against the disk,
|
|
62
|
+
* 3. while unverified and within `maxRounds`, call `install(name, expected)`
|
|
63
|
+
* for each mismatched package (exact, isolated re-pin) then re-verify.
|
|
64
|
+
*
|
|
65
|
+
* Returns the FINAL VerificationResult (ok=true iff the disk converged). The
|
|
66
|
+
* integrator decides how to react (exit non-zero, log, fold into the ledger).
|
|
67
|
+
*
|
|
68
|
+
* @param installMap manifest npm pins (name → exact version)
|
|
69
|
+
* @param overrides FB pin overrides (name → version); {} for none
|
|
70
|
+
* @param deps.install re-pin one package to an exact version (side-effecting)
|
|
71
|
+
* @param deps.readVersion disk reader (defaults to readInstalledNpmVersion)
|
|
72
|
+
* @param deps.maxRounds max convergence rounds (default 3)
|
|
73
|
+
*/
|
|
74
|
+
export declare function runConverge(installMap: Record<string, string>, overrides: Record<string, string>, deps: {
|
|
75
|
+
install: (name: string, version: string) => void;
|
|
76
|
+
readVersion?: VersionReader;
|
|
77
|
+
maxRounds?: number;
|
|
78
|
+
}): VerificationResult;
|
|
79
|
+
/**
|
|
80
|
+
* Minimal read-only view of the install ledger. The ledger SCHEMA is owned by
|
|
81
|
+
* lib/ledger.ts (S3); this is a structural subset used only to read `expected`
|
|
82
|
+
* pins for offline verification (e.g. `snapshot doctor`) without importing the
|
|
83
|
+
* ledger module — so verify stays dependency-light and merge-clean.
|
|
84
|
+
*/
|
|
85
|
+
export interface LedgerLike {
|
|
86
|
+
packages?: Array<{
|
|
87
|
+
name?: unknown;
|
|
88
|
+
expected?: unknown;
|
|
89
|
+
}>;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Extract a `name → expected pin` map from a parsed ledger, defensively (unknown
|
|
93
|
+
* shapes / missing fields are skipped rather than throwing). Returns {} when the
|
|
94
|
+
* ledger has no usable expected pins.
|
|
95
|
+
*/
|
|
96
|
+
export declare function expectedMapFromLedger(ledger: LedgerLike | null | undefined): Record<string, string>;
|
|
97
|
+
/**
|
|
98
|
+
* Format mismatches into a fail-loud, multi-line `expected X, got Y` report.
|
|
99
|
+
*/
|
|
100
|
+
export declare function formatVerificationFailure(mismatches: VersionMismatch[]): string;
|
|
101
|
+
//# sourceMappingURL=install-verify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install-verify.d.ts","sourceRoot":"","sources":["../../src/lib/install-verify.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,eAAe;IAC9B,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,QAAQ,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,iEAAiE;IACjE,EAAE,EAAE,OAAO,CAAC;IACZ,+DAA+D;IAC/D,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,mDAAmD;IACnD,aAAa,EAAE,MAAM,CAAC;IACtB,8BAA8B;IAC9B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,oFAAoF;AACpF,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;AAmB5D,wEAAwE;AACxE,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAYnE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,IAAI,GAAE;IAAE,WAAW,CAAC,EAAE,aAAa,CAAA;CAAO,GACzC,kBAAkB,CAsBpB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,CACzB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACjC,IAAI,EAAE;IACJ,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,WAAW,CAAC,EAAE,aAAa,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GACA,kBAAkB,CAapB;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAC1D;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAUnG;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,eAAe,EAAE,GAAG,MAAM,CAU/E"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { existsSync, readFileSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { applyPinOverrides } from './pin-override.js';
|
|
5
|
+
let cachedGlobalRoot;
|
|
6
|
+
/**
|
|
7
|
+
* Resolve the global npm `node_modules` root once (`npm root -g`) and cache it.
|
|
8
|
+
* Returns null if npm is unavailable.
|
|
9
|
+
*/
|
|
10
|
+
function getGlobalNpmRoot() {
|
|
11
|
+
if (cachedGlobalRoot !== undefined)
|
|
12
|
+
return cachedGlobalRoot;
|
|
13
|
+
try {
|
|
14
|
+
const root = execSync('npm root -g', { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
15
|
+
cachedGlobalRoot = root || null;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
cachedGlobalRoot = null;
|
|
19
|
+
}
|
|
20
|
+
return cachedGlobalRoot;
|
|
21
|
+
}
|
|
22
|
+
/** Test seam: reset the cached global npm root (used by unit tests). */
|
|
23
|
+
export function _resetGlobalRootCache() {
|
|
24
|
+
cachedGlobalRoot = undefined;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Read the ACTUAL on-disk version of a globally-installed npm package by reading
|
|
28
|
+
* its installed package.json directly from the global root. This reflects what
|
|
29
|
+
* `require()`/the runtime will actually load — unlike `npm ls` which reports the
|
|
30
|
+
* resolution tree. One `npm root -g` call is amortized across all packages
|
|
31
|
+
* (cached), so this is O(1) process spawns regardless of package count.
|
|
32
|
+
*
|
|
33
|
+
* Returns the version string, or null if the package is not installed on disk.
|
|
34
|
+
*/
|
|
35
|
+
export function readInstalledNpmVersion(name) {
|
|
36
|
+
const root = getGlobalNpmRoot();
|
|
37
|
+
if (!root)
|
|
38
|
+
return null;
|
|
39
|
+
// Scoped names ("@ist/sesh") join correctly into "<root>/@ist/sesh".
|
|
40
|
+
const pkgJsonPath = join(root, name, 'package.json');
|
|
41
|
+
if (!existsSync(pkgJsonPath))
|
|
42
|
+
return null;
|
|
43
|
+
try {
|
|
44
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
|
|
45
|
+
return typeof pkg.version === 'string' ? pkg.version : null;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Verify that every package in `installMap` (name → exact pin) is present on
|
|
53
|
+
* disk at exactly that version.
|
|
54
|
+
*
|
|
55
|
+
* Pure & side-effect free (aside from the injected reader): pass a custom
|
|
56
|
+
* `readVersion` for unit tests; defaults to reading the real global npm tree.
|
|
57
|
+
*/
|
|
58
|
+
export function verifyInstallMap(installMap, opts = {}) {
|
|
59
|
+
const readVersion = opts.readVersion ?? readInstalledNpmVersion;
|
|
60
|
+
const mismatches = [];
|
|
61
|
+
let verifiedCount = 0;
|
|
62
|
+
const names = Object.keys(installMap).sort((a, b) => a.localeCompare(b));
|
|
63
|
+
for (const name of names) {
|
|
64
|
+
const expected = installMap[name];
|
|
65
|
+
const got = readVersion(name);
|
|
66
|
+
if (got === expected) {
|
|
67
|
+
verifiedCount++;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
mismatches.push({ name, expected, got });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
ok: mismatches.length === 0,
|
|
75
|
+
mismatches,
|
|
76
|
+
verifiedCount,
|
|
77
|
+
total: names.length,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* S1 — convergence. Dependency-injected & pure (no console, no direct npm) so it
|
|
82
|
+
* is fully unit-testable and the integrator (install.ts) owns logging/exit.
|
|
83
|
+
*
|
|
84
|
+
* Given the manifest npm install-map and any FB pin overrides:
|
|
85
|
+
* 1. compute the effective map (pins with overrides applied),
|
|
86
|
+
* 2. verify it against the disk,
|
|
87
|
+
* 3. while unverified and within `maxRounds`, call `install(name, expected)`
|
|
88
|
+
* for each mismatched package (exact, isolated re-pin) then re-verify.
|
|
89
|
+
*
|
|
90
|
+
* Returns the FINAL VerificationResult (ok=true iff the disk converged). The
|
|
91
|
+
* integrator decides how to react (exit non-zero, log, fold into the ledger).
|
|
92
|
+
*
|
|
93
|
+
* @param installMap manifest npm pins (name → exact version)
|
|
94
|
+
* @param overrides FB pin overrides (name → version); {} for none
|
|
95
|
+
* @param deps.install re-pin one package to an exact version (side-effecting)
|
|
96
|
+
* @param deps.readVersion disk reader (defaults to readInstalledNpmVersion)
|
|
97
|
+
* @param deps.maxRounds max convergence rounds (default 3)
|
|
98
|
+
*/
|
|
99
|
+
export function runConverge(installMap, overrides, deps) {
|
|
100
|
+
const effectiveMap = applyPinOverrides(installMap, overrides);
|
|
101
|
+
const readVersion = deps.readVersion ?? readInstalledNpmVersion;
|
|
102
|
+
const maxRounds = deps.maxRounds ?? 3;
|
|
103
|
+
let verification = verifyInstallMap(effectiveMap, { readVersion });
|
|
104
|
+
for (let round = 1; round <= maxRounds && !verification.ok; round++) {
|
|
105
|
+
for (const m of verification.mismatches) {
|
|
106
|
+
deps.install(m.name, m.expected);
|
|
107
|
+
}
|
|
108
|
+
verification = verifyInstallMap(effectiveMap, { readVersion });
|
|
109
|
+
}
|
|
110
|
+
return verification;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Extract a `name → expected pin` map from a parsed ledger, defensively (unknown
|
|
114
|
+
* shapes / missing fields are skipped rather than throwing). Returns {} when the
|
|
115
|
+
* ledger has no usable expected pins.
|
|
116
|
+
*/
|
|
117
|
+
export function expectedMapFromLedger(ledger) {
|
|
118
|
+
const map = {};
|
|
119
|
+
const pkgs = ledger?.packages;
|
|
120
|
+
if (!Array.isArray(pkgs))
|
|
121
|
+
return map;
|
|
122
|
+
for (const p of pkgs) {
|
|
123
|
+
if (p && typeof p.name === 'string' && typeof p.expected === 'string') {
|
|
124
|
+
map[p.name] = p.expected;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return map;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Format mismatches into a fail-loud, multi-line `expected X, got Y` report.
|
|
131
|
+
*/
|
|
132
|
+
export function formatVerificationFailure(mismatches) {
|
|
133
|
+
const lines = [
|
|
134
|
+
`Install verification failed: ${mismatches.length} package(s) do not match the manifest pin.`,
|
|
135
|
+
`The disk state diverged from the snapshot (SSOT). Re-run with --converge to auto-repair.`,
|
|
136
|
+
];
|
|
137
|
+
for (const m of mismatches) {
|
|
138
|
+
const got = m.got === null ? '(not installed)' : m.got;
|
|
139
|
+
lines.push(` ✗ ${m.name}: expected ${m.expected}, got ${got}`);
|
|
140
|
+
}
|
|
141
|
+
return lines.join('\n');
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=install-verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install-verify.js","sourceRoot":"","sources":["../../src/lib/install-verify.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================
|
|
3
|
+
* Install Ledger (S3 — beta-pipeline-robustness-2026-08-30 §4)
|
|
4
|
+
* ============================================================
|
|
5
|
+
*
|
|
6
|
+
* Records, at the END of an install, the provenance of every npm package that
|
|
7
|
+
* was installed: the manifest pin we asked for (`expected`), the version that
|
|
8
|
+
* actually landed on disk (`resolved`), the tarball URL it came from, the
|
|
9
|
+
* installer/step that produced it, and the ordered list of install steps.
|
|
10
|
+
*
|
|
11
|
+
* Goal (AC3.1): after any install, "이 버전이 어디서 왔나" ("where did this
|
|
12
|
+
* version come from?") is answerable from the ledger ALONE — no reverse
|
|
13
|
+
* engineering of the global node_modules.
|
|
14
|
+
*
|
|
15
|
+
* SCHEMA OWNERSHIP (manager freeze 2026-08-30): this file is the SSOT for the
|
|
16
|
+
* ledger schema; ledger.json is owned by S3 (this worker). Field names follow
|
|
17
|
+
* the 기획서. S1 (verify) and FB (pin override) live in verify-owned libs and
|
|
18
|
+
* do NOT own this schema. Dependency direction is one-way (verify → ledger):
|
|
19
|
+
* ledger.ts imports NOTHING from install-verify.ts / pin-override.ts.
|
|
20
|
+
*
|
|
21
|
+
* Writing the ledger MUST NOT break an install — every entry point here is
|
|
22
|
+
* best-effort and swallows its own errors.
|
|
23
|
+
*/
|
|
24
|
+
/** Current ledger schema version. Bump on breaking field changes. */
|
|
25
|
+
export declare const LEDGER_SCHEMA_VERSION: 1;
|
|
26
|
+
/** One recorded install step, in execution order. */
|
|
27
|
+
export interface LedgerStep {
|
|
28
|
+
/** 1-based execution order across the whole install. */
|
|
29
|
+
order: number;
|
|
30
|
+
/**
|
|
31
|
+
* Which phase of the install produced this step. Stable identifiers:
|
|
32
|
+
* 'pre-install-batch' — the single up-front `npm install -g` batch
|
|
33
|
+
* 'pre-install-fallback' — per-package retry after the pre-install batch failed
|
|
34
|
+
* 'layer-batch' — a per-snapshot-layer npm batch (installSnapshot)
|
|
35
|
+
* 'layer-fallback' — per-package retry after a layer batch failed
|
|
36
|
+
* 'layer-parallel' — non-npm (glpkg/generic) parallel install
|
|
37
|
+
* 'skit' — skit-adapter install
|
|
38
|
+
*/
|
|
39
|
+
phase: string;
|
|
40
|
+
/** Installer name: 'npm-proxy' | 'glpkg' | 'skit' | ... */
|
|
41
|
+
installer: string;
|
|
42
|
+
/** The exact command executed (or a synthetic label for non-shell installers). */
|
|
43
|
+
command: string;
|
|
44
|
+
/** name@version specs this step targeted. */
|
|
45
|
+
specs: string[];
|
|
46
|
+
/** Outcome of the step. */
|
|
47
|
+
result: 'ok' | 'fallback' | 'failed';
|
|
48
|
+
}
|
|
49
|
+
/** Provenance for a single npm package, resolved after install. */
|
|
50
|
+
export interface LedgerPackage {
|
|
51
|
+
/** Package name, e.g. "@ist/sesh". */
|
|
52
|
+
name: string;
|
|
53
|
+
/** Manifest pin we requested (SSOT intent). null if not declared as npm here. */
|
|
54
|
+
expected: string | null;
|
|
55
|
+
/** Version actually on disk after install (from `npm ls -g`). null if absent. */
|
|
56
|
+
resolved: string | null;
|
|
57
|
+
/** Tarball URL the resolved version was fetched from (from `npm ls -g`). */
|
|
58
|
+
tarballUrl: string | null;
|
|
59
|
+
/** Human/structured summary answering "where did this version come from?". */
|
|
60
|
+
resolutionSource: string;
|
|
61
|
+
/** Installer that handled this package. */
|
|
62
|
+
installer: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* S1 (verify) verdict, folded into the ledger so verification and provenance
|
|
66
|
+
* live in one artifact. WRITTEN BY THE VERIFY WIRING (install.ts integrator),
|
|
67
|
+
* shaped to verifyInstallMap()'s return contract. Optional: absent until the
|
|
68
|
+
* verify pass runs.
|
|
69
|
+
*/
|
|
70
|
+
export interface LedgerVerification {
|
|
71
|
+
ok: boolean;
|
|
72
|
+
verifiedCount: number;
|
|
73
|
+
total: number;
|
|
74
|
+
mismatches: Array<{
|
|
75
|
+
name: string;
|
|
76
|
+
expected: string | null;
|
|
77
|
+
got: string | null;
|
|
78
|
+
}>;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* AC1.4 — core-floor verdict folded into the ledger. Records every below-floor
|
|
82
|
+
* copy of the singleton package (@ist/adapters-core) found in the nested tree.
|
|
83
|
+
* Written by the install.ts integrator (coreFloorAssert lives in core-floor.ts).
|
|
84
|
+
*/
|
|
85
|
+
export interface LedgerCoreFloor {
|
|
86
|
+
ok: boolean;
|
|
87
|
+
pkg: string;
|
|
88
|
+
minVersion: string;
|
|
89
|
+
offenders: Array<{
|
|
90
|
+
path: string;
|
|
91
|
+
version: string;
|
|
92
|
+
}>;
|
|
93
|
+
}
|
|
94
|
+
/** The install ledger written to ~/.config/ist-snapshot/ledger.json. */
|
|
95
|
+
export interface InstallLedger {
|
|
96
|
+
schemaVersion: typeof LEDGER_SCHEMA_VERSION;
|
|
97
|
+
snapshot: {
|
|
98
|
+
name: string;
|
|
99
|
+
version: string;
|
|
100
|
+
};
|
|
101
|
+
/** ISO timestamp the ledger was recorded. */
|
|
102
|
+
recordedAt: string;
|
|
103
|
+
/** snapshot CLI version that produced this ledger. */
|
|
104
|
+
cliVersion: string;
|
|
105
|
+
/** Registry the packages were fetched through (registry-proxy URL). */
|
|
106
|
+
registry: string;
|
|
107
|
+
packages: LedgerPackage[];
|
|
108
|
+
installSteps: LedgerStep[];
|
|
109
|
+
/** FB pin overrides applied this install (name → version). Empty unless FB used. */
|
|
110
|
+
overrides: Record<string, string>;
|
|
111
|
+
/** S1 verification verdict, if the verify pass ran. */
|
|
112
|
+
verification?: LedgerVerification;
|
|
113
|
+
/** AC1.4 core-floor verdict (nested singleton floor), if the assert ran. */
|
|
114
|
+
coreFloor?: LedgerCoreFloor;
|
|
115
|
+
/**
|
|
116
|
+
* DEEP (NOT --depth=0) `npm ls -g <names> --json --all` output captured
|
|
117
|
+
* post-install, stored verbatim. Scoped to the install's npm packages +
|
|
118
|
+
* @ist/adapters-core so NESTED copies are visible — this both (a) recovers
|
|
119
|
+
* "who required version X?" and (b) feeds coreFloorAssert (AC1.4). null if the
|
|
120
|
+
* query failed.
|
|
121
|
+
*/
|
|
122
|
+
dependencyTreeRaw: unknown | null;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Accumulates install steps in execution order. One instance per install;
|
|
126
|
+
* threaded through installSnapshot via InstallSnapshotOptions.recorder.
|
|
127
|
+
*/
|
|
128
|
+
export declare class StepRecorder {
|
|
129
|
+
private steps;
|
|
130
|
+
record(step: Omit<LedgerStep, 'order'>): void;
|
|
131
|
+
getSteps(): LedgerStep[];
|
|
132
|
+
}
|
|
133
|
+
/** Path to the ledger file (sits next to current.json). */
|
|
134
|
+
export declare function getLedgerPath(): string;
|
|
135
|
+
export interface ResolvedQuery {
|
|
136
|
+
/** name → { version, tarballUrl } as read from the global tree. */
|
|
137
|
+
map: Map<string, {
|
|
138
|
+
version: string | null;
|
|
139
|
+
tarballUrl: string | null;
|
|
140
|
+
}>;
|
|
141
|
+
/** The raw parsed `npm ls` JSON (for dependencyTreeRaw), or null on failure. */
|
|
142
|
+
raw: unknown | null;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Query the globally-installed version + tarball URL for the given package
|
|
146
|
+
* names via a single `npm ls -g ... --json` call, and return the raw tree too.
|
|
147
|
+
* Best-effort: on total failure returns an all-null map and raw=null.
|
|
148
|
+
*
|
|
149
|
+
* NOTE: this is provenance extraction (version + tarball + raw tree), NOT the
|
|
150
|
+
* verify worker's single-name readInstalledNpmVersion(). No duplication of
|
|
151
|
+
* that helper — S3 never imports install-verify.
|
|
152
|
+
*/
|
|
153
|
+
export declare function queryResolvedVersions(names: string[]): ResolvedQuery;
|
|
154
|
+
/**
|
|
155
|
+
* Derive the canonical npm tarball URL for a resolved package.
|
|
156
|
+
*
|
|
157
|
+
* npm 7+ does NOT persist the fetched tarball URL for global installs
|
|
158
|
+
* (`npm ls -g --json` omits `resolved`, package.json is stripped of `_resolved`,
|
|
159
|
+
* and there is no global lockfile). So when the authoritative URL is missing we
|
|
160
|
+
* reconstruct the standard registry path — the same one npm would fetch from the
|
|
161
|
+
* scope's configured registry: `<registry>/<name>/-/<unscoped>-<version>.tgz`.
|
|
162
|
+
*
|
|
163
|
+
* Returns null when the version is unknown (nothing to point at). Best-effort:
|
|
164
|
+
* the registry is the one configured for the scope during install (registry-proxy).
|
|
165
|
+
*/
|
|
166
|
+
export declare function deriveTarballUrl(registry: string, name: string, version: string | null): string | null;
|
|
167
|
+
/**
|
|
168
|
+
* Capture a DEEP dependency tree (`npm ls -g <names> --json --all`) for the
|
|
169
|
+
* ledger's `dependencyTreeRaw` and for coreFloorAssert (AC1.4).
|
|
170
|
+
*
|
|
171
|
+
* ★ Deliberately NOT `--depth=0`: nested copies must be visible (an old
|
|
172
|
+
* @ist/adapters-core buried under another package is exactly what re-fragments
|
|
173
|
+
* the registry). To avoid the cost of dumping the ENTIRE global tree, we scope
|
|
174
|
+
* the query to the install's npm packages + the singleton package — with filter
|
|
175
|
+
* args, `npm ls` prunes to the paths that reach those names (full depth), which
|
|
176
|
+
* is bounded yet still surfaces every nested copy of the targets.
|
|
177
|
+
*
|
|
178
|
+
* Best-effort: returns null on failure (ledger writing never fails an install).
|
|
179
|
+
*/
|
|
180
|
+
export declare function queryDependencyTree(names: string[]): unknown | null;
|
|
181
|
+
export interface BuildLedgerInput {
|
|
182
|
+
snapshotName: string;
|
|
183
|
+
snapshotVersion: string;
|
|
184
|
+
cliVersion: string;
|
|
185
|
+
registry: string;
|
|
186
|
+
/** Manifest pins for npm packages: name -> version. The "expected" set. */
|
|
187
|
+
expected: Record<string, string>;
|
|
188
|
+
/** Installer used for the npm packages (typically 'npm-proxy'). */
|
|
189
|
+
installer: string;
|
|
190
|
+
steps: LedgerStep[];
|
|
191
|
+
/** FB pin overrides (name -> version), if any. */
|
|
192
|
+
overrides?: Record<string, string>;
|
|
193
|
+
/**
|
|
194
|
+
* DEEP dependency tree for `dependencyTreeRaw` (from queryDependencyTree).
|
|
195
|
+
* When provided it takes precedence over the resolver's shallow tree — the
|
|
196
|
+
* integrator captures a nested-visible tree (AC1.4). null/undefined → fall
|
|
197
|
+
* back to the resolver's tree.
|
|
198
|
+
*/
|
|
199
|
+
dependencyTreeRaw?: unknown | null;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Assemble the ledger: query disk for resolved versions/tarballs, then join
|
|
203
|
+
* against the expected pins and install steps to explain each package's origin.
|
|
204
|
+
* `now` is injected (callers stamp the timestamp) to keep this pure/testable.
|
|
205
|
+
* `resolver` is injectable for tests; defaults to the real `npm ls -g` query.
|
|
206
|
+
*/
|
|
207
|
+
export declare function buildLedger(input: BuildLedgerInput, now: string, resolver?: (names: string[]) => ResolvedQuery): InstallLedger;
|
|
208
|
+
/** Write the ledger to disk. Best-effort — never throws into the caller. */
|
|
209
|
+
export declare function writeLedger(ledger: InstallLedger): boolean;
|
|
210
|
+
/** Read the ledger from disk, or null if absent/unreadable. */
|
|
211
|
+
export declare function readLedger(): InstallLedger | null;
|
|
212
|
+
//# sourceMappingURL=ledger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ledger.d.ts","sourceRoot":"","sources":["../../src/lib/ledger.ts"],"names":[],"mappings":"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"}
|