@lunora/cli 1.0.0-alpha.67 → 1.0.0-alpha.69

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/index.d.mts CHANGED
@@ -483,8 +483,12 @@ interface DevCommandOptions {
483
483
  ensureExample?: typeof ensureDevVarsExample;
484
484
  /** Injection seam for tests — defaults to the real empty-secret/admin-token filler. */
485
485
  fillSecrets?: typeof fillDevSecrets;
486
+ /** Injection seam for tests — defaults to the real free-port probe ({@link findAvailablePort}). */
487
+ findFreePort?: (preferred: number) => Promise<number>;
486
488
  /** Dev flavor override (tests / callers that already detected it) — defaults to {@link detectDevFlavor}. */
487
489
  flavor?: DevFlavor;
490
+ /** Injection seam for tests — defaults to the real IPv6-loopback probe ({@link hasIpv6Loopback}). */
491
+ hasIpv6Loopback?: () => boolean;
488
492
  logger: Logger;
489
493
  /** Injection seam for tests — defaults to the real remote-config materializer. */
490
494
  materializeRemote?: typeof materializeRemoteWranglerConfig;
@@ -531,6 +535,12 @@ interface DevCommandPlan {
531
535
  * regardless.
532
536
  */
533
537
  frameworkHint?: string;
538
+ /**
539
+ * True when `wrangler dev` was given `--ip 127.0.0.1` because the host has no
540
+ * IPv6 loopback (`::1`) — surfaced so the dev loop can note the rebind.
541
+ * Always `false` for the vite flavor (the plugin owns its own bind).
542
+ */
543
+ ipv4LoopbackForced: boolean;
534
544
  /** The remote-binding decision: which D1/KV/R2 bindings hit the deployed worker. */
535
545
  remote: DevRemotePlan;
536
546
  studioEnabled: boolean;
package/dist/index.d.ts CHANGED
@@ -483,8 +483,12 @@ interface DevCommandOptions {
483
483
  ensureExample?: typeof ensureDevVarsExample;
484
484
  /** Injection seam for tests — defaults to the real empty-secret/admin-token filler. */
485
485
  fillSecrets?: typeof fillDevSecrets;
486
+ /** Injection seam for tests — defaults to the real free-port probe ({@link findAvailablePort}). */
487
+ findFreePort?: (preferred: number) => Promise<number>;
486
488
  /** Dev flavor override (tests / callers that already detected it) — defaults to {@link detectDevFlavor}. */
487
489
  flavor?: DevFlavor;
490
+ /** Injection seam for tests — defaults to the real IPv6-loopback probe ({@link hasIpv6Loopback}). */
491
+ hasIpv6Loopback?: () => boolean;
488
492
  logger: Logger;
489
493
  /** Injection seam for tests — defaults to the real remote-config materializer. */
490
494
  materializeRemote?: typeof materializeRemoteWranglerConfig;
@@ -531,6 +535,12 @@ interface DevCommandPlan {
531
535
  * regardless.
532
536
  */
533
537
  frameworkHint?: string;
538
+ /**
539
+ * True when `wrangler dev` was given `--ip 127.0.0.1` because the host has no
540
+ * IPv6 loopback (`::1`) — surfaced so the dev loop can note the rebind.
541
+ * Always `false` for the vite flavor (the plugin owns its own bind).
542
+ */
543
+ ipv4LoopbackForced: boolean;
534
544
  /** The remote-binding decision: which D1/KV/R2 bindings hit the deployed worker. */
535
545
  remote: DevRemotePlan;
536
546
  studioEnabled: boolean;
@@ -1,6 +1,6 @@
1
1
  import { readLinkedProject } from '@lunora/config';
2
2
  import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
3
- import { e as execArgsFor, d as detectPackageManager } from '../packem_shared/detect-package-manager-Cxo6Tpyx.mjs';
3
+ import { e as execArgsFor, d as detectPackageManager } from '../packem_shared/detect-package-manager-DvEthdCw.mjs';
4
4
  import { defaultSpawner } from '../packem_shared/createRecordingSpawner-Cw5Iu73G.mjs';
5
5
 
6
6
  const LOG_FORMATS = /* @__PURE__ */ new Set(["json", "pretty"]);
@@ -2,7 +2,7 @@ import { mkdtempSync, rmSync, existsSync, readdirSync, statSync } from 'node:fs'
2
2
  import { tmpdir } from 'node:os';
3
3
  import { join, relative } from 'node:path';
4
4
  import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
5
- import { e as execArgsFor, d as detectPackageManager } from '../packem_shared/detect-package-manager-Cxo6Tpyx.mjs';
5
+ import { e as execArgsFor, d as detectPackageManager } from '../packem_shared/detect-package-manager-DvEthdCw.mjs';
6
6
  import { defaultSpawner } from '../packem_shared/createRecordingSpawner-Cw5Iu73G.mjs';
7
7
 
8
8
  const walk = (root) => {
@@ -4,7 +4,7 @@ import { runCodegen } from '@lunora/codegen';
4
4
  import { p as parseApiSpec } from '../packem_shared/api-spec-Bx0iKbxA.mjs';
5
5
  import { a as renderCodegenHint } from '../packem_shared/codegen-error-DJG-ghs_.mjs';
6
6
  import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
7
- import { e as execArgsFor, d as detectPackageManager } from '../packem_shared/detect-package-manager-Cxo6Tpyx.mjs';
7
+ import { e as execArgsFor, d as detectPackageManager } from '../packem_shared/detect-package-manager-DvEthdCw.mjs';
8
8
  import { v as validateOutputFormat, i as isJsonFormat, p as printJson, l as loggerForFormat } from '../packem_shared/output-format-B4642rjE.mjs';
9
9
  import { r as runSchemaDriftGate } from '../packem_shared/schema-drift-gate-BtBt0as0.mjs';
10
10
  import { defaultSpawner } from '../packem_shared/createRecordingSpawner-Cw5Iu73G.mjs';
@@ -1,5 +1,5 @@
1
1
  import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
2
- import { e as execArgsFor, d as detectPackageManager } from '../packem_shared/detect-package-manager-Cxo6Tpyx.mjs';
2
+ import { e as execArgsFor, d as detectPackageManager } from '../packem_shared/detect-package-manager-DvEthdCw.mjs';
3
3
  import { i as isDockerAvailable } from '../packem_shared/docker-hMQ97KSQ.mjs';
4
4
  import { defaultSpawner } from '../packem_shared/createRecordingSpawner-Cw5Iu73G.mjs';
5
5
 
@@ -1,5 +1,5 @@
1
1
  import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
2
- import { e as execArgsFor, d as detectPackageManager } from '../packem_shared/detect-package-manager-Cxo6Tpyx.mjs';
2
+ import { e as execArgsFor, d as detectPackageManager } from '../packem_shared/detect-package-manager-DvEthdCw.mjs';
3
3
  import { defaultSpawner } from '../packem_shared/createRecordingSpawner-Cw5Iu73G.mjs';
4
4
 
5
5
  const withEnv = (args, env) => {
@@ -2,9 +2,9 @@ import { writeFileSync, existsSync, readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { DEV_VARS_FILE, DEV_VARS_KEY_PATTERN, generateSecretValue, DEV_VARS_EXAMPLE_FILE, parseDevVariableEntries, isPlaceholderValue, packageNamesFromBindings, inferLunoraBindings, requiredSecrets, isMintableSecretKey } from '@lunora/config';
4
4
  import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
5
- import { d as detectPackageManager, e as execArgsFor } from '../packem_shared/detect-package-manager-Cxo6Tpyx.mjs';
5
+ import { d as detectPackageManager, e as execArgsFor } from '../packem_shared/detect-package-manager-DvEthdCw.mjs';
6
6
  import { defaultSpawner } from '../packem_shared/createRecordingSpawner-Cw5Iu73G.mjs';
7
- import { l as listRemoteSecrets } from '../packem_shared/wrangler-secrets-P2_ZUR-k.mjs';
7
+ import { l as listRemoteSecrets } from '../packem_shared/wrangler-secrets-Dq_Fkbm-.mjs';
8
8
 
9
9
  const NEWLINE_PRESENT = /[\r\n]/u;
10
10
  const UNREPRESENTABLE_PRESENT = /["\\]/u;
@@ -1,16 +1,17 @@
1
1
  import { spawnSync, spawn } from 'node:child_process';
2
- import { detectAgentRules, claimDevServerState, clearDevServerState, readDevServerState, readLiveDevServerState, readProjectDependencyNames, DEV_HANDOFF_ENV, isRecordedProcessCurrent, DEV_LOG_FILE, DEV_LOG_FILE_ENV, DEV_DAEMON_ENV, detectAiAgent, resolveRemoteEnabled, readProjectRemotePreference, detectFramework, inferLunoraBindings, packageNamesFromBindings, ensureDevVarsExample, ensureDevVariables, isInteractive, DEV_VARS_FILE, DEV_VARS_EXAMPLE_FILE, fillDevSecrets, updateDevServerState, claimAgentRulesHint, AGENT_RULES_HINT, discoverContainerInfo, streamContainerLogs, materializeRemoteWranglerConfig, formatLunoraEvent } from '@lunora/config';
2
+ import { detectAgentRules, claimDevServerState, clearDevServerState, readDevServerState, readLiveDevServerState, readProjectDependencyNames, DEV_HANDOFF_ENV, isRecordedProcessCurrent, DEV_LOG_FILE, DEV_LOG_FILE_ENV, DEV_DAEMON_ENV, updateDevServerState, detectAiAgent, resolveRemoteEnabled, readProjectRemotePreference, inferLunoraBindings, packageNamesFromBindings, ensureDevVarsExample, ensureDevVariables, isInteractive, DEV_VARS_FILE, DEV_VARS_EXAMPLE_FILE, fillDevSecrets, claimAgentRulesHint, AGENT_RULES_HINT, findWranglerFile, readWranglerJsonc, detectFramework, discoverContainerInfo, streamContainerLogs, materializeRemoteWranglerConfig, formatLunoraEvent } from '@lunora/config';
3
3
  import { p as parseApiSpec } from '../packem_shared/api-spec-Bx0iKbxA.mjs';
4
4
  import { existsSync, watch, readFileSync, statSync, openSync, readSync, closeSync, mkdirSync } from 'node:fs';
5
5
  import { join, dirname } from 'node:path';
6
6
  import { runCodegen } from '@lunora/codegen';
7
7
  import { r as renderCodegenFailure } from '../packem_shared/codegen-error-DJG-ghs_.mjs';
8
8
  import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
9
- import { d as detectPackageManager, e as execArgsFor, r as runScriptCommand } from '../packem_shared/detect-package-manager-Cxo6Tpyx.mjs';
9
+ import { d as detectPackageManager, e as execArgsFor, r as runScriptCommand } from '../packem_shared/detect-package-manager-DvEthdCw.mjs';
10
+ import { createServer, connect } from 'node:net';
10
11
  import { forceJsonLogging } from '../packem_shared/createLogger-CIWSHrTL.mjs';
12
+ import { networkInterfaces } from 'node:os';
11
13
  import { spawnShellCompat } from '../packem_shared/createRecordingSpawner-Cw5Iu73G.mjs';
12
- import { createServer, request } from 'node:http';
13
- import { connect } from 'node:net';
14
+ import { createServer as createServer$1, request } from 'node:http';
14
15
  import { loadStudioAssets, studioAssetsStamp, renderStudioHtml, resolveAdminToken, SCHEMA_EDIT_ENDPOINT, POLICY_SCAFFOLD_ENDPOINT, SEED_ENDPOINT, serveJsonHandler, isStandaloneModulePath, readStandaloneAsset, assetContentType, handleSchemaEditRequest, handlePolicyScaffoldRequest, handleSeedRequest } from '@lunora/config/studio-host';
15
16
  import { c as createTuiConfirm } from '../packem_shared/tui-prompts-BjEN8XgP.mjs';
16
17
  import { p as printJson } from '../packem_shared/output-format-B4642rjE.mjs';
@@ -75,6 +76,32 @@ const startCodegenWatch = (options) => {
75
76
  };
76
77
  };
77
78
 
79
+ const DEFAULT_PROBE_ATTEMPTS = 64;
80
+ const LOOPBACK_HOST = "127.0.0.1";
81
+ const MAX_PORT = 65535;
82
+ const isPortFree = (port, host = LOOPBACK_HOST) => new Promise((resolve) => {
83
+ const server = createServer();
84
+ server.once("error", () => {
85
+ resolve(false);
86
+ });
87
+ server.once("listening", () => {
88
+ server.close(() => {
89
+ resolve(true);
90
+ });
91
+ });
92
+ server.listen(port, host);
93
+ });
94
+ const findAvailablePort = async (preferred, host = LOOPBACK_HOST, attempts = DEFAULT_PROBE_ATTEMPTS) => {
95
+ for (let port = preferred; port < preferred + attempts && port <= MAX_PORT; port += 1) {
96
+ if (await isPortFree(port, host)) {
97
+ return port;
98
+ }
99
+ }
100
+ return preferred;
101
+ };
102
+
103
+ const hasIpv6Loopback = (readInterfaces = networkInterfaces) => Object.values(readInterfaces()).some((addresses) => addresses?.some((address) => address.internal && address.address === "::1"));
104
+
78
105
  const PROXY_PREFIX = "/_lunora";
79
106
  const pathnameOf = (url) => {
80
107
  const queryIndex = url.indexOf("?");
@@ -201,7 +228,7 @@ const startStudioServer = async (options) => {
201
228
  return true;
202
229
  };
203
230
  const document = Buffer.from(html);
204
- const server = createServer((request, response) => {
231
+ const server = createServer$1((request, response) => {
205
232
  const pathname = pathnameOf(request.url ?? "/");
206
233
  if (isLoopback && !isLoopbackHost(request.headers.host)) {
207
234
  response.statusCode = 403;
@@ -437,11 +464,18 @@ const runDevBackground = async (options) => {
437
464
  for (const line of readLogTail(logPath, FAILURE_LOG_TAIL_LINES)) {
438
465
  logger.error(` ${line}`);
439
466
  }
440
- clearDevServerState(cwd, child.pid);
467
+ clearDevServerState(cwd, child.pid ?? process.pid);
441
468
  return { code: outcome.exitCode === 0 ? 1 : outcome.exitCode };
442
469
  }
470
+ if (child.pid !== void 0) {
471
+ const current = readDevServerState(cwd);
472
+ if (current?.pid === process.pid) {
473
+ updateDevServerState(cwd, { logFile: logPath, pid: child.pid });
474
+ }
475
+ }
476
+ const pidHint = child.pid === void 0 ? "" : ` (pid ${String(child.pid)})`;
443
477
  logger.warn(
444
- `dev server did not confirm ready within ${String(Math.round(timeout / 1e3))}s — it may still be compiling. Check \`lunora dev status\` and \`lunora dev logs\`; \`lunora dev stop\` shuts it down.`
478
+ `dev server did not confirm ready within ${String(Math.round(timeout / 1e3))}s — it may still be compiling${pidHint}. Check \`lunora dev status\` and \`lunora dev logs\`; \`lunora dev stop\` shuts it down.`
445
479
  );
446
480
  return { code: 1 };
447
481
  };
@@ -516,10 +550,10 @@ const processGroupId = (pid) => {
516
550
  return void 0;
517
551
  }
518
552
  };
519
- const forceKillRecordedServer = (state, signal) => {
520
- if (process.platform === "win32") {
553
+ const forceKillRecordedServer = (state, signal, platform = process.platform, spawnSyncImpl = spawnSync) => {
554
+ if (platform === "win32") {
521
555
  try {
522
- spawnSync("taskkill", ["/pid", String(state.pid), "/T", "/F"], { stdio: "ignore" });
556
+ spawnSyncImpl("taskkill", ["/pid", String(state.pid), "/T", "/F"], { stdio: "ignore" });
523
557
  } catch {
524
558
  }
525
559
  return;
@@ -536,6 +570,16 @@ const forceKillRecordedServer = (state, signal) => {
536
570
  } catch {
537
571
  }
538
572
  };
573
+ const reportSurvivedForceKill = (state, options) => {
574
+ if (options.json) {
575
+ printJson({ pid: state.pid, stopped: false });
576
+ } else {
577
+ options.logger.error(
578
+ `dev server (pid ${String(state.pid)}) survived the force-kill — record kept; retry \`lunora dev stop\` or kill the process manually.`
579
+ );
580
+ }
581
+ return { code: 1 };
582
+ };
539
583
  const runDevStop = async (options) => {
540
584
  const { cwd, logger } = options;
541
585
  const state = readDevServerState(cwd);
@@ -545,6 +589,8 @@ const runDevStop = async (options) => {
545
589
  });
546
590
  const pollInterval = options.pollIntervalMs ?? POLL_INTERVAL_MS;
547
591
  const grace = options.stopGraceMs ?? STOP_GRACE_MS;
592
+ const platform = options.platform ?? process.platform;
593
+ const spawnSyncImpl = options.spawnSyncImpl ?? spawnSync;
548
594
  if (state === void 0 || !alive(state.pid)) {
549
595
  if (state !== void 0) {
550
596
  clearDevServerState(cwd, state.pid);
@@ -565,7 +611,11 @@ const runDevStop = async (options) => {
565
611
  await sleep(pollInterval);
566
612
  }
567
613
  if (alive(state.pid)) {
568
- forceKillRecordedServer(state, signal);
614
+ forceKillRecordedServer(state, signal, platform, spawnSyncImpl);
615
+ await sleep(pollInterval);
616
+ if (alive(state.pid)) {
617
+ return reportSurvivedForceKill(state, options);
618
+ }
569
619
  }
570
620
  clearDevServerState(cwd, state.pid);
571
621
  if (options.json) {
@@ -669,6 +719,29 @@ const resolveRemotePlan = (options, cwd) => {
669
719
  }
670
720
  return { args: ["--config", result.configPath], plan: { bindings, cleanup, enabled: true } };
671
721
  };
722
+ const resolveLoopbackArgs = (cwd, hasLoopback) => {
723
+ const wranglerPath = findWranglerFile(cwd);
724
+ if (wranglerPath !== void 0) {
725
+ const { parsed } = readWranglerJsonc(wranglerPath);
726
+ if (parsed?.dev?.ip !== void 0) {
727
+ return [];
728
+ }
729
+ }
730
+ return hasLoopback() ? [] : ["--ip", "127.0.0.1"];
731
+ };
732
+ const resolveWorkerPort = async (options, cwd) => {
733
+ if (options.workerPort !== void 0) {
734
+ return options.workerPort;
735
+ }
736
+ const wranglerPath = findWranglerFile(cwd);
737
+ if (wranglerPath !== void 0) {
738
+ const { parsed } = readWranglerJsonc(wranglerPath);
739
+ if (typeof parsed?.dev?.port === "number") {
740
+ return parsed.dev.port;
741
+ }
742
+ }
743
+ return (options.findFreePort ?? findAvailablePort)(DEFAULT_WORKER_PORT);
744
+ };
672
745
  const planDevCommand = (options) => {
673
746
  const cwd = options.cwd ?? process.cwd();
674
747
  const manager = detectPackageManager(cwd);
@@ -678,6 +751,7 @@ const planDevCommand = (options) => {
678
751
  return {
679
752
  codegenEnabled: false,
680
753
  flavor,
754
+ ipv4LoopbackForced: false,
681
755
  remote: { bindings: [], cleanup: () => {
682
756
  }, enabled: options.remote === true },
683
757
  studioEnabled: false,
@@ -697,11 +771,13 @@ const planDevCommand = (options) => {
697
771
  const frameworkHint = detection.framework === "none" ? void 0 : `this project uses ${detection.framework} — the worker runs inside Vite there. run \`${runScriptCommand(manager, "dev")}\` for the full app (frontend + HMR); \`lunora dev\` starts only the worker.`;
698
772
  const workerPort = options.workerPort ?? DEFAULT_WORKER_PORT;
699
773
  const remote = resolveRemotePlan(options, cwd);
700
- const exec = execArgsFor(manager, "wrangler", ["dev", "--port", String(workerPort), "--var", "WORKER_ENV:development", ...remote.args]);
774
+ const loopbackArgs = resolveLoopbackArgs(cwd, options.hasIpv6Loopback ?? hasIpv6Loopback);
775
+ const exec = execArgsFor(manager, "wrangler", ["dev", "--port", String(workerPort), ...loopbackArgs, "--var", "WORKER_ENV:development", ...remote.args]);
701
776
  return {
702
777
  codegenEnabled: options.codegen !== false,
703
778
  flavor,
704
779
  frameworkHint,
780
+ ipv4LoopbackForced: loopbackArgs.length > 0,
705
781
  remote: remote.plan,
706
782
  studioEnabled: options.studio !== false,
707
783
  studioPort: options.port ?? DEFAULT_STUDIO_PORT,
@@ -899,8 +975,14 @@ const claimStartRecord = (plan, cwd) => {
899
975
  );
900
976
  return claim.ok ? void 0 : claim.existing;
901
977
  };
978
+ const buildDevPlan = async (options) => {
979
+ const cwd = options.cwd ?? process.cwd();
980
+ const flavor = options.flavor ?? detectDevFlavor(cwd);
981
+ const workerPort = flavor === "wrangler" ? await resolveWorkerPort(options, cwd) : options.workerPort;
982
+ return planDevCommand({ ...options, cwd, flavor, workerPort });
983
+ };
902
984
  const runDevCommand = async (options) => {
903
- const plan = planDevCommand(options);
985
+ const plan = await buildDevPlan(options);
904
986
  const { logger } = options;
905
987
  const cwd = plan.wrangler.cwd ?? process.cwd();
906
988
  const handles = { remoteCleanup: plan.remote.cleanup };
@@ -922,6 +1004,11 @@ const runDevCommand = async (options) => {
922
1004
  logger.info(
923
1005
  plan.flavor === "vite" ? "starting vite dev (worker + studio + codegen run inside Vite via @lunora/vite)" : "starting wrangler dev + studio"
924
1006
  );
1007
+ if (plan.ipv4LoopbackForced) {
1008
+ logger.info(
1009
+ "no IPv6 loopback (::1) on this host — binding the worker to 127.0.0.1 (--ip) so wrangler dev doesn't crash. Pin `dev.ip` in wrangler.jsonc to override."
1010
+ );
1011
+ }
925
1012
  if (plan.codegenEnabled) {
926
1013
  handles.codegen = (options.startCodegen ?? startCodegenWatch)({ apiSpec: options.apiSpec, logger, projectRoot: cwd });
927
1014
  }
@@ -1026,4 +1113,4 @@ const execute = defineHandler(async ({ argument, cwd, logger, options }) => {
1026
1113
  });
1027
1114
  });
1028
1115
 
1029
- export { detectDevFlavor, execute, planDevCommand, resolveRemotePlan, runDevCommand };
1116
+ export { detectDevFlavor, execute, planDevCommand, resolveRemotePlan, resolveWorkerPort, runDevCommand };
@@ -7,7 +7,7 @@ import { Project } from 'ts-morph';
7
7
  import { p as parseApiSpec } from '../packem_shared/api-spec-Bx0iKbxA.mjs';
8
8
  import { r as readWranglerName } from '../packem_shared/wrangler-name-cy4yhm9j.mjs';
9
9
  import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
10
- import { e as execArgsFor, d as detectPackageManager } from '../packem_shared/detect-package-manager-Cxo6Tpyx.mjs';
10
+ import { e as execArgsFor, d as detectPackageManager } from '../packem_shared/detect-package-manager-DvEthdCw.mjs';
11
11
  import { a as isRailpackAvailable, i as isDockerAvailable } from '../packem_shared/docker-hMQ97KSQ.mjs';
12
12
  import { v as validateOutputFormat, i as isJsonFormat, p as printJson, l as loggerForFormat } from '../packem_shared/output-format-B4642rjE.mjs';
13
13
  import { containerBuildTag } from '@lunora/container';
@@ -15,7 +15,7 @@ import { defaultSpawner } from '../packem_shared/createRecordingSpawner-Cw5Iu73G
15
15
  import { r as resolveWorkerUrl } from '../packem_shared/resolve-target-qbsJ_5sF.mjs';
16
16
  import { r as runSchemaDriftGate } from '../packem_shared/schema-drift-gate-BtBt0as0.mjs';
17
17
  import { c as createTuiConfirm } from '../packem_shared/tui-prompts-BjEN8XgP.mjs';
18
- import { l as listRemoteSecrets } from '../packem_shared/wrangler-secrets-P2_ZUR-k.mjs';
18
+ import { l as listRemoteSecrets } from '../packem_shared/wrangler-secrets-Dq_Fkbm-.mjs';
19
19
  import { runMigrateDataCommand } from './runMigrateGenerateCommand.mjs';
20
20
 
21
21
  const WORKERS_DEV_URL = /https?:\/\/[^\s"'<>]+\.workers\.dev[^\s"'<>]*/u;
@@ -7,7 +7,7 @@ import { downloadTemplate } from 'giget';
7
7
  import { modify, applyEdits } from 'jsonc-parser';
8
8
  import { join, dirname } from 'node:path';
9
9
  import { d as defineHandler } from '../packem_shared/command-lYnl4QyF.mjs';
10
- import { d as detectPackageManager, a as detectInstalledManagers, i as installArgsFor, r as runScriptCommand } from '../packem_shared/detect-package-manager-Cxo6Tpyx.mjs';
10
+ import { d as detectPackageManager, a as detectInstalledManagers, i as installArgsFor, r as runScriptCommand } from '../packem_shared/detect-package-manager-DvEthdCw.mjs';
11
11
  import MagicString from 'magic-string';
12
12
  import { Project, SyntaxKind } from 'ts-morph';
13
13
  import { P as PromptCancelledError } from '../packem_shared/prompt-cancelled-APzX1Im-.mjs';
@@ -1940,8 +1940,7 @@ const maybeOfferGit = async (options, target) => {
1940
1940
  options.logger.warn("`git init` failed — initialize it yourself later with `git init`.");
1941
1941
  }
1942
1942
  };
1943
- const printNextSteps = async (name, installed, insideMonorepo) => {
1944
- const manager = installed ?? "pnpm";
1943
+ const printNextSteps = async (name, installed, manager, insideMonorepo) => {
1945
1944
  const steps = [{ code: `cd ./${name}`, lead: "Enter your project directory using" }];
1946
1945
  if (installed === void 0) {
1947
1946
  steps.push({ code: `${manager} install`, lead: "Install dependencies with", tail: insideMonorepo ? " from the workspace root" : void 0 });
@@ -2462,7 +2461,8 @@ const runPostScaffold = async (options, result, cwd) => {
2462
2461
  const installedManager = options.inPlace === true ? void 0 : await maybeOfferInstall(options, result.target);
2463
2462
  if (options.inPlace !== true) {
2464
2463
  await maybeOfferGit(options, result.target);
2465
- await printNextSteps(basename(result.target), installedManager, isInsideMonorepo(cwd));
2464
+ const manager = installedManager ?? detectPackageManager(result.target);
2465
+ await printNextSteps(basename(result.target), installedManager, manager, isInsideMonorepo(cwd));
2466
2466
  await emitMascot(options.logger);
2467
2467
  }
2468
2468
  };
@@ -1,9 +1,7 @@
1
1
  import { spawnSync } from 'node:child_process';
2
- import { existsSync, readFileSync } from 'node:fs';
3
- import { dirname, join } from '@visulima/path';
2
+ import { LunoraError } from '@lunora/errors';
3
+ import { findPackageManagerSync, identifyInitiatingPackageManager } from '@visulima/package/package-manager';
4
4
 
5
- const FALLBACK = "pnpm";
6
- const KNOWN_MANAGERS = ["pnpm", "yarn", "npm", "bun"];
7
5
  const INSTALL_PREFERENCE = ["pnpm", "bun", "yarn", "npm"];
8
6
  const isManagerInstalled = (manager) => {
9
7
  try {
@@ -16,34 +14,23 @@ const detectInstalledManagers = (probe = isManagerInstalled) => INSTALL_PREFEREN
16
14
  const installArgsFor = (manager) => {
17
15
  return { args: ["install"], command: manager };
18
16
  };
19
- const parseDeclaredManager = (declared) => {
20
- if (typeof declared !== "string") {
21
- return void 0;
22
- }
23
- return KNOWN_MANAGERS.find((manager) => declared.startsWith(`${manager}@`));
24
- };
25
- const readDeclaredManager = (directory) => {
26
- const candidate = join(directory, "package.json");
27
- if (!existsSync(candidate)) {
28
- return void 0;
29
- }
17
+ const detectPackageManager = (startDirectory) => {
30
18
  try {
31
- const parsed = JSON.parse(readFileSync(candidate, "utf8"));
32
- return parseDeclaredManager(parsed.packageManager);
19
+ return findPackageManagerSync(startDirectory).packageManager;
33
20
  } catch {
34
- return void 0;
35
21
  }
36
- };
37
- const detectPackageManager = (startDirectory) => {
38
- let directory = startDirectory;
39
- while (directory && directory !== dirname(directory)) {
40
- const declared = readDeclaredManager(directory);
41
- if (declared !== void 0) {
42
- return declared;
43
- }
44
- directory = dirname(directory);
22
+ const initiating = identifyInitiatingPackageManager();
23
+ if (initiating !== void 0) {
24
+ return initiating.name === "cnpm" ? "npm" : initiating.name;
25
+ }
26
+ const [installed] = detectInstalledManagers();
27
+ if (installed !== void 0) {
28
+ return installed;
45
29
  }
46
- return FALLBACK;
30
+ throw new LunoraError(
31
+ "INTERNAL",
32
+ "Could not detect a package manager: no lock file or `packageManager` field was found, and none (pnpm, bun, yarn, npm) is installed on PATH."
33
+ );
47
34
  };
48
35
  const execArgsFor = (manager, command, args) => {
49
36
  if (manager === "yarn") {
@@ -1,4 +1,5 @@
1
1
  import { execFile } from 'node:child_process';
2
+ import { e as execArgsFor, d as detectPackageManager } from './detect-package-manager-DvEthdCw.mjs';
2
3
 
3
4
  const execCode = (error) => {
4
5
  if (!error) {
@@ -25,15 +26,16 @@ const parseSecretNames = (stdout) => {
25
26
  return [...names].toSorted((a, b) => a.localeCompare(b));
26
27
  };
27
28
  const listRemoteSecrets = async (inputs) => {
28
- const args = ["exec", "wrangler", "secret", "list", "--format", "json"];
29
+ const wranglerArgs = ["secret", "list", "--format", "json"];
29
30
  if (inputs.env !== void 0) {
30
- args.push("--env", inputs.env);
31
+ wranglerArgs.push("--env", inputs.env);
31
32
  }
32
33
  if (inputs.temporary) {
33
- args.push("--temporary");
34
+ wranglerArgs.push("--temporary");
34
35
  }
36
+ const { args, command } = execArgsFor(detectPackageManager(inputs.cwd), "wrangler", wranglerArgs);
35
37
  const runner = inputs.runner ?? defaultRunner;
36
- const result = await runner("pnpm", args, inputs.cwd);
38
+ const result = await runner(command, args, inputs.cwd);
37
39
  if (result.code !== 0) {
38
40
  return { error: result.stderr.trim() || `wrangler secret list exited ${String(result.code)}`, names: [], ok: false };
39
41
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/cli",
3
- "version": "1.0.0-alpha.67",
3
+ "version": "1.0.0-alpha.69",
4
4
  "description": "The Lunora CLI: init, dev, deploy, codegen, run, reset, and migrate commands",
5
5
  "keywords": [
6
6
  "agent-skills",
@@ -51,16 +51,17 @@
51
51
  "access": "public"
52
52
  },
53
53
  "dependencies": {
54
- "@bomb.sh/tab": "0.0.17",
55
- "@lunora/codegen": "1.0.0-alpha.33",
56
- "@lunora/config": "1.0.0-alpha.55",
57
- "@lunora/container": "1.0.0-alpha.6",
58
- "@lunora/d1": "1.0.0-alpha.23",
59
- "@lunora/errors": "1.0.0-alpha.1",
60
- "@lunora/seed": "1.0.0-alpha.16",
54
+ "@bomb.sh/tab": "0.0.16",
55
+ "@lunora/codegen": "1.0.0-alpha.34",
56
+ "@lunora/config": "1.0.0-alpha.56",
57
+ "@lunora/container": "1.0.0-alpha.7",
58
+ "@lunora/d1": "1.0.0-alpha.24",
59
+ "@lunora/errors": "1.0.0-alpha.2",
60
+ "@lunora/seed": "1.0.0-alpha.17",
61
61
  "@visulima/cerebro": "3.0.0-alpha.32",
62
62
  "@visulima/error": "6.0.0-alpha.34",
63
63
  "@visulima/fs": "5.0.0-alpha.32",
64
+ "@visulima/package": "5.0.0-alpha.23",
64
65
  "@visulima/pail": "4.0.0-alpha.22",
65
66
  "@visulima/path": "3.0.0-alpha.13",
66
67
  "@visulima/spinner": "1.0.0-alpha.4",