@bluealba/platform-cli 0.3.0 → 0.3.1-feature-platform-cli-prefix-221

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 (33) hide show
  1. package/dist/index.js +627 -307
  2. package/package.json +5 -5
  3. package/templates/bootstrap-service-template/package.json +2 -2
  4. package/templates/customization-ui-module-template/Dockerfile +4 -4
  5. package/templates/customization-ui-module-template/Dockerfile.development +1 -1
  6. package/templates/customization-ui-module-template/package.json +5 -5
  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}}-core/local}/.env.example +1 -1
  10. package/templates/platform-init-template/{local → {{platformName}}-core/local}/platform-docker-compose.yml +1 -1
  11. package/templates/platform-init-template/{local/core-docker-compose.yml → {{platformName}}-core/local/{{platformName}}-core-docker-compose.yml} +7 -7
  12. package/templates/react-ui-module-template/Dockerfile +3 -3
  13. package/templates/react-ui-module-template/Dockerfile.development +1 -1
  14. package/templates/react-ui-module-template/package.json +2 -2
  15. package/templates/react-ui-module-template/src/Icon.tsx +1 -1
  16. package/templates/platform-init-template/local/docker-compose.yml +0 -3
  17. package/templates/platform-init-template/local/package.json +0 -18
  18. package/templates/platform-init-template/local/scripts/build.sh +0 -18
  19. package/templates/platform-init-template/local/scripts/install.sh +0 -18
  20. /package/templates/customization-ui-module-template/src/{platform-customization-ui.tsx → {{platformName}}-customization-ui.tsx} +0 -0
  21. /package/templates/platform-init-template/{core → {{platformName}}-core}/.changeset/config.json +0 -0
  22. /package/templates/platform-init-template/{core → {{platformName}}-core}/.nvmrc +0 -0
  23. /package/templates/platform-init-template/{core → {{platformName}}-core}/.syncpackrc +0 -0
  24. /package/templates/platform-init-template/{local → {{platformName}}-core/local}/environment/pae-nestjs-gateway-service.env +0 -0
  25. /package/templates/platform-init-template/{local → {{platformName}}-core/local}/nginx.conf +0 -0
  26. /package/templates/platform-init-template/{local → {{platformName}}-core/local}/ssl/cert.pem +0 -0
  27. /package/templates/platform-init-template/{local → {{platformName}}-core/local}/ssl/key.pem +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
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 Box4, Text as Text7, useApp, useInput } from "ink";
10
10
 
11
11
  // src/components/prompt.tsx
12
12
  import { Box, Text } from "ink";
@@ -54,53 +54,76 @@ var CommandPalette = React.memo(function CommandPalette2({ commands, selectedInd
54
54
  });
55
55
 
56
56
  // src/components/scrollback-history.tsx
57
- import { Static, Text as Text4 } from "ink";
57
+ import { Static, Text as Text5 } from "ink";
58
58
 
59
59
  // src/components/welcome-banner.tsx
60
+ import React3 from "react";
61
+ import { Box as Box3, Text as Text4 } from "ink";
62
+
63
+ // src/components/working-directory.tsx
60
64
  import React2 from "react";
61
- import { Box as Box3, Text as Text3 } from "ink";
65
+ import { Text as Text3 } from "ink";
66
+ import { cwd } from "process";
67
+ import { homedir } from "os";
62
68
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
69
+ function shortenPath(fullPath) {
70
+ const home = homedir();
71
+ return fullPath.startsWith(home) ? `~${fullPath.slice(home.length)}` : fullPath;
72
+ }
73
+ var WorkingDirectory = React2.memo(function WorkingDirectory2() {
74
+ const dir = shortenPath(cwd());
75
+ return /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
76
+ " ",
77
+ /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: "cwd:" }),
78
+ " ",
79
+ dir
80
+ ] });
81
+ });
82
+
83
+ // src/components/welcome-banner.tsx
84
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
63
85
  var ASCII_ART = [
64
86
  "|---. .---|",
65
87
  "| \\/ |",
66
88
  "| /\\ |",
67
89
  "|---' '---|"
68
90
  ];
69
- var WelcomeBanner = React2.memo(function WelcomeBanner2({ version: version2 }) {
70
- return /* @__PURE__ */ jsxs3(Box3, { borderStyle: "round", flexDirection: "column", paddingX: 1, paddingY: 1, children: [
71
- /* @__PURE__ */ jsxs3(Text3, { bold: true, color: "cyan", children: [
91
+ var WelcomeBanner = React3.memo(function WelcomeBanner2({ version: version2 }) {
92
+ return /* @__PURE__ */ jsxs4(Box3, { borderStyle: "round", flexDirection: "column", paddingX: 1, paddingY: 1, children: [
93
+ /* @__PURE__ */ jsxs4(Text4, { bold: true, color: "cyan", children: [
72
94
  "Blue Alba Platform CLI v",
73
95
  version2
74
96
  ] }),
75
- /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, children: [
76
- /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", marginRight: 2, children: ASCII_ART.map((line, i) => /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: line }, i)) }),
77
- /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", justifyContent: "center", marginRight: 2, children: [
78
- /* @__PURE__ */ jsx3(Text3, { bold: true, children: "Welcome to the" }),
79
- /* @__PURE__ */ jsx3(Text3, { bold: true, children: "Blue Alba Platform!" })
97
+ /* @__PURE__ */ jsxs4(Box3, { marginTop: 1, children: [
98
+ /* @__PURE__ */ jsx4(Box3, { flexDirection: "column", marginRight: 2, children: ASCII_ART.map((line, i) => /* @__PURE__ */ jsx4(Text4, { color: "yellow", children: line }, i)) }),
99
+ /* @__PURE__ */ jsxs4(Box3, { flexDirection: "column", justifyContent: "center", marginRight: 2, children: [
100
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: "Welcome to the" }),
101
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: "Blue Alba Platform!" })
80
102
  ] }),
81
- /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: ASCII_ART.map((_, i) => /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\u2502" }, i)) }),
82
- /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginLeft: 2, children: [
83
- /* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: "Tips for getting started" }),
84
- /* @__PURE__ */ jsxs3(Text3, { children: [
103
+ /* @__PURE__ */ jsx4(Box3, { flexDirection: "column", children: ASCII_ART.map((_, i) => /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2502" }, i)) }),
104
+ /* @__PURE__ */ jsxs4(Box3, { flexDirection: "column", marginLeft: 2, children: [
105
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: "Tips for getting started" }),
106
+ /* @__PURE__ */ jsxs4(Text4, { children: [
85
107
  "Run ",
86
- /* @__PURE__ */ jsx3(Text3, { color: "green", children: "/init" }),
108
+ /* @__PURE__ */ jsx4(Text4, { color: "green", children: "/init" }),
87
109
  " to start building a platform"
88
110
  ] })
89
111
  ] })
90
- ] })
112
+ ] }),
113
+ /* @__PURE__ */ jsx4(WorkingDirectory, {})
91
114
  ] });
92
115
  });
93
116
 
94
117
  // src/components/scrollback-history.tsx
95
- import { jsx as jsx4 } from "react/jsx-runtime";
118
+ import { jsx as jsx5 } from "react/jsx-runtime";
96
119
  function ScrollbackHistory({ items, version: version2 }) {
97
- return /* @__PURE__ */ jsx4(Static, { items, children: (item) => item.kind === "banner" ? /* @__PURE__ */ jsx4(WelcomeBanner, { version: version2 }, item.id) : /* @__PURE__ */ jsx4(Text4, { children: item.line }, item.id) });
120
+ return /* @__PURE__ */ jsx5(Static, { items, children: (item) => item.kind === "banner" ? /* @__PURE__ */ jsx5(WelcomeBanner, { version: version2 }, item.id) : /* @__PURE__ */ jsx5(Text5, { children: item.line }, item.id) });
98
121
  }
99
122
 
100
123
  // src/components/spinner.tsx
101
124
  import { useState, useEffect } from "react";
102
- import { Text as Text5 } from "ink";
103
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
125
+ import { Text as Text6 } from "ink";
126
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
104
127
  var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
105
128
  var INTERVAL_MS = 80;
106
129
  function Spinner({ label }) {
@@ -111,12 +134,12 @@ function Spinner({ label }) {
111
134
  }, INTERVAL_MS);
112
135
  return () => clearInterval(id);
113
136
  }, []);
114
- return /* @__PURE__ */ jsxs4(Text5, { children: [
115
- /* @__PURE__ */ jsxs4(Text5, { color: "cyan", children: [
137
+ return /* @__PURE__ */ jsxs5(Text6, { children: [
138
+ /* @__PURE__ */ jsxs5(Text6, { color: "cyan", children: [
116
139
  FRAMES[frame],
117
140
  " "
118
141
  ] }),
119
- label && /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: label })
142
+ label && /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: label })
120
143
  ] });
121
144
  }
122
145
 
@@ -124,8 +147,7 @@ function Spinner({ label }) {
124
147
  import { Fzf } from "fzf";
125
148
 
126
149
  // src/commands/create-application/create-application.command.ts
127
- import { join as join10 } from "path";
128
- import { cwd as cwd2 } from "process";
150
+ import { join as join11, resolve } from "path";
129
151
  import { mkdir as mkdir2 } from "fs/promises";
130
152
 
131
153
  // src/commands/create-application/scaffold-application-monorepo.ts
@@ -290,8 +312,8 @@ var nestjsServiceModuleTemplateDir = join6(
290
312
  "templates",
291
313
  "nestjs-service-module-template"
292
314
  );
293
- async function scaffoldNestjsService(serviceDir, organizationName, applicationName, applicationDisplayName, serviceBaseDir, logger) {
294
- const serviceName = `${applicationName}-service`;
315
+ async function scaffoldNestjsService(serviceDir, organizationName, platformName, applicationName, applicationDisplayName, serviceBaseDir, logger) {
316
+ const serviceName = `${platformName}-${applicationName}-service`;
295
317
  const serviceDisplayName = `${applicationDisplayName} Service`;
296
318
  logger.log(`Creating NestJS service "${serviceName}"...`);
297
319
  await applyTemplate(
@@ -322,33 +344,35 @@ async function addModuleEntry(bootstrapServiceDir, applicationName, entry, logge
322
344
  }
323
345
 
324
346
  // src/commands/create-application/module-entry-builders.ts
325
- function buildServiceModuleEntry(organizationName, applicationName, applicationDisplayName) {
347
+ function buildServiceModuleEntry(organizationName, platformName, applicationName, applicationDisplayName) {
348
+ const serviceName = `${platformName}-${applicationName}-service`;
326
349
  return {
327
- name: `@${organizationName}/${applicationName}-service`,
350
+ name: `@${organizationName}/${serviceName}`,
328
351
  displayName: `${applicationDisplayName} Service`,
329
352
  type: "service",
330
- baseUrl: `/${applicationName}-service`,
353
+ baseUrl: `/${serviceName}`,
331
354
  service: {
332
- host: `${applicationName}-service`,
355
+ host: serviceName,
333
356
  port: 80
334
357
  },
335
358
  dependsOn: []
336
359
  };
337
360
  }
338
- function buildUiModuleEntry(organizationName, applicationName, applicationDisplayName) {
361
+ function buildUiModuleEntry(organizationName, platformName, applicationName, applicationDisplayName) {
362
+ const uiName = `${platformName}-${applicationName}-ui`;
339
363
  return {
340
- name: `@${organizationName}/${applicationName}-ui`,
364
+ name: `@${organizationName}/${uiName}`,
341
365
  displayName: applicationDisplayName,
342
366
  type: "app",
343
- baseUrl: `/${applicationName}-ui`,
367
+ baseUrl: `/${uiName}`,
344
368
  service: {
345
- host: `${applicationName}-ui`,
369
+ host: uiName,
346
370
  port: 80
347
371
  },
348
372
  ui: {
349
373
  route: `/${applicationName}`,
350
374
  mountAtSelector: "#pae-shell-ui-content",
351
- bundleFile: `${organizationName}-${applicationName}-ui.js`,
375
+ bundleFile: `${organizationName}-${uiName}.js`,
352
376
  customProps: {}
353
377
  },
354
378
  dependsOn: [`@bluealba/pae-shell-ui`]
@@ -357,10 +381,11 @@ function buildUiModuleEntry(organizationName, applicationName, applicationDispla
357
381
 
358
382
  // src/commands/create-application/create-docker-compose.ts
359
383
  import { writeFile as writeFile3 } from "fs/promises";
360
- function buildBootstrapBlock(applicationName) {
361
- return ` ${applicationName}-bootstrap-service:
384
+ function buildBootstrapBlock(platformName, applicationName) {
385
+ const bootstrapName = `${platformName}-${applicationName}-bootstrap-service`;
386
+ return ` ${bootstrapName}:
362
387
  build:
363
- context: ../${applicationName}/services/${applicationName}-bootstrap-service
388
+ context: ../../${platformName}-${applicationName}/services/${bootstrapName}
364
389
  dockerfile: Dockerfile.development
365
390
  environment:
366
391
  - NODE_TLS_REJECT_UNAUTHORIZED=0
@@ -370,27 +395,29 @@ function buildBootstrapBlock(applicationName) {
370
395
  - GATEWAY_SERVICE_URL=\${PAE_GATEWAY_URL}
371
396
  - SERVICE_ACCESS_SECRET=\${PAE_GATEWAY_SERVICE_ACCESS_SECRET}
372
397
  volumes:
373
- - \${PWD}/../:/app/out
398
+ - \${PWD}/:/app/out
374
399
  depends_on:
375
400
  pae-nestjs-gateway-service:
376
401
  condition: service_healthy
377
402
  `;
378
403
  }
379
- function buildUiBlock(applicationName, uiPort) {
380
- return ` ${applicationName}-ui:
404
+ function buildUiBlock(platformName, applicationName, uiPort) {
405
+ const uiName = `${platformName}-${applicationName}-ui`;
406
+ return ` ${uiName}:
381
407
  build:
382
- context: ../${applicationName}/ui/${applicationName}-ui
408
+ context: ../../${platformName}-${applicationName}/ui/${uiName}
383
409
  dockerfile: Dockerfile.development
384
410
  ports:
385
411
  - ${uiPort}:80
386
412
  volumes:
387
- - \${PWD}/../:/app/out
413
+ - \${PWD}/:/app/out
388
414
  `;
389
415
  }
390
- function buildBackendBlock(applicationName, servicePort) {
391
- return ` ${applicationName}-service:
416
+ function buildBackendBlock(platformName, applicationName, servicePort) {
417
+ const serviceName = `${platformName}-${applicationName}-service`;
418
+ return ` ${serviceName}:
392
419
  build:
393
- context: ../${applicationName}/services/${applicationName}-service
420
+ context: ../../${platformName}-${applicationName}/services/${serviceName}
394
421
  dockerfile: Dockerfile.development
395
422
  args:
396
423
  - BA_NPM_AUTH_TOKEN=$BA_NPM_AUTH_TOKEN
@@ -399,18 +426,18 @@ function buildBackendBlock(applicationName, servicePort) {
399
426
  environment:
400
427
  - GATEWAY_URL=\${PAE_GATEWAY_URL}
401
428
  volumes:
402
- - \${PWD}/../:/app/out
429
+ - \${PWD}/:/app/out
403
430
  `;
404
431
  }
405
- async function createDockerCompose(dockerComposePath, applicationName, hasUserInterface, hasBackendService, uiPort, servicePort, logger) {
406
- const blocks = ["services:\n", buildBootstrapBlock(applicationName)];
432
+ async function createDockerCompose(dockerComposePath, applicationName, platformName, hasUserInterface, hasBackendService, uiPort, servicePort, logger) {
433
+ const blocks = ["services:\n", buildBootstrapBlock(platformName, applicationName)];
407
434
  if (hasUserInterface) {
408
- blocks.push(buildUiBlock(applicationName, uiPort));
435
+ blocks.push(buildUiBlock(platformName, applicationName, uiPort));
409
436
  }
410
437
  if (hasBackendService) {
411
- blocks.push(buildBackendBlock(applicationName, servicePort));
438
+ blocks.push(buildBackendBlock(platformName, applicationName, servicePort));
412
439
  }
413
- const content = blocks.join("\n");
440
+ const content = blocks.join("\n") + "\n";
414
441
  try {
415
442
  await writeFile3(dockerComposePath, content, "utf-8");
416
443
  logger.log(`Created docker-compose: ${dockerComposePath}`);
@@ -420,32 +447,17 @@ async function createDockerCompose(dockerComposePath, applicationName, hasUserIn
420
447
  }
421
448
  }
422
449
 
423
- // src/commands/create-application/update-root-docker-compose.ts
424
- import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
425
- async function updateRootDockerCompose(rootDockerComposePath, applicationName, logger) {
426
- try {
427
- const existing = await readFile3(rootDockerComposePath, "utf-8");
428
- const includeLine = ` - path: ./${applicationName}-docker-compose.yml
429
- `;
430
- await writeFile4(rootDockerComposePath, existing + includeLine, "utf-8");
431
- logger.log(`Updated root docker-compose: ${rootDockerComposePath}`);
432
- } catch (err) {
433
- const message = err instanceof Error ? err.message : String(err);
434
- logger.log(`Warning: Could not update root docker-compose \u2014 ${message}`);
435
- }
436
- }
437
-
438
450
  // src/commands/create-application/port-allocator.ts
439
451
  import { join as join8 } from "path";
440
- import { readdir as readdir2, readFile as readFile4 } from "fs/promises";
452
+ import { readdir as readdir2, readFile as readFile3 } from "fs/promises";
441
453
  async function getNextAvailablePort(localDir) {
442
454
  const allPorts = [];
443
455
  try {
444
456
  const files = await readdir2(localDir);
445
- const dockerComposeFiles = files.filter((f) => f.endsWith("-docker-compose.yml"));
446
- for (const file of dockerComposeFiles) {
457
+ const ymlFiles = files.filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
458
+ for (const file of ymlFiles) {
447
459
  try {
448
- const content = await readFile4(join8(localDir, file), "utf-8");
460
+ const content = await readFile3(join8(localDir, file), "utf-8");
449
461
  const regex = /^\s*-\s*"?(\d+):\d+"?\s*$/gm;
450
462
  let match;
451
463
  while ((match = regex.exec(content)) !== null) {
@@ -464,18 +476,95 @@ function formatError(err) {
464
476
  return err instanceof Error ? err.message : String(err);
465
477
  }
466
478
 
467
- // src/utils/platform-check.ts
468
- import { access } from "fs/promises";
469
- import { join as join9 } from "path";
470
- import { cwd } from "process";
471
- async function isPlatformInitialized() {
479
+ // src/utils/platform-layout.ts
480
+ import { access, readdir as readdir3 } from "fs/promises";
481
+ import { join as join9, dirname as dirname7 } from "path";
482
+ import { cwd as cwd2 } from "process";
483
+ async function findCoreDirIn(dir) {
484
+ let entries;
472
485
  try {
473
- await access(join9(cwd(), "core"));
474
- await access(join9(cwd(), "local"));
475
- return true;
486
+ entries = await readdir3(dir, { withFileTypes: true });
476
487
  } catch {
477
- return false;
488
+ return null;
478
489
  }
490
+ const dirs = entries.filter((e) => e.isDirectory());
491
+ for (const entry of dirs) {
492
+ if (entry.name.endsWith("-core")) {
493
+ try {
494
+ await access(join9(dir, entry.name, "product.manifest.json"));
495
+ return entry.name;
496
+ } catch {
497
+ }
498
+ }
499
+ }
500
+ const coreEntry = dirs.find((e) => e.name === "core");
501
+ if (coreEntry) {
502
+ try {
503
+ await access(join9(dir, "core", "product.manifest.json"));
504
+ return "core";
505
+ } catch {
506
+ }
507
+ }
508
+ return null;
509
+ }
510
+ async function findPlatformLayout(startDir = cwd2()) {
511
+ let dir = startDir;
512
+ while (true) {
513
+ const coreDirName = await findCoreDirIn(dir);
514
+ if (coreDirName) {
515
+ const coreDir = join9(dir, coreDirName);
516
+ return {
517
+ rootDir: dir,
518
+ coreDir,
519
+ coreDirName,
520
+ localDir: join9(coreDir, "local")
521
+ };
522
+ }
523
+ const parent = dirname7(dir);
524
+ if (parent === dir) {
525
+ return null;
526
+ }
527
+ dir = parent;
528
+ }
529
+ }
530
+
531
+ // src/utils/platform-check.ts
532
+ async function isPlatformInitialized() {
533
+ return await findPlatformLayout() !== null;
534
+ }
535
+
536
+ // src/utils/manifest.ts
537
+ import { readFile as readFile4, writeFile as writeFile4, access as access2 } from "fs/promises";
538
+ import { join as join10 } from "path";
539
+ import { cwd as cwd3 } from "process";
540
+ function manifestPath(rootDir, coreDirName = "core") {
541
+ return join10(rootDir, coreDirName, "product.manifest.json");
542
+ }
543
+ async function readManifest(rootDir = cwd3(), coreDirName = "core") {
544
+ const content = await readFile4(manifestPath(rootDir, coreDirName), "utf-8");
545
+ return JSON.parse(content);
546
+ }
547
+ async function writeManifest(manifest, rootDir = cwd3(), coreDirName = "core") {
548
+ await writeFile4(manifestPath(rootDir, coreDirName), JSON.stringify(manifest, null, 2), "utf-8");
549
+ }
550
+ function createInitialManifest(params) {
551
+ const { organizationName, platformName, platformDisplayName } = params;
552
+ return {
553
+ version: "1",
554
+ product: {
555
+ name: platformName,
556
+ displayName: platformDisplayName,
557
+ organization: organizationName,
558
+ scope: `@${organizationName}`
559
+ },
560
+ applications: []
561
+ };
562
+ }
563
+ function addApplicationToManifest(manifest, app) {
564
+ return {
565
+ ...manifest,
566
+ applications: [...manifest.applications, app]
567
+ };
479
568
  }
480
569
 
481
570
  // src/commands/create-application/create-application.command.ts
@@ -486,22 +575,30 @@ var createApplicationCommand = {
486
575
  };
487
576
  async function createApplication(params, logger) {
488
577
  const {
489
- organizationName,
490
- platformName,
491
578
  applicationName,
492
579
  applicationDisplayName,
493
580
  applicationDescription,
494
581
  hasUserInterface,
495
582
  hasBackendService
496
583
  } = params;
497
- if (!await isPlatformInitialized()) {
584
+ const layout = await findPlatformLayout();
585
+ if (!layout) {
498
586
  logger.log("Error: Cannot create an application \u2014 no platform initialized in this directory.");
499
587
  return;
500
588
  }
501
- const rootDir = cwd2();
502
- const applicationDir = join10(rootDir, applicationName);
503
- const bootstrapServiceDir = join10(applicationDir, "services", `${applicationName}-bootstrap-service`);
504
- const localDir = join10(rootDir, "local");
589
+ const { rootDir, coreDirName, localDir } = layout;
590
+ let manifest;
591
+ try {
592
+ manifest = await readManifest(rootDir, coreDirName);
593
+ } catch (err) {
594
+ logger.log(`Error: Could not read product manifest \u2014 ${formatError(err)}`);
595
+ return;
596
+ }
597
+ const { organization: organizationName, name: platformName } = manifest.product;
598
+ const localPath = `../${platformName}-${applicationName}`;
599
+ const applicationDir = resolve(join11(rootDir, coreDirName), localPath);
600
+ const bootstrapServiceName = `${platformName}-${applicationName}-bootstrap-service`;
601
+ const bootstrapServiceDir = join11(applicationDir, "services", bootstrapServiceName);
505
602
  logger.log(`Creating application monorepo "${applicationName}"...`);
506
603
  try {
507
604
  await scaffoldApplicationMonorepo(applicationDir, organizationName, platformName, applicationName, logger);
@@ -509,12 +606,12 @@ async function createApplication(params, logger) {
509
606
  logger.log(`Error: Could not scaffold application monorepo \u2014 ${formatError(err)}`);
510
607
  return;
511
608
  }
512
- await mkdir2(join10(applicationDir, "services"), { recursive: true });
513
- await mkdir2(join10(applicationDir, "ui"), { recursive: true });
514
- logger.log(`Creating bootstrap service "${applicationName}-bootstrap-service"...`);
515
- const bootstrapServiceDir_var = `${applicationName}/services`;
609
+ await mkdir2(join11(applicationDir, "services"), { recursive: true });
610
+ await mkdir2(join11(applicationDir, "ui"), { recursive: true });
611
+ logger.log(`Creating bootstrap service "${bootstrapServiceName}"...`);
612
+ const bootstrapServiceBaseDir = `${platformName}-${applicationName}/services`;
516
613
  try {
517
- await scaffoldBootstrap(bootstrapServiceDir, organizationName, applicationName, bootstrapServiceDir_var, logger);
614
+ await scaffoldBootstrap(bootstrapServiceDir, organizationName, `${platformName}-${applicationName}`, bootstrapServiceBaseDir, logger);
518
615
  } catch (err) {
519
616
  logger.log(`Error: Could not scaffold bootstrap service \u2014 ${formatError(err)}`);
520
617
  return;
@@ -530,7 +627,7 @@ async function createApplication(params, logger) {
530
627
  await addModuleEntry(
531
628
  bootstrapServiceDir,
532
629
  applicationName,
533
- buildUiModuleEntry(organizationName, applicationName, applicationDisplayName),
630
+ buildUiModuleEntry(organizationName, platformName, applicationName, applicationDisplayName),
534
631
  logger
535
632
  );
536
633
  }
@@ -538,13 +635,14 @@ async function createApplication(params, logger) {
538
635
  await addModuleEntry(
539
636
  bootstrapServiceDir,
540
637
  applicationName,
541
- buildServiceModuleEntry(organizationName, applicationName, applicationDisplayName),
638
+ buildServiceModuleEntry(organizationName, platformName, applicationName, applicationDisplayName),
542
639
  logger
543
640
  );
544
641
  }
545
642
  if (hasUserInterface) {
546
- const uiDir = join10(applicationDir, "ui", `${applicationName}-ui`);
547
- const uiBaseDir = `${applicationName}/ui`;
643
+ const uiName = `${platformName}-${applicationName}-ui`;
644
+ const uiDir = join11(applicationDir, "ui", uiName);
645
+ const uiBaseDir = `${platformName}-${applicationName}/ui`;
548
646
  try {
549
647
  await scaffoldUiModule(uiDir, organizationName, platformName, applicationName, applicationDisplayName, uiBaseDir, logger);
550
648
  } catch (err) {
@@ -553,36 +651,49 @@ async function createApplication(params, logger) {
553
651
  }
554
652
  }
555
653
  if (hasBackendService) {
556
- const serviceDir = join10(applicationDir, "services", `${applicationName}-service`);
557
- const serviceBaseDir = `${applicationName}/services`;
654
+ const serviceName = `${platformName}-${applicationName}-service`;
655
+ const serviceDir = join11(applicationDir, "services", serviceName);
656
+ const serviceBaseDir = `${platformName}-${applicationName}/services`;
558
657
  try {
559
- await scaffoldNestjsService(serviceDir, organizationName, applicationName, applicationDisplayName, serviceBaseDir, logger);
658
+ await scaffoldNestjsService(serviceDir, organizationName, platformName, applicationName, applicationDisplayName, serviceBaseDir, logger);
560
659
  } catch (err) {
561
660
  logger.log(`Error: Could not scaffold NestJS service \u2014 ${formatError(err)}`);
562
661
  return;
563
662
  }
564
663
  }
565
- const dockerComposePath = join10(localDir, `${applicationName}-docker-compose.yml`);
664
+ const dockerComposePath = join11(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
566
665
  const basePort = await getNextAvailablePort(localDir);
567
666
  const uiPort = hasUserInterface ? basePort : 0;
568
667
  const servicePort = hasBackendService ? hasUserInterface ? basePort + 1 : basePort : 0;
569
668
  await createDockerCompose(
570
669
  dockerComposePath,
571
670
  applicationName,
671
+ platformName,
572
672
  hasUserInterface,
573
673
  hasBackendService,
574
674
  uiPort,
575
675
  servicePort,
576
676
  logger
577
677
  );
578
- const rootDockerComposePath = join10(localDir, "docker-compose.yml");
579
- await updateRootDockerCompose(rootDockerComposePath, applicationName, logger);
678
+ const updatedManifest = addApplicationToManifest(manifest, {
679
+ name: applicationName,
680
+ displayName: applicationDisplayName,
681
+ description: applicationDescription,
682
+ localPath,
683
+ repository: null
684
+ });
685
+ try {
686
+ await writeManifest(updatedManifest, rootDir, coreDirName);
687
+ logger.log(`Updated product manifest with application "${applicationName}".`);
688
+ } catch (err) {
689
+ logger.log(`Warning: Could not update product manifest \u2014 ${formatError(err)}`);
690
+ }
580
691
  logger.log(`Done! Application "${applicationName}" created at ${applicationDir}`);
581
692
  }
582
693
 
583
694
  // src/commands/init/init.command.ts
584
- import { join as join16 } from "path";
585
- import { cwd as cwd3 } from "process";
695
+ import { join as join17 } from "path";
696
+ import { cwd as cwd4 } from "process";
586
697
 
587
698
  // src/utils/string.ts
588
699
  function camelize(name) {
@@ -591,9 +702,9 @@ function camelize(name) {
591
702
 
592
703
  // src/commands/init/scaffold-platform.ts
593
704
  import { fileURLToPath as fileURLToPath6 } from "url";
594
- import { join as join11, dirname as dirname7 } from "path";
595
- var templateDir = join11(
596
- dirname7(fileURLToPath6(import.meta.url)),
705
+ import { join as join12, dirname as dirname8 } from "path";
706
+ var templateDir = join12(
707
+ dirname8(fileURLToPath6(import.meta.url)),
597
708
  "..",
598
709
  "templates",
599
710
  "platform-init-template"
@@ -604,9 +715,9 @@ async function scaffoldPlatform(outputDir, variables, logger) {
604
715
 
605
716
  // src/commands/init/scaffold-platform-bootstrap.ts
606
717
  import { fileURLToPath as fileURLToPath7 } from "url";
607
- import { join as join12, dirname as dirname8 } from "path";
608
- var templateDir2 = join12(
609
- dirname8(fileURLToPath7(import.meta.url)),
718
+ import { join as join13, dirname as dirname9 } from "path";
719
+ var templateDir2 = join13(
720
+ dirname9(fileURLToPath7(import.meta.url)),
610
721
  "..",
611
722
  "templates",
612
723
  "bootstrap-service-template"
@@ -617,9 +728,9 @@ async function scaffoldPlatformBootstrap(outputDir, variables, logger) {
617
728
 
618
729
  // src/commands/init/scaffold-customization-ui.ts
619
730
  import { fileURLToPath as fileURLToPath8 } from "url";
620
- import { join as join13, dirname as dirname9 } from "path";
621
- var templateDir3 = join13(
622
- dirname9(fileURLToPath8(import.meta.url)),
731
+ import { join as join14, dirname as dirname10 } from "path";
732
+ var templateDir3 = join14(
733
+ dirname10(fileURLToPath8(import.meta.url)),
623
734
  "..",
624
735
  "templates",
625
736
  "customization-ui-module-template"
@@ -629,19 +740,20 @@ async function scaffoldCustomizationUi(outputDir, variables, logger) {
629
740
  }
630
741
 
631
742
  // src/commands/init/register-customization-module.ts
632
- import { join as join14 } from "path";
743
+ import { join as join15 } from "path";
633
744
  import { readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
634
- async function registerCustomizationModule(bootstrapServiceDir, organizationName, logger) {
635
- const modulesJsonPath = join14(bootstrapServiceDir, "src", "data", "platform", "modules.json");
745
+ async function registerCustomizationModule(bootstrapServiceDir, organizationName, logger, platformName = "platform") {
746
+ const modulesJsonPath = join15(bootstrapServiceDir, "src", "data", "platform", "modules.json");
747
+ const customizationUiName = `${platformName}-customization-ui`;
636
748
  const entry = {
637
- name: `@${organizationName}/platform-customization-ui`,
749
+ name: `@${organizationName}/${customizationUiName}`,
638
750
  displayName: "Platform Customization UI",
639
751
  type: "app",
640
- baseUrl: "/platform-customization-ui",
641
- service: { host: "platform-customization-ui", port: 80 },
752
+ baseUrl: `/${customizationUiName}`,
753
+ service: { host: customizationUiName, port: 80 },
642
754
  ui: {
643
755
  route: "/",
644
- bundleFile: "platform-customization-ui.js",
756
+ bundleFile: `${customizationUiName}.js`,
645
757
  isPlatformCustomization: true,
646
758
  customProps: {}
647
759
  },
@@ -655,7 +767,7 @@ async function registerCustomizationModule(bootstrapServiceDir, organizationName
655
767
 
656
768
  // src/commands/init/generate-local-env.ts
657
769
  import { readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
658
- import { join as join15 } from "path";
770
+ import { join as join16 } from "path";
659
771
 
660
772
  // src/utils/random.ts
661
773
  import { randomBytes } from "crypto";
@@ -664,10 +776,10 @@ function generateRandomSecret() {
664
776
  }
665
777
 
666
778
  // src/commands/init/generate-local-env.ts
667
- async function generateLocalEnv(outputDir, logger) {
668
- const examplePath = join15(outputDir, "local", ".env.example");
669
- const envPath = join15(outputDir, "local", ".env");
670
- logger.log("Generating local/.env with random secrets...");
779
+ async function generateLocalEnv(outputDir, logger, coreDirName = "core") {
780
+ const examplePath = join16(outputDir, coreDirName, "local", ".env.example");
781
+ const envPath = join16(outputDir, coreDirName, "local", ".env");
782
+ logger.log(`Generating ${coreDirName}/local/.env with random secrets...`);
671
783
  const content = await readFile6(examplePath, "utf-8");
672
784
  const result = content.replace(/^(PAE_AUTH_JWT_SECRET|PAE_GATEWAY_SERVICE_ACCESS_SECRET|PAE_DB_PASSWORD)=$/gm, (_, key) => {
673
785
  return `${key}=${generateRandomSecret()}`;
@@ -688,15 +800,18 @@ async function init(params, logger) {
688
800
  }
689
801
  const { organizationName, platformName, platformDisplayName } = params;
690
802
  const platformTitle = camelize(platformName);
691
- const outputDir = cwd3();
803
+ const outputDir = cwd4();
692
804
  logger.log(`Initializing ${platformTitle} platform for @${organizationName}...`);
805
+ const coreDirName = `${platformName}-core`;
806
+ const bootstrapServiceName = `${platformName}-bootstrap-service`;
807
+ const customizationUiName = `${platformName}-customization-ui`;
693
808
  const variables = {
694
809
  organizationName,
695
810
  platformName,
696
811
  platformTitle,
697
812
  platformDisplayName,
698
- bootstrapName: "platform",
699
- bootstrapServiceDir: "core/services"
813
+ bootstrapName: platformName,
814
+ bootstrapServiceDir: `${coreDirName}/services`
700
815
  };
701
816
  try {
702
817
  await scaffoldPlatform(outputDir, variables, logger);
@@ -705,14 +820,22 @@ async function init(params, logger) {
705
820
  return;
706
821
  }
707
822
  try {
708
- await generateLocalEnv(outputDir, logger);
823
+ await generateLocalEnv(outputDir, logger, coreDirName);
709
824
  } catch (err) {
710
- logger.log(`Error: Could not generate local/.env \u2014 ${formatError(err)}`);
825
+ logger.log(`Error: Could not generate ${coreDirName}/local/.env \u2014 ${formatError(err)}`);
826
+ return;
827
+ }
828
+ try {
829
+ const manifest = createInitialManifest({ organizationName, platformName, platformDisplayName });
830
+ await writeManifest(manifest, outputDir, coreDirName);
831
+ logger.log(`Created product manifest: ${coreDirName}/product.manifest.json`);
832
+ } catch (err) {
833
+ logger.log(`Error: Could not write product manifest \u2014 ${formatError(err)}`);
711
834
  return;
712
835
  }
713
836
  try {
714
837
  await scaffoldPlatformBootstrap(
715
- join16(outputDir, "core", "services", "platform-bootstrap-service"),
838
+ join17(outputDir, coreDirName, "services", bootstrapServiceName),
716
839
  variables,
717
840
  logger
718
841
  );
@@ -722,7 +845,7 @@ async function init(params, logger) {
722
845
  }
723
846
  try {
724
847
  await scaffoldCustomizationUi(
725
- join16(outputDir, "core", "ui", "platform-customization-ui"),
848
+ join17(outputDir, coreDirName, "ui", customizationUiName),
726
849
  variables,
727
850
  logger
728
851
  );
@@ -732,9 +855,10 @@ async function init(params, logger) {
732
855
  }
733
856
  try {
734
857
  await registerCustomizationModule(
735
- join16(outputDir, "core", "services", "platform-bootstrap-service"),
858
+ join17(outputDir, coreDirName, "services", bootstrapServiceName),
736
859
  organizationName,
737
- logger
860
+ logger,
861
+ platformName
738
862
  );
739
863
  } catch (err) {
740
864
  logger.log(`Error: Could not register customization module \u2014 ${formatError(err)}`);
@@ -744,8 +868,7 @@ async function init(params, logger) {
744
868
  }
745
869
 
746
870
  // src/commands/configure-idp/configure-idp.command.ts
747
- import { join as join17 } from "path";
748
- import { cwd as cwd4 } from "process";
871
+ import { join as join18 } from "path";
749
872
  import { fetch as undiciFetch, Agent } from "undici";
750
873
 
751
874
  // src/utils/env-reader.ts
@@ -823,11 +946,12 @@ var configureIdpCommand = {
823
946
  description: "Configure an Identity Provider (IDP) in the gateway"
824
947
  };
825
948
  async function configureIdp(params, logger) {
826
- if (!await isPlatformInitialized()) {
949
+ const layout = await findPlatformLayout();
950
+ if (!layout) {
827
951
  logger.log("Error: Cannot configure an IDP \u2014 no platform initialized in this directory.");
828
952
  return;
829
953
  }
830
- const envPath = join17(cwd4(), "local", ".env");
954
+ const envPath = join18(layout.localDir, ".env");
831
955
  let env;
832
956
  try {
833
957
  env = await readEnvFile(envPath);
@@ -838,11 +962,11 @@ async function configureIdp(params, logger) {
838
962
  const gatewayUrl = env.get("PAE_GATEWAY_HOST_URL");
839
963
  const accessSecret = env.get("PAE_GATEWAY_SERVICE_ACCESS_SECRET");
840
964
  if (!gatewayUrl) {
841
- logger.log("Error: PAE_GATEWAY_HOST_URL is not set in local/.env");
965
+ logger.log("Error: PAE_GATEWAY_HOST_URL is not set in core/local/.env");
842
966
  return;
843
967
  }
844
968
  if (!accessSecret) {
845
- logger.log("Error: PAE_GATEWAY_SERVICE_ACCESS_SECRET is not set in local/.env");
969
+ logger.log("Error: PAE_GATEWAY_SERVICE_ACCESS_SECRET is not set in core/local/.env");
846
970
  return;
847
971
  }
848
972
  const provider = idpProviderRegistry.get(params.providerType);
@@ -879,15 +1003,14 @@ async function configureIdp(params, logger) {
879
1003
  }
880
1004
 
881
1005
  // src/commands/create-service-module/create-service-module.command.ts
882
- import { join as join19 } from "path";
883
- import { cwd as cwd5 } from "process";
884
- import { access as access2, readFile as readFile9 } from "fs/promises";
1006
+ import { join as join20, resolve as resolve2 } from "path";
1007
+ import { access as access3 } from "fs/promises";
885
1008
 
886
1009
  // src/commands/create-service-module/scaffold-service-module.ts
887
1010
  import { fileURLToPath as fileURLToPath9 } from "url";
888
- import { join as join18, dirname as dirname10 } from "path";
889
- var nestjsServiceModuleTemplateDir2 = join18(
890
- dirname10(fileURLToPath9(import.meta.url)),
1011
+ import { join as join19, dirname as dirname11 } from "path";
1012
+ var nestjsServiceModuleTemplateDir2 = join19(
1013
+ dirname11(fileURLToPath9(import.meta.url)),
891
1014
  "..",
892
1015
  "templates",
893
1016
  "nestjs-service-module-template"
@@ -922,11 +1045,11 @@ function buildCustomServiceModuleEntry(organizationName, serviceName, serviceDis
922
1045
 
923
1046
  // src/commands/create-service-module/append-docker-compose.ts
924
1047
  import { readFile as readFile8, writeFile as writeFile7 } from "fs/promises";
925
- async function appendServiceToDockerCompose(dockerComposePath, serviceName, applicationName, port, logger) {
1048
+ async function appendServiceToDockerCompose(dockerComposePath, serviceName, platformName, applicationName, port, logger) {
926
1049
  const block = `
927
1050
  ${serviceName}:
928
1051
  build:
929
- context: ../${applicationName}/services/${serviceName}
1052
+ context: ../../${platformName}-${applicationName}/services/${serviceName}
930
1053
  dockerfile: Dockerfile.development
931
1054
  args:
932
1055
  - BA_NPM_AUTH_TOKEN=$BA_NPM_AUTH_TOKEN
@@ -935,7 +1058,7 @@ async function appendServiceToDockerCompose(dockerComposePath, serviceName, appl
935
1058
  environment:
936
1059
  - GATEWAY_URL=\${PAE_GATEWAY_URL}
937
1060
  volumes:
938
- - \${PWD}/../:/app/out
1061
+ - \${PWD}/:/app/out
939
1062
  `;
940
1063
  try {
941
1064
  const existing = await readFile8(dockerComposePath, "utf-8");
@@ -955,41 +1078,41 @@ var createServiceModuleCommand = {
955
1078
  };
956
1079
  async function createServiceModule(params, logger) {
957
1080
  const { applicationName, serviceName, serviceDisplayName } = params;
958
- if (!await isPlatformInitialized()) {
1081
+ const layout = await findPlatformLayout();
1082
+ if (!layout) {
959
1083
  logger.log("Error: Cannot create a service module \u2014 no platform initialized in this directory.");
960
1084
  return;
961
1085
  }
962
- const rootDir = cwd5();
963
- const applicationDir = join19(rootDir, applicationName);
1086
+ const { rootDir, coreDirName, localDir } = layout;
1087
+ let manifest;
964
1088
  try {
965
- await access2(applicationDir);
1089
+ manifest = await readManifest(rootDir, coreDirName);
1090
+ } catch (err) {
1091
+ logger.log(`Error: Could not read product manifest \u2014 ${formatError(err)}`);
1092
+ return;
1093
+ }
1094
+ const { organization: organizationName, name: platformName } = manifest.product;
1095
+ const appEntry = manifest.applications.find((a) => a.name === applicationName);
1096
+ if (!appEntry) {
1097
+ logger.log(`Error: The specified application "${applicationName}" is not registered in the product manifest.`);
1098
+ return;
1099
+ }
1100
+ const applicationDir = resolve2(join20(rootDir, coreDirName), appEntry.localPath);
1101
+ try {
1102
+ await access3(applicationDir);
966
1103
  } catch {
967
1104
  logger.log(`Error: The specified application "${applicationName}" does not exist in the platform.`);
968
1105
  return;
969
1106
  }
970
- const fullServiceName = `${serviceName}-service`;
971
- const serviceDir = join19(applicationDir, "services", fullServiceName);
1107
+ const fullServiceName = `${platformName}-${serviceName}-service`;
1108
+ const serviceDir = join20(applicationDir, "services", fullServiceName);
972
1109
  try {
973
- await access2(serviceDir);
1110
+ await access3(serviceDir);
974
1111
  logger.log(`Error: A service named "${fullServiceName}" already exists in application "${applicationName}".`);
975
1112
  return;
976
1113
  } catch {
977
1114
  }
978
- let organizationName;
979
- try {
980
- const corePackageJson = JSON.parse(
981
- await readFile9(join19(rootDir, "core", "package.json"), "utf-8")
982
- );
983
- const scopeMatch = corePackageJson.name.match(/^@([^/]+)\//);
984
- organizationName = scopeMatch?.[1];
985
- if (!organizationName) {
986
- throw new Error(`Could not parse organization from package name: "${corePackageJson.name}"`);
987
- }
988
- } catch (err) {
989
- logger.log(`Error: Could not read core/package.json \u2014 ${formatError(err)}`);
990
- return;
991
- }
992
- const serviceBaseDir = `${applicationName}/services`;
1115
+ const serviceBaseDir = `${platformName}-${applicationName}/services`;
993
1116
  try {
994
1117
  await scaffoldServiceModule(
995
1118
  serviceDir,
@@ -1003,36 +1126,35 @@ async function createServiceModule(params, logger) {
1003
1126
  logger.log(`Error: Could not scaffold NestJS service \u2014 ${formatError(err)}`);
1004
1127
  return;
1005
1128
  }
1006
- const bootstrapServiceDir = join19(applicationDir, "services", `${applicationName}-bootstrap-service`);
1129
+ const bootstrapServiceDir = join20(applicationDir, "services", `${platformName}-${applicationName}-bootstrap-service`);
1007
1130
  const moduleEntry = buildCustomServiceModuleEntry(organizationName, fullServiceName, serviceDisplayName);
1008
1131
  await addModuleEntry(bootstrapServiceDir, applicationName, moduleEntry, logger);
1009
- const localDir = join19(rootDir, "local");
1010
- const dockerComposePath = join19(localDir, `${applicationName}-docker-compose.yml`);
1132
+ const dockerComposePath = join20(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
1011
1133
  const port = await getNextAvailablePort(localDir);
1012
- await appendServiceToDockerCompose(dockerComposePath, fullServiceName, applicationName, port, logger);
1134
+ await appendServiceToDockerCompose(dockerComposePath, fullServiceName, platformName, applicationName, port, logger);
1013
1135
  logger.log(`Done! Service module "${fullServiceName}" added to application "${applicationName}".`);
1014
1136
  }
1015
1137
 
1016
1138
  // src/commands/create-ui-module/create-ui-module.command.ts
1017
- import { join as join20 } from "path";
1018
- import { cwd as cwd6 } from "process";
1019
- import { access as access3, readdir as readdir3, readFile as readFile11 } from "fs/promises";
1139
+ import { join as join21, resolve as resolve3 } from "path";
1140
+ import { access as access4, readdir as readdir4 } from "fs/promises";
1020
1141
 
1021
1142
  // src/commands/create-ui-module/append-ui-docker-compose.ts
1022
- import { readFile as readFile10, writeFile as writeFile8 } from "fs/promises";
1023
- async function appendUiToDockerCompose(dockerComposePath, applicationName, port, logger) {
1143
+ import { readFile as readFile9, writeFile as writeFile8 } from "fs/promises";
1144
+ async function appendUiToDockerCompose(dockerComposePath, platformName, applicationName, port, logger) {
1145
+ const uiName = `${platformName}-${applicationName}-ui`;
1024
1146
  const block = `
1025
- ${applicationName}-ui:
1147
+ ${uiName}:
1026
1148
  build:
1027
- context: ../${applicationName}/ui/${applicationName}-ui
1149
+ context: ../../${platformName}-${applicationName}/ui/${uiName}
1028
1150
  dockerfile: Dockerfile.development
1029
1151
  ports:
1030
1152
  - ${port}:80
1031
1153
  volumes:
1032
- - \${PWD}/../:/app/out
1154
+ - \${PWD}/:/app/out
1033
1155
  `;
1034
1156
  try {
1035
- const existing = await readFile10(dockerComposePath, "utf-8");
1157
+ const existing = await readFile9(dockerComposePath, "utf-8");
1036
1158
  await writeFile8(dockerComposePath, existing + block, "utf-8");
1037
1159
  logger.log(`Updated docker-compose: ${dockerComposePath}`);
1038
1160
  } catch (err) {
@@ -1049,21 +1171,35 @@ var createUiModuleCommand = {
1049
1171
  };
1050
1172
  async function createUiModule(params, logger) {
1051
1173
  const { applicationName, applicationDisplayName } = params;
1052
- if (!await isPlatformInitialized()) {
1174
+ const layout = await findPlatformLayout();
1175
+ if (!layout) {
1053
1176
  logger.log("Error: Cannot create a UI module \u2014 no platform initialized in this directory.");
1054
1177
  return;
1055
1178
  }
1056
- const rootDir = cwd6();
1057
- const applicationDir = join20(rootDir, applicationName);
1179
+ const { rootDir, coreDirName, localDir } = layout;
1180
+ let manifest;
1058
1181
  try {
1059
- await access3(applicationDir);
1182
+ manifest = await readManifest(rootDir, coreDirName);
1183
+ } catch (err) {
1184
+ logger.log(`Error: Could not read product manifest \u2014 ${formatError(err)}`);
1185
+ return;
1186
+ }
1187
+ const { organization: organizationName, name: platformName } = manifest.product;
1188
+ const appEntry = manifest.applications.find((a) => a.name === applicationName);
1189
+ if (!appEntry) {
1190
+ logger.log(`Error: The specified application "${applicationName}" is not registered in the product manifest.`);
1191
+ return;
1192
+ }
1193
+ const applicationDir = resolve3(join21(rootDir, coreDirName), appEntry.localPath);
1194
+ try {
1195
+ await access4(applicationDir);
1060
1196
  } catch {
1061
1197
  logger.log(`Error: The specified application "${applicationName}" does not exist in the platform.`);
1062
1198
  return;
1063
1199
  }
1064
- const uiDir = join20(applicationDir, "ui");
1200
+ const uiDir = join21(applicationDir, "ui");
1065
1201
  try {
1066
- const uiEntries = await readdir3(uiDir);
1202
+ const uiEntries = await readdir4(uiDir);
1067
1203
  const existingUiModules = uiEntries.filter((e) => e !== ".gitkeep");
1068
1204
  if (existingUiModules.length > 0) {
1069
1205
  logger.log(`Error: Currently we support only one UI module per application. Application "${applicationName}" already has a UI module.`);
@@ -1071,39 +1207,22 @@ async function createUiModule(params, logger) {
1071
1207
  }
1072
1208
  } catch {
1073
1209
  }
1074
- let organizationName;
1075
- let platformName;
1076
- try {
1077
- const corePackageJson = JSON.parse(
1078
- await readFile11(join20(rootDir, "core", "package.json"), "utf-8")
1079
- );
1080
- const scopeMatch = corePackageJson.name.match(/^@([^/]+)\//);
1081
- organizationName = scopeMatch?.[1];
1082
- const rawName = corePackageJson.name.replace(/^@[^/]+\//, "").replace(/-core$/, "");
1083
- platformName = rawName;
1084
- if (!organizationName || !platformName) {
1085
- throw new Error(`Could not parse organization/platform from package name: "${corePackageJson.name}"`);
1086
- }
1087
- } catch (err) {
1088
- logger.log(`Error: Could not read core/package.json \u2014 ${formatError(err)}`);
1089
- return;
1090
- }
1091
- const uiOutputDir = join20(uiDir, `${applicationName}-ui`);
1092
- const uiBaseDir = `${applicationName}/ui`;
1210
+ const uiName = `${platformName}-${applicationName}-ui`;
1211
+ const uiOutputDir = join21(uiDir, uiName);
1212
+ const uiBaseDir = `${platformName}-${applicationName}/ui`;
1093
1213
  try {
1094
1214
  await scaffoldUiModule(uiOutputDir, organizationName, platformName, applicationName, applicationDisplayName, uiBaseDir, logger);
1095
1215
  } catch (err) {
1096
1216
  logger.log(`Error: Could not scaffold UI module \u2014 ${formatError(err)}`);
1097
1217
  return;
1098
1218
  }
1099
- const bootstrapServiceDir = join20(applicationDir, "services", `${applicationName}-bootstrap-service`);
1100
- const moduleEntry = buildUiModuleEntry(organizationName, applicationName, applicationDisplayName);
1219
+ const bootstrapServiceDir = join21(applicationDir, "services", `${platformName}-${applicationName}-bootstrap-service`);
1220
+ const moduleEntry = buildUiModuleEntry(organizationName, platformName, applicationName, applicationDisplayName);
1101
1221
  await addModuleEntry(bootstrapServiceDir, applicationName, moduleEntry, logger);
1102
- const localDir = join20(rootDir, "local");
1103
- const dockerComposePath = join20(localDir, `${applicationName}-docker-compose.yml`);
1222
+ const dockerComposePath = join21(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
1104
1223
  const port = await getNextAvailablePort(localDir);
1105
- await appendUiToDockerCompose(dockerComposePath, applicationName, port, logger);
1106
- logger.log(`Done! UI module "${applicationName}-ui" added to application "${applicationName}".`);
1224
+ await appendUiToDockerCompose(dockerComposePath, platformName, applicationName, port, logger);
1225
+ logger.log(`Done! UI module "${uiName}" added to application "${applicationName}".`);
1107
1226
  }
1108
1227
 
1109
1228
  // src/commands/registry.ts
@@ -1145,11 +1264,6 @@ var APP_STATE = {
1145
1264
  // src/hooks/use-command-runner.ts
1146
1265
  import { useState as useState2, useCallback, useRef } from "react";
1147
1266
 
1148
- // src/controllers/ui/create-application.ui-controller.ts
1149
- import { readFile as readFile12 } from "fs/promises";
1150
- import { join as join21 } from "path";
1151
- import { cwd as cwd7 } from "process";
1152
-
1153
1267
  // src/services/create-application.service.ts
1154
1268
  async function createApplicationService(params, logger) {
1155
1269
  await createApplication(params, logger);
@@ -1161,24 +1275,6 @@ async function createApplicationUiController(ctx) {
1161
1275
  ctx.log("Error: Cannot create an application \u2014 no platform initialized in this directory.");
1162
1276
  return;
1163
1277
  }
1164
- let organizationName;
1165
- let platformName;
1166
- try {
1167
- const corePackageJson = JSON.parse(
1168
- await readFile12(join21(cwd7(), "core", "package.json"), "utf-8")
1169
- );
1170
- const scopeMatch = corePackageJson.name.match(/^@([^/]+)\//);
1171
- organizationName = scopeMatch?.[1];
1172
- const rawName = corePackageJson.name.replace(/^@[^/]+\//, "").replace(/-core$/, "");
1173
- platformName = rawName;
1174
- if (!organizationName || !platformName) {
1175
- throw new Error(`Could not parse organization/platform from package name: "${corePackageJson.name}"`);
1176
- }
1177
- } catch (err) {
1178
- const message = err instanceof Error ? err.message : String(err);
1179
- ctx.log(`Error: Could not read core/package.json \u2014 ${message}`);
1180
- return;
1181
- }
1182
1278
  const applicationName = await ctx.prompt("Application name:");
1183
1279
  if (!/^[a-z0-9-]+$/.test(applicationName)) {
1184
1280
  ctx.log(`Error: Application name "${applicationName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
@@ -1198,8 +1294,6 @@ async function createApplicationUiController(ctx) {
1198
1294
  }
1199
1295
  await createApplicationService(
1200
1296
  {
1201
- organizationName,
1202
- platformName,
1203
1297
  applicationName,
1204
1298
  applicationDisplayName,
1205
1299
  applicationDescription,
@@ -1333,7 +1427,7 @@ function useCommandRunner({ appendStaticItem, setState }) {
1333
1427
  abortControllerRef.current = null;
1334
1428
  promptResolveRef.current = null;
1335
1429
  }, []);
1336
- const runCommand = useCallback(
1430
+ const runCommand2 = useCallback(
1337
1431
  (cmd) => {
1338
1432
  const controller = new AbortController();
1339
1433
  abortControllerRef.current = controller;
@@ -1347,14 +1441,14 @@ function useCommandRunner({ appendStaticItem, setState }) {
1347
1441
  }
1348
1442
  },
1349
1443
  prompt(message) {
1350
- return new Promise((resolve, reject) => {
1444
+ return new Promise((resolve5, reject) => {
1351
1445
  if (controller.signal.aborted) {
1352
1446
  reject(new DOMException("Aborted", "AbortError"));
1353
1447
  return;
1354
1448
  }
1355
1449
  setPromptMessage(message);
1356
1450
  setPromptValue("");
1357
- promptResolveRef.current = resolve;
1451
+ promptResolveRef.current = resolve5;
1358
1452
  setState(APP_STATE.PROMPTING);
1359
1453
  controller.signal.addEventListener(
1360
1454
  "abort",
@@ -1386,15 +1480,15 @@ function useCommandRunner({ appendStaticItem, setState }) {
1386
1480
  );
1387
1481
  const handlePromptSubmit = useCallback(
1388
1482
  (value) => {
1389
- const resolve = promptResolveRef.current;
1483
+ const resolve5 = promptResolveRef.current;
1390
1484
  promptResolveRef.current = null;
1391
1485
  setState(APP_STATE.EXECUTING);
1392
- resolve?.(value);
1486
+ resolve5?.(value);
1393
1487
  },
1394
1488
  [setState]
1395
1489
  );
1396
1490
  return {
1397
- runCommand,
1491
+ runCommand: runCommand2,
1398
1492
  handlePromptSubmit,
1399
1493
  abortExecution,
1400
1494
  promptMessage,
@@ -1404,7 +1498,7 @@ function useCommandRunner({ appendStaticItem, setState }) {
1404
1498
  }
1405
1499
 
1406
1500
  // src/app.tsx
1407
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1501
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1408
1502
  var require2 = createRequire(import.meta.url);
1409
1503
  var { version } = require2("../package.json");
1410
1504
  function App() {
@@ -1416,7 +1510,7 @@ function App() {
1416
1510
  const appendStaticItem = useCallback2((item) => {
1417
1511
  setStaticItems((prev) => [...prev, item]);
1418
1512
  }, []);
1419
- const { runCommand, handlePromptSubmit, abortExecution, promptMessage, promptValue, setPromptValue } = useCommandRunner({ appendStaticItem, setState });
1513
+ const { runCommand: runCommand2, handlePromptSubmit, abortExecution, promptMessage, promptValue, setPromptValue } = useCommandRunner({ appendStaticItem, setState });
1420
1514
  const query = inputValue.startsWith("/") ? inputValue.slice(1) : "";
1421
1515
  const filteredCommands = registry.search(query);
1422
1516
  useInput(
@@ -1448,7 +1542,7 @@ function App() {
1448
1542
  const cmd = filteredCommands[selectedIndex];
1449
1543
  if (cmd) {
1450
1544
  setInputValue("");
1451
- runCommand(cmd);
1545
+ runCommand2(cmd);
1452
1546
  }
1453
1547
  return;
1454
1548
  }
@@ -1484,24 +1578,24 @@ function App() {
1484
1578
  if (!value.startsWith("/")) return;
1485
1579
  const name = value.slice(1).trim();
1486
1580
  const cmd = registry.get(name);
1487
- if (cmd) runCommand(cmd);
1581
+ if (cmd) runCommand2(cmd);
1488
1582
  },
1489
- [runCommand]
1583
+ [runCommand2]
1490
1584
  );
1491
1585
  function renderActiveArea() {
1492
1586
  switch (state) {
1493
1587
  case APP_STATE.EXECUTING:
1494
- return /* @__PURE__ */ jsxs5(Box4, { flexDirection: "row", gap: 1, children: [
1495
- /* @__PURE__ */ jsx6(Spinner, { label: "Running\u2026" }),
1496
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "(esc to cancel)" })
1588
+ return /* @__PURE__ */ jsxs6(Box4, { flexDirection: "row", gap: 1, children: [
1589
+ /* @__PURE__ */ jsx7(Spinner, { label: "Running\u2026" }),
1590
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "(esc to cancel)" })
1497
1591
  ] });
1498
1592
  case APP_STATE.PROMPTING:
1499
- return /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", children: [
1500
- /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: promptMessage }),
1501
- /* @__PURE__ */ jsx6(Prompt, { value: promptValue, onChange: setPromptValue, onSubmit: handlePromptSubmit })
1593
+ return /* @__PURE__ */ jsxs6(Box4, { flexDirection: "column", children: [
1594
+ /* @__PURE__ */ jsx7(Text7, { color: "cyan", children: promptMessage }),
1595
+ /* @__PURE__ */ jsx7(Prompt, { value: promptValue, onChange: setPromptValue, onSubmit: handlePromptSubmit })
1502
1596
  ] });
1503
1597
  default:
1504
- return /* @__PURE__ */ jsx6(
1598
+ return /* @__PURE__ */ jsx7(
1505
1599
  Prompt,
1506
1600
  {
1507
1601
  value: inputValue,
@@ -1514,17 +1608,14 @@ function App() {
1514
1608
  );
1515
1609
  }
1516
1610
  }
1517
- return /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", children: [
1518
- /* @__PURE__ */ jsx6(ScrollbackHistory, { items: staticItems, version }),
1611
+ return /* @__PURE__ */ jsxs6(Box4, { flexDirection: "column", children: [
1612
+ /* @__PURE__ */ jsx7(ScrollbackHistory, { items: staticItems, version }),
1519
1613
  renderActiveArea(),
1520
- state === APP_STATE.PALETTE && /* @__PURE__ */ jsx6(CommandPalette, { commands: filteredCommands, selectedIndex })
1614
+ state === APP_STATE.PALETTE && /* @__PURE__ */ jsx7(CommandPalette, { commands: filteredCommands, selectedIndex })
1521
1615
  ] });
1522
1616
  }
1523
1617
 
1524
1618
  // src/controllers/cli/create-application.cli-controller.ts
1525
- import { readFile as readFile13 } from "fs/promises";
1526
- import { join as join22 } from "path";
1527
- import { cwd as cwd8 } from "process";
1528
1619
  async function createApplicationCliController(args2) {
1529
1620
  if (!await isPlatformInitialized()) {
1530
1621
  console.error("Error: Cannot create an application \u2014 no platform initialized in this directory.");
@@ -1545,28 +1636,8 @@ async function createApplicationCliController(args2) {
1545
1636
  console.error(`Error: Application name "${applicationName}" is invalid. Use only lowercase letters, numbers, and hyphens.`);
1546
1637
  process.exit(1);
1547
1638
  }
1548
- let organizationName;
1549
- let platformName;
1550
- try {
1551
- const corePackageJson = JSON.parse(
1552
- await readFile13(join22(cwd8(), "core", "package.json"), "utf-8")
1553
- );
1554
- const scopeMatch = corePackageJson.name.match(/^@([^/]+)\//);
1555
- organizationName = scopeMatch?.[1];
1556
- const rawName = corePackageJson.name.replace(/^@[^/]+\//, "").replace(/-core$/, "");
1557
- platformName = rawName;
1558
- if (!organizationName || !platformName) {
1559
- throw new Error(`Could not parse organization/platform from package name: "${corePackageJson.name}"`);
1560
- }
1561
- } catch (err) {
1562
- const message = err instanceof Error ? err.message : String(err);
1563
- console.error(`Error: Could not read core/package.json \u2014 ${message}`);
1564
- process.exit(1);
1565
- }
1566
1639
  await createApplicationService(
1567
1640
  {
1568
- organizationName,
1569
- platformName,
1570
1641
  applicationName,
1571
1642
  applicationDisplayName,
1572
1643
  applicationDescription,
@@ -1623,22 +1694,187 @@ async function configureIdpCliController(args2) {
1623
1694
  await configureIdpService({ providerType, name, issuer, clientId, clientSecret, extras }, logger);
1624
1695
  }
1625
1696
 
1626
- // src/utils/run-npm-script.ts
1697
+ // src/commands/local-scripts/docker-compose-orchestrator.ts
1627
1698
  import { spawn } from "child_process";
1628
- import { access as access4 } from "fs/promises";
1629
- import { join as join23 } from "path";
1630
- async function runNpmScript(scriptName, logger, signal) {
1631
- const localDir = join23(process.cwd(), "local");
1699
+ import { access as access5 } from "fs/promises";
1700
+ import { join as join22 } from "path";
1701
+ function runDockerCompose(args2, logger, rootDir, signal) {
1702
+ return new Promise((resolvePromise) => {
1703
+ const child = spawn("docker", ["compose", ...args2], {
1704
+ shell: false,
1705
+ stdio: ["ignore", "pipe", "pipe"],
1706
+ cwd: rootDir
1707
+ });
1708
+ const onAbort = () => {
1709
+ child.kill("SIGTERM");
1710
+ };
1711
+ if (signal) {
1712
+ if (signal.aborted) {
1713
+ child.kill("SIGTERM");
1714
+ resolvePromise();
1715
+ return;
1716
+ }
1717
+ signal.addEventListener("abort", onAbort, { once: true });
1718
+ }
1719
+ child.stdout.on("data", (data) => {
1720
+ for (const line of data.toString().split("\n")) {
1721
+ if (line.trim()) logger.log(line);
1722
+ }
1723
+ });
1724
+ child.stderr.on("data", (data) => {
1725
+ for (const line of data.toString().split("\n")) {
1726
+ if (line.trim()) logger.log(line);
1727
+ }
1728
+ });
1729
+ child.on("close", (code, sig) => {
1730
+ signal?.removeEventListener("abort", onAbort);
1731
+ if (sig === "SIGTERM" || signal?.aborted) {
1732
+ logger.log("Command cancelled.");
1733
+ } else if (code !== 0) {
1734
+ logger.log(`docker compose exited with code ${code}.`);
1735
+ }
1736
+ resolvePromise();
1737
+ });
1738
+ child.on("error", (err) => {
1739
+ signal?.removeEventListener("abort", onAbort);
1740
+ logger.log(`Failed to run docker compose: ${err.message}`);
1741
+ resolvePromise();
1742
+ });
1743
+ });
1744
+ }
1745
+ async function getAppComposePaths(localDir, platformName, manifest) {
1746
+ const results = [];
1747
+ for (const app of manifest.applications) {
1748
+ const prefixedPath = join22(localDir, `${platformName}-${app.name}-docker-compose.yml`);
1749
+ const unprefixedPath = join22(localDir, `${app.name}-docker-compose.yml`);
1750
+ let resolved = null;
1751
+ try {
1752
+ await access5(prefixedPath);
1753
+ resolved = prefixedPath;
1754
+ } catch {
1755
+ try {
1756
+ await access5(unprefixedPath);
1757
+ resolved = unprefixedPath;
1758
+ } catch {
1759
+ }
1760
+ }
1761
+ if (resolved) {
1762
+ results.push({ composePath: resolved, appName: app.name });
1763
+ }
1764
+ }
1765
+ return results;
1766
+ }
1767
+ async function buildAllComposeArgs(layout, manifest, logger) {
1768
+ const { rootDir, coreDirName, localDir } = layout;
1769
+ const platformName = manifest.product.name;
1770
+ const prefixedCoreCompose = join22(localDir, `${coreDirName}-docker-compose.yml`);
1771
+ const unprefixedCoreCompose = join22(localDir, "core-docker-compose.yml");
1772
+ let coreComposePath;
1632
1773
  try {
1633
- await access4(localDir);
1774
+ await access5(prefixedCoreCompose);
1775
+ coreComposePath = prefixedCoreCompose;
1634
1776
  } catch {
1635
- logger.log(`Error: "local/" directory not found. Run "platform init" first.`);
1636
- return;
1777
+ coreComposePath = unprefixedCoreCompose;
1778
+ }
1779
+ const fileArgs = [
1780
+ "-f",
1781
+ join22(localDir, "platform-docker-compose.yml"),
1782
+ "-f",
1783
+ coreComposePath
1784
+ ];
1785
+ const appEntries = await getAppComposePaths(localDir, platformName, manifest);
1786
+ for (const { composePath } of appEntries) {
1787
+ fileArgs.push("-f", composePath);
1788
+ }
1789
+ for (const app of manifest.applications) {
1790
+ if (!appEntries.find((e) => e.appName === app.name)) {
1791
+ logger.log(`Warning: No docker-compose found for application "${app.name}" in ${coreDirName}/local/ \u2014 skipping.`);
1792
+ }
1637
1793
  }
1638
- return new Promise((resolve) => {
1639
- const child = spawn("npm", ["run", scriptName], {
1640
- cwd: localDir,
1641
- shell: true,
1794
+ return fileArgs;
1795
+ }
1796
+ async function startEnvironment(layout, manifest, logger, signal) {
1797
+ const { rootDir, localDir } = layout;
1798
+ const platformName = manifest.product.name;
1799
+ const envFile = join22(localDir, ".env");
1800
+ const fileArgs = await buildAllComposeArgs(layout, manifest, logger);
1801
+ logger.log("Starting environment...");
1802
+ await runDockerCompose(
1803
+ [
1804
+ "-p",
1805
+ `${platformName}-platform`,
1806
+ "--project-directory",
1807
+ localDir,
1808
+ "--env-file",
1809
+ envFile,
1810
+ ...fileArgs,
1811
+ "up",
1812
+ "-d",
1813
+ "--build",
1814
+ "--remove-orphans"
1815
+ ],
1816
+ logger,
1817
+ rootDir,
1818
+ signal
1819
+ );
1820
+ }
1821
+ async function stopEnvironment(layout, manifest, logger, signal) {
1822
+ const { rootDir, localDir } = layout;
1823
+ const platformName = manifest.product.name;
1824
+ const envFile = join22(localDir, ".env");
1825
+ const fileArgs = await buildAllComposeArgs(layout, manifest, logger);
1826
+ logger.log("Stopping environment...");
1827
+ await runDockerCompose(
1828
+ [
1829
+ "-p",
1830
+ `${platformName}-platform`,
1831
+ "--project-directory",
1832
+ localDir,
1833
+ "--env-file",
1834
+ envFile,
1835
+ ...fileArgs,
1836
+ "down"
1837
+ ],
1838
+ logger,
1839
+ rootDir,
1840
+ signal
1841
+ );
1842
+ }
1843
+ async function destroyEnvironment(layout, manifest, logger, signal) {
1844
+ const { rootDir, localDir } = layout;
1845
+ const platformName = manifest.product.name;
1846
+ const envFile = join22(localDir, ".env");
1847
+ const fileArgs = await buildAllComposeArgs(layout, manifest, logger);
1848
+ logger.log("Destroying environment...");
1849
+ await runDockerCompose(
1850
+ [
1851
+ "-p",
1852
+ `${platformName}-platform`,
1853
+ "--project-directory",
1854
+ localDir,
1855
+ "--env-file",
1856
+ envFile,
1857
+ ...fileArgs,
1858
+ "down",
1859
+ "-v",
1860
+ "--rmi",
1861
+ "all"
1862
+ ],
1863
+ logger,
1864
+ rootDir,
1865
+ signal
1866
+ );
1867
+ }
1868
+
1869
+ // src/commands/local-scripts/npm-orchestrator.ts
1870
+ import { spawn as spawn2 } from "child_process";
1871
+ import { access as access6 } from "fs/promises";
1872
+ import { resolve as resolve4, join as join23 } from "path";
1873
+ function runCommand(command, args2, workDir, logger, signal) {
1874
+ return new Promise((resolvePromise) => {
1875
+ const child = spawn2(command, args2, {
1876
+ cwd: workDir,
1877
+ shell: false,
1642
1878
  stdio: ["ignore", "pipe", "pipe"]
1643
1879
  });
1644
1880
  const onAbort = () => {
@@ -1647,7 +1883,7 @@ async function runNpmScript(scriptName, logger, signal) {
1647
1883
  if (signal) {
1648
1884
  if (signal.aborted) {
1649
1885
  child.kill("SIGTERM");
1650
- resolve();
1886
+ resolvePromise();
1651
1887
  return;
1652
1888
  }
1653
1889
  signal.addEventListener("abort", onAbort, { once: true });
@@ -1665,19 +1901,72 @@ async function runNpmScript(scriptName, logger, signal) {
1665
1901
  child.on("close", (code, sig) => {
1666
1902
  signal?.removeEventListener("abort", onAbort);
1667
1903
  if (sig === "SIGTERM" || signal?.aborted) {
1668
- logger.log(`Command cancelled.`);
1904
+ logger.log("Command cancelled.");
1669
1905
  } else if (code !== 0) {
1670
- logger.log(`Command "npm run ${scriptName}" exited with code ${code}.`);
1906
+ logger.log(`Command "${command} ${args2.join(" ")}" exited with code ${code}.`);
1671
1907
  }
1672
- resolve();
1908
+ resolvePromise();
1673
1909
  });
1674
1910
  child.on("error", (err) => {
1675
1911
  signal?.removeEventListener("abort", onAbort);
1676
- logger.log(`Failed to run "npm run ${scriptName}": ${err.message}`);
1677
- resolve();
1912
+ logger.log(`Failed to run "${command} ${args2.join(" ")}": ${err.message}`);
1913
+ resolvePromise();
1678
1914
  });
1679
1915
  });
1680
1916
  }
1917
+ async function dirExists(dirPath) {
1918
+ try {
1919
+ await access6(dirPath);
1920
+ return true;
1921
+ } catch {
1922
+ return false;
1923
+ }
1924
+ }
1925
+ async function installDependencies(layout, manifest, logger, signal) {
1926
+ const { coreDir, coreDirName } = layout;
1927
+ const appDirs = [];
1928
+ for (const app of manifest.applications) {
1929
+ const appDir = resolve4(join23(coreDir), app.localPath);
1930
+ if (!await dirExists(appDir)) {
1931
+ logger.log(`Warning: Application directory "${app.name}" not found at ${appDir} \u2014 skipping install.`);
1932
+ continue;
1933
+ }
1934
+ appDirs.push({ name: app.name, dir: appDir });
1935
+ }
1936
+ const targets = [{ name: coreDirName, dir: coreDir }, ...appDirs];
1937
+ logger.log(`Installing dependencies in parallel: ${targets.map((t) => t.name).join(", ")}...`);
1938
+ await Promise.all(
1939
+ targets.map(
1940
+ ({ name, dir }) => runCommand("npm", ["install"], dir, logger, signal).then(() => {
1941
+ logger.log(`\u2713 ${name} install done`);
1942
+ })
1943
+ )
1944
+ );
1945
+ }
1946
+ async function buildAll(layout, manifest, logger, signal) {
1947
+ const { coreDir, coreDirName } = layout;
1948
+ logger.log(`Building ${coreDirName}/...`);
1949
+ await runCommand("npx", ["turbo", "build"], coreDir, logger, signal);
1950
+ if (signal?.aborted) return;
1951
+ const appDirs = [];
1952
+ for (const app of manifest.applications) {
1953
+ const appDir = resolve4(join23(coreDir), app.localPath);
1954
+ if (!await dirExists(appDir)) {
1955
+ logger.log(`Warning: Application directory "${app.name}" not found at ${appDir} \u2014 skipping build.`);
1956
+ continue;
1957
+ }
1958
+ appDirs.push({ name: app.name, dir: appDir });
1959
+ }
1960
+ if (appDirs.length === 0) return;
1961
+ logger.log(`Building apps in parallel: ${appDirs.map((a) => a.name).join(", ")}...`);
1962
+ await Promise.all(
1963
+ appDirs.map(
1964
+ ({ name, dir }) => runCommand("npm", ["run", "build"], dir, logger, signal).then(() => {
1965
+ logger.log(`\u2713 ${name} build done`);
1966
+ })
1967
+ )
1968
+ );
1969
+ }
1681
1970
 
1682
1971
  // src/commands/local-scripts/local-script.command.ts
1683
1972
  var INSTALL_COMMAND_NAME = "install";
@@ -1686,7 +1975,38 @@ var START_COMMAND_NAME = "start";
1686
1975
  var STOP_COMMAND_NAME = "stop";
1687
1976
  var DESTROY_COMMAND_NAME = "destroy";
1688
1977
  async function runLocalScript(scriptName, logger, signal) {
1689
- await runNpmScript(scriptName, logger, signal);
1978
+ const layout = await findPlatformLayout();
1979
+ if (!layout) {
1980
+ logger.log(`Error: Cannot run "${scriptName}" \u2014 no platform initialized in this directory.`);
1981
+ return;
1982
+ }
1983
+ const { rootDir, coreDirName } = layout;
1984
+ let manifest;
1985
+ try {
1986
+ manifest = await readManifest(rootDir, coreDirName);
1987
+ } catch (err) {
1988
+ logger.log(`Error: Could not read product manifest \u2014 ${formatError(err)}`);
1989
+ return;
1990
+ }
1991
+ switch (scriptName) {
1992
+ case START_COMMAND_NAME:
1993
+ await startEnvironment(layout, manifest, logger, signal);
1994
+ break;
1995
+ case STOP_COMMAND_NAME:
1996
+ await stopEnvironment(layout, manifest, logger, signal);
1997
+ break;
1998
+ case DESTROY_COMMAND_NAME:
1999
+ await destroyEnvironment(layout, manifest, logger, signal);
2000
+ break;
2001
+ case INSTALL_COMMAND_NAME:
2002
+ await installDependencies(layout, manifest, logger, signal);
2003
+ break;
2004
+ case BUILD_COMMAND_NAME:
2005
+ await buildAll(layout, manifest, logger, signal);
2006
+ break;
2007
+ default:
2008
+ logger.log(`Error: Unknown script "${scriptName}".`);
2009
+ }
1690
2010
  }
1691
2011
 
1692
2012
  // src/services/local-script.service.ts
@@ -1812,10 +2132,10 @@ function parseKeyValueArgs(args2) {
1812
2132
  }
1813
2133
 
1814
2134
  // src/index.tsx
1815
- import { jsx as jsx7 } from "react/jsx-runtime";
2135
+ import { jsx as jsx8 } from "react/jsx-runtime";
1816
2136
  var args = process.argv.slice(2);
1817
2137
  if (args.length === 0) {
1818
- render(/* @__PURE__ */ jsx7(App, {}));
2138
+ render(/* @__PURE__ */ jsx8(App, {}));
1819
2139
  } else {
1820
2140
  const commandName = args[0];
1821
2141
  const params = parseKeyValueArgs(args.slice(1));