@open-mercato/cli 0.6.3-develop.3881.1.0b590ac4eb → 0.6.3-develop.3901.1.ddad60693a
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/.turbo/turbo-build.log +1 -1
- package/dist/lib/in-process-generate-watcher-mode.js +13 -0
- package/dist/lib/in-process-generate-watcher-mode.js.map +7 -0
- package/dist/lib/in-process-generate-watcher.js +112 -0
- package/dist/lib/in-process-generate-watcher.js.map +7 -0
- package/dist/mercato.js +99 -75
- package/dist/mercato.js.map +2 -2
- package/package.json +5 -5
- package/src/__tests__/mercato.test.ts +9 -0
- package/src/lib/__tests__/in-process-generate-watcher-mode.test.ts +20 -0
- package/src/lib/__tests__/in-process-generate-watcher.test.ts +191 -0
- package/src/lib/in-process-generate-watcher-mode.ts +21 -0
- package/src/lib/in-process-generate-watcher.ts +175 -0
- package/src/mercato.ts +126 -81
package/.turbo/turbo-build.log
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
function resolveGenerateWatcherMode(env) {
|
|
2
|
+
const raw = env.OM_DEV_GENERATE_WATCH_MODE;
|
|
3
|
+
if (typeof raw !== "string") return "in-process";
|
|
4
|
+
const normalized = raw.trim().toLowerCase();
|
|
5
|
+
if (normalized === "legacy" || normalized === "sidecar" || normalized === "out-of-process") {
|
|
6
|
+
return "legacy";
|
|
7
|
+
}
|
|
8
|
+
return "in-process";
|
|
9
|
+
}
|
|
10
|
+
export {
|
|
11
|
+
resolveGenerateWatcherMode
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=in-process-generate-watcher-mode.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/lib/in-process-generate-watcher-mode.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Resolves the watcher-mode env flag that governs whether the dev runtime\n * runs the generate watcher in-process (default) or as a legacy out-of-process\n * `mercato generate watch --skip-initial` sidecar.\n *\n * `OM_DEV_GENERATE_WATCH_MODE=legacy` opts back into the pre-consolidation\n * behavior (one extra long-running Node process). Any other value, missing\n * value, or unparseable value resolves to `in-process`.\n */\n\nexport type GenerateWatcherMode = 'in-process' | 'legacy'\n\nexport function resolveGenerateWatcherMode(env: NodeJS.ProcessEnv): GenerateWatcherMode {\n const raw = env.OM_DEV_GENERATE_WATCH_MODE\n if (typeof raw !== 'string') return 'in-process'\n const normalized = raw.trim().toLowerCase()\n if (normalized === 'legacy' || normalized === 'sidecar' || normalized === 'out-of-process') {\n return 'legacy'\n }\n return 'in-process'\n}\n"],
|
|
5
|
+
"mappings": "AAYO,SAAS,2BAA2B,KAA6C;AACtF,QAAM,MAAM,IAAI;AAChB,MAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,QAAM,aAAa,IAAI,KAAK,EAAE,YAAY;AAC1C,MAAI,eAAe,YAAY,eAAe,aAAa,eAAe,kBAAkB;AAC1F,WAAO;AAAA,EACT;AACA,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
const MIN_POLL_MS = 250;
|
|
2
|
+
const DEFAULT_POLL_MS = 1e3;
|
|
3
|
+
function resolvePollMs(value) {
|
|
4
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return DEFAULT_POLL_MS;
|
|
5
|
+
const numeric = Math.floor(value);
|
|
6
|
+
return numeric >= MIN_POLL_MS ? numeric : DEFAULT_POLL_MS;
|
|
7
|
+
}
|
|
8
|
+
function startInProcessGenerateWatcher(options) {
|
|
9
|
+
const logger = options.logger ?? console;
|
|
10
|
+
const quiet = options.quiet === true;
|
|
11
|
+
const pollMs = resolvePollMs(options.pollMs);
|
|
12
|
+
const skipInitial = options.skipInitial === true;
|
|
13
|
+
const { computeStructureChecksum, runGenerators } = options;
|
|
14
|
+
let stopping = false;
|
|
15
|
+
let pollTimer = null;
|
|
16
|
+
let running = false;
|
|
17
|
+
let pendingReason = null;
|
|
18
|
+
let previousChecksum = "";
|
|
19
|
+
let doneResolve = null;
|
|
20
|
+
const done = new Promise((resolve) => {
|
|
21
|
+
doneResolve = resolve;
|
|
22
|
+
});
|
|
23
|
+
async function runOnce(reason) {
|
|
24
|
+
if (running) {
|
|
25
|
+
pendingReason = reason;
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
running = true;
|
|
29
|
+
try {
|
|
30
|
+
if (!quiet) {
|
|
31
|
+
logger.log(`[generate:watch] Regenerating (${reason})...`);
|
|
32
|
+
}
|
|
33
|
+
await runGenerators(reason);
|
|
34
|
+
if (!quiet) {
|
|
35
|
+
logger.log("[generate:watch] Generators completed.");
|
|
36
|
+
}
|
|
37
|
+
} catch (error) {
|
|
38
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
39
|
+
logger.error(`[generate:watch] Generation failed: ${message}`);
|
|
40
|
+
} finally {
|
|
41
|
+
running = false;
|
|
42
|
+
if (pendingReason && !stopping) {
|
|
43
|
+
const queued = pendingReason;
|
|
44
|
+
pendingReason = null;
|
|
45
|
+
await runOnce(queued);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function scheduleNext() {
|
|
50
|
+
if (stopping) return;
|
|
51
|
+
pollTimer = setTimeout(() => {
|
|
52
|
+
void (async () => {
|
|
53
|
+
if (stopping) return;
|
|
54
|
+
try {
|
|
55
|
+
const nextChecksum = await computeStructureChecksum();
|
|
56
|
+
if (nextChecksum !== previousChecksum) {
|
|
57
|
+
previousChecksum = nextChecksum;
|
|
58
|
+
await runOnce("structure change");
|
|
59
|
+
}
|
|
60
|
+
} catch (error) {
|
|
61
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
62
|
+
logger.error(`[generate:watch] Poll cycle failed: ${message}`);
|
|
63
|
+
} finally {
|
|
64
|
+
if (!stopping) scheduleNext();
|
|
65
|
+
}
|
|
66
|
+
})();
|
|
67
|
+
}, pollMs);
|
|
68
|
+
pollTimer.unref?.();
|
|
69
|
+
}
|
|
70
|
+
void (async () => {
|
|
71
|
+
try {
|
|
72
|
+
if (!skipInitial) {
|
|
73
|
+
await runOnce("initial");
|
|
74
|
+
}
|
|
75
|
+
previousChecksum = await computeStructureChecksum();
|
|
76
|
+
if (!quiet) {
|
|
77
|
+
if (skipInitial) {
|
|
78
|
+
logger.log("[generate:watch] Skipping initial regeneration and watching the current generated state.");
|
|
79
|
+
}
|
|
80
|
+
logger.log(`[generate:watch] Watching structural module files every ${pollMs}ms (in-process).`);
|
|
81
|
+
}
|
|
82
|
+
scheduleNext();
|
|
83
|
+
} catch (error) {
|
|
84
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
85
|
+
logger.error(`[generate:watch] Initial setup failed: ${message}`);
|
|
86
|
+
scheduleNext();
|
|
87
|
+
}
|
|
88
|
+
})();
|
|
89
|
+
async function close() {
|
|
90
|
+
if (stopping) {
|
|
91
|
+
await done;
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
stopping = true;
|
|
95
|
+
if (pollTimer) {
|
|
96
|
+
clearTimeout(pollTimer);
|
|
97
|
+
pollTimer = null;
|
|
98
|
+
}
|
|
99
|
+
if (doneResolve) {
|
|
100
|
+
doneResolve();
|
|
101
|
+
doneResolve = null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
done,
|
|
106
|
+
close
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
export {
|
|
110
|
+
startInProcessGenerateWatcher
|
|
111
|
+
};
|
|
112
|
+
//# sourceMappingURL=in-process-generate-watcher.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/lib/in-process-generate-watcher.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * In-process generate watcher.\n *\n * Polls a structural-fingerprint function on a configurable interval and\n * invokes the supplied generator callback whenever the fingerprint changes.\n * The polling loop is identical to the legacy standalone watcher; the only\n * difference is that it now runs inside whatever process calls it\n * (typically `mercato server dev`), so the dev runtime no longer needs a\n * sidecar `mercato generate watch` Node process.\n *\n * Contract preserved 1:1 with the prior standalone watcher:\n * - Default poll interval 1000 ms (minimum 250 ms).\n * - One initial generator run unless `skipInitial` is true.\n * - Concurrent regeneration requests are coalesced (running + pending).\n * - Generator errors are logged but never crash the watcher.\n * - The polling timer uses `.unref()` so it never blocks process exit.\n */\n\nexport type GenerateWatcherLogger = Pick<Console, 'log' | 'error'>\n\nexport type GenerateWatcherOptions = {\n /**\n * Function that returns the current structural fingerprint of the module\n * tree. The watcher re-runs `runGenerators` whenever this value changes.\n */\n computeStructureChecksum: () => Promise<string> | string\n /**\n * Function that performs the actual regeneration work. Called once on\n * startup (unless `skipInitial`) and again whenever the checksum changes.\n * The `reason` argument is suitable for logging (`'initial'`,\n * `'structure change'`, `'queued change'`).\n */\n runGenerators: (reason: string) => Promise<void>\n /** Poll interval in milliseconds. Defaults to 1000. Clamped to >= 250. */\n pollMs?: number\n /** Skip the initial regeneration on startup. Defaults to false. */\n skipInitial?: boolean\n /** Suppress informational logs. Errors are always logged. */\n quiet?: boolean\n /** Logger override. Defaults to `console`. */\n logger?: GenerateWatcherLogger\n}\n\nexport type GenerateWatcherHandle = {\n /** Resolves when the watcher loop has stopped (after `close()`). */\n readonly done: Promise<void>\n /** Stop the polling loop and resolve `done`. Idempotent. */\n close(): Promise<void>\n}\n\nconst MIN_POLL_MS = 250\nconst DEFAULT_POLL_MS = 1000\n\nfunction resolvePollMs(value: number | undefined): number {\n if (typeof value !== 'number' || !Number.isFinite(value)) return DEFAULT_POLL_MS\n const numeric = Math.floor(value)\n return numeric >= MIN_POLL_MS ? numeric : DEFAULT_POLL_MS\n}\n\n/**\n * Start an in-process generate watcher. The returned handle exposes a\n * `close()` to stop polling and a `done` promise that resolves once the\n * watcher loop has finished.\n */\nexport function startInProcessGenerateWatcher(\n options: GenerateWatcherOptions,\n): GenerateWatcherHandle {\n const logger = options.logger ?? console\n const quiet = options.quiet === true\n const pollMs = resolvePollMs(options.pollMs)\n const skipInitial = options.skipInitial === true\n const { computeStructureChecksum, runGenerators } = options\n\n let stopping = false\n let pollTimer: ReturnType<typeof setTimeout> | null = null\n let running = false\n let pendingReason: string | null = null\n let previousChecksum = ''\n let doneResolve: (() => void) | null = null\n const done = new Promise<void>((resolve) => {\n doneResolve = resolve\n })\n\n async function runOnce(reason: string): Promise<void> {\n if (running) {\n pendingReason = reason\n return\n }\n running = true\n try {\n if (!quiet) {\n logger.log(`[generate:watch] Regenerating (${reason})...`)\n }\n await runGenerators(reason)\n if (!quiet) {\n logger.log('[generate:watch] Generators completed.')\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n logger.error(`[generate:watch] Generation failed: ${message}`)\n } finally {\n running = false\n if (pendingReason && !stopping) {\n const queued = pendingReason\n pendingReason = null\n await runOnce(queued)\n }\n }\n }\n\n function scheduleNext(): void {\n if (stopping) return\n pollTimer = setTimeout(() => {\n void (async () => {\n if (stopping) return\n try {\n const nextChecksum = await computeStructureChecksum()\n if (nextChecksum !== previousChecksum) {\n previousChecksum = nextChecksum\n await runOnce('structure change')\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n logger.error(`[generate:watch] Poll cycle failed: ${message}`)\n } finally {\n if (!stopping) scheduleNext()\n }\n })()\n }, pollMs)\n pollTimer.unref?.()\n }\n\n void (async () => {\n try {\n if (!skipInitial) {\n await runOnce('initial')\n }\n previousChecksum = await computeStructureChecksum()\n if (!quiet) {\n if (skipInitial) {\n logger.log('[generate:watch] Skipping initial regeneration and watching the current generated state.')\n }\n logger.log(`[generate:watch] Watching structural module files every ${pollMs}ms (in-process).`)\n }\n scheduleNext()\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n logger.error(`[generate:watch] Initial setup failed: ${message}`)\n // Even if the initial bootstrap fails, schedule polling so a later\n // checksum recovery still picks up the next change on disk.\n scheduleNext()\n }\n })()\n\n async function close(): Promise<void> {\n if (stopping) {\n await done\n return\n }\n stopping = true\n if (pollTimer) {\n clearTimeout(pollTimer)\n pollTimer = null\n }\n if (doneResolve) {\n doneResolve()\n doneResolve = null\n }\n }\n\n return {\n done,\n close,\n }\n}\n"],
|
|
5
|
+
"mappings": "AAkDA,MAAM,cAAc;AACpB,MAAM,kBAAkB;AAExB,SAAS,cAAc,OAAmC;AACxD,MAAI,OAAO,UAAU,YAAY,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO;AACjE,QAAM,UAAU,KAAK,MAAM,KAAK;AAChC,SAAO,WAAW,cAAc,UAAU;AAC5C;AAOO,SAAS,8BACd,SACuB;AACvB,QAAM,SAAS,QAAQ,UAAU;AACjC,QAAM,QAAQ,QAAQ,UAAU;AAChC,QAAM,SAAS,cAAc,QAAQ,MAAM;AAC3C,QAAM,cAAc,QAAQ,gBAAgB;AAC5C,QAAM,EAAE,0BAA0B,cAAc,IAAI;AAEpD,MAAI,WAAW;AACf,MAAI,YAAkD;AACtD,MAAI,UAAU;AACd,MAAI,gBAA+B;AACnC,MAAI,mBAAmB;AACvB,MAAI,cAAmC;AACvC,QAAM,OAAO,IAAI,QAAc,CAAC,YAAY;AAC1C,kBAAc;AAAA,EAChB,CAAC;AAED,iBAAe,QAAQ,QAA+B;AACpD,QAAI,SAAS;AACX,sBAAgB;AAChB;AAAA,IACF;AACA,cAAU;AACV,QAAI;AACF,UAAI,CAAC,OAAO;AACV,eAAO,IAAI,kCAAkC,MAAM,MAAM;AAAA,MAC3D;AACA,YAAM,cAAc,MAAM;AAC1B,UAAI,CAAC,OAAO;AACV,eAAO,IAAI,wCAAwC;AAAA,MACrD;AAAA,IACF,SAAS,OAAO;AACd,YAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,aAAO,MAAM,uCAAuC,OAAO,EAAE;AAAA,IAC/D,UAAE;AACA,gBAAU;AACV,UAAI,iBAAiB,CAAC,UAAU;AAC9B,cAAM,SAAS;AACf,wBAAgB;AAChB,cAAM,QAAQ,MAAM;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AAEA,WAAS,eAAqB;AAC5B,QAAI,SAAU;AACd,gBAAY,WAAW,MAAM;AAC3B,YAAM,YAAY;AAChB,YAAI,SAAU;AACd,YAAI;AACF,gBAAM,eAAe,MAAM,yBAAyB;AACpD,cAAI,iBAAiB,kBAAkB;AACrC,+BAAmB;AACnB,kBAAM,QAAQ,kBAAkB;AAAA,UAClC;AAAA,QACF,SAAS,OAAO;AACd,gBAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,iBAAO,MAAM,uCAAuC,OAAO,EAAE;AAAA,QAC/D,UAAE;AACA,cAAI,CAAC,SAAU,cAAa;AAAA,QAC9B;AAAA,MACF,GAAG;AAAA,IACL,GAAG,MAAM;AACT,cAAU,QAAQ;AAAA,EACpB;AAEA,QAAM,YAAY;AAChB,QAAI;AACF,UAAI,CAAC,aAAa;AAChB,cAAM,QAAQ,SAAS;AAAA,MACzB;AACA,yBAAmB,MAAM,yBAAyB;AAClD,UAAI,CAAC,OAAO;AACV,YAAI,aAAa;AACf,iBAAO,IAAI,0FAA0F;AAAA,QACvG;AACA,eAAO,IAAI,2DAA2D,MAAM,kBAAkB;AAAA,MAChG;AACA,mBAAa;AAAA,IACf,SAAS,OAAO;AACd,YAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,aAAO,MAAM,0CAA0C,OAAO,EAAE;AAGhE,mBAAa;AAAA,IACf;AAAA,EACF,GAAG;AAEH,iBAAe,QAAuB;AACpC,QAAI,UAAU;AACZ,YAAM;AACN;AAAA,IACF;AACA,eAAW;AACX,QAAI,WAAW;AACb,mBAAa,SAAS;AACtB,kBAAY;AAAA,IACd;AACA,QAAI,aAAa;AACf,kBAAY;AACZ,oBAAc;AAAA,IAChB;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/mercato.js
CHANGED
|
@@ -16,6 +16,12 @@ import {
|
|
|
16
16
|
resolveLazySchedulerRestart
|
|
17
17
|
} from "./lib/auto-spawn-scheduler.js";
|
|
18
18
|
import { startLazySchedulerSupervisor } from "./lib/scheduler-supervisor.js";
|
|
19
|
+
import {
|
|
20
|
+
startInProcessGenerateWatcher
|
|
21
|
+
} from "./lib/in-process-generate-watcher.js";
|
|
22
|
+
import {
|
|
23
|
+
resolveGenerateWatcherMode
|
|
24
|
+
} from "./lib/in-process-generate-watcher-mode.js";
|
|
19
25
|
import { parseModuleInstallArgs } from "./lib/module-install-args.js";
|
|
20
26
|
import { resolveNextBuildIdCandidate } from "./lib/next-build-id.js";
|
|
21
27
|
import { acquireServerStartLock } from "./lib/server-start-lock.js";
|
|
@@ -414,6 +420,45 @@ async function runPostGenerateStructuralCachePurge(quiet) {
|
|
|
414
420
|
}
|
|
415
421
|
}
|
|
416
422
|
}
|
|
423
|
+
async function runGeneratorSuite(quiet) {
|
|
424
|
+
const { createResolver } = await import("./lib/resolver.js");
|
|
425
|
+
const {
|
|
426
|
+
generateEntityIds,
|
|
427
|
+
generateModuleRegistry,
|
|
428
|
+
generateModuleRegistryApp,
|
|
429
|
+
generateModuleRegistryCli,
|
|
430
|
+
generateModuleEntities,
|
|
431
|
+
generateModuleDi,
|
|
432
|
+
generateModulePackageSources,
|
|
433
|
+
generateOpenApi
|
|
434
|
+
} = await import("./lib/generators/index.js");
|
|
435
|
+
const resolver = createResolver();
|
|
436
|
+
await generateEntityIds({ resolver, quiet });
|
|
437
|
+
await generateModuleRegistry({ resolver, quiet });
|
|
438
|
+
await generateModuleRegistryApp({ resolver, quiet });
|
|
439
|
+
await generateModuleRegistryCli({ resolver, quiet });
|
|
440
|
+
await generateModuleEntities({ resolver, quiet });
|
|
441
|
+
await generateModuleDi({ resolver, quiet });
|
|
442
|
+
await generateModulePackageSources({ resolver, quiet });
|
|
443
|
+
await generateOpenApi({ resolver, quiet });
|
|
444
|
+
}
|
|
445
|
+
function createGenerateWatchChecksumFn() {
|
|
446
|
+
return async () => {
|
|
447
|
+
const { createResolver } = await import("./lib/resolver.js");
|
|
448
|
+
const { calculateStructureChecksum } = await import("./lib/utils.js");
|
|
449
|
+
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
|
+
]);
|
|
454
|
+
for (const entry of resolver.loadEnabledModules()) {
|
|
455
|
+
const roots = resolver.getModulePaths(entry);
|
|
456
|
+
tracked.add(roots.appBase);
|
|
457
|
+
tracked.add(roots.pkgBase);
|
|
458
|
+
}
|
|
459
|
+
return calculateStructureChecksum(Array.from(tracked));
|
|
460
|
+
};
|
|
461
|
+
}
|
|
417
462
|
async function buildAllModules() {
|
|
418
463
|
const modules = getCliModules();
|
|
419
464
|
let appCli = [];
|
|
@@ -1204,28 +1249,6 @@ async function run(argv = process.argv) {
|
|
|
1204
1249
|
}
|
|
1205
1250
|
]
|
|
1206
1251
|
});
|
|
1207
|
-
const runGeneratorSuite = async (quiet) => {
|
|
1208
|
-
const { createResolver } = await import("./lib/resolver.js");
|
|
1209
|
-
const {
|
|
1210
|
-
generateEntityIds,
|
|
1211
|
-
generateModuleRegistry,
|
|
1212
|
-
generateModuleRegistryApp,
|
|
1213
|
-
generateModuleRegistryCli,
|
|
1214
|
-
generateModuleEntities,
|
|
1215
|
-
generateModuleDi,
|
|
1216
|
-
generateModulePackageSources,
|
|
1217
|
-
generateOpenApi
|
|
1218
|
-
} = await import("./lib/generators/index.js");
|
|
1219
|
-
const resolver = createResolver();
|
|
1220
|
-
await generateEntityIds({ resolver, quiet });
|
|
1221
|
-
await generateModuleRegistry({ resolver, quiet });
|
|
1222
|
-
await generateModuleRegistryApp({ resolver, quiet });
|
|
1223
|
-
await generateModuleRegistryCli({ resolver, quiet });
|
|
1224
|
-
await generateModuleEntities({ resolver, quiet });
|
|
1225
|
-
await generateModuleDi({ resolver, quiet });
|
|
1226
|
-
await generateModulePackageSources({ resolver, quiet });
|
|
1227
|
-
await generateOpenApi({ resolver, quiet });
|
|
1228
|
-
};
|
|
1229
1252
|
all.push({
|
|
1230
1253
|
id: "generate",
|
|
1231
1254
|
cli: [
|
|
@@ -1242,70 +1265,40 @@ async function run(argv = process.argv) {
|
|
|
1242
1265
|
{
|
|
1243
1266
|
command: "watch",
|
|
1244
1267
|
run: async (args) => {
|
|
1245
|
-
const { createResolver } = await import("./lib/resolver.js");
|
|
1246
|
-
const { calculateStructureChecksum } = await import("./lib/utils.js");
|
|
1247
1268
|
const quiet = args.includes("--quiet") || args.includes("-q");
|
|
1248
1269
|
const skipInitial = args.includes("--skip-initial");
|
|
1249
1270
|
const intervalArg = args.find((arg) => arg.startsWith("--interval="));
|
|
1250
1271
|
const parsedInterval = intervalArg ? Number.parseInt(intervalArg.split("=")[1] ?? "", 10) : NaN;
|
|
1251
1272
|
const intervalMs = Number.isFinite(parsedInterval) && parsedInterval >= 250 ? parsedInterval : 1e3;
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
path.join(resolver.getAppDir(), "src", "modules.ts"),
|
|
1259
|
-
path.join(resolver.getAppDir(), "src", "modules")
|
|
1260
|
-
]);
|
|
1261
|
-
for (const entry of resolver.loadEnabledModules()) {
|
|
1262
|
-
const roots = resolver.getModulePaths(entry);
|
|
1263
|
-
tracked.add(roots.appBase);
|
|
1264
|
-
tracked.add(roots.pkgBase);
|
|
1265
|
-
}
|
|
1266
|
-
return Array.from(tracked);
|
|
1267
|
-
};
|
|
1268
|
-
const runWatchGeneration = async (reason) => {
|
|
1269
|
-
if (running) {
|
|
1270
|
-
pending = true;
|
|
1271
|
-
return;
|
|
1272
|
-
}
|
|
1273
|
-
running = true;
|
|
1274
|
-
try {
|
|
1275
|
-
if (!quiet) {
|
|
1276
|
-
console.log(`[generate:watch] Regenerating (${reason})...`);
|
|
1277
|
-
}
|
|
1273
|
+
const watcher = startInProcessGenerateWatcher({
|
|
1274
|
+
pollMs: intervalMs,
|
|
1275
|
+
skipInitial,
|
|
1276
|
+
quiet,
|
|
1277
|
+
computeStructureChecksum: createGenerateWatchChecksumFn(),
|
|
1278
|
+
runGenerators: async () => {
|
|
1278
1279
|
await runGeneratorSuite(true);
|
|
1279
1280
|
await runPostGenerateStructuralCachePurge(true);
|
|
1280
|
-
if (!quiet) {
|
|
1281
|
-
console.log("[generate:watch] Generators completed.");
|
|
1282
|
-
}
|
|
1283
|
-
} catch (error) {
|
|
1284
|
-
console.error("[generate:watch] Generation failed:", error instanceof Error ? error.message : error);
|
|
1285
|
-
} finally {
|
|
1286
|
-
running = false;
|
|
1287
|
-
if (pending) {
|
|
1288
|
-
pending = false;
|
|
1289
|
-
await runWatchGeneration("queued change");
|
|
1290
|
-
}
|
|
1291
1281
|
}
|
|
1282
|
+
});
|
|
1283
|
+
const shutdownSignals = ["SIGINT", "SIGTERM"];
|
|
1284
|
+
let shuttingDown = false;
|
|
1285
|
+
const handleSignal = () => {
|
|
1286
|
+
if (shuttingDown) return;
|
|
1287
|
+
shuttingDown = true;
|
|
1288
|
+
void watcher.close();
|
|
1292
1289
|
};
|
|
1293
|
-
|
|
1294
|
-
|
|
1290
|
+
for (const signal of shutdownSignals) {
|
|
1291
|
+
process.once(signal, handleSignal);
|
|
1295
1292
|
}
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1293
|
+
const keepAlive = setInterval(() => {
|
|
1294
|
+
}, 1 << 30);
|
|
1295
|
+
try {
|
|
1296
|
+
await watcher.done;
|
|
1297
|
+
} finally {
|
|
1298
|
+
clearInterval(keepAlive);
|
|
1299
|
+
for (const signal of shutdownSignals) {
|
|
1300
|
+
process.removeListener(signal, handleSignal);
|
|
1300
1301
|
}
|
|
1301
|
-
console.log(`[generate:watch] Watching structural module files every ${intervalMs}ms`);
|
|
1302
|
-
}
|
|
1303
|
-
while (true) {
|
|
1304
|
-
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
1305
|
-
const nextChecksum = calculateStructureChecksum(getTrackedPaths());
|
|
1306
|
-
if (nextChecksum === previousChecksum) continue;
|
|
1307
|
-
previousChecksum = nextChecksum;
|
|
1308
|
-
await runWatchGeneration("structure change");
|
|
1309
1302
|
}
|
|
1310
1303
|
}
|
|
1311
1304
|
},
|
|
@@ -1400,6 +1393,8 @@ async function run(argv = process.argv) {
|
|
|
1400
1393
|
let envChangePromiseResolve = null;
|
|
1401
1394
|
let activeLazySupervisor = null;
|
|
1402
1395
|
let activeLazySchedulerSupervisor = null;
|
|
1396
|
+
let activeGenerateWatcher = null;
|
|
1397
|
+
const generateWatcherMode = resolveGenerateWatcherMode(process.env);
|
|
1403
1398
|
const envReloader = createDevEnvReloader(appDir, process.env, initialProcessEnvironmentEntries);
|
|
1404
1399
|
function cleanup() {
|
|
1405
1400
|
console.log("[server] Shutting down...");
|
|
@@ -1414,6 +1409,9 @@ async function run(argv = process.argv) {
|
|
|
1414
1409
|
if (activeLazySchedulerSupervisor) {
|
|
1415
1410
|
void activeLazySchedulerSupervisor.close().catch(() => void 0);
|
|
1416
1411
|
}
|
|
1412
|
+
if (activeGenerateWatcher) {
|
|
1413
|
+
void activeGenerateWatcher.close().catch(() => void 0);
|
|
1414
|
+
}
|
|
1417
1415
|
}
|
|
1418
1416
|
async function cleanupAndWait() {
|
|
1419
1417
|
cleanup();
|
|
@@ -1439,6 +1437,13 @@ async function run(argv = process.argv) {
|
|
|
1439
1437
|
}
|
|
1440
1438
|
activeLazySchedulerSupervisor = null;
|
|
1441
1439
|
}
|
|
1440
|
+
if (activeGenerateWatcher) {
|
|
1441
|
+
try {
|
|
1442
|
+
await activeGenerateWatcher.close();
|
|
1443
|
+
} catch {
|
|
1444
|
+
}
|
|
1445
|
+
activeGenerateWatcher = null;
|
|
1446
|
+
}
|
|
1442
1447
|
const lockFile = path.join(appDir, ".mercato", "next", "dev", "lock");
|
|
1443
1448
|
try {
|
|
1444
1449
|
fs.unlinkSync(lockFile);
|
|
@@ -1569,6 +1574,25 @@ async function run(argv = process.argv) {
|
|
|
1569
1574
|
managedExitPromises.push(waitForManagedProcessExit(schedulerProcess, "Scheduler polling engine"));
|
|
1570
1575
|
}
|
|
1571
1576
|
}
|
|
1577
|
+
if (generateWatcherMode === "in-process") {
|
|
1578
|
+
console.log("[server] In-process generate watcher enabled \u2014 structural changes will regenerate without a sidecar process.");
|
|
1579
|
+
activeGenerateWatcher = startInProcessGenerateWatcher({
|
|
1580
|
+
// `--skip-initial` equivalent: `yarn dev` always runs an
|
|
1581
|
+
// initial `mercato generate` before reaching the server
|
|
1582
|
+
// command, so the watcher must not re-run generators at
|
|
1583
|
+
// boot time. Otherwise dev startup pays a generator pass
|
|
1584
|
+
// twice in a row.
|
|
1585
|
+
skipInitial: true,
|
|
1586
|
+
quiet: false,
|
|
1587
|
+
computeStructureChecksum: createGenerateWatchChecksumFn(),
|
|
1588
|
+
runGenerators: async () => {
|
|
1589
|
+
await runGeneratorSuite(true);
|
|
1590
|
+
await runPostGenerateStructuralCachePurge(true);
|
|
1591
|
+
}
|
|
1592
|
+
});
|
|
1593
|
+
} else {
|
|
1594
|
+
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
|
+
}
|
|
1572
1596
|
const firstExit = await Promise.race(managedExitPromises);
|
|
1573
1597
|
await cleanupAndWait();
|
|
1574
1598
|
envChangePromiseResolve = null;
|