@open-mercato/cli 0.5.1-develop.3032.01699048cb → 0.5.1-develop.3043.1a796c3920
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/agentic/shared/AGENTS.md.template +1 -1
- package/dist/lib/__integration__/TC-INT-007.spec.js +201 -0
- package/dist/lib/__integration__/TC-INT-007.spec.js.map +7 -0
- package/dist/lib/dev-env-reload.js +89 -0
- package/dist/lib/dev-env-reload.js.map +7 -0
- package/dist/lib/generators/extensions/ai-agents.js +218 -0
- package/dist/lib/generators/extensions/ai-agents.js.map +7 -0
- package/dist/lib/generators/extensions/ai-tools.js +56 -1
- package/dist/lib/generators/extensions/ai-tools.js.map +2 -2
- package/dist/lib/generators/extensions/index.js +2 -0
- package/dist/lib/generators/extensions/index.js.map +2 -2
- package/dist/lib/testing/integration-discovery.js +102 -5
- package/dist/lib/testing/integration-discovery.js.map +2 -2
- package/dist/mercato.js +153 -79
- package/dist/mercato.js.map +2 -2
- package/package.json +5 -5
- package/src/__tests__/mercato.test.ts +301 -25
- package/src/lib/__integration__/TC-INT-007.spec.ts +228 -0
- package/src/lib/__tests__/dev-env-reload.test.ts +62 -0
- package/src/lib/dev-env-reload.ts +110 -0
- package/src/lib/generators/__tests__/module-subset.test.ts +14 -0
- package/src/lib/generators/__tests__/output-snapshots.test.ts +17 -0
- package/src/lib/generators/__tests__/scanner.test.ts +1 -1
- package/src/lib/generators/__tests__/structural-contracts.test.ts +26 -0
- package/src/lib/generators/extensions/ai-agents.ts +240 -0
- package/src/lib/generators/extensions/ai-tools.ts +72 -1
- package/src/lib/generators/extensions/index.ts +2 -0
- package/src/lib/testing/__tests__/integration-discovery.test.ts +68 -0
- package/src/lib/testing/integration-discovery.ts +127 -3
- package/src/mercato.ts +190 -83
package/dist/mercato.js
CHANGED
|
@@ -7,10 +7,12 @@ import { resolveInitDerivedSecrets } from "./lib/init-secrets.js";
|
|
|
7
7
|
import { parseModuleInstallArgs } from "./lib/module-install-args.js";
|
|
8
8
|
import { resolveNextBuildIdCandidate } from "./lib/next-build-id.js";
|
|
9
9
|
import { acquireServerStartLock } from "./lib/server-start-lock.js";
|
|
10
|
+
import { createDevEnvReloader, watchDevEnvFiles } from "./lib/dev-env-reload.js";
|
|
10
11
|
const lazyIntegration = () => import("./lib/testing/integration.js");
|
|
11
12
|
import path from "node:path";
|
|
12
13
|
import fs from "node:fs";
|
|
13
14
|
let envLoaded = false;
|
|
15
|
+
const initialProcessEnvironmentEntries = Object.entries(process.env);
|
|
14
16
|
async function runWithCapturedExitCode(action) {
|
|
15
17
|
const previousExitCode = process.exitCode;
|
|
16
18
|
process.exitCode = void 0;
|
|
@@ -248,6 +250,43 @@ function formatManagedProcessExitStatus(result) {
|
|
|
248
250
|
function createManagedProcessExitError(result) {
|
|
249
251
|
return new Error(`[server] ${result.label} exited unexpectedly with ${formatManagedProcessExitStatus(result)}.`);
|
|
250
252
|
}
|
|
253
|
+
function isDevServerRestartResult(result) {
|
|
254
|
+
return "restart" in result && result.restart === true;
|
|
255
|
+
}
|
|
256
|
+
function formatQueueWorkerLabel(queueNames) {
|
|
257
|
+
if (queueNames.length === 0) return "Queue worker";
|
|
258
|
+
const sorted = [...queueNames].sort((a, b) => a.localeCompare(b));
|
|
259
|
+
const preview = sorted.length > 4 ? `${sorted.slice(0, 4).join(", ")}, +${sorted.length - 4} more` : sorted.join(", ");
|
|
260
|
+
return `Queue worker (${preview})`;
|
|
261
|
+
}
|
|
262
|
+
function lookupModuleCommand(allModules, moduleName, commandName) {
|
|
263
|
+
const mod = allModules.find((entry) => entry.id === moduleName);
|
|
264
|
+
if (!mod) {
|
|
265
|
+
return { status: "missing-module" };
|
|
266
|
+
}
|
|
267
|
+
if (!mod.cli || mod.cli.length === 0) {
|
|
268
|
+
return { status: "missing-cli" };
|
|
269
|
+
}
|
|
270
|
+
const command = mod.cli.find((entry) => entry.command === commandName);
|
|
271
|
+
if (!command) {
|
|
272
|
+
return { status: "missing-command" };
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
status: "ok",
|
|
276
|
+
module: mod,
|
|
277
|
+
command
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function describeMissingModuleCommand(result) {
|
|
281
|
+
switch (result.status) {
|
|
282
|
+
case "missing-module":
|
|
283
|
+
return "module not enabled";
|
|
284
|
+
case "missing-cli":
|
|
285
|
+
return "module has no CLI commands";
|
|
286
|
+
case "missing-command":
|
|
287
|
+
return "command not found";
|
|
288
|
+
}
|
|
289
|
+
}
|
|
251
290
|
function ensureNextBuildIdInConfiguredDistDir(appDir) {
|
|
252
291
|
const configuredDistDir = path.join(appDir, ".mercato", "next");
|
|
253
292
|
const configuredBuildIdPath = path.join(configuredDistDir, "BUILD_ID");
|
|
@@ -304,36 +343,24 @@ async function handleDirectEjectCommand(args) {
|
|
|
304
343
|
return 0;
|
|
305
344
|
}
|
|
306
345
|
async function runModuleCommand(allModules, moduleName, commandName, args = [], options = {}) {
|
|
307
|
-
const
|
|
308
|
-
if (
|
|
309
|
-
if (options.optional) {
|
|
310
|
-
if (!options.silentOptional) {
|
|
311
|
-
console.log(`\u23ED\uFE0F Skipping "${moduleName}:${commandName}" \u2014 module not enabled`);
|
|
312
|
-
}
|
|
313
|
-
return false;
|
|
314
|
-
}
|
|
315
|
-
throw new Error(`Module not found: "${moduleName}"`);
|
|
316
|
-
}
|
|
317
|
-
if (!mod.cli || mod.cli.length === 0) {
|
|
346
|
+
const resolved = lookupModuleCommand(allModules, moduleName, commandName);
|
|
347
|
+
if (resolved.status !== "ok") {
|
|
318
348
|
if (options.optional) {
|
|
319
349
|
if (!options.silentOptional) {
|
|
320
|
-
console.log(`\u23ED\uFE0F Skipping "${moduleName}:${commandName}" \u2014
|
|
350
|
+
console.log(`\u23ED\uFE0F Skipping "${moduleName}:${commandName}" \u2014 ${describeMissingModuleCommand(resolved)}`);
|
|
321
351
|
}
|
|
322
352
|
return false;
|
|
323
353
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}
|
|
332
|
-
return false;
|
|
354
|
+
switch (resolved.status) {
|
|
355
|
+
case "missing-module":
|
|
356
|
+
throw new Error(`Module not found: "${moduleName}"`);
|
|
357
|
+
case "missing-cli":
|
|
358
|
+
throw new Error(`Module "${moduleName}" has no CLI commands`);
|
|
359
|
+
case "missing-command":
|
|
360
|
+
throw new Error(`Command "${commandName}" not found in module "${moduleName}"`);
|
|
333
361
|
}
|
|
334
|
-
throw new Error(`Command "${commandName}" not found in module "${moduleName}"`);
|
|
335
362
|
}
|
|
336
|
-
await
|
|
363
|
+
await resolved.command.run(args);
|
|
337
364
|
return true;
|
|
338
365
|
}
|
|
339
366
|
async function runPostGenerateStructuralCachePurge(quiet) {
|
|
@@ -1351,12 +1378,11 @@ async function run(argv = process.argv) {
|
|
|
1351
1378
|
const env = resolveEnvironment();
|
|
1352
1379
|
const appDir = env.appDir;
|
|
1353
1380
|
const nodeModulesBases = Array.from(/* @__PURE__ */ new Set([env.rootDir, appDir]));
|
|
1354
|
-
|
|
1355
|
-
const autoSpawnWorkers = process.env.AUTO_SPAWN_WORKERS !== "false";
|
|
1356
|
-
const autoSpawnScheduler = process.env.AUTO_SPAWN_SCHEDULER !== "false";
|
|
1357
|
-
const queueStrategy = process.env.QUEUE_STRATEGY || "local";
|
|
1358
|
-
const runtimeEnv = buildServerProcessEnvironment(process.env);
|
|
1381
|
+
let processes = [];
|
|
1359
1382
|
let didRetryCorruptedTurbopackCache = false;
|
|
1383
|
+
let stopping = false;
|
|
1384
|
+
let envChangePromiseResolve = null;
|
|
1385
|
+
const envReloader = createDevEnvReloader(appDir, process.env, initialProcessEnvironmentEntries);
|
|
1360
1386
|
function cleanup() {
|
|
1361
1387
|
console.log("[server] Shutting down...");
|
|
1362
1388
|
for (const proc of processes) {
|
|
@@ -1370,7 +1396,7 @@ async function run(argv = process.argv) {
|
|
|
1370
1396
|
await Promise.all(
|
|
1371
1397
|
processes.map(
|
|
1372
1398
|
(proc) => new Promise((resolve) => {
|
|
1373
|
-
if (proc.exitCode !== null) return resolve();
|
|
1399
|
+
if (proc.exitCode !== null || proc.signalCode !== null) return resolve();
|
|
1374
1400
|
proc.on("exit", () => resolve());
|
|
1375
1401
|
})
|
|
1376
1402
|
)
|
|
@@ -1380,16 +1406,33 @@ async function run(argv = process.argv) {
|
|
|
1380
1406
|
fs.unlinkSync(lockFile);
|
|
1381
1407
|
} catch {
|
|
1382
1408
|
}
|
|
1409
|
+
processes = [];
|
|
1383
1410
|
}
|
|
1384
|
-
process.on("SIGTERM",
|
|
1385
|
-
|
|
1411
|
+
process.on("SIGTERM", () => {
|
|
1412
|
+
stopping = true;
|
|
1413
|
+
cleanup();
|
|
1414
|
+
});
|
|
1415
|
+
process.on("SIGINT", () => {
|
|
1416
|
+
stopping = true;
|
|
1417
|
+
cleanup();
|
|
1418
|
+
});
|
|
1386
1419
|
console.log("[server] Starting Open Mercato in dev mode...");
|
|
1387
1420
|
const { createResolver: createResolverForSources } = await import("./lib/resolver.js");
|
|
1388
1421
|
const { generateModulePackageSources } = await import("./lib/generators/index.js");
|
|
1389
1422
|
await generateModulePackageSources({ resolver: createResolverForSources(), quiet: true });
|
|
1390
1423
|
const nextBin = resolveInstalledBinary(nodeModulesBases, "next/dist/bin/next");
|
|
1391
1424
|
const mercatoBin = resolveInstalledBinary(nodeModulesBases, "@open-mercato/cli/bin/mercato");
|
|
1392
|
-
const
|
|
1425
|
+
const stopEnvWatcher = watchDevEnvFiles(appDir, (filePath) => {
|
|
1426
|
+
envChangePromiseResolve?.({
|
|
1427
|
+
label: "Environment file change",
|
|
1428
|
+
restart: true,
|
|
1429
|
+
filePath
|
|
1430
|
+
});
|
|
1431
|
+
});
|
|
1432
|
+
const waitForEnvChange = () => new Promise((resolve) => {
|
|
1433
|
+
envChangePromiseResolve = resolve;
|
|
1434
|
+
});
|
|
1435
|
+
const startNextDev = (runtimeEnv) => new Promise((resolve) => {
|
|
1393
1436
|
const nextProcess = spawn("node", [nextBin, "dev", "--turbopack"], {
|
|
1394
1437
|
stdio: ["inherit", "pipe", "pipe"],
|
|
1395
1438
|
env: runtimeEnv,
|
|
@@ -1418,7 +1461,7 @@ async function run(argv = process.argv) {
|
|
|
1418
1461
|
didRetryCorruptedTurbopackCache = true;
|
|
1419
1462
|
console.log("[server] Detected corrupted Turbopack dev cache. Clearing .mercato/next/dev and restarting Next.js once...");
|
|
1420
1463
|
removeTurbopackDevCache(appDir);
|
|
1421
|
-
return resolve(await startNextDev());
|
|
1464
|
+
return resolve(await startNextDev(runtimeEnv));
|
|
1422
1465
|
}
|
|
1423
1466
|
resolve({
|
|
1424
1467
|
label: "Next.js dev server",
|
|
@@ -1427,37 +1470,61 @@ async function run(argv = process.argv) {
|
|
|
1427
1470
|
});
|
|
1428
1471
|
});
|
|
1429
1472
|
});
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
const
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1473
|
+
try {
|
|
1474
|
+
while (!stopping) {
|
|
1475
|
+
envReloader.reload();
|
|
1476
|
+
const runtimeEnv = buildServerProcessEnvironment(process.env);
|
|
1477
|
+
const autoSpawnWorkers = process.env.AUTO_SPAWN_WORKERS !== "false";
|
|
1478
|
+
const autoSpawnScheduler = process.env.AUTO_SPAWN_SCHEDULER !== "false";
|
|
1479
|
+
const queueStrategy = process.env.QUEUE_STRATEGY || "local";
|
|
1480
|
+
const schedulerCommand = lookupModuleCommand(getCliModules(), "scheduler", "start");
|
|
1481
|
+
const managedExitPromises = [
|
|
1482
|
+
startNextDev(runtimeEnv),
|
|
1483
|
+
waitForEnvChange()
|
|
1484
|
+
];
|
|
1485
|
+
if (autoSpawnWorkers) {
|
|
1486
|
+
const discoveredWorkerQueues = [...new Set(getRegisteredCliWorkers().map((worker) => worker.queue))];
|
|
1487
|
+
if (discoveredWorkerQueues.length === 0) {
|
|
1488
|
+
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.");
|
|
1489
|
+
} else {
|
|
1490
|
+
console.log("[server] Starting workers for all queues...");
|
|
1491
|
+
const workerProcess = spawn("node", [mercatoBin, "queue", "worker", "--all"], {
|
|
1492
|
+
stdio: "inherit",
|
|
1493
|
+
env: runtimeEnv,
|
|
1494
|
+
cwd: appDir
|
|
1495
|
+
});
|
|
1496
|
+
processes.push(workerProcess);
|
|
1497
|
+
managedExitPromises.push(waitForManagedProcessExit(workerProcess, formatQueueWorkerLabel(discoveredWorkerQueues)));
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
if (autoSpawnScheduler && queueStrategy === "local") {
|
|
1501
|
+
if (schedulerCommand.status !== "ok") {
|
|
1502
|
+
console.log(`[server] Skipping scheduler auto-start \u2014 ${describeMissingModuleCommand(schedulerCommand)}`);
|
|
1503
|
+
} else {
|
|
1504
|
+
console.log("[server] Starting scheduler polling engine...");
|
|
1505
|
+
const schedulerProcess = spawn("node", [mercatoBin, "scheduler", "start"], {
|
|
1506
|
+
stdio: "inherit",
|
|
1507
|
+
env: runtimeEnv,
|
|
1508
|
+
cwd: appDir
|
|
1509
|
+
});
|
|
1510
|
+
processes.push(schedulerProcess);
|
|
1511
|
+
managedExitPromises.push(waitForManagedProcessExit(schedulerProcess, "Scheduler polling engine"));
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
const firstExit = await Promise.race(managedExitPromises);
|
|
1515
|
+
await cleanupAndWait();
|
|
1516
|
+
envChangePromiseResolve = null;
|
|
1517
|
+
if (isDevServerRestartResult(firstExit)) {
|
|
1518
|
+
console.log(`[server] Detected environment file change (${path.basename(firstExit.filePath)}). Restarting app runtime...`);
|
|
1519
|
+
continue;
|
|
1520
|
+
}
|
|
1521
|
+
if (!isExpectedManagedExitSignal(firstExit.signal)) {
|
|
1522
|
+
throw createManagedProcessExitError(firstExit);
|
|
1523
|
+
}
|
|
1524
|
+
stopping = true;
|
|
1445
1525
|
}
|
|
1446
|
-
}
|
|
1447
|
-
|
|
1448
|
-
console.log("[server] Starting scheduler polling engine...");
|
|
1449
|
-
const schedulerProcess = spawn("node", [mercatoBin, "scheduler", "start"], {
|
|
1450
|
-
stdio: "inherit",
|
|
1451
|
-
env: runtimeEnv,
|
|
1452
|
-
cwd: appDir
|
|
1453
|
-
});
|
|
1454
|
-
processes.push(schedulerProcess);
|
|
1455
|
-
managedExitPromises.push(waitForManagedProcessExit(schedulerProcess, "Scheduler polling engine"));
|
|
1456
|
-
}
|
|
1457
|
-
const firstExit = await Promise.race(managedExitPromises);
|
|
1458
|
-
await cleanupAndWait();
|
|
1459
|
-
if (!isExpectedManagedExitSignal(firstExit.signal)) {
|
|
1460
|
-
throw createManagedProcessExitError(firstExit);
|
|
1526
|
+
} finally {
|
|
1527
|
+
stopEnvWatcher();
|
|
1461
1528
|
}
|
|
1462
1529
|
}
|
|
1463
1530
|
},
|
|
@@ -1474,13 +1541,14 @@ async function run(argv = process.argv) {
|
|
|
1474
1541
|
const autoSpawnScheduler = process.env.AUTO_SPAWN_SCHEDULER !== "false";
|
|
1475
1542
|
const queueStrategy = process.env.QUEUE_STRATEGY || "local";
|
|
1476
1543
|
const runtimeEnv = buildServerProcessEnvironment(process.env);
|
|
1544
|
+
const schedulerCommand = lookupModuleCommand(getCliModules(), "scheduler", "start");
|
|
1477
1545
|
const serverStartLock = acquireServerStartLock(appDir, {
|
|
1478
1546
|
port: runtimeEnv.PORT ?? process.env.PORT ?? null
|
|
1479
1547
|
});
|
|
1480
1548
|
function cleanup() {
|
|
1481
1549
|
console.log("[server] Shutting down...");
|
|
1482
1550
|
for (const proc of processes) {
|
|
1483
|
-
if (!proc.killed) {
|
|
1551
|
+
if (!proc.killed && proc.exitCode === null && proc.signalCode === null) {
|
|
1484
1552
|
proc.kill("SIGTERM");
|
|
1485
1553
|
}
|
|
1486
1554
|
}
|
|
@@ -1490,7 +1558,7 @@ async function run(argv = process.argv) {
|
|
|
1490
1558
|
await Promise.all(
|
|
1491
1559
|
processes.map(
|
|
1492
1560
|
(proc) => new Promise((resolve) => {
|
|
1493
|
-
if (proc.exitCode !== null) return resolve();
|
|
1561
|
+
if (proc.exitCode !== null || proc.signalCode !== null) return resolve();
|
|
1494
1562
|
proc.on("exit", () => resolve());
|
|
1495
1563
|
})
|
|
1496
1564
|
)
|
|
@@ -1509,6 +1577,9 @@ async function run(argv = process.argv) {
|
|
|
1509
1577
|
cwd: appDir
|
|
1510
1578
|
});
|
|
1511
1579
|
processes.push(nextProcess);
|
|
1580
|
+
const managedExitPromises = [
|
|
1581
|
+
waitForManagedProcessExit(nextProcess, "Next.js production server")
|
|
1582
|
+
];
|
|
1512
1583
|
if (autoSpawnWorkers) {
|
|
1513
1584
|
const discoveredWorkerQueues = [...new Set(getRegisteredCliWorkers().map((worker) => worker.queue))];
|
|
1514
1585
|
if (discoveredWorkerQueues.length === 0) {
|
|
@@ -1521,25 +1592,28 @@ async function run(argv = process.argv) {
|
|
|
1521
1592
|
cwd: appDir
|
|
1522
1593
|
});
|
|
1523
1594
|
processes.push(workerProcess);
|
|
1595
|
+
managedExitPromises.push(waitForManagedProcessExit(workerProcess, formatQueueWorkerLabel(discoveredWorkerQueues)));
|
|
1524
1596
|
}
|
|
1525
1597
|
}
|
|
1526
1598
|
if (autoSpawnScheduler && queueStrategy === "local") {
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1599
|
+
if (schedulerCommand.status !== "ok") {
|
|
1600
|
+
console.log(`[server] Skipping scheduler auto-start \u2014 ${describeMissingModuleCommand(schedulerCommand)}`);
|
|
1601
|
+
} else {
|
|
1602
|
+
console.log("[server] Starting scheduler polling engine...");
|
|
1603
|
+
const schedulerProcess = spawn("node", [mercatoBin, "scheduler", "start"], {
|
|
1604
|
+
stdio: "inherit",
|
|
1605
|
+
env: runtimeEnv,
|
|
1606
|
+
cwd: appDir
|
|
1607
|
+
});
|
|
1608
|
+
processes.push(schedulerProcess);
|
|
1609
|
+
managedExitPromises.push(waitForManagedProcessExit(schedulerProcess, "Scheduler polling engine"));
|
|
1610
|
+
}
|
|
1534
1611
|
}
|
|
1535
|
-
await Promise.race(
|
|
1536
|
-
processes.map(
|
|
1537
|
-
(proc) => new Promise((resolve) => {
|
|
1538
|
-
proc.on("exit", () => resolve());
|
|
1539
|
-
})
|
|
1540
|
-
)
|
|
1541
|
-
);
|
|
1612
|
+
const firstExit = await Promise.race(managedExitPromises);
|
|
1542
1613
|
await cleanupAndWait();
|
|
1614
|
+
if (!isExpectedManagedExitSignal(firstExit.signal)) {
|
|
1615
|
+
throw createManagedProcessExitError(firstExit);
|
|
1616
|
+
}
|
|
1543
1617
|
} finally {
|
|
1544
1618
|
serverStartLock.release();
|
|
1545
1619
|
}
|