@bluealba/platform-cli 0.3.1-feature-platform-cli-228 → 0.3.1-feature-platform-cli-229
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/index.js +548 -435
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1299,273 +1299,17 @@ async function createUiModule(params, logger) {
|
|
|
1299
1299
|
}
|
|
1300
1300
|
|
|
1301
1301
|
// src/commands/status/status-checks.ts
|
|
1302
|
-
import { spawn } from "child_process";
|
|
1303
|
-
import { access as
|
|
1304
|
-
import { join as
|
|
1305
|
-
function spawnCapture(cmd, args2, cwd5) {
|
|
1306
|
-
return new Promise((resolvePromise) => {
|
|
1307
|
-
const child = spawn(cmd, args2, {
|
|
1308
|
-
cwd: cwd5,
|
|
1309
|
-
shell: false,
|
|
1310
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
1311
|
-
});
|
|
1312
|
-
let stdout = "";
|
|
1313
|
-
let stderr = "";
|
|
1314
|
-
child.stdout.on("data", (data) => {
|
|
1315
|
-
stdout += data.toString();
|
|
1316
|
-
});
|
|
1317
|
-
child.stderr.on("data", (data) => {
|
|
1318
|
-
stderr += data.toString();
|
|
1319
|
-
});
|
|
1320
|
-
child.on("close", (code) => {
|
|
1321
|
-
resolvePromise({ code: code ?? 1, stdout, stderr });
|
|
1322
|
-
});
|
|
1323
|
-
child.on("error", () => {
|
|
1324
|
-
resolvePromise({ code: 1, stdout, stderr });
|
|
1325
|
-
});
|
|
1326
|
-
});
|
|
1327
|
-
}
|
|
1328
|
-
async function pathExists(p) {
|
|
1329
|
-
try {
|
|
1330
|
-
await access5(p);
|
|
1331
|
-
return true;
|
|
1332
|
-
} catch {
|
|
1333
|
-
return false;
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
|
-
async function checkNodeVersion() {
|
|
1337
|
-
const { code, stdout } = await spawnCapture("node", ["--version"]);
|
|
1338
|
-
if (code !== 0 || !stdout.trim()) {
|
|
1339
|
-
return { name: "Node.js", available: false, version: null, versionOk: false, detail: "not found in PATH" };
|
|
1340
|
-
}
|
|
1341
|
-
const version2 = stdout.trim();
|
|
1342
|
-
const match = version2.match(/^v(\d+)/);
|
|
1343
|
-
const major = match ? parseInt(match[1], 10) : 0;
|
|
1344
|
-
const versionOk = major >= 22;
|
|
1345
|
-
return {
|
|
1346
|
-
name: "Node.js",
|
|
1347
|
-
available: true,
|
|
1348
|
-
version: version2,
|
|
1349
|
-
versionOk,
|
|
1350
|
-
detail: versionOk ? void 0 : `>=22 required, found ${version2}`
|
|
1351
|
-
};
|
|
1352
|
-
}
|
|
1353
|
-
async function checkDocker() {
|
|
1354
|
-
const { code, stdout } = await spawnCapture("docker", ["info", "--format", "{{.ServerVersion}}"]);
|
|
1355
|
-
if (code === 0 && stdout.trim()) {
|
|
1356
|
-
return { name: "Docker", available: true, version: stdout.trim(), versionOk: true };
|
|
1357
|
-
}
|
|
1358
|
-
const { code: vCode, stdout: vOut } = await spawnCapture("docker", ["--version"]);
|
|
1359
|
-
if (vCode === 0 && vOut.trim()) {
|
|
1360
|
-
return {
|
|
1361
|
-
name: "Docker",
|
|
1362
|
-
available: false,
|
|
1363
|
-
version: null,
|
|
1364
|
-
versionOk: false,
|
|
1365
|
-
detail: "installed but daemon is not running"
|
|
1366
|
-
};
|
|
1367
|
-
}
|
|
1368
|
-
return { name: "Docker", available: false, version: null, versionOk: false, detail: "not found in PATH" };
|
|
1369
|
-
}
|
|
1370
|
-
async function checkInstalled(layout, manifest) {
|
|
1371
|
-
const results = [];
|
|
1372
|
-
const coreCheck = {
|
|
1373
|
-
name: layout.coreDirName,
|
|
1374
|
-
path: join22(layout.coreDir, "node_modules"),
|
|
1375
|
-
exists: await pathExists(join22(layout.coreDir, "node_modules"))
|
|
1376
|
-
};
|
|
1377
|
-
results.push(coreCheck);
|
|
1378
|
-
for (const app of manifest.applications) {
|
|
1379
|
-
const appDir = resolve4(layout.coreDir, app.localPath);
|
|
1380
|
-
const nodeModulesPath = join22(appDir, "node_modules");
|
|
1381
|
-
results.push({
|
|
1382
|
-
name: app.name,
|
|
1383
|
-
path: nodeModulesPath,
|
|
1384
|
-
exists: await pathExists(nodeModulesPath)
|
|
1385
|
-
});
|
|
1386
|
-
}
|
|
1387
|
-
return results;
|
|
1388
|
-
}
|
|
1389
|
-
async function checkBuilt(layout, manifest) {
|
|
1390
|
-
const results = [];
|
|
1391
|
-
const coreTurboPath = join22(layout.coreDir, ".turbo");
|
|
1392
|
-
results.push({
|
|
1393
|
-
name: layout.coreDirName,
|
|
1394
|
-
path: coreTurboPath,
|
|
1395
|
-
exists: await pathExists(coreTurboPath)
|
|
1396
|
-
});
|
|
1397
|
-
for (const app of manifest.applications) {
|
|
1398
|
-
const appDir = resolve4(layout.coreDir, app.localPath);
|
|
1399
|
-
const appTurboPath = join22(appDir, ".turbo");
|
|
1400
|
-
results.push({
|
|
1401
|
-
name: app.name,
|
|
1402
|
-
path: appTurboPath,
|
|
1403
|
-
exists: await pathExists(appTurboPath)
|
|
1404
|
-
});
|
|
1405
|
-
}
|
|
1406
|
-
return results;
|
|
1407
|
-
}
|
|
1408
|
-
async function resolveComposeFiles(layout, manifest) {
|
|
1409
|
-
const { localDir, coreDirName } = layout;
|
|
1410
|
-
const platformName = manifest.product.name;
|
|
1411
|
-
const files = [join22(localDir, "platform-docker-compose.yml")];
|
|
1412
|
-
const prefixedCore = join22(localDir, `${coreDirName}-docker-compose.yml`);
|
|
1413
|
-
const unprefixedCore = join22(localDir, "core-docker-compose.yml");
|
|
1414
|
-
files.push(await pathExists(prefixedCore) ? prefixedCore : unprefixedCore);
|
|
1415
|
-
for (const app of manifest.applications) {
|
|
1416
|
-
const prefixed = join22(localDir, `${platformName}-${app.name}-docker-compose.yml`);
|
|
1417
|
-
const unprefixed = join22(localDir, `${app.name}-docker-compose.yml`);
|
|
1418
|
-
if (await pathExists(prefixed)) files.push(prefixed);
|
|
1419
|
-
else if (await pathExists(unprefixed)) files.push(unprefixed);
|
|
1420
|
-
}
|
|
1421
|
-
return files;
|
|
1422
|
-
}
|
|
1423
|
-
async function checkContainers(layout, manifest) {
|
|
1424
|
-
const platformName = manifest.product.name;
|
|
1425
|
-
const projectName = `${platformName}-platform`;
|
|
1426
|
-
const composeFiles = await resolveComposeFiles(layout, manifest);
|
|
1427
|
-
const fileArgs = composeFiles.flatMap((f) => ["-f", f]);
|
|
1428
|
-
const { code: cfgCode, stdout: cfgOut } = await spawnCapture(
|
|
1429
|
-
"docker",
|
|
1430
|
-
["compose", "-p", projectName, ...fileArgs, "config", "--services"],
|
|
1431
|
-
layout.rootDir
|
|
1432
|
-
);
|
|
1433
|
-
const expectedServices = cfgCode === 0 ? cfgOut.trim().split("\n").map((s) => s.trim()).filter(Boolean) : [];
|
|
1434
|
-
const { stdout: psOut } = await spawnCapture(
|
|
1435
|
-
"docker",
|
|
1436
|
-
["compose", "-p", projectName, ...fileArgs, "ps", "--format", "json"],
|
|
1437
|
-
layout.rootDir
|
|
1438
|
-
);
|
|
1439
|
-
const runningByService = /* @__PURE__ */ new Map();
|
|
1440
|
-
for (const line of psOut.trim().split("\n")) {
|
|
1441
|
-
const trimmed = line.trim();
|
|
1442
|
-
if (!trimmed) continue;
|
|
1443
|
-
try {
|
|
1444
|
-
const obj = JSON.parse(trimmed);
|
|
1445
|
-
const service = String(obj["Service"] ?? obj["Name"] ?? "");
|
|
1446
|
-
if (service) {
|
|
1447
|
-
runningByService.set(service, {
|
|
1448
|
-
state: String(obj["State"] ?? "unknown").toLowerCase(),
|
|
1449
|
-
ports: String(obj["Publishers"] ? formatPublishers(obj["Publishers"]) : obj["Ports"] ?? "")
|
|
1450
|
-
});
|
|
1451
|
-
}
|
|
1452
|
-
} catch {
|
|
1453
|
-
}
|
|
1454
|
-
}
|
|
1455
|
-
if (expectedServices.length > 0) {
|
|
1456
|
-
return expectedServices.map((service) => {
|
|
1457
|
-
const actual = runningByService.get(service);
|
|
1458
|
-
return {
|
|
1459
|
-
name: service,
|
|
1460
|
-
state: actual ? actual.state : "not started",
|
|
1461
|
-
ports: actual ? actual.ports : ""
|
|
1462
|
-
};
|
|
1463
|
-
});
|
|
1464
|
-
}
|
|
1465
|
-
return Array.from(runningByService.entries()).map(([name, info]) => ({
|
|
1466
|
-
name,
|
|
1467
|
-
...info
|
|
1468
|
-
}));
|
|
1469
|
-
}
|
|
1470
|
-
function formatPublishers(publishers) {
|
|
1471
|
-
if (!Array.isArray(publishers)) return "";
|
|
1472
|
-
return publishers.map((p) => {
|
|
1473
|
-
if (typeof p !== "object" || p === null) return "";
|
|
1474
|
-
const pub = p;
|
|
1475
|
-
const host = pub["URL"] ?? "0.0.0.0";
|
|
1476
|
-
const published = pub["PublishedPort"];
|
|
1477
|
-
const target = pub["TargetPort"];
|
|
1478
|
-
const proto = pub["Protocol"] ?? "tcp";
|
|
1479
|
-
if (!published || !target) return "";
|
|
1480
|
-
return `${host}:${published}->${target}/${proto}`;
|
|
1481
|
-
}).filter(Boolean).join(", ");
|
|
1482
|
-
}
|
|
1483
|
-
function computeNextStep(result) {
|
|
1484
|
-
if (!result.lifecycle.initialized) return "init";
|
|
1485
|
-
if (!result.lifecycle.installed) return "install";
|
|
1486
|
-
if (!result.lifecycle.built) return "build";
|
|
1487
|
-
if (!result.lifecycle.running) return "start";
|
|
1488
|
-
return null;
|
|
1489
|
-
}
|
|
1490
|
-
async function gatherStatus() {
|
|
1491
|
-
const layout = await findPlatformLayout();
|
|
1492
|
-
const [nodeCheck, dockerCheck] = await Promise.all([checkNodeVersion(), checkDocker()]);
|
|
1493
|
-
const prerequisites = [nodeCheck, dockerCheck];
|
|
1494
|
-
if (!layout) {
|
|
1495
|
-
return {
|
|
1496
|
-
projectInfo: null,
|
|
1497
|
-
prerequisites,
|
|
1498
|
-
lifecycle: {
|
|
1499
|
-
initialized: false,
|
|
1500
|
-
installed: false,
|
|
1501
|
-
installedDetails: [],
|
|
1502
|
-
built: false,
|
|
1503
|
-
builtDetails: [],
|
|
1504
|
-
running: false
|
|
1505
|
-
},
|
|
1506
|
-
containers: []
|
|
1507
|
-
};
|
|
1508
|
-
}
|
|
1509
|
-
let manifest;
|
|
1510
|
-
try {
|
|
1511
|
-
manifest = await readManifest(layout.rootDir, layout.coreDirName);
|
|
1512
|
-
} catch {
|
|
1513
|
-
return {
|
|
1514
|
-
projectInfo: null,
|
|
1515
|
-
prerequisites,
|
|
1516
|
-
lifecycle: {
|
|
1517
|
-
initialized: true,
|
|
1518
|
-
installed: false,
|
|
1519
|
-
installedDetails: [],
|
|
1520
|
-
built: false,
|
|
1521
|
-
builtDetails: [],
|
|
1522
|
-
running: false
|
|
1523
|
-
},
|
|
1524
|
-
containers: []
|
|
1525
|
-
};
|
|
1526
|
-
}
|
|
1527
|
-
const [installedDetails, builtDetails, containers] = await Promise.all([
|
|
1528
|
-
checkInstalled(layout, manifest),
|
|
1529
|
-
checkBuilt(layout, manifest),
|
|
1530
|
-
checkContainers(layout, manifest)
|
|
1531
|
-
]);
|
|
1532
|
-
const projectInfo = {
|
|
1533
|
-
productName: manifest.product.name,
|
|
1534
|
-
displayName: manifest.product.displayName,
|
|
1535
|
-
organization: manifest.product.organization,
|
|
1536
|
-
scope: manifest.product.scope,
|
|
1537
|
-
applicationCount: manifest.applications.length,
|
|
1538
|
-
applicationNames: manifest.applications.map((a) => a.name)
|
|
1539
|
-
};
|
|
1540
|
-
return {
|
|
1541
|
-
projectInfo,
|
|
1542
|
-
prerequisites,
|
|
1543
|
-
lifecycle: {
|
|
1544
|
-
initialized: true,
|
|
1545
|
-
installed: installedDetails.every((d) => d.exists),
|
|
1546
|
-
installedDetails,
|
|
1547
|
-
built: builtDetails.every((d) => d.exists),
|
|
1548
|
-
builtDetails,
|
|
1549
|
-
running: containers.length > 0 && containers.every((c) => c.state === "running")
|
|
1550
|
-
},
|
|
1551
|
-
containers
|
|
1552
|
-
};
|
|
1553
|
-
}
|
|
1554
|
-
|
|
1555
|
-
// src/commands/status/status.command.ts
|
|
1556
|
-
var STATUS_COMMAND_NAME = "status";
|
|
1557
|
-
var statusCommand = {
|
|
1558
|
-
name: STATUS_COMMAND_NAME,
|
|
1559
|
-
description: "Show platform status and health checks"
|
|
1560
|
-
};
|
|
1302
|
+
import { spawn as spawn3 } from "child_process";
|
|
1303
|
+
import { access as access7 } from "fs/promises";
|
|
1304
|
+
import { join as join24, resolve as resolve5 } from "path";
|
|
1561
1305
|
|
|
1562
1306
|
// src/commands/local-scripts/docker-compose-orchestrator.ts
|
|
1563
|
-
import { spawn
|
|
1564
|
-
import { access as
|
|
1565
|
-
import { join as
|
|
1307
|
+
import { spawn } from "child_process";
|
|
1308
|
+
import { access as access5 } from "fs/promises";
|
|
1309
|
+
import { join as join22 } from "path";
|
|
1566
1310
|
function runDockerCompose(args2, logger, rootDir, signal) {
|
|
1567
1311
|
return new Promise((resolvePromise) => {
|
|
1568
|
-
const child =
|
|
1312
|
+
const child = spawn("docker", ["compose", ...args2], {
|
|
1569
1313
|
shell: false,
|
|
1570
1314
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1571
1315
|
cwd: rootDir
|
|
@@ -1609,7 +1353,7 @@ function runDockerCompose(args2, logger, rootDir, signal) {
|
|
|
1609
1353
|
}
|
|
1610
1354
|
function captureDockerCompose(args2, rootDir) {
|
|
1611
1355
|
return new Promise((resolvePromise, reject) => {
|
|
1612
|
-
const child =
|
|
1356
|
+
const child = spawn("docker", ["compose", ...args2], {
|
|
1613
1357
|
shell: false,
|
|
1614
1358
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1615
1359
|
cwd: rootDir
|
|
@@ -1628,15 +1372,15 @@ function captureDockerCompose(args2, rootDir) {
|
|
|
1628
1372
|
async function getAppComposePaths(localDir, platformName, manifest) {
|
|
1629
1373
|
const results = [];
|
|
1630
1374
|
for (const app of manifest.applications) {
|
|
1631
|
-
const prefixedPath =
|
|
1632
|
-
const unprefixedPath =
|
|
1375
|
+
const prefixedPath = join22(localDir, `${platformName}-${app.name}-docker-compose.yml`);
|
|
1376
|
+
const unprefixedPath = join22(localDir, `${app.name}-docker-compose.yml`);
|
|
1633
1377
|
let resolved = null;
|
|
1634
1378
|
try {
|
|
1635
|
-
await
|
|
1379
|
+
await access5(prefixedPath);
|
|
1636
1380
|
resolved = prefixedPath;
|
|
1637
1381
|
} catch {
|
|
1638
1382
|
try {
|
|
1639
|
-
await
|
|
1383
|
+
await access5(unprefixedPath);
|
|
1640
1384
|
resolved = unprefixedPath;
|
|
1641
1385
|
} catch {
|
|
1642
1386
|
}
|
|
@@ -1650,18 +1394,18 @@ async function getAppComposePaths(localDir, platformName, manifest) {
|
|
|
1650
1394
|
async function buildFullComposeArgs(layout, manifest, logger) {
|
|
1651
1395
|
const { coreDirName, localDir } = layout;
|
|
1652
1396
|
const platformName = manifest.product.name;
|
|
1653
|
-
const prefixedCoreCompose =
|
|
1654
|
-
const unprefixedCoreCompose =
|
|
1397
|
+
const prefixedCoreCompose = join22(localDir, `${coreDirName}-docker-compose.yml`);
|
|
1398
|
+
const unprefixedCoreCompose = join22(localDir, "core-docker-compose.yml");
|
|
1655
1399
|
let coreComposePath;
|
|
1656
1400
|
try {
|
|
1657
|
-
await
|
|
1401
|
+
await access5(prefixedCoreCompose);
|
|
1658
1402
|
coreComposePath = prefixedCoreCompose;
|
|
1659
1403
|
} catch {
|
|
1660
1404
|
coreComposePath = unprefixedCoreCompose;
|
|
1661
1405
|
}
|
|
1662
1406
|
const fileArgs = [
|
|
1663
1407
|
"-f",
|
|
1664
|
-
|
|
1408
|
+
join22(localDir, "platform-docker-compose.yml"),
|
|
1665
1409
|
"-f",
|
|
1666
1410
|
coreComposePath
|
|
1667
1411
|
];
|
|
@@ -1681,16 +1425,16 @@ async function buildSelectedComposeFiles(layout, selectedManifest, includeCore)
|
|
|
1681
1425
|
const platformName = selectedManifest.product.name;
|
|
1682
1426
|
const files = [];
|
|
1683
1427
|
if (includeCore) {
|
|
1684
|
-
const prefixedCoreCompose =
|
|
1685
|
-
const unprefixedCoreCompose =
|
|
1428
|
+
const prefixedCoreCompose = join22(localDir, `${coreDirName}-docker-compose.yml`);
|
|
1429
|
+
const unprefixedCoreCompose = join22(localDir, "core-docker-compose.yml");
|
|
1686
1430
|
let coreComposePath;
|
|
1687
1431
|
try {
|
|
1688
|
-
await
|
|
1432
|
+
await access5(prefixedCoreCompose);
|
|
1689
1433
|
coreComposePath = prefixedCoreCompose;
|
|
1690
1434
|
} catch {
|
|
1691
1435
|
coreComposePath = unprefixedCoreCompose;
|
|
1692
1436
|
}
|
|
1693
|
-
files.push(
|
|
1437
|
+
files.push(join22(localDir, "platform-docker-compose.yml"), coreComposePath);
|
|
1694
1438
|
}
|
|
1695
1439
|
const appEntries = await getAppComposePaths(localDir, platformName, selectedManifest);
|
|
1696
1440
|
for (const { composePath } of appEntries) {
|
|
@@ -1720,14 +1464,21 @@ async function getServicesFromComposeFiles(selectedFiles, allFiles, rootDir) {
|
|
|
1720
1464
|
const allOutput = await captureDockerCompose([...allFileArgs, "config", "--services"], rootDir);
|
|
1721
1465
|
const allServices = new Set(allOutput.split("\n").map((s) => s.trim()).filter(Boolean));
|
|
1722
1466
|
const contextFileArgs = contextFiles.flatMap((f) => ["-f", f]);
|
|
1723
|
-
|
|
1724
|
-
|
|
1467
|
+
let contextServices;
|
|
1468
|
+
try {
|
|
1469
|
+
const contextOutput = await captureDockerCompose([...contextFileArgs, "config", "--services"], rootDir);
|
|
1470
|
+
contextServices = new Set(contextOutput.split("\n").map((s) => s.trim()).filter(Boolean));
|
|
1471
|
+
} catch {
|
|
1472
|
+
const selectedFileArgs = selectedFiles.flatMap((f) => ["-f", f]);
|
|
1473
|
+
const fallbackOutput = await captureDockerCompose([...selectedFileArgs, "config", "--services"], rootDir);
|
|
1474
|
+
return fallbackOutput.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
1475
|
+
}
|
|
1725
1476
|
return [...allServices].filter((s) => !contextServices.has(s));
|
|
1726
1477
|
}
|
|
1727
1478
|
async function startEnvironment(layout, manifest, logger, signal, includeCore = true, fullManifest) {
|
|
1728
1479
|
const { rootDir, localDir } = layout;
|
|
1729
1480
|
const platformName = manifest.product.name;
|
|
1730
|
-
const envFile =
|
|
1481
|
+
const envFile = join22(localDir, ".env");
|
|
1731
1482
|
const isSelective = fullManifest !== void 0;
|
|
1732
1483
|
const fileArgs = await buildFullComposeArgs(layout, isSelective ? fullManifest : manifest, logger);
|
|
1733
1484
|
const projectArgs = ["-p", `${platformName}-platform`, "--project-directory", localDir, "--env-file", envFile];
|
|
@@ -1749,7 +1500,7 @@ async function startEnvironment(layout, manifest, logger, signal, includeCore =
|
|
|
1749
1500
|
async function stopEnvironment(layout, manifest, logger, signal, includeCore = true, fullManifest) {
|
|
1750
1501
|
const { rootDir, localDir } = layout;
|
|
1751
1502
|
const platformName = manifest.product.name;
|
|
1752
|
-
const envFile =
|
|
1503
|
+
const envFile = join22(localDir, ".env");
|
|
1753
1504
|
const isSelective = fullManifest !== void 0;
|
|
1754
1505
|
const fileArgs = await buildFullComposeArgs(layout, isSelective ? fullManifest : manifest, logger);
|
|
1755
1506
|
const projectArgs = ["-p", `${platformName}-platform`, "--project-directory", localDir, "--env-file", envFile];
|
|
@@ -1771,7 +1522,7 @@ async function stopEnvironment(layout, manifest, logger, signal, includeCore = t
|
|
|
1771
1522
|
async function destroyEnvironment(layout, manifest, logger, signal, includeCore = true, fullManifest) {
|
|
1772
1523
|
const { rootDir, localDir } = layout;
|
|
1773
1524
|
const platformName = manifest.product.name;
|
|
1774
|
-
const envFile =
|
|
1525
|
+
const envFile = join22(localDir, ".env");
|
|
1775
1526
|
const isSelective = fullManifest !== void 0;
|
|
1776
1527
|
const fileArgs = await buildFullComposeArgs(layout, isSelective ? fullManifest : manifest, logger);
|
|
1777
1528
|
const projectArgs = ["-p", `${platformName}-platform`, "--project-directory", localDir, "--env-file", envFile];
|
|
@@ -1795,12 +1546,12 @@ async function destroyEnvironment(layout, manifest, logger, signal, includeCore
|
|
|
1795
1546
|
}
|
|
1796
1547
|
|
|
1797
1548
|
// src/commands/local-scripts/npm-orchestrator.ts
|
|
1798
|
-
import { spawn as
|
|
1799
|
-
import { access as
|
|
1800
|
-
import { resolve as
|
|
1549
|
+
import { spawn as spawn2 } from "child_process";
|
|
1550
|
+
import { access as access6 } from "fs/promises";
|
|
1551
|
+
import { resolve as resolve4, join as join23 } from "path";
|
|
1801
1552
|
function runCommand(command, args2, workDir, logger, signal) {
|
|
1802
1553
|
return new Promise((resolvePromise) => {
|
|
1803
|
-
const child =
|
|
1554
|
+
const child = spawn2(command, args2, {
|
|
1804
1555
|
cwd: workDir,
|
|
1805
1556
|
shell: false,
|
|
1806
1557
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -1833,164 +1584,517 @@ function runCommand(command, args2, workDir, logger, signal) {
|
|
|
1833
1584
|
} else if (code !== 0) {
|
|
1834
1585
|
logger.log(`Command "${command} ${args2.join(" ")}" exited with code ${code}.`);
|
|
1835
1586
|
}
|
|
1836
|
-
resolvePromise();
|
|
1837
|
-
});
|
|
1838
|
-
child.on("error", (err) => {
|
|
1839
|
-
signal?.removeEventListener("abort", onAbort);
|
|
1840
|
-
logger.log(`Failed to run "${command} ${args2.join(" ")}": ${err.message}`);
|
|
1841
|
-
resolvePromise();
|
|
1587
|
+
resolvePromise();
|
|
1588
|
+
});
|
|
1589
|
+
child.on("error", (err) => {
|
|
1590
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1591
|
+
logger.log(`Failed to run "${command} ${args2.join(" ")}": ${err.message}`);
|
|
1592
|
+
resolvePromise();
|
|
1593
|
+
});
|
|
1594
|
+
});
|
|
1595
|
+
}
|
|
1596
|
+
async function dirExists(dirPath) {
|
|
1597
|
+
try {
|
|
1598
|
+
await access6(dirPath);
|
|
1599
|
+
return true;
|
|
1600
|
+
} catch {
|
|
1601
|
+
return false;
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
async function installDependencies(layout, manifest, logger, signal, includeCore = true) {
|
|
1605
|
+
const { coreDir, coreDirName } = layout;
|
|
1606
|
+
const appDirs = [];
|
|
1607
|
+
for (const app of manifest.applications) {
|
|
1608
|
+
const appDir = resolve4(join23(coreDir), app.localPath);
|
|
1609
|
+
if (!await dirExists(appDir)) {
|
|
1610
|
+
logger.log(`Warning: Application directory "${app.name}" not found at ${appDir} \u2014 skipping install.`);
|
|
1611
|
+
continue;
|
|
1612
|
+
}
|
|
1613
|
+
appDirs.push({ name: app.name, dir: appDir });
|
|
1614
|
+
}
|
|
1615
|
+
const targets = includeCore ? [{ name: coreDirName, dir: coreDir }, ...appDirs] : appDirs;
|
|
1616
|
+
logger.log(`Installing dependencies in parallel: ${targets.map((t) => t.name).join(", ")}...`);
|
|
1617
|
+
await Promise.all(
|
|
1618
|
+
targets.map(
|
|
1619
|
+
({ name, dir }) => runCommand("npm", ["install"], dir, logger, signal).then(() => {
|
|
1620
|
+
logger.log(`\u2713 ${name} install done`);
|
|
1621
|
+
})
|
|
1622
|
+
)
|
|
1623
|
+
);
|
|
1624
|
+
}
|
|
1625
|
+
async function buildAll(layout, manifest, logger, signal, includeCore = true) {
|
|
1626
|
+
const { coreDir, coreDirName } = layout;
|
|
1627
|
+
if (includeCore) {
|
|
1628
|
+
logger.log(`Building ${coreDirName}/...`);
|
|
1629
|
+
await runCommand("npx", ["turbo", "build"], coreDir, logger, signal);
|
|
1630
|
+
if (signal?.aborted) return;
|
|
1631
|
+
}
|
|
1632
|
+
const appDirs = [];
|
|
1633
|
+
for (const app of manifest.applications) {
|
|
1634
|
+
const appDir = resolve4(join23(coreDir), app.localPath);
|
|
1635
|
+
if (!await dirExists(appDir)) {
|
|
1636
|
+
logger.log(`Warning: Application directory "${app.name}" not found at ${appDir} \u2014 skipping build.`);
|
|
1637
|
+
continue;
|
|
1638
|
+
}
|
|
1639
|
+
appDirs.push({ name: app.name, dir: appDir });
|
|
1640
|
+
}
|
|
1641
|
+
if (appDirs.length === 0) return;
|
|
1642
|
+
logger.log(`Building apps in parallel: ${appDirs.map((a) => a.name).join(", ")}...`);
|
|
1643
|
+
await Promise.all(
|
|
1644
|
+
appDirs.map(
|
|
1645
|
+
({ name, dir }) => runCommand("npm", ["run", "build"], dir, logger, signal).then(() => {
|
|
1646
|
+
logger.log(`\u2713 ${name} build done`);
|
|
1647
|
+
})
|
|
1648
|
+
)
|
|
1649
|
+
);
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
// src/commands/local-scripts/local-script.command.ts
|
|
1653
|
+
var INSTALL_COMMAND_NAME = "install";
|
|
1654
|
+
var BUILD_COMMAND_NAME = "build";
|
|
1655
|
+
var START_COMMAND_NAME = "start";
|
|
1656
|
+
var STOP_COMMAND_NAME = "stop";
|
|
1657
|
+
var DESTROY_COMMAND_NAME = "destroy";
|
|
1658
|
+
var CORE_APP_NAME = "core";
|
|
1659
|
+
var installCommand = {
|
|
1660
|
+
name: INSTALL_COMMAND_NAME,
|
|
1661
|
+
description: "Install dependencies in the local dev environment",
|
|
1662
|
+
hidden: (ctx) => !ctx.platformInitialized
|
|
1663
|
+
};
|
|
1664
|
+
var buildCommand = {
|
|
1665
|
+
name: BUILD_COMMAND_NAME,
|
|
1666
|
+
description: "Build the local dev environment",
|
|
1667
|
+
hidden: (ctx) => !ctx.platformInitialized
|
|
1668
|
+
};
|
|
1669
|
+
var startCommand = {
|
|
1670
|
+
name: START_COMMAND_NAME,
|
|
1671
|
+
description: "Start the local dev environment",
|
|
1672
|
+
hidden: (ctx) => !ctx.platformInitialized
|
|
1673
|
+
};
|
|
1674
|
+
var stopCommand = {
|
|
1675
|
+
name: STOP_COMMAND_NAME,
|
|
1676
|
+
description: "Stop the local dev environment",
|
|
1677
|
+
hidden: (ctx) => !ctx.platformInitialized
|
|
1678
|
+
};
|
|
1679
|
+
var destroyCommand = {
|
|
1680
|
+
name: DESTROY_COMMAND_NAME,
|
|
1681
|
+
description: "Destroy the local dev environment",
|
|
1682
|
+
hidden: (ctx) => !ctx.platformInitialized
|
|
1683
|
+
};
|
|
1684
|
+
var localScriptCommands = [
|
|
1685
|
+
installCommand,
|
|
1686
|
+
buildCommand,
|
|
1687
|
+
startCommand,
|
|
1688
|
+
stopCommand,
|
|
1689
|
+
destroyCommand
|
|
1690
|
+
];
|
|
1691
|
+
async function runLocalScript(scriptName, logger, signal, appNames) {
|
|
1692
|
+
const layout = await findPlatformLayout();
|
|
1693
|
+
if (!layout) {
|
|
1694
|
+
logger.log(`Error: Cannot run "${scriptName}" \u2014 no platform initialized in this directory.`);
|
|
1695
|
+
return;
|
|
1696
|
+
}
|
|
1697
|
+
const { rootDir, coreDirName } = layout;
|
|
1698
|
+
let manifest;
|
|
1699
|
+
try {
|
|
1700
|
+
manifest = await readManifest(rootDir, coreDirName);
|
|
1701
|
+
} catch (err) {
|
|
1702
|
+
logger.log(`Error: Could not read product manifest \u2014 ${formatError(err)}`);
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1705
|
+
let includeCore = true;
|
|
1706
|
+
const fullManifest = manifest;
|
|
1707
|
+
let isSelective = false;
|
|
1708
|
+
if (appNames && appNames.length > 0) {
|
|
1709
|
+
isSelective = true;
|
|
1710
|
+
includeCore = appNames.includes(CORE_APP_NAME);
|
|
1711
|
+
const appNamesWithoutCore = appNames.filter((n) => n !== CORE_APP_NAME);
|
|
1712
|
+
const nameSet = new Set(appNamesWithoutCore);
|
|
1713
|
+
const unknown = appNamesWithoutCore.filter((n) => !manifest.applications.some((a) => a.name === n));
|
|
1714
|
+
if (unknown.length > 0) {
|
|
1715
|
+
logger.log(`Warning: Unknown application(s): ${unknown.join(", ")} \u2014 ignoring.`);
|
|
1716
|
+
}
|
|
1717
|
+
const filtered = manifest.applications.filter((a) => nameSet.has(a.name));
|
|
1718
|
+
if (!includeCore && filtered.length === 0) {
|
|
1719
|
+
logger.log("No matching applications found. Nothing to do.");
|
|
1720
|
+
return;
|
|
1721
|
+
}
|
|
1722
|
+
manifest = { ...manifest, applications: filtered };
|
|
1723
|
+
}
|
|
1724
|
+
switch (scriptName) {
|
|
1725
|
+
case START_COMMAND_NAME:
|
|
1726
|
+
await startEnvironment(layout, manifest, logger, signal, includeCore, isSelective ? fullManifest : void 0);
|
|
1727
|
+
break;
|
|
1728
|
+
case STOP_COMMAND_NAME:
|
|
1729
|
+
await stopEnvironment(layout, manifest, logger, signal, includeCore, isSelective ? fullManifest : void 0);
|
|
1730
|
+
break;
|
|
1731
|
+
case DESTROY_COMMAND_NAME:
|
|
1732
|
+
await destroyEnvironment(layout, manifest, logger, signal, includeCore, isSelective ? fullManifest : void 0);
|
|
1733
|
+
break;
|
|
1734
|
+
case INSTALL_COMMAND_NAME:
|
|
1735
|
+
await installDependencies(layout, manifest, logger, signal, includeCore);
|
|
1736
|
+
break;
|
|
1737
|
+
case BUILD_COMMAND_NAME:
|
|
1738
|
+
await buildAll(layout, manifest, logger, signal, includeCore);
|
|
1739
|
+
break;
|
|
1740
|
+
default:
|
|
1741
|
+
logger.log(`Error: Unknown script "${scriptName}".`);
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
// src/commands/status/status-checks.ts
|
|
1746
|
+
function spawnCapture(cmd, args2, cwd5) {
|
|
1747
|
+
return new Promise((resolvePromise) => {
|
|
1748
|
+
const child = spawn3(cmd, args2, {
|
|
1749
|
+
cwd: cwd5,
|
|
1750
|
+
shell: false,
|
|
1751
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1752
|
+
});
|
|
1753
|
+
let stdout = "";
|
|
1754
|
+
let stderr = "";
|
|
1755
|
+
child.stdout.on("data", (data) => {
|
|
1756
|
+
stdout += data.toString();
|
|
1757
|
+
});
|
|
1758
|
+
child.stderr.on("data", (data) => {
|
|
1759
|
+
stderr += data.toString();
|
|
1760
|
+
});
|
|
1761
|
+
child.on("close", (code) => {
|
|
1762
|
+
resolvePromise({ code: code ?? 1, stdout, stderr });
|
|
1763
|
+
});
|
|
1764
|
+
child.on("error", () => {
|
|
1765
|
+
resolvePromise({ code: 1, stdout, stderr });
|
|
1766
|
+
});
|
|
1767
|
+
});
|
|
1768
|
+
}
|
|
1769
|
+
async function pathExists(p) {
|
|
1770
|
+
try {
|
|
1771
|
+
await access7(p);
|
|
1772
|
+
return true;
|
|
1773
|
+
} catch {
|
|
1774
|
+
return false;
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
async function checkNodeVersion() {
|
|
1778
|
+
const { code, stdout } = await spawnCapture("node", ["--version"]);
|
|
1779
|
+
if (code !== 0 || !stdout.trim()) {
|
|
1780
|
+
return { name: "Node.js", available: false, version: null, versionOk: false, detail: "not found in PATH" };
|
|
1781
|
+
}
|
|
1782
|
+
const version2 = stdout.trim();
|
|
1783
|
+
const match = version2.match(/^v(\d+)/);
|
|
1784
|
+
const major = match ? parseInt(match[1], 10) : 0;
|
|
1785
|
+
const versionOk = major >= 22;
|
|
1786
|
+
return {
|
|
1787
|
+
name: "Node.js",
|
|
1788
|
+
available: true,
|
|
1789
|
+
version: version2,
|
|
1790
|
+
versionOk,
|
|
1791
|
+
detail: versionOk ? void 0 : `>=22 required, found ${version2}`
|
|
1792
|
+
};
|
|
1793
|
+
}
|
|
1794
|
+
async function checkDocker() {
|
|
1795
|
+
const { code, stdout } = await spawnCapture("docker", ["info", "--format", "{{.ServerVersion}}"]);
|
|
1796
|
+
if (code === 0 && stdout.trim()) {
|
|
1797
|
+
return { name: "Docker", available: true, version: stdout.trim(), versionOk: true };
|
|
1798
|
+
}
|
|
1799
|
+
const { code: vCode, stdout: vOut } = await spawnCapture("docker", ["--version"]);
|
|
1800
|
+
if (vCode === 0 && vOut.trim()) {
|
|
1801
|
+
return {
|
|
1802
|
+
name: "Docker",
|
|
1803
|
+
available: false,
|
|
1804
|
+
version: null,
|
|
1805
|
+
versionOk: false,
|
|
1806
|
+
detail: "installed but daemon is not running"
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1809
|
+
return { name: "Docker", available: false, version: null, versionOk: false, detail: "not found in PATH" };
|
|
1810
|
+
}
|
|
1811
|
+
async function checkInstalled(layout, manifest) {
|
|
1812
|
+
const results = [];
|
|
1813
|
+
const coreCheck = {
|
|
1814
|
+
name: layout.coreDirName,
|
|
1815
|
+
path: join24(layout.coreDir, "node_modules"),
|
|
1816
|
+
exists: await pathExists(join24(layout.coreDir, "node_modules"))
|
|
1817
|
+
};
|
|
1818
|
+
results.push(coreCheck);
|
|
1819
|
+
for (const app of manifest.applications) {
|
|
1820
|
+
const appDir = resolve5(layout.coreDir, app.localPath);
|
|
1821
|
+
const nodeModulesPath = join24(appDir, "node_modules");
|
|
1822
|
+
results.push({
|
|
1823
|
+
name: app.name,
|
|
1824
|
+
path: nodeModulesPath,
|
|
1825
|
+
exists: await pathExists(nodeModulesPath)
|
|
1826
|
+
});
|
|
1827
|
+
}
|
|
1828
|
+
return results;
|
|
1829
|
+
}
|
|
1830
|
+
async function checkBuilt(layout, manifest) {
|
|
1831
|
+
const results = [];
|
|
1832
|
+
const coreTurboPath = join24(layout.coreDir, ".turbo");
|
|
1833
|
+
results.push({
|
|
1834
|
+
name: layout.coreDirName,
|
|
1835
|
+
path: coreTurboPath,
|
|
1836
|
+
exists: await pathExists(coreTurboPath)
|
|
1837
|
+
});
|
|
1838
|
+
for (const app of manifest.applications) {
|
|
1839
|
+
const appDir = resolve5(layout.coreDir, app.localPath);
|
|
1840
|
+
const appTurboPath = join24(appDir, ".turbo");
|
|
1841
|
+
results.push({
|
|
1842
|
+
name: app.name,
|
|
1843
|
+
path: appTurboPath,
|
|
1844
|
+
exists: await pathExists(appTurboPath)
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1847
|
+
return results;
|
|
1848
|
+
}
|
|
1849
|
+
async function resolveComposeFiles(layout, manifest) {
|
|
1850
|
+
const { localDir, coreDirName } = layout;
|
|
1851
|
+
const platformName = manifest.product.name;
|
|
1852
|
+
const files = [join24(localDir, "platform-docker-compose.yml")];
|
|
1853
|
+
const prefixedCore = join24(localDir, `${coreDirName}-docker-compose.yml`);
|
|
1854
|
+
const unprefixedCore = join24(localDir, "core-docker-compose.yml");
|
|
1855
|
+
files.push(await pathExists(prefixedCore) ? prefixedCore : unprefixedCore);
|
|
1856
|
+
for (const app of manifest.applications) {
|
|
1857
|
+
const prefixed = join24(localDir, `${platformName}-${app.name}-docker-compose.yml`);
|
|
1858
|
+
const unprefixed = join24(localDir, `${app.name}-docker-compose.yml`);
|
|
1859
|
+
if (await pathExists(prefixed)) files.push(prefixed);
|
|
1860
|
+
else if (await pathExists(unprefixed)) files.push(unprefixed);
|
|
1861
|
+
}
|
|
1862
|
+
return files;
|
|
1863
|
+
}
|
|
1864
|
+
async function checkContainers(layout, manifest) {
|
|
1865
|
+
const platformName = manifest.product.name;
|
|
1866
|
+
const projectName = `${platformName}-platform`;
|
|
1867
|
+
const composeFiles = await resolveComposeFiles(layout, manifest);
|
|
1868
|
+
const fileArgs = composeFiles.flatMap((f) => ["-f", f]);
|
|
1869
|
+
const { code: cfgCode, stdout: cfgOut } = await spawnCapture(
|
|
1870
|
+
"docker",
|
|
1871
|
+
["compose", "-p", projectName, ...fileArgs, "config", "--services"],
|
|
1872
|
+
layout.rootDir
|
|
1873
|
+
);
|
|
1874
|
+
const expectedServices = cfgCode === 0 ? cfgOut.trim().split("\n").map((s) => s.trim()).filter(Boolean) : [];
|
|
1875
|
+
const { stdout: psOut } = await spawnCapture(
|
|
1876
|
+
"docker",
|
|
1877
|
+
["compose", "-p", projectName, ...fileArgs, "ps", "--format", "json"],
|
|
1878
|
+
layout.rootDir
|
|
1879
|
+
);
|
|
1880
|
+
const runningByService = /* @__PURE__ */ new Map();
|
|
1881
|
+
for (const line of psOut.trim().split("\n")) {
|
|
1882
|
+
const trimmed = line.trim();
|
|
1883
|
+
if (!trimmed) continue;
|
|
1884
|
+
try {
|
|
1885
|
+
const obj = JSON.parse(trimmed);
|
|
1886
|
+
const service = String(obj["Service"] ?? obj["Name"] ?? "");
|
|
1887
|
+
if (service) {
|
|
1888
|
+
runningByService.set(service, {
|
|
1889
|
+
state: String(obj["State"] ?? "unknown").toLowerCase(),
|
|
1890
|
+
ports: String(obj["Publishers"] ? formatPublishers(obj["Publishers"]) : obj["Ports"] ?? "")
|
|
1891
|
+
});
|
|
1892
|
+
}
|
|
1893
|
+
} catch {
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
if (expectedServices.length > 0) {
|
|
1897
|
+
return expectedServices.map((service) => {
|
|
1898
|
+
const actual = runningByService.get(service);
|
|
1899
|
+
return {
|
|
1900
|
+
name: service,
|
|
1901
|
+
state: actual ? actual.state : "not started",
|
|
1902
|
+
ports: actual ? actual.ports : ""
|
|
1903
|
+
};
|
|
1842
1904
|
});
|
|
1843
|
-
}
|
|
1905
|
+
}
|
|
1906
|
+
return Array.from(runningByService.entries()).map(([name, info]) => ({
|
|
1907
|
+
name,
|
|
1908
|
+
...info
|
|
1909
|
+
}));
|
|
1844
1910
|
}
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
return
|
|
1849
|
-
|
|
1850
|
-
|
|
1911
|
+
function formatPublishers(publishers) {
|
|
1912
|
+
if (!Array.isArray(publishers)) return "";
|
|
1913
|
+
return publishers.map((p) => {
|
|
1914
|
+
if (typeof p !== "object" || p === null) return "";
|
|
1915
|
+
const pub = p;
|
|
1916
|
+
const host = pub["URL"] ?? "0.0.0.0";
|
|
1917
|
+
const published = pub["PublishedPort"];
|
|
1918
|
+
const target = pub["TargetPort"];
|
|
1919
|
+
const proto = pub["Protocol"] ?? "tcp";
|
|
1920
|
+
if (!published || !target) return "";
|
|
1921
|
+
return `${host}:${published}->${target}/${proto}`;
|
|
1922
|
+
}).filter(Boolean).join(", ");
|
|
1923
|
+
}
|
|
1924
|
+
function computeNextStepInfo(result) {
|
|
1925
|
+
if (!result.lifecycle.initialized) {
|
|
1926
|
+
return { step: "init", appNames: [], allApps: true };
|
|
1927
|
+
}
|
|
1928
|
+
if (!result.lifecycle.installed) {
|
|
1929
|
+
const failing = result.lifecycle.installedDetails.filter((d) => !d.exists).map((d) => d.name === result.coreDirName ? CORE_APP_NAME : d.name);
|
|
1930
|
+
const allApps = failing.length === result.lifecycle.installedDetails.length;
|
|
1931
|
+
return { step: "install", appNames: failing, allApps };
|
|
1932
|
+
}
|
|
1933
|
+
if (!result.lifecycle.built) {
|
|
1934
|
+
const failing = result.lifecycle.builtDetails.filter((d) => !d.exists).map((d) => d.name === result.coreDirName ? CORE_APP_NAME : d.name);
|
|
1935
|
+
const allApps = failing.length === result.lifecycle.builtDetails.length;
|
|
1936
|
+
return { step: "build", appNames: failing, allApps };
|
|
1851
1937
|
}
|
|
1938
|
+
if (!result.lifecycle.running) {
|
|
1939
|
+
const failing = result.containersByApp.filter((g) => !g.allRunning).map((g) => g.appName);
|
|
1940
|
+
const allApps = failing.length === result.containersByApp.length;
|
|
1941
|
+
return { step: "start", appNames: failing, allApps };
|
|
1942
|
+
}
|
|
1943
|
+
return { step: null, appNames: [], allApps: true };
|
|
1852
1944
|
}
|
|
1853
|
-
async function
|
|
1854
|
-
const
|
|
1855
|
-
const
|
|
1945
|
+
async function getServicesForComposeFiles(selectedFiles, allFiles, rootDir) {
|
|
1946
|
+
const selectedSet = new Set(selectedFiles);
|
|
1947
|
+
const contextFiles = allFiles.filter((f) => !selectedSet.has(f));
|
|
1948
|
+
if (contextFiles.length === 0) {
|
|
1949
|
+
const fileArgs = selectedFiles.flatMap((f) => ["-f", f]);
|
|
1950
|
+
const { stdout } = await spawnCapture("docker", ["compose", ...fileArgs, "config", "--services"], rootDir);
|
|
1951
|
+
return stdout.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
1952
|
+
}
|
|
1953
|
+
const allFileArgs = allFiles.flatMap((f) => ["-f", f]);
|
|
1954
|
+
const { stdout: allOut } = await spawnCapture("docker", ["compose", ...allFileArgs, "config", "--services"], rootDir);
|
|
1955
|
+
const allServices = new Set(allOut.split("\n").map((s) => s.trim()).filter(Boolean));
|
|
1956
|
+
const contextFileArgs = contextFiles.flatMap((f) => ["-f", f]);
|
|
1957
|
+
const { stdout: ctxOut } = await spawnCapture("docker", ["compose", ...contextFileArgs, "config", "--services"], rootDir);
|
|
1958
|
+
const contextServices = new Set(ctxOut.split("\n").map((s) => s.trim()).filter(Boolean));
|
|
1959
|
+
return [...allServices].filter((s) => !contextServices.has(s));
|
|
1960
|
+
}
|
|
1961
|
+
async function checkContainersPerApp(layout, manifest, allContainers) {
|
|
1962
|
+
const { localDir, coreDirName, rootDir } = layout;
|
|
1963
|
+
const platformName = manifest.product.name;
|
|
1964
|
+
const prefixedCore = join24(localDir, `${coreDirName}-docker-compose.yml`);
|
|
1965
|
+
const unprefixedCore = join24(localDir, "core-docker-compose.yml");
|
|
1966
|
+
const coreComposePath = await pathExists(prefixedCore) ? prefixedCore : unprefixedCore;
|
|
1967
|
+
const platformComposePath = join24(localDir, "platform-docker-compose.yml");
|
|
1968
|
+
const allFiles = [platformComposePath, coreComposePath];
|
|
1969
|
+
const appComposeMap = /* @__PURE__ */ new Map();
|
|
1856
1970
|
for (const app of manifest.applications) {
|
|
1857
|
-
const
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1971
|
+
const prefixed = join24(localDir, `${platformName}-${app.name}-docker-compose.yml`);
|
|
1972
|
+
const unprefixed = join24(localDir, `${app.name}-docker-compose.yml`);
|
|
1973
|
+
if (await pathExists(prefixed)) {
|
|
1974
|
+
appComposeMap.set(app.name, prefixed);
|
|
1975
|
+
allFiles.push(prefixed);
|
|
1976
|
+
} else if (await pathExists(unprefixed)) {
|
|
1977
|
+
appComposeMap.set(app.name, unprefixed);
|
|
1978
|
+
allFiles.push(unprefixed);
|
|
1861
1979
|
}
|
|
1862
|
-
appDirs.push({ name: app.name, dir: appDir });
|
|
1863
1980
|
}
|
|
1864
|
-
const
|
|
1865
|
-
|
|
1866
|
-
await
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
})
|
|
1871
|
-
)
|
|
1981
|
+
const containerByName = new Map(allContainers.map((c) => [c.name, c]));
|
|
1982
|
+
const coreFiles = [platformComposePath, coreComposePath];
|
|
1983
|
+
const { stdout: coreOut } = await spawnCapture(
|
|
1984
|
+
"docker",
|
|
1985
|
+
["compose", ...coreFiles.flatMap((f) => ["-f", f]), "config", "--services"],
|
|
1986
|
+
rootDir
|
|
1872
1987
|
);
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
const { coreDir, coreDirName } = layout;
|
|
1876
|
-
if (includeCore) {
|
|
1877
|
-
logger.log(`Building ${coreDirName}/...`);
|
|
1878
|
-
await runCommand("npx", ["turbo", "build"], coreDir, logger, signal);
|
|
1879
|
-
if (signal?.aborted) return;
|
|
1880
|
-
}
|
|
1881
|
-
const appDirs = [];
|
|
1988
|
+
const coreServiceNames = coreOut.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
1989
|
+
const appServicesMap = /* @__PURE__ */ new Map();
|
|
1882
1990
|
for (const app of manifest.applications) {
|
|
1883
|
-
const
|
|
1884
|
-
if (!
|
|
1885
|
-
|
|
1886
|
-
|
|
1991
|
+
const appComposePath = appComposeMap.get(app.name);
|
|
1992
|
+
if (!appComposePath) continue;
|
|
1993
|
+
try {
|
|
1994
|
+
const appServices = await getServicesForComposeFiles([appComposePath], allFiles, rootDir);
|
|
1995
|
+
appServicesMap.set(app.name, appServices);
|
|
1996
|
+
} catch {
|
|
1887
1997
|
}
|
|
1888
|
-
appDirs.push({ name: app.name, dir: appDir });
|
|
1889
1998
|
}
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1999
|
+
const groups = [];
|
|
2000
|
+
const coreContainers = coreServiceNames.map((s) => containerByName.get(s) ?? { name: s, state: "not started", ports: "" });
|
|
2001
|
+
groups.push({
|
|
2002
|
+
appName: CORE_APP_NAME,
|
|
2003
|
+
containers: coreContainers,
|
|
2004
|
+
allRunning: coreContainers.length > 0 && coreContainers.every((c) => c.state === "running")
|
|
2005
|
+
});
|
|
2006
|
+
for (const app of manifest.applications) {
|
|
2007
|
+
const appServices = appServicesMap.get(app.name);
|
|
2008
|
+
if (!appServices) continue;
|
|
2009
|
+
const appContainers = appServices.map((s) => containerByName.get(s) ?? { name: s, state: "not started", ports: "" });
|
|
2010
|
+
groups.push({
|
|
2011
|
+
appName: app.name,
|
|
2012
|
+
containers: appContainers,
|
|
2013
|
+
allRunning: appContainers.length > 0 && appContainers.every((c) => c.state === "running")
|
|
2014
|
+
});
|
|
2015
|
+
}
|
|
2016
|
+
return groups;
|
|
1899
2017
|
}
|
|
1900
|
-
|
|
1901
|
-
// src/commands/local-scripts/local-script.command.ts
|
|
1902
|
-
var INSTALL_COMMAND_NAME = "install";
|
|
1903
|
-
var BUILD_COMMAND_NAME = "build";
|
|
1904
|
-
var START_COMMAND_NAME = "start";
|
|
1905
|
-
var STOP_COMMAND_NAME = "stop";
|
|
1906
|
-
var DESTROY_COMMAND_NAME = "destroy";
|
|
1907
|
-
var CORE_APP_NAME = "core";
|
|
1908
|
-
var installCommand = {
|
|
1909
|
-
name: INSTALL_COMMAND_NAME,
|
|
1910
|
-
description: "Install dependencies in the local dev environment",
|
|
1911
|
-
hidden: (ctx) => !ctx.platformInitialized
|
|
1912
|
-
};
|
|
1913
|
-
var buildCommand = {
|
|
1914
|
-
name: BUILD_COMMAND_NAME,
|
|
1915
|
-
description: "Build the local dev environment",
|
|
1916
|
-
hidden: (ctx) => !ctx.platformInitialized
|
|
1917
|
-
};
|
|
1918
|
-
var startCommand = {
|
|
1919
|
-
name: START_COMMAND_NAME,
|
|
1920
|
-
description: "Start the local dev environment",
|
|
1921
|
-
hidden: (ctx) => !ctx.platformInitialized
|
|
1922
|
-
};
|
|
1923
|
-
var stopCommand = {
|
|
1924
|
-
name: STOP_COMMAND_NAME,
|
|
1925
|
-
description: "Stop the local dev environment",
|
|
1926
|
-
hidden: (ctx) => !ctx.platformInitialized
|
|
1927
|
-
};
|
|
1928
|
-
var destroyCommand = {
|
|
1929
|
-
name: DESTROY_COMMAND_NAME,
|
|
1930
|
-
description: "Destroy the local dev environment",
|
|
1931
|
-
hidden: (ctx) => !ctx.platformInitialized
|
|
1932
|
-
};
|
|
1933
|
-
var localScriptCommands = [
|
|
1934
|
-
installCommand,
|
|
1935
|
-
buildCommand,
|
|
1936
|
-
startCommand,
|
|
1937
|
-
stopCommand,
|
|
1938
|
-
destroyCommand
|
|
1939
|
-
];
|
|
1940
|
-
async function runLocalScript(scriptName, logger, signal, appNames) {
|
|
2018
|
+
async function gatherStatus() {
|
|
1941
2019
|
const layout = await findPlatformLayout();
|
|
2020
|
+
const [nodeCheck, dockerCheck] = await Promise.all([checkNodeVersion(), checkDocker()]);
|
|
2021
|
+
const prerequisites = [nodeCheck, dockerCheck];
|
|
1942
2022
|
if (!layout) {
|
|
1943
|
-
|
|
1944
|
-
|
|
2023
|
+
return {
|
|
2024
|
+
projectInfo: null,
|
|
2025
|
+
prerequisites,
|
|
2026
|
+
lifecycle: {
|
|
2027
|
+
initialized: false,
|
|
2028
|
+
installed: false,
|
|
2029
|
+
installedDetails: [],
|
|
2030
|
+
built: false,
|
|
2031
|
+
builtDetails: [],
|
|
2032
|
+
running: false
|
|
2033
|
+
},
|
|
2034
|
+
containers: [],
|
|
2035
|
+
containersByApp: [],
|
|
2036
|
+
coreDirName: null
|
|
2037
|
+
};
|
|
1945
2038
|
}
|
|
1946
|
-
const { rootDir, coreDirName } = layout;
|
|
1947
2039
|
let manifest;
|
|
1948
2040
|
try {
|
|
1949
|
-
manifest = await readManifest(rootDir, coreDirName);
|
|
1950
|
-
} catch
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
}
|
|
1966
|
-
const filtered = manifest.applications.filter((a) => nameSet.has(a.name));
|
|
1967
|
-
if (!includeCore && filtered.length === 0) {
|
|
1968
|
-
logger.log("No matching applications found. Nothing to do.");
|
|
1969
|
-
return;
|
|
1970
|
-
}
|
|
1971
|
-
manifest = { ...manifest, applications: filtered };
|
|
1972
|
-
}
|
|
1973
|
-
switch (scriptName) {
|
|
1974
|
-
case START_COMMAND_NAME:
|
|
1975
|
-
await startEnvironment(layout, manifest, logger, signal, includeCore, isSelective ? fullManifest : void 0);
|
|
1976
|
-
break;
|
|
1977
|
-
case STOP_COMMAND_NAME:
|
|
1978
|
-
await stopEnvironment(layout, manifest, logger, signal, includeCore, isSelective ? fullManifest : void 0);
|
|
1979
|
-
break;
|
|
1980
|
-
case DESTROY_COMMAND_NAME:
|
|
1981
|
-
await destroyEnvironment(layout, manifest, logger, signal, includeCore, isSelective ? fullManifest : void 0);
|
|
1982
|
-
break;
|
|
1983
|
-
case INSTALL_COMMAND_NAME:
|
|
1984
|
-
await installDependencies(layout, manifest, logger, signal, includeCore);
|
|
1985
|
-
break;
|
|
1986
|
-
case BUILD_COMMAND_NAME:
|
|
1987
|
-
await buildAll(layout, manifest, logger, signal, includeCore);
|
|
1988
|
-
break;
|
|
1989
|
-
default:
|
|
1990
|
-
logger.log(`Error: Unknown script "${scriptName}".`);
|
|
2041
|
+
manifest = await readManifest(layout.rootDir, layout.coreDirName);
|
|
2042
|
+
} catch {
|
|
2043
|
+
return {
|
|
2044
|
+
projectInfo: null,
|
|
2045
|
+
prerequisites,
|
|
2046
|
+
lifecycle: {
|
|
2047
|
+
initialized: true,
|
|
2048
|
+
installed: false,
|
|
2049
|
+
installedDetails: [],
|
|
2050
|
+
built: false,
|
|
2051
|
+
builtDetails: [],
|
|
2052
|
+
running: false
|
|
2053
|
+
},
|
|
2054
|
+
containers: [],
|
|
2055
|
+
containersByApp: [],
|
|
2056
|
+
coreDirName: layout.coreDirName
|
|
2057
|
+
};
|
|
1991
2058
|
}
|
|
2059
|
+
const [installedDetails, builtDetails, containers] = await Promise.all([
|
|
2060
|
+
checkInstalled(layout, manifest),
|
|
2061
|
+
checkBuilt(layout, manifest),
|
|
2062
|
+
checkContainers(layout, manifest)
|
|
2063
|
+
]);
|
|
2064
|
+
const containersByApp = await checkContainersPerApp(layout, manifest, containers);
|
|
2065
|
+
const projectInfo = {
|
|
2066
|
+
productName: manifest.product.name,
|
|
2067
|
+
displayName: manifest.product.displayName,
|
|
2068
|
+
organization: manifest.product.organization,
|
|
2069
|
+
scope: manifest.product.scope,
|
|
2070
|
+
applicationCount: manifest.applications.length,
|
|
2071
|
+
applicationNames: manifest.applications.map((a) => a.name)
|
|
2072
|
+
};
|
|
2073
|
+
return {
|
|
2074
|
+
projectInfo,
|
|
2075
|
+
prerequisites,
|
|
2076
|
+
lifecycle: {
|
|
2077
|
+
initialized: true,
|
|
2078
|
+
installed: installedDetails.every((d) => d.exists),
|
|
2079
|
+
installedDetails,
|
|
2080
|
+
built: builtDetails.every((d) => d.exists),
|
|
2081
|
+
builtDetails,
|
|
2082
|
+
running: containers.length > 0 && containers.every((c) => c.state === "running")
|
|
2083
|
+
},
|
|
2084
|
+
containers,
|
|
2085
|
+
containersByApp,
|
|
2086
|
+
coreDirName: layout.coreDirName
|
|
2087
|
+
};
|
|
1992
2088
|
}
|
|
1993
2089
|
|
|
2090
|
+
// src/commands/status/status.command.ts
|
|
2091
|
+
var STATUS_COMMAND_NAME = "status";
|
|
2092
|
+
var statusCommand = {
|
|
2093
|
+
name: STATUS_COMMAND_NAME,
|
|
2094
|
+
description: "Show platform status and health checks",
|
|
2095
|
+
hidden: (ctx) => !ctx.platformInitialized
|
|
2096
|
+
};
|
|
2097
|
+
|
|
1994
2098
|
// src/commands/registry.ts
|
|
1995
2099
|
var CommandRegistry = class {
|
|
1996
2100
|
commands = /* @__PURE__ */ new Map();
|
|
@@ -2328,24 +2432,29 @@ async function statusUiController(ctx) {
|
|
|
2328
2432
|
for (const line of formatStatusLines(result)) {
|
|
2329
2433
|
ctx.log(line);
|
|
2330
2434
|
}
|
|
2331
|
-
const
|
|
2332
|
-
if (
|
|
2435
|
+
const { step, appNames, allApps } = computeNextStepInfo(result);
|
|
2436
|
+
if (step === null) {
|
|
2333
2437
|
ctx.log("");
|
|
2334
2438
|
ctx.log("All checks passed!");
|
|
2335
2439
|
return;
|
|
2336
2440
|
}
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2441
|
+
if (step === "init") {
|
|
2442
|
+
const shouldRun = await ctx.confirm(`Next step: "init". Run it now?`, true);
|
|
2443
|
+
if (!shouldRun) return;
|
|
2340
2444
|
await initUiController(ctx);
|
|
2341
2445
|
} else {
|
|
2342
|
-
|
|
2446
|
+
const appListStr = allApps ? "all apps" : appNames.join(", ");
|
|
2447
|
+
const shouldRun = await ctx.confirm(`Run ${step} for ${appListStr}?`, true);
|
|
2448
|
+
if (!shouldRun) return;
|
|
2449
|
+
await localScriptService(step, ctx, ctx.signal, allApps ? void 0 : appNames);
|
|
2343
2450
|
}
|
|
2344
2451
|
const resultAfter = await statusService();
|
|
2345
|
-
const nextAfter =
|
|
2346
|
-
|
|
2452
|
+
const nextAfter = computeNextStepInfo(resultAfter);
|
|
2453
|
+
const sameStep = nextAfter.step === step;
|
|
2454
|
+
const noProgress = sameStep && (allApps ? nextAfter.allApps : appNames.some((a) => nextAfter.appNames.includes(a)));
|
|
2455
|
+
if (noProgress) {
|
|
2347
2456
|
ctx.log("");
|
|
2348
|
-
ctx.log(`"${
|
|
2457
|
+
ctx.log(`"${step}" did not complete successfully. Check the output above for errors.`);
|
|
2349
2458
|
return;
|
|
2350
2459
|
}
|
|
2351
2460
|
}
|
|
@@ -2953,10 +3062,14 @@ var statusCliController = async (_args) => {
|
|
|
2953
3062
|
for (const line of lines) {
|
|
2954
3063
|
console.log(line);
|
|
2955
3064
|
}
|
|
2956
|
-
const
|
|
2957
|
-
if (
|
|
3065
|
+
const { step, appNames, allApps } = computeNextStepInfo(result);
|
|
3066
|
+
if (step !== null) {
|
|
2958
3067
|
console.log("");
|
|
2959
|
-
|
|
3068
|
+
if (step === "init" || allApps) {
|
|
3069
|
+
console.log(`Hint: Run "platform ${step}" to continue.`);
|
|
3070
|
+
} else {
|
|
3071
|
+
console.log(`Hint: Run "platform ${step} ${appNames.join(" ")}" to continue.`);
|
|
3072
|
+
}
|
|
2960
3073
|
process.exit(1);
|
|
2961
3074
|
}
|
|
2962
3075
|
};
|
package/package.json
CHANGED