@bluealba/platform-cli 0.3.1-feature-platform-cli-223 → 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
@@ -5,8 +5,8 @@ import { render } from "ink";
5
5
 
6
6
  // src/app.tsx
7
7
  import { createRequire } from "module";
8
- import { useState as useState3, useCallback as useCallback2 } from "react";
9
- import { Box as Box4, Text as Text7, useApp, useInput } from "ink";
8
+ import { useState as useState3, useCallback as useCallback2, useEffect as useEffect2 } from "react";
9
+ import { Box as Box6, Text as Text9, useApp, useInput } from "ink";
10
10
 
11
11
  // src/components/prompt.tsx
12
12
  import { Box, Text } from "ink";
@@ -143,6 +143,31 @@ function Spinner({ label }) {
143
143
  ] });
144
144
  }
145
145
 
146
+ // src/components/select-list.tsx
147
+ import React4 from "react";
148
+ import { Box as Box4, Text as Text7 } from "ink";
149
+ import { jsx as jsx7 } from "react/jsx-runtime";
150
+ var SelectList = React4.memo(function SelectList2({ options, selectedIndex }) {
151
+ if (options.length === 0) {
152
+ return /* @__PURE__ */ jsx7(Box4, { paddingLeft: 2, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "No options available" }) });
153
+ }
154
+ return /* @__PURE__ */ jsx7(Box4, { flexDirection: "column", paddingLeft: 2, children: options.map((opt, i) => {
155
+ const isSelected = i === selectedIndex;
156
+ return /* @__PURE__ */ jsx7(Box4, { children: /* @__PURE__ */ jsx7(Text7, { color: "cyan", bold: isSelected, inverse: isSelected, children: opt.label }) }, opt.value);
157
+ }) });
158
+ });
159
+
160
+ // src/components/confirm-prompt.tsx
161
+ import { Box as Box5, Text as Text8 } from "ink";
162
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
163
+ function ConfirmPrompt({ value }) {
164
+ return /* @__PURE__ */ jsxs6(Box5, { gap: 2, paddingLeft: 2, children: [
165
+ /* @__PURE__ */ jsx8(Text8, { color: "cyan", bold: value, inverse: value, children: "Yes" }),
166
+ /* @__PURE__ */ jsx8(Text8, { color: "cyan", bold: !value, inverse: !value, children: "No" }),
167
+ /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "(\u2190/\u2192 to toggle, Enter to confirm)" })
168
+ ] });
169
+ }
170
+
146
171
  // src/commands/registry.ts
147
172
  import { Fzf } from "fzf";
148
173
 
@@ -254,7 +279,12 @@ async function scaffoldBootstrap(bootstrapServiceDir, organizationName, bootstra
254
279
  {
255
280
  templateDir: bootstrapTemplateDir,
256
281
  outputDir: bootstrapServiceDir,
257
- variables: { organizationName, bootstrapName, bootstrapServiceDir: bootstrapServiceDir_var },
282
+ variables: {
283
+ organizationName,
284
+ bootstrapName,
285
+ bootstrapServiceName: `${bootstrapName}-bootstrap-service`,
286
+ bootstrapServiceDir: bootstrapServiceDir_var
287
+ },
258
288
  exclude: ["src/data/shared-libraries.json", "src/data/platform/modules-config.json"]
259
289
  },
260
290
  (message) => logger.log(message)
@@ -358,8 +388,8 @@ function buildServiceModuleEntry(organizationName, platformName, applicationName
358
388
  dependsOn: []
359
389
  };
360
390
  }
361
- function buildUiModuleEntry(organizationName, platformName, applicationName, applicationDisplayName) {
362
- const uiName = `${platformName}-${applicationName}-ui`;
391
+ function buildUiModuleEntry(organizationName, platformName, applicationName, applicationDisplayName, uiNameOverride) {
392
+ const uiName = uiNameOverride ?? `${platformName}-${applicationName}-ui`;
363
393
  return {
364
394
  name: `@${organizationName}/${uiName}`,
365
395
  displayName: applicationDisplayName,
@@ -791,7 +821,8 @@ async function generateLocalEnv(outputDir, logger, coreDirName = "core") {
791
821
  var INIT_COMMAND_NAME = "init";
792
822
  var initCommand = {
793
823
  name: INIT_COMMAND_NAME,
794
- description: "Initialize a new platform"
824
+ description: "Initialize a new platform",
825
+ hidden: ({ platformInitialized }) => platformInitialized
795
826
  };
796
827
  async function init(params, logger) {
797
828
  if (await isPlatformInitialized()) {
@@ -803,14 +834,18 @@ async function init(params, logger) {
803
834
  const outputDir = cwd4();
804
835
  logger.log(`Initializing ${platformTitle} platform for @${organizationName}...`);
805
836
  const coreDirName = `${platformName}-core`;
806
- const bootstrapServiceName = `${platformName}-bootstrap-service`;
807
- const customizationUiName = `${platformName}-customization-ui`;
837
+ const bootstrapSuffix = params.bootstrapServiceSuffix ?? "bootstrap-service";
838
+ const customizationUiSuffix = params.customizationUiSuffix ?? "customization-ui";
839
+ const bootstrapServiceName = `${platformName}-${bootstrapSuffix}`;
840
+ const customizationUiName = `${platformName}-${customizationUiSuffix}`;
808
841
  const variables = {
809
842
  organizationName,
810
843
  platformName,
811
844
  platformTitle,
812
845
  platformDisplayName,
813
846
  bootstrapName: platformName,
847
+ bootstrapServiceName,
848
+ customizationUiName,
814
849
  bootstrapServiceDir: `${coreDirName}/services`
815
850
  };
816
851
  try {
@@ -1077,7 +1112,7 @@ var createServiceModuleCommand = {
1077
1112
  description: "Add a new service module to an existing application"
1078
1113
  };
1079
1114
  async function createServiceModule(params, logger) {
1080
- const { applicationName, serviceName, serviceDisplayName } = params;
1115
+ const { applicationName, serviceName, serviceDisplayName, serviceNameSuffix } = params;
1081
1116
  const layout = await findPlatformLayout();
1082
1117
  if (!layout) {
1083
1118
  logger.log("Error: Cannot create a service module \u2014 no platform initialized in this directory.");
@@ -1104,7 +1139,8 @@ async function createServiceModule(params, logger) {
1104
1139
  logger.log(`Error: The specified application "${applicationName}" does not exist in the platform.`);
1105
1140
  return;
1106
1141
  }
1107
- const fullServiceName = `${platformName}-${serviceName}-service`;
1142
+ const suffix = serviceNameSuffix === void 0 ? "service" : serviceNameSuffix;
1143
+ const fullServiceName = suffix ? `${platformName}-${serviceName}-${suffix}` : `${platformName}-${serviceName}`;
1108
1144
  const serviceDir = join20(applicationDir, "services", fullServiceName);
1109
1145
  try {
1110
1146
  await access3(serviceDir);
@@ -1141,8 +1177,8 @@ import { access as access4, readdir as readdir4 } from "fs/promises";
1141
1177
 
1142
1178
  // src/commands/create-ui-module/append-ui-docker-compose.ts
1143
1179
  import { readFile as readFile9, writeFile as writeFile8 } from "fs/promises";
1144
- async function appendUiToDockerCompose(dockerComposePath, platformName, applicationName, port, logger) {
1145
- const uiName = `${platformName}-${applicationName}-ui`;
1180
+ async function appendUiToDockerCompose(dockerComposePath, platformName, applicationName, port, logger, uiNameOverride) {
1181
+ const uiName = uiNameOverride ?? `${platformName}-${applicationName}-ui`;
1146
1182
  const block = `
1147
1183
  ${uiName}:
1148
1184
  build:
@@ -1170,7 +1206,7 @@ var createUiModuleCommand = {
1170
1206
  description: "Add a UI module to an existing application"
1171
1207
  };
1172
1208
  async function createUiModule(params, logger) {
1173
- const { applicationName, applicationDisplayName } = params;
1209
+ const { applicationName, applicationDisplayName, uiModuleSuffix } = params;
1174
1210
  const layout = await findPlatformLayout();
1175
1211
  if (!layout) {
1176
1212
  logger.log("Error: Cannot create a UI module \u2014 no platform initialized in this directory.");
@@ -1207,7 +1243,8 @@ async function createUiModule(params, logger) {
1207
1243
  }
1208
1244
  } catch {
1209
1245
  }
1210
- const uiName = `${platformName}-${applicationName}-ui`;
1246
+ const uiSuffix = uiModuleSuffix === void 0 ? "ui" : uiModuleSuffix;
1247
+ const uiName = uiSuffix ? `${platformName}-${applicationName}-${uiSuffix}` : `${platformName}-${applicationName}`;
1211
1248
  const uiOutputDir = join21(uiDir, uiName);
1212
1249
  const uiBaseDir = `${platformName}-${applicationName}/ui`;
1213
1250
  try {
@@ -1217,14 +1254,275 @@ async function createUiModule(params, logger) {
1217
1254
  return;
1218
1255
  }
1219
1256
  const bootstrapServiceDir = join21(applicationDir, "services", `${platformName}-${applicationName}-bootstrap-service`);
1220
- const moduleEntry = buildUiModuleEntry(organizationName, platformName, applicationName, applicationDisplayName);
1257
+ const moduleEntry = buildUiModuleEntry(organizationName, platformName, applicationName, applicationDisplayName, uiName);
1221
1258
  await addModuleEntry(bootstrapServiceDir, applicationName, moduleEntry, logger);
1222
1259
  const dockerComposePath = join21(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
1223
1260
  const port = await getNextAvailablePort(localDir);
1224
- await appendUiToDockerCompose(dockerComposePath, platformName, applicationName, port, logger);
1261
+ await appendUiToDockerCompose(dockerComposePath, platformName, applicationName, port, logger, uiName);
1225
1262
  logger.log(`Done! UI module "${uiName}" added to application "${applicationName}".`);
1226
1263
  }
1227
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
+
1228
1526
  // src/commands/registry.ts
1229
1527
  var CommandRegistry = class {
1230
1528
  commands = /* @__PURE__ */ new Map();
@@ -1245,6 +1543,14 @@ var CommandRegistry = class {
1245
1543
  });
1246
1544
  return fzf.find(query).map((result) => result.item);
1247
1545
  }
1546
+ searchVisible(query, visibilityCtx) {
1547
+ const visible = this.getAll().filter((cmd) => !cmd.hidden?.(visibilityCtx));
1548
+ if (!query) return visible;
1549
+ const fzf = new Fzf(visible, {
1550
+ selector: (cmd) => `${cmd.name} ${cmd.description}`
1551
+ });
1552
+ return fzf.find(query).map((result) => result.item);
1553
+ }
1248
1554
  };
1249
1555
  var registry = new CommandRegistry();
1250
1556
  registry.register(createApplicationCommand);
@@ -1252,6 +1558,7 @@ registry.register(initCommand);
1252
1558
  registry.register(configureIdpCommand);
1253
1559
  registry.register(createServiceModuleCommand);
1254
1560
  registry.register(createUiModuleCommand);
1561
+ registry.register(statusCommand);
1255
1562
 
1256
1563
  // src/app-state.ts
1257
1564
  var APP_STATE = {
@@ -1282,23 +1589,15 @@ async function createApplicationUiController(ctx) {
1282
1589
  }
1283
1590
  const applicationDisplayName = await ctx.prompt("Application display name:");
1284
1591
  const applicationDescription = await ctx.prompt("Application description:");
1285
- const hasUserInterfaceAnswer = await ctx.prompt("Does this application have a user interface? (yes/no):");
1286
- if (hasUserInterfaceAnswer !== "yes" && hasUserInterfaceAnswer !== "no") {
1287
- ctx.log(`Error: Please answer "yes" or "no".`);
1288
- return;
1289
- }
1290
- const hasBackendServiceAnswer = await ctx.prompt("Does this application have a backend service? (yes/no):");
1291
- if (hasBackendServiceAnswer !== "yes" && hasBackendServiceAnswer !== "no") {
1292
- ctx.log(`Error: Please answer "yes" or "no".`);
1293
- return;
1294
- }
1592
+ const hasUserInterface = await ctx.confirm("Does this application have a user interface?");
1593
+ const hasBackendService = await ctx.confirm("Does this application have a backend service?");
1295
1594
  await createApplicationService(
1296
1595
  {
1297
1596
  applicationName,
1298
1597
  applicationDisplayName,
1299
1598
  applicationDescription,
1300
- hasUserInterface: hasUserInterfaceAnswer === "yes",
1301
- hasBackendService: hasBackendServiceAnswer === "yes"
1599
+ hasUserInterface,
1600
+ hasBackendService
1302
1601
  },
1303
1602
  ctx
1304
1603
  );
@@ -1318,7 +1617,32 @@ async function initUiController(ctx) {
1318
1617
  const organizationName = await ctx.prompt("Organization name:");
1319
1618
  const platformName = await ctx.prompt("Platform name:");
1320
1619
  const platformDisplayName = await ctx.prompt("Platform display name:");
1321
- await initService({ organizationName, platformName, platformDisplayName }, ctx);
1620
+ const defaultBootstrapSuffix = "bootstrap-service";
1621
+ const defaultCustomizationUiSuffix = "customization-ui";
1622
+ ctx.log(`Default artifact names:`);
1623
+ ctx.log(` Bootstrap service: ${platformName}-${defaultBootstrapSuffix}`);
1624
+ ctx.log(` Customization UI: ${platformName}-${defaultCustomizationUiSuffix}`);
1625
+ const customize = await ctx.confirm("Customize artifact names?", false);
1626
+ let bootstrapServiceSuffix = defaultBootstrapSuffix;
1627
+ let customizationUiSuffix = defaultCustomizationUiSuffix;
1628
+ if (customize) {
1629
+ const bsSuffix = await ctx.prompt(`Bootstrap service suffix (${platformName}-...):`, defaultBootstrapSuffix);
1630
+ if (!/^[a-z0-9-]+$/.test(bsSuffix)) {
1631
+ ctx.log(`Error: Suffix "${bsSuffix}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
1632
+ return;
1633
+ }
1634
+ bootstrapServiceSuffix = bsSuffix;
1635
+ const cuiSuffix = await ctx.prompt(`Customization UI suffix (${platformName}-...):`, defaultCustomizationUiSuffix);
1636
+ if (!/^[a-z0-9-]+$/.test(cuiSuffix)) {
1637
+ ctx.log(`Error: Suffix "${cuiSuffix}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
1638
+ return;
1639
+ }
1640
+ customizationUiSuffix = cuiSuffix;
1641
+ }
1642
+ await initService(
1643
+ { organizationName, platformName, platformDisplayName, bootstrapServiceSuffix, customizationUiSuffix },
1644
+ ctx
1645
+ );
1322
1646
  }
1323
1647
 
1324
1648
  // src/services/configure-idp.service.ts
@@ -1333,14 +1657,11 @@ async function configureIdpUiController(ctx) {
1333
1657
  return;
1334
1658
  }
1335
1659
  const providers = getAllProviders();
1336
- const options = providers.map((p, i) => `${i + 1}: ${p.displayName}`).join(", ");
1337
- const selectionInput = await ctx.prompt(`Select IDP type (${options}):`);
1338
- const index = parseInt(selectionInput.trim(), 10) - 1;
1339
- if (isNaN(index) || index < 0 || index >= providers.length) {
1340
- ctx.log(`Error: Invalid selection "${selectionInput}"`);
1341
- return;
1342
- }
1343
- const provider = providers[index];
1660
+ const providerType = await ctx.select(
1661
+ "Select IDP type:",
1662
+ providers.map((p) => ({ label: p.displayName, value: p.type }))
1663
+ );
1664
+ const provider = providers.find((p) => p.type === providerType);
1344
1665
  const name = await ctx.prompt("Provider name:");
1345
1666
  const issuer = await ctx.prompt("Issuer URL:");
1346
1667
  const clientId = await ctx.prompt("Client ID:");
@@ -1366,19 +1687,38 @@ async function createServiceModuleUiController(ctx) {
1366
1687
  ctx.log("Error: Cannot create a service module \u2014 no platform initialized in this directory.");
1367
1688
  return;
1368
1689
  }
1369
- const applicationName = await ctx.prompt("Application name:");
1370
- if (!/^[a-z0-9-]+$/.test(applicationName)) {
1371
- ctx.log(`Error: Application name "${applicationName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
1690
+ const layout = await findPlatformLayout();
1691
+ if (!layout) {
1692
+ ctx.log("Error: Cannot create a service module \u2014 no platform initialized in this directory.");
1372
1693
  return;
1373
1694
  }
1695
+ let manifest = await readManifest(layout.rootDir, layout.coreDirName);
1696
+ if (manifest.applications.length === 0) {
1697
+ ctx.log("No applications found in this platform.");
1698
+ const createFirst = await ctx.confirm("Would you like to create an application first?");
1699
+ if (!createFirst) return;
1700
+ await createApplicationUiController(ctx);
1701
+ manifest = await readManifest(layout.rootDir, layout.coreDirName);
1702
+ if (manifest.applications.length === 0) {
1703
+ ctx.log("Error: No applications available after creation attempt.");
1704
+ return;
1705
+ }
1706
+ }
1707
+ const applicationName = await ctx.select(
1708
+ "Select application:",
1709
+ manifest.applications.map((a) => ({ label: `${a.name} \u2014 ${a.displayName}`, value: a.name }))
1710
+ );
1374
1711
  const serviceName = await ctx.prompt("Service name:");
1375
1712
  if (!/^[a-z0-9-]+$/.test(serviceName)) {
1376
1713
  ctx.log(`Error: Service name "${serviceName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
1377
1714
  return;
1378
1715
  }
1716
+ const platformName = manifest.product.name;
1717
+ const includeSuffix = await ctx.confirm(`Include "-service" suffix? (${platformName}-${serviceName}-service)`, true);
1718
+ const serviceNameSuffix = includeSuffix ? "service" : null;
1379
1719
  const serviceDisplayName = await ctx.prompt("Service display name:");
1380
1720
  await createServiceModuleService(
1381
- { applicationName, serviceName, serviceDisplayName },
1721
+ { applicationName, serviceName, serviceDisplayName, serviceNameSuffix },
1382
1722
  ctx
1383
1723
  );
1384
1724
  }
@@ -1394,313 +1734,124 @@ async function createUiModuleUiController(ctx) {
1394
1734
  ctx.log("Error: Cannot create a UI module \u2014 no platform initialized in this directory.");
1395
1735
  return;
1396
1736
  }
1397
- const applicationName = await ctx.prompt("Application name:");
1398
- if (!/^[a-z0-9-]+$/.test(applicationName)) {
1399
- ctx.log(`Error: Application name "${applicationName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
1737
+ const layout = await findPlatformLayout();
1738
+ if (!layout) {
1739
+ ctx.log("Error: Cannot create a UI module \u2014 no platform initialized in this directory.");
1400
1740
  return;
1401
1741
  }
1742
+ let manifest = await readManifest(layout.rootDir, layout.coreDirName);
1743
+ if (manifest.applications.length === 0) {
1744
+ ctx.log("No applications found in this platform.");
1745
+ const createFirst = await ctx.confirm("Would you like to create an application first?");
1746
+ if (!createFirst) return;
1747
+ await createApplicationUiController(ctx);
1748
+ manifest = await readManifest(layout.rootDir, layout.coreDirName);
1749
+ if (manifest.applications.length === 0) {
1750
+ ctx.log("Error: No applications available after creation attempt.");
1751
+ return;
1752
+ }
1753
+ }
1754
+ const applicationName = await ctx.select(
1755
+ "Select application:",
1756
+ manifest.applications.map((a) => ({ label: `${a.name} \u2014 ${a.displayName}`, value: a.name }))
1757
+ );
1758
+ const platformName = manifest.product.name;
1759
+ const includeSuffix = await ctx.confirm(`Include "-ui" suffix? (${platformName}-${applicationName}-ui)`, true);
1760
+ const uiModuleSuffix = includeSuffix ? "ui" : null;
1402
1761
  const applicationDisplayName = await ctx.prompt("Application display name:");
1403
1762
  await createUiModuleService(
1404
- { applicationName, applicationDisplayName },
1763
+ { applicationName, applicationDisplayName, uiModuleSuffix },
1405
1764
  ctx
1406
1765
  );
1407
1766
  }
1408
1767
 
1409
- // src/controllers/ui/registry.ts
1410
- var uiControllers = /* @__PURE__ */ new Map([
1411
- [CREATE_APPLICATION_COMMAND_NAME, createApplicationUiController],
1412
- [INIT_COMMAND_NAME, initUiController],
1413
- [CONFIGURE_IDP_COMMAND_NAME, configureIdpUiController],
1414
- [CREATE_SERVICE_MODULE_COMMAND_NAME, createServiceModuleUiController],
1415
- [CREATE_UI_MODULE_COMMAND_NAME, createUiModuleUiController]
1416
- ]);
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
+ };
1417
1789
 
1418
- // src/hooks/use-command-runner.ts
1419
- function useCommandRunner({ appendStaticItem, setState }) {
1420
- const outputCountRef = useRef(0);
1421
- const abortControllerRef = useRef(null);
1422
- const promptResolveRef = useRef(null);
1423
- const [promptMessage, setPromptMessage] = useState2("");
1424
- const [promptValue, setPromptValue] = useState2("");
1425
- const abortExecution = useCallback(() => {
1426
- abortControllerRef.current?.abort();
1427
- abortControllerRef.current = null;
1428
- promptResolveRef.current = null;
1429
- }, []);
1430
- const runCommand2 = useCallback(
1431
- (cmd) => {
1432
- const controller = new AbortController();
1433
- abortControllerRef.current = controller;
1434
- setState(APP_STATE.EXECUTING);
1435
- const ctx = {
1436
- signal: controller.signal,
1437
- log(message) {
1438
- if (!controller.signal.aborted) {
1439
- const id = `output-${outputCountRef.current++}`;
1440
- appendStaticItem({ kind: "output", id, line: message });
1441
- }
1442
- },
1443
- prompt(message) {
1444
- return new Promise((resolve5, reject) => {
1445
- if (controller.signal.aborted) {
1446
- reject(new DOMException("Aborted", "AbortError"));
1447
- return;
1448
- }
1449
- setPromptMessage(message);
1450
- setPromptValue("");
1451
- promptResolveRef.current = resolve5;
1452
- setState(APP_STATE.PROMPTING);
1453
- controller.signal.addEventListener(
1454
- "abort",
1455
- () => reject(new DOMException("Aborted", "AbortError")),
1456
- { once: true }
1457
- );
1458
- });
1459
- }
1460
- };
1461
- const uiController = uiControllers.get(cmd.name);
1462
- if (!uiController) {
1463
- const id = `output-${outputCountRef.current++}`;
1464
- appendStaticItem({ kind: "output", id, line: `Error: No controller found for "${cmd.name}"` });
1465
- setState(APP_STATE.IDLE);
1466
- 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/)")}`);
1467
1822
  }
1468
- uiController(ctx).then(() => {
1469
- if (!controller.signal.aborted) {
1470
- setState(APP_STATE.IDLE);
1471
- }
1472
- }).catch((err) => {
1473
- if (controller.signal.aborted) return;
1474
- const id = `output-${outputCountRef.current++}`;
1475
- appendStaticItem({ kind: "output", id, line: `Error: ${formatError(err)}` });
1476
- setState(APP_STATE.IDLE);
1477
- });
1478
- },
1479
- [appendStaticItem, setState]
1480
- );
1481
- const handlePromptSubmit = useCallback(
1482
- (value) => {
1483
- const resolve5 = promptResolveRef.current;
1484
- promptResolveRef.current = null;
1485
- setState(APP_STATE.EXECUTING);
1486
- resolve5?.(value);
1487
- },
1488
- [setState]
1489
- );
1490
- return {
1491
- runCommand: runCommand2,
1492
- handlePromptSubmit,
1493
- abortExecution,
1494
- promptMessage,
1495
- promptValue,
1496
- setPromptValue
1497
- };
1498
- }
1499
-
1500
- // src/app.tsx
1501
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1502
- var require2 = createRequire(import.meta.url);
1503
- var { version } = require2("../package.json");
1504
- function App() {
1505
- const { exit } = useApp();
1506
- const [state, setState] = useState3(APP_STATE.IDLE);
1507
- const [inputValue, setInputValue] = useState3("");
1508
- const [selectedIndex, setSelectedIndex] = useState3(0);
1509
- const [staticItems, setStaticItems] = useState3([{ kind: "banner", id: "banner" }]);
1510
- const appendStaticItem = useCallback2((item) => {
1511
- setStaticItems((prev) => [...prev, item]);
1512
- }, []);
1513
- const { runCommand: runCommand2, handlePromptSubmit, abortExecution, promptMessage, promptValue, setPromptValue } = useCommandRunner({ appendStaticItem, setState });
1514
- const query = inputValue.startsWith("/") ? inputValue.slice(1) : "";
1515
- const filteredCommands = registry.search(query);
1516
- useInput(
1517
- (input, key) => {
1518
- if (key.ctrl && input === "c") {
1519
- exit();
1520
- return;
1521
- }
1522
- if ((state === APP_STATE.EXECUTING || state === APP_STATE.PROMPTING) && key.escape) {
1523
- abortExecution();
1524
- setState(APP_STATE.IDLE);
1525
- return;
1526
- }
1527
- if (state === APP_STATE.PALETTE) {
1528
- if (key.escape) {
1529
- setState(APP_STATE.IDLE);
1530
- setInputValue("");
1531
- return;
1532
- }
1533
- if (key.upArrow) {
1534
- setSelectedIndex((prev) => Math.max(0, prev - 1));
1535
- return;
1536
- }
1537
- if (key.downArrow) {
1538
- setSelectedIndex((prev) => Math.min(filteredCommands.length - 1, prev + 1));
1539
- return;
1540
- }
1541
- if (key.return) {
1542
- const cmd = filteredCommands[selectedIndex];
1543
- if (cmd) {
1544
- setInputValue("");
1545
- runCommand2(cmd);
1546
- }
1547
- return;
1548
- }
1549
- if (key.backspace || key.delete) {
1550
- setInputValue((prev) => {
1551
- const next = prev.slice(0, -1);
1552
- if (!next.startsWith("/")) {
1553
- setState(APP_STATE.IDLE);
1554
- return "";
1555
- }
1556
- return next;
1557
- });
1558
- setSelectedIndex(0);
1559
- return;
1560
- }
1561
- if (input && !key.ctrl && !key.meta) {
1562
- setInputValue((prev) => prev + input);
1563
- setSelectedIndex(0);
1564
- }
1565
- }
1566
- },
1567
- { isActive: true }
1568
- );
1569
- const handleIdleInputChange = useCallback2((value) => {
1570
- setInputValue(value);
1571
- if (value.startsWith("/")) {
1572
- setState(APP_STATE.PALETTE);
1573
- setSelectedIndex(0);
1574
- }
1575
- }, []);
1576
- const handleIdleSubmit = useCallback2(
1577
- (value) => {
1578
- if (!value.startsWith("/")) return;
1579
- const name = value.slice(1).trim();
1580
- const cmd = registry.get(name);
1581
- if (cmd) runCommand2(cmd);
1582
- },
1583
- [runCommand2]
1584
- );
1585
- function renderActiveArea() {
1586
- switch (state) {
1587
- case APP_STATE.EXECUTING:
1588
- return /* @__PURE__ */ jsxs6(Box4, { flexDirection: "row", gap: 1, children: [
1589
- /* @__PURE__ */ jsx7(Spinner, { label: "Running\u2026" }),
1590
- /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "(esc to cancel)" })
1591
- ] });
1592
- case APP_STATE.PROMPTING:
1593
- return /* @__PURE__ */ jsxs6(Box4, { flexDirection: "column", children: [
1594
- /* @__PURE__ */ jsx7(Text7, { color: "cyan", children: promptMessage }),
1595
- /* @__PURE__ */ jsx7(Prompt, { value: promptValue, onChange: setPromptValue, onSubmit: handlePromptSubmit })
1596
- ] });
1597
- default:
1598
- return /* @__PURE__ */ jsx7(
1599
- Prompt,
1600
- {
1601
- value: inputValue,
1602
- onChange: state === APP_STATE.IDLE ? handleIdleInputChange : () => {
1603
- },
1604
- onSubmit: state === APP_STATE.IDLE ? handleIdleSubmit : () => {
1605
- },
1606
- disabled: state === APP_STATE.PALETTE
1607
- }
1608
- );
1609
- }
1610
- }
1611
- return /* @__PURE__ */ jsxs6(Box4, { flexDirection: "column", children: [
1612
- /* @__PURE__ */ jsx7(ScrollbackHistory, { items: staticItems, version }),
1613
- renderActiveArea(),
1614
- state === APP_STATE.PALETTE && /* @__PURE__ */ jsx7(CommandPalette, { commands: filteredCommands, selectedIndex })
1615
- ] });
1616
- }
1617
-
1618
- // src/controllers/cli/create-application.cli-controller.ts
1619
- async function createApplicationCliController(args2) {
1620
- if (!await isPlatformInitialized()) {
1621
- console.error("Error: Cannot create an application \u2014 no platform initialized in this directory.");
1622
- process.exit(1);
1623
- }
1624
- const {
1625
- applicationName,
1626
- applicationDisplayName,
1627
- applicationDescription,
1628
- hasUserInterface,
1629
- hasBackendService
1630
- } = args2;
1631
- if (!applicationName || !applicationDisplayName || !applicationDescription) {
1632
- console.error("Error: applicationName, applicationDisplayName, and applicationDescription are required.");
1633
- process.exit(1);
1634
- }
1635
- if (!/^[a-z0-9-]+$/.test(applicationName)) {
1636
- console.error(`Error: Application name "${applicationName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
1637
- process.exit(1);
1638
- }
1639
- await createApplicationService(
1640
- {
1641
- applicationName,
1642
- applicationDisplayName,
1643
- applicationDescription,
1644
- hasUserInterface: hasUserInterface === "yes",
1645
- hasBackendService: hasBackendService === "yes"
1646
- },
1647
- { log: console.log }
1648
- );
1649
- }
1650
-
1651
- // src/controllers/cli/init.cli-controller.ts
1652
- async function initCliController(args2) {
1653
- if (await isPlatformInitialized()) {
1654
- console.error("Error: Cannot initialize a new platform \u2014 a platform is already initialized in this directory.");
1655
- process.exit(1);
1656
- }
1657
- const { organizationName, platformName, platformDisplayName } = args2;
1658
- if (!organizationName || !platformName || !platformDisplayName) {
1659
- console.error("Error: organizationName, platformName, and platformDisplayName are required.");
1660
- process.exit(1);
1661
- }
1662
- await initService(
1663
- { organizationName, platformName, platformDisplayName },
1664
- { log: console.log }
1665
- );
1666
- }
1667
-
1668
- // src/controllers/cli/configure-idp.cli-controller.ts
1669
- async function configureIdpCliController(args2) {
1670
- const logger = { log: console.log };
1671
- if (!await isPlatformInitialized()) {
1672
- console.error("Error: Cannot configure an IDP \u2014 no platform initialized in this directory.");
1673
- process.exit(1);
1674
- }
1675
- const { providerType, name, issuer, clientId, clientSecret } = args2;
1676
- if (!providerType || !name || !issuer || !clientId || !clientSecret) {
1677
- logger.log("Error: Missing required arguments: providerType, name, issuer, clientId, clientSecret");
1678
- process.exit(1);
1679
- }
1680
- const provider = idpProviderRegistry.get(providerType);
1681
- if (!provider) {
1682
- logger.log(`Error: Unknown IDP type "${providerType}"`);
1683
- process.exit(1);
1684
- }
1685
- const extras = {};
1686
- for (const field of provider.fields) {
1687
- const value = args2[field.key];
1688
- if (!value) {
1689
- logger.log(`Error: Missing required argument for ${providerType}: ${field.key}`);
1690
- process.exit(1);
1691
- }
1692
- extras[field.key] = value;
1693
- }
1694
- await configureIdpService({ providerType, name, issuer, clientId, clientSecret, extras }, logger);
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;
1695
1846
  }
1696
1847
 
1697
1848
  // src/commands/local-scripts/docker-compose-orchestrator.ts
1698
- import { spawn } from "child_process";
1699
- import { access as access5 } from "fs/promises";
1700
- import { join as join22 } from "path";
1849
+ import { spawn as spawn2 } from "child_process";
1850
+ import { access as access6 } from "fs/promises";
1851
+ import { join as join23 } from "path";
1701
1852
  function runDockerCompose(args2, logger, rootDir, signal) {
1702
1853
  return new Promise((resolvePromise) => {
1703
- const child = spawn("docker", ["compose", ...args2], {
1854
+ const child = spawn2("docker", ["compose", ...args2], {
1704
1855
  shell: false,
1705
1856
  stdio: ["ignore", "pipe", "pipe"],
1706
1857
  cwd: rootDir
@@ -1745,15 +1896,15 @@ function runDockerCompose(args2, logger, rootDir, signal) {
1745
1896
  async function getAppComposePaths(localDir, platformName, manifest) {
1746
1897
  const results = [];
1747
1898
  for (const app of manifest.applications) {
1748
- const prefixedPath = join22(localDir, `${platformName}-${app.name}-docker-compose.yml`);
1749
- 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`);
1750
1901
  let resolved = null;
1751
1902
  try {
1752
- await access5(prefixedPath);
1903
+ await access6(prefixedPath);
1753
1904
  resolved = prefixedPath;
1754
1905
  } catch {
1755
1906
  try {
1756
- await access5(unprefixedPath);
1907
+ await access6(unprefixedPath);
1757
1908
  resolved = unprefixedPath;
1758
1909
  } catch {
1759
1910
  }
@@ -1767,18 +1918,18 @@ async function getAppComposePaths(localDir, platformName, manifest) {
1767
1918
  async function buildAllComposeArgs(layout, manifest, logger) {
1768
1919
  const { rootDir, coreDirName, localDir } = layout;
1769
1920
  const platformName = manifest.product.name;
1770
- const prefixedCoreCompose = join22(localDir, `${coreDirName}-docker-compose.yml`);
1771
- 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");
1772
1923
  let coreComposePath;
1773
1924
  try {
1774
- await access5(prefixedCoreCompose);
1925
+ await access6(prefixedCoreCompose);
1775
1926
  coreComposePath = prefixedCoreCompose;
1776
1927
  } catch {
1777
1928
  coreComposePath = unprefixedCoreCompose;
1778
1929
  }
1779
1930
  const fileArgs = [
1780
1931
  "-f",
1781
- join22(localDir, "platform-docker-compose.yml"),
1932
+ join23(localDir, "platform-docker-compose.yml"),
1782
1933
  "-f",
1783
1934
  coreComposePath
1784
1935
  ];
@@ -1796,7 +1947,7 @@ async function buildAllComposeArgs(layout, manifest, logger) {
1796
1947
  async function startEnvironment(layout, manifest, logger, signal) {
1797
1948
  const { rootDir, localDir } = layout;
1798
1949
  const platformName = manifest.product.name;
1799
- const envFile = join22(localDir, ".env");
1950
+ const envFile = join23(localDir, ".env");
1800
1951
  const fileArgs = await buildAllComposeArgs(layout, manifest, logger);
1801
1952
  logger.log("Starting environment...");
1802
1953
  await runDockerCompose(
@@ -1821,7 +1972,7 @@ async function startEnvironment(layout, manifest, logger, signal) {
1821
1972
  async function stopEnvironment(layout, manifest, logger, signal) {
1822
1973
  const { rootDir, localDir } = layout;
1823
1974
  const platformName = manifest.product.name;
1824
- const envFile = join22(localDir, ".env");
1975
+ const envFile = join23(localDir, ".env");
1825
1976
  const fileArgs = await buildAllComposeArgs(layout, manifest, logger);
1826
1977
  logger.log("Stopping environment...");
1827
1978
  await runDockerCompose(
@@ -1843,7 +1994,7 @@ async function stopEnvironment(layout, manifest, logger, signal) {
1843
1994
  async function destroyEnvironment(layout, manifest, logger, signal) {
1844
1995
  const { rootDir, localDir } = layout;
1845
1996
  const platformName = manifest.product.name;
1846
- const envFile = join22(localDir, ".env");
1997
+ const envFile = join23(localDir, ".env");
1847
1998
  const fileArgs = await buildAllComposeArgs(layout, manifest, logger);
1848
1999
  logger.log("Destroying environment...");
1849
2000
  await runDockerCompose(
@@ -1867,12 +2018,12 @@ async function destroyEnvironment(layout, manifest, logger, signal) {
1867
2018
  }
1868
2019
 
1869
2020
  // src/commands/local-scripts/npm-orchestrator.ts
1870
- import { spawn as spawn2 } from "child_process";
1871
- import { access as access6 } from "fs/promises";
1872
- 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";
1873
2024
  function runCommand(command, args2, workDir, logger, signal) {
1874
2025
  return new Promise((resolvePromise) => {
1875
- const child = spawn2(command, args2, {
2026
+ const child = spawn3(command, args2, {
1876
2027
  cwd: workDir,
1877
2028
  shell: false,
1878
2029
  stdio: ["ignore", "pipe", "pipe"]
@@ -1916,7 +2067,7 @@ function runCommand(command, args2, workDir, logger, signal) {
1916
2067
  }
1917
2068
  async function dirExists(dirPath) {
1918
2069
  try {
1919
- await access6(dirPath);
2070
+ await access7(dirPath);
1920
2071
  return true;
1921
2072
  } catch {
1922
2073
  return false;
@@ -1926,7 +2077,7 @@ async function installDependencies(layout, manifest, logger, signal) {
1926
2077
  const { coreDir, coreDirName } = layout;
1927
2078
  const appDirs = [];
1928
2079
  for (const app of manifest.applications) {
1929
- const appDir = resolve4(join23(coreDir), app.localPath);
2080
+ const appDir = resolve5(join24(coreDir), app.localPath);
1930
2081
  if (!await dirExists(appDir)) {
1931
2082
  logger.log(`Warning: Application directory "${app.name}" not found at ${appDir} \u2014 skipping install.`);
1932
2083
  continue;
@@ -1950,7 +2101,7 @@ async function buildAll(layout, manifest, logger, signal) {
1950
2101
  if (signal?.aborted) return;
1951
2102
  const appDirs = [];
1952
2103
  for (const app of manifest.applications) {
1953
- const appDir = resolve4(join23(coreDir), app.localPath);
2104
+ const appDir = resolve5(join24(coreDir), app.localPath);
1954
2105
  if (!await dirExists(appDir)) {
1955
2106
  logger.log(`Warning: Application directory "${app.name}" not found at ${appDir} \u2014 skipping build.`);
1956
2107
  continue;
@@ -2014,6 +2165,431 @@ async function localScriptService(scriptName, logger, signal) {
2014
2165
  await runLocalScript(scriptName, logger, signal);
2015
2166
  }
2016
2167
 
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
+ }
2195
+ }
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
+
2017
2593
  // src/utils/cli-spinner.ts
2018
2594
  var FRAMES2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
2019
2595
  var INTERVAL_MS2 = 80;
@@ -2103,6 +2679,21 @@ async function createUiModuleCliController(args2) {
2103
2679
  );
2104
2680
  }
2105
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
+
2106
2697
  // src/controllers/cli/registry.ts
2107
2698
  var cliControllers = /* @__PURE__ */ new Map([
2108
2699
  [CREATE_APPLICATION_COMMAND_NAME, createApplicationCliController],
@@ -2110,6 +2701,7 @@ var cliControllers = /* @__PURE__ */ new Map([
2110
2701
  [CONFIGURE_IDP_COMMAND_NAME, configureIdpCliController],
2111
2702
  [CREATE_SERVICE_MODULE_COMMAND_NAME, createServiceModuleCliController],
2112
2703
  [CREATE_UI_MODULE_COMMAND_NAME, createUiModuleCliController],
2704
+ [STATUS_COMMAND_NAME, statusCliController],
2113
2705
  [INSTALL_COMMAND_NAME, createLocalScriptCliController(INSTALL_COMMAND_NAME)],
2114
2706
  [BUILD_COMMAND_NAME, createLocalScriptCliController(BUILD_COMMAND_NAME)],
2115
2707
  [START_COMMAND_NAME, createLocalScriptCliController(START_COMMAND_NAME)],
@@ -2132,10 +2724,10 @@ function parseKeyValueArgs(args2) {
2132
2724
  }
2133
2725
 
2134
2726
  // src/index.tsx
2135
- import { jsx as jsx8 } from "react/jsx-runtime";
2727
+ import { jsx as jsx10 } from "react/jsx-runtime";
2136
2728
  var args = process.argv.slice(2);
2137
2729
  if (args.length === 0) {
2138
- render(/* @__PURE__ */ jsx8(App, {}));
2730
+ render(/* @__PURE__ */ jsx10(App, {}));
2139
2731
  } else {
2140
2732
  const commandName = args[0];
2141
2733
  const params = parseKeyValueArgs(args.slice(1));