@open-mercato/cli 0.6.4-develop.3938.1.c97dbde799 → 0.6.4-develop.3944.1.4100aa7fbe
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/generate-watch-structure.js +153 -0
- package/dist/lib/generate-watch-structure.js.map +7 -0
- package/dist/mercato.js +100 -17
- package/dist/mercato.js.map +2 -2
- package/package.json +5 -5
- package/src/lib/__tests__/dev-env-reload.test.ts +1 -1
- package/src/lib/__tests__/generate-watch-structure.test.ts +80 -0
- package/src/lib/generate-watch-structure.ts +170 -0
- package/src/mercato.ts +112 -18
package/.turbo/turbo-build.log
CHANGED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
MODULE_CODE_EXTENSIONS,
|
|
6
|
+
SCAN_CONFIGS,
|
|
7
|
+
scanModuleDir,
|
|
8
|
+
stripModuleCodeExtension
|
|
9
|
+
} from "./generators/scanner.js";
|
|
10
|
+
const STRUCTURAL_CONVENTION_FILES = [
|
|
11
|
+
"index.ts",
|
|
12
|
+
"cli.ts",
|
|
13
|
+
"di.ts",
|
|
14
|
+
"acl.ts",
|
|
15
|
+
"setup.ts",
|
|
16
|
+
"encryption.ts",
|
|
17
|
+
"ce.ts",
|
|
18
|
+
"search.ts",
|
|
19
|
+
"events.ts",
|
|
20
|
+
"notifications.ts",
|
|
21
|
+
"notifications.client.ts",
|
|
22
|
+
"notifications.handlers.ts",
|
|
23
|
+
"translations.ts",
|
|
24
|
+
"generators.ts",
|
|
25
|
+
"ai-tools.ts",
|
|
26
|
+
"ai-agents.ts",
|
|
27
|
+
"analytics.ts",
|
|
28
|
+
"workflows.ts",
|
|
29
|
+
"inbox-actions.ts",
|
|
30
|
+
"message-types.ts",
|
|
31
|
+
"message-objects.ts",
|
|
32
|
+
"integration.ts",
|
|
33
|
+
"security.mfa-providers.ts",
|
|
34
|
+
"security.sudo.ts",
|
|
35
|
+
"data/entities.ts",
|
|
36
|
+
"data/extensions.ts",
|
|
37
|
+
"data/fields.ts",
|
|
38
|
+
"data/enrichers.ts",
|
|
39
|
+
"data/guards.ts",
|
|
40
|
+
"api/interceptors.ts",
|
|
41
|
+
"commands/interceptors.ts",
|
|
42
|
+
"widgets/components.ts",
|
|
43
|
+
"widgets/injection-table.ts",
|
|
44
|
+
"frontend/middleware.ts",
|
|
45
|
+
"backend/middleware.ts"
|
|
46
|
+
];
|
|
47
|
+
const CONTENT_SENSITIVE_SCAN_CONFIGS = [
|
|
48
|
+
SCAN_CONFIGS.apiRoutes,
|
|
49
|
+
SCAN_CONFIGS.apiPlainFiles,
|
|
50
|
+
SCAN_CONFIGS.subscribers,
|
|
51
|
+
SCAN_CONFIGS.workers,
|
|
52
|
+
SCAN_CONFIGS.dashboardWidgets,
|
|
53
|
+
SCAN_CONFIGS.injectionWidgets
|
|
54
|
+
];
|
|
55
|
+
const ROUTE_SHAPE_SCAN_CONFIGS = [
|
|
56
|
+
SCAN_CONFIGS.frontendPages,
|
|
57
|
+
SCAN_CONFIGS.backendPages
|
|
58
|
+
];
|
|
59
|
+
function checksum(value) {
|
|
60
|
+
return crypto.createHash("md5").update(value).digest("hex");
|
|
61
|
+
}
|
|
62
|
+
function fileRecord(filePath, base, mode) {
|
|
63
|
+
if (!fs.existsSync(filePath)) return null;
|
|
64
|
+
let stat;
|
|
65
|
+
try {
|
|
66
|
+
stat = fs.statSync(filePath);
|
|
67
|
+
} catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
if (!stat.isFile()) return null;
|
|
71
|
+
const rel = path.relative(base, filePath).replace(/\\/g, "/");
|
|
72
|
+
if (mode === "shape") {
|
|
73
|
+
return `file:${rel}`;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
return `file:${rel}:${stat.size}:${checksum(fs.readFileSync(filePath, "utf8"))}`;
|
|
77
|
+
} catch {
|
|
78
|
+
return `file:${rel}:unreadable`;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function resolveCodeFile(base, relativePath) {
|
|
82
|
+
const stripped = stripModuleCodeExtension(relativePath);
|
|
83
|
+
const candidates = MODULE_CODE_EXTENSIONS.map((extension) => `${stripped}${extension}`);
|
|
84
|
+
for (const candidate of candidates) {
|
|
85
|
+
const filePath = path.join(base, ...candidate.split("/"));
|
|
86
|
+
if (fs.existsSync(filePath)) return filePath;
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
function hasInlinePageMetadata(filePath) {
|
|
91
|
+
try {
|
|
92
|
+
const source = fs.readFileSync(filePath, "utf8");
|
|
93
|
+
return /\bexport\s+(?:const|let|var|function|class)\s+metadata\b/.test(source) || /\bexport\s+\{[^}]*\bmetadata\b[^}]*\}/.test(source);
|
|
94
|
+
} catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function addConventionRecords(records, roots) {
|
|
99
|
+
for (const base of [roots.pkgBase, roots.appBase]) {
|
|
100
|
+
records.push(`module-root:${base}:${fs.existsSync(base) ? "present" : "missing"}`);
|
|
101
|
+
for (const relativePath of STRUCTURAL_CONVENTION_FILES) {
|
|
102
|
+
const filePath = resolveCodeFile(base, relativePath);
|
|
103
|
+
if (!filePath) {
|
|
104
|
+
records.push(`missing:${base}:${relativePath}`);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const record = fileRecord(filePath, base, "content");
|
|
108
|
+
if (record) records.push(record);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function addScannedRecords(records, roots) {
|
|
113
|
+
for (const config of CONTENT_SENSITIVE_SCAN_CONFIGS) {
|
|
114
|
+
for (const scanned of scanModuleDir(roots, config)) {
|
|
115
|
+
const base = scanned.fromApp ? roots.appBase : roots.pkgBase;
|
|
116
|
+
const filePath = path.join(base, ...config.folder.split("/"), ...scanned.relPath.split("/"));
|
|
117
|
+
const record = fileRecord(filePath, base, "content");
|
|
118
|
+
if (record) records.push(`${config.folder}:${record}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
for (const config of ROUTE_SHAPE_SCAN_CONFIGS) {
|
|
122
|
+
for (const scanned of scanModuleDir(roots, config)) {
|
|
123
|
+
const base = scanned.fromApp ? roots.appBase : roots.pkgBase;
|
|
124
|
+
const folderPath = path.join(base, ...config.folder.split("/"));
|
|
125
|
+
const filePath = path.join(folderPath, ...scanned.relPath.split("/"));
|
|
126
|
+
const pageRecord = fileRecord(filePath, base, hasInlinePageMetadata(filePath) ? "content" : "shape");
|
|
127
|
+
if (pageRecord) records.push(`${config.folder}:${pageRecord}`);
|
|
128
|
+
const dir = path.dirname(filePath);
|
|
129
|
+
const stem = stripModuleCodeExtension(path.basename(filePath));
|
|
130
|
+
const metaCandidates = stem === "page" ? ["page.meta", "meta"] : [`${stem}.meta`, "meta"];
|
|
131
|
+
for (const candidate of metaCandidates) {
|
|
132
|
+
const metaPath = resolveCodeFile(dir, candidate);
|
|
133
|
+
if (!metaPath) continue;
|
|
134
|
+
const record = fileRecord(metaPath, base, "content");
|
|
135
|
+
if (record) records.push(`${config.folder}:meta:${record}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function calculateGenerateWatchStructureChecksum(options) {
|
|
141
|
+
const records = [];
|
|
142
|
+
const modulesRecord = fileRecord(options.modulesFile, path.dirname(options.modulesFile), "content");
|
|
143
|
+
records.push(modulesRecord ?? `missing:${options.modulesFile}`);
|
|
144
|
+
for (const roots of options.moduleRoots) {
|
|
145
|
+
addConventionRecords(records, roots);
|
|
146
|
+
addScannedRecords(records, roots);
|
|
147
|
+
}
|
|
148
|
+
return checksum(records.sort((a, b) => a.localeCompare(b)).join("\n"));
|
|
149
|
+
}
|
|
150
|
+
export {
|
|
151
|
+
calculateGenerateWatchStructureChecksum
|
|
152
|
+
};
|
|
153
|
+
//# sourceMappingURL=generate-watch-structure.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/lib/generate-watch-structure.ts"],
|
|
4
|
+
"sourcesContent": ["import crypto from 'node:crypto'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport {\n MODULE_CODE_EXTENSIONS,\n SCAN_CONFIGS,\n scanModuleDir,\n stripModuleCodeExtension,\n type ModuleRoots,\n} from './generators/scanner'\n\nconst STRUCTURAL_CONVENTION_FILES = [\n 'index.ts',\n 'cli.ts',\n 'di.ts',\n 'acl.ts',\n 'setup.ts',\n 'encryption.ts',\n 'ce.ts',\n 'search.ts',\n 'events.ts',\n 'notifications.ts',\n 'notifications.client.ts',\n 'notifications.handlers.ts',\n 'translations.ts',\n 'generators.ts',\n 'ai-tools.ts',\n 'ai-agents.ts',\n 'analytics.ts',\n 'workflows.ts',\n 'inbox-actions.ts',\n 'message-types.ts',\n 'message-objects.ts',\n 'integration.ts',\n 'security.mfa-providers.ts',\n 'security.sudo.ts',\n 'data/entities.ts',\n 'data/extensions.ts',\n 'data/fields.ts',\n 'data/enrichers.ts',\n 'data/guards.ts',\n 'api/interceptors.ts',\n 'commands/interceptors.ts',\n 'widgets/components.ts',\n 'widgets/injection-table.ts',\n 'frontend/middleware.ts',\n 'backend/middleware.ts',\n] as const\n\nconst CONTENT_SENSITIVE_SCAN_CONFIGS = [\n SCAN_CONFIGS.apiRoutes,\n SCAN_CONFIGS.apiPlainFiles,\n SCAN_CONFIGS.subscribers,\n SCAN_CONFIGS.workers,\n SCAN_CONFIGS.dashboardWidgets,\n SCAN_CONFIGS.injectionWidgets,\n] as const\n\nconst ROUTE_SHAPE_SCAN_CONFIGS = [\n SCAN_CONFIGS.frontendPages,\n SCAN_CONFIGS.backendPages,\n] as const\n\nfunction checksum(value: string): string {\n return crypto.createHash('md5').update(value).digest('hex')\n}\n\nfunction fileRecord(filePath: string, base: string, mode: 'content' | 'shape'): string | null {\n if (!fs.existsSync(filePath)) return null\n let stat: fs.Stats\n try {\n stat = fs.statSync(filePath)\n } catch {\n return null\n }\n if (!stat.isFile()) return null\n const rel = path.relative(base, filePath).replace(/\\\\/g, '/')\n if (mode === 'shape') {\n return `file:${rel}`\n }\n try {\n return `file:${rel}:${stat.size}:${checksum(fs.readFileSync(filePath, 'utf8'))}`\n } catch {\n return `file:${rel}:unreadable`\n }\n}\n\nfunction resolveCodeFile(base: string, relativePath: string): string | null {\n const stripped = stripModuleCodeExtension(relativePath)\n const candidates = MODULE_CODE_EXTENSIONS.map((extension) => `${stripped}${extension}`)\n for (const candidate of candidates) {\n const filePath = path.join(base, ...candidate.split('/'))\n if (fs.existsSync(filePath)) return filePath\n }\n return null\n}\n\nfunction hasInlinePageMetadata(filePath: string): boolean {\n try {\n const source = fs.readFileSync(filePath, 'utf8')\n return /\\bexport\\s+(?:const|let|var|function|class)\\s+metadata\\b/.test(source)\n || /\\bexport\\s+\\{[^}]*\\bmetadata\\b[^}]*\\}/.test(source)\n } catch {\n return false\n }\n}\n\nfunction addConventionRecords(records: string[], roots: ModuleRoots): void {\n for (const base of [roots.pkgBase, roots.appBase]) {\n records.push(`module-root:${base}:${fs.existsSync(base) ? 'present' : 'missing'}`)\n for (const relativePath of STRUCTURAL_CONVENTION_FILES) {\n const filePath = resolveCodeFile(base, relativePath)\n if (!filePath) {\n records.push(`missing:${base}:${relativePath}`)\n continue\n }\n const record = fileRecord(filePath, base, 'content')\n if (record) records.push(record)\n }\n }\n}\n\nfunction addScannedRecords(records: string[], roots: ModuleRoots): void {\n for (const config of CONTENT_SENSITIVE_SCAN_CONFIGS) {\n for (const scanned of scanModuleDir(roots, config)) {\n const base = scanned.fromApp ? roots.appBase : roots.pkgBase\n const filePath = path.join(base, ...config.folder.split('/'), ...scanned.relPath.split('/'))\n const record = fileRecord(filePath, base, 'content')\n if (record) records.push(`${config.folder}:${record}`)\n }\n }\n\n for (const config of ROUTE_SHAPE_SCAN_CONFIGS) {\n for (const scanned of scanModuleDir(roots, config)) {\n const base = scanned.fromApp ? roots.appBase : roots.pkgBase\n const folderPath = path.join(base, ...config.folder.split('/'))\n const filePath = path.join(folderPath, ...scanned.relPath.split('/'))\n const pageRecord = fileRecord(filePath, base, hasInlinePageMetadata(filePath) ? 'content' : 'shape')\n if (pageRecord) records.push(`${config.folder}:${pageRecord}`)\n\n const dir = path.dirname(filePath)\n const stem = stripModuleCodeExtension(path.basename(filePath))\n const metaCandidates = stem === 'page'\n ? ['page.meta', 'meta']\n : [`${stem}.meta`, 'meta']\n for (const candidate of metaCandidates) {\n const metaPath = resolveCodeFile(dir, candidate)\n if (!metaPath) continue\n const record = fileRecord(metaPath, base, 'content')\n if (record) records.push(`${config.folder}:meta:${record}`)\n }\n }\n }\n}\n\nexport function calculateGenerateWatchStructureChecksum(options: {\n modulesFile: string\n moduleRoots: ModuleRoots[]\n}): string {\n const records: string[] = []\n const modulesRecord = fileRecord(options.modulesFile, path.dirname(options.modulesFile), 'content')\n records.push(modulesRecord ?? `missing:${options.modulesFile}`)\n\n for (const roots of options.moduleRoots) {\n addConventionRecords(records, roots)\n addScannedRecords(records, roots)\n }\n\n return checksum(records.sort((a, b) => a.localeCompare(b)).join('\\n'))\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/mercato.js
CHANGED
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
import { parseModuleInstallArgs } from "./lib/module-install-args.js";
|
|
26
26
|
import { resolveNextBuildIdCandidate } from "./lib/next-build-id.js";
|
|
27
27
|
import { acquireServerStartLock } from "./lib/server-start-lock.js";
|
|
28
|
-
import { createDevEnvReloader, watchDevEnvFiles
|
|
28
|
+
import { createDevEnvReloader, watchDevEnvFiles } from "./lib/dev-env-reload.js";
|
|
29
29
|
const lazyIntegration = () => import("./lib/testing/integration.js");
|
|
30
30
|
import path from "node:path";
|
|
31
31
|
import fs from "node:fs";
|
|
@@ -246,6 +246,82 @@ 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
|
+
}
|
|
249
325
|
function waitForManagedProcessExit(proc, label) {
|
|
250
326
|
return new Promise((resolve) => {
|
|
251
327
|
proc.on("exit", (code, signal) => {
|
|
@@ -445,18 +521,17 @@ async function runGeneratorSuite(quiet) {
|
|
|
445
521
|
function createGenerateWatchChecksumFn() {
|
|
446
522
|
return async () => {
|
|
447
523
|
const { createResolver } = await import("./lib/resolver.js");
|
|
448
|
-
const {
|
|
524
|
+
const { calculateGenerateWatchStructureChecksum } = await import("./lib/generate-watch-structure.js");
|
|
449
525
|
const resolver = createResolver();
|
|
450
|
-
const
|
|
451
|
-
path.join(resolver.getAppDir(), "src", "modules.ts"),
|
|
452
|
-
path.join(resolver.getAppDir(), "src", "modules")
|
|
453
|
-
]);
|
|
526
|
+
const moduleRoots = [];
|
|
454
527
|
for (const entry of resolver.loadEnabledModules()) {
|
|
455
528
|
const roots = resolver.getModulePaths(entry);
|
|
456
|
-
|
|
457
|
-
tracked.add(roots.pkgBase);
|
|
529
|
+
moduleRoots.push({ appBase: roots.appBase, pkgBase: roots.pkgBase });
|
|
458
530
|
}
|
|
459
|
-
return
|
|
531
|
+
return calculateGenerateWatchStructureChecksum({
|
|
532
|
+
modulesFile: path.join(resolver.getAppDir(), "src", "modules.ts"),
|
|
533
|
+
moduleRoots
|
|
534
|
+
});
|
|
460
535
|
};
|
|
461
536
|
}
|
|
462
537
|
async function buildAllModules() {
|
|
@@ -1394,6 +1469,7 @@ async function run(argv = process.argv) {
|
|
|
1394
1469
|
let activeLazySupervisor = null;
|
|
1395
1470
|
let activeLazySchedulerSupervisor = null;
|
|
1396
1471
|
let activeGenerateWatcher = null;
|
|
1472
|
+
let lastRestartReason = null;
|
|
1397
1473
|
const generateWatcherMode = resolveGenerateWatcherMode(process.env);
|
|
1398
1474
|
const envReloader = createDevEnvReloader(appDir, process.env, initialProcessEnvironmentEntries);
|
|
1399
1475
|
function cleanup() {
|
|
@@ -1472,17 +1548,13 @@ async function run(argv = process.argv) {
|
|
|
1472
1548
|
filePath
|
|
1473
1549
|
});
|
|
1474
1550
|
});
|
|
1475
|
-
const stopRuntimeWatcher = watchDevRuntimeFiles(appDir, (filePath) => {
|
|
1476
|
-
devRestartPromiseResolve?.({
|
|
1477
|
-
label: "Runtime graph change",
|
|
1478
|
-
restart: true,
|
|
1479
|
-
filePath
|
|
1480
|
-
});
|
|
1481
|
-
});
|
|
1482
1551
|
const waitForDevRestart = () => new Promise((resolve) => {
|
|
1483
1552
|
devRestartPromiseResolve = resolve;
|
|
1484
1553
|
});
|
|
1485
1554
|
const startNextDev = (runtimeEnv) => new Promise((resolve) => {
|
|
1555
|
+
writeDevSplashRuntimeStarting(
|
|
1556
|
+
lastRestartReason ? `Restarting Next.js dev server. Reason: ${lastRestartReason}` : "Starting Next.js dev server"
|
|
1557
|
+
);
|
|
1486
1558
|
const nextProcess = spawn("node", [nextBin, "dev", "--turbopack"], {
|
|
1487
1559
|
stdio: ["inherit", "pipe", "pipe"],
|
|
1488
1560
|
env: runtimeEnv,
|
|
@@ -1490,11 +1562,17 @@ async function run(argv = process.argv) {
|
|
|
1490
1562
|
});
|
|
1491
1563
|
processes.push(nextProcess);
|
|
1492
1564
|
let combinedOutput = "";
|
|
1565
|
+
let reportedReady = false;
|
|
1493
1566
|
const appendOutput = (chunk) => {
|
|
1494
1567
|
combinedOutput += chunk;
|
|
1495
1568
|
if (combinedOutput.length > 32768) {
|
|
1496
1569
|
combinedOutput = combinedOutput.slice(-32768);
|
|
1497
1570
|
}
|
|
1571
|
+
if (!reportedReady && /\bready in\b/i.test(chunk)) {
|
|
1572
|
+
reportedReady = true;
|
|
1573
|
+
writeDevSplashRuntimeReady(lastRestartReason ?? void 0);
|
|
1574
|
+
lastRestartReason = null;
|
|
1575
|
+
}
|
|
1498
1576
|
};
|
|
1499
1577
|
nextProcess.stdout?.on("data", (chunk) => {
|
|
1500
1578
|
const text = typeof chunk === "string" ? chunk : chunk.toString();
|
|
@@ -1509,6 +1587,8 @@ async function run(argv = process.argv) {
|
|
|
1509
1587
|
nextProcess.on("exit", async (code, signal) => {
|
|
1510
1588
|
if (!didRetryCorruptedTurbopackCache && isTurbopackCacheCorruption(combinedOutput)) {
|
|
1511
1589
|
didRetryCorruptedTurbopackCache = true;
|
|
1590
|
+
lastRestartReason = "corrupted Turbopack dev cache";
|
|
1591
|
+
writeDevSplashRuntimeRestarting(lastRestartReason);
|
|
1512
1592
|
console.log("[server] Detected corrupted Turbopack dev cache. Clearing .mercato/next/dev and restarting Next.js once...");
|
|
1513
1593
|
removeTurbopackDevCache(appDir);
|
|
1514
1594
|
return resolve(await startNextDev(runtimeEnv));
|
|
@@ -1601,6 +1681,10 @@ async function run(argv = process.argv) {
|
|
|
1601
1681
|
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`.");
|
|
1602
1682
|
}
|
|
1603
1683
|
const firstExit = await Promise.race(managedExitPromises);
|
|
1684
|
+
if (isDevServerRestartResult(firstExit)) {
|
|
1685
|
+
lastRestartReason = `${firstExit.label.toLowerCase()} (${path.basename(firstExit.filePath)})`;
|
|
1686
|
+
writeDevSplashRuntimeRestarting(lastRestartReason);
|
|
1687
|
+
}
|
|
1604
1688
|
await cleanupAndWait();
|
|
1605
1689
|
devRestartPromiseResolve = null;
|
|
1606
1690
|
if (isDevServerRestartResult(firstExit)) {
|
|
@@ -1614,7 +1698,6 @@ async function run(argv = process.argv) {
|
|
|
1614
1698
|
}
|
|
1615
1699
|
} finally {
|
|
1616
1700
|
stopEnvWatcher();
|
|
1617
|
-
stopRuntimeWatcher();
|
|
1618
1701
|
}
|
|
1619
1702
|
}
|
|
1620
1703
|
},
|