@bluealba/platform-cli 0.3.1 → 1.0.0
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 +2068 -457
- package/package.json +7 -6
- package/templates/bootstrap-service-template/Dockerfile +1 -1
- package/templates/bootstrap-service-template/Dockerfile.development +1 -1
- package/templates/bootstrap-service-template/README.md +1 -1
- package/templates/bootstrap-service-template/package.json +5 -5
- package/templates/customization-ui-module-template/package.json +3 -3
- package/templates/platform-init-template/{{{platformName}}-local → {{platformName}}-core/local}/platform-docker-compose.yml +6 -6
- package/templates/platform-init-template/{{{platformName}}-local → {{platformName}}-core/local}/{{platformName}}-core-docker-compose.yml +7 -7
- package/templates/react-ui-module-template/package.json +2 -2
- package/templates/react-ui-module-template/src/Icon.tsx +1 -1
- package/templates/platform-init-template/{{platformName}}-local/docker-compose.yml +0 -3
- package/templates/platform-init-template/{{platformName}}-local/package.json +0 -18
- package/templates/platform-init-template/{{platformName}}-local/scripts/build.sh +0 -18
- package/templates/platform-init-template/{{platformName}}-local/scripts/install.sh +0 -18
- /package/templates/platform-init-template/{{{platformName}}-local → {{platformName}}-core/local}/.env.example +0 -0
- /package/templates/platform-init-template/{{{platformName}}-local → {{platformName}}-core/local}/environment/pae-nestjs-gateway-service.env +0 -0
- /package/templates/platform-init-template/{{{platformName}}-local → {{platformName}}-core/local}/nginx.conf +0 -0
- /package/templates/platform-init-template/{{{platformName}}-local → {{platformName}}-core/local}/ssl/cert.pem +0 -0
- /package/templates/platform-init-template/{{{platformName}}-local → {{platformName}}-core/local}/ssl/key.pem +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";
|
|
@@ -58,20 +58,25 @@ import { Static, Text as Text5 } from "ink";
|
|
|
58
58
|
|
|
59
59
|
// src/components/welcome-banner.tsx
|
|
60
60
|
import React3 from "react";
|
|
61
|
-
import { Box as
|
|
61
|
+
import { Box as Box3, Text as Text4 } from "ink";
|
|
62
62
|
|
|
63
63
|
// src/components/working-directory.tsx
|
|
64
64
|
import React2 from "react";
|
|
65
|
-
import
|
|
66
|
-
import {
|
|
65
|
+
import { Text as Text3 } from "ink";
|
|
66
|
+
import { cwd } from "process";
|
|
67
|
+
import { homedir } from "os";
|
|
67
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
|
+
}
|
|
68
73
|
var WorkingDirectory = React2.memo(function WorkingDirectory2() {
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
|
75
80
|
] });
|
|
76
81
|
});
|
|
77
82
|
|
|
@@ -84,19 +89,19 @@ var ASCII_ART = [
|
|
|
84
89
|
"|---' '---|"
|
|
85
90
|
];
|
|
86
91
|
var WelcomeBanner = React3.memo(function WelcomeBanner2({ version: version2 }) {
|
|
87
|
-
return /* @__PURE__ */ jsxs4(
|
|
92
|
+
return /* @__PURE__ */ jsxs4(Box3, { borderStyle: "round", flexDirection: "column", paddingX: 1, paddingY: 1, children: [
|
|
88
93
|
/* @__PURE__ */ jsxs4(Text4, { bold: true, color: "cyan", children: [
|
|
89
94
|
"Blue Alba Platform CLI v",
|
|
90
95
|
version2
|
|
91
96
|
] }),
|
|
92
|
-
/* @__PURE__ */ jsxs4(
|
|
93
|
-
/* @__PURE__ */ jsx4(
|
|
94
|
-
/* @__PURE__ */ jsxs4(
|
|
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: [
|
|
95
100
|
/* @__PURE__ */ jsx4(Text4, { bold: true, children: "Welcome to the" }),
|
|
96
101
|
/* @__PURE__ */ jsx4(Text4, { bold: true, children: "Blue Alba Platform!" })
|
|
97
102
|
] }),
|
|
98
|
-
/* @__PURE__ */ jsx4(
|
|
99
|
-
/* @__PURE__ */ jsxs4(
|
|
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: [
|
|
100
105
|
/* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: "Tips for getting started" }),
|
|
101
106
|
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
102
107
|
"Run ",
|
|
@@ -105,7 +110,7 @@ var WelcomeBanner = React3.memo(function WelcomeBanner2({ version: version2 }) {
|
|
|
105
110
|
] })
|
|
106
111
|
] })
|
|
107
112
|
] }),
|
|
108
|
-
/* @__PURE__ */ jsx4(
|
|
113
|
+
/* @__PURE__ */ jsx4(WorkingDirectory, {})
|
|
109
114
|
] });
|
|
110
115
|
});
|
|
111
116
|
|
|
@@ -138,12 +143,68 @@ function Spinner({ label }) {
|
|
|
138
143
|
] });
|
|
139
144
|
}
|
|
140
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)" })
|
|
200
|
+
] });
|
|
201
|
+
}
|
|
202
|
+
|
|
141
203
|
// src/commands/registry.ts
|
|
142
204
|
import { Fzf } from "fzf";
|
|
143
205
|
|
|
144
206
|
// src/commands/create-application/create-application.command.ts
|
|
145
|
-
import { join as
|
|
146
|
-
import { cwd as cwd2 } from "process";
|
|
207
|
+
import { join as join11, resolve } from "path";
|
|
147
208
|
import { mkdir as mkdir2 } from "fs/promises";
|
|
148
209
|
|
|
149
210
|
// src/commands/create-application/scaffold-application-monorepo.ts
|
|
@@ -250,7 +311,12 @@ async function scaffoldBootstrap(bootstrapServiceDir, organizationName, bootstra
|
|
|
250
311
|
{
|
|
251
312
|
templateDir: bootstrapTemplateDir,
|
|
252
313
|
outputDir: bootstrapServiceDir,
|
|
253
|
-
variables: {
|
|
314
|
+
variables: {
|
|
315
|
+
organizationName,
|
|
316
|
+
bootstrapName,
|
|
317
|
+
bootstrapServiceName: `${bootstrapName}-bootstrap-service`,
|
|
318
|
+
bootstrapServiceDir: bootstrapServiceDir_var
|
|
319
|
+
},
|
|
254
320
|
exclude: ["src/data/shared-libraries.json", "src/data/platform/modules-config.json"]
|
|
255
321
|
},
|
|
256
322
|
(message) => logger.log(message)
|
|
@@ -287,7 +353,7 @@ var reactUiModuleTemplateDir = join5(
|
|
|
287
353
|
"react-ui-module-template"
|
|
288
354
|
);
|
|
289
355
|
async function scaffoldUiModule(uiDir, organizationName, platformName, applicationName, applicationDisplayName, uiBaseDir, logger) {
|
|
290
|
-
logger.log(`Creating UI module "${
|
|
356
|
+
logger.log(`Creating UI module "${applicationName}-ui"...`);
|
|
291
357
|
await applyTemplate(
|
|
292
358
|
{
|
|
293
359
|
templateDir: reactUiModuleTemplateDir,
|
|
@@ -341,32 +407,34 @@ async function addModuleEntry(bootstrapServiceDir, applicationName, entry, logge
|
|
|
341
407
|
|
|
342
408
|
// src/commands/create-application/module-entry-builders.ts
|
|
343
409
|
function buildServiceModuleEntry(organizationName, platformName, applicationName, applicationDisplayName) {
|
|
410
|
+
const serviceName = `${platformName}-${applicationName}-service`;
|
|
344
411
|
return {
|
|
345
|
-
name: `@${organizationName}/${
|
|
412
|
+
name: `@${organizationName}/${serviceName}`,
|
|
346
413
|
displayName: `${applicationDisplayName} Service`,
|
|
347
414
|
type: "service",
|
|
348
|
-
baseUrl: `/${
|
|
415
|
+
baseUrl: `/${serviceName}`,
|
|
349
416
|
service: {
|
|
350
|
-
host:
|
|
417
|
+
host: serviceName,
|
|
351
418
|
port: 80
|
|
352
419
|
},
|
|
353
420
|
dependsOn: []
|
|
354
421
|
};
|
|
355
422
|
}
|
|
356
|
-
function buildUiModuleEntry(organizationName, platformName, applicationName, applicationDisplayName) {
|
|
423
|
+
function buildUiModuleEntry(organizationName, platformName, applicationName, applicationDisplayName, uiNameOverride) {
|
|
424
|
+
const uiName = uiNameOverride ?? `${platformName}-${applicationName}-ui`;
|
|
357
425
|
return {
|
|
358
|
-
name: `@${organizationName}/${
|
|
426
|
+
name: `@${organizationName}/${uiName}`,
|
|
359
427
|
displayName: applicationDisplayName,
|
|
360
428
|
type: "app",
|
|
361
|
-
baseUrl: `/${
|
|
429
|
+
baseUrl: `/${uiName}`,
|
|
362
430
|
service: {
|
|
363
|
-
host:
|
|
431
|
+
host: uiName,
|
|
364
432
|
port: 80
|
|
365
433
|
},
|
|
366
434
|
ui: {
|
|
367
435
|
route: `/${applicationName}`,
|
|
368
436
|
mountAtSelector: "#pae-shell-ui-content",
|
|
369
|
-
bundleFile: `${organizationName}-${
|
|
437
|
+
bundleFile: `${organizationName}-${uiName}.js`,
|
|
370
438
|
customProps: {}
|
|
371
439
|
},
|
|
372
440
|
dependsOn: [`@bluealba/pae-shell-ui`]
|
|
@@ -376,39 +444,42 @@ function buildUiModuleEntry(organizationName, platformName, applicationName, app
|
|
|
376
444
|
// src/commands/create-application/create-docker-compose.ts
|
|
377
445
|
import { writeFile as writeFile3 } from "fs/promises";
|
|
378
446
|
function buildBootstrapBlock(platformName, applicationName) {
|
|
379
|
-
|
|
447
|
+
const bootstrapName = `${platformName}-${applicationName}-bootstrap-service`;
|
|
448
|
+
return ` ${bootstrapName}:
|
|
380
449
|
build:
|
|
381
|
-
context:
|
|
450
|
+
context: ../../${platformName}-${applicationName}/services/${bootstrapName}
|
|
382
451
|
dockerfile: Dockerfile.development
|
|
383
452
|
environment:
|
|
384
453
|
- NODE_TLS_REJECT_UNAUTHORIZED=0
|
|
385
|
-
- SERVICE_ACCESS_NAME=${
|
|
454
|
+
- SERVICE_ACCESS_NAME=${applicationName}-bootstrap
|
|
386
455
|
- WAIT_TIME=5000
|
|
387
456
|
- SYNC_STRATEGY=\${PAE_BOOTSTRAP_SYNC_STRATEGY}
|
|
388
457
|
- GATEWAY_SERVICE_URL=\${PAE_GATEWAY_URL}
|
|
389
458
|
- SERVICE_ACCESS_SECRET=\${PAE_GATEWAY_SERVICE_ACCESS_SECRET}
|
|
390
459
|
volumes:
|
|
391
|
-
- \${PWD}
|
|
460
|
+
- \${PWD}/:/app/out
|
|
392
461
|
depends_on:
|
|
393
462
|
pae-nestjs-gateway-service:
|
|
394
463
|
condition: service_healthy
|
|
395
464
|
`;
|
|
396
465
|
}
|
|
397
466
|
function buildUiBlock(platformName, applicationName, uiPort) {
|
|
398
|
-
|
|
467
|
+
const uiName = `${platformName}-${applicationName}-ui`;
|
|
468
|
+
return ` ${uiName}:
|
|
399
469
|
build:
|
|
400
|
-
context:
|
|
470
|
+
context: ../../${platformName}-${applicationName}/ui/${uiName}
|
|
401
471
|
dockerfile: Dockerfile.development
|
|
402
472
|
ports:
|
|
403
473
|
- ${uiPort}:80
|
|
404
474
|
volumes:
|
|
405
|
-
- \${PWD}
|
|
475
|
+
- \${PWD}/:/app/out
|
|
406
476
|
`;
|
|
407
477
|
}
|
|
408
478
|
function buildBackendBlock(platformName, applicationName, servicePort) {
|
|
409
|
-
|
|
479
|
+
const serviceName = `${platformName}-${applicationName}-service`;
|
|
480
|
+
return ` ${serviceName}:
|
|
410
481
|
build:
|
|
411
|
-
context:
|
|
482
|
+
context: ../../${platformName}-${applicationName}/services/${serviceName}
|
|
412
483
|
dockerfile: Dockerfile.development
|
|
413
484
|
args:
|
|
414
485
|
- BA_NPM_AUTH_TOKEN=$BA_NPM_AUTH_TOKEN
|
|
@@ -417,10 +488,10 @@ function buildBackendBlock(platformName, applicationName, servicePort) {
|
|
|
417
488
|
environment:
|
|
418
489
|
- GATEWAY_URL=\${PAE_GATEWAY_URL}
|
|
419
490
|
volumes:
|
|
420
|
-
- \${PWD}
|
|
491
|
+
- \${PWD}/:/app/out
|
|
421
492
|
`;
|
|
422
493
|
}
|
|
423
|
-
async function createDockerCompose(dockerComposePath,
|
|
494
|
+
async function createDockerCompose(dockerComposePath, applicationName, platformName, hasUserInterface, hasBackendService, uiPort, servicePort, logger) {
|
|
424
495
|
const blocks = ["services:\n", buildBootstrapBlock(platformName, applicationName)];
|
|
425
496
|
if (hasUserInterface) {
|
|
426
497
|
blocks.push(buildUiBlock(platformName, applicationName, uiPort));
|
|
@@ -428,7 +499,7 @@ async function createDockerCompose(dockerComposePath, platformName, applicationN
|
|
|
428
499
|
if (hasBackendService) {
|
|
429
500
|
blocks.push(buildBackendBlock(platformName, applicationName, servicePort));
|
|
430
501
|
}
|
|
431
|
-
const content = blocks.join("\n");
|
|
502
|
+
const content = blocks.join("\n") + "\n";
|
|
432
503
|
try {
|
|
433
504
|
await writeFile3(dockerComposePath, content, "utf-8");
|
|
434
505
|
logger.log(`Created docker-compose: ${dockerComposePath}`);
|
|
@@ -438,32 +509,17 @@ async function createDockerCompose(dockerComposePath, platformName, applicationN
|
|
|
438
509
|
}
|
|
439
510
|
}
|
|
440
511
|
|
|
441
|
-
// src/commands/create-application/update-root-docker-compose.ts
|
|
442
|
-
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
443
|
-
async function updateRootDockerCompose(rootDockerComposePath, platformName, applicationName, logger) {
|
|
444
|
-
try {
|
|
445
|
-
const existing = await readFile3(rootDockerComposePath, "utf-8");
|
|
446
|
-
const includeLine = ` - path: ./${platformName}-${applicationName}-docker-compose.yml
|
|
447
|
-
`;
|
|
448
|
-
await writeFile4(rootDockerComposePath, existing + includeLine, "utf-8");
|
|
449
|
-
logger.log(`Updated root docker-compose: ${rootDockerComposePath}`);
|
|
450
|
-
} catch (err) {
|
|
451
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
452
|
-
logger.log(`Warning: Could not update root docker-compose \u2014 ${message}`);
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
512
|
// src/commands/create-application/port-allocator.ts
|
|
457
513
|
import { join as join8 } from "path";
|
|
458
|
-
import { readdir as readdir2, readFile as
|
|
514
|
+
import { readdir as readdir2, readFile as readFile3 } from "fs/promises";
|
|
459
515
|
async function getNextAvailablePort(localDir) {
|
|
460
516
|
const allPorts = [];
|
|
461
517
|
try {
|
|
462
518
|
const files = await readdir2(localDir);
|
|
463
|
-
const
|
|
464
|
-
for (const file of
|
|
519
|
+
const ymlFiles = files.filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
|
|
520
|
+
for (const file of ymlFiles) {
|
|
465
521
|
try {
|
|
466
|
-
const content = await
|
|
522
|
+
const content = await readFile3(join8(localDir, file), "utf-8");
|
|
467
523
|
const regex = /^\s*-\s*"?(\d+):\d+"?\s*$/gm;
|
|
468
524
|
let match;
|
|
469
525
|
while ((match = regex.exec(content)) !== null) {
|
|
@@ -482,78 +538,143 @@ function formatError(err) {
|
|
|
482
538
|
return err instanceof Error ? err.message : String(err);
|
|
483
539
|
}
|
|
484
540
|
|
|
485
|
-
// src/utils/platform-
|
|
486
|
-
import { access, readdir as readdir3
|
|
487
|
-
import { join as join9 } from "path";
|
|
488
|
-
import { cwd } from "process";
|
|
489
|
-
async function
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
const
|
|
497
|
-
|
|
498
|
-
|
|
541
|
+
// src/utils/platform-layout.ts
|
|
542
|
+
import { access, readdir as readdir3 } from "fs/promises";
|
|
543
|
+
import { join as join9, dirname as dirname7 } from "path";
|
|
544
|
+
import { cwd as cwd2 } from "process";
|
|
545
|
+
async function findCoreDirIn(dir) {
|
|
546
|
+
let entries;
|
|
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
|
+
}
|
|
499
569
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
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;
|
|
505
590
|
}
|
|
506
|
-
return { platformName, organizationName: scopeMatch[1] };
|
|
507
591
|
}
|
|
592
|
+
|
|
593
|
+
// src/utils/platform-check.ts
|
|
508
594
|
async function isPlatformInitialized() {
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
595
|
+
return await findPlatformLayout() !== null;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// src/utils/manifest.ts
|
|
599
|
+
import { readFile as readFile4, writeFile as writeFile4, access as access2 } from "fs/promises";
|
|
600
|
+
import { join as join10 } from "path";
|
|
601
|
+
import { cwd as cwd3 } from "process";
|
|
602
|
+
function manifestPath(rootDir, coreDirName = "core") {
|
|
603
|
+
return join10(rootDir, coreDirName, "product.manifest.json");
|
|
604
|
+
}
|
|
605
|
+
async function readManifest(rootDir = cwd3(), coreDirName = "core") {
|
|
606
|
+
const content = await readFile4(manifestPath(rootDir, coreDirName), "utf-8");
|
|
607
|
+
return JSON.parse(content);
|
|
608
|
+
}
|
|
609
|
+
async function writeManifest(manifest, rootDir = cwd3(), coreDirName = "core") {
|
|
610
|
+
await writeFile4(manifestPath(rootDir, coreDirName), JSON.stringify(manifest, null, 2), "utf-8");
|
|
611
|
+
}
|
|
612
|
+
function createInitialManifest(params) {
|
|
613
|
+
const { organizationName, platformName, platformDisplayName } = params;
|
|
614
|
+
return {
|
|
615
|
+
version: "1",
|
|
616
|
+
product: {
|
|
617
|
+
name: platformName,
|
|
618
|
+
displayName: platformDisplayName,
|
|
619
|
+
organization: organizationName,
|
|
620
|
+
scope: `@${organizationName}`
|
|
621
|
+
},
|
|
622
|
+
applications: []
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
function addApplicationToManifest(manifest, app) {
|
|
626
|
+
return {
|
|
627
|
+
...manifest,
|
|
628
|
+
applications: [...manifest.applications, app]
|
|
629
|
+
};
|
|
518
630
|
}
|
|
519
631
|
|
|
520
632
|
// src/commands/create-application/create-application.command.ts
|
|
521
633
|
var CREATE_APPLICATION_COMMAND_NAME = "create-application";
|
|
522
634
|
var createApplicationCommand = {
|
|
523
635
|
name: CREATE_APPLICATION_COMMAND_NAME,
|
|
524
|
-
description: "Create an application in a platform"
|
|
636
|
+
description: "Create an application in a platform",
|
|
637
|
+
hidden: (ctx) => !ctx.platformInitialized
|
|
525
638
|
};
|
|
526
639
|
async function createApplication(params, logger) {
|
|
527
640
|
const {
|
|
528
|
-
organizationName,
|
|
529
|
-
platformName,
|
|
530
641
|
applicationName,
|
|
531
642
|
applicationDisplayName,
|
|
532
643
|
applicationDescription,
|
|
533
644
|
hasUserInterface,
|
|
534
645
|
hasBackendService
|
|
535
646
|
} = params;
|
|
536
|
-
|
|
647
|
+
const layout = await findPlatformLayout();
|
|
648
|
+
if (!layout) {
|
|
537
649
|
logger.log("Error: Cannot create an application \u2014 no platform initialized in this directory.");
|
|
538
650
|
return;
|
|
539
651
|
}
|
|
540
|
-
const rootDir =
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
652
|
+
const { rootDir, coreDirName, localDir } = layout;
|
|
653
|
+
let manifest;
|
|
654
|
+
try {
|
|
655
|
+
manifest = await readManifest(rootDir, coreDirName);
|
|
656
|
+
} catch (err) {
|
|
657
|
+
logger.log(`Error: Could not read product manifest \u2014 ${formatError(err)}`);
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
const { organization: organizationName, name: platformName } = manifest.product;
|
|
661
|
+
const localPath = `../${platformName}-${applicationName}`;
|
|
662
|
+
const applicationDir = resolve(join11(rootDir, coreDirName), localPath);
|
|
663
|
+
const bootstrapServiceName = `${platformName}-${applicationName}-bootstrap-service`;
|
|
664
|
+
const bootstrapServiceDir = join11(applicationDir, "services", bootstrapServiceName);
|
|
665
|
+
logger.log(`Creating application monorepo "${applicationName}"...`);
|
|
545
666
|
try {
|
|
546
667
|
await scaffoldApplicationMonorepo(applicationDir, organizationName, platformName, applicationName, logger);
|
|
547
668
|
} catch (err) {
|
|
548
669
|
logger.log(`Error: Could not scaffold application monorepo \u2014 ${formatError(err)}`);
|
|
549
670
|
return;
|
|
550
671
|
}
|
|
551
|
-
await mkdir2(
|
|
552
|
-
await mkdir2(
|
|
553
|
-
logger.log(`Creating bootstrap service "${
|
|
554
|
-
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`;
|
|
555
676
|
try {
|
|
556
|
-
await scaffoldBootstrap(bootstrapServiceDir, organizationName, `${platformName}-${applicationName}`,
|
|
677
|
+
await scaffoldBootstrap(bootstrapServiceDir, organizationName, `${platformName}-${applicationName}`, bootstrapServiceBaseDir, logger);
|
|
557
678
|
} catch (err) {
|
|
558
679
|
logger.log(`Error: Could not scaffold bootstrap service \u2014 ${formatError(err)}`);
|
|
559
680
|
return;
|
|
@@ -582,7 +703,8 @@ async function createApplication(params, logger) {
|
|
|
582
703
|
);
|
|
583
704
|
}
|
|
584
705
|
if (hasUserInterface) {
|
|
585
|
-
const
|
|
706
|
+
const uiName = `${platformName}-${applicationName}-ui`;
|
|
707
|
+
const uiDir = join11(applicationDir, "ui", uiName);
|
|
586
708
|
const uiBaseDir = `${platformName}-${applicationName}/ui`;
|
|
587
709
|
try {
|
|
588
710
|
await scaffoldUiModule(uiDir, organizationName, platformName, applicationName, applicationDisplayName, uiBaseDir, logger);
|
|
@@ -592,7 +714,8 @@ async function createApplication(params, logger) {
|
|
|
592
714
|
}
|
|
593
715
|
}
|
|
594
716
|
if (hasBackendService) {
|
|
595
|
-
const
|
|
717
|
+
const serviceName = `${platformName}-${applicationName}-service`;
|
|
718
|
+
const serviceDir = join11(applicationDir, "services", serviceName);
|
|
596
719
|
const serviceBaseDir = `${platformName}-${applicationName}/services`;
|
|
597
720
|
try {
|
|
598
721
|
await scaffoldNestjsService(serviceDir, organizationName, platformName, applicationName, applicationDisplayName, serviceBaseDir, logger);
|
|
@@ -601,28 +724,39 @@ async function createApplication(params, logger) {
|
|
|
601
724
|
return;
|
|
602
725
|
}
|
|
603
726
|
}
|
|
604
|
-
const dockerComposePath =
|
|
727
|
+
const dockerComposePath = join11(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
|
|
605
728
|
const basePort = await getNextAvailablePort(localDir);
|
|
606
729
|
const uiPort = hasUserInterface ? basePort : 0;
|
|
607
730
|
const servicePort = hasBackendService ? hasUserInterface ? basePort + 1 : basePort : 0;
|
|
608
731
|
await createDockerCompose(
|
|
609
732
|
dockerComposePath,
|
|
610
|
-
platformName,
|
|
611
733
|
applicationName,
|
|
734
|
+
platformName,
|
|
612
735
|
hasUserInterface,
|
|
613
736
|
hasBackendService,
|
|
614
737
|
uiPort,
|
|
615
738
|
servicePort,
|
|
616
739
|
logger
|
|
617
740
|
);
|
|
618
|
-
const
|
|
619
|
-
|
|
620
|
-
|
|
741
|
+
const updatedManifest = addApplicationToManifest(manifest, {
|
|
742
|
+
name: applicationName,
|
|
743
|
+
displayName: applicationDisplayName,
|
|
744
|
+
description: applicationDescription,
|
|
745
|
+
localPath,
|
|
746
|
+
repository: null
|
|
747
|
+
});
|
|
748
|
+
try {
|
|
749
|
+
await writeManifest(updatedManifest, rootDir, coreDirName);
|
|
750
|
+
logger.log(`Updated product manifest with application "${applicationName}".`);
|
|
751
|
+
} catch (err) {
|
|
752
|
+
logger.log(`Warning: Could not update product manifest \u2014 ${formatError(err)}`);
|
|
753
|
+
}
|
|
754
|
+
logger.log(`Done! Application "${applicationName}" created at ${applicationDir}`);
|
|
621
755
|
}
|
|
622
756
|
|
|
623
757
|
// src/commands/init/init.command.ts
|
|
624
|
-
import { join as
|
|
625
|
-
import { cwd as
|
|
758
|
+
import { join as join17 } from "path";
|
|
759
|
+
import { cwd as cwd4 } from "process";
|
|
626
760
|
|
|
627
761
|
// src/utils/string.ts
|
|
628
762
|
function camelize(name) {
|
|
@@ -631,9 +765,9 @@ function camelize(name) {
|
|
|
631
765
|
|
|
632
766
|
// src/commands/init/scaffold-platform.ts
|
|
633
767
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
634
|
-
import { join as
|
|
635
|
-
var templateDir =
|
|
636
|
-
|
|
768
|
+
import { join as join12, dirname as dirname8 } from "path";
|
|
769
|
+
var templateDir = join12(
|
|
770
|
+
dirname8(fileURLToPath6(import.meta.url)),
|
|
637
771
|
"..",
|
|
638
772
|
"templates",
|
|
639
773
|
"platform-init-template"
|
|
@@ -644,9 +778,9 @@ async function scaffoldPlatform(outputDir, variables, logger) {
|
|
|
644
778
|
|
|
645
779
|
// src/commands/init/scaffold-platform-bootstrap.ts
|
|
646
780
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
647
|
-
import { join as
|
|
648
|
-
var templateDir2 =
|
|
649
|
-
|
|
781
|
+
import { join as join13, dirname as dirname9 } from "path";
|
|
782
|
+
var templateDir2 = join13(
|
|
783
|
+
dirname9(fileURLToPath7(import.meta.url)),
|
|
650
784
|
"..",
|
|
651
785
|
"templates",
|
|
652
786
|
"bootstrap-service-template"
|
|
@@ -657,9 +791,9 @@ async function scaffoldPlatformBootstrap(outputDir, variables, logger) {
|
|
|
657
791
|
|
|
658
792
|
// src/commands/init/scaffold-customization-ui.ts
|
|
659
793
|
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
660
|
-
import { join as
|
|
661
|
-
var templateDir3 =
|
|
662
|
-
|
|
794
|
+
import { join as join14, dirname as dirname10 } from "path";
|
|
795
|
+
var templateDir3 = join14(
|
|
796
|
+
dirname10(fileURLToPath8(import.meta.url)),
|
|
663
797
|
"..",
|
|
664
798
|
"templates",
|
|
665
799
|
"customization-ui-module-template"
|
|
@@ -669,33 +803,34 @@ async function scaffoldCustomizationUi(outputDir, variables, logger) {
|
|
|
669
803
|
}
|
|
670
804
|
|
|
671
805
|
// src/commands/init/register-customization-module.ts
|
|
672
|
-
import { join as
|
|
673
|
-
import { readFile as
|
|
674
|
-
async function registerCustomizationModule(bootstrapServiceDir, organizationName,
|
|
675
|
-
const modulesJsonPath =
|
|
806
|
+
import { join as join15 } from "path";
|
|
807
|
+
import { readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
|
|
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`;
|
|
676
811
|
const entry = {
|
|
677
|
-
name: `@${organizationName}/${
|
|
812
|
+
name: `@${organizationName}/${customizationUiName}`,
|
|
678
813
|
displayName: "Platform Customization UI",
|
|
679
814
|
type: "app",
|
|
680
|
-
baseUrl: `/${
|
|
681
|
-
service: { host:
|
|
815
|
+
baseUrl: `/${customizationUiName}`,
|
|
816
|
+
service: { host: customizationUiName, port: 80 },
|
|
682
817
|
ui: {
|
|
683
818
|
route: "/",
|
|
684
|
-
bundleFile: `${
|
|
819
|
+
bundleFile: `${customizationUiName}.js`,
|
|
685
820
|
isPlatformCustomization: true,
|
|
686
821
|
customProps: {}
|
|
687
822
|
},
|
|
688
823
|
dependsOn: []
|
|
689
824
|
};
|
|
690
|
-
const existing = JSON.parse(await
|
|
825
|
+
const existing = JSON.parse(await readFile5(modulesJsonPath, "utf-8"));
|
|
691
826
|
existing.push(entry);
|
|
692
827
|
await writeFile5(modulesJsonPath, JSON.stringify(existing, null, 2) + "\n", "utf-8");
|
|
693
828
|
logger.log(`Registered customization module in ${modulesJsonPath}`);
|
|
694
829
|
}
|
|
695
830
|
|
|
696
831
|
// src/commands/init/generate-local-env.ts
|
|
697
|
-
import { readFile as
|
|
698
|
-
import { join as
|
|
832
|
+
import { readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
|
|
833
|
+
import { join as join16 } from "path";
|
|
699
834
|
|
|
700
835
|
// src/utils/random.ts
|
|
701
836
|
import { randomBytes } from "crypto";
|
|
@@ -704,11 +839,11 @@ function generateRandomSecret() {
|
|
|
704
839
|
}
|
|
705
840
|
|
|
706
841
|
// src/commands/init/generate-local-env.ts
|
|
707
|
-
async function generateLocalEnv(outputDir,
|
|
708
|
-
const examplePath =
|
|
709
|
-
const envPath =
|
|
710
|
-
logger.log(`Generating ${
|
|
711
|
-
const content = await
|
|
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...`);
|
|
846
|
+
const content = await readFile6(examplePath, "utf-8");
|
|
712
847
|
const result = content.replace(/^(PAE_AUTH_JWT_SECRET|PAE_GATEWAY_SERVICE_ACCESS_SECRET|PAE_DB_PASSWORD)=$/gm, (_, key) => {
|
|
713
848
|
return `${key}=${generateRandomSecret()}`;
|
|
714
849
|
});
|
|
@@ -719,7 +854,8 @@ async function generateLocalEnv(outputDir, platformName, logger) {
|
|
|
719
854
|
var INIT_COMMAND_NAME = "init";
|
|
720
855
|
var initCommand = {
|
|
721
856
|
name: INIT_COMMAND_NAME,
|
|
722
|
-
description: "Initialize a new platform"
|
|
857
|
+
description: "Initialize a new platform",
|
|
858
|
+
hidden: ({ platformInitialized }) => platformInitialized
|
|
723
859
|
};
|
|
724
860
|
async function init(params, logger) {
|
|
725
861
|
if (await isPlatformInitialized()) {
|
|
@@ -728,15 +864,22 @@ async function init(params, logger) {
|
|
|
728
864
|
}
|
|
729
865
|
const { organizationName, platformName, platformDisplayName } = params;
|
|
730
866
|
const platformTitle = camelize(platformName);
|
|
731
|
-
const outputDir =
|
|
867
|
+
const outputDir = cwd4();
|
|
732
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}`;
|
|
733
874
|
const variables = {
|
|
734
875
|
organizationName,
|
|
735
876
|
platformName,
|
|
736
877
|
platformTitle,
|
|
737
878
|
platformDisplayName,
|
|
738
879
|
bootstrapName: platformName,
|
|
739
|
-
|
|
880
|
+
bootstrapServiceName,
|
|
881
|
+
customizationUiName,
|
|
882
|
+
bootstrapServiceDir: `${coreDirName}/services`
|
|
740
883
|
};
|
|
741
884
|
try {
|
|
742
885
|
await scaffoldPlatform(outputDir, variables, logger);
|
|
@@ -745,14 +888,22 @@ async function init(params, logger) {
|
|
|
745
888
|
return;
|
|
746
889
|
}
|
|
747
890
|
try {
|
|
748
|
-
await generateLocalEnv(outputDir,
|
|
891
|
+
await generateLocalEnv(outputDir, logger, coreDirName);
|
|
892
|
+
} catch (err) {
|
|
893
|
+
logger.log(`Error: Could not generate ${coreDirName}/local/.env \u2014 ${formatError(err)}`);
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
try {
|
|
897
|
+
const manifest = createInitialManifest({ organizationName, platformName, platformDisplayName });
|
|
898
|
+
await writeManifest(manifest, outputDir, coreDirName);
|
|
899
|
+
logger.log(`Created product manifest: ${coreDirName}/product.manifest.json`);
|
|
749
900
|
} catch (err) {
|
|
750
|
-
logger.log(`Error: Could not
|
|
901
|
+
logger.log(`Error: Could not write product manifest \u2014 ${formatError(err)}`);
|
|
751
902
|
return;
|
|
752
903
|
}
|
|
753
904
|
try {
|
|
754
905
|
await scaffoldPlatformBootstrap(
|
|
755
|
-
|
|
906
|
+
join17(outputDir, coreDirName, "services", bootstrapServiceName),
|
|
756
907
|
variables,
|
|
757
908
|
logger
|
|
758
909
|
);
|
|
@@ -762,7 +913,7 @@ async function init(params, logger) {
|
|
|
762
913
|
}
|
|
763
914
|
try {
|
|
764
915
|
await scaffoldCustomizationUi(
|
|
765
|
-
|
|
916
|
+
join17(outputDir, coreDirName, "ui", customizationUiName),
|
|
766
917
|
variables,
|
|
767
918
|
logger
|
|
768
919
|
);
|
|
@@ -772,10 +923,10 @@ async function init(params, logger) {
|
|
|
772
923
|
}
|
|
773
924
|
try {
|
|
774
925
|
await registerCustomizationModule(
|
|
775
|
-
|
|
926
|
+
join17(outputDir, coreDirName, "services", bootstrapServiceName),
|
|
776
927
|
organizationName,
|
|
777
|
-
|
|
778
|
-
|
|
928
|
+
logger,
|
|
929
|
+
platformName
|
|
779
930
|
);
|
|
780
931
|
} catch (err) {
|
|
781
932
|
logger.log(`Error: Could not register customization module \u2014 ${formatError(err)}`);
|
|
@@ -785,16 +936,15 @@ async function init(params, logger) {
|
|
|
785
936
|
}
|
|
786
937
|
|
|
787
938
|
// src/commands/configure-idp/configure-idp.command.ts
|
|
788
|
-
import { join as
|
|
789
|
-
import { cwd as cwd4 } from "process";
|
|
939
|
+
import { join as join18 } from "path";
|
|
790
940
|
import { fetch as undiciFetch, Agent } from "undici";
|
|
791
941
|
|
|
792
942
|
// src/utils/env-reader.ts
|
|
793
|
-
import { readFile as
|
|
943
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
794
944
|
async function readEnvFile(filePath) {
|
|
795
945
|
let content;
|
|
796
946
|
try {
|
|
797
|
-
content = await
|
|
947
|
+
content = await readFile7(filePath, "utf-8");
|
|
798
948
|
} catch (error) {
|
|
799
949
|
if (error.code === "ENOENT") {
|
|
800
950
|
throw new Error(`.env file not found at ${filePath}`);
|
|
@@ -861,15 +1011,16 @@ function getAllProviders() {
|
|
|
861
1011
|
var CONFIGURE_IDP_COMMAND_NAME = "configure-idp";
|
|
862
1012
|
var configureIdpCommand = {
|
|
863
1013
|
name: CONFIGURE_IDP_COMMAND_NAME,
|
|
864
|
-
description: "Configure an Identity Provider (IDP) in the gateway"
|
|
1014
|
+
description: "Configure an Identity Provider (IDP) in the gateway",
|
|
1015
|
+
hidden: (ctx) => !ctx.platformInitialized
|
|
865
1016
|
};
|
|
866
1017
|
async function configureIdp(params, logger) {
|
|
867
|
-
|
|
1018
|
+
const layout = await findPlatformLayout();
|
|
1019
|
+
if (!layout) {
|
|
868
1020
|
logger.log("Error: Cannot configure an IDP \u2014 no platform initialized in this directory.");
|
|
869
1021
|
return;
|
|
870
1022
|
}
|
|
871
|
-
const
|
|
872
|
-
const envPath = join17(cwd4(), `${platformName}-local`, ".env");
|
|
1023
|
+
const envPath = join18(layout.localDir, ".env");
|
|
873
1024
|
let env;
|
|
874
1025
|
try {
|
|
875
1026
|
env = await readEnvFile(envPath);
|
|
@@ -880,11 +1031,11 @@ async function configureIdp(params, logger) {
|
|
|
880
1031
|
const gatewayUrl = env.get("PAE_GATEWAY_HOST_URL");
|
|
881
1032
|
const accessSecret = env.get("PAE_GATEWAY_SERVICE_ACCESS_SECRET");
|
|
882
1033
|
if (!gatewayUrl) {
|
|
883
|
-
logger.log(
|
|
1034
|
+
logger.log("Error: PAE_GATEWAY_HOST_URL is not set in core/local/.env");
|
|
884
1035
|
return;
|
|
885
1036
|
}
|
|
886
1037
|
if (!accessSecret) {
|
|
887
|
-
logger.log(
|
|
1038
|
+
logger.log("Error: PAE_GATEWAY_SERVICE_ACCESS_SECRET is not set in core/local/.env");
|
|
888
1039
|
return;
|
|
889
1040
|
}
|
|
890
1041
|
const provider = idpProviderRegistry.get(params.providerType);
|
|
@@ -921,15 +1072,14 @@ async function configureIdp(params, logger) {
|
|
|
921
1072
|
}
|
|
922
1073
|
|
|
923
1074
|
// src/commands/create-service-module/create-service-module.command.ts
|
|
924
|
-
import { join as
|
|
925
|
-
import {
|
|
926
|
-
import { access as access2 } from "fs/promises";
|
|
1075
|
+
import { join as join20, resolve as resolve2 } from "path";
|
|
1076
|
+
import { access as access3 } from "fs/promises";
|
|
927
1077
|
|
|
928
1078
|
// src/commands/create-service-module/scaffold-service-module.ts
|
|
929
1079
|
import { fileURLToPath as fileURLToPath9 } from "url";
|
|
930
|
-
import { join as
|
|
931
|
-
var nestjsServiceModuleTemplateDir2 =
|
|
932
|
-
|
|
1080
|
+
import { join as join19, dirname as dirname11 } from "path";
|
|
1081
|
+
var nestjsServiceModuleTemplateDir2 = join19(
|
|
1082
|
+
dirname11(fileURLToPath9(import.meta.url)),
|
|
933
1083
|
"..",
|
|
934
1084
|
"templates",
|
|
935
1085
|
"nestjs-service-module-template"
|
|
@@ -963,12 +1113,12 @@ function buildCustomServiceModuleEntry(organizationName, serviceName, serviceDis
|
|
|
963
1113
|
}
|
|
964
1114
|
|
|
965
1115
|
// src/commands/create-service-module/append-docker-compose.ts
|
|
966
|
-
import { readFile as
|
|
1116
|
+
import { readFile as readFile8, writeFile as writeFile7 } from "fs/promises";
|
|
967
1117
|
async function appendServiceToDockerCompose(dockerComposePath, serviceName, platformName, applicationName, port, logger) {
|
|
968
1118
|
const block = `
|
|
969
1119
|
${serviceName}:
|
|
970
1120
|
build:
|
|
971
|
-
context:
|
|
1121
|
+
context: ../../${platformName}-${applicationName}/services/${serviceName}
|
|
972
1122
|
dockerfile: Dockerfile.development
|
|
973
1123
|
args:
|
|
974
1124
|
- BA_NPM_AUTH_TOKEN=$BA_NPM_AUTH_TOKEN
|
|
@@ -977,10 +1127,10 @@ async function appendServiceToDockerCompose(dockerComposePath, serviceName, plat
|
|
|
977
1127
|
environment:
|
|
978
1128
|
- GATEWAY_URL=\${PAE_GATEWAY_URL}
|
|
979
1129
|
volumes:
|
|
980
|
-
- \${PWD}
|
|
1130
|
+
- \${PWD}/:/app/out
|
|
981
1131
|
`;
|
|
982
1132
|
try {
|
|
983
|
-
const existing = await
|
|
1133
|
+
const existing = await readFile8(dockerComposePath, "utf-8");
|
|
984
1134
|
await writeFile7(dockerComposePath, existing + block, "utf-8");
|
|
985
1135
|
logger.log(`Updated docker-compose: ${dockerComposePath}`);
|
|
986
1136
|
} catch (err) {
|
|
@@ -993,37 +1143,43 @@ async function appendServiceToDockerCompose(dockerComposePath, serviceName, plat
|
|
|
993
1143
|
var CREATE_SERVICE_MODULE_COMMAND_NAME = "create-service-module";
|
|
994
1144
|
var createServiceModuleCommand = {
|
|
995
1145
|
name: CREATE_SERVICE_MODULE_COMMAND_NAME,
|
|
996
|
-
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
|
|
997
1148
|
};
|
|
998
1149
|
async function createServiceModule(params, logger) {
|
|
999
|
-
const { applicationName, serviceName, serviceDisplayName } = params;
|
|
1000
|
-
|
|
1150
|
+
const { applicationName, serviceName, serviceDisplayName, serviceNameSuffix } = params;
|
|
1151
|
+
const layout = await findPlatformLayout();
|
|
1152
|
+
if (!layout) {
|
|
1001
1153
|
logger.log("Error: Cannot create a service module \u2014 no platform initialized in this directory.");
|
|
1002
1154
|
return;
|
|
1003
1155
|
}
|
|
1004
|
-
const rootDir =
|
|
1005
|
-
let
|
|
1006
|
-
let platformName;
|
|
1156
|
+
const { rootDir, coreDirName, localDir } = layout;
|
|
1157
|
+
let manifest;
|
|
1007
1158
|
try {
|
|
1008
|
-
|
|
1009
|
-
organizationName = manifest.organizationName;
|
|
1010
|
-
platformName = manifest.platformName;
|
|
1159
|
+
manifest = await readManifest(rootDir, coreDirName);
|
|
1011
1160
|
} catch (err) {
|
|
1012
|
-
logger.log(`Error: Could not read
|
|
1161
|
+
logger.log(`Error: Could not read product manifest \u2014 ${formatError(err)}`);
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
const { organization: organizationName, name: platformName } = manifest.product;
|
|
1165
|
+
const appEntry = manifest.applications.find((a) => a.name === applicationName);
|
|
1166
|
+
if (!appEntry) {
|
|
1167
|
+
logger.log(`Error: The specified application "${applicationName}" is not registered in the product manifest.`);
|
|
1013
1168
|
return;
|
|
1014
1169
|
}
|
|
1015
|
-
const applicationDir =
|
|
1170
|
+
const applicationDir = resolve2(join20(rootDir, coreDirName), appEntry.localPath);
|
|
1016
1171
|
try {
|
|
1017
|
-
await
|
|
1172
|
+
await access3(applicationDir);
|
|
1018
1173
|
} catch {
|
|
1019
|
-
logger.log(`Error: The specified application "${
|
|
1174
|
+
logger.log(`Error: The specified application "${applicationName}" does not exist in the platform.`);
|
|
1020
1175
|
return;
|
|
1021
1176
|
}
|
|
1022
|
-
const
|
|
1023
|
-
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);
|
|
1024
1180
|
try {
|
|
1025
|
-
await
|
|
1026
|
-
logger.log(`Error: A service named "${fullServiceName}" already exists in application "${
|
|
1181
|
+
await access3(serviceDir);
|
|
1182
|
+
logger.log(`Error: A service named "${fullServiceName}" already exists in application "${applicationName}".`);
|
|
1027
1183
|
return;
|
|
1028
1184
|
} catch {
|
|
1029
1185
|
}
|
|
@@ -1041,36 +1197,35 @@ async function createServiceModule(params, logger) {
|
|
|
1041
1197
|
logger.log(`Error: Could not scaffold NestJS service \u2014 ${formatError(err)}`);
|
|
1042
1198
|
return;
|
|
1043
1199
|
}
|
|
1044
|
-
const bootstrapServiceDir =
|
|
1200
|
+
const bootstrapServiceDir = join20(applicationDir, "services", `${platformName}-${applicationName}-bootstrap-service`);
|
|
1045
1201
|
const moduleEntry = buildCustomServiceModuleEntry(organizationName, fullServiceName, serviceDisplayName);
|
|
1046
1202
|
await addModuleEntry(bootstrapServiceDir, applicationName, moduleEntry, logger);
|
|
1047
|
-
const
|
|
1048
|
-
const dockerComposePath = join19(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
|
|
1203
|
+
const dockerComposePath = join20(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
|
|
1049
1204
|
const port = await getNextAvailablePort(localDir);
|
|
1050
1205
|
await appendServiceToDockerCompose(dockerComposePath, fullServiceName, platformName, applicationName, port, logger);
|
|
1051
|
-
logger.log(`Done! Service module "${fullServiceName}" added to application "${
|
|
1206
|
+
logger.log(`Done! Service module "${fullServiceName}" added to application "${applicationName}".`);
|
|
1052
1207
|
}
|
|
1053
1208
|
|
|
1054
1209
|
// src/commands/create-ui-module/create-ui-module.command.ts
|
|
1055
|
-
import { join as
|
|
1056
|
-
import {
|
|
1057
|
-
import { access as access3, readdir as readdir4 } from "fs/promises";
|
|
1210
|
+
import { join as join21, resolve as resolve3 } from "path";
|
|
1211
|
+
import { access as access4, readdir as readdir4 } from "fs/promises";
|
|
1058
1212
|
|
|
1059
1213
|
// src/commands/create-ui-module/append-ui-docker-compose.ts
|
|
1060
|
-
import { readFile as
|
|
1061
|
-
async function appendUiToDockerCompose(dockerComposePath, platformName, applicationName, port, logger) {
|
|
1214
|
+
import { readFile as readFile9, writeFile as writeFile8 } from "fs/promises";
|
|
1215
|
+
async function appendUiToDockerCompose(dockerComposePath, platformName, applicationName, port, logger, uiNameOverride) {
|
|
1216
|
+
const uiName = uiNameOverride ?? `${platformName}-${applicationName}-ui`;
|
|
1062
1217
|
const block = `
|
|
1063
|
-
${
|
|
1218
|
+
${uiName}:
|
|
1064
1219
|
build:
|
|
1065
|
-
context:
|
|
1220
|
+
context: ../../${platformName}-${applicationName}/ui/${uiName}
|
|
1066
1221
|
dockerfile: Dockerfile.development
|
|
1067
1222
|
ports:
|
|
1068
1223
|
- ${port}:80
|
|
1069
1224
|
volumes:
|
|
1070
|
-
- \${PWD}
|
|
1225
|
+
- \${PWD}/:/app/out
|
|
1071
1226
|
`;
|
|
1072
1227
|
try {
|
|
1073
|
-
const existing = await
|
|
1228
|
+
const existing = await readFile9(dockerComposePath, "utf-8");
|
|
1074
1229
|
await writeFile8(dockerComposePath, existing + block, "utf-8");
|
|
1075
1230
|
logger.log(`Updated docker-compose: ${dockerComposePath}`);
|
|
1076
1231
|
} catch (err) {
|
|
@@ -1083,43 +1238,50 @@ async function appendUiToDockerCompose(dockerComposePath, platformName, applicat
|
|
|
1083
1238
|
var CREATE_UI_MODULE_COMMAND_NAME = "create-ui-module";
|
|
1084
1239
|
var createUiModuleCommand = {
|
|
1085
1240
|
name: CREATE_UI_MODULE_COMMAND_NAME,
|
|
1086
|
-
description: "Add a UI module to an existing application"
|
|
1241
|
+
description: "Add a UI module to an existing application",
|
|
1242
|
+
hidden: (ctx) => !ctx.platformInitialized
|
|
1087
1243
|
};
|
|
1088
1244
|
async function createUiModule(params, logger) {
|
|
1089
|
-
const { applicationName, applicationDisplayName } = params;
|
|
1090
|
-
|
|
1245
|
+
const { applicationName, applicationDisplayName, uiModuleSuffix } = params;
|
|
1246
|
+
const layout = await findPlatformLayout();
|
|
1247
|
+
if (!layout) {
|
|
1091
1248
|
logger.log("Error: Cannot create a UI module \u2014 no platform initialized in this directory.");
|
|
1092
1249
|
return;
|
|
1093
1250
|
}
|
|
1094
|
-
const rootDir =
|
|
1095
|
-
let
|
|
1096
|
-
let platformName;
|
|
1251
|
+
const { rootDir, coreDirName, localDir } = layout;
|
|
1252
|
+
let manifest;
|
|
1097
1253
|
try {
|
|
1098
|
-
|
|
1099
|
-
organizationName = manifest.organizationName;
|
|
1100
|
-
platformName = manifest.platformName;
|
|
1254
|
+
manifest = await readManifest(rootDir, coreDirName);
|
|
1101
1255
|
} catch (err) {
|
|
1102
|
-
logger.log(`Error: Could not read
|
|
1256
|
+
logger.log(`Error: Could not read product manifest \u2014 ${formatError(err)}`);
|
|
1103
1257
|
return;
|
|
1104
1258
|
}
|
|
1105
|
-
const
|
|
1259
|
+
const { organization: organizationName, name: platformName } = manifest.product;
|
|
1260
|
+
const appEntry = manifest.applications.find((a) => a.name === applicationName);
|
|
1261
|
+
if (!appEntry) {
|
|
1262
|
+
logger.log(`Error: The specified application "${applicationName}" is not registered in the product manifest.`);
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
const applicationDir = resolve3(join21(rootDir, coreDirName), appEntry.localPath);
|
|
1106
1266
|
try {
|
|
1107
|
-
await
|
|
1267
|
+
await access4(applicationDir);
|
|
1108
1268
|
} catch {
|
|
1109
|
-
logger.log(`Error: The specified application "${
|
|
1269
|
+
logger.log(`Error: The specified application "${applicationName}" does not exist in the platform.`);
|
|
1110
1270
|
return;
|
|
1111
1271
|
}
|
|
1112
|
-
const uiDir =
|
|
1272
|
+
const uiDir = join21(applicationDir, "ui");
|
|
1113
1273
|
try {
|
|
1114
1274
|
const uiEntries = await readdir4(uiDir);
|
|
1115
1275
|
const existingUiModules = uiEntries.filter((e) => e !== ".gitkeep");
|
|
1116
1276
|
if (existingUiModules.length > 0) {
|
|
1117
|
-
logger.log(`Error: Currently we support only one UI module per application. Application "${
|
|
1277
|
+
logger.log(`Error: Currently we support only one UI module per application. Application "${applicationName}" already has a UI module.`);
|
|
1118
1278
|
return;
|
|
1119
1279
|
}
|
|
1120
1280
|
} catch {
|
|
1121
1281
|
}
|
|
1122
|
-
const
|
|
1282
|
+
const uiSuffix = uiModuleSuffix === void 0 ? "ui" : uiModuleSuffix;
|
|
1283
|
+
const uiName = uiSuffix ? `${platformName}-${applicationName}-${uiSuffix}` : `${platformName}-${applicationName}`;
|
|
1284
|
+
const uiOutputDir = join21(uiDir, uiName);
|
|
1123
1285
|
const uiBaseDir = `${platformName}-${applicationName}/ui`;
|
|
1124
1286
|
try {
|
|
1125
1287
|
await scaffoldUiModule(uiOutputDir, organizationName, platformName, applicationName, applicationDisplayName, uiBaseDir, logger);
|
|
@@ -1127,123 +1289,1069 @@ async function createUiModule(params, logger) {
|
|
|
1127
1289
|
logger.log(`Error: Could not scaffold UI module \u2014 ${formatError(err)}`);
|
|
1128
1290
|
return;
|
|
1129
1291
|
}
|
|
1130
|
-
const bootstrapServiceDir =
|
|
1131
|
-
const moduleEntry = buildUiModuleEntry(organizationName, platformName, applicationName, applicationDisplayName);
|
|
1292
|
+
const bootstrapServiceDir = join21(applicationDir, "services", `${platformName}-${applicationName}-bootstrap-service`);
|
|
1293
|
+
const moduleEntry = buildUiModuleEntry(organizationName, platformName, applicationName, applicationDisplayName, uiName);
|
|
1132
1294
|
await addModuleEntry(bootstrapServiceDir, applicationName, moduleEntry, logger);
|
|
1133
|
-
const
|
|
1134
|
-
const dockerComposePath = join20(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
|
|
1295
|
+
const dockerComposePath = join21(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
|
|
1135
1296
|
const port = await getNextAvailablePort(localDir);
|
|
1136
|
-
await appendUiToDockerCompose(dockerComposePath, platformName, applicationName, port, logger);
|
|
1137
|
-
logger.log(`Done! UI module "${
|
|
1297
|
+
await appendUiToDockerCompose(dockerComposePath, platformName, applicationName, port, logger, uiName);
|
|
1298
|
+
logger.log(`Done! UI module "${uiName}" added to application "${applicationName}".`);
|
|
1138
1299
|
}
|
|
1139
1300
|
|
|
1140
|
-
// src/commands/
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
this.commands.set(command.name, command);
|
|
1145
|
-
}
|
|
1146
|
-
get(name) {
|
|
1147
|
-
return this.commands.get(name);
|
|
1148
|
-
}
|
|
1149
|
-
getAll() {
|
|
1150
|
-
return Array.from(this.commands.values());
|
|
1151
|
-
}
|
|
1152
|
-
search(query) {
|
|
1153
|
-
const all = this.getAll();
|
|
1154
|
-
if (!query) return all;
|
|
1155
|
-
const fzf = new Fzf(all, {
|
|
1156
|
-
selector: (cmd) => `${cmd.name} ${cmd.description}`
|
|
1157
|
-
});
|
|
1158
|
-
return fzf.find(query).map((result) => result.item);
|
|
1159
|
-
}
|
|
1160
|
-
};
|
|
1161
|
-
var registry = new CommandRegistry();
|
|
1162
|
-
registry.register(createApplicationCommand);
|
|
1163
|
-
registry.register(initCommand);
|
|
1164
|
-
registry.register(configureIdpCommand);
|
|
1165
|
-
registry.register(createServiceModuleCommand);
|
|
1166
|
-
registry.register(createUiModuleCommand);
|
|
1167
|
-
|
|
1168
|
-
// src/app-state.ts
|
|
1169
|
-
var APP_STATE = {
|
|
1170
|
-
IDLE: "idle",
|
|
1171
|
-
PALETTE: "palette",
|
|
1172
|
-
EXECUTING: "executing",
|
|
1173
|
-
PROMPTING: "prompting"
|
|
1174
|
-
};
|
|
1175
|
-
|
|
1176
|
-
// src/hooks/use-command-runner.ts
|
|
1177
|
-
import { useState as useState2, useCallback, useRef } from "react";
|
|
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";
|
|
1178
1305
|
|
|
1179
|
-
// src/
|
|
1180
|
-
|
|
1181
|
-
|
|
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
|
+
});
|
|
1182
1353
|
}
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
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
|
+
}
|
|
1189
1391
|
}
|
|
1190
|
-
|
|
1191
|
-
|
|
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;
|
|
1192
1400
|
try {
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1401
|
+
await access5(prefixedCoreCompose);
|
|
1402
|
+
coreComposePath = prefixedCoreCompose;
|
|
1403
|
+
} catch {
|
|
1404
|
+
coreComposePath = unprefixedCoreCompose;
|
|
1405
|
+
}
|
|
1406
|
+
const fileArgs = [
|
|
1407
|
+
"-f",
|
|
1408
|
+
join22(localDir, "platform-docker-compose.yml"),
|
|
1409
|
+
"-f",
|
|
1410
|
+
coreComposePath
|
|
1411
|
+
];
|
|
1412
|
+
const appEntries = await getAppComposePaths(localDir, platformName, manifest);
|
|
1413
|
+
for (const { composePath } of appEntries) {
|
|
1414
|
+
fileArgs.push("-f", composePath);
|
|
1415
|
+
}
|
|
1416
|
+
for (const app of manifest.applications) {
|
|
1417
|
+
if (!appEntries.find((e) => e.appName === app.name)) {
|
|
1418
|
+
logger.log(`Warning: No docker-compose found for application "${app.name}" in ${coreDirName}/local/ \u2014 skipping.`);
|
|
1419
|
+
}
|
|
1205
1420
|
}
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1421
|
+
return fileArgs;
|
|
1422
|
+
}
|
|
1423
|
+
async function buildSelectedComposeFiles(layout, selectedManifest, includeCore) {
|
|
1424
|
+
const { coreDirName, localDir } = layout;
|
|
1425
|
+
const platformName = selectedManifest.product.name;
|
|
1426
|
+
const files = [];
|
|
1427
|
+
if (includeCore) {
|
|
1428
|
+
const prefixedCoreCompose = join22(localDir, `${coreDirName}-docker-compose.yml`);
|
|
1429
|
+
const unprefixedCoreCompose = join22(localDir, "core-docker-compose.yml");
|
|
1430
|
+
let coreComposePath;
|
|
1431
|
+
try {
|
|
1432
|
+
await access5(prefixedCoreCompose);
|
|
1433
|
+
coreComposePath = prefixedCoreCompose;
|
|
1434
|
+
} catch {
|
|
1435
|
+
coreComposePath = unprefixedCoreCompose;
|
|
1436
|
+
}
|
|
1437
|
+
files.push(join22(localDir, "platform-docker-compose.yml"), coreComposePath);
|
|
1212
1438
|
}
|
|
1213
|
-
const
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
return;
|
|
1439
|
+
const appEntries = await getAppComposePaths(localDir, platformName, selectedManifest);
|
|
1440
|
+
for (const { composePath } of appEntries) {
|
|
1441
|
+
files.push(composePath);
|
|
1217
1442
|
}
|
|
1218
|
-
|
|
1219
|
-
{
|
|
1220
|
-
organizationName,
|
|
1221
|
-
platformName,
|
|
1222
|
-
applicationName,
|
|
1223
|
-
applicationDisplayName,
|
|
1224
|
-
applicationDescription,
|
|
1225
|
-
hasUserInterface: hasUserInterfaceAnswer === "yes",
|
|
1226
|
-
hasBackendService: hasBackendServiceAnswer === "yes"
|
|
1227
|
-
},
|
|
1228
|
-
ctx
|
|
1229
|
-
);
|
|
1443
|
+
return files;
|
|
1230
1444
|
}
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1445
|
+
function extractFilePaths(fileArgs) {
|
|
1446
|
+
const paths = [];
|
|
1447
|
+
for (let i = 0; i < fileArgs.length; i++) {
|
|
1448
|
+
if (fileArgs[i] === "-f" && i + 1 < fileArgs.length) {
|
|
1449
|
+
paths.push(fileArgs[i + 1]);
|
|
1450
|
+
i++;
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
return paths;
|
|
1454
|
+
}
|
|
1455
|
+
async function getServicesFromComposeFiles(selectedFiles, allFiles, rootDir) {
|
|
1456
|
+
const selectedSet = new Set(selectedFiles);
|
|
1457
|
+
const contextFiles = allFiles.filter((f) => !selectedSet.has(f));
|
|
1458
|
+
if (contextFiles.length === 0) {
|
|
1459
|
+
const fileArgs = selectedFiles.flatMap((f) => ["-f", f]);
|
|
1460
|
+
const output = await captureDockerCompose([...fileArgs, "config", "--services"], rootDir);
|
|
1461
|
+
return output.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
1462
|
+
}
|
|
1463
|
+
const allFileArgs = allFiles.flatMap((f) => ["-f", f]);
|
|
1464
|
+
const allOutput = await captureDockerCompose([...allFileArgs, "config", "--services"], rootDir);
|
|
1465
|
+
const allServices = new Set(allOutput.split("\n").map((s) => s.trim()).filter(Boolean));
|
|
1466
|
+
const contextFileArgs = contextFiles.flatMap((f) => ["-f", f]);
|
|
1467
|
+
let contextServices;
|
|
1468
|
+
try {
|
|
1469
|
+
const contextOutput = await captureDockerCompose([...contextFileArgs, "config", "--services"], rootDir);
|
|
1470
|
+
contextServices = new Set(contextOutput.split("\n").map((s) => s.trim()).filter(Boolean));
|
|
1471
|
+
} catch {
|
|
1472
|
+
const selectedFileArgs = selectedFiles.flatMap((f) => ["-f", f]);
|
|
1473
|
+
const fallbackOutput = await captureDockerCompose([...selectedFileArgs, "config", "--services"], rootDir);
|
|
1474
|
+
return fallbackOutput.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
1475
|
+
}
|
|
1476
|
+
return [...allServices].filter((s) => !contextServices.has(s));
|
|
1477
|
+
}
|
|
1478
|
+
async function startEnvironment(layout, manifest, logger, signal, includeCore = true, fullManifest) {
|
|
1479
|
+
const { rootDir, localDir } = layout;
|
|
1480
|
+
const platformName = manifest.product.name;
|
|
1481
|
+
const envFile = join22(localDir, ".env");
|
|
1482
|
+
const isSelective = fullManifest !== void 0;
|
|
1483
|
+
const fileArgs = await buildFullComposeArgs(layout, isSelective ? fullManifest : manifest, logger);
|
|
1484
|
+
const projectArgs = ["-p", `${platformName}-platform`, "--project-directory", localDir, "--env-file", envFile];
|
|
1485
|
+
if (isSelective) {
|
|
1486
|
+
const selectedFiles = await buildSelectedComposeFiles(layout, manifest, includeCore);
|
|
1487
|
+
const allFiles = extractFilePaths(fileArgs);
|
|
1488
|
+
const services = await getServicesFromComposeFiles(selectedFiles, allFiles, rootDir);
|
|
1489
|
+
if (services.length === 0) {
|
|
1490
|
+
logger.log("No services found for selected targets. Nothing to start.");
|
|
1491
|
+
return;
|
|
1492
|
+
}
|
|
1493
|
+
logger.log(`Starting: ${services.join(", ")}...`);
|
|
1494
|
+
await runDockerCompose([...projectArgs, ...fileArgs, "up", "-d", "--build", ...services], logger, rootDir, signal);
|
|
1495
|
+
} else {
|
|
1496
|
+
logger.log("Starting environment...");
|
|
1497
|
+
await runDockerCompose([...projectArgs, ...fileArgs, "up", "-d", "--build", "--remove-orphans"], logger, rootDir, signal);
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
async function stopEnvironment(layout, manifest, logger, signal, includeCore = true, fullManifest) {
|
|
1501
|
+
const { rootDir, localDir } = layout;
|
|
1502
|
+
const platformName = manifest.product.name;
|
|
1503
|
+
const envFile = join22(localDir, ".env");
|
|
1504
|
+
const isSelective = fullManifest !== void 0;
|
|
1505
|
+
const fileArgs = await buildFullComposeArgs(layout, isSelective ? fullManifest : manifest, logger);
|
|
1506
|
+
const projectArgs = ["-p", `${platformName}-platform`, "--project-directory", localDir, "--env-file", envFile];
|
|
1507
|
+
if (isSelective) {
|
|
1508
|
+
const selectedFiles = await buildSelectedComposeFiles(layout, manifest, includeCore);
|
|
1509
|
+
const allFiles = extractFilePaths(fileArgs);
|
|
1510
|
+
const services = await getServicesFromComposeFiles(selectedFiles, allFiles, rootDir);
|
|
1511
|
+
if (services.length === 0) {
|
|
1512
|
+
logger.log("No services found for selected targets. Nothing to stop.");
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1515
|
+
logger.log(`Stopping: ${services.join(", ")}...`);
|
|
1516
|
+
await runDockerCompose([...projectArgs, ...fileArgs, "stop", ...services], logger, rootDir, signal);
|
|
1517
|
+
} else {
|
|
1518
|
+
logger.log("Stopping environment...");
|
|
1519
|
+
await runDockerCompose([...projectArgs, ...fileArgs, "down"], logger, rootDir, signal);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
async function destroyEnvironment(layout, manifest, logger, signal, includeCore = true, fullManifest) {
|
|
1523
|
+
const { rootDir, localDir } = layout;
|
|
1524
|
+
const platformName = manifest.product.name;
|
|
1525
|
+
const envFile = join22(localDir, ".env");
|
|
1526
|
+
const isSelective = fullManifest !== void 0;
|
|
1527
|
+
const fileArgs = await buildFullComposeArgs(layout, isSelective ? fullManifest : manifest, logger);
|
|
1528
|
+
const projectArgs = ["-p", `${platformName}-platform`, "--project-directory", localDir, "--env-file", envFile];
|
|
1529
|
+
if (isSelective) {
|
|
1530
|
+
const selectedFiles = await buildSelectedComposeFiles(layout, manifest, includeCore);
|
|
1531
|
+
const allFiles = extractFilePaths(fileArgs);
|
|
1532
|
+
const services = await getServicesFromComposeFiles(selectedFiles, allFiles, rootDir);
|
|
1533
|
+
if (services.length === 0) {
|
|
1534
|
+
logger.log("No services found for selected targets. Nothing to destroy.");
|
|
1535
|
+
return;
|
|
1536
|
+
}
|
|
1537
|
+
logger.log(`Destroying: ${services.join(", ")}...`);
|
|
1538
|
+
await runDockerCompose([...projectArgs, ...fileArgs, "stop", ...services], logger, rootDir, signal);
|
|
1539
|
+
if (!signal?.aborted) {
|
|
1540
|
+
await runDockerCompose([...projectArgs, ...fileArgs, "rm", "-f", ...services], logger, rootDir, signal);
|
|
1541
|
+
}
|
|
1542
|
+
} else {
|
|
1543
|
+
logger.log("Destroying environment...");
|
|
1544
|
+
await runDockerCompose([...projectArgs, ...fileArgs, "down", "-v", "--rmi", "all"], logger, rootDir, signal);
|
|
1545
|
+
}
|
|
1235
1546
|
}
|
|
1236
1547
|
|
|
1237
|
-
// src/
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
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:");
|
|
1244
2327
|
const platformName = await ctx.prompt("Platform name:");
|
|
1245
2328
|
const platformDisplayName = await ctx.prompt("Platform display name:");
|
|
1246
|
-
|
|
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
|
+
);
|
|
1247
2355
|
}
|
|
1248
2356
|
|
|
1249
2357
|
// src/services/configure-idp.service.ts
|
|
@@ -1258,15 +2366,12 @@ async function configureIdpUiController(ctx) {
|
|
|
1258
2366
|
return;
|
|
1259
2367
|
}
|
|
1260
2368
|
const providers = getAllProviders();
|
|
1261
|
-
const
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
}
|
|
1268
|
-
const provider = providers[index];
|
|
1269
|
-
const name = provider.type;
|
|
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);
|
|
2374
|
+
const name = await ctx.prompt("Provider name:");
|
|
1270
2375
|
const issuer = await ctx.prompt("Issuer URL:");
|
|
1271
2376
|
const clientId = await ctx.prompt("Client ID:");
|
|
1272
2377
|
const clientSecret = await ctx.prompt("Client Secret:");
|
|
@@ -1291,19 +2396,38 @@ async function createServiceModuleUiController(ctx) {
|
|
|
1291
2396
|
ctx.log("Error: Cannot create a service module \u2014 no platform initialized in this directory.");
|
|
1292
2397
|
return;
|
|
1293
2398
|
}
|
|
1294
|
-
const
|
|
1295
|
-
if (
|
|
1296
|
-
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.");
|
|
1297
2402
|
return;
|
|
1298
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
|
+
);
|
|
1299
2420
|
const serviceName = await ctx.prompt("Service name:");
|
|
1300
2421
|
if (!/^[a-z0-9-]+$/.test(serviceName)) {
|
|
1301
2422
|
ctx.log(`Error: Service name "${serviceName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
|
|
1302
2423
|
return;
|
|
1303
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;
|
|
1304
2428
|
const serviceDisplayName = await ctx.prompt("Service display name:");
|
|
1305
2429
|
await createServiceModuleService(
|
|
1306
|
-
{ applicationName, serviceName, serviceDisplayName },
|
|
2430
|
+
{ applicationName, serviceName, serviceDisplayName, serviceNameSuffix },
|
|
1307
2431
|
ctx
|
|
1308
2432
|
);
|
|
1309
2433
|
}
|
|
@@ -1319,40 +2443,355 @@ async function createUiModuleUiController(ctx) {
|
|
|
1319
2443
|
ctx.log("Error: Cannot create a UI module \u2014 no platform initialized in this directory.");
|
|
1320
2444
|
return;
|
|
1321
2445
|
}
|
|
1322
|
-
const
|
|
1323
|
-
if (
|
|
1324
|
-
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.");
|
|
1325
2449
|
return;
|
|
1326
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;
|
|
1327
2470
|
const applicationDisplayName = await ctx.prompt("Application display name:");
|
|
1328
2471
|
await createUiModuleService(
|
|
1329
|
-
{ applicationName, applicationDisplayName },
|
|
2472
|
+
{ applicationName, applicationDisplayName, uiModuleSuffix },
|
|
1330
2473
|
ctx
|
|
1331
2474
|
);
|
|
1332
2475
|
}
|
|
1333
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
|
+
|
|
1334
2761
|
// src/controllers/ui/registry.ts
|
|
1335
2762
|
var uiControllers = /* @__PURE__ */ new Map([
|
|
1336
2763
|
[CREATE_APPLICATION_COMMAND_NAME, createApplicationUiController],
|
|
1337
2764
|
[INIT_COMMAND_NAME, initUiController],
|
|
1338
2765
|
[CONFIGURE_IDP_COMMAND_NAME, configureIdpUiController],
|
|
1339
2766
|
[CREATE_SERVICE_MODULE_COMMAND_NAME, createServiceModuleUiController],
|
|
1340
|
-
[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]
|
|
1341
2775
|
]);
|
|
1342
2776
|
|
|
1343
2777
|
// src/hooks/use-command-runner.ts
|
|
1344
|
-
function useCommandRunner({ appendStaticItem, setState }) {
|
|
2778
|
+
function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
|
|
1345
2779
|
const outputCountRef = useRef(0);
|
|
1346
2780
|
const abortControllerRef = useRef(null);
|
|
1347
2781
|
const promptResolveRef = useRef(null);
|
|
1348
2782
|
const [promptMessage, setPromptMessage] = useState2("");
|
|
1349
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());
|
|
1350
2788
|
const abortExecution = useCallback(() => {
|
|
1351
2789
|
abortControllerRef.current?.abort();
|
|
1352
2790
|
abortControllerRef.current = null;
|
|
1353
2791
|
promptResolveRef.current = null;
|
|
2792
|
+
setPromptMode({ kind: "text" });
|
|
1354
2793
|
}, []);
|
|
1355
|
-
const
|
|
2794
|
+
const runCommand2 = useCallback(
|
|
1356
2795
|
(cmd) => {
|
|
1357
2796
|
const controller = new AbortController();
|
|
1358
2797
|
abortControllerRef.current = controller;
|
|
@@ -1365,15 +2804,73 @@ function useCommandRunner({ appendStaticItem, setState }) {
|
|
|
1365
2804
|
appendStaticItem({ kind: "output", id, line: message });
|
|
1366
2805
|
}
|
|
1367
2806
|
},
|
|
1368
|
-
prompt(message) {
|
|
1369
|
-
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) => {
|
|
1370
2866
|
if (controller.signal.aborted) {
|
|
1371
2867
|
reject(new DOMException("Aborted", "AbortError"));
|
|
1372
2868
|
return;
|
|
1373
2869
|
}
|
|
1374
2870
|
setPromptMessage(message);
|
|
1375
|
-
|
|
1376
|
-
|
|
2871
|
+
setPromptMode({ kind: "confirm" });
|
|
2872
|
+
setConfirmValue(defaultValue ?? true);
|
|
2873
|
+
promptResolveRef.current = (value) => resolve6(value === "true");
|
|
1377
2874
|
setState(APP_STATE.PROMPTING);
|
|
1378
2875
|
controller.signal.addEventListener(
|
|
1379
2876
|
"abort",
|
|
@@ -1393,37 +2890,47 @@ function useCommandRunner({ appendStaticItem, setState }) {
|
|
|
1393
2890
|
uiController(ctx).then(() => {
|
|
1394
2891
|
if (!controller.signal.aborted) {
|
|
1395
2892
|
setState(APP_STATE.IDLE);
|
|
2893
|
+
onCommandComplete?.();
|
|
1396
2894
|
}
|
|
1397
2895
|
}).catch((err) => {
|
|
1398
2896
|
if (controller.signal.aborted) return;
|
|
1399
2897
|
const id = `output-${outputCountRef.current++}`;
|
|
1400
2898
|
appendStaticItem({ kind: "output", id, line: `Error: ${formatError(err)}` });
|
|
1401
2899
|
setState(APP_STATE.IDLE);
|
|
2900
|
+
onCommandComplete?.();
|
|
1402
2901
|
});
|
|
1403
2902
|
},
|
|
1404
|
-
[appendStaticItem, setState]
|
|
2903
|
+
[appendStaticItem, setState, onCommandComplete]
|
|
1405
2904
|
);
|
|
1406
2905
|
const handlePromptSubmit = useCallback(
|
|
1407
2906
|
(value) => {
|
|
1408
|
-
const
|
|
2907
|
+
const resolve6 = promptResolveRef.current;
|
|
1409
2908
|
promptResolveRef.current = null;
|
|
2909
|
+
setPromptMode({ kind: "text" });
|
|
1410
2910
|
setState(APP_STATE.EXECUTING);
|
|
1411
|
-
|
|
2911
|
+
resolve6?.(value);
|
|
1412
2912
|
},
|
|
1413
2913
|
[setState]
|
|
1414
2914
|
);
|
|
1415
2915
|
return {
|
|
1416
|
-
runCommand,
|
|
2916
|
+
runCommand: runCommand2,
|
|
1417
2917
|
handlePromptSubmit,
|
|
1418
2918
|
abortExecution,
|
|
1419
2919
|
promptMessage,
|
|
1420
2920
|
promptValue,
|
|
1421
|
-
setPromptValue
|
|
2921
|
+
setPromptValue,
|
|
2922
|
+
promptMode,
|
|
2923
|
+
selectIndex,
|
|
2924
|
+
setSelectIndex,
|
|
2925
|
+
confirmValue,
|
|
2926
|
+
setConfirmValue,
|
|
2927
|
+
multiselectChecked,
|
|
2928
|
+
setMultiselectChecked
|
|
1422
2929
|
};
|
|
1423
2930
|
}
|
|
1424
2931
|
|
|
1425
2932
|
// src/app.tsx
|
|
1426
|
-
import { jsx as
|
|
2933
|
+
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1427
2934
|
var require2 = createRequire(import.meta.url);
|
|
1428
2935
|
var { version } = require2("../package.json");
|
|
1429
2936
|
function App() {
|
|
@@ -1432,12 +2939,35 @@ function App() {
|
|
|
1432
2939
|
const [inputValue, setInputValue] = useState3("");
|
|
1433
2940
|
const [selectedIndex, setSelectedIndex] = useState3(0);
|
|
1434
2941
|
const [staticItems, setStaticItems] = useState3([{ kind: "banner", id: "banner" }]);
|
|
2942
|
+
const [platformInitialized, setPlatformInitialized] = useState3(false);
|
|
2943
|
+
useEffect2(() => {
|
|
2944
|
+
isPlatformInitialized().then(setPlatformInitialized).catch(() => {
|
|
2945
|
+
});
|
|
2946
|
+
}, []);
|
|
1435
2947
|
const appendStaticItem = useCallback2((item) => {
|
|
1436
2948
|
setStaticItems((prev) => [...prev, item]);
|
|
1437
2949
|
}, []);
|
|
1438
|
-
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 });
|
|
1439
2969
|
const query = inputValue.startsWith("/") ? inputValue.slice(1) : "";
|
|
1440
|
-
const filteredCommands = registry.
|
|
2970
|
+
const filteredCommands = registry.searchVisible(query, { platformInitialized });
|
|
1441
2971
|
useInput(
|
|
1442
2972
|
(input, key) => {
|
|
1443
2973
|
if (key.ctrl && input === "c") {
|
|
@@ -1449,6 +2979,73 @@ function App() {
|
|
|
1449
2979
|
setState(APP_STATE.IDLE);
|
|
1450
2980
|
return;
|
|
1451
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
|
+
}
|
|
1452
3049
|
if (state === APP_STATE.PALETTE) {
|
|
1453
3050
|
if (key.escape) {
|
|
1454
3051
|
setState(APP_STATE.IDLE);
|
|
@@ -1467,7 +3064,7 @@ function App() {
|
|
|
1467
3064
|
const cmd = filteredCommands[selectedIndex];
|
|
1468
3065
|
if (cmd) {
|
|
1469
3066
|
setInputValue("");
|
|
1470
|
-
|
|
3067
|
+
runCommand2(cmd);
|
|
1471
3068
|
}
|
|
1472
3069
|
return;
|
|
1473
3070
|
}
|
|
@@ -1503,24 +3100,34 @@ function App() {
|
|
|
1503
3100
|
if (!value.startsWith("/")) return;
|
|
1504
3101
|
const name = value.slice(1).trim();
|
|
1505
3102
|
const cmd = registry.get(name);
|
|
1506
|
-
if (cmd)
|
|
3103
|
+
if (cmd) runCommand2(cmd);
|
|
1507
3104
|
},
|
|
1508
|
-
[
|
|
3105
|
+
[runCommand2]
|
|
1509
3106
|
);
|
|
1510
3107
|
function renderActiveArea() {
|
|
1511
3108
|
switch (state) {
|
|
1512
3109
|
case APP_STATE.EXECUTING:
|
|
1513
|
-
return /* @__PURE__ */
|
|
1514
|
-
/* @__PURE__ */
|
|
1515
|
-
/* @__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)" })
|
|
1516
3113
|
] });
|
|
1517
3114
|
case APP_STATE.PROMPTING:
|
|
1518
|
-
return /* @__PURE__ */
|
|
1519
|
-
/* @__PURE__ */
|
|
1520
|
-
/* @__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 })
|
|
1521
3128
|
] });
|
|
1522
3129
|
default:
|
|
1523
|
-
return /* @__PURE__ */
|
|
3130
|
+
return /* @__PURE__ */ jsx10(
|
|
1524
3131
|
Prompt,
|
|
1525
3132
|
{
|
|
1526
3133
|
value: inputValue,
|
|
@@ -1533,10 +3140,10 @@ function App() {
|
|
|
1533
3140
|
);
|
|
1534
3141
|
}
|
|
1535
3142
|
}
|
|
1536
|
-
return /* @__PURE__ */
|
|
1537
|
-
/* @__PURE__ */
|
|
3143
|
+
return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", children: [
|
|
3144
|
+
/* @__PURE__ */ jsx10(ScrollbackHistory, { items: staticItems, version }),
|
|
1538
3145
|
renderActiveArea(),
|
|
1539
|
-
state === APP_STATE.PALETTE && /* @__PURE__ */
|
|
3146
|
+
state === APP_STATE.PALETTE && /* @__PURE__ */ jsx10(CommandPalette, { commands: filteredCommands, selectedIndex })
|
|
1540
3147
|
] });
|
|
1541
3148
|
}
|
|
1542
3149
|
|
|
@@ -1561,21 +3168,8 @@ async function createApplicationCliController(args2) {
|
|
|
1561
3168
|
console.error(`Error: Application name "${applicationName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
|
|
1562
3169
|
process.exit(1);
|
|
1563
3170
|
}
|
|
1564
|
-
let organizationName;
|
|
1565
|
-
let platformName;
|
|
1566
|
-
try {
|
|
1567
|
-
const manifest = await readPlatformManifest();
|
|
1568
|
-
organizationName = manifest.organizationName;
|
|
1569
|
-
platformName = manifest.platformName;
|
|
1570
|
-
} catch (err) {
|
|
1571
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1572
|
-
console.error(`Error: Could not read .platform.json \u2014 ${message}`);
|
|
1573
|
-
process.exit(1);
|
|
1574
|
-
}
|
|
1575
3171
|
await createApplicationService(
|
|
1576
3172
|
{
|
|
1577
|
-
organizationName,
|
|
1578
|
-
platformName,
|
|
1579
3173
|
applicationName,
|
|
1580
3174
|
applicationDisplayName,
|
|
1581
3175
|
applicationDescription,
|
|
@@ -1592,13 +3186,13 @@ async function initCliController(args2) {
|
|
|
1592
3186
|
console.error("Error: Cannot initialize a new platform \u2014 a platform is already initialized in this directory.");
|
|
1593
3187
|
process.exit(1);
|
|
1594
3188
|
}
|
|
1595
|
-
const { organizationName, platformName, platformDisplayName } = args2;
|
|
3189
|
+
const { organizationName, platformName, platformDisplayName, bootstrapServiceSuffix, customizationUiSuffix } = args2;
|
|
1596
3190
|
if (!organizationName || !platformName || !platformDisplayName) {
|
|
1597
3191
|
console.error("Error: organizationName, platformName, and platformDisplayName are required.");
|
|
1598
3192
|
process.exit(1);
|
|
1599
3193
|
}
|
|
1600
3194
|
await initService(
|
|
1601
|
-
{ organizationName, platformName, platformDisplayName },
|
|
3195
|
+
{ organizationName, platformName, platformDisplayName, bootstrapServiceSuffix, customizationUiSuffix },
|
|
1602
3196
|
{ log: console.log }
|
|
1603
3197
|
);
|
|
1604
3198
|
}
|
|
@@ -1610,9 +3204,9 @@ async function configureIdpCliController(args2) {
|
|
|
1610
3204
|
console.error("Error: Cannot configure an IDP \u2014 no platform initialized in this directory.");
|
|
1611
3205
|
process.exit(1);
|
|
1612
3206
|
}
|
|
1613
|
-
const { providerType, issuer, clientId, clientSecret } = args2;
|
|
1614
|
-
if (!providerType || !issuer || !clientId || !clientSecret) {
|
|
1615
|
-
logger.log("Error: Missing required arguments: providerType, issuer, clientId, clientSecret");
|
|
3207
|
+
const { providerType, name, issuer, clientId, clientSecret } = args2;
|
|
3208
|
+
if (!providerType || !name || !issuer || !clientId || !clientSecret) {
|
|
3209
|
+
logger.log("Error: Missing required arguments: providerType, name, issuer, clientId, clientSecret");
|
|
1616
3210
|
process.exit(1);
|
|
1617
3211
|
}
|
|
1618
3212
|
const provider = idpProviderRegistry.get(providerType);
|
|
@@ -1629,83 +3223,9 @@ async function configureIdpCliController(args2) {
|
|
|
1629
3223
|
}
|
|
1630
3224
|
extras[field.key] = value;
|
|
1631
3225
|
}
|
|
1632
|
-
const name = providerType;
|
|
1633
3226
|
await configureIdpService({ providerType, name, issuer, clientId, clientSecret, extras }, logger);
|
|
1634
3227
|
}
|
|
1635
3228
|
|
|
1636
|
-
// src/utils/run-npm-script.ts
|
|
1637
|
-
import { spawn } from "child_process";
|
|
1638
|
-
import { access as access4 } from "fs/promises";
|
|
1639
|
-
import { join as join21 } from "path";
|
|
1640
|
-
async function runNpmScript(scriptName, logger, signal) {
|
|
1641
|
-
let localDir;
|
|
1642
|
-
try {
|
|
1643
|
-
const { platformName } = await readPlatformManifest();
|
|
1644
|
-
localDir = join21(process.cwd(), `${platformName}-local`);
|
|
1645
|
-
await access4(localDir);
|
|
1646
|
-
} catch {
|
|
1647
|
-
logger.log(`Error: No initialized platform found in this directory. Run "platform init" first.`);
|
|
1648
|
-
return;
|
|
1649
|
-
}
|
|
1650
|
-
return new Promise((resolve) => {
|
|
1651
|
-
const child = spawn("npm", ["run", scriptName], {
|
|
1652
|
-
cwd: localDir,
|
|
1653
|
-
shell: true,
|
|
1654
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
1655
|
-
});
|
|
1656
|
-
const onAbort = () => {
|
|
1657
|
-
child.kill("SIGTERM");
|
|
1658
|
-
};
|
|
1659
|
-
if (signal) {
|
|
1660
|
-
if (signal.aborted) {
|
|
1661
|
-
child.kill("SIGTERM");
|
|
1662
|
-
resolve();
|
|
1663
|
-
return;
|
|
1664
|
-
}
|
|
1665
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
1666
|
-
}
|
|
1667
|
-
child.stdout.on("data", (data) => {
|
|
1668
|
-
for (const line of data.toString().split("\n")) {
|
|
1669
|
-
if (line.trim()) logger.log(line);
|
|
1670
|
-
}
|
|
1671
|
-
});
|
|
1672
|
-
child.stderr.on("data", (data) => {
|
|
1673
|
-
for (const line of data.toString().split("\n")) {
|
|
1674
|
-
if (line.trim()) logger.log(line);
|
|
1675
|
-
}
|
|
1676
|
-
});
|
|
1677
|
-
child.on("close", (code, sig) => {
|
|
1678
|
-
signal?.removeEventListener("abort", onAbort);
|
|
1679
|
-
if (sig === "SIGTERM" || signal?.aborted) {
|
|
1680
|
-
logger.log(`Command cancelled.`);
|
|
1681
|
-
} else if (code !== 0) {
|
|
1682
|
-
logger.log(`Command "npm run ${scriptName}" exited with code ${code}.`);
|
|
1683
|
-
}
|
|
1684
|
-
resolve();
|
|
1685
|
-
});
|
|
1686
|
-
child.on("error", (err) => {
|
|
1687
|
-
signal?.removeEventListener("abort", onAbort);
|
|
1688
|
-
logger.log(`Failed to run "npm run ${scriptName}": ${err.message}`);
|
|
1689
|
-
resolve();
|
|
1690
|
-
});
|
|
1691
|
-
});
|
|
1692
|
-
}
|
|
1693
|
-
|
|
1694
|
-
// src/commands/local-scripts/local-script.command.ts
|
|
1695
|
-
var INSTALL_COMMAND_NAME = "install";
|
|
1696
|
-
var BUILD_COMMAND_NAME = "build";
|
|
1697
|
-
var START_COMMAND_NAME = "start";
|
|
1698
|
-
var STOP_COMMAND_NAME = "stop";
|
|
1699
|
-
var DESTROY_COMMAND_NAME = "destroy";
|
|
1700
|
-
async function runLocalScript(scriptName, logger, signal) {
|
|
1701
|
-
await runNpmScript(scriptName, logger, signal);
|
|
1702
|
-
}
|
|
1703
|
-
|
|
1704
|
-
// src/services/local-script.service.ts
|
|
1705
|
-
async function localScriptService(scriptName, logger, signal) {
|
|
1706
|
-
await runLocalScript(scriptName, logger, signal);
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1709
3229
|
// src/utils/cli-spinner.ts
|
|
1710
3230
|
var FRAMES2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1711
3231
|
var INTERVAL_MS2 = 80;
|
|
@@ -1741,10 +3261,11 @@ function createCliSpinner(label) {
|
|
|
1741
3261
|
|
|
1742
3262
|
// src/controllers/cli/local-script.cli-controller.ts
|
|
1743
3263
|
function createLocalScriptCliController(scriptName) {
|
|
1744
|
-
return async (
|
|
3264
|
+
return async (args2) => {
|
|
1745
3265
|
const spinner = createCliSpinner(`Running ${scriptName}\u2026`);
|
|
1746
3266
|
spinner.start();
|
|
1747
|
-
|
|
3267
|
+
const appNames = args2._positional ? JSON.parse(args2._positional) : void 0;
|
|
3268
|
+
await localScriptService(scriptName, { log: (msg) => spinner.log(msg) }, void 0, appNames);
|
|
1748
3269
|
spinner.stop();
|
|
1749
3270
|
};
|
|
1750
3271
|
}
|
|
@@ -1795,6 +3316,88 @@ async function createUiModuleCliController(args2) {
|
|
|
1795
3316
|
);
|
|
1796
3317
|
}
|
|
1797
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
|
+
|
|
1798
3401
|
// src/controllers/cli/registry.ts
|
|
1799
3402
|
var cliControllers = /* @__PURE__ */ new Map([
|
|
1800
3403
|
[CREATE_APPLICATION_COMMAND_NAME, createApplicationCliController],
|
|
@@ -1802,32 +3405,40 @@ var cliControllers = /* @__PURE__ */ new Map([
|
|
|
1802
3405
|
[CONFIGURE_IDP_COMMAND_NAME, configureIdpCliController],
|
|
1803
3406
|
[CREATE_SERVICE_MODULE_COMMAND_NAME, createServiceModuleCliController],
|
|
1804
3407
|
[CREATE_UI_MODULE_COMMAND_NAME, createUiModuleCliController],
|
|
3408
|
+
[STATUS_COMMAND_NAME, statusCliController],
|
|
1805
3409
|
[INSTALL_COMMAND_NAME, createLocalScriptCliController(INSTALL_COMMAND_NAME)],
|
|
1806
3410
|
[BUILD_COMMAND_NAME, createLocalScriptCliController(BUILD_COMMAND_NAME)],
|
|
1807
3411
|
[START_COMMAND_NAME, createLocalScriptCliController(START_COMMAND_NAME)],
|
|
1808
3412
|
[STOP_COMMAND_NAME, createLocalScriptCliController(STOP_COMMAND_NAME)],
|
|
1809
|
-
[DESTROY_COMMAND_NAME, createLocalScriptCliController(DESTROY_COMMAND_NAME)]
|
|
3413
|
+
[DESTROY_COMMAND_NAME, createLocalScriptCliController(DESTROY_COMMAND_NAME)],
|
|
3414
|
+
[MANAGE_PLATFORM_ADMINS_COMMAND_NAME, managePlatformAdminsCliController]
|
|
1810
3415
|
]);
|
|
1811
3416
|
|
|
1812
3417
|
// src/utils/parse-args.ts
|
|
1813
3418
|
function parseKeyValueArgs(args2) {
|
|
1814
3419
|
const result = {};
|
|
3420
|
+
const positional = [];
|
|
1815
3421
|
for (const arg of args2) {
|
|
1816
3422
|
const eqIndex = arg.indexOf("=");
|
|
1817
3423
|
if (eqIndex > 0) {
|
|
1818
3424
|
const key = arg.slice(0, eqIndex);
|
|
1819
3425
|
const value = arg.slice(eqIndex + 1);
|
|
1820
3426
|
result[key] = value;
|
|
3427
|
+
} else {
|
|
3428
|
+
positional.push(arg);
|
|
1821
3429
|
}
|
|
1822
3430
|
}
|
|
3431
|
+
if (positional.length > 0) {
|
|
3432
|
+
result._positional = JSON.stringify(positional);
|
|
3433
|
+
}
|
|
1823
3434
|
return result;
|
|
1824
3435
|
}
|
|
1825
3436
|
|
|
1826
3437
|
// src/index.tsx
|
|
1827
|
-
import { jsx as
|
|
3438
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
1828
3439
|
var args = process.argv.slice(2);
|
|
1829
3440
|
if (args.length === 0) {
|
|
1830
|
-
render(/* @__PURE__ */
|
|
3441
|
+
render(/* @__PURE__ */ jsx11(App, {}));
|
|
1831
3442
|
} else {
|
|
1832
3443
|
const commandName = args[0];
|
|
1833
3444
|
const params = parseKeyValueArgs(args.slice(1));
|