@bluealba/platform-cli 0.3.1-feature-platform-cli-223 → 0.3.1-feature-platform-cli-225
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +945 -353
- package/package.json +3 -2
- package/templates/bootstrap-service-template/Dockerfile +1 -1
- package/templates/bootstrap-service-template/Dockerfile.development +1 -1
- package/templates/bootstrap-service-template/README.md +1 -1
- package/templates/bootstrap-service-template/package.json +1 -1
- package/templates/platform-init-template/{{platformName}}-core/local/{{platformName}}-core-docker-compose.yml +5 -5
package/dist/index.js
CHANGED
|
@@ -5,8 +5,8 @@ import { render } from "ink";
|
|
|
5
5
|
|
|
6
6
|
// src/app.tsx
|
|
7
7
|
import { createRequire } from "module";
|
|
8
|
-
import { useState as useState3, useCallback as useCallback2 } from "react";
|
|
9
|
-
import { Box as
|
|
8
|
+
import { useState as useState3, useCallback as useCallback2, useEffect as useEffect2 } from "react";
|
|
9
|
+
import { Box as Box6, Text as Text9, useApp, useInput } from "ink";
|
|
10
10
|
|
|
11
11
|
// src/components/prompt.tsx
|
|
12
12
|
import { Box, Text } from "ink";
|
|
@@ -143,6 +143,31 @@ function Spinner({ label }) {
|
|
|
143
143
|
] });
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
// src/components/select-list.tsx
|
|
147
|
+
import React4 from "react";
|
|
148
|
+
import { Box as Box4, Text as Text7 } from "ink";
|
|
149
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
150
|
+
var SelectList = React4.memo(function SelectList2({ options, selectedIndex }) {
|
|
151
|
+
if (options.length === 0) {
|
|
152
|
+
return /* @__PURE__ */ jsx7(Box4, { paddingLeft: 2, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "No options available" }) });
|
|
153
|
+
}
|
|
154
|
+
return /* @__PURE__ */ jsx7(Box4, { flexDirection: "column", paddingLeft: 2, children: options.map((opt, i) => {
|
|
155
|
+
const isSelected = i === selectedIndex;
|
|
156
|
+
return /* @__PURE__ */ jsx7(Box4, { children: /* @__PURE__ */ jsx7(Text7, { color: "cyan", bold: isSelected, inverse: isSelected, children: opt.label }) }, opt.value);
|
|
157
|
+
}) });
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// src/components/confirm-prompt.tsx
|
|
161
|
+
import { Box as Box5, Text as Text8 } from "ink";
|
|
162
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
163
|
+
function ConfirmPrompt({ value }) {
|
|
164
|
+
return /* @__PURE__ */ jsxs6(Box5, { gap: 2, paddingLeft: 2, children: [
|
|
165
|
+
/* @__PURE__ */ jsx8(Text8, { color: "cyan", bold: value, inverse: value, children: "Yes" }),
|
|
166
|
+
/* @__PURE__ */ jsx8(Text8, { color: "cyan", bold: !value, inverse: !value, children: "No" }),
|
|
167
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "(\u2190/\u2192 to toggle, Enter to confirm)" })
|
|
168
|
+
] });
|
|
169
|
+
}
|
|
170
|
+
|
|
146
171
|
// src/commands/registry.ts
|
|
147
172
|
import { Fzf } from "fzf";
|
|
148
173
|
|
|
@@ -254,7 +279,12 @@ async function scaffoldBootstrap(bootstrapServiceDir, organizationName, bootstra
|
|
|
254
279
|
{
|
|
255
280
|
templateDir: bootstrapTemplateDir,
|
|
256
281
|
outputDir: bootstrapServiceDir,
|
|
257
|
-
variables: {
|
|
282
|
+
variables: {
|
|
283
|
+
organizationName,
|
|
284
|
+
bootstrapName,
|
|
285
|
+
bootstrapServiceName: `${bootstrapName}-bootstrap-service`,
|
|
286
|
+
bootstrapServiceDir: bootstrapServiceDir_var
|
|
287
|
+
},
|
|
258
288
|
exclude: ["src/data/shared-libraries.json", "src/data/platform/modules-config.json"]
|
|
259
289
|
},
|
|
260
290
|
(message) => logger.log(message)
|
|
@@ -358,8 +388,8 @@ function buildServiceModuleEntry(organizationName, platformName, applicationName
|
|
|
358
388
|
dependsOn: []
|
|
359
389
|
};
|
|
360
390
|
}
|
|
361
|
-
function buildUiModuleEntry(organizationName, platformName, applicationName, applicationDisplayName) {
|
|
362
|
-
const uiName = `${platformName}-${applicationName}-ui`;
|
|
391
|
+
function buildUiModuleEntry(organizationName, platformName, applicationName, applicationDisplayName, uiNameOverride) {
|
|
392
|
+
const uiName = uiNameOverride ?? `${platformName}-${applicationName}-ui`;
|
|
363
393
|
return {
|
|
364
394
|
name: `@${organizationName}/${uiName}`,
|
|
365
395
|
displayName: applicationDisplayName,
|
|
@@ -791,7 +821,8 @@ async function generateLocalEnv(outputDir, logger, coreDirName = "core") {
|
|
|
791
821
|
var INIT_COMMAND_NAME = "init";
|
|
792
822
|
var initCommand = {
|
|
793
823
|
name: INIT_COMMAND_NAME,
|
|
794
|
-
description: "Initialize a new platform"
|
|
824
|
+
description: "Initialize a new platform",
|
|
825
|
+
hidden: ({ platformInitialized }) => platformInitialized
|
|
795
826
|
};
|
|
796
827
|
async function init(params, logger) {
|
|
797
828
|
if (await isPlatformInitialized()) {
|
|
@@ -803,14 +834,18 @@ async function init(params, logger) {
|
|
|
803
834
|
const outputDir = cwd4();
|
|
804
835
|
logger.log(`Initializing ${platformTitle} platform for @${organizationName}...`);
|
|
805
836
|
const coreDirName = `${platformName}-core`;
|
|
806
|
-
const
|
|
807
|
-
const
|
|
837
|
+
const bootstrapSuffix = params.bootstrapServiceSuffix ?? "bootstrap-service";
|
|
838
|
+
const customizationUiSuffix = params.customizationUiSuffix ?? "customization-ui";
|
|
839
|
+
const bootstrapServiceName = `${platformName}-${bootstrapSuffix}`;
|
|
840
|
+
const customizationUiName = `${platformName}-${customizationUiSuffix}`;
|
|
808
841
|
const variables = {
|
|
809
842
|
organizationName,
|
|
810
843
|
platformName,
|
|
811
844
|
platformTitle,
|
|
812
845
|
platformDisplayName,
|
|
813
846
|
bootstrapName: platformName,
|
|
847
|
+
bootstrapServiceName,
|
|
848
|
+
customizationUiName,
|
|
814
849
|
bootstrapServiceDir: `${coreDirName}/services`
|
|
815
850
|
};
|
|
816
851
|
try {
|
|
@@ -1077,7 +1112,7 @@ var createServiceModuleCommand = {
|
|
|
1077
1112
|
description: "Add a new service module to an existing application"
|
|
1078
1113
|
};
|
|
1079
1114
|
async function createServiceModule(params, logger) {
|
|
1080
|
-
const { applicationName, serviceName, serviceDisplayName } = params;
|
|
1115
|
+
const { applicationName, serviceName, serviceDisplayName, serviceNameSuffix } = params;
|
|
1081
1116
|
const layout = await findPlatformLayout();
|
|
1082
1117
|
if (!layout) {
|
|
1083
1118
|
logger.log("Error: Cannot create a service module \u2014 no platform initialized in this directory.");
|
|
@@ -1104,7 +1139,8 @@ async function createServiceModule(params, logger) {
|
|
|
1104
1139
|
logger.log(`Error: The specified application "${applicationName}" does not exist in the platform.`);
|
|
1105
1140
|
return;
|
|
1106
1141
|
}
|
|
1107
|
-
const
|
|
1142
|
+
const suffix = serviceNameSuffix === void 0 ? "service" : serviceNameSuffix;
|
|
1143
|
+
const fullServiceName = suffix ? `${platformName}-${serviceName}-${suffix}` : `${platformName}-${serviceName}`;
|
|
1108
1144
|
const serviceDir = join20(applicationDir, "services", fullServiceName);
|
|
1109
1145
|
try {
|
|
1110
1146
|
await access3(serviceDir);
|
|
@@ -1141,8 +1177,8 @@ import { access as access4, readdir as readdir4 } from "fs/promises";
|
|
|
1141
1177
|
|
|
1142
1178
|
// src/commands/create-ui-module/append-ui-docker-compose.ts
|
|
1143
1179
|
import { readFile as readFile9, writeFile as writeFile8 } from "fs/promises";
|
|
1144
|
-
async function appendUiToDockerCompose(dockerComposePath, platformName, applicationName, port, logger) {
|
|
1145
|
-
const uiName = `${platformName}-${applicationName}-ui`;
|
|
1180
|
+
async function appendUiToDockerCompose(dockerComposePath, platformName, applicationName, port, logger, uiNameOverride) {
|
|
1181
|
+
const uiName = uiNameOverride ?? `${platformName}-${applicationName}-ui`;
|
|
1146
1182
|
const block = `
|
|
1147
1183
|
${uiName}:
|
|
1148
1184
|
build:
|
|
@@ -1170,7 +1206,7 @@ var createUiModuleCommand = {
|
|
|
1170
1206
|
description: "Add a UI module to an existing application"
|
|
1171
1207
|
};
|
|
1172
1208
|
async function createUiModule(params, logger) {
|
|
1173
|
-
const { applicationName, applicationDisplayName } = params;
|
|
1209
|
+
const { applicationName, applicationDisplayName, uiModuleSuffix } = params;
|
|
1174
1210
|
const layout = await findPlatformLayout();
|
|
1175
1211
|
if (!layout) {
|
|
1176
1212
|
logger.log("Error: Cannot create a UI module \u2014 no platform initialized in this directory.");
|
|
@@ -1207,7 +1243,8 @@ async function createUiModule(params, logger) {
|
|
|
1207
1243
|
}
|
|
1208
1244
|
} catch {
|
|
1209
1245
|
}
|
|
1210
|
-
const
|
|
1246
|
+
const uiSuffix = uiModuleSuffix === void 0 ? "ui" : uiModuleSuffix;
|
|
1247
|
+
const uiName = uiSuffix ? `${platformName}-${applicationName}-${uiSuffix}` : `${platformName}-${applicationName}`;
|
|
1211
1248
|
const uiOutputDir = join21(uiDir, uiName);
|
|
1212
1249
|
const uiBaseDir = `${platformName}-${applicationName}/ui`;
|
|
1213
1250
|
try {
|
|
@@ -1217,14 +1254,275 @@ async function createUiModule(params, logger) {
|
|
|
1217
1254
|
return;
|
|
1218
1255
|
}
|
|
1219
1256
|
const bootstrapServiceDir = join21(applicationDir, "services", `${platformName}-${applicationName}-bootstrap-service`);
|
|
1220
|
-
const moduleEntry = buildUiModuleEntry(organizationName, platformName, applicationName, applicationDisplayName);
|
|
1257
|
+
const moduleEntry = buildUiModuleEntry(organizationName, platformName, applicationName, applicationDisplayName, uiName);
|
|
1221
1258
|
await addModuleEntry(bootstrapServiceDir, applicationName, moduleEntry, logger);
|
|
1222
1259
|
const dockerComposePath = join21(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
|
|
1223
1260
|
const port = await getNextAvailablePort(localDir);
|
|
1224
|
-
await appendUiToDockerCompose(dockerComposePath, platformName, applicationName, port, logger);
|
|
1261
|
+
await appendUiToDockerCompose(dockerComposePath, platformName, applicationName, port, logger, uiName);
|
|
1225
1262
|
logger.log(`Done! UI module "${uiName}" added to application "${applicationName}".`);
|
|
1226
1263
|
}
|
|
1227
1264
|
|
|
1265
|
+
// src/commands/status/status-checks.ts
|
|
1266
|
+
import { spawn } from "child_process";
|
|
1267
|
+
import { access as access5 } from "fs/promises";
|
|
1268
|
+
import { join as join22, resolve as resolve4 } from "path";
|
|
1269
|
+
function spawnCapture(cmd, args2, cwd5) {
|
|
1270
|
+
return new Promise((resolvePromise) => {
|
|
1271
|
+
const child = spawn(cmd, args2, {
|
|
1272
|
+
cwd: cwd5,
|
|
1273
|
+
shell: false,
|
|
1274
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1275
|
+
});
|
|
1276
|
+
let stdout = "";
|
|
1277
|
+
let stderr = "";
|
|
1278
|
+
child.stdout.on("data", (data) => {
|
|
1279
|
+
stdout += data.toString();
|
|
1280
|
+
});
|
|
1281
|
+
child.stderr.on("data", (data) => {
|
|
1282
|
+
stderr += data.toString();
|
|
1283
|
+
});
|
|
1284
|
+
child.on("close", (code) => {
|
|
1285
|
+
resolvePromise({ code: code ?? 1, stdout, stderr });
|
|
1286
|
+
});
|
|
1287
|
+
child.on("error", () => {
|
|
1288
|
+
resolvePromise({ code: 1, stdout, stderr });
|
|
1289
|
+
});
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
async function pathExists(p) {
|
|
1293
|
+
try {
|
|
1294
|
+
await access5(p);
|
|
1295
|
+
return true;
|
|
1296
|
+
} catch {
|
|
1297
|
+
return false;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
async function checkNodeVersion() {
|
|
1301
|
+
const { code, stdout } = await spawnCapture("node", ["--version"]);
|
|
1302
|
+
if (code !== 0 || !stdout.trim()) {
|
|
1303
|
+
return { name: "Node.js", available: false, version: null, versionOk: false, detail: "not found in PATH" };
|
|
1304
|
+
}
|
|
1305
|
+
const version2 = stdout.trim();
|
|
1306
|
+
const match = version2.match(/^v(\d+)/);
|
|
1307
|
+
const major = match ? parseInt(match[1], 10) : 0;
|
|
1308
|
+
const versionOk = major >= 22;
|
|
1309
|
+
return {
|
|
1310
|
+
name: "Node.js",
|
|
1311
|
+
available: true,
|
|
1312
|
+
version: version2,
|
|
1313
|
+
versionOk,
|
|
1314
|
+
detail: versionOk ? void 0 : `>=22 required, found ${version2}`
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
async function checkDocker() {
|
|
1318
|
+
const { code, stdout } = await spawnCapture("docker", ["info", "--format", "{{.ServerVersion}}"]);
|
|
1319
|
+
if (code === 0 && stdout.trim()) {
|
|
1320
|
+
return { name: "Docker", available: true, version: stdout.trim(), versionOk: true };
|
|
1321
|
+
}
|
|
1322
|
+
const { code: vCode, stdout: vOut } = await spawnCapture("docker", ["--version"]);
|
|
1323
|
+
if (vCode === 0 && vOut.trim()) {
|
|
1324
|
+
return {
|
|
1325
|
+
name: "Docker",
|
|
1326
|
+
available: false,
|
|
1327
|
+
version: null,
|
|
1328
|
+
versionOk: false,
|
|
1329
|
+
detail: "installed but daemon is not running"
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
return { name: "Docker", available: false, version: null, versionOk: false, detail: "not found in PATH" };
|
|
1333
|
+
}
|
|
1334
|
+
async function checkInstalled(layout, manifest) {
|
|
1335
|
+
const results = [];
|
|
1336
|
+
const coreCheck = {
|
|
1337
|
+
name: layout.coreDirName,
|
|
1338
|
+
path: join22(layout.coreDir, "node_modules"),
|
|
1339
|
+
exists: await pathExists(join22(layout.coreDir, "node_modules"))
|
|
1340
|
+
};
|
|
1341
|
+
results.push(coreCheck);
|
|
1342
|
+
for (const app of manifest.applications) {
|
|
1343
|
+
const appDir = resolve4(layout.coreDir, app.localPath);
|
|
1344
|
+
const nodeModulesPath = join22(appDir, "node_modules");
|
|
1345
|
+
results.push({
|
|
1346
|
+
name: app.name,
|
|
1347
|
+
path: nodeModulesPath,
|
|
1348
|
+
exists: await pathExists(nodeModulesPath)
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
return results;
|
|
1352
|
+
}
|
|
1353
|
+
async function checkBuilt(layout, manifest) {
|
|
1354
|
+
const results = [];
|
|
1355
|
+
const coreTurboPath = join22(layout.coreDir, ".turbo");
|
|
1356
|
+
results.push({
|
|
1357
|
+
name: layout.coreDirName,
|
|
1358
|
+
path: coreTurboPath,
|
|
1359
|
+
exists: await pathExists(coreTurboPath)
|
|
1360
|
+
});
|
|
1361
|
+
for (const app of manifest.applications) {
|
|
1362
|
+
const appDir = resolve4(layout.coreDir, app.localPath);
|
|
1363
|
+
const appTurboPath = join22(appDir, ".turbo");
|
|
1364
|
+
results.push({
|
|
1365
|
+
name: app.name,
|
|
1366
|
+
path: appTurboPath,
|
|
1367
|
+
exists: await pathExists(appTurboPath)
|
|
1368
|
+
});
|
|
1369
|
+
}
|
|
1370
|
+
return results;
|
|
1371
|
+
}
|
|
1372
|
+
async function resolveComposeFiles(layout, manifest) {
|
|
1373
|
+
const { localDir, coreDirName } = layout;
|
|
1374
|
+
const platformName = manifest.product.name;
|
|
1375
|
+
const files = [join22(localDir, "platform-docker-compose.yml")];
|
|
1376
|
+
const prefixedCore = join22(localDir, `${coreDirName}-docker-compose.yml`);
|
|
1377
|
+
const unprefixedCore = join22(localDir, "core-docker-compose.yml");
|
|
1378
|
+
files.push(await pathExists(prefixedCore) ? prefixedCore : unprefixedCore);
|
|
1379
|
+
for (const app of manifest.applications) {
|
|
1380
|
+
const prefixed = join22(localDir, `${platformName}-${app.name}-docker-compose.yml`);
|
|
1381
|
+
const unprefixed = join22(localDir, `${app.name}-docker-compose.yml`);
|
|
1382
|
+
if (await pathExists(prefixed)) files.push(prefixed);
|
|
1383
|
+
else if (await pathExists(unprefixed)) files.push(unprefixed);
|
|
1384
|
+
}
|
|
1385
|
+
return files;
|
|
1386
|
+
}
|
|
1387
|
+
async function checkContainers(layout, manifest) {
|
|
1388
|
+
const platformName = manifest.product.name;
|
|
1389
|
+
const projectName = `${platformName}-platform`;
|
|
1390
|
+
const composeFiles = await resolveComposeFiles(layout, manifest);
|
|
1391
|
+
const fileArgs = composeFiles.flatMap((f) => ["-f", f]);
|
|
1392
|
+
const { code: cfgCode, stdout: cfgOut } = await spawnCapture(
|
|
1393
|
+
"docker",
|
|
1394
|
+
["compose", "-p", projectName, ...fileArgs, "config", "--services"],
|
|
1395
|
+
layout.rootDir
|
|
1396
|
+
);
|
|
1397
|
+
const expectedServices = cfgCode === 0 ? cfgOut.trim().split("\n").map((s) => s.trim()).filter(Boolean) : [];
|
|
1398
|
+
const { stdout: psOut } = await spawnCapture(
|
|
1399
|
+
"docker",
|
|
1400
|
+
["compose", "-p", projectName, ...fileArgs, "ps", "--format", "json"],
|
|
1401
|
+
layout.rootDir
|
|
1402
|
+
);
|
|
1403
|
+
const runningByService = /* @__PURE__ */ new Map();
|
|
1404
|
+
for (const line of psOut.trim().split("\n")) {
|
|
1405
|
+
const trimmed = line.trim();
|
|
1406
|
+
if (!trimmed) continue;
|
|
1407
|
+
try {
|
|
1408
|
+
const obj = JSON.parse(trimmed);
|
|
1409
|
+
const service = String(obj["Service"] ?? obj["Name"] ?? "");
|
|
1410
|
+
if (service) {
|
|
1411
|
+
runningByService.set(service, {
|
|
1412
|
+
state: String(obj["State"] ?? "unknown").toLowerCase(),
|
|
1413
|
+
ports: String(obj["Publishers"] ? formatPublishers(obj["Publishers"]) : obj["Ports"] ?? "")
|
|
1414
|
+
});
|
|
1415
|
+
}
|
|
1416
|
+
} catch {
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
if (expectedServices.length > 0) {
|
|
1420
|
+
return expectedServices.map((service) => {
|
|
1421
|
+
const actual = runningByService.get(service);
|
|
1422
|
+
return {
|
|
1423
|
+
name: service,
|
|
1424
|
+
state: actual ? actual.state : "not started",
|
|
1425
|
+
ports: actual ? actual.ports : ""
|
|
1426
|
+
};
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
1429
|
+
return Array.from(runningByService.entries()).map(([name, info]) => ({
|
|
1430
|
+
name,
|
|
1431
|
+
...info
|
|
1432
|
+
}));
|
|
1433
|
+
}
|
|
1434
|
+
function formatPublishers(publishers) {
|
|
1435
|
+
if (!Array.isArray(publishers)) return "";
|
|
1436
|
+
return publishers.map((p) => {
|
|
1437
|
+
if (typeof p !== "object" || p === null) return "";
|
|
1438
|
+
const pub = p;
|
|
1439
|
+
const host = pub["URL"] ?? "0.0.0.0";
|
|
1440
|
+
const published = pub["PublishedPort"];
|
|
1441
|
+
const target = pub["TargetPort"];
|
|
1442
|
+
const proto = pub["Protocol"] ?? "tcp";
|
|
1443
|
+
if (!published || !target) return "";
|
|
1444
|
+
return `${host}:${published}->${target}/${proto}`;
|
|
1445
|
+
}).filter(Boolean).join(", ");
|
|
1446
|
+
}
|
|
1447
|
+
function computeNextStep(result) {
|
|
1448
|
+
if (!result.lifecycle.initialized) return "init";
|
|
1449
|
+
if (!result.lifecycle.installed) return "install";
|
|
1450
|
+
if (!result.lifecycle.built) return "build";
|
|
1451
|
+
if (!result.lifecycle.running) return "start";
|
|
1452
|
+
return null;
|
|
1453
|
+
}
|
|
1454
|
+
async function gatherStatus() {
|
|
1455
|
+
const layout = await findPlatformLayout();
|
|
1456
|
+
const [nodeCheck, dockerCheck] = await Promise.all([checkNodeVersion(), checkDocker()]);
|
|
1457
|
+
const prerequisites = [nodeCheck, dockerCheck];
|
|
1458
|
+
if (!layout) {
|
|
1459
|
+
return {
|
|
1460
|
+
projectInfo: null,
|
|
1461
|
+
prerequisites,
|
|
1462
|
+
lifecycle: {
|
|
1463
|
+
initialized: false,
|
|
1464
|
+
installed: false,
|
|
1465
|
+
installedDetails: [],
|
|
1466
|
+
built: false,
|
|
1467
|
+
builtDetails: [],
|
|
1468
|
+
running: false
|
|
1469
|
+
},
|
|
1470
|
+
containers: []
|
|
1471
|
+
};
|
|
1472
|
+
}
|
|
1473
|
+
let manifest;
|
|
1474
|
+
try {
|
|
1475
|
+
manifest = await readManifest(layout.rootDir, layout.coreDirName);
|
|
1476
|
+
} catch {
|
|
1477
|
+
return {
|
|
1478
|
+
projectInfo: null,
|
|
1479
|
+
prerequisites,
|
|
1480
|
+
lifecycle: {
|
|
1481
|
+
initialized: true,
|
|
1482
|
+
installed: false,
|
|
1483
|
+
installedDetails: [],
|
|
1484
|
+
built: false,
|
|
1485
|
+
builtDetails: [],
|
|
1486
|
+
running: false
|
|
1487
|
+
},
|
|
1488
|
+
containers: []
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
const [installedDetails, builtDetails, containers] = await Promise.all([
|
|
1492
|
+
checkInstalled(layout, manifest),
|
|
1493
|
+
checkBuilt(layout, manifest),
|
|
1494
|
+
checkContainers(layout, manifest)
|
|
1495
|
+
]);
|
|
1496
|
+
const projectInfo = {
|
|
1497
|
+
productName: manifest.product.name,
|
|
1498
|
+
displayName: manifest.product.displayName,
|
|
1499
|
+
organization: manifest.product.organization,
|
|
1500
|
+
scope: manifest.product.scope,
|
|
1501
|
+
applicationCount: manifest.applications.length,
|
|
1502
|
+
applicationNames: manifest.applications.map((a) => a.name)
|
|
1503
|
+
};
|
|
1504
|
+
return {
|
|
1505
|
+
projectInfo,
|
|
1506
|
+
prerequisites,
|
|
1507
|
+
lifecycle: {
|
|
1508
|
+
initialized: true,
|
|
1509
|
+
installed: installedDetails.every((d) => d.exists),
|
|
1510
|
+
installedDetails,
|
|
1511
|
+
built: builtDetails.every((d) => d.exists),
|
|
1512
|
+
builtDetails,
|
|
1513
|
+
running: containers.length > 0 && containers.every((c) => c.state === "running")
|
|
1514
|
+
},
|
|
1515
|
+
containers
|
|
1516
|
+
};
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
// src/commands/status/status.command.ts
|
|
1520
|
+
var STATUS_COMMAND_NAME = "status";
|
|
1521
|
+
var statusCommand = {
|
|
1522
|
+
name: STATUS_COMMAND_NAME,
|
|
1523
|
+
description: "Show platform status and health checks"
|
|
1524
|
+
};
|
|
1525
|
+
|
|
1228
1526
|
// src/commands/registry.ts
|
|
1229
1527
|
var CommandRegistry = class {
|
|
1230
1528
|
commands = /* @__PURE__ */ new Map();
|
|
@@ -1245,6 +1543,14 @@ var CommandRegistry = class {
|
|
|
1245
1543
|
});
|
|
1246
1544
|
return fzf.find(query).map((result) => result.item);
|
|
1247
1545
|
}
|
|
1546
|
+
searchVisible(query, visibilityCtx) {
|
|
1547
|
+
const visible = this.getAll().filter((cmd) => !cmd.hidden?.(visibilityCtx));
|
|
1548
|
+
if (!query) return visible;
|
|
1549
|
+
const fzf = new Fzf(visible, {
|
|
1550
|
+
selector: (cmd) => `${cmd.name} ${cmd.description}`
|
|
1551
|
+
});
|
|
1552
|
+
return fzf.find(query).map((result) => result.item);
|
|
1553
|
+
}
|
|
1248
1554
|
};
|
|
1249
1555
|
var registry = new CommandRegistry();
|
|
1250
1556
|
registry.register(createApplicationCommand);
|
|
@@ -1252,6 +1558,7 @@ registry.register(initCommand);
|
|
|
1252
1558
|
registry.register(configureIdpCommand);
|
|
1253
1559
|
registry.register(createServiceModuleCommand);
|
|
1254
1560
|
registry.register(createUiModuleCommand);
|
|
1561
|
+
registry.register(statusCommand);
|
|
1255
1562
|
|
|
1256
1563
|
// src/app-state.ts
|
|
1257
1564
|
var APP_STATE = {
|
|
@@ -1282,23 +1589,15 @@ async function createApplicationUiController(ctx) {
|
|
|
1282
1589
|
}
|
|
1283
1590
|
const applicationDisplayName = await ctx.prompt("Application display name:");
|
|
1284
1591
|
const applicationDescription = await ctx.prompt("Application description:");
|
|
1285
|
-
const
|
|
1286
|
-
|
|
1287
|
-
ctx.log(`Error: Please answer "yes" or "no".`);
|
|
1288
|
-
return;
|
|
1289
|
-
}
|
|
1290
|
-
const hasBackendServiceAnswer = await ctx.prompt("Does this application have a backend service? (yes/no):");
|
|
1291
|
-
if (hasBackendServiceAnswer !== "yes" && hasBackendServiceAnswer !== "no") {
|
|
1292
|
-
ctx.log(`Error: Please answer "yes" or "no".`);
|
|
1293
|
-
return;
|
|
1294
|
-
}
|
|
1592
|
+
const hasUserInterface = await ctx.confirm("Does this application have a user interface?");
|
|
1593
|
+
const hasBackendService = await ctx.confirm("Does this application have a backend service?");
|
|
1295
1594
|
await createApplicationService(
|
|
1296
1595
|
{
|
|
1297
1596
|
applicationName,
|
|
1298
1597
|
applicationDisplayName,
|
|
1299
1598
|
applicationDescription,
|
|
1300
|
-
hasUserInterface
|
|
1301
|
-
hasBackendService
|
|
1599
|
+
hasUserInterface,
|
|
1600
|
+
hasBackendService
|
|
1302
1601
|
},
|
|
1303
1602
|
ctx
|
|
1304
1603
|
);
|
|
@@ -1318,7 +1617,32 @@ async function initUiController(ctx) {
|
|
|
1318
1617
|
const organizationName = await ctx.prompt("Organization name:");
|
|
1319
1618
|
const platformName = await ctx.prompt("Platform name:");
|
|
1320
1619
|
const platformDisplayName = await ctx.prompt("Platform display name:");
|
|
1321
|
-
|
|
1620
|
+
const defaultBootstrapSuffix = "bootstrap-service";
|
|
1621
|
+
const defaultCustomizationUiSuffix = "customization-ui";
|
|
1622
|
+
ctx.log(`Default artifact names:`);
|
|
1623
|
+
ctx.log(` Bootstrap service: ${platformName}-${defaultBootstrapSuffix}`);
|
|
1624
|
+
ctx.log(` Customization UI: ${platformName}-${defaultCustomizationUiSuffix}`);
|
|
1625
|
+
const customize = await ctx.confirm("Customize artifact names?", false);
|
|
1626
|
+
let bootstrapServiceSuffix = defaultBootstrapSuffix;
|
|
1627
|
+
let customizationUiSuffix = defaultCustomizationUiSuffix;
|
|
1628
|
+
if (customize) {
|
|
1629
|
+
const bsSuffix = await ctx.prompt(`Bootstrap service suffix (${platformName}-...):`, defaultBootstrapSuffix);
|
|
1630
|
+
if (!/^[a-z0-9-]+$/.test(bsSuffix)) {
|
|
1631
|
+
ctx.log(`Error: Suffix "${bsSuffix}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
bootstrapServiceSuffix = bsSuffix;
|
|
1635
|
+
const cuiSuffix = await ctx.prompt(`Customization UI suffix (${platformName}-...):`, defaultCustomizationUiSuffix);
|
|
1636
|
+
if (!/^[a-z0-9-]+$/.test(cuiSuffix)) {
|
|
1637
|
+
ctx.log(`Error: Suffix "${cuiSuffix}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
customizationUiSuffix = cuiSuffix;
|
|
1641
|
+
}
|
|
1642
|
+
await initService(
|
|
1643
|
+
{ organizationName, platformName, platformDisplayName, bootstrapServiceSuffix, customizationUiSuffix },
|
|
1644
|
+
ctx
|
|
1645
|
+
);
|
|
1322
1646
|
}
|
|
1323
1647
|
|
|
1324
1648
|
// src/services/configure-idp.service.ts
|
|
@@ -1333,14 +1657,11 @@ async function configureIdpUiController(ctx) {
|
|
|
1333
1657
|
return;
|
|
1334
1658
|
}
|
|
1335
1659
|
const providers = getAllProviders();
|
|
1336
|
-
const
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
return;
|
|
1342
|
-
}
|
|
1343
|
-
const provider = providers[index];
|
|
1660
|
+
const providerType = await ctx.select(
|
|
1661
|
+
"Select IDP type:",
|
|
1662
|
+
providers.map((p) => ({ label: p.displayName, value: p.type }))
|
|
1663
|
+
);
|
|
1664
|
+
const provider = providers.find((p) => p.type === providerType);
|
|
1344
1665
|
const name = await ctx.prompt("Provider name:");
|
|
1345
1666
|
const issuer = await ctx.prompt("Issuer URL:");
|
|
1346
1667
|
const clientId = await ctx.prompt("Client ID:");
|
|
@@ -1366,19 +1687,38 @@ async function createServiceModuleUiController(ctx) {
|
|
|
1366
1687
|
ctx.log("Error: Cannot create a service module \u2014 no platform initialized in this directory.");
|
|
1367
1688
|
return;
|
|
1368
1689
|
}
|
|
1369
|
-
const
|
|
1370
|
-
if (
|
|
1371
|
-
ctx.log(
|
|
1690
|
+
const layout = await findPlatformLayout();
|
|
1691
|
+
if (!layout) {
|
|
1692
|
+
ctx.log("Error: Cannot create a service module \u2014 no platform initialized in this directory.");
|
|
1372
1693
|
return;
|
|
1373
1694
|
}
|
|
1695
|
+
let manifest = await readManifest(layout.rootDir, layout.coreDirName);
|
|
1696
|
+
if (manifest.applications.length === 0) {
|
|
1697
|
+
ctx.log("No applications found in this platform.");
|
|
1698
|
+
const createFirst = await ctx.confirm("Would you like to create an application first?");
|
|
1699
|
+
if (!createFirst) return;
|
|
1700
|
+
await createApplicationUiController(ctx);
|
|
1701
|
+
manifest = await readManifest(layout.rootDir, layout.coreDirName);
|
|
1702
|
+
if (manifest.applications.length === 0) {
|
|
1703
|
+
ctx.log("Error: No applications available after creation attempt.");
|
|
1704
|
+
return;
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
const applicationName = await ctx.select(
|
|
1708
|
+
"Select application:",
|
|
1709
|
+
manifest.applications.map((a) => ({ label: `${a.name} \u2014 ${a.displayName}`, value: a.name }))
|
|
1710
|
+
);
|
|
1374
1711
|
const serviceName = await ctx.prompt("Service name:");
|
|
1375
1712
|
if (!/^[a-z0-9-]+$/.test(serviceName)) {
|
|
1376
1713
|
ctx.log(`Error: Service name "${serviceName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
|
|
1377
1714
|
return;
|
|
1378
1715
|
}
|
|
1716
|
+
const platformName = manifest.product.name;
|
|
1717
|
+
const includeSuffix = await ctx.confirm(`Include "-service" suffix? (${platformName}-${serviceName}-service)`, true);
|
|
1718
|
+
const serviceNameSuffix = includeSuffix ? "service" : null;
|
|
1379
1719
|
const serviceDisplayName = await ctx.prompt("Service display name:");
|
|
1380
1720
|
await createServiceModuleService(
|
|
1381
|
-
{ applicationName, serviceName, serviceDisplayName },
|
|
1721
|
+
{ applicationName, serviceName, serviceDisplayName, serviceNameSuffix },
|
|
1382
1722
|
ctx
|
|
1383
1723
|
);
|
|
1384
1724
|
}
|
|
@@ -1394,313 +1734,124 @@ async function createUiModuleUiController(ctx) {
|
|
|
1394
1734
|
ctx.log("Error: Cannot create a UI module \u2014 no platform initialized in this directory.");
|
|
1395
1735
|
return;
|
|
1396
1736
|
}
|
|
1397
|
-
const
|
|
1398
|
-
if (
|
|
1399
|
-
ctx.log(
|
|
1737
|
+
const layout = await findPlatformLayout();
|
|
1738
|
+
if (!layout) {
|
|
1739
|
+
ctx.log("Error: Cannot create a UI module \u2014 no platform initialized in this directory.");
|
|
1400
1740
|
return;
|
|
1401
1741
|
}
|
|
1742
|
+
let manifest = await readManifest(layout.rootDir, layout.coreDirName);
|
|
1743
|
+
if (manifest.applications.length === 0) {
|
|
1744
|
+
ctx.log("No applications found in this platform.");
|
|
1745
|
+
const createFirst = await ctx.confirm("Would you like to create an application first?");
|
|
1746
|
+
if (!createFirst) return;
|
|
1747
|
+
await createApplicationUiController(ctx);
|
|
1748
|
+
manifest = await readManifest(layout.rootDir, layout.coreDirName);
|
|
1749
|
+
if (manifest.applications.length === 0) {
|
|
1750
|
+
ctx.log("Error: No applications available after creation attempt.");
|
|
1751
|
+
return;
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
const applicationName = await ctx.select(
|
|
1755
|
+
"Select application:",
|
|
1756
|
+
manifest.applications.map((a) => ({ label: `${a.name} \u2014 ${a.displayName}`, value: a.name }))
|
|
1757
|
+
);
|
|
1758
|
+
const platformName = manifest.product.name;
|
|
1759
|
+
const includeSuffix = await ctx.confirm(`Include "-ui" suffix? (${platformName}-${applicationName}-ui)`, true);
|
|
1760
|
+
const uiModuleSuffix = includeSuffix ? "ui" : null;
|
|
1402
1761
|
const applicationDisplayName = await ctx.prompt("Application display name:");
|
|
1403
1762
|
await createUiModuleService(
|
|
1404
|
-
{ applicationName, applicationDisplayName },
|
|
1763
|
+
{ applicationName, applicationDisplayName, uiModuleSuffix },
|
|
1405
1764
|
ctx
|
|
1406
1765
|
);
|
|
1407
1766
|
}
|
|
1408
1767
|
|
|
1409
|
-
// src/
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1768
|
+
// src/services/status.service.ts
|
|
1769
|
+
async function statusService() {
|
|
1770
|
+
return gatherStatus();
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
// src/utils/theme.ts
|
|
1774
|
+
import chalk from "chalk";
|
|
1775
|
+
var theme = {
|
|
1776
|
+
prompt: chalk.green,
|
|
1777
|
+
commandName: chalk.cyan,
|
|
1778
|
+
commandDescription: chalk.gray,
|
|
1779
|
+
selected: chalk.bgBlue.white,
|
|
1780
|
+
output: chalk.white,
|
|
1781
|
+
muted: chalk.dim,
|
|
1782
|
+
error: chalk.red,
|
|
1783
|
+
success: chalk.green,
|
|
1784
|
+
warning: chalk.yellow,
|
|
1785
|
+
info: chalk.cyan,
|
|
1786
|
+
section: chalk.bold.white,
|
|
1787
|
+
label: chalk.dim
|
|
1788
|
+
};
|
|
1417
1789
|
|
|
1418
|
-
// src/
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
const
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
(
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
setPromptValue("");
|
|
1451
|
-
promptResolveRef.current = resolve5;
|
|
1452
|
-
setState(APP_STATE.PROMPTING);
|
|
1453
|
-
controller.signal.addEventListener(
|
|
1454
|
-
"abort",
|
|
1455
|
-
() => reject(new DOMException("Aborted", "AbortError")),
|
|
1456
|
-
{ once: true }
|
|
1457
|
-
);
|
|
1458
|
-
});
|
|
1459
|
-
}
|
|
1460
|
-
};
|
|
1461
|
-
const uiController = uiControllers.get(cmd.name);
|
|
1462
|
-
if (!uiController) {
|
|
1463
|
-
const id = `output-${outputCountRef.current++}`;
|
|
1464
|
-
appendStaticItem({ kind: "output", id, line: `Error: No controller found for "${cmd.name}"` });
|
|
1465
|
-
setState(APP_STATE.IDLE);
|
|
1466
|
-
return;
|
|
1790
|
+
// src/commands/status/status-formatter.ts
|
|
1791
|
+
var CHECK = theme.success("\u2713");
|
|
1792
|
+
var CROSS = theme.error("\u2717");
|
|
1793
|
+
function tick(ok) {
|
|
1794
|
+
return ok ? CHECK : CROSS;
|
|
1795
|
+
}
|
|
1796
|
+
function formatStatusLines(result) {
|
|
1797
|
+
const lines = [];
|
|
1798
|
+
if (result.projectInfo) {
|
|
1799
|
+
const p = result.projectInfo;
|
|
1800
|
+
lines.push(theme.info("\u25B8 ") + theme.section(p.displayName) + theme.label(` (${p.productName})`));
|
|
1801
|
+
lines.push(theme.label(" Organization: ") + p.organization + theme.label(` (${p.scope})`));
|
|
1802
|
+
const appList = p.applicationNames.length > 0 ? p.applicationNames.join(", ") : "(none)";
|
|
1803
|
+
lines.push(theme.label(" Applications: ") + `${p.applicationCount} \u2014 ` + appList);
|
|
1804
|
+
lines.push("");
|
|
1805
|
+
}
|
|
1806
|
+
lines.push(theme.section("Prerequisites"));
|
|
1807
|
+
for (const pre of result.prerequisites) {
|
|
1808
|
+
const ok = pre.available && pre.versionOk;
|
|
1809
|
+
const versionStr = pre.version ? theme.label(` ${pre.version}`) : "";
|
|
1810
|
+
const detailStr = pre.detail ? theme.warning(` \u2014 ${pre.detail}`) : "";
|
|
1811
|
+
lines.push(` ${tick(ok)} ${pre.name}${versionStr}${detailStr}`);
|
|
1812
|
+
}
|
|
1813
|
+
lines.push("");
|
|
1814
|
+
lines.push(theme.section("Lifecycle"));
|
|
1815
|
+
lines.push(` ${tick(result.lifecycle.initialized)} Platform initialized`);
|
|
1816
|
+
const installed = result.lifecycle.installed;
|
|
1817
|
+
lines.push(` ${tick(installed)} Dependencies installed`);
|
|
1818
|
+
if (!installed && result.lifecycle.installedDetails.length > 0) {
|
|
1819
|
+
for (const d of result.lifecycle.installedDetails) {
|
|
1820
|
+
if (!d.exists) {
|
|
1821
|
+
lines.push(` ${CROSS} ${theme.label(d.name)} ${theme.warning("(missing node_modules/)")}`);
|
|
1467
1822
|
}
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
)
|
|
1481
|
-
|
|
1482
|
-
(
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
return
|
|
1491
|
-
runCommand: runCommand2,
|
|
1492
|
-
handlePromptSubmit,
|
|
1493
|
-
abortExecution,
|
|
1494
|
-
promptMessage,
|
|
1495
|
-
promptValue,
|
|
1496
|
-
setPromptValue
|
|
1497
|
-
};
|
|
1498
|
-
}
|
|
1499
|
-
|
|
1500
|
-
// src/app.tsx
|
|
1501
|
-
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1502
|
-
var require2 = createRequire(import.meta.url);
|
|
1503
|
-
var { version } = require2("../package.json");
|
|
1504
|
-
function App() {
|
|
1505
|
-
const { exit } = useApp();
|
|
1506
|
-
const [state, setState] = useState3(APP_STATE.IDLE);
|
|
1507
|
-
const [inputValue, setInputValue] = useState3("");
|
|
1508
|
-
const [selectedIndex, setSelectedIndex] = useState3(0);
|
|
1509
|
-
const [staticItems, setStaticItems] = useState3([{ kind: "banner", id: "banner" }]);
|
|
1510
|
-
const appendStaticItem = useCallback2((item) => {
|
|
1511
|
-
setStaticItems((prev) => [...prev, item]);
|
|
1512
|
-
}, []);
|
|
1513
|
-
const { runCommand: runCommand2, handlePromptSubmit, abortExecution, promptMessage, promptValue, setPromptValue } = useCommandRunner({ appendStaticItem, setState });
|
|
1514
|
-
const query = inputValue.startsWith("/") ? inputValue.slice(1) : "";
|
|
1515
|
-
const filteredCommands = registry.search(query);
|
|
1516
|
-
useInput(
|
|
1517
|
-
(input, key) => {
|
|
1518
|
-
if (key.ctrl && input === "c") {
|
|
1519
|
-
exit();
|
|
1520
|
-
return;
|
|
1521
|
-
}
|
|
1522
|
-
if ((state === APP_STATE.EXECUTING || state === APP_STATE.PROMPTING) && key.escape) {
|
|
1523
|
-
abortExecution();
|
|
1524
|
-
setState(APP_STATE.IDLE);
|
|
1525
|
-
return;
|
|
1526
|
-
}
|
|
1527
|
-
if (state === APP_STATE.PALETTE) {
|
|
1528
|
-
if (key.escape) {
|
|
1529
|
-
setState(APP_STATE.IDLE);
|
|
1530
|
-
setInputValue("");
|
|
1531
|
-
return;
|
|
1532
|
-
}
|
|
1533
|
-
if (key.upArrow) {
|
|
1534
|
-
setSelectedIndex((prev) => Math.max(0, prev - 1));
|
|
1535
|
-
return;
|
|
1536
|
-
}
|
|
1537
|
-
if (key.downArrow) {
|
|
1538
|
-
setSelectedIndex((prev) => Math.min(filteredCommands.length - 1, prev + 1));
|
|
1539
|
-
return;
|
|
1540
|
-
}
|
|
1541
|
-
if (key.return) {
|
|
1542
|
-
const cmd = filteredCommands[selectedIndex];
|
|
1543
|
-
if (cmd) {
|
|
1544
|
-
setInputValue("");
|
|
1545
|
-
runCommand2(cmd);
|
|
1546
|
-
}
|
|
1547
|
-
return;
|
|
1548
|
-
}
|
|
1549
|
-
if (key.backspace || key.delete) {
|
|
1550
|
-
setInputValue((prev) => {
|
|
1551
|
-
const next = prev.slice(0, -1);
|
|
1552
|
-
if (!next.startsWith("/")) {
|
|
1553
|
-
setState(APP_STATE.IDLE);
|
|
1554
|
-
return "";
|
|
1555
|
-
}
|
|
1556
|
-
return next;
|
|
1557
|
-
});
|
|
1558
|
-
setSelectedIndex(0);
|
|
1559
|
-
return;
|
|
1560
|
-
}
|
|
1561
|
-
if (input && !key.ctrl && !key.meta) {
|
|
1562
|
-
setInputValue((prev) => prev + input);
|
|
1563
|
-
setSelectedIndex(0);
|
|
1564
|
-
}
|
|
1565
|
-
}
|
|
1566
|
-
},
|
|
1567
|
-
{ isActive: true }
|
|
1568
|
-
);
|
|
1569
|
-
const handleIdleInputChange = useCallback2((value) => {
|
|
1570
|
-
setInputValue(value);
|
|
1571
|
-
if (value.startsWith("/")) {
|
|
1572
|
-
setState(APP_STATE.PALETTE);
|
|
1573
|
-
setSelectedIndex(0);
|
|
1574
|
-
}
|
|
1575
|
-
}, []);
|
|
1576
|
-
const handleIdleSubmit = useCallback2(
|
|
1577
|
-
(value) => {
|
|
1578
|
-
if (!value.startsWith("/")) return;
|
|
1579
|
-
const name = value.slice(1).trim();
|
|
1580
|
-
const cmd = registry.get(name);
|
|
1581
|
-
if (cmd) runCommand2(cmd);
|
|
1582
|
-
},
|
|
1583
|
-
[runCommand2]
|
|
1584
|
-
);
|
|
1585
|
-
function renderActiveArea() {
|
|
1586
|
-
switch (state) {
|
|
1587
|
-
case APP_STATE.EXECUTING:
|
|
1588
|
-
return /* @__PURE__ */ jsxs6(Box4, { flexDirection: "row", gap: 1, children: [
|
|
1589
|
-
/* @__PURE__ */ jsx7(Spinner, { label: "Running\u2026" }),
|
|
1590
|
-
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "(esc to cancel)" })
|
|
1591
|
-
] });
|
|
1592
|
-
case APP_STATE.PROMPTING:
|
|
1593
|
-
return /* @__PURE__ */ jsxs6(Box4, { flexDirection: "column", children: [
|
|
1594
|
-
/* @__PURE__ */ jsx7(Text7, { color: "cyan", children: promptMessage }),
|
|
1595
|
-
/* @__PURE__ */ jsx7(Prompt, { value: promptValue, onChange: setPromptValue, onSubmit: handlePromptSubmit })
|
|
1596
|
-
] });
|
|
1597
|
-
default:
|
|
1598
|
-
return /* @__PURE__ */ jsx7(
|
|
1599
|
-
Prompt,
|
|
1600
|
-
{
|
|
1601
|
-
value: inputValue,
|
|
1602
|
-
onChange: state === APP_STATE.IDLE ? handleIdleInputChange : () => {
|
|
1603
|
-
},
|
|
1604
|
-
onSubmit: state === APP_STATE.IDLE ? handleIdleSubmit : () => {
|
|
1605
|
-
},
|
|
1606
|
-
disabled: state === APP_STATE.PALETTE
|
|
1607
|
-
}
|
|
1608
|
-
);
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1611
|
-
return /* @__PURE__ */ jsxs6(Box4, { flexDirection: "column", children: [
|
|
1612
|
-
/* @__PURE__ */ jsx7(ScrollbackHistory, { items: staticItems, version }),
|
|
1613
|
-
renderActiveArea(),
|
|
1614
|
-
state === APP_STATE.PALETTE && /* @__PURE__ */ jsx7(CommandPalette, { commands: filteredCommands, selectedIndex })
|
|
1615
|
-
] });
|
|
1616
|
-
}
|
|
1617
|
-
|
|
1618
|
-
// src/controllers/cli/create-application.cli-controller.ts
|
|
1619
|
-
async function createApplicationCliController(args2) {
|
|
1620
|
-
if (!await isPlatformInitialized()) {
|
|
1621
|
-
console.error("Error: Cannot create an application \u2014 no platform initialized in this directory.");
|
|
1622
|
-
process.exit(1);
|
|
1623
|
-
}
|
|
1624
|
-
const {
|
|
1625
|
-
applicationName,
|
|
1626
|
-
applicationDisplayName,
|
|
1627
|
-
applicationDescription,
|
|
1628
|
-
hasUserInterface,
|
|
1629
|
-
hasBackendService
|
|
1630
|
-
} = args2;
|
|
1631
|
-
if (!applicationName || !applicationDisplayName || !applicationDescription) {
|
|
1632
|
-
console.error("Error: applicationName, applicationDisplayName, and applicationDescription are required.");
|
|
1633
|
-
process.exit(1);
|
|
1634
|
-
}
|
|
1635
|
-
if (!/^[a-z0-9-]+$/.test(applicationName)) {
|
|
1636
|
-
console.error(`Error: Application name "${applicationName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
|
|
1637
|
-
process.exit(1);
|
|
1638
|
-
}
|
|
1639
|
-
await createApplicationService(
|
|
1640
|
-
{
|
|
1641
|
-
applicationName,
|
|
1642
|
-
applicationDisplayName,
|
|
1643
|
-
applicationDescription,
|
|
1644
|
-
hasUserInterface: hasUserInterface === "yes",
|
|
1645
|
-
hasBackendService: hasBackendService === "yes"
|
|
1646
|
-
},
|
|
1647
|
-
{ log: console.log }
|
|
1648
|
-
);
|
|
1649
|
-
}
|
|
1650
|
-
|
|
1651
|
-
// src/controllers/cli/init.cli-controller.ts
|
|
1652
|
-
async function initCliController(args2) {
|
|
1653
|
-
if (await isPlatformInitialized()) {
|
|
1654
|
-
console.error("Error: Cannot initialize a new platform \u2014 a platform is already initialized in this directory.");
|
|
1655
|
-
process.exit(1);
|
|
1656
|
-
}
|
|
1657
|
-
const { organizationName, platformName, platformDisplayName } = args2;
|
|
1658
|
-
if (!organizationName || !platformName || !platformDisplayName) {
|
|
1659
|
-
console.error("Error: organizationName, platformName, and platformDisplayName are required.");
|
|
1660
|
-
process.exit(1);
|
|
1661
|
-
}
|
|
1662
|
-
await initService(
|
|
1663
|
-
{ organizationName, platformName, platformDisplayName },
|
|
1664
|
-
{ log: console.log }
|
|
1665
|
-
);
|
|
1666
|
-
}
|
|
1667
|
-
|
|
1668
|
-
// src/controllers/cli/configure-idp.cli-controller.ts
|
|
1669
|
-
async function configureIdpCliController(args2) {
|
|
1670
|
-
const logger = { log: console.log };
|
|
1671
|
-
if (!await isPlatformInitialized()) {
|
|
1672
|
-
console.error("Error: Cannot configure an IDP \u2014 no platform initialized in this directory.");
|
|
1673
|
-
process.exit(1);
|
|
1674
|
-
}
|
|
1675
|
-
const { providerType, name, issuer, clientId, clientSecret } = args2;
|
|
1676
|
-
if (!providerType || !name || !issuer || !clientId || !clientSecret) {
|
|
1677
|
-
logger.log("Error: Missing required arguments: providerType, name, issuer, clientId, clientSecret");
|
|
1678
|
-
process.exit(1);
|
|
1679
|
-
}
|
|
1680
|
-
const provider = idpProviderRegistry.get(providerType);
|
|
1681
|
-
if (!provider) {
|
|
1682
|
-
logger.log(`Error: Unknown IDP type "${providerType}"`);
|
|
1683
|
-
process.exit(1);
|
|
1684
|
-
}
|
|
1685
|
-
const extras = {};
|
|
1686
|
-
for (const field of provider.fields) {
|
|
1687
|
-
const value = args2[field.key];
|
|
1688
|
-
if (!value) {
|
|
1689
|
-
logger.log(`Error: Missing required argument for ${providerType}: ${field.key}`);
|
|
1690
|
-
process.exit(1);
|
|
1691
|
-
}
|
|
1692
|
-
extras[field.key] = value;
|
|
1693
|
-
}
|
|
1694
|
-
await configureIdpService({ providerType, name, issuer, clientId, clientSecret, extras }, logger);
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
const built = result.lifecycle.built;
|
|
1826
|
+
lines.push(` ${tick(built)} Build completed`);
|
|
1827
|
+
if (!built && result.lifecycle.builtDetails.length > 0) {
|
|
1828
|
+
for (const d of result.lifecycle.builtDetails) {
|
|
1829
|
+
if (!d.exists) {
|
|
1830
|
+
lines.push(` ${CROSS} ${theme.label(d.name)} ${theme.warning("(missing .turbo/)")}`);
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
lines.push(` ${tick(result.lifecycle.running)} Environment running`);
|
|
1835
|
+
if (result.containers.length > 0) {
|
|
1836
|
+
lines.push("");
|
|
1837
|
+
lines.push(theme.section("Containers"));
|
|
1838
|
+
for (const c of result.containers) {
|
|
1839
|
+
const ok = c.state === "running";
|
|
1840
|
+
const stateStr = ok ? theme.success(c.state) : c.state === "not started" ? theme.label(c.state) : theme.error(c.state);
|
|
1841
|
+
const ports = c.ports ? theme.label(` ${c.ports}`) : "";
|
|
1842
|
+
lines.push(` ${tick(ok)} ${c.name.padEnd(35)} ${stateStr}${ports}`);
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
return lines;
|
|
1695
1846
|
}
|
|
1696
1847
|
|
|
1697
1848
|
// src/commands/local-scripts/docker-compose-orchestrator.ts
|
|
1698
|
-
import { spawn } from "child_process";
|
|
1699
|
-
import { access as
|
|
1700
|
-
import { join as
|
|
1849
|
+
import { spawn as spawn2 } from "child_process";
|
|
1850
|
+
import { access as access6 } from "fs/promises";
|
|
1851
|
+
import { join as join23 } from "path";
|
|
1701
1852
|
function runDockerCompose(args2, logger, rootDir, signal) {
|
|
1702
1853
|
return new Promise((resolvePromise) => {
|
|
1703
|
-
const child =
|
|
1854
|
+
const child = spawn2("docker", ["compose", ...args2], {
|
|
1704
1855
|
shell: false,
|
|
1705
1856
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1706
1857
|
cwd: rootDir
|
|
@@ -1745,15 +1896,15 @@ function runDockerCompose(args2, logger, rootDir, signal) {
|
|
|
1745
1896
|
async function getAppComposePaths(localDir, platformName, manifest) {
|
|
1746
1897
|
const results = [];
|
|
1747
1898
|
for (const app of manifest.applications) {
|
|
1748
|
-
const prefixedPath =
|
|
1749
|
-
const unprefixedPath =
|
|
1899
|
+
const prefixedPath = join23(localDir, `${platformName}-${app.name}-docker-compose.yml`);
|
|
1900
|
+
const unprefixedPath = join23(localDir, `${app.name}-docker-compose.yml`);
|
|
1750
1901
|
let resolved = null;
|
|
1751
1902
|
try {
|
|
1752
|
-
await
|
|
1903
|
+
await access6(prefixedPath);
|
|
1753
1904
|
resolved = prefixedPath;
|
|
1754
1905
|
} catch {
|
|
1755
1906
|
try {
|
|
1756
|
-
await
|
|
1907
|
+
await access6(unprefixedPath);
|
|
1757
1908
|
resolved = unprefixedPath;
|
|
1758
1909
|
} catch {
|
|
1759
1910
|
}
|
|
@@ -1767,18 +1918,18 @@ async function getAppComposePaths(localDir, platformName, manifest) {
|
|
|
1767
1918
|
async function buildAllComposeArgs(layout, manifest, logger) {
|
|
1768
1919
|
const { rootDir, coreDirName, localDir } = layout;
|
|
1769
1920
|
const platformName = manifest.product.name;
|
|
1770
|
-
const prefixedCoreCompose =
|
|
1771
|
-
const unprefixedCoreCompose =
|
|
1921
|
+
const prefixedCoreCompose = join23(localDir, `${coreDirName}-docker-compose.yml`);
|
|
1922
|
+
const unprefixedCoreCompose = join23(localDir, "core-docker-compose.yml");
|
|
1772
1923
|
let coreComposePath;
|
|
1773
1924
|
try {
|
|
1774
|
-
await
|
|
1925
|
+
await access6(prefixedCoreCompose);
|
|
1775
1926
|
coreComposePath = prefixedCoreCompose;
|
|
1776
1927
|
} catch {
|
|
1777
1928
|
coreComposePath = unprefixedCoreCompose;
|
|
1778
1929
|
}
|
|
1779
1930
|
const fileArgs = [
|
|
1780
1931
|
"-f",
|
|
1781
|
-
|
|
1932
|
+
join23(localDir, "platform-docker-compose.yml"),
|
|
1782
1933
|
"-f",
|
|
1783
1934
|
coreComposePath
|
|
1784
1935
|
];
|
|
@@ -1796,7 +1947,7 @@ async function buildAllComposeArgs(layout, manifest, logger) {
|
|
|
1796
1947
|
async function startEnvironment(layout, manifest, logger, signal) {
|
|
1797
1948
|
const { rootDir, localDir } = layout;
|
|
1798
1949
|
const platformName = manifest.product.name;
|
|
1799
|
-
const envFile =
|
|
1950
|
+
const envFile = join23(localDir, ".env");
|
|
1800
1951
|
const fileArgs = await buildAllComposeArgs(layout, manifest, logger);
|
|
1801
1952
|
logger.log("Starting environment...");
|
|
1802
1953
|
await runDockerCompose(
|
|
@@ -1821,7 +1972,7 @@ async function startEnvironment(layout, manifest, logger, signal) {
|
|
|
1821
1972
|
async function stopEnvironment(layout, manifest, logger, signal) {
|
|
1822
1973
|
const { rootDir, localDir } = layout;
|
|
1823
1974
|
const platformName = manifest.product.name;
|
|
1824
|
-
const envFile =
|
|
1975
|
+
const envFile = join23(localDir, ".env");
|
|
1825
1976
|
const fileArgs = await buildAllComposeArgs(layout, manifest, logger);
|
|
1826
1977
|
logger.log("Stopping environment...");
|
|
1827
1978
|
await runDockerCompose(
|
|
@@ -1843,7 +1994,7 @@ async function stopEnvironment(layout, manifest, logger, signal) {
|
|
|
1843
1994
|
async function destroyEnvironment(layout, manifest, logger, signal) {
|
|
1844
1995
|
const { rootDir, localDir } = layout;
|
|
1845
1996
|
const platformName = manifest.product.name;
|
|
1846
|
-
const envFile =
|
|
1997
|
+
const envFile = join23(localDir, ".env");
|
|
1847
1998
|
const fileArgs = await buildAllComposeArgs(layout, manifest, logger);
|
|
1848
1999
|
logger.log("Destroying environment...");
|
|
1849
2000
|
await runDockerCompose(
|
|
@@ -1867,12 +2018,12 @@ async function destroyEnvironment(layout, manifest, logger, signal) {
|
|
|
1867
2018
|
}
|
|
1868
2019
|
|
|
1869
2020
|
// src/commands/local-scripts/npm-orchestrator.ts
|
|
1870
|
-
import { spawn as
|
|
1871
|
-
import { access as
|
|
1872
|
-
import { resolve as
|
|
2021
|
+
import { spawn as spawn3 } from "child_process";
|
|
2022
|
+
import { access as access7 } from "fs/promises";
|
|
2023
|
+
import { resolve as resolve5, join as join24 } from "path";
|
|
1873
2024
|
function runCommand(command, args2, workDir, logger, signal) {
|
|
1874
2025
|
return new Promise((resolvePromise) => {
|
|
1875
|
-
const child =
|
|
2026
|
+
const child = spawn3(command, args2, {
|
|
1876
2027
|
cwd: workDir,
|
|
1877
2028
|
shell: false,
|
|
1878
2029
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -1916,7 +2067,7 @@ function runCommand(command, args2, workDir, logger, signal) {
|
|
|
1916
2067
|
}
|
|
1917
2068
|
async function dirExists(dirPath) {
|
|
1918
2069
|
try {
|
|
1919
|
-
await
|
|
2070
|
+
await access7(dirPath);
|
|
1920
2071
|
return true;
|
|
1921
2072
|
} catch {
|
|
1922
2073
|
return false;
|
|
@@ -1926,7 +2077,7 @@ async function installDependencies(layout, manifest, logger, signal) {
|
|
|
1926
2077
|
const { coreDir, coreDirName } = layout;
|
|
1927
2078
|
const appDirs = [];
|
|
1928
2079
|
for (const app of manifest.applications) {
|
|
1929
|
-
const appDir =
|
|
2080
|
+
const appDir = resolve5(join24(coreDir), app.localPath);
|
|
1930
2081
|
if (!await dirExists(appDir)) {
|
|
1931
2082
|
logger.log(`Warning: Application directory "${app.name}" not found at ${appDir} \u2014 skipping install.`);
|
|
1932
2083
|
continue;
|
|
@@ -1950,7 +2101,7 @@ async function buildAll(layout, manifest, logger, signal) {
|
|
|
1950
2101
|
if (signal?.aborted) return;
|
|
1951
2102
|
const appDirs = [];
|
|
1952
2103
|
for (const app of manifest.applications) {
|
|
1953
|
-
const appDir =
|
|
2104
|
+
const appDir = resolve5(join24(coreDir), app.localPath);
|
|
1954
2105
|
if (!await dirExists(appDir)) {
|
|
1955
2106
|
logger.log(`Warning: Application directory "${app.name}" not found at ${appDir} \u2014 skipping build.`);
|
|
1956
2107
|
continue;
|
|
@@ -2014,6 +2165,431 @@ async function localScriptService(scriptName, logger, signal) {
|
|
|
2014
2165
|
await runLocalScript(scriptName, logger, signal);
|
|
2015
2166
|
}
|
|
2016
2167
|
|
|
2168
|
+
// src/controllers/ui/status.ui-controller.ts
|
|
2169
|
+
async function statusUiController(ctx) {
|
|
2170
|
+
while (true) {
|
|
2171
|
+
const result = await statusService();
|
|
2172
|
+
for (const line of formatStatusLines(result)) {
|
|
2173
|
+
ctx.log(line);
|
|
2174
|
+
}
|
|
2175
|
+
const next = computeNextStep(result);
|
|
2176
|
+
if (next === null) {
|
|
2177
|
+
ctx.log("");
|
|
2178
|
+
ctx.log("All checks passed!");
|
|
2179
|
+
return;
|
|
2180
|
+
}
|
|
2181
|
+
const shouldRun = await ctx.confirm(`Next step: "${next}". Run it now?`, true);
|
|
2182
|
+
if (!shouldRun) return;
|
|
2183
|
+
if (next === "init") {
|
|
2184
|
+
await initUiController(ctx);
|
|
2185
|
+
} else {
|
|
2186
|
+
await localScriptService(next, ctx, ctx.signal);
|
|
2187
|
+
}
|
|
2188
|
+
const resultAfter = await statusService();
|
|
2189
|
+
const nextAfter = computeNextStep(resultAfter);
|
|
2190
|
+
if (nextAfter === next) {
|
|
2191
|
+
ctx.log("");
|
|
2192
|
+
ctx.log(`"${next}" did not complete successfully. Check the output above for errors.`);
|
|
2193
|
+
return;
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
// src/controllers/ui/registry.ts
|
|
2199
|
+
var uiControllers = /* @__PURE__ */ new Map([
|
|
2200
|
+
[CREATE_APPLICATION_COMMAND_NAME, createApplicationUiController],
|
|
2201
|
+
[INIT_COMMAND_NAME, initUiController],
|
|
2202
|
+
[CONFIGURE_IDP_COMMAND_NAME, configureIdpUiController],
|
|
2203
|
+
[CREATE_SERVICE_MODULE_COMMAND_NAME, createServiceModuleUiController],
|
|
2204
|
+
[CREATE_UI_MODULE_COMMAND_NAME, createUiModuleUiController],
|
|
2205
|
+
[STATUS_COMMAND_NAME, statusUiController]
|
|
2206
|
+
]);
|
|
2207
|
+
|
|
2208
|
+
// src/hooks/use-command-runner.ts
|
|
2209
|
+
function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
|
|
2210
|
+
const outputCountRef = useRef(0);
|
|
2211
|
+
const abortControllerRef = useRef(null);
|
|
2212
|
+
const promptResolveRef = useRef(null);
|
|
2213
|
+
const [promptMessage, setPromptMessage] = useState2("");
|
|
2214
|
+
const [promptValue, setPromptValue] = useState2("");
|
|
2215
|
+
const [promptMode, setPromptMode] = useState2({ kind: "text" });
|
|
2216
|
+
const [selectIndex, setSelectIndex] = useState2(0);
|
|
2217
|
+
const [confirmValue, setConfirmValue] = useState2(true);
|
|
2218
|
+
const abortExecution = useCallback(() => {
|
|
2219
|
+
abortControllerRef.current?.abort();
|
|
2220
|
+
abortControllerRef.current = null;
|
|
2221
|
+
promptResolveRef.current = null;
|
|
2222
|
+
setPromptMode({ kind: "text" });
|
|
2223
|
+
}, []);
|
|
2224
|
+
const runCommand2 = useCallback(
|
|
2225
|
+
(cmd) => {
|
|
2226
|
+
const controller = new AbortController();
|
|
2227
|
+
abortControllerRef.current = controller;
|
|
2228
|
+
setState(APP_STATE.EXECUTING);
|
|
2229
|
+
const ctx = {
|
|
2230
|
+
signal: controller.signal,
|
|
2231
|
+
log(message) {
|
|
2232
|
+
if (!controller.signal.aborted) {
|
|
2233
|
+
const id = `output-${outputCountRef.current++}`;
|
|
2234
|
+
appendStaticItem({ kind: "output", id, line: message });
|
|
2235
|
+
}
|
|
2236
|
+
},
|
|
2237
|
+
prompt(message, defaultValue) {
|
|
2238
|
+
return new Promise((resolve6, reject) => {
|
|
2239
|
+
if (controller.signal.aborted) {
|
|
2240
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
2241
|
+
return;
|
|
2242
|
+
}
|
|
2243
|
+
setPromptMessage(message);
|
|
2244
|
+
setPromptValue(defaultValue ?? "");
|
|
2245
|
+
setPromptMode({ kind: "text" });
|
|
2246
|
+
promptResolveRef.current = resolve6;
|
|
2247
|
+
setState(APP_STATE.PROMPTING);
|
|
2248
|
+
controller.signal.addEventListener(
|
|
2249
|
+
"abort",
|
|
2250
|
+
() => reject(new DOMException("Aborted", "AbortError")),
|
|
2251
|
+
{ once: true }
|
|
2252
|
+
);
|
|
2253
|
+
});
|
|
2254
|
+
},
|
|
2255
|
+
select(message, options) {
|
|
2256
|
+
return new Promise((resolve6, reject) => {
|
|
2257
|
+
if (controller.signal.aborted) {
|
|
2258
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
2259
|
+
return;
|
|
2260
|
+
}
|
|
2261
|
+
setPromptMessage(message);
|
|
2262
|
+
setPromptMode({ kind: "select", options });
|
|
2263
|
+
setSelectIndex(0);
|
|
2264
|
+
promptResolveRef.current = resolve6;
|
|
2265
|
+
setState(APP_STATE.PROMPTING);
|
|
2266
|
+
controller.signal.addEventListener(
|
|
2267
|
+
"abort",
|
|
2268
|
+
() => reject(new DOMException("Aborted", "AbortError")),
|
|
2269
|
+
{ once: true }
|
|
2270
|
+
);
|
|
2271
|
+
});
|
|
2272
|
+
},
|
|
2273
|
+
confirm(message, defaultValue) {
|
|
2274
|
+
return new Promise((resolve6, reject) => {
|
|
2275
|
+
if (controller.signal.aborted) {
|
|
2276
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
2277
|
+
return;
|
|
2278
|
+
}
|
|
2279
|
+
setPromptMessage(message);
|
|
2280
|
+
setPromptMode({ kind: "confirm" });
|
|
2281
|
+
setConfirmValue(defaultValue ?? true);
|
|
2282
|
+
promptResolveRef.current = (value) => resolve6(value === "true");
|
|
2283
|
+
setState(APP_STATE.PROMPTING);
|
|
2284
|
+
controller.signal.addEventListener(
|
|
2285
|
+
"abort",
|
|
2286
|
+
() => reject(new DOMException("Aborted", "AbortError")),
|
|
2287
|
+
{ once: true }
|
|
2288
|
+
);
|
|
2289
|
+
});
|
|
2290
|
+
}
|
|
2291
|
+
};
|
|
2292
|
+
const uiController = uiControllers.get(cmd.name);
|
|
2293
|
+
if (!uiController) {
|
|
2294
|
+
const id = `output-${outputCountRef.current++}`;
|
|
2295
|
+
appendStaticItem({ kind: "output", id, line: `Error: No controller found for "${cmd.name}"` });
|
|
2296
|
+
setState(APP_STATE.IDLE);
|
|
2297
|
+
return;
|
|
2298
|
+
}
|
|
2299
|
+
uiController(ctx).then(() => {
|
|
2300
|
+
if (!controller.signal.aborted) {
|
|
2301
|
+
setState(APP_STATE.IDLE);
|
|
2302
|
+
onCommandComplete?.();
|
|
2303
|
+
}
|
|
2304
|
+
}).catch((err) => {
|
|
2305
|
+
if (controller.signal.aborted) return;
|
|
2306
|
+
const id = `output-${outputCountRef.current++}`;
|
|
2307
|
+
appendStaticItem({ kind: "output", id, line: `Error: ${formatError(err)}` });
|
|
2308
|
+
setState(APP_STATE.IDLE);
|
|
2309
|
+
onCommandComplete?.();
|
|
2310
|
+
});
|
|
2311
|
+
},
|
|
2312
|
+
[appendStaticItem, setState, onCommandComplete]
|
|
2313
|
+
);
|
|
2314
|
+
const handlePromptSubmit = useCallback(
|
|
2315
|
+
(value) => {
|
|
2316
|
+
const resolve6 = promptResolveRef.current;
|
|
2317
|
+
promptResolveRef.current = null;
|
|
2318
|
+
setPromptMode({ kind: "text" });
|
|
2319
|
+
setState(APP_STATE.EXECUTING);
|
|
2320
|
+
resolve6?.(value);
|
|
2321
|
+
},
|
|
2322
|
+
[setState]
|
|
2323
|
+
);
|
|
2324
|
+
return {
|
|
2325
|
+
runCommand: runCommand2,
|
|
2326
|
+
handlePromptSubmit,
|
|
2327
|
+
abortExecution,
|
|
2328
|
+
promptMessage,
|
|
2329
|
+
promptValue,
|
|
2330
|
+
setPromptValue,
|
|
2331
|
+
promptMode,
|
|
2332
|
+
selectIndex,
|
|
2333
|
+
setSelectIndex,
|
|
2334
|
+
confirmValue,
|
|
2335
|
+
setConfirmValue
|
|
2336
|
+
};
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
// src/app.tsx
|
|
2340
|
+
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2341
|
+
var require2 = createRequire(import.meta.url);
|
|
2342
|
+
var { version } = require2("../package.json");
|
|
2343
|
+
function App() {
|
|
2344
|
+
const { exit } = useApp();
|
|
2345
|
+
const [state, setState] = useState3(APP_STATE.IDLE);
|
|
2346
|
+
const [inputValue, setInputValue] = useState3("");
|
|
2347
|
+
const [selectedIndex, setSelectedIndex] = useState3(0);
|
|
2348
|
+
const [staticItems, setStaticItems] = useState3([{ kind: "banner", id: "banner" }]);
|
|
2349
|
+
const [platformInitialized, setPlatformInitialized] = useState3(false);
|
|
2350
|
+
useEffect2(() => {
|
|
2351
|
+
isPlatformInitialized().then(setPlatformInitialized).catch(() => {
|
|
2352
|
+
});
|
|
2353
|
+
}, []);
|
|
2354
|
+
const appendStaticItem = useCallback2((item) => {
|
|
2355
|
+
setStaticItems((prev) => [...prev, item]);
|
|
2356
|
+
}, []);
|
|
2357
|
+
const handleCommandComplete = useCallback2(() => {
|
|
2358
|
+
isPlatformInitialized().then(setPlatformInitialized).catch(() => {
|
|
2359
|
+
});
|
|
2360
|
+
}, []);
|
|
2361
|
+
const {
|
|
2362
|
+
runCommand: runCommand2,
|
|
2363
|
+
handlePromptSubmit,
|
|
2364
|
+
abortExecution,
|
|
2365
|
+
promptMessage,
|
|
2366
|
+
promptValue,
|
|
2367
|
+
setPromptValue,
|
|
2368
|
+
promptMode,
|
|
2369
|
+
selectIndex,
|
|
2370
|
+
setSelectIndex,
|
|
2371
|
+
confirmValue,
|
|
2372
|
+
setConfirmValue
|
|
2373
|
+
} = useCommandRunner({ appendStaticItem, setState, onCommandComplete: handleCommandComplete });
|
|
2374
|
+
const query = inputValue.startsWith("/") ? inputValue.slice(1) : "";
|
|
2375
|
+
const filteredCommands = registry.searchVisible(query, { platformInitialized });
|
|
2376
|
+
useInput(
|
|
2377
|
+
(input, key) => {
|
|
2378
|
+
if (key.ctrl && input === "c") {
|
|
2379
|
+
exit();
|
|
2380
|
+
return;
|
|
2381
|
+
}
|
|
2382
|
+
if ((state === APP_STATE.EXECUTING || state === APP_STATE.PROMPTING) && key.escape) {
|
|
2383
|
+
abortExecution();
|
|
2384
|
+
setState(APP_STATE.IDLE);
|
|
2385
|
+
return;
|
|
2386
|
+
}
|
|
2387
|
+
if (state === APP_STATE.PROMPTING) {
|
|
2388
|
+
if (promptMode.kind === "select") {
|
|
2389
|
+
if (key.upArrow) {
|
|
2390
|
+
setSelectIndex((prev) => Math.max(0, prev - 1));
|
|
2391
|
+
return;
|
|
2392
|
+
}
|
|
2393
|
+
if (key.downArrow) {
|
|
2394
|
+
setSelectIndex((prev) => Math.min(promptMode.options.length - 1, prev + 1));
|
|
2395
|
+
return;
|
|
2396
|
+
}
|
|
2397
|
+
if (key.return) {
|
|
2398
|
+
const selected = promptMode.options[selectIndex];
|
|
2399
|
+
if (selected) handlePromptSubmit(selected.value);
|
|
2400
|
+
return;
|
|
2401
|
+
}
|
|
2402
|
+
return;
|
|
2403
|
+
}
|
|
2404
|
+
if (promptMode.kind === "confirm") {
|
|
2405
|
+
if (key.leftArrow || key.upArrow) {
|
|
2406
|
+
setConfirmValue(true);
|
|
2407
|
+
return;
|
|
2408
|
+
}
|
|
2409
|
+
if (key.rightArrow || key.downArrow) {
|
|
2410
|
+
setConfirmValue(false);
|
|
2411
|
+
return;
|
|
2412
|
+
}
|
|
2413
|
+
if (key.return) {
|
|
2414
|
+
handlePromptSubmit(confirmValue ? "true" : "false");
|
|
2415
|
+
return;
|
|
2416
|
+
}
|
|
2417
|
+
return;
|
|
2418
|
+
}
|
|
2419
|
+
return;
|
|
2420
|
+
}
|
|
2421
|
+
if (state === APP_STATE.PALETTE) {
|
|
2422
|
+
if (key.escape) {
|
|
2423
|
+
setState(APP_STATE.IDLE);
|
|
2424
|
+
setInputValue("");
|
|
2425
|
+
return;
|
|
2426
|
+
}
|
|
2427
|
+
if (key.upArrow) {
|
|
2428
|
+
setSelectedIndex((prev) => Math.max(0, prev - 1));
|
|
2429
|
+
return;
|
|
2430
|
+
}
|
|
2431
|
+
if (key.downArrow) {
|
|
2432
|
+
setSelectedIndex((prev) => Math.min(filteredCommands.length - 1, prev + 1));
|
|
2433
|
+
return;
|
|
2434
|
+
}
|
|
2435
|
+
if (key.return) {
|
|
2436
|
+
const cmd = filteredCommands[selectedIndex];
|
|
2437
|
+
if (cmd) {
|
|
2438
|
+
setInputValue("");
|
|
2439
|
+
runCommand2(cmd);
|
|
2440
|
+
}
|
|
2441
|
+
return;
|
|
2442
|
+
}
|
|
2443
|
+
if (key.backspace || key.delete) {
|
|
2444
|
+
setInputValue((prev) => {
|
|
2445
|
+
const next = prev.slice(0, -1);
|
|
2446
|
+
if (!next.startsWith("/")) {
|
|
2447
|
+
setState(APP_STATE.IDLE);
|
|
2448
|
+
return "";
|
|
2449
|
+
}
|
|
2450
|
+
return next;
|
|
2451
|
+
});
|
|
2452
|
+
setSelectedIndex(0);
|
|
2453
|
+
return;
|
|
2454
|
+
}
|
|
2455
|
+
if (input && !key.ctrl && !key.meta) {
|
|
2456
|
+
setInputValue((prev) => prev + input);
|
|
2457
|
+
setSelectedIndex(0);
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
},
|
|
2461
|
+
{ isActive: true }
|
|
2462
|
+
);
|
|
2463
|
+
const handleIdleInputChange = useCallback2((value) => {
|
|
2464
|
+
setInputValue(value);
|
|
2465
|
+
if (value.startsWith("/")) {
|
|
2466
|
+
setState(APP_STATE.PALETTE);
|
|
2467
|
+
setSelectedIndex(0);
|
|
2468
|
+
}
|
|
2469
|
+
}, []);
|
|
2470
|
+
const handleIdleSubmit = useCallback2(
|
|
2471
|
+
(value) => {
|
|
2472
|
+
if (!value.startsWith("/")) return;
|
|
2473
|
+
const name = value.slice(1).trim();
|
|
2474
|
+
const cmd = registry.get(name);
|
|
2475
|
+
if (cmd) runCommand2(cmd);
|
|
2476
|
+
},
|
|
2477
|
+
[runCommand2]
|
|
2478
|
+
);
|
|
2479
|
+
function renderActiveArea() {
|
|
2480
|
+
switch (state) {
|
|
2481
|
+
case APP_STATE.EXECUTING:
|
|
2482
|
+
return /* @__PURE__ */ jsxs7(Box6, { flexDirection: "row", gap: 1, children: [
|
|
2483
|
+
/* @__PURE__ */ jsx9(Spinner, { label: "Running\u2026" }),
|
|
2484
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "(esc to cancel)" })
|
|
2485
|
+
] });
|
|
2486
|
+
case APP_STATE.PROMPTING:
|
|
2487
|
+
return /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", children: [
|
|
2488
|
+
/* @__PURE__ */ jsx9(Text9, { color: "cyan", children: promptMessage }),
|
|
2489
|
+
promptMode.kind === "select" && /* @__PURE__ */ jsx9(SelectList, { options: promptMode.options, selectedIndex: selectIndex }),
|
|
2490
|
+
promptMode.kind === "confirm" && /* @__PURE__ */ jsx9(ConfirmPrompt, { value: confirmValue }),
|
|
2491
|
+
promptMode.kind === "text" && /* @__PURE__ */ jsx9(Prompt, { value: promptValue, onChange: setPromptValue, onSubmit: handlePromptSubmit })
|
|
2492
|
+
] });
|
|
2493
|
+
default:
|
|
2494
|
+
return /* @__PURE__ */ jsx9(
|
|
2495
|
+
Prompt,
|
|
2496
|
+
{
|
|
2497
|
+
value: inputValue,
|
|
2498
|
+
onChange: state === APP_STATE.IDLE ? handleIdleInputChange : () => {
|
|
2499
|
+
},
|
|
2500
|
+
onSubmit: state === APP_STATE.IDLE ? handleIdleSubmit : () => {
|
|
2501
|
+
},
|
|
2502
|
+
disabled: state === APP_STATE.PALETTE
|
|
2503
|
+
}
|
|
2504
|
+
);
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
return /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", children: [
|
|
2508
|
+
/* @__PURE__ */ jsx9(ScrollbackHistory, { items: staticItems, version }),
|
|
2509
|
+
renderActiveArea(),
|
|
2510
|
+
state === APP_STATE.PALETTE && /* @__PURE__ */ jsx9(CommandPalette, { commands: filteredCommands, selectedIndex })
|
|
2511
|
+
] });
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
// src/controllers/cli/create-application.cli-controller.ts
|
|
2515
|
+
async function createApplicationCliController(args2) {
|
|
2516
|
+
if (!await isPlatformInitialized()) {
|
|
2517
|
+
console.error("Error: Cannot create an application \u2014 no platform initialized in this directory.");
|
|
2518
|
+
process.exit(1);
|
|
2519
|
+
}
|
|
2520
|
+
const {
|
|
2521
|
+
applicationName,
|
|
2522
|
+
applicationDisplayName,
|
|
2523
|
+
applicationDescription,
|
|
2524
|
+
hasUserInterface,
|
|
2525
|
+
hasBackendService
|
|
2526
|
+
} = args2;
|
|
2527
|
+
if (!applicationName || !applicationDisplayName || !applicationDescription) {
|
|
2528
|
+
console.error("Error: applicationName, applicationDisplayName, and applicationDescription are required.");
|
|
2529
|
+
process.exit(1);
|
|
2530
|
+
}
|
|
2531
|
+
if (!/^[a-z0-9-]+$/.test(applicationName)) {
|
|
2532
|
+
console.error(`Error: Application name "${applicationName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
|
|
2533
|
+
process.exit(1);
|
|
2534
|
+
}
|
|
2535
|
+
await createApplicationService(
|
|
2536
|
+
{
|
|
2537
|
+
applicationName,
|
|
2538
|
+
applicationDisplayName,
|
|
2539
|
+
applicationDescription,
|
|
2540
|
+
hasUserInterface: hasUserInterface === "yes",
|
|
2541
|
+
hasBackendService: hasBackendService === "yes"
|
|
2542
|
+
},
|
|
2543
|
+
{ log: console.log }
|
|
2544
|
+
);
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
// src/controllers/cli/init.cli-controller.ts
|
|
2548
|
+
async function initCliController(args2) {
|
|
2549
|
+
if (await isPlatformInitialized()) {
|
|
2550
|
+
console.error("Error: Cannot initialize a new platform \u2014 a platform is already initialized in this directory.");
|
|
2551
|
+
process.exit(1);
|
|
2552
|
+
}
|
|
2553
|
+
const { organizationName, platformName, platformDisplayName, bootstrapServiceSuffix, customizationUiSuffix } = args2;
|
|
2554
|
+
if (!organizationName || !platformName || !platformDisplayName) {
|
|
2555
|
+
console.error("Error: organizationName, platformName, and platformDisplayName are required.");
|
|
2556
|
+
process.exit(1);
|
|
2557
|
+
}
|
|
2558
|
+
await initService(
|
|
2559
|
+
{ organizationName, platformName, platformDisplayName, bootstrapServiceSuffix, customizationUiSuffix },
|
|
2560
|
+
{ log: console.log }
|
|
2561
|
+
);
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2564
|
+
// src/controllers/cli/configure-idp.cli-controller.ts
|
|
2565
|
+
async function configureIdpCliController(args2) {
|
|
2566
|
+
const logger = { log: console.log };
|
|
2567
|
+
if (!await isPlatformInitialized()) {
|
|
2568
|
+
console.error("Error: Cannot configure an IDP \u2014 no platform initialized in this directory.");
|
|
2569
|
+
process.exit(1);
|
|
2570
|
+
}
|
|
2571
|
+
const { providerType, name, issuer, clientId, clientSecret } = args2;
|
|
2572
|
+
if (!providerType || !name || !issuer || !clientId || !clientSecret) {
|
|
2573
|
+
logger.log("Error: Missing required arguments: providerType, name, issuer, clientId, clientSecret");
|
|
2574
|
+
process.exit(1);
|
|
2575
|
+
}
|
|
2576
|
+
const provider = idpProviderRegistry.get(providerType);
|
|
2577
|
+
if (!provider) {
|
|
2578
|
+
logger.log(`Error: Unknown IDP type "${providerType}"`);
|
|
2579
|
+
process.exit(1);
|
|
2580
|
+
}
|
|
2581
|
+
const extras = {};
|
|
2582
|
+
for (const field of provider.fields) {
|
|
2583
|
+
const value = args2[field.key];
|
|
2584
|
+
if (!value) {
|
|
2585
|
+
logger.log(`Error: Missing required argument for ${providerType}: ${field.key}`);
|
|
2586
|
+
process.exit(1);
|
|
2587
|
+
}
|
|
2588
|
+
extras[field.key] = value;
|
|
2589
|
+
}
|
|
2590
|
+
await configureIdpService({ providerType, name, issuer, clientId, clientSecret, extras }, logger);
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2017
2593
|
// src/utils/cli-spinner.ts
|
|
2018
2594
|
var FRAMES2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
2019
2595
|
var INTERVAL_MS2 = 80;
|
|
@@ -2103,6 +2679,21 @@ async function createUiModuleCliController(args2) {
|
|
|
2103
2679
|
);
|
|
2104
2680
|
}
|
|
2105
2681
|
|
|
2682
|
+
// src/controllers/cli/status.cli-controller.ts
|
|
2683
|
+
var statusCliController = async (_args) => {
|
|
2684
|
+
const result = await statusService();
|
|
2685
|
+
const lines = formatStatusLines(result);
|
|
2686
|
+
for (const line of lines) {
|
|
2687
|
+
console.log(line);
|
|
2688
|
+
}
|
|
2689
|
+
const next = computeNextStep(result);
|
|
2690
|
+
if (next !== null) {
|
|
2691
|
+
console.log("");
|
|
2692
|
+
console.log(`Hint: Run "platform ${next}" to continue.`);
|
|
2693
|
+
process.exit(1);
|
|
2694
|
+
}
|
|
2695
|
+
};
|
|
2696
|
+
|
|
2106
2697
|
// src/controllers/cli/registry.ts
|
|
2107
2698
|
var cliControllers = /* @__PURE__ */ new Map([
|
|
2108
2699
|
[CREATE_APPLICATION_COMMAND_NAME, createApplicationCliController],
|
|
@@ -2110,6 +2701,7 @@ var cliControllers = /* @__PURE__ */ new Map([
|
|
|
2110
2701
|
[CONFIGURE_IDP_COMMAND_NAME, configureIdpCliController],
|
|
2111
2702
|
[CREATE_SERVICE_MODULE_COMMAND_NAME, createServiceModuleCliController],
|
|
2112
2703
|
[CREATE_UI_MODULE_COMMAND_NAME, createUiModuleCliController],
|
|
2704
|
+
[STATUS_COMMAND_NAME, statusCliController],
|
|
2113
2705
|
[INSTALL_COMMAND_NAME, createLocalScriptCliController(INSTALL_COMMAND_NAME)],
|
|
2114
2706
|
[BUILD_COMMAND_NAME, createLocalScriptCliController(BUILD_COMMAND_NAME)],
|
|
2115
2707
|
[START_COMMAND_NAME, createLocalScriptCliController(START_COMMAND_NAME)],
|
|
@@ -2132,10 +2724,10 @@ function parseKeyValueArgs(args2) {
|
|
|
2132
2724
|
}
|
|
2133
2725
|
|
|
2134
2726
|
// src/index.tsx
|
|
2135
|
-
import { jsx as
|
|
2727
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
2136
2728
|
var args = process.argv.slice(2);
|
|
2137
2729
|
if (args.length === 0) {
|
|
2138
|
-
render(/* @__PURE__ */
|
|
2730
|
+
render(/* @__PURE__ */ jsx10(App, {}));
|
|
2139
2731
|
} else {
|
|
2140
2732
|
const commandName = args[0];
|
|
2141
2733
|
const params = parseKeyValueArgs(args.slice(1));
|