@bluealba/platform-cli 0.2.1 → 0.3.1
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 +496 -154
- package/package.json +1 -1
- package/templates/customization-ui-module-template/Dockerfile +4 -4
- package/templates/customization-ui-module-template/Dockerfile.development +1 -1
- package/templates/customization-ui-module-template/package.json +3 -3
- package/templates/customization-ui-module-template/src/root.component.tsx +2 -11
- package/templates/customization-ui-module-template/tsconfig.json +1 -1
- package/templates/customization-ui-module-template/webpack.config.js +2 -2
- package/templates/platform-init-template/{local → {{platformName}}-local}/.env.example +1 -1
- package/templates/platform-init-template/{{platformName}}-local/docker-compose.yml +3 -0
- package/templates/platform-init-template/{local → {{platformName}}-local}/package.json +3 -3
- package/templates/platform-init-template/{local → {{platformName}}-local}/scripts/build.sh +1 -1
- package/templates/platform-init-template/{local → {{platformName}}-local}/scripts/install.sh +1 -1
- package/templates/platform-init-template/{local/core-docker-compose.yml → {{platformName}}-local/{{platformName}}-core-docker-compose.yml} +5 -5
- package/templates/react-ui-module-template/Dockerfile +3 -3
- package/templates/react-ui-module-template/Dockerfile.development +1 -1
- package/templates/react-ui-module-template/package.json +1 -1
- package/templates/customization-ui-module-template/src/components/ExpandedNavbarLogo.tsx +0 -62
- package/templates/customization-ui-module-template/src/components/ExtensionPoints/index.tsx +0 -28
- package/templates/customization-ui-module-template/src/components/Logo.tsx +0 -55
- package/templates/customization-ui-module-template/src/components/SplashLogo.tsx +0 -52
- package/templates/customization-ui-module-template/src/hooks/useDynamicStyleSheet.ts +0 -18
- package/templates/platform-init-template/local/docker-compose.yml +0 -3
- /package/templates/customization-ui-module-template/src/{platform-customization-ui.tsx → {{platformName}}-customization-ui.tsx} +0 -0
- /package/templates/platform-init-template/{core → {{platformName}}-core}/.changeset/config.json +0 -0
- /package/templates/platform-init-template/{core → {{platformName}}-core}/.nvmrc +0 -0
- /package/templates/platform-init-template/{core → {{platformName}}-core}/.syncpackrc +0 -0
- /package/templates/platform-init-template/{core → {{platformName}}-core}/package.json +0 -0
- /package/templates/platform-init-template/{core → {{platformName}}-core}/packages-versions.json +0 -0
- /package/templates/platform-init-template/{core → {{platformName}}-core}/scripts/preinstall.mjs +0 -0
- /package/templates/platform-init-template/{core → {{platformName}}-core}/services/.gitkeep +0 -0
- /package/templates/platform-init-template/{core → {{platformName}}-core}/turbo.json +0 -0
- /package/templates/platform-init-template/{core → {{platformName}}-core}/ui/.gitkeep +0 -0
- /package/templates/platform-init-template/{local → {{platformName}}-local}/environment/pae-nestjs-gateway-service.env +0 -0
- /package/templates/platform-init-template/{local → {{platformName}}-local}/nginx.conf +0 -0
- /package/templates/platform-init-template/{local → {{platformName}}-local}/platform-docker-compose.yml +0 -0
- /package/templates/platform-init-template/{local → {{platformName}}-local}/ssl/cert.pem +0 -0
- /package/templates/platform-init-template/{local → {{platformName}}-local}/ssl/key.pem +0 -0
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { render } from "ink";
|
|
|
6
6
|
// src/app.tsx
|
|
7
7
|
import { createRequire } from "module";
|
|
8
8
|
import { useState as useState3, useCallback as useCallback2 } from "react";
|
|
9
|
-
import { Box as
|
|
9
|
+
import { Box as Box5, Text as Text7, useApp, useInput } from "ink";
|
|
10
10
|
|
|
11
11
|
// src/components/prompt.tsx
|
|
12
12
|
import { Box, Text } from "ink";
|
|
@@ -54,54 +54,71 @@ var CommandPalette = React.memo(function CommandPalette2({ commands, selectedInd
|
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
// src/components/scrollback-history.tsx
|
|
57
|
-
import { Static, Text as
|
|
57
|
+
import { Static, Text as Text5 } from "ink";
|
|
58
58
|
|
|
59
59
|
// src/components/welcome-banner.tsx
|
|
60
|
+
import React3 from "react";
|
|
61
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
62
|
+
|
|
63
|
+
// src/components/working-directory.tsx
|
|
60
64
|
import React2 from "react";
|
|
65
|
+
import os from "os";
|
|
61
66
|
import { Box as Box3, Text as Text3 } from "ink";
|
|
62
67
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
68
|
+
var WorkingDirectory = React2.memo(function WorkingDirectory2() {
|
|
69
|
+
const cwd7 = process.cwd();
|
|
70
|
+
const home = os.homedir();
|
|
71
|
+
const displayPath = cwd7.startsWith(home) ? "~" + cwd7.slice(home.length) : cwd7;
|
|
72
|
+
return /* @__PURE__ */ jsxs3(Box3, { paddingLeft: 2, children: [
|
|
73
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, dimColor: true, children: "Working Dir: " }),
|
|
74
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: displayPath })
|
|
75
|
+
] });
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// src/components/welcome-banner.tsx
|
|
79
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
63
80
|
var ASCII_ART = [
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
" / | \\ "
|
|
81
|
+
"|---. .---|",
|
|
82
|
+
"| \\/ |",
|
|
83
|
+
"| /\\ |",
|
|
84
|
+
"|---' '---|"
|
|
69
85
|
];
|
|
70
|
-
var WelcomeBanner =
|
|
71
|
-
return /* @__PURE__ */
|
|
72
|
-
/* @__PURE__ */
|
|
86
|
+
var WelcomeBanner = React3.memo(function WelcomeBanner2({ version: version2 }) {
|
|
87
|
+
return /* @__PURE__ */ jsxs4(Box4, { borderStyle: "round", flexDirection: "column", paddingX: 1, paddingY: 1, children: [
|
|
88
|
+
/* @__PURE__ */ jsxs4(Text4, { bold: true, color: "cyan", children: [
|
|
73
89
|
"Blue Alba Platform CLI v",
|
|
74
90
|
version2
|
|
75
91
|
] }),
|
|
76
|
-
/* @__PURE__ */
|
|
77
|
-
/* @__PURE__ */
|
|
78
|
-
/* @__PURE__ */
|
|
79
|
-
/* @__PURE__ */
|
|
80
|
-
/* @__PURE__ */
|
|
92
|
+
/* @__PURE__ */ jsxs4(Box4, { marginTop: 1, children: [
|
|
93
|
+
/* @__PURE__ */ jsx4(Box4, { flexDirection: "column", marginRight: 2, children: ASCII_ART.map((line, i) => /* @__PURE__ */ jsx4(Text4, { color: "yellow", children: line }, i)) }),
|
|
94
|
+
/* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", justifyContent: "center", marginRight: 2, children: [
|
|
95
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, children: "Welcome to the" }),
|
|
96
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, children: "Blue Alba Platform!" })
|
|
81
97
|
] }),
|
|
82
|
-
/* @__PURE__ */
|
|
83
|
-
/* @__PURE__ */
|
|
84
|
-
/* @__PURE__ */
|
|
85
|
-
/* @__PURE__ */
|
|
98
|
+
/* @__PURE__ */ jsx4(Box4, { flexDirection: "column", children: ASCII_ART.map((_, i) => /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2502" }, i)) }),
|
|
99
|
+
/* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginLeft: 2, children: [
|
|
100
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: "Tips for getting started" }),
|
|
101
|
+
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
86
102
|
"Run ",
|
|
87
|
-
/* @__PURE__ */
|
|
103
|
+
/* @__PURE__ */ jsx4(Text4, { color: "green", children: "/init" }),
|
|
88
104
|
" to start building a platform"
|
|
89
105
|
] })
|
|
90
106
|
] })
|
|
91
|
-
] })
|
|
107
|
+
] }),
|
|
108
|
+
/* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(WorkingDirectory, {}) })
|
|
92
109
|
] });
|
|
93
110
|
});
|
|
94
111
|
|
|
95
112
|
// src/components/scrollback-history.tsx
|
|
96
|
-
import { jsx as
|
|
113
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
97
114
|
function ScrollbackHistory({ items, version: version2 }) {
|
|
98
|
-
return /* @__PURE__ */
|
|
115
|
+
return /* @__PURE__ */ jsx5(Static, { items, children: (item) => item.kind === "banner" ? /* @__PURE__ */ jsx5(WelcomeBanner, { version: version2 }, item.id) : /* @__PURE__ */ jsx5(Text5, { children: item.line }, item.id) });
|
|
99
116
|
}
|
|
100
117
|
|
|
101
118
|
// src/components/spinner.tsx
|
|
102
119
|
import { useState, useEffect } from "react";
|
|
103
|
-
import { Text as
|
|
104
|
-
import { jsx as
|
|
120
|
+
import { Text as Text6 } from "ink";
|
|
121
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
105
122
|
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
106
123
|
var INTERVAL_MS = 80;
|
|
107
124
|
function Spinner({ label }) {
|
|
@@ -112,12 +129,12 @@ function Spinner({ label }) {
|
|
|
112
129
|
}, INTERVAL_MS);
|
|
113
130
|
return () => clearInterval(id);
|
|
114
131
|
}, []);
|
|
115
|
-
return /* @__PURE__ */
|
|
116
|
-
/* @__PURE__ */
|
|
132
|
+
return /* @__PURE__ */ jsxs5(Text6, { children: [
|
|
133
|
+
/* @__PURE__ */ jsxs5(Text6, { color: "cyan", children: [
|
|
117
134
|
FRAMES[frame],
|
|
118
135
|
" "
|
|
119
136
|
] }),
|
|
120
|
-
label && /* @__PURE__ */
|
|
137
|
+
label && /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: label })
|
|
121
138
|
] });
|
|
122
139
|
}
|
|
123
140
|
|
|
@@ -270,7 +287,7 @@ var reactUiModuleTemplateDir = join5(
|
|
|
270
287
|
"react-ui-module-template"
|
|
271
288
|
);
|
|
272
289
|
async function scaffoldUiModule(uiDir, organizationName, platformName, applicationName, applicationDisplayName, uiBaseDir, logger) {
|
|
273
|
-
logger.log(`Creating UI module "${applicationName}-ui"...`);
|
|
290
|
+
logger.log(`Creating UI module "${platformName}-${applicationName}-ui"...`);
|
|
274
291
|
await applyTemplate(
|
|
275
292
|
{
|
|
276
293
|
templateDir: reactUiModuleTemplateDir,
|
|
@@ -291,8 +308,8 @@ var nestjsServiceModuleTemplateDir = join6(
|
|
|
291
308
|
"templates",
|
|
292
309
|
"nestjs-service-module-template"
|
|
293
310
|
);
|
|
294
|
-
async function scaffoldNestjsService(serviceDir, organizationName, applicationName, applicationDisplayName, serviceBaseDir, logger) {
|
|
295
|
-
const serviceName = `${applicationName}-service`;
|
|
311
|
+
async function scaffoldNestjsService(serviceDir, organizationName, platformName, applicationName, applicationDisplayName, serviceBaseDir, logger) {
|
|
312
|
+
const serviceName = `${platformName}-${applicationName}-service`;
|
|
296
313
|
const serviceDisplayName = `${applicationDisplayName} Service`;
|
|
297
314
|
logger.log(`Creating NestJS service "${serviceName}"...`);
|
|
298
315
|
await applyTemplate(
|
|
@@ -323,33 +340,33 @@ async function addModuleEntry(bootstrapServiceDir, applicationName, entry, logge
|
|
|
323
340
|
}
|
|
324
341
|
|
|
325
342
|
// src/commands/create-application/module-entry-builders.ts
|
|
326
|
-
function buildServiceModuleEntry(organizationName, applicationName, applicationDisplayName) {
|
|
343
|
+
function buildServiceModuleEntry(organizationName, platformName, applicationName, applicationDisplayName) {
|
|
327
344
|
return {
|
|
328
|
-
name: `@${organizationName}/${applicationName}-service`,
|
|
345
|
+
name: `@${organizationName}/${platformName}-${applicationName}-service`,
|
|
329
346
|
displayName: `${applicationDisplayName} Service`,
|
|
330
347
|
type: "service",
|
|
331
|
-
baseUrl: `/${applicationName}-service`,
|
|
348
|
+
baseUrl: `/${platformName}-${applicationName}-service`,
|
|
332
349
|
service: {
|
|
333
|
-
host: `${applicationName}-service`,
|
|
350
|
+
host: `${platformName}-${applicationName}-service`,
|
|
334
351
|
port: 80
|
|
335
352
|
},
|
|
336
353
|
dependsOn: []
|
|
337
354
|
};
|
|
338
355
|
}
|
|
339
|
-
function buildUiModuleEntry(organizationName, applicationName, applicationDisplayName) {
|
|
356
|
+
function buildUiModuleEntry(organizationName, platformName, applicationName, applicationDisplayName) {
|
|
340
357
|
return {
|
|
341
|
-
name: `@${organizationName}/${applicationName}-ui`,
|
|
358
|
+
name: `@${organizationName}/${platformName}-${applicationName}-ui`,
|
|
342
359
|
displayName: applicationDisplayName,
|
|
343
360
|
type: "app",
|
|
344
|
-
baseUrl: `/${applicationName}-ui`,
|
|
361
|
+
baseUrl: `/${platformName}-${applicationName}-ui`,
|
|
345
362
|
service: {
|
|
346
|
-
host: `${applicationName}-ui`,
|
|
363
|
+
host: `${platformName}-${applicationName}-ui`,
|
|
347
364
|
port: 80
|
|
348
365
|
},
|
|
349
366
|
ui: {
|
|
350
367
|
route: `/${applicationName}`,
|
|
351
368
|
mountAtSelector: "#pae-shell-ui-content",
|
|
352
|
-
bundleFile: `${organizationName}-${applicationName}-ui.js`,
|
|
369
|
+
bundleFile: `${organizationName}-${platformName}-${applicationName}-ui.js`,
|
|
353
370
|
customProps: {}
|
|
354
371
|
},
|
|
355
372
|
dependsOn: [`@bluealba/pae-shell-ui`]
|
|
@@ -358,14 +375,14 @@ function buildUiModuleEntry(organizationName, applicationName, applicationDispla
|
|
|
358
375
|
|
|
359
376
|
// src/commands/create-application/create-docker-compose.ts
|
|
360
377
|
import { writeFile as writeFile3 } from "fs/promises";
|
|
361
|
-
function buildBootstrapBlock(applicationName) {
|
|
362
|
-
return ` ${applicationName}-bootstrap-service:
|
|
378
|
+
function buildBootstrapBlock(platformName, applicationName) {
|
|
379
|
+
return ` ${platformName}-${applicationName}-bootstrap-service:
|
|
363
380
|
build:
|
|
364
|
-
context: ../${applicationName}/services/${applicationName}-bootstrap-service
|
|
381
|
+
context: ../${platformName}-${applicationName}/services/${platformName}-${applicationName}-bootstrap-service
|
|
365
382
|
dockerfile: Dockerfile.development
|
|
366
383
|
environment:
|
|
367
384
|
- NODE_TLS_REJECT_UNAUTHORIZED=0
|
|
368
|
-
- SERVICE_ACCESS_NAME=${applicationName}-bootstrap
|
|
385
|
+
- SERVICE_ACCESS_NAME=${platformName}-${applicationName}-bootstrap
|
|
369
386
|
- WAIT_TIME=5000
|
|
370
387
|
- SYNC_STRATEGY=\${PAE_BOOTSTRAP_SYNC_STRATEGY}
|
|
371
388
|
- GATEWAY_SERVICE_URL=\${PAE_GATEWAY_URL}
|
|
@@ -377,10 +394,10 @@ function buildBootstrapBlock(applicationName) {
|
|
|
377
394
|
condition: service_healthy
|
|
378
395
|
`;
|
|
379
396
|
}
|
|
380
|
-
function buildUiBlock(applicationName, uiPort) {
|
|
381
|
-
return ` ${applicationName}-ui:
|
|
397
|
+
function buildUiBlock(platformName, applicationName, uiPort) {
|
|
398
|
+
return ` ${platformName}-${applicationName}-ui:
|
|
382
399
|
build:
|
|
383
|
-
context: ../${applicationName}/ui/${applicationName}-ui
|
|
400
|
+
context: ../${platformName}-${applicationName}/ui/${platformName}-${applicationName}-ui
|
|
384
401
|
dockerfile: Dockerfile.development
|
|
385
402
|
ports:
|
|
386
403
|
- ${uiPort}:80
|
|
@@ -388,10 +405,10 @@ function buildUiBlock(applicationName, uiPort) {
|
|
|
388
405
|
- \${PWD}/../:/app/out
|
|
389
406
|
`;
|
|
390
407
|
}
|
|
391
|
-
function buildBackendBlock(applicationName, servicePort) {
|
|
392
|
-
return ` ${applicationName}-service:
|
|
408
|
+
function buildBackendBlock(platformName, applicationName, servicePort) {
|
|
409
|
+
return ` ${platformName}-${applicationName}-service:
|
|
393
410
|
build:
|
|
394
|
-
context: ../${applicationName}/services/${applicationName}-service
|
|
411
|
+
context: ../${platformName}-${applicationName}/services/${platformName}-${applicationName}-service
|
|
395
412
|
dockerfile: Dockerfile.development
|
|
396
413
|
args:
|
|
397
414
|
- BA_NPM_AUTH_TOKEN=$BA_NPM_AUTH_TOKEN
|
|
@@ -403,13 +420,13 @@ function buildBackendBlock(applicationName, servicePort) {
|
|
|
403
420
|
- \${PWD}/../:/app/out
|
|
404
421
|
`;
|
|
405
422
|
}
|
|
406
|
-
async function createDockerCompose(dockerComposePath, applicationName, hasUserInterface, hasBackendService, uiPort, servicePort, logger) {
|
|
407
|
-
const blocks = ["services:\n", buildBootstrapBlock(applicationName)];
|
|
423
|
+
async function createDockerCompose(dockerComposePath, platformName, applicationName, hasUserInterface, hasBackendService, uiPort, servicePort, logger) {
|
|
424
|
+
const blocks = ["services:\n", buildBootstrapBlock(platformName, applicationName)];
|
|
408
425
|
if (hasUserInterface) {
|
|
409
|
-
blocks.push(buildUiBlock(applicationName, uiPort));
|
|
426
|
+
blocks.push(buildUiBlock(platformName, applicationName, uiPort));
|
|
410
427
|
}
|
|
411
428
|
if (hasBackendService) {
|
|
412
|
-
blocks.push(buildBackendBlock(applicationName, servicePort));
|
|
429
|
+
blocks.push(buildBackendBlock(platformName, applicationName, servicePort));
|
|
413
430
|
}
|
|
414
431
|
const content = blocks.join("\n");
|
|
415
432
|
try {
|
|
@@ -423,10 +440,10 @@ async function createDockerCompose(dockerComposePath, applicationName, hasUserIn
|
|
|
423
440
|
|
|
424
441
|
// src/commands/create-application/update-root-docker-compose.ts
|
|
425
442
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
426
|
-
async function updateRootDockerCompose(rootDockerComposePath, applicationName, logger) {
|
|
443
|
+
async function updateRootDockerCompose(rootDockerComposePath, platformName, applicationName, logger) {
|
|
427
444
|
try {
|
|
428
445
|
const existing = await readFile3(rootDockerComposePath, "utf-8");
|
|
429
|
-
const includeLine = ` - path: ./${applicationName}-docker-compose.yml
|
|
446
|
+
const includeLine = ` - path: ./${platformName}-${applicationName}-docker-compose.yml
|
|
430
447
|
`;
|
|
431
448
|
await writeFile4(rootDockerComposePath, existing + includeLine, "utf-8");
|
|
432
449
|
logger.log(`Updated root docker-compose: ${rootDockerComposePath}`);
|
|
@@ -466,13 +483,34 @@ function formatError(err) {
|
|
|
466
483
|
}
|
|
467
484
|
|
|
468
485
|
// src/utils/platform-check.ts
|
|
469
|
-
import { access } from "fs/promises";
|
|
486
|
+
import { access, readdir as readdir3, readFile as readFile5 } from "fs/promises";
|
|
470
487
|
import { join as join9 } from "path";
|
|
471
488
|
import { cwd } from "process";
|
|
489
|
+
async function findCoreDirName(dir) {
|
|
490
|
+
const entries = await readdir3(dir, { withFileTypes: true });
|
|
491
|
+
const coreEntry = entries.find((e) => e.isDirectory() && e.name.endsWith("-core"));
|
|
492
|
+
return coreEntry?.name ?? null;
|
|
493
|
+
}
|
|
494
|
+
async function readPlatformManifest(dir) {
|
|
495
|
+
const baseDir = dir ?? cwd();
|
|
496
|
+
const coreDirName = await findCoreDirName(baseDir);
|
|
497
|
+
if (!coreDirName) {
|
|
498
|
+
throw new Error("No *-core directory found \u2014 platform not initialized in this directory.");
|
|
499
|
+
}
|
|
500
|
+
const platformName = coreDirName.replace(/-core$/, "");
|
|
501
|
+
const pkgJson = JSON.parse(await readFile5(join9(baseDir, coreDirName, "package.json"), "utf-8"));
|
|
502
|
+
const scopeMatch = pkgJson.name.match(/^@([^/]+)\//);
|
|
503
|
+
if (!scopeMatch) {
|
|
504
|
+
throw new Error(`Could not parse organization from package name: "${pkgJson.name}"`);
|
|
505
|
+
}
|
|
506
|
+
return { platformName, organizationName: scopeMatch[1] };
|
|
507
|
+
}
|
|
472
508
|
async function isPlatformInitialized() {
|
|
473
509
|
try {
|
|
474
|
-
await
|
|
475
|
-
|
|
510
|
+
const coreDirName = await findCoreDirName(cwd());
|
|
511
|
+
if (!coreDirName) return false;
|
|
512
|
+
const platformName = coreDirName.replace(/-core$/, "");
|
|
513
|
+
await access(join9(cwd(), `${platformName}-local`));
|
|
476
514
|
return true;
|
|
477
515
|
} catch {
|
|
478
516
|
return false;
|
|
@@ -500,10 +538,10 @@ async function createApplication(params, logger) {
|
|
|
500
538
|
return;
|
|
501
539
|
}
|
|
502
540
|
const rootDir = cwd2();
|
|
503
|
-
const applicationDir = join10(rootDir, applicationName);
|
|
504
|
-
const bootstrapServiceDir = join10(applicationDir, "services", `${applicationName}-bootstrap-service`);
|
|
505
|
-
const localDir = join10(rootDir,
|
|
506
|
-
logger.log(`Creating application monorepo "${applicationName}"...`);
|
|
541
|
+
const applicationDir = join10(rootDir, `${platformName}-${applicationName}`);
|
|
542
|
+
const bootstrapServiceDir = join10(applicationDir, "services", `${platformName}-${applicationName}-bootstrap-service`);
|
|
543
|
+
const localDir = join10(rootDir, `${platformName}-local`);
|
|
544
|
+
logger.log(`Creating application monorepo "${platformName}-${applicationName}"...`);
|
|
507
545
|
try {
|
|
508
546
|
await scaffoldApplicationMonorepo(applicationDir, organizationName, platformName, applicationName, logger);
|
|
509
547
|
} catch (err) {
|
|
@@ -512,10 +550,10 @@ async function createApplication(params, logger) {
|
|
|
512
550
|
}
|
|
513
551
|
await mkdir2(join10(applicationDir, "services"), { recursive: true });
|
|
514
552
|
await mkdir2(join10(applicationDir, "ui"), { recursive: true });
|
|
515
|
-
logger.log(`Creating bootstrap service "${applicationName}-bootstrap-service"...`);
|
|
516
|
-
const bootstrapServiceDir_var = `${applicationName}/services`;
|
|
553
|
+
logger.log(`Creating bootstrap service "${platformName}-${applicationName}-bootstrap-service"...`);
|
|
554
|
+
const bootstrapServiceDir_var = `${platformName}-${applicationName}/services`;
|
|
517
555
|
try {
|
|
518
|
-
await scaffoldBootstrap(bootstrapServiceDir, organizationName, applicationName
|
|
556
|
+
await scaffoldBootstrap(bootstrapServiceDir, organizationName, `${platformName}-${applicationName}`, bootstrapServiceDir_var, logger);
|
|
519
557
|
} catch (err) {
|
|
520
558
|
logger.log(`Error: Could not scaffold bootstrap service \u2014 ${formatError(err)}`);
|
|
521
559
|
return;
|
|
@@ -531,7 +569,7 @@ async function createApplication(params, logger) {
|
|
|
531
569
|
await addModuleEntry(
|
|
532
570
|
bootstrapServiceDir,
|
|
533
571
|
applicationName,
|
|
534
|
-
buildUiModuleEntry(organizationName, applicationName, applicationDisplayName),
|
|
572
|
+
buildUiModuleEntry(organizationName, platformName, applicationName, applicationDisplayName),
|
|
535
573
|
logger
|
|
536
574
|
);
|
|
537
575
|
}
|
|
@@ -539,13 +577,13 @@ async function createApplication(params, logger) {
|
|
|
539
577
|
await addModuleEntry(
|
|
540
578
|
bootstrapServiceDir,
|
|
541
579
|
applicationName,
|
|
542
|
-
buildServiceModuleEntry(organizationName, applicationName, applicationDisplayName),
|
|
580
|
+
buildServiceModuleEntry(organizationName, platformName, applicationName, applicationDisplayName),
|
|
543
581
|
logger
|
|
544
582
|
);
|
|
545
583
|
}
|
|
546
584
|
if (hasUserInterface) {
|
|
547
|
-
const uiDir = join10(applicationDir, "ui", `${applicationName}-ui`);
|
|
548
|
-
const uiBaseDir = `${applicationName}/ui`;
|
|
585
|
+
const uiDir = join10(applicationDir, "ui", `${platformName}-${applicationName}-ui`);
|
|
586
|
+
const uiBaseDir = `${platformName}-${applicationName}/ui`;
|
|
549
587
|
try {
|
|
550
588
|
await scaffoldUiModule(uiDir, organizationName, platformName, applicationName, applicationDisplayName, uiBaseDir, logger);
|
|
551
589
|
} catch (err) {
|
|
@@ -554,21 +592,22 @@ async function createApplication(params, logger) {
|
|
|
554
592
|
}
|
|
555
593
|
}
|
|
556
594
|
if (hasBackendService) {
|
|
557
|
-
const serviceDir = join10(applicationDir, "services", `${applicationName}-service`);
|
|
558
|
-
const serviceBaseDir = `${applicationName}/services`;
|
|
595
|
+
const serviceDir = join10(applicationDir, "services", `${platformName}-${applicationName}-service`);
|
|
596
|
+
const serviceBaseDir = `${platformName}-${applicationName}/services`;
|
|
559
597
|
try {
|
|
560
|
-
await scaffoldNestjsService(serviceDir, organizationName, applicationName, applicationDisplayName, serviceBaseDir, logger);
|
|
598
|
+
await scaffoldNestjsService(serviceDir, organizationName, platformName, applicationName, applicationDisplayName, serviceBaseDir, logger);
|
|
561
599
|
} catch (err) {
|
|
562
600
|
logger.log(`Error: Could not scaffold NestJS service \u2014 ${formatError(err)}`);
|
|
563
601
|
return;
|
|
564
602
|
}
|
|
565
603
|
}
|
|
566
|
-
const dockerComposePath = join10(localDir, `${applicationName}-docker-compose.yml`);
|
|
604
|
+
const dockerComposePath = join10(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
|
|
567
605
|
const basePort = await getNextAvailablePort(localDir);
|
|
568
606
|
const uiPort = hasUserInterface ? basePort : 0;
|
|
569
607
|
const servicePort = hasBackendService ? hasUserInterface ? basePort + 1 : basePort : 0;
|
|
570
608
|
await createDockerCompose(
|
|
571
609
|
dockerComposePath,
|
|
610
|
+
platformName,
|
|
572
611
|
applicationName,
|
|
573
612
|
hasUserInterface,
|
|
574
613
|
hasBackendService,
|
|
@@ -577,8 +616,8 @@ async function createApplication(params, logger) {
|
|
|
577
616
|
logger
|
|
578
617
|
);
|
|
579
618
|
const rootDockerComposePath = join10(localDir, "docker-compose.yml");
|
|
580
|
-
await updateRootDockerCompose(rootDockerComposePath, applicationName, logger);
|
|
581
|
-
logger.log(`Done! Application "${applicationName}" created at ${applicationDir}`);
|
|
619
|
+
await updateRootDockerCompose(rootDockerComposePath, platformName, applicationName, logger);
|
|
620
|
+
logger.log(`Done! Application "${platformName}-${applicationName}" created at ${applicationDir}`);
|
|
582
621
|
}
|
|
583
622
|
|
|
584
623
|
// src/commands/init/init.command.ts
|
|
@@ -631,31 +670,31 @@ async function scaffoldCustomizationUi(outputDir, variables, logger) {
|
|
|
631
670
|
|
|
632
671
|
// src/commands/init/register-customization-module.ts
|
|
633
672
|
import { join as join14 } from "path";
|
|
634
|
-
import { readFile as
|
|
635
|
-
async function registerCustomizationModule(bootstrapServiceDir, organizationName, logger) {
|
|
673
|
+
import { readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
|
|
674
|
+
async function registerCustomizationModule(bootstrapServiceDir, organizationName, platformName, logger) {
|
|
636
675
|
const modulesJsonPath = join14(bootstrapServiceDir, "src", "data", "platform", "modules.json");
|
|
637
676
|
const entry = {
|
|
638
|
-
name: `@${organizationName}
|
|
677
|
+
name: `@${organizationName}/${platformName}-customization-ui`,
|
|
639
678
|
displayName: "Platform Customization UI",
|
|
640
679
|
type: "app",
|
|
641
|
-
baseUrl:
|
|
642
|
-
service: { host:
|
|
680
|
+
baseUrl: `/${platformName}-customization-ui`,
|
|
681
|
+
service: { host: `${platformName}-customization-ui`, port: 80 },
|
|
643
682
|
ui: {
|
|
644
683
|
route: "/",
|
|
645
|
-
bundleFile:
|
|
684
|
+
bundleFile: `${platformName}-customization-ui.js`,
|
|
646
685
|
isPlatformCustomization: true,
|
|
647
686
|
customProps: {}
|
|
648
687
|
},
|
|
649
688
|
dependsOn: []
|
|
650
689
|
};
|
|
651
|
-
const existing = JSON.parse(await
|
|
690
|
+
const existing = JSON.parse(await readFile6(modulesJsonPath, "utf-8"));
|
|
652
691
|
existing.push(entry);
|
|
653
692
|
await writeFile5(modulesJsonPath, JSON.stringify(existing, null, 2) + "\n", "utf-8");
|
|
654
693
|
logger.log(`Registered customization module in ${modulesJsonPath}`);
|
|
655
694
|
}
|
|
656
695
|
|
|
657
696
|
// src/commands/init/generate-local-env.ts
|
|
658
|
-
import { readFile as
|
|
697
|
+
import { readFile as readFile7, writeFile as writeFile6 } from "fs/promises";
|
|
659
698
|
import { join as join15 } from "path";
|
|
660
699
|
|
|
661
700
|
// src/utils/random.ts
|
|
@@ -665,11 +704,11 @@ function generateRandomSecret() {
|
|
|
665
704
|
}
|
|
666
705
|
|
|
667
706
|
// src/commands/init/generate-local-env.ts
|
|
668
|
-
async function generateLocalEnv(outputDir, logger) {
|
|
669
|
-
const examplePath = join15(outputDir,
|
|
670
|
-
const envPath = join15(outputDir,
|
|
671
|
-
logger.log(
|
|
672
|
-
const content = await
|
|
707
|
+
async function generateLocalEnv(outputDir, platformName, logger) {
|
|
708
|
+
const examplePath = join15(outputDir, `${platformName}-local`, ".env.example");
|
|
709
|
+
const envPath = join15(outputDir, `${platformName}-local`, ".env");
|
|
710
|
+
logger.log(`Generating ${platformName}-local/.env with random secrets...`);
|
|
711
|
+
const content = await readFile7(examplePath, "utf-8");
|
|
673
712
|
const result = content.replace(/^(PAE_AUTH_JWT_SECRET|PAE_GATEWAY_SERVICE_ACCESS_SECRET|PAE_DB_PASSWORD)=$/gm, (_, key) => {
|
|
674
713
|
return `${key}=${generateRandomSecret()}`;
|
|
675
714
|
});
|
|
@@ -696,8 +735,8 @@ async function init(params, logger) {
|
|
|
696
735
|
platformName,
|
|
697
736
|
platformTitle,
|
|
698
737
|
platformDisplayName,
|
|
699
|
-
bootstrapName:
|
|
700
|
-
bootstrapServiceDir:
|
|
738
|
+
bootstrapName: platformName,
|
|
739
|
+
bootstrapServiceDir: `${platformName}-core/services`
|
|
701
740
|
};
|
|
702
741
|
try {
|
|
703
742
|
await scaffoldPlatform(outputDir, variables, logger);
|
|
@@ -706,14 +745,14 @@ async function init(params, logger) {
|
|
|
706
745
|
return;
|
|
707
746
|
}
|
|
708
747
|
try {
|
|
709
|
-
await generateLocalEnv(outputDir, logger);
|
|
748
|
+
await generateLocalEnv(outputDir, platformName, logger);
|
|
710
749
|
} catch (err) {
|
|
711
|
-
logger.log(`Error: Could not generate local/.env \u2014 ${formatError(err)}`);
|
|
750
|
+
logger.log(`Error: Could not generate ${platformName}-local/.env \u2014 ${formatError(err)}`);
|
|
712
751
|
return;
|
|
713
752
|
}
|
|
714
753
|
try {
|
|
715
754
|
await scaffoldPlatformBootstrap(
|
|
716
|
-
join16(outputDir,
|
|
755
|
+
join16(outputDir, `${platformName}-core`, "services", `${platformName}-bootstrap-service`),
|
|
717
756
|
variables,
|
|
718
757
|
logger
|
|
719
758
|
);
|
|
@@ -723,7 +762,7 @@ async function init(params, logger) {
|
|
|
723
762
|
}
|
|
724
763
|
try {
|
|
725
764
|
await scaffoldCustomizationUi(
|
|
726
|
-
join16(outputDir,
|
|
765
|
+
join16(outputDir, `${platformName}-core`, "ui", `${platformName}-customization-ui`),
|
|
727
766
|
variables,
|
|
728
767
|
logger
|
|
729
768
|
);
|
|
@@ -733,8 +772,9 @@ async function init(params, logger) {
|
|
|
733
772
|
}
|
|
734
773
|
try {
|
|
735
774
|
await registerCustomizationModule(
|
|
736
|
-
join16(outputDir,
|
|
775
|
+
join16(outputDir, `${platformName}-core`, "services", `${platformName}-bootstrap-service`),
|
|
737
776
|
organizationName,
|
|
777
|
+
platformName,
|
|
738
778
|
logger
|
|
739
779
|
);
|
|
740
780
|
} catch (err) {
|
|
@@ -750,11 +790,11 @@ import { cwd as cwd4 } from "process";
|
|
|
750
790
|
import { fetch as undiciFetch, Agent } from "undici";
|
|
751
791
|
|
|
752
792
|
// src/utils/env-reader.ts
|
|
753
|
-
import { readFile as
|
|
793
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
754
794
|
async function readEnvFile(filePath) {
|
|
755
795
|
let content;
|
|
756
796
|
try {
|
|
757
|
-
content = await
|
|
797
|
+
content = await readFile8(filePath, "utf-8");
|
|
758
798
|
} catch (error) {
|
|
759
799
|
if (error.code === "ENOENT") {
|
|
760
800
|
throw new Error(`.env file not found at ${filePath}`);
|
|
@@ -828,7 +868,8 @@ async function configureIdp(params, logger) {
|
|
|
828
868
|
logger.log("Error: Cannot configure an IDP \u2014 no platform initialized in this directory.");
|
|
829
869
|
return;
|
|
830
870
|
}
|
|
831
|
-
const
|
|
871
|
+
const { platformName } = await readPlatformManifest();
|
|
872
|
+
const envPath = join17(cwd4(), `${platformName}-local`, ".env");
|
|
832
873
|
let env;
|
|
833
874
|
try {
|
|
834
875
|
env = await readEnvFile(envPath);
|
|
@@ -839,11 +880,11 @@ async function configureIdp(params, logger) {
|
|
|
839
880
|
const gatewayUrl = env.get("PAE_GATEWAY_HOST_URL");
|
|
840
881
|
const accessSecret = env.get("PAE_GATEWAY_SERVICE_ACCESS_SECRET");
|
|
841
882
|
if (!gatewayUrl) {
|
|
842
|
-
logger.log(
|
|
883
|
+
logger.log(`Error: PAE_GATEWAY_HOST_URL is not set in ${platformName}-local/.env`);
|
|
843
884
|
return;
|
|
844
885
|
}
|
|
845
886
|
if (!accessSecret) {
|
|
846
|
-
logger.log(
|
|
887
|
+
logger.log(`Error: PAE_GATEWAY_SERVICE_ACCESS_SECRET is not set in ${platformName}-local/.env`);
|
|
847
888
|
return;
|
|
848
889
|
}
|
|
849
890
|
const provider = idpProviderRegistry.get(params.providerType);
|
|
@@ -879,6 +920,223 @@ async function configureIdp(params, logger) {
|
|
|
879
920
|
logger.log(`IDP provider "${params.name}" configured successfully.`);
|
|
880
921
|
}
|
|
881
922
|
|
|
923
|
+
// src/commands/create-service-module/create-service-module.command.ts
|
|
924
|
+
import { join as join19 } from "path";
|
|
925
|
+
import { cwd as cwd5 } from "process";
|
|
926
|
+
import { access as access2 } from "fs/promises";
|
|
927
|
+
|
|
928
|
+
// src/commands/create-service-module/scaffold-service-module.ts
|
|
929
|
+
import { fileURLToPath as fileURLToPath9 } from "url";
|
|
930
|
+
import { join as join18, dirname as dirname10 } from "path";
|
|
931
|
+
var nestjsServiceModuleTemplateDir2 = join18(
|
|
932
|
+
dirname10(fileURLToPath9(import.meta.url)),
|
|
933
|
+
"..",
|
|
934
|
+
"templates",
|
|
935
|
+
"nestjs-service-module-template"
|
|
936
|
+
);
|
|
937
|
+
async function scaffoldServiceModule(serviceDir, organizationName, serviceName, serviceDisplayName, serviceBaseDir, logger) {
|
|
938
|
+
logger.log(`Creating NestJS service "${serviceName}"...`);
|
|
939
|
+
await applyTemplate(
|
|
940
|
+
{
|
|
941
|
+
templateDir: nestjsServiceModuleTemplateDir2,
|
|
942
|
+
outputDir: serviceDir,
|
|
943
|
+
variables: { organizationName, serviceName, serviceDisplayName, serviceBaseDir }
|
|
944
|
+
},
|
|
945
|
+
(message) => logger.log(message)
|
|
946
|
+
);
|
|
947
|
+
logger.log(`Done! Service output: ${serviceDir}`);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// src/commands/create-service-module/service-module-entry-builder.ts
|
|
951
|
+
function buildCustomServiceModuleEntry(organizationName, serviceName, serviceDisplayName) {
|
|
952
|
+
return {
|
|
953
|
+
name: `@${organizationName}/${serviceName}`,
|
|
954
|
+
displayName: serviceDisplayName,
|
|
955
|
+
type: "service",
|
|
956
|
+
baseUrl: `/${serviceName}`,
|
|
957
|
+
service: {
|
|
958
|
+
host: serviceName,
|
|
959
|
+
port: 80
|
|
960
|
+
},
|
|
961
|
+
dependsOn: []
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
// src/commands/create-service-module/append-docker-compose.ts
|
|
966
|
+
import { readFile as readFile9, writeFile as writeFile7 } from "fs/promises";
|
|
967
|
+
async function appendServiceToDockerCompose(dockerComposePath, serviceName, platformName, applicationName, port, logger) {
|
|
968
|
+
const block = `
|
|
969
|
+
${serviceName}:
|
|
970
|
+
build:
|
|
971
|
+
context: ../${platformName}-${applicationName}/services/${serviceName}
|
|
972
|
+
dockerfile: Dockerfile.development
|
|
973
|
+
args:
|
|
974
|
+
- BA_NPM_AUTH_TOKEN=$BA_NPM_AUTH_TOKEN
|
|
975
|
+
ports:
|
|
976
|
+
- ${port}:80
|
|
977
|
+
environment:
|
|
978
|
+
- GATEWAY_URL=\${PAE_GATEWAY_URL}
|
|
979
|
+
volumes:
|
|
980
|
+
- \${PWD}/../:/app/out
|
|
981
|
+
`;
|
|
982
|
+
try {
|
|
983
|
+
const existing = await readFile9(dockerComposePath, "utf-8");
|
|
984
|
+
await writeFile7(dockerComposePath, existing + block, "utf-8");
|
|
985
|
+
logger.log(`Updated docker-compose: ${dockerComposePath}`);
|
|
986
|
+
} catch (err) {
|
|
987
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
988
|
+
logger.log(`Warning: Could not update docker-compose \u2014 ${message}`);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
// src/commands/create-service-module/create-service-module.command.ts
|
|
993
|
+
var CREATE_SERVICE_MODULE_COMMAND_NAME = "create-service-module";
|
|
994
|
+
var createServiceModuleCommand = {
|
|
995
|
+
name: CREATE_SERVICE_MODULE_COMMAND_NAME,
|
|
996
|
+
description: "Add a new service module to an existing application"
|
|
997
|
+
};
|
|
998
|
+
async function createServiceModule(params, logger) {
|
|
999
|
+
const { applicationName, serviceName, serviceDisplayName } = params;
|
|
1000
|
+
if (!await isPlatformInitialized()) {
|
|
1001
|
+
logger.log("Error: Cannot create a service module \u2014 no platform initialized in this directory.");
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
const rootDir = cwd5();
|
|
1005
|
+
let organizationName;
|
|
1006
|
+
let platformName;
|
|
1007
|
+
try {
|
|
1008
|
+
const manifest = await readPlatformManifest(rootDir);
|
|
1009
|
+
organizationName = manifest.organizationName;
|
|
1010
|
+
platformName = manifest.platformName;
|
|
1011
|
+
} catch (err) {
|
|
1012
|
+
logger.log(`Error: Could not read .platform.json \u2014 ${formatError(err)}`);
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
const applicationDir = join19(rootDir, `${platformName}-${applicationName}`);
|
|
1016
|
+
try {
|
|
1017
|
+
await access2(applicationDir);
|
|
1018
|
+
} catch {
|
|
1019
|
+
logger.log(`Error: The specified application "${platformName}-${applicationName}" does not exist in the platform.`);
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
const fullServiceName = `${platformName}-${serviceName}-service`;
|
|
1023
|
+
const serviceDir = join19(applicationDir, "services", fullServiceName);
|
|
1024
|
+
try {
|
|
1025
|
+
await access2(serviceDir);
|
|
1026
|
+
logger.log(`Error: A service named "${fullServiceName}" already exists in application "${platformName}-${applicationName}".`);
|
|
1027
|
+
return;
|
|
1028
|
+
} catch {
|
|
1029
|
+
}
|
|
1030
|
+
const serviceBaseDir = `${platformName}-${applicationName}/services`;
|
|
1031
|
+
try {
|
|
1032
|
+
await scaffoldServiceModule(
|
|
1033
|
+
serviceDir,
|
|
1034
|
+
organizationName,
|
|
1035
|
+
fullServiceName,
|
|
1036
|
+
serviceDisplayName,
|
|
1037
|
+
serviceBaseDir,
|
|
1038
|
+
logger
|
|
1039
|
+
);
|
|
1040
|
+
} catch (err) {
|
|
1041
|
+
logger.log(`Error: Could not scaffold NestJS service \u2014 ${formatError(err)}`);
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
const bootstrapServiceDir = join19(applicationDir, "services", `${platformName}-${applicationName}-bootstrap-service`);
|
|
1045
|
+
const moduleEntry = buildCustomServiceModuleEntry(organizationName, fullServiceName, serviceDisplayName);
|
|
1046
|
+
await addModuleEntry(bootstrapServiceDir, applicationName, moduleEntry, logger);
|
|
1047
|
+
const localDir = join19(rootDir, `${platformName}-local`);
|
|
1048
|
+
const dockerComposePath = join19(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
|
|
1049
|
+
const port = await getNextAvailablePort(localDir);
|
|
1050
|
+
await appendServiceToDockerCompose(dockerComposePath, fullServiceName, platformName, applicationName, port, logger);
|
|
1051
|
+
logger.log(`Done! Service module "${fullServiceName}" added to application "${platformName}-${applicationName}".`);
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
// src/commands/create-ui-module/create-ui-module.command.ts
|
|
1055
|
+
import { join as join20 } from "path";
|
|
1056
|
+
import { cwd as cwd6 } from "process";
|
|
1057
|
+
import { access as access3, readdir as readdir4 } from "fs/promises";
|
|
1058
|
+
|
|
1059
|
+
// src/commands/create-ui-module/append-ui-docker-compose.ts
|
|
1060
|
+
import { readFile as readFile10, writeFile as writeFile8 } from "fs/promises";
|
|
1061
|
+
async function appendUiToDockerCompose(dockerComposePath, platformName, applicationName, port, logger) {
|
|
1062
|
+
const block = `
|
|
1063
|
+
${platformName}-${applicationName}-ui:
|
|
1064
|
+
build:
|
|
1065
|
+
context: ../${platformName}-${applicationName}/ui/${platformName}-${applicationName}-ui
|
|
1066
|
+
dockerfile: Dockerfile.development
|
|
1067
|
+
ports:
|
|
1068
|
+
- ${port}:80
|
|
1069
|
+
volumes:
|
|
1070
|
+
- \${PWD}/../:/app/out
|
|
1071
|
+
`;
|
|
1072
|
+
try {
|
|
1073
|
+
const existing = await readFile10(dockerComposePath, "utf-8");
|
|
1074
|
+
await writeFile8(dockerComposePath, existing + block, "utf-8");
|
|
1075
|
+
logger.log(`Updated docker-compose: ${dockerComposePath}`);
|
|
1076
|
+
} catch (err) {
|
|
1077
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1078
|
+
logger.log(`Warning: Could not update docker-compose \u2014 ${message}`);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
// src/commands/create-ui-module/create-ui-module.command.ts
|
|
1083
|
+
var CREATE_UI_MODULE_COMMAND_NAME = "create-ui-module";
|
|
1084
|
+
var createUiModuleCommand = {
|
|
1085
|
+
name: CREATE_UI_MODULE_COMMAND_NAME,
|
|
1086
|
+
description: "Add a UI module to an existing application"
|
|
1087
|
+
};
|
|
1088
|
+
async function createUiModule(params, logger) {
|
|
1089
|
+
const { applicationName, applicationDisplayName } = params;
|
|
1090
|
+
if (!await isPlatformInitialized()) {
|
|
1091
|
+
logger.log("Error: Cannot create a UI module \u2014 no platform initialized in this directory.");
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
const rootDir = cwd6();
|
|
1095
|
+
let organizationName;
|
|
1096
|
+
let platformName;
|
|
1097
|
+
try {
|
|
1098
|
+
const manifest = await readPlatformManifest(rootDir);
|
|
1099
|
+
organizationName = manifest.organizationName;
|
|
1100
|
+
platformName = manifest.platformName;
|
|
1101
|
+
} catch (err) {
|
|
1102
|
+
logger.log(`Error: Could not read .platform.json \u2014 ${formatError(err)}`);
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
const applicationDir = join20(rootDir, `${platformName}-${applicationName}`);
|
|
1106
|
+
try {
|
|
1107
|
+
await access3(applicationDir);
|
|
1108
|
+
} catch {
|
|
1109
|
+
logger.log(`Error: The specified application "${platformName}-${applicationName}" does not exist in the platform.`);
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
const uiDir = join20(applicationDir, "ui");
|
|
1113
|
+
try {
|
|
1114
|
+
const uiEntries = await readdir4(uiDir);
|
|
1115
|
+
const existingUiModules = uiEntries.filter((e) => e !== ".gitkeep");
|
|
1116
|
+
if (existingUiModules.length > 0) {
|
|
1117
|
+
logger.log(`Error: Currently we support only one UI module per application. Application "${platformName}-${applicationName}" already has a UI module.`);
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
} catch {
|
|
1121
|
+
}
|
|
1122
|
+
const uiOutputDir = join20(uiDir, `${platformName}-${applicationName}-ui`);
|
|
1123
|
+
const uiBaseDir = `${platformName}-${applicationName}/ui`;
|
|
1124
|
+
try {
|
|
1125
|
+
await scaffoldUiModule(uiOutputDir, organizationName, platformName, applicationName, applicationDisplayName, uiBaseDir, logger);
|
|
1126
|
+
} catch (err) {
|
|
1127
|
+
logger.log(`Error: Could not scaffold UI module \u2014 ${formatError(err)}`);
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
const bootstrapServiceDir = join20(applicationDir, "services", `${platformName}-${applicationName}-bootstrap-service`);
|
|
1131
|
+
const moduleEntry = buildUiModuleEntry(organizationName, platformName, applicationName, applicationDisplayName);
|
|
1132
|
+
await addModuleEntry(bootstrapServiceDir, applicationName, moduleEntry, logger);
|
|
1133
|
+
const localDir = join20(rootDir, `${platformName}-local`);
|
|
1134
|
+
const dockerComposePath = join20(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
|
|
1135
|
+
const port = await getNextAvailablePort(localDir);
|
|
1136
|
+
await appendUiToDockerCompose(dockerComposePath, platformName, applicationName, port, logger);
|
|
1137
|
+
logger.log(`Done! UI module "${platformName}-${applicationName}-ui" added to application "${platformName}-${applicationName}".`);
|
|
1138
|
+
}
|
|
1139
|
+
|
|
882
1140
|
// src/commands/registry.ts
|
|
883
1141
|
var CommandRegistry = class {
|
|
884
1142
|
commands = /* @__PURE__ */ new Map();
|
|
@@ -904,6 +1162,8 @@ var registry = new CommandRegistry();
|
|
|
904
1162
|
registry.register(createApplicationCommand);
|
|
905
1163
|
registry.register(initCommand);
|
|
906
1164
|
registry.register(configureIdpCommand);
|
|
1165
|
+
registry.register(createServiceModuleCommand);
|
|
1166
|
+
registry.register(createUiModuleCommand);
|
|
907
1167
|
|
|
908
1168
|
// src/app-state.ts
|
|
909
1169
|
var APP_STATE = {
|
|
@@ -916,11 +1176,6 @@ var APP_STATE = {
|
|
|
916
1176
|
// src/hooks/use-command-runner.ts
|
|
917
1177
|
import { useState as useState2, useCallback, useRef } from "react";
|
|
918
1178
|
|
|
919
|
-
// src/controllers/ui/create-application.ui-controller.ts
|
|
920
|
-
import { readFile as readFile8 } from "fs/promises";
|
|
921
|
-
import { join as join18 } from "path";
|
|
922
|
-
import { cwd as cwd5 } from "process";
|
|
923
|
-
|
|
924
1179
|
// src/services/create-application.service.ts
|
|
925
1180
|
async function createApplicationService(params, logger) {
|
|
926
1181
|
await createApplication(params, logger);
|
|
@@ -935,19 +1190,12 @@ async function createApplicationUiController(ctx) {
|
|
|
935
1190
|
let organizationName;
|
|
936
1191
|
let platformName;
|
|
937
1192
|
try {
|
|
938
|
-
const
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
const scopeMatch = corePackageJson.name.match(/^@([^/]+)\//);
|
|
942
|
-
organizationName = scopeMatch?.[1];
|
|
943
|
-
const rawName = corePackageJson.name.replace(/^@[^/]+\//, "").replace(/-core$/, "");
|
|
944
|
-
platformName = rawName;
|
|
945
|
-
if (!organizationName || !platformName) {
|
|
946
|
-
throw new Error(`Could not parse organization/platform from package name: "${corePackageJson.name}"`);
|
|
947
|
-
}
|
|
1193
|
+
const manifest = await readPlatformManifest();
|
|
1194
|
+
organizationName = manifest.organizationName;
|
|
1195
|
+
platformName = manifest.platformName;
|
|
948
1196
|
} catch (err) {
|
|
949
1197
|
const message = err instanceof Error ? err.message : String(err);
|
|
950
|
-
ctx.log(`Error: Could not read
|
|
1198
|
+
ctx.log(`Error: Could not read .platform.json \u2014 ${message}`);
|
|
951
1199
|
return;
|
|
952
1200
|
}
|
|
953
1201
|
const applicationName = await ctx.prompt("Application name:");
|
|
@@ -1018,7 +1266,7 @@ async function configureIdpUiController(ctx) {
|
|
|
1018
1266
|
return;
|
|
1019
1267
|
}
|
|
1020
1268
|
const provider = providers[index];
|
|
1021
|
-
const name =
|
|
1269
|
+
const name = provider.type;
|
|
1022
1270
|
const issuer = await ctx.prompt("Issuer URL:");
|
|
1023
1271
|
const clientId = await ctx.prompt("Client ID:");
|
|
1024
1272
|
const clientSecret = await ctx.prompt("Client Secret:");
|
|
@@ -1032,11 +1280,64 @@ async function configureIdpUiController(ctx) {
|
|
|
1032
1280
|
);
|
|
1033
1281
|
}
|
|
1034
1282
|
|
|
1283
|
+
// src/services/create-service-module.service.ts
|
|
1284
|
+
async function createServiceModuleService(params, logger) {
|
|
1285
|
+
await createServiceModule(params, logger);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
// src/controllers/ui/create-service-module.ui-controller.ts
|
|
1289
|
+
async function createServiceModuleUiController(ctx) {
|
|
1290
|
+
if (!await isPlatformInitialized()) {
|
|
1291
|
+
ctx.log("Error: Cannot create a service module \u2014 no platform initialized in this directory.");
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
const applicationName = await ctx.prompt("Application name:");
|
|
1295
|
+
if (!/^[a-z0-9-]+$/.test(applicationName)) {
|
|
1296
|
+
ctx.log(`Error: Application name "${applicationName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
const serviceName = await ctx.prompt("Service name:");
|
|
1300
|
+
if (!/^[a-z0-9-]+$/.test(serviceName)) {
|
|
1301
|
+
ctx.log(`Error: Service name "${serviceName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
const serviceDisplayName = await ctx.prompt("Service display name:");
|
|
1305
|
+
await createServiceModuleService(
|
|
1306
|
+
{ applicationName, serviceName, serviceDisplayName },
|
|
1307
|
+
ctx
|
|
1308
|
+
);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
// src/services/create-ui-module.service.ts
|
|
1312
|
+
async function createUiModuleService(params, logger) {
|
|
1313
|
+
await createUiModule(params, logger);
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
// src/controllers/ui/create-ui-module.ui-controller.ts
|
|
1317
|
+
async function createUiModuleUiController(ctx) {
|
|
1318
|
+
if (!await isPlatformInitialized()) {
|
|
1319
|
+
ctx.log("Error: Cannot create a UI module \u2014 no platform initialized in this directory.");
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
const applicationName = await ctx.prompt("Application name:");
|
|
1323
|
+
if (!/^[a-z0-9-]+$/.test(applicationName)) {
|
|
1324
|
+
ctx.log(`Error: Application name "${applicationName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
const applicationDisplayName = await ctx.prompt("Application display name:");
|
|
1328
|
+
await createUiModuleService(
|
|
1329
|
+
{ applicationName, applicationDisplayName },
|
|
1330
|
+
ctx
|
|
1331
|
+
);
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1035
1334
|
// src/controllers/ui/registry.ts
|
|
1036
1335
|
var uiControllers = /* @__PURE__ */ new Map([
|
|
1037
1336
|
[CREATE_APPLICATION_COMMAND_NAME, createApplicationUiController],
|
|
1038
1337
|
[INIT_COMMAND_NAME, initUiController],
|
|
1039
|
-
[CONFIGURE_IDP_COMMAND_NAME, configureIdpUiController]
|
|
1338
|
+
[CONFIGURE_IDP_COMMAND_NAME, configureIdpUiController],
|
|
1339
|
+
[CREATE_SERVICE_MODULE_COMMAND_NAME, createServiceModuleUiController],
|
|
1340
|
+
[CREATE_UI_MODULE_COMMAND_NAME, createUiModuleUiController]
|
|
1040
1341
|
]);
|
|
1041
1342
|
|
|
1042
1343
|
// src/hooks/use-command-runner.ts
|
|
@@ -1122,7 +1423,7 @@ function useCommandRunner({ appendStaticItem, setState }) {
|
|
|
1122
1423
|
}
|
|
1123
1424
|
|
|
1124
1425
|
// src/app.tsx
|
|
1125
|
-
import { jsx as
|
|
1426
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1126
1427
|
var require2 = createRequire(import.meta.url);
|
|
1127
1428
|
var { version } = require2("../package.json");
|
|
1128
1429
|
function App() {
|
|
@@ -1209,17 +1510,17 @@ function App() {
|
|
|
1209
1510
|
function renderActiveArea() {
|
|
1210
1511
|
switch (state) {
|
|
1211
1512
|
case APP_STATE.EXECUTING:
|
|
1212
|
-
return /* @__PURE__ */
|
|
1213
|
-
/* @__PURE__ */
|
|
1214
|
-
/* @__PURE__ */
|
|
1513
|
+
return /* @__PURE__ */ jsxs6(Box5, { flexDirection: "row", gap: 1, children: [
|
|
1514
|
+
/* @__PURE__ */ jsx7(Spinner, { label: "Running\u2026" }),
|
|
1515
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "(esc to cancel)" })
|
|
1215
1516
|
] });
|
|
1216
1517
|
case APP_STATE.PROMPTING:
|
|
1217
|
-
return /* @__PURE__ */
|
|
1218
|
-
/* @__PURE__ */
|
|
1219
|
-
/* @__PURE__ */
|
|
1518
|
+
return /* @__PURE__ */ jsxs6(Box5, { flexDirection: "column", children: [
|
|
1519
|
+
/* @__PURE__ */ jsx7(Text7, { color: "cyan", children: promptMessage }),
|
|
1520
|
+
/* @__PURE__ */ jsx7(Prompt, { value: promptValue, onChange: setPromptValue, onSubmit: handlePromptSubmit })
|
|
1220
1521
|
] });
|
|
1221
1522
|
default:
|
|
1222
|
-
return /* @__PURE__ */
|
|
1523
|
+
return /* @__PURE__ */ jsx7(
|
|
1223
1524
|
Prompt,
|
|
1224
1525
|
{
|
|
1225
1526
|
value: inputValue,
|
|
@@ -1232,17 +1533,14 @@ function App() {
|
|
|
1232
1533
|
);
|
|
1233
1534
|
}
|
|
1234
1535
|
}
|
|
1235
|
-
return /* @__PURE__ */
|
|
1236
|
-
/* @__PURE__ */
|
|
1536
|
+
return /* @__PURE__ */ jsxs6(Box5, { flexDirection: "column", children: [
|
|
1537
|
+
/* @__PURE__ */ jsx7(ScrollbackHistory, { items: staticItems, version }),
|
|
1237
1538
|
renderActiveArea(),
|
|
1238
|
-
state === APP_STATE.PALETTE && /* @__PURE__ */
|
|
1539
|
+
state === APP_STATE.PALETTE && /* @__PURE__ */ jsx7(CommandPalette, { commands: filteredCommands, selectedIndex })
|
|
1239
1540
|
] });
|
|
1240
1541
|
}
|
|
1241
1542
|
|
|
1242
1543
|
// src/controllers/cli/create-application.cli-controller.ts
|
|
1243
|
-
import { readFile as readFile9 } from "fs/promises";
|
|
1244
|
-
import { join as join19 } from "path";
|
|
1245
|
-
import { cwd as cwd6 } from "process";
|
|
1246
1544
|
async function createApplicationCliController(args2) {
|
|
1247
1545
|
if (!await isPlatformInitialized()) {
|
|
1248
1546
|
console.error("Error: Cannot create an application \u2014 no platform initialized in this directory.");
|
|
@@ -1266,19 +1564,12 @@ async function createApplicationCliController(args2) {
|
|
|
1266
1564
|
let organizationName;
|
|
1267
1565
|
let platformName;
|
|
1268
1566
|
try {
|
|
1269
|
-
const
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
const scopeMatch = corePackageJson.name.match(/^@([^/]+)\//);
|
|
1273
|
-
organizationName = scopeMatch?.[1];
|
|
1274
|
-
const rawName = corePackageJson.name.replace(/^@[^/]+\//, "").replace(/-core$/, "");
|
|
1275
|
-
platformName = rawName;
|
|
1276
|
-
if (!organizationName || !platformName) {
|
|
1277
|
-
throw new Error(`Could not parse organization/platform from package name: "${corePackageJson.name}"`);
|
|
1278
|
-
}
|
|
1567
|
+
const manifest = await readPlatformManifest();
|
|
1568
|
+
organizationName = manifest.organizationName;
|
|
1569
|
+
platformName = manifest.platformName;
|
|
1279
1570
|
} catch (err) {
|
|
1280
1571
|
const message = err instanceof Error ? err.message : String(err);
|
|
1281
|
-
console.error(`Error: Could not read
|
|
1572
|
+
console.error(`Error: Could not read .platform.json \u2014 ${message}`);
|
|
1282
1573
|
process.exit(1);
|
|
1283
1574
|
}
|
|
1284
1575
|
await createApplicationService(
|
|
@@ -1319,9 +1610,9 @@ async function configureIdpCliController(args2) {
|
|
|
1319
1610
|
console.error("Error: Cannot configure an IDP \u2014 no platform initialized in this directory.");
|
|
1320
1611
|
process.exit(1);
|
|
1321
1612
|
}
|
|
1322
|
-
const { providerType,
|
|
1323
|
-
if (!providerType || !
|
|
1324
|
-
logger.log("Error: Missing required arguments: providerType,
|
|
1613
|
+
const { providerType, issuer, clientId, clientSecret } = args2;
|
|
1614
|
+
if (!providerType || !issuer || !clientId || !clientSecret) {
|
|
1615
|
+
logger.log("Error: Missing required arguments: providerType, issuer, clientId, clientSecret");
|
|
1325
1616
|
process.exit(1);
|
|
1326
1617
|
}
|
|
1327
1618
|
const provider = idpProviderRegistry.get(providerType);
|
|
@@ -1338,19 +1629,22 @@ async function configureIdpCliController(args2) {
|
|
|
1338
1629
|
}
|
|
1339
1630
|
extras[field.key] = value;
|
|
1340
1631
|
}
|
|
1632
|
+
const name = providerType;
|
|
1341
1633
|
await configureIdpService({ providerType, name, issuer, clientId, clientSecret, extras }, logger);
|
|
1342
1634
|
}
|
|
1343
1635
|
|
|
1344
1636
|
// src/utils/run-npm-script.ts
|
|
1345
1637
|
import { spawn } from "child_process";
|
|
1346
|
-
import { access as
|
|
1347
|
-
import { join as
|
|
1638
|
+
import { access as access4 } from "fs/promises";
|
|
1639
|
+
import { join as join21 } from "path";
|
|
1348
1640
|
async function runNpmScript(scriptName, logger, signal) {
|
|
1349
|
-
|
|
1641
|
+
let localDir;
|
|
1350
1642
|
try {
|
|
1351
|
-
await
|
|
1643
|
+
const { platformName } = await readPlatformManifest();
|
|
1644
|
+
localDir = join21(process.cwd(), `${platformName}-local`);
|
|
1645
|
+
await access4(localDir);
|
|
1352
1646
|
} catch {
|
|
1353
|
-
logger.log(`Error:
|
|
1647
|
+
logger.log(`Error: No initialized platform found in this directory. Run "platform init" first.`);
|
|
1354
1648
|
return;
|
|
1355
1649
|
}
|
|
1356
1650
|
return new Promise((resolve) => {
|
|
@@ -1455,11 +1749,59 @@ function createLocalScriptCliController(scriptName) {
|
|
|
1455
1749
|
};
|
|
1456
1750
|
}
|
|
1457
1751
|
|
|
1752
|
+
// src/controllers/cli/create-service-module.cli-controller.ts
|
|
1753
|
+
async function createServiceModuleCliController(args2) {
|
|
1754
|
+
if (!await isPlatformInitialized()) {
|
|
1755
|
+
console.error("Error: Cannot create a service module \u2014 no platform initialized in this directory.");
|
|
1756
|
+
process.exit(1);
|
|
1757
|
+
}
|
|
1758
|
+
const { applicationName, serviceName, serviceDisplayName } = args2;
|
|
1759
|
+
if (!applicationName || !serviceName || !serviceDisplayName) {
|
|
1760
|
+
console.error("Error: applicationName, serviceName, and serviceDisplayName are required.");
|
|
1761
|
+
process.exit(1);
|
|
1762
|
+
}
|
|
1763
|
+
if (!/^[a-z0-9-]+$/.test(applicationName)) {
|
|
1764
|
+
console.error(`Error: Application name "${applicationName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
|
|
1765
|
+
process.exit(1);
|
|
1766
|
+
}
|
|
1767
|
+
if (!/^[a-z0-9-]+$/.test(serviceName)) {
|
|
1768
|
+
console.error(`Error: Service name "${serviceName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
|
|
1769
|
+
process.exit(1);
|
|
1770
|
+
}
|
|
1771
|
+
await createServiceModuleService(
|
|
1772
|
+
{ applicationName, serviceName, serviceDisplayName },
|
|
1773
|
+
{ log: console.log }
|
|
1774
|
+
);
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
// src/controllers/cli/create-ui-module.cli-controller.ts
|
|
1778
|
+
async function createUiModuleCliController(args2) {
|
|
1779
|
+
if (!await isPlatformInitialized()) {
|
|
1780
|
+
console.error("Error: Cannot create a UI module \u2014 no platform initialized in this directory.");
|
|
1781
|
+
process.exit(1);
|
|
1782
|
+
}
|
|
1783
|
+
const { applicationName, applicationDisplayName } = args2;
|
|
1784
|
+
if (!applicationName || !applicationDisplayName) {
|
|
1785
|
+
console.error("Error: applicationName and applicationDisplayName are required.");
|
|
1786
|
+
process.exit(1);
|
|
1787
|
+
}
|
|
1788
|
+
if (!/^[a-z0-9-]+$/.test(applicationName)) {
|
|
1789
|
+
console.error(`Error: Application name "${applicationName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
|
|
1790
|
+
process.exit(1);
|
|
1791
|
+
}
|
|
1792
|
+
await createUiModuleService(
|
|
1793
|
+
{ applicationName, applicationDisplayName },
|
|
1794
|
+
{ log: console.log }
|
|
1795
|
+
);
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1458
1798
|
// src/controllers/cli/registry.ts
|
|
1459
1799
|
var cliControllers = /* @__PURE__ */ new Map([
|
|
1460
1800
|
[CREATE_APPLICATION_COMMAND_NAME, createApplicationCliController],
|
|
1461
1801
|
[INIT_COMMAND_NAME, initCliController],
|
|
1462
1802
|
[CONFIGURE_IDP_COMMAND_NAME, configureIdpCliController],
|
|
1803
|
+
[CREATE_SERVICE_MODULE_COMMAND_NAME, createServiceModuleCliController],
|
|
1804
|
+
[CREATE_UI_MODULE_COMMAND_NAME, createUiModuleCliController],
|
|
1463
1805
|
[INSTALL_COMMAND_NAME, createLocalScriptCliController(INSTALL_COMMAND_NAME)],
|
|
1464
1806
|
[BUILD_COMMAND_NAME, createLocalScriptCliController(BUILD_COMMAND_NAME)],
|
|
1465
1807
|
[START_COMMAND_NAME, createLocalScriptCliController(START_COMMAND_NAME)],
|
|
@@ -1482,10 +1824,10 @@ function parseKeyValueArgs(args2) {
|
|
|
1482
1824
|
}
|
|
1483
1825
|
|
|
1484
1826
|
// src/index.tsx
|
|
1485
|
-
import { jsx as
|
|
1827
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
1486
1828
|
var args = process.argv.slice(2);
|
|
1487
1829
|
if (args.length === 0) {
|
|
1488
|
-
render(/* @__PURE__ */
|
|
1830
|
+
render(/* @__PURE__ */ jsx8(App, {}));
|
|
1489
1831
|
} else {
|
|
1490
1832
|
const commandName = args[0];
|
|
1491
1833
|
const params = parseKeyValueArgs(args.slice(1));
|