@bluealba/platform-cli 0.2.1 → 0.3.1

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