@bluealba/platform-cli 0.2.0 → 0.3.0-feature-platform-cli-211

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 (22) hide show
  1. package/dist/index.js +776 -163
  2. package/package.json +5 -5
  3. package/templates/bootstrap-service-template/package.json +2 -2
  4. package/templates/customization-ui-module-template/package.json +2 -2
  5. package/templates/customization-ui-module-template/src/root.component.tsx +2 -11
  6. package/templates/platform-init-template/{local → core/local}/core-docker-compose.yml +4 -4
  7. package/templates/platform-init-template/{local → core/local}/platform-docker-compose.yml +1 -1
  8. package/templates/react-ui-module-template/package.json +1 -1
  9. package/templates/customization-ui-module-template/src/components/ExpandedNavbarLogo.tsx +0 -62
  10. package/templates/customization-ui-module-template/src/components/ExtensionPoints/index.tsx +0 -28
  11. package/templates/customization-ui-module-template/src/components/Logo.tsx +0 -55
  12. package/templates/customization-ui-module-template/src/components/SplashLogo.tsx +0 -52
  13. package/templates/customization-ui-module-template/src/hooks/useDynamicStyleSheet.ts +0 -18
  14. package/templates/platform-init-template/local/docker-compose.yml +0 -3
  15. package/templates/platform-init-template/local/package.json +0 -18
  16. package/templates/platform-init-template/local/scripts/build.sh +0 -18
  17. package/templates/platform-init-template/local/scripts/install.sh +0 -18
  18. /package/templates/platform-init-template/{local → core/local}/.env.example +0 -0
  19. /package/templates/platform-init-template/{local → core/local}/environment/pae-nestjs-gateway-service.env +0 -0
  20. /package/templates/platform-init-template/{local → core/local}/nginx.conf +0 -0
  21. /package/templates/platform-init-template/{local → core/local}/ssl/cert.pem +0 -0
  22. /package/templates/platform-init-template/{local → core/local}/ssl/key.pem +0 -0
package/dist/index.js CHANGED
@@ -61,11 +61,10 @@ import React2 from "react";
61
61
  import { Box as Box3, Text as Text3 } from "ink";
62
62
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
63
63
  var ASCII_ART = [
64
- " \\ | / ",
65
- " \\ | / ",
66
- " ----*---- ",
67
- " / | \\ ",
68
- " / | \\ "
64
+ "|---. .---|",
65
+ "| \\/ |",
66
+ "| /\\ |",
67
+ "|---' '---|"
69
68
  ];
70
69
  var WelcomeBanner = React2.memo(function WelcomeBanner2({ version: version2 }) {
71
70
  return /* @__PURE__ */ jsxs3(Box3, { borderStyle: "round", flexDirection: "column", paddingX: 1, paddingY: 1, children: [
@@ -125,8 +124,7 @@ function Spinner({ label }) {
125
124
  import { Fzf } from "fzf";
126
125
 
127
126
  // src/commands/create-application/create-application.command.ts
128
- import { join as join9 } from "path";
129
- import { cwd } from "process";
127
+ import { join as join10, resolve } from "path";
130
128
  import { mkdir as mkdir2 } from "fs/promises";
131
129
 
132
130
  // src/commands/create-application/scaffold-application-monorepo.ts
@@ -358,10 +356,10 @@ function buildUiModuleEntry(organizationName, applicationName, applicationDispla
358
356
 
359
357
  // src/commands/create-application/create-docker-compose.ts
360
358
  import { writeFile as writeFile3 } from "fs/promises";
361
- function buildBootstrapBlock(applicationName) {
359
+ function buildBootstrapBlock(platformName, applicationName) {
362
360
  return ` ${applicationName}-bootstrap-service:
363
361
  build:
364
- context: ../${applicationName}/services/${applicationName}-bootstrap-service
362
+ context: ../../${platformName}-${applicationName}/services/${applicationName}-bootstrap-service
365
363
  dockerfile: Dockerfile.development
366
364
  environment:
367
365
  - NODE_TLS_REJECT_UNAUTHORIZED=0
@@ -371,27 +369,27 @@ function buildBootstrapBlock(applicationName) {
371
369
  - GATEWAY_SERVICE_URL=\${PAE_GATEWAY_URL}
372
370
  - SERVICE_ACCESS_SECRET=\${PAE_GATEWAY_SERVICE_ACCESS_SECRET}
373
371
  volumes:
374
- - \${PWD}/../:/app/out
372
+ - \${PWD}/:/app/out
375
373
  depends_on:
376
374
  pae-nestjs-gateway-service:
377
375
  condition: service_healthy
378
376
  `;
379
377
  }
380
- function buildUiBlock(applicationName, uiPort) {
378
+ function buildUiBlock(platformName, applicationName, uiPort) {
381
379
  return ` ${applicationName}-ui:
382
380
  build:
383
- context: ../${applicationName}/ui/${applicationName}-ui
381
+ context: ../../${platformName}-${applicationName}/ui/${applicationName}-ui
384
382
  dockerfile: Dockerfile.development
385
383
  ports:
386
384
  - ${uiPort}:80
387
385
  volumes:
388
- - \${PWD}/../:/app/out
386
+ - \${PWD}/:/app/out
389
387
  `;
390
388
  }
391
- function buildBackendBlock(applicationName, servicePort) {
389
+ function buildBackendBlock(platformName, applicationName, servicePort) {
392
390
  return ` ${applicationName}-service:
393
391
  build:
394
- context: ../${applicationName}/services/${applicationName}-service
392
+ context: ../../${platformName}-${applicationName}/services/${applicationName}-service
395
393
  dockerfile: Dockerfile.development
396
394
  args:
397
395
  - BA_NPM_AUTH_TOKEN=$BA_NPM_AUTH_TOKEN
@@ -400,18 +398,18 @@ function buildBackendBlock(applicationName, servicePort) {
400
398
  environment:
401
399
  - GATEWAY_URL=\${PAE_GATEWAY_URL}
402
400
  volumes:
403
- - \${PWD}/../:/app/out
401
+ - \${PWD}/:/app/out
404
402
  `;
405
403
  }
406
- async function createDockerCompose(dockerComposePath, applicationName, hasUserInterface, hasBackendService, uiPort, servicePort, logger) {
407
- const blocks = ["services:\n", buildBootstrapBlock(applicationName)];
404
+ async function createDockerCompose(dockerComposePath, applicationName, platformName, hasUserInterface, hasBackendService, uiPort, servicePort, logger) {
405
+ const blocks = ["services:\n", buildBootstrapBlock(platformName, applicationName)];
408
406
  if (hasUserInterface) {
409
- blocks.push(buildUiBlock(applicationName, uiPort));
407
+ blocks.push(buildUiBlock(platformName, applicationName, uiPort));
410
408
  }
411
409
  if (hasBackendService) {
412
- blocks.push(buildBackendBlock(applicationName, servicePort));
410
+ blocks.push(buildBackendBlock(platformName, applicationName, servicePort));
413
411
  }
414
- const content = blocks.join("\n");
412
+ const content = blocks.join("\n") + "\n";
415
413
  try {
416
414
  await writeFile3(dockerComposePath, content, "utf-8");
417
415
  logger.log(`Created docker-compose: ${dockerComposePath}`);
@@ -421,32 +419,18 @@ async function createDockerCompose(dockerComposePath, applicationName, hasUserIn
421
419
  }
422
420
  }
423
421
 
424
- // src/commands/create-application/update-root-docker-compose.ts
425
- import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
426
- async function updateRootDockerCompose(rootDockerComposePath, applicationName, logger) {
427
- try {
428
- const existing = await readFile3(rootDockerComposePath, "utf-8");
429
- const includeLine = ` - path: ./${applicationName}-docker-compose.yml
430
- `;
431
- await writeFile4(rootDockerComposePath, existing + includeLine, "utf-8");
432
- logger.log(`Updated root docker-compose: ${rootDockerComposePath}`);
433
- } catch (err) {
434
- const message = err instanceof Error ? err.message : String(err);
435
- logger.log(`Warning: Could not update root docker-compose \u2014 ${message}`);
436
- }
437
- }
438
-
439
422
  // src/commands/create-application/port-allocator.ts
440
423
  import { join as join8 } from "path";
441
- import { readdir as readdir2, readFile as readFile4 } from "fs/promises";
442
- async function getNextAvailablePort(localDir) {
424
+ import { readdir as readdir2, readFile as readFile3 } from "fs/promises";
425
+ async function getNextAvailablePort(rootDir) {
443
426
  const allPorts = [];
427
+ const localDir = join8(rootDir, "core", "local");
444
428
  try {
445
429
  const files = await readdir2(localDir);
446
- const dockerComposeFiles = files.filter((f) => f.endsWith("-docker-compose.yml"));
447
- for (const file of dockerComposeFiles) {
430
+ const ymlFiles = files.filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
431
+ for (const file of ymlFiles) {
448
432
  try {
449
- const content = await readFile4(join8(localDir, file), "utf-8");
433
+ const content = await readFile3(join8(localDir, file), "utf-8");
450
434
  const regex = /^\s*-\s*"?(\d+):\d+"?\s*$/gm;
451
435
  let match;
452
436
  while ((match = regex.exec(content)) !== null) {
@@ -465,6 +449,71 @@ function formatError(err) {
465
449
  return err instanceof Error ? err.message : String(err);
466
450
  }
467
451
 
452
+ // src/utils/platform-check.ts
453
+ import { cwd as cwd2 } from "process";
454
+ import { dirname as dirname7 } from "path";
455
+
456
+ // 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);
463
+ }
464
+ async function readManifest(rootDir = cwd()) {
465
+ const content = await readFile4(manifestPath(rootDir), "utf-8");
466
+ return JSON.parse(content);
467
+ }
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
+ }
478
+ }
479
+ function createInitialManifest(params) {
480
+ const { organizationName, platformName, platformDisplayName } = params;
481
+ return {
482
+ version: "1",
483
+ product: {
484
+ name: platformName,
485
+ displayName: platformDisplayName,
486
+ organization: organizationName,
487
+ scope: `@${organizationName}`
488
+ },
489
+ applications: []
490
+ };
491
+ }
492
+ function addApplicationToManifest(manifest, app) {
493
+ return {
494
+ ...manifest,
495
+ applications: [...manifest.applications, app]
496
+ };
497
+ }
498
+
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
+
468
517
  // src/commands/create-application/create-application.command.ts
469
518
  var CREATE_APPLICATION_COMMAND_NAME = "create-application";
470
519
  var createApplicationCommand = {
@@ -473,18 +522,28 @@ var createApplicationCommand = {
473
522
  };
474
523
  async function createApplication(params, logger) {
475
524
  const {
476
- organizationName,
477
- platformName,
478
525
  applicationName,
479
526
  applicationDisplayName,
480
527
  applicationDescription,
481
528
  hasUserInterface,
482
529
  hasBackendService
483
530
  } = params;
484
- const rootDir = cwd();
485
- const applicationDir = join9(rootDir, applicationName);
486
- const bootstrapServiceDir = join9(applicationDir, "services", `${applicationName}-bootstrap-service`);
487
- const localDir = join9(rootDir, "local");
531
+ const rootDir = await findRootDir();
532
+ if (!rootDir) {
533
+ logger.log("Error: Cannot create an application \u2014 no platform initialized in this directory.");
534
+ return;
535
+ }
536
+ let manifest;
537
+ try {
538
+ manifest = await readManifest(rootDir);
539
+ } catch (err) {
540
+ logger.log(`Error: Could not read product manifest \u2014 ${formatError(err)}`);
541
+ return;
542
+ }
543
+ const { organization: organizationName, name: platformName } = manifest.product;
544
+ const localPath = `../${platformName}-${applicationName}`;
545
+ const applicationDir = resolve(join10(rootDir, "core"), localPath);
546
+ const bootstrapServiceDir = join10(applicationDir, "services", `${applicationName}-bootstrap-service`);
488
547
  logger.log(`Creating application monorepo "${applicationName}"...`);
489
548
  try {
490
549
  await scaffoldApplicationMonorepo(applicationDir, organizationName, platformName, applicationName, logger);
@@ -492,10 +551,10 @@ async function createApplication(params, logger) {
492
551
  logger.log(`Error: Could not scaffold application monorepo \u2014 ${formatError(err)}`);
493
552
  return;
494
553
  }
495
- await mkdir2(join9(applicationDir, "services"), { recursive: true });
496
- await mkdir2(join9(applicationDir, "ui"), { recursive: true });
554
+ await mkdir2(join10(applicationDir, "services"), { recursive: true });
555
+ await mkdir2(join10(applicationDir, "ui"), { recursive: true });
497
556
  logger.log(`Creating bootstrap service "${applicationName}-bootstrap-service"...`);
498
- const bootstrapServiceDir_var = `${applicationName}/services`;
557
+ const bootstrapServiceDir_var = `${platformName}-${applicationName}/services`;
499
558
  try {
500
559
  await scaffoldBootstrap(bootstrapServiceDir, organizationName, applicationName, bootstrapServiceDir_var, logger);
501
560
  } catch (err) {
@@ -526,8 +585,8 @@ async function createApplication(params, logger) {
526
585
  );
527
586
  }
528
587
  if (hasUserInterface) {
529
- const uiDir = join9(applicationDir, "ui", `${applicationName}-ui`);
530
- const uiBaseDir = `${applicationName}/ui`;
588
+ const uiDir = join10(applicationDir, "ui", `${applicationName}-ui`);
589
+ const uiBaseDir = `${platformName}-${applicationName}/ui`;
531
590
  try {
532
591
  await scaffoldUiModule(uiDir, organizationName, platformName, applicationName, applicationDisplayName, uiBaseDir, logger);
533
592
  } catch (err) {
@@ -536,8 +595,8 @@ async function createApplication(params, logger) {
536
595
  }
537
596
  }
538
597
  if (hasBackendService) {
539
- const serviceDir = join9(applicationDir, "services", `${applicationName}-service`);
540
- const serviceBaseDir = `${applicationName}/services`;
598
+ const serviceDir = join10(applicationDir, "services", `${applicationName}-service`);
599
+ const serviceBaseDir = `${platformName}-${applicationName}/services`;
541
600
  try {
542
601
  await scaffoldNestjsService(serviceDir, organizationName, applicationName, applicationDisplayName, serviceBaseDir, logger);
543
602
  } catch (err) {
@@ -545,27 +604,39 @@ async function createApplication(params, logger) {
545
604
  return;
546
605
  }
547
606
  }
548
- const dockerComposePath = join9(localDir, `${applicationName}-docker-compose.yml`);
549
- const basePort = await getNextAvailablePort(localDir);
607
+ const dockerComposePath = join10(rootDir, "core", "local", `${applicationName}-docker-compose.yml`);
608
+ const basePort = await getNextAvailablePort(rootDir);
550
609
  const uiPort = hasUserInterface ? basePort : 0;
551
610
  const servicePort = hasBackendService ? hasUserInterface ? basePort + 1 : basePort : 0;
552
611
  await createDockerCompose(
553
612
  dockerComposePath,
554
613
  applicationName,
614
+ platformName,
555
615
  hasUserInterface,
556
616
  hasBackendService,
557
617
  uiPort,
558
618
  servicePort,
559
619
  logger
560
620
  );
561
- const rootDockerComposePath = join9(localDir, "docker-compose.yml");
562
- await updateRootDockerCompose(rootDockerComposePath, applicationName, logger);
621
+ const updatedManifest = addApplicationToManifest(manifest, {
622
+ name: applicationName,
623
+ displayName: applicationDisplayName,
624
+ description: applicationDescription,
625
+ localPath,
626
+ repository: null
627
+ });
628
+ try {
629
+ await writeManifest(updatedManifest, rootDir);
630
+ logger.log(`Updated product manifest with application "${applicationName}".`);
631
+ } catch (err) {
632
+ logger.log(`Warning: Could not update product manifest \u2014 ${formatError(err)}`);
633
+ }
563
634
  logger.log(`Done! Application "${applicationName}" created at ${applicationDir}`);
564
635
  }
565
636
 
566
637
  // src/commands/init/init.command.ts
567
- import { join as join15 } from "path";
568
- import { cwd as cwd2 } from "process";
638
+ import { join as join16 } from "path";
639
+ import { cwd as cwd3 } from "process";
569
640
 
570
641
  // src/utils/string.ts
571
642
  function camelize(name) {
@@ -574,9 +645,9 @@ function camelize(name) {
574
645
 
575
646
  // src/commands/init/scaffold-platform.ts
576
647
  import { fileURLToPath as fileURLToPath6 } from "url";
577
- import { join as join10, dirname as dirname7 } from "path";
578
- var templateDir = join10(
579
- dirname7(fileURLToPath6(import.meta.url)),
648
+ import { join as join11, dirname as dirname8 } from "path";
649
+ var templateDir = join11(
650
+ dirname8(fileURLToPath6(import.meta.url)),
580
651
  "..",
581
652
  "templates",
582
653
  "platform-init-template"
@@ -587,9 +658,9 @@ async function scaffoldPlatform(outputDir, variables, logger) {
587
658
 
588
659
  // src/commands/init/scaffold-platform-bootstrap.ts
589
660
  import { fileURLToPath as fileURLToPath7 } from "url";
590
- import { join as join11, dirname as dirname8 } from "path";
591
- var templateDir2 = join11(
592
- dirname8(fileURLToPath7(import.meta.url)),
661
+ import { join as join12, dirname as dirname9 } from "path";
662
+ var templateDir2 = join12(
663
+ dirname9(fileURLToPath7(import.meta.url)),
593
664
  "..",
594
665
  "templates",
595
666
  "bootstrap-service-template"
@@ -600,9 +671,9 @@ async function scaffoldPlatformBootstrap(outputDir, variables, logger) {
600
671
 
601
672
  // src/commands/init/scaffold-customization-ui.ts
602
673
  import { fileURLToPath as fileURLToPath8 } from "url";
603
- import { join as join12, dirname as dirname9 } from "path";
604
- var templateDir3 = join12(
605
- dirname9(fileURLToPath8(import.meta.url)),
674
+ import { join as join13, dirname as dirname10 } from "path";
675
+ var templateDir3 = join13(
676
+ dirname10(fileURLToPath8(import.meta.url)),
606
677
  "..",
607
678
  "templates",
608
679
  "customization-ui-module-template"
@@ -612,10 +683,10 @@ async function scaffoldCustomizationUi(outputDir, variables, logger) {
612
683
  }
613
684
 
614
685
  // src/commands/init/register-customization-module.ts
615
- import { join as join13 } from "path";
686
+ import { join as join14 } from "path";
616
687
  import { readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
617
688
  async function registerCustomizationModule(bootstrapServiceDir, organizationName, logger) {
618
- const modulesJsonPath = join13(bootstrapServiceDir, "src", "data", "platform", "modules.json");
689
+ const modulesJsonPath = join14(bootstrapServiceDir, "src", "data", "platform", "modules.json");
619
690
  const entry = {
620
691
  name: `@${organizationName}/platform-customization-ui`,
621
692
  displayName: "Platform Customization UI",
@@ -638,7 +709,7 @@ async function registerCustomizationModule(bootstrapServiceDir, organizationName
638
709
 
639
710
  // src/commands/init/generate-local-env.ts
640
711
  import { readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
641
- import { join as join14 } from "path";
712
+ import { join as join15 } from "path";
642
713
 
643
714
  // src/utils/random.ts
644
715
  import { randomBytes } from "crypto";
@@ -648,9 +719,9 @@ function generateRandomSecret() {
648
719
 
649
720
  // src/commands/init/generate-local-env.ts
650
721
  async function generateLocalEnv(outputDir, logger) {
651
- const examplePath = join14(outputDir, "local", ".env.example");
652
- const envPath = join14(outputDir, "local", ".env");
653
- logger.log("Generating local/.env with random secrets...");
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...");
654
725
  const content = await readFile6(examplePath, "utf-8");
655
726
  const result = content.replace(/^(PAE_AUTH_JWT_SECRET|PAE_GATEWAY_SERVICE_ACCESS_SECRET|PAE_DB_PASSWORD)=$/gm, (_, key) => {
656
727
  return `${key}=${generateRandomSecret()}`;
@@ -665,9 +736,13 @@ var initCommand = {
665
736
  description: "Initialize a new platform"
666
737
  };
667
738
  async function init(params, logger) {
739
+ if (await isPlatformInitialized()) {
740
+ logger.log("Error: Cannot initialize a new platform \u2014 a platform is already initialized in this directory.");
741
+ return;
742
+ }
668
743
  const { organizationName, platformName, platformDisplayName } = params;
669
744
  const platformTitle = camelize(platformName);
670
- const outputDir = cwd2();
745
+ const outputDir = cwd3();
671
746
  logger.log(`Initializing ${platformTitle} platform for @${organizationName}...`);
672
747
  const variables = {
673
748
  organizationName,
@@ -686,12 +761,20 @@ async function init(params, logger) {
686
761
  try {
687
762
  await generateLocalEnv(outputDir, logger);
688
763
  } catch (err) {
689
- logger.log(`Error: Could not generate local/.env \u2014 ${formatError(err)}`);
764
+ logger.log(`Error: Could not generate core/local/.env \u2014 ${formatError(err)}`);
765
+ return;
766
+ }
767
+ try {
768
+ const manifest = createInitialManifest({ organizationName, platformName, platformDisplayName });
769
+ await writeManifest(manifest, outputDir);
770
+ logger.log("Created product manifest: core/product.manifest.json");
771
+ } catch (err) {
772
+ logger.log(`Error: Could not write product manifest \u2014 ${formatError(err)}`);
690
773
  return;
691
774
  }
692
775
  try {
693
776
  await scaffoldPlatformBootstrap(
694
- join15(outputDir, "core", "services", "platform-bootstrap-service"),
777
+ join16(outputDir, "core", "services", "platform-bootstrap-service"),
695
778
  variables,
696
779
  logger
697
780
  );
@@ -701,7 +784,7 @@ async function init(params, logger) {
701
784
  }
702
785
  try {
703
786
  await scaffoldCustomizationUi(
704
- join15(outputDir, "core", "ui", "platform-customization-ui"),
787
+ join16(outputDir, "core", "ui", "platform-customization-ui"),
705
788
  variables,
706
789
  logger
707
790
  );
@@ -711,7 +794,7 @@ async function init(params, logger) {
711
794
  }
712
795
  try {
713
796
  await registerCustomizationModule(
714
- join15(outputDir, "core", "services", "platform-bootstrap-service"),
797
+ join16(outputDir, "core", "services", "platform-bootstrap-service"),
715
798
  organizationName,
716
799
  logger
717
800
  );
@@ -723,8 +806,7 @@ async function init(params, logger) {
723
806
  }
724
807
 
725
808
  // src/commands/configure-idp/configure-idp.command.ts
726
- import { join as join16 } from "path";
727
- import { cwd as cwd3 } from "process";
809
+ import { join as join17 } from "path";
728
810
  import { fetch as undiciFetch, Agent } from "undici";
729
811
 
730
812
  // src/utils/env-reader.ts
@@ -802,7 +884,12 @@ var configureIdpCommand = {
802
884
  description: "Configure an Identity Provider (IDP) in the gateway"
803
885
  };
804
886
  async function configureIdp(params, logger) {
805
- const envPath = join16(cwd3(), "local", ".env");
887
+ const rootDir = await findRootDir();
888
+ if (!rootDir) {
889
+ logger.log("Error: Cannot configure an IDP \u2014 no platform initialized in this directory.");
890
+ return;
891
+ }
892
+ const envPath = join17(rootDir, "core", "local", ".env");
806
893
  let env;
807
894
  try {
808
895
  env = await readEnvFile(envPath);
@@ -813,11 +900,11 @@ async function configureIdp(params, logger) {
813
900
  const gatewayUrl = env.get("PAE_GATEWAY_HOST_URL");
814
901
  const accessSecret = env.get("PAE_GATEWAY_SERVICE_ACCESS_SECRET");
815
902
  if (!gatewayUrl) {
816
- logger.log("Error: PAE_GATEWAY_HOST_URL is not set in local/.env");
903
+ logger.log("Error: PAE_GATEWAY_HOST_URL is not set in core/local/.env");
817
904
  return;
818
905
  }
819
906
  if (!accessSecret) {
820
- logger.log("Error: PAE_GATEWAY_SERVICE_ACCESS_SECRET is not set in local/.env");
907
+ logger.log("Error: PAE_GATEWAY_SERVICE_ACCESS_SECRET is not set in core/local/.env");
821
908
  return;
822
909
  }
823
910
  const provider = idpProviderRegistry.get(params.providerType);
@@ -853,6 +940,225 @@ async function configureIdp(params, logger) {
853
940
  logger.log(`IDP provider "${params.name}" configured successfully.`);
854
941
  }
855
942
 
943
+ // 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";
946
+
947
+ // src/commands/create-service-module/scaffold-service-module.ts
948
+ import { fileURLToPath as fileURLToPath9 } from "url";
949
+ import { join as join18, dirname as dirname11 } from "path";
950
+ var nestjsServiceModuleTemplateDir2 = join18(
951
+ dirname11(fileURLToPath9(import.meta.url)),
952
+ "..",
953
+ "templates",
954
+ "nestjs-service-module-template"
955
+ );
956
+ async function scaffoldServiceModule(serviceDir, organizationName, serviceName, serviceDisplayName, serviceBaseDir, logger) {
957
+ logger.log(`Creating NestJS service "${serviceName}"...`);
958
+ await applyTemplate(
959
+ {
960
+ templateDir: nestjsServiceModuleTemplateDir2,
961
+ outputDir: serviceDir,
962
+ variables: { organizationName, serviceName, serviceDisplayName, serviceBaseDir }
963
+ },
964
+ (message) => logger.log(message)
965
+ );
966
+ logger.log(`Done! Service output: ${serviceDir}`);
967
+ }
968
+
969
+ // src/commands/create-service-module/service-module-entry-builder.ts
970
+ function buildCustomServiceModuleEntry(organizationName, serviceName, serviceDisplayName) {
971
+ return {
972
+ name: `@${organizationName}/${serviceName}`,
973
+ displayName: serviceDisplayName,
974
+ type: "service",
975
+ baseUrl: `/${serviceName}`,
976
+ service: {
977
+ host: serviceName,
978
+ port: 80
979
+ },
980
+ dependsOn: []
981
+ };
982
+ }
983
+
984
+ // src/commands/create-service-module/append-docker-compose.ts
985
+ import { readFile as readFile8, writeFile as writeFile7 } from "fs/promises";
986
+ async function appendServiceToDockerCompose(dockerComposePath, serviceName, platformName, applicationName, port, logger) {
987
+ const block = `
988
+ ${serviceName}:
989
+ build:
990
+ context: ../../${platformName}-${applicationName}/services/${serviceName}
991
+ dockerfile: Dockerfile.development
992
+ args:
993
+ - BA_NPM_AUTH_TOKEN=$BA_NPM_AUTH_TOKEN
994
+ ports:
995
+ - ${port}:80
996
+ environment:
997
+ - GATEWAY_URL=\${PAE_GATEWAY_URL}
998
+ volumes:
999
+ - \${PWD}/:/app/out
1000
+ `;
1001
+ try {
1002
+ const existing = await readFile8(dockerComposePath, "utf-8");
1003
+ await writeFile7(dockerComposePath, existing + block, "utf-8");
1004
+ logger.log(`Updated docker-compose: ${dockerComposePath}`);
1005
+ } catch (err) {
1006
+ const message = err instanceof Error ? err.message : String(err);
1007
+ logger.log(`Warning: Could not update docker-compose \u2014 ${message}`);
1008
+ }
1009
+ }
1010
+
1011
+ // src/commands/create-service-module/create-service-module.command.ts
1012
+ var CREATE_SERVICE_MODULE_COMMAND_NAME = "create-service-module";
1013
+ var createServiceModuleCommand = {
1014
+ name: CREATE_SERVICE_MODULE_COMMAND_NAME,
1015
+ description: "Add a new service module to an existing application"
1016
+ };
1017
+ async function createServiceModule(params, logger) {
1018
+ const { applicationName, serviceName, serviceDisplayName } = params;
1019
+ const rootDir = await findRootDir();
1020
+ if (!rootDir) {
1021
+ logger.log("Error: Cannot create a service module \u2014 no platform initialized in this directory.");
1022
+ return;
1023
+ }
1024
+ let manifest;
1025
+ try {
1026
+ manifest = await readManifest(rootDir);
1027
+ } catch (err) {
1028
+ logger.log(`Error: Could not read product manifest \u2014 ${formatError(err)}`);
1029
+ return;
1030
+ }
1031
+ const { organization: organizationName, name: platformName } = manifest.product;
1032
+ const appEntry = manifest.applications.find((a) => a.name === applicationName);
1033
+ if (!appEntry) {
1034
+ logger.log(`Error: The specified application "${applicationName}" is not registered in the product manifest.`);
1035
+ return;
1036
+ }
1037
+ const applicationDir = resolve2(join19(rootDir, "core"), appEntry.localPath);
1038
+ try {
1039
+ await access2(applicationDir);
1040
+ } catch {
1041
+ logger.log(`Error: The specified application "${applicationName}" does not exist in the platform.`);
1042
+ return;
1043
+ }
1044
+ const fullServiceName = `${serviceName}-service`;
1045
+ const serviceDir = join19(applicationDir, "services", fullServiceName);
1046
+ try {
1047
+ await access2(serviceDir);
1048
+ logger.log(`Error: A service named "${fullServiceName}" already exists in application "${applicationName}".`);
1049
+ return;
1050
+ } catch {
1051
+ }
1052
+ const serviceBaseDir = `${platformName}-${applicationName}/services`;
1053
+ try {
1054
+ await scaffoldServiceModule(
1055
+ serviceDir,
1056
+ organizationName,
1057
+ fullServiceName,
1058
+ serviceDisplayName,
1059
+ serviceBaseDir,
1060
+ logger
1061
+ );
1062
+ } catch (err) {
1063
+ logger.log(`Error: Could not scaffold NestJS service \u2014 ${formatError(err)}`);
1064
+ return;
1065
+ }
1066
+ const bootstrapServiceDir = join19(applicationDir, "services", `${applicationName}-bootstrap-service`);
1067
+ const moduleEntry = buildCustomServiceModuleEntry(organizationName, fullServiceName, serviceDisplayName);
1068
+ await addModuleEntry(bootstrapServiceDir, applicationName, moduleEntry, logger);
1069
+ const dockerComposePath = join19(rootDir, "core", "local", `${applicationName}-docker-compose.yml`);
1070
+ const port = await getNextAvailablePort(rootDir);
1071
+ await appendServiceToDockerCompose(dockerComposePath, fullServiceName, platformName, applicationName, port, logger);
1072
+ logger.log(`Done! Service module "${fullServiceName}" added to application "${applicationName}".`);
1073
+ }
1074
+
1075
+ // 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";
1078
+
1079
+ // src/commands/create-ui-module/append-ui-docker-compose.ts
1080
+ import { readFile as readFile9, writeFile as writeFile8 } from "fs/promises";
1081
+ async function appendUiToDockerCompose(dockerComposePath, platformName, applicationName, port, logger) {
1082
+ const block = `
1083
+ ${applicationName}-ui:
1084
+ build:
1085
+ context: ../../${platformName}-${applicationName}/ui/${applicationName}-ui
1086
+ dockerfile: Dockerfile.development
1087
+ ports:
1088
+ - ${port}:80
1089
+ volumes:
1090
+ - \${PWD}/:/app/out
1091
+ `;
1092
+ try {
1093
+ const existing = await readFile9(dockerComposePath, "utf-8");
1094
+ await writeFile8(dockerComposePath, existing + block, "utf-8");
1095
+ logger.log(`Updated docker-compose: ${dockerComposePath}`);
1096
+ } catch (err) {
1097
+ const message = err instanceof Error ? err.message : String(err);
1098
+ logger.log(`Warning: Could not update docker-compose \u2014 ${message}`);
1099
+ }
1100
+ }
1101
+
1102
+ // src/commands/create-ui-module/create-ui-module.command.ts
1103
+ var CREATE_UI_MODULE_COMMAND_NAME = "create-ui-module";
1104
+ var createUiModuleCommand = {
1105
+ name: CREATE_UI_MODULE_COMMAND_NAME,
1106
+ description: "Add a UI module to an existing application"
1107
+ };
1108
+ async function createUiModule(params, logger) {
1109
+ const { applicationName, applicationDisplayName } = params;
1110
+ const rootDir = await findRootDir();
1111
+ if (!rootDir) {
1112
+ logger.log("Error: Cannot create a UI module \u2014 no platform initialized in this directory.");
1113
+ return;
1114
+ }
1115
+ let manifest;
1116
+ try {
1117
+ manifest = await readManifest(rootDir);
1118
+ } catch (err) {
1119
+ logger.log(`Error: Could not read product manifest \u2014 ${formatError(err)}`);
1120
+ return;
1121
+ }
1122
+ const { organization: organizationName, name: platformName } = manifest.product;
1123
+ const appEntry = manifest.applications.find((a) => a.name === applicationName);
1124
+ if (!appEntry) {
1125
+ logger.log(`Error: The specified application "${applicationName}" is not registered in the product manifest.`);
1126
+ return;
1127
+ }
1128
+ const applicationDir = resolve3(join20(rootDir, "core"), appEntry.localPath);
1129
+ try {
1130
+ await access3(applicationDir);
1131
+ } catch {
1132
+ logger.log(`Error: The specified application "${applicationName}" does not exist in the platform.`);
1133
+ return;
1134
+ }
1135
+ const uiDir = join20(applicationDir, "ui");
1136
+ try {
1137
+ const uiEntries = await readdir3(uiDir);
1138
+ const existingUiModules = uiEntries.filter((e) => e !== ".gitkeep");
1139
+ if (existingUiModules.length > 0) {
1140
+ logger.log(`Error: Currently we support only one UI module per application. Application "${applicationName}" already has a UI module.`);
1141
+ return;
1142
+ }
1143
+ } catch {
1144
+ }
1145
+ const uiOutputDir = join20(uiDir, `${applicationName}-ui`);
1146
+ const uiBaseDir = `${platformName}-${applicationName}/ui`;
1147
+ try {
1148
+ await scaffoldUiModule(uiOutputDir, organizationName, platformName, applicationName, applicationDisplayName, uiBaseDir, logger);
1149
+ } catch (err) {
1150
+ logger.log(`Error: Could not scaffold UI module \u2014 ${formatError(err)}`);
1151
+ return;
1152
+ }
1153
+ const bootstrapServiceDir = join20(applicationDir, "services", `${applicationName}-bootstrap-service`);
1154
+ const moduleEntry = buildUiModuleEntry(organizationName, applicationName, applicationDisplayName);
1155
+ 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}".`);
1160
+ }
1161
+
856
1162
  // src/commands/registry.ts
857
1163
  var CommandRegistry = class {
858
1164
  commands = /* @__PURE__ */ new Map();
@@ -878,6 +1184,8 @@ var registry = new CommandRegistry();
878
1184
  registry.register(createApplicationCommand);
879
1185
  registry.register(initCommand);
880
1186
  registry.register(configureIdpCommand);
1187
+ registry.register(createServiceModuleCommand);
1188
+ registry.register(createUiModuleCommand);
881
1189
 
882
1190
  // src/app-state.ts
883
1191
  var APP_STATE = {
@@ -890,11 +1198,6 @@ var APP_STATE = {
890
1198
  // src/hooks/use-command-runner.ts
891
1199
  import { useState as useState2, useCallback, useRef } from "react";
892
1200
 
893
- // src/controllers/ui/create-application.ui-controller.ts
894
- import { readFile as readFile8 } from "fs/promises";
895
- import { join as join17 } from "path";
896
- import { cwd as cwd4 } from "process";
897
-
898
1201
  // src/services/create-application.service.ts
899
1202
  async function createApplicationService(params, logger) {
900
1203
  await createApplication(params, logger);
@@ -902,22 +1205,8 @@ async function createApplicationService(params, logger) {
902
1205
 
903
1206
  // src/controllers/ui/create-application.ui-controller.ts
904
1207
  async function createApplicationUiController(ctx) {
905
- let organizationName;
906
- let platformName;
907
- try {
908
- const corePackageJson = JSON.parse(
909
- await readFile8(join17(cwd4(), "core", "package.json"), "utf-8")
910
- );
911
- const scopeMatch = corePackageJson.name.match(/^@([^/]+)\//);
912
- organizationName = scopeMatch?.[1];
913
- const rawName = corePackageJson.name.replace(/^@[^/]+\//, "").replace(/-core$/, "");
914
- platformName = rawName;
915
- if (!organizationName || !platformName) {
916
- throw new Error(`Could not parse organization/platform from package name: "${corePackageJson.name}"`);
917
- }
918
- } catch (err) {
919
- const message = err instanceof Error ? err.message : String(err);
920
- ctx.log(`Error: Could not read core/package.json \u2014 ${message}`);
1208
+ if (!await isPlatformInitialized()) {
1209
+ ctx.log("Error: Cannot create an application \u2014 no platform initialized in this directory.");
921
1210
  return;
922
1211
  }
923
1212
  const applicationName = await ctx.prompt("Application name:");
@@ -939,8 +1228,6 @@ async function createApplicationUiController(ctx) {
939
1228
  }
940
1229
  await createApplicationService(
941
1230
  {
942
- organizationName,
943
- platformName,
944
1231
  applicationName,
945
1232
  applicationDisplayName,
946
1233
  applicationDescription,
@@ -958,6 +1245,10 @@ async function initService(params, logger) {
958
1245
 
959
1246
  // src/controllers/ui/init.ui-controller.ts
960
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;
1251
+ }
961
1252
  const organizationName = await ctx.prompt("Organization name:");
962
1253
  const platformName = await ctx.prompt("Platform name:");
963
1254
  const platformDisplayName = await ctx.prompt("Platform display name:");
@@ -971,6 +1262,10 @@ async function configureIdpService(params, logger) {
971
1262
 
972
1263
  // src/controllers/ui/configure-idp.ui-controller.ts
973
1264
  async function configureIdpUiController(ctx) {
1265
+ if (!await isPlatformInitialized()) {
1266
+ ctx.log("Error: Cannot configure an IDP \u2014 no platform initialized in this directory.");
1267
+ return;
1268
+ }
974
1269
  const providers = getAllProviders();
975
1270
  const options = providers.map((p, i) => `${i + 1}: ${p.displayName}`).join(", ");
976
1271
  const selectionInput = await ctx.prompt(`Select IDP type (${options}):`);
@@ -994,11 +1289,64 @@ async function configureIdpUiController(ctx) {
994
1289
  );
995
1290
  }
996
1291
 
1292
+ // src/services/create-service-module.service.ts
1293
+ async function createServiceModuleService(params, logger) {
1294
+ await createServiceModule(params, logger);
1295
+ }
1296
+
1297
+ // src/controllers/ui/create-service-module.ui-controller.ts
1298
+ async function createServiceModuleUiController(ctx) {
1299
+ if (!await isPlatformInitialized()) {
1300
+ ctx.log("Error: Cannot create a service module \u2014 no platform initialized in this directory.");
1301
+ return;
1302
+ }
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.`);
1306
+ return;
1307
+ }
1308
+ const serviceName = await ctx.prompt("Service name:");
1309
+ if (!/^[a-z0-9-]+$/.test(serviceName)) {
1310
+ ctx.log(`Error: Service name "${serviceName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
1311
+ return;
1312
+ }
1313
+ const serviceDisplayName = await ctx.prompt("Service display name:");
1314
+ await createServiceModuleService(
1315
+ { applicationName, serviceName, serviceDisplayName },
1316
+ ctx
1317
+ );
1318
+ }
1319
+
1320
+ // src/services/create-ui-module.service.ts
1321
+ async function createUiModuleService(params, logger) {
1322
+ await createUiModule(params, logger);
1323
+ }
1324
+
1325
+ // src/controllers/ui/create-ui-module.ui-controller.ts
1326
+ async function createUiModuleUiController(ctx) {
1327
+ if (!await isPlatformInitialized()) {
1328
+ ctx.log("Error: Cannot create a UI module \u2014 no platform initialized in this directory.");
1329
+ return;
1330
+ }
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.`);
1334
+ return;
1335
+ }
1336
+ const applicationDisplayName = await ctx.prompt("Application display name:");
1337
+ await createUiModuleService(
1338
+ { applicationName, applicationDisplayName },
1339
+ ctx
1340
+ );
1341
+ }
1342
+
997
1343
  // src/controllers/ui/registry.ts
998
1344
  var uiControllers = /* @__PURE__ */ new Map([
999
1345
  [CREATE_APPLICATION_COMMAND_NAME, createApplicationUiController],
1000
1346
  [INIT_COMMAND_NAME, initUiController],
1001
- [CONFIGURE_IDP_COMMAND_NAME, configureIdpUiController]
1347
+ [CONFIGURE_IDP_COMMAND_NAME, configureIdpUiController],
1348
+ [CREATE_SERVICE_MODULE_COMMAND_NAME, createServiceModuleUiController],
1349
+ [CREATE_UI_MODULE_COMMAND_NAME, createUiModuleUiController]
1002
1350
  ]);
1003
1351
 
1004
1352
  // src/hooks/use-command-runner.ts
@@ -1013,7 +1361,7 @@ function useCommandRunner({ appendStaticItem, setState }) {
1013
1361
  abortControllerRef.current = null;
1014
1362
  promptResolveRef.current = null;
1015
1363
  }, []);
1016
- const runCommand = useCallback(
1364
+ const runCommand2 = useCallback(
1017
1365
  (cmd) => {
1018
1366
  const controller = new AbortController();
1019
1367
  abortControllerRef.current = controller;
@@ -1027,14 +1375,14 @@ function useCommandRunner({ appendStaticItem, setState }) {
1027
1375
  }
1028
1376
  },
1029
1377
  prompt(message) {
1030
- return new Promise((resolve, reject) => {
1378
+ return new Promise((resolve5, reject) => {
1031
1379
  if (controller.signal.aborted) {
1032
1380
  reject(new DOMException("Aborted", "AbortError"));
1033
1381
  return;
1034
1382
  }
1035
1383
  setPromptMessage(message);
1036
1384
  setPromptValue("");
1037
- promptResolveRef.current = resolve;
1385
+ promptResolveRef.current = resolve5;
1038
1386
  setState(APP_STATE.PROMPTING);
1039
1387
  controller.signal.addEventListener(
1040
1388
  "abort",
@@ -1066,15 +1414,15 @@ function useCommandRunner({ appendStaticItem, setState }) {
1066
1414
  );
1067
1415
  const handlePromptSubmit = useCallback(
1068
1416
  (value) => {
1069
- const resolve = promptResolveRef.current;
1417
+ const resolve5 = promptResolveRef.current;
1070
1418
  promptResolveRef.current = null;
1071
1419
  setState(APP_STATE.EXECUTING);
1072
- resolve?.(value);
1420
+ resolve5?.(value);
1073
1421
  },
1074
1422
  [setState]
1075
1423
  );
1076
1424
  return {
1077
- runCommand,
1425
+ runCommand: runCommand2,
1078
1426
  handlePromptSubmit,
1079
1427
  abortExecution,
1080
1428
  promptMessage,
@@ -1096,7 +1444,7 @@ function App() {
1096
1444
  const appendStaticItem = useCallback2((item) => {
1097
1445
  setStaticItems((prev) => [...prev, item]);
1098
1446
  }, []);
1099
- const { runCommand, handlePromptSubmit, abortExecution, promptMessage, promptValue, setPromptValue } = useCommandRunner({ appendStaticItem, setState });
1447
+ const { runCommand: runCommand2, handlePromptSubmit, abortExecution, promptMessage, promptValue, setPromptValue } = useCommandRunner({ appendStaticItem, setState });
1100
1448
  const query = inputValue.startsWith("/") ? inputValue.slice(1) : "";
1101
1449
  const filteredCommands = registry.search(query);
1102
1450
  useInput(
@@ -1128,7 +1476,7 @@ function App() {
1128
1476
  const cmd = filteredCommands[selectedIndex];
1129
1477
  if (cmd) {
1130
1478
  setInputValue("");
1131
- runCommand(cmd);
1479
+ runCommand2(cmd);
1132
1480
  }
1133
1481
  return;
1134
1482
  }
@@ -1164,9 +1512,9 @@ function App() {
1164
1512
  if (!value.startsWith("/")) return;
1165
1513
  const name = value.slice(1).trim();
1166
1514
  const cmd = registry.get(name);
1167
- if (cmd) runCommand(cmd);
1515
+ if (cmd) runCommand2(cmd);
1168
1516
  },
1169
- [runCommand]
1517
+ [runCommand2]
1170
1518
  );
1171
1519
  function renderActiveArea() {
1172
1520
  switch (state) {
@@ -1202,10 +1550,11 @@ function App() {
1202
1550
  }
1203
1551
 
1204
1552
  // src/controllers/cli/create-application.cli-controller.ts
1205
- import { readFile as readFile9 } from "fs/promises";
1206
- import { join as join18 } from "path";
1207
- import { cwd as cwd5 } from "process";
1208
1553
  async function createApplicationCliController(args2) {
1554
+ if (!await isPlatformInitialized()) {
1555
+ console.error("Error: Cannot create an application \u2014 no platform initialized in this directory.");
1556
+ process.exit(1);
1557
+ }
1209
1558
  const {
1210
1559
  applicationName,
1211
1560
  applicationDisplayName,
@@ -1221,28 +1570,8 @@ async function createApplicationCliController(args2) {
1221
1570
  console.error(`Error: Application name "${applicationName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
1222
1571
  process.exit(1);
1223
1572
  }
1224
- let organizationName;
1225
- let platformName;
1226
- try {
1227
- const corePackageJson = JSON.parse(
1228
- await readFile9(join18(cwd5(), "core", "package.json"), "utf-8")
1229
- );
1230
- const scopeMatch = corePackageJson.name.match(/^@([^/]+)\//);
1231
- organizationName = scopeMatch?.[1];
1232
- const rawName = corePackageJson.name.replace(/^@[^/]+\//, "").replace(/-core$/, "");
1233
- platformName = rawName;
1234
- if (!organizationName || !platformName) {
1235
- throw new Error(`Could not parse organization/platform from package name: "${corePackageJson.name}"`);
1236
- }
1237
- } catch (err) {
1238
- const message = err instanceof Error ? err.message : String(err);
1239
- console.error(`Error: Could not read core/package.json \u2014 ${message}`);
1240
- process.exit(1);
1241
- }
1242
1573
  await createApplicationService(
1243
1574
  {
1244
- organizationName,
1245
- platformName,
1246
1575
  applicationName,
1247
1576
  applicationDisplayName,
1248
1577
  applicationDescription,
@@ -1255,6 +1584,10 @@ async function createApplicationCliController(args2) {
1255
1584
 
1256
1585
  // src/controllers/cli/init.cli-controller.ts
1257
1586
  async function initCliController(args2) {
1587
+ if (await isPlatformInitialized()) {
1588
+ console.error("Error: Cannot initialize a new platform \u2014 a platform is already initialized in this directory.");
1589
+ process.exit(1);
1590
+ }
1258
1591
  const { organizationName, platformName, platformDisplayName } = args2;
1259
1592
  if (!organizationName || !platformName || !platformDisplayName) {
1260
1593
  console.error("Error: organizationName, platformName, and platformDisplayName are required.");
@@ -1269,6 +1602,10 @@ async function initCliController(args2) {
1269
1602
  // src/controllers/cli/configure-idp.cli-controller.ts
1270
1603
  async function configureIdpCliController(args2) {
1271
1604
  const logger = { log: console.log };
1605
+ if (!await isPlatformInitialized()) {
1606
+ console.error("Error: Cannot configure an IDP \u2014 no platform initialized in this directory.");
1607
+ process.exit(1);
1608
+ }
1272
1609
  const { providerType, name, issuer, clientId, clientSecret } = args2;
1273
1610
  if (!providerType || !name || !issuer || !clientId || !clientSecret) {
1274
1611
  logger.log("Error: Missing required arguments: providerType, name, issuer, clientId, clientSecret");
@@ -1291,22 +1628,167 @@ async function configureIdpCliController(args2) {
1291
1628
  await configureIdpService({ providerType, name, issuer, clientId, clientSecret, extras }, logger);
1292
1629
  }
1293
1630
 
1294
- // src/utils/run-npm-script.ts
1631
+ // src/commands/local-scripts/docker-compose-orchestrator.ts
1295
1632
  import { spawn } from "child_process";
1296
- import { access } from "fs/promises";
1297
- import { join as join19 } from "path";
1298
- async function runNpmScript(scriptName, logger, signal) {
1299
- const localDir = join19(process.cwd(), "local");
1300
- try {
1301
- await access(localDir);
1302
- } catch {
1303
- logger.log(`Error: "local/" directory not found. Run "platform init" first.`);
1304
- return;
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);
1305
1702
  }
1306
- return new Promise((resolve) => {
1307
- const child = spawn("npm", ["run", scriptName], {
1308
- cwd: localDir,
1309
- shell: true,
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,
1310
1792
  stdio: ["ignore", "pipe", "pipe"]
1311
1793
  });
1312
1794
  const onAbort = () => {
@@ -1315,7 +1797,7 @@ async function runNpmScript(scriptName, logger, signal) {
1315
1797
  if (signal) {
1316
1798
  if (signal.aborted) {
1317
1799
  child.kill("SIGTERM");
1318
- resolve();
1800
+ resolvePromise();
1319
1801
  return;
1320
1802
  }
1321
1803
  signal.addEventListener("abort", onAbort, { once: true });
@@ -1333,19 +1815,72 @@ async function runNpmScript(scriptName, logger, signal) {
1333
1815
  child.on("close", (code, sig) => {
1334
1816
  signal?.removeEventListener("abort", onAbort);
1335
1817
  if (sig === "SIGTERM" || signal?.aborted) {
1336
- logger.log(`Command cancelled.`);
1818
+ logger.log("Command cancelled.");
1337
1819
  } else if (code !== 0) {
1338
- logger.log(`Command "npm run ${scriptName}" exited with code ${code}.`);
1820
+ logger.log(`Command "${command} ${args2.join(" ")}" exited with code ${code}.`);
1339
1821
  }
1340
- resolve();
1822
+ resolvePromise();
1341
1823
  });
1342
1824
  child.on("error", (err) => {
1343
1825
  signal?.removeEventListener("abort", onAbort);
1344
- logger.log(`Failed to run "npm run ${scriptName}": ${err.message}`);
1345
- resolve();
1826
+ logger.log(`Failed to run "${command} ${args2.join(" ")}": ${err.message}`);
1827
+ resolvePromise();
1346
1828
  });
1347
1829
  });
1348
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
+ }
1349
1884
 
1350
1885
  // src/commands/local-scripts/local-script.command.ts
1351
1886
  var INSTALL_COMMAND_NAME = "install";
@@ -1354,7 +1889,37 @@ var START_COMMAND_NAME = "start";
1354
1889
  var STOP_COMMAND_NAME = "stop";
1355
1890
  var DESTROY_COMMAND_NAME = "destroy";
1356
1891
  async function runLocalScript(scriptName, logger, signal) {
1357
- await runNpmScript(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
+ }
1358
1923
  }
1359
1924
 
1360
1925
  // src/services/local-script.service.ts
@@ -1405,11 +1970,59 @@ function createLocalScriptCliController(scriptName) {
1405
1970
  };
1406
1971
  }
1407
1972
 
1973
+ // src/controllers/cli/create-service-module.cli-controller.ts
1974
+ async function createServiceModuleCliController(args2) {
1975
+ if (!await isPlatformInitialized()) {
1976
+ console.error("Error: Cannot create a service module \u2014 no platform initialized in this directory.");
1977
+ process.exit(1);
1978
+ }
1979
+ const { applicationName, serviceName, serviceDisplayName } = args2;
1980
+ if (!applicationName || !serviceName || !serviceDisplayName) {
1981
+ console.error("Error: applicationName, serviceName, and serviceDisplayName are required.");
1982
+ process.exit(1);
1983
+ }
1984
+ if (!/^[a-z0-9-]+$/.test(applicationName)) {
1985
+ console.error(`Error: Application name "${applicationName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
1986
+ process.exit(1);
1987
+ }
1988
+ if (!/^[a-z0-9-]+$/.test(serviceName)) {
1989
+ console.error(`Error: Service name "${serviceName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
1990
+ process.exit(1);
1991
+ }
1992
+ await createServiceModuleService(
1993
+ { applicationName, serviceName, serviceDisplayName },
1994
+ { log: console.log }
1995
+ );
1996
+ }
1997
+
1998
+ // src/controllers/cli/create-ui-module.cli-controller.ts
1999
+ async function createUiModuleCliController(args2) {
2000
+ if (!await isPlatformInitialized()) {
2001
+ console.error("Error: Cannot create a UI module \u2014 no platform initialized in this directory.");
2002
+ process.exit(1);
2003
+ }
2004
+ const { applicationName, applicationDisplayName } = args2;
2005
+ if (!applicationName || !applicationDisplayName) {
2006
+ console.error("Error: applicationName and applicationDisplayName are required.");
2007
+ process.exit(1);
2008
+ }
2009
+ if (!/^[a-z0-9-]+$/.test(applicationName)) {
2010
+ console.error(`Error: Application name "${applicationName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
2011
+ process.exit(1);
2012
+ }
2013
+ await createUiModuleService(
2014
+ { applicationName, applicationDisplayName },
2015
+ { log: console.log }
2016
+ );
2017
+ }
2018
+
1408
2019
  // src/controllers/cli/registry.ts
1409
2020
  var cliControllers = /* @__PURE__ */ new Map([
1410
2021
  [CREATE_APPLICATION_COMMAND_NAME, createApplicationCliController],
1411
2022
  [INIT_COMMAND_NAME, initCliController],
1412
2023
  [CONFIGURE_IDP_COMMAND_NAME, configureIdpCliController],
2024
+ [CREATE_SERVICE_MODULE_COMMAND_NAME, createServiceModuleCliController],
2025
+ [CREATE_UI_MODULE_COMMAND_NAME, createUiModuleCliController],
1413
2026
  [INSTALL_COMMAND_NAME, createLocalScriptCliController(INSTALL_COMMAND_NAME)],
1414
2027
  [BUILD_COMMAND_NAME, createLocalScriptCliController(BUILD_COMMAND_NAME)],
1415
2028
  [START_COMMAND_NAME, createLocalScriptCliController(START_COMMAND_NAME)],