@microwiseai/snapshot 0.3.71 → 0.5.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/clone-all.d.ts +21 -0
- package/dist/commands/clone-all.d.ts.map +1 -0
- package/dist/commands/clone-all.js +57 -0
- package/dist/commands/clone-all.js.map +1 -0
- package/dist/commands/install.d.ts +9 -1
- package/dist/commands/install.d.ts.map +1 -1
- package/dist/commands/install.js +219 -49
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/license.js.map +1 -1
- package/dist/commands/list.d.ts +15 -0
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +21 -39
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/publish.d.ts.map +1 -1
- package/dist/commands/publish.js +22 -0
- package/dist/commands/publish.js.map +1 -1
- package/dist/index.js +51 -29
- package/dist/index.js.map +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +15 -10
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/gitlab.d.ts.map +1 -1
- package/dist/lib/gitlab.js +6 -8
- package/dist/lib/gitlab.js.map +1 -1
- package/dist/lib/transitive-resolver.js.map +1 -1
- package/dist/lib/types.d.ts +1 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/version-validator.d.ts +28 -0
- package/dist/lib/version-validator.d.ts.map +1 -0
- package/dist/lib/version-validator.js +105 -0
- package/dist/lib/version-validator.js.map +1 -0
- package/dist/lib.js.map +1 -1
- package/node_modules/@microwiseai/snapshot-dedup/dist/dedup.d.ts +86 -1
- package/node_modules/@microwiseai/snapshot-dedup/dist/dedup.d.ts.map +1 -1
- package/node_modules/@microwiseai/snapshot-dedup/dist/dedup.js +171 -5
- package/node_modules/@microwiseai/snapshot-dedup/dist/dedup.js.map +1 -1
- package/node_modules/@microwiseai/snapshot-dedup/dist/index.d.ts +1 -1
- package/node_modules/@microwiseai/snapshot-dedup/dist/index.d.ts.map +1 -1
- package/node_modules/@microwiseai/snapshot-dedup/dist/index.js +4 -2
- package/node_modules/@microwiseai/snapshot-dedup/dist/index.js.map +1 -1
- package/node_modules/@microwiseai/snapshot-dedup/package.json +8 -2
- package/package.json +18 -4
- package/dist/commands/doctor.d.ts +0 -2
- package/dist/commands/doctor.d.ts.map +0 -1
- package/dist/commands/doctor.js +0 -37
- package/dist/commands/doctor.js.map +0 -1
- package/dist/commands/setup.d.ts +0 -5
- package/dist/commands/setup.d.ts.map +0 -1
- package/dist/commands/setup.js +0 -52
- package/dist/commands/setup.js.map +0 -1
- package/dist/lib/access-sync.test.d.ts +0 -2
- package/dist/lib/access-sync.test.d.ts.map +0 -1
- package/dist/lib/access-sync.test.js +0 -112
- package/dist/lib/access-sync.test.js.map +0 -1
- package/dist/lib/auth-flow.test.d.ts +0 -2
- package/dist/lib/auth-flow.test.d.ts.map +0 -1
- package/dist/lib/auth-flow.test.js +0 -265
- package/dist/lib/auth-flow.test.js.map +0 -1
- package/dist/lib/config.test.d.ts +0 -2
- package/dist/lib/config.test.d.ts.map +0 -1
- package/dist/lib/config.test.js +0 -151
- package/dist/lib/config.test.js.map +0 -1
- package/dist/lib/gitlab.test.d.ts +0 -2
- package/dist/lib/gitlab.test.d.ts.map +0 -1
- package/dist/lib/gitlab.test.js +0 -63
- package/dist/lib/gitlab.test.js.map +0 -1
- package/dist/lib/prerequisites.d.ts +0 -33
- package/dist/lib/prerequisites.d.ts.map +0 -1
- package/dist/lib/prerequisites.js +0 -123
- package/dist/lib/prerequisites.js.map +0 -1
- package/dist/lib/registry.d.ts +0 -3
- package/dist/lib/registry.d.ts.map +0 -1
- package/dist/lib/registry.js +0 -53
- package/dist/lib/registry.js.map +0 -1
- package/dist/lib/transitive-resolver.test.d.ts +0 -2
- package/dist/lib/transitive-resolver.test.d.ts.map +0 -1
- package/dist/lib/transitive-resolver.test.js +0 -279
- package/dist/lib/transitive-resolver.test.js.map +0 -1
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `snapshot clone-all` — clone the full SOURCE closure a snapshot declares.
|
|
3
|
+
*
|
|
4
|
+
* Thin adapter over the published, typed @gitdot/clone-lib@0.2.0: it loads
|
|
5
|
+
* snapshot.json (the install set + registry scope→group), resolves it to a
|
|
6
|
+
* resolved-lock (S2), writes snapshot.lock.json, and clones the closure. All
|
|
7
|
+
* real logic — name→projectId resolution, coordinate/visibility/commitSha
|
|
8
|
+
* pinning, closure cloning, structured reporting — lives in the library.
|
|
9
|
+
*
|
|
10
|
+
* The GitLab token is the caller's own (getGitlabToken → glpkg token /
|
|
11
|
+
* GITLAB_TOKEN), decoupled from the install proxy: cloning is plain git, judged
|
|
12
|
+
* by repo visibility + caller access.
|
|
13
|
+
*/
|
|
14
|
+
export interface CloneAllOptions {
|
|
15
|
+
manifest?: string;
|
|
16
|
+
mode?: string;
|
|
17
|
+
dest?: string;
|
|
18
|
+
json?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare function cloneAllCommand(options?: CloneAllOptions): Promise<void>;
|
|
21
|
+
//# sourceMappingURL=clone-all.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clone-all.d.ts","sourceRoot":"","sources":["../../src/commands/clone-all.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,eAAe,CAAC,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6DlF"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { readFileSync, mkdirSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { resolveFromManifest, cloneFromLock, writeLock, createGitLabApi } from '@gitdot/clone-lib';
|
|
5
|
+
import { getGitlabToken } from '../lib/config.js';
|
|
6
|
+
export async function cloneAllCommand(options = {}) {
|
|
7
|
+
const manifestPath = options.manifest || 'snapshot.json';
|
|
8
|
+
const mode = options.mode === 'pinned' ? 'pinned' : 'head';
|
|
9
|
+
const dest = options.dest || process.cwd();
|
|
10
|
+
const token = getGitlabToken();
|
|
11
|
+
if (!token) {
|
|
12
|
+
console.error(chalk.red('Error: no GitLab token. Set GITLAB_TOKEN or configure glpkg (glpkg config save <token>).'));
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
let manifest;
|
|
16
|
+
try {
|
|
17
|
+
manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
console.error(chalk.red(`Error: could not read manifest "${manifestPath}": ${err.message}`));
|
|
21
|
+
process.exit(1);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const registry = manifest.registry || {};
|
|
25
|
+
const subset = manifest.name;
|
|
26
|
+
const api = createGitLabApi({ token, host: 'gitlab.com' });
|
|
27
|
+
const { lock, warnings } = await resolveFromManifest(manifest, {
|
|
28
|
+
api,
|
|
29
|
+
scopes: Object.keys(registry),
|
|
30
|
+
subset,
|
|
31
|
+
});
|
|
32
|
+
mkdirSync(dest, { recursive: true });
|
|
33
|
+
const lockPath = join(dest, 'snapshot.lock.json');
|
|
34
|
+
writeLock(lockPath, lock);
|
|
35
|
+
const report = await cloneFromLock(lockPath, { mode, dest });
|
|
36
|
+
if (options.json) {
|
|
37
|
+
// --json is the primary contract: emit the structured report verbatim.
|
|
38
|
+
console.log(JSON.stringify({ report, lock: lockPath, warnings }, null, 2));
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
console.log(chalk.blue.bold('snapshot clone-all'));
|
|
42
|
+
console.log(chalk.gray(`manifest: ${manifestPath} | mode: ${mode} | dest: ${dest}`));
|
|
43
|
+
console.log(chalk.gray(`lock: ${lockPath}`));
|
|
44
|
+
console.log(`cloned ${chalk.green(report.cloned.length)} | ` +
|
|
45
|
+
`skipped ${report.skipped.length} | ` +
|
|
46
|
+
`no-access ${report['skipped-no-access'].length} | ` +
|
|
47
|
+
`not-found ${report['not-found'].length} | ` +
|
|
48
|
+
`failed ${chalk.red(report.failed.length)}`);
|
|
49
|
+
console.log(chalk.gray(`closureSize ${report.closureSize} | credentialsNeeded ${report.credentialsNeeded}`));
|
|
50
|
+
for (const w of warnings)
|
|
51
|
+
console.log(chalk.yellow(`⚠ ${w}`));
|
|
52
|
+
}
|
|
53
|
+
if (report.failed.length > 0 || report['not-found'].length > 0) {
|
|
54
|
+
process.exitCode = 1;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=clone-all.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clone-all.js","sourceRoot":"","sources":["../../src/commands/clone-all.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC7C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEnG,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAsBlD,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,UAA2B,EAAE;IACjE,MAAM,YAAY,GAAG,OAAO,CAAC,QAAQ,IAAI,eAAe,CAAC;IACzD,MAAM,IAAI,GAAc,OAAO,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC;IACtE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAE3C,MAAM,KAAK,GAAG,cAAc,EAAE,CAAC;IAC/B,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CACX,KAAK,CAAC,GAAG,CAAC,0FAA0F,CAAC,CACtG,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,CAAa,CAAC;IACxE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,mCAAmC,YAAY,MAAO,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACxG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAChB,OAAO;IACT,CAAC;IAED,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,IAAI,EAAE,CAAC;IACzC,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC;IAE7B,MAAM,GAAG,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC;IAC3D,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,MAAM,mBAAmB,CAAC,QAAQ,EAAE;QAC7D,GAAG;QACH,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC;QAC7B,MAAM;KACP,CAAC,CAAC;IAEH,SAAS,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,EAAE,oBAAoB,CAAC,CAAC;IAClD,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAE1B,MAAM,MAAM,GAAgB,MAAM,aAAa,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAE1E,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QACjB,uEAAuE;QACvE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7E,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,CAAC;QACnD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,YAAY,YAAY,IAAI,YAAY,IAAI,EAAE,CAAC,CAAC,CAAC;QACrF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,QAAQ,EAAE,CAAC,CAAC,CAAC;QAC7C,OAAO,CAAC,GAAG,CACT,UAAU,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK;YAC9C,WAAW,MAAM,CAAC,OAAO,CAAC,MAAM,KAAK;YACrC,aAAa,MAAM,CAAC,mBAAmB,CAAC,CAAC,MAAM,KAAK;YACpD,aAAa,MAAM,CAAC,WAAW,CAAC,CAAC,MAAM,KAAK;YAC5C,UAAU,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAC9C,CAAC;QACF,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,IAAI,CAAC,eAAe,MAAM,CAAC,WAAW,wBAAwB,MAAM,CAAC,iBAAiB,EAAE,CAAC,CAChG,CAAC;QACF,KAAK,MAAM,CAAC,IAAI,QAAQ;YAAE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;IAChE,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,WAAW,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/D,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC;AACH,CAAC"}
|
|
@@ -12,5 +12,13 @@ export declare function installSnapshot(snapshot: Snapshot, depth?: number, pare
|
|
|
12
12
|
result: InstallResult;
|
|
13
13
|
installers: Record<string, InstallerConfig>;
|
|
14
14
|
}>;
|
|
15
|
-
export
|
|
15
|
+
export interface InstallCommandOptions {
|
|
16
|
+
/** Converge onto the new version: remove previous-version-only packages (ADR 001). */
|
|
17
|
+
replace?: boolean;
|
|
18
|
+
/** With --replace, show what would change without modifying anything. */
|
|
19
|
+
dryRun?: boolean;
|
|
20
|
+
/** Skip the removal confirmation prompt. */
|
|
21
|
+
yes?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export declare function installCommand(name: string, options?: InstallCommandOptions): Promise<void>;
|
|
16
24
|
//# sourceMappingURL=install.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/commands/install.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/commands/install.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAiBjE,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAkeD,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,eAAe,CACnC,QAAQ,EAAE,QAAQ,EAClB,KAAK,GAAE,MAAU,EACjB,gBAAgB,GAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAM,EACtD,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;CAAE,CAAC,CA8VjF;AA4FD,MAAM,WAAW,qBAAqB;IACpC,sFAAsF;IACtF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,yEAAyE;IACzE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,4CAA4C;IAC5C,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAoLD,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,qBAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAuZrG"}
|
package/dist/commands/install.js
CHANGED
|
@@ -12,7 +12,8 @@ import { installSkitPackage } from '../lib/skit-adapter.js';
|
|
|
12
12
|
import { markLicenseChecked } from '@microwiseai/license';
|
|
13
13
|
import { authorizeSession, packagesToSpecs } from '../lib/session.js';
|
|
14
14
|
import { parallelInstall, recordToEntries } from '@microwiseai/snapshot-parallel';
|
|
15
|
-
import { dedupWithReport, dedupInstallBlock, recordInstalledSpecs } from '@microwiseai/snapshot-dedup';
|
|
15
|
+
import { dedupWithReport, dedupInstallBlock, recordInstalledSpecs, recordReferences, computeReplacePlan, applyReplaceHistory, } from '@microwiseai/snapshot-dedup';
|
|
16
|
+
import { createInterface } from 'readline';
|
|
16
17
|
// CLI version — dynamically read from package.json (SSOT)
|
|
17
18
|
import { createRequire } from 'module';
|
|
18
19
|
const _require = createRequire(import.meta.url);
|
|
@@ -60,22 +61,28 @@ const DEFAULT_INSTALLERS = {
|
|
|
60
61
|
command: 'glpkg install {pkg}',
|
|
61
62
|
check: 'glpkg --version',
|
|
62
63
|
hint: 'npm install -g @glpkg/cli',
|
|
64
|
+
// glpkg has no `uninstall` subcommand; its packages are global npm packages,
|
|
65
|
+
// so removal falls back to npm. (ADR 001 §d)
|
|
66
|
+
uninstall: 'npm uninstall -g {pkg}',
|
|
63
67
|
},
|
|
64
68
|
// Legacy alias for backward compatibility
|
|
65
69
|
'gitlab-install': {
|
|
66
70
|
command: 'glpkg install {pkg}',
|
|
67
71
|
check: 'glpkg --version',
|
|
68
72
|
hint: 'npm install -g @glpkg/cli (gitlab-install is deprecated, use glpkg)',
|
|
73
|
+
uninstall: 'npm uninstall -g {pkg}',
|
|
69
74
|
},
|
|
70
75
|
'skit': {
|
|
71
76
|
command: 'skit install {pkg}',
|
|
72
77
|
check: 'skit --version',
|
|
73
78
|
hint: 'npm install -g @microwiseai/skit',
|
|
79
|
+
uninstall: 'skit uninstall {pkg}', // verified: `skit uninstall|rm <package>`
|
|
74
80
|
},
|
|
75
81
|
'npm-proxy': {
|
|
76
82
|
command: 'npm install -g {pkg}',
|
|
77
83
|
check: 'npm --version',
|
|
78
84
|
hint: 'npm is required',
|
|
85
|
+
uninstall: 'npm uninstall -g {pkg}', // verified: `npm uninstall`
|
|
79
86
|
},
|
|
80
87
|
};
|
|
81
88
|
/**
|
|
@@ -209,20 +216,6 @@ function runCommand(command) {
|
|
|
209
216
|
return false;
|
|
210
217
|
}
|
|
211
218
|
}
|
|
212
|
-
/**
|
|
213
|
-
* PackageType을 installer 이름으로 변환
|
|
214
|
-
*/
|
|
215
|
-
function packageTypeToInstaller(type, useProxy) {
|
|
216
|
-
switch (type) {
|
|
217
|
-
case 'skit':
|
|
218
|
-
return 'skit';
|
|
219
|
-
case 'npm':
|
|
220
|
-
return useProxy ? 'npm-proxy' : 'glpkg';
|
|
221
|
-
case 'generic':
|
|
222
|
-
default:
|
|
223
|
-
return 'glpkg';
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
219
|
/**
|
|
227
220
|
* Check if a skit package is already installed with the specified version
|
|
228
221
|
*/
|
|
@@ -793,7 +786,169 @@ async function collectAllPackages(snapshot) {
|
|
|
793
786
|
}
|
|
794
787
|
return allPackages;
|
|
795
788
|
}
|
|
796
|
-
|
|
789
|
+
/**
|
|
790
|
+
* Whether a package was installed as a skit (→ removed via `skit uninstall`).
|
|
791
|
+
* Determined offline from ~/.ist/installed-kits.json; everything else is treated
|
|
792
|
+
* as a global npm package (`npm uninstall -g`).
|
|
793
|
+
*/
|
|
794
|
+
function isSkitPackage(name) {
|
|
795
|
+
try {
|
|
796
|
+
const p = join(homedir(), '.ist', 'installed-kits.json');
|
|
797
|
+
if (!existsSync(p))
|
|
798
|
+
return false;
|
|
799
|
+
const kits = JSON.parse(readFileSync(p, 'utf-8'));
|
|
800
|
+
return !!kits[name];
|
|
801
|
+
}
|
|
802
|
+
catch {
|
|
803
|
+
return false;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
/** Build the uninstall command for a package, or null if no uninstaller is known. */
|
|
807
|
+
function uninstallCommandFor(name, installers) {
|
|
808
|
+
const installerName = isSkitPackage(name) ? 'skit' : 'npm-proxy';
|
|
809
|
+
const cfg = installers[installerName] || DEFAULT_INSTALLERS[installerName];
|
|
810
|
+
if (!cfg?.uninstall)
|
|
811
|
+
return null;
|
|
812
|
+
return cfg.uninstall.replace('{pkg}', name); // {pkg} = name only (uninstall takes no version)
|
|
813
|
+
}
|
|
814
|
+
/** Yes/No confirmation. Non-interactive (no TTY) → false (safe default: don't remove). */
|
|
815
|
+
async function confirmPrompt(question) {
|
|
816
|
+
if (!process.stdin.isTTY)
|
|
817
|
+
return false;
|
|
818
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
819
|
+
try {
|
|
820
|
+
const answer = await new Promise((resolve) => {
|
|
821
|
+
rl.question(chalk.yellow(`${question} [y/N] `), resolve);
|
|
822
|
+
});
|
|
823
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
824
|
+
}
|
|
825
|
+
finally {
|
|
826
|
+
rl.close();
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
function printReplacePlan(plan) {
|
|
830
|
+
console.log('');
|
|
831
|
+
console.log(chalk.cyan('► Replace mode: converging to the new version set'));
|
|
832
|
+
if (plan.remove.length > 0) {
|
|
833
|
+
console.log(chalk.yellow(` Will remove ${plan.remove.length} package(s) no longer in this snapshot:`));
|
|
834
|
+
for (const item of plan.remove) {
|
|
835
|
+
console.log(chalk.red(` − ${item.name}@${item.version}`));
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
if (plan.keepShared.length > 0) {
|
|
839
|
+
console.log(chalk.gray(` Keeping ${plan.keepShared.length} shared package(s) (still used by other snapshots):`));
|
|
840
|
+
for (const item of plan.keepShared) {
|
|
841
|
+
console.log(chalk.gray(` • ${item.name}@${item.version} ← ${item.remainingRefs.join(', ')}`));
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
if (plan.stale.length > 0) {
|
|
845
|
+
console.log(chalk.gray(` Cleaning ${plan.stale.length} superseded history entr(y/ies) (already replaced by new version):`));
|
|
846
|
+
for (const item of plan.stale) {
|
|
847
|
+
console.log(chalk.gray(` ~ ${item.name}@${item.version}`));
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Replace mode (ADR 001): after a successful install of a new version, remove
|
|
853
|
+
* packages that belonged to the previous version but are no longer part of the
|
|
854
|
+
* new version — UNLESS another installed snapshot still references them.
|
|
855
|
+
*/
|
|
856
|
+
async function runReplaceMode(snapshotName, newSet, options, installers) {
|
|
857
|
+
const plan = computeReplacePlan(snapshotName, newSet);
|
|
858
|
+
const hasWork = plan.remove.length > 0 || plan.keepShared.length > 0 || plan.stale.length > 0;
|
|
859
|
+
if (!hasWork) {
|
|
860
|
+
console.log('');
|
|
861
|
+
console.log(chalk.gray('► Replace mode: nothing to converge — system already matches this version.'));
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
printReplacePlan(plan);
|
|
865
|
+
if (options.dryRun) {
|
|
866
|
+
console.log('');
|
|
867
|
+
console.log(chalk.gray('Dry-run: no packages removed and history left unchanged.'));
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
// Confirm before any destructive uninstall.
|
|
871
|
+
if (plan.remove.length > 0 && !options.yes) {
|
|
872
|
+
const ok = await confirmPrompt(`Remove ${plan.remove.length} package(s)?`);
|
|
873
|
+
if (!ok) {
|
|
874
|
+
console.log(chalk.yellow('Aborted — no packages removed.'));
|
|
875
|
+
if (!process.stdin.isTTY) {
|
|
876
|
+
console.log(chalk.gray(' (non-interactive shell — pass --yes to remove)'));
|
|
877
|
+
}
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
// Uninstall each removal candidate; only history-delete the ones that succeed.
|
|
882
|
+
const removed = [];
|
|
883
|
+
const failed = [];
|
|
884
|
+
for (const item of plan.remove) {
|
|
885
|
+
const cmd = uninstallCommandFor(item.name, installers);
|
|
886
|
+
if (!cmd) {
|
|
887
|
+
console.log(chalk.yellow(` ⚠ No uninstaller for ${item.name} — skipping (manual removal required).`));
|
|
888
|
+
continue;
|
|
889
|
+
}
|
|
890
|
+
console.log(chalk.gray(` Removing ${item.name}...`));
|
|
891
|
+
if (runCommand(cmd)) {
|
|
892
|
+
console.log(` ${item.name}${chalk.green(' ✓ removed')}`);
|
|
893
|
+
removed.push(item);
|
|
894
|
+
}
|
|
895
|
+
else {
|
|
896
|
+
console.log(` ${item.name}${chalk.red(' ✗ removal failed')}`);
|
|
897
|
+
if (lastCommandError)
|
|
898
|
+
console.log(chalk.gray(` ${lastCommandError}`));
|
|
899
|
+
failed.push(item);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
// Update history: delete removed entries; drop this snapshot from kept/stale refs.
|
|
903
|
+
applyReplaceHistory(snapshotName, removed, plan.keepShared, plan.stale);
|
|
904
|
+
console.log('');
|
|
905
|
+
console.log(chalk.green(`✓ Replace complete: removed ${removed.length}, kept ${plan.keepShared.length} shared` +
|
|
906
|
+
(failed.length > 0 ? chalk.red(`, ${failed.length} failed`) : '')));
|
|
907
|
+
if (failed.length > 0) {
|
|
908
|
+
console.log(chalk.yellow(' Failed removals were left installed and remain in history for retry:'));
|
|
909
|
+
for (const item of failed)
|
|
910
|
+
console.log(chalk.red(` − ${item.name}@${item.version}`));
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* Dry-run preview: report what an install WOULD do, with no install side
|
|
915
|
+
* effects (no install/uninstall, no ~/.npmrc, no history). Import telemetry is
|
|
916
|
+
* a separate, global concern gated in index.ts (skipped for --dry-run there).
|
|
917
|
+
* Reads only (snapshot already fetched; extends bases resolved read-only inside
|
|
918
|
+
* collectAllPackages, which swallows fetch errors and returns a partial set).
|
|
919
|
+
* Never installs, uninstalls, or writes ~/.npmrc / history.
|
|
920
|
+
*/
|
|
921
|
+
async function previewInstall(snapshot, options) {
|
|
922
|
+
console.log('');
|
|
923
|
+
console.log(chalk.cyan('► Dry-run: preview only — nothing will be installed or changed.'));
|
|
924
|
+
// Resolve the full package set across the entire extends chain (reads only).
|
|
925
|
+
const previewPackages = await collectAllPackages(snapshot);
|
|
926
|
+
const entries = Object.entries(previewPackages).sort(([a], [b]) => a.localeCompare(b));
|
|
927
|
+
console.log('');
|
|
928
|
+
if (entries.length === 0) {
|
|
929
|
+
console.log(chalk.gray(' (No packages declared in this snapshot or its extends chain.)'));
|
|
930
|
+
}
|
|
931
|
+
else {
|
|
932
|
+
console.log(chalk.yellow(`Would install ${entries.length} package(s) (extends chain resolved):`));
|
|
933
|
+
for (const [n, v] of entries) {
|
|
934
|
+
console.log(chalk.gray(` + ${n}@${v}`));
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
// With --replace, preview the convergence plan too (history read only, no write).
|
|
938
|
+
if (options.replace) {
|
|
939
|
+
const plan = computeReplacePlan(snapshot.name, previewPackages);
|
|
940
|
+
if (plan.remove.length > 0 || plan.keepShared.length > 0 || plan.stale.length > 0) {
|
|
941
|
+
printReplacePlan(plan);
|
|
942
|
+
}
|
|
943
|
+
else {
|
|
944
|
+
console.log('');
|
|
945
|
+
console.log(chalk.gray('► Replace preview: nothing to converge — system already matches this version.'));
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
console.log('');
|
|
949
|
+
console.log(chalk.gray('Dry-run complete: no installs, no uninstalls, no .npmrc or history changes.'));
|
|
950
|
+
}
|
|
951
|
+
export async function installCommand(name, options = {}) {
|
|
797
952
|
console.log(chalk.blue.bold(`
|
|
798
953
|
╔══════════════════════════════════════════════════════════════╗
|
|
799
954
|
║ Snapshot Installer ║
|
|
@@ -851,45 +1006,56 @@ export async function installCommand(name) {
|
|
|
851
1006
|
}
|
|
852
1007
|
catch (error) {
|
|
853
1008
|
const errMsg = error instanceof Error ? error.message : String(error);
|
|
1009
|
+
if (options.dryRun) {
|
|
1010
|
+
// Pure preview: a fetch failure (offline / no auth) is not fatal — we just
|
|
1011
|
+
// cannot resolve what would be installed. Report a limited preview and
|
|
1012
|
+
// exit 0 without touching anything.
|
|
1013
|
+
console.log(chalk.yellow('⚠ Dry-run preview limited: could not fetch snapshot metadata.'));
|
|
1014
|
+
console.log(chalk.gray(` Reason: ${errMsg}`));
|
|
1015
|
+
console.log(chalk.gray(' (No packages installed, no files changed.)'));
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
854
1018
|
console.log(chalk.red(`Error: ${errMsg}`));
|
|
855
|
-
console.log(chalk.yellow('Check
|
|
1019
|
+
console.log(chalk.yellow('Check the snapshot name and version, e.g. snapshot install @scope/name@version'));
|
|
856
1020
|
process.exit(1);
|
|
857
1021
|
}
|
|
858
1022
|
console.log(chalk.green('✓') + ` Snapshot: ${snapshot.name} v${snapshot.version}`);
|
|
859
1023
|
if (snapshot.description) {
|
|
860
1024
|
console.log(chalk.gray(` ${snapshot.description}`));
|
|
861
1025
|
}
|
|
1026
|
+
// ========== Dry-run: preview with no install side effects ==========
|
|
1027
|
+
// A dry-run must not install/uninstall anything and must not touch ~/.npmrc,
|
|
1028
|
+
// the global node_modules, or the install history. We only READ snapshot
|
|
1029
|
+
// metadata (already fetched; extends bases resolved read-only) to report what
|
|
1030
|
+
// WOULD be installed — and, with --replace, what would be removed/kept/cleaned.
|
|
1031
|
+
// We exit here BEFORE the access gate, session authorization, .npmrc
|
|
1032
|
+
// configuration, the npm pre-install batch, and installSnapshot — i.e. before
|
|
1033
|
+
// any write or network-write. Reached before recordInstalledSpecs /
|
|
1034
|
+
// recordReferences, so no history mutation occurs either.
|
|
1035
|
+
if (options.dryRun) {
|
|
1036
|
+
await previewInstall(snapshot, options);
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
862
1039
|
// ========== Access Check (based on snapshot.json access field) ==========
|
|
863
|
-
//
|
|
864
|
-
// - access: "public"
|
|
865
|
-
// - access: "authenticated" → IST login required, no license needed
|
|
1040
|
+
// snapshot is a general installer. Access is simplified to two tiers:
|
|
1041
|
+
// - access: "public" → no license required
|
|
866
1042
|
// - access: { type: "licensed", productId } → license required
|
|
867
|
-
// -
|
|
1043
|
+
// - anything else / absent → access decided SERVER-side
|
|
1044
|
+
// (registry-proxy / license API are authoritative; a 401/403 fails loud).
|
|
1045
|
+
// No client-side login gate: access control is backend-authoritative.
|
|
868
1046
|
const accessField = snapshot.access;
|
|
869
1047
|
const isPublicAccess = accessField === 'public';
|
|
870
|
-
const isAuthenticatedAccess = accessField === 'authenticated';
|
|
871
1048
|
const isLicensedAccess = !!accessField && typeof accessField === 'object' && accessField.type === 'licensed';
|
|
872
|
-
//
|
|
1049
|
+
// Optional identity for telemetry only (anonymous when absent). NOT a gate.
|
|
1050
|
+
// A wrapper (e.g. ist-setup) may layer a login system on top of snapshot via
|
|
1051
|
+
// the authToken injection point (IST_SESSION_TOKEN env / getProxyAuthToken).
|
|
873
1052
|
const istCredentials = getIstCredentials();
|
|
874
|
-
if (istCredentials?.userId) {
|
|
875
|
-
console.log(chalk.green('✓') + ' IST login verified');
|
|
876
|
-
}
|
|
877
|
-
else if (isPublicAccess) {
|
|
878
|
-
console.log(chalk.yellow('⚠') + ' No IST login - installing public package');
|
|
879
|
-
}
|
|
880
|
-
else {
|
|
881
|
-
console.log(chalk.red('⛔ IST login required for non-public packages'));
|
|
882
|
-
console.log(chalk.gray(' Run: ist auth login'));
|
|
883
|
-
console.log(chalk.gray(' Or install a public snapshot first (no login required)'));
|
|
884
|
-
process.exit(1);
|
|
885
|
-
}
|
|
886
1053
|
// --- License Check ---
|
|
887
1054
|
const storedLicenseKey = getLicenseKey();
|
|
888
1055
|
let licenseValid = false;
|
|
889
|
-
if (isPublicAccess
|
|
890
|
-
// Public
|
|
891
|
-
|
|
892
|
-
console.log(chalk.green('✓') + ` ${label} snapshot - no license required`);
|
|
1056
|
+
if (isPublicAccess) {
|
|
1057
|
+
// Public snapshots — skip license check entirely
|
|
1058
|
+
console.log(chalk.green('✓') + ' Public snapshot - no license required');
|
|
893
1059
|
}
|
|
894
1060
|
else if (storedLicenseKey) {
|
|
895
1061
|
try {
|
|
@@ -1132,17 +1298,21 @@ export async function installCommand(name) {
|
|
|
1132
1298
|
console.log('');
|
|
1133
1299
|
console.log(chalk.green('✓') + ' Snapshot info saved');
|
|
1134
1300
|
}
|
|
1135
|
-
//
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1301
|
+
// Record references for the ENTIRE declared set (installed + dedup-skipped),
|
|
1302
|
+
// so replace mode has accurate referencedBy data (ADR 001 §c). This runs on
|
|
1303
|
+
// every install, not just --replace, to build up the reference graph.
|
|
1304
|
+
recordReferences(allPackages, snapshot.name);
|
|
1305
|
+
// ========== Replace mode (ADR 001) ==========
|
|
1306
|
+
// Only converge after a fully successful install — never remove old packages
|
|
1307
|
+
// when the new version's install partially failed.
|
|
1308
|
+
if (options.replace) {
|
|
1309
|
+
if (result.failed.length > 0) {
|
|
1310
|
+
console.log('');
|
|
1311
|
+
console.log(chalk.yellow('⚠ Skipping --replace: install had failures; not removing previous packages.'));
|
|
1312
|
+
}
|
|
1313
|
+
else {
|
|
1314
|
+
await runReplaceMode(snapshot.name, allPackages, options, effectiveInstallers);
|
|
1144
1315
|
}
|
|
1145
|
-
console.log(chalk.gray(`\nRun 'snapshot setup' to install, or 'snapshot doctor' for details.`));
|
|
1146
1316
|
}
|
|
1147
1317
|
// Send telemetry (fire and forget)
|
|
1148
1318
|
sendInstallTelemetry({
|