@bluealba/platform-cli 0.3.1-feature-platform-cli-224 → 0.3.1-feature-platform-cli-225

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 CHANGED
@@ -279,7 +279,12 @@ async function scaffoldBootstrap(bootstrapServiceDir, organizationName, bootstra
279
279
  {
280
280
  templateDir: bootstrapTemplateDir,
281
281
  outputDir: bootstrapServiceDir,
282
- variables: { organizationName, bootstrapName, bootstrapServiceDir: bootstrapServiceDir_var },
282
+ variables: {
283
+ organizationName,
284
+ bootstrapName,
285
+ bootstrapServiceName: `${bootstrapName}-bootstrap-service`,
286
+ bootstrapServiceDir: bootstrapServiceDir_var
287
+ },
283
288
  exclude: ["src/data/shared-libraries.json", "src/data/platform/modules-config.json"]
284
289
  },
285
290
  (message) => logger.log(message)
@@ -839,6 +844,8 @@ async function init(params, logger) {
839
844
  platformTitle,
840
845
  platformDisplayName,
841
846
  bootstrapName: platformName,
847
+ bootstrapServiceName,
848
+ customizationUiName,
842
849
  bootstrapServiceDir: `${coreDirName}/services`
843
850
  };
844
851
  try {
@@ -1255,6 +1262,267 @@ async function createUiModule(params, logger) {
1255
1262
  logger.log(`Done! UI module "${uiName}" added to application "${applicationName}".`);
1256
1263
  }
1257
1264
 
1265
+ // src/commands/status/status-checks.ts
1266
+ import { spawn } from "child_process";
1267
+ import { access as access5 } from "fs/promises";
1268
+ import { join as join22, resolve as resolve4 } from "path";
1269
+ function spawnCapture(cmd, args2, cwd5) {
1270
+ return new Promise((resolvePromise) => {
1271
+ const child = spawn(cmd, args2, {
1272
+ cwd: cwd5,
1273
+ shell: false,
1274
+ stdio: ["ignore", "pipe", "pipe"]
1275
+ });
1276
+ let stdout = "";
1277
+ let stderr = "";
1278
+ child.stdout.on("data", (data) => {
1279
+ stdout += data.toString();
1280
+ });
1281
+ child.stderr.on("data", (data) => {
1282
+ stderr += data.toString();
1283
+ });
1284
+ child.on("close", (code) => {
1285
+ resolvePromise({ code: code ?? 1, stdout, stderr });
1286
+ });
1287
+ child.on("error", () => {
1288
+ resolvePromise({ code: 1, stdout, stderr });
1289
+ });
1290
+ });
1291
+ }
1292
+ async function pathExists(p) {
1293
+ try {
1294
+ await access5(p);
1295
+ return true;
1296
+ } catch {
1297
+ return false;
1298
+ }
1299
+ }
1300
+ async function checkNodeVersion() {
1301
+ const { code, stdout } = await spawnCapture("node", ["--version"]);
1302
+ if (code !== 0 || !stdout.trim()) {
1303
+ return { name: "Node.js", available: false, version: null, versionOk: false, detail: "not found in PATH" };
1304
+ }
1305
+ const version2 = stdout.trim();
1306
+ const match = version2.match(/^v(\d+)/);
1307
+ const major = match ? parseInt(match[1], 10) : 0;
1308
+ const versionOk = major >= 22;
1309
+ return {
1310
+ name: "Node.js",
1311
+ available: true,
1312
+ version: version2,
1313
+ versionOk,
1314
+ detail: versionOk ? void 0 : `>=22 required, found ${version2}`
1315
+ };
1316
+ }
1317
+ async function checkDocker() {
1318
+ const { code, stdout } = await spawnCapture("docker", ["info", "--format", "{{.ServerVersion}}"]);
1319
+ if (code === 0 && stdout.trim()) {
1320
+ return { name: "Docker", available: true, version: stdout.trim(), versionOk: true };
1321
+ }
1322
+ const { code: vCode, stdout: vOut } = await spawnCapture("docker", ["--version"]);
1323
+ if (vCode === 0 && vOut.trim()) {
1324
+ return {
1325
+ name: "Docker",
1326
+ available: false,
1327
+ version: null,
1328
+ versionOk: false,
1329
+ detail: "installed but daemon is not running"
1330
+ };
1331
+ }
1332
+ return { name: "Docker", available: false, version: null, versionOk: false, detail: "not found in PATH" };
1333
+ }
1334
+ async function checkInstalled(layout, manifest) {
1335
+ const results = [];
1336
+ const coreCheck = {
1337
+ name: layout.coreDirName,
1338
+ path: join22(layout.coreDir, "node_modules"),
1339
+ exists: await pathExists(join22(layout.coreDir, "node_modules"))
1340
+ };
1341
+ results.push(coreCheck);
1342
+ for (const app of manifest.applications) {
1343
+ const appDir = resolve4(layout.coreDir, app.localPath);
1344
+ const nodeModulesPath = join22(appDir, "node_modules");
1345
+ results.push({
1346
+ name: app.name,
1347
+ path: nodeModulesPath,
1348
+ exists: await pathExists(nodeModulesPath)
1349
+ });
1350
+ }
1351
+ return results;
1352
+ }
1353
+ async function checkBuilt(layout, manifest) {
1354
+ const results = [];
1355
+ const coreTurboPath = join22(layout.coreDir, ".turbo");
1356
+ results.push({
1357
+ name: layout.coreDirName,
1358
+ path: coreTurboPath,
1359
+ exists: await pathExists(coreTurboPath)
1360
+ });
1361
+ for (const app of manifest.applications) {
1362
+ const appDir = resolve4(layout.coreDir, app.localPath);
1363
+ const appTurboPath = join22(appDir, ".turbo");
1364
+ results.push({
1365
+ name: app.name,
1366
+ path: appTurboPath,
1367
+ exists: await pathExists(appTurboPath)
1368
+ });
1369
+ }
1370
+ return results;
1371
+ }
1372
+ async function resolveComposeFiles(layout, manifest) {
1373
+ const { localDir, coreDirName } = layout;
1374
+ const platformName = manifest.product.name;
1375
+ const files = [join22(localDir, "platform-docker-compose.yml")];
1376
+ const prefixedCore = join22(localDir, `${coreDirName}-docker-compose.yml`);
1377
+ const unprefixedCore = join22(localDir, "core-docker-compose.yml");
1378
+ files.push(await pathExists(prefixedCore) ? prefixedCore : unprefixedCore);
1379
+ for (const app of manifest.applications) {
1380
+ const prefixed = join22(localDir, `${platformName}-${app.name}-docker-compose.yml`);
1381
+ const unprefixed = join22(localDir, `${app.name}-docker-compose.yml`);
1382
+ if (await pathExists(prefixed)) files.push(prefixed);
1383
+ else if (await pathExists(unprefixed)) files.push(unprefixed);
1384
+ }
1385
+ return files;
1386
+ }
1387
+ async function checkContainers(layout, manifest) {
1388
+ const platformName = manifest.product.name;
1389
+ const projectName = `${platformName}-platform`;
1390
+ const composeFiles = await resolveComposeFiles(layout, manifest);
1391
+ const fileArgs = composeFiles.flatMap((f) => ["-f", f]);
1392
+ const { code: cfgCode, stdout: cfgOut } = await spawnCapture(
1393
+ "docker",
1394
+ ["compose", "-p", projectName, ...fileArgs, "config", "--services"],
1395
+ layout.rootDir
1396
+ );
1397
+ const expectedServices = cfgCode === 0 ? cfgOut.trim().split("\n").map((s) => s.trim()).filter(Boolean) : [];
1398
+ const { stdout: psOut } = await spawnCapture(
1399
+ "docker",
1400
+ ["compose", "-p", projectName, ...fileArgs, "ps", "--format", "json"],
1401
+ layout.rootDir
1402
+ );
1403
+ const runningByService = /* @__PURE__ */ new Map();
1404
+ for (const line of psOut.trim().split("\n")) {
1405
+ const trimmed = line.trim();
1406
+ if (!trimmed) continue;
1407
+ try {
1408
+ const obj = JSON.parse(trimmed);
1409
+ const service = String(obj["Service"] ?? obj["Name"] ?? "");
1410
+ if (service) {
1411
+ runningByService.set(service, {
1412
+ state: String(obj["State"] ?? "unknown").toLowerCase(),
1413
+ ports: String(obj["Publishers"] ? formatPublishers(obj["Publishers"]) : obj["Ports"] ?? "")
1414
+ });
1415
+ }
1416
+ } catch {
1417
+ }
1418
+ }
1419
+ if (expectedServices.length > 0) {
1420
+ return expectedServices.map((service) => {
1421
+ const actual = runningByService.get(service);
1422
+ return {
1423
+ name: service,
1424
+ state: actual ? actual.state : "not started",
1425
+ ports: actual ? actual.ports : ""
1426
+ };
1427
+ });
1428
+ }
1429
+ return Array.from(runningByService.entries()).map(([name, info]) => ({
1430
+ name,
1431
+ ...info
1432
+ }));
1433
+ }
1434
+ function formatPublishers(publishers) {
1435
+ if (!Array.isArray(publishers)) return "";
1436
+ return publishers.map((p) => {
1437
+ if (typeof p !== "object" || p === null) return "";
1438
+ const pub = p;
1439
+ const host = pub["URL"] ?? "0.0.0.0";
1440
+ const published = pub["PublishedPort"];
1441
+ const target = pub["TargetPort"];
1442
+ const proto = pub["Protocol"] ?? "tcp";
1443
+ if (!published || !target) return "";
1444
+ return `${host}:${published}->${target}/${proto}`;
1445
+ }).filter(Boolean).join(", ");
1446
+ }
1447
+ function computeNextStep(result) {
1448
+ if (!result.lifecycle.initialized) return "init";
1449
+ if (!result.lifecycle.installed) return "install";
1450
+ if (!result.lifecycle.built) return "build";
1451
+ if (!result.lifecycle.running) return "start";
1452
+ return null;
1453
+ }
1454
+ async function gatherStatus() {
1455
+ const layout = await findPlatformLayout();
1456
+ const [nodeCheck, dockerCheck] = await Promise.all([checkNodeVersion(), checkDocker()]);
1457
+ const prerequisites = [nodeCheck, dockerCheck];
1458
+ if (!layout) {
1459
+ return {
1460
+ projectInfo: null,
1461
+ prerequisites,
1462
+ lifecycle: {
1463
+ initialized: false,
1464
+ installed: false,
1465
+ installedDetails: [],
1466
+ built: false,
1467
+ builtDetails: [],
1468
+ running: false
1469
+ },
1470
+ containers: []
1471
+ };
1472
+ }
1473
+ let manifest;
1474
+ try {
1475
+ manifest = await readManifest(layout.rootDir, layout.coreDirName);
1476
+ } catch {
1477
+ return {
1478
+ projectInfo: null,
1479
+ prerequisites,
1480
+ lifecycle: {
1481
+ initialized: true,
1482
+ installed: false,
1483
+ installedDetails: [],
1484
+ built: false,
1485
+ builtDetails: [],
1486
+ running: false
1487
+ },
1488
+ containers: []
1489
+ };
1490
+ }
1491
+ const [installedDetails, builtDetails, containers] = await Promise.all([
1492
+ checkInstalled(layout, manifest),
1493
+ checkBuilt(layout, manifest),
1494
+ checkContainers(layout, manifest)
1495
+ ]);
1496
+ const projectInfo = {
1497
+ productName: manifest.product.name,
1498
+ displayName: manifest.product.displayName,
1499
+ organization: manifest.product.organization,
1500
+ scope: manifest.product.scope,
1501
+ applicationCount: manifest.applications.length,
1502
+ applicationNames: manifest.applications.map((a) => a.name)
1503
+ };
1504
+ return {
1505
+ projectInfo,
1506
+ prerequisites,
1507
+ lifecycle: {
1508
+ initialized: true,
1509
+ installed: installedDetails.every((d) => d.exists),
1510
+ installedDetails,
1511
+ built: builtDetails.every((d) => d.exists),
1512
+ builtDetails,
1513
+ running: containers.length > 0 && containers.every((c) => c.state === "running")
1514
+ },
1515
+ containers
1516
+ };
1517
+ }
1518
+
1519
+ // src/commands/status/status.command.ts
1520
+ var STATUS_COMMAND_NAME = "status";
1521
+ var statusCommand = {
1522
+ name: STATUS_COMMAND_NAME,
1523
+ description: "Show platform status and health checks"
1524
+ };
1525
+
1258
1526
  // src/commands/registry.ts
1259
1527
  var CommandRegistry = class {
1260
1528
  commands = /* @__PURE__ */ new Map();
@@ -1290,6 +1558,7 @@ registry.register(initCommand);
1290
1558
  registry.register(configureIdpCommand);
1291
1559
  registry.register(createServiceModuleCommand);
1292
1560
  registry.register(createUiModuleCommand);
1561
+ registry.register(statusCommand);
1293
1562
 
1294
1563
  // src/app-state.ts
1295
1564
  var APP_STATE = {
@@ -1496,410 +1765,96 @@ async function createUiModuleUiController(ctx) {
1496
1765
  );
1497
1766
  }
1498
1767
 
1499
- // src/controllers/ui/registry.ts
1500
- var uiControllers = /* @__PURE__ */ new Map([
1501
- [CREATE_APPLICATION_COMMAND_NAME, createApplicationUiController],
1502
- [INIT_COMMAND_NAME, initUiController],
1503
- [CONFIGURE_IDP_COMMAND_NAME, configureIdpUiController],
1504
- [CREATE_SERVICE_MODULE_COMMAND_NAME, createServiceModuleUiController],
1505
- [CREATE_UI_MODULE_COMMAND_NAME, createUiModuleUiController]
1506
- ]);
1768
+ // src/services/status.service.ts
1769
+ async function statusService() {
1770
+ return gatherStatus();
1771
+ }
1772
+
1773
+ // src/utils/theme.ts
1774
+ import chalk from "chalk";
1775
+ var theme = {
1776
+ prompt: chalk.green,
1777
+ commandName: chalk.cyan,
1778
+ commandDescription: chalk.gray,
1779
+ selected: chalk.bgBlue.white,
1780
+ output: chalk.white,
1781
+ muted: chalk.dim,
1782
+ error: chalk.red,
1783
+ success: chalk.green,
1784
+ warning: chalk.yellow,
1785
+ info: chalk.cyan,
1786
+ section: chalk.bold.white,
1787
+ label: chalk.dim
1788
+ };
1507
1789
 
1508
- // src/hooks/use-command-runner.ts
1509
- function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
1510
- const outputCountRef = useRef(0);
1511
- const abortControllerRef = useRef(null);
1512
- const promptResolveRef = useRef(null);
1513
- const [promptMessage, setPromptMessage] = useState2("");
1514
- const [promptValue, setPromptValue] = useState2("");
1515
- const [promptMode, setPromptMode] = useState2({ kind: "text" });
1516
- const [selectIndex, setSelectIndex] = useState2(0);
1517
- const [confirmValue, setConfirmValue] = useState2(true);
1518
- const abortExecution = useCallback(() => {
1519
- abortControllerRef.current?.abort();
1520
- abortControllerRef.current = null;
1521
- promptResolveRef.current = null;
1522
- setPromptMode({ kind: "text" });
1523
- }, []);
1524
- const runCommand2 = useCallback(
1525
- (cmd) => {
1526
- const controller = new AbortController();
1527
- abortControllerRef.current = controller;
1528
- setState(APP_STATE.EXECUTING);
1529
- const ctx = {
1530
- signal: controller.signal,
1531
- log(message) {
1532
- if (!controller.signal.aborted) {
1533
- const id = `output-${outputCountRef.current++}`;
1534
- appendStaticItem({ kind: "output", id, line: message });
1535
- }
1536
- },
1537
- prompt(message, defaultValue) {
1538
- return new Promise((resolve5, reject) => {
1539
- if (controller.signal.aborted) {
1540
- reject(new DOMException("Aborted", "AbortError"));
1541
- return;
1542
- }
1543
- setPromptMessage(message);
1544
- setPromptValue(defaultValue ?? "");
1545
- setPromptMode({ kind: "text" });
1546
- promptResolveRef.current = resolve5;
1547
- setState(APP_STATE.PROMPTING);
1548
- controller.signal.addEventListener(
1549
- "abort",
1550
- () => reject(new DOMException("Aborted", "AbortError")),
1551
- { once: true }
1552
- );
1553
- });
1554
- },
1555
- select(message, options) {
1556
- return new Promise((resolve5, reject) => {
1557
- if (controller.signal.aborted) {
1558
- reject(new DOMException("Aborted", "AbortError"));
1559
- return;
1560
- }
1561
- setPromptMessage(message);
1562
- setPromptMode({ kind: "select", options });
1563
- setSelectIndex(0);
1564
- promptResolveRef.current = resolve5;
1565
- setState(APP_STATE.PROMPTING);
1566
- controller.signal.addEventListener(
1567
- "abort",
1568
- () => reject(new DOMException("Aborted", "AbortError")),
1569
- { once: true }
1570
- );
1571
- });
1572
- },
1573
- confirm(message, defaultValue) {
1574
- return new Promise((resolve5, reject) => {
1575
- if (controller.signal.aborted) {
1576
- reject(new DOMException("Aborted", "AbortError"));
1577
- return;
1578
- }
1579
- setPromptMessage(message);
1580
- setPromptMode({ kind: "confirm" });
1581
- setConfirmValue(defaultValue ?? true);
1582
- promptResolveRef.current = (value) => resolve5(value === "true");
1583
- setState(APP_STATE.PROMPTING);
1584
- controller.signal.addEventListener(
1585
- "abort",
1586
- () => reject(new DOMException("Aborted", "AbortError")),
1587
- { once: true }
1588
- );
1589
- });
1590
- }
1591
- };
1592
- const uiController = uiControllers.get(cmd.name);
1593
- if (!uiController) {
1594
- const id = `output-${outputCountRef.current++}`;
1595
- appendStaticItem({ kind: "output", id, line: `Error: No controller found for "${cmd.name}"` });
1596
- setState(APP_STATE.IDLE);
1597
- return;
1790
+ // src/commands/status/status-formatter.ts
1791
+ var CHECK = theme.success("\u2713");
1792
+ var CROSS = theme.error("\u2717");
1793
+ function tick(ok) {
1794
+ return ok ? CHECK : CROSS;
1795
+ }
1796
+ function formatStatusLines(result) {
1797
+ const lines = [];
1798
+ if (result.projectInfo) {
1799
+ const p = result.projectInfo;
1800
+ lines.push(theme.info("\u25B8 ") + theme.section(p.displayName) + theme.label(` (${p.productName})`));
1801
+ lines.push(theme.label(" Organization: ") + p.organization + theme.label(` (${p.scope})`));
1802
+ const appList = p.applicationNames.length > 0 ? p.applicationNames.join(", ") : "(none)";
1803
+ lines.push(theme.label(" Applications: ") + `${p.applicationCount} \u2014 ` + appList);
1804
+ lines.push("");
1805
+ }
1806
+ lines.push(theme.section("Prerequisites"));
1807
+ for (const pre of result.prerequisites) {
1808
+ const ok = pre.available && pre.versionOk;
1809
+ const versionStr = pre.version ? theme.label(` ${pre.version}`) : "";
1810
+ const detailStr = pre.detail ? theme.warning(` \u2014 ${pre.detail}`) : "";
1811
+ lines.push(` ${tick(ok)} ${pre.name}${versionStr}${detailStr}`);
1812
+ }
1813
+ lines.push("");
1814
+ lines.push(theme.section("Lifecycle"));
1815
+ lines.push(` ${tick(result.lifecycle.initialized)} Platform initialized`);
1816
+ const installed = result.lifecycle.installed;
1817
+ lines.push(` ${tick(installed)} Dependencies installed`);
1818
+ if (!installed && result.lifecycle.installedDetails.length > 0) {
1819
+ for (const d of result.lifecycle.installedDetails) {
1820
+ if (!d.exists) {
1821
+ lines.push(` ${CROSS} ${theme.label(d.name)} ${theme.warning("(missing node_modules/)")}`);
1598
1822
  }
1599
- uiController(ctx).then(() => {
1600
- if (!controller.signal.aborted) {
1601
- setState(APP_STATE.IDLE);
1602
- onCommandComplete?.();
1603
- }
1604
- }).catch((err) => {
1605
- if (controller.signal.aborted) return;
1606
- const id = `output-${outputCountRef.current++}`;
1607
- appendStaticItem({ kind: "output", id, line: `Error: ${formatError(err)}` });
1608
- setState(APP_STATE.IDLE);
1609
- onCommandComplete?.();
1610
- });
1611
- },
1612
- [appendStaticItem, setState, onCommandComplete]
1613
- );
1614
- const handlePromptSubmit = useCallback(
1615
- (value) => {
1616
- const resolve5 = promptResolveRef.current;
1617
- promptResolveRef.current = null;
1618
- setPromptMode({ kind: "text" });
1619
- setState(APP_STATE.EXECUTING);
1620
- resolve5?.(value);
1621
- },
1622
- [setState]
1623
- );
1624
- return {
1625
- runCommand: runCommand2,
1626
- handlePromptSubmit,
1627
- abortExecution,
1628
- promptMessage,
1629
- promptValue,
1630
- setPromptValue,
1631
- promptMode,
1632
- selectIndex,
1633
- setSelectIndex,
1634
- confirmValue,
1635
- setConfirmValue
1636
- };
1823
+ }
1824
+ }
1825
+ const built = result.lifecycle.built;
1826
+ lines.push(` ${tick(built)} Build completed`);
1827
+ if (!built && result.lifecycle.builtDetails.length > 0) {
1828
+ for (const d of result.lifecycle.builtDetails) {
1829
+ if (!d.exists) {
1830
+ lines.push(` ${CROSS} ${theme.label(d.name)} ${theme.warning("(missing .turbo/)")}`);
1831
+ }
1832
+ }
1833
+ }
1834
+ lines.push(` ${tick(result.lifecycle.running)} Environment running`);
1835
+ if (result.containers.length > 0) {
1836
+ lines.push("");
1837
+ lines.push(theme.section("Containers"));
1838
+ for (const c of result.containers) {
1839
+ const ok = c.state === "running";
1840
+ const stateStr = ok ? theme.success(c.state) : c.state === "not started" ? theme.label(c.state) : theme.error(c.state);
1841
+ const ports = c.ports ? theme.label(` ${c.ports}`) : "";
1842
+ lines.push(` ${tick(ok)} ${c.name.padEnd(35)} ${stateStr}${ports}`);
1843
+ }
1844
+ }
1845
+ return lines;
1637
1846
  }
1638
1847
 
1639
- // src/app.tsx
1640
- import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
1641
- var require2 = createRequire(import.meta.url);
1642
- var { version } = require2("../package.json");
1643
- function App() {
1644
- const { exit } = useApp();
1645
- const [state, setState] = useState3(APP_STATE.IDLE);
1646
- const [inputValue, setInputValue] = useState3("");
1647
- const [selectedIndex, setSelectedIndex] = useState3(0);
1648
- const [staticItems, setStaticItems] = useState3([{ kind: "banner", id: "banner" }]);
1649
- const [platformInitialized, setPlatformInitialized] = useState3(false);
1650
- useEffect2(() => {
1651
- isPlatformInitialized().then(setPlatformInitialized).catch(() => {
1652
- });
1653
- }, []);
1654
- const appendStaticItem = useCallback2((item) => {
1655
- setStaticItems((prev) => [...prev, item]);
1656
- }, []);
1657
- const handleCommandComplete = useCallback2(() => {
1658
- isPlatformInitialized().then(setPlatformInitialized).catch(() => {
1659
- });
1660
- }, []);
1661
- const {
1662
- runCommand: runCommand2,
1663
- handlePromptSubmit,
1664
- abortExecution,
1665
- promptMessage,
1666
- promptValue,
1667
- setPromptValue,
1668
- promptMode,
1669
- selectIndex,
1670
- setSelectIndex,
1671
- confirmValue,
1672
- setConfirmValue
1673
- } = useCommandRunner({ appendStaticItem, setState, onCommandComplete: handleCommandComplete });
1674
- const query = inputValue.startsWith("/") ? inputValue.slice(1) : "";
1675
- const filteredCommands = registry.searchVisible(query, { platformInitialized });
1676
- useInput(
1677
- (input, key) => {
1678
- if (key.ctrl && input === "c") {
1679
- exit();
1680
- return;
1681
- }
1682
- if ((state === APP_STATE.EXECUTING || state === APP_STATE.PROMPTING) && key.escape) {
1683
- abortExecution();
1684
- setState(APP_STATE.IDLE);
1685
- return;
1686
- }
1687
- if (state === APP_STATE.PROMPTING) {
1688
- if (promptMode.kind === "select") {
1689
- if (key.upArrow) {
1690
- setSelectIndex((prev) => Math.max(0, prev - 1));
1691
- return;
1692
- }
1693
- if (key.downArrow) {
1694
- setSelectIndex((prev) => Math.min(promptMode.options.length - 1, prev + 1));
1695
- return;
1696
- }
1697
- if (key.return) {
1698
- const selected = promptMode.options[selectIndex];
1699
- if (selected) handlePromptSubmit(selected.value);
1700
- return;
1701
- }
1702
- return;
1703
- }
1704
- if (promptMode.kind === "confirm") {
1705
- if (key.leftArrow || key.upArrow) {
1706
- setConfirmValue(true);
1707
- return;
1708
- }
1709
- if (key.rightArrow || key.downArrow) {
1710
- setConfirmValue(false);
1711
- return;
1712
- }
1713
- if (key.return) {
1714
- handlePromptSubmit(confirmValue ? "true" : "false");
1715
- return;
1716
- }
1717
- return;
1718
- }
1719
- return;
1720
- }
1721
- if (state === APP_STATE.PALETTE) {
1722
- if (key.escape) {
1723
- setState(APP_STATE.IDLE);
1724
- setInputValue("");
1725
- return;
1726
- }
1727
- if (key.upArrow) {
1728
- setSelectedIndex((prev) => Math.max(0, prev - 1));
1729
- return;
1730
- }
1731
- if (key.downArrow) {
1732
- setSelectedIndex((prev) => Math.min(filteredCommands.length - 1, prev + 1));
1733
- return;
1734
- }
1735
- if (key.return) {
1736
- const cmd = filteredCommands[selectedIndex];
1737
- if (cmd) {
1738
- setInputValue("");
1739
- runCommand2(cmd);
1740
- }
1741
- return;
1742
- }
1743
- if (key.backspace || key.delete) {
1744
- setInputValue((prev) => {
1745
- const next = prev.slice(0, -1);
1746
- if (!next.startsWith("/")) {
1747
- setState(APP_STATE.IDLE);
1748
- return "";
1749
- }
1750
- return next;
1751
- });
1752
- setSelectedIndex(0);
1753
- return;
1754
- }
1755
- if (input && !key.ctrl && !key.meta) {
1756
- setInputValue((prev) => prev + input);
1757
- setSelectedIndex(0);
1758
- }
1759
- }
1760
- },
1761
- { isActive: true }
1762
- );
1763
- const handleIdleInputChange = useCallback2((value) => {
1764
- setInputValue(value);
1765
- if (value.startsWith("/")) {
1766
- setState(APP_STATE.PALETTE);
1767
- setSelectedIndex(0);
1768
- }
1769
- }, []);
1770
- const handleIdleSubmit = useCallback2(
1771
- (value) => {
1772
- if (!value.startsWith("/")) return;
1773
- const name = value.slice(1).trim();
1774
- const cmd = registry.get(name);
1775
- if (cmd) runCommand2(cmd);
1776
- },
1777
- [runCommand2]
1778
- );
1779
- function renderActiveArea() {
1780
- switch (state) {
1781
- case APP_STATE.EXECUTING:
1782
- return /* @__PURE__ */ jsxs7(Box6, { flexDirection: "row", gap: 1, children: [
1783
- /* @__PURE__ */ jsx9(Spinner, { label: "Running\u2026" }),
1784
- /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "(esc to cancel)" })
1785
- ] });
1786
- case APP_STATE.PROMPTING:
1787
- return /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", children: [
1788
- /* @__PURE__ */ jsx9(Text9, { color: "cyan", children: promptMessage }),
1789
- promptMode.kind === "select" && /* @__PURE__ */ jsx9(SelectList, { options: promptMode.options, selectedIndex: selectIndex }),
1790
- promptMode.kind === "confirm" && /* @__PURE__ */ jsx9(ConfirmPrompt, { value: confirmValue }),
1791
- promptMode.kind === "text" && /* @__PURE__ */ jsx9(Prompt, { value: promptValue, onChange: setPromptValue, onSubmit: handlePromptSubmit })
1792
- ] });
1793
- default:
1794
- return /* @__PURE__ */ jsx9(
1795
- Prompt,
1796
- {
1797
- value: inputValue,
1798
- onChange: state === APP_STATE.IDLE ? handleIdleInputChange : () => {
1799
- },
1800
- onSubmit: state === APP_STATE.IDLE ? handleIdleSubmit : () => {
1801
- },
1802
- disabled: state === APP_STATE.PALETTE
1803
- }
1804
- );
1805
- }
1806
- }
1807
- return /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", children: [
1808
- /* @__PURE__ */ jsx9(ScrollbackHistory, { items: staticItems, version }),
1809
- renderActiveArea(),
1810
- state === APP_STATE.PALETTE && /* @__PURE__ */ jsx9(CommandPalette, { commands: filteredCommands, selectedIndex })
1811
- ] });
1812
- }
1813
-
1814
- // src/controllers/cli/create-application.cli-controller.ts
1815
- async function createApplicationCliController(args2) {
1816
- if (!await isPlatformInitialized()) {
1817
- console.error("Error: Cannot create an application \u2014 no platform initialized in this directory.");
1818
- process.exit(1);
1819
- }
1820
- const {
1821
- applicationName,
1822
- applicationDisplayName,
1823
- applicationDescription,
1824
- hasUserInterface,
1825
- hasBackendService
1826
- } = args2;
1827
- if (!applicationName || !applicationDisplayName || !applicationDescription) {
1828
- console.error("Error: applicationName, applicationDisplayName, and applicationDescription are required.");
1829
- process.exit(1);
1830
- }
1831
- if (!/^[a-z0-9-]+$/.test(applicationName)) {
1832
- console.error(`Error: Application name "${applicationName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
1833
- process.exit(1);
1834
- }
1835
- await createApplicationService(
1836
- {
1837
- applicationName,
1838
- applicationDisplayName,
1839
- applicationDescription,
1840
- hasUserInterface: hasUserInterface === "yes",
1841
- hasBackendService: hasBackendService === "yes"
1842
- },
1843
- { log: console.log }
1844
- );
1845
- }
1846
-
1847
- // src/controllers/cli/init.cli-controller.ts
1848
- async function initCliController(args2) {
1849
- if (await isPlatformInitialized()) {
1850
- console.error("Error: Cannot initialize a new platform \u2014 a platform is already initialized in this directory.");
1851
- process.exit(1);
1852
- }
1853
- const { organizationName, platformName, platformDisplayName, bootstrapServiceSuffix, customizationUiSuffix } = args2;
1854
- if (!organizationName || !platformName || !platformDisplayName) {
1855
- console.error("Error: organizationName, platformName, and platformDisplayName are required.");
1856
- process.exit(1);
1857
- }
1858
- await initService(
1859
- { organizationName, platformName, platformDisplayName, bootstrapServiceSuffix, customizationUiSuffix },
1860
- { log: console.log }
1861
- );
1862
- }
1863
-
1864
- // src/controllers/cli/configure-idp.cli-controller.ts
1865
- async function configureIdpCliController(args2) {
1866
- const logger = { log: console.log };
1867
- if (!await isPlatformInitialized()) {
1868
- console.error("Error: Cannot configure an IDP \u2014 no platform initialized in this directory.");
1869
- process.exit(1);
1870
- }
1871
- const { providerType, name, issuer, clientId, clientSecret } = args2;
1872
- if (!providerType || !name || !issuer || !clientId || !clientSecret) {
1873
- logger.log("Error: Missing required arguments: providerType, name, issuer, clientId, clientSecret");
1874
- process.exit(1);
1875
- }
1876
- const provider = idpProviderRegistry.get(providerType);
1877
- if (!provider) {
1878
- logger.log(`Error: Unknown IDP type "${providerType}"`);
1879
- process.exit(1);
1880
- }
1881
- const extras = {};
1882
- for (const field of provider.fields) {
1883
- const value = args2[field.key];
1884
- if (!value) {
1885
- logger.log(`Error: Missing required argument for ${providerType}: ${field.key}`);
1886
- process.exit(1);
1887
- }
1888
- extras[field.key] = value;
1889
- }
1890
- await configureIdpService({ providerType, name, issuer, clientId, clientSecret, extras }, logger);
1891
- }
1892
-
1893
- // src/commands/local-scripts/docker-compose-orchestrator.ts
1894
- import { spawn } from "child_process";
1895
- import { access as access5 } from "fs/promises";
1896
- import { join as join22 } from "path";
1897
- function runDockerCompose(args2, logger, rootDir, signal) {
1898
- return new Promise((resolvePromise) => {
1899
- const child = spawn("docker", ["compose", ...args2], {
1900
- shell: false,
1901
- stdio: ["ignore", "pipe", "pipe"],
1902
- cwd: rootDir
1848
+ // src/commands/local-scripts/docker-compose-orchestrator.ts
1849
+ import { spawn as spawn2 } from "child_process";
1850
+ import { access as access6 } from "fs/promises";
1851
+ import { join as join23 } from "path";
1852
+ function runDockerCompose(args2, logger, rootDir, signal) {
1853
+ return new Promise((resolvePromise) => {
1854
+ const child = spawn2("docker", ["compose", ...args2], {
1855
+ shell: false,
1856
+ stdio: ["ignore", "pipe", "pipe"],
1857
+ cwd: rootDir
1903
1858
  });
1904
1859
  const onAbort = () => {
1905
1860
  child.kill("SIGTERM");
@@ -1941,15 +1896,15 @@ function runDockerCompose(args2, logger, rootDir, signal) {
1941
1896
  async function getAppComposePaths(localDir, platformName, manifest) {
1942
1897
  const results = [];
1943
1898
  for (const app of manifest.applications) {
1944
- const prefixedPath = join22(localDir, `${platformName}-${app.name}-docker-compose.yml`);
1945
- const unprefixedPath = join22(localDir, `${app.name}-docker-compose.yml`);
1899
+ const prefixedPath = join23(localDir, `${platformName}-${app.name}-docker-compose.yml`);
1900
+ const unprefixedPath = join23(localDir, `${app.name}-docker-compose.yml`);
1946
1901
  let resolved = null;
1947
1902
  try {
1948
- await access5(prefixedPath);
1903
+ await access6(prefixedPath);
1949
1904
  resolved = prefixedPath;
1950
1905
  } catch {
1951
1906
  try {
1952
- await access5(unprefixedPath);
1907
+ await access6(unprefixedPath);
1953
1908
  resolved = unprefixedPath;
1954
1909
  } catch {
1955
1910
  }
@@ -1963,18 +1918,18 @@ async function getAppComposePaths(localDir, platformName, manifest) {
1963
1918
  async function buildAllComposeArgs(layout, manifest, logger) {
1964
1919
  const { rootDir, coreDirName, localDir } = layout;
1965
1920
  const platformName = manifest.product.name;
1966
- const prefixedCoreCompose = join22(localDir, `${coreDirName}-docker-compose.yml`);
1967
- const unprefixedCoreCompose = join22(localDir, "core-docker-compose.yml");
1921
+ const prefixedCoreCompose = join23(localDir, `${coreDirName}-docker-compose.yml`);
1922
+ const unprefixedCoreCompose = join23(localDir, "core-docker-compose.yml");
1968
1923
  let coreComposePath;
1969
1924
  try {
1970
- await access5(prefixedCoreCompose);
1925
+ await access6(prefixedCoreCompose);
1971
1926
  coreComposePath = prefixedCoreCompose;
1972
1927
  } catch {
1973
1928
  coreComposePath = unprefixedCoreCompose;
1974
1929
  }
1975
1930
  const fileArgs = [
1976
1931
  "-f",
1977
- join22(localDir, "platform-docker-compose.yml"),
1932
+ join23(localDir, "platform-docker-compose.yml"),
1978
1933
  "-f",
1979
1934
  coreComposePath
1980
1935
  ];
@@ -1992,7 +1947,7 @@ async function buildAllComposeArgs(layout, manifest, logger) {
1992
1947
  async function startEnvironment(layout, manifest, logger, signal) {
1993
1948
  const { rootDir, localDir } = layout;
1994
1949
  const platformName = manifest.product.name;
1995
- const envFile = join22(localDir, ".env");
1950
+ const envFile = join23(localDir, ".env");
1996
1951
  const fileArgs = await buildAllComposeArgs(layout, manifest, logger);
1997
1952
  logger.log("Starting environment...");
1998
1953
  await runDockerCompose(
@@ -2017,7 +1972,7 @@ async function startEnvironment(layout, manifest, logger, signal) {
2017
1972
  async function stopEnvironment(layout, manifest, logger, signal) {
2018
1973
  const { rootDir, localDir } = layout;
2019
1974
  const platformName = manifest.product.name;
2020
- const envFile = join22(localDir, ".env");
1975
+ const envFile = join23(localDir, ".env");
2021
1976
  const fileArgs = await buildAllComposeArgs(layout, manifest, logger);
2022
1977
  logger.log("Stopping environment...");
2023
1978
  await runDockerCompose(
@@ -2039,7 +1994,7 @@ async function stopEnvironment(layout, manifest, logger, signal) {
2039
1994
  async function destroyEnvironment(layout, manifest, logger, signal) {
2040
1995
  const { rootDir, localDir } = layout;
2041
1996
  const platformName = manifest.product.name;
2042
- const envFile = join22(localDir, ".env");
1997
+ const envFile = join23(localDir, ".env");
2043
1998
  const fileArgs = await buildAllComposeArgs(layout, manifest, logger);
2044
1999
  logger.log("Destroying environment...");
2045
2000
  await runDockerCompose(
@@ -2063,12 +2018,12 @@ async function destroyEnvironment(layout, manifest, logger, signal) {
2063
2018
  }
2064
2019
 
2065
2020
  // src/commands/local-scripts/npm-orchestrator.ts
2066
- import { spawn as spawn2 } from "child_process";
2067
- import { access as access6 } from "fs/promises";
2068
- import { resolve as resolve4, join as join23 } from "path";
2021
+ import { spawn as spawn3 } from "child_process";
2022
+ import { access as access7 } from "fs/promises";
2023
+ import { resolve as resolve5, join as join24 } from "path";
2069
2024
  function runCommand(command, args2, workDir, logger, signal) {
2070
2025
  return new Promise((resolvePromise) => {
2071
- const child = spawn2(command, args2, {
2026
+ const child = spawn3(command, args2, {
2072
2027
  cwd: workDir,
2073
2028
  shell: false,
2074
2029
  stdio: ["ignore", "pipe", "pipe"]
@@ -2112,7 +2067,7 @@ function runCommand(command, args2, workDir, logger, signal) {
2112
2067
  }
2113
2068
  async function dirExists(dirPath) {
2114
2069
  try {
2115
- await access6(dirPath);
2070
+ await access7(dirPath);
2116
2071
  return true;
2117
2072
  } catch {
2118
2073
  return false;
@@ -2122,7 +2077,7 @@ async function installDependencies(layout, manifest, logger, signal) {
2122
2077
  const { coreDir, coreDirName } = layout;
2123
2078
  const appDirs = [];
2124
2079
  for (const app of manifest.applications) {
2125
- const appDir = resolve4(join23(coreDir), app.localPath);
2080
+ const appDir = resolve5(join24(coreDir), app.localPath);
2126
2081
  if (!await dirExists(appDir)) {
2127
2082
  logger.log(`Warning: Application directory "${app.name}" not found at ${appDir} \u2014 skipping install.`);
2128
2083
  continue;
@@ -2146,7 +2101,7 @@ async function buildAll(layout, manifest, logger, signal) {
2146
2101
  if (signal?.aborted) return;
2147
2102
  const appDirs = [];
2148
2103
  for (const app of manifest.applications) {
2149
- const appDir = resolve4(join23(coreDir), app.localPath);
2104
+ const appDir = resolve5(join24(coreDir), app.localPath);
2150
2105
  if (!await dirExists(appDir)) {
2151
2106
  logger.log(`Warning: Application directory "${app.name}" not found at ${appDir} \u2014 skipping build.`);
2152
2107
  continue;
@@ -2210,18 +2165,443 @@ async function localScriptService(scriptName, logger, signal) {
2210
2165
  await runLocalScript(scriptName, logger, signal);
2211
2166
  }
2212
2167
 
2213
- // src/utils/cli-spinner.ts
2214
- var FRAMES2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
2215
- var INTERVAL_MS2 = 80;
2216
- function createCliSpinner(label) {
2217
- let frame = 0;
2218
- let intervalId = null;
2219
- function render2() {
2220
- process.stdout.write(`\r${FRAMES2[frame]} ${label}`);
2221
- frame = (frame + 1) % FRAMES2.length;
2168
+ // src/controllers/ui/status.ui-controller.ts
2169
+ async function statusUiController(ctx) {
2170
+ while (true) {
2171
+ const result = await statusService();
2172
+ for (const line of formatStatusLines(result)) {
2173
+ ctx.log(line);
2174
+ }
2175
+ const next = computeNextStep(result);
2176
+ if (next === null) {
2177
+ ctx.log("");
2178
+ ctx.log("All checks passed!");
2179
+ return;
2180
+ }
2181
+ const shouldRun = await ctx.confirm(`Next step: "${next}". Run it now?`, true);
2182
+ if (!shouldRun) return;
2183
+ if (next === "init") {
2184
+ await initUiController(ctx);
2185
+ } else {
2186
+ await localScriptService(next, ctx, ctx.signal);
2187
+ }
2188
+ const resultAfter = await statusService();
2189
+ const nextAfter = computeNextStep(resultAfter);
2190
+ if (nextAfter === next) {
2191
+ ctx.log("");
2192
+ ctx.log(`"${next}" did not complete successfully. Check the output above for errors.`);
2193
+ return;
2194
+ }
2222
2195
  }
2223
- function clearLine() {
2224
- process.stdout.write("\r\x1B[K");
2196
+ }
2197
+
2198
+ // src/controllers/ui/registry.ts
2199
+ var uiControllers = /* @__PURE__ */ new Map([
2200
+ [CREATE_APPLICATION_COMMAND_NAME, createApplicationUiController],
2201
+ [INIT_COMMAND_NAME, initUiController],
2202
+ [CONFIGURE_IDP_COMMAND_NAME, configureIdpUiController],
2203
+ [CREATE_SERVICE_MODULE_COMMAND_NAME, createServiceModuleUiController],
2204
+ [CREATE_UI_MODULE_COMMAND_NAME, createUiModuleUiController],
2205
+ [STATUS_COMMAND_NAME, statusUiController]
2206
+ ]);
2207
+
2208
+ // src/hooks/use-command-runner.ts
2209
+ function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
2210
+ const outputCountRef = useRef(0);
2211
+ const abortControllerRef = useRef(null);
2212
+ const promptResolveRef = useRef(null);
2213
+ const [promptMessage, setPromptMessage] = useState2("");
2214
+ const [promptValue, setPromptValue] = useState2("");
2215
+ const [promptMode, setPromptMode] = useState2({ kind: "text" });
2216
+ const [selectIndex, setSelectIndex] = useState2(0);
2217
+ const [confirmValue, setConfirmValue] = useState2(true);
2218
+ const abortExecution = useCallback(() => {
2219
+ abortControllerRef.current?.abort();
2220
+ abortControllerRef.current = null;
2221
+ promptResolveRef.current = null;
2222
+ setPromptMode({ kind: "text" });
2223
+ }, []);
2224
+ const runCommand2 = useCallback(
2225
+ (cmd) => {
2226
+ const controller = new AbortController();
2227
+ abortControllerRef.current = controller;
2228
+ setState(APP_STATE.EXECUTING);
2229
+ const ctx = {
2230
+ signal: controller.signal,
2231
+ log(message) {
2232
+ if (!controller.signal.aborted) {
2233
+ const id = `output-${outputCountRef.current++}`;
2234
+ appendStaticItem({ kind: "output", id, line: message });
2235
+ }
2236
+ },
2237
+ prompt(message, defaultValue) {
2238
+ return new Promise((resolve6, reject) => {
2239
+ if (controller.signal.aborted) {
2240
+ reject(new DOMException("Aborted", "AbortError"));
2241
+ return;
2242
+ }
2243
+ setPromptMessage(message);
2244
+ setPromptValue(defaultValue ?? "");
2245
+ setPromptMode({ kind: "text" });
2246
+ promptResolveRef.current = resolve6;
2247
+ setState(APP_STATE.PROMPTING);
2248
+ controller.signal.addEventListener(
2249
+ "abort",
2250
+ () => reject(new DOMException("Aborted", "AbortError")),
2251
+ { once: true }
2252
+ );
2253
+ });
2254
+ },
2255
+ select(message, options) {
2256
+ return new Promise((resolve6, reject) => {
2257
+ if (controller.signal.aborted) {
2258
+ reject(new DOMException("Aborted", "AbortError"));
2259
+ return;
2260
+ }
2261
+ setPromptMessage(message);
2262
+ setPromptMode({ kind: "select", options });
2263
+ setSelectIndex(0);
2264
+ promptResolveRef.current = resolve6;
2265
+ setState(APP_STATE.PROMPTING);
2266
+ controller.signal.addEventListener(
2267
+ "abort",
2268
+ () => reject(new DOMException("Aborted", "AbortError")),
2269
+ { once: true }
2270
+ );
2271
+ });
2272
+ },
2273
+ confirm(message, defaultValue) {
2274
+ return new Promise((resolve6, reject) => {
2275
+ if (controller.signal.aborted) {
2276
+ reject(new DOMException("Aborted", "AbortError"));
2277
+ return;
2278
+ }
2279
+ setPromptMessage(message);
2280
+ setPromptMode({ kind: "confirm" });
2281
+ setConfirmValue(defaultValue ?? true);
2282
+ promptResolveRef.current = (value) => resolve6(value === "true");
2283
+ setState(APP_STATE.PROMPTING);
2284
+ controller.signal.addEventListener(
2285
+ "abort",
2286
+ () => reject(new DOMException("Aborted", "AbortError")),
2287
+ { once: true }
2288
+ );
2289
+ });
2290
+ }
2291
+ };
2292
+ const uiController = uiControllers.get(cmd.name);
2293
+ if (!uiController) {
2294
+ const id = `output-${outputCountRef.current++}`;
2295
+ appendStaticItem({ kind: "output", id, line: `Error: No controller found for "${cmd.name}"` });
2296
+ setState(APP_STATE.IDLE);
2297
+ return;
2298
+ }
2299
+ uiController(ctx).then(() => {
2300
+ if (!controller.signal.aborted) {
2301
+ setState(APP_STATE.IDLE);
2302
+ onCommandComplete?.();
2303
+ }
2304
+ }).catch((err) => {
2305
+ if (controller.signal.aborted) return;
2306
+ const id = `output-${outputCountRef.current++}`;
2307
+ appendStaticItem({ kind: "output", id, line: `Error: ${formatError(err)}` });
2308
+ setState(APP_STATE.IDLE);
2309
+ onCommandComplete?.();
2310
+ });
2311
+ },
2312
+ [appendStaticItem, setState, onCommandComplete]
2313
+ );
2314
+ const handlePromptSubmit = useCallback(
2315
+ (value) => {
2316
+ const resolve6 = promptResolveRef.current;
2317
+ promptResolveRef.current = null;
2318
+ setPromptMode({ kind: "text" });
2319
+ setState(APP_STATE.EXECUTING);
2320
+ resolve6?.(value);
2321
+ },
2322
+ [setState]
2323
+ );
2324
+ return {
2325
+ runCommand: runCommand2,
2326
+ handlePromptSubmit,
2327
+ abortExecution,
2328
+ promptMessage,
2329
+ promptValue,
2330
+ setPromptValue,
2331
+ promptMode,
2332
+ selectIndex,
2333
+ setSelectIndex,
2334
+ confirmValue,
2335
+ setConfirmValue
2336
+ };
2337
+ }
2338
+
2339
+ // src/app.tsx
2340
+ import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
2341
+ var require2 = createRequire(import.meta.url);
2342
+ var { version } = require2("../package.json");
2343
+ function App() {
2344
+ const { exit } = useApp();
2345
+ const [state, setState] = useState3(APP_STATE.IDLE);
2346
+ const [inputValue, setInputValue] = useState3("");
2347
+ const [selectedIndex, setSelectedIndex] = useState3(0);
2348
+ const [staticItems, setStaticItems] = useState3([{ kind: "banner", id: "banner" }]);
2349
+ const [platformInitialized, setPlatformInitialized] = useState3(false);
2350
+ useEffect2(() => {
2351
+ isPlatformInitialized().then(setPlatformInitialized).catch(() => {
2352
+ });
2353
+ }, []);
2354
+ const appendStaticItem = useCallback2((item) => {
2355
+ setStaticItems((prev) => [...prev, item]);
2356
+ }, []);
2357
+ const handleCommandComplete = useCallback2(() => {
2358
+ isPlatformInitialized().then(setPlatformInitialized).catch(() => {
2359
+ });
2360
+ }, []);
2361
+ const {
2362
+ runCommand: runCommand2,
2363
+ handlePromptSubmit,
2364
+ abortExecution,
2365
+ promptMessage,
2366
+ promptValue,
2367
+ setPromptValue,
2368
+ promptMode,
2369
+ selectIndex,
2370
+ setSelectIndex,
2371
+ confirmValue,
2372
+ setConfirmValue
2373
+ } = useCommandRunner({ appendStaticItem, setState, onCommandComplete: handleCommandComplete });
2374
+ const query = inputValue.startsWith("/") ? inputValue.slice(1) : "";
2375
+ const filteredCommands = registry.searchVisible(query, { platformInitialized });
2376
+ useInput(
2377
+ (input, key) => {
2378
+ if (key.ctrl && input === "c") {
2379
+ exit();
2380
+ return;
2381
+ }
2382
+ if ((state === APP_STATE.EXECUTING || state === APP_STATE.PROMPTING) && key.escape) {
2383
+ abortExecution();
2384
+ setState(APP_STATE.IDLE);
2385
+ return;
2386
+ }
2387
+ if (state === APP_STATE.PROMPTING) {
2388
+ if (promptMode.kind === "select") {
2389
+ if (key.upArrow) {
2390
+ setSelectIndex((prev) => Math.max(0, prev - 1));
2391
+ return;
2392
+ }
2393
+ if (key.downArrow) {
2394
+ setSelectIndex((prev) => Math.min(promptMode.options.length - 1, prev + 1));
2395
+ return;
2396
+ }
2397
+ if (key.return) {
2398
+ const selected = promptMode.options[selectIndex];
2399
+ if (selected) handlePromptSubmit(selected.value);
2400
+ return;
2401
+ }
2402
+ return;
2403
+ }
2404
+ if (promptMode.kind === "confirm") {
2405
+ if (key.leftArrow || key.upArrow) {
2406
+ setConfirmValue(true);
2407
+ return;
2408
+ }
2409
+ if (key.rightArrow || key.downArrow) {
2410
+ setConfirmValue(false);
2411
+ return;
2412
+ }
2413
+ if (key.return) {
2414
+ handlePromptSubmit(confirmValue ? "true" : "false");
2415
+ return;
2416
+ }
2417
+ return;
2418
+ }
2419
+ return;
2420
+ }
2421
+ if (state === APP_STATE.PALETTE) {
2422
+ if (key.escape) {
2423
+ setState(APP_STATE.IDLE);
2424
+ setInputValue("");
2425
+ return;
2426
+ }
2427
+ if (key.upArrow) {
2428
+ setSelectedIndex((prev) => Math.max(0, prev - 1));
2429
+ return;
2430
+ }
2431
+ if (key.downArrow) {
2432
+ setSelectedIndex((prev) => Math.min(filteredCommands.length - 1, prev + 1));
2433
+ return;
2434
+ }
2435
+ if (key.return) {
2436
+ const cmd = filteredCommands[selectedIndex];
2437
+ if (cmd) {
2438
+ setInputValue("");
2439
+ runCommand2(cmd);
2440
+ }
2441
+ return;
2442
+ }
2443
+ if (key.backspace || key.delete) {
2444
+ setInputValue((prev) => {
2445
+ const next = prev.slice(0, -1);
2446
+ if (!next.startsWith("/")) {
2447
+ setState(APP_STATE.IDLE);
2448
+ return "";
2449
+ }
2450
+ return next;
2451
+ });
2452
+ setSelectedIndex(0);
2453
+ return;
2454
+ }
2455
+ if (input && !key.ctrl && !key.meta) {
2456
+ setInputValue((prev) => prev + input);
2457
+ setSelectedIndex(0);
2458
+ }
2459
+ }
2460
+ },
2461
+ { isActive: true }
2462
+ );
2463
+ const handleIdleInputChange = useCallback2((value) => {
2464
+ setInputValue(value);
2465
+ if (value.startsWith("/")) {
2466
+ setState(APP_STATE.PALETTE);
2467
+ setSelectedIndex(0);
2468
+ }
2469
+ }, []);
2470
+ const handleIdleSubmit = useCallback2(
2471
+ (value) => {
2472
+ if (!value.startsWith("/")) return;
2473
+ const name = value.slice(1).trim();
2474
+ const cmd = registry.get(name);
2475
+ if (cmd) runCommand2(cmd);
2476
+ },
2477
+ [runCommand2]
2478
+ );
2479
+ function renderActiveArea() {
2480
+ switch (state) {
2481
+ case APP_STATE.EXECUTING:
2482
+ return /* @__PURE__ */ jsxs7(Box6, { flexDirection: "row", gap: 1, children: [
2483
+ /* @__PURE__ */ jsx9(Spinner, { label: "Running\u2026" }),
2484
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "(esc to cancel)" })
2485
+ ] });
2486
+ case APP_STATE.PROMPTING:
2487
+ return /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", children: [
2488
+ /* @__PURE__ */ jsx9(Text9, { color: "cyan", children: promptMessage }),
2489
+ promptMode.kind === "select" && /* @__PURE__ */ jsx9(SelectList, { options: promptMode.options, selectedIndex: selectIndex }),
2490
+ promptMode.kind === "confirm" && /* @__PURE__ */ jsx9(ConfirmPrompt, { value: confirmValue }),
2491
+ promptMode.kind === "text" && /* @__PURE__ */ jsx9(Prompt, { value: promptValue, onChange: setPromptValue, onSubmit: handlePromptSubmit })
2492
+ ] });
2493
+ default:
2494
+ return /* @__PURE__ */ jsx9(
2495
+ Prompt,
2496
+ {
2497
+ value: inputValue,
2498
+ onChange: state === APP_STATE.IDLE ? handleIdleInputChange : () => {
2499
+ },
2500
+ onSubmit: state === APP_STATE.IDLE ? handleIdleSubmit : () => {
2501
+ },
2502
+ disabled: state === APP_STATE.PALETTE
2503
+ }
2504
+ );
2505
+ }
2506
+ }
2507
+ return /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", children: [
2508
+ /* @__PURE__ */ jsx9(ScrollbackHistory, { items: staticItems, version }),
2509
+ renderActiveArea(),
2510
+ state === APP_STATE.PALETTE && /* @__PURE__ */ jsx9(CommandPalette, { commands: filteredCommands, selectedIndex })
2511
+ ] });
2512
+ }
2513
+
2514
+ // src/controllers/cli/create-application.cli-controller.ts
2515
+ async function createApplicationCliController(args2) {
2516
+ if (!await isPlatformInitialized()) {
2517
+ console.error("Error: Cannot create an application \u2014 no platform initialized in this directory.");
2518
+ process.exit(1);
2519
+ }
2520
+ const {
2521
+ applicationName,
2522
+ applicationDisplayName,
2523
+ applicationDescription,
2524
+ hasUserInterface,
2525
+ hasBackendService
2526
+ } = args2;
2527
+ if (!applicationName || !applicationDisplayName || !applicationDescription) {
2528
+ console.error("Error: applicationName, applicationDisplayName, and applicationDescription are required.");
2529
+ process.exit(1);
2530
+ }
2531
+ if (!/^[a-z0-9-]+$/.test(applicationName)) {
2532
+ console.error(`Error: Application name "${applicationName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
2533
+ process.exit(1);
2534
+ }
2535
+ await createApplicationService(
2536
+ {
2537
+ applicationName,
2538
+ applicationDisplayName,
2539
+ applicationDescription,
2540
+ hasUserInterface: hasUserInterface === "yes",
2541
+ hasBackendService: hasBackendService === "yes"
2542
+ },
2543
+ { log: console.log }
2544
+ );
2545
+ }
2546
+
2547
+ // src/controllers/cli/init.cli-controller.ts
2548
+ async function initCliController(args2) {
2549
+ if (await isPlatformInitialized()) {
2550
+ console.error("Error: Cannot initialize a new platform \u2014 a platform is already initialized in this directory.");
2551
+ process.exit(1);
2552
+ }
2553
+ const { organizationName, platformName, platformDisplayName, bootstrapServiceSuffix, customizationUiSuffix } = args2;
2554
+ if (!organizationName || !platformName || !platformDisplayName) {
2555
+ console.error("Error: organizationName, platformName, and platformDisplayName are required.");
2556
+ process.exit(1);
2557
+ }
2558
+ await initService(
2559
+ { organizationName, platformName, platformDisplayName, bootstrapServiceSuffix, customizationUiSuffix },
2560
+ { log: console.log }
2561
+ );
2562
+ }
2563
+
2564
+ // src/controllers/cli/configure-idp.cli-controller.ts
2565
+ async function configureIdpCliController(args2) {
2566
+ const logger = { log: console.log };
2567
+ if (!await isPlatformInitialized()) {
2568
+ console.error("Error: Cannot configure an IDP \u2014 no platform initialized in this directory.");
2569
+ process.exit(1);
2570
+ }
2571
+ const { providerType, name, issuer, clientId, clientSecret } = args2;
2572
+ if (!providerType || !name || !issuer || !clientId || !clientSecret) {
2573
+ logger.log("Error: Missing required arguments: providerType, name, issuer, clientId, clientSecret");
2574
+ process.exit(1);
2575
+ }
2576
+ const provider = idpProviderRegistry.get(providerType);
2577
+ if (!provider) {
2578
+ logger.log(`Error: Unknown IDP type "${providerType}"`);
2579
+ process.exit(1);
2580
+ }
2581
+ const extras = {};
2582
+ for (const field of provider.fields) {
2583
+ const value = args2[field.key];
2584
+ if (!value) {
2585
+ logger.log(`Error: Missing required argument for ${providerType}: ${field.key}`);
2586
+ process.exit(1);
2587
+ }
2588
+ extras[field.key] = value;
2589
+ }
2590
+ await configureIdpService({ providerType, name, issuer, clientId, clientSecret, extras }, logger);
2591
+ }
2592
+
2593
+ // src/utils/cli-spinner.ts
2594
+ var FRAMES2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
2595
+ var INTERVAL_MS2 = 80;
2596
+ function createCliSpinner(label) {
2597
+ let frame = 0;
2598
+ let intervalId = null;
2599
+ function render2() {
2600
+ process.stdout.write(`\r${FRAMES2[frame]} ${label}`);
2601
+ frame = (frame + 1) % FRAMES2.length;
2602
+ }
2603
+ function clearLine() {
2604
+ process.stdout.write("\r\x1B[K");
2225
2605
  }
2226
2606
  return {
2227
2607
  start() {
@@ -2299,6 +2679,21 @@ async function createUiModuleCliController(args2) {
2299
2679
  );
2300
2680
  }
2301
2681
 
2682
+ // src/controllers/cli/status.cli-controller.ts
2683
+ var statusCliController = async (_args) => {
2684
+ const result = await statusService();
2685
+ const lines = formatStatusLines(result);
2686
+ for (const line of lines) {
2687
+ console.log(line);
2688
+ }
2689
+ const next = computeNextStep(result);
2690
+ if (next !== null) {
2691
+ console.log("");
2692
+ console.log(`Hint: Run "platform ${next}" to continue.`);
2693
+ process.exit(1);
2694
+ }
2695
+ };
2696
+
2302
2697
  // src/controllers/cli/registry.ts
2303
2698
  var cliControllers = /* @__PURE__ */ new Map([
2304
2699
  [CREATE_APPLICATION_COMMAND_NAME, createApplicationCliController],
@@ -2306,6 +2701,7 @@ var cliControllers = /* @__PURE__ */ new Map([
2306
2701
  [CONFIGURE_IDP_COMMAND_NAME, configureIdpCliController],
2307
2702
  [CREATE_SERVICE_MODULE_COMMAND_NAME, createServiceModuleCliController],
2308
2703
  [CREATE_UI_MODULE_COMMAND_NAME, createUiModuleCliController],
2704
+ [STATUS_COMMAND_NAME, statusCliController],
2309
2705
  [INSTALL_COMMAND_NAME, createLocalScriptCliController(INSTALL_COMMAND_NAME)],
2310
2706
  [BUILD_COMMAND_NAME, createLocalScriptCliController(BUILD_COMMAND_NAME)],
2311
2707
  [START_COMMAND_NAME, createLocalScriptCliController(START_COMMAND_NAME)],