@bluealba/platform-cli 0.3.0-feature-platform-cli-222 → 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 +1995 -605
- package/package.json +3 -2
- package/templates/bootstrap-service-template/Dockerfile +1 -1
- package/templates/bootstrap-service-template/Dockerfile.development +1 -1
- package/templates/bootstrap-service-template/README.md +1 -1
- package/templates/bootstrap-service-template/package.json +3 -3
- 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 +4 -4
- 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/{core → {{platformName}}-core}/local/.env.example +1 -1
- package/templates/platform-init-template/{core → {{platformName}}-core}/local/platform-docker-compose.yml +5 -5
- package/templates/platform-init-template/{core/local/core-docker-compose.yml → {{platformName}}-core/local/{{platformName}}-core-docker-compose.yml} +5 -5
- 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 +2 -2
- package/templates/react-ui-module-template/src/Icon.tsx +1 -1
- /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/{core → {{platformName}}-core}/local/environment/pae-nestjs-gateway-service.env +0 -0
- /package/templates/platform-init-template/{core → {{platformName}}-core}/local/nginx.conf +0 -0
- /package/templates/platform-init-template/{core → {{platformName}}-core}/local/ssl/cert.pem +0 -0
- /package/templates/platform-init-template/{core → {{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,7 +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
|
|
207
|
+
import { join as join11, resolve } from "path";
|
|
128
208
|
import { mkdir as mkdir2 } from "fs/promises";
|
|
129
209
|
|
|
130
210
|
// src/commands/create-application/scaffold-application-monorepo.ts
|
|
@@ -231,7 +311,12 @@ async function scaffoldBootstrap(bootstrapServiceDir, organizationName, bootstra
|
|
|
231
311
|
{
|
|
232
312
|
templateDir: bootstrapTemplateDir,
|
|
233
313
|
outputDir: bootstrapServiceDir,
|
|
234
|
-
variables: {
|
|
314
|
+
variables: {
|
|
315
|
+
organizationName,
|
|
316
|
+
bootstrapName,
|
|
317
|
+
bootstrapServiceName: `${bootstrapName}-bootstrap-service`,
|
|
318
|
+
bootstrapServiceDir: bootstrapServiceDir_var
|
|
319
|
+
},
|
|
235
320
|
exclude: ["src/data/shared-libraries.json", "src/data/platform/modules-config.json"]
|
|
236
321
|
},
|
|
237
322
|
(message) => logger.log(message)
|
|
@@ -289,8 +374,8 @@ var nestjsServiceModuleTemplateDir = join6(
|
|
|
289
374
|
"templates",
|
|
290
375
|
"nestjs-service-module-template"
|
|
291
376
|
);
|
|
292
|
-
async function scaffoldNestjsService(serviceDir, organizationName, applicationName, applicationDisplayName, serviceBaseDir, logger) {
|
|
293
|
-
const serviceName = `${applicationName}-service`;
|
|
377
|
+
async function scaffoldNestjsService(serviceDir, organizationName, platformName, applicationName, applicationDisplayName, serviceBaseDir, logger) {
|
|
378
|
+
const serviceName = `${platformName}-${applicationName}-service`;
|
|
294
379
|
const serviceDisplayName = `${applicationDisplayName} Service`;
|
|
295
380
|
logger.log(`Creating NestJS service "${serviceName}"...`);
|
|
296
381
|
await applyTemplate(
|
|
@@ -321,33 +406,35 @@ async function addModuleEntry(bootstrapServiceDir, applicationName, entry, logge
|
|
|
321
406
|
}
|
|
322
407
|
|
|
323
408
|
// src/commands/create-application/module-entry-builders.ts
|
|
324
|
-
function buildServiceModuleEntry(organizationName, applicationName, applicationDisplayName) {
|
|
409
|
+
function buildServiceModuleEntry(organizationName, platformName, applicationName, applicationDisplayName) {
|
|
410
|
+
const serviceName = `${platformName}-${applicationName}-service`;
|
|
325
411
|
return {
|
|
326
|
-
name: `@${organizationName}/${
|
|
412
|
+
name: `@${organizationName}/${serviceName}`,
|
|
327
413
|
displayName: `${applicationDisplayName} Service`,
|
|
328
414
|
type: "service",
|
|
329
|
-
baseUrl: `/${
|
|
415
|
+
baseUrl: `/${serviceName}`,
|
|
330
416
|
service: {
|
|
331
|
-
host:
|
|
417
|
+
host: serviceName,
|
|
332
418
|
port: 80
|
|
333
419
|
},
|
|
334
420
|
dependsOn: []
|
|
335
421
|
};
|
|
336
422
|
}
|
|
337
|
-
function buildUiModuleEntry(organizationName, applicationName, applicationDisplayName) {
|
|
423
|
+
function buildUiModuleEntry(organizationName, platformName, applicationName, applicationDisplayName, uiNameOverride) {
|
|
424
|
+
const uiName = uiNameOverride ?? `${platformName}-${applicationName}-ui`;
|
|
338
425
|
return {
|
|
339
|
-
name: `@${organizationName}/${
|
|
426
|
+
name: `@${organizationName}/${uiName}`,
|
|
340
427
|
displayName: applicationDisplayName,
|
|
341
428
|
type: "app",
|
|
342
|
-
baseUrl: `/${
|
|
429
|
+
baseUrl: `/${uiName}`,
|
|
343
430
|
service: {
|
|
344
|
-
host:
|
|
431
|
+
host: uiName,
|
|
345
432
|
port: 80
|
|
346
433
|
},
|
|
347
434
|
ui: {
|
|
348
435
|
route: `/${applicationName}`,
|
|
349
436
|
mountAtSelector: "#pae-shell-ui-content",
|
|
350
|
-
bundleFile: `${organizationName}-${
|
|
437
|
+
bundleFile: `${organizationName}-${uiName}.js`,
|
|
351
438
|
customProps: {}
|
|
352
439
|
},
|
|
353
440
|
dependsOn: [`@bluealba/pae-shell-ui`]
|
|
@@ -357,9 +444,10 @@ function buildUiModuleEntry(organizationName, applicationName, applicationDispla
|
|
|
357
444
|
// src/commands/create-application/create-docker-compose.ts
|
|
358
445
|
import { writeFile as writeFile3 } from "fs/promises";
|
|
359
446
|
function buildBootstrapBlock(platformName, applicationName) {
|
|
360
|
-
|
|
447
|
+
const bootstrapName = `${platformName}-${applicationName}-bootstrap-service`;
|
|
448
|
+
return ` ${bootstrapName}:
|
|
361
449
|
build:
|
|
362
|
-
context: ../../${platformName}-${applicationName}/services/${
|
|
450
|
+
context: ../../${platformName}-${applicationName}/services/${bootstrapName}
|
|
363
451
|
dockerfile: Dockerfile.development
|
|
364
452
|
environment:
|
|
365
453
|
- NODE_TLS_REJECT_UNAUTHORIZED=0
|
|
@@ -376,9 +464,10 @@ function buildBootstrapBlock(platformName, applicationName) {
|
|
|
376
464
|
`;
|
|
377
465
|
}
|
|
378
466
|
function buildUiBlock(platformName, applicationName, uiPort) {
|
|
379
|
-
|
|
467
|
+
const uiName = `${platformName}-${applicationName}-ui`;
|
|
468
|
+
return ` ${uiName}:
|
|
380
469
|
build:
|
|
381
|
-
context: ../../${platformName}-${applicationName}/ui/${
|
|
470
|
+
context: ../../${platformName}-${applicationName}/ui/${uiName}
|
|
382
471
|
dockerfile: Dockerfile.development
|
|
383
472
|
ports:
|
|
384
473
|
- ${uiPort}:80
|
|
@@ -387,9 +476,10 @@ function buildUiBlock(platformName, applicationName, uiPort) {
|
|
|
387
476
|
`;
|
|
388
477
|
}
|
|
389
478
|
function buildBackendBlock(platformName, applicationName, servicePort) {
|
|
390
|
-
|
|
479
|
+
const serviceName = `${platformName}-${applicationName}-service`;
|
|
480
|
+
return ` ${serviceName}:
|
|
391
481
|
build:
|
|
392
|
-
context: ../../${platformName}-${applicationName}/services/${
|
|
482
|
+
context: ../../${platformName}-${applicationName}/services/${serviceName}
|
|
393
483
|
dockerfile: Dockerfile.development
|
|
394
484
|
args:
|
|
395
485
|
- BA_NPM_AUTH_TOKEN=$BA_NPM_AUTH_TOKEN
|
|
@@ -422,9 +512,8 @@ async function createDockerCompose(dockerComposePath, applicationName, platformN
|
|
|
422
512
|
// src/commands/create-application/port-allocator.ts
|
|
423
513
|
import { join as join8 } from "path";
|
|
424
514
|
import { readdir as readdir2, readFile as readFile3 } from "fs/promises";
|
|
425
|
-
async function getNextAvailablePort(
|
|
515
|
+
async function getNextAvailablePort(localDir) {
|
|
426
516
|
const allPorts = [];
|
|
427
|
-
const localDir = join8(rootDir, "core", "local");
|
|
428
517
|
try {
|
|
429
518
|
const files = await readdir2(localDir);
|
|
430
519
|
const ymlFiles = files.filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
|
|
@@ -449,32 +538,76 @@ function formatError(err) {
|
|
|
449
538
|
return err instanceof Error ? err.message : String(err);
|
|
450
539
|
}
|
|
451
540
|
|
|
452
|
-
// src/utils/platform-
|
|
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";
|
|
453
544
|
import { cwd as cwd2 } from "process";
|
|
454
|
-
|
|
545
|
+
async function findCoreDirIn(dir) {
|
|
546
|
+
let entries;
|
|
547
|
+
try {
|
|
548
|
+
entries = await readdir3(dir, { withFileTypes: true });
|
|
549
|
+
} catch {
|
|
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;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// src/utils/platform-check.ts
|
|
594
|
+
async function isPlatformInitialized() {
|
|
595
|
+
return await findPlatformLayout() !== null;
|
|
596
|
+
}
|
|
455
597
|
|
|
456
598
|
// src/utils/manifest.ts
|
|
457
|
-
import { readFile as readFile4, writeFile as writeFile4, access } from "fs/promises";
|
|
458
|
-
import { join as
|
|
459
|
-
import { cwd } from "process";
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
return join9(rootDir, MANIFEST_RELATIVE_PATH);
|
|
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");
|
|
463
604
|
}
|
|
464
|
-
async function readManifest(rootDir =
|
|
465
|
-
const content = await readFile4(manifestPath(rootDir), "utf-8");
|
|
605
|
+
async function readManifest(rootDir = cwd3(), coreDirName = "core") {
|
|
606
|
+
const content = await readFile4(manifestPath(rootDir, coreDirName), "utf-8");
|
|
466
607
|
return JSON.parse(content);
|
|
467
608
|
}
|
|
468
|
-
async function writeManifest(manifest, rootDir =
|
|
469
|
-
await writeFile4(manifestPath(rootDir), JSON.stringify(manifest, null, 2), "utf-8");
|
|
470
|
-
}
|
|
471
|
-
async function manifestExists(rootDir = cwd()) {
|
|
472
|
-
try {
|
|
473
|
-
await access(manifestPath(rootDir));
|
|
474
|
-
return true;
|
|
475
|
-
} catch {
|
|
476
|
-
return false;
|
|
477
|
-
}
|
|
609
|
+
async function writeManifest(manifest, rootDir = cwd3(), coreDirName = "core") {
|
|
610
|
+
await writeFile4(manifestPath(rootDir, coreDirName), JSON.stringify(manifest, null, 2), "utf-8");
|
|
478
611
|
}
|
|
479
612
|
function createInitialManifest(params) {
|
|
480
613
|
const { organizationName, platformName, platformDisplayName } = params;
|
|
@@ -496,29 +629,12 @@ function addApplicationToManifest(manifest, app) {
|
|
|
496
629
|
};
|
|
497
630
|
}
|
|
498
631
|
|
|
499
|
-
// src/utils/platform-check.ts
|
|
500
|
-
async function findRootDir(startDir = cwd2()) {
|
|
501
|
-
let dir = startDir;
|
|
502
|
-
while (true) {
|
|
503
|
-
if (await manifestExists(dir)) {
|
|
504
|
-
return dir;
|
|
505
|
-
}
|
|
506
|
-
const parent = dirname7(dir);
|
|
507
|
-
if (parent === dir) {
|
|
508
|
-
return null;
|
|
509
|
-
}
|
|
510
|
-
dir = parent;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
async function isPlatformInitialized() {
|
|
514
|
-
return await findRootDir() !== null;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
632
|
// src/commands/create-application/create-application.command.ts
|
|
518
633
|
var CREATE_APPLICATION_COMMAND_NAME = "create-application";
|
|
519
634
|
var createApplicationCommand = {
|
|
520
635
|
name: CREATE_APPLICATION_COMMAND_NAME,
|
|
521
|
-
description: "Create an application in a platform"
|
|
636
|
+
description: "Create an application in a platform",
|
|
637
|
+
hidden: (ctx) => !ctx.platformInitialized
|
|
522
638
|
};
|
|
523
639
|
async function createApplication(params, logger) {
|
|
524
640
|
const {
|
|
@@ -528,22 +644,24 @@ async function createApplication(params, logger) {
|
|
|
528
644
|
hasUserInterface,
|
|
529
645
|
hasBackendService
|
|
530
646
|
} = params;
|
|
531
|
-
const
|
|
532
|
-
if (!
|
|
647
|
+
const layout = await findPlatformLayout();
|
|
648
|
+
if (!layout) {
|
|
533
649
|
logger.log("Error: Cannot create an application \u2014 no platform initialized in this directory.");
|
|
534
650
|
return;
|
|
535
651
|
}
|
|
652
|
+
const { rootDir, coreDirName, localDir } = layout;
|
|
536
653
|
let manifest;
|
|
537
654
|
try {
|
|
538
|
-
manifest = await readManifest(rootDir);
|
|
655
|
+
manifest = await readManifest(rootDir, coreDirName);
|
|
539
656
|
} catch (err) {
|
|
540
657
|
logger.log(`Error: Could not read product manifest \u2014 ${formatError(err)}`);
|
|
541
658
|
return;
|
|
542
659
|
}
|
|
543
660
|
const { organization: organizationName, name: platformName } = manifest.product;
|
|
544
661
|
const localPath = `../${platformName}-${applicationName}`;
|
|
545
|
-
const applicationDir = resolve(
|
|
546
|
-
const
|
|
662
|
+
const applicationDir = resolve(join11(rootDir, coreDirName), localPath);
|
|
663
|
+
const bootstrapServiceName = `${platformName}-${applicationName}-bootstrap-service`;
|
|
664
|
+
const bootstrapServiceDir = join11(applicationDir, "services", bootstrapServiceName);
|
|
547
665
|
logger.log(`Creating application monorepo "${applicationName}"...`);
|
|
548
666
|
try {
|
|
549
667
|
await scaffoldApplicationMonorepo(applicationDir, organizationName, platformName, applicationName, logger);
|
|
@@ -551,12 +669,12 @@ async function createApplication(params, logger) {
|
|
|
551
669
|
logger.log(`Error: Could not scaffold application monorepo \u2014 ${formatError(err)}`);
|
|
552
670
|
return;
|
|
553
671
|
}
|
|
554
|
-
await mkdir2(
|
|
555
|
-
await mkdir2(
|
|
556
|
-
logger.log(`Creating bootstrap service "${
|
|
557
|
-
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`;
|
|
558
676
|
try {
|
|
559
|
-
await scaffoldBootstrap(bootstrapServiceDir, organizationName, applicationName
|
|
677
|
+
await scaffoldBootstrap(bootstrapServiceDir, organizationName, `${platformName}-${applicationName}`, bootstrapServiceBaseDir, logger);
|
|
560
678
|
} catch (err) {
|
|
561
679
|
logger.log(`Error: Could not scaffold bootstrap service \u2014 ${formatError(err)}`);
|
|
562
680
|
return;
|
|
@@ -572,7 +690,7 @@ async function createApplication(params, logger) {
|
|
|
572
690
|
await addModuleEntry(
|
|
573
691
|
bootstrapServiceDir,
|
|
574
692
|
applicationName,
|
|
575
|
-
buildUiModuleEntry(organizationName, applicationName, applicationDisplayName),
|
|
693
|
+
buildUiModuleEntry(organizationName, platformName, applicationName, applicationDisplayName),
|
|
576
694
|
logger
|
|
577
695
|
);
|
|
578
696
|
}
|
|
@@ -580,12 +698,13 @@ async function createApplication(params, logger) {
|
|
|
580
698
|
await addModuleEntry(
|
|
581
699
|
bootstrapServiceDir,
|
|
582
700
|
applicationName,
|
|
583
|
-
buildServiceModuleEntry(organizationName, applicationName, applicationDisplayName),
|
|
701
|
+
buildServiceModuleEntry(organizationName, platformName, applicationName, applicationDisplayName),
|
|
584
702
|
logger
|
|
585
703
|
);
|
|
586
704
|
}
|
|
587
705
|
if (hasUserInterface) {
|
|
588
|
-
const
|
|
706
|
+
const uiName = `${platformName}-${applicationName}-ui`;
|
|
707
|
+
const uiDir = join11(applicationDir, "ui", uiName);
|
|
589
708
|
const uiBaseDir = `${platformName}-${applicationName}/ui`;
|
|
590
709
|
try {
|
|
591
710
|
await scaffoldUiModule(uiDir, organizationName, platformName, applicationName, applicationDisplayName, uiBaseDir, logger);
|
|
@@ -595,17 +714,18 @@ async function createApplication(params, logger) {
|
|
|
595
714
|
}
|
|
596
715
|
}
|
|
597
716
|
if (hasBackendService) {
|
|
598
|
-
const
|
|
717
|
+
const serviceName = `${platformName}-${applicationName}-service`;
|
|
718
|
+
const serviceDir = join11(applicationDir, "services", serviceName);
|
|
599
719
|
const serviceBaseDir = `${platformName}-${applicationName}/services`;
|
|
600
720
|
try {
|
|
601
|
-
await scaffoldNestjsService(serviceDir, organizationName, applicationName, applicationDisplayName, serviceBaseDir, logger);
|
|
721
|
+
await scaffoldNestjsService(serviceDir, organizationName, platformName, applicationName, applicationDisplayName, serviceBaseDir, logger);
|
|
602
722
|
} catch (err) {
|
|
603
723
|
logger.log(`Error: Could not scaffold NestJS service \u2014 ${formatError(err)}`);
|
|
604
724
|
return;
|
|
605
725
|
}
|
|
606
726
|
}
|
|
607
|
-
const dockerComposePath =
|
|
608
|
-
const basePort = await getNextAvailablePort(
|
|
727
|
+
const dockerComposePath = join11(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
|
|
728
|
+
const basePort = await getNextAvailablePort(localDir);
|
|
609
729
|
const uiPort = hasUserInterface ? basePort : 0;
|
|
610
730
|
const servicePort = hasBackendService ? hasUserInterface ? basePort + 1 : basePort : 0;
|
|
611
731
|
await createDockerCompose(
|
|
@@ -626,7 +746,7 @@ async function createApplication(params, logger) {
|
|
|
626
746
|
repository: null
|
|
627
747
|
});
|
|
628
748
|
try {
|
|
629
|
-
await writeManifest(updatedManifest, rootDir);
|
|
749
|
+
await writeManifest(updatedManifest, rootDir, coreDirName);
|
|
630
750
|
logger.log(`Updated product manifest with application "${applicationName}".`);
|
|
631
751
|
} catch (err) {
|
|
632
752
|
logger.log(`Warning: Could not update product manifest \u2014 ${formatError(err)}`);
|
|
@@ -635,8 +755,8 @@ async function createApplication(params, logger) {
|
|
|
635
755
|
}
|
|
636
756
|
|
|
637
757
|
// src/commands/init/init.command.ts
|
|
638
|
-
import { join as
|
|
639
|
-
import { cwd as
|
|
758
|
+
import { join as join17 } from "path";
|
|
759
|
+
import { cwd as cwd4 } from "process";
|
|
640
760
|
|
|
641
761
|
// src/utils/string.ts
|
|
642
762
|
function camelize(name) {
|
|
@@ -645,8 +765,8 @@ function camelize(name) {
|
|
|
645
765
|
|
|
646
766
|
// src/commands/init/scaffold-platform.ts
|
|
647
767
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
648
|
-
import { join as
|
|
649
|
-
var templateDir =
|
|
768
|
+
import { join as join12, dirname as dirname8 } from "path";
|
|
769
|
+
var templateDir = join12(
|
|
650
770
|
dirname8(fileURLToPath6(import.meta.url)),
|
|
651
771
|
"..",
|
|
652
772
|
"templates",
|
|
@@ -658,8 +778,8 @@ async function scaffoldPlatform(outputDir, variables, logger) {
|
|
|
658
778
|
|
|
659
779
|
// src/commands/init/scaffold-platform-bootstrap.ts
|
|
660
780
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
661
|
-
import { join as
|
|
662
|
-
var templateDir2 =
|
|
781
|
+
import { join as join13, dirname as dirname9 } from "path";
|
|
782
|
+
var templateDir2 = join13(
|
|
663
783
|
dirname9(fileURLToPath7(import.meta.url)),
|
|
664
784
|
"..",
|
|
665
785
|
"templates",
|
|
@@ -671,8 +791,8 @@ async function scaffoldPlatformBootstrap(outputDir, variables, logger) {
|
|
|
671
791
|
|
|
672
792
|
// src/commands/init/scaffold-customization-ui.ts
|
|
673
793
|
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
674
|
-
import { join as
|
|
675
|
-
var templateDir3 =
|
|
794
|
+
import { join as join14, dirname as dirname10 } from "path";
|
|
795
|
+
var templateDir3 = join14(
|
|
676
796
|
dirname10(fileURLToPath8(import.meta.url)),
|
|
677
797
|
"..",
|
|
678
798
|
"templates",
|
|
@@ -683,19 +803,20 @@ async function scaffoldCustomizationUi(outputDir, variables, logger) {
|
|
|
683
803
|
}
|
|
684
804
|
|
|
685
805
|
// src/commands/init/register-customization-module.ts
|
|
686
|
-
import { join as
|
|
806
|
+
import { join as join15 } from "path";
|
|
687
807
|
import { readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
|
|
688
|
-
async function registerCustomizationModule(bootstrapServiceDir, organizationName, logger) {
|
|
689
|
-
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`;
|
|
690
811
|
const entry = {
|
|
691
|
-
name: `@${organizationName}
|
|
812
|
+
name: `@${organizationName}/${customizationUiName}`,
|
|
692
813
|
displayName: "Platform Customization UI",
|
|
693
814
|
type: "app",
|
|
694
|
-
baseUrl:
|
|
695
|
-
service: { host:
|
|
815
|
+
baseUrl: `/${customizationUiName}`,
|
|
816
|
+
service: { host: customizationUiName, port: 80 },
|
|
696
817
|
ui: {
|
|
697
818
|
route: "/",
|
|
698
|
-
bundleFile:
|
|
819
|
+
bundleFile: `${customizationUiName}.js`,
|
|
699
820
|
isPlatformCustomization: true,
|
|
700
821
|
customProps: {}
|
|
701
822
|
},
|
|
@@ -709,7 +830,7 @@ async function registerCustomizationModule(bootstrapServiceDir, organizationName
|
|
|
709
830
|
|
|
710
831
|
// src/commands/init/generate-local-env.ts
|
|
711
832
|
import { readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
|
|
712
|
-
import { join as
|
|
833
|
+
import { join as join16 } from "path";
|
|
713
834
|
|
|
714
835
|
// src/utils/random.ts
|
|
715
836
|
import { randomBytes } from "crypto";
|
|
@@ -718,10 +839,10 @@ function generateRandomSecret() {
|
|
|
718
839
|
}
|
|
719
840
|
|
|
720
841
|
// src/commands/init/generate-local-env.ts
|
|
721
|
-
async function generateLocalEnv(outputDir, logger) {
|
|
722
|
-
const examplePath =
|
|
723
|
-
const envPath =
|
|
724
|
-
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...`);
|
|
725
846
|
const content = await readFile6(examplePath, "utf-8");
|
|
726
847
|
const result = content.replace(/^(PAE_AUTH_JWT_SECRET|PAE_GATEWAY_SERVICE_ACCESS_SECRET|PAE_DB_PASSWORD)=$/gm, (_, key) => {
|
|
727
848
|
return `${key}=${generateRandomSecret()}`;
|
|
@@ -733,7 +854,8 @@ async function generateLocalEnv(outputDir, logger) {
|
|
|
733
854
|
var INIT_COMMAND_NAME = "init";
|
|
734
855
|
var initCommand = {
|
|
735
856
|
name: INIT_COMMAND_NAME,
|
|
736
|
-
description: "Initialize a new platform"
|
|
857
|
+
description: "Initialize a new platform",
|
|
858
|
+
hidden: ({ platformInitialized }) => platformInitialized
|
|
737
859
|
};
|
|
738
860
|
async function init(params, logger) {
|
|
739
861
|
if (await isPlatformInitialized()) {
|
|
@@ -742,15 +864,22 @@ async function init(params, logger) {
|
|
|
742
864
|
}
|
|
743
865
|
const { organizationName, platformName, platformDisplayName } = params;
|
|
744
866
|
const platformTitle = camelize(platformName);
|
|
745
|
-
const outputDir =
|
|
867
|
+
const outputDir = cwd4();
|
|
746
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}`;
|
|
747
874
|
const variables = {
|
|
748
875
|
organizationName,
|
|
749
876
|
platformName,
|
|
750
877
|
platformTitle,
|
|
751
878
|
platformDisplayName,
|
|
752
|
-
bootstrapName:
|
|
753
|
-
|
|
879
|
+
bootstrapName: platformName,
|
|
880
|
+
bootstrapServiceName,
|
|
881
|
+
customizationUiName,
|
|
882
|
+
bootstrapServiceDir: `${coreDirName}/services`
|
|
754
883
|
};
|
|
755
884
|
try {
|
|
756
885
|
await scaffoldPlatform(outputDir, variables, logger);
|
|
@@ -759,22 +888,22 @@ async function init(params, logger) {
|
|
|
759
888
|
return;
|
|
760
889
|
}
|
|
761
890
|
try {
|
|
762
|
-
await generateLocalEnv(outputDir, logger);
|
|
891
|
+
await generateLocalEnv(outputDir, logger, coreDirName);
|
|
763
892
|
} catch (err) {
|
|
764
|
-
logger.log(`Error: Could not generate
|
|
893
|
+
logger.log(`Error: Could not generate ${coreDirName}/local/.env \u2014 ${formatError(err)}`);
|
|
765
894
|
return;
|
|
766
895
|
}
|
|
767
896
|
try {
|
|
768
897
|
const manifest = createInitialManifest({ organizationName, platformName, platformDisplayName });
|
|
769
|
-
await writeManifest(manifest, outputDir);
|
|
770
|
-
logger.log(
|
|
898
|
+
await writeManifest(manifest, outputDir, coreDirName);
|
|
899
|
+
logger.log(`Created product manifest: ${coreDirName}/product.manifest.json`);
|
|
771
900
|
} catch (err) {
|
|
772
901
|
logger.log(`Error: Could not write product manifest \u2014 ${formatError(err)}`);
|
|
773
902
|
return;
|
|
774
903
|
}
|
|
775
904
|
try {
|
|
776
905
|
await scaffoldPlatformBootstrap(
|
|
777
|
-
|
|
906
|
+
join17(outputDir, coreDirName, "services", bootstrapServiceName),
|
|
778
907
|
variables,
|
|
779
908
|
logger
|
|
780
909
|
);
|
|
@@ -784,7 +913,7 @@ async function init(params, logger) {
|
|
|
784
913
|
}
|
|
785
914
|
try {
|
|
786
915
|
await scaffoldCustomizationUi(
|
|
787
|
-
|
|
916
|
+
join17(outputDir, coreDirName, "ui", customizationUiName),
|
|
788
917
|
variables,
|
|
789
918
|
logger
|
|
790
919
|
);
|
|
@@ -794,9 +923,10 @@ async function init(params, logger) {
|
|
|
794
923
|
}
|
|
795
924
|
try {
|
|
796
925
|
await registerCustomizationModule(
|
|
797
|
-
|
|
926
|
+
join17(outputDir, coreDirName, "services", bootstrapServiceName),
|
|
798
927
|
organizationName,
|
|
799
|
-
logger
|
|
928
|
+
logger,
|
|
929
|
+
platformName
|
|
800
930
|
);
|
|
801
931
|
} catch (err) {
|
|
802
932
|
logger.log(`Error: Could not register customization module \u2014 ${formatError(err)}`);
|
|
@@ -806,7 +936,7 @@ async function init(params, logger) {
|
|
|
806
936
|
}
|
|
807
937
|
|
|
808
938
|
// src/commands/configure-idp/configure-idp.command.ts
|
|
809
|
-
import { join as
|
|
939
|
+
import { join as join18 } from "path";
|
|
810
940
|
import { fetch as undiciFetch, Agent } from "undici";
|
|
811
941
|
|
|
812
942
|
// src/utils/env-reader.ts
|
|
@@ -881,15 +1011,16 @@ function getAllProviders() {
|
|
|
881
1011
|
var CONFIGURE_IDP_COMMAND_NAME = "configure-idp";
|
|
882
1012
|
var configureIdpCommand = {
|
|
883
1013
|
name: CONFIGURE_IDP_COMMAND_NAME,
|
|
884
|
-
description: "Configure an Identity Provider (IDP) in the gateway"
|
|
1014
|
+
description: "Configure an Identity Provider (IDP) in the gateway",
|
|
1015
|
+
hidden: (ctx) => !ctx.platformInitialized
|
|
885
1016
|
};
|
|
886
1017
|
async function configureIdp(params, logger) {
|
|
887
|
-
const
|
|
888
|
-
if (!
|
|
1018
|
+
const layout = await findPlatformLayout();
|
|
1019
|
+
if (!layout) {
|
|
889
1020
|
logger.log("Error: Cannot configure an IDP \u2014 no platform initialized in this directory.");
|
|
890
1021
|
return;
|
|
891
1022
|
}
|
|
892
|
-
const envPath =
|
|
1023
|
+
const envPath = join18(layout.localDir, ".env");
|
|
893
1024
|
let env;
|
|
894
1025
|
try {
|
|
895
1026
|
env = await readEnvFile(envPath);
|
|
@@ -941,13 +1072,13 @@ async function configureIdp(params, logger) {
|
|
|
941
1072
|
}
|
|
942
1073
|
|
|
943
1074
|
// src/commands/create-service-module/create-service-module.command.ts
|
|
944
|
-
import { join as
|
|
945
|
-
import { access as
|
|
1075
|
+
import { join as join20, resolve as resolve2 } from "path";
|
|
1076
|
+
import { access as access3 } from "fs/promises";
|
|
946
1077
|
|
|
947
1078
|
// src/commands/create-service-module/scaffold-service-module.ts
|
|
948
1079
|
import { fileURLToPath as fileURLToPath9 } from "url";
|
|
949
|
-
import { join as
|
|
950
|
-
var nestjsServiceModuleTemplateDir2 =
|
|
1080
|
+
import { join as join19, dirname as dirname11 } from "path";
|
|
1081
|
+
var nestjsServiceModuleTemplateDir2 = join19(
|
|
951
1082
|
dirname11(fileURLToPath9(import.meta.url)),
|
|
952
1083
|
"..",
|
|
953
1084
|
"templates",
|
|
@@ -1012,18 +1143,20 @@ async function appendServiceToDockerCompose(dockerComposePath, serviceName, plat
|
|
|
1012
1143
|
var CREATE_SERVICE_MODULE_COMMAND_NAME = "create-service-module";
|
|
1013
1144
|
var createServiceModuleCommand = {
|
|
1014
1145
|
name: CREATE_SERVICE_MODULE_COMMAND_NAME,
|
|
1015
|
-
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
|
|
1016
1148
|
};
|
|
1017
1149
|
async function createServiceModule(params, logger) {
|
|
1018
|
-
const { applicationName, serviceName, serviceDisplayName } = params;
|
|
1019
|
-
const
|
|
1020
|
-
if (!
|
|
1150
|
+
const { applicationName, serviceName, serviceDisplayName, serviceNameSuffix } = params;
|
|
1151
|
+
const layout = await findPlatformLayout();
|
|
1152
|
+
if (!layout) {
|
|
1021
1153
|
logger.log("Error: Cannot create a service module \u2014 no platform initialized in this directory.");
|
|
1022
1154
|
return;
|
|
1023
1155
|
}
|
|
1156
|
+
const { rootDir, coreDirName, localDir } = layout;
|
|
1024
1157
|
let manifest;
|
|
1025
1158
|
try {
|
|
1026
|
-
manifest = await readManifest(rootDir);
|
|
1159
|
+
manifest = await readManifest(rootDir, coreDirName);
|
|
1027
1160
|
} catch (err) {
|
|
1028
1161
|
logger.log(`Error: Could not read product manifest \u2014 ${formatError(err)}`);
|
|
1029
1162
|
return;
|
|
@@ -1034,17 +1167,18 @@ async function createServiceModule(params, logger) {
|
|
|
1034
1167
|
logger.log(`Error: The specified application "${applicationName}" is not registered in the product manifest.`);
|
|
1035
1168
|
return;
|
|
1036
1169
|
}
|
|
1037
|
-
const applicationDir = resolve2(
|
|
1170
|
+
const applicationDir = resolve2(join20(rootDir, coreDirName), appEntry.localPath);
|
|
1038
1171
|
try {
|
|
1039
|
-
await
|
|
1172
|
+
await access3(applicationDir);
|
|
1040
1173
|
} catch {
|
|
1041
1174
|
logger.log(`Error: The specified application "${applicationName}" does not exist in the platform.`);
|
|
1042
1175
|
return;
|
|
1043
1176
|
}
|
|
1044
|
-
const
|
|
1045
|
-
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);
|
|
1046
1180
|
try {
|
|
1047
|
-
await
|
|
1181
|
+
await access3(serviceDir);
|
|
1048
1182
|
logger.log(`Error: A service named "${fullServiceName}" already exists in application "${applicationName}".`);
|
|
1049
1183
|
return;
|
|
1050
1184
|
} catch {
|
|
@@ -1063,26 +1197,27 @@ async function createServiceModule(params, logger) {
|
|
|
1063
1197
|
logger.log(`Error: Could not scaffold NestJS service \u2014 ${formatError(err)}`);
|
|
1064
1198
|
return;
|
|
1065
1199
|
}
|
|
1066
|
-
const bootstrapServiceDir =
|
|
1200
|
+
const bootstrapServiceDir = join20(applicationDir, "services", `${platformName}-${applicationName}-bootstrap-service`);
|
|
1067
1201
|
const moduleEntry = buildCustomServiceModuleEntry(organizationName, fullServiceName, serviceDisplayName);
|
|
1068
1202
|
await addModuleEntry(bootstrapServiceDir, applicationName, moduleEntry, logger);
|
|
1069
|
-
const dockerComposePath =
|
|
1070
|
-
const port = await getNextAvailablePort(
|
|
1203
|
+
const dockerComposePath = join20(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
|
|
1204
|
+
const port = await getNextAvailablePort(localDir);
|
|
1071
1205
|
await appendServiceToDockerCompose(dockerComposePath, fullServiceName, platformName, applicationName, port, logger);
|
|
1072
1206
|
logger.log(`Done! Service module "${fullServiceName}" added to application "${applicationName}".`);
|
|
1073
1207
|
}
|
|
1074
1208
|
|
|
1075
1209
|
// src/commands/create-ui-module/create-ui-module.command.ts
|
|
1076
|
-
import { join as
|
|
1077
|
-
import { access as
|
|
1210
|
+
import { join as join21, resolve as resolve3 } from "path";
|
|
1211
|
+
import { access as access4, readdir as readdir4 } from "fs/promises";
|
|
1078
1212
|
|
|
1079
1213
|
// src/commands/create-ui-module/append-ui-docker-compose.ts
|
|
1080
1214
|
import { readFile as readFile9, writeFile as writeFile8 } from "fs/promises";
|
|
1081
|
-
async function appendUiToDockerCompose(dockerComposePath, platformName, applicationName, port, logger) {
|
|
1215
|
+
async function appendUiToDockerCompose(dockerComposePath, platformName, applicationName, port, logger, uiNameOverride) {
|
|
1216
|
+
const uiName = uiNameOverride ?? `${platformName}-${applicationName}-ui`;
|
|
1082
1217
|
const block = `
|
|
1083
|
-
${
|
|
1218
|
+
${uiName}:
|
|
1084
1219
|
build:
|
|
1085
|
-
context: ../../${platformName}-${applicationName}/ui/${
|
|
1220
|
+
context: ../../${platformName}-${applicationName}/ui/${uiName}
|
|
1086
1221
|
dockerfile: Dockerfile.development
|
|
1087
1222
|
ports:
|
|
1088
1223
|
- ${port}:80
|
|
@@ -1103,18 +1238,20 @@ async function appendUiToDockerCompose(dockerComposePath, platformName, applicat
|
|
|
1103
1238
|
var CREATE_UI_MODULE_COMMAND_NAME = "create-ui-module";
|
|
1104
1239
|
var createUiModuleCommand = {
|
|
1105
1240
|
name: CREATE_UI_MODULE_COMMAND_NAME,
|
|
1106
|
-
description: "Add a UI module to an existing application"
|
|
1241
|
+
description: "Add a UI module to an existing application",
|
|
1242
|
+
hidden: (ctx) => !ctx.platformInitialized
|
|
1107
1243
|
};
|
|
1108
1244
|
async function createUiModule(params, logger) {
|
|
1109
|
-
const { applicationName, applicationDisplayName } = params;
|
|
1110
|
-
const
|
|
1111
|
-
if (!
|
|
1245
|
+
const { applicationName, applicationDisplayName, uiModuleSuffix } = params;
|
|
1246
|
+
const layout = await findPlatformLayout();
|
|
1247
|
+
if (!layout) {
|
|
1112
1248
|
logger.log("Error: Cannot create a UI module \u2014 no platform initialized in this directory.");
|
|
1113
1249
|
return;
|
|
1114
1250
|
}
|
|
1251
|
+
const { rootDir, coreDirName, localDir } = layout;
|
|
1115
1252
|
let manifest;
|
|
1116
1253
|
try {
|
|
1117
|
-
manifest = await readManifest(rootDir);
|
|
1254
|
+
manifest = await readManifest(rootDir, coreDirName);
|
|
1118
1255
|
} catch (err) {
|
|
1119
1256
|
logger.log(`Error: Could not read product manifest \u2014 ${formatError(err)}`);
|
|
1120
1257
|
return;
|
|
@@ -1125,16 +1262,16 @@ async function createUiModule(params, logger) {
|
|
|
1125
1262
|
logger.log(`Error: The specified application "${applicationName}" is not registered in the product manifest.`);
|
|
1126
1263
|
return;
|
|
1127
1264
|
}
|
|
1128
|
-
const applicationDir = resolve3(
|
|
1265
|
+
const applicationDir = resolve3(join21(rootDir, coreDirName), appEntry.localPath);
|
|
1129
1266
|
try {
|
|
1130
|
-
await
|
|
1267
|
+
await access4(applicationDir);
|
|
1131
1268
|
} catch {
|
|
1132
1269
|
logger.log(`Error: The specified application "${applicationName}" does not exist in the platform.`);
|
|
1133
1270
|
return;
|
|
1134
1271
|
}
|
|
1135
|
-
const uiDir =
|
|
1272
|
+
const uiDir = join21(applicationDir, "ui");
|
|
1136
1273
|
try {
|
|
1137
|
-
const uiEntries = await
|
|
1274
|
+
const uiEntries = await readdir4(uiDir);
|
|
1138
1275
|
const existingUiModules = uiEntries.filter((e) => e !== ".gitkeep");
|
|
1139
1276
|
if (existingUiModules.length > 0) {
|
|
1140
1277
|
logger.log(`Error: Currently we support only one UI module per application. Application "${applicationName}" already has a UI module.`);
|
|
@@ -1142,7 +1279,9 @@ async function createUiModule(params, logger) {
|
|
|
1142
1279
|
}
|
|
1143
1280
|
} catch {
|
|
1144
1281
|
}
|
|
1145
|
-
const
|
|
1282
|
+
const uiSuffix = uiModuleSuffix === void 0 ? "ui" : uiModuleSuffix;
|
|
1283
|
+
const uiName = uiSuffix ? `${platformName}-${applicationName}-${uiSuffix}` : `${platformName}-${applicationName}`;
|
|
1284
|
+
const uiOutputDir = join21(uiDir, uiName);
|
|
1146
1285
|
const uiBaseDir = `${platformName}-${applicationName}/ui`;
|
|
1147
1286
|
try {
|
|
1148
1287
|
await scaffoldUiModule(uiOutputDir, organizationName, platformName, applicationName, applicationDisplayName, uiBaseDir, logger);
|
|
@@ -1150,109 +1289,1069 @@ async function createUiModule(params, logger) {
|
|
|
1150
1289
|
logger.log(`Error: Could not scaffold UI module \u2014 ${formatError(err)}`);
|
|
1151
1290
|
return;
|
|
1152
1291
|
}
|
|
1153
|
-
const bootstrapServiceDir =
|
|
1154
|
-
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);
|
|
1155
1294
|
await addModuleEntry(bootstrapServiceDir, applicationName, moduleEntry, logger);
|
|
1156
|
-
const dockerComposePath =
|
|
1157
|
-
const port = await getNextAvailablePort(
|
|
1158
|
-
await appendUiToDockerCompose(dockerComposePath, platformName, applicationName, port, logger);
|
|
1159
|
-
logger.log(`Done! UI module "${
|
|
1295
|
+
const dockerComposePath = join21(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
|
|
1296
|
+
const port = await getNextAvailablePort(localDir);
|
|
1297
|
+
await appendUiToDockerCompose(dockerComposePath, platformName, applicationName, port, logger, uiName);
|
|
1298
|
+
logger.log(`Done! UI module "${uiName}" added to application "${applicationName}".`);
|
|
1160
1299
|
}
|
|
1161
1300
|
|
|
1162
|
-
// src/commands/
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
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";
|
|
1305
|
+
|
|
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
|
+
});
|
|
1353
|
+
}
|
|
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
|
+
}
|
|
1167
1391
|
}
|
|
1168
|
-
|
|
1169
|
-
|
|
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;
|
|
1400
|
+
try {
|
|
1401
|
+
await access5(prefixedCoreCompose);
|
|
1402
|
+
coreComposePath = prefixedCoreCompose;
|
|
1403
|
+
} catch {
|
|
1404
|
+
coreComposePath = unprefixedCoreCompose;
|
|
1170
1405
|
}
|
|
1171
|
-
|
|
1172
|
-
|
|
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);
|
|
1173
1415
|
}
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
selector: (cmd) => `${cmd.name} ${cmd.description}`
|
|
1179
|
-
});
|
|
1180
|
-
return fzf.find(query).map((result) => result.item);
|
|
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.`);
|
|
1419
|
+
}
|
|
1181
1420
|
}
|
|
1182
|
-
|
|
1183
|
-
var registry = new CommandRegistry();
|
|
1184
|
-
registry.register(createApplicationCommand);
|
|
1185
|
-
registry.register(initCommand);
|
|
1186
|
-
registry.register(configureIdpCommand);
|
|
1187
|
-
registry.register(createServiceModuleCommand);
|
|
1188
|
-
registry.register(createUiModuleCommand);
|
|
1189
|
-
|
|
1190
|
-
// src/app-state.ts
|
|
1191
|
-
var APP_STATE = {
|
|
1192
|
-
IDLE: "idle",
|
|
1193
|
-
PALETTE: "palette",
|
|
1194
|
-
EXECUTING: "executing",
|
|
1195
|
-
PROMPTING: "prompting"
|
|
1196
|
-
};
|
|
1197
|
-
|
|
1198
|
-
// src/hooks/use-command-runner.ts
|
|
1199
|
-
import { useState as useState2, useCallback, useRef } from "react";
|
|
1200
|
-
|
|
1201
|
-
// src/services/create-application.service.ts
|
|
1202
|
-
async function createApplicationService(params, logger) {
|
|
1203
|
-
await createApplication(params, logger);
|
|
1421
|
+
return fileArgs;
|
|
1204
1422
|
}
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
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);
|
|
1211
1438
|
}
|
|
1212
|
-
const
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
return;
|
|
1439
|
+
const appEntries = await getAppComposePaths(localDir, platformName, selectedManifest);
|
|
1440
|
+
for (const { composePath } of appEntries) {
|
|
1441
|
+
files.push(composePath);
|
|
1216
1442
|
}
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1443
|
+
return files;
|
|
1444
|
+
}
|
|
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
|
+
}
|
|
1223
1452
|
}
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
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);
|
|
1228
1475
|
}
|
|
1229
|
-
|
|
1230
|
-
{
|
|
1231
|
-
applicationName,
|
|
1232
|
-
applicationDisplayName,
|
|
1233
|
-
applicationDescription,
|
|
1234
|
-
hasUserInterface: hasUserInterfaceAnswer === "yes",
|
|
1235
|
-
hasBackendService: hasBackendServiceAnswer === "yes"
|
|
1236
|
-
},
|
|
1237
|
-
ctx
|
|
1238
|
-
);
|
|
1476
|
+
return [...allServices].filter((s) => !contextServices.has(s));
|
|
1239
1477
|
}
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
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
|
+
}
|
|
1244
1499
|
}
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
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);
|
|
1251
1520
|
}
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
const
|
|
1255
|
-
|
|
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);
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
// src/controllers/ui/init.ui-controller.ts
|
|
2321
|
+
async function initUiController(ctx) {
|
|
2322
|
+
if (await isPlatformInitialized()) {
|
|
2323
|
+
ctx.log("Error: Cannot initialize a new platform \u2014 a platform is already initialized in this directory.");
|
|
2324
|
+
return;
|
|
2325
|
+
}
|
|
2326
|
+
const organizationName = await ctx.prompt("Organization name:");
|
|
2327
|
+
const platformName = await ctx.prompt("Platform name:");
|
|
2328
|
+
const platformDisplayName = await ctx.prompt("Platform display name:");
|
|
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
|
+
);
|
|
1256
2355
|
}
|
|
1257
2356
|
|
|
1258
2357
|
// src/services/configure-idp.service.ts
|
|
@@ -1267,14 +2366,11 @@ async function configureIdpUiController(ctx) {
|
|
|
1267
2366
|
return;
|
|
1268
2367
|
}
|
|
1269
2368
|
const providers = getAllProviders();
|
|
1270
|
-
const
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
return;
|
|
1276
|
-
}
|
|
1277
|
-
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);
|
|
1278
2374
|
const name = await ctx.prompt("Provider name:");
|
|
1279
2375
|
const issuer = await ctx.prompt("Issuer URL:");
|
|
1280
2376
|
const clientId = await ctx.prompt("Client ID:");
|
|
@@ -1300,19 +2396,38 @@ async function createServiceModuleUiController(ctx) {
|
|
|
1300
2396
|
ctx.log("Error: Cannot create a service module \u2014 no platform initialized in this directory.");
|
|
1301
2397
|
return;
|
|
1302
2398
|
}
|
|
1303
|
-
const
|
|
1304
|
-
if (
|
|
1305
|
-
ctx.log(
|
|
2399
|
+
const layout = await findPlatformLayout();
|
|
2400
|
+
if (!layout) {
|
|
2401
|
+
ctx.log("Error: Cannot create a service module \u2014 no platform initialized in this directory.");
|
|
1306
2402
|
return;
|
|
1307
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
|
+
}
|
|
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
|
+
);
|
|
1308
2420
|
const serviceName = await ctx.prompt("Service name:");
|
|
1309
2421
|
if (!/^[a-z0-9-]+$/.test(serviceName)) {
|
|
1310
2422
|
ctx.log(`Error: Service name "${serviceName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
|
|
1311
2423
|
return;
|
|
1312
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;
|
|
1313
2428
|
const serviceDisplayName = await ctx.prompt("Service display name:");
|
|
1314
2429
|
await createServiceModuleService(
|
|
1315
|
-
{ applicationName, serviceName, serviceDisplayName },
|
|
2430
|
+
{ applicationName, serviceName, serviceDisplayName, serviceNameSuffix },
|
|
1316
2431
|
ctx
|
|
1317
2432
|
);
|
|
1318
2433
|
}
|
|
@@ -1328,38 +2443,353 @@ async function createUiModuleUiController(ctx) {
|
|
|
1328
2443
|
ctx.log("Error: Cannot create a UI module \u2014 no platform initialized in this directory.");
|
|
1329
2444
|
return;
|
|
1330
2445
|
}
|
|
1331
|
-
const
|
|
1332
|
-
if (
|
|
1333
|
-
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.");
|
|
1334
2449
|
return;
|
|
1335
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;
|
|
1336
2470
|
const applicationDisplayName = await ctx.prompt("Application display name:");
|
|
1337
2471
|
await createUiModuleService(
|
|
1338
|
-
{ applicationName, applicationDisplayName },
|
|
2472
|
+
{ applicationName, applicationDisplayName, uiModuleSuffix },
|
|
1339
2473
|
ctx
|
|
1340
2474
|
);
|
|
1341
2475
|
}
|
|
1342
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
|
+
|
|
1343
2761
|
// src/controllers/ui/registry.ts
|
|
1344
2762
|
var uiControllers = /* @__PURE__ */ new Map([
|
|
1345
2763
|
[CREATE_APPLICATION_COMMAND_NAME, createApplicationUiController],
|
|
1346
2764
|
[INIT_COMMAND_NAME, initUiController],
|
|
1347
2765
|
[CONFIGURE_IDP_COMMAND_NAME, configureIdpUiController],
|
|
1348
2766
|
[CREATE_SERVICE_MODULE_COMMAND_NAME, createServiceModuleUiController],
|
|
1349
|
-
[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]
|
|
1350
2775
|
]);
|
|
1351
2776
|
|
|
1352
2777
|
// src/hooks/use-command-runner.ts
|
|
1353
|
-
function useCommandRunner({ appendStaticItem, setState }) {
|
|
2778
|
+
function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
|
|
1354
2779
|
const outputCountRef = useRef(0);
|
|
1355
2780
|
const abortControllerRef = useRef(null);
|
|
1356
2781
|
const promptResolveRef = useRef(null);
|
|
1357
2782
|
const [promptMessage, setPromptMessage] = useState2("");
|
|
1358
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());
|
|
1359
2788
|
const abortExecution = useCallback(() => {
|
|
1360
2789
|
abortControllerRef.current?.abort();
|
|
1361
2790
|
abortControllerRef.current = null;
|
|
1362
2791
|
promptResolveRef.current = null;
|
|
2792
|
+
setPromptMode({ kind: "text" });
|
|
1363
2793
|
}, []);
|
|
1364
2794
|
const runCommand2 = useCallback(
|
|
1365
2795
|
(cmd) => {
|
|
@@ -1374,15 +2804,73 @@ function useCommandRunner({ appendStaticItem, setState }) {
|
|
|
1374
2804
|
appendStaticItem({ kind: "output", id, line: message });
|
|
1375
2805
|
}
|
|
1376
2806
|
},
|
|
1377
|
-
prompt(message) {
|
|
1378
|
-
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) => {
|
|
1379
2866
|
if (controller.signal.aborted) {
|
|
1380
2867
|
reject(new DOMException("Aborted", "AbortError"));
|
|
1381
2868
|
return;
|
|
1382
2869
|
}
|
|
1383
2870
|
setPromptMessage(message);
|
|
1384
|
-
|
|
1385
|
-
|
|
2871
|
+
setPromptMode({ kind: "confirm" });
|
|
2872
|
+
setConfirmValue(defaultValue ?? true);
|
|
2873
|
+
promptResolveRef.current = (value) => resolve6(value === "true");
|
|
1386
2874
|
setState(APP_STATE.PROMPTING);
|
|
1387
2875
|
controller.signal.addEventListener(
|
|
1388
2876
|
"abort",
|
|
@@ -1402,22 +2890,25 @@ function useCommandRunner({ appendStaticItem, setState }) {
|
|
|
1402
2890
|
uiController(ctx).then(() => {
|
|
1403
2891
|
if (!controller.signal.aborted) {
|
|
1404
2892
|
setState(APP_STATE.IDLE);
|
|
2893
|
+
onCommandComplete?.();
|
|
1405
2894
|
}
|
|
1406
2895
|
}).catch((err) => {
|
|
1407
2896
|
if (controller.signal.aborted) return;
|
|
1408
2897
|
const id = `output-${outputCountRef.current++}`;
|
|
1409
2898
|
appendStaticItem({ kind: "output", id, line: `Error: ${formatError(err)}` });
|
|
1410
2899
|
setState(APP_STATE.IDLE);
|
|
2900
|
+
onCommandComplete?.();
|
|
1411
2901
|
});
|
|
1412
2902
|
},
|
|
1413
|
-
[appendStaticItem, setState]
|
|
2903
|
+
[appendStaticItem, setState, onCommandComplete]
|
|
1414
2904
|
);
|
|
1415
2905
|
const handlePromptSubmit = useCallback(
|
|
1416
2906
|
(value) => {
|
|
1417
|
-
const
|
|
2907
|
+
const resolve6 = promptResolveRef.current;
|
|
1418
2908
|
promptResolveRef.current = null;
|
|
2909
|
+
setPromptMode({ kind: "text" });
|
|
1419
2910
|
setState(APP_STATE.EXECUTING);
|
|
1420
|
-
|
|
2911
|
+
resolve6?.(value);
|
|
1421
2912
|
},
|
|
1422
2913
|
[setState]
|
|
1423
2914
|
);
|
|
@@ -1427,12 +2918,19 @@ function useCommandRunner({ appendStaticItem, setState }) {
|
|
|
1427
2918
|
abortExecution,
|
|
1428
2919
|
promptMessage,
|
|
1429
2920
|
promptValue,
|
|
1430
|
-
setPromptValue
|
|
2921
|
+
setPromptValue,
|
|
2922
|
+
promptMode,
|
|
2923
|
+
selectIndex,
|
|
2924
|
+
setSelectIndex,
|
|
2925
|
+
confirmValue,
|
|
2926
|
+
setConfirmValue,
|
|
2927
|
+
multiselectChecked,
|
|
2928
|
+
setMultiselectChecked
|
|
1431
2929
|
};
|
|
1432
2930
|
}
|
|
1433
2931
|
|
|
1434
2932
|
// src/app.tsx
|
|
1435
|
-
import { jsx as
|
|
2933
|
+
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1436
2934
|
var require2 = createRequire(import.meta.url);
|
|
1437
2935
|
var { version } = require2("../package.json");
|
|
1438
2936
|
function App() {
|
|
@@ -1441,12 +2939,35 @@ function App() {
|
|
|
1441
2939
|
const [inputValue, setInputValue] = useState3("");
|
|
1442
2940
|
const [selectedIndex, setSelectedIndex] = useState3(0);
|
|
1443
2941
|
const [staticItems, setStaticItems] = useState3([{ kind: "banner", id: "banner" }]);
|
|
2942
|
+
const [platformInitialized, setPlatformInitialized] = useState3(false);
|
|
2943
|
+
useEffect2(() => {
|
|
2944
|
+
isPlatformInitialized().then(setPlatformInitialized).catch(() => {
|
|
2945
|
+
});
|
|
2946
|
+
}, []);
|
|
1444
2947
|
const appendStaticItem = useCallback2((item) => {
|
|
1445
2948
|
setStaticItems((prev) => [...prev, item]);
|
|
1446
2949
|
}, []);
|
|
1447
|
-
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 });
|
|
1448
2969
|
const query = inputValue.startsWith("/") ? inputValue.slice(1) : "";
|
|
1449
|
-
const filteredCommands = registry.
|
|
2970
|
+
const filteredCommands = registry.searchVisible(query, { platformInitialized });
|
|
1450
2971
|
useInput(
|
|
1451
2972
|
(input, key) => {
|
|
1452
2973
|
if (key.ctrl && input === "c") {
|
|
@@ -1458,6 +2979,73 @@ function App() {
|
|
|
1458
2979
|
setState(APP_STATE.IDLE);
|
|
1459
2980
|
return;
|
|
1460
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
|
+
}
|
|
1461
3049
|
if (state === APP_STATE.PALETTE) {
|
|
1462
3050
|
if (key.escape) {
|
|
1463
3051
|
setState(APP_STATE.IDLE);
|
|
@@ -1519,17 +3107,27 @@ function App() {
|
|
|
1519
3107
|
function renderActiveArea() {
|
|
1520
3108
|
switch (state) {
|
|
1521
3109
|
case APP_STATE.EXECUTING:
|
|
1522
|
-
return /* @__PURE__ */
|
|
1523
|
-
/* @__PURE__ */
|
|
1524
|
-
/* @__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)" })
|
|
1525
3113
|
] });
|
|
1526
3114
|
case APP_STATE.PROMPTING:
|
|
1527
|
-
return /* @__PURE__ */
|
|
1528
|
-
/* @__PURE__ */
|
|
1529
|
-
/* @__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 })
|
|
1530
3128
|
] });
|
|
1531
3129
|
default:
|
|
1532
|
-
return /* @__PURE__ */
|
|
3130
|
+
return /* @__PURE__ */ jsx10(
|
|
1533
3131
|
Prompt,
|
|
1534
3132
|
{
|
|
1535
3133
|
value: inputValue,
|
|
@@ -1542,10 +3140,10 @@ function App() {
|
|
|
1542
3140
|
);
|
|
1543
3141
|
}
|
|
1544
3142
|
}
|
|
1545
|
-
return /* @__PURE__ */
|
|
1546
|
-
/* @__PURE__ */
|
|
3143
|
+
return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", children: [
|
|
3144
|
+
/* @__PURE__ */ jsx10(ScrollbackHistory, { items: staticItems, version }),
|
|
1547
3145
|
renderActiveArea(),
|
|
1548
|
-
state === APP_STATE.PALETTE && /* @__PURE__ */
|
|
3146
|
+
state === APP_STATE.PALETTE && /* @__PURE__ */ jsx10(CommandPalette, { commands: filteredCommands, selectedIndex })
|
|
1549
3147
|
] });
|
|
1550
3148
|
}
|
|
1551
3149
|
|
|
@@ -1588,13 +3186,13 @@ async function initCliController(args2) {
|
|
|
1588
3186
|
console.error("Error: Cannot initialize a new platform \u2014 a platform is already initialized in this directory.");
|
|
1589
3187
|
process.exit(1);
|
|
1590
3188
|
}
|
|
1591
|
-
const { organizationName, platformName, platformDisplayName } = args2;
|
|
3189
|
+
const { organizationName, platformName, platformDisplayName, bootstrapServiceSuffix, customizationUiSuffix } = args2;
|
|
1592
3190
|
if (!organizationName || !platformName || !platformDisplayName) {
|
|
1593
3191
|
console.error("Error: organizationName, platformName, and platformDisplayName are required.");
|
|
1594
3192
|
process.exit(1);
|
|
1595
3193
|
}
|
|
1596
3194
|
await initService(
|
|
1597
|
-
{ organizationName, platformName, platformDisplayName },
|
|
3195
|
+
{ organizationName, platformName, platformDisplayName, bootstrapServiceSuffix, customizationUiSuffix },
|
|
1598
3196
|
{ log: console.log }
|
|
1599
3197
|
);
|
|
1600
3198
|
}
|
|
@@ -1628,305 +3226,6 @@ async function configureIdpCliController(args2) {
|
|
|
1628
3226
|
await configureIdpService({ providerType, name, issuer, clientId, clientSecret, extras }, logger);
|
|
1629
3227
|
}
|
|
1630
3228
|
|
|
1631
|
-
// src/commands/local-scripts/docker-compose-orchestrator.ts
|
|
1632
|
-
import { spawn } from "child_process";
|
|
1633
|
-
import { access as access4 } from "fs/promises";
|
|
1634
|
-
import { join as join21 } from "path";
|
|
1635
|
-
function runDockerCompose(args2, logger, rootDir, signal) {
|
|
1636
|
-
return new Promise((resolvePromise) => {
|
|
1637
|
-
const child = spawn("docker", ["compose", ...args2], {
|
|
1638
|
-
shell: false,
|
|
1639
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
1640
|
-
cwd: rootDir
|
|
1641
|
-
});
|
|
1642
|
-
const onAbort = () => {
|
|
1643
|
-
child.kill("SIGTERM");
|
|
1644
|
-
};
|
|
1645
|
-
if (signal) {
|
|
1646
|
-
if (signal.aborted) {
|
|
1647
|
-
child.kill("SIGTERM");
|
|
1648
|
-
resolvePromise();
|
|
1649
|
-
return;
|
|
1650
|
-
}
|
|
1651
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
1652
|
-
}
|
|
1653
|
-
child.stdout.on("data", (data) => {
|
|
1654
|
-
for (const line of data.toString().split("\n")) {
|
|
1655
|
-
if (line.trim()) logger.log(line);
|
|
1656
|
-
}
|
|
1657
|
-
});
|
|
1658
|
-
child.stderr.on("data", (data) => {
|
|
1659
|
-
for (const line of data.toString().split("\n")) {
|
|
1660
|
-
if (line.trim()) logger.log(line);
|
|
1661
|
-
}
|
|
1662
|
-
});
|
|
1663
|
-
child.on("close", (code, sig) => {
|
|
1664
|
-
signal?.removeEventListener("abort", onAbort);
|
|
1665
|
-
if (sig === "SIGTERM" || signal?.aborted) {
|
|
1666
|
-
logger.log("Command cancelled.");
|
|
1667
|
-
} else if (code !== 0) {
|
|
1668
|
-
logger.log(`docker compose exited with code ${code}.`);
|
|
1669
|
-
}
|
|
1670
|
-
resolvePromise();
|
|
1671
|
-
});
|
|
1672
|
-
child.on("error", (err) => {
|
|
1673
|
-
signal?.removeEventListener("abort", onAbort);
|
|
1674
|
-
logger.log(`Failed to run docker compose: ${err.message}`);
|
|
1675
|
-
resolvePromise();
|
|
1676
|
-
});
|
|
1677
|
-
});
|
|
1678
|
-
}
|
|
1679
|
-
async function getAppComposePaths(rootDir, manifest) {
|
|
1680
|
-
const results = [];
|
|
1681
|
-
for (const app of manifest.applications) {
|
|
1682
|
-
const composePath = join21(rootDir, "core", "local", `${app.name}-docker-compose.yml`);
|
|
1683
|
-
try {
|
|
1684
|
-
await access4(composePath);
|
|
1685
|
-
results.push({ composePath, appName: app.name });
|
|
1686
|
-
} catch {
|
|
1687
|
-
}
|
|
1688
|
-
}
|
|
1689
|
-
return results;
|
|
1690
|
-
}
|
|
1691
|
-
async function buildAllComposeArgs(rootDir, manifest, logger) {
|
|
1692
|
-
const localDir = join21(rootDir, "core", "local");
|
|
1693
|
-
const fileArgs = [
|
|
1694
|
-
"-f",
|
|
1695
|
-
join21(localDir, "platform-docker-compose.yml"),
|
|
1696
|
-
"-f",
|
|
1697
|
-
join21(localDir, "core-docker-compose.yml")
|
|
1698
|
-
];
|
|
1699
|
-
const appEntries = await getAppComposePaths(rootDir, manifest);
|
|
1700
|
-
for (const { composePath, appName } of appEntries) {
|
|
1701
|
-
fileArgs.push("-f", composePath);
|
|
1702
|
-
}
|
|
1703
|
-
for (const app of manifest.applications) {
|
|
1704
|
-
if (!appEntries.find((e) => e.appName === app.name)) {
|
|
1705
|
-
logger.log(`Warning: No docker-compose found for application "${app.name}" in core/local/ \u2014 skipping.`);
|
|
1706
|
-
}
|
|
1707
|
-
}
|
|
1708
|
-
return fileArgs;
|
|
1709
|
-
}
|
|
1710
|
-
async function startEnvironment(rootDir, manifest, logger, signal) {
|
|
1711
|
-
const platformName = manifest.product.name;
|
|
1712
|
-
const envFile = join21(rootDir, "core", "local", ".env");
|
|
1713
|
-
const fileArgs = await buildAllComposeArgs(rootDir, manifest, logger);
|
|
1714
|
-
const localDir = join21(rootDir, "core", "local");
|
|
1715
|
-
logger.log("Starting environment...");
|
|
1716
|
-
await runDockerCompose(
|
|
1717
|
-
[
|
|
1718
|
-
"-p",
|
|
1719
|
-
`${platformName}-platform`,
|
|
1720
|
-
"--project-directory",
|
|
1721
|
-
localDir,
|
|
1722
|
-
"--env-file",
|
|
1723
|
-
envFile,
|
|
1724
|
-
...fileArgs,
|
|
1725
|
-
"up",
|
|
1726
|
-
"-d",
|
|
1727
|
-
"--build",
|
|
1728
|
-
"--remove-orphans"
|
|
1729
|
-
],
|
|
1730
|
-
logger,
|
|
1731
|
-
rootDir,
|
|
1732
|
-
signal
|
|
1733
|
-
);
|
|
1734
|
-
}
|
|
1735
|
-
async function stopEnvironment(rootDir, manifest, logger, signal) {
|
|
1736
|
-
const platformName = manifest.product.name;
|
|
1737
|
-
const envFile = join21(rootDir, "core", "local", ".env");
|
|
1738
|
-
const fileArgs = await buildAllComposeArgs(rootDir, manifest, logger);
|
|
1739
|
-
const localDir = join21(rootDir, "core", "local");
|
|
1740
|
-
logger.log("Stopping environment...");
|
|
1741
|
-
await runDockerCompose(
|
|
1742
|
-
[
|
|
1743
|
-
"-p",
|
|
1744
|
-
`${platformName}-platform`,
|
|
1745
|
-
"--project-directory",
|
|
1746
|
-
localDir,
|
|
1747
|
-
"--env-file",
|
|
1748
|
-
envFile,
|
|
1749
|
-
...fileArgs,
|
|
1750
|
-
"down"
|
|
1751
|
-
],
|
|
1752
|
-
logger,
|
|
1753
|
-
rootDir,
|
|
1754
|
-
signal
|
|
1755
|
-
);
|
|
1756
|
-
}
|
|
1757
|
-
async function destroyEnvironment(rootDir, manifest, logger, signal) {
|
|
1758
|
-
const platformName = manifest.product.name;
|
|
1759
|
-
const envFile = join21(rootDir, "core", "local", ".env");
|
|
1760
|
-
const fileArgs = await buildAllComposeArgs(rootDir, manifest, logger);
|
|
1761
|
-
const localDir = join21(rootDir, "core", "local");
|
|
1762
|
-
logger.log("Destroying environment...");
|
|
1763
|
-
await runDockerCompose(
|
|
1764
|
-
[
|
|
1765
|
-
"-p",
|
|
1766
|
-
`${platformName}-platform`,
|
|
1767
|
-
"--project-directory",
|
|
1768
|
-
localDir,
|
|
1769
|
-
"--env-file",
|
|
1770
|
-
envFile,
|
|
1771
|
-
...fileArgs,
|
|
1772
|
-
"down",
|
|
1773
|
-
"-v",
|
|
1774
|
-
"--rmi",
|
|
1775
|
-
"all"
|
|
1776
|
-
],
|
|
1777
|
-
logger,
|
|
1778
|
-
rootDir,
|
|
1779
|
-
signal
|
|
1780
|
-
);
|
|
1781
|
-
}
|
|
1782
|
-
|
|
1783
|
-
// src/commands/local-scripts/npm-orchestrator.ts
|
|
1784
|
-
import { spawn as spawn2 } from "child_process";
|
|
1785
|
-
import { access as access5 } from "fs/promises";
|
|
1786
|
-
import { join as join22, resolve as resolve4 } from "path";
|
|
1787
|
-
function runCommand(command, args2, workDir, logger, signal) {
|
|
1788
|
-
return new Promise((resolvePromise) => {
|
|
1789
|
-
const child = spawn2(command, args2, {
|
|
1790
|
-
cwd: workDir,
|
|
1791
|
-
shell: false,
|
|
1792
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
1793
|
-
});
|
|
1794
|
-
const onAbort = () => {
|
|
1795
|
-
child.kill("SIGTERM");
|
|
1796
|
-
};
|
|
1797
|
-
if (signal) {
|
|
1798
|
-
if (signal.aborted) {
|
|
1799
|
-
child.kill("SIGTERM");
|
|
1800
|
-
resolvePromise();
|
|
1801
|
-
return;
|
|
1802
|
-
}
|
|
1803
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
1804
|
-
}
|
|
1805
|
-
child.stdout.on("data", (data) => {
|
|
1806
|
-
for (const line of data.toString().split("\n")) {
|
|
1807
|
-
if (line.trim()) logger.log(line);
|
|
1808
|
-
}
|
|
1809
|
-
});
|
|
1810
|
-
child.stderr.on("data", (data) => {
|
|
1811
|
-
for (const line of data.toString().split("\n")) {
|
|
1812
|
-
if (line.trim()) logger.log(line);
|
|
1813
|
-
}
|
|
1814
|
-
});
|
|
1815
|
-
child.on("close", (code, sig) => {
|
|
1816
|
-
signal?.removeEventListener("abort", onAbort);
|
|
1817
|
-
if (sig === "SIGTERM" || signal?.aborted) {
|
|
1818
|
-
logger.log("Command cancelled.");
|
|
1819
|
-
} else if (code !== 0) {
|
|
1820
|
-
logger.log(`Command "${command} ${args2.join(" ")}" exited with code ${code}.`);
|
|
1821
|
-
}
|
|
1822
|
-
resolvePromise();
|
|
1823
|
-
});
|
|
1824
|
-
child.on("error", (err) => {
|
|
1825
|
-
signal?.removeEventListener("abort", onAbort);
|
|
1826
|
-
logger.log(`Failed to run "${command} ${args2.join(" ")}": ${err.message}`);
|
|
1827
|
-
resolvePromise();
|
|
1828
|
-
});
|
|
1829
|
-
});
|
|
1830
|
-
}
|
|
1831
|
-
async function dirExists(dirPath) {
|
|
1832
|
-
try {
|
|
1833
|
-
await access5(dirPath);
|
|
1834
|
-
return true;
|
|
1835
|
-
} catch {
|
|
1836
|
-
return false;
|
|
1837
|
-
}
|
|
1838
|
-
}
|
|
1839
|
-
async function installDependencies(rootDir, manifest, logger, signal) {
|
|
1840
|
-
const coreDir = join22(rootDir, "core");
|
|
1841
|
-
const appDirs = [];
|
|
1842
|
-
for (const app of manifest.applications) {
|
|
1843
|
-
const appDir = resolve4(join22(rootDir, "core"), app.localPath);
|
|
1844
|
-
if (!await dirExists(appDir)) {
|
|
1845
|
-
logger.log(`Warning: Application directory "${app.name}" not found at ${appDir} \u2014 skipping install.`);
|
|
1846
|
-
continue;
|
|
1847
|
-
}
|
|
1848
|
-
appDirs.push({ name: app.name, dir: appDir });
|
|
1849
|
-
}
|
|
1850
|
-
const targets = [{ name: "core", dir: coreDir }, ...appDirs];
|
|
1851
|
-
logger.log(`Installing dependencies in parallel: ${targets.map((t) => t.name).join(", ")}...`);
|
|
1852
|
-
await Promise.all(
|
|
1853
|
-
targets.map(
|
|
1854
|
-
({ name, dir }) => runCommand("npm", ["install"], dir, logger, signal).then(() => {
|
|
1855
|
-
logger.log(`\u2713 ${name} install done`);
|
|
1856
|
-
})
|
|
1857
|
-
)
|
|
1858
|
-
);
|
|
1859
|
-
}
|
|
1860
|
-
async function buildAll(rootDir, manifest, logger, signal) {
|
|
1861
|
-
const coreDir = join22(rootDir, "core");
|
|
1862
|
-
logger.log("Building core/...");
|
|
1863
|
-
await runCommand("npx", ["turbo", "build"], coreDir, logger, signal);
|
|
1864
|
-
if (signal?.aborted) return;
|
|
1865
|
-
const appDirs = [];
|
|
1866
|
-
for (const app of manifest.applications) {
|
|
1867
|
-
const appDir = resolve4(join22(rootDir, "core"), app.localPath);
|
|
1868
|
-
if (!await dirExists(appDir)) {
|
|
1869
|
-
logger.log(`Warning: Application directory "${app.name}" not found at ${appDir} \u2014 skipping build.`);
|
|
1870
|
-
continue;
|
|
1871
|
-
}
|
|
1872
|
-
appDirs.push({ name: app.name, dir: appDir });
|
|
1873
|
-
}
|
|
1874
|
-
if (appDirs.length === 0) return;
|
|
1875
|
-
logger.log(`Building apps in parallel: ${appDirs.map((a) => a.name).join(", ")}...`);
|
|
1876
|
-
await Promise.all(
|
|
1877
|
-
appDirs.map(
|
|
1878
|
-
({ name, dir }) => runCommand("npm", ["run", "build"], dir, logger, signal).then(() => {
|
|
1879
|
-
logger.log(`\u2713 ${name} build done`);
|
|
1880
|
-
})
|
|
1881
|
-
)
|
|
1882
|
-
);
|
|
1883
|
-
}
|
|
1884
|
-
|
|
1885
|
-
// src/commands/local-scripts/local-script.command.ts
|
|
1886
|
-
var INSTALL_COMMAND_NAME = "install";
|
|
1887
|
-
var BUILD_COMMAND_NAME = "build";
|
|
1888
|
-
var START_COMMAND_NAME = "start";
|
|
1889
|
-
var STOP_COMMAND_NAME = "stop";
|
|
1890
|
-
var DESTROY_COMMAND_NAME = "destroy";
|
|
1891
|
-
async function runLocalScript(scriptName, logger, signal) {
|
|
1892
|
-
const rootDir = await findRootDir();
|
|
1893
|
-
if (!rootDir) {
|
|
1894
|
-
logger.log(`Error: Cannot run "${scriptName}" \u2014 no platform initialized in this directory.`);
|
|
1895
|
-
return;
|
|
1896
|
-
}
|
|
1897
|
-
let manifest;
|
|
1898
|
-
try {
|
|
1899
|
-
manifest = await readManifest(rootDir);
|
|
1900
|
-
} catch (err) {
|
|
1901
|
-
logger.log(`Error: Could not read product manifest \u2014 ${formatError(err)}`);
|
|
1902
|
-
return;
|
|
1903
|
-
}
|
|
1904
|
-
switch (scriptName) {
|
|
1905
|
-
case START_COMMAND_NAME:
|
|
1906
|
-
await startEnvironment(rootDir, manifest, logger, signal);
|
|
1907
|
-
break;
|
|
1908
|
-
case STOP_COMMAND_NAME:
|
|
1909
|
-
await stopEnvironment(rootDir, manifest, logger, signal);
|
|
1910
|
-
break;
|
|
1911
|
-
case DESTROY_COMMAND_NAME:
|
|
1912
|
-
await destroyEnvironment(rootDir, manifest, logger, signal);
|
|
1913
|
-
break;
|
|
1914
|
-
case INSTALL_COMMAND_NAME:
|
|
1915
|
-
await installDependencies(rootDir, manifest, logger, signal);
|
|
1916
|
-
break;
|
|
1917
|
-
case BUILD_COMMAND_NAME:
|
|
1918
|
-
await buildAll(rootDir, manifest, logger, signal);
|
|
1919
|
-
break;
|
|
1920
|
-
default:
|
|
1921
|
-
logger.log(`Error: Unknown script "${scriptName}".`);
|
|
1922
|
-
}
|
|
1923
|
-
}
|
|
1924
|
-
|
|
1925
|
-
// src/services/local-script.service.ts
|
|
1926
|
-
async function localScriptService(scriptName, logger, signal) {
|
|
1927
|
-
await runLocalScript(scriptName, logger, signal);
|
|
1928
|
-
}
|
|
1929
|
-
|
|
1930
3229
|
// src/utils/cli-spinner.ts
|
|
1931
3230
|
var FRAMES2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1932
3231
|
var INTERVAL_MS2 = 80;
|
|
@@ -1962,10 +3261,11 @@ function createCliSpinner(label) {
|
|
|
1962
3261
|
|
|
1963
3262
|
// src/controllers/cli/local-script.cli-controller.ts
|
|
1964
3263
|
function createLocalScriptCliController(scriptName) {
|
|
1965
|
-
return async (
|
|
3264
|
+
return async (args2) => {
|
|
1966
3265
|
const spinner = createCliSpinner(`Running ${scriptName}\u2026`);
|
|
1967
3266
|
spinner.start();
|
|
1968
|
-
|
|
3267
|
+
const appNames = args2._positional ? JSON.parse(args2._positional) : void 0;
|
|
3268
|
+
await localScriptService(scriptName, { log: (msg) => spinner.log(msg) }, void 0, appNames);
|
|
1969
3269
|
spinner.stop();
|
|
1970
3270
|
};
|
|
1971
3271
|
}
|
|
@@ -2016,6 +3316,88 @@ async function createUiModuleCliController(args2) {
|
|
|
2016
3316
|
);
|
|
2017
3317
|
}
|
|
2018
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
|
+
|
|
2019
3401
|
// src/controllers/cli/registry.ts
|
|
2020
3402
|
var cliControllers = /* @__PURE__ */ new Map([
|
|
2021
3403
|
[CREATE_APPLICATION_COMMAND_NAME, createApplicationCliController],
|
|
@@ -2023,32 +3405,40 @@ var cliControllers = /* @__PURE__ */ new Map([
|
|
|
2023
3405
|
[CONFIGURE_IDP_COMMAND_NAME, configureIdpCliController],
|
|
2024
3406
|
[CREATE_SERVICE_MODULE_COMMAND_NAME, createServiceModuleCliController],
|
|
2025
3407
|
[CREATE_UI_MODULE_COMMAND_NAME, createUiModuleCliController],
|
|
3408
|
+
[STATUS_COMMAND_NAME, statusCliController],
|
|
2026
3409
|
[INSTALL_COMMAND_NAME, createLocalScriptCliController(INSTALL_COMMAND_NAME)],
|
|
2027
3410
|
[BUILD_COMMAND_NAME, createLocalScriptCliController(BUILD_COMMAND_NAME)],
|
|
2028
3411
|
[START_COMMAND_NAME, createLocalScriptCliController(START_COMMAND_NAME)],
|
|
2029
3412
|
[STOP_COMMAND_NAME, createLocalScriptCliController(STOP_COMMAND_NAME)],
|
|
2030
|
-
[DESTROY_COMMAND_NAME, createLocalScriptCliController(DESTROY_COMMAND_NAME)]
|
|
3413
|
+
[DESTROY_COMMAND_NAME, createLocalScriptCliController(DESTROY_COMMAND_NAME)],
|
|
3414
|
+
[MANAGE_PLATFORM_ADMINS_COMMAND_NAME, managePlatformAdminsCliController]
|
|
2031
3415
|
]);
|
|
2032
3416
|
|
|
2033
3417
|
// src/utils/parse-args.ts
|
|
2034
3418
|
function parseKeyValueArgs(args2) {
|
|
2035
3419
|
const result = {};
|
|
3420
|
+
const positional = [];
|
|
2036
3421
|
for (const arg of args2) {
|
|
2037
3422
|
const eqIndex = arg.indexOf("=");
|
|
2038
3423
|
if (eqIndex > 0) {
|
|
2039
3424
|
const key = arg.slice(0, eqIndex);
|
|
2040
3425
|
const value = arg.slice(eqIndex + 1);
|
|
2041
3426
|
result[key] = value;
|
|
3427
|
+
} else {
|
|
3428
|
+
positional.push(arg);
|
|
2042
3429
|
}
|
|
2043
3430
|
}
|
|
3431
|
+
if (positional.length > 0) {
|
|
3432
|
+
result._positional = JSON.stringify(positional);
|
|
3433
|
+
}
|
|
2044
3434
|
return result;
|
|
2045
3435
|
}
|
|
2046
3436
|
|
|
2047
3437
|
// src/index.tsx
|
|
2048
|
-
import { jsx as
|
|
3438
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
2049
3439
|
var args = process.argv.slice(2);
|
|
2050
3440
|
if (args.length === 0) {
|
|
2051
|
-
render(/* @__PURE__ */
|
|
3441
|
+
render(/* @__PURE__ */ jsx11(App, {}));
|
|
2052
3442
|
} else {
|
|
2053
3443
|
const commandName = args[0];
|
|
2054
3444
|
const params = parseKeyValueArgs(args.slice(1));
|