@microwiseai/snapshot 0.5.0 → 0.5.2
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/install.d.ts.map +1 -1
- package/dist/commands/install.js +195 -34
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/preflight.d.ts +31 -0
- package/dist/commands/preflight.d.ts.map +1 -0
- package/dist/commands/preflight.js +83 -0
- package/dist/commands/preflight.js.map +1 -0
- package/dist/commands/publish.d.ts +4 -0
- package/dist/commands/publish.d.ts.map +1 -1
- package/dist/commands/publish.js +35 -1
- package/dist/commands/publish.js.map +1 -1
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/distkit-closure-bridge.d.ts +102 -0
- package/dist/lib/distkit-closure-bridge.d.ts.map +1 -0
- package/dist/lib/distkit-closure-bridge.js +177 -0
- package/dist/lib/distkit-closure-bridge.js.map +1 -0
- package/dist/lib/distkit-closure-parity.d.ts +110 -0
- package/dist/lib/distkit-closure-parity.d.ts.map +1 -0
- package/dist/lib/distkit-closure-parity.js +134 -0
- package/dist/lib/distkit-closure-parity.js.map +1 -0
- package/dist/lib/distkit-routing-port.d.ts +37 -0
- package/dist/lib/distkit-routing-port.d.ts.map +1 -0
- package/dist/lib/distkit-routing-port.js +72 -0
- package/dist/lib/distkit-routing-port.js.map +1 -0
- package/dist/lib/install-timeline.d.ts +81 -0
- package/dist/lib/install-timeline.d.ts.map +1 -0
- package/dist/lib/install-timeline.js +243 -0
- package/dist/lib/install-timeline.js.map +1 -0
- package/dist/lib/preflight-adapter.d.ts +52 -0
- package/dist/lib/preflight-adapter.d.ts.map +1 -0
- package/dist/lib/preflight-adapter.js +224 -0
- package/dist/lib/preflight-adapter.js.map +1 -0
- package/dist/lib/transitive-resolver.d.ts +121 -0
- package/dist/lib/transitive-resolver.d.ts.map +1 -1
- package/dist/lib/transitive-resolver.js +190 -0
- package/dist/lib/transitive-resolver.js.map +1 -1
- package/node_modules/@gitdot/clone-lib/README.md +117 -0
- package/node_modules/@gitdot/clone-lib/dist/bin/gitdot-resolve.d.ts +9 -0
- package/node_modules/@gitdot/clone-lib/dist/bin/gitdot-resolve.d.ts.map +1 -0
- package/node_modules/@gitdot/clone-lib/dist/bin/gitdot-resolve.js +154 -0
- package/node_modules/@gitdot/clone-lib/dist/bin/gitdot-resolve.js.map +1 -0
- package/node_modules/@gitdot/clone-lib/dist/clone.d.ts +21 -0
- package/node_modules/@gitdot/clone-lib/dist/clone.d.ts.map +1 -0
- package/node_modules/@gitdot/clone-lib/dist/clone.js +228 -0
- package/node_modules/@gitdot/clone-lib/dist/clone.js.map +1 -0
- package/node_modules/@gitdot/clone-lib/dist/dotpath.d.ts +10 -0
- package/node_modules/@gitdot/clone-lib/dist/dotpath.d.ts.map +1 -0
- package/node_modules/@gitdot/clone-lib/dist/dotpath.js +27 -0
- package/node_modules/@gitdot/clone-lib/dist/dotpath.js.map +1 -0
- package/node_modules/@gitdot/clone-lib/dist/git.d.ts +23 -0
- package/node_modules/@gitdot/clone-lib/dist/git.d.ts.map +1 -0
- package/node_modules/@gitdot/clone-lib/dist/git.js +134 -0
- package/node_modules/@gitdot/clone-lib/dist/git.js.map +1 -0
- package/node_modules/@gitdot/clone-lib/dist/index.d.ts +17 -0
- package/node_modules/@gitdot/clone-lib/dist/index.d.ts.map +1 -0
- package/node_modules/@gitdot/clone-lib/dist/index.js +35 -0
- package/node_modules/@gitdot/clone-lib/dist/index.js.map +1 -0
- package/node_modules/@gitdot/clone-lib/dist/lock.d.ts +24 -0
- package/node_modules/@gitdot/clone-lib/dist/lock.d.ts.map +1 -0
- package/node_modules/@gitdot/clone-lib/dist/lock.js +97 -0
- package/node_modules/@gitdot/clone-lib/dist/lock.js.map +1 -0
- package/node_modules/@gitdot/clone-lib/dist/resolve/gitlab.d.ts +15 -0
- package/node_modules/@gitdot/clone-lib/dist/resolve/gitlab.d.ts.map +1 -0
- package/node_modules/@gitdot/clone-lib/dist/resolve/gitlab.js +124 -0
- package/node_modules/@gitdot/clone-lib/dist/resolve/gitlab.js.map +1 -0
- package/node_modules/@gitdot/clone-lib/dist/resolve/index.d.ts +12 -0
- package/node_modules/@gitdot/clone-lib/dist/resolve/index.d.ts.map +1 -0
- package/node_modules/@gitdot/clone-lib/dist/resolve/index.js +31 -0
- package/node_modules/@gitdot/clone-lib/dist/resolve/index.js.map +1 -0
- package/node_modules/@gitdot/clone-lib/dist/resolve/manifest.d.ts +32 -0
- package/node_modules/@gitdot/clone-lib/dist/resolve/manifest.d.ts.map +1 -0
- package/node_modules/@gitdot/clone-lib/dist/resolve/manifest.js +159 -0
- package/node_modules/@gitdot/clone-lib/dist/resolve/manifest.js.map +1 -0
- package/node_modules/@gitdot/clone-lib/dist/resolve/member.d.ts +44 -0
- package/node_modules/@gitdot/clone-lib/dist/resolve/member.d.ts.map +1 -0
- package/node_modules/@gitdot/clone-lib/dist/resolve/member.js +163 -0
- package/node_modules/@gitdot/clone-lib/dist/resolve/member.js.map +1 -0
- package/node_modules/@gitdot/clone-lib/dist/resolve/resolve.d.ts +18 -0
- package/node_modules/@gitdot/clone-lib/dist/resolve/resolve.d.ts.map +1 -0
- package/node_modules/@gitdot/clone-lib/dist/resolve/resolve.js +155 -0
- package/node_modules/@gitdot/clone-lib/dist/resolve/resolve.js.map +1 -0
- package/node_modules/@gitdot/clone-lib/dist/types.d.ts +225 -0
- package/node_modules/@gitdot/clone-lib/dist/types.d.ts.map +1 -0
- package/node_modules/@gitdot/clone-lib/dist/types.js +9 -0
- package/node_modules/@gitdot/clone-lib/dist/types.js.map +1 -0
- package/node_modules/@gitdot/clone-lib/package.json +53 -0
- package/node_modules/@microwiseai/install-preflight/dist/index.d.ts +27 -0
- package/node_modules/@microwiseai/install-preflight/dist/index.d.ts.map +1 -0
- package/node_modules/@microwiseai/install-preflight/dist/index.js +26 -0
- package/node_modules/@microwiseai/install-preflight/dist/index.js.map +1 -0
- package/node_modules/@microwiseai/install-preflight/dist/policy.d.ts +42 -0
- package/node_modules/@microwiseai/install-preflight/dist/policy.d.ts.map +1 -0
- package/node_modules/@microwiseai/install-preflight/dist/policy.js +79 -0
- package/node_modules/@microwiseai/install-preflight/dist/policy.js.map +1 -0
- package/node_modules/@microwiseai/install-preflight/dist/preflight.d.ts +18 -0
- package/node_modules/@microwiseai/install-preflight/dist/preflight.d.ts.map +1 -0
- package/node_modules/@microwiseai/install-preflight/dist/preflight.js +0 -0
- package/node_modules/@microwiseai/install-preflight/dist/preflight.js.map +1 -0
- package/node_modules/@microwiseai/install-preflight/dist/resolvers.d.ts +26 -0
- package/node_modules/@microwiseai/install-preflight/dist/resolvers.d.ts.map +1 -0
- package/node_modules/@microwiseai/install-preflight/dist/resolvers.js +63 -0
- package/node_modules/@microwiseai/install-preflight/dist/resolvers.js.map +1 -0
- package/node_modules/@microwiseai/install-preflight/dist/semver.d.ts +45 -0
- package/node_modules/@microwiseai/install-preflight/dist/semver.d.ts.map +1 -0
- package/node_modules/@microwiseai/install-preflight/dist/semver.js +195 -0
- package/node_modules/@microwiseai/install-preflight/dist/semver.js.map +1 -0
- package/node_modules/@microwiseai/install-preflight/dist/types.d.ts +136 -0
- package/node_modules/@microwiseai/install-preflight/dist/types.d.ts.map +1 -0
- package/node_modules/@microwiseai/install-preflight/dist/types.js +11 -0
- package/node_modules/@microwiseai/install-preflight/dist/types.js.map +1 -0
- package/node_modules/@microwiseai/install-preflight/package.json +49 -0
- package/package.json +12 -4
|
@@ -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;AAwBjE,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAsmBD,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,CA0brG"}
|
package/dist/commands/install.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { execSync } from 'child_process';
|
|
3
|
-
import { writeFileSync, existsSync, readFileSync, unlinkSync } from 'fs';
|
|
2
|
+
import { execSync, spawn } from 'child_process';
|
|
3
|
+
import { writeFileSync, existsSync, readFileSync, unlinkSync, mkdirSync } from 'fs';
|
|
4
4
|
import { homedir, platform, arch } from 'os';
|
|
5
5
|
import { join } from 'path';
|
|
6
6
|
import { fetchSnapshotFromRepo, snapshotNameToRepoName } from '../lib/gitlab.js';
|
|
@@ -14,6 +14,12 @@ import { authorizeSession, packagesToSpecs } from '../lib/session.js';
|
|
|
14
14
|
import { parallelInstall, recordToEntries } from '@microwiseai/snapshot-parallel';
|
|
15
15
|
import { dedupWithReport, dedupInstallBlock, recordInstalledSpecs, recordReferences, computeReplacePlan, applyReplaceHistory, } from '@microwiseai/snapshot-dedup';
|
|
16
16
|
import { createInterface } from 'readline';
|
|
17
|
+
import { createInstallTimeline } from '../lib/install-timeline.js';
|
|
18
|
+
// Install-timeline instrumentation (investigation only, env opt-in). A disabled
|
|
19
|
+
// no-op is the default so module-level callers (runCommand, installSkitPackages)
|
|
20
|
+
// always have a valid handle; installCommand() reassigns a live instance when
|
|
21
|
+
// SNAPSHOT_INSTALL_TIMELINE is set. Adds zero behavior — only measurement.
|
|
22
|
+
let activeTimeline = createInstallTimeline({ enabled: false });
|
|
17
23
|
// CLI version — dynamically read from package.json (SSOT)
|
|
18
24
|
import { createRequire } from 'module';
|
|
19
25
|
const _require = createRequire(import.meta.url);
|
|
@@ -194,27 +200,74 @@ function commandExists(cmd) {
|
|
|
194
200
|
// Captures the failure reason of the most recent runCommand() call so callers
|
|
195
201
|
// can surface it (debugging visibility). Undefined when the last call succeeded.
|
|
196
202
|
let lastCommandError;
|
|
203
|
+
// Bound the captured-stderr buffer so a chatty command can't grow it without
|
|
204
|
+
// limit; on overflow we keep the tail (where the actual failure usually is).
|
|
205
|
+
const MAX_STDERR_CAPTURE = 64 * 1024;
|
|
206
|
+
// Runs a shell command, streaming its output live while still capturing stderr
|
|
207
|
+
// for failure diagnostics.
|
|
208
|
+
//
|
|
209
|
+
// Why spawn (async) instead of execSync: long installs (e.g. `npm install -g`
|
|
210
|
+
// of the whole pre-install batch) push their live progress to STDERR. execSync
|
|
211
|
+
// with a piped stderr hid that progress entirely — the terminal sat silent for
|
|
212
|
+
// minutes, then dumped a summary. Here stderr is piped so we can BOTH tee it to
|
|
213
|
+
// our own stderr in real time (visible progress) AND accumulate it, so the raw
|
|
214
|
+
// reason is still available via lastCommandError when the command fails.
|
|
215
|
+
//
|
|
216
|
+
// stdout stays inherited (live, unbuffered). shell:true preserves the previous
|
|
217
|
+
// execSync parsing of the full command string.
|
|
197
218
|
function runCommand(command) {
|
|
198
219
|
lastCommandError = undefined;
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
220
|
+
// Instrumentation only: the timeline sanitizes the command label internally,
|
|
221
|
+
// so tokens are never recorded. No effect when the timeline is disabled.
|
|
222
|
+
const span = activeTimeline.start(command, { category: 'command' });
|
|
223
|
+
return new Promise((resolve) => {
|
|
224
|
+
const child = spawn(command, {
|
|
225
|
+
shell: true,
|
|
226
|
+
stdio: ['inherit', 'inherit', 'pipe'],
|
|
227
|
+
});
|
|
228
|
+
let stderrBuf = '';
|
|
229
|
+
child.stderr?.on('data', (chunk) => {
|
|
230
|
+
// Live streaming: echo the command's progress as it happens.
|
|
231
|
+
process.stderr.write(chunk);
|
|
232
|
+
// Capture (bounded) for failure diagnostics.
|
|
233
|
+
stderrBuf += chunk.toString();
|
|
234
|
+
if (stderrBuf.length > MAX_STDERR_CAPTURE) {
|
|
235
|
+
stderrBuf = stderrBuf.slice(-MAX_STDERR_CAPTURE);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
// 'error' (spawn failed) and 'close' (process exited) are mutually exclusive
|
|
239
|
+
// in practice, but guard against a double-resolve just in case.
|
|
240
|
+
let settled = false;
|
|
241
|
+
const finish = (ok) => {
|
|
242
|
+
if (settled)
|
|
243
|
+
return;
|
|
244
|
+
settled = true;
|
|
245
|
+
resolve(ok);
|
|
246
|
+
};
|
|
247
|
+
child.on('error', (err) => {
|
|
248
|
+
// Spawn-level failure (e.g. shell missing) — no exit code available.
|
|
249
|
+
lastCommandError = `spawn error — ${err.message}`;
|
|
250
|
+
span.end({ success: false, exitReason: 'spawn error' });
|
|
251
|
+
console.log(chalk.red(` Command failed to start: ${command}`));
|
|
252
|
+
console.log(chalk.red(err.message));
|
|
253
|
+
finish(false);
|
|
254
|
+
});
|
|
255
|
+
child.on('close', (code) => {
|
|
256
|
+
if (code === 0) {
|
|
257
|
+
span.end({ success: true });
|
|
258
|
+
finish(true);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const detail = stderrBuf.trim() || 'unknown error';
|
|
262
|
+
lastCommandError = `exit code ${code ?? 'unknown'} — ${detail}`;
|
|
263
|
+
span.end({ success: false, exitReason: `exit code ${code ?? 'unknown'}` });
|
|
264
|
+
// stderr was already streamed live above, so we don't re-print the full
|
|
265
|
+
// detail here (that would duplicate it). Callers surface lastCommandError
|
|
266
|
+
// where they want a compact "Reason:" line.
|
|
267
|
+
console.log(chalk.red(` Command failed (exit code ${code ?? 'unknown'}): ${command}`));
|
|
268
|
+
finish(false);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
218
271
|
}
|
|
219
272
|
/**
|
|
220
273
|
* Check if a skit package is already installed with the specified version
|
|
@@ -247,6 +300,71 @@ function isNpmGlobalInstalled(name, version) {
|
|
|
247
300
|
return false;
|
|
248
301
|
}
|
|
249
302
|
}
|
|
303
|
+
// Preflight is a diagnostic: it runs once per process and warns loudly, but
|
|
304
|
+
// does NOT abort — skit still tries, so any real error path stays intact.
|
|
305
|
+
let istWritabilityChecked = false;
|
|
306
|
+
/**
|
|
307
|
+
* Verify the current user can actually write into ~/.ist before skit installs.
|
|
308
|
+
*
|
|
309
|
+
* Motivation: when ~/.ist is mounted as a root:root Docker named volume and the
|
|
310
|
+
* process runs as a non-root user, skit's writes fail with EACCES. skit swallows
|
|
311
|
+
* that into an opaque "Error: undefined" and the install silently under-counts
|
|
312
|
+
* (e.g. 7/12). This surfaces the real cause up front with a concrete fix.
|
|
313
|
+
*
|
|
314
|
+
* Detection is a real write probe (create+delete a temp file), which is more
|
|
315
|
+
* reliable than fs.accessSync(W_OK) — the latter can misreport under root/ACLs.
|
|
316
|
+
* Missing directories are created first (that itself exercises writability of
|
|
317
|
+
* the parent). Never throws; a probe failure just flags the directory.
|
|
318
|
+
*/
|
|
319
|
+
function checkIstWritable() {
|
|
320
|
+
if (istWritabilityChecked)
|
|
321
|
+
return;
|
|
322
|
+
istWritabilityChecked = true;
|
|
323
|
+
const istRoot = join(homedir(), '.ist');
|
|
324
|
+
// Directories skit writes into: the root itself (installed-kits.json lives
|
|
325
|
+
// here), global prompts, and profiles.
|
|
326
|
+
const targets = [
|
|
327
|
+
istRoot,
|
|
328
|
+
join(istRoot, 'prompts', 'global'),
|
|
329
|
+
join(istRoot, 'profiles'),
|
|
330
|
+
];
|
|
331
|
+
const notWritable = [];
|
|
332
|
+
for (const dir of targets) {
|
|
333
|
+
try {
|
|
334
|
+
if (!existsSync(dir)) {
|
|
335
|
+
// Creating the dir also proves the parent is writable. If this throws
|
|
336
|
+
// (EACCES on a root-owned volume), the dir is not usable → flag it.
|
|
337
|
+
mkdirSync(dir, { recursive: true });
|
|
338
|
+
}
|
|
339
|
+
const probe = join(dir, `.snapshot-write-probe-${process.pid}`);
|
|
340
|
+
writeFileSync(probe, '');
|
|
341
|
+
unlinkSync(probe);
|
|
342
|
+
}
|
|
343
|
+
catch {
|
|
344
|
+
notWritable.push(dir);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
// installed-kits.json may already exist with restrictive ownership even when
|
|
348
|
+
// its directory is writable — probe the file directly if present.
|
|
349
|
+
const kitsFile = join(istRoot, 'installed-kits.json');
|
|
350
|
+
if (existsSync(kitsFile)) {
|
|
351
|
+
try {
|
|
352
|
+
writeFileSync(kitsFile, readFileSync(kitsFile));
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
notWritable.push(kitsFile);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (notWritable.length > 0) {
|
|
359
|
+
console.log('');
|
|
360
|
+
console.log(chalk.yellow('⚠ ~/.ist is not writable by current user (owned by root?) — skit installs will fail.'));
|
|
361
|
+
for (const p of notWritable) {
|
|
362
|
+
console.log(chalk.gray(` not writable: ${p}`));
|
|
363
|
+
}
|
|
364
|
+
console.log(chalk.gray(' Fix directory ownership: chown -R $(whoami) ~/.ist'));
|
|
365
|
+
console.log('');
|
|
366
|
+
}
|
|
367
|
+
}
|
|
250
368
|
/**
|
|
251
369
|
* Skit 패키지 설치 (lib 우선, CLI fallback)
|
|
252
370
|
* @ist/skit lib가 있으면 직접 호출, 없으면 CLI 사용
|
|
@@ -257,6 +375,10 @@ async function installSkitPackages(packages, snapshotName, indent, sessionId) {
|
|
|
257
375
|
if (packageEntries.length === 0) {
|
|
258
376
|
return result;
|
|
259
377
|
}
|
|
378
|
+
// Diagnostic preflight (once/process): if ~/.ist isn't writable by the current
|
|
379
|
+
// user, skit installs fail opaquely ("Error: undefined", under-counted total).
|
|
380
|
+
// Warn with the concrete fix before we start the skit loop.
|
|
381
|
+
checkIstWritable();
|
|
260
382
|
const total = packageEntries.length;
|
|
261
383
|
console.log('');
|
|
262
384
|
console.log(chalk.yellow(`${indent}=== ${snapshotName}: ${total} skits ===`));
|
|
@@ -269,7 +391,14 @@ async function installSkitPackages(packages, snapshotName, indent, sessionId) {
|
|
|
269
391
|
if (isSkitInstalled(entry.name, entry.version)) {
|
|
270
392
|
return { success: true, skipped: true };
|
|
271
393
|
}
|
|
394
|
+
// Per-package timing (investigation only) — surfaces individual skit
|
|
395
|
+
// hotspots (e.g. infra-kit/mwd-kit) under the install-snapshot phase.
|
|
396
|
+
const pkgSpan = activeTimeline.start(`${entry.name}@${entry.version}`, {
|
|
397
|
+
category: 'package',
|
|
398
|
+
meta: { installer: 'skit' },
|
|
399
|
+
});
|
|
272
400
|
const installResult = await installSkitPackage(entry.name, entry.version, skitOptions);
|
|
401
|
+
pkgSpan.end({ success: installResult.success });
|
|
273
402
|
return {
|
|
274
403
|
success: installResult.success,
|
|
275
404
|
error: installResult.error,
|
|
@@ -321,7 +450,7 @@ async function installPackagesByType(packages, installerName, installers, snapsh
|
|
|
321
450
|
if (!commandExists(cmdName)) {
|
|
322
451
|
console.log(chalk.yellow(`${indent}${installerName} not found. Attempting auto-install...`));
|
|
323
452
|
console.log(chalk.gray(`${indent} Running: ${installerConfig.hint}`));
|
|
324
|
-
if (runCommand(installerConfig.hint)) {
|
|
453
|
+
if (await runCommand(installerConfig.hint)) {
|
|
325
454
|
console.log(chalk.green(`${indent} ✓ ${installerName} installed successfully`));
|
|
326
455
|
}
|
|
327
456
|
else {
|
|
@@ -354,7 +483,7 @@ async function installPackagesByType(packages, installerName, installers, snapsh
|
|
|
354
483
|
const specs = toInstall.map(e => `${e.name}@${e.version}`);
|
|
355
484
|
const batchCommand = installerConfig.command.replace('{pkg}', specs.join(' '));
|
|
356
485
|
console.log(chalk.gray(`${indent} Running batch: npm install -g (${specs.length} packages)`));
|
|
357
|
-
const success = runCommand(batchCommand);
|
|
486
|
+
const success = await runCommand(batchCommand);
|
|
358
487
|
if (success) {
|
|
359
488
|
for (const spec of specs) {
|
|
360
489
|
console.log(`${indent}${chalk.blue('batch')} ${spec}...${chalk.green(' ✓')}`);
|
|
@@ -369,7 +498,7 @@ async function installPackagesByType(packages, installerName, installers, snapsh
|
|
|
369
498
|
for (const entry of toInstall) {
|
|
370
499
|
const pkgSpec = `${entry.name}@${entry.version}`;
|
|
371
500
|
const command = installerConfig.command.replace('{pkg}', pkgSpec);
|
|
372
|
-
const ok = runCommand(command);
|
|
501
|
+
const ok = await runCommand(command);
|
|
373
502
|
if (ok) {
|
|
374
503
|
console.log(`${indent}${chalk.blue('fallback')} ${pkgSpec}...${chalk.green(' ✓')}`);
|
|
375
504
|
result.installed.push(pkgSpec);
|
|
@@ -387,7 +516,7 @@ async function installPackagesByType(packages, installerName, installers, snapsh
|
|
|
387
516
|
const parallelResult = await parallelInstall(entries, async (entry) => {
|
|
388
517
|
const pkgSpec = `${entry.name}@${entry.version}`;
|
|
389
518
|
const command = installerConfig.command.replace('{pkg}', pkgSpec);
|
|
390
|
-
const success = runCommand(command);
|
|
519
|
+
const success = await runCommand(command);
|
|
391
520
|
return { success, error: success ? undefined : `Command failed: ${command}` };
|
|
392
521
|
}, {
|
|
393
522
|
concurrency: 4,
|
|
@@ -562,7 +691,7 @@ export async function installSnapshot(snapshot, depth = 0, parentInstallers = {}
|
|
|
562
691
|
if (!commandExists(cmdName)) {
|
|
563
692
|
console.log(chalk.yellow(`${indent}${installerName} not found. Attempting auto-install...`));
|
|
564
693
|
console.log(chalk.gray(`${indent} Running: ${installerConfig.hint}`));
|
|
565
|
-
if (runCommand(installerConfig.hint)) {
|
|
694
|
+
if (await runCommand(installerConfig.hint)) {
|
|
566
695
|
console.log(chalk.green(`${indent} ✓ ${installerName} installed successfully`));
|
|
567
696
|
}
|
|
568
697
|
else {
|
|
@@ -596,7 +725,7 @@ export async function installSnapshot(snapshot, depth = 0, parentInstallers = {}
|
|
|
596
725
|
const specs = toInstall.map(e => `${e.name}@${e.version}`);
|
|
597
726
|
const batchCommand = installCfg.command.replace('{pkg}', specs.join(' '));
|
|
598
727
|
console.log(chalk.gray(`${indent} Running batch: npm install -g (${specs.length} packages)`));
|
|
599
|
-
const success = runCommand(batchCommand);
|
|
728
|
+
const success = await runCommand(batchCommand);
|
|
600
729
|
if (success) {
|
|
601
730
|
for (const spec of specs) {
|
|
602
731
|
console.log(`${indent}${chalk.blue('batch')} ${spec}...${chalk.green(' ✓')}`);
|
|
@@ -611,7 +740,7 @@ export async function installSnapshot(snapshot, depth = 0, parentInstallers = {}
|
|
|
611
740
|
for (const entry of toInstall) {
|
|
612
741
|
const pkgSpec = `${entry.name}@${entry.version}`;
|
|
613
742
|
const command = installCfg.command.replace('{pkg}', pkgSpec);
|
|
614
|
-
const ok = runCommand(command);
|
|
743
|
+
const ok = await runCommand(command);
|
|
615
744
|
if (ok) {
|
|
616
745
|
console.log(`${indent}${chalk.blue('fallback')} ${pkgSpec}...${chalk.green(' ✓')}`);
|
|
617
746
|
result.installed.push(pkgSpec);
|
|
@@ -629,7 +758,7 @@ export async function installSnapshot(snapshot, depth = 0, parentInstallers = {}
|
|
|
629
758
|
const parallelResult = await parallelInstall(installEntries, async (entry) => {
|
|
630
759
|
const pkgSpec = `${entry.name}@${entry.version}`;
|
|
631
760
|
const command = installCfg.command.replace('{pkg}', pkgSpec);
|
|
632
|
-
const success = runCommand(command);
|
|
761
|
+
const success = await runCommand(command);
|
|
633
762
|
return { success, error: success ? undefined : `Command failed: ${command}` };
|
|
634
763
|
}, {
|
|
635
764
|
concurrency: 4,
|
|
@@ -659,7 +788,7 @@ export async function installSnapshot(snapshot, depth = 0, parentInstallers = {}
|
|
|
659
788
|
if (!commandExists(cmdName)) {
|
|
660
789
|
console.log(chalk.yellow(`${indent}glpkg not found. Attempting auto-install...`));
|
|
661
790
|
console.log(chalk.gray(`${indent} Running: ${installerConfig.hint}`));
|
|
662
|
-
if (runCommand(installerConfig.hint)) {
|
|
791
|
+
if (await runCommand(installerConfig.hint)) {
|
|
663
792
|
console.log(chalk.green(`${indent} ✓ glpkg installed successfully`));
|
|
664
793
|
}
|
|
665
794
|
else {
|
|
@@ -677,7 +806,7 @@ export async function installSnapshot(snapshot, depth = 0, parentInstallers = {}
|
|
|
677
806
|
const parallelResult = await parallelInstall(legacyEntries, async (entry) => {
|
|
678
807
|
const pkgSpec = `${entry.name}@${entry.version}`;
|
|
679
808
|
const command = legacyCfg.command.replace('{pkg}', pkgSpec);
|
|
680
|
-
const success = runCommand(command);
|
|
809
|
+
const success = await runCommand(command);
|
|
681
810
|
return { success, error: success ? undefined : `Command failed: ${command}` };
|
|
682
811
|
}, {
|
|
683
812
|
concurrency: 4,
|
|
@@ -888,7 +1017,7 @@ async function runReplaceMode(snapshotName, newSet, options, installers) {
|
|
|
888
1017
|
continue;
|
|
889
1018
|
}
|
|
890
1019
|
console.log(chalk.gray(` Removing ${item.name}...`));
|
|
891
|
-
if (runCommand(cmd)) {
|
|
1020
|
+
if (await runCommand(cmd)) {
|
|
892
1021
|
console.log(` ${item.name}${chalk.green(' ✓ removed')}`);
|
|
893
1022
|
removed.push(item);
|
|
894
1023
|
}
|
|
@@ -949,6 +1078,10 @@ async function previewInstall(snapshot, options) {
|
|
|
949
1078
|
console.log(chalk.gray('Dry-run complete: no installs, no uninstalls, no .npmrc or history changes.'));
|
|
950
1079
|
}
|
|
951
1080
|
export async function installCommand(name, options = {}) {
|
|
1081
|
+
// Install-timeline instrumentation: live instance when SNAPSHOT_INSTALL_TIMELINE
|
|
1082
|
+
// is set, otherwise a zero-overhead no-op. Purely additive — never alters the
|
|
1083
|
+
// install path or its output when disabled.
|
|
1084
|
+
activeTimeline = createInstallTimeline();
|
|
952
1085
|
console.log(chalk.blue.bold(`
|
|
953
1086
|
╔══════════════════════════════════════════════════════════════╗
|
|
954
1087
|
║ Snapshot Installer ║
|
|
@@ -982,6 +1115,7 @@ export async function installCommand(name, options = {}) {
|
|
|
982
1115
|
//
|
|
983
1116
|
// License key CANNOT be used as Bearer token for registry-proxy — they are
|
|
984
1117
|
// distinct: AuthN (uid via IST JWT) vs AuthZ (entitlement via license key).
|
|
1118
|
+
const authSpan = activeTimeline.start('auth-token', { category: 'phase' });
|
|
985
1119
|
const licenseKey = getLicenseKey();
|
|
986
1120
|
let authToken = null;
|
|
987
1121
|
let authType = 'anonymous';
|
|
@@ -998,13 +1132,16 @@ export async function installCommand(name, options = {}) {
|
|
|
998
1132
|
console.log(chalk.yellow('⚠') + ' No auth token — anonymous access');
|
|
999
1133
|
}
|
|
1000
1134
|
const useProxy = true;
|
|
1135
|
+
authSpan.end({ authType });
|
|
1001
1136
|
// Fetch snapshot first — access check depends on snapshot.json content
|
|
1002
1137
|
console.log(chalk.gray('Fetching snapshot...'));
|
|
1138
|
+
const fetchSpan = activeTimeline.start('fetch-snapshot', { category: 'phase' });
|
|
1003
1139
|
let snapshot;
|
|
1004
1140
|
try {
|
|
1005
1141
|
snapshot = await fetchSnapshotFromRepo(name, authToken);
|
|
1006
1142
|
}
|
|
1007
1143
|
catch (error) {
|
|
1144
|
+
fetchSpan.end({ success: false });
|
|
1008
1145
|
const errMsg = error instanceof Error ? error.message : String(error);
|
|
1009
1146
|
if (options.dryRun) {
|
|
1010
1147
|
// Pure preview: a fetch failure (offline / no auth) is not fatal — we just
|
|
@@ -1019,6 +1156,7 @@ export async function installCommand(name, options = {}) {
|
|
|
1019
1156
|
console.log(chalk.yellow('Check the snapshot name and version, e.g. snapshot install @scope/name@version'));
|
|
1020
1157
|
process.exit(1);
|
|
1021
1158
|
}
|
|
1159
|
+
fetchSpan.end({ success: true });
|
|
1022
1160
|
console.log(chalk.green('✓') + ` Snapshot: ${snapshot.name} v${snapshot.version}`);
|
|
1023
1161
|
if (snapshot.description) {
|
|
1024
1162
|
console.log(chalk.gray(` ${snapshot.description}`));
|
|
@@ -1051,6 +1189,10 @@ export async function installCommand(name, options = {}) {
|
|
|
1051
1189
|
// the authToken injection point (IST_SESSION_TOKEN env / getProxyAuthToken).
|
|
1052
1190
|
const istCredentials = getIstCredentials();
|
|
1053
1191
|
// --- License Check ---
|
|
1192
|
+
const accessSpan = activeTimeline.start('access-license', {
|
|
1193
|
+
category: 'phase',
|
|
1194
|
+
meta: { access: isPublicAccess ? 'public' : isLicensedAccess ? 'licensed' : 'server-decided' },
|
|
1195
|
+
});
|
|
1054
1196
|
const storedLicenseKey = getLicenseKey();
|
|
1055
1197
|
let licenseValid = false;
|
|
1056
1198
|
if (isPublicAccess) {
|
|
@@ -1097,8 +1239,10 @@ export async function installCommand(name, options = {}) {
|
|
|
1097
1239
|
}
|
|
1098
1240
|
console.log(chalk.green('✓') + ` License verified for ${productId}`);
|
|
1099
1241
|
}
|
|
1242
|
+
accessSpan.end({ licenseValid });
|
|
1100
1243
|
// =======================================================================
|
|
1101
1244
|
// ========== Authorize Session with Registry Proxy ==========
|
|
1245
|
+
const authorizeSpan = activeTimeline.start('authorize-session', { category: 'phase' });
|
|
1102
1246
|
let sessionId;
|
|
1103
1247
|
if (authToken) {
|
|
1104
1248
|
// Collect all packages for session authorization
|
|
@@ -1134,8 +1278,10 @@ export async function installCommand(name, options = {}) {
|
|
|
1134
1278
|
}
|
|
1135
1279
|
}
|
|
1136
1280
|
}
|
|
1281
|
+
authorizeSpan.end({ sessionAuthorized: sessionId !== undefined });
|
|
1137
1282
|
// =============================================================
|
|
1138
1283
|
// Collect all packages from entire extends chain to determine scopes
|
|
1284
|
+
const configureSpan = activeTimeline.start('configure-npmrc', { category: 'phase' });
|
|
1139
1285
|
const allPackages = await collectAllPackages(snapshot);
|
|
1140
1286
|
// Configure .npmrc for proxy (with or without auth token)
|
|
1141
1287
|
let npmrcBackup = null;
|
|
@@ -1155,6 +1301,7 @@ export async function installCommand(name, options = {}) {
|
|
|
1155
1301
|
}
|
|
1156
1302
|
}
|
|
1157
1303
|
}
|
|
1304
|
+
configureSpan.end({ packageCount: Object.keys(allPackages).length });
|
|
1158
1305
|
// Guarantee .npmrc restoration even on ^C. A try/finally only covers thrown
|
|
1159
1306
|
// errors and normal returns — it does NOT run on SIGINT/SIGTERM, so an
|
|
1160
1307
|
// interrupt during the pre-install batch or the main install would leave a
|
|
@@ -1192,6 +1339,8 @@ export async function installCommand(name, options = {}) {
|
|
|
1192
1339
|
// ========== Pre-install: batch all npm packages from extends chain ==========
|
|
1193
1340
|
// Collect all npm-type packages across the entire extends chain and install
|
|
1194
1341
|
// them in a single `npm install -g` call, instead of one per snapshot layer.
|
|
1342
|
+
const batchSpan = activeTimeline.start('npm-batch-preinstall', { category: 'phase' });
|
|
1343
|
+
let batchSpecs = [];
|
|
1195
1344
|
const allNpmPackages = await collectAllNpmPackages(snapshot, useProxy);
|
|
1196
1345
|
const npmEntries = Object.entries(allNpmPackages);
|
|
1197
1346
|
if (npmEntries.length > 0) {
|
|
@@ -1209,9 +1358,10 @@ export async function installCommand(name, options = {}) {
|
|
|
1209
1358
|
}
|
|
1210
1359
|
if (toInstall.length > 0) {
|
|
1211
1360
|
const specs = toInstall.map(e => `${e.name}@${e.version}`);
|
|
1361
|
+
batchSpecs = specs;
|
|
1212
1362
|
const npmCommand = DEFAULT_INSTALLERS['npm-proxy'].command.replace('{pkg}', specs.join(' '));
|
|
1213
1363
|
console.log(chalk.gray(` Running single batch: npm install -g (${specs.length} packages)`));
|
|
1214
|
-
const batchSuccess = runCommand(npmCommand);
|
|
1364
|
+
const batchSuccess = await runCommand(npmCommand);
|
|
1215
1365
|
if (batchSuccess) {
|
|
1216
1366
|
for (const spec of specs) {
|
|
1217
1367
|
console.log(` ${chalk.blue('batch')} ${spec}...${chalk.green(' ✓')}`);
|
|
@@ -1227,7 +1377,7 @@ export async function installCommand(name, options = {}) {
|
|
|
1227
1377
|
for (const entry of toInstall) {
|
|
1228
1378
|
const pkgSpec = `${entry.name}@${entry.version}`;
|
|
1229
1379
|
const cmd = DEFAULT_INSTALLERS['npm-proxy'].command.replace('{pkg}', pkgSpec);
|
|
1230
|
-
const ok = runCommand(cmd);
|
|
1380
|
+
const ok = await runCommand(cmd);
|
|
1231
1381
|
if (ok) {
|
|
1232
1382
|
console.log(` ${chalk.blue('fallback')} ${pkgSpec}...${chalk.green(' ✓')}`);
|
|
1233
1383
|
}
|
|
@@ -1241,11 +1391,13 @@ export async function installCommand(name, options = {}) {
|
|
|
1241
1391
|
console.log(chalk.green('✓ All npm packages already installed'));
|
|
1242
1392
|
}
|
|
1243
1393
|
}
|
|
1394
|
+
batchSpan.end({ specs: batchSpecs, npmDeclared: npmEntries.length, npmToInstall: batchSpecs.length });
|
|
1244
1395
|
// =============================================================================
|
|
1245
1396
|
// Install (handles extends recursively)
|
|
1246
1397
|
// installSnapshot's internal isNpmGlobalInstalled checks will skip already-installed packages
|
|
1247
1398
|
let installError = null;
|
|
1248
1399
|
let result = { installed: [], failed: [] };
|
|
1400
|
+
const installSpan = activeTimeline.start('install-snapshot', { category: 'phase' });
|
|
1249
1401
|
try {
|
|
1250
1402
|
// Auto-detect 모드: snapshot.install이 없으면 자동 판별
|
|
1251
1403
|
const autoDetect = !snapshot.install;
|
|
@@ -1260,6 +1412,11 @@ export async function installCommand(name, options = {}) {
|
|
|
1260
1412
|
installError = err instanceof Error ? err : new Error(String(err));
|
|
1261
1413
|
}
|
|
1262
1414
|
finally {
|
|
1415
|
+
installSpan.end({
|
|
1416
|
+
installed: result.installed.length,
|
|
1417
|
+
failed: result.failed.length,
|
|
1418
|
+
success: installError === null,
|
|
1419
|
+
});
|
|
1263
1420
|
// Restore .npmrc + clear the session-token env (idempotent: a SIGINT/SIGTERM
|
|
1264
1421
|
// handler may have already run this). Then drop the signal handlers we
|
|
1265
1422
|
// registered, so they don't leak across multiple installs in one process.
|
|
@@ -1268,6 +1425,7 @@ export async function installCommand(name, options = {}) {
|
|
|
1268
1425
|
process.removeListener('SIGTERM', onSignal);
|
|
1269
1426
|
}
|
|
1270
1427
|
if (installError) {
|
|
1428
|
+
activeTimeline.finalize();
|
|
1271
1429
|
console.log(chalk.red(`Error during installation: ${installError.message}`));
|
|
1272
1430
|
process.exit(1);
|
|
1273
1431
|
}
|
|
@@ -1314,6 +1472,9 @@ export async function installCommand(name, options = {}) {
|
|
|
1314
1472
|
await runReplaceMode(snapshot.name, allPackages, options, effectiveInstallers);
|
|
1315
1473
|
}
|
|
1316
1474
|
}
|
|
1475
|
+
// Emit the install timeline (investigation only; no-op unless opted-in) before
|
|
1476
|
+
// the fire-and-forget telemetry so the summary/JSONL reflect the whole run.
|
|
1477
|
+
activeTimeline.finalize();
|
|
1317
1478
|
// Send telemetry (fire and forget)
|
|
1318
1479
|
sendInstallTelemetry({
|
|
1319
1480
|
package: snapshot.name,
|