@bluealba/platform-cli 0.3.0-feature-platform-cli-222 → 0.3.1-develop-233

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