run402 3.8.0 → 3.8.1

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.
@@ -0,0 +1,722 @@
1
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname, join, normalize, resolve, sep } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { configDir } from "./config.mjs";
5
+
6
+ export const UPDATE_CHECK_CACHE_VERSION = 1;
7
+ export const UPDATE_CHECK_TTL_MS = 24 * 60 * 60 * 1000;
8
+ export const UPDATE_CHECK_TIMEOUT_MS = 1500;
9
+ export const UPDATE_NOTICE_TYPE = "cli.update_available";
10
+
11
+ const CLI_PACKAGE_URL = new URL("../package.json", import.meta.url);
12
+ const TRUE_VALUES = new Set(["1", "true", "yes", "on"]);
13
+ const FALSE_VALUES = new Set(["0", "false", "no", "off"]);
14
+ const KNOWN_PACKAGE_MANAGERS = new Set(["npm", "pnpm", "yarn", "bun"]);
15
+
16
+ export function currentRun402Version({ packageUrl = CLI_PACKAGE_URL } = {}) {
17
+ try {
18
+ const pkg = JSON.parse(readFileSync(packageUrl, "utf8"));
19
+ return typeof pkg.version === "string" ? pkg.version : "0.0.0";
20
+ } catch {
21
+ return "0.0.0";
22
+ }
23
+ }
24
+
25
+ export function updateCachePath({ dir = configDir() } = {}) {
26
+ return join(dir, "cli-update-check.json");
27
+ }
28
+
29
+ export function readUpdateCache({ path = updateCachePath() } = {}) {
30
+ try {
31
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
32
+ if (!parsed || typeof parsed !== "object" || parsed.schema_version !== UPDATE_CHECK_CACHE_VERSION) {
33
+ return null;
34
+ }
35
+ if (typeof parsed.checked_at !== "string") return null;
36
+ return parsed;
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
41
+
42
+ export function writeUpdateCache(record, { path = updateCachePath() } = {}) {
43
+ try {
44
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
45
+ writeFileSync(path, JSON.stringify({
46
+ schema_version: UPDATE_CHECK_CACHE_VERSION,
47
+ package: "run402",
48
+ ...record,
49
+ }, null, 2) + "\n", { mode: 0o600 });
50
+ return true;
51
+ } catch {
52
+ return false;
53
+ }
54
+ }
55
+
56
+ export function isUpdateOptedOut(env = process.env) {
57
+ return envFlag(env.RUN402_NO_UPDATE_CHECK) === true;
58
+ }
59
+
60
+ export function shouldRunLiveUpdateCheck({
61
+ env = process.env,
62
+ stderrIsTTY = process.stderr?.isTTY,
63
+ stdoutIsTTY = process.stdout?.isTTY,
64
+ explicit = false,
65
+ } = {}) {
66
+ if (isUpdateOptedOut(env)) return false;
67
+ if (explicit) return true;
68
+ if (envFlag(env.RUN402_UPDATE_CHECK) === true) return true;
69
+ if (isCi(env)) return false;
70
+ return Boolean(stderrIsTTY || stdoutIsTTY);
71
+ }
72
+
73
+ export function isCacheFresh(cache, { now = Date.now(), ttlMs = UPDATE_CHECK_TTL_MS } = {}) {
74
+ if (!cache?.checked_at) return false;
75
+ const checked = Date.parse(cache.checked_at);
76
+ if (!Number.isFinite(checked)) return false;
77
+ return now - checked >= 0 && now - checked < ttlMs;
78
+ }
79
+
80
+ export function npmRegistryBase(env = process.env) {
81
+ const value = env.RUN402_NPM_REGISTRY || env.npm_config_registry || "https://registry.npmjs.org/";
82
+ try {
83
+ const url = new URL(value);
84
+ if (!url.pathname.endsWith("/")) url.pathname += "/";
85
+ return url.toString();
86
+ } catch {
87
+ return "https://registry.npmjs.org/";
88
+ }
89
+ }
90
+
91
+ export async function fetchLatestRun402Version({
92
+ env = process.env,
93
+ fetchImpl = globalThis.fetch,
94
+ timeoutMs = UPDATE_CHECK_TIMEOUT_MS,
95
+ registry = npmRegistryBase(env),
96
+ } = {}) {
97
+ if (typeof fetchImpl !== "function") {
98
+ throw new Error("fetch is unavailable");
99
+ }
100
+ const controller = new AbortController();
101
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
102
+ try {
103
+ const res = await fetchImpl(new URL("run402/latest", registry), {
104
+ headers: { accept: "application/json" },
105
+ signal: controller.signal,
106
+ });
107
+ if (!res.ok) throw new Error(`npm registry returned HTTP ${res.status}`);
108
+ const body = await res.json();
109
+ const latest = body?.version;
110
+ if (!isValidSemver(latest)) {
111
+ throw new Error("npm registry response did not contain a valid latest version");
112
+ }
113
+ return { latest, registry, checked_at: new Date().toISOString(), source: "registry" };
114
+ } finally {
115
+ clearTimeout(timer);
116
+ }
117
+ }
118
+
119
+ export async function refreshUpdateCheck({
120
+ env = process.env,
121
+ fetchImpl = globalThis.fetch,
122
+ current = currentRun402Version(),
123
+ cachePath = updateCachePath(),
124
+ timeoutMs = UPDATE_CHECK_TIMEOUT_MS,
125
+ registry = npmRegistryBase(env),
126
+ } = {}) {
127
+ try {
128
+ const latest = await fetchLatestRun402Version({ env, fetchImpl, timeoutMs, registry });
129
+ const record = {
130
+ current,
131
+ latest: latest.latest,
132
+ checked_at: latest.checked_at,
133
+ source: latest.source,
134
+ registry: latest.registry,
135
+ error: null,
136
+ };
137
+ writeUpdateCache(record, { path: cachePath });
138
+ return { ok: true, ...record };
139
+ } catch (err) {
140
+ const record = {
141
+ current,
142
+ latest: null,
143
+ checked_at: new Date().toISOString(),
144
+ source: "registry",
145
+ registry,
146
+ error: {
147
+ code: errorCode(err),
148
+ message: err instanceof Error ? err.message : String(err),
149
+ },
150
+ };
151
+ writeUpdateCache(record, { path: cachePath });
152
+ return { ok: false, ...record };
153
+ }
154
+ }
155
+
156
+ export function createUpdateCheckScheduler({
157
+ cwd = process.cwd(),
158
+ env = process.env,
159
+ argv = process.argv,
160
+ execPath = process.argv[1],
161
+ current = currentRun402Version(),
162
+ now = Date.now(),
163
+ cachePath = updateCachePath(),
164
+ fetchImpl = globalThis.fetch,
165
+ stderrIsTTY = process.stderr?.isTTY,
166
+ stdoutIsTTY = process.stdout?.isTTY,
167
+ command = ["run402", ...argv.slice(2)],
168
+ } = {}) {
169
+ const cache = isUpdateOptedOut(env) ? null : readUpdateCache({ path: cachePath });
170
+ const cachedNotice = cache ? updateNoticeFromRecord(cache, {
171
+ cwd,
172
+ env,
173
+ argv,
174
+ execPath,
175
+ current,
176
+ now,
177
+ command,
178
+ source: "cache",
179
+ }) : null;
180
+
181
+ let liveCompleted = false;
182
+ let liveNotice = null;
183
+ let livePromise = null;
184
+ const shouldRefresh = !isUpdateOptedOut(env) &&
185
+ !isCacheFresh(cache, { now }) &&
186
+ shouldRunLiveUpdateCheck({ env, stderrIsTTY, stdoutIsTTY });
187
+
188
+ if (shouldRefresh) {
189
+ livePromise = refreshUpdateCheck({ env, fetchImpl, current, cachePath })
190
+ .then((record) => {
191
+ liveNotice = updateNoticeFromRecord(record, {
192
+ cwd,
193
+ env,
194
+ argv,
195
+ execPath,
196
+ current,
197
+ now: Date.now(),
198
+ command,
199
+ source: "registry",
200
+ });
201
+ liveCompleted = true;
202
+ return liveNotice;
203
+ })
204
+ .catch(() => {
205
+ liveCompleted = true;
206
+ return null;
207
+ });
208
+ }
209
+
210
+ return {
211
+ cache,
212
+ cachedNotice,
213
+ livePromise,
214
+ getCompletedLiveNotice() {
215
+ return liveCompleted ? liveNotice : null;
216
+ },
217
+ };
218
+ }
219
+
220
+ export async function doctorUpdateCheck({
221
+ refresh = false,
222
+ cwd = process.cwd(),
223
+ env = process.env,
224
+ argv = process.argv,
225
+ execPath = process.argv[1],
226
+ current = currentRun402Version(),
227
+ cachePath = updateCachePath(),
228
+ fetchImpl = globalThis.fetch,
229
+ now = Date.now(),
230
+ } = {}) {
231
+ const install = detectInstallContext({ cwd, env, argv, execPath });
232
+ if (isUpdateOptedOut(env)) {
233
+ return {
234
+ name: "cli_update",
235
+ status: "skipped",
236
+ value: {
237
+ current,
238
+ latest: null,
239
+ install_context: install.kind,
240
+ confidence: install.confidence,
241
+ package_manager: install.package_manager,
242
+ cache: { path: cachePath, fresh: false, source: "disabled" },
243
+ },
244
+ hint: "RUN402_NO_UPDATE_CHECK=1 is set.",
245
+ };
246
+ }
247
+
248
+ let record = readUpdateCache({ path: cachePath });
249
+ if (refresh) {
250
+ record = await refreshUpdateCheck({ env, fetchImpl, current, cachePath });
251
+ }
252
+
253
+ if (!record) {
254
+ return {
255
+ name: "cli_update",
256
+ status: "unknown",
257
+ value: {
258
+ current,
259
+ latest: null,
260
+ install_context: install.kind,
261
+ confidence: install.confidence,
262
+ package_manager: install.package_manager,
263
+ cache: { path: cachePath, fresh: false, source: "none" },
264
+ },
265
+ hint: "No cached npm version check yet. Run 'run402 doctor --refresh' to check now.",
266
+ };
267
+ }
268
+
269
+ const freshness = isCacheFresh(record, { now });
270
+ const notice = updateNoticeFromRecord(record, {
271
+ cwd,
272
+ env,
273
+ argv,
274
+ execPath,
275
+ current,
276
+ now,
277
+ command: ["run402", "doctor"],
278
+ source: record.source ?? "cache",
279
+ });
280
+ const comparison = compareSemver(current, record.latest);
281
+ const stale = notice !== null;
282
+ const status = stale ? "warning" : record.latest && comparison !== null ? "ok" : "unknown";
283
+ return {
284
+ name: "cli_update",
285
+ status,
286
+ value: {
287
+ current,
288
+ latest: record.latest ?? null,
289
+ checked_at: record.checked_at,
290
+ install_context: install.kind,
291
+ confidence: install.confidence,
292
+ package_manager: install.package_manager,
293
+ cache: {
294
+ path: cachePath,
295
+ fresh: freshness,
296
+ source: record.source ?? "cache",
297
+ error: record.error ?? null,
298
+ },
299
+ ...(stale ? { next_actions: notice.next_actions } : {}),
300
+ },
301
+ ...(stale
302
+ ? { hint: `A newer run402 CLI is available (${current} -> ${record.latest}).` }
303
+ : record.error
304
+ ? { hint: "Could not check npm for the latest run402 version; other doctor checks still ran." }
305
+ : {}),
306
+ };
307
+ }
308
+
309
+ export function updateNoticeFromRecord(record, {
310
+ cwd = process.cwd(),
311
+ env = process.env,
312
+ argv = process.argv,
313
+ execPath = process.argv[1],
314
+ current = currentRun402Version(),
315
+ now = Date.now(),
316
+ command = ["run402", ...argv.slice(2)],
317
+ source = "cache",
318
+ } = {}) {
319
+ if (!record || isUpdateOptedOut(env)) return null;
320
+ const latest = record.latest;
321
+ if (!isValidSemver(current) || !isValidSemver(latest)) return null;
322
+ const comparison = compareSemver(current, latest);
323
+ if (comparison === null || comparison >= 0) return null;
324
+ const install = detectInstallContext({ cwd, env, argv, execPath });
325
+ const next_actions = upgradeNextActions({ install, cwd, command });
326
+ return {
327
+ type: UPDATE_NOTICE_TYPE,
328
+ schema_version: 1,
329
+ severity: "warning",
330
+ package: "run402",
331
+ current,
332
+ latest,
333
+ install_context: install.kind,
334
+ confidence: install.confidence,
335
+ checked_at: record.checked_at ?? new Date(now).toISOString(),
336
+ source,
337
+ next_actions,
338
+ };
339
+ }
340
+
341
+ export function emitUpdateNotice(notice, {
342
+ jsonStream = false,
343
+ quiet = false,
344
+ stdout = console.log,
345
+ stderr = console.error,
346
+ } = {}) {
347
+ if (!notice) return false;
348
+ if (jsonStream) {
349
+ stdout(JSON.stringify(notice));
350
+ return true;
351
+ }
352
+ if (quiet) return false;
353
+ stderr(JSON.stringify(notice));
354
+ return true;
355
+ }
356
+
357
+ export function compareSemver(a, b) {
358
+ const left = parseSemver(a);
359
+ const right = parseSemver(b);
360
+ if (!left || !right) return null;
361
+ for (const key of ["major", "minor", "patch"]) {
362
+ if (left[key] < right[key]) return -1;
363
+ if (left[key] > right[key]) return 1;
364
+ }
365
+ if (left.prerelease === right.prerelease) return 0;
366
+ if (!left.prerelease && right.prerelease) return 1;
367
+ if (left.prerelease && !right.prerelease) return -1;
368
+ return comparePrerelease(left.prerelease, right.prerelease);
369
+ }
370
+
371
+ export function isValidSemver(value) {
372
+ return parseSemver(value) !== null;
373
+ }
374
+
375
+ export function detectInstallContext({
376
+ cwd = process.cwd(),
377
+ env = process.env,
378
+ argv = process.argv,
379
+ execPath = process.argv[1],
380
+ platform = process.platform,
381
+ } = {}) {
382
+ const resolvedCwd = safeResolve(cwd);
383
+ const executable = normalizePath(execPath || env._ || "");
384
+ const packageInfos = findPackageAncestry(resolvedCwd);
385
+ const nearestPackageInfo = packageInfos[0] ?? null;
386
+ const run402PackageInfo = packageInfos.find((info) => declaresRun402(info.pkg)) ?? null;
387
+ const workspacePackageInfo = packageInfos.find((info) => hasWorkspaces(info.pkg)) ?? null;
388
+ const packageInfo = run402PackageInfo ?? workspacePackageInfo ?? nearestPackageInfo;
389
+ const packageManager = detectPackageManager({ cwd: resolvedCwd, env, packageInfo });
390
+ const command = detectExecCommand({ env, argv, executable });
391
+ const run402Declared = Boolean(run402PackageInfo);
392
+ const projectRoot = packageInfo?.dir ? normalizePath(packageInfo.dir) : null;
393
+ const executableUnderProject = projectRoot ? isPathInside(executable, projectRoot) : false;
394
+ const localNodeModules = executableUnderProject &&
395
+ (containsPathSegment(executable, "node_modules") || command === "yarn_pnp");
396
+
397
+ if (command === "npx" || command === "npm_exec" || command === "bunx") {
398
+ return {
399
+ kind: "ephemeral_exec",
400
+ confidence: command === "npx" || command === "bunx" ? "high" : "medium",
401
+ package_manager: command === "bunx" ? "bun" : packageManager.name,
402
+ cwd: resolvedCwd,
403
+ package_root: packageInfo?.dir ?? nearestPackageInfo?.dir ?? null,
404
+ reasons: [command],
405
+ };
406
+ }
407
+
408
+ if (localNodeModules || (command === "yarn_pnp" && run402Declared)) {
409
+ return {
410
+ kind: "local_project",
411
+ confidence: run402Declared || localNodeModules ? "high" : "medium",
412
+ package_manager: packageManager.name,
413
+ cwd: packageInfo?.dir ?? resolvedCwd,
414
+ package_root: packageInfo?.dir ?? nearestPackageInfo?.dir ?? null,
415
+ reasons: [
416
+ localNodeModules ? "project_node_modules" : "yarn_pnp",
417
+ ...(run402Declared ? ["package_declares_run402"] : []),
418
+ ...(workspacePackageInfo && workspacePackageInfo !== nearestPackageInfo ? ["workspace_root"] : []),
419
+ ],
420
+ };
421
+ }
422
+
423
+ if (looksLikeGlobalRun402Path(executable, env, platform)) {
424
+ return {
425
+ kind: "global_npm",
426
+ confidence: "high",
427
+ package_manager: "npm",
428
+ cwd: resolvedCwd,
429
+ package_root: packageInfo?.dir ?? nearestPackageInfo?.dir ?? null,
430
+ reasons: ["global_node_modules"],
431
+ };
432
+ }
433
+
434
+ if (containsPathSegment(executable, ".pnpm") && projectRoot && executableUnderProject) {
435
+ return {
436
+ kind: "local_project",
437
+ confidence: "medium",
438
+ package_manager: "pnpm",
439
+ cwd: packageInfo?.dir ?? resolvedCwd,
440
+ package_root: packageInfo?.dir ?? nearestPackageInfo?.dir ?? null,
441
+ reasons: ["pnpm_store_layout"],
442
+ };
443
+ }
444
+
445
+ if (isPackageManagerShim(executable)) {
446
+ return {
447
+ kind: "package_manager_shim",
448
+ confidence: "low",
449
+ package_manager: packageManager.name,
450
+ cwd: packageInfo?.dir ?? resolvedCwd,
451
+ package_root: packageInfo?.dir ?? nearestPackageInfo?.dir ?? null,
452
+ reasons: ["shim_path"],
453
+ };
454
+ }
455
+
456
+ return {
457
+ kind: "custom_path",
458
+ confidence: executable ? "low" : "medium",
459
+ package_manager: packageManager.name,
460
+ cwd: packageInfo?.dir ?? resolvedCwd,
461
+ package_root: packageInfo?.dir ?? nearestPackageInfo?.dir ?? null,
462
+ reasons: executable ? ["unclassified_executable"] : ["missing_executable_path"],
463
+ };
464
+ }
465
+
466
+ export function detectPackageManager({ cwd = process.cwd(), env = process.env, packageInfo = findNearestPackageInfo(cwd) } = {}) {
467
+ const fromPackageManager = packageInfo?.pkg?.packageManager;
468
+ if (typeof fromPackageManager === "string") {
469
+ const name = fromPackageManager.split("@")[0];
470
+ if (KNOWN_PACKAGE_MANAGERS.has(name)) return { name, source: "packageManager", confidence: "high" };
471
+ }
472
+
473
+ const userAgent = env.npm_config_user_agent;
474
+ if (typeof userAgent === "string") {
475
+ const name = userAgent.split(/[ /]/)[0];
476
+ if (KNOWN_PACKAGE_MANAGERS.has(name)) return { name, source: "npm_config_user_agent", confidence: "medium" };
477
+ }
478
+
479
+ const lockfileManager = detectLockfileManager(packageInfo?.dir ?? cwd);
480
+ if (lockfileManager) return { name: lockfileManager, source: "lockfile", confidence: "medium" };
481
+
482
+ return { name: "npm", source: "default", confidence: "low" };
483
+ }
484
+
485
+ export function upgradeNextActions({ install, cwd = process.cwd(), command = ["run402"] } = {}) {
486
+ const resolvedCwd = install?.cwd ?? safeResolve(cwd);
487
+ const context = install?.kind ?? "custom_path";
488
+ const pm = install?.package_manager ?? "npm";
489
+ if (context === "local_project") {
490
+ const argv = localUpgradeArgv(pm);
491
+ return [upgradeAction({
492
+ argv,
493
+ cwd: resolvedCwd,
494
+ install,
495
+ mutates_project: true,
496
+ mutates_global_install: false,
497
+ })];
498
+ }
499
+ if (context === "global_npm") {
500
+ return [upgradeAction({
501
+ argv: ["npm", "install", "-g", "run402@latest"],
502
+ cwd: resolvedCwd,
503
+ install,
504
+ mutates_project: false,
505
+ mutates_global_install: true,
506
+ })];
507
+ }
508
+ if (context === "ephemeral_exec") {
509
+ const rerun = command?.[0] === "run402" ? command.slice(1) : command ?? [];
510
+ const launcher = pm === "bun" ? ["bunx", "run402@latest"] : ["npx", "-y", "run402@latest"];
511
+ return [upgradeAction({
512
+ argv: [...launcher, ...rerun],
513
+ cwd: resolvedCwd,
514
+ install,
515
+ mutates_project: false,
516
+ mutates_global_install: false,
517
+ })];
518
+ }
519
+ return [upgradeAction({
520
+ argv: ["run402", "doctor", "--refresh"],
521
+ cwd: resolvedCwd,
522
+ install,
523
+ mutates_project: false,
524
+ mutates_global_install: false,
525
+ why: "The current run402 executable path is custom or ambiguous; doctor can show more context.",
526
+ })];
527
+ }
528
+
529
+ function upgradeAction({ argv, cwd, install, mutates_project, mutates_global_install, why }) {
530
+ return {
531
+ type: "upgrade_client",
532
+ package: "run402",
533
+ target: "latest",
534
+ command: shellCommand(argv),
535
+ argv,
536
+ cwd,
537
+ install_context: install?.kind ?? "custom_path",
538
+ package_manager: install?.package_manager ?? null,
539
+ confidence: install?.confidence ?? "low",
540
+ mutates_project,
541
+ mutates_global_install,
542
+ ...(why ? { why } : {}),
543
+ };
544
+ }
545
+
546
+ function localUpgradeArgv(pm) {
547
+ if (pm === "pnpm") return ["pnpm", "add", "-D", "run402@latest"];
548
+ if (pm === "yarn") return ["yarn", "add", "-D", "run402@latest"];
549
+ if (pm === "bun") return ["bun", "add", "-d", "run402@latest"];
550
+ return ["npm", "install", "-D", "run402@latest"];
551
+ }
552
+
553
+ function shellCommand(argv) {
554
+ return argv.map((part) => /^[A-Za-z0-9_./:=@+-]+$/.test(part) ? part : JSON.stringify(part)).join(" ");
555
+ }
556
+
557
+ function parseSemver(value) {
558
+ if (typeof value !== "string") return null;
559
+ const match = value.trim().match(/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/);
560
+ if (!match) return null;
561
+ return {
562
+ major: Number(match[1]),
563
+ minor: Number(match[2]),
564
+ patch: Number(match[3]),
565
+ prerelease: match[4] ?? "",
566
+ };
567
+ }
568
+
569
+ function comparePrerelease(a, b) {
570
+ const left = String(a).split(".");
571
+ const right = String(b).split(".");
572
+ const max = Math.max(left.length, right.length);
573
+ for (let i = 0; i < max; i += 1) {
574
+ const l = left[i];
575
+ const r = right[i];
576
+ if (l === undefined) return -1;
577
+ if (r === undefined) return 1;
578
+ const ln = /^\d+$/.test(l) ? Number(l) : null;
579
+ const rn = /^\d+$/.test(r) ? Number(r) : null;
580
+ if (ln !== null && rn !== null) {
581
+ if (ln < rn) return -1;
582
+ if (ln > rn) return 1;
583
+ } else if (ln !== null) {
584
+ return -1;
585
+ } else if (rn !== null) {
586
+ return 1;
587
+ } else {
588
+ const cmp = l.localeCompare(r);
589
+ if (cmp !== 0) return cmp < 0 ? -1 : 1;
590
+ }
591
+ }
592
+ return 0;
593
+ }
594
+
595
+ function detectExecCommand({ env, argv, executable }) {
596
+ const joinedArgv = Array.isArray(argv) ? argv.join(" ") : "";
597
+ if (containsPathSegment(executable, "_npx") || /\bnpx\b/.test(joinedArgv) || env.npm_config_npx === "true") {
598
+ return "npx";
599
+ }
600
+ if (env.npm_command === "exec" || env.npm_lifecycle_event === "npx") return "npm_exec";
601
+ if (containsPathSegment(executable, "bunx") || /\bbunx\b/.test(joinedArgv) || env.BUN_INSTALL) {
602
+ if (env.npm_command === "exec" || containsPathSegment(executable, "bunx")) return "bunx";
603
+ }
604
+ if (typeof env.NODE_OPTIONS === "string" && env.NODE_OPTIONS.includes(".pnp.")) return "yarn_pnp";
605
+ return "direct";
606
+ }
607
+
608
+ function findNearestPackageInfo(startDir) {
609
+ return findPackageAncestry(startDir)[0] ?? null;
610
+ }
611
+
612
+ function findPackageAncestry(startDir) {
613
+ const packages = [];
614
+ let current = safeResolve(startDir);
615
+ for (;;) {
616
+ const pkgPath = join(current, "package.json");
617
+ try {
618
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
619
+ packages.push({ dir: current, pkg });
620
+ } catch {
621
+ // keep walking
622
+ }
623
+ const parent = dirname(current);
624
+ if (parent === current) return packages;
625
+ current = parent;
626
+ }
627
+ }
628
+
629
+ function detectLockfileManager(dir) {
630
+ const files = [
631
+ ["pnpm-lock.yaml", "pnpm"],
632
+ ["yarn.lock", "yarn"],
633
+ ["bun.lock", "bun"],
634
+ ["bun.lockb", "bun"],
635
+ ["package-lock.json", "npm"],
636
+ ["npm-shrinkwrap.json", "npm"],
637
+ ];
638
+ for (const [file, pm] of files) {
639
+ try {
640
+ readFileSync(join(dir, file));
641
+ return pm;
642
+ } catch {
643
+ // ignore
644
+ }
645
+ }
646
+ return null;
647
+ }
648
+
649
+ function declaresRun402(pkg) {
650
+ if (!pkg || typeof pkg !== "object") return false;
651
+ return Boolean(
652
+ pkg.dependencies?.run402 ||
653
+ pkg.devDependencies?.run402 ||
654
+ pkg.optionalDependencies?.run402 ||
655
+ pkg.peerDependencies?.run402,
656
+ );
657
+ }
658
+
659
+ function hasWorkspaces(pkg) {
660
+ if (!pkg || typeof pkg !== "object") return false;
661
+ return Array.isArray(pkg.workspaces) || Array.isArray(pkg.workspaces?.packages);
662
+ }
663
+
664
+ function looksLikeGlobalRun402Path(executable, env, platform) {
665
+ if (!executable) return false;
666
+ const prefix = env.npm_config_prefix ? normalizePath(env.npm_config_prefix) : "";
667
+ if (prefix && isPathInside(executable, prefix) && containsPathSegment(executable, "run402")) return true;
668
+ if (platform === "win32" && /[\\/]npm[\\/]node_modules[\\/]run402[\\/]/i.test(executable)) return true;
669
+ return /[\\/]lib[\\/]node_modules[\\/]run402[\\/]/.test(executable);
670
+ }
671
+
672
+ function isPackageManagerShim(executable) {
673
+ return /[\\/]node_modules[\\/]\.bin[\\/]run402(?:\.cmd|\.ps1)?$/i.test(executable) ||
674
+ /[\\/]bin[\\/]run402(?:\.cmd|\.ps1)?$/i.test(executable) ||
675
+ executable.endsWith(`${sep}run402.cmd`) ||
676
+ executable.endsWith(`${sep}run402.ps1`);
677
+ }
678
+
679
+ function containsPathSegment(path, segment) {
680
+ if (!path) return false;
681
+ const normalized = normalizePath(path).toLowerCase();
682
+ return normalized.split(/[\\/]+/).includes(segment.toLowerCase());
683
+ }
684
+
685
+ function normalizePath(value) {
686
+ if (!value || typeof value !== "string") return "";
687
+ return normalize(value.replace(/\\/g, sep));
688
+ }
689
+
690
+ function safeResolve(value) {
691
+ try {
692
+ return resolve(value);
693
+ } catch {
694
+ return process.cwd();
695
+ }
696
+ }
697
+
698
+ function isPathInside(child, parent) {
699
+ const normalizedChild = normalizePath(child);
700
+ const normalizedParent = normalizePath(parent);
701
+ return normalizedChild === normalizedParent || normalizedChild.startsWith(normalizedParent.endsWith(sep) ? normalizedParent : normalizedParent + sep);
702
+ }
703
+
704
+ function envFlag(value) {
705
+ if (typeof value !== "string") return null;
706
+ const normalized = value.trim().toLowerCase();
707
+ if (TRUE_VALUES.has(normalized)) return true;
708
+ if (FALSE_VALUES.has(normalized)) return false;
709
+ return null;
710
+ }
711
+
712
+ function isCi(env) {
713
+ return env.CI === "true" || env.GITHUB_ACTIONS === "true" || env.BUILDKITE === "true" || env.GITLAB_CI === "true";
714
+ }
715
+
716
+ function errorCode(err) {
717
+ if (err?.name === "AbortError") return "TIMEOUT";
718
+ if (err instanceof Error && /valid latest version/.test(err.message)) return "INVALID_LATEST";
719
+ return "NETWORK_ERROR";
720
+ }
721
+
722
+ export const __filename = fileURLToPath(import.meta.url);