@saptools/cf-inspector 0.8.0 → 0.9.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/cli.js CHANGED
@@ -152,12 +152,1153 @@ var init_wsTransport = __esm({
152
152
  });
153
153
 
154
154
  // src/cli.ts
155
- import process13 from "process";
155
+ import process14 from "process";
156
156
 
157
157
  // src/cli/program.ts
158
+ import process13 from "process";
159
+
160
+ // ../core/src/package-metadata.ts
158
161
  import { readFileSync } from "fs";
159
- import { dirname, join as join2 } from "path";
162
+ import { dirname, join } from "path";
160
163
  import { fileURLToPath } from "url";
164
+
165
+ // ../core/src/records.ts
166
+ function isRecord(value) {
167
+ return typeof value === "object" && value !== null && !Array.isArray(value);
168
+ }
169
+ function errorMessage(error) {
170
+ return error instanceof Error ? error.message : String(error);
171
+ }
172
+ function readString(record, key) {
173
+ const value = record[key];
174
+ return typeof value === "string" && value.length > 0 ? value : void 0;
175
+ }
176
+
177
+ // ../core/src/package-metadata.ts
178
+ var MAX_WALK_UP = 6;
179
+ function readPackageManifest(directory) {
180
+ let raw;
181
+ try {
182
+ raw = readFileSync(join(directory, "package.json"), "utf8");
183
+ } catch {
184
+ return;
185
+ }
186
+ let parsed;
187
+ try {
188
+ parsed = JSON.parse(raw);
189
+ } catch {
190
+ return;
191
+ }
192
+ if (!isRecord(parsed)) {
193
+ return;
194
+ }
195
+ const name = readString(parsed, "name");
196
+ const version = readString(parsed, "version");
197
+ if (name === void 0 || version === void 0) {
198
+ return;
199
+ }
200
+ return { name, version, directory };
201
+ }
202
+ function findPackageMetadata(startDirectory, expectedName) {
203
+ let current = startDirectory;
204
+ for (let depth = 0; depth < MAX_WALK_UP; depth += 1) {
205
+ const manifest = readPackageManifest(current);
206
+ if (manifest?.name === expectedName) {
207
+ return manifest;
208
+ }
209
+ const parent = dirname(current);
210
+ if (parent === current) {
211
+ return;
212
+ }
213
+ current = parent;
214
+ }
215
+ return;
216
+ }
217
+ function readPackageMetadata(importMetaUrl, expectedName) {
218
+ const startDirectory = dirname(fileURLToPath(importMetaUrl));
219
+ const found = findPackageMetadata(startDirectory, expectedName);
220
+ if (found === void 0) {
221
+ throw new Error(`Cannot find the package.json of ${expectedName} above ${startDirectory}`);
222
+ }
223
+ return found;
224
+ }
225
+
226
+ // ../core/src/saptools-paths.ts
227
+ import { chmodSync, mkdirSync, readFileSync as readFileSync2, renameSync, rmSync, writeFileSync } from "fs";
228
+ import { homedir } from "os";
229
+ import { join as join2 } from "path";
230
+ var SAPTOOLS_DIR_NAME = ".saptools";
231
+ var SAPTOOLS_ROOT_ENV = "SAPTOOLS_ROOT";
232
+ function resolveSaptoolsRoot(explicit, env = process.env) {
233
+ if (explicit !== void 0 && explicit.length > 0) {
234
+ return explicit;
235
+ }
236
+ const fromEnv = env[SAPTOOLS_ROOT_ENV];
237
+ if (fromEnv !== void 0 && fromEnv.length > 0) {
238
+ return fromEnv;
239
+ }
240
+ return join2(homedir(), SAPTOOLS_DIR_NAME);
241
+ }
242
+ function ensurePrivateDirectorySync(directory) {
243
+ mkdirSync(directory, { recursive: true, mode: 448 });
244
+ try {
245
+ chmodSync(directory, 448);
246
+ } catch {
247
+ }
248
+ }
249
+ function writeFileAtomicSync(path, data, mode = 384) {
250
+ const temp = `${path}.${String(process.pid)}.${Math.random().toString(36).slice(2, 8)}.tmp`;
251
+ try {
252
+ writeFileSync(temp, data, { encoding: "utf8", mode });
253
+ try {
254
+ chmodSync(temp, mode);
255
+ } catch {
256
+ }
257
+ renameSync(temp, path);
258
+ } catch (error) {
259
+ rmSync(temp, { force: true });
260
+ throw error;
261
+ }
262
+ }
263
+ function readJsonFileSync(path) {
264
+ let raw;
265
+ try {
266
+ raw = readFileSync2(path, "utf8");
267
+ } catch {
268
+ return;
269
+ }
270
+ try {
271
+ return JSON.parse(raw);
272
+ } catch {
273
+ return;
274
+ }
275
+ }
276
+
277
+ // ../core/src/self-update/reexec.ts
278
+ import { spawn } from "child_process";
279
+ import { constants } from "os";
280
+ var REEXEC_MARKER_ENV = "SAPTOOLS_SELF_UPDATE_REEXEC";
281
+ var FORWARDED_SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
282
+ function nativeExecve() {
283
+ return process.execve?.bind(process);
284
+ }
285
+ function defaultRuntime() {
286
+ return {
287
+ platform: process.platform,
288
+ execve: nativeExecve(),
289
+ spawnImpl: spawn,
290
+ onSignal: (signal, handler) => {
291
+ process.on(signal, handler);
292
+ },
293
+ offSignal: (signal, handler) => {
294
+ process.off(signal, handler);
295
+ },
296
+ kill: (pid, signal) => {
297
+ process.kill(pid, signal);
298
+ },
299
+ exit: (code) => {
300
+ process.exit(code);
301
+ }
302
+ };
303
+ }
304
+ function buildReexecArgv(request2) {
305
+ return [...request2.execArgv, request2.binPath, ...request2.args];
306
+ }
307
+ function reexecEnvironment(env) {
308
+ return { ...env, [REEXEC_MARKER_ENV]: "1" };
309
+ }
310
+ function signalExitCode(signal) {
311
+ const number = constants.signals[signal];
312
+ return 128 + number;
313
+ }
314
+ async function reexecProcess(request2, runtimeOverrides = {}) {
315
+ const runtime = { ...defaultRuntime(), ...runtimeOverrides };
316
+ const argv = buildReexecArgv(request2);
317
+ const env = reexecEnvironment(request2.env);
318
+ if (runtime.platform !== "win32" && runtime.execve !== void 0) {
319
+ try {
320
+ runtime.execve(request2.execPath, [request2.execPath, ...argv], env);
321
+ } catch {
322
+ }
323
+ }
324
+ await new Promise((resolve, reject) => {
325
+ const child = runtime.spawnImpl(request2.execPath, argv, { stdio: "inherit", env });
326
+ const forward = (signal) => {
327
+ child.kill(signal);
328
+ };
329
+ for (const signal of FORWARDED_SIGNALS) {
330
+ runtime.onSignal(signal, forward);
331
+ }
332
+ const detach = () => {
333
+ for (const signal of FORWARDED_SIGNALS) {
334
+ runtime.offSignal(signal, forward);
335
+ }
336
+ };
337
+ child.once("error", (error) => {
338
+ detach();
339
+ reject(error);
340
+ });
341
+ child.once("exit", (code, signal) => {
342
+ detach();
343
+ if (signal === null) {
344
+ runtime.exit(code ?? 1);
345
+ } else {
346
+ runtime.kill(process.pid, signal);
347
+ runtime.exit(signalExitCode(signal));
348
+ }
349
+ resolve();
350
+ });
351
+ });
352
+ }
353
+
354
+ // ../core/src/self-update/policy.ts
355
+ var POLICY_ENV = "SAPTOOLS_AUTO_UPDATE";
356
+ var INTERVAL_ENV = "SAPTOOLS_UPDATE_INTERVAL_MINUTES";
357
+ var DEBUG_ENV = "SAPTOOLS_UPDATE_DEBUG";
358
+ var DEFAULT_CHECK_INTERVAL_MINUTES = 60;
359
+ var SELF_UPDATE_COMMAND = "self-update";
360
+ function parsePolicy(raw) {
361
+ if (raw === void 0) {
362
+ return;
363
+ }
364
+ const value = raw.trim().toLowerCase();
365
+ if (["on", "1", "true", "yes", "auto", "always"].includes(value)) {
366
+ return "on";
367
+ }
368
+ if (["notify", "check"].includes(value)) {
369
+ return "notify";
370
+ }
371
+ if (["off", "0", "false", "no", "never", "disabled"].includes(value)) {
372
+ return "off";
373
+ }
374
+ return;
375
+ }
376
+ function isTruthyFlag(raw) {
377
+ return raw !== void 0 && !["", "0", "false", "no", "off"].includes(raw.trim().toLowerCase());
378
+ }
379
+ function readExplicitPolicy(env, envPrefix) {
380
+ const perPackage = envPrefix === void 0 ? void 0 : parsePolicy(env[`${envPrefix}_AUTO_UPDATE`]);
381
+ return perPackage ?? parsePolicy(env[POLICY_ENV]);
382
+ }
383
+ function off(reason, explicit) {
384
+ return { policy: "off", reason, explicit };
385
+ }
386
+ function notify(reason, explicit) {
387
+ return { policy: "notify", reason, explicit };
388
+ }
389
+ function locationDecision(location, explicit) {
390
+ switch (location.kind) {
391
+ case "local":
392
+ return off("running from a source checkout or linked package, not a package-manager install", explicit);
393
+ case "npx":
394
+ return off("running through npx or dlx, which resolves the version on its own", explicit);
395
+ case "unknown":
396
+ return notify(`install location not recognized (${location.detail})`, explicit);
397
+ case "npm-global":
398
+ case "pnpm-global":
399
+ case "yarn-global":
400
+ case "bun-global":
401
+ case "volta":
402
+ return;
403
+ }
404
+ }
405
+ function resolveUpdatePolicy(input) {
406
+ const { env, location } = input;
407
+ const manual = input.manual === true;
408
+ const explicit = readExplicitPolicy(env, input.envPrefix);
409
+ if (!manual && isTruthyFlag(env[REEXEC_MARKER_ENV])) {
410
+ return off("already re-executed after an update", false);
411
+ }
412
+ const commandPath = input.commandPath ?? "";
413
+ if (!manual && (commandPath === SELF_UPDATE_COMMAND || (input.skipCommands ?? []).includes(commandPath))) {
414
+ return off(`the "${commandPath}" command is excluded from automatic updates`, false);
415
+ }
416
+ if (!manual && explicit === "off") {
417
+ return off(`${POLICY_ENV} is off`, true);
418
+ }
419
+ if (!manual && explicit === void 0) {
420
+ if (isTruthyFlag(env["NO_UPDATE_NOTIFIER"])) {
421
+ return off("NO_UPDATE_NOTIFIER is set", false);
422
+ }
423
+ if (isTruthyFlag(env["CI"])) {
424
+ return off("running in CI", false);
425
+ }
426
+ if (env["NODE_ENV"] === "test") {
427
+ return off("NODE_ENV is test", false);
428
+ }
429
+ }
430
+ const byLocation = locationDecision(location, explicit !== void 0);
431
+ if (byLocation !== void 0) {
432
+ return byLocation;
433
+ }
434
+ if (!location.writable) {
435
+ return notify("the install directory is not writable by this user", explicit !== void 0);
436
+ }
437
+ if (manual) {
438
+ return { policy: "on", reason: "requested explicitly", explicit: true };
439
+ }
440
+ return explicit === void 0 ? { policy: "on", reason: "default", explicit: false } : { policy: explicit, reason: `${POLICY_ENV} is ${explicit}`, explicit: true };
441
+ }
442
+ function resolveCheckIntervalMs(env) {
443
+ const raw = env[INTERVAL_ENV];
444
+ if (raw === void 0) {
445
+ return DEFAULT_CHECK_INTERVAL_MINUTES * 6e4;
446
+ }
447
+ const minutes = Number(raw.trim());
448
+ if (!Number.isFinite(minutes) || minutes < 0) {
449
+ return DEFAULT_CHECK_INTERVAL_MINUTES * 6e4;
450
+ }
451
+ return Math.round(minutes * 6e4);
452
+ }
453
+
454
+ // ../core/src/self-update/run.ts
455
+ import { spawn as spawn2 } from "child_process";
456
+ import { homedir as homedir2 } from "os";
457
+
458
+ // ../core/src/self-update/install-location.ts
459
+ import { accessSync, constants as constants2, realpathSync } from "fs";
460
+ import { basename, dirname as dirname2, join as join3, sep } from "path";
461
+ var UPGRADABLE_KINDS = /* @__PURE__ */ new Set(["npm-global", "pnpm-global", "yarn-global", "bun-global", "volta"]);
462
+ function isUpgradableKind(kind) {
463
+ return UPGRADABLE_KINDS.has(kind);
464
+ }
465
+ function defaultIsWritable(path) {
466
+ try {
467
+ accessSync(path, constants2.W_OK);
468
+ return true;
469
+ } catch {
470
+ return false;
471
+ }
472
+ }
473
+ function toPosix(path) {
474
+ return path.split(sep).join("/");
475
+ }
476
+ function isPnpmGlobal(posixDirectory, env) {
477
+ if (posixDirectory.includes("/pnpm/global/")) {
478
+ return true;
479
+ }
480
+ const home = env["PNPM_HOME"];
481
+ return home !== void 0 && home.length > 0 && posixDirectory.startsWith(`${toPosix(home).replace(/\/+$/, "")}/global/`);
482
+ }
483
+ function nodeModulesRootOf(packageDirectory) {
484
+ const parent = dirname2(packageDirectory);
485
+ if (basename(parent) === "node_modules") {
486
+ return parent;
487
+ }
488
+ const grandparent = dirname2(parent);
489
+ return basename(grandparent) === "node_modules" ? grandparent : void 0;
490
+ }
491
+ function npmPrefixFor(packageDirectory, platform) {
492
+ const nodeModules = nodeModulesRootOf(packageDirectory);
493
+ if (nodeModules === void 0) {
494
+ return;
495
+ }
496
+ const parent = dirname2(nodeModules);
497
+ if (platform === "win32") {
498
+ return basename(parent).toLowerCase() === "npm" ? parent : void 0;
499
+ }
500
+ return basename(parent) === "lib" ? dirname2(parent) : void 0;
501
+ }
502
+ function classifyKind(packageDirectory, platform, env) {
503
+ const posix = toPosix(packageDirectory);
504
+ if (posix.includes("/_npx/") || /\/dlx-[^/]+\//.test(posix) || posix.includes("/pnpm/dlx/") || posix.includes("/.cache/pnpm/dlx")) {
505
+ return "npx";
506
+ }
507
+ if (posix.includes("/.volta/tools/image/packages/")) {
508
+ return "volta";
509
+ }
510
+ if (posix.includes("/.bun/install/global/")) {
511
+ return "bun-global";
512
+ }
513
+ if (posix.includes("/.config/yarn/global/") || posix.includes("/yarn/global/node_modules/")) {
514
+ return "yarn-global";
515
+ }
516
+ if (isPnpmGlobal(posix, env)) {
517
+ return "pnpm-global";
518
+ }
519
+ if (!posix.includes("/node_modules/")) {
520
+ return "local";
521
+ }
522
+ return npmPrefixFor(packageDirectory, platform) === void 0 ? "local" : "npm-global";
523
+ }
524
+ function writableForUpgrade(packageDirectory, prefix, platform, isWritable) {
525
+ const targets = [packageDirectory, dirname2(packageDirectory)];
526
+ const nodeModules = nodeModulesRootOf(packageDirectory);
527
+ if (nodeModules !== void 0) {
528
+ targets.push(nodeModules);
529
+ }
530
+ if (prefix !== void 0) {
531
+ targets.push(platform === "win32" ? prefix : join3(prefix, "bin"));
532
+ }
533
+ return targets.every((target) => isWritable(target));
534
+ }
535
+ function describe(kind, packageDirectory, prefix) {
536
+ if (kind === "npm-global" && prefix !== void 0) {
537
+ return `npm global install under ${prefix}`;
538
+ }
539
+ return `${kind} install at ${packageDirectory}`;
540
+ }
541
+ function unknownLocation(detail) {
542
+ return { kind: "unknown", packageDirectory: void 0, prefix: void 0, writable: false, detail };
543
+ }
544
+ function detectInstallLocation(options) {
545
+ const platform = options.platform ?? process.platform;
546
+ const env = options.env ?? process.env;
547
+ const realpath = options.realpath ?? ((path) => realpathSync(path));
548
+ const isWritable = options.isWritable ?? defaultIsWritable;
549
+ let resolvedBin;
550
+ try {
551
+ resolvedBin = realpath(options.binPath);
552
+ } catch (error) {
553
+ return unknownLocation(`cannot resolve ${options.binPath}: ${errorMessage(error)}`);
554
+ }
555
+ const manifest = findPackageMetadata(dirname2(resolvedBin), options.packageName);
556
+ if (manifest === void 0) {
557
+ return unknownLocation(`no package.json for ${options.packageName} above ${resolvedBin}`);
558
+ }
559
+ const packageDirectory = manifest.directory;
560
+ const kind = classifyKind(packageDirectory, platform, env);
561
+ const prefix = kind === "npm-global" ? npmPrefixFor(packageDirectory, platform) : void 0;
562
+ const writable = isUpgradableKind(kind) && writableForUpgrade(packageDirectory, prefix, platform, isWritable);
563
+ return { kind, packageDirectory, prefix, writable, detail: describe(kind, packageDirectory, prefix) };
564
+ }
565
+
566
+ // ../core/src/self-update/installer.ts
567
+ import { existsSync, realpathSync as realpathSync2 } from "fs";
568
+ import { tmpdir } from "os";
569
+ import { dirname as dirname3, join as join4 } from "path";
570
+ var DEFAULT_INSTALL_TIMEOUT_MS = 18e4;
571
+ function manualInstallCommand(kind, spec) {
572
+ switch (kind) {
573
+ case "pnpm-global":
574
+ return `pnpm add -g ${spec}`;
575
+ case "yarn-global":
576
+ return `yarn global add ${spec}`;
577
+ case "bun-global":
578
+ return `bun add -g ${spec}`;
579
+ case "volta":
580
+ return `volta install ${spec}`;
581
+ case "npm-global":
582
+ case "npx":
583
+ case "local":
584
+ case "unknown":
585
+ return `npm install -g ${spec}`;
586
+ }
587
+ }
588
+ function resolveNpmInvocation(execPath, platform, exists, realpath) {
589
+ const binDirectory = dirname3(execPath);
590
+ if (platform === "win32") {
591
+ const cli = join4(binDirectory, "node_modules", "npm", "bin", "npm-cli.js");
592
+ return exists(cli) ? { file: execPath, leadingArgs: [cli] } : void 0;
593
+ }
594
+ const sibling = join4(binDirectory, "npm");
595
+ if (exists(sibling)) {
596
+ try {
597
+ const target = realpath(sibling);
598
+ return target.endsWith(".js") ? { file: execPath, leadingArgs: [target] } : { file: target, leadingArgs: [] };
599
+ } catch {
600
+ }
601
+ }
602
+ return { file: "npm", leadingArgs: [] };
603
+ }
604
+ function buildNpmCommand(options, spec) {
605
+ if (options.location.prefix === void 0) {
606
+ return;
607
+ }
608
+ const npm = resolveNpmInvocation(
609
+ options.execPath ?? process.execPath,
610
+ options.platform ?? process.platform,
611
+ options.exists ?? existsSync,
612
+ options.realpath ?? ((path) => realpathSync2(path))
613
+ );
614
+ if (npm === void 0) {
615
+ return;
616
+ }
617
+ return {
618
+ file: npm.file,
619
+ args: [
620
+ ...npm.leadingArgs,
621
+ "install",
622
+ "--global",
623
+ "--prefix",
624
+ options.location.prefix,
625
+ spec,
626
+ "--registry",
627
+ options.registryUrl,
628
+ "--no-fund",
629
+ "--no-audit",
630
+ "--no-update-notifier",
631
+ "--loglevel=error"
632
+ ],
633
+ display: manualInstallCommand("npm-global", spec)
634
+ };
635
+ }
636
+ function buildInstallCommand(options) {
637
+ const spec = `${options.packageName}@${options.version}`;
638
+ switch (options.location.kind) {
639
+ case "npm-global":
640
+ return buildNpmCommand(options, spec);
641
+ case "pnpm-global":
642
+ return { file: "pnpm", args: ["add", "--global", spec], display: manualInstallCommand("pnpm-global", spec) };
643
+ case "yarn-global":
644
+ return { file: "yarn", args: ["global", "add", spec], display: manualInstallCommand("yarn-global", spec) };
645
+ case "bun-global":
646
+ return { file: "bun", args: ["add", "--global", spec], display: manualInstallCommand("bun-global", spec) };
647
+ case "volta":
648
+ return { file: "volta", args: ["install", spec], display: manualInstallCommand("volta", spec) };
649
+ case "npx":
650
+ case "local":
651
+ case "unknown":
652
+ return;
653
+ }
654
+ }
655
+ function lastLine(text) {
656
+ const lines = text.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
657
+ return lines.at(-1) ?? "";
658
+ }
659
+ function runInstall(command, options) {
660
+ const timeoutMs = options.timeoutMs ?? DEFAULT_INSTALL_TIMEOUT_MS;
661
+ const env = { ...options.env, SAPTOOLS_AUTO_UPDATE: "off" };
662
+ delete env["SAP_EMAIL"];
663
+ delete env["SAP_PASSWORD"];
664
+ return new Promise((resolve) => {
665
+ let child;
666
+ try {
667
+ child = options.spawnImpl(command.file, command.args, { cwd: tmpdir(), env, stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
668
+ } catch (error) {
669
+ resolve({ ok: false, reason: errorMessage(error) });
670
+ return;
671
+ }
672
+ let stderr = "";
673
+ child.stderr?.on("data", (chunk) => {
674
+ stderr = `${stderr}${chunk.toString()}`.slice(-4e3);
675
+ });
676
+ const timer = setTimeout(() => {
677
+ child.kill("SIGKILL");
678
+ }, timeoutMs);
679
+ child.once("error", (error) => {
680
+ clearTimeout(timer);
681
+ resolve({ ok: false, reason: `${command.file}: ${error.message}` });
682
+ });
683
+ child.once("close", (code, signal) => {
684
+ clearTimeout(timer);
685
+ if (code === 0) {
686
+ resolve({ ok: true });
687
+ return;
688
+ }
689
+ const detail = lastLine(stderr);
690
+ const suffix = detail.length > 0 ? `: ${detail}` : "";
691
+ resolve({
692
+ ok: false,
693
+ reason: signal === null ? `${command.display} exited with code ${String(code ?? -1)}${suffix}` : `${command.display} was killed by ${signal} after ${String(Math.round(timeoutMs / 1e3))}s`
694
+ });
695
+ });
696
+ });
697
+ }
698
+
699
+ // ../core/src/self-update/registry.ts
700
+ import { readFileSync as readFileSync3 } from "fs";
701
+ import { join as join5 } from "path";
702
+
703
+ // ../core/src/self-update/semver.ts
704
+ var SEMVER_PATTERN = /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
705
+ function parseSemver(text) {
706
+ const match = SEMVER_PATTERN.exec(text.trim());
707
+ if (match === null) {
708
+ return;
709
+ }
710
+ const [, major, minor, patch, prerelease] = match;
711
+ if (major === void 0 || minor === void 0 || patch === void 0) {
712
+ return;
713
+ }
714
+ return {
715
+ major: Number(major),
716
+ minor: Number(minor),
717
+ patch: Number(patch),
718
+ prerelease: prerelease === void 0 ? [] : prerelease.split(".")
719
+ };
720
+ }
721
+ function compareIdentifiers(left, right) {
722
+ const leftNumeric = /^\d+$/.test(left);
723
+ const rightNumeric = /^\d+$/.test(right);
724
+ if (leftNumeric && rightNumeric) {
725
+ return Math.sign(Number(left) - Number(right));
726
+ }
727
+ if (leftNumeric) {
728
+ return -1;
729
+ }
730
+ if (rightNumeric) {
731
+ return 1;
732
+ }
733
+ if (left === right) {
734
+ return 0;
735
+ }
736
+ return left < right ? -1 : 1;
737
+ }
738
+ function comparePrerelease(left, right) {
739
+ if (left.length === 0 && right.length === 0) {
740
+ return 0;
741
+ }
742
+ if (left.length === 0) {
743
+ return 1;
744
+ }
745
+ if (right.length === 0) {
746
+ return -1;
747
+ }
748
+ for (const [index, identifier] of left.entries()) {
749
+ const other = right[index];
750
+ if (other === void 0) {
751
+ return 1;
752
+ }
753
+ const result = compareIdentifiers(identifier, other);
754
+ if (result !== 0) {
755
+ return result;
756
+ }
757
+ }
758
+ return left.length < right.length ? -1 : 0;
759
+ }
760
+ function compareSemver(left, right) {
761
+ for (const key of ["major", "minor", "patch"]) {
762
+ if (left[key] !== right[key]) {
763
+ return left[key] < right[key] ? -1 : 1;
764
+ }
765
+ }
766
+ return comparePrerelease(left.prerelease, right.prerelease);
767
+ }
768
+ function isNewerRelease(candidate, current) {
769
+ const next = parseSemver(candidate);
770
+ const installed = parseSemver(current);
771
+ if (next === void 0 || installed === void 0) {
772
+ return false;
773
+ }
774
+ if (next.prerelease.length > 0) {
775
+ return false;
776
+ }
777
+ return compareSemver(next, installed) > 0;
778
+ }
779
+
780
+ // ../core/src/self-update/registry.ts
781
+ var DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org";
782
+ var REGISTRY_ENV = "SAPTOOLS_NPM_REGISTRY";
783
+ var DEFAULT_CHECK_TIMEOUT_MS = 2e3;
784
+ function normalizeRegistryUrl(value) {
785
+ if (value === void 0) {
786
+ return;
787
+ }
788
+ const trimmed = value.trim().replace(/\/+$/, "");
789
+ if (!/^https?:\/\/\S+$/i.test(trimmed)) {
790
+ return;
791
+ }
792
+ return trimmed;
793
+ }
794
+ function registryFromNpmrc(text, key) {
795
+ if (text === void 0) {
796
+ return;
797
+ }
798
+ for (const line of text.split(/\r?\n/)) {
799
+ const trimmed = line.trim();
800
+ if (trimmed.length === 0 || trimmed.startsWith("#") || trimmed.startsWith(";")) {
801
+ continue;
802
+ }
803
+ const separator = trimmed.indexOf("=");
804
+ if (separator === -1 || trimmed.slice(0, separator).trim() !== key) {
805
+ continue;
806
+ }
807
+ return trimmed.slice(separator + 1).trim().replace(/^"(.*)"$/, "$1");
808
+ }
809
+ return;
810
+ }
811
+ function readUserNpmrc(homeDirectory) {
812
+ try {
813
+ return readFileSync3(join5(homeDirectory, ".npmrc"), "utf8");
814
+ } catch {
815
+ return;
816
+ }
817
+ }
818
+ function resolveRegistryUrl(env, userNpmrc) {
819
+ const candidates = [
820
+ env[REGISTRY_ENV],
821
+ env["npm_config_registry"],
822
+ registryFromNpmrc(userNpmrc, "@saptools:registry"),
823
+ registryFromNpmrc(userNpmrc, "registry")
824
+ ];
825
+ for (const candidate of candidates) {
826
+ const normalized = normalizeRegistryUrl(candidate);
827
+ if (normalized !== void 0) {
828
+ return normalized;
829
+ }
830
+ }
831
+ return DEFAULT_NPM_REGISTRY;
832
+ }
833
+ async function fetchJson(fetchImpl, url, headers, timeoutMs) {
834
+ try {
835
+ const response = await fetchImpl(url, { headers, signal: AbortSignal.timeout(timeoutMs), redirect: "follow" });
836
+ if (!response.ok) {
837
+ return { ok: false, status: response.status, reason: `HTTP ${String(response.status)} from ${url}` };
838
+ }
839
+ const body = await response.json();
840
+ return { ok: true, body };
841
+ } catch (error) {
842
+ return { ok: false, status: void 0, reason: `${errorMessage(error)} (${url})` };
843
+ }
844
+ }
845
+ function readLatestTag(body, nestedUnderDistTags) {
846
+ if (!isRecord(body)) {
847
+ return;
848
+ }
849
+ const tags = nestedUnderDistTags ? body["dist-tags"] : body;
850
+ if (!isRecord(tags)) {
851
+ return;
852
+ }
853
+ const latest = readString(tags, "latest");
854
+ return latest !== void 0 && parseSemver(latest) !== void 0 ? latest : void 0;
855
+ }
856
+ async function fetchLatestVersion(packageName, registryUrl, options = {}) {
857
+ const fetchImpl = options.fetchImpl ?? fetch;
858
+ const timeoutMs = options.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS;
859
+ const encoded = encodeURIComponent(packageName);
860
+ const baseHeaders = { "user-agent": options.userAgent ?? "saptools-self-update" };
861
+ const distTags = await fetchJson(fetchImpl, `${registryUrl}/-/package/${encoded}/dist-tags`, { ...baseHeaders, accept: "application/json" }, timeoutMs);
862
+ if (distTags.ok) {
863
+ const latest2 = readLatestTag(distTags.body, false);
864
+ return latest2 === void 0 ? { ok: false, reason: "dist-tags response carries no valid latest version" } : { ok: true, latest: latest2 };
865
+ }
866
+ if (distTags.status === void 0) {
867
+ return { ok: false, reason: distTags.reason };
868
+ }
869
+ const packument = await fetchJson(
870
+ fetchImpl,
871
+ `${registryUrl}/${encoded}`,
872
+ { ...baseHeaders, accept: "application/vnd.npm.install-v1+json" },
873
+ timeoutMs
874
+ );
875
+ if (!packument.ok) {
876
+ return { ok: false, reason: packument.reason };
877
+ }
878
+ const latest = readLatestTag(packument.body, true);
879
+ return latest === void 0 ? { ok: false, reason: "packument carries no valid latest dist-tag" } : { ok: true, latest };
880
+ }
881
+
882
+ // ../core/src/self-update/state.ts
883
+ import { closeSync, openSync, rmSync as rmSync2, statSync, utimesSync, writeSync } from "fs";
884
+ import { dirname as dirname4, join as join6 } from "path";
885
+ var EMPTY_UPDATE_STATE = { version: 1 };
886
+ var UPDATES_DIRECTORY_NAME = "updates";
887
+ var DEFAULT_LOCK_STALE_MS = 10 * 6e4;
888
+ var STRING_FIELDS = ["checkedAt", "latest", "lastFailureAt", "lastFailureReason", "notifiedVersion", "notifiedAt"];
889
+ function updateStateFileName(packageName) {
890
+ return `${packageName.replace(/^@/, "").replaceAll("/", "__")}.json`;
891
+ }
892
+ function updateStatePath(saptoolsRoot, packageName) {
893
+ return join6(saptoolsRoot, UPDATES_DIRECTORY_NAME, updateStateFileName(packageName));
894
+ }
895
+ function updateLockPath(statePath) {
896
+ return statePath.replace(/\.json$/, ".lock");
897
+ }
898
+ function readInstallAttempt(value) {
899
+ if (!isRecord(value)) {
900
+ return;
901
+ }
902
+ const version = readString(value, "version");
903
+ const at = readString(value, "at");
904
+ const ok = value["ok"];
905
+ if (version === void 0 || at === void 0 || typeof ok !== "boolean") {
906
+ return;
907
+ }
908
+ const reason = readString(value, "reason");
909
+ return reason === void 0 ? { version, at, ok } : { version, at, ok, reason };
910
+ }
911
+ function readUpdateState(path) {
912
+ const parsed = readJsonFileSync(path);
913
+ if (!isRecord(parsed) || parsed["version"] !== 1) {
914
+ return EMPTY_UPDATE_STATE;
915
+ }
916
+ const draft = { version: 1 };
917
+ for (const key of STRING_FIELDS) {
918
+ const value = readString(parsed, key);
919
+ if (value !== void 0) {
920
+ draft[key] = value;
921
+ }
922
+ }
923
+ const lastInstall = readInstallAttempt(parsed["lastInstall"]);
924
+ if (lastInstall !== void 0) {
925
+ draft.lastInstall = lastInstall;
926
+ }
927
+ return draft;
928
+ }
929
+ function clearFailure(state) {
930
+ const next = { ...state };
931
+ delete next.lastFailureAt;
932
+ delete next.lastFailureReason;
933
+ return next;
934
+ }
935
+ function writeUpdateState(path, state) {
936
+ ensurePrivateDirectorySync(dirname4(path));
937
+ writeFileAtomicSync(path, `${JSON.stringify(state, null, 2)}
938
+ `, 384);
939
+ }
940
+ function tryCreateLock(lockPath, now) {
941
+ let descriptor;
942
+ try {
943
+ descriptor = openSync(lockPath, "wx", 384);
944
+ } catch {
945
+ return;
946
+ }
947
+ try {
948
+ writeSync(descriptor, `${JSON.stringify({ pid: process.pid, at: now.toISOString() })}
949
+ `);
950
+ } finally {
951
+ closeSync(descriptor);
952
+ }
953
+ utimesSync(lockPath, now, now);
954
+ return {
955
+ release: () => {
956
+ rmSync2(lockPath, { force: true });
957
+ }
958
+ };
959
+ }
960
+ function isStaleLock(lockPath, now, staleMs) {
961
+ try {
962
+ return now.getTime() - statSync(lockPath).mtimeMs > staleMs;
963
+ } catch {
964
+ return false;
965
+ }
966
+ }
967
+ function acquireUpdateLock(lockPath, now, staleMs = DEFAULT_LOCK_STALE_MS) {
968
+ ensurePrivateDirectorySync(dirname4(lockPath));
969
+ const lock = tryCreateLock(lockPath, now);
970
+ if (lock !== void 0) {
971
+ return lock;
972
+ }
973
+ if (!isStaleLock(lockPath, now, staleMs)) {
974
+ return;
975
+ }
976
+ rmSync2(lockPath, { force: true });
977
+ return tryCreateLock(lockPath, now);
978
+ }
979
+
980
+ // ../core/src/self-update/run.ts
981
+ var FAILURE_BACKOFF_MS = 15 * 6e4;
982
+ var NOTIFY_INTERVAL_MS = 24 * 60 * 6e4;
983
+ var INSTALL_RETRY_BACKOFF_MS = 24 * 60 * 6e4;
984
+ function defaultRuntime2() {
985
+ return {
986
+ env: process.env,
987
+ argv: process.argv,
988
+ execPath: process.execPath,
989
+ execArgv: process.execArgv,
990
+ platform: process.platform,
991
+ homeDirectory: homedir2(),
992
+ now: () => /* @__PURE__ */ new Date(),
993
+ fetchImpl: fetch,
994
+ spawnImpl: spawn2,
995
+ reexecImpl: reexecProcess,
996
+ checkTimeoutMs: DEFAULT_CHECK_TIMEOUT_MS,
997
+ installTimeoutMs: DEFAULT_INSTALL_TIMEOUT_MS
998
+ };
999
+ }
1000
+ function ageMs(isoTimestamp, nowMs) {
1001
+ const then = Date.parse(isoTimestamp);
1002
+ return Number.isNaN(then) || then > nowMs ? Number.POSITIVE_INFINITY : nowMs - then;
1003
+ }
1004
+ function detectLocation(options, runtime, binPath) {
1005
+ return detectInstallLocation({
1006
+ binPath,
1007
+ packageName: options.packageName,
1008
+ platform: runtime.platform,
1009
+ env: runtime.env,
1010
+ ...runtime.realpath === void 0 ? {} : { realpath: runtime.realpath },
1011
+ ...runtime.isWritable === void 0 ? {} : { isWritable: runtime.isWritable }
1012
+ });
1013
+ }
1014
+ function createContext(options, runtime) {
1015
+ const { env } = runtime;
1016
+ const binPath = runtime.argv[1] ?? "";
1017
+ const location = detectLocation(options, runtime, binPath);
1018
+ const decision = resolveUpdatePolicy({
1019
+ env,
1020
+ location,
1021
+ skipCommands: options.skipCommands ?? [],
1022
+ manual: options.manual === true,
1023
+ ...options.envPrefix === void 0 ? {} : { envPrefix: options.envPrefix },
1024
+ ...options.commandPath === void 0 ? {} : { commandPath: options.commandPath }
1025
+ });
1026
+ const statePath = updateStatePath(resolveSaptoolsRoot(options.saptoolsRoot, env), options.packageName);
1027
+ const notice = options.notice ?? ((line) => {
1028
+ process.stderr.write(`${options.binName}: ${line}
1029
+ `);
1030
+ });
1031
+ const debug = isTruthyFlag(env[DEBUG_ENV]) ? (line) => {
1032
+ process.stderr.write(`${options.binName}: [self-update] ${line}
1033
+ `);
1034
+ } : () => {
1035
+ };
1036
+ return {
1037
+ options,
1038
+ runtime,
1039
+ binPath,
1040
+ location,
1041
+ decision,
1042
+ registryUrl: resolveRegistryUrl(env, readUserNpmrc(runtime.homeDirectory)),
1043
+ statePath,
1044
+ intervalMs: resolveCheckIntervalMs(env),
1045
+ // Loaded lazily in resolveLatest: a disabled run must not touch ~/.saptools at all.
1046
+ state: EMPTY_UPDATE_STATE,
1047
+ notice,
1048
+ debug
1049
+ };
1050
+ }
1051
+ function saveState(ctx) {
1052
+ try {
1053
+ writeUpdateState(ctx.statePath, ctx.state);
1054
+ } catch (error) {
1055
+ ctx.debug(`cannot write ${ctx.statePath}: ${errorMessage(error)}`);
1056
+ }
1057
+ }
1058
+ async function resolveLatest(ctx) {
1059
+ ctx.state = readUpdateState(ctx.statePath);
1060
+ const { state } = ctx;
1061
+ const nowMs = ctx.runtime.now().getTime();
1062
+ if (ctx.options.manual !== true) {
1063
+ if (state.latest !== void 0 && state.checkedAt !== void 0 && ageMs(state.checkedAt, nowMs) < ctx.intervalMs) {
1064
+ ctx.debug(`using cached latest ${state.latest} (checked ${state.checkedAt})`);
1065
+ return state.latest;
1066
+ }
1067
+ if (state.lastFailureAt !== void 0 && ageMs(state.lastFailureAt, nowMs) < FAILURE_BACKOFF_MS) {
1068
+ ctx.debug(`skipping the registry check after a recent failure: ${state.lastFailureReason ?? "unknown"}`);
1069
+ return state.latest;
1070
+ }
1071
+ }
1072
+ const result = await fetchLatestVersion(ctx.options.packageName, ctx.registryUrl, {
1073
+ fetchImpl: ctx.runtime.fetchImpl,
1074
+ timeoutMs: ctx.runtime.checkTimeoutMs,
1075
+ userAgent: `${ctx.options.binName}/${ctx.options.currentVersion} saptools-self-update`
1076
+ });
1077
+ const at = ctx.runtime.now().toISOString();
1078
+ if (result.ok) {
1079
+ ctx.state = clearFailure({ ...ctx.state, checkedAt: at, latest: result.latest });
1080
+ saveState(ctx);
1081
+ return result.latest;
1082
+ }
1083
+ ctx.state = { ...ctx.state, lastFailureAt: at, lastFailureReason: result.reason };
1084
+ saveState(ctx);
1085
+ ctx.debug(`registry check failed: ${result.reason}`);
1086
+ return ctx.state.latest;
1087
+ }
1088
+ function notify2(ctx, latest, reason) {
1089
+ const nowMs = ctx.runtime.now().getTime();
1090
+ const alreadyAnnounced = ctx.state.notifiedVersion === latest && ctx.state.notifiedAt !== void 0 && ageMs(ctx.state.notifiedAt, nowMs) < NOTIFY_INTERVAL_MS;
1091
+ if (!alreadyAnnounced || ctx.options.manual === true) {
1092
+ const command = manualInstallCommand(ctx.location.kind, `${ctx.options.packageName}@${latest}`);
1093
+ ctx.notice(`${latest} is available (installed ${ctx.options.currentVersion}) but was not installed: ${reason}. Run: ${command}`);
1094
+ ctx.state = { ...ctx.state, notifiedVersion: latest, notifiedAt: ctx.runtime.now().toISOString() };
1095
+ saveState(ctx);
1096
+ }
1097
+ return { kind: "notified", latest, reason };
1098
+ }
1099
+ function recentInstallFailure(ctx, latest) {
1100
+ const attempt = ctx.state.lastInstall;
1101
+ if (ctx.options.manual === true || attempt === void 0 || attempt.ok || attempt.version !== latest) {
1102
+ return;
1103
+ }
1104
+ if (ageMs(attempt.at, ctx.runtime.now().getTime()) >= INSTALL_RETRY_BACKOFF_MS) {
1105
+ return;
1106
+ }
1107
+ return `the previous attempt failed (${attempt.reason ?? "unknown reason"})`;
1108
+ }
1109
+ function recordInstall(ctx, latest, ok, reason) {
1110
+ const at = ctx.runtime.now().toISOString();
1111
+ ctx.state = { ...ctx.state, lastInstall: reason === void 0 ? { version: latest, at, ok } : { version: latest, at, ok, reason } };
1112
+ saveState(ctx);
1113
+ }
1114
+ async function installLatest(ctx, latest) {
1115
+ const command = buildInstallCommand({
1116
+ location: ctx.location,
1117
+ packageName: ctx.options.packageName,
1118
+ version: latest,
1119
+ registryUrl: ctx.registryUrl,
1120
+ execPath: ctx.runtime.execPath,
1121
+ platform: ctx.runtime.platform,
1122
+ ...ctx.runtime.exists === void 0 ? {} : { exists: ctx.runtime.exists },
1123
+ ...ctx.runtime.realpath === void 0 ? {} : { realpath: ctx.runtime.realpath }
1124
+ });
1125
+ if (command === void 0) {
1126
+ return notify2(ctx, latest, "no supported package manager was found for this install");
1127
+ }
1128
+ const lock = acquireUpdateLock(updateLockPath(ctx.statePath), ctx.runtime.now());
1129
+ if (lock === void 0) {
1130
+ ctx.debug("another process holds the update lock");
1131
+ return { kind: "skipped", reason: "another process is installing the update" };
1132
+ }
1133
+ try {
1134
+ ctx.notice(`updating ${ctx.options.currentVersion} -> ${latest} ...`);
1135
+ const result = await runInstall(command, { env: ctx.runtime.env, timeoutMs: ctx.runtime.installTimeoutMs, spawnImpl: ctx.runtime.spawnImpl });
1136
+ const installed = ctx.location.packageDirectory === void 0 ? void 0 : readPackageManifest(ctx.location.packageDirectory)?.version;
1137
+ const failure = result.ok ? installed === latest ? void 0 : `the installed version is ${installed ?? "unreadable"}, not ${latest}` : result.reason;
1138
+ if (failure !== void 0) {
1139
+ recordInstall(ctx, latest, false, failure);
1140
+ ctx.notice(`update to ${latest} failed (${failure}); continuing with ${ctx.options.currentVersion}. Run: ${command.display}`);
1141
+ return { kind: "failed", latest, reason: failure };
1142
+ }
1143
+ recordInstall(ctx, latest, true);
1144
+ } finally {
1145
+ lock.release();
1146
+ }
1147
+ return;
1148
+ }
1149
+ async function upgrade(ctx, latest) {
1150
+ const failed = await installLatest(ctx, latest);
1151
+ if (failed !== void 0) {
1152
+ return failed;
1153
+ }
1154
+ const outcome = { kind: "updated", from: ctx.options.currentVersion, to: latest };
1155
+ if (ctx.options.reexec === false) {
1156
+ ctx.notice(`updated to ${latest}`);
1157
+ return outcome;
1158
+ }
1159
+ ctx.notice(`updated to ${latest}; re-running the command`);
1160
+ try {
1161
+ await ctx.runtime.reexecImpl({
1162
+ execPath: ctx.runtime.execPath,
1163
+ execArgv: ctx.runtime.execArgv,
1164
+ binPath: ctx.binPath,
1165
+ args: ctx.runtime.argv.slice(2),
1166
+ env: ctx.runtime.env
1167
+ });
1168
+ } catch (error) {
1169
+ ctx.notice(`could not re-run on ${latest} (${errorMessage(error)}); continuing with the already loaded ${ctx.options.currentVersion}`);
1170
+ }
1171
+ return outcome;
1172
+ }
1173
+ async function runSelfUpdateUnsafe(options, runtime) {
1174
+ const ctx = createContext(options, runtime);
1175
+ if (ctx.decision.policy === "off") {
1176
+ ctx.debug(`off: ${ctx.decision.reason}`);
1177
+ return { kind: "skipped", reason: ctx.decision.reason };
1178
+ }
1179
+ const latest = await resolveLatest(ctx);
1180
+ if (latest === void 0) {
1181
+ return { kind: "skipped", reason: "the latest version is unknown" };
1182
+ }
1183
+ if (!isNewerRelease(latest, options.currentVersion)) {
1184
+ return { kind: "current", latest };
1185
+ }
1186
+ if (ctx.decision.policy === "notify") {
1187
+ return notify2(ctx, latest, ctx.decision.reason);
1188
+ }
1189
+ const backoff = recentInstallFailure(ctx, latest);
1190
+ if (backoff !== void 0) {
1191
+ return notify2(ctx, latest, backoff);
1192
+ }
1193
+ return await upgrade(ctx, latest);
1194
+ }
1195
+ async function runSelfUpdate(options, overrides = {}) {
1196
+ const runtime = { ...defaultRuntime2(), ...overrides };
1197
+ try {
1198
+ return await runSelfUpdateUnsafe(options, runtime);
1199
+ } catch (error) {
1200
+ const reason = errorMessage(error);
1201
+ if (isTruthyFlag(runtime.env[DEBUG_ENV])) {
1202
+ process.stderr.write(`${options.binName}: [self-update] ${reason}
1203
+ `);
1204
+ }
1205
+ return { kind: "skipped", reason };
1206
+ }
1207
+ }
1208
+ async function inspectSelfUpdate(options, overrides = {}) {
1209
+ const runtime = { ...defaultRuntime2(), ...overrides };
1210
+ const ctx = createContext({ ...options, manual: true }, runtime);
1211
+ const result = await fetchLatestVersion(options.packageName, ctx.registryUrl, {
1212
+ fetchImpl: runtime.fetchImpl,
1213
+ timeoutMs: runtime.checkTimeoutMs,
1214
+ userAgent: `${options.binName}/${options.currentVersion} saptools-self-update`
1215
+ });
1216
+ return {
1217
+ packageName: options.packageName,
1218
+ installed: options.currentVersion,
1219
+ location: ctx.location,
1220
+ policy: resolveUpdatePolicy({
1221
+ env: runtime.env,
1222
+ location: ctx.location,
1223
+ ...options.envPrefix === void 0 ? {} : { envPrefix: options.envPrefix }
1224
+ }),
1225
+ registryUrl: ctx.registryUrl,
1226
+ statePath: ctx.statePath,
1227
+ latest: result.ok ? result.latest : void 0,
1228
+ checkError: result.ok ? void 0 : result.reason
1229
+ };
1230
+ }
1231
+
1232
+ // ../core/src/self-update/commander.ts
1233
+ function commandPathOf(command) {
1234
+ const names = [];
1235
+ let current = command;
1236
+ while (current.parent !== null) {
1237
+ names.unshift(current.name());
1238
+ current = current.parent;
1239
+ }
1240
+ return names.join(" ");
1241
+ }
1242
+ function attachSelfUpdate(program, options) {
1243
+ program.hook("preAction", async (_thisCommand, actionCommand) => {
1244
+ await runSelfUpdate({ ...options, commandPath: commandPathOf(actionCommand) });
1245
+ });
1246
+ }
1247
+ function formatSelfUpdateStatus(status) {
1248
+ const latest = status.latest ?? (status.checkError === void 0 ? "unknown" : `unknown (${status.checkError})`);
1249
+ const install = status.location.packageDirectory === void 0 ? status.location.detail : `${status.location.kind} ${status.location.packageDirectory}`;
1250
+ return [
1251
+ `package=${status.packageName}`,
1252
+ `installed=${status.installed}`,
1253
+ `latest=${latest}`,
1254
+ `policy=${status.policy.policy} (${status.policy.reason})`,
1255
+ `install=${install}`,
1256
+ `writable=${status.location.writable ? "yes" : "no"}`,
1257
+ `registry=${status.registryUrl}`,
1258
+ `state=${status.statePath}`
1259
+ ];
1260
+ }
1261
+ function describeOutcome(outcome) {
1262
+ switch (outcome.kind) {
1263
+ case "current":
1264
+ return `current (${outcome.latest} is the newest release)`;
1265
+ case "updated":
1266
+ return `updated ${outcome.from} -> ${outcome.to}`;
1267
+ case "notified":
1268
+ return `not installed (${outcome.reason})`;
1269
+ case "failed":
1270
+ return `failed (${outcome.reason})`;
1271
+ case "skipped":
1272
+ return `skipped (${outcome.reason})`;
1273
+ }
1274
+ }
1275
+ async function runSelfUpdateCommand(options, checkOnly) {
1276
+ const print = options.print ?? ((line) => {
1277
+ process.stdout.write(`${line}
1278
+ `);
1279
+ });
1280
+ const status = await inspectSelfUpdate(options);
1281
+ for (const line of formatSelfUpdateStatus(status)) {
1282
+ print(line);
1283
+ }
1284
+ if (checkOnly) {
1285
+ const verdict = status.latest === void 0 ? "unknown" : isNewerRelease(status.latest, status.installed) ? "update-available" : "current";
1286
+ print(`result=${verdict}`);
1287
+ return;
1288
+ }
1289
+ const outcome = await runSelfUpdate({ ...options, manual: true, reexec: false });
1290
+ print(`result=${describeOutcome(outcome)}`);
1291
+ if (outcome.kind === "failed") {
1292
+ process.exitCode = 1;
1293
+ }
1294
+ }
1295
+ function registerSelfUpdateCommand(program, options) {
1296
+ program.command(SELF_UPDATE_COMMAND).description("check npm for a newer release and install it now; every other command does this automatically (at most once an hour)").option("--check", "report whether a newer release exists without installing it").action(async (flags) => {
1297
+ await runSelfUpdateCommand(options, flags.check === true);
1298
+ });
1299
+ }
1300
+
1301
+ // src/cli/program.ts
161
1302
  import { Command } from "commander";
162
1303
 
163
1304
  // src/cli/commands/attach.ts
@@ -169,7 +1310,7 @@ import { request } from "http";
169
1310
  import { performance } from "perf_hooks";
170
1311
  var InvalidDiscoveryPayloadError = class extends CfInspectorError {
171
1312
  };
172
- async function fetchJson(url, timeoutMs) {
1313
+ async function fetchJson2(url, timeoutMs) {
173
1314
  const deadline = performance.now() + timeoutMs;
174
1315
  let lastError;
175
1316
  while (performance.now() < deadline) {
@@ -296,7 +1437,7 @@ function toInspectorTarget(value, source) {
296
1437
  }
297
1438
  async function discoverInspectorTargets(host, port, timeoutMs) {
298
1439
  const url = `http://${host}:${port.toString()}/json/list`;
299
- const raw = await fetchJson(url, timeoutMs);
1440
+ const raw = await fetchJson2(url, timeoutMs);
300
1441
  if (!Array.isArray(raw) || raw.length === 0) {
301
1442
  throw new CfInspectorError(
302
1443
  "INSPECTOR_DISCOVERY_FAILED",
@@ -316,7 +1457,7 @@ function readVersionField(value, ...keys) {
316
1457
  }
317
1458
  async function fetchInspectorVersion(host, port, timeoutMs) {
318
1459
  const url = `http://${host}:${port.toString()}/json/version`;
319
- const raw = await fetchJson(url, timeoutMs);
1460
+ const raw = await fetchJson2(url, timeoutMs);
320
1461
  if (typeof raw !== "object" || raw === null) {
321
1462
  throw new CfInspectorError(
322
1463
  "INSPECTOR_DISCOVERY_FAILED",
@@ -639,7 +1780,7 @@ import { performance as performance2 } from "perf_hooks";
639
1780
  // src/cdp/client.ts
640
1781
  init_types();
641
1782
  import { EventEmitter } from "events";
642
- var DEFAULT_REQUEST_TIMEOUT_MS = 15e3;
1783
+ var DEFAULT_REQUEST_TIMEOUT_MS = 6e4;
643
1784
  function parseMessage(raw) {
644
1785
  try {
645
1786
  const value = JSON.parse(raw);
@@ -1019,7 +2160,7 @@ function asString(value, fallback = "") {
1019
2160
  function asNumber(value, fallback = 0) {
1020
2161
  return typeof value === "number" && Number.isFinite(value) ? value : fallback;
1021
2162
  }
1022
- function isRecord(value) {
2163
+ function isRecord2(value) {
1023
2164
  return typeof value === "object" && value !== null && !Array.isArray(value);
1024
2165
  }
1025
2166
  function nonEmptyString(value) {
@@ -1036,7 +2177,7 @@ function optionalBoolean(value) {
1036
2177
  return typeof value === "boolean" ? value : void 0;
1037
2178
  }
1038
2179
  function toScriptLocation(value) {
1039
- if (!isRecord(value)) {
2180
+ if (!isRecord2(value)) {
1040
2181
  return void 0;
1041
2182
  }
1042
2183
  const scriptId = nonEmptyString(value["scriptId"]);
@@ -1057,7 +2198,7 @@ function toResolvedLocations(value) {
1057
2198
  }
1058
2199
  return value.flatMap((entry) => {
1059
2200
  const location = toScriptLocation(entry);
1060
- if (location === void 0 || !isRecord(entry)) {
2201
+ if (location === void 0 || !isRecord2(entry)) {
1061
2202
  return [];
1062
2203
  }
1063
2204
  const url = typeof entry["url"] === "string" ? entry["url"] : void 0;
@@ -1070,7 +2211,7 @@ function toBreakLocations(value) {
1070
2211
  }
1071
2212
  return value.flatMap((entry) => {
1072
2213
  const location = toScriptLocation(entry);
1073
- if (location === void 0 || !isRecord(entry)) {
2214
+ if (location === void 0 || !isRecord2(entry)) {
1074
2215
  return [];
1075
2216
  }
1076
2217
  const type = nonEmptyString(entry["type"]);
@@ -1087,7 +2228,7 @@ function optionalOwnField(key, value) {
1087
2228
  return Object.hasOwn(value, key) ? { [key]: value[key] } : {};
1088
2229
  }
1089
2230
  function toRemoteObject(value) {
1090
- if (!isRecord(value)) {
2231
+ if (!isRecord2(value)) {
1091
2232
  return void 0;
1092
2233
  }
1093
2234
  const type = nonEmptyString(value["type"]);
@@ -1114,7 +2255,7 @@ function optionalTextField(key, value) {
1114
2255
  return typeof value === "string" ? { [key]: value } : {};
1115
2256
  }
1116
2257
  function toScope(value) {
1117
- if (!isRecord(value)) {
2258
+ if (!isRecord2(value)) {
1118
2259
  return void 0;
1119
2260
  }
1120
2261
  const type = nonEmptyString(value["type"]);
@@ -1162,7 +2303,7 @@ function toCallFrameMetadata(candidate, location, scripts) {
1162
2303
  };
1163
2304
  }
1164
2305
  function toCallFrame(value, scripts) {
1165
- if (!isRecord(value)) {
2306
+ if (!isRecord2(value)) {
1166
2307
  return void 0;
1167
2308
  }
1168
2309
  const callFrameId = nonEmptyString(value["callFrameId"]);
@@ -1186,7 +2327,7 @@ function toCallFrames(value, scripts) {
1186
2327
  }) : [];
1187
2328
  }
1188
2329
  function toStackTraceId(value) {
1189
- if (!isRecord(value)) {
2330
+ if (!isRecord2(value)) {
1190
2331
  return void 0;
1191
2332
  }
1192
2333
  const id = nonEmptyString(value["id"]);
@@ -1197,7 +2338,7 @@ function toStackTraceId(value) {
1197
2338
  return debuggerId === void 0 ? { id } : { id, debuggerId };
1198
2339
  }
1199
2340
  function toStackTraceFrame(value) {
1200
- if (!isRecord(value)) {
2341
+ if (!isRecord2(value)) {
1201
2342
  return void 0;
1202
2343
  }
1203
2344
  const scriptId = nonEmptyString(value["scriptId"]);
@@ -1213,7 +2354,7 @@ function toStackTraceFrame(value) {
1213
2354
  };
1214
2355
  }
1215
2356
  function toStackTrace(value) {
1216
- if (!isRecord(value) || !Array.isArray(value["callFrames"])) {
2357
+ if (!isRecord2(value) || !Array.isArray(value["callFrames"])) {
1217
2358
  return void 0;
1218
2359
  }
1219
2360
  const callFrames = value["callFrames"].flatMap((entry) => {
@@ -1231,7 +2372,7 @@ function toStackTrace(value) {
1231
2372
  };
1232
2373
  }
1233
2374
  function toPauseEvent(value, receivedAtMs, scripts) {
1234
- const params = isRecord(value) ? value : {};
2375
+ const params = isRecord2(value) ? value : {};
1235
2376
  const asyncStackTrace = toStackTrace(params["asyncStackTrace"]);
1236
2377
  const asyncStackTraceId = toStackTraceId(params["asyncStackTraceId"]);
1237
2378
  const asyncCallStackTraceId = toStackTraceId(params["asyncCallStackTraceId"]);
@@ -1247,7 +2388,7 @@ function toPauseEvent(value, receivedAtMs, scripts) {
1247
2388
  };
1248
2389
  }
1249
2390
  function toScriptInfo(value) {
1250
- if (!isRecord(value)) {
2391
+ if (!isRecord2(value)) {
1251
2392
  return void 0;
1252
2393
  }
1253
2394
  const scriptId = nonEmptyString(value["scriptId"]);
@@ -1789,10 +2930,10 @@ var DEFAULT_EXCEPTION_TIMEOUT_SEC = 30;
1789
2930
  init_types();
1790
2931
  import { execFileSync } from "child_process";
1791
2932
  import { createHash, randomUUID } from "crypto";
1792
- import { constants } from "fs";
2933
+ import { constants as constants3 } from "fs";
1793
2934
  import { mkdir, open, readFile, readdir, stat, unlink, writeFile } from "fs/promises";
1794
- import { homedir } from "os";
1795
- import { join } from "path";
2935
+ import { homedir as homedir3 } from "os";
2936
+ import { join as join7 } from "path";
1796
2937
  var ELECTION_WINDOW_MS = 25;
1797
2938
  var LOCK_FILE_SUFFIX = ".lock";
1798
2939
  async function acquireDebugSessionLock(target, options = {}) {
@@ -1804,8 +2945,8 @@ async function acquireDebugSessionLock(target, options = {}) {
1804
2945
  const targetIdentity = debugTargetIdentity(target);
1805
2946
  const key = createHash("sha256").update(targetIdentity).digest("hex");
1806
2947
  const lockRoot = options.stateRoot ?? defaultStateRoot();
1807
- const lockDirectory = join(lockRoot, "cf-inspector", "locks");
1808
- const ownPath = join(lockDirectory, `${key}.${pid.toString()}.${token}${LOCK_FILE_SUFFIX}`);
2948
+ const lockDirectory = join7(lockRoot, "cf-inspector", "locks");
2949
+ const ownPath = join7(lockDirectory, `${key}.${pid.toString()}.${token}${LOCK_FILE_SUFFIX}`);
1809
2950
  const metadata = {
1810
2951
  pid,
1811
2952
  ...ownerProcessStart === void 0 ? {} : { processStart: ownerProcessStart },
@@ -1815,7 +2956,7 @@ async function acquireDebugSessionLock(target, options = {}) {
1815
2956
  target: targetIdentity
1816
2957
  };
1817
2958
  await mkdir(lockDirectory, { recursive: true, mode: 448 });
1818
- const handle = await open(ownPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 384);
2959
+ const handle = await open(ownPath, constants3.O_CREAT | constants3.O_EXCL | constants3.O_WRONLY, 384);
1819
2960
  try {
1820
2961
  await handle.writeFile(`${JSON.stringify(metadata)}
1821
2962
  `, "utf8");
@@ -1884,7 +3025,7 @@ function debugTargetIdentity(target) {
1884
3025
  }
1885
3026
  function defaultStateRoot() {
1886
3027
  const configured = process.env["CF_INSPECTOR_STATE_DIR"]?.trim();
1887
- return configured === void 0 || configured.length === 0 ? join(homedir(), ".saptools") : configured;
3028
+ return configured === void 0 || configured.length === 0 ? join7(homedir3(), ".saptools") : configured;
1888
3029
  }
1889
3030
  async function findLiveContenders(lockDirectory, key, ownPath, isProcessAlive, getProcessStart) {
1890
3031
  const prefix = `${key}.`;
@@ -1894,7 +3035,7 @@ async function findLiveContenders(lockDirectory, key, ownPath, isProcessAlive, g
1894
3035
  if (!name.startsWith(prefix) || !name.endsWith(LOCK_FILE_SUFFIX)) {
1895
3036
  continue;
1896
3037
  }
1897
- const path = join(lockDirectory, name);
3038
+ const path = join7(lockDirectory, name);
1898
3039
  if (path === ownPath) {
1899
3040
  continue;
1900
3041
  }
@@ -1925,7 +3066,7 @@ async function findLiveContenders(lockDirectory, key, ownPath, isProcessAlive, g
1925
3066
  async function readLockMetadata(path) {
1926
3067
  try {
1927
3068
  const parsed = JSON.parse(await readFile(path, "utf8"));
1928
- if (!isRecord2(parsed)) {
3069
+ if (!isRecord3(parsed)) {
1929
3070
  return void 0;
1930
3071
  }
1931
3072
  const pid = parsed["pid"];
@@ -2016,7 +3157,7 @@ function normalizeHost(host) {
2016
3157
  const normalized = host.trim().toLowerCase();
2017
3158
  return normalized === "localhost" || normalized === "::1" ? "127.0.0.1" : normalized;
2018
3159
  }
2019
- function isRecord2(value) {
3160
+ function isRecord3(value) {
2020
3161
  return typeof value === "object" && value !== null;
2021
3162
  }
2022
3163
  function isNodeError(error, code) {
@@ -5404,27 +6545,27 @@ var collectStrings = (value, prev = []) => [
5404
6545
  value
5405
6546
  ];
5406
6547
  var READY_EVENT_DESCRIPTION = "Emit a versioned breakpoint-armed JSON event on stderr after every current isolate is armed";
5407
- function readPackageVersion() {
5408
- let current = dirname(fileURLToPath(import.meta.url));
5409
- for (let depth = 0; depth < 4; depth += 1) {
5410
- const candidate = join2(current, "package.json");
5411
- try {
5412
- const parsed = JSON.parse(readFileSync(candidate, "utf8"));
5413
- if (typeof parsed === "object" && parsed !== null) {
5414
- const record = parsed;
5415
- if (record["name"] === "@saptools/cf-inspector" && typeof record["version"] === "string") {
5416
- return record["version"];
5417
- }
5418
- }
5419
- } catch {
5420
- }
5421
- current = dirname(current);
6548
+ function writeUpdateNotice(argv, line) {
6549
+ if (argv.includes("--emit-ready-event")) {
6550
+ return;
5422
6551
  }
5423
- throw new Error("Unable to read @saptools/cf-inspector package version");
6552
+ process13.stderr.write(`cf-inspector: ${line}
6553
+ `);
5424
6554
  }
5425
6555
  async function main(argv) {
6556
+ const { version } = readPackageMetadata(import.meta.url, "@saptools/cf-inspector");
6557
+ const selfUpdate = {
6558
+ packageName: "@saptools/cf-inspector",
6559
+ currentVersion: version,
6560
+ binName: "cf-inspector",
6561
+ envPrefix: "CF_INSPECTOR",
6562
+ notice: (line) => {
6563
+ writeUpdateNotice(argv, line);
6564
+ }
6565
+ };
5426
6566
  const program = new Command();
5427
- program.name("cf-inspector").version(readPackageVersion()).description("Drive a Node.js inspector from the command line \u2014 set breakpoints, capture snapshots, evaluate expressions");
6567
+ program.name("cf-inspector").version(version).description("Drive a Node.js inspector from the command line \u2014 set breakpoints, capture snapshots, evaluate expressions");
6568
+ attachSelfUpdate(program, selfUpdate);
5428
6569
  registerSnapshot(program);
5429
6570
  registerLog(program);
5430
6571
  registerWatch(program);
@@ -5434,6 +6575,7 @@ async function main(argv) {
5434
6575
  registerListScripts(program);
5435
6576
  registerListTargets(program);
5436
6577
  registerAttach(program);
6578
+ registerSelfUpdateCommand(program, selfUpdate);
5437
6579
  await program.parseAsync([...argv]);
5438
6580
  }
5439
6581
  function registerCheckBreakpoint(program) {
@@ -5517,20 +6659,20 @@ function registerAttach(program) {
5517
6659
  // src/cli.ts
5518
6660
  init_types();
5519
6661
  try {
5520
- await main(process13.argv);
6662
+ await main(process14.argv);
5521
6663
  } catch (err) {
5522
6664
  if (err instanceof CfInspectorError) {
5523
- process13.stderr.write(`Error [${err.code}]: ${err.message}
6665
+ process14.stderr.write(`Error [${err.code}]: ${err.message}
5524
6666
  `);
5525
6667
  if (err.detail !== void 0) {
5526
- process13.stderr.write(` detail: ${err.detail}
6668
+ process14.stderr.write(` detail: ${err.detail}
5527
6669
  `);
5528
6670
  }
5529
- process13.exit(1);
6671
+ process14.exit(1);
5530
6672
  }
5531
6673
  const message = err instanceof Error ? err.message : String(err);
5532
- process13.stderr.write(`Error: ${message}
6674
+ process14.stderr.write(`Error: ${message}
5533
6675
  `);
5534
- process13.exit(1);
6676
+ process14.exit(1);
5535
6677
  }
5536
6678
  //# sourceMappingURL=cli.js.map