@open-mercato/cli 0.6.3-develop.3876.1.d40fe4ec2d → 0.6.3-develop.3894.1.352abf4240
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/lib/testing/integration.js +5 -1
- package/dist/lib/testing/integration.js.map +2 -2
- 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/lib/testing/integration.ts +5 -1
- 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
|
+
}
|
|
@@ -1209,6 +1209,10 @@ function buildReusableEnvironment(baseUrl, databaseUrl, queueBaseDir, captureScr
|
|
|
1209
1209
|
OM_ENABLE_ENTERPRISE_MODULES_SECURITY: process.env.OM_ENABLE_ENTERPRISE_MODULES_SECURITY ?? enterpriseModulesFlag,
|
|
1210
1210
|
OM_TEST_MODE: "1",
|
|
1211
1211
|
OM_TEST_AUTH_RATE_LIMIT_MODE: "opt-in",
|
|
1212
|
+
// Tests assert on access_logs immediately after CRUD reads; keep the
|
|
1213
|
+
// blocking write path on inside the integration runtime so tests do
|
|
1214
|
+
// not have to call flushPendingCrudAccessLogs() explicitly.
|
|
1215
|
+
OM_CRUD_ACCESS_LOG_BLOCKING: process.env.OM_CRUD_ACCESS_LOG_BLOCKING ?? "1",
|
|
1212
1216
|
OM_WEBHOOKS_ALLOW_PRIVATE_URLS: process.env.OM_WEBHOOKS_ALLOW_PRIVATE_URLS ?? "1",
|
|
1213
1217
|
ENABLE_CRUD_API_CACHE: "true",
|
|
1214
1218
|
MOCK_GATEWAY_WEBHOOK_SECRET: "open-mercato-mock-dev-webhook-secret",
|
|
@@ -2339,7 +2343,7 @@ async function startEphemeralEnvironment(options) {
|
|
|
2339
2343
|
NEXT_PUBLIC_UMES_DEVTOOLS: "true",
|
|
2340
2344
|
CI: "true",
|
|
2341
2345
|
TENANT_DATA_ENCRYPTION_FALLBACK_KEY: process.env.TENANT_DATA_ENCRYPTION_FALLBACK_KEY ?? "om-ephemeral-integration-fallback-key",
|
|
2342
|
-
AUTO_SPAWN_WORKERS: "
|
|
2346
|
+
AUTO_SPAWN_WORKERS: process.env.AUTO_SPAWN_WORKERS ?? "true",
|
|
2343
2347
|
AUTO_SPAWN_SCHEDULER: "false",
|
|
2344
2348
|
OM_CLI_QUIET: "1",
|
|
2345
2349
|
MERCATO_QUIET: "1",
|