@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.
Files changed (2) hide show
  1. package/dist/index.js +548 -435
  2. 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 access5 } from "fs/promises";
1304
- import { join as join22, resolve as resolve4 } from "path";
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 as spawn2 } from "child_process";
1564
- import { access as access6 } from "fs/promises";
1565
- import { join as join23 } from "path";
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 = spawn2("docker", ["compose", ...args2], {
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 = spawn2("docker", ["compose", ...args2], {
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 = join23(localDir, `${platformName}-${app.name}-docker-compose.yml`);
1632
- const unprefixedPath = join23(localDir, `${app.name}-docker-compose.yml`);
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 access6(prefixedPath);
1379
+ await access5(prefixedPath);
1636
1380
  resolved = prefixedPath;
1637
1381
  } catch {
1638
1382
  try {
1639
- await access6(unprefixedPath);
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 = join23(localDir, `${coreDirName}-docker-compose.yml`);
1654
- const unprefixedCoreCompose = join23(localDir, "core-docker-compose.yml");
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 access6(prefixedCoreCompose);
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
- join23(localDir, "platform-docker-compose.yml"),
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 = join23(localDir, `${coreDirName}-docker-compose.yml`);
1685
- const unprefixedCoreCompose = join23(localDir, "core-docker-compose.yml");
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 access6(prefixedCoreCompose);
1432
+ await access5(prefixedCoreCompose);
1689
1433
  coreComposePath = prefixedCoreCompose;
1690
1434
  } catch {
1691
1435
  coreComposePath = unprefixedCoreCompose;
1692
1436
  }
1693
- files.push(join23(localDir, "platform-docker-compose.yml"), coreComposePath);
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
- const contextOutput = await captureDockerCompose([...contextFileArgs, "config", "--services"], rootDir);
1724
- const contextServices = new Set(contextOutput.split("\n").map((s) => s.trim()).filter(Boolean));
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 = join23(localDir, ".env");
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 = join23(localDir, ".env");
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 = join23(localDir, ".env");
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 spawn3 } from "child_process";
1799
- import { access as access7 } from "fs/promises";
1800
- import { resolve as resolve5, join as join24 } from "path";
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 = spawn3(command, args2, {
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
- async function dirExists(dirPath) {
1846
- try {
1847
- await access7(dirPath);
1848
- return true;
1849
- } catch {
1850
- return false;
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 installDependencies(layout, manifest, logger, signal, includeCore = true) {
1854
- const { coreDir, coreDirName } = layout;
1855
- const appDirs = [];
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 appDir = resolve5(join24(coreDir), app.localPath);
1858
- if (!await dirExists(appDir)) {
1859
- logger.log(`Warning: Application directory "${app.name}" not found at ${appDir} \u2014 skipping install.`);
1860
- continue;
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 targets = includeCore ? [{ name: coreDirName, dir: coreDir }, ...appDirs] : appDirs;
1865
- logger.log(`Installing dependencies in parallel: ${targets.map((t) => t.name).join(", ")}...`);
1866
- await Promise.all(
1867
- targets.map(
1868
- ({ name, dir }) => runCommand("npm", ["install"], dir, logger, signal).then(() => {
1869
- logger.log(`\u2713 ${name} install done`);
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
- async function buildAll(layout, manifest, logger, signal, includeCore = true) {
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 appDir = resolve5(join24(coreDir), app.localPath);
1884
- if (!await dirExists(appDir)) {
1885
- logger.log(`Warning: Application directory "${app.name}" not found at ${appDir} \u2014 skipping build.`);
1886
- continue;
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
- if (appDirs.length === 0) return;
1891
- logger.log(`Building apps in parallel: ${appDirs.map((a) => a.name).join(", ")}...`);
1892
- await Promise.all(
1893
- appDirs.map(
1894
- ({ name, dir }) => runCommand("npm", ["run", "build"], dir, logger, signal).then(() => {
1895
- logger.log(`\u2713 ${name} build done`);
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
- logger.log(`Error: Cannot run "${scriptName}" \u2014 no platform initialized in this directory.`);
1944
- return;
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 (err) {
1951
- logger.log(`Error: Could not read product manifest \u2014 ${formatError(err)}`);
1952
- return;
1953
- }
1954
- let includeCore = true;
1955
- const fullManifest = manifest;
1956
- let isSelective = false;
1957
- if (appNames && appNames.length > 0) {
1958
- isSelective = true;
1959
- includeCore = appNames.includes(CORE_APP_NAME);
1960
- const appNamesWithoutCore = appNames.filter((n) => n !== CORE_APP_NAME);
1961
- const nameSet = new Set(appNamesWithoutCore);
1962
- const unknown = appNamesWithoutCore.filter((n) => !manifest.applications.some((a) => a.name === n));
1963
- if (unknown.length > 0) {
1964
- logger.log(`Warning: Unknown application(s): ${unknown.join(", ")} \u2014 ignoring.`);
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 next = computeNextStep(result);
2332
- if (next === null) {
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
- const shouldRun = await ctx.confirm(`Next step: "${next}". Run it now?`, true);
2338
- if (!shouldRun) return;
2339
- if (next === "init") {
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
- await localScriptService(next, ctx, ctx.signal);
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 = computeNextStep(resultAfter);
2346
- if (nextAfter === next) {
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(`"${next}" did not complete successfully. Check the output above for errors.`);
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 next = computeNextStep(result);
2957
- if (next !== null) {
3065
+ const { step, appNames, allApps } = computeNextStepInfo(result);
3066
+ if (step !== null) {
2958
3067
  console.log("");
2959
- console.log(`Hint: Run "platform ${next}" to continue.`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bluealba/platform-cli",
3
- "version": "0.3.1-feature-platform-cli-228",
3
+ "version": "0.3.1-feature-platform-cli-229",
4
4
  "description": "Blue Alba Platform CLI",
5
5
  "license": "PolyForm-Noncommercial-1.0.0",
6
6
  "type": "module",