@bluealba/platform-cli 0.3.1-feature-platform-cli-225 → 0.3.1-feature-add-new-commands-227

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +855 -582
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { render } from "ink";
6
6
  // src/app.tsx
7
7
  import { createRequire } from "module";
8
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";
9
+ import { Box as Box7, Text as Text10, useApp, useInput } from "ink";
10
10
 
11
11
  // src/components/prompt.tsx
12
12
  import { Box, Text } from "ink";
@@ -157,14 +157,46 @@ var SelectList = React4.memo(function SelectList2({ options, selectedIndex }) {
157
157
  }) });
158
158
  });
159
159
 
160
- // src/components/confirm-prompt.tsx
160
+ // src/components/multi-select-list.tsx
161
+ import React5 from "react";
161
162
  import { Box as Box5, Text as Text8 } from "ink";
162
163
  import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
164
+ var MultiSelectList = React5.memo(function MultiSelectList2({
165
+ options,
166
+ focusedIndex,
167
+ checkedIndices
168
+ }) {
169
+ if (options.length === 0) {
170
+ return /* @__PURE__ */ jsx8(Box5, { paddingLeft: 2, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "No options available" }) });
171
+ }
172
+ const allChecked = checkedIndices.size === options.length;
173
+ const selectAllFocused = focusedIndex === 0;
174
+ return /* @__PURE__ */ jsxs6(Box5, { flexDirection: "column", paddingLeft: 2, children: [
175
+ /* @__PURE__ */ jsx8(Box5, { children: /* @__PURE__ */ jsxs6(Text8, { color: "cyan", bold: selectAllFocused, inverse: selectAllFocused, children: [
176
+ allChecked ? "[x]" : "[ ]",
177
+ " Select all"
178
+ ] }) }, "select-all"),
179
+ options.map((opt, i) => {
180
+ const isFocused = focusedIndex === i + 1;
181
+ const isChecked = checkedIndices.has(i);
182
+ return /* @__PURE__ */ jsx8(Box5, { children: /* @__PURE__ */ jsxs6(Text8, { color: "cyan", bold: isFocused, inverse: isFocused, children: [
183
+ isChecked ? "[x]" : "[ ]",
184
+ " ",
185
+ opt.label
186
+ ] }) }, opt.value);
187
+ }),
188
+ /* @__PURE__ */ jsx8(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "(Space to toggle, Enter to confirm)" }) })
189
+ ] });
190
+ });
191
+
192
+ // src/components/confirm-prompt.tsx
193
+ import { Box as Box6, Text as Text9 } from "ink";
194
+ import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
163
195
  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)" })
196
+ return /* @__PURE__ */ jsxs7(Box6, { gap: 2, paddingLeft: 2, children: [
197
+ /* @__PURE__ */ jsx9(Text9, { color: "cyan", bold: value, inverse: value, children: "Yes" }),
198
+ /* @__PURE__ */ jsx9(Text9, { color: "cyan", bold: !value, inverse: !value, children: "No" }),
199
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "(\u2190/\u2192 to toggle, Enter to confirm)" })
168
200
  ] });
169
201
  }
170
202
 
@@ -601,7 +633,8 @@ function addApplicationToManifest(manifest, app) {
601
633
  var CREATE_APPLICATION_COMMAND_NAME = "create-application";
602
634
  var createApplicationCommand = {
603
635
  name: CREATE_APPLICATION_COMMAND_NAME,
604
- description: "Create an application in a platform"
636
+ description: "Create an application in a platform",
637
+ hidden: (ctx) => !ctx.platformInitialized
605
638
  };
606
639
  async function createApplication(params, logger) {
607
640
  const {
@@ -978,7 +1011,8 @@ function getAllProviders() {
978
1011
  var CONFIGURE_IDP_COMMAND_NAME = "configure-idp";
979
1012
  var configureIdpCommand = {
980
1013
  name: CONFIGURE_IDP_COMMAND_NAME,
981
- description: "Configure an Identity Provider (IDP) in the gateway"
1014
+ description: "Configure an Identity Provider (IDP) in the gateway",
1015
+ hidden: (ctx) => !ctx.platformInitialized
982
1016
  };
983
1017
  async function configureIdp(params, logger) {
984
1018
  const layout = await findPlatformLayout();
@@ -1109,7 +1143,8 @@ async function appendServiceToDockerCompose(dockerComposePath, serviceName, plat
1109
1143
  var CREATE_SERVICE_MODULE_COMMAND_NAME = "create-service-module";
1110
1144
  var createServiceModuleCommand = {
1111
1145
  name: CREATE_SERVICE_MODULE_COMMAND_NAME,
1112
- description: "Add a new service module to an existing application"
1146
+ description: "Add a new service module to an existing application",
1147
+ hidden: (ctx) => !ctx.platformInitialized
1113
1148
  };
1114
1149
  async function createServiceModule(params, logger) {
1115
1150
  const { applicationName, serviceName, serviceDisplayName, serviceNameSuffix } = params;
@@ -1203,7 +1238,8 @@ async function appendUiToDockerCompose(dockerComposePath, platformName, applicat
1203
1238
  var CREATE_UI_MODULE_COMMAND_NAME = "create-ui-module";
1204
1239
  var createUiModuleCommand = {
1205
1240
  name: CREATE_UI_MODULE_COMMAND_NAME,
1206
- description: "Add a UI module to an existing application"
1241
+ description: "Add a UI module to an existing application",
1242
+ hidden: (ctx) => !ctx.platformInitialized
1207
1243
  };
1208
1244
  async function createUiModule(params, logger) {
1209
1245
  const { applicationName, applicationDisplayName, uiModuleSuffix } = params;
@@ -1523,498 +1559,239 @@ var statusCommand = {
1523
1559
  description: "Show platform status and health checks"
1524
1560
  };
1525
1561
 
1526
- // src/commands/registry.ts
1527
- var CommandRegistry = class {
1528
- commands = /* @__PURE__ */ new Map();
1529
- register(command) {
1530
- this.commands.set(command.name, command);
1531
- }
1532
- get(name) {
1533
- return this.commands.get(name);
1534
- }
1535
- getAll() {
1536
- return Array.from(this.commands.values());
1537
- }
1538
- search(query) {
1539
- const all = this.getAll();
1540
- if (!query) return all;
1541
- const fzf = new Fzf(all, {
1542
- selector: (cmd) => `${cmd.name} ${cmd.description}`
1562
+ // src/commands/local-scripts/docker-compose-orchestrator.ts
1563
+ import { spawn as spawn2 } from "child_process";
1564
+ import { access as access6 } from "fs/promises";
1565
+ import { join as join23 } from "path";
1566
+ function runDockerCompose(args2, logger, rootDir, signal) {
1567
+ return new Promise((resolvePromise) => {
1568
+ const child = spawn2("docker", ["compose", ...args2], {
1569
+ shell: false,
1570
+ stdio: ["ignore", "pipe", "pipe"],
1571
+ cwd: rootDir
1543
1572
  });
1544
- return fzf.find(query).map((result) => result.item);
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}`
1573
+ const onAbort = () => {
1574
+ child.kill("SIGTERM");
1575
+ };
1576
+ if (signal) {
1577
+ if (signal.aborted) {
1578
+ child.kill("SIGTERM");
1579
+ resolvePromise();
1580
+ return;
1581
+ }
1582
+ signal.addEventListener("abort", onAbort, { once: true });
1583
+ }
1584
+ child.stdout.on("data", (data) => {
1585
+ for (const line of data.toString().split("\n")) {
1586
+ if (line.trim()) logger.log(line);
1587
+ }
1551
1588
  });
1552
- return fzf.find(query).map((result) => result.item);
1589
+ child.stderr.on("data", (data) => {
1590
+ for (const line of data.toString().split("\n")) {
1591
+ if (line.trim()) logger.log(line);
1592
+ }
1593
+ });
1594
+ child.on("close", (code, sig) => {
1595
+ signal?.removeEventListener("abort", onAbort);
1596
+ if (sig === "SIGTERM" || signal?.aborted) {
1597
+ logger.log("Command cancelled.");
1598
+ } else if (code !== 0) {
1599
+ logger.log(`docker compose exited with code ${code}.`);
1600
+ }
1601
+ resolvePromise();
1602
+ });
1603
+ child.on("error", (err) => {
1604
+ signal?.removeEventListener("abort", onAbort);
1605
+ logger.log(`Failed to run docker compose: ${err.message}`);
1606
+ resolvePromise();
1607
+ });
1608
+ });
1609
+ }
1610
+ function captureDockerCompose(args2, rootDir) {
1611
+ return new Promise((resolvePromise, reject) => {
1612
+ const child = spawn2("docker", ["compose", ...args2], {
1613
+ shell: false,
1614
+ stdio: ["ignore", "pipe", "pipe"],
1615
+ cwd: rootDir
1616
+ });
1617
+ let stdout = "";
1618
+ child.stdout.on("data", (data) => {
1619
+ stdout += data.toString();
1620
+ });
1621
+ child.on("close", (code) => {
1622
+ if (code !== 0) reject(new Error(`docker compose config --services exited with code ${code}`));
1623
+ else resolvePromise(stdout);
1624
+ });
1625
+ child.on("error", (err) => reject(err));
1626
+ });
1627
+ }
1628
+ async function getAppComposePaths(localDir, platformName, manifest) {
1629
+ const results = [];
1630
+ for (const app of manifest.applications) {
1631
+ const prefixedPath = join23(localDir, `${platformName}-${app.name}-docker-compose.yml`);
1632
+ const unprefixedPath = join23(localDir, `${app.name}-docker-compose.yml`);
1633
+ let resolved = null;
1634
+ try {
1635
+ await access6(prefixedPath);
1636
+ resolved = prefixedPath;
1637
+ } catch {
1638
+ try {
1639
+ await access6(unprefixedPath);
1640
+ resolved = unprefixedPath;
1641
+ } catch {
1642
+ }
1643
+ }
1644
+ if (resolved) {
1645
+ results.push({ composePath: resolved, appName: app.name });
1646
+ }
1553
1647
  }
1554
- };
1555
- var registry = new CommandRegistry();
1556
- registry.register(createApplicationCommand);
1557
- registry.register(initCommand);
1558
- registry.register(configureIdpCommand);
1559
- registry.register(createServiceModuleCommand);
1560
- registry.register(createUiModuleCommand);
1561
- registry.register(statusCommand);
1562
-
1563
- // src/app-state.ts
1564
- var APP_STATE = {
1565
- IDLE: "idle",
1566
- PALETTE: "palette",
1567
- EXECUTING: "executing",
1568
- PROMPTING: "prompting"
1569
- };
1570
-
1571
- // src/hooks/use-command-runner.ts
1572
- import { useState as useState2, useCallback, useRef } from "react";
1573
-
1574
- // src/services/create-application.service.ts
1575
- async function createApplicationService(params, logger) {
1576
- await createApplication(params, logger);
1648
+ return results;
1577
1649
  }
1578
-
1579
- // src/controllers/ui/create-application.ui-controller.ts
1580
- async function createApplicationUiController(ctx) {
1581
- if (!await isPlatformInitialized()) {
1582
- ctx.log("Error: Cannot create an application \u2014 no platform initialized in this directory.");
1583
- return;
1650
+ async function buildFullComposeArgs(layout, manifest, logger) {
1651
+ const { coreDirName, localDir } = layout;
1652
+ const platformName = manifest.product.name;
1653
+ const prefixedCoreCompose = join23(localDir, `${coreDirName}-docker-compose.yml`);
1654
+ const unprefixedCoreCompose = join23(localDir, "core-docker-compose.yml");
1655
+ let coreComposePath;
1656
+ try {
1657
+ await access6(prefixedCoreCompose);
1658
+ coreComposePath = prefixedCoreCompose;
1659
+ } catch {
1660
+ coreComposePath = unprefixedCoreCompose;
1584
1661
  }
1585
- const applicationName = await ctx.prompt("Application name:");
1586
- if (!/^[a-z0-9-]+$/.test(applicationName)) {
1587
- ctx.log(`Error: Application name "${applicationName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
1588
- return;
1662
+ const fileArgs = [
1663
+ "-f",
1664
+ join23(localDir, "platform-docker-compose.yml"),
1665
+ "-f",
1666
+ coreComposePath
1667
+ ];
1668
+ const appEntries = await getAppComposePaths(localDir, platformName, manifest);
1669
+ for (const { composePath } of appEntries) {
1670
+ fileArgs.push("-f", composePath);
1589
1671
  }
1590
- const applicationDisplayName = await ctx.prompt("Application display name:");
1591
- const applicationDescription = await ctx.prompt("Application description:");
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?");
1594
- await createApplicationService(
1595
- {
1596
- applicationName,
1597
- applicationDisplayName,
1598
- applicationDescription,
1599
- hasUserInterface,
1600
- hasBackendService
1601
- },
1602
- ctx
1603
- );
1672
+ for (const app of manifest.applications) {
1673
+ if (!appEntries.find((e) => e.appName === app.name)) {
1674
+ logger.log(`Warning: No docker-compose found for application "${app.name}" in ${coreDirName}/local/ \u2014 skipping.`);
1675
+ }
1676
+ }
1677
+ return fileArgs;
1604
1678
  }
1605
-
1606
- // src/services/init.service.ts
1607
- async function initService(params, logger) {
1608
- await init(params, logger);
1679
+ async function buildSelectedComposeFiles(layout, selectedManifest, includeCore) {
1680
+ const { coreDirName, localDir } = layout;
1681
+ const platformName = selectedManifest.product.name;
1682
+ const files = [];
1683
+ if (includeCore) {
1684
+ const prefixedCoreCompose = join23(localDir, `${coreDirName}-docker-compose.yml`);
1685
+ const unprefixedCoreCompose = join23(localDir, "core-docker-compose.yml");
1686
+ let coreComposePath;
1687
+ try {
1688
+ await access6(prefixedCoreCompose);
1689
+ coreComposePath = prefixedCoreCompose;
1690
+ } catch {
1691
+ coreComposePath = unprefixedCoreCompose;
1692
+ }
1693
+ files.push(join23(localDir, "platform-docker-compose.yml"), coreComposePath);
1694
+ }
1695
+ const appEntries = await getAppComposePaths(localDir, platformName, selectedManifest);
1696
+ for (const { composePath } of appEntries) {
1697
+ files.push(composePath);
1698
+ }
1699
+ return files;
1609
1700
  }
1610
-
1611
- // src/controllers/ui/init.ui-controller.ts
1612
- async function initUiController(ctx) {
1613
- if (await isPlatformInitialized()) {
1614
- ctx.log("Error: Cannot initialize a new platform \u2014 a platform is already initialized in this directory.");
1615
- return;
1701
+ function extractFilePaths(fileArgs) {
1702
+ const paths = [];
1703
+ for (let i = 0; i < fileArgs.length; i++) {
1704
+ if (fileArgs[i] === "-f" && i + 1 < fileArgs.length) {
1705
+ paths.push(fileArgs[i + 1]);
1706
+ i++;
1707
+ }
1616
1708
  }
1617
- const organizationName = await ctx.prompt("Organization name:");
1618
- const platformName = await ctx.prompt("Platform name:");
1619
- const platformDisplayName = await ctx.prompt("Platform display name:");
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.`);
1709
+ return paths;
1710
+ }
1711
+ async function getServicesFromComposeFiles(selectedFiles, allFiles, rootDir) {
1712
+ const selectedSet = new Set(selectedFiles);
1713
+ const contextFiles = allFiles.filter((f) => !selectedSet.has(f));
1714
+ if (contextFiles.length === 0) {
1715
+ const fileArgs = selectedFiles.flatMap((f) => ["-f", f]);
1716
+ const output = await captureDockerCompose([...fileArgs, "config", "--services"], rootDir);
1717
+ return output.split("\n").map((s) => s.trim()).filter(Boolean);
1718
+ }
1719
+ const allFileArgs = allFiles.flatMap((f) => ["-f", f]);
1720
+ const allOutput = await captureDockerCompose([...allFileArgs, "config", "--services"], rootDir);
1721
+ const allServices = new Set(allOutput.split("\n").map((s) => s.trim()).filter(Boolean));
1722
+ const contextFileArgs = contextFiles.flatMap((f) => ["-f", f]);
1723
+ const contextOutput = await captureDockerCompose([...contextFileArgs, "config", "--services"], rootDir);
1724
+ const contextServices = new Set(contextOutput.split("\n").map((s) => s.trim()).filter(Boolean));
1725
+ return [...allServices].filter((s) => !contextServices.has(s));
1726
+ }
1727
+ async function startEnvironment(layout, manifest, logger, signal, includeCore = true, fullManifest) {
1728
+ const { rootDir, localDir } = layout;
1729
+ const platformName = manifest.product.name;
1730
+ const envFile = join23(localDir, ".env");
1731
+ const isSelective = fullManifest !== void 0;
1732
+ const fileArgs = await buildFullComposeArgs(layout, isSelective ? fullManifest : manifest, logger);
1733
+ const projectArgs = ["-p", `${platformName}-platform`, "--project-directory", localDir, "--env-file", envFile];
1734
+ if (isSelective) {
1735
+ const selectedFiles = await buildSelectedComposeFiles(layout, manifest, includeCore);
1736
+ const allFiles = extractFilePaths(fileArgs);
1737
+ const services = await getServicesFromComposeFiles(selectedFiles, allFiles, rootDir);
1738
+ if (services.length === 0) {
1739
+ logger.log("No services found for selected targets. Nothing to start.");
1632
1740
  return;
1633
1741
  }
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.`);
1742
+ logger.log(`Starting: ${services.join(", ")}...`);
1743
+ await runDockerCompose([...projectArgs, ...fileArgs, "up", "-d", "--build", ...services], logger, rootDir, signal);
1744
+ } else {
1745
+ logger.log("Starting environment...");
1746
+ await runDockerCompose([...projectArgs, ...fileArgs, "up", "-d", "--build", "--remove-orphans"], logger, rootDir, signal);
1747
+ }
1748
+ }
1749
+ async function stopEnvironment(layout, manifest, logger, signal, includeCore = true, fullManifest) {
1750
+ const { rootDir, localDir } = layout;
1751
+ const platformName = manifest.product.name;
1752
+ const envFile = join23(localDir, ".env");
1753
+ const isSelective = fullManifest !== void 0;
1754
+ const fileArgs = await buildFullComposeArgs(layout, isSelective ? fullManifest : manifest, logger);
1755
+ const projectArgs = ["-p", `${platformName}-platform`, "--project-directory", localDir, "--env-file", envFile];
1756
+ if (isSelective) {
1757
+ const selectedFiles = await buildSelectedComposeFiles(layout, manifest, includeCore);
1758
+ const allFiles = extractFilePaths(fileArgs);
1759
+ const services = await getServicesFromComposeFiles(selectedFiles, allFiles, rootDir);
1760
+ if (services.length === 0) {
1761
+ logger.log("No services found for selected targets. Nothing to stop.");
1638
1762
  return;
1639
1763
  }
1640
- customizationUiSuffix = cuiSuffix;
1764
+ logger.log(`Stopping: ${services.join(", ")}...`);
1765
+ await runDockerCompose([...projectArgs, ...fileArgs, "stop", ...services], logger, rootDir, signal);
1766
+ } else {
1767
+ logger.log("Stopping environment...");
1768
+ await runDockerCompose([...projectArgs, ...fileArgs, "down"], logger, rootDir, signal);
1641
1769
  }
1642
- await initService(
1643
- { organizationName, platformName, platformDisplayName, bootstrapServiceSuffix, customizationUiSuffix },
1644
- ctx
1645
- );
1646
1770
  }
1647
-
1648
- // src/services/configure-idp.service.ts
1649
- async function configureIdpService(params, logger) {
1650
- await configureIdp(params, logger);
1651
- }
1652
-
1653
- // src/controllers/ui/configure-idp.ui-controller.ts
1654
- async function configureIdpUiController(ctx) {
1655
- if (!await isPlatformInitialized()) {
1656
- ctx.log("Error: Cannot configure an IDP \u2014 no platform initialized in this directory.");
1657
- return;
1658
- }
1659
- const providers = getAllProviders();
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);
1665
- const name = await ctx.prompt("Provider name:");
1666
- const issuer = await ctx.prompt("Issuer URL:");
1667
- const clientId = await ctx.prompt("Client ID:");
1668
- const clientSecret = await ctx.prompt("Client Secret:");
1669
- const extras = {};
1670
- for (const field of provider.fields) {
1671
- extras[field.key] = await ctx.prompt(field.prompt);
1672
- }
1673
- await configureIdpService(
1674
- { providerType: provider.type, name, issuer, clientId, clientSecret, extras },
1675
- ctx
1676
- );
1677
- }
1678
-
1679
- // src/services/create-service-module.service.ts
1680
- async function createServiceModuleService(params, logger) {
1681
- await createServiceModule(params, logger);
1682
- }
1683
-
1684
- // src/controllers/ui/create-service-module.ui-controller.ts
1685
- async function createServiceModuleUiController(ctx) {
1686
- if (!await isPlatformInitialized()) {
1687
- ctx.log("Error: Cannot create a service module \u2014 no platform initialized in this directory.");
1688
- return;
1689
- }
1690
- const layout = await findPlatformLayout();
1691
- if (!layout) {
1692
- ctx.log("Error: Cannot create a service module \u2014 no platform initialized in this directory.");
1693
- return;
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
- );
1711
- const serviceName = await ctx.prompt("Service name:");
1712
- if (!/^[a-z0-9-]+$/.test(serviceName)) {
1713
- ctx.log(`Error: Service name "${serviceName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
1714
- return;
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;
1719
- const serviceDisplayName = await ctx.prompt("Service display name:");
1720
- await createServiceModuleService(
1721
- { applicationName, serviceName, serviceDisplayName, serviceNameSuffix },
1722
- ctx
1723
- );
1724
- }
1725
-
1726
- // src/services/create-ui-module.service.ts
1727
- async function createUiModuleService(params, logger) {
1728
- await createUiModule(params, logger);
1729
- }
1730
-
1731
- // src/controllers/ui/create-ui-module.ui-controller.ts
1732
- async function createUiModuleUiController(ctx) {
1733
- if (!await isPlatformInitialized()) {
1734
- ctx.log("Error: Cannot create a UI module \u2014 no platform initialized in this directory.");
1735
- return;
1736
- }
1737
- const layout = await findPlatformLayout();
1738
- if (!layout) {
1739
- ctx.log("Error: Cannot create a UI module \u2014 no platform initialized in this directory.");
1740
- return;
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;
1761
- const applicationDisplayName = await ctx.prompt("Application display name:");
1762
- await createUiModuleService(
1763
- { applicationName, applicationDisplayName, uiModuleSuffix },
1764
- ctx
1765
- );
1766
- }
1767
-
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
- };
1789
-
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/)")}`);
1822
- }
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;
1846
- }
1847
-
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
1858
- });
1859
- const onAbort = () => {
1860
- child.kill("SIGTERM");
1861
- };
1862
- if (signal) {
1863
- if (signal.aborted) {
1864
- child.kill("SIGTERM");
1865
- resolvePromise();
1866
- return;
1867
- }
1868
- signal.addEventListener("abort", onAbort, { once: true });
1869
- }
1870
- child.stdout.on("data", (data) => {
1871
- for (const line of data.toString().split("\n")) {
1872
- if (line.trim()) logger.log(line);
1873
- }
1874
- });
1875
- child.stderr.on("data", (data) => {
1876
- for (const line of data.toString().split("\n")) {
1877
- if (line.trim()) logger.log(line);
1878
- }
1879
- });
1880
- child.on("close", (code, sig) => {
1881
- signal?.removeEventListener("abort", onAbort);
1882
- if (sig === "SIGTERM" || signal?.aborted) {
1883
- logger.log("Command cancelled.");
1884
- } else if (code !== 0) {
1885
- logger.log(`docker compose exited with code ${code}.`);
1886
- }
1887
- resolvePromise();
1888
- });
1889
- child.on("error", (err) => {
1890
- signal?.removeEventListener("abort", onAbort);
1891
- logger.log(`Failed to run docker compose: ${err.message}`);
1892
- resolvePromise();
1893
- });
1894
- });
1895
- }
1896
- async function getAppComposePaths(localDir, platformName, manifest) {
1897
- const results = [];
1898
- for (const app of manifest.applications) {
1899
- const prefixedPath = join23(localDir, `${platformName}-${app.name}-docker-compose.yml`);
1900
- const unprefixedPath = join23(localDir, `${app.name}-docker-compose.yml`);
1901
- let resolved = null;
1902
- try {
1903
- await access6(prefixedPath);
1904
- resolved = prefixedPath;
1905
- } catch {
1906
- try {
1907
- await access6(unprefixedPath);
1908
- resolved = unprefixedPath;
1909
- } catch {
1910
- }
1911
- }
1912
- if (resolved) {
1913
- results.push({ composePath: resolved, appName: app.name });
1914
- }
1915
- }
1916
- return results;
1917
- }
1918
- async function buildAllComposeArgs(layout, manifest, logger) {
1919
- const { rootDir, coreDirName, localDir } = layout;
1920
- const platformName = manifest.product.name;
1921
- const prefixedCoreCompose = join23(localDir, `${coreDirName}-docker-compose.yml`);
1922
- const unprefixedCoreCompose = join23(localDir, "core-docker-compose.yml");
1923
- let coreComposePath;
1924
- try {
1925
- await access6(prefixedCoreCompose);
1926
- coreComposePath = prefixedCoreCompose;
1927
- } catch {
1928
- coreComposePath = unprefixedCoreCompose;
1929
- }
1930
- const fileArgs = [
1931
- "-f",
1932
- join23(localDir, "platform-docker-compose.yml"),
1933
- "-f",
1934
- coreComposePath
1935
- ];
1936
- const appEntries = await getAppComposePaths(localDir, platformName, manifest);
1937
- for (const { composePath } of appEntries) {
1938
- fileArgs.push("-f", composePath);
1939
- }
1940
- for (const app of manifest.applications) {
1941
- if (!appEntries.find((e) => e.appName === app.name)) {
1942
- logger.log(`Warning: No docker-compose found for application "${app.name}" in ${coreDirName}/local/ \u2014 skipping.`);
1943
- }
1944
- }
1945
- return fileArgs;
1946
- }
1947
- async function startEnvironment(layout, manifest, logger, signal) {
1948
- const { rootDir, localDir } = layout;
1949
- const platformName = manifest.product.name;
1950
- const envFile = join23(localDir, ".env");
1951
- const fileArgs = await buildAllComposeArgs(layout, manifest, logger);
1952
- logger.log("Starting environment...");
1953
- await runDockerCompose(
1954
- [
1955
- "-p",
1956
- `${platformName}-platform`,
1957
- "--project-directory",
1958
- localDir,
1959
- "--env-file",
1960
- envFile,
1961
- ...fileArgs,
1962
- "up",
1963
- "-d",
1964
- "--build",
1965
- "--remove-orphans"
1966
- ],
1967
- logger,
1968
- rootDir,
1969
- signal
1970
- );
1971
- }
1972
- async function stopEnvironment(layout, manifest, logger, signal) {
1973
- const { rootDir, localDir } = layout;
1974
- const platformName = manifest.product.name;
1975
- const envFile = join23(localDir, ".env");
1976
- const fileArgs = await buildAllComposeArgs(layout, manifest, logger);
1977
- logger.log("Stopping environment...");
1978
- await runDockerCompose(
1979
- [
1980
- "-p",
1981
- `${platformName}-platform`,
1982
- "--project-directory",
1983
- localDir,
1984
- "--env-file",
1985
- envFile,
1986
- ...fileArgs,
1987
- "down"
1988
- ],
1989
- logger,
1990
- rootDir,
1991
- signal
1992
- );
1993
- }
1994
- async function destroyEnvironment(layout, manifest, logger, signal) {
1995
- const { rootDir, localDir } = layout;
1996
- const platformName = manifest.product.name;
1997
- const envFile = join23(localDir, ".env");
1998
- const fileArgs = await buildAllComposeArgs(layout, manifest, logger);
1999
- logger.log("Destroying environment...");
2000
- await runDockerCompose(
2001
- [
2002
- "-p",
2003
- `${platformName}-platform`,
2004
- "--project-directory",
2005
- localDir,
2006
- "--env-file",
2007
- envFile,
2008
- ...fileArgs,
2009
- "down",
2010
- "-v",
2011
- "--rmi",
2012
- "all"
2013
- ],
2014
- logger,
2015
- rootDir,
2016
- signal
2017
- );
1771
+ async function destroyEnvironment(layout, manifest, logger, signal, includeCore = true, fullManifest) {
1772
+ const { rootDir, localDir } = layout;
1773
+ const platformName = manifest.product.name;
1774
+ const envFile = join23(localDir, ".env");
1775
+ const isSelective = fullManifest !== void 0;
1776
+ const fileArgs = await buildFullComposeArgs(layout, isSelective ? fullManifest : manifest, logger);
1777
+ const projectArgs = ["-p", `${platformName}-platform`, "--project-directory", localDir, "--env-file", envFile];
1778
+ if (isSelective) {
1779
+ const selectedFiles = await buildSelectedComposeFiles(layout, manifest, includeCore);
1780
+ const allFiles = extractFilePaths(fileArgs);
1781
+ const services = await getServicesFromComposeFiles(selectedFiles, allFiles, rootDir);
1782
+ if (services.length === 0) {
1783
+ logger.log("No services found for selected targets. Nothing to destroy.");
1784
+ return;
1785
+ }
1786
+ logger.log(`Destroying: ${services.join(", ")}...`);
1787
+ await runDockerCompose([...projectArgs, ...fileArgs, "stop", ...services], logger, rootDir, signal);
1788
+ if (!signal?.aborted) {
1789
+ await runDockerCompose([...projectArgs, ...fileArgs, "rm", "-f", ...services], logger, rootDir, signal);
1790
+ }
1791
+ } else {
1792
+ logger.log("Destroying environment...");
1793
+ await runDockerCompose([...projectArgs, ...fileArgs, "down", "-v", "--rmi", "all"], logger, rootDir, signal);
1794
+ }
2018
1795
  }
2019
1796
 
2020
1797
  // src/commands/local-scripts/npm-orchestrator.ts
@@ -2072,97 +1849,476 @@ async function dirExists(dirPath) {
2072
1849
  } catch {
2073
1850
  return false;
2074
1851
  }
2075
- }
2076
- async function installDependencies(layout, manifest, logger, signal) {
2077
- const { coreDir, coreDirName } = layout;
2078
- const appDirs = [];
2079
- for (const app of manifest.applications) {
2080
- const appDir = resolve5(join24(coreDir), app.localPath);
2081
- if (!await dirExists(appDir)) {
2082
- logger.log(`Warning: Application directory "${app.name}" not found at ${appDir} \u2014 skipping install.`);
2083
- continue;
1852
+ }
1853
+ async function installDependencies(layout, manifest, logger, signal, includeCore = true) {
1854
+ const { coreDir, coreDirName } = layout;
1855
+ const appDirs = [];
1856
+ for (const app of manifest.applications) {
1857
+ const appDir = resolve5(join24(coreDir), app.localPath);
1858
+ if (!await dirExists(appDir)) {
1859
+ logger.log(`Warning: Application directory "${app.name}" not found at ${appDir} \u2014 skipping install.`);
1860
+ continue;
1861
+ }
1862
+ appDirs.push({ name: app.name, dir: appDir });
1863
+ }
1864
+ const targets = includeCore ? [{ name: coreDirName, dir: coreDir }, ...appDirs] : appDirs;
1865
+ logger.log(`Installing dependencies in parallel: ${targets.map((t) => t.name).join(", ")}...`);
1866
+ await Promise.all(
1867
+ targets.map(
1868
+ ({ name, dir }) => runCommand("npm", ["install"], dir, logger, signal).then(() => {
1869
+ logger.log(`\u2713 ${name} install done`);
1870
+ })
1871
+ )
1872
+ );
1873
+ }
1874
+ async function buildAll(layout, manifest, logger, signal, includeCore = true) {
1875
+ const { coreDir, coreDirName } = layout;
1876
+ if (includeCore) {
1877
+ logger.log(`Building ${coreDirName}/...`);
1878
+ await runCommand("npx", ["turbo", "build"], coreDir, logger, signal);
1879
+ if (signal?.aborted) return;
1880
+ }
1881
+ const appDirs = [];
1882
+ for (const app of manifest.applications) {
1883
+ const appDir = resolve5(join24(coreDir), app.localPath);
1884
+ if (!await dirExists(appDir)) {
1885
+ logger.log(`Warning: Application directory "${app.name}" not found at ${appDir} \u2014 skipping build.`);
1886
+ continue;
1887
+ }
1888
+ appDirs.push({ name: app.name, dir: appDir });
1889
+ }
1890
+ if (appDirs.length === 0) return;
1891
+ logger.log(`Building apps in parallel: ${appDirs.map((a) => a.name).join(", ")}...`);
1892
+ await Promise.all(
1893
+ appDirs.map(
1894
+ ({ name, dir }) => runCommand("npm", ["run", "build"], dir, logger, signal).then(() => {
1895
+ logger.log(`\u2713 ${name} build done`);
1896
+ })
1897
+ )
1898
+ );
1899
+ }
1900
+
1901
+ // src/commands/local-scripts/local-script.command.ts
1902
+ var INSTALL_COMMAND_NAME = "install";
1903
+ var BUILD_COMMAND_NAME = "build";
1904
+ var START_COMMAND_NAME = "start";
1905
+ var STOP_COMMAND_NAME = "stop";
1906
+ var DESTROY_COMMAND_NAME = "destroy";
1907
+ var CORE_APP_NAME = "core";
1908
+ var installCommand = {
1909
+ name: INSTALL_COMMAND_NAME,
1910
+ description: "Install dependencies in the local dev environment",
1911
+ hidden: (ctx) => !ctx.platformInitialized
1912
+ };
1913
+ var buildCommand = {
1914
+ name: BUILD_COMMAND_NAME,
1915
+ description: "Build the local dev environment",
1916
+ hidden: (ctx) => !ctx.platformInitialized
1917
+ };
1918
+ var startCommand = {
1919
+ name: START_COMMAND_NAME,
1920
+ description: "Start the local dev environment",
1921
+ hidden: (ctx) => !ctx.platformInitialized
1922
+ };
1923
+ var stopCommand = {
1924
+ name: STOP_COMMAND_NAME,
1925
+ description: "Stop the local dev environment",
1926
+ hidden: (ctx) => !ctx.platformInitialized
1927
+ };
1928
+ var destroyCommand = {
1929
+ name: DESTROY_COMMAND_NAME,
1930
+ description: "Destroy the local dev environment",
1931
+ hidden: (ctx) => !ctx.platformInitialized
1932
+ };
1933
+ var localScriptCommands = [
1934
+ installCommand,
1935
+ buildCommand,
1936
+ startCommand,
1937
+ stopCommand,
1938
+ destroyCommand
1939
+ ];
1940
+ async function runLocalScript(scriptName, logger, signal, appNames) {
1941
+ const layout = await findPlatformLayout();
1942
+ if (!layout) {
1943
+ logger.log(`Error: Cannot run "${scriptName}" \u2014 no platform initialized in this directory.`);
1944
+ return;
1945
+ }
1946
+ const { rootDir, coreDirName } = layout;
1947
+ let manifest;
1948
+ try {
1949
+ manifest = await readManifest(rootDir, coreDirName);
1950
+ } catch (err) {
1951
+ logger.log(`Error: Could not read product manifest \u2014 ${formatError(err)}`);
1952
+ return;
1953
+ }
1954
+ let includeCore = true;
1955
+ const fullManifest = manifest;
1956
+ let isSelective = false;
1957
+ if (appNames && appNames.length > 0) {
1958
+ isSelective = true;
1959
+ includeCore = appNames.includes(CORE_APP_NAME);
1960
+ const appNamesWithoutCore = appNames.filter((n) => n !== CORE_APP_NAME);
1961
+ const nameSet = new Set(appNamesWithoutCore);
1962
+ const unknown = appNamesWithoutCore.filter((n) => !manifest.applications.some((a) => a.name === n));
1963
+ if (unknown.length > 0) {
1964
+ logger.log(`Warning: Unknown application(s): ${unknown.join(", ")} \u2014 ignoring.`);
1965
+ }
1966
+ const filtered = manifest.applications.filter((a) => nameSet.has(a.name));
1967
+ if (!includeCore && filtered.length === 0) {
1968
+ logger.log("No matching applications found. Nothing to do.");
1969
+ return;
1970
+ }
1971
+ manifest = { ...manifest, applications: filtered };
1972
+ }
1973
+ switch (scriptName) {
1974
+ case START_COMMAND_NAME:
1975
+ await startEnvironment(layout, manifest, logger, signal, includeCore, isSelective ? fullManifest : void 0);
1976
+ break;
1977
+ case STOP_COMMAND_NAME:
1978
+ await stopEnvironment(layout, manifest, logger, signal, includeCore, isSelective ? fullManifest : void 0);
1979
+ break;
1980
+ case DESTROY_COMMAND_NAME:
1981
+ await destroyEnvironment(layout, manifest, logger, signal, includeCore, isSelective ? fullManifest : void 0);
1982
+ break;
1983
+ case INSTALL_COMMAND_NAME:
1984
+ await installDependencies(layout, manifest, logger, signal, includeCore);
1985
+ break;
1986
+ case BUILD_COMMAND_NAME:
1987
+ await buildAll(layout, manifest, logger, signal, includeCore);
1988
+ break;
1989
+ default:
1990
+ logger.log(`Error: Unknown script "${scriptName}".`);
1991
+ }
1992
+ }
1993
+
1994
+ // src/commands/registry.ts
1995
+ var CommandRegistry = class {
1996
+ commands = /* @__PURE__ */ new Map();
1997
+ register(command) {
1998
+ this.commands.set(command.name, command);
1999
+ }
2000
+ get(name) {
2001
+ return this.commands.get(name);
2002
+ }
2003
+ getAll() {
2004
+ return Array.from(this.commands.values());
2005
+ }
2006
+ search(query) {
2007
+ const all = this.getAll();
2008
+ if (!query) return all;
2009
+ const fzf = new Fzf(all, {
2010
+ selector: (cmd) => `${cmd.name} ${cmd.description}`
2011
+ });
2012
+ return fzf.find(query).map((result) => result.item);
2013
+ }
2014
+ searchVisible(query, visibilityCtx) {
2015
+ const visible = this.getAll().filter((cmd) => !cmd.hidden?.(visibilityCtx));
2016
+ if (!query) return visible;
2017
+ const fzf = new Fzf(visible, {
2018
+ selector: (cmd) => `${cmd.name} ${cmd.description}`
2019
+ });
2020
+ return fzf.find(query).map((result) => result.item);
2021
+ }
2022
+ };
2023
+ var registry = new CommandRegistry();
2024
+ registry.register(initCommand);
2025
+ registry.register(configureIdpCommand);
2026
+ registry.register(createApplicationCommand);
2027
+ registry.register(createServiceModuleCommand);
2028
+ registry.register(createUiModuleCommand);
2029
+ for (const cmd of localScriptCommands) {
2030
+ registry.register(cmd);
2031
+ }
2032
+ registry.register(statusCommand);
2033
+
2034
+ // src/app-state.ts
2035
+ var APP_STATE = {
2036
+ IDLE: "idle",
2037
+ PALETTE: "palette",
2038
+ EXECUTING: "executing",
2039
+ PROMPTING: "prompting"
2040
+ };
2041
+
2042
+ // src/hooks/use-command-runner.ts
2043
+ import { useState as useState2, useCallback, useRef } from "react";
2044
+
2045
+ // src/services/create-application.service.ts
2046
+ async function createApplicationService(params, logger) {
2047
+ await createApplication(params, logger);
2048
+ }
2049
+
2050
+ // src/controllers/ui/create-application.ui-controller.ts
2051
+ async function createApplicationUiController(ctx) {
2052
+ if (!await isPlatformInitialized()) {
2053
+ ctx.log("Error: Cannot create an application \u2014 no platform initialized in this directory.");
2054
+ return;
2055
+ }
2056
+ const applicationName = await ctx.prompt("Application name:");
2057
+ if (!/^[a-z0-9-]+$/.test(applicationName)) {
2058
+ ctx.log(`Error: Application name "${applicationName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
2059
+ return;
2060
+ }
2061
+ const applicationDisplayName = await ctx.prompt("Application display name:");
2062
+ const applicationDescription = await ctx.prompt("Application description:");
2063
+ const hasUserInterface = await ctx.confirm("Does this application have a user interface?");
2064
+ const hasBackendService = await ctx.confirm("Does this application have a backend service?");
2065
+ await createApplicationService(
2066
+ {
2067
+ applicationName,
2068
+ applicationDisplayName,
2069
+ applicationDescription,
2070
+ hasUserInterface,
2071
+ hasBackendService
2072
+ },
2073
+ ctx
2074
+ );
2075
+ }
2076
+
2077
+ // src/services/init.service.ts
2078
+ async function initService(params, logger) {
2079
+ await init(params, logger);
2080
+ }
2081
+
2082
+ // src/controllers/ui/init.ui-controller.ts
2083
+ async function initUiController(ctx) {
2084
+ if (await isPlatformInitialized()) {
2085
+ ctx.log("Error: Cannot initialize a new platform \u2014 a platform is already initialized in this directory.");
2086
+ return;
2087
+ }
2088
+ const organizationName = await ctx.prompt("Organization name:");
2089
+ const platformName = await ctx.prompt("Platform name:");
2090
+ const platformDisplayName = await ctx.prompt("Platform display name:");
2091
+ const defaultBootstrapSuffix = "bootstrap-service";
2092
+ const defaultCustomizationUiSuffix = "customization-ui";
2093
+ ctx.log(`Default artifact names:`);
2094
+ ctx.log(` Bootstrap service: ${platformName}-${defaultBootstrapSuffix}`);
2095
+ ctx.log(` Customization UI: ${platformName}-${defaultCustomizationUiSuffix}`);
2096
+ const customize = await ctx.confirm("Customize artifact names?", false);
2097
+ let bootstrapServiceSuffix = defaultBootstrapSuffix;
2098
+ let customizationUiSuffix = defaultCustomizationUiSuffix;
2099
+ if (customize) {
2100
+ const bsSuffix = await ctx.prompt(`Bootstrap service suffix (${platformName}-...):`, defaultBootstrapSuffix);
2101
+ if (!/^[a-z0-9-]+$/.test(bsSuffix)) {
2102
+ ctx.log(`Error: Suffix "${bsSuffix}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
2103
+ return;
2104
+ }
2105
+ bootstrapServiceSuffix = bsSuffix;
2106
+ const cuiSuffix = await ctx.prompt(`Customization UI suffix (${platformName}-...):`, defaultCustomizationUiSuffix);
2107
+ if (!/^[a-z0-9-]+$/.test(cuiSuffix)) {
2108
+ ctx.log(`Error: Suffix "${cuiSuffix}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
2109
+ return;
2110
+ }
2111
+ customizationUiSuffix = cuiSuffix;
2112
+ }
2113
+ await initService(
2114
+ { organizationName, platformName, platformDisplayName, bootstrapServiceSuffix, customizationUiSuffix },
2115
+ ctx
2116
+ );
2117
+ }
2118
+
2119
+ // src/services/configure-idp.service.ts
2120
+ async function configureIdpService(params, logger) {
2121
+ await configureIdp(params, logger);
2122
+ }
2123
+
2124
+ // src/controllers/ui/configure-idp.ui-controller.ts
2125
+ async function configureIdpUiController(ctx) {
2126
+ if (!await isPlatformInitialized()) {
2127
+ ctx.log("Error: Cannot configure an IDP \u2014 no platform initialized in this directory.");
2128
+ return;
2129
+ }
2130
+ const providers = getAllProviders();
2131
+ const providerType = await ctx.select(
2132
+ "Select IDP type:",
2133
+ providers.map((p) => ({ label: p.displayName, value: p.type }))
2134
+ );
2135
+ const provider = providers.find((p) => p.type === providerType);
2136
+ const name = await ctx.prompt("Provider name:");
2137
+ const issuer = await ctx.prompt("Issuer URL:");
2138
+ const clientId = await ctx.prompt("Client ID:");
2139
+ const clientSecret = await ctx.prompt("Client Secret:");
2140
+ const extras = {};
2141
+ for (const field of provider.fields) {
2142
+ extras[field.key] = await ctx.prompt(field.prompt);
2143
+ }
2144
+ await configureIdpService(
2145
+ { providerType: provider.type, name, issuer, clientId, clientSecret, extras },
2146
+ ctx
2147
+ );
2148
+ }
2149
+
2150
+ // src/services/create-service-module.service.ts
2151
+ async function createServiceModuleService(params, logger) {
2152
+ await createServiceModule(params, logger);
2153
+ }
2154
+
2155
+ // src/controllers/ui/create-service-module.ui-controller.ts
2156
+ async function createServiceModuleUiController(ctx) {
2157
+ if (!await isPlatformInitialized()) {
2158
+ ctx.log("Error: Cannot create a service module \u2014 no platform initialized in this directory.");
2159
+ return;
2160
+ }
2161
+ const layout = await findPlatformLayout();
2162
+ if (!layout) {
2163
+ ctx.log("Error: Cannot create a service module \u2014 no platform initialized in this directory.");
2164
+ return;
2165
+ }
2166
+ let manifest = await readManifest(layout.rootDir, layout.coreDirName);
2167
+ if (manifest.applications.length === 0) {
2168
+ ctx.log("No applications found in this platform.");
2169
+ const createFirst = await ctx.confirm("Would you like to create an application first?");
2170
+ if (!createFirst) return;
2171
+ await createApplicationUiController(ctx);
2172
+ manifest = await readManifest(layout.rootDir, layout.coreDirName);
2173
+ if (manifest.applications.length === 0) {
2174
+ ctx.log("Error: No applications available after creation attempt.");
2175
+ return;
2084
2176
  }
2085
- appDirs.push({ name: app.name, dir: appDir });
2086
2177
  }
2087
- const targets = [{ name: coreDirName, dir: coreDir }, ...appDirs];
2088
- logger.log(`Installing dependencies in parallel: ${targets.map((t) => t.name).join(", ")}...`);
2089
- await Promise.all(
2090
- targets.map(
2091
- ({ name, dir }) => runCommand("npm", ["install"], dir, logger, signal).then(() => {
2092
- logger.log(`\u2713 ${name} install done`);
2093
- })
2094
- )
2178
+ const applicationName = await ctx.select(
2179
+ "Select application:",
2180
+ manifest.applications.map((a) => ({ label: `${a.name} \u2014 ${a.displayName}`, value: a.name }))
2095
2181
  );
2096
- }
2097
- async function buildAll(layout, manifest, logger, signal) {
2098
- const { coreDir, coreDirName } = layout;
2099
- logger.log(`Building ${coreDirName}/...`);
2100
- await runCommand("npx", ["turbo", "build"], coreDir, logger, signal);
2101
- if (signal?.aborted) return;
2102
- const appDirs = [];
2103
- for (const app of manifest.applications) {
2104
- const appDir = resolve5(join24(coreDir), app.localPath);
2105
- if (!await dirExists(appDir)) {
2106
- logger.log(`Warning: Application directory "${app.name}" not found at ${appDir} \u2014 skipping build.`);
2107
- continue;
2108
- }
2109
- appDirs.push({ name: app.name, dir: appDir });
2182
+ const serviceName = await ctx.prompt("Service name:");
2183
+ if (!/^[a-z0-9-]+$/.test(serviceName)) {
2184
+ ctx.log(`Error: Service name "${serviceName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
2185
+ return;
2110
2186
  }
2111
- if (appDirs.length === 0) return;
2112
- logger.log(`Building apps in parallel: ${appDirs.map((a) => a.name).join(", ")}...`);
2113
- await Promise.all(
2114
- appDirs.map(
2115
- ({ name, dir }) => runCommand("npm", ["run", "build"], dir, logger, signal).then(() => {
2116
- logger.log(`\u2713 ${name} build done`);
2117
- })
2118
- )
2187
+ const platformName = manifest.product.name;
2188
+ const includeSuffix = await ctx.confirm(`Include "-service" suffix? (${platformName}-${serviceName}-service)`, true);
2189
+ const serviceNameSuffix = includeSuffix ? "service" : null;
2190
+ const serviceDisplayName = await ctx.prompt("Service display name:");
2191
+ await createServiceModuleService(
2192
+ { applicationName, serviceName, serviceDisplayName, serviceNameSuffix },
2193
+ ctx
2119
2194
  );
2120
2195
  }
2121
2196
 
2122
- // src/commands/local-scripts/local-script.command.ts
2123
- var INSTALL_COMMAND_NAME = "install";
2124
- var BUILD_COMMAND_NAME = "build";
2125
- var START_COMMAND_NAME = "start";
2126
- var STOP_COMMAND_NAME = "stop";
2127
- var DESTROY_COMMAND_NAME = "destroy";
2128
- async function runLocalScript(scriptName, logger, signal) {
2197
+ // src/services/create-ui-module.service.ts
2198
+ async function createUiModuleService(params, logger) {
2199
+ await createUiModule(params, logger);
2200
+ }
2201
+
2202
+ // src/controllers/ui/create-ui-module.ui-controller.ts
2203
+ async function createUiModuleUiController(ctx) {
2204
+ if (!await isPlatformInitialized()) {
2205
+ ctx.log("Error: Cannot create a UI module \u2014 no platform initialized in this directory.");
2206
+ return;
2207
+ }
2129
2208
  const layout = await findPlatformLayout();
2130
2209
  if (!layout) {
2131
- logger.log(`Error: Cannot run "${scriptName}" \u2014 no platform initialized in this directory.`);
2210
+ ctx.log("Error: Cannot create a UI module \u2014 no platform initialized in this directory.");
2132
2211
  return;
2133
2212
  }
2134
- const { rootDir, coreDirName } = layout;
2135
- let manifest;
2136
- try {
2137
- manifest = await readManifest(rootDir, coreDirName);
2138
- } catch (err) {
2139
- logger.log(`Error: Could not read product manifest \u2014 ${formatError(err)}`);
2140
- return;
2213
+ let manifest = await readManifest(layout.rootDir, layout.coreDirName);
2214
+ if (manifest.applications.length === 0) {
2215
+ ctx.log("No applications found in this platform.");
2216
+ const createFirst = await ctx.confirm("Would you like to create an application first?");
2217
+ if (!createFirst) return;
2218
+ await createApplicationUiController(ctx);
2219
+ manifest = await readManifest(layout.rootDir, layout.coreDirName);
2220
+ if (manifest.applications.length === 0) {
2221
+ ctx.log("Error: No applications available after creation attempt.");
2222
+ return;
2223
+ }
2141
2224
  }
2142
- switch (scriptName) {
2143
- case START_COMMAND_NAME:
2144
- await startEnvironment(layout, manifest, logger, signal);
2145
- break;
2146
- case STOP_COMMAND_NAME:
2147
- await stopEnvironment(layout, manifest, logger, signal);
2148
- break;
2149
- case DESTROY_COMMAND_NAME:
2150
- await destroyEnvironment(layout, manifest, logger, signal);
2151
- break;
2152
- case INSTALL_COMMAND_NAME:
2153
- await installDependencies(layout, manifest, logger, signal);
2154
- break;
2155
- case BUILD_COMMAND_NAME:
2156
- await buildAll(layout, manifest, logger, signal);
2157
- break;
2158
- default:
2159
- logger.log(`Error: Unknown script "${scriptName}".`);
2225
+ const applicationName = await ctx.select(
2226
+ "Select application:",
2227
+ manifest.applications.map((a) => ({ label: `${a.name} \u2014 ${a.displayName}`, value: a.name }))
2228
+ );
2229
+ const platformName = manifest.product.name;
2230
+ const includeSuffix = await ctx.confirm(`Include "-ui" suffix? (${platformName}-${applicationName}-ui)`, true);
2231
+ const uiModuleSuffix = includeSuffix ? "ui" : null;
2232
+ const applicationDisplayName = await ctx.prompt("Application display name:");
2233
+ await createUiModuleService(
2234
+ { applicationName, applicationDisplayName, uiModuleSuffix },
2235
+ ctx
2236
+ );
2237
+ }
2238
+
2239
+ // src/services/status.service.ts
2240
+ async function statusService() {
2241
+ return gatherStatus();
2242
+ }
2243
+
2244
+ // src/utils/theme.ts
2245
+ import chalk from "chalk";
2246
+ var theme = {
2247
+ prompt: chalk.green,
2248
+ commandName: chalk.cyan,
2249
+ commandDescription: chalk.gray,
2250
+ selected: chalk.bgBlue.white,
2251
+ output: chalk.white,
2252
+ muted: chalk.dim,
2253
+ error: chalk.red,
2254
+ success: chalk.green,
2255
+ warning: chalk.yellow,
2256
+ info: chalk.cyan,
2257
+ section: chalk.bold.white,
2258
+ label: chalk.dim
2259
+ };
2260
+
2261
+ // src/commands/status/status-formatter.ts
2262
+ var CHECK = theme.success("\u2713");
2263
+ var CROSS = theme.error("\u2717");
2264
+ function tick(ok) {
2265
+ return ok ? CHECK : CROSS;
2266
+ }
2267
+ function formatStatusLines(result) {
2268
+ const lines = [];
2269
+ if (result.projectInfo) {
2270
+ const p = result.projectInfo;
2271
+ lines.push(theme.info("\u25B8 ") + theme.section(p.displayName) + theme.label(` (${p.productName})`));
2272
+ lines.push(theme.label(" Organization: ") + p.organization + theme.label(` (${p.scope})`));
2273
+ const appList = p.applicationNames.length > 0 ? p.applicationNames.join(", ") : "(none)";
2274
+ lines.push(theme.label(" Applications: ") + `${p.applicationCount} \u2014 ` + appList);
2275
+ lines.push("");
2276
+ }
2277
+ lines.push(theme.section("Prerequisites"));
2278
+ for (const pre of result.prerequisites) {
2279
+ const ok = pre.available && pre.versionOk;
2280
+ const versionStr = pre.version ? theme.label(` ${pre.version}`) : "";
2281
+ const detailStr = pre.detail ? theme.warning(` \u2014 ${pre.detail}`) : "";
2282
+ lines.push(` ${tick(ok)} ${pre.name}${versionStr}${detailStr}`);
2283
+ }
2284
+ lines.push("");
2285
+ lines.push(theme.section("Lifecycle"));
2286
+ lines.push(` ${tick(result.lifecycle.initialized)} Platform initialized`);
2287
+ const installed = result.lifecycle.installed;
2288
+ lines.push(` ${tick(installed)} Dependencies installed`);
2289
+ if (!installed && result.lifecycle.installedDetails.length > 0) {
2290
+ for (const d of result.lifecycle.installedDetails) {
2291
+ if (!d.exists) {
2292
+ lines.push(` ${CROSS} ${theme.label(d.name)} ${theme.warning("(missing node_modules/)")}`);
2293
+ }
2294
+ }
2295
+ }
2296
+ const built = result.lifecycle.built;
2297
+ lines.push(` ${tick(built)} Build completed`);
2298
+ if (!built && result.lifecycle.builtDetails.length > 0) {
2299
+ for (const d of result.lifecycle.builtDetails) {
2300
+ if (!d.exists) {
2301
+ lines.push(` ${CROSS} ${theme.label(d.name)} ${theme.warning("(missing .turbo/)")}`);
2302
+ }
2303
+ }
2304
+ }
2305
+ lines.push(` ${tick(result.lifecycle.running)} Environment running`);
2306
+ if (result.containers.length > 0) {
2307
+ lines.push("");
2308
+ lines.push(theme.section("Containers"));
2309
+ for (const c of result.containers) {
2310
+ const ok = c.state === "running";
2311
+ const stateStr = ok ? theme.success(c.state) : c.state === "not started" ? theme.label(c.state) : theme.error(c.state);
2312
+ const ports = c.ports ? theme.label(` ${c.ports}`) : "";
2313
+ lines.push(` ${tick(ok)} ${c.name.padEnd(35)} ${stateStr}${ports}`);
2314
+ }
2160
2315
  }
2316
+ return lines;
2161
2317
  }
2162
2318
 
2163
2319
  // src/services/local-script.service.ts
2164
- async function localScriptService(scriptName, logger, signal) {
2165
- await runLocalScript(scriptName, logger, signal);
2320
+ async function localScriptService(scriptName, logger, signal, appNames) {
2321
+ await runLocalScript(scriptName, logger, signal, appNames);
2166
2322
  }
2167
2323
 
2168
2324
  // src/controllers/ui/status.ui-controller.ts
@@ -2195,6 +2351,44 @@ async function statusUiController(ctx) {
2195
2351
  }
2196
2352
  }
2197
2353
 
2354
+ // src/controllers/ui/local-script.ui-controller.ts
2355
+ function createLocalScriptUiController(scriptName) {
2356
+ return async (ctx) => {
2357
+ const layout = await findPlatformLayout();
2358
+ if (!layout) {
2359
+ ctx.log(`Error: Cannot run "${scriptName}" \u2014 no platform initialized in this directory.`);
2360
+ return;
2361
+ }
2362
+ let manifest;
2363
+ try {
2364
+ manifest = await readManifest(layout.rootDir, layout.coreDirName);
2365
+ } catch (err) {
2366
+ ctx.log(`Error: Could not read product manifest \u2014 ${formatError(err)}`);
2367
+ return;
2368
+ }
2369
+ if (manifest.applications.length === 0) {
2370
+ await localScriptService(scriptName, ctx, ctx.signal);
2371
+ return;
2372
+ }
2373
+ const options = [
2374
+ { label: "Platform (Core)", value: CORE_APP_NAME },
2375
+ ...manifest.applications.map((app) => ({
2376
+ label: `${app.displayName} (${app.name})`,
2377
+ value: app.name
2378
+ }))
2379
+ ];
2380
+ const selected = await ctx.multiselect(
2381
+ `Select targets to ${scriptName}:`,
2382
+ options
2383
+ );
2384
+ if (selected.length === 0) {
2385
+ ctx.log("Nothing selected. Nothing to do.");
2386
+ return;
2387
+ }
2388
+ await localScriptService(scriptName, ctx, ctx.signal, selected);
2389
+ };
2390
+ }
2391
+
2198
2392
  // src/controllers/ui/registry.ts
2199
2393
  var uiControllers = /* @__PURE__ */ new Map([
2200
2394
  [CREATE_APPLICATION_COMMAND_NAME, createApplicationUiController],
@@ -2202,7 +2396,12 @@ var uiControllers = /* @__PURE__ */ new Map([
2202
2396
  [CONFIGURE_IDP_COMMAND_NAME, configureIdpUiController],
2203
2397
  [CREATE_SERVICE_MODULE_COMMAND_NAME, createServiceModuleUiController],
2204
2398
  [CREATE_UI_MODULE_COMMAND_NAME, createUiModuleUiController],
2205
- [STATUS_COMMAND_NAME, statusUiController]
2399
+ [STATUS_COMMAND_NAME, statusUiController],
2400
+ [INSTALL_COMMAND_NAME, createLocalScriptUiController(INSTALL_COMMAND_NAME)],
2401
+ [BUILD_COMMAND_NAME, createLocalScriptUiController(BUILD_COMMAND_NAME)],
2402
+ [START_COMMAND_NAME, createLocalScriptUiController(START_COMMAND_NAME)],
2403
+ [STOP_COMMAND_NAME, createLocalScriptUiController(STOP_COMMAND_NAME)],
2404
+ [DESTROY_COMMAND_NAME, createLocalScriptUiController(DESTROY_COMMAND_NAME)]
2206
2405
  ]);
2207
2406
 
2208
2407
  // src/hooks/use-command-runner.ts
@@ -2215,6 +2414,7 @@ function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
2215
2414
  const [promptMode, setPromptMode] = useState2({ kind: "text" });
2216
2415
  const [selectIndex, setSelectIndex] = useState2(0);
2217
2416
  const [confirmValue, setConfirmValue] = useState2(true);
2417
+ const [multiselectChecked, setMultiselectChecked] = useState2(/* @__PURE__ */ new Set());
2218
2418
  const abortExecution = useCallback(() => {
2219
2419
  abortControllerRef.current?.abort();
2220
2420
  abortControllerRef.current = null;
@@ -2270,6 +2470,27 @@ function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
2270
2470
  );
2271
2471
  });
2272
2472
  },
2473
+ multiselect(message, options) {
2474
+ return new Promise((resolve6, reject) => {
2475
+ if (controller.signal.aborted) {
2476
+ reject(new DOMException("Aborted", "AbortError"));
2477
+ return;
2478
+ }
2479
+ setPromptMessage(message);
2480
+ setPromptMode({ kind: "multiselect", options });
2481
+ setSelectIndex(0);
2482
+ setMultiselectChecked(new Set(options.map((_, i) => i)));
2483
+ promptResolveRef.current = (value) => {
2484
+ resolve6(value ? value.split(",") : []);
2485
+ };
2486
+ setState(APP_STATE.PROMPTING);
2487
+ controller.signal.addEventListener(
2488
+ "abort",
2489
+ () => reject(new DOMException("Aborted", "AbortError")),
2490
+ { once: true }
2491
+ );
2492
+ });
2493
+ },
2273
2494
  confirm(message, defaultValue) {
2274
2495
  return new Promise((resolve6, reject) => {
2275
2496
  if (controller.signal.aborted) {
@@ -2332,12 +2553,14 @@ function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
2332
2553
  selectIndex,
2333
2554
  setSelectIndex,
2334
2555
  confirmValue,
2335
- setConfirmValue
2556
+ setConfirmValue,
2557
+ multiselectChecked,
2558
+ setMultiselectChecked
2336
2559
  };
2337
2560
  }
2338
2561
 
2339
2562
  // src/app.tsx
2340
- import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
2563
+ import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
2341
2564
  var require2 = createRequire(import.meta.url);
2342
2565
  var { version } = require2("../package.json");
2343
2566
  function App() {
@@ -2369,7 +2592,9 @@ function App() {
2369
2592
  selectIndex,
2370
2593
  setSelectIndex,
2371
2594
  confirmValue,
2372
- setConfirmValue
2595
+ setConfirmValue,
2596
+ multiselectChecked,
2597
+ setMultiselectChecked
2373
2598
  } = useCommandRunner({ appendStaticItem, setState, onCommandComplete: handleCommandComplete });
2374
2599
  const query = inputValue.startsWith("/") ? inputValue.slice(1) : "";
2375
2600
  const filteredCommands = registry.searchVisible(query, { platformInitialized });
@@ -2401,6 +2626,39 @@ function App() {
2401
2626
  }
2402
2627
  return;
2403
2628
  }
2629
+ if (promptMode.kind === "multiselect") {
2630
+ if (key.upArrow) {
2631
+ setSelectIndex((prev) => Math.max(0, prev - 1));
2632
+ return;
2633
+ }
2634
+ if (key.downArrow) {
2635
+ setSelectIndex((prev) => Math.min(promptMode.options.length, prev + 1));
2636
+ return;
2637
+ }
2638
+ if (input === " ") {
2639
+ if (selectIndex === 0) {
2640
+ const allChecked = multiselectChecked.size === promptMode.options.length;
2641
+ setMultiselectChecked(
2642
+ allChecked ? /* @__PURE__ */ new Set() : new Set(promptMode.options.map((_, i) => i))
2643
+ );
2644
+ } else {
2645
+ const optIdx = selectIndex - 1;
2646
+ setMultiselectChecked((prev) => {
2647
+ const next = new Set(prev);
2648
+ if (next.has(optIdx)) next.delete(optIdx);
2649
+ else next.add(optIdx);
2650
+ return next;
2651
+ });
2652
+ }
2653
+ return;
2654
+ }
2655
+ if (key.return) {
2656
+ const selected = promptMode.options.filter((_, i) => multiselectChecked.has(i)).map((o) => o.value).join(",");
2657
+ handlePromptSubmit(selected);
2658
+ return;
2659
+ }
2660
+ return;
2661
+ }
2404
2662
  if (promptMode.kind === "confirm") {
2405
2663
  if (key.leftArrow || key.upArrow) {
2406
2664
  setConfirmValue(true);
@@ -2479,19 +2737,27 @@ function App() {
2479
2737
  function renderActiveArea() {
2480
2738
  switch (state) {
2481
2739
  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)" })
2740
+ return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "row", gap: 1, children: [
2741
+ /* @__PURE__ */ jsx10(Spinner, { label: "Running\u2026" }),
2742
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "(esc to cancel)" })
2485
2743
  ] });
2486
2744
  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 })
2745
+ return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", children: [
2746
+ /* @__PURE__ */ jsx10(Text10, { color: "cyan", children: promptMessage }),
2747
+ promptMode.kind === "select" && /* @__PURE__ */ jsx10(SelectList, { options: promptMode.options, selectedIndex: selectIndex }),
2748
+ promptMode.kind === "multiselect" && /* @__PURE__ */ jsx10(
2749
+ MultiSelectList,
2750
+ {
2751
+ options: promptMode.options,
2752
+ focusedIndex: selectIndex,
2753
+ checkedIndices: multiselectChecked
2754
+ }
2755
+ ),
2756
+ promptMode.kind === "confirm" && /* @__PURE__ */ jsx10(ConfirmPrompt, { value: confirmValue }),
2757
+ promptMode.kind === "text" && /* @__PURE__ */ jsx10(Prompt, { value: promptValue, onChange: setPromptValue, onSubmit: handlePromptSubmit })
2492
2758
  ] });
2493
2759
  default:
2494
- return /* @__PURE__ */ jsx9(
2760
+ return /* @__PURE__ */ jsx10(
2495
2761
  Prompt,
2496
2762
  {
2497
2763
  value: inputValue,
@@ -2504,10 +2770,10 @@ function App() {
2504
2770
  );
2505
2771
  }
2506
2772
  }
2507
- return /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", children: [
2508
- /* @__PURE__ */ jsx9(ScrollbackHistory, { items: staticItems, version }),
2773
+ return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", children: [
2774
+ /* @__PURE__ */ jsx10(ScrollbackHistory, { items: staticItems, version }),
2509
2775
  renderActiveArea(),
2510
- state === APP_STATE.PALETTE && /* @__PURE__ */ jsx9(CommandPalette, { commands: filteredCommands, selectedIndex })
2776
+ state === APP_STATE.PALETTE && /* @__PURE__ */ jsx10(CommandPalette, { commands: filteredCommands, selectedIndex })
2511
2777
  ] });
2512
2778
  }
2513
2779
 
@@ -2625,10 +2891,11 @@ function createCliSpinner(label) {
2625
2891
 
2626
2892
  // src/controllers/cli/local-script.cli-controller.ts
2627
2893
  function createLocalScriptCliController(scriptName) {
2628
- return async (_args) => {
2894
+ return async (args2) => {
2629
2895
  const spinner = createCliSpinner(`Running ${scriptName}\u2026`);
2630
2896
  spinner.start();
2631
- await localScriptService(scriptName, { log: (msg) => spinner.log(msg) });
2897
+ const appNames = args2._positional ? JSON.parse(args2._positional) : void 0;
2898
+ await localScriptService(scriptName, { log: (msg) => spinner.log(msg) }, void 0, appNames);
2632
2899
  spinner.stop();
2633
2900
  };
2634
2901
  }
@@ -2712,22 +2979,28 @@ var cliControllers = /* @__PURE__ */ new Map([
2712
2979
  // src/utils/parse-args.ts
2713
2980
  function parseKeyValueArgs(args2) {
2714
2981
  const result = {};
2982
+ const positional = [];
2715
2983
  for (const arg of args2) {
2716
2984
  const eqIndex = arg.indexOf("=");
2717
2985
  if (eqIndex > 0) {
2718
2986
  const key = arg.slice(0, eqIndex);
2719
2987
  const value = arg.slice(eqIndex + 1);
2720
2988
  result[key] = value;
2989
+ } else {
2990
+ positional.push(arg);
2721
2991
  }
2722
2992
  }
2993
+ if (positional.length > 0) {
2994
+ result._positional = JSON.stringify(positional);
2995
+ }
2723
2996
  return result;
2724
2997
  }
2725
2998
 
2726
2999
  // src/index.tsx
2727
- import { jsx as jsx10 } from "react/jsx-runtime";
3000
+ import { jsx as jsx11 } from "react/jsx-runtime";
2728
3001
  var args = process.argv.slice(2);
2729
3002
  if (args.length === 0) {
2730
- render(/* @__PURE__ */ jsx10(App, {}));
3003
+ render(/* @__PURE__ */ jsx11(App, {}));
2731
3004
  } else {
2732
3005
  const commandName = args[0];
2733
3006
  const params = parseKeyValueArgs(args.slice(1));