@open-mercato/cli 0.6.3-develop.3894.1.352abf4240 → 0.6.3

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/mercato.js CHANGED
@@ -246,6 +246,102 @@ function buildServerProcessEnvironment(environment) {
246
246
  }
247
247
  return runtimeEnv;
248
248
  }
249
+ function resolveDevRuntimeBaseUrl(environment = process.env) {
250
+ const configured = environment.APP_URL ?? environment.NEXT_PUBLIC_APP_URL ?? environment.NEXTAUTH_URL;
251
+ if (configured?.trim()) {
252
+ return configured.trim().replace(/\/+$/, "");
253
+ }
254
+ return `http://localhost:${environment.PORT?.trim() || "3000"}`;
255
+ }
256
+ function writeDevSplashChildState(state) {
257
+ if (process.env.OM_DEV_SPLASH_RUNTIME_WRAPPER === "1") return;
258
+ const stateFile = process.env.OM_DEV_SPLASH_CHILD_STATE_FILE;
259
+ if (!stateFile?.trim()) return;
260
+ try {
261
+ fs.mkdirSync(path.dirname(stateFile), { recursive: true });
262
+ fs.writeFileSync(stateFile, `${JSON.stringify({
263
+ mode: process.env.OM_DEV_SPLASH_MODE || "dev",
264
+ failed: false,
265
+ failureLines: [],
266
+ failureCommand: null,
267
+ ...state
268
+ }, null, 2)}
269
+ `);
270
+ } catch {
271
+ }
272
+ }
273
+ function writeDevSplashRuntimeStarting(detail = "Starting Next.js dev server") {
274
+ writeDevSplashChildState({
275
+ phase: "Preparing app runtime",
276
+ detail,
277
+ ready: false,
278
+ readyUrl: null,
279
+ loginUrl: null,
280
+ progressLabel: "Launching app runtime",
281
+ activity: detail
282
+ });
283
+ }
284
+ function resolveSplashProgressFallback() {
285
+ const current = Number.parseInt(process.env.OM_DEV_SPLASH_STAGE_CURRENT ?? "", 10);
286
+ const total = Number.parseInt(process.env.OM_DEV_SPLASH_STAGE_TOTAL ?? "", 10);
287
+ if (Number.isFinite(current) && Number.isFinite(total) && total > 0) {
288
+ return { current, total };
289
+ }
290
+ if (process.env.OM_DEV_SPLASH_MODE === "greenfield" || process.env.OM_DEV_SPLASH_MODE === "setup") {
291
+ return { current: 5, total: 5 };
292
+ }
293
+ return { current: 3, total: 3 };
294
+ }
295
+ function writeDevSplashRuntimeRestarting(reason) {
296
+ const progress = resolveSplashProgressFallback();
297
+ writeDevSplashChildState({
298
+ phase: "App runtime is restarting",
299
+ detail: `Reason: ${reason}`,
300
+ ready: false,
301
+ readyUrl: null,
302
+ loginUrl: null,
303
+ progressCurrent: progress.current,
304
+ progressTotal: progress.total,
305
+ progressLabel: "Restarting app runtime",
306
+ activity: `App runtime restart: ${reason}`
307
+ });
308
+ }
309
+ function writeDevSplashRuntimeReady(reason) {
310
+ const readyUrl = resolveDevRuntimeBaseUrl();
311
+ const progress = resolveSplashProgressFallback();
312
+ writeDevSplashChildState({
313
+ phase: "App is ready",
314
+ detail: reason ? `Restart completed after ${reason}` : "Next.js dev server is ready",
315
+ ready: true,
316
+ readyUrl,
317
+ loginUrl: `${readyUrl}/login`,
318
+ progressCurrent: progress.current,
319
+ progressTotal: progress.total,
320
+ progressPercent: 100,
321
+ progressLabel: "App is ready",
322
+ activity: reason ? `Restart completed after ${reason}` : "App runtime is ready"
323
+ });
324
+ }
325
+ function resolveDevWarmupReadyTimeoutMs(environment = process.env) {
326
+ const parsed = Number.parseInt(environment.OM_DEV_WARMUP_READY_TIMEOUT_MS ?? "", 10);
327
+ if (Number.isFinite(parsed) && parsed >= 0) return parsed;
328
+ return 3e5;
329
+ }
330
+ async function waitForDevWarmupReadyFile(filePath, options = {}) {
331
+ const normalized = filePath?.trim();
332
+ if (!normalized) return "ready";
333
+ const timeoutMs = options.timeoutMs ?? resolveDevWarmupReadyTimeoutMs();
334
+ const startedAt = Date.now();
335
+ while (true) {
336
+ if (options.signal?.aborted) return "aborted";
337
+ try {
338
+ if (fs.existsSync(normalized)) return "ready";
339
+ } catch {
340
+ }
341
+ if (timeoutMs >= 0 && Date.now() - startedAt >= timeoutMs) return "timeout";
342
+ await new Promise((resolve) => setTimeout(resolve, 250));
343
+ }
344
+ }
249
345
  function waitForManagedProcessExit(proc, label) {
250
346
  return new Promise((resolve) => {
251
347
  proc.on("exit", (code, signal) => {
@@ -445,18 +541,17 @@ async function runGeneratorSuite(quiet) {
445
541
  function createGenerateWatchChecksumFn() {
446
542
  return async () => {
447
543
  const { createResolver } = await import("./lib/resolver.js");
448
- const { calculateStructureChecksum } = await import("./lib/utils.js");
544
+ const { calculateGenerateWatchStructureChecksum } = await import("./lib/generate-watch-structure.js");
449
545
  const resolver = createResolver();
450
- const tracked = /* @__PURE__ */ new Set([
451
- path.join(resolver.getAppDir(), "src", "modules.ts"),
452
- path.join(resolver.getAppDir(), "src", "modules")
453
- ]);
546
+ const moduleRoots = [];
454
547
  for (const entry of resolver.loadEnabledModules()) {
455
548
  const roots = resolver.getModulePaths(entry);
456
- tracked.add(roots.appBase);
457
- tracked.add(roots.pkgBase);
549
+ moduleRoots.push({ appBase: roots.appBase, pkgBase: roots.pkgBase });
458
550
  }
459
- return calculateStructureChecksum(Array.from(tracked));
551
+ return calculateGenerateWatchStructureChecksum({
552
+ modulesFile: path.join(resolver.getAppDir(), "src", "modules.ts"),
553
+ moduleRoots
554
+ });
460
555
  };
461
556
  }
462
557
  async function buildAllModules() {
@@ -1390,10 +1485,11 @@ async function run(argv = process.argv) {
1390
1485
  let processes = [];
1391
1486
  let didRetryCorruptedTurbopackCache = false;
1392
1487
  let stopping = false;
1393
- let envChangePromiseResolve = null;
1488
+ let devRestartPromiseResolve = null;
1394
1489
  let activeLazySupervisor = null;
1395
1490
  let activeLazySchedulerSupervisor = null;
1396
1491
  let activeGenerateWatcher = null;
1492
+ let lastRestartReason = null;
1397
1493
  const generateWatcherMode = resolveGenerateWatcherMode(process.env);
1398
1494
  const envReloader = createDevEnvReloader(appDir, process.env, initialProcessEnvironmentEntries);
1399
1495
  function cleanup() {
@@ -1466,53 +1562,74 @@ async function run(argv = process.argv) {
1466
1562
  const nextBin = resolveInstalledBinary(nodeModulesBases, "next/dist/bin/next");
1467
1563
  const mercatoBin = resolveInstalledBinary(nodeModulesBases, "@open-mercato/cli/bin/mercato");
1468
1564
  const stopEnvWatcher = watchDevEnvFiles(appDir, (filePath) => {
1469
- envChangePromiseResolve?.({
1565
+ devRestartPromiseResolve?.({
1470
1566
  label: "Environment file change",
1471
1567
  restart: true,
1472
1568
  filePath
1473
1569
  });
1474
1570
  });
1475
- const waitForEnvChange = () => new Promise((resolve) => {
1476
- envChangePromiseResolve = resolve;
1571
+ const waitForDevRestart = () => new Promise((resolve) => {
1572
+ devRestartPromiseResolve = resolve;
1477
1573
  });
1478
- const startNextDev = (runtimeEnv) => new Promise((resolve) => {
1479
- const nextProcess = spawn("node", [nextBin, "dev", "--turbopack"], {
1480
- stdio: ["inherit", "pipe", "pipe"],
1481
- env: runtimeEnv,
1482
- cwd: appDir
1574
+ const startNextDev = (runtimeEnv) => {
1575
+ let readyResolve = () => void 0;
1576
+ const readyPromise = new Promise((resolve) => {
1577
+ readyResolve = resolve;
1483
1578
  });
1484
- processes.push(nextProcess);
1485
- let combinedOutput = "";
1486
- const appendOutput = (chunk) => {
1487
- combinedOutput += chunk;
1488
- if (combinedOutput.length > 32768) {
1489
- combinedOutput = combinedOutput.slice(-32768);
1490
- }
1491
- };
1492
- nextProcess.stdout?.on("data", (chunk) => {
1493
- const text = typeof chunk === "string" ? chunk : chunk.toString();
1494
- process.stdout.write(text);
1495
- appendOutput(text);
1496
- });
1497
- nextProcess.stderr?.on("data", (chunk) => {
1498
- const text = typeof chunk === "string" ? chunk : chunk.toString();
1499
- process.stderr.write(text);
1500
- appendOutput(text);
1501
- });
1502
- nextProcess.on("exit", async (code, signal) => {
1503
- if (!didRetryCorruptedTurbopackCache && isTurbopackCacheCorruption(combinedOutput)) {
1504
- didRetryCorruptedTurbopackCache = true;
1505
- console.log("[server] Detected corrupted Turbopack dev cache. Clearing .mercato/next/dev and restarting Next.js once...");
1506
- removeTurbopackDevCache(appDir);
1507
- return resolve(await startNextDev(runtimeEnv));
1508
- }
1509
- resolve({
1510
- label: "Next.js dev server",
1511
- code,
1512
- signal
1579
+ const exitPromise = new Promise((resolve) => {
1580
+ writeDevSplashRuntimeStarting(
1581
+ lastRestartReason ? `Restarting Next.js dev server. Reason: ${lastRestartReason}` : "Starting Next.js dev server"
1582
+ );
1583
+ const nextProcess = spawn("node", [nextBin, "dev", "--turbopack"], {
1584
+ stdio: ["inherit", "pipe", "pipe"],
1585
+ env: runtimeEnv,
1586
+ cwd: appDir
1587
+ });
1588
+ processes.push(nextProcess);
1589
+ let combinedOutput = "";
1590
+ let reportedReady = false;
1591
+ const appendOutput = (chunk) => {
1592
+ combinedOutput += chunk;
1593
+ if (combinedOutput.length > 32768) {
1594
+ combinedOutput = combinedOutput.slice(-32768);
1595
+ }
1596
+ if (!reportedReady && /\bready in\b/i.test(chunk)) {
1597
+ reportedReady = true;
1598
+ writeDevSplashRuntimeReady(lastRestartReason ?? void 0);
1599
+ lastRestartReason = null;
1600
+ readyResolve();
1601
+ }
1602
+ };
1603
+ nextProcess.stdout?.on("data", (chunk) => {
1604
+ const text = typeof chunk === "string" ? chunk : chunk.toString();
1605
+ process.stdout.write(text);
1606
+ appendOutput(text);
1607
+ });
1608
+ nextProcess.stderr?.on("data", (chunk) => {
1609
+ const text = typeof chunk === "string" ? chunk : chunk.toString();
1610
+ process.stderr.write(text);
1611
+ appendOutput(text);
1612
+ });
1613
+ nextProcess.on("exit", async (code, signal) => {
1614
+ if (!didRetryCorruptedTurbopackCache && isTurbopackCacheCorruption(combinedOutput)) {
1615
+ didRetryCorruptedTurbopackCache = true;
1616
+ lastRestartReason = "corrupted Turbopack dev cache";
1617
+ writeDevSplashRuntimeRestarting(lastRestartReason);
1618
+ console.log("[server] Detected corrupted Turbopack dev cache. Clearing .mercato/next/dev and restarting Next.js once...");
1619
+ removeTurbopackDevCache(appDir);
1620
+ const restarted = startNextDev(runtimeEnv);
1621
+ restarted.readyPromise.then(readyResolve);
1622
+ return resolve(await restarted.exitPromise);
1623
+ }
1624
+ resolve({
1625
+ label: "Next.js dev server",
1626
+ code,
1627
+ signal
1628
+ });
1513
1629
  });
1514
1630
  });
1515
- });
1631
+ return { exitPromise, readyPromise };
1632
+ };
1516
1633
  try {
1517
1634
  while (!stopping) {
1518
1635
  envReloader.reload();
@@ -1521,59 +1638,84 @@ async function run(argv = process.argv) {
1521
1638
  const autoSpawnSchedulerMode = resolveAutoSpawnSchedulerMode(process.env);
1522
1639
  const queueStrategy = process.env.QUEUE_STRATEGY || "local";
1523
1640
  const schedulerCommand = lookupModuleCommand(getCliModules(), "scheduler", "start");
1641
+ const nextRuntime = startNextDev(runtimeEnv);
1642
+ const restartPromise = waitForDevRestart();
1643
+ const backgroundStartAbort = new AbortController();
1644
+ const cancelBackgroundStart = () => backgroundStartAbort.abort();
1645
+ nextRuntime.exitPromise.finally(cancelBackgroundStart);
1646
+ restartPromise.then(cancelBackgroundStart);
1647
+ let backgroundExitResolve = () => void 0;
1648
+ const backgroundExitPromise = new Promise((resolve) => {
1649
+ backgroundExitResolve = resolve;
1650
+ });
1524
1651
  const managedExitPromises = [
1525
- startNextDev(runtimeEnv),
1526
- waitForEnvChange()
1652
+ nextRuntime.exitPromise,
1653
+ restartPromise,
1654
+ backgroundExitPromise
1527
1655
  ];
1528
- if (autoSpawnWorkersMode !== "off") {
1529
- const discoveredWorkers = getRegisteredCliWorkers();
1530
- const discoveredWorkerQueues = [...new Set(discoveredWorkers.map((worker) => worker.queue))];
1531
- if (discoveredWorkerQueues.length === 0) {
1532
- console.error("[server] AUTO_SPAWN_WORKERS is enabled, but no queues were discovered from CLI modules. Run `yarn generate` and verify `.mercato/generated/modules.cli.generated.ts` contains worker entries. Continuing without auto-spawned workers.");
1533
- } else if (autoSpawnWorkersMode === "lazy") {
1534
- console.log(`[server] Lazy worker auto-spawn enabled \u2014 workers will start on first job (${discoveredWorkerQueues.length} queue(s) watched).`);
1535
- activeLazySupervisor = startLazyWorkerSupervisor({
1536
- mercatoBin,
1537
- appDir,
1538
- runtimeEnv,
1539
- workers: discoveredWorkers,
1540
- pollMs: resolveLazyPollMs(process.env),
1541
- restartOnUnexpectedExit: resolveLazyRestart(process.env)
1542
- });
1543
- } else {
1544
- console.log("[server] Eager worker auto-spawn enabled - starting workers for all queues...");
1545
- const workerProcess = spawn("node", [mercatoBin, "queue", "worker", "--all"], {
1546
- stdio: "inherit",
1547
- env: runtimeEnv,
1548
- cwd: appDir
1549
- });
1550
- processes.push(workerProcess);
1551
- managedExitPromises.push(waitForManagedProcessExit(workerProcess, formatQueueWorkerLabel(discoveredWorkerQueues)));
1656
+ const startBackgroundServices = async () => {
1657
+ if (stopping || backgroundStartAbort.signal.aborted) return;
1658
+ const warmupReady = await waitForDevWarmupReadyFile(process.env.OM_DEV_WARMUP_READY_FILE, {
1659
+ timeoutMs: resolveDevWarmupReadyTimeoutMs(process.env),
1660
+ signal: backgroundStartAbort.signal
1661
+ });
1662
+ if (warmupReady === "aborted" || stopping || backgroundStartAbort.signal.aborted) return;
1663
+ if (warmupReady === "timeout") {
1664
+ console.warn("[server] Timed out waiting for dev warmup marker; starting background services anyway.");
1552
1665
  }
1553
- }
1554
- if (autoSpawnSchedulerMode !== "off" && queueStrategy === "local") {
1555
- if (schedulerCommand.status !== "ok") {
1556
- console.log(`[server] Skipping scheduler auto-start \u2014 ${describeMissingModuleCommand(schedulerCommand)}`);
1557
- } else if (autoSpawnSchedulerMode === "lazy") {
1558
- console.log("[server] Lazy scheduler auto-spawn enabled - scheduler will start when an enabled schedule exists.");
1559
- activeLazySchedulerSupervisor = startLazySchedulerSupervisor({
1560
- mercatoBin,
1561
- appDir,
1562
- runtimeEnv,
1563
- pollMs: resolveLazySchedulerPollMs(process.env),
1564
- restartOnUnexpectedExit: resolveLazySchedulerRestart(process.env)
1565
- });
1566
- } else {
1567
- console.log("[server] Eager scheduler auto-spawn enabled - starting scheduler polling engine...");
1568
- const schedulerProcess = spawn("node", [mercatoBin, "scheduler", "start"], {
1569
- stdio: "inherit",
1570
- env: runtimeEnv,
1571
- cwd: appDir
1572
- });
1573
- processes.push(schedulerProcess);
1574
- managedExitPromises.push(waitForManagedProcessExit(schedulerProcess, "Scheduler polling engine"));
1666
+ if (autoSpawnWorkersMode !== "off") {
1667
+ const discoveredWorkers = getRegisteredCliWorkers();
1668
+ const discoveredWorkerQueues = [...new Set(discoveredWorkers.map((worker) => worker.queue))];
1669
+ if (discoveredWorkerQueues.length === 0) {
1670
+ console.error("[server] AUTO_SPAWN_WORKERS is enabled, but no queues were discovered from CLI modules. Run `yarn generate` and verify `.mercato/generated/modules.cli.generated.ts` contains worker entries. Continuing without auto-spawned workers.");
1671
+ } else if (autoSpawnWorkersMode === "lazy") {
1672
+ console.log(`[server] Lazy worker auto-spawn enabled \u2014 workers will start on first job (${discoveredWorkerQueues.length} queue(s) watched).`);
1673
+ activeLazySupervisor = startLazyWorkerSupervisor({
1674
+ mercatoBin,
1675
+ appDir,
1676
+ runtimeEnv,
1677
+ workers: discoveredWorkers,
1678
+ pollMs: resolveLazyPollMs(process.env),
1679
+ restartOnUnexpectedExit: resolveLazyRestart(process.env)
1680
+ });
1681
+ } else {
1682
+ console.log("[server] Eager worker auto-spawn enabled - starting workers for all queues...");
1683
+ const workerProcess = spawn("node", [mercatoBin, "queue", "worker", "--all"], {
1684
+ stdio: "inherit",
1685
+ env: runtimeEnv,
1686
+ cwd: appDir
1687
+ });
1688
+ processes.push(workerProcess);
1689
+ waitForManagedProcessExit(workerProcess, formatQueueWorkerLabel(discoveredWorkerQueues)).then(backgroundExitResolve);
1690
+ }
1575
1691
  }
1576
- }
1692
+ if (autoSpawnSchedulerMode !== "off" && queueStrategy === "local") {
1693
+ if (schedulerCommand.status !== "ok") {
1694
+ console.log(`[server] Skipping scheduler auto-start \u2014 ${describeMissingModuleCommand(schedulerCommand)}`);
1695
+ } else if (autoSpawnSchedulerMode === "lazy") {
1696
+ console.log("[server] Lazy scheduler auto-spawn enabled - scheduler will start when an enabled schedule exists.");
1697
+ activeLazySchedulerSupervisor = startLazySchedulerSupervisor({
1698
+ mercatoBin,
1699
+ appDir,
1700
+ runtimeEnv,
1701
+ pollMs: resolveLazySchedulerPollMs(process.env),
1702
+ restartOnUnexpectedExit: resolveLazySchedulerRestart(process.env)
1703
+ });
1704
+ } else {
1705
+ console.log("[server] Eager scheduler auto-spawn enabled - starting scheduler polling engine...");
1706
+ const schedulerProcess = spawn("node", [mercatoBin, "scheduler", "start"], {
1707
+ stdio: "inherit",
1708
+ env: runtimeEnv,
1709
+ cwd: appDir
1710
+ });
1711
+ processes.push(schedulerProcess);
1712
+ waitForManagedProcessExit(schedulerProcess, "Scheduler polling engine").then(backgroundExitResolve);
1713
+ }
1714
+ }
1715
+ };
1716
+ nextRuntime.readyPromise.then(() => {
1717
+ void startBackgroundServices();
1718
+ });
1577
1719
  if (generateWatcherMode === "in-process") {
1578
1720
  console.log("[server] In-process generate watcher enabled \u2014 structural changes will regenerate without a sidecar process.");
1579
1721
  activeGenerateWatcher = startInProcessGenerateWatcher({
@@ -1594,10 +1736,14 @@ async function run(argv = process.argv) {
1594
1736
  console.log("[server] Legacy out-of-process generate watcher selected via OM_DEV_GENERATE_WATCH_MODE=legacy \u2014 expect the dev orchestrator to spawn `mercato generate watch --skip-initial`.");
1595
1737
  }
1596
1738
  const firstExit = await Promise.race(managedExitPromises);
1739
+ if (isDevServerRestartResult(firstExit)) {
1740
+ lastRestartReason = `${firstExit.label.toLowerCase()} (${path.basename(firstExit.filePath)})`;
1741
+ writeDevSplashRuntimeRestarting(lastRestartReason);
1742
+ }
1597
1743
  await cleanupAndWait();
1598
- envChangePromiseResolve = null;
1744
+ devRestartPromiseResolve = null;
1599
1745
  if (isDevServerRestartResult(firstExit)) {
1600
- console.log(`[server] Detected environment file change (${path.basename(firstExit.filePath)}). Restarting app runtime...`);
1746
+ console.log(`[server] Detected ${firstExit.label.toLowerCase()} (${path.basename(firstExit.filePath)}). Restarting app runtime...`);
1601
1747
  continue;
1602
1748
  }
1603
1749
  if (!isExpectedManagedExit(firstExit, { stopping })) {