@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.
- package/dist/index.js +855 -582
- 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
|
|
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/
|
|
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__ */
|
|
165
|
-
/* @__PURE__ */
|
|
166
|
-
/* @__PURE__ */
|
|
167
|
-
/* @__PURE__ */
|
|
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/
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
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
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
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
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
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
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
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
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
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
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
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
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
const
|
|
1621
|
-
const
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
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
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
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
|
|
2088
|
-
|
|
2089
|
-
|
|
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
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
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
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
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/
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
async function
|
|
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
|
-
|
|
2210
|
+
ctx.log("Error: Cannot create a UI module \u2014 no platform initialized in this directory.");
|
|
2132
2211
|
return;
|
|
2133
2212
|
}
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
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
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
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
|
|
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__ */
|
|
2483
|
-
/* @__PURE__ */
|
|
2484
|
-
/* @__PURE__ */
|
|
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__ */
|
|
2488
|
-
/* @__PURE__ */
|
|
2489
|
-
promptMode.kind === "select" && /* @__PURE__ */
|
|
2490
|
-
promptMode.kind === "
|
|
2491
|
-
|
|
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__ */
|
|
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__ */
|
|
2508
|
-
/* @__PURE__ */
|
|
2773
|
+
return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", children: [
|
|
2774
|
+
/* @__PURE__ */ jsx10(ScrollbackHistory, { items: staticItems, version }),
|
|
2509
2775
|
renderActiveArea(),
|
|
2510
|
-
state === APP_STATE.PALETTE && /* @__PURE__ */
|
|
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 (
|
|
2894
|
+
return async (args2) => {
|
|
2629
2895
|
const spinner = createCliSpinner(`Running ${scriptName}\u2026`);
|
|
2630
2896
|
spinner.start();
|
|
2631
|
-
|
|
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
|
|
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__ */
|
|
3003
|
+
render(/* @__PURE__ */ jsx11(App, {}));
|
|
2731
3004
|
} else {
|
|
2732
3005
|
const commandName = args[0];
|
|
2733
3006
|
const params = parseKeyValueArgs(args.slice(1));
|