@bluealba/platform-cli 0.3.0 → 0.3.1-develop-233
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 +2094 -471
- package/package.json +7 -6
- 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 +5 -5
- package/templates/customization-ui-module-template/Dockerfile +4 -4
- package/templates/customization-ui-module-template/Dockerfile.development +1 -1
- package/templates/customization-ui-module-template/package.json +6 -6
- package/templates/customization-ui-module-template/tsconfig.json +1 -1
- package/templates/customization-ui-module-template/webpack.config.js +2 -2
- package/templates/platform-init-template/{local → {{platformName}}-core/local}/.env.example +1 -1
- package/templates/platform-init-template/{local → {{platformName}}-core/local}/platform-docker-compose.yml +6 -6
- package/templates/platform-init-template/{local/core-docker-compose.yml → {{platformName}}-core/local/{{platformName}}-core-docker-compose.yml} +7 -7
- package/templates/react-ui-module-template/Dockerfile +3 -3
- package/templates/react-ui-module-template/Dockerfile.development +1 -1
- package/templates/react-ui-module-template/package.json +3 -3
- package/templates/react-ui-module-template/src/Icon.tsx +1 -1
- package/templates/platform-init-template/local/docker-compose.yml +0 -3
- package/templates/platform-init-template/local/package.json +0 -18
- package/templates/platform-init-template/local/scripts/build.sh +0 -18
- package/templates/platform-init-template/local/scripts/install.sh +0 -18
- /package/templates/customization-ui-module-template/src/{platform-customization-ui.tsx → {{platformName}}-customization-ui.tsx} +0 -0
- /package/templates/platform-init-template/{core → {{platformName}}-core}/.changeset/config.json +0 -0
- /package/templates/platform-init-template/{core → {{platformName}}-core}/.nvmrc +0 -0
- /package/templates/platform-init-template/{core → {{platformName}}-core}/.syncpackrc +0 -0
- /package/templates/platform-init-template/{local → {{platformName}}-core/local}/environment/pae-nestjs-gateway-service.env +0 -0
- /package/templates/platform-init-template/{local → {{platformName}}-core/local}/nginx.conf +0 -0
- /package/templates/platform-init-template/{local → {{platformName}}-core/local}/ssl/cert.pem +0 -0
- /package/templates/platform-init-template/{local → {{platformName}}-core/local}/ssl/key.pem +0 -0
- /package/templates/platform-init-template/{core → {{platformName}}-core}/package.json +0 -0
- /package/templates/platform-init-template/{core → {{platformName}}-core}/packages-versions.json +0 -0
- /package/templates/platform-init-template/{core → {{platformName}}-core}/scripts/preinstall.mjs +0 -0
- /package/templates/platform-init-template/{core → {{platformName}}-core}/services/.gitkeep +0 -0
- /package/templates/platform-init-template/{core → {{platformName}}-core}/turbo.json +0 -0
- /package/templates/platform-init-template/{core → {{platformName}}-core}/ui/.gitkeep +0 -0
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 Box7, Text as Text10, useApp, useInput } from "ink";
|
|
10
10
|
|
|
11
11
|
// src/components/prompt.tsx
|
|
12
12
|
import { Box, Text } from "ink";
|
|
@@ -54,53 +54,76 @@ var CommandPalette = React.memo(function CommandPalette2({ commands, selectedInd
|
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
// src/components/scrollback-history.tsx
|
|
57
|
-
import { Static, Text as
|
|
57
|
+
import { Static, Text as Text5 } from "ink";
|
|
58
58
|
|
|
59
59
|
// src/components/welcome-banner.tsx
|
|
60
|
+
import React3 from "react";
|
|
61
|
+
import { Box as Box3, Text as Text4 } from "ink";
|
|
62
|
+
|
|
63
|
+
// src/components/working-directory.tsx
|
|
60
64
|
import React2 from "react";
|
|
61
|
-
import {
|
|
65
|
+
import { Text as Text3 } from "ink";
|
|
66
|
+
import { cwd } from "process";
|
|
67
|
+
import { homedir } from "os";
|
|
62
68
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
69
|
+
function shortenPath(fullPath) {
|
|
70
|
+
const home = homedir();
|
|
71
|
+
return fullPath.startsWith(home) ? `~${fullPath.slice(home.length)}` : fullPath;
|
|
72
|
+
}
|
|
73
|
+
var WorkingDirectory = React2.memo(function WorkingDirectory2() {
|
|
74
|
+
const dir = shortenPath(cwd());
|
|
75
|
+
return /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
76
|
+
" ",
|
|
77
|
+
/* @__PURE__ */ jsx3(Text3, { color: "cyan", children: "cwd:" }),
|
|
78
|
+
" ",
|
|
79
|
+
dir
|
|
80
|
+
] });
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// src/components/welcome-banner.tsx
|
|
84
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
63
85
|
var ASCII_ART = [
|
|
64
86
|
"|---. .---|",
|
|
65
87
|
"| \\/ |",
|
|
66
88
|
"| /\\ |",
|
|
67
89
|
"|---' '---|"
|
|
68
90
|
];
|
|
69
|
-
var WelcomeBanner =
|
|
70
|
-
return /* @__PURE__ */
|
|
71
|
-
/* @__PURE__ */
|
|
91
|
+
var WelcomeBanner = React3.memo(function WelcomeBanner2({ version: version2 }) {
|
|
92
|
+
return /* @__PURE__ */ jsxs4(Box3, { borderStyle: "round", flexDirection: "column", paddingX: 1, paddingY: 1, children: [
|
|
93
|
+
/* @__PURE__ */ jsxs4(Text4, { bold: true, color: "cyan", children: [
|
|
72
94
|
"Blue Alba Platform CLI v",
|
|
73
95
|
version2
|
|
74
96
|
] }),
|
|
75
|
-
/* @__PURE__ */
|
|
76
|
-
/* @__PURE__ */
|
|
77
|
-
/* @__PURE__ */
|
|
78
|
-
/* @__PURE__ */
|
|
79
|
-
/* @__PURE__ */
|
|
97
|
+
/* @__PURE__ */ jsxs4(Box3, { marginTop: 1, children: [
|
|
98
|
+
/* @__PURE__ */ jsx4(Box3, { flexDirection: "column", marginRight: 2, children: ASCII_ART.map((line, i) => /* @__PURE__ */ jsx4(Text4, { color: "yellow", children: line }, i)) }),
|
|
99
|
+
/* @__PURE__ */ jsxs4(Box3, { flexDirection: "column", justifyContent: "center", marginRight: 2, children: [
|
|
100
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, children: "Welcome to the" }),
|
|
101
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, children: "Blue Alba Platform!" })
|
|
80
102
|
] }),
|
|
81
|
-
/* @__PURE__ */
|
|
82
|
-
/* @__PURE__ */
|
|
83
|
-
/* @__PURE__ */
|
|
84
|
-
/* @__PURE__ */
|
|
103
|
+
/* @__PURE__ */ jsx4(Box3, { flexDirection: "column", children: ASCII_ART.map((_, i) => /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2502" }, i)) }),
|
|
104
|
+
/* @__PURE__ */ jsxs4(Box3, { flexDirection: "column", marginLeft: 2, children: [
|
|
105
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: "Tips for getting started" }),
|
|
106
|
+
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
85
107
|
"Run ",
|
|
86
|
-
/* @__PURE__ */
|
|
108
|
+
/* @__PURE__ */ jsx4(Text4, { color: "green", children: "/init" }),
|
|
87
109
|
" to start building a platform"
|
|
88
110
|
] })
|
|
89
111
|
] })
|
|
90
|
-
] })
|
|
112
|
+
] }),
|
|
113
|
+
/* @__PURE__ */ jsx4(WorkingDirectory, {})
|
|
91
114
|
] });
|
|
92
115
|
});
|
|
93
116
|
|
|
94
117
|
// src/components/scrollback-history.tsx
|
|
95
|
-
import { jsx as
|
|
118
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
96
119
|
function ScrollbackHistory({ items, version: version2 }) {
|
|
97
|
-
return /* @__PURE__ */
|
|
120
|
+
return /* @__PURE__ */ jsx5(Static, { items, children: (item) => item.kind === "banner" ? /* @__PURE__ */ jsx5(WelcomeBanner, { version: version2 }, item.id) : /* @__PURE__ */ jsx5(Text5, { children: item.line }, item.id) });
|
|
98
121
|
}
|
|
99
122
|
|
|
100
123
|
// src/components/spinner.tsx
|
|
101
124
|
import { useState, useEffect } from "react";
|
|
102
|
-
import { Text as
|
|
103
|
-
import { jsx as
|
|
125
|
+
import { Text as Text6 } from "ink";
|
|
126
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
104
127
|
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
105
128
|
var INTERVAL_MS = 80;
|
|
106
129
|
function Spinner({ label }) {
|
|
@@ -111,12 +134,69 @@ function Spinner({ label }) {
|
|
|
111
134
|
}, INTERVAL_MS);
|
|
112
135
|
return () => clearInterval(id);
|
|
113
136
|
}, []);
|
|
114
|
-
return /* @__PURE__ */
|
|
115
|
-
/* @__PURE__ */
|
|
137
|
+
return /* @__PURE__ */ jsxs5(Text6, { children: [
|
|
138
|
+
/* @__PURE__ */ jsxs5(Text6, { color: "cyan", children: [
|
|
116
139
|
FRAMES[frame],
|
|
117
140
|
" "
|
|
118
141
|
] }),
|
|
119
|
-
label && /* @__PURE__ */
|
|
142
|
+
label && /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: label })
|
|
143
|
+
] });
|
|
144
|
+
}
|
|
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/multi-select-list.tsx
|
|
161
|
+
import React5 from "react";
|
|
162
|
+
import { Box as Box5, Text as Text8 } from "ink";
|
|
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";
|
|
195
|
+
function ConfirmPrompt({ value }) {
|
|
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)" })
|
|
120
200
|
] });
|
|
121
201
|
}
|
|
122
202
|
|
|
@@ -124,8 +204,7 @@ function Spinner({ label }) {
|
|
|
124
204
|
import { Fzf } from "fzf";
|
|
125
205
|
|
|
126
206
|
// src/commands/create-application/create-application.command.ts
|
|
127
|
-
import { join as
|
|
128
|
-
import { cwd as cwd2 } from "process";
|
|
207
|
+
import { join as join11, resolve } from "path";
|
|
129
208
|
import { mkdir as mkdir2 } from "fs/promises";
|
|
130
209
|
|
|
131
210
|
// src/commands/create-application/scaffold-application-monorepo.ts
|
|
@@ -232,7 +311,12 @@ async function scaffoldBootstrap(bootstrapServiceDir, organizationName, bootstra
|
|
|
232
311
|
{
|
|
233
312
|
templateDir: bootstrapTemplateDir,
|
|
234
313
|
outputDir: bootstrapServiceDir,
|
|
235
|
-
variables: {
|
|
314
|
+
variables: {
|
|
315
|
+
organizationName,
|
|
316
|
+
bootstrapName,
|
|
317
|
+
bootstrapServiceName: `${bootstrapName}-bootstrap-service`,
|
|
318
|
+
bootstrapServiceDir: bootstrapServiceDir_var
|
|
319
|
+
},
|
|
236
320
|
exclude: ["src/data/shared-libraries.json", "src/data/platform/modules-config.json"]
|
|
237
321
|
},
|
|
238
322
|
(message) => logger.log(message)
|
|
@@ -290,8 +374,8 @@ var nestjsServiceModuleTemplateDir = join6(
|
|
|
290
374
|
"templates",
|
|
291
375
|
"nestjs-service-module-template"
|
|
292
376
|
);
|
|
293
|
-
async function scaffoldNestjsService(serviceDir, organizationName, applicationName, applicationDisplayName, serviceBaseDir, logger) {
|
|
294
|
-
const serviceName = `${applicationName}-service`;
|
|
377
|
+
async function scaffoldNestjsService(serviceDir, organizationName, platformName, applicationName, applicationDisplayName, serviceBaseDir, logger) {
|
|
378
|
+
const serviceName = `${platformName}-${applicationName}-service`;
|
|
295
379
|
const serviceDisplayName = `${applicationDisplayName} Service`;
|
|
296
380
|
logger.log(`Creating NestJS service "${serviceName}"...`);
|
|
297
381
|
await applyTemplate(
|
|
@@ -322,33 +406,35 @@ async function addModuleEntry(bootstrapServiceDir, applicationName, entry, logge
|
|
|
322
406
|
}
|
|
323
407
|
|
|
324
408
|
// src/commands/create-application/module-entry-builders.ts
|
|
325
|
-
function buildServiceModuleEntry(organizationName, applicationName, applicationDisplayName) {
|
|
409
|
+
function buildServiceModuleEntry(organizationName, platformName, applicationName, applicationDisplayName) {
|
|
410
|
+
const serviceName = `${platformName}-${applicationName}-service`;
|
|
326
411
|
return {
|
|
327
|
-
name: `@${organizationName}/${
|
|
412
|
+
name: `@${organizationName}/${serviceName}`,
|
|
328
413
|
displayName: `${applicationDisplayName} Service`,
|
|
329
414
|
type: "service",
|
|
330
|
-
baseUrl: `/${
|
|
415
|
+
baseUrl: `/${serviceName}`,
|
|
331
416
|
service: {
|
|
332
|
-
host:
|
|
417
|
+
host: serviceName,
|
|
333
418
|
port: 80
|
|
334
419
|
},
|
|
335
420
|
dependsOn: []
|
|
336
421
|
};
|
|
337
422
|
}
|
|
338
|
-
function buildUiModuleEntry(organizationName, applicationName, applicationDisplayName) {
|
|
423
|
+
function buildUiModuleEntry(organizationName, platformName, applicationName, applicationDisplayName, uiNameOverride) {
|
|
424
|
+
const uiName = uiNameOverride ?? `${platformName}-${applicationName}-ui`;
|
|
339
425
|
return {
|
|
340
|
-
name: `@${organizationName}/${
|
|
426
|
+
name: `@${organizationName}/${uiName}`,
|
|
341
427
|
displayName: applicationDisplayName,
|
|
342
428
|
type: "app",
|
|
343
|
-
baseUrl: `/${
|
|
429
|
+
baseUrl: `/${uiName}`,
|
|
344
430
|
service: {
|
|
345
|
-
host:
|
|
431
|
+
host: uiName,
|
|
346
432
|
port: 80
|
|
347
433
|
},
|
|
348
434
|
ui: {
|
|
349
435
|
route: `/${applicationName}`,
|
|
350
436
|
mountAtSelector: "#pae-shell-ui-content",
|
|
351
|
-
bundleFile: `${organizationName}-${
|
|
437
|
+
bundleFile: `${organizationName}-${uiName}.js`,
|
|
352
438
|
customProps: {}
|
|
353
439
|
},
|
|
354
440
|
dependsOn: [`@bluealba/pae-shell-ui`]
|
|
@@ -357,10 +443,11 @@ function buildUiModuleEntry(organizationName, applicationName, applicationDispla
|
|
|
357
443
|
|
|
358
444
|
// src/commands/create-application/create-docker-compose.ts
|
|
359
445
|
import { writeFile as writeFile3 } from "fs/promises";
|
|
360
|
-
function buildBootstrapBlock(applicationName) {
|
|
361
|
-
|
|
446
|
+
function buildBootstrapBlock(platformName, applicationName) {
|
|
447
|
+
const bootstrapName = `${platformName}-${applicationName}-bootstrap-service`;
|
|
448
|
+
return ` ${bootstrapName}:
|
|
362
449
|
build:
|
|
363
|
-
context:
|
|
450
|
+
context: ../../${platformName}-${applicationName}/services/${bootstrapName}
|
|
364
451
|
dockerfile: Dockerfile.development
|
|
365
452
|
environment:
|
|
366
453
|
- NODE_TLS_REJECT_UNAUTHORIZED=0
|
|
@@ -370,27 +457,29 @@ function buildBootstrapBlock(applicationName) {
|
|
|
370
457
|
- GATEWAY_SERVICE_URL=\${PAE_GATEWAY_URL}
|
|
371
458
|
- SERVICE_ACCESS_SECRET=\${PAE_GATEWAY_SERVICE_ACCESS_SECRET}
|
|
372
459
|
volumes:
|
|
373
|
-
- \${PWD}
|
|
460
|
+
- \${PWD}/:/app/out
|
|
374
461
|
depends_on:
|
|
375
462
|
pae-nestjs-gateway-service:
|
|
376
463
|
condition: service_healthy
|
|
377
464
|
`;
|
|
378
465
|
}
|
|
379
|
-
function buildUiBlock(applicationName, uiPort) {
|
|
380
|
-
|
|
466
|
+
function buildUiBlock(platformName, applicationName, uiPort) {
|
|
467
|
+
const uiName = `${platformName}-${applicationName}-ui`;
|
|
468
|
+
return ` ${uiName}:
|
|
381
469
|
build:
|
|
382
|
-
context:
|
|
470
|
+
context: ../../${platformName}-${applicationName}/ui/${uiName}
|
|
383
471
|
dockerfile: Dockerfile.development
|
|
384
472
|
ports:
|
|
385
473
|
- ${uiPort}:80
|
|
386
474
|
volumes:
|
|
387
|
-
- \${PWD}
|
|
475
|
+
- \${PWD}/:/app/out
|
|
388
476
|
`;
|
|
389
477
|
}
|
|
390
|
-
function buildBackendBlock(applicationName, servicePort) {
|
|
391
|
-
|
|
478
|
+
function buildBackendBlock(platformName, applicationName, servicePort) {
|
|
479
|
+
const serviceName = `${platformName}-${applicationName}-service`;
|
|
480
|
+
return ` ${serviceName}:
|
|
392
481
|
build:
|
|
393
|
-
context:
|
|
482
|
+
context: ../../${platformName}-${applicationName}/services/${serviceName}
|
|
394
483
|
dockerfile: Dockerfile.development
|
|
395
484
|
args:
|
|
396
485
|
- BA_NPM_AUTH_TOKEN=$BA_NPM_AUTH_TOKEN
|
|
@@ -399,18 +488,18 @@ function buildBackendBlock(applicationName, servicePort) {
|
|
|
399
488
|
environment:
|
|
400
489
|
- GATEWAY_URL=\${PAE_GATEWAY_URL}
|
|
401
490
|
volumes:
|
|
402
|
-
- \${PWD}
|
|
491
|
+
- \${PWD}/:/app/out
|
|
403
492
|
`;
|
|
404
493
|
}
|
|
405
|
-
async function createDockerCompose(dockerComposePath, applicationName, hasUserInterface, hasBackendService, uiPort, servicePort, logger) {
|
|
406
|
-
const blocks = ["services:\n", buildBootstrapBlock(applicationName)];
|
|
494
|
+
async function createDockerCompose(dockerComposePath, applicationName, platformName, hasUserInterface, hasBackendService, uiPort, servicePort, logger) {
|
|
495
|
+
const blocks = ["services:\n", buildBootstrapBlock(platformName, applicationName)];
|
|
407
496
|
if (hasUserInterface) {
|
|
408
|
-
blocks.push(buildUiBlock(applicationName, uiPort));
|
|
497
|
+
blocks.push(buildUiBlock(platformName, applicationName, uiPort));
|
|
409
498
|
}
|
|
410
499
|
if (hasBackendService) {
|
|
411
|
-
blocks.push(buildBackendBlock(applicationName, servicePort));
|
|
500
|
+
blocks.push(buildBackendBlock(platformName, applicationName, servicePort));
|
|
412
501
|
}
|
|
413
|
-
const content = blocks.join("\n");
|
|
502
|
+
const content = blocks.join("\n") + "\n";
|
|
414
503
|
try {
|
|
415
504
|
await writeFile3(dockerComposePath, content, "utf-8");
|
|
416
505
|
logger.log(`Created docker-compose: ${dockerComposePath}`);
|
|
@@ -420,32 +509,17 @@ async function createDockerCompose(dockerComposePath, applicationName, hasUserIn
|
|
|
420
509
|
}
|
|
421
510
|
}
|
|
422
511
|
|
|
423
|
-
// src/commands/create-application/update-root-docker-compose.ts
|
|
424
|
-
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
425
|
-
async function updateRootDockerCompose(rootDockerComposePath, applicationName, logger) {
|
|
426
|
-
try {
|
|
427
|
-
const existing = await readFile3(rootDockerComposePath, "utf-8");
|
|
428
|
-
const includeLine = ` - path: ./${applicationName}-docker-compose.yml
|
|
429
|
-
`;
|
|
430
|
-
await writeFile4(rootDockerComposePath, existing + includeLine, "utf-8");
|
|
431
|
-
logger.log(`Updated root docker-compose: ${rootDockerComposePath}`);
|
|
432
|
-
} catch (err) {
|
|
433
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
434
|
-
logger.log(`Warning: Could not update root docker-compose \u2014 ${message}`);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
512
|
// src/commands/create-application/port-allocator.ts
|
|
439
513
|
import { join as join8 } from "path";
|
|
440
|
-
import { readdir as readdir2, readFile as
|
|
514
|
+
import { readdir as readdir2, readFile as readFile3 } from "fs/promises";
|
|
441
515
|
async function getNextAvailablePort(localDir) {
|
|
442
516
|
const allPorts = [];
|
|
443
517
|
try {
|
|
444
518
|
const files = await readdir2(localDir);
|
|
445
|
-
const
|
|
446
|
-
for (const file of
|
|
519
|
+
const ymlFiles = files.filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
|
|
520
|
+
for (const file of ymlFiles) {
|
|
447
521
|
try {
|
|
448
|
-
const content = await
|
|
522
|
+
const content = await readFile3(join8(localDir, file), "utf-8");
|
|
449
523
|
const regex = /^\s*-\s*"?(\d+):\d+"?\s*$/gm;
|
|
450
524
|
let match;
|
|
451
525
|
while ((match = regex.exec(content)) !== null) {
|
|
@@ -464,44 +538,130 @@ function formatError(err) {
|
|
|
464
538
|
return err instanceof Error ? err.message : String(err);
|
|
465
539
|
}
|
|
466
540
|
|
|
467
|
-
// src/utils/platform-
|
|
468
|
-
import { access } from "fs/promises";
|
|
469
|
-
import { join as join9 } from "path";
|
|
470
|
-
import { cwd } from "process";
|
|
471
|
-
async function
|
|
541
|
+
// src/utils/platform-layout.ts
|
|
542
|
+
import { access, readdir as readdir3 } from "fs/promises";
|
|
543
|
+
import { join as join9, dirname as dirname7 } from "path";
|
|
544
|
+
import { cwd as cwd2 } from "process";
|
|
545
|
+
async function findCoreDirIn(dir) {
|
|
546
|
+
let entries;
|
|
472
547
|
try {
|
|
473
|
-
await
|
|
474
|
-
await access(join9(cwd(), "local"));
|
|
475
|
-
return true;
|
|
548
|
+
entries = await readdir3(dir, { withFileTypes: true });
|
|
476
549
|
} catch {
|
|
477
|
-
return
|
|
550
|
+
return null;
|
|
551
|
+
}
|
|
552
|
+
const dirs = entries.filter((e) => e.isDirectory());
|
|
553
|
+
for (const entry of dirs) {
|
|
554
|
+
if (entry.name.endsWith("-core")) {
|
|
555
|
+
try {
|
|
556
|
+
await access(join9(dir, entry.name, "product.manifest.json"));
|
|
557
|
+
return entry.name;
|
|
558
|
+
} catch {
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
const coreEntry = dirs.find((e) => e.name === "core");
|
|
563
|
+
if (coreEntry) {
|
|
564
|
+
try {
|
|
565
|
+
await access(join9(dir, "core", "product.manifest.json"));
|
|
566
|
+
return "core";
|
|
567
|
+
} catch {
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
return null;
|
|
571
|
+
}
|
|
572
|
+
async function findPlatformLayout(startDir = cwd2()) {
|
|
573
|
+
let dir = startDir;
|
|
574
|
+
while (true) {
|
|
575
|
+
const coreDirName = await findCoreDirIn(dir);
|
|
576
|
+
if (coreDirName) {
|
|
577
|
+
const coreDir = join9(dir, coreDirName);
|
|
578
|
+
return {
|
|
579
|
+
rootDir: dir,
|
|
580
|
+
coreDir,
|
|
581
|
+
coreDirName,
|
|
582
|
+
localDir: join9(coreDir, "local")
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
const parent = dirname7(dir);
|
|
586
|
+
if (parent === dir) {
|
|
587
|
+
return null;
|
|
588
|
+
}
|
|
589
|
+
dir = parent;
|
|
478
590
|
}
|
|
479
591
|
}
|
|
480
592
|
|
|
593
|
+
// src/utils/platform-check.ts
|
|
594
|
+
async function isPlatformInitialized() {
|
|
595
|
+
return await findPlatformLayout() !== null;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// src/utils/manifest.ts
|
|
599
|
+
import { readFile as readFile4, writeFile as writeFile4, access as access2 } from "fs/promises";
|
|
600
|
+
import { join as join10 } from "path";
|
|
601
|
+
import { cwd as cwd3 } from "process";
|
|
602
|
+
function manifestPath(rootDir, coreDirName = "core") {
|
|
603
|
+
return join10(rootDir, coreDirName, "product.manifest.json");
|
|
604
|
+
}
|
|
605
|
+
async function readManifest(rootDir = cwd3(), coreDirName = "core") {
|
|
606
|
+
const content = await readFile4(manifestPath(rootDir, coreDirName), "utf-8");
|
|
607
|
+
return JSON.parse(content);
|
|
608
|
+
}
|
|
609
|
+
async function writeManifest(manifest, rootDir = cwd3(), coreDirName = "core") {
|
|
610
|
+
await writeFile4(manifestPath(rootDir, coreDirName), JSON.stringify(manifest, null, 2), "utf-8");
|
|
611
|
+
}
|
|
612
|
+
function createInitialManifest(params) {
|
|
613
|
+
const { organizationName, platformName, platformDisplayName } = params;
|
|
614
|
+
return {
|
|
615
|
+
version: "1",
|
|
616
|
+
product: {
|
|
617
|
+
name: platformName,
|
|
618
|
+
displayName: platformDisplayName,
|
|
619
|
+
organization: organizationName,
|
|
620
|
+
scope: `@${organizationName}`
|
|
621
|
+
},
|
|
622
|
+
applications: []
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
function addApplicationToManifest(manifest, app) {
|
|
626
|
+
return {
|
|
627
|
+
...manifest,
|
|
628
|
+
applications: [...manifest.applications, app]
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
|
|
481
632
|
// src/commands/create-application/create-application.command.ts
|
|
482
633
|
var CREATE_APPLICATION_COMMAND_NAME = "create-application";
|
|
483
634
|
var createApplicationCommand = {
|
|
484
635
|
name: CREATE_APPLICATION_COMMAND_NAME,
|
|
485
|
-
description: "Create an application in a platform"
|
|
636
|
+
description: "Create an application in a platform",
|
|
637
|
+
hidden: (ctx) => !ctx.platformInitialized
|
|
486
638
|
};
|
|
487
639
|
async function createApplication(params, logger) {
|
|
488
640
|
const {
|
|
489
|
-
organizationName,
|
|
490
|
-
platformName,
|
|
491
641
|
applicationName,
|
|
492
642
|
applicationDisplayName,
|
|
493
643
|
applicationDescription,
|
|
494
644
|
hasUserInterface,
|
|
495
645
|
hasBackendService
|
|
496
646
|
} = params;
|
|
497
|
-
|
|
647
|
+
const layout = await findPlatformLayout();
|
|
648
|
+
if (!layout) {
|
|
498
649
|
logger.log("Error: Cannot create an application \u2014 no platform initialized in this directory.");
|
|
499
650
|
return;
|
|
500
651
|
}
|
|
501
|
-
const rootDir =
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
652
|
+
const { rootDir, coreDirName, localDir } = layout;
|
|
653
|
+
let manifest;
|
|
654
|
+
try {
|
|
655
|
+
manifest = await readManifest(rootDir, coreDirName);
|
|
656
|
+
} catch (err) {
|
|
657
|
+
logger.log(`Error: Could not read product manifest \u2014 ${formatError(err)}`);
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
const { organization: organizationName, name: platformName } = manifest.product;
|
|
661
|
+
const localPath = `../${platformName}-${applicationName}`;
|
|
662
|
+
const applicationDir = resolve(join11(rootDir, coreDirName), localPath);
|
|
663
|
+
const bootstrapServiceName = `${platformName}-${applicationName}-bootstrap-service`;
|
|
664
|
+
const bootstrapServiceDir = join11(applicationDir, "services", bootstrapServiceName);
|
|
505
665
|
logger.log(`Creating application monorepo "${applicationName}"...`);
|
|
506
666
|
try {
|
|
507
667
|
await scaffoldApplicationMonorepo(applicationDir, organizationName, platformName, applicationName, logger);
|
|
@@ -509,12 +669,12 @@ async function createApplication(params, logger) {
|
|
|
509
669
|
logger.log(`Error: Could not scaffold application monorepo \u2014 ${formatError(err)}`);
|
|
510
670
|
return;
|
|
511
671
|
}
|
|
512
|
-
await mkdir2(
|
|
513
|
-
await mkdir2(
|
|
514
|
-
logger.log(`Creating bootstrap service "${
|
|
515
|
-
const
|
|
672
|
+
await mkdir2(join11(applicationDir, "services"), { recursive: true });
|
|
673
|
+
await mkdir2(join11(applicationDir, "ui"), { recursive: true });
|
|
674
|
+
logger.log(`Creating bootstrap service "${bootstrapServiceName}"...`);
|
|
675
|
+
const bootstrapServiceBaseDir = `${platformName}-${applicationName}/services`;
|
|
516
676
|
try {
|
|
517
|
-
await scaffoldBootstrap(bootstrapServiceDir, organizationName, applicationName
|
|
677
|
+
await scaffoldBootstrap(bootstrapServiceDir, organizationName, `${platformName}-${applicationName}`, bootstrapServiceBaseDir, logger);
|
|
518
678
|
} catch (err) {
|
|
519
679
|
logger.log(`Error: Could not scaffold bootstrap service \u2014 ${formatError(err)}`);
|
|
520
680
|
return;
|
|
@@ -530,7 +690,7 @@ async function createApplication(params, logger) {
|
|
|
530
690
|
await addModuleEntry(
|
|
531
691
|
bootstrapServiceDir,
|
|
532
692
|
applicationName,
|
|
533
|
-
buildUiModuleEntry(organizationName, applicationName, applicationDisplayName),
|
|
693
|
+
buildUiModuleEntry(organizationName, platformName, applicationName, applicationDisplayName),
|
|
534
694
|
logger
|
|
535
695
|
);
|
|
536
696
|
}
|
|
@@ -538,13 +698,14 @@ async function createApplication(params, logger) {
|
|
|
538
698
|
await addModuleEntry(
|
|
539
699
|
bootstrapServiceDir,
|
|
540
700
|
applicationName,
|
|
541
|
-
buildServiceModuleEntry(organizationName, applicationName, applicationDisplayName),
|
|
701
|
+
buildServiceModuleEntry(organizationName, platformName, applicationName, applicationDisplayName),
|
|
542
702
|
logger
|
|
543
703
|
);
|
|
544
704
|
}
|
|
545
705
|
if (hasUserInterface) {
|
|
546
|
-
const
|
|
547
|
-
const
|
|
706
|
+
const uiName = `${platformName}-${applicationName}-ui`;
|
|
707
|
+
const uiDir = join11(applicationDir, "ui", uiName);
|
|
708
|
+
const uiBaseDir = `${platformName}-${applicationName}/ui`;
|
|
548
709
|
try {
|
|
549
710
|
await scaffoldUiModule(uiDir, organizationName, platformName, applicationName, applicationDisplayName, uiBaseDir, logger);
|
|
550
711
|
} catch (err) {
|
|
@@ -553,36 +714,49 @@ async function createApplication(params, logger) {
|
|
|
553
714
|
}
|
|
554
715
|
}
|
|
555
716
|
if (hasBackendService) {
|
|
556
|
-
const
|
|
557
|
-
const
|
|
717
|
+
const serviceName = `${platformName}-${applicationName}-service`;
|
|
718
|
+
const serviceDir = join11(applicationDir, "services", serviceName);
|
|
719
|
+
const serviceBaseDir = `${platformName}-${applicationName}/services`;
|
|
558
720
|
try {
|
|
559
|
-
await scaffoldNestjsService(serviceDir, organizationName, applicationName, applicationDisplayName, serviceBaseDir, logger);
|
|
721
|
+
await scaffoldNestjsService(serviceDir, organizationName, platformName, applicationName, applicationDisplayName, serviceBaseDir, logger);
|
|
560
722
|
} catch (err) {
|
|
561
723
|
logger.log(`Error: Could not scaffold NestJS service \u2014 ${formatError(err)}`);
|
|
562
724
|
return;
|
|
563
725
|
}
|
|
564
726
|
}
|
|
565
|
-
const dockerComposePath =
|
|
727
|
+
const dockerComposePath = join11(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
|
|
566
728
|
const basePort = await getNextAvailablePort(localDir);
|
|
567
729
|
const uiPort = hasUserInterface ? basePort : 0;
|
|
568
730
|
const servicePort = hasBackendService ? hasUserInterface ? basePort + 1 : basePort : 0;
|
|
569
731
|
await createDockerCompose(
|
|
570
732
|
dockerComposePath,
|
|
571
733
|
applicationName,
|
|
734
|
+
platformName,
|
|
572
735
|
hasUserInterface,
|
|
573
736
|
hasBackendService,
|
|
574
737
|
uiPort,
|
|
575
738
|
servicePort,
|
|
576
739
|
logger
|
|
577
740
|
);
|
|
578
|
-
const
|
|
579
|
-
|
|
741
|
+
const updatedManifest = addApplicationToManifest(manifest, {
|
|
742
|
+
name: applicationName,
|
|
743
|
+
displayName: applicationDisplayName,
|
|
744
|
+
description: applicationDescription,
|
|
745
|
+
localPath,
|
|
746
|
+
repository: null
|
|
747
|
+
});
|
|
748
|
+
try {
|
|
749
|
+
await writeManifest(updatedManifest, rootDir, coreDirName);
|
|
750
|
+
logger.log(`Updated product manifest with application "${applicationName}".`);
|
|
751
|
+
} catch (err) {
|
|
752
|
+
logger.log(`Warning: Could not update product manifest \u2014 ${formatError(err)}`);
|
|
753
|
+
}
|
|
580
754
|
logger.log(`Done! Application "${applicationName}" created at ${applicationDir}`);
|
|
581
755
|
}
|
|
582
756
|
|
|
583
757
|
// src/commands/init/init.command.ts
|
|
584
|
-
import { join as
|
|
585
|
-
import { cwd as
|
|
758
|
+
import { join as join17 } from "path";
|
|
759
|
+
import { cwd as cwd4 } from "process";
|
|
586
760
|
|
|
587
761
|
// src/utils/string.ts
|
|
588
762
|
function camelize(name) {
|
|
@@ -591,9 +765,9 @@ function camelize(name) {
|
|
|
591
765
|
|
|
592
766
|
// src/commands/init/scaffold-platform.ts
|
|
593
767
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
594
|
-
import { join as
|
|
595
|
-
var templateDir =
|
|
596
|
-
|
|
768
|
+
import { join as join12, dirname as dirname8 } from "path";
|
|
769
|
+
var templateDir = join12(
|
|
770
|
+
dirname8(fileURLToPath6(import.meta.url)),
|
|
597
771
|
"..",
|
|
598
772
|
"templates",
|
|
599
773
|
"platform-init-template"
|
|
@@ -604,9 +778,9 @@ async function scaffoldPlatform(outputDir, variables, logger) {
|
|
|
604
778
|
|
|
605
779
|
// src/commands/init/scaffold-platform-bootstrap.ts
|
|
606
780
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
607
|
-
import { join as
|
|
608
|
-
var templateDir2 =
|
|
609
|
-
|
|
781
|
+
import { join as join13, dirname as dirname9 } from "path";
|
|
782
|
+
var templateDir2 = join13(
|
|
783
|
+
dirname9(fileURLToPath7(import.meta.url)),
|
|
610
784
|
"..",
|
|
611
785
|
"templates",
|
|
612
786
|
"bootstrap-service-template"
|
|
@@ -617,9 +791,9 @@ async function scaffoldPlatformBootstrap(outputDir, variables, logger) {
|
|
|
617
791
|
|
|
618
792
|
// src/commands/init/scaffold-customization-ui.ts
|
|
619
793
|
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
620
|
-
import { join as
|
|
621
|
-
var templateDir3 =
|
|
622
|
-
|
|
794
|
+
import { join as join14, dirname as dirname10 } from "path";
|
|
795
|
+
var templateDir3 = join14(
|
|
796
|
+
dirname10(fileURLToPath8(import.meta.url)),
|
|
623
797
|
"..",
|
|
624
798
|
"templates",
|
|
625
799
|
"customization-ui-module-template"
|
|
@@ -629,19 +803,20 @@ async function scaffoldCustomizationUi(outputDir, variables, logger) {
|
|
|
629
803
|
}
|
|
630
804
|
|
|
631
805
|
// src/commands/init/register-customization-module.ts
|
|
632
|
-
import { join as
|
|
806
|
+
import { join as join15 } from "path";
|
|
633
807
|
import { readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
|
|
634
|
-
async function registerCustomizationModule(bootstrapServiceDir, organizationName, logger) {
|
|
635
|
-
const modulesJsonPath =
|
|
808
|
+
async function registerCustomizationModule(bootstrapServiceDir, organizationName, logger, platformName = "platform") {
|
|
809
|
+
const modulesJsonPath = join15(bootstrapServiceDir, "src", "data", "platform", "modules.json");
|
|
810
|
+
const customizationUiName = `${platformName}-customization-ui`;
|
|
636
811
|
const entry = {
|
|
637
|
-
name: `@${organizationName}
|
|
812
|
+
name: `@${organizationName}/${customizationUiName}`,
|
|
638
813
|
displayName: "Platform Customization UI",
|
|
639
814
|
type: "app",
|
|
640
|
-
baseUrl:
|
|
641
|
-
service: { host:
|
|
815
|
+
baseUrl: `/${customizationUiName}`,
|
|
816
|
+
service: { host: customizationUiName, port: 80 },
|
|
642
817
|
ui: {
|
|
643
818
|
route: "/",
|
|
644
|
-
bundleFile:
|
|
819
|
+
bundleFile: `${customizationUiName}.js`,
|
|
645
820
|
isPlatformCustomization: true,
|
|
646
821
|
customProps: {}
|
|
647
822
|
},
|
|
@@ -655,7 +830,7 @@ async function registerCustomizationModule(bootstrapServiceDir, organizationName
|
|
|
655
830
|
|
|
656
831
|
// src/commands/init/generate-local-env.ts
|
|
657
832
|
import { readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
|
|
658
|
-
import { join as
|
|
833
|
+
import { join as join16 } from "path";
|
|
659
834
|
|
|
660
835
|
// src/utils/random.ts
|
|
661
836
|
import { randomBytes } from "crypto";
|
|
@@ -664,10 +839,10 @@ function generateRandomSecret() {
|
|
|
664
839
|
}
|
|
665
840
|
|
|
666
841
|
// src/commands/init/generate-local-env.ts
|
|
667
|
-
async function generateLocalEnv(outputDir, logger) {
|
|
668
|
-
const examplePath =
|
|
669
|
-
const envPath =
|
|
670
|
-
logger.log(
|
|
842
|
+
async function generateLocalEnv(outputDir, logger, coreDirName = "core") {
|
|
843
|
+
const examplePath = join16(outputDir, coreDirName, "local", ".env.example");
|
|
844
|
+
const envPath = join16(outputDir, coreDirName, "local", ".env");
|
|
845
|
+
logger.log(`Generating ${coreDirName}/local/.env with random secrets...`);
|
|
671
846
|
const content = await readFile6(examplePath, "utf-8");
|
|
672
847
|
const result = content.replace(/^(PAE_AUTH_JWT_SECRET|PAE_GATEWAY_SERVICE_ACCESS_SECRET|PAE_DB_PASSWORD)=$/gm, (_, key) => {
|
|
673
848
|
return `${key}=${generateRandomSecret()}`;
|
|
@@ -679,7 +854,8 @@ async function generateLocalEnv(outputDir, logger) {
|
|
|
679
854
|
var INIT_COMMAND_NAME = "init";
|
|
680
855
|
var initCommand = {
|
|
681
856
|
name: INIT_COMMAND_NAME,
|
|
682
|
-
description: "Initialize a new platform"
|
|
857
|
+
description: "Initialize a new platform",
|
|
858
|
+
hidden: ({ platformInitialized }) => platformInitialized
|
|
683
859
|
};
|
|
684
860
|
async function init(params, logger) {
|
|
685
861
|
if (await isPlatformInitialized()) {
|
|
@@ -688,15 +864,22 @@ async function init(params, logger) {
|
|
|
688
864
|
}
|
|
689
865
|
const { organizationName, platformName, platformDisplayName } = params;
|
|
690
866
|
const platformTitle = camelize(platformName);
|
|
691
|
-
const outputDir =
|
|
867
|
+
const outputDir = cwd4();
|
|
692
868
|
logger.log(`Initializing ${platformTitle} platform for @${organizationName}...`);
|
|
869
|
+
const coreDirName = `${platformName}-core`;
|
|
870
|
+
const bootstrapSuffix = params.bootstrapServiceSuffix ?? "bootstrap-service";
|
|
871
|
+
const customizationUiSuffix = params.customizationUiSuffix ?? "customization-ui";
|
|
872
|
+
const bootstrapServiceName = `${platformName}-${bootstrapSuffix}`;
|
|
873
|
+
const customizationUiName = `${platformName}-${customizationUiSuffix}`;
|
|
693
874
|
const variables = {
|
|
694
875
|
organizationName,
|
|
695
876
|
platformName,
|
|
696
877
|
platformTitle,
|
|
697
878
|
platformDisplayName,
|
|
698
|
-
bootstrapName:
|
|
699
|
-
|
|
879
|
+
bootstrapName: platformName,
|
|
880
|
+
bootstrapServiceName,
|
|
881
|
+
customizationUiName,
|
|
882
|
+
bootstrapServiceDir: `${coreDirName}/services`
|
|
700
883
|
};
|
|
701
884
|
try {
|
|
702
885
|
await scaffoldPlatform(outputDir, variables, logger);
|
|
@@ -705,14 +888,22 @@ async function init(params, logger) {
|
|
|
705
888
|
return;
|
|
706
889
|
}
|
|
707
890
|
try {
|
|
708
|
-
await generateLocalEnv(outputDir, logger);
|
|
891
|
+
await generateLocalEnv(outputDir, logger, coreDirName);
|
|
892
|
+
} catch (err) {
|
|
893
|
+
logger.log(`Error: Could not generate ${coreDirName}/local/.env \u2014 ${formatError(err)}`);
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
try {
|
|
897
|
+
const manifest = createInitialManifest({ organizationName, platformName, platformDisplayName });
|
|
898
|
+
await writeManifest(manifest, outputDir, coreDirName);
|
|
899
|
+
logger.log(`Created product manifest: ${coreDirName}/product.manifest.json`);
|
|
709
900
|
} catch (err) {
|
|
710
|
-
logger.log(`Error: Could not
|
|
901
|
+
logger.log(`Error: Could not write product manifest \u2014 ${formatError(err)}`);
|
|
711
902
|
return;
|
|
712
903
|
}
|
|
713
904
|
try {
|
|
714
905
|
await scaffoldPlatformBootstrap(
|
|
715
|
-
|
|
906
|
+
join17(outputDir, coreDirName, "services", bootstrapServiceName),
|
|
716
907
|
variables,
|
|
717
908
|
logger
|
|
718
909
|
);
|
|
@@ -722,7 +913,7 @@ async function init(params, logger) {
|
|
|
722
913
|
}
|
|
723
914
|
try {
|
|
724
915
|
await scaffoldCustomizationUi(
|
|
725
|
-
|
|
916
|
+
join17(outputDir, coreDirName, "ui", customizationUiName),
|
|
726
917
|
variables,
|
|
727
918
|
logger
|
|
728
919
|
);
|
|
@@ -732,9 +923,10 @@ async function init(params, logger) {
|
|
|
732
923
|
}
|
|
733
924
|
try {
|
|
734
925
|
await registerCustomizationModule(
|
|
735
|
-
|
|
926
|
+
join17(outputDir, coreDirName, "services", bootstrapServiceName),
|
|
736
927
|
organizationName,
|
|
737
|
-
logger
|
|
928
|
+
logger,
|
|
929
|
+
platformName
|
|
738
930
|
);
|
|
739
931
|
} catch (err) {
|
|
740
932
|
logger.log(`Error: Could not register customization module \u2014 ${formatError(err)}`);
|
|
@@ -744,8 +936,7 @@ async function init(params, logger) {
|
|
|
744
936
|
}
|
|
745
937
|
|
|
746
938
|
// src/commands/configure-idp/configure-idp.command.ts
|
|
747
|
-
import { join as
|
|
748
|
-
import { cwd as cwd4 } from "process";
|
|
939
|
+
import { join as join18 } from "path";
|
|
749
940
|
import { fetch as undiciFetch, Agent } from "undici";
|
|
750
941
|
|
|
751
942
|
// src/utils/env-reader.ts
|
|
@@ -820,14 +1011,16 @@ function getAllProviders() {
|
|
|
820
1011
|
var CONFIGURE_IDP_COMMAND_NAME = "configure-idp";
|
|
821
1012
|
var configureIdpCommand = {
|
|
822
1013
|
name: CONFIGURE_IDP_COMMAND_NAME,
|
|
823
|
-
description: "Configure an Identity Provider (IDP) in the gateway"
|
|
1014
|
+
description: "Configure an Identity Provider (IDP) in the gateway",
|
|
1015
|
+
hidden: (ctx) => !ctx.platformInitialized
|
|
824
1016
|
};
|
|
825
1017
|
async function configureIdp(params, logger) {
|
|
826
|
-
|
|
1018
|
+
const layout = await findPlatformLayout();
|
|
1019
|
+
if (!layout) {
|
|
827
1020
|
logger.log("Error: Cannot configure an IDP \u2014 no platform initialized in this directory.");
|
|
828
1021
|
return;
|
|
829
1022
|
}
|
|
830
|
-
const envPath =
|
|
1023
|
+
const envPath = join18(layout.localDir, ".env");
|
|
831
1024
|
let env;
|
|
832
1025
|
try {
|
|
833
1026
|
env = await readEnvFile(envPath);
|
|
@@ -838,11 +1031,11 @@ async function configureIdp(params, logger) {
|
|
|
838
1031
|
const gatewayUrl = env.get("PAE_GATEWAY_HOST_URL");
|
|
839
1032
|
const accessSecret = env.get("PAE_GATEWAY_SERVICE_ACCESS_SECRET");
|
|
840
1033
|
if (!gatewayUrl) {
|
|
841
|
-
logger.log("Error: PAE_GATEWAY_HOST_URL is not set in local/.env");
|
|
1034
|
+
logger.log("Error: PAE_GATEWAY_HOST_URL is not set in core/local/.env");
|
|
842
1035
|
return;
|
|
843
1036
|
}
|
|
844
1037
|
if (!accessSecret) {
|
|
845
|
-
logger.log("Error: PAE_GATEWAY_SERVICE_ACCESS_SECRET is not set in local/.env");
|
|
1038
|
+
logger.log("Error: PAE_GATEWAY_SERVICE_ACCESS_SECRET is not set in core/local/.env");
|
|
846
1039
|
return;
|
|
847
1040
|
}
|
|
848
1041
|
const provider = idpProviderRegistry.get(params.providerType);
|
|
@@ -879,15 +1072,14 @@ async function configureIdp(params, logger) {
|
|
|
879
1072
|
}
|
|
880
1073
|
|
|
881
1074
|
// src/commands/create-service-module/create-service-module.command.ts
|
|
882
|
-
import { join as
|
|
883
|
-
import {
|
|
884
|
-
import { access as access2, readFile as readFile9 } from "fs/promises";
|
|
1075
|
+
import { join as join20, resolve as resolve2 } from "path";
|
|
1076
|
+
import { access as access3 } from "fs/promises";
|
|
885
1077
|
|
|
886
1078
|
// src/commands/create-service-module/scaffold-service-module.ts
|
|
887
1079
|
import { fileURLToPath as fileURLToPath9 } from "url";
|
|
888
|
-
import { join as
|
|
889
|
-
var nestjsServiceModuleTemplateDir2 =
|
|
890
|
-
|
|
1080
|
+
import { join as join19, dirname as dirname11 } from "path";
|
|
1081
|
+
var nestjsServiceModuleTemplateDir2 = join19(
|
|
1082
|
+
dirname11(fileURLToPath9(import.meta.url)),
|
|
891
1083
|
"..",
|
|
892
1084
|
"templates",
|
|
893
1085
|
"nestjs-service-module-template"
|
|
@@ -922,11 +1114,11 @@ function buildCustomServiceModuleEntry(organizationName, serviceName, serviceDis
|
|
|
922
1114
|
|
|
923
1115
|
// src/commands/create-service-module/append-docker-compose.ts
|
|
924
1116
|
import { readFile as readFile8, writeFile as writeFile7 } from "fs/promises";
|
|
925
|
-
async function appendServiceToDockerCompose(dockerComposePath, serviceName, applicationName, port, logger) {
|
|
1117
|
+
async function appendServiceToDockerCompose(dockerComposePath, serviceName, platformName, applicationName, port, logger) {
|
|
926
1118
|
const block = `
|
|
927
1119
|
${serviceName}:
|
|
928
1120
|
build:
|
|
929
|
-
context:
|
|
1121
|
+
context: ../../${platformName}-${applicationName}/services/${serviceName}
|
|
930
1122
|
dockerfile: Dockerfile.development
|
|
931
1123
|
args:
|
|
932
1124
|
- BA_NPM_AUTH_TOKEN=$BA_NPM_AUTH_TOKEN
|
|
@@ -935,7 +1127,7 @@ async function appendServiceToDockerCompose(dockerComposePath, serviceName, appl
|
|
|
935
1127
|
environment:
|
|
936
1128
|
- GATEWAY_URL=\${PAE_GATEWAY_URL}
|
|
937
1129
|
volumes:
|
|
938
|
-
- \${PWD}
|
|
1130
|
+
- \${PWD}/:/app/out
|
|
939
1131
|
`;
|
|
940
1132
|
try {
|
|
941
1133
|
const existing = await readFile8(dockerComposePath, "utf-8");
|
|
@@ -951,45 +1143,47 @@ async function appendServiceToDockerCompose(dockerComposePath, serviceName, appl
|
|
|
951
1143
|
var CREATE_SERVICE_MODULE_COMMAND_NAME = "create-service-module";
|
|
952
1144
|
var createServiceModuleCommand = {
|
|
953
1145
|
name: CREATE_SERVICE_MODULE_COMMAND_NAME,
|
|
954
|
-
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
|
|
955
1148
|
};
|
|
956
1149
|
async function createServiceModule(params, logger) {
|
|
957
|
-
const { applicationName, serviceName, serviceDisplayName } = params;
|
|
958
|
-
|
|
1150
|
+
const { applicationName, serviceName, serviceDisplayName, serviceNameSuffix } = params;
|
|
1151
|
+
const layout = await findPlatformLayout();
|
|
1152
|
+
if (!layout) {
|
|
959
1153
|
logger.log("Error: Cannot create a service module \u2014 no platform initialized in this directory.");
|
|
960
1154
|
return;
|
|
961
1155
|
}
|
|
962
|
-
const rootDir =
|
|
963
|
-
|
|
1156
|
+
const { rootDir, coreDirName, localDir } = layout;
|
|
1157
|
+
let manifest;
|
|
1158
|
+
try {
|
|
1159
|
+
manifest = await readManifest(rootDir, coreDirName);
|
|
1160
|
+
} catch (err) {
|
|
1161
|
+
logger.log(`Error: Could not read product manifest \u2014 ${formatError(err)}`);
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
const { organization: organizationName, name: platformName } = manifest.product;
|
|
1165
|
+
const appEntry = manifest.applications.find((a) => a.name === applicationName);
|
|
1166
|
+
if (!appEntry) {
|
|
1167
|
+
logger.log(`Error: The specified application "${applicationName}" is not registered in the product manifest.`);
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
const applicationDir = resolve2(join20(rootDir, coreDirName), appEntry.localPath);
|
|
964
1171
|
try {
|
|
965
|
-
await
|
|
1172
|
+
await access3(applicationDir);
|
|
966
1173
|
} catch {
|
|
967
1174
|
logger.log(`Error: The specified application "${applicationName}" does not exist in the platform.`);
|
|
968
1175
|
return;
|
|
969
1176
|
}
|
|
970
|
-
const
|
|
971
|
-
const
|
|
1177
|
+
const suffix = serviceNameSuffix === void 0 ? "service" : serviceNameSuffix;
|
|
1178
|
+
const fullServiceName = suffix ? `${platformName}-${serviceName}-${suffix}` : `${platformName}-${serviceName}`;
|
|
1179
|
+
const serviceDir = join20(applicationDir, "services", fullServiceName);
|
|
972
1180
|
try {
|
|
973
|
-
await
|
|
1181
|
+
await access3(serviceDir);
|
|
974
1182
|
logger.log(`Error: A service named "${fullServiceName}" already exists in application "${applicationName}".`);
|
|
975
1183
|
return;
|
|
976
1184
|
} catch {
|
|
977
1185
|
}
|
|
978
|
-
|
|
979
|
-
try {
|
|
980
|
-
const corePackageJson = JSON.parse(
|
|
981
|
-
await readFile9(join19(rootDir, "core", "package.json"), "utf-8")
|
|
982
|
-
);
|
|
983
|
-
const scopeMatch = corePackageJson.name.match(/^@([^/]+)\//);
|
|
984
|
-
organizationName = scopeMatch?.[1];
|
|
985
|
-
if (!organizationName) {
|
|
986
|
-
throw new Error(`Could not parse organization from package name: "${corePackageJson.name}"`);
|
|
987
|
-
}
|
|
988
|
-
} catch (err) {
|
|
989
|
-
logger.log(`Error: Could not read core/package.json \u2014 ${formatError(err)}`);
|
|
990
|
-
return;
|
|
991
|
-
}
|
|
992
|
-
const serviceBaseDir = `${applicationName}/services`;
|
|
1186
|
+
const serviceBaseDir = `${platformName}-${applicationName}/services`;
|
|
993
1187
|
try {
|
|
994
1188
|
await scaffoldServiceModule(
|
|
995
1189
|
serviceDir,
|
|
@@ -1003,36 +1197,35 @@ async function createServiceModule(params, logger) {
|
|
|
1003
1197
|
logger.log(`Error: Could not scaffold NestJS service \u2014 ${formatError(err)}`);
|
|
1004
1198
|
return;
|
|
1005
1199
|
}
|
|
1006
|
-
const bootstrapServiceDir =
|
|
1200
|
+
const bootstrapServiceDir = join20(applicationDir, "services", `${platformName}-${applicationName}-bootstrap-service`);
|
|
1007
1201
|
const moduleEntry = buildCustomServiceModuleEntry(organizationName, fullServiceName, serviceDisplayName);
|
|
1008
1202
|
await addModuleEntry(bootstrapServiceDir, applicationName, moduleEntry, logger);
|
|
1009
|
-
const
|
|
1010
|
-
const dockerComposePath = join19(localDir, `${applicationName}-docker-compose.yml`);
|
|
1203
|
+
const dockerComposePath = join20(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
|
|
1011
1204
|
const port = await getNextAvailablePort(localDir);
|
|
1012
|
-
await appendServiceToDockerCompose(dockerComposePath, fullServiceName, applicationName, port, logger);
|
|
1205
|
+
await appendServiceToDockerCompose(dockerComposePath, fullServiceName, platformName, applicationName, port, logger);
|
|
1013
1206
|
logger.log(`Done! Service module "${fullServiceName}" added to application "${applicationName}".`);
|
|
1014
1207
|
}
|
|
1015
1208
|
|
|
1016
1209
|
// src/commands/create-ui-module/create-ui-module.command.ts
|
|
1017
|
-
import { join as
|
|
1018
|
-
import {
|
|
1019
|
-
import { access as access3, readdir as readdir3, readFile as readFile11 } from "fs/promises";
|
|
1210
|
+
import { join as join21, resolve as resolve3 } from "path";
|
|
1211
|
+
import { access as access4, readdir as readdir4 } from "fs/promises";
|
|
1020
1212
|
|
|
1021
1213
|
// src/commands/create-ui-module/append-ui-docker-compose.ts
|
|
1022
|
-
import { readFile as
|
|
1023
|
-
async function appendUiToDockerCompose(dockerComposePath, applicationName, port, logger) {
|
|
1214
|
+
import { readFile as readFile9, writeFile as writeFile8 } from "fs/promises";
|
|
1215
|
+
async function appendUiToDockerCompose(dockerComposePath, platformName, applicationName, port, logger, uiNameOverride) {
|
|
1216
|
+
const uiName = uiNameOverride ?? `${platformName}-${applicationName}-ui`;
|
|
1024
1217
|
const block = `
|
|
1025
|
-
${
|
|
1218
|
+
${uiName}:
|
|
1026
1219
|
build:
|
|
1027
|
-
context:
|
|
1220
|
+
context: ../../${platformName}-${applicationName}/ui/${uiName}
|
|
1028
1221
|
dockerfile: Dockerfile.development
|
|
1029
1222
|
ports:
|
|
1030
1223
|
- ${port}:80
|
|
1031
1224
|
volumes:
|
|
1032
|
-
- \${PWD}
|
|
1225
|
+
- \${PWD}/:/app/out
|
|
1033
1226
|
`;
|
|
1034
1227
|
try {
|
|
1035
|
-
const existing = await
|
|
1228
|
+
const existing = await readFile9(dockerComposePath, "utf-8");
|
|
1036
1229
|
await writeFile8(dockerComposePath, existing + block, "utf-8");
|
|
1037
1230
|
logger.log(`Updated docker-compose: ${dockerComposePath}`);
|
|
1038
1231
|
} catch (err) {
|
|
@@ -1045,25 +1238,40 @@ async function appendUiToDockerCompose(dockerComposePath, applicationName, port,
|
|
|
1045
1238
|
var CREATE_UI_MODULE_COMMAND_NAME = "create-ui-module";
|
|
1046
1239
|
var createUiModuleCommand = {
|
|
1047
1240
|
name: CREATE_UI_MODULE_COMMAND_NAME,
|
|
1048
|
-
description: "Add a UI module to an existing application"
|
|
1241
|
+
description: "Add a UI module to an existing application",
|
|
1242
|
+
hidden: (ctx) => !ctx.platformInitialized
|
|
1049
1243
|
};
|
|
1050
1244
|
async function createUiModule(params, logger) {
|
|
1051
|
-
const { applicationName, applicationDisplayName } = params;
|
|
1052
|
-
|
|
1245
|
+
const { applicationName, applicationDisplayName, uiModuleSuffix } = params;
|
|
1246
|
+
const layout = await findPlatformLayout();
|
|
1247
|
+
if (!layout) {
|
|
1053
1248
|
logger.log("Error: Cannot create a UI module \u2014 no platform initialized in this directory.");
|
|
1054
1249
|
return;
|
|
1055
1250
|
}
|
|
1056
|
-
const rootDir =
|
|
1057
|
-
|
|
1251
|
+
const { rootDir, coreDirName, localDir } = layout;
|
|
1252
|
+
let manifest;
|
|
1058
1253
|
try {
|
|
1059
|
-
await
|
|
1254
|
+
manifest = await readManifest(rootDir, coreDirName);
|
|
1255
|
+
} catch (err) {
|
|
1256
|
+
logger.log(`Error: Could not read product manifest \u2014 ${formatError(err)}`);
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
const { organization: organizationName, name: platformName } = manifest.product;
|
|
1260
|
+
const appEntry = manifest.applications.find((a) => a.name === applicationName);
|
|
1261
|
+
if (!appEntry) {
|
|
1262
|
+
logger.log(`Error: The specified application "${applicationName}" is not registered in the product manifest.`);
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
const applicationDir = resolve3(join21(rootDir, coreDirName), appEntry.localPath);
|
|
1266
|
+
try {
|
|
1267
|
+
await access4(applicationDir);
|
|
1060
1268
|
} catch {
|
|
1061
1269
|
logger.log(`Error: The specified application "${applicationName}" does not exist in the platform.`);
|
|
1062
1270
|
return;
|
|
1063
1271
|
}
|
|
1064
|
-
const uiDir =
|
|
1272
|
+
const uiDir = join21(applicationDir, "ui");
|
|
1065
1273
|
try {
|
|
1066
|
-
const uiEntries = await
|
|
1274
|
+
const uiEntries = await readdir4(uiDir);
|
|
1067
1275
|
const existingUiModules = uiEntries.filter((e) => e !== ".gitkeep");
|
|
1068
1276
|
if (existingUiModules.length > 0) {
|
|
1069
1277
|
logger.log(`Error: Currently we support only one UI module per application. Application "${applicationName}" already has a UI module.`);
|
|
@@ -1071,148 +1279,1042 @@ async function createUiModule(params, logger) {
|
|
|
1071
1279
|
}
|
|
1072
1280
|
} catch {
|
|
1073
1281
|
}
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
await readFile11(join20(rootDir, "core", "package.json"), "utf-8")
|
|
1079
|
-
);
|
|
1080
|
-
const scopeMatch = corePackageJson.name.match(/^@([^/]+)\//);
|
|
1081
|
-
organizationName = scopeMatch?.[1];
|
|
1082
|
-
const rawName = corePackageJson.name.replace(/^@[^/]+\//, "").replace(/-core$/, "");
|
|
1083
|
-
platformName = rawName;
|
|
1084
|
-
if (!organizationName || !platformName) {
|
|
1085
|
-
throw new Error(`Could not parse organization/platform from package name: "${corePackageJson.name}"`);
|
|
1086
|
-
}
|
|
1087
|
-
} catch (err) {
|
|
1088
|
-
logger.log(`Error: Could not read core/package.json \u2014 ${formatError(err)}`);
|
|
1089
|
-
return;
|
|
1090
|
-
}
|
|
1091
|
-
const uiOutputDir = join20(uiDir, `${applicationName}-ui`);
|
|
1092
|
-
const uiBaseDir = `${applicationName}/ui`;
|
|
1282
|
+
const uiSuffix = uiModuleSuffix === void 0 ? "ui" : uiModuleSuffix;
|
|
1283
|
+
const uiName = uiSuffix ? `${platformName}-${applicationName}-${uiSuffix}` : `${platformName}-${applicationName}`;
|
|
1284
|
+
const uiOutputDir = join21(uiDir, uiName);
|
|
1285
|
+
const uiBaseDir = `${platformName}-${applicationName}/ui`;
|
|
1093
1286
|
try {
|
|
1094
1287
|
await scaffoldUiModule(uiOutputDir, organizationName, platformName, applicationName, applicationDisplayName, uiBaseDir, logger);
|
|
1095
1288
|
} catch (err) {
|
|
1096
1289
|
logger.log(`Error: Could not scaffold UI module \u2014 ${formatError(err)}`);
|
|
1097
1290
|
return;
|
|
1098
1291
|
}
|
|
1099
|
-
const bootstrapServiceDir =
|
|
1100
|
-
const moduleEntry = buildUiModuleEntry(organizationName, applicationName, applicationDisplayName);
|
|
1292
|
+
const bootstrapServiceDir = join21(applicationDir, "services", `${platformName}-${applicationName}-bootstrap-service`);
|
|
1293
|
+
const moduleEntry = buildUiModuleEntry(organizationName, platformName, applicationName, applicationDisplayName, uiName);
|
|
1101
1294
|
await addModuleEntry(bootstrapServiceDir, applicationName, moduleEntry, logger);
|
|
1102
|
-
const
|
|
1103
|
-
const dockerComposePath = join20(localDir, `${applicationName}-docker-compose.yml`);
|
|
1295
|
+
const dockerComposePath = join21(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
|
|
1104
1296
|
const port = await getNextAvailablePort(localDir);
|
|
1105
|
-
await appendUiToDockerCompose(dockerComposePath, applicationName, port, logger);
|
|
1106
|
-
logger.log(`Done! UI module "${
|
|
1297
|
+
await appendUiToDockerCompose(dockerComposePath, platformName, applicationName, port, logger, uiName);
|
|
1298
|
+
logger.log(`Done! UI module "${uiName}" added to application "${applicationName}".`);
|
|
1107
1299
|
}
|
|
1108
1300
|
|
|
1109
|
-
// src/commands/
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
this.commands.set(command.name, command);
|
|
1114
|
-
}
|
|
1115
|
-
get(name) {
|
|
1116
|
-
return this.commands.get(name);
|
|
1117
|
-
}
|
|
1118
|
-
getAll() {
|
|
1119
|
-
return Array.from(this.commands.values());
|
|
1120
|
-
}
|
|
1121
|
-
search(query) {
|
|
1122
|
-
const all = this.getAll();
|
|
1123
|
-
if (!query) return all;
|
|
1124
|
-
const fzf = new Fzf(all, {
|
|
1125
|
-
selector: (cmd) => `${cmd.name} ${cmd.description}`
|
|
1126
|
-
});
|
|
1127
|
-
return fzf.find(query).map((result) => result.item);
|
|
1128
|
-
}
|
|
1129
|
-
};
|
|
1130
|
-
var registry = new CommandRegistry();
|
|
1131
|
-
registry.register(createApplicationCommand);
|
|
1132
|
-
registry.register(initCommand);
|
|
1133
|
-
registry.register(configureIdpCommand);
|
|
1134
|
-
registry.register(createServiceModuleCommand);
|
|
1135
|
-
registry.register(createUiModuleCommand);
|
|
1136
|
-
|
|
1137
|
-
// src/app-state.ts
|
|
1138
|
-
var APP_STATE = {
|
|
1139
|
-
IDLE: "idle",
|
|
1140
|
-
PALETTE: "palette",
|
|
1141
|
-
EXECUTING: "executing",
|
|
1142
|
-
PROMPTING: "prompting"
|
|
1143
|
-
};
|
|
1144
|
-
|
|
1145
|
-
// src/hooks/use-command-runner.ts
|
|
1146
|
-
import { useState as useState2, useCallback, useRef } from "react";
|
|
1147
|
-
|
|
1148
|
-
// src/controllers/ui/create-application.ui-controller.ts
|
|
1149
|
-
import { readFile as readFile12 } from "fs/promises";
|
|
1150
|
-
import { join as join21 } from "path";
|
|
1151
|
-
import { cwd as cwd7 } from "process";
|
|
1301
|
+
// src/commands/status/status-checks.ts
|
|
1302
|
+
import { spawn as spawn3 } from "child_process";
|
|
1303
|
+
import { access as access7 } from "fs/promises";
|
|
1304
|
+
import { join as join24, resolve as resolve5 } from "path";
|
|
1152
1305
|
|
|
1153
|
-
// src/
|
|
1154
|
-
|
|
1155
|
-
|
|
1306
|
+
// src/commands/local-scripts/docker-compose-orchestrator.ts
|
|
1307
|
+
import { spawn } from "child_process";
|
|
1308
|
+
import { access as access5 } from "fs/promises";
|
|
1309
|
+
import { join as join22 } from "path";
|
|
1310
|
+
function runDockerCompose(args2, logger, rootDir, signal) {
|
|
1311
|
+
return new Promise((resolvePromise) => {
|
|
1312
|
+
const child = spawn("docker", ["compose", ...args2], {
|
|
1313
|
+
shell: false,
|
|
1314
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1315
|
+
cwd: rootDir
|
|
1316
|
+
});
|
|
1317
|
+
const onAbort = () => {
|
|
1318
|
+
child.kill("SIGTERM");
|
|
1319
|
+
};
|
|
1320
|
+
if (signal) {
|
|
1321
|
+
if (signal.aborted) {
|
|
1322
|
+
child.kill("SIGTERM");
|
|
1323
|
+
resolvePromise();
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1327
|
+
}
|
|
1328
|
+
child.stdout.on("data", (data) => {
|
|
1329
|
+
for (const line of data.toString().split("\n")) {
|
|
1330
|
+
if (line.trim()) logger.log(line);
|
|
1331
|
+
}
|
|
1332
|
+
});
|
|
1333
|
+
child.stderr.on("data", (data) => {
|
|
1334
|
+
for (const line of data.toString().split("\n")) {
|
|
1335
|
+
if (line.trim()) logger.log(line);
|
|
1336
|
+
}
|
|
1337
|
+
});
|
|
1338
|
+
child.on("close", (code, sig) => {
|
|
1339
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1340
|
+
if (sig === "SIGTERM" || signal?.aborted) {
|
|
1341
|
+
logger.log("Command cancelled.");
|
|
1342
|
+
} else if (code !== 0) {
|
|
1343
|
+
logger.log(`docker compose exited with code ${code}.`);
|
|
1344
|
+
}
|
|
1345
|
+
resolvePromise();
|
|
1346
|
+
});
|
|
1347
|
+
child.on("error", (err) => {
|
|
1348
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1349
|
+
logger.log(`Failed to run docker compose: ${err.message}`);
|
|
1350
|
+
resolvePromise();
|
|
1351
|
+
});
|
|
1352
|
+
});
|
|
1156
1353
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1354
|
+
function captureDockerCompose(args2, rootDir) {
|
|
1355
|
+
return new Promise((resolvePromise, reject) => {
|
|
1356
|
+
const child = spawn("docker", ["compose", ...args2], {
|
|
1357
|
+
shell: false,
|
|
1358
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1359
|
+
cwd: rootDir
|
|
1360
|
+
});
|
|
1361
|
+
let stdout = "";
|
|
1362
|
+
child.stdout.on("data", (data) => {
|
|
1363
|
+
stdout += data.toString();
|
|
1364
|
+
});
|
|
1365
|
+
child.on("close", (code) => {
|
|
1366
|
+
if (code !== 0) reject(new Error(`docker compose config --services exited with code ${code}`));
|
|
1367
|
+
else resolvePromise(stdout);
|
|
1368
|
+
});
|
|
1369
|
+
child.on("error", (err) => reject(err));
|
|
1370
|
+
});
|
|
1371
|
+
}
|
|
1372
|
+
async function getAppComposePaths(localDir, platformName, manifest) {
|
|
1373
|
+
const results = [];
|
|
1374
|
+
for (const app of manifest.applications) {
|
|
1375
|
+
const prefixedPath = join22(localDir, `${platformName}-${app.name}-docker-compose.yml`);
|
|
1376
|
+
const unprefixedPath = join22(localDir, `${app.name}-docker-compose.yml`);
|
|
1377
|
+
let resolved = null;
|
|
1378
|
+
try {
|
|
1379
|
+
await access5(prefixedPath);
|
|
1380
|
+
resolved = prefixedPath;
|
|
1381
|
+
} catch {
|
|
1382
|
+
try {
|
|
1383
|
+
await access5(unprefixedPath);
|
|
1384
|
+
resolved = unprefixedPath;
|
|
1385
|
+
} catch {
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
if (resolved) {
|
|
1389
|
+
results.push({ composePath: resolved, appName: app.name });
|
|
1390
|
+
}
|
|
1163
1391
|
}
|
|
1164
|
-
|
|
1165
|
-
|
|
1392
|
+
return results;
|
|
1393
|
+
}
|
|
1394
|
+
async function buildFullComposeArgs(layout, manifest, logger) {
|
|
1395
|
+
const { coreDirName, localDir } = layout;
|
|
1396
|
+
const platformName = manifest.product.name;
|
|
1397
|
+
const prefixedCoreCompose = join22(localDir, `${coreDirName}-docker-compose.yml`);
|
|
1398
|
+
const unprefixedCoreCompose = join22(localDir, "core-docker-compose.yml");
|
|
1399
|
+
let coreComposePath;
|
|
1166
1400
|
try {
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1401
|
+
await access5(prefixedCoreCompose);
|
|
1402
|
+
coreComposePath = prefixedCoreCompose;
|
|
1403
|
+
} catch {
|
|
1404
|
+
coreComposePath = unprefixedCoreCompose;
|
|
1405
|
+
}
|
|
1406
|
+
const fileArgs = [
|
|
1407
|
+
"-f",
|
|
1408
|
+
join22(localDir, "platform-docker-compose.yml"),
|
|
1409
|
+
"-f",
|
|
1410
|
+
coreComposePath
|
|
1411
|
+
];
|
|
1412
|
+
const appEntries = await getAppComposePaths(localDir, platformName, manifest);
|
|
1413
|
+
for (const { composePath } of appEntries) {
|
|
1414
|
+
fileArgs.push("-f", composePath);
|
|
1415
|
+
}
|
|
1416
|
+
for (const app of manifest.applications) {
|
|
1417
|
+
if (!appEntries.find((e) => e.appName === app.name)) {
|
|
1418
|
+
logger.log(`Warning: No docker-compose found for application "${app.name}" in ${coreDirName}/local/ \u2014 skipping.`);
|
|
1176
1419
|
}
|
|
1177
|
-
} catch (err) {
|
|
1178
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1179
|
-
ctx.log(`Error: Could not read core/package.json \u2014 ${message}`);
|
|
1180
|
-
return;
|
|
1181
|
-
}
|
|
1182
|
-
const applicationName = await ctx.prompt("Application name:");
|
|
1183
|
-
if (!/^[a-z0-9-]+$/.test(applicationName)) {
|
|
1184
|
-
ctx.log(`Error: Application name "${applicationName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
|
|
1185
|
-
return;
|
|
1186
1420
|
}
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1421
|
+
return fileArgs;
|
|
1422
|
+
}
|
|
1423
|
+
async function buildSelectedComposeFiles(layout, selectedManifest, includeCore) {
|
|
1424
|
+
const { coreDirName, localDir } = layout;
|
|
1425
|
+
const platformName = selectedManifest.product.name;
|
|
1426
|
+
const files = [];
|
|
1427
|
+
if (includeCore) {
|
|
1428
|
+
const prefixedCoreCompose = join22(localDir, `${coreDirName}-docker-compose.yml`);
|
|
1429
|
+
const unprefixedCoreCompose = join22(localDir, "core-docker-compose.yml");
|
|
1430
|
+
let coreComposePath;
|
|
1431
|
+
try {
|
|
1432
|
+
await access5(prefixedCoreCompose);
|
|
1433
|
+
coreComposePath = prefixedCoreCompose;
|
|
1434
|
+
} catch {
|
|
1435
|
+
coreComposePath = unprefixedCoreCompose;
|
|
1436
|
+
}
|
|
1437
|
+
files.push(join22(localDir, "platform-docker-compose.yml"), coreComposePath);
|
|
1193
1438
|
}
|
|
1194
|
-
const
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
return;
|
|
1439
|
+
const appEntries = await getAppComposePaths(localDir, platformName, selectedManifest);
|
|
1440
|
+
for (const { composePath } of appEntries) {
|
|
1441
|
+
files.push(composePath);
|
|
1198
1442
|
}
|
|
1199
|
-
|
|
1200
|
-
{
|
|
1201
|
-
organizationName,
|
|
1202
|
-
platformName,
|
|
1203
|
-
applicationName,
|
|
1204
|
-
applicationDisplayName,
|
|
1205
|
-
applicationDescription,
|
|
1206
|
-
hasUserInterface: hasUserInterfaceAnswer === "yes",
|
|
1207
|
-
hasBackendService: hasBackendServiceAnswer === "yes"
|
|
1208
|
-
},
|
|
1209
|
-
ctx
|
|
1210
|
-
);
|
|
1443
|
+
return files;
|
|
1211
1444
|
}
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1445
|
+
function extractFilePaths(fileArgs) {
|
|
1446
|
+
const paths = [];
|
|
1447
|
+
for (let i = 0; i < fileArgs.length; i++) {
|
|
1448
|
+
if (fileArgs[i] === "-f" && i + 1 < fileArgs.length) {
|
|
1449
|
+
paths.push(fileArgs[i + 1]);
|
|
1450
|
+
i++;
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
return paths;
|
|
1454
|
+
}
|
|
1455
|
+
async function getServicesFromComposeFiles(selectedFiles, allFiles, rootDir) {
|
|
1456
|
+
const selectedSet = new Set(selectedFiles);
|
|
1457
|
+
const contextFiles = allFiles.filter((f) => !selectedSet.has(f));
|
|
1458
|
+
if (contextFiles.length === 0) {
|
|
1459
|
+
const fileArgs = selectedFiles.flatMap((f) => ["-f", f]);
|
|
1460
|
+
const output = await captureDockerCompose([...fileArgs, "config", "--services"], rootDir);
|
|
1461
|
+
return output.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
1462
|
+
}
|
|
1463
|
+
const allFileArgs = allFiles.flatMap((f) => ["-f", f]);
|
|
1464
|
+
const allOutput = await captureDockerCompose([...allFileArgs, "config", "--services"], rootDir);
|
|
1465
|
+
const allServices = new Set(allOutput.split("\n").map((s) => s.trim()).filter(Boolean));
|
|
1466
|
+
const contextFileArgs = contextFiles.flatMap((f) => ["-f", f]);
|
|
1467
|
+
let contextServices;
|
|
1468
|
+
try {
|
|
1469
|
+
const contextOutput = await captureDockerCompose([...contextFileArgs, "config", "--services"], rootDir);
|
|
1470
|
+
contextServices = new Set(contextOutput.split("\n").map((s) => s.trim()).filter(Boolean));
|
|
1471
|
+
} catch {
|
|
1472
|
+
const selectedFileArgs = selectedFiles.flatMap((f) => ["-f", f]);
|
|
1473
|
+
const fallbackOutput = await captureDockerCompose([...selectedFileArgs, "config", "--services"], rootDir);
|
|
1474
|
+
return fallbackOutput.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
1475
|
+
}
|
|
1476
|
+
return [...allServices].filter((s) => !contextServices.has(s));
|
|
1477
|
+
}
|
|
1478
|
+
async function startEnvironment(layout, manifest, logger, signal, includeCore = true, fullManifest) {
|
|
1479
|
+
const { rootDir, localDir } = layout;
|
|
1480
|
+
const platformName = manifest.product.name;
|
|
1481
|
+
const envFile = join22(localDir, ".env");
|
|
1482
|
+
const isSelective = fullManifest !== void 0;
|
|
1483
|
+
const fileArgs = await buildFullComposeArgs(layout, isSelective ? fullManifest : manifest, logger);
|
|
1484
|
+
const projectArgs = ["-p", `${platformName}-platform`, "--project-directory", localDir, "--env-file", envFile];
|
|
1485
|
+
if (isSelective) {
|
|
1486
|
+
const selectedFiles = await buildSelectedComposeFiles(layout, manifest, includeCore);
|
|
1487
|
+
const allFiles = extractFilePaths(fileArgs);
|
|
1488
|
+
const services = await getServicesFromComposeFiles(selectedFiles, allFiles, rootDir);
|
|
1489
|
+
if (services.length === 0) {
|
|
1490
|
+
logger.log("No services found for selected targets. Nothing to start.");
|
|
1491
|
+
return;
|
|
1492
|
+
}
|
|
1493
|
+
logger.log(`Starting: ${services.join(", ")}...`);
|
|
1494
|
+
await runDockerCompose([...projectArgs, ...fileArgs, "up", "-d", "--build", ...services], logger, rootDir, signal);
|
|
1495
|
+
} else {
|
|
1496
|
+
logger.log("Starting environment...");
|
|
1497
|
+
await runDockerCompose([...projectArgs, ...fileArgs, "up", "-d", "--build", "--remove-orphans"], logger, rootDir, signal);
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
async function stopEnvironment(layout, manifest, logger, signal, includeCore = true, fullManifest) {
|
|
1501
|
+
const { rootDir, localDir } = layout;
|
|
1502
|
+
const platformName = manifest.product.name;
|
|
1503
|
+
const envFile = join22(localDir, ".env");
|
|
1504
|
+
const isSelective = fullManifest !== void 0;
|
|
1505
|
+
const fileArgs = await buildFullComposeArgs(layout, isSelective ? fullManifest : manifest, logger);
|
|
1506
|
+
const projectArgs = ["-p", `${platformName}-platform`, "--project-directory", localDir, "--env-file", envFile];
|
|
1507
|
+
if (isSelective) {
|
|
1508
|
+
const selectedFiles = await buildSelectedComposeFiles(layout, manifest, includeCore);
|
|
1509
|
+
const allFiles = extractFilePaths(fileArgs);
|
|
1510
|
+
const services = await getServicesFromComposeFiles(selectedFiles, allFiles, rootDir);
|
|
1511
|
+
if (services.length === 0) {
|
|
1512
|
+
logger.log("No services found for selected targets. Nothing to stop.");
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1515
|
+
logger.log(`Stopping: ${services.join(", ")}...`);
|
|
1516
|
+
await runDockerCompose([...projectArgs, ...fileArgs, "stop", ...services], logger, rootDir, signal);
|
|
1517
|
+
} else {
|
|
1518
|
+
logger.log("Stopping environment...");
|
|
1519
|
+
await runDockerCompose([...projectArgs, ...fileArgs, "down"], logger, rootDir, signal);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
async function destroyEnvironment(layout, manifest, logger, signal, includeCore = true, fullManifest) {
|
|
1523
|
+
const { rootDir, localDir } = layout;
|
|
1524
|
+
const platformName = manifest.product.name;
|
|
1525
|
+
const envFile = join22(localDir, ".env");
|
|
1526
|
+
const isSelective = fullManifest !== void 0;
|
|
1527
|
+
const fileArgs = await buildFullComposeArgs(layout, isSelective ? fullManifest : manifest, logger);
|
|
1528
|
+
const projectArgs = ["-p", `${platformName}-platform`, "--project-directory", localDir, "--env-file", envFile];
|
|
1529
|
+
if (isSelective) {
|
|
1530
|
+
const selectedFiles = await buildSelectedComposeFiles(layout, manifest, includeCore);
|
|
1531
|
+
const allFiles = extractFilePaths(fileArgs);
|
|
1532
|
+
const services = await getServicesFromComposeFiles(selectedFiles, allFiles, rootDir);
|
|
1533
|
+
if (services.length === 0) {
|
|
1534
|
+
logger.log("No services found for selected targets. Nothing to destroy.");
|
|
1535
|
+
return;
|
|
1536
|
+
}
|
|
1537
|
+
logger.log(`Destroying: ${services.join(", ")}...`);
|
|
1538
|
+
await runDockerCompose([...projectArgs, ...fileArgs, "stop", ...services], logger, rootDir, signal);
|
|
1539
|
+
if (!signal?.aborted) {
|
|
1540
|
+
await runDockerCompose([...projectArgs, ...fileArgs, "rm", "-f", ...services], logger, rootDir, signal);
|
|
1541
|
+
}
|
|
1542
|
+
} else {
|
|
1543
|
+
logger.log("Destroying environment...");
|
|
1544
|
+
await runDockerCompose([...projectArgs, ...fileArgs, "down", "-v", "--rmi", "all"], logger, rootDir, signal);
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
// src/commands/local-scripts/npm-orchestrator.ts
|
|
1549
|
+
import { spawn as spawn2 } from "child_process";
|
|
1550
|
+
import { access as access6 } from "fs/promises";
|
|
1551
|
+
import { resolve as resolve4, join as join23 } from "path";
|
|
1552
|
+
function runCommand(command, args2, workDir, logger, signal) {
|
|
1553
|
+
return new Promise((resolvePromise) => {
|
|
1554
|
+
const child = spawn2(command, args2, {
|
|
1555
|
+
cwd: workDir,
|
|
1556
|
+
shell: false,
|
|
1557
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1558
|
+
});
|
|
1559
|
+
const onAbort = () => {
|
|
1560
|
+
child.kill("SIGTERM");
|
|
1561
|
+
};
|
|
1562
|
+
if (signal) {
|
|
1563
|
+
if (signal.aborted) {
|
|
1564
|
+
child.kill("SIGTERM");
|
|
1565
|
+
resolvePromise();
|
|
1566
|
+
return;
|
|
1567
|
+
}
|
|
1568
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1569
|
+
}
|
|
1570
|
+
child.stdout.on("data", (data) => {
|
|
1571
|
+
for (const line of data.toString().split("\n")) {
|
|
1572
|
+
if (line.trim()) logger.log(line);
|
|
1573
|
+
}
|
|
1574
|
+
});
|
|
1575
|
+
child.stderr.on("data", (data) => {
|
|
1576
|
+
for (const line of data.toString().split("\n")) {
|
|
1577
|
+
if (line.trim()) logger.log(line);
|
|
1578
|
+
}
|
|
1579
|
+
});
|
|
1580
|
+
child.on("close", (code, sig) => {
|
|
1581
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1582
|
+
if (sig === "SIGTERM" || signal?.aborted) {
|
|
1583
|
+
logger.log("Command cancelled.");
|
|
1584
|
+
} else if (code !== 0) {
|
|
1585
|
+
logger.log(`Command "${command} ${args2.join(" ")}" exited with code ${code}.`);
|
|
1586
|
+
}
|
|
1587
|
+
resolvePromise();
|
|
1588
|
+
});
|
|
1589
|
+
child.on("error", (err) => {
|
|
1590
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1591
|
+
logger.log(`Failed to run "${command} ${args2.join(" ")}": ${err.message}`);
|
|
1592
|
+
resolvePromise();
|
|
1593
|
+
});
|
|
1594
|
+
});
|
|
1595
|
+
}
|
|
1596
|
+
async function dirExists(dirPath) {
|
|
1597
|
+
try {
|
|
1598
|
+
await access6(dirPath);
|
|
1599
|
+
return true;
|
|
1600
|
+
} catch {
|
|
1601
|
+
return false;
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
async function installDependencies(layout, manifest, logger, signal, includeCore = true) {
|
|
1605
|
+
const { coreDir, coreDirName } = layout;
|
|
1606
|
+
const appDirs = [];
|
|
1607
|
+
for (const app of manifest.applications) {
|
|
1608
|
+
const appDir = resolve4(join23(coreDir), app.localPath);
|
|
1609
|
+
if (!await dirExists(appDir)) {
|
|
1610
|
+
logger.log(`Warning: Application directory "${app.name}" not found at ${appDir} \u2014 skipping install.`);
|
|
1611
|
+
continue;
|
|
1612
|
+
}
|
|
1613
|
+
appDirs.push({ name: app.name, dir: appDir });
|
|
1614
|
+
}
|
|
1615
|
+
const targets = includeCore ? [{ name: coreDirName, dir: coreDir }, ...appDirs] : appDirs;
|
|
1616
|
+
logger.log(`Installing dependencies in parallel: ${targets.map((t) => t.name).join(", ")}...`);
|
|
1617
|
+
await Promise.all(
|
|
1618
|
+
targets.map(
|
|
1619
|
+
({ name, dir }) => runCommand("npm", ["install"], dir, logger, signal).then(() => {
|
|
1620
|
+
logger.log(`\u2713 ${name} install done`);
|
|
1621
|
+
})
|
|
1622
|
+
)
|
|
1623
|
+
);
|
|
1624
|
+
}
|
|
1625
|
+
async function buildAll(layout, manifest, logger, signal, includeCore = true) {
|
|
1626
|
+
const { coreDir, coreDirName } = layout;
|
|
1627
|
+
if (includeCore) {
|
|
1628
|
+
logger.log(`Building ${coreDirName}/...`);
|
|
1629
|
+
await runCommand("npx", ["turbo", "build"], coreDir, logger, signal);
|
|
1630
|
+
if (signal?.aborted) return;
|
|
1631
|
+
}
|
|
1632
|
+
const appDirs = [];
|
|
1633
|
+
for (const app of manifest.applications) {
|
|
1634
|
+
const appDir = resolve4(join23(coreDir), app.localPath);
|
|
1635
|
+
if (!await dirExists(appDir)) {
|
|
1636
|
+
logger.log(`Warning: Application directory "${app.name}" not found at ${appDir} \u2014 skipping build.`);
|
|
1637
|
+
continue;
|
|
1638
|
+
}
|
|
1639
|
+
appDirs.push({ name: app.name, dir: appDir });
|
|
1640
|
+
}
|
|
1641
|
+
if (appDirs.length === 0) return;
|
|
1642
|
+
logger.log(`Building apps in parallel: ${appDirs.map((a) => a.name).join(", ")}...`);
|
|
1643
|
+
await Promise.all(
|
|
1644
|
+
appDirs.map(
|
|
1645
|
+
({ name, dir }) => runCommand("npm", ["run", "build"], dir, logger, signal).then(() => {
|
|
1646
|
+
logger.log(`\u2713 ${name} build done`);
|
|
1647
|
+
})
|
|
1648
|
+
)
|
|
1649
|
+
);
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
// src/commands/local-scripts/local-script.command.ts
|
|
1653
|
+
var INSTALL_COMMAND_NAME = "install";
|
|
1654
|
+
var BUILD_COMMAND_NAME = "build";
|
|
1655
|
+
var START_COMMAND_NAME = "start";
|
|
1656
|
+
var STOP_COMMAND_NAME = "stop";
|
|
1657
|
+
var DESTROY_COMMAND_NAME = "destroy";
|
|
1658
|
+
var CORE_APP_NAME = "core";
|
|
1659
|
+
var installCommand = {
|
|
1660
|
+
name: INSTALL_COMMAND_NAME,
|
|
1661
|
+
description: "Install dependencies in the local dev environment",
|
|
1662
|
+
hidden: (ctx) => !ctx.platformInitialized
|
|
1663
|
+
};
|
|
1664
|
+
var buildCommand = {
|
|
1665
|
+
name: BUILD_COMMAND_NAME,
|
|
1666
|
+
description: "Build the local dev environment",
|
|
1667
|
+
hidden: (ctx) => !ctx.platformInitialized
|
|
1668
|
+
};
|
|
1669
|
+
var startCommand = {
|
|
1670
|
+
name: START_COMMAND_NAME,
|
|
1671
|
+
description: "Start the local dev environment",
|
|
1672
|
+
hidden: (ctx) => !ctx.platformInitialized
|
|
1673
|
+
};
|
|
1674
|
+
var stopCommand = {
|
|
1675
|
+
name: STOP_COMMAND_NAME,
|
|
1676
|
+
description: "Stop the local dev environment",
|
|
1677
|
+
hidden: (ctx) => !ctx.platformInitialized
|
|
1678
|
+
};
|
|
1679
|
+
var destroyCommand = {
|
|
1680
|
+
name: DESTROY_COMMAND_NAME,
|
|
1681
|
+
description: "Destroy the local dev environment",
|
|
1682
|
+
hidden: (ctx) => !ctx.platformInitialized
|
|
1683
|
+
};
|
|
1684
|
+
var localScriptCommands = [
|
|
1685
|
+
installCommand,
|
|
1686
|
+
buildCommand,
|
|
1687
|
+
startCommand,
|
|
1688
|
+
stopCommand,
|
|
1689
|
+
destroyCommand
|
|
1690
|
+
];
|
|
1691
|
+
async function runLocalScript(scriptName, logger, signal, appNames) {
|
|
1692
|
+
const layout = await findPlatformLayout();
|
|
1693
|
+
if (!layout) {
|
|
1694
|
+
logger.log(`Error: Cannot run "${scriptName}" \u2014 no platform initialized in this directory.`);
|
|
1695
|
+
return;
|
|
1696
|
+
}
|
|
1697
|
+
const { rootDir, coreDirName } = layout;
|
|
1698
|
+
let manifest;
|
|
1699
|
+
try {
|
|
1700
|
+
manifest = await readManifest(rootDir, coreDirName);
|
|
1701
|
+
} catch (err) {
|
|
1702
|
+
logger.log(`Error: Could not read product manifest \u2014 ${formatError(err)}`);
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1705
|
+
let includeCore = true;
|
|
1706
|
+
const fullManifest = manifest;
|
|
1707
|
+
let isSelective = false;
|
|
1708
|
+
if (appNames && appNames.length > 0) {
|
|
1709
|
+
isSelective = true;
|
|
1710
|
+
includeCore = appNames.includes(CORE_APP_NAME);
|
|
1711
|
+
const appNamesWithoutCore = appNames.filter((n) => n !== CORE_APP_NAME);
|
|
1712
|
+
const nameSet = new Set(appNamesWithoutCore);
|
|
1713
|
+
const unknown = appNamesWithoutCore.filter((n) => !manifest.applications.some((a) => a.name === n));
|
|
1714
|
+
if (unknown.length > 0) {
|
|
1715
|
+
logger.log(`Warning: Unknown application(s): ${unknown.join(", ")} \u2014 ignoring.`);
|
|
1716
|
+
}
|
|
1717
|
+
const filtered = manifest.applications.filter((a) => nameSet.has(a.name));
|
|
1718
|
+
if (!includeCore && filtered.length === 0) {
|
|
1719
|
+
logger.log("No matching applications found. Nothing to do.");
|
|
1720
|
+
return;
|
|
1721
|
+
}
|
|
1722
|
+
manifest = { ...manifest, applications: filtered };
|
|
1723
|
+
}
|
|
1724
|
+
switch (scriptName) {
|
|
1725
|
+
case START_COMMAND_NAME:
|
|
1726
|
+
await startEnvironment(layout, manifest, logger, signal, includeCore, isSelective ? fullManifest : void 0);
|
|
1727
|
+
break;
|
|
1728
|
+
case STOP_COMMAND_NAME:
|
|
1729
|
+
await stopEnvironment(layout, manifest, logger, signal, includeCore, isSelective ? fullManifest : void 0);
|
|
1730
|
+
break;
|
|
1731
|
+
case DESTROY_COMMAND_NAME:
|
|
1732
|
+
await destroyEnvironment(layout, manifest, logger, signal, includeCore, isSelective ? fullManifest : void 0);
|
|
1733
|
+
break;
|
|
1734
|
+
case INSTALL_COMMAND_NAME:
|
|
1735
|
+
await installDependencies(layout, manifest, logger, signal, includeCore);
|
|
1736
|
+
break;
|
|
1737
|
+
case BUILD_COMMAND_NAME:
|
|
1738
|
+
await buildAll(layout, manifest, logger, signal, includeCore);
|
|
1739
|
+
break;
|
|
1740
|
+
default:
|
|
1741
|
+
logger.log(`Error: Unknown script "${scriptName}".`);
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
// src/commands/status/status-checks.ts
|
|
1746
|
+
function spawnCapture(cmd, args2, cwd5) {
|
|
1747
|
+
return new Promise((resolvePromise) => {
|
|
1748
|
+
const child = spawn3(cmd, args2, {
|
|
1749
|
+
cwd: cwd5,
|
|
1750
|
+
shell: false,
|
|
1751
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1752
|
+
});
|
|
1753
|
+
let stdout = "";
|
|
1754
|
+
let stderr = "";
|
|
1755
|
+
child.stdout.on("data", (data) => {
|
|
1756
|
+
stdout += data.toString();
|
|
1757
|
+
});
|
|
1758
|
+
child.stderr.on("data", (data) => {
|
|
1759
|
+
stderr += data.toString();
|
|
1760
|
+
});
|
|
1761
|
+
child.on("close", (code) => {
|
|
1762
|
+
resolvePromise({ code: code ?? 1, stdout, stderr });
|
|
1763
|
+
});
|
|
1764
|
+
child.on("error", () => {
|
|
1765
|
+
resolvePromise({ code: 1, stdout, stderr });
|
|
1766
|
+
});
|
|
1767
|
+
});
|
|
1768
|
+
}
|
|
1769
|
+
async function pathExists(p) {
|
|
1770
|
+
try {
|
|
1771
|
+
await access7(p);
|
|
1772
|
+
return true;
|
|
1773
|
+
} catch {
|
|
1774
|
+
return false;
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
async function checkNodeVersion() {
|
|
1778
|
+
const { code, stdout } = await spawnCapture("node", ["--version"]);
|
|
1779
|
+
if (code !== 0 || !stdout.trim()) {
|
|
1780
|
+
return { name: "Node.js", available: false, version: null, versionOk: false, detail: "not found in PATH" };
|
|
1781
|
+
}
|
|
1782
|
+
const version2 = stdout.trim();
|
|
1783
|
+
const match = version2.match(/^v(\d+)/);
|
|
1784
|
+
const major = match ? parseInt(match[1], 10) : 0;
|
|
1785
|
+
const versionOk = major >= 22;
|
|
1786
|
+
return {
|
|
1787
|
+
name: "Node.js",
|
|
1788
|
+
available: true,
|
|
1789
|
+
version: version2,
|
|
1790
|
+
versionOk,
|
|
1791
|
+
detail: versionOk ? void 0 : `>=22 required, found ${version2}`
|
|
1792
|
+
};
|
|
1793
|
+
}
|
|
1794
|
+
async function checkDocker() {
|
|
1795
|
+
const { code, stdout } = await spawnCapture("docker", ["info", "--format", "{{.ServerVersion}}"]);
|
|
1796
|
+
if (code === 0 && stdout.trim()) {
|
|
1797
|
+
return { name: "Docker", available: true, version: stdout.trim(), versionOk: true };
|
|
1798
|
+
}
|
|
1799
|
+
const { code: vCode, stdout: vOut } = await spawnCapture("docker", ["--version"]);
|
|
1800
|
+
if (vCode === 0 && vOut.trim()) {
|
|
1801
|
+
return {
|
|
1802
|
+
name: "Docker",
|
|
1803
|
+
available: false,
|
|
1804
|
+
version: null,
|
|
1805
|
+
versionOk: false,
|
|
1806
|
+
detail: "installed but daemon is not running"
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1809
|
+
return { name: "Docker", available: false, version: null, versionOk: false, detail: "not found in PATH" };
|
|
1810
|
+
}
|
|
1811
|
+
async function checkInstalled(layout, manifest) {
|
|
1812
|
+
const results = [];
|
|
1813
|
+
const coreCheck = {
|
|
1814
|
+
name: layout.coreDirName,
|
|
1815
|
+
path: join24(layout.coreDir, "node_modules"),
|
|
1816
|
+
exists: await pathExists(join24(layout.coreDir, "node_modules"))
|
|
1817
|
+
};
|
|
1818
|
+
results.push(coreCheck);
|
|
1819
|
+
for (const app of manifest.applications) {
|
|
1820
|
+
const appDir = resolve5(layout.coreDir, app.localPath);
|
|
1821
|
+
const nodeModulesPath = join24(appDir, "node_modules");
|
|
1822
|
+
results.push({
|
|
1823
|
+
name: app.name,
|
|
1824
|
+
path: nodeModulesPath,
|
|
1825
|
+
exists: await pathExists(nodeModulesPath)
|
|
1826
|
+
});
|
|
1827
|
+
}
|
|
1828
|
+
return results;
|
|
1829
|
+
}
|
|
1830
|
+
async function checkBuilt(layout, manifest) {
|
|
1831
|
+
const results = [];
|
|
1832
|
+
const coreTurboPath = join24(layout.coreDir, ".turbo");
|
|
1833
|
+
results.push({
|
|
1834
|
+
name: layout.coreDirName,
|
|
1835
|
+
path: coreTurboPath,
|
|
1836
|
+
exists: await pathExists(coreTurboPath)
|
|
1837
|
+
});
|
|
1838
|
+
for (const app of manifest.applications) {
|
|
1839
|
+
const appDir = resolve5(layout.coreDir, app.localPath);
|
|
1840
|
+
const appTurboPath = join24(appDir, ".turbo");
|
|
1841
|
+
results.push({
|
|
1842
|
+
name: app.name,
|
|
1843
|
+
path: appTurboPath,
|
|
1844
|
+
exists: await pathExists(appTurboPath)
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1847
|
+
return results;
|
|
1848
|
+
}
|
|
1849
|
+
async function resolveComposeFiles(layout, manifest) {
|
|
1850
|
+
const { localDir, coreDirName } = layout;
|
|
1851
|
+
const platformName = manifest.product.name;
|
|
1852
|
+
const files = [join24(localDir, "platform-docker-compose.yml")];
|
|
1853
|
+
const prefixedCore = join24(localDir, `${coreDirName}-docker-compose.yml`);
|
|
1854
|
+
const unprefixedCore = join24(localDir, "core-docker-compose.yml");
|
|
1855
|
+
files.push(await pathExists(prefixedCore) ? prefixedCore : unprefixedCore);
|
|
1856
|
+
for (const app of manifest.applications) {
|
|
1857
|
+
const prefixed = join24(localDir, `${platformName}-${app.name}-docker-compose.yml`);
|
|
1858
|
+
const unprefixed = join24(localDir, `${app.name}-docker-compose.yml`);
|
|
1859
|
+
if (await pathExists(prefixed)) files.push(prefixed);
|
|
1860
|
+
else if (await pathExists(unprefixed)) files.push(unprefixed);
|
|
1861
|
+
}
|
|
1862
|
+
return files;
|
|
1863
|
+
}
|
|
1864
|
+
async function checkContainers(layout, manifest) {
|
|
1865
|
+
const platformName = manifest.product.name;
|
|
1866
|
+
const projectName = `${platformName}-platform`;
|
|
1867
|
+
const composeFiles = await resolveComposeFiles(layout, manifest);
|
|
1868
|
+
const fileArgs = composeFiles.flatMap((f) => ["-f", f]);
|
|
1869
|
+
const { code: cfgCode, stdout: cfgOut } = await spawnCapture(
|
|
1870
|
+
"docker",
|
|
1871
|
+
["compose", "-p", projectName, ...fileArgs, "config", "--services"],
|
|
1872
|
+
layout.rootDir
|
|
1873
|
+
);
|
|
1874
|
+
const expectedServices = cfgCode === 0 ? cfgOut.trim().split("\n").map((s) => s.trim()).filter(Boolean) : [];
|
|
1875
|
+
const { stdout: psOut } = await spawnCapture(
|
|
1876
|
+
"docker",
|
|
1877
|
+
["compose", "-p", projectName, ...fileArgs, "ps", "--format", "json"],
|
|
1878
|
+
layout.rootDir
|
|
1879
|
+
);
|
|
1880
|
+
const runningByService = /* @__PURE__ */ new Map();
|
|
1881
|
+
for (const line of psOut.trim().split("\n")) {
|
|
1882
|
+
const trimmed = line.trim();
|
|
1883
|
+
if (!trimmed) continue;
|
|
1884
|
+
try {
|
|
1885
|
+
const obj = JSON.parse(trimmed);
|
|
1886
|
+
const service = String(obj["Service"] ?? obj["Name"] ?? "");
|
|
1887
|
+
if (service) {
|
|
1888
|
+
runningByService.set(service, {
|
|
1889
|
+
state: String(obj["State"] ?? "unknown").toLowerCase(),
|
|
1890
|
+
ports: String(obj["Publishers"] ? formatPublishers(obj["Publishers"]) : obj["Ports"] ?? "")
|
|
1891
|
+
});
|
|
1892
|
+
}
|
|
1893
|
+
} catch {
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
if (expectedServices.length > 0) {
|
|
1897
|
+
return expectedServices.map((service) => {
|
|
1898
|
+
const actual = runningByService.get(service);
|
|
1899
|
+
return {
|
|
1900
|
+
name: service,
|
|
1901
|
+
state: actual ? actual.state : "not started",
|
|
1902
|
+
ports: actual ? actual.ports : ""
|
|
1903
|
+
};
|
|
1904
|
+
});
|
|
1905
|
+
}
|
|
1906
|
+
return Array.from(runningByService.entries()).map(([name, info]) => ({
|
|
1907
|
+
name,
|
|
1908
|
+
...info
|
|
1909
|
+
}));
|
|
1910
|
+
}
|
|
1911
|
+
function formatPublishers(publishers) {
|
|
1912
|
+
if (!Array.isArray(publishers)) return "";
|
|
1913
|
+
return publishers.map((p) => {
|
|
1914
|
+
if (typeof p !== "object" || p === null) return "";
|
|
1915
|
+
const pub = p;
|
|
1916
|
+
const host = pub["URL"] ?? "0.0.0.0";
|
|
1917
|
+
const published = pub["PublishedPort"];
|
|
1918
|
+
const target = pub["TargetPort"];
|
|
1919
|
+
const proto = pub["Protocol"] ?? "tcp";
|
|
1920
|
+
if (!published || !target) return "";
|
|
1921
|
+
return `${host}:${published}->${target}/${proto}`;
|
|
1922
|
+
}).filter(Boolean).join(", ");
|
|
1923
|
+
}
|
|
1924
|
+
function computeNextStepInfo(result) {
|
|
1925
|
+
if (!result.lifecycle.initialized) {
|
|
1926
|
+
return { step: "init", appNames: [], allApps: true };
|
|
1927
|
+
}
|
|
1928
|
+
if (!result.lifecycle.installed) {
|
|
1929
|
+
const failing = result.lifecycle.installedDetails.filter((d) => !d.exists).map((d) => d.name === result.coreDirName ? CORE_APP_NAME : d.name);
|
|
1930
|
+
const allApps = failing.length === result.lifecycle.installedDetails.length;
|
|
1931
|
+
return { step: "install", appNames: failing, allApps };
|
|
1932
|
+
}
|
|
1933
|
+
if (!result.lifecycle.built) {
|
|
1934
|
+
const failing = result.lifecycle.builtDetails.filter((d) => !d.exists).map((d) => d.name === result.coreDirName ? CORE_APP_NAME : d.name);
|
|
1935
|
+
const allApps = failing.length === result.lifecycle.builtDetails.length;
|
|
1936
|
+
return { step: "build", appNames: failing, allApps };
|
|
1937
|
+
}
|
|
1938
|
+
if (!result.lifecycle.running) {
|
|
1939
|
+
const failing = result.containersByApp.filter((g) => !g.allRunning).map((g) => g.appName);
|
|
1940
|
+
const allApps = failing.length === result.containersByApp.length;
|
|
1941
|
+
return { step: "start", appNames: failing, allApps };
|
|
1942
|
+
}
|
|
1943
|
+
return { step: null, appNames: [], allApps: true };
|
|
1944
|
+
}
|
|
1945
|
+
async function getServicesForComposeFiles(selectedFiles, allFiles, rootDir) {
|
|
1946
|
+
const selectedSet = new Set(selectedFiles);
|
|
1947
|
+
const contextFiles = allFiles.filter((f) => !selectedSet.has(f));
|
|
1948
|
+
if (contextFiles.length === 0) {
|
|
1949
|
+
const fileArgs = selectedFiles.flatMap((f) => ["-f", f]);
|
|
1950
|
+
const { stdout } = await spawnCapture("docker", ["compose", ...fileArgs, "config", "--services"], rootDir);
|
|
1951
|
+
return stdout.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
1952
|
+
}
|
|
1953
|
+
const allFileArgs = allFiles.flatMap((f) => ["-f", f]);
|
|
1954
|
+
const { stdout: allOut } = await spawnCapture("docker", ["compose", ...allFileArgs, "config", "--services"], rootDir);
|
|
1955
|
+
const allServices = new Set(allOut.split("\n").map((s) => s.trim()).filter(Boolean));
|
|
1956
|
+
const contextFileArgs = contextFiles.flatMap((f) => ["-f", f]);
|
|
1957
|
+
const { stdout: ctxOut } = await spawnCapture("docker", ["compose", ...contextFileArgs, "config", "--services"], rootDir);
|
|
1958
|
+
const contextServices = new Set(ctxOut.split("\n").map((s) => s.trim()).filter(Boolean));
|
|
1959
|
+
return [...allServices].filter((s) => !contextServices.has(s));
|
|
1960
|
+
}
|
|
1961
|
+
async function checkContainersPerApp(layout, manifest, allContainers) {
|
|
1962
|
+
const { localDir, coreDirName, rootDir } = layout;
|
|
1963
|
+
const platformName = manifest.product.name;
|
|
1964
|
+
const prefixedCore = join24(localDir, `${coreDirName}-docker-compose.yml`);
|
|
1965
|
+
const unprefixedCore = join24(localDir, "core-docker-compose.yml");
|
|
1966
|
+
const coreComposePath = await pathExists(prefixedCore) ? prefixedCore : unprefixedCore;
|
|
1967
|
+
const platformComposePath = join24(localDir, "platform-docker-compose.yml");
|
|
1968
|
+
const allFiles = [platformComposePath, coreComposePath];
|
|
1969
|
+
const appComposeMap = /* @__PURE__ */ new Map();
|
|
1970
|
+
for (const app of manifest.applications) {
|
|
1971
|
+
const prefixed = join24(localDir, `${platformName}-${app.name}-docker-compose.yml`);
|
|
1972
|
+
const unprefixed = join24(localDir, `${app.name}-docker-compose.yml`);
|
|
1973
|
+
if (await pathExists(prefixed)) {
|
|
1974
|
+
appComposeMap.set(app.name, prefixed);
|
|
1975
|
+
allFiles.push(prefixed);
|
|
1976
|
+
} else if (await pathExists(unprefixed)) {
|
|
1977
|
+
appComposeMap.set(app.name, unprefixed);
|
|
1978
|
+
allFiles.push(unprefixed);
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
const containerByName = new Map(allContainers.map((c) => [c.name, c]));
|
|
1982
|
+
const coreFiles = [platformComposePath, coreComposePath];
|
|
1983
|
+
const { stdout: coreOut } = await spawnCapture(
|
|
1984
|
+
"docker",
|
|
1985
|
+
["compose", ...coreFiles.flatMap((f) => ["-f", f]), "config", "--services"],
|
|
1986
|
+
rootDir
|
|
1987
|
+
);
|
|
1988
|
+
const coreServiceNames = coreOut.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
1989
|
+
const appServicesMap = /* @__PURE__ */ new Map();
|
|
1990
|
+
for (const app of manifest.applications) {
|
|
1991
|
+
const appComposePath = appComposeMap.get(app.name);
|
|
1992
|
+
if (!appComposePath) continue;
|
|
1993
|
+
try {
|
|
1994
|
+
const appServices = await getServicesForComposeFiles([appComposePath], allFiles, rootDir);
|
|
1995
|
+
appServicesMap.set(app.name, appServices);
|
|
1996
|
+
} catch {
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
const groups = [];
|
|
2000
|
+
const coreContainers = coreServiceNames.map((s) => containerByName.get(s) ?? { name: s, state: "not started", ports: "" });
|
|
2001
|
+
groups.push({
|
|
2002
|
+
appName: CORE_APP_NAME,
|
|
2003
|
+
containers: coreContainers,
|
|
2004
|
+
allRunning: coreContainers.length > 0 && coreContainers.every((c) => c.state === "running")
|
|
2005
|
+
});
|
|
2006
|
+
for (const app of manifest.applications) {
|
|
2007
|
+
const appServices = appServicesMap.get(app.name);
|
|
2008
|
+
if (!appServices) continue;
|
|
2009
|
+
const appContainers = appServices.map((s) => containerByName.get(s) ?? { name: s, state: "not started", ports: "" });
|
|
2010
|
+
groups.push({
|
|
2011
|
+
appName: app.name,
|
|
2012
|
+
containers: appContainers,
|
|
2013
|
+
allRunning: appContainers.length > 0 && appContainers.every((c) => c.state === "running")
|
|
2014
|
+
});
|
|
2015
|
+
}
|
|
2016
|
+
return groups;
|
|
2017
|
+
}
|
|
2018
|
+
async function gatherStatus() {
|
|
2019
|
+
const layout = await findPlatformLayout();
|
|
2020
|
+
const [nodeCheck, dockerCheck] = await Promise.all([checkNodeVersion(), checkDocker()]);
|
|
2021
|
+
const prerequisites = [nodeCheck, dockerCheck];
|
|
2022
|
+
if (!layout) {
|
|
2023
|
+
return {
|
|
2024
|
+
projectInfo: null,
|
|
2025
|
+
prerequisites,
|
|
2026
|
+
lifecycle: {
|
|
2027
|
+
initialized: false,
|
|
2028
|
+
installed: false,
|
|
2029
|
+
installedDetails: [],
|
|
2030
|
+
built: false,
|
|
2031
|
+
builtDetails: [],
|
|
2032
|
+
running: false
|
|
2033
|
+
},
|
|
2034
|
+
containers: [],
|
|
2035
|
+
containersByApp: [],
|
|
2036
|
+
coreDirName: null
|
|
2037
|
+
};
|
|
2038
|
+
}
|
|
2039
|
+
let manifest;
|
|
2040
|
+
try {
|
|
2041
|
+
manifest = await readManifest(layout.rootDir, layout.coreDirName);
|
|
2042
|
+
} catch {
|
|
2043
|
+
return {
|
|
2044
|
+
projectInfo: null,
|
|
2045
|
+
prerequisites,
|
|
2046
|
+
lifecycle: {
|
|
2047
|
+
initialized: true,
|
|
2048
|
+
installed: false,
|
|
2049
|
+
installedDetails: [],
|
|
2050
|
+
built: false,
|
|
2051
|
+
builtDetails: [],
|
|
2052
|
+
running: false
|
|
2053
|
+
},
|
|
2054
|
+
containers: [],
|
|
2055
|
+
containersByApp: [],
|
|
2056
|
+
coreDirName: layout.coreDirName
|
|
2057
|
+
};
|
|
2058
|
+
}
|
|
2059
|
+
const [installedDetails, builtDetails, containers] = await Promise.all([
|
|
2060
|
+
checkInstalled(layout, manifest),
|
|
2061
|
+
checkBuilt(layout, manifest),
|
|
2062
|
+
checkContainers(layout, manifest)
|
|
2063
|
+
]);
|
|
2064
|
+
const containersByApp = await checkContainersPerApp(layout, manifest, containers);
|
|
2065
|
+
const projectInfo = {
|
|
2066
|
+
productName: manifest.product.name,
|
|
2067
|
+
displayName: manifest.product.displayName,
|
|
2068
|
+
organization: manifest.product.organization,
|
|
2069
|
+
scope: manifest.product.scope,
|
|
2070
|
+
applicationCount: manifest.applications.length,
|
|
2071
|
+
applicationNames: manifest.applications.map((a) => a.name)
|
|
2072
|
+
};
|
|
2073
|
+
return {
|
|
2074
|
+
projectInfo,
|
|
2075
|
+
prerequisites,
|
|
2076
|
+
lifecycle: {
|
|
2077
|
+
initialized: true,
|
|
2078
|
+
installed: installedDetails.every((d) => d.exists),
|
|
2079
|
+
installedDetails,
|
|
2080
|
+
built: builtDetails.every((d) => d.exists),
|
|
2081
|
+
builtDetails,
|
|
2082
|
+
running: containers.length > 0 && containers.every((c) => c.state === "running")
|
|
2083
|
+
},
|
|
2084
|
+
containers,
|
|
2085
|
+
containersByApp,
|
|
2086
|
+
coreDirName: layout.coreDirName
|
|
2087
|
+
};
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
// src/commands/status/status.command.ts
|
|
2091
|
+
var STATUS_COMMAND_NAME = "status";
|
|
2092
|
+
var statusCommand = {
|
|
2093
|
+
name: STATUS_COMMAND_NAME,
|
|
2094
|
+
description: "Show platform status and health checks",
|
|
2095
|
+
hidden: (ctx) => !ctx.platformInitialized
|
|
2096
|
+
};
|
|
2097
|
+
|
|
2098
|
+
// src/commands/manage-platform-admins/manage-platform-admins.command.ts
|
|
2099
|
+
import { join as join25 } from "path";
|
|
2100
|
+
import { fetch as undiciFetch2, Agent as Agent2 } from "undici";
|
|
2101
|
+
var MANAGE_PLATFORM_ADMINS_COMMAND_NAME = "manage-platform-admins";
|
|
2102
|
+
var managePlatformAdminsCommand = {
|
|
2103
|
+
name: MANAGE_PLATFORM_ADMINS_COMMAND_NAME,
|
|
2104
|
+
description: "Manage platform administrators (list, add, remove)",
|
|
2105
|
+
hidden: (ctx) => !ctx.platformInitialized
|
|
2106
|
+
};
|
|
2107
|
+
async function getGatewayConfig(logger) {
|
|
2108
|
+
const layout = await findPlatformLayout();
|
|
2109
|
+
if (!layout) {
|
|
2110
|
+
logger.log("Error: Cannot manage platform admins \u2014 no platform initialized in this directory.");
|
|
2111
|
+
return null;
|
|
2112
|
+
}
|
|
2113
|
+
const envPath = join25(layout.localDir, ".env");
|
|
2114
|
+
let env;
|
|
2115
|
+
try {
|
|
2116
|
+
env = await readEnvFile(envPath);
|
|
2117
|
+
} catch (error) {
|
|
2118
|
+
logger.log(`Error: ${error.message}`);
|
|
2119
|
+
return null;
|
|
2120
|
+
}
|
|
2121
|
+
const gatewayUrl = env.get("PAE_GATEWAY_HOST_URL");
|
|
2122
|
+
const accessSecret = env.get("PAE_GATEWAY_SERVICE_ACCESS_SECRET");
|
|
2123
|
+
if (!gatewayUrl) {
|
|
2124
|
+
logger.log("Error: PAE_GATEWAY_HOST_URL is not set in core/local/.env");
|
|
2125
|
+
return null;
|
|
2126
|
+
}
|
|
2127
|
+
if (!accessSecret) {
|
|
2128
|
+
logger.log("Error: PAE_GATEWAY_SERVICE_ACCESS_SECRET is not set in core/local/.env");
|
|
2129
|
+
return null;
|
|
2130
|
+
}
|
|
2131
|
+
return { gatewayUrl, accessSecret };
|
|
2132
|
+
}
|
|
2133
|
+
async function listPlatformAdmins(logger) {
|
|
2134
|
+
const config = await getGatewayConfig(logger);
|
|
2135
|
+
if (!config) return [];
|
|
2136
|
+
const { gatewayUrl, accessSecret } = config;
|
|
2137
|
+
const agent = new Agent2({ connect: { rejectUnauthorized: false } });
|
|
2138
|
+
let response;
|
|
2139
|
+
try {
|
|
2140
|
+
response = await undiciFetch2(`${gatewayUrl}/applications/platform/rules`, {
|
|
2141
|
+
method: "GET",
|
|
2142
|
+
headers: {
|
|
2143
|
+
Accept: "application/json",
|
|
2144
|
+
Authorization: `Bearer ${accessSecret}`
|
|
2145
|
+
},
|
|
2146
|
+
dispatcher: agent
|
|
2147
|
+
});
|
|
2148
|
+
} catch (error) {
|
|
2149
|
+
const err = error;
|
|
2150
|
+
const cause = err.cause instanceof Error ? ` (cause: ${err.cause.message})` : err.cause ? ` (cause: ${String(err.cause)})` : "";
|
|
2151
|
+
logger.log(`Error: Failed to reach gateway \u2014 ${err.message}${cause}`);
|
|
2152
|
+
return [];
|
|
2153
|
+
}
|
|
2154
|
+
if (!response.ok) {
|
|
2155
|
+
const body = await response.text().catch(() => "");
|
|
2156
|
+
logger.log(`Error: Server responded with ${response.status}${body ? ` \u2014 ${body}` : ""}`);
|
|
2157
|
+
return [];
|
|
2158
|
+
}
|
|
2159
|
+
const rules = await response.json();
|
|
2160
|
+
return rules.filter(
|
|
2161
|
+
(r) => r.subjectType === "user" && r.resourceType === "role" && r.resourceName === "admin"
|
|
2162
|
+
).map((r) => ({ username: r.subject, ruleId: String(r.id) }));
|
|
2163
|
+
}
|
|
2164
|
+
async function addPlatformAdmin(username, logger) {
|
|
2165
|
+
const config = await getGatewayConfig(logger);
|
|
2166
|
+
if (!config) return false;
|
|
2167
|
+
const { gatewayUrl, accessSecret } = config;
|
|
2168
|
+
const agent = new Agent2({ connect: { rejectUnauthorized: false } });
|
|
2169
|
+
let response;
|
|
2170
|
+
try {
|
|
2171
|
+
response = await undiciFetch2(`${gatewayUrl}/applications/platform/rules`, {
|
|
2172
|
+
method: "POST",
|
|
2173
|
+
headers: {
|
|
2174
|
+
"Content-Type": "application/json",
|
|
2175
|
+
Accept: "application/json",
|
|
2176
|
+
Authorization: `Bearer ${accessSecret}`
|
|
2177
|
+
},
|
|
2178
|
+
body: JSON.stringify({
|
|
2179
|
+
subject: username,
|
|
2180
|
+
subjectType: "user",
|
|
2181
|
+
resourceType: "role",
|
|
2182
|
+
resource: "admin"
|
|
2183
|
+
}),
|
|
2184
|
+
dispatcher: agent
|
|
2185
|
+
});
|
|
2186
|
+
} catch (error) {
|
|
2187
|
+
const err = error;
|
|
2188
|
+
const cause = err.cause instanceof Error ? ` (cause: ${err.cause.message})` : err.cause ? ` (cause: ${String(err.cause)})` : "";
|
|
2189
|
+
logger.log(`Error: Failed to reach gateway \u2014 ${err.message}${cause}`);
|
|
2190
|
+
return false;
|
|
2191
|
+
}
|
|
2192
|
+
if (response.status === 409) {
|
|
2193
|
+
logger.log(`'${username}' is already a platform admin.`);
|
|
2194
|
+
return false;
|
|
2195
|
+
}
|
|
2196
|
+
if (!response.ok) {
|
|
2197
|
+
const body = await response.text().catch(() => "");
|
|
2198
|
+
logger.log(`Error: Server responded with ${response.status}${body ? ` \u2014 ${body}` : ""}`);
|
|
2199
|
+
return false;
|
|
2200
|
+
}
|
|
2201
|
+
return true;
|
|
2202
|
+
}
|
|
2203
|
+
async function removePlatformAdmin(ruleId, logger) {
|
|
2204
|
+
const config = await getGatewayConfig(logger);
|
|
2205
|
+
if (!config) return false;
|
|
2206
|
+
const { gatewayUrl, accessSecret } = config;
|
|
2207
|
+
const agent = new Agent2({ connect: { rejectUnauthorized: false } });
|
|
2208
|
+
let response;
|
|
2209
|
+
try {
|
|
2210
|
+
response = await undiciFetch2(`${gatewayUrl}/applications/platform/rules/${ruleId}`, {
|
|
2211
|
+
method: "DELETE",
|
|
2212
|
+
headers: {
|
|
2213
|
+
Authorization: `Bearer ${accessSecret}`
|
|
2214
|
+
},
|
|
2215
|
+
dispatcher: agent
|
|
2216
|
+
});
|
|
2217
|
+
} catch (error) {
|
|
2218
|
+
const err = error;
|
|
2219
|
+
const cause = err.cause instanceof Error ? ` (cause: ${err.cause.message})` : err.cause ? ` (cause: ${String(err.cause)})` : "";
|
|
2220
|
+
logger.log(`Error: Failed to reach gateway \u2014 ${err.message}${cause}`);
|
|
2221
|
+
return false;
|
|
2222
|
+
}
|
|
2223
|
+
if (!response.ok) {
|
|
2224
|
+
const body = await response.text().catch(() => "");
|
|
2225
|
+
logger.log(`Error: Server responded with ${response.status}${body ? ` \u2014 ${body}` : ""}`);
|
|
2226
|
+
return false;
|
|
2227
|
+
}
|
|
2228
|
+
return true;
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
// src/commands/registry.ts
|
|
2232
|
+
var CommandRegistry = class {
|
|
2233
|
+
commands = /* @__PURE__ */ new Map();
|
|
2234
|
+
register(command) {
|
|
2235
|
+
this.commands.set(command.name, command);
|
|
2236
|
+
}
|
|
2237
|
+
get(name) {
|
|
2238
|
+
return this.commands.get(name);
|
|
2239
|
+
}
|
|
2240
|
+
getAll() {
|
|
2241
|
+
return Array.from(this.commands.values());
|
|
2242
|
+
}
|
|
2243
|
+
search(query) {
|
|
2244
|
+
const all = this.getAll();
|
|
2245
|
+
if (!query) return all;
|
|
2246
|
+
const fzf = new Fzf(all, {
|
|
2247
|
+
selector: (cmd) => `${cmd.name} ${cmd.description}`
|
|
2248
|
+
});
|
|
2249
|
+
return fzf.find(query).map((result) => result.item);
|
|
2250
|
+
}
|
|
2251
|
+
searchVisible(query, visibilityCtx) {
|
|
2252
|
+
const visible = this.getAll().filter((cmd) => !cmd.hidden?.(visibilityCtx));
|
|
2253
|
+
if (!query) return visible;
|
|
2254
|
+
const fzf = new Fzf(visible, {
|
|
2255
|
+
selector: (cmd) => `${cmd.name} ${cmd.description}`
|
|
2256
|
+
});
|
|
2257
|
+
return fzf.find(query).map((result) => result.item);
|
|
2258
|
+
}
|
|
2259
|
+
};
|
|
2260
|
+
var registry = new CommandRegistry();
|
|
2261
|
+
registry.register(initCommand);
|
|
2262
|
+
registry.register(configureIdpCommand);
|
|
2263
|
+
registry.register(createApplicationCommand);
|
|
2264
|
+
registry.register(createServiceModuleCommand);
|
|
2265
|
+
registry.register(createUiModuleCommand);
|
|
2266
|
+
for (const cmd of localScriptCommands) {
|
|
2267
|
+
registry.register(cmd);
|
|
2268
|
+
}
|
|
2269
|
+
registry.register(statusCommand);
|
|
2270
|
+
registry.register(managePlatformAdminsCommand);
|
|
2271
|
+
|
|
2272
|
+
// src/app-state.ts
|
|
2273
|
+
var APP_STATE = {
|
|
2274
|
+
IDLE: "idle",
|
|
2275
|
+
PALETTE: "palette",
|
|
2276
|
+
EXECUTING: "executing",
|
|
2277
|
+
PROMPTING: "prompting"
|
|
2278
|
+
};
|
|
2279
|
+
|
|
2280
|
+
// src/hooks/use-command-runner.ts
|
|
2281
|
+
import { useState as useState2, useCallback, useRef } from "react";
|
|
2282
|
+
|
|
2283
|
+
// src/services/create-application.service.ts
|
|
2284
|
+
async function createApplicationService(params, logger) {
|
|
2285
|
+
await createApplication(params, logger);
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
// src/controllers/ui/create-application.ui-controller.ts
|
|
2289
|
+
async function createApplicationUiController(ctx) {
|
|
2290
|
+
if (!await isPlatformInitialized()) {
|
|
2291
|
+
ctx.log("Error: Cannot create an application \u2014 no platform initialized in this directory.");
|
|
2292
|
+
return;
|
|
2293
|
+
}
|
|
2294
|
+
const applicationName = await ctx.prompt("Application name:");
|
|
2295
|
+
if (!/^[a-z0-9-]+$/.test(applicationName)) {
|
|
2296
|
+
ctx.log(`Error: Application name "${applicationName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
|
|
2297
|
+
return;
|
|
2298
|
+
}
|
|
2299
|
+
const applicationDisplayName = await ctx.prompt("Application display name:");
|
|
2300
|
+
const applicationDescription = await ctx.prompt("Application description:");
|
|
2301
|
+
const hasUserInterface = await ctx.confirm("Does this application have a user interface?");
|
|
2302
|
+
const hasBackendService = await ctx.confirm("Does this application have a backend service?");
|
|
2303
|
+
await createApplicationService(
|
|
2304
|
+
{
|
|
2305
|
+
applicationName,
|
|
2306
|
+
applicationDisplayName,
|
|
2307
|
+
applicationDescription,
|
|
2308
|
+
hasUserInterface,
|
|
2309
|
+
hasBackendService
|
|
2310
|
+
},
|
|
2311
|
+
ctx
|
|
2312
|
+
);
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2315
|
+
// src/services/init.service.ts
|
|
2316
|
+
async function initService(params, logger) {
|
|
2317
|
+
await init(params, logger);
|
|
1216
2318
|
}
|
|
1217
2319
|
|
|
1218
2320
|
// src/controllers/ui/init.ui-controller.ts
|
|
@@ -1224,7 +2326,32 @@ async function initUiController(ctx) {
|
|
|
1224
2326
|
const organizationName = await ctx.prompt("Organization name:");
|
|
1225
2327
|
const platformName = await ctx.prompt("Platform name:");
|
|
1226
2328
|
const platformDisplayName = await ctx.prompt("Platform display name:");
|
|
1227
|
-
|
|
2329
|
+
const defaultBootstrapSuffix = "bootstrap-service";
|
|
2330
|
+
const defaultCustomizationUiSuffix = "customization-ui";
|
|
2331
|
+
ctx.log(`Default artifact names:`);
|
|
2332
|
+
ctx.log(` Bootstrap service: ${platformName}-${defaultBootstrapSuffix}`);
|
|
2333
|
+
ctx.log(` Customization UI: ${platformName}-${defaultCustomizationUiSuffix}`);
|
|
2334
|
+
const customize = await ctx.confirm("Customize artifact names?", false);
|
|
2335
|
+
let bootstrapServiceSuffix = defaultBootstrapSuffix;
|
|
2336
|
+
let customizationUiSuffix = defaultCustomizationUiSuffix;
|
|
2337
|
+
if (customize) {
|
|
2338
|
+
const bsSuffix = await ctx.prompt(`Bootstrap service suffix (${platformName}-...):`, defaultBootstrapSuffix);
|
|
2339
|
+
if (!/^[a-z0-9-]+$/.test(bsSuffix)) {
|
|
2340
|
+
ctx.log(`Error: Suffix "${bsSuffix}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
|
|
2341
|
+
return;
|
|
2342
|
+
}
|
|
2343
|
+
bootstrapServiceSuffix = bsSuffix;
|
|
2344
|
+
const cuiSuffix = await ctx.prompt(`Customization UI suffix (${platformName}-...):`, defaultCustomizationUiSuffix);
|
|
2345
|
+
if (!/^[a-z0-9-]+$/.test(cuiSuffix)) {
|
|
2346
|
+
ctx.log(`Error: Suffix "${cuiSuffix}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
|
|
2347
|
+
return;
|
|
2348
|
+
}
|
|
2349
|
+
customizationUiSuffix = cuiSuffix;
|
|
2350
|
+
}
|
|
2351
|
+
await initService(
|
|
2352
|
+
{ organizationName, platformName, platformDisplayName, bootstrapServiceSuffix, customizationUiSuffix },
|
|
2353
|
+
ctx
|
|
2354
|
+
);
|
|
1228
2355
|
}
|
|
1229
2356
|
|
|
1230
2357
|
// src/services/configure-idp.service.ts
|
|
@@ -1239,14 +2366,11 @@ async function configureIdpUiController(ctx) {
|
|
|
1239
2366
|
return;
|
|
1240
2367
|
}
|
|
1241
2368
|
const providers = getAllProviders();
|
|
1242
|
-
const
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
return;
|
|
1248
|
-
}
|
|
1249
|
-
const provider = providers[index];
|
|
2369
|
+
const providerType = await ctx.select(
|
|
2370
|
+
"Select IDP type:",
|
|
2371
|
+
providers.map((p) => ({ label: p.displayName, value: p.type }))
|
|
2372
|
+
);
|
|
2373
|
+
const provider = providers.find((p) => p.type === providerType);
|
|
1250
2374
|
const name = await ctx.prompt("Provider name:");
|
|
1251
2375
|
const issuer = await ctx.prompt("Issuer URL:");
|
|
1252
2376
|
const clientId = await ctx.prompt("Client ID:");
|
|
@@ -1272,19 +2396,38 @@ async function createServiceModuleUiController(ctx) {
|
|
|
1272
2396
|
ctx.log("Error: Cannot create a service module \u2014 no platform initialized in this directory.");
|
|
1273
2397
|
return;
|
|
1274
2398
|
}
|
|
1275
|
-
const
|
|
1276
|
-
if (
|
|
1277
|
-
ctx.log(
|
|
1278
|
-
return;
|
|
2399
|
+
const layout = await findPlatformLayout();
|
|
2400
|
+
if (!layout) {
|
|
2401
|
+
ctx.log("Error: Cannot create a service module \u2014 no platform initialized in this directory.");
|
|
2402
|
+
return;
|
|
2403
|
+
}
|
|
2404
|
+
let manifest = await readManifest(layout.rootDir, layout.coreDirName);
|
|
2405
|
+
if (manifest.applications.length === 0) {
|
|
2406
|
+
ctx.log("No applications found in this platform.");
|
|
2407
|
+
const createFirst = await ctx.confirm("Would you like to create an application first?");
|
|
2408
|
+
if (!createFirst) return;
|
|
2409
|
+
await createApplicationUiController(ctx);
|
|
2410
|
+
manifest = await readManifest(layout.rootDir, layout.coreDirName);
|
|
2411
|
+
if (manifest.applications.length === 0) {
|
|
2412
|
+
ctx.log("Error: No applications available after creation attempt.");
|
|
2413
|
+
return;
|
|
2414
|
+
}
|
|
1279
2415
|
}
|
|
2416
|
+
const applicationName = await ctx.select(
|
|
2417
|
+
"Select application:",
|
|
2418
|
+
manifest.applications.map((a) => ({ label: `${a.name} \u2014 ${a.displayName}`, value: a.name }))
|
|
2419
|
+
);
|
|
1280
2420
|
const serviceName = await ctx.prompt("Service name:");
|
|
1281
2421
|
if (!/^[a-z0-9-]+$/.test(serviceName)) {
|
|
1282
2422
|
ctx.log(`Error: Service name "${serviceName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
|
|
1283
2423
|
return;
|
|
1284
2424
|
}
|
|
2425
|
+
const platformName = manifest.product.name;
|
|
2426
|
+
const includeSuffix = await ctx.confirm(`Include "-service" suffix? (${platformName}-${serviceName}-service)`, true);
|
|
2427
|
+
const serviceNameSuffix = includeSuffix ? "service" : null;
|
|
1285
2428
|
const serviceDisplayName = await ctx.prompt("Service display name:");
|
|
1286
2429
|
await createServiceModuleService(
|
|
1287
|
-
{ applicationName, serviceName, serviceDisplayName },
|
|
2430
|
+
{ applicationName, serviceName, serviceDisplayName, serviceNameSuffix },
|
|
1288
2431
|
ctx
|
|
1289
2432
|
);
|
|
1290
2433
|
}
|
|
@@ -1300,40 +2443,355 @@ async function createUiModuleUiController(ctx) {
|
|
|
1300
2443
|
ctx.log("Error: Cannot create a UI module \u2014 no platform initialized in this directory.");
|
|
1301
2444
|
return;
|
|
1302
2445
|
}
|
|
1303
|
-
const
|
|
1304
|
-
if (
|
|
1305
|
-
ctx.log(
|
|
2446
|
+
const layout = await findPlatformLayout();
|
|
2447
|
+
if (!layout) {
|
|
2448
|
+
ctx.log("Error: Cannot create a UI module \u2014 no platform initialized in this directory.");
|
|
1306
2449
|
return;
|
|
1307
2450
|
}
|
|
2451
|
+
let manifest = await readManifest(layout.rootDir, layout.coreDirName);
|
|
2452
|
+
if (manifest.applications.length === 0) {
|
|
2453
|
+
ctx.log("No applications found in this platform.");
|
|
2454
|
+
const createFirst = await ctx.confirm("Would you like to create an application first?");
|
|
2455
|
+
if (!createFirst) return;
|
|
2456
|
+
await createApplicationUiController(ctx);
|
|
2457
|
+
manifest = await readManifest(layout.rootDir, layout.coreDirName);
|
|
2458
|
+
if (manifest.applications.length === 0) {
|
|
2459
|
+
ctx.log("Error: No applications available after creation attempt.");
|
|
2460
|
+
return;
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
const applicationName = await ctx.select(
|
|
2464
|
+
"Select application:",
|
|
2465
|
+
manifest.applications.map((a) => ({ label: `${a.name} \u2014 ${a.displayName}`, value: a.name }))
|
|
2466
|
+
);
|
|
2467
|
+
const platformName = manifest.product.name;
|
|
2468
|
+
const includeSuffix = await ctx.confirm(`Include "-ui" suffix? (${platformName}-${applicationName}-ui)`, true);
|
|
2469
|
+
const uiModuleSuffix = includeSuffix ? "ui" : null;
|
|
1308
2470
|
const applicationDisplayName = await ctx.prompt("Application display name:");
|
|
1309
2471
|
await createUiModuleService(
|
|
1310
|
-
{ applicationName, applicationDisplayName },
|
|
2472
|
+
{ applicationName, applicationDisplayName, uiModuleSuffix },
|
|
1311
2473
|
ctx
|
|
1312
2474
|
);
|
|
1313
2475
|
}
|
|
1314
2476
|
|
|
2477
|
+
// src/services/status.service.ts
|
|
2478
|
+
async function statusService() {
|
|
2479
|
+
return gatherStatus();
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
// src/utils/theme.ts
|
|
2483
|
+
import chalk from "chalk";
|
|
2484
|
+
var theme = {
|
|
2485
|
+
prompt: chalk.green,
|
|
2486
|
+
commandName: chalk.cyan,
|
|
2487
|
+
commandDescription: chalk.gray,
|
|
2488
|
+
selected: chalk.bgBlue.white,
|
|
2489
|
+
output: chalk.white,
|
|
2490
|
+
muted: chalk.dim,
|
|
2491
|
+
error: chalk.red,
|
|
2492
|
+
success: chalk.green,
|
|
2493
|
+
warning: chalk.yellow,
|
|
2494
|
+
info: chalk.cyan,
|
|
2495
|
+
section: chalk.bold.white,
|
|
2496
|
+
label: chalk.dim
|
|
2497
|
+
};
|
|
2498
|
+
|
|
2499
|
+
// src/commands/status/status-formatter.ts
|
|
2500
|
+
var CHECK = theme.success("\u2713");
|
|
2501
|
+
var CROSS = theme.error("\u2717");
|
|
2502
|
+
function tick(ok) {
|
|
2503
|
+
return ok ? CHECK : CROSS;
|
|
2504
|
+
}
|
|
2505
|
+
function formatStatusLines(result) {
|
|
2506
|
+
const lines = [];
|
|
2507
|
+
if (result.projectInfo) {
|
|
2508
|
+
const p = result.projectInfo;
|
|
2509
|
+
lines.push(theme.info("\u25B8 ") + theme.section(p.displayName) + theme.label(` (${p.productName})`));
|
|
2510
|
+
lines.push(theme.label(" Organization: ") + p.organization + theme.label(` (${p.scope})`));
|
|
2511
|
+
const appList = p.applicationNames.length > 0 ? p.applicationNames.join(", ") : "(none)";
|
|
2512
|
+
lines.push(theme.label(" Applications: ") + `${p.applicationCount} \u2014 ` + appList);
|
|
2513
|
+
lines.push("");
|
|
2514
|
+
}
|
|
2515
|
+
lines.push(theme.section("Prerequisites"));
|
|
2516
|
+
for (const pre of result.prerequisites) {
|
|
2517
|
+
const ok = pre.available && pre.versionOk;
|
|
2518
|
+
const versionStr = pre.version ? theme.label(` ${pre.version}`) : "";
|
|
2519
|
+
const detailStr = pre.detail ? theme.warning(` \u2014 ${pre.detail}`) : "";
|
|
2520
|
+
lines.push(` ${tick(ok)} ${pre.name}${versionStr}${detailStr}`);
|
|
2521
|
+
}
|
|
2522
|
+
lines.push("");
|
|
2523
|
+
lines.push(theme.section("Lifecycle"));
|
|
2524
|
+
lines.push(` ${tick(result.lifecycle.initialized)} Platform initialized`);
|
|
2525
|
+
const installed = result.lifecycle.installed;
|
|
2526
|
+
lines.push(` ${tick(installed)} Dependencies installed`);
|
|
2527
|
+
if (!installed && result.lifecycle.installedDetails.length > 0) {
|
|
2528
|
+
for (const d of result.lifecycle.installedDetails) {
|
|
2529
|
+
if (!d.exists) {
|
|
2530
|
+
lines.push(` ${CROSS} ${theme.label(d.name)} ${theme.warning("(missing node_modules/)")}`);
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
const built = result.lifecycle.built;
|
|
2535
|
+
lines.push(` ${tick(built)} Build completed`);
|
|
2536
|
+
if (!built && result.lifecycle.builtDetails.length > 0) {
|
|
2537
|
+
for (const d of result.lifecycle.builtDetails) {
|
|
2538
|
+
if (!d.exists) {
|
|
2539
|
+
lines.push(` ${CROSS} ${theme.label(d.name)} ${theme.warning("(missing .turbo/)")}`);
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
lines.push(` ${tick(result.lifecycle.running)} Environment running`);
|
|
2544
|
+
if (result.containers.length > 0) {
|
|
2545
|
+
lines.push("");
|
|
2546
|
+
lines.push(theme.section("Containers"));
|
|
2547
|
+
for (const c of result.containers) {
|
|
2548
|
+
const ok = c.state === "running";
|
|
2549
|
+
const stateStr = ok ? theme.success(c.state) : c.state === "not started" ? theme.label(c.state) : theme.error(c.state);
|
|
2550
|
+
const ports = c.ports ? theme.label(` ${c.ports}`) : "";
|
|
2551
|
+
lines.push(` ${tick(ok)} ${c.name.padEnd(35)} ${stateStr}${ports}`);
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
return lines;
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
// src/services/local-script.service.ts
|
|
2558
|
+
async function localScriptService(scriptName, logger, signal, appNames) {
|
|
2559
|
+
await runLocalScript(scriptName, logger, signal, appNames);
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2562
|
+
// src/controllers/ui/status.ui-controller.ts
|
|
2563
|
+
async function statusUiController(ctx) {
|
|
2564
|
+
while (true) {
|
|
2565
|
+
const result = await statusService();
|
|
2566
|
+
for (const line of formatStatusLines(result)) {
|
|
2567
|
+
ctx.log(line);
|
|
2568
|
+
}
|
|
2569
|
+
const { step, appNames, allApps } = computeNextStepInfo(result);
|
|
2570
|
+
if (step === null) {
|
|
2571
|
+
ctx.log("");
|
|
2572
|
+
ctx.log("All checks passed!");
|
|
2573
|
+
return;
|
|
2574
|
+
}
|
|
2575
|
+
if (step === "init") {
|
|
2576
|
+
const shouldRun = await ctx.confirm(`Next step: "init". Run it now?`, true);
|
|
2577
|
+
if (!shouldRun) return;
|
|
2578
|
+
await initUiController(ctx);
|
|
2579
|
+
} else {
|
|
2580
|
+
const appListStr = allApps ? "all apps" : appNames.join(", ");
|
|
2581
|
+
const shouldRun = await ctx.confirm(`Run ${step} for ${appListStr}?`, true);
|
|
2582
|
+
if (!shouldRun) return;
|
|
2583
|
+
await localScriptService(step, ctx, ctx.signal, allApps ? void 0 : appNames);
|
|
2584
|
+
}
|
|
2585
|
+
const resultAfter = await statusService();
|
|
2586
|
+
const nextAfter = computeNextStepInfo(resultAfter);
|
|
2587
|
+
const sameStep = nextAfter.step === step;
|
|
2588
|
+
const noProgress = sameStep && (allApps ? nextAfter.allApps : appNames.some((a) => nextAfter.appNames.includes(a)));
|
|
2589
|
+
if (noProgress) {
|
|
2590
|
+
ctx.log("");
|
|
2591
|
+
ctx.log(`"${step}" did not complete successfully. Check the output above for errors.`);
|
|
2592
|
+
return;
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
// src/controllers/ui/local-script.ui-controller.ts
|
|
2598
|
+
function createLocalScriptUiController(scriptName) {
|
|
2599
|
+
return async (ctx) => {
|
|
2600
|
+
const layout = await findPlatformLayout();
|
|
2601
|
+
if (!layout) {
|
|
2602
|
+
ctx.log(`Error: Cannot run "${scriptName}" \u2014 no platform initialized in this directory.`);
|
|
2603
|
+
return;
|
|
2604
|
+
}
|
|
2605
|
+
let manifest;
|
|
2606
|
+
try {
|
|
2607
|
+
manifest = await readManifest(layout.rootDir, layout.coreDirName);
|
|
2608
|
+
} catch (err) {
|
|
2609
|
+
ctx.log(`Error: Could not read product manifest \u2014 ${formatError(err)}`);
|
|
2610
|
+
return;
|
|
2611
|
+
}
|
|
2612
|
+
if (manifest.applications.length === 0) {
|
|
2613
|
+
await localScriptService(scriptName, ctx, ctx.signal);
|
|
2614
|
+
return;
|
|
2615
|
+
}
|
|
2616
|
+
const options = [
|
|
2617
|
+
{ label: "Platform (Core)", value: CORE_APP_NAME },
|
|
2618
|
+
...manifest.applications.map((app) => ({
|
|
2619
|
+
label: `${app.displayName} (${app.name})`,
|
|
2620
|
+
value: app.name
|
|
2621
|
+
}))
|
|
2622
|
+
];
|
|
2623
|
+
const selected = await ctx.multiselect(
|
|
2624
|
+
`Select targets to ${scriptName}:`,
|
|
2625
|
+
options
|
|
2626
|
+
);
|
|
2627
|
+
if (selected.length === 0) {
|
|
2628
|
+
ctx.log("Nothing selected. Nothing to do.");
|
|
2629
|
+
return;
|
|
2630
|
+
}
|
|
2631
|
+
await localScriptService(scriptName, ctx, ctx.signal, selected);
|
|
2632
|
+
};
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
// src/controllers/ui/manage-platform-admins.ui-controller.ts
|
|
2636
|
+
import { execSync } from "child_process";
|
|
2637
|
+
|
|
2638
|
+
// src/services/manage-platform-admins.service.ts
|
|
2639
|
+
async function listAdminsService(logger) {
|
|
2640
|
+
return listPlatformAdmins(logger);
|
|
2641
|
+
}
|
|
2642
|
+
async function addAdminService(username, logger) {
|
|
2643
|
+
return addPlatformAdmin(username, logger);
|
|
2644
|
+
}
|
|
2645
|
+
async function removeAdminService(ruleId, logger) {
|
|
2646
|
+
return removePlatformAdmin(ruleId, logger);
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2649
|
+
// src/controllers/ui/manage-platform-admins.ui-controller.ts
|
|
2650
|
+
function getDefaultUsername() {
|
|
2651
|
+
try {
|
|
2652
|
+
const email = execSync("git config user.email", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
2653
|
+
return email || void 0;
|
|
2654
|
+
} catch {
|
|
2655
|
+
return void 0;
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
async function managePlatformAdminsUiController(ctx) {
|
|
2659
|
+
if (!await isPlatformInitialized()) {
|
|
2660
|
+
ctx.log("Error: Cannot manage platform admins \u2014 no platform initialized in this directory.");
|
|
2661
|
+
return;
|
|
2662
|
+
}
|
|
2663
|
+
while (true) {
|
|
2664
|
+
const action = await ctx.select("What would you like to do?", [
|
|
2665
|
+
{ label: "List current admins", value: "list" },
|
|
2666
|
+
{ label: "Add admin(s)", value: "add" },
|
|
2667
|
+
{ label: "Remove admin(s)", value: "remove" },
|
|
2668
|
+
{ label: "Back to main menu", value: "back" }
|
|
2669
|
+
]);
|
|
2670
|
+
if (action === "list") {
|
|
2671
|
+
await handleList(ctx);
|
|
2672
|
+
} else if (action === "add") {
|
|
2673
|
+
await handleAdd(ctx);
|
|
2674
|
+
} else if (action === "remove") {
|
|
2675
|
+
await handleRemove(ctx);
|
|
2676
|
+
} else if (action === "back") {
|
|
2677
|
+
break;
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
async function handleList(ctx) {
|
|
2682
|
+
const admins = await listAdminsService(ctx);
|
|
2683
|
+
if (admins.length === 0) {
|
|
2684
|
+
ctx.log("No platform admins found.");
|
|
2685
|
+
} else {
|
|
2686
|
+
ctx.log("Current platform admins:");
|
|
2687
|
+
for (const admin of admins) {
|
|
2688
|
+
ctx.log(` - ${admin.username}`);
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
async function handleAdd(ctx) {
|
|
2693
|
+
const currentAdmins = await listAdminsService(ctx);
|
|
2694
|
+
const existingUsernames = new Set(currentAdmins.map((a) => a.username));
|
|
2695
|
+
const pendingAdmins = [];
|
|
2696
|
+
const suggestedUsername = getDefaultUsername();
|
|
2697
|
+
const effectiveSuggestion = suggestedUsername && !existingUsernames.has(suggestedUsername) ? suggestedUsername : void 0;
|
|
2698
|
+
let isFirstIteration = true;
|
|
2699
|
+
while (true) {
|
|
2700
|
+
const defaultValue = isFirstIteration ? effectiveSuggestion : void 0;
|
|
2701
|
+
const username = (await ctx.prompt("Username to add as admin:", defaultValue)).trim();
|
|
2702
|
+
isFirstIteration = false;
|
|
2703
|
+
if (!username) {
|
|
2704
|
+
ctx.log("Username cannot be empty, skipping.");
|
|
2705
|
+
} else if (existingUsernames.has(username)) {
|
|
2706
|
+
ctx.log(`'${username}' is already a platform admin.`);
|
|
2707
|
+
} else if (pendingAdmins.includes(username)) {
|
|
2708
|
+
ctx.log(`'${username}' is already in the pending list.`);
|
|
2709
|
+
} else {
|
|
2710
|
+
pendingAdmins.push(username);
|
|
2711
|
+
ctx.log(`Admins to add: ${pendingAdmins.join(", ")}`);
|
|
2712
|
+
}
|
|
2713
|
+
const addAnother = await ctx.confirm("Add another admin?", false);
|
|
2714
|
+
if (!addAnother) break;
|
|
2715
|
+
}
|
|
2716
|
+
if (pendingAdmins.length === 0) {
|
|
2717
|
+
ctx.log("No admins to add.");
|
|
2718
|
+
return;
|
|
2719
|
+
}
|
|
2720
|
+
let successCount = 0;
|
|
2721
|
+
for (const username of pendingAdmins) {
|
|
2722
|
+
const ok = await addAdminService(username, ctx);
|
|
2723
|
+
if (ok) {
|
|
2724
|
+
ctx.log(`'${username}' granted platform admin access.`);
|
|
2725
|
+
successCount++;
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
ctx.log(`Successfully added ${successCount} of ${pendingAdmins.length} admin(s).`);
|
|
2729
|
+
}
|
|
2730
|
+
async function handleRemove(ctx) {
|
|
2731
|
+
const admins = await listAdminsService(ctx);
|
|
2732
|
+
if (admins.length === 0) {
|
|
2733
|
+
ctx.log("No platform admins to remove.");
|
|
2734
|
+
return;
|
|
2735
|
+
}
|
|
2736
|
+
const selected = await ctx.multiselect(
|
|
2737
|
+
"Select admins to remove:",
|
|
2738
|
+
admins.map((a) => ({ label: a.username, value: a.ruleId }))
|
|
2739
|
+
);
|
|
2740
|
+
if (selected.length === 0) {
|
|
2741
|
+
ctx.log("No admins selected.");
|
|
2742
|
+
return;
|
|
2743
|
+
}
|
|
2744
|
+
const confirmed = await ctx.confirm(`Remove ${selected.length} admin(s)?`, false);
|
|
2745
|
+
if (!confirmed) {
|
|
2746
|
+
ctx.log("Cancelled.");
|
|
2747
|
+
return;
|
|
2748
|
+
}
|
|
2749
|
+
let successCount = 0;
|
|
2750
|
+
for (const ruleId of selected) {
|
|
2751
|
+
const admin = admins.find((a) => a.ruleId === ruleId);
|
|
2752
|
+
const ok = await removeAdminService(ruleId, ctx);
|
|
2753
|
+
if (ok) {
|
|
2754
|
+
ctx.log(`'${admin?.username ?? ruleId}' removed from platform admins.`);
|
|
2755
|
+
successCount++;
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
ctx.log(`Successfully removed ${successCount} of ${selected.length} admin(s).`);
|
|
2759
|
+
}
|
|
2760
|
+
|
|
1315
2761
|
// src/controllers/ui/registry.ts
|
|
1316
2762
|
var uiControllers = /* @__PURE__ */ new Map([
|
|
1317
2763
|
[CREATE_APPLICATION_COMMAND_NAME, createApplicationUiController],
|
|
1318
2764
|
[INIT_COMMAND_NAME, initUiController],
|
|
1319
2765
|
[CONFIGURE_IDP_COMMAND_NAME, configureIdpUiController],
|
|
1320
2766
|
[CREATE_SERVICE_MODULE_COMMAND_NAME, createServiceModuleUiController],
|
|
1321
|
-
[CREATE_UI_MODULE_COMMAND_NAME, createUiModuleUiController]
|
|
2767
|
+
[CREATE_UI_MODULE_COMMAND_NAME, createUiModuleUiController],
|
|
2768
|
+
[STATUS_COMMAND_NAME, statusUiController],
|
|
2769
|
+
[INSTALL_COMMAND_NAME, createLocalScriptUiController(INSTALL_COMMAND_NAME)],
|
|
2770
|
+
[BUILD_COMMAND_NAME, createLocalScriptUiController(BUILD_COMMAND_NAME)],
|
|
2771
|
+
[START_COMMAND_NAME, createLocalScriptUiController(START_COMMAND_NAME)],
|
|
2772
|
+
[STOP_COMMAND_NAME, createLocalScriptUiController(STOP_COMMAND_NAME)],
|
|
2773
|
+
[DESTROY_COMMAND_NAME, createLocalScriptUiController(DESTROY_COMMAND_NAME)],
|
|
2774
|
+
[MANAGE_PLATFORM_ADMINS_COMMAND_NAME, managePlatformAdminsUiController]
|
|
1322
2775
|
]);
|
|
1323
2776
|
|
|
1324
2777
|
// src/hooks/use-command-runner.ts
|
|
1325
|
-
function useCommandRunner({ appendStaticItem, setState }) {
|
|
2778
|
+
function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
|
|
1326
2779
|
const outputCountRef = useRef(0);
|
|
1327
2780
|
const abortControllerRef = useRef(null);
|
|
1328
2781
|
const promptResolveRef = useRef(null);
|
|
1329
2782
|
const [promptMessage, setPromptMessage] = useState2("");
|
|
1330
2783
|
const [promptValue, setPromptValue] = useState2("");
|
|
2784
|
+
const [promptMode, setPromptMode] = useState2({ kind: "text" });
|
|
2785
|
+
const [selectIndex, setSelectIndex] = useState2(0);
|
|
2786
|
+
const [confirmValue, setConfirmValue] = useState2(true);
|
|
2787
|
+
const [multiselectChecked, setMultiselectChecked] = useState2(/* @__PURE__ */ new Set());
|
|
1331
2788
|
const abortExecution = useCallback(() => {
|
|
1332
2789
|
abortControllerRef.current?.abort();
|
|
1333
2790
|
abortControllerRef.current = null;
|
|
1334
2791
|
promptResolveRef.current = null;
|
|
2792
|
+
setPromptMode({ kind: "text" });
|
|
1335
2793
|
}, []);
|
|
1336
|
-
const
|
|
2794
|
+
const runCommand2 = useCallback(
|
|
1337
2795
|
(cmd) => {
|
|
1338
2796
|
const controller = new AbortController();
|
|
1339
2797
|
abortControllerRef.current = controller;
|
|
@@ -1346,15 +2804,73 @@ function useCommandRunner({ appendStaticItem, setState }) {
|
|
|
1346
2804
|
appendStaticItem({ kind: "output", id, line: message });
|
|
1347
2805
|
}
|
|
1348
2806
|
},
|
|
1349
|
-
prompt(message) {
|
|
1350
|
-
return new Promise((
|
|
2807
|
+
prompt(message, defaultValue) {
|
|
2808
|
+
return new Promise((resolve6, reject) => {
|
|
2809
|
+
if (controller.signal.aborted) {
|
|
2810
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
2811
|
+
return;
|
|
2812
|
+
}
|
|
2813
|
+
setPromptMessage(message);
|
|
2814
|
+
setPromptValue(defaultValue ?? "");
|
|
2815
|
+
setPromptMode({ kind: "text" });
|
|
2816
|
+
promptResolveRef.current = resolve6;
|
|
2817
|
+
setState(APP_STATE.PROMPTING);
|
|
2818
|
+
controller.signal.addEventListener(
|
|
2819
|
+
"abort",
|
|
2820
|
+
() => reject(new DOMException("Aborted", "AbortError")),
|
|
2821
|
+
{ once: true }
|
|
2822
|
+
);
|
|
2823
|
+
});
|
|
2824
|
+
},
|
|
2825
|
+
select(message, options) {
|
|
2826
|
+
return new Promise((resolve6, reject) => {
|
|
2827
|
+
if (controller.signal.aborted) {
|
|
2828
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
2829
|
+
return;
|
|
2830
|
+
}
|
|
2831
|
+
setPromptMessage(message);
|
|
2832
|
+
setPromptMode({ kind: "select", options });
|
|
2833
|
+
setSelectIndex(0);
|
|
2834
|
+
promptResolveRef.current = resolve6;
|
|
2835
|
+
setState(APP_STATE.PROMPTING);
|
|
2836
|
+
controller.signal.addEventListener(
|
|
2837
|
+
"abort",
|
|
2838
|
+
() => reject(new DOMException("Aborted", "AbortError")),
|
|
2839
|
+
{ once: true }
|
|
2840
|
+
);
|
|
2841
|
+
});
|
|
2842
|
+
},
|
|
2843
|
+
multiselect(message, options) {
|
|
2844
|
+
return new Promise((resolve6, reject) => {
|
|
2845
|
+
if (controller.signal.aborted) {
|
|
2846
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
2847
|
+
return;
|
|
2848
|
+
}
|
|
2849
|
+
setPromptMessage(message);
|
|
2850
|
+
setPromptMode({ kind: "multiselect", options });
|
|
2851
|
+
setSelectIndex(0);
|
|
2852
|
+
setMultiselectChecked(new Set(options.map((_, i) => i)));
|
|
2853
|
+
promptResolveRef.current = (value) => {
|
|
2854
|
+
resolve6(value ? value.split(",") : []);
|
|
2855
|
+
};
|
|
2856
|
+
setState(APP_STATE.PROMPTING);
|
|
2857
|
+
controller.signal.addEventListener(
|
|
2858
|
+
"abort",
|
|
2859
|
+
() => reject(new DOMException("Aborted", "AbortError")),
|
|
2860
|
+
{ once: true }
|
|
2861
|
+
);
|
|
2862
|
+
});
|
|
2863
|
+
},
|
|
2864
|
+
confirm(message, defaultValue) {
|
|
2865
|
+
return new Promise((resolve6, reject) => {
|
|
1351
2866
|
if (controller.signal.aborted) {
|
|
1352
2867
|
reject(new DOMException("Aborted", "AbortError"));
|
|
1353
2868
|
return;
|
|
1354
2869
|
}
|
|
1355
2870
|
setPromptMessage(message);
|
|
1356
|
-
|
|
1357
|
-
|
|
2871
|
+
setPromptMode({ kind: "confirm" });
|
|
2872
|
+
setConfirmValue(defaultValue ?? true);
|
|
2873
|
+
promptResolveRef.current = (value) => resolve6(value === "true");
|
|
1358
2874
|
setState(APP_STATE.PROMPTING);
|
|
1359
2875
|
controller.signal.addEventListener(
|
|
1360
2876
|
"abort",
|
|
@@ -1374,37 +2890,47 @@ function useCommandRunner({ appendStaticItem, setState }) {
|
|
|
1374
2890
|
uiController(ctx).then(() => {
|
|
1375
2891
|
if (!controller.signal.aborted) {
|
|
1376
2892
|
setState(APP_STATE.IDLE);
|
|
2893
|
+
onCommandComplete?.();
|
|
1377
2894
|
}
|
|
1378
2895
|
}).catch((err) => {
|
|
1379
2896
|
if (controller.signal.aborted) return;
|
|
1380
2897
|
const id = `output-${outputCountRef.current++}`;
|
|
1381
2898
|
appendStaticItem({ kind: "output", id, line: `Error: ${formatError(err)}` });
|
|
1382
2899
|
setState(APP_STATE.IDLE);
|
|
2900
|
+
onCommandComplete?.();
|
|
1383
2901
|
});
|
|
1384
2902
|
},
|
|
1385
|
-
[appendStaticItem, setState]
|
|
2903
|
+
[appendStaticItem, setState, onCommandComplete]
|
|
1386
2904
|
);
|
|
1387
2905
|
const handlePromptSubmit = useCallback(
|
|
1388
2906
|
(value) => {
|
|
1389
|
-
const
|
|
2907
|
+
const resolve6 = promptResolveRef.current;
|
|
1390
2908
|
promptResolveRef.current = null;
|
|
2909
|
+
setPromptMode({ kind: "text" });
|
|
1391
2910
|
setState(APP_STATE.EXECUTING);
|
|
1392
|
-
|
|
2911
|
+
resolve6?.(value);
|
|
1393
2912
|
},
|
|
1394
2913
|
[setState]
|
|
1395
2914
|
);
|
|
1396
2915
|
return {
|
|
1397
|
-
runCommand,
|
|
2916
|
+
runCommand: runCommand2,
|
|
1398
2917
|
handlePromptSubmit,
|
|
1399
2918
|
abortExecution,
|
|
1400
2919
|
promptMessage,
|
|
1401
2920
|
promptValue,
|
|
1402
|
-
setPromptValue
|
|
2921
|
+
setPromptValue,
|
|
2922
|
+
promptMode,
|
|
2923
|
+
selectIndex,
|
|
2924
|
+
setSelectIndex,
|
|
2925
|
+
confirmValue,
|
|
2926
|
+
setConfirmValue,
|
|
2927
|
+
multiselectChecked,
|
|
2928
|
+
setMultiselectChecked
|
|
1403
2929
|
};
|
|
1404
2930
|
}
|
|
1405
2931
|
|
|
1406
2932
|
// src/app.tsx
|
|
1407
|
-
import { jsx as
|
|
2933
|
+
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1408
2934
|
var require2 = createRequire(import.meta.url);
|
|
1409
2935
|
var { version } = require2("../package.json");
|
|
1410
2936
|
function App() {
|
|
@@ -1413,12 +2939,35 @@ function App() {
|
|
|
1413
2939
|
const [inputValue, setInputValue] = useState3("");
|
|
1414
2940
|
const [selectedIndex, setSelectedIndex] = useState3(0);
|
|
1415
2941
|
const [staticItems, setStaticItems] = useState3([{ kind: "banner", id: "banner" }]);
|
|
2942
|
+
const [platformInitialized, setPlatformInitialized] = useState3(false);
|
|
2943
|
+
useEffect2(() => {
|
|
2944
|
+
isPlatformInitialized().then(setPlatformInitialized).catch(() => {
|
|
2945
|
+
});
|
|
2946
|
+
}, []);
|
|
1416
2947
|
const appendStaticItem = useCallback2((item) => {
|
|
1417
2948
|
setStaticItems((prev) => [...prev, item]);
|
|
1418
2949
|
}, []);
|
|
1419
|
-
const
|
|
2950
|
+
const handleCommandComplete = useCallback2(() => {
|
|
2951
|
+
isPlatformInitialized().then(setPlatformInitialized).catch(() => {
|
|
2952
|
+
});
|
|
2953
|
+
}, []);
|
|
2954
|
+
const {
|
|
2955
|
+
runCommand: runCommand2,
|
|
2956
|
+
handlePromptSubmit,
|
|
2957
|
+
abortExecution,
|
|
2958
|
+
promptMessage,
|
|
2959
|
+
promptValue,
|
|
2960
|
+
setPromptValue,
|
|
2961
|
+
promptMode,
|
|
2962
|
+
selectIndex,
|
|
2963
|
+
setSelectIndex,
|
|
2964
|
+
confirmValue,
|
|
2965
|
+
setConfirmValue,
|
|
2966
|
+
multiselectChecked,
|
|
2967
|
+
setMultiselectChecked
|
|
2968
|
+
} = useCommandRunner({ appendStaticItem, setState, onCommandComplete: handleCommandComplete });
|
|
1420
2969
|
const query = inputValue.startsWith("/") ? inputValue.slice(1) : "";
|
|
1421
|
-
const filteredCommands = registry.
|
|
2970
|
+
const filteredCommands = registry.searchVisible(query, { platformInitialized });
|
|
1422
2971
|
useInput(
|
|
1423
2972
|
(input, key) => {
|
|
1424
2973
|
if (key.ctrl && input === "c") {
|
|
@@ -1430,6 +2979,73 @@ function App() {
|
|
|
1430
2979
|
setState(APP_STATE.IDLE);
|
|
1431
2980
|
return;
|
|
1432
2981
|
}
|
|
2982
|
+
if (state === APP_STATE.PROMPTING) {
|
|
2983
|
+
if (promptMode.kind === "select") {
|
|
2984
|
+
if (key.upArrow) {
|
|
2985
|
+
setSelectIndex((prev) => Math.max(0, prev - 1));
|
|
2986
|
+
return;
|
|
2987
|
+
}
|
|
2988
|
+
if (key.downArrow) {
|
|
2989
|
+
setSelectIndex((prev) => Math.min(promptMode.options.length - 1, prev + 1));
|
|
2990
|
+
return;
|
|
2991
|
+
}
|
|
2992
|
+
if (key.return) {
|
|
2993
|
+
const selected = promptMode.options[selectIndex];
|
|
2994
|
+
if (selected) handlePromptSubmit(selected.value);
|
|
2995
|
+
return;
|
|
2996
|
+
}
|
|
2997
|
+
return;
|
|
2998
|
+
}
|
|
2999
|
+
if (promptMode.kind === "multiselect") {
|
|
3000
|
+
if (key.upArrow) {
|
|
3001
|
+
setSelectIndex((prev) => Math.max(0, prev - 1));
|
|
3002
|
+
return;
|
|
3003
|
+
}
|
|
3004
|
+
if (key.downArrow) {
|
|
3005
|
+
setSelectIndex((prev) => Math.min(promptMode.options.length, prev + 1));
|
|
3006
|
+
return;
|
|
3007
|
+
}
|
|
3008
|
+
if (input === " ") {
|
|
3009
|
+
if (selectIndex === 0) {
|
|
3010
|
+
const allChecked = multiselectChecked.size === promptMode.options.length;
|
|
3011
|
+
setMultiselectChecked(
|
|
3012
|
+
allChecked ? /* @__PURE__ */ new Set() : new Set(promptMode.options.map((_, i) => i))
|
|
3013
|
+
);
|
|
3014
|
+
} else {
|
|
3015
|
+
const optIdx = selectIndex - 1;
|
|
3016
|
+
setMultiselectChecked((prev) => {
|
|
3017
|
+
const next = new Set(prev);
|
|
3018
|
+
if (next.has(optIdx)) next.delete(optIdx);
|
|
3019
|
+
else next.add(optIdx);
|
|
3020
|
+
return next;
|
|
3021
|
+
});
|
|
3022
|
+
}
|
|
3023
|
+
return;
|
|
3024
|
+
}
|
|
3025
|
+
if (key.return) {
|
|
3026
|
+
const selected = promptMode.options.filter((_, i) => multiselectChecked.has(i)).map((o) => o.value).join(",");
|
|
3027
|
+
handlePromptSubmit(selected);
|
|
3028
|
+
return;
|
|
3029
|
+
}
|
|
3030
|
+
return;
|
|
3031
|
+
}
|
|
3032
|
+
if (promptMode.kind === "confirm") {
|
|
3033
|
+
if (key.leftArrow || key.upArrow) {
|
|
3034
|
+
setConfirmValue(true);
|
|
3035
|
+
return;
|
|
3036
|
+
}
|
|
3037
|
+
if (key.rightArrow || key.downArrow) {
|
|
3038
|
+
setConfirmValue(false);
|
|
3039
|
+
return;
|
|
3040
|
+
}
|
|
3041
|
+
if (key.return) {
|
|
3042
|
+
handlePromptSubmit(confirmValue ? "true" : "false");
|
|
3043
|
+
return;
|
|
3044
|
+
}
|
|
3045
|
+
return;
|
|
3046
|
+
}
|
|
3047
|
+
return;
|
|
3048
|
+
}
|
|
1433
3049
|
if (state === APP_STATE.PALETTE) {
|
|
1434
3050
|
if (key.escape) {
|
|
1435
3051
|
setState(APP_STATE.IDLE);
|
|
@@ -1448,7 +3064,7 @@ function App() {
|
|
|
1448
3064
|
const cmd = filteredCommands[selectedIndex];
|
|
1449
3065
|
if (cmd) {
|
|
1450
3066
|
setInputValue("");
|
|
1451
|
-
|
|
3067
|
+
runCommand2(cmd);
|
|
1452
3068
|
}
|
|
1453
3069
|
return;
|
|
1454
3070
|
}
|
|
@@ -1484,24 +3100,34 @@ function App() {
|
|
|
1484
3100
|
if (!value.startsWith("/")) return;
|
|
1485
3101
|
const name = value.slice(1).trim();
|
|
1486
3102
|
const cmd = registry.get(name);
|
|
1487
|
-
if (cmd)
|
|
3103
|
+
if (cmd) runCommand2(cmd);
|
|
1488
3104
|
},
|
|
1489
|
-
[
|
|
3105
|
+
[runCommand2]
|
|
1490
3106
|
);
|
|
1491
3107
|
function renderActiveArea() {
|
|
1492
3108
|
switch (state) {
|
|
1493
3109
|
case APP_STATE.EXECUTING:
|
|
1494
|
-
return /* @__PURE__ */
|
|
1495
|
-
/* @__PURE__ */
|
|
1496
|
-
/* @__PURE__ */
|
|
3110
|
+
return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "row", gap: 1, children: [
|
|
3111
|
+
/* @__PURE__ */ jsx10(Spinner, { label: "Running\u2026" }),
|
|
3112
|
+
/* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "(esc to cancel)" })
|
|
1497
3113
|
] });
|
|
1498
3114
|
case APP_STATE.PROMPTING:
|
|
1499
|
-
return /* @__PURE__ */
|
|
1500
|
-
/* @__PURE__ */
|
|
1501
|
-
/* @__PURE__ */
|
|
3115
|
+
return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", children: [
|
|
3116
|
+
/* @__PURE__ */ jsx10(Text10, { color: "cyan", children: promptMessage }),
|
|
3117
|
+
promptMode.kind === "select" && /* @__PURE__ */ jsx10(SelectList, { options: promptMode.options, selectedIndex: selectIndex }),
|
|
3118
|
+
promptMode.kind === "multiselect" && /* @__PURE__ */ jsx10(
|
|
3119
|
+
MultiSelectList,
|
|
3120
|
+
{
|
|
3121
|
+
options: promptMode.options,
|
|
3122
|
+
focusedIndex: selectIndex,
|
|
3123
|
+
checkedIndices: multiselectChecked
|
|
3124
|
+
}
|
|
3125
|
+
),
|
|
3126
|
+
promptMode.kind === "confirm" && /* @__PURE__ */ jsx10(ConfirmPrompt, { value: confirmValue }),
|
|
3127
|
+
promptMode.kind === "text" && /* @__PURE__ */ jsx10(Prompt, { value: promptValue, onChange: setPromptValue, onSubmit: handlePromptSubmit })
|
|
1502
3128
|
] });
|
|
1503
3129
|
default:
|
|
1504
|
-
return /* @__PURE__ */
|
|
3130
|
+
return /* @__PURE__ */ jsx10(
|
|
1505
3131
|
Prompt,
|
|
1506
3132
|
{
|
|
1507
3133
|
value: inputValue,
|
|
@@ -1514,17 +3140,14 @@ function App() {
|
|
|
1514
3140
|
);
|
|
1515
3141
|
}
|
|
1516
3142
|
}
|
|
1517
|
-
return /* @__PURE__ */
|
|
1518
|
-
/* @__PURE__ */
|
|
3143
|
+
return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", children: [
|
|
3144
|
+
/* @__PURE__ */ jsx10(ScrollbackHistory, { items: staticItems, version }),
|
|
1519
3145
|
renderActiveArea(),
|
|
1520
|
-
state === APP_STATE.PALETTE && /* @__PURE__ */
|
|
3146
|
+
state === APP_STATE.PALETTE && /* @__PURE__ */ jsx10(CommandPalette, { commands: filteredCommands, selectedIndex })
|
|
1521
3147
|
] });
|
|
1522
3148
|
}
|
|
1523
3149
|
|
|
1524
3150
|
// src/controllers/cli/create-application.cli-controller.ts
|
|
1525
|
-
import { readFile as readFile13 } from "fs/promises";
|
|
1526
|
-
import { join as join22 } from "path";
|
|
1527
|
-
import { cwd as cwd8 } from "process";
|
|
1528
3151
|
async function createApplicationCliController(args2) {
|
|
1529
3152
|
if (!await isPlatformInitialized()) {
|
|
1530
3153
|
console.error("Error: Cannot create an application \u2014 no platform initialized in this directory.");
|
|
@@ -1545,28 +3168,8 @@ async function createApplicationCliController(args2) {
|
|
|
1545
3168
|
console.error(`Error: Application name "${applicationName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
|
|
1546
3169
|
process.exit(1);
|
|
1547
3170
|
}
|
|
1548
|
-
let organizationName;
|
|
1549
|
-
let platformName;
|
|
1550
|
-
try {
|
|
1551
|
-
const corePackageJson = JSON.parse(
|
|
1552
|
-
await readFile13(join22(cwd8(), "core", "package.json"), "utf-8")
|
|
1553
|
-
);
|
|
1554
|
-
const scopeMatch = corePackageJson.name.match(/^@([^/]+)\//);
|
|
1555
|
-
organizationName = scopeMatch?.[1];
|
|
1556
|
-
const rawName = corePackageJson.name.replace(/^@[^/]+\//, "").replace(/-core$/, "");
|
|
1557
|
-
platformName = rawName;
|
|
1558
|
-
if (!organizationName || !platformName) {
|
|
1559
|
-
throw new Error(`Could not parse organization/platform from package name: "${corePackageJson.name}"`);
|
|
1560
|
-
}
|
|
1561
|
-
} catch (err) {
|
|
1562
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1563
|
-
console.error(`Error: Could not read core/package.json \u2014 ${message}`);
|
|
1564
|
-
process.exit(1);
|
|
1565
|
-
}
|
|
1566
3171
|
await createApplicationService(
|
|
1567
3172
|
{
|
|
1568
|
-
organizationName,
|
|
1569
|
-
platformName,
|
|
1570
3173
|
applicationName,
|
|
1571
3174
|
applicationDisplayName,
|
|
1572
3175
|
applicationDescription,
|
|
@@ -1583,13 +3186,13 @@ async function initCliController(args2) {
|
|
|
1583
3186
|
console.error("Error: Cannot initialize a new platform \u2014 a platform is already initialized in this directory.");
|
|
1584
3187
|
process.exit(1);
|
|
1585
3188
|
}
|
|
1586
|
-
const { organizationName, platformName, platformDisplayName } = args2;
|
|
3189
|
+
const { organizationName, platformName, platformDisplayName, bootstrapServiceSuffix, customizationUiSuffix } = args2;
|
|
1587
3190
|
if (!organizationName || !platformName || !platformDisplayName) {
|
|
1588
3191
|
console.error("Error: organizationName, platformName, and platformDisplayName are required.");
|
|
1589
3192
|
process.exit(1);
|
|
1590
3193
|
}
|
|
1591
3194
|
await initService(
|
|
1592
|
-
{ organizationName, platformName, platformDisplayName },
|
|
3195
|
+
{ organizationName, platformName, platformDisplayName, bootstrapServiceSuffix, customizationUiSuffix },
|
|
1593
3196
|
{ log: console.log }
|
|
1594
3197
|
);
|
|
1595
3198
|
}
|
|
@@ -1623,77 +3226,6 @@ async function configureIdpCliController(args2) {
|
|
|
1623
3226
|
await configureIdpService({ providerType, name, issuer, clientId, clientSecret, extras }, logger);
|
|
1624
3227
|
}
|
|
1625
3228
|
|
|
1626
|
-
// src/utils/run-npm-script.ts
|
|
1627
|
-
import { spawn } from "child_process";
|
|
1628
|
-
import { access as access4 } from "fs/promises";
|
|
1629
|
-
import { join as join23 } from "path";
|
|
1630
|
-
async function runNpmScript(scriptName, logger, signal) {
|
|
1631
|
-
const localDir = join23(process.cwd(), "local");
|
|
1632
|
-
try {
|
|
1633
|
-
await access4(localDir);
|
|
1634
|
-
} catch {
|
|
1635
|
-
logger.log(`Error: "local/" directory not found. Run "platform init" first.`);
|
|
1636
|
-
return;
|
|
1637
|
-
}
|
|
1638
|
-
return new Promise((resolve) => {
|
|
1639
|
-
const child = spawn("npm", ["run", scriptName], {
|
|
1640
|
-
cwd: localDir,
|
|
1641
|
-
shell: true,
|
|
1642
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
1643
|
-
});
|
|
1644
|
-
const onAbort = () => {
|
|
1645
|
-
child.kill("SIGTERM");
|
|
1646
|
-
};
|
|
1647
|
-
if (signal) {
|
|
1648
|
-
if (signal.aborted) {
|
|
1649
|
-
child.kill("SIGTERM");
|
|
1650
|
-
resolve();
|
|
1651
|
-
return;
|
|
1652
|
-
}
|
|
1653
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
1654
|
-
}
|
|
1655
|
-
child.stdout.on("data", (data) => {
|
|
1656
|
-
for (const line of data.toString().split("\n")) {
|
|
1657
|
-
if (line.trim()) logger.log(line);
|
|
1658
|
-
}
|
|
1659
|
-
});
|
|
1660
|
-
child.stderr.on("data", (data) => {
|
|
1661
|
-
for (const line of data.toString().split("\n")) {
|
|
1662
|
-
if (line.trim()) logger.log(line);
|
|
1663
|
-
}
|
|
1664
|
-
});
|
|
1665
|
-
child.on("close", (code, sig) => {
|
|
1666
|
-
signal?.removeEventListener("abort", onAbort);
|
|
1667
|
-
if (sig === "SIGTERM" || signal?.aborted) {
|
|
1668
|
-
logger.log(`Command cancelled.`);
|
|
1669
|
-
} else if (code !== 0) {
|
|
1670
|
-
logger.log(`Command "npm run ${scriptName}" exited with code ${code}.`);
|
|
1671
|
-
}
|
|
1672
|
-
resolve();
|
|
1673
|
-
});
|
|
1674
|
-
child.on("error", (err) => {
|
|
1675
|
-
signal?.removeEventListener("abort", onAbort);
|
|
1676
|
-
logger.log(`Failed to run "npm run ${scriptName}": ${err.message}`);
|
|
1677
|
-
resolve();
|
|
1678
|
-
});
|
|
1679
|
-
});
|
|
1680
|
-
}
|
|
1681
|
-
|
|
1682
|
-
// src/commands/local-scripts/local-script.command.ts
|
|
1683
|
-
var INSTALL_COMMAND_NAME = "install";
|
|
1684
|
-
var BUILD_COMMAND_NAME = "build";
|
|
1685
|
-
var START_COMMAND_NAME = "start";
|
|
1686
|
-
var STOP_COMMAND_NAME = "stop";
|
|
1687
|
-
var DESTROY_COMMAND_NAME = "destroy";
|
|
1688
|
-
async function runLocalScript(scriptName, logger, signal) {
|
|
1689
|
-
await runNpmScript(scriptName, logger, signal);
|
|
1690
|
-
}
|
|
1691
|
-
|
|
1692
|
-
// src/services/local-script.service.ts
|
|
1693
|
-
async function localScriptService(scriptName, logger, signal) {
|
|
1694
|
-
await runLocalScript(scriptName, logger, signal);
|
|
1695
|
-
}
|
|
1696
|
-
|
|
1697
3229
|
// src/utils/cli-spinner.ts
|
|
1698
3230
|
var FRAMES2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1699
3231
|
var INTERVAL_MS2 = 80;
|
|
@@ -1729,10 +3261,11 @@ function createCliSpinner(label) {
|
|
|
1729
3261
|
|
|
1730
3262
|
// src/controllers/cli/local-script.cli-controller.ts
|
|
1731
3263
|
function createLocalScriptCliController(scriptName) {
|
|
1732
|
-
return async (
|
|
3264
|
+
return async (args2) => {
|
|
1733
3265
|
const spinner = createCliSpinner(`Running ${scriptName}\u2026`);
|
|
1734
3266
|
spinner.start();
|
|
1735
|
-
|
|
3267
|
+
const appNames = args2._positional ? JSON.parse(args2._positional) : void 0;
|
|
3268
|
+
await localScriptService(scriptName, { log: (msg) => spinner.log(msg) }, void 0, appNames);
|
|
1736
3269
|
spinner.stop();
|
|
1737
3270
|
};
|
|
1738
3271
|
}
|
|
@@ -1783,6 +3316,88 @@ async function createUiModuleCliController(args2) {
|
|
|
1783
3316
|
);
|
|
1784
3317
|
}
|
|
1785
3318
|
|
|
3319
|
+
// src/controllers/cli/status.cli-controller.ts
|
|
3320
|
+
var statusCliController = async (_args) => {
|
|
3321
|
+
const result = await statusService();
|
|
3322
|
+
const lines = formatStatusLines(result);
|
|
3323
|
+
for (const line of lines) {
|
|
3324
|
+
console.log(line);
|
|
3325
|
+
}
|
|
3326
|
+
const { step, appNames, allApps } = computeNextStepInfo(result);
|
|
3327
|
+
if (step !== null) {
|
|
3328
|
+
console.log("");
|
|
3329
|
+
if (step === "init" || allApps) {
|
|
3330
|
+
console.log(`Hint: Run "platform ${step}" to continue.`);
|
|
3331
|
+
} else {
|
|
3332
|
+
console.log(`Hint: Run "platform ${step} ${appNames.join(" ")}" to continue.`);
|
|
3333
|
+
}
|
|
3334
|
+
process.exit(1);
|
|
3335
|
+
}
|
|
3336
|
+
};
|
|
3337
|
+
|
|
3338
|
+
// src/controllers/cli/manage-platform-admins.cli-controller.ts
|
|
3339
|
+
async function managePlatformAdminsCliController(args2) {
|
|
3340
|
+
const logger = { log: console.log };
|
|
3341
|
+
if (!await isPlatformInitialized()) {
|
|
3342
|
+
console.error("Error: Cannot manage platform admins \u2014 no platform initialized in this directory.");
|
|
3343
|
+
process.exit(1);
|
|
3344
|
+
}
|
|
3345
|
+
const { action } = args2;
|
|
3346
|
+
if (!action || !["list", "add", "remove"].includes(action)) {
|
|
3347
|
+
logger.log("Error: Missing or invalid 'action' argument. Valid values: list, add, remove");
|
|
3348
|
+
logger.log("Usage:");
|
|
3349
|
+
logger.log(" platform manage-platform-admins action=list");
|
|
3350
|
+
logger.log(" platform manage-platform-admins action=add usernames=alice,bob");
|
|
3351
|
+
logger.log(" platform manage-platform-admins action=remove usernames=alice");
|
|
3352
|
+
process.exit(1);
|
|
3353
|
+
}
|
|
3354
|
+
if (action === "list") {
|
|
3355
|
+
const admins = await listAdminsService(logger);
|
|
3356
|
+
if (admins.length === 0) {
|
|
3357
|
+
logger.log("No platform admins found.");
|
|
3358
|
+
} else {
|
|
3359
|
+
logger.log("Current platform admins:");
|
|
3360
|
+
for (const admin of admins) {
|
|
3361
|
+
logger.log(` - ${admin.username}`);
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
return;
|
|
3365
|
+
}
|
|
3366
|
+
const { usernames } = args2;
|
|
3367
|
+
if (!usernames) {
|
|
3368
|
+
logger.log(`Error: Missing required argument 'usernames' for action '${action}'`);
|
|
3369
|
+
process.exit(1);
|
|
3370
|
+
}
|
|
3371
|
+
const usernameList = usernames.split(",").map((u) => u.trim()).filter(Boolean);
|
|
3372
|
+
if (usernameList.length === 0) {
|
|
3373
|
+
logger.log("Error: 'usernames' argument is empty.");
|
|
3374
|
+
process.exit(1);
|
|
3375
|
+
}
|
|
3376
|
+
if (action === "add") {
|
|
3377
|
+
for (const username of usernameList) {
|
|
3378
|
+
const ok = await addAdminService(username, logger);
|
|
3379
|
+
if (ok) {
|
|
3380
|
+
logger.log(`'${username}' granted platform admin access.`);
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3383
|
+
return;
|
|
3384
|
+
}
|
|
3385
|
+
if (action === "remove") {
|
|
3386
|
+
const admins = await listAdminsService(logger);
|
|
3387
|
+
for (const username of usernameList) {
|
|
3388
|
+
const admin = admins.find((a) => a.username === username);
|
|
3389
|
+
if (!admin) {
|
|
3390
|
+
logger.log(`'${username}' is not currently a platform admin.`);
|
|
3391
|
+
continue;
|
|
3392
|
+
}
|
|
3393
|
+
const ok = await removeAdminService(admin.ruleId, logger);
|
|
3394
|
+
if (ok) {
|
|
3395
|
+
logger.log(`'${username}' removed from platform admins.`);
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
|
|
1786
3401
|
// src/controllers/cli/registry.ts
|
|
1787
3402
|
var cliControllers = /* @__PURE__ */ new Map([
|
|
1788
3403
|
[CREATE_APPLICATION_COMMAND_NAME, createApplicationCliController],
|
|
@@ -1790,32 +3405,40 @@ var cliControllers = /* @__PURE__ */ new Map([
|
|
|
1790
3405
|
[CONFIGURE_IDP_COMMAND_NAME, configureIdpCliController],
|
|
1791
3406
|
[CREATE_SERVICE_MODULE_COMMAND_NAME, createServiceModuleCliController],
|
|
1792
3407
|
[CREATE_UI_MODULE_COMMAND_NAME, createUiModuleCliController],
|
|
3408
|
+
[STATUS_COMMAND_NAME, statusCliController],
|
|
1793
3409
|
[INSTALL_COMMAND_NAME, createLocalScriptCliController(INSTALL_COMMAND_NAME)],
|
|
1794
3410
|
[BUILD_COMMAND_NAME, createLocalScriptCliController(BUILD_COMMAND_NAME)],
|
|
1795
3411
|
[START_COMMAND_NAME, createLocalScriptCliController(START_COMMAND_NAME)],
|
|
1796
3412
|
[STOP_COMMAND_NAME, createLocalScriptCliController(STOP_COMMAND_NAME)],
|
|
1797
|
-
[DESTROY_COMMAND_NAME, createLocalScriptCliController(DESTROY_COMMAND_NAME)]
|
|
3413
|
+
[DESTROY_COMMAND_NAME, createLocalScriptCliController(DESTROY_COMMAND_NAME)],
|
|
3414
|
+
[MANAGE_PLATFORM_ADMINS_COMMAND_NAME, managePlatformAdminsCliController]
|
|
1798
3415
|
]);
|
|
1799
3416
|
|
|
1800
3417
|
// src/utils/parse-args.ts
|
|
1801
3418
|
function parseKeyValueArgs(args2) {
|
|
1802
3419
|
const result = {};
|
|
3420
|
+
const positional = [];
|
|
1803
3421
|
for (const arg of args2) {
|
|
1804
3422
|
const eqIndex = arg.indexOf("=");
|
|
1805
3423
|
if (eqIndex > 0) {
|
|
1806
3424
|
const key = arg.slice(0, eqIndex);
|
|
1807
3425
|
const value = arg.slice(eqIndex + 1);
|
|
1808
3426
|
result[key] = value;
|
|
3427
|
+
} else {
|
|
3428
|
+
positional.push(arg);
|
|
1809
3429
|
}
|
|
1810
3430
|
}
|
|
3431
|
+
if (positional.length > 0) {
|
|
3432
|
+
result._positional = JSON.stringify(positional);
|
|
3433
|
+
}
|
|
1811
3434
|
return result;
|
|
1812
3435
|
}
|
|
1813
3436
|
|
|
1814
3437
|
// src/index.tsx
|
|
1815
|
-
import { jsx as
|
|
3438
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
1816
3439
|
var args = process.argv.slice(2);
|
|
1817
3440
|
if (args.length === 0) {
|
|
1818
|
-
render(/* @__PURE__ */
|
|
3441
|
+
render(/* @__PURE__ */ jsx11(App, {}));
|
|
1819
3442
|
} else {
|
|
1820
3443
|
const commandName = args[0];
|
|
1821
3444
|
const params = parseKeyValueArgs(args.slice(1));
|