@bluealba/platform-cli 1.2.0-alpha.2 → 1.2.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -204,7 +204,7 @@ function ConfirmPrompt({ value }) {
204
204
  import { Fzf } from "fzf";
205
205
 
206
206
  // src/commands/create-application/create-application.command.ts
207
- import { join as join12, resolve as resolve2 } from "path";
207
+ import { join as join13, resolve as resolve2 } from "path";
208
208
  import { cwd as cwd4 } from "process";
209
209
  import { mkdir as mkdir3 } from "fs/promises";
210
210
 
@@ -730,6 +730,15 @@ async function getDefaults() {
730
730
  const cache = await readCacheFile();
731
731
  return cache.defaults;
732
732
  }
733
+ var SENSITIVE_KEYS = /* @__PURE__ */ new Set(["clientSecret"]);
734
+ function maskSensitiveFields(obj) {
735
+ return JSON.parse(
736
+ JSON.stringify(
737
+ obj,
738
+ (key, value) => SENSITIVE_KEYS.has(key) && typeof value === "string" && value.length > 0 ? "***" : value
739
+ )
740
+ );
741
+ }
733
742
  var LEGACY_CONFIG_FILE = "standalone.json";
734
743
  async function readLegacyConfig(appDir) {
735
744
  try {
@@ -742,6 +751,50 @@ async function readLegacyConfig(appDir) {
742
751
  }
743
752
  }
744
753
 
754
+ // src/utils/workspace-claude-md.ts
755
+ import { join as join12 } from "path";
756
+ import { writeFile as writeFile6 } from "fs/promises";
757
+ async function writeWorkspaceClaudeMd(rootDir, manifest) {
758
+ const { product, applications } = manifest;
759
+ const coreDirName = `${product.name}-core`;
760
+ const appLines = applications.map(
761
+ (app) => `- \`${product.name}-${app.name}/\` \u2014 ${app.displayName}: ${app.description}`
762
+ );
763
+ const appSection = appLines.length > 0 ? `
764
+ ### Applications
765
+
766
+ ${appLines.join("\n")}
767
+ ` : "";
768
+ const content = `# ${product.displayName} Platform
769
+
770
+ This is a **Blue Alba Platform** workspace for \`@${product.organization}\`.
771
+
772
+ For any platform-related task, use the \`/platform\` skill \u2014 it routes to the correct specialized skill automatically.
773
+
774
+ ## Workspace Layout
775
+
776
+ ### Core
777
+
778
+ - \`${coreDirName}/\` \u2014 Core monorepo (bootstrap service, customization UI, local dev environment)
779
+ - \`${coreDirName}/local/\` \u2014 Docker Compose files, .env, SSL certificates
780
+ - \`${coreDirName}/local/platform-docker-compose.yml\` \u2014 Platform infrastructure (nginx, postgres, gateway)
781
+ - \`${coreDirName}/local/${product.name}-core-docker-compose.yml\` \u2014 Core application services
782
+ - \`${coreDirName}/local/.env\` \u2014 Environment variables (shared by all applications)
783
+ ${appSection}
784
+ ## Common Commands
785
+
786
+ \`\`\`sh
787
+ platform install # Install all monorepos
788
+ platform start # Start the full platform locally via Docker
789
+ platform stop # Stop all running containers
790
+ platform status # Show platform health and service status
791
+ platform create-application # Add a new application to this platform
792
+ platform standalone # Run a single application in isolation
793
+ \`\`\`
794
+ `;
795
+ await writeFile6(join12(rootDir, "CLAUDE.md"), content, "utf-8");
796
+ }
797
+
745
798
  // src/commands/create-application/create-application.command.ts
746
799
  var CREATE_APPLICATION_COMMAND_NAME = "create-application";
747
800
  var createApplicationCommand = {
@@ -779,7 +832,7 @@ async function createApplication(params, logger) {
779
832
  platformName = manifest.product.name;
780
833
  organizationName = manifest.product.organization;
781
834
  const localPath = `../${platformName}-${applicationName}`;
782
- applicationDir = resolve2(join12(rootDir, coreDirName), localPath);
835
+ applicationDir = resolve2(join13(rootDir, coreDirName), localPath);
783
836
  } else {
784
837
  if (!params.platformName || !params.organizationName) {
785
838
  logger.log("Error: platformName and organizationName are required when creating an application outside a platform.");
@@ -787,10 +840,10 @@ async function createApplication(params, logger) {
787
840
  }
788
841
  platformName = params.platformName;
789
842
  organizationName = params.organizationName;
790
- applicationDir = join12(cwd4(), `${platformName}-${applicationName}`);
843
+ applicationDir = join13(cwd4(), `${platformName}-${applicationName}`);
791
844
  }
792
845
  const bootstrapServiceName = `${platformName}-${applicationName}-bootstrap-service`;
793
- const bootstrapServiceDir = join12(applicationDir, "services", bootstrapServiceName);
846
+ const bootstrapServiceDir = join13(applicationDir, "services", bootstrapServiceName);
794
847
  logger.log(`Creating application monorepo "${applicationName}"...`);
795
848
  try {
796
849
  await scaffoldApplicationMonorepo(applicationDir, organizationName, platformName, applicationName, logger);
@@ -798,8 +851,8 @@ async function createApplication(params, logger) {
798
851
  logger.log(`Error: Could not scaffold application monorepo \u2014 ${formatError(err)}`);
799
852
  return;
800
853
  }
801
- await mkdir3(join12(applicationDir, "services"), { recursive: true });
802
- await mkdir3(join12(applicationDir, "ui"), { recursive: true });
854
+ await mkdir3(join13(applicationDir, "services"), { recursive: true });
855
+ await mkdir3(join13(applicationDir, "ui"), { recursive: true });
803
856
  logger.log(`Creating bootstrap service "${bootstrapServiceName}"...`);
804
857
  const bootstrapServiceBaseDir = `${platformName}-${applicationName}/services`;
805
858
  try {
@@ -833,7 +886,7 @@ async function createApplication(params, logger) {
833
886
  }
834
887
  if (hasUserInterface) {
835
888
  const uiName = `${platformName}-${applicationName}-ui`;
836
- const uiDir = join12(applicationDir, "ui", uiName);
889
+ const uiDir = join13(applicationDir, "ui", uiName);
837
890
  const uiBaseDir = `${platformName}-${applicationName}/ui`;
838
891
  try {
839
892
  await scaffoldUiModule(uiDir, organizationName, platformName, applicationName, applicationDisplayName, uiBaseDir, logger);
@@ -844,7 +897,7 @@ async function createApplication(params, logger) {
844
897
  }
845
898
  if (hasBackendService) {
846
899
  const serviceName = `${platformName}-${applicationName}-service`;
847
- const serviceDir = join12(applicationDir, "services", serviceName);
900
+ const serviceDir = join13(applicationDir, "services", serviceName);
848
901
  const serviceBaseDir = `${platformName}-${applicationName}/services`;
849
902
  try {
850
903
  await scaffoldNestjsService(serviceDir, organizationName, platformName, applicationName, applicationDisplayName, serviceBaseDir, logger);
@@ -853,7 +906,7 @@ async function createApplication(params, logger) {
853
906
  return;
854
907
  }
855
908
  }
856
- const dockerComposePath = join12(applicationDir, "docker-compose.yml");
909
+ const dockerComposePath = join13(applicationDir, "docker-compose.yml");
857
910
  let uiPort;
858
911
  let servicePort;
859
912
  if (insidePlatform && localDir && manifest) {
@@ -889,6 +942,12 @@ async function createApplication(params, logger) {
889
942
  } catch (err) {
890
943
  logger.log(`Warning: Could not update product manifest \u2014 ${formatError(err)}`);
891
944
  }
945
+ try {
946
+ await writeWorkspaceClaudeMd(rootDir, updatedManifest);
947
+ logger.log(`Updated workspace CLAUDE.md.`);
948
+ } catch (err) {
949
+ logger.log(`Warning: Could not update workspace CLAUDE.md \u2014 ${formatError(err)}`);
950
+ }
892
951
  } else {
893
952
  try {
894
953
  await saveDefaults({ platformName, organizationName });
@@ -906,7 +965,7 @@ async function createApplication(params, logger) {
906
965
  }
907
966
 
908
967
  // src/commands/init/init.command.ts
909
- import { join as join18 } from "path";
968
+ import { join as join19 } from "path";
910
969
  import { cwd as cwd5 } from "process";
911
970
 
912
971
  // src/utils/string.ts
@@ -916,8 +975,8 @@ function camelize(name) {
916
975
 
917
976
  // src/commands/init/scaffold-platform.ts
918
977
  import { fileURLToPath as fileURLToPath6 } from "url";
919
- import { join as join13, dirname as dirname8 } from "path";
920
- var templateDir = join13(
978
+ import { join as join14, dirname as dirname8 } from "path";
979
+ var templateDir = join14(
921
980
  dirname8(fileURLToPath6(import.meta.url)),
922
981
  "..",
923
982
  "templates",
@@ -929,8 +988,8 @@ async function scaffoldPlatform(outputDir, variables, logger) {
929
988
 
930
989
  // src/commands/init/scaffold-platform-bootstrap.ts
931
990
  import { fileURLToPath as fileURLToPath7 } from "url";
932
- import { join as join14, dirname as dirname9 } from "path";
933
- var templateDir2 = join14(
991
+ import { join as join15, dirname as dirname9 } from "path";
992
+ var templateDir2 = join15(
934
993
  dirname9(fileURLToPath7(import.meta.url)),
935
994
  "..",
936
995
  "templates",
@@ -942,8 +1001,8 @@ async function scaffoldPlatformBootstrap(outputDir, variables, logger) {
942
1001
 
943
1002
  // src/commands/init/scaffold-customization-ui.ts
944
1003
  import { fileURLToPath as fileURLToPath8 } from "url";
945
- import { join as join15, dirname as dirname10 } from "path";
946
- var templateDir3 = join15(
1004
+ import { join as join16, dirname as dirname10 } from "path";
1005
+ var templateDir3 = join16(
947
1006
  dirname10(fileURLToPath8(import.meta.url)),
948
1007
  "..",
949
1008
  "templates",
@@ -954,10 +1013,10 @@ async function scaffoldCustomizationUi(outputDir, variables, logger) {
954
1013
  }
955
1014
 
956
1015
  // src/commands/init/register-customization-module.ts
957
- import { join as join16 } from "path";
958
- import { readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
1016
+ import { join as join17 } from "path";
1017
+ import { readFile as readFile6, writeFile as writeFile7 } from "fs/promises";
959
1018
  async function registerCustomizationModule(bootstrapServiceDir, organizationName, logger, platformName = "platform") {
960
- const modulesJsonPath = join16(bootstrapServiceDir, "src", "data", "platform", "modules.json");
1019
+ const modulesJsonPath = join17(bootstrapServiceDir, "src", "data", "platform", "modules.json");
961
1020
  const customizationUiName = `${platformName}-customization-ui`;
962
1021
  const entry = {
963
1022
  name: `@${organizationName}/${customizationUiName}`,
@@ -975,13 +1034,13 @@ async function registerCustomizationModule(bootstrapServiceDir, organizationName
975
1034
  };
976
1035
  const existing = JSON.parse(await readFile6(modulesJsonPath, "utf-8"));
977
1036
  existing.push(entry);
978
- await writeFile6(modulesJsonPath, JSON.stringify(existing, null, 2) + "\n", "utf-8");
1037
+ await writeFile7(modulesJsonPath, JSON.stringify(existing, null, 2) + "\n", "utf-8");
979
1038
  logger.log(`Registered customization module in ${modulesJsonPath}`);
980
1039
  }
981
1040
 
982
1041
  // src/commands/init/generate-local-env.ts
983
- import { readFile as readFile7, writeFile as writeFile7 } from "fs/promises";
984
- import { join as join17 } from "path";
1042
+ import { readFile as readFile7, writeFile as writeFile8 } from "fs/promises";
1043
+ import { join as join18 } from "path";
985
1044
 
986
1045
  // src/utils/random.ts
987
1046
  import { randomBytes } from "crypto";
@@ -991,14 +1050,14 @@ function generateRandomSecret() {
991
1050
 
992
1051
  // src/commands/init/generate-local-env.ts
993
1052
  async function generateLocalEnv(outputDir, logger, coreDirName = "core") {
994
- const examplePath = join17(outputDir, coreDirName, "local", ".env.example");
995
- const envPath = join17(outputDir, coreDirName, "local", ".env");
1053
+ const examplePath = join18(outputDir, coreDirName, "local", ".env.example");
1054
+ const envPath = join18(outputDir, coreDirName, "local", ".env");
996
1055
  logger.log(`Generating ${coreDirName}/local/.env with random secrets...`);
997
1056
  const content = await readFile7(examplePath, "utf-8");
998
1057
  const result = content.replace(/^(PAE_AUTH_JWT_SECRET|PAE_GATEWAY_SERVICE_ACCESS_SECRET|PAE_DB_PASSWORD)=$/gm, (_, key) => {
999
1058
  return `${key}=${generateRandomSecret()}`;
1000
1059
  });
1001
- await writeFile7(envPath, result, "utf-8");
1060
+ await writeFile8(envPath, result, "utf-8");
1002
1061
  }
1003
1062
 
1004
1063
  // src/commands/init/init.command.ts
@@ -1044,17 +1103,24 @@ async function init(params, logger) {
1044
1103
  logger.log(`Error: Could not generate ${coreDirName}/local/.env \u2014 ${formatError(err)}`);
1045
1104
  return;
1046
1105
  }
1106
+ let manifest;
1047
1107
  try {
1048
- const manifest = createInitialManifest({ organizationName, platformName, platformDisplayName });
1108
+ manifest = createInitialManifest({ organizationName, platformName, platformDisplayName });
1049
1109
  await writeManifest(manifest, outputDir, coreDirName);
1050
1110
  logger.log(`Created product manifest: ${coreDirName}/product.manifest.json`);
1051
1111
  } catch (err) {
1052
1112
  logger.log(`Error: Could not write product manifest \u2014 ${formatError(err)}`);
1053
1113
  return;
1054
1114
  }
1115
+ try {
1116
+ await writeWorkspaceClaudeMd(outputDir, manifest);
1117
+ logger.log(`Created workspace CLAUDE.md`);
1118
+ } catch (err) {
1119
+ logger.log(`Warning: Could not write workspace CLAUDE.md \u2014 ${formatError(err)}`);
1120
+ }
1055
1121
  try {
1056
1122
  await scaffoldPlatformBootstrap(
1057
- join18(outputDir, coreDirName, "services", bootstrapServiceName),
1123
+ join19(outputDir, coreDirName, "services", bootstrapServiceName),
1058
1124
  variables,
1059
1125
  logger
1060
1126
  );
@@ -1064,7 +1130,7 @@ async function init(params, logger) {
1064
1130
  }
1065
1131
  try {
1066
1132
  await scaffoldCustomizationUi(
1067
- join18(outputDir, coreDirName, "ui", customizationUiName),
1133
+ join19(outputDir, coreDirName, "ui", customizationUiName),
1068
1134
  variables,
1069
1135
  logger
1070
1136
  );
@@ -1074,7 +1140,7 @@ async function init(params, logger) {
1074
1140
  }
1075
1141
  try {
1076
1142
  await registerCustomizationModule(
1077
- join18(outputDir, coreDirName, "services", bootstrapServiceName),
1143
+ join19(outputDir, coreDirName, "services", bootstrapServiceName),
1078
1144
  organizationName,
1079
1145
  logger,
1080
1146
  platformName
@@ -1087,7 +1153,7 @@ async function init(params, logger) {
1087
1153
  }
1088
1154
 
1089
1155
  // src/commands/configure-idp/configure-idp.command.ts
1090
- import { join as join19 } from "path";
1156
+ import { join as join20 } from "path";
1091
1157
  import { fetch as undiciFetch, Agent } from "undici";
1092
1158
 
1093
1159
  // src/utils/env-reader.ts
@@ -1171,7 +1237,7 @@ async function configureIdp(params, logger) {
1171
1237
  logger.log("Error: Cannot configure an IDP \u2014 no platform initialized in this directory.");
1172
1238
  return;
1173
1239
  }
1174
- const envPath = join19(layout.localDir, ".env");
1240
+ const envPath = join20(layout.localDir, ".env");
1175
1241
  let env;
1176
1242
  try {
1177
1243
  env = await readEnvFile(envPath);
@@ -1223,13 +1289,13 @@ async function configureIdp(params, logger) {
1223
1289
  }
1224
1290
 
1225
1291
  // src/commands/create-service-module/create-service-module.command.ts
1226
- import { join as join21, resolve as resolve3 } from "path";
1227
- import { access as access4 } from "fs/promises";
1292
+ import { join as join22, resolve as resolve3 } from "path";
1293
+ import { access as access5 } from "fs/promises";
1228
1294
 
1229
1295
  // src/commands/create-service-module/scaffold-service-module.ts
1230
1296
  import { fileURLToPath as fileURLToPath9 } from "url";
1231
- import { join as join20, dirname as dirname11 } from "path";
1232
- var nestjsServiceModuleTemplateDir2 = join20(
1297
+ import { join as join21, dirname as dirname11 } from "path";
1298
+ var nestjsServiceModuleTemplateDir2 = join21(
1233
1299
  dirname11(fileURLToPath9(import.meta.url)),
1234
1300
  "..",
1235
1301
  "templates",
@@ -1264,7 +1330,7 @@ function buildCustomServiceModuleEntry(organizationName, serviceName, serviceDis
1264
1330
  }
1265
1331
 
1266
1332
  // src/commands/create-service-module/append-docker-compose.ts
1267
- import { readFile as readFile9, writeFile as writeFile8 } from "fs/promises";
1333
+ import { readFile as readFile9, writeFile as writeFile9 } from "fs/promises";
1268
1334
  async function appendServiceToDockerCompose(dockerComposePath, serviceName, platformName, applicationName, port, logger) {
1269
1335
  const block = `
1270
1336
  ${serviceName}:
@@ -1280,7 +1346,7 @@ async function appendServiceToDockerCompose(dockerComposePath, serviceName, plat
1280
1346
  `;
1281
1347
  try {
1282
1348
  const existing = await readFile9(dockerComposePath, "utf-8");
1283
- await writeFile8(dockerComposePath, existing + block, "utf-8");
1349
+ await writeFile9(dockerComposePath, existing + block, "utf-8");
1284
1350
  logger.log(`Updated docker-compose: ${dockerComposePath}`);
1285
1351
  } catch (err) {
1286
1352
  const message = err instanceof Error ? err.message : String(err);
@@ -1288,6 +1354,46 @@ async function appendServiceToDockerCompose(dockerComposePath, serviceName, plat
1288
1354
  }
1289
1355
  }
1290
1356
 
1357
+ // src/commands/pipelines/update-azure-pipeline-services.ts
1358
+ import { readFile as readFile10, writeFile as writeFile10, access as access4 } from "fs/promises";
1359
+ var END_MARKER = "# platform-cli:managed-services-end";
1360
+ async function addServiceToAzurePipeline(pipelinePath, serviceName, serviceFolder, logger) {
1361
+ try {
1362
+ await access4(pipelinePath);
1363
+ } catch {
1364
+ return;
1365
+ }
1366
+ let content;
1367
+ try {
1368
+ content = await readFile10(pipelinePath, "utf-8");
1369
+ } catch (err) {
1370
+ logger.log(`Warning: Could not read Azure pipeline file \u2014 ${err instanceof Error ? err.message : String(err)}`);
1371
+ return;
1372
+ }
1373
+ if (!content.includes(END_MARKER)) {
1374
+ logger.log(`Warning: Azure pipeline file does not contain managed services markers \u2014 skipping automatic update.`);
1375
+ logger.log(`Add the service manually to .azuredevops/azure-pipeline.yml`);
1376
+ return;
1377
+ }
1378
+ if (content.includes(`- name: ${serviceName}`)) {
1379
+ logger.log(`Service "${serviceName}" is already listed in the Azure pipeline \u2014 skipping.`);
1380
+ return;
1381
+ }
1382
+ const newEntry = ` - name: ${serviceName}
1383
+ folder: ${serviceFolder}
1384
+ `;
1385
+ const updated = content.replace(
1386
+ ` ${END_MARKER}`,
1387
+ `${newEntry} ${END_MARKER}`
1388
+ );
1389
+ try {
1390
+ await writeFile10(pipelinePath, updated, "utf-8");
1391
+ logger.log(`Updated Azure pipeline: added "${serviceName}" to services list.`);
1392
+ } catch (err) {
1393
+ logger.log(`Warning: Could not update Azure pipeline \u2014 ${err instanceof Error ? err.message : String(err)}`);
1394
+ }
1395
+ }
1396
+
1291
1397
  // src/commands/create-service-module/create-service-module.command.ts
1292
1398
  var CREATE_SERVICE_MODULE_COMMAND_NAME = "create-service-module";
1293
1399
  var createServiceModuleCommand = {
@@ -1316,18 +1422,18 @@ async function createServiceModule(params, logger) {
1316
1422
  logger.log(`Error: The specified application "${applicationName}" is not registered in the product manifest.`);
1317
1423
  return;
1318
1424
  }
1319
- const applicationDir = resolve3(join21(rootDir, coreDirName), appEntry.localPath);
1425
+ const applicationDir = resolve3(join22(rootDir, coreDirName), appEntry.localPath);
1320
1426
  try {
1321
- await access4(applicationDir);
1427
+ await access5(applicationDir);
1322
1428
  } catch {
1323
1429
  logger.log(`Error: The specified application "${applicationName}" does not exist in the platform.`);
1324
1430
  return;
1325
1431
  }
1326
1432
  const suffix = serviceNameSuffix === void 0 ? "service" : serviceNameSuffix;
1327
1433
  const fullServiceName = suffix ? `${platformName}-${serviceName}-${suffix}` : `${platformName}-${serviceName}`;
1328
- const serviceDir = join21(applicationDir, "services", fullServiceName);
1434
+ const serviceDir = join22(applicationDir, "services", fullServiceName);
1329
1435
  try {
1330
- await access4(serviceDir);
1436
+ await access5(serviceDir);
1331
1437
  logger.log(`Error: A service named "${fullServiceName}" already exists in application "${applicationName}".`);
1332
1438
  return;
1333
1439
  } catch {
@@ -1346,21 +1452,23 @@ async function createServiceModule(params, logger) {
1346
1452
  logger.log(`Error: Could not scaffold NestJS service \u2014 ${formatError(err)}`);
1347
1453
  return;
1348
1454
  }
1349
- const bootstrapServiceDir = join21(applicationDir, "services", `${platformName}-${applicationName}-bootstrap-service`);
1455
+ const bootstrapServiceDir = join22(applicationDir, "services", `${platformName}-${applicationName}-bootstrap-service`);
1350
1456
  const moduleEntry = buildCustomServiceModuleEntry(organizationName, fullServiceName, serviceDisplayName);
1351
1457
  await addModuleEntry(bootstrapServiceDir, applicationName, moduleEntry, logger);
1352
- const dockerComposePath = join21(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
1458
+ const dockerComposePath = join22(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
1353
1459
  const port = await getNextAvailablePort(localDir);
1354
1460
  await appendServiceToDockerCompose(dockerComposePath, fullServiceName, platformName, applicationName, port, logger);
1461
+ const azurePipelinePath = join22(applicationDir, ".azuredevops", "azure-pipeline.yml");
1462
+ await addServiceToAzurePipeline(azurePipelinePath, fullServiceName, `services/${fullServiceName}`, logger);
1355
1463
  logger.log(`Done! Service module "${fullServiceName}" added to application "${applicationName}".`);
1356
1464
  }
1357
1465
 
1358
1466
  // src/commands/create-ui-module/create-ui-module.command.ts
1359
- import { join as join22, resolve as resolve4 } from "path";
1360
- import { access as access5, readdir as readdir4 } from "fs/promises";
1467
+ import { join as join23, resolve as resolve4 } from "path";
1468
+ import { access as access6, readdir as readdir4 } from "fs/promises";
1361
1469
 
1362
1470
  // src/commands/create-ui-module/append-ui-docker-compose.ts
1363
- import { readFile as readFile10, writeFile as writeFile9 } from "fs/promises";
1471
+ import { readFile as readFile11, writeFile as writeFile11 } from "fs/promises";
1364
1472
  async function appendUiToDockerCompose(dockerComposePath, platformName, applicationName, port, logger, uiNameOverride) {
1365
1473
  const uiName = uiNameOverride ?? `${platformName}-${applicationName}-ui`;
1366
1474
  const block = `
@@ -1374,8 +1482,8 @@ async function appendUiToDockerCompose(dockerComposePath, platformName, applicat
1374
1482
  - \${PWD}/:/app/out
1375
1483
  `;
1376
1484
  try {
1377
- const existing = await readFile10(dockerComposePath, "utf-8");
1378
- await writeFile9(dockerComposePath, existing + block, "utf-8");
1485
+ const existing = await readFile11(dockerComposePath, "utf-8");
1486
+ await writeFile11(dockerComposePath, existing + block, "utf-8");
1379
1487
  logger.log(`Updated docker-compose: ${dockerComposePath}`);
1380
1488
  } catch (err) {
1381
1489
  const message = err instanceof Error ? err.message : String(err);
@@ -1411,14 +1519,14 @@ async function createUiModule(params, logger) {
1411
1519
  logger.log(`Error: The specified application "${applicationName}" is not registered in the product manifest.`);
1412
1520
  return;
1413
1521
  }
1414
- const applicationDir = resolve4(join22(rootDir, coreDirName), appEntry.localPath);
1522
+ const applicationDir = resolve4(join23(rootDir, coreDirName), appEntry.localPath);
1415
1523
  try {
1416
- await access5(applicationDir);
1524
+ await access6(applicationDir);
1417
1525
  } catch {
1418
1526
  logger.log(`Error: The specified application "${applicationName}" does not exist in the platform.`);
1419
1527
  return;
1420
1528
  }
1421
- const uiDir = join22(applicationDir, "ui");
1529
+ const uiDir = join23(applicationDir, "ui");
1422
1530
  try {
1423
1531
  const uiEntries = await readdir4(uiDir);
1424
1532
  const existingUiModules = uiEntries.filter((e) => e !== ".gitkeep");
@@ -1430,7 +1538,7 @@ async function createUiModule(params, logger) {
1430
1538
  }
1431
1539
  const uiSuffix = uiModuleSuffix === void 0 ? "ui" : uiModuleSuffix;
1432
1540
  const uiName = uiSuffix ? `${platformName}-${applicationName}-${uiSuffix}` : `${platformName}-${applicationName}`;
1433
- const uiOutputDir = join22(uiDir, uiName);
1541
+ const uiOutputDir = join23(uiDir, uiName);
1434
1542
  const uiBaseDir = `${platformName}-${applicationName}/ui`;
1435
1543
  try {
1436
1544
  await scaffoldUiModule(uiOutputDir, organizationName, platformName, applicationName, applicationDisplayName, uiBaseDir, logger);
@@ -1438,25 +1546,27 @@ async function createUiModule(params, logger) {
1438
1546
  logger.log(`Error: Could not scaffold UI module \u2014 ${formatError(err)}`);
1439
1547
  return;
1440
1548
  }
1441
- const bootstrapServiceDir = join22(applicationDir, "services", `${platformName}-${applicationName}-bootstrap-service`);
1549
+ const bootstrapServiceDir = join23(applicationDir, "services", `${platformName}-${applicationName}-bootstrap-service`);
1442
1550
  const moduleEntry = buildUiModuleEntry(organizationName, platformName, applicationName, applicationDisplayName, uiName);
1443
1551
  await addModuleEntry(bootstrapServiceDir, applicationName, moduleEntry, logger);
1444
- const dockerComposePath = join22(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
1552
+ const dockerComposePath = join23(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
1445
1553
  const port = await getNextAvailablePort(localDir);
1446
1554
  await appendUiToDockerCompose(dockerComposePath, platformName, applicationName, port, logger, uiName);
1555
+ const azurePipelinePath = join23(applicationDir, ".azuredevops", "azure-pipeline.yml");
1556
+ await addServiceToAzurePipeline(azurePipelinePath, uiName, `ui/${uiName}`, logger);
1447
1557
  logger.log(`Done! UI module "${uiName}" added to application "${applicationName}".`);
1448
1558
  }
1449
1559
 
1450
1560
  // src/commands/status/status-checks.ts
1451
1561
  import { spawn as spawn3 } from "child_process";
1452
- import { access as access8 } from "fs/promises";
1453
- import { join as join25, resolve as resolve7 } from "path";
1562
+ import { access as access9 } from "fs/promises";
1563
+ import { join as join26, resolve as resolve7 } from "path";
1454
1564
  import { fetch as undiciFetch2, Agent as Agent2 } from "undici";
1455
1565
 
1456
1566
  // src/commands/local-scripts/docker-compose-orchestrator.ts
1457
1567
  import { spawn } from "child_process";
1458
- import { access as access6 } from "fs/promises";
1459
- import { join as join23, resolve as resolve5 } from "path";
1568
+ import { access as access7, readFile as readFile12, writeFile as writeFile12 } from "fs/promises";
1569
+ import { dirname as dirname12, join as join24, resolve as resolve5 } from "path";
1460
1570
  function runDockerCompose(args2, logger, rootDir, signal) {
1461
1571
  return new Promise((resolvePromise) => {
1462
1572
  const child = spawn("docker", ["compose", ...args2], {
@@ -1519,25 +1629,38 @@ function captureDockerCompose(args2, rootDir) {
1519
1629
  child.on("error", (err) => reject(err));
1520
1630
  });
1521
1631
  }
1632
+ async function generateProxyAppCompose(appDir, localDir, platformName, applicationName) {
1633
+ const appComposePath = join24(appDir, "docker-compose.yml");
1634
+ const content = await readFile12(appComposePath, "utf-8");
1635
+ const withAbsContexts = content.replace(
1636
+ /^(\s+context:\s+)\.\/(.*)$/gm,
1637
+ `$1${appDir}/$2`
1638
+ );
1639
+ const appParentDir = dirname12(appDir);
1640
+ const rewritten = withAbsContexts.replace(/\$\{PWD\}/g, appParentDir);
1641
+ const proxyPath = join24(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
1642
+ await writeFile12(proxyPath, rewritten, "utf-8");
1643
+ return proxyPath;
1644
+ }
1522
1645
  async function getAppComposePaths(localDir, coreDir, platformName, manifest, logger) {
1523
1646
  const results = [];
1524
1647
  for (const app of manifest.applications) {
1525
- const appDir = resolve5(join23(coreDir), app.localPath);
1526
- const newPath = join23(appDir, "docker-compose.yml");
1527
- const legacyPrefixedPath = join23(localDir, `${platformName}-${app.name}-docker-compose.yml`);
1528
- const legacyUnprefixedPath = join23(localDir, `${app.name}-docker-compose.yml`);
1648
+ const appDir = resolve5(join24(coreDir), app.localPath);
1649
+ const newPath = join24(appDir, "docker-compose.yml");
1650
+ const legacyPrefixedPath = join24(localDir, `${platformName}-${app.name}-docker-compose.yml`);
1651
+ const legacyUnprefixedPath = join24(localDir, `${app.name}-docker-compose.yml`);
1529
1652
  let resolved = null;
1530
1653
  try {
1531
- await access6(newPath);
1532
- resolved = newPath;
1654
+ await access7(newPath);
1655
+ resolved = await generateProxyAppCompose(appDir, localDir, platformName, app.name);
1533
1656
  } catch {
1534
1657
  try {
1535
- await access6(legacyPrefixedPath);
1658
+ await access7(legacyPrefixedPath);
1536
1659
  resolved = legacyPrefixedPath;
1537
1660
  logger?.log(`Warning: docker-compose for "${app.name}" found in legacy location (core/local/). Consider moving it to the app monorepo root.`);
1538
1661
  } catch {
1539
1662
  try {
1540
- await access6(legacyUnprefixedPath);
1663
+ await access7(legacyUnprefixedPath);
1541
1664
  resolved = legacyUnprefixedPath;
1542
1665
  logger?.log(`Warning: docker-compose for "${app.name}" found in legacy location (core/local/). Consider moving it to the app monorepo root.`);
1543
1666
  } catch {
@@ -1553,18 +1676,18 @@ async function getAppComposePaths(localDir, coreDir, platformName, manifest, log
1553
1676
  async function buildFullComposeArgs(layout, manifest, logger) {
1554
1677
  const { coreDirName, localDir, coreDir } = layout;
1555
1678
  const platformName = manifest.product.name;
1556
- const prefixedCoreCompose = join23(localDir, `${coreDirName}-docker-compose.yml`);
1557
- const unprefixedCoreCompose = join23(localDir, "core-docker-compose.yml");
1679
+ const prefixedCoreCompose = join24(localDir, `${coreDirName}-docker-compose.yml`);
1680
+ const unprefixedCoreCompose = join24(localDir, "core-docker-compose.yml");
1558
1681
  let coreComposePath;
1559
1682
  try {
1560
- await access6(prefixedCoreCompose);
1683
+ await access7(prefixedCoreCompose);
1561
1684
  coreComposePath = prefixedCoreCompose;
1562
1685
  } catch {
1563
1686
  coreComposePath = unprefixedCoreCompose;
1564
1687
  }
1565
1688
  const fileArgs = [
1566
1689
  "-f",
1567
- join23(localDir, "platform-docker-compose.yml"),
1690
+ join24(localDir, "platform-docker-compose.yml"),
1568
1691
  "-f",
1569
1692
  coreComposePath
1570
1693
  ];
@@ -1584,16 +1707,16 @@ async function buildSelectedComposeFiles(layout, selectedManifest, includeCore)
1584
1707
  const platformName = selectedManifest.product.name;
1585
1708
  const files = [];
1586
1709
  if (includeCore) {
1587
- const prefixedCoreCompose = join23(localDir, `${coreDirName}-docker-compose.yml`);
1588
- const unprefixedCoreCompose = join23(localDir, "core-docker-compose.yml");
1710
+ const prefixedCoreCompose = join24(localDir, `${coreDirName}-docker-compose.yml`);
1711
+ const unprefixedCoreCompose = join24(localDir, "core-docker-compose.yml");
1589
1712
  let coreComposePath;
1590
1713
  try {
1591
- await access6(prefixedCoreCompose);
1714
+ await access7(prefixedCoreCompose);
1592
1715
  coreComposePath = prefixedCoreCompose;
1593
1716
  } catch {
1594
1717
  coreComposePath = unprefixedCoreCompose;
1595
1718
  }
1596
- files.push(join23(localDir, "platform-docker-compose.yml"), coreComposePath);
1719
+ files.push(join24(localDir, "platform-docker-compose.yml"), coreComposePath);
1597
1720
  }
1598
1721
  const appEntries = await getAppComposePaths(localDir, coreDir, platformName, selectedManifest);
1599
1722
  for (const { composePath } of appEntries) {
@@ -1637,7 +1760,7 @@ async function getServicesFromComposeFiles(selectedFiles, allFiles, rootDir) {
1637
1760
  async function startEnvironment(layout, manifest, logger, signal, includeCore = true, fullManifest) {
1638
1761
  const { rootDir, localDir } = layout;
1639
1762
  const platformName = manifest.product.name;
1640
- const envFile = join23(localDir, ".env");
1763
+ const envFile = join24(localDir, ".env");
1641
1764
  const isSelective = fullManifest !== void 0;
1642
1765
  const fileArgs = await buildFullComposeArgs(layout, isSelective ? fullManifest : manifest, logger);
1643
1766
  const projectArgs = ["-p", `${platformName}-platform`, "--project-directory", localDir, "--env-file", envFile];
@@ -1659,7 +1782,7 @@ async function startEnvironment(layout, manifest, logger, signal, includeCore =
1659
1782
  async function stopEnvironment(layout, manifest, logger, signal, includeCore = true, fullManifest) {
1660
1783
  const { rootDir, localDir } = layout;
1661
1784
  const platformName = manifest.product.name;
1662
- const envFile = join23(localDir, ".env");
1785
+ const envFile = join24(localDir, ".env");
1663
1786
  const isSelective = fullManifest !== void 0;
1664
1787
  const fileArgs = await buildFullComposeArgs(layout, isSelective ? fullManifest : manifest, logger);
1665
1788
  const projectArgs = ["-p", `${platformName}-platform`, "--project-directory", localDir, "--env-file", envFile];
@@ -1681,7 +1804,7 @@ async function stopEnvironment(layout, manifest, logger, signal, includeCore = t
1681
1804
  async function resetEnvironment(layout, manifest, logger, signal) {
1682
1805
  const { rootDir, localDir } = layout;
1683
1806
  const platformName = manifest.product.name;
1684
- const envFile = join23(localDir, ".env");
1807
+ const envFile = join24(localDir, ".env");
1685
1808
  const fileArgs = await buildFullComposeArgs(layout, manifest, logger);
1686
1809
  const projectArgs = ["-p", `${platformName}-platform`, "--project-directory", localDir, "--env-file", envFile];
1687
1810
  await runDockerCompose([...projectArgs, ...fileArgs, "down", "-v"], logger, rootDir, signal);
@@ -1689,7 +1812,7 @@ async function resetEnvironment(layout, manifest, logger, signal) {
1689
1812
  async function destroyEnvironment(layout, manifest, logger, signal, includeCore = true, fullManifest, removeImages = true) {
1690
1813
  const { rootDir, localDir } = layout;
1691
1814
  const platformName = manifest.product.name;
1692
- const envFile = join23(localDir, ".env");
1815
+ const envFile = join24(localDir, ".env");
1693
1816
  const isSelective = fullManifest !== void 0;
1694
1817
  const fileArgs = await buildFullComposeArgs(layout, isSelective ? fullManifest : manifest, logger);
1695
1818
  const projectArgs = ["-p", `${platformName}-platform`, "--project-directory", localDir, "--env-file", envFile];
@@ -1715,8 +1838,8 @@ async function destroyEnvironment(layout, manifest, logger, signal, includeCore
1715
1838
 
1716
1839
  // src/commands/local-scripts/npm-orchestrator.ts
1717
1840
  import { spawn as spawn2 } from "child_process";
1718
- import { access as access7 } from "fs/promises";
1719
- import { resolve as resolve6, join as join24 } from "path";
1841
+ import { access as access8 } from "fs/promises";
1842
+ import { resolve as resolve6, join as join25 } from "path";
1720
1843
  function runCommand(command, args2, workDir, logger, signal) {
1721
1844
  return new Promise((resolvePromise) => {
1722
1845
  const child = spawn2(command, args2, {
@@ -1763,7 +1886,7 @@ function runCommand(command, args2, workDir, logger, signal) {
1763
1886
  }
1764
1887
  async function dirExists(dirPath) {
1765
1888
  try {
1766
- await access7(dirPath);
1889
+ await access8(dirPath);
1767
1890
  return true;
1768
1891
  } catch {
1769
1892
  return false;
@@ -1773,7 +1896,7 @@ async function installDependencies(layout, manifest, logger, signal, includeCore
1773
1896
  const { coreDir, coreDirName } = layout;
1774
1897
  const appDirs = [];
1775
1898
  for (const app of manifest.applications) {
1776
- const appDir = resolve6(join24(coreDir), app.localPath);
1899
+ const appDir = resolve6(join25(coreDir), app.localPath);
1777
1900
  if (!await dirExists(appDir)) {
1778
1901
  logger.log(`Warning: Application directory "${app.name}" not found at ${appDir} \u2014 skipping install.`);
1779
1902
  continue;
@@ -1799,7 +1922,7 @@ async function buildAll(layout, manifest, logger, signal, includeCore = true) {
1799
1922
  }
1800
1923
  const appDirs = [];
1801
1924
  for (const app of manifest.applications) {
1802
- const appDir = resolve6(join24(coreDir), app.localPath);
1925
+ const appDir = resolve6(join25(coreDir), app.localPath);
1803
1926
  if (!await dirExists(appDir)) {
1804
1927
  logger.log(`Warning: Application directory "${app.name}" not found at ${appDir} \u2014 skipping build.`);
1805
1928
  continue;
@@ -1936,7 +2059,7 @@ function spawnCapture(cmd, args2, cwd11) {
1936
2059
  }
1937
2060
  async function pathExists(p) {
1938
2061
  try {
1939
- await access8(p);
2062
+ await access9(p);
1940
2063
  return true;
1941
2064
  } catch {
1942
2065
  return false;
@@ -1980,13 +2103,13 @@ async function checkInstalled(layout, manifest) {
1980
2103
  const results = [];
1981
2104
  const coreCheck = {
1982
2105
  name: layout.coreDirName,
1983
- path: join25(layout.coreDir, "node_modules"),
1984
- exists: await pathExists(join25(layout.coreDir, "node_modules"))
2106
+ path: join26(layout.coreDir, "node_modules"),
2107
+ exists: await pathExists(join26(layout.coreDir, "node_modules"))
1985
2108
  };
1986
2109
  results.push(coreCheck);
1987
2110
  for (const app of manifest.applications) {
1988
2111
  const appDir = resolve7(layout.coreDir, app.localPath);
1989
- const nodeModulesPath = join25(appDir, "node_modules");
2112
+ const nodeModulesPath = join26(appDir, "node_modules");
1990
2113
  results.push({
1991
2114
  name: app.name,
1992
2115
  path: nodeModulesPath,
@@ -1997,7 +2120,7 @@ async function checkInstalled(layout, manifest) {
1997
2120
  }
1998
2121
  async function checkBuilt(layout, manifest) {
1999
2122
  const results = [];
2000
- const coreTurboPath = join25(layout.coreDir, ".turbo");
2123
+ const coreTurboPath = join26(layout.coreDir, ".turbo");
2001
2124
  results.push({
2002
2125
  name: layout.coreDirName,
2003
2126
  path: coreTurboPath,
@@ -2005,7 +2128,7 @@ async function checkBuilt(layout, manifest) {
2005
2128
  });
2006
2129
  for (const app of manifest.applications) {
2007
2130
  const appDir = resolve7(layout.coreDir, app.localPath);
2008
- const appTurboPath = join25(appDir, ".turbo");
2131
+ const appTurboPath = join26(appDir, ".turbo");
2009
2132
  results.push({
2010
2133
  name: app.name,
2011
2134
  path: appTurboPath,
@@ -2017,13 +2140,13 @@ async function checkBuilt(layout, manifest) {
2017
2140
  async function resolveComposeFiles(layout, manifest) {
2018
2141
  const { localDir, coreDirName } = layout;
2019
2142
  const platformName = manifest.product.name;
2020
- const files = [join25(localDir, "platform-docker-compose.yml")];
2021
- const prefixedCore = join25(localDir, `${coreDirName}-docker-compose.yml`);
2022
- const unprefixedCore = join25(localDir, "core-docker-compose.yml");
2143
+ const files = [join26(localDir, "platform-docker-compose.yml")];
2144
+ const prefixedCore = join26(localDir, `${coreDirName}-docker-compose.yml`);
2145
+ const unprefixedCore = join26(localDir, "core-docker-compose.yml");
2023
2146
  files.push(await pathExists(prefixedCore) ? prefixedCore : unprefixedCore);
2024
2147
  for (const app of manifest.applications) {
2025
- const prefixed = join25(localDir, `${platformName}-${app.name}-docker-compose.yml`);
2026
- const unprefixed = join25(localDir, `${app.name}-docker-compose.yml`);
2148
+ const prefixed = join26(localDir, `${platformName}-${app.name}-docker-compose.yml`);
2149
+ const unprefixed = join26(localDir, `${app.name}-docker-compose.yml`);
2027
2150
  if (await pathExists(prefixed)) files.push(prefixed);
2028
2151
  else if (await pathExists(unprefixed)) files.push(unprefixed);
2029
2152
  }
@@ -2129,15 +2252,15 @@ async function getServicesForComposeFiles(selectedFiles, allFiles, rootDir) {
2129
2252
  async function checkContainersPerApp(layout, manifest, allContainers) {
2130
2253
  const { localDir, coreDirName, rootDir } = layout;
2131
2254
  const platformName = manifest.product.name;
2132
- const prefixedCore = join25(localDir, `${coreDirName}-docker-compose.yml`);
2133
- const unprefixedCore = join25(localDir, "core-docker-compose.yml");
2255
+ const prefixedCore = join26(localDir, `${coreDirName}-docker-compose.yml`);
2256
+ const unprefixedCore = join26(localDir, "core-docker-compose.yml");
2134
2257
  const coreComposePath = await pathExists(prefixedCore) ? prefixedCore : unprefixedCore;
2135
- const platformComposePath = join25(localDir, "platform-docker-compose.yml");
2258
+ const platformComposePath = join26(localDir, "platform-docker-compose.yml");
2136
2259
  const allFiles = [platformComposePath, coreComposePath];
2137
2260
  const appComposeMap = /* @__PURE__ */ new Map();
2138
2261
  for (const app of manifest.applications) {
2139
- const prefixed = join25(localDir, `${platformName}-${app.name}-docker-compose.yml`);
2140
- const unprefixed = join25(localDir, `${app.name}-docker-compose.yml`);
2262
+ const prefixed = join26(localDir, `${platformName}-${app.name}-docker-compose.yml`);
2263
+ const unprefixed = join26(localDir, `${app.name}-docker-compose.yml`);
2141
2264
  if (await pathExists(prefixed)) {
2142
2265
  appComposeMap.set(app.name, prefixed);
2143
2266
  allFiles.push(prefixed);
@@ -2184,7 +2307,7 @@ async function checkContainersPerApp(layout, manifest, allContainers) {
2184
2307
  return groups;
2185
2308
  }
2186
2309
  async function checkIdpProviders(layout) {
2187
- const envPath = join25(layout.localDir, ".env");
2310
+ const envPath = join26(layout.localDir, ".env");
2188
2311
  let env;
2189
2312
  try {
2190
2313
  env = await readEnvFile(envPath);
@@ -2303,7 +2426,7 @@ var statusCommand = {
2303
2426
  };
2304
2427
 
2305
2428
  // src/commands/manage-platform-admins/manage-platform-admins.command.ts
2306
- import { join as join26 } from "path";
2429
+ import { join as join27 } from "path";
2307
2430
  import { fetch as undiciFetch3, Agent as Agent3 } from "undici";
2308
2431
  var MANAGE_PLATFORM_ADMINS_COMMAND_NAME = "manage-platform-admins";
2309
2432
  var managePlatformAdminsCommand = {
@@ -2317,7 +2440,7 @@ async function getGatewayConfig(logger) {
2317
2440
  logger.log("Error: Cannot manage platform admins \u2014 no platform initialized in this directory.");
2318
2441
  return null;
2319
2442
  }
2320
- const envPath = join26(layout.localDir, ".env");
2443
+ const envPath = join27(layout.localDir, ".env");
2321
2444
  let env;
2322
2445
  try {
2323
2446
  env = await readEnvFile(envPath);
@@ -2439,59 +2562,59 @@ async function removePlatformAdmin(ruleId, logger) {
2439
2562
  var baPlatformPlugin = {
2440
2563
  name: "ba-platform-plugin",
2441
2564
  description: "Blue Alba Platform knowledge and CLI skills for AI assistants",
2442
- version: "1.1.0",
2565
+ version: "1.2.0",
2443
2566
  skills: [
2444
2567
  {
2445
2568
  name: "platform",
2446
- description: "Comprehensive Blue Alba Platform architecture and concepts knowledge",
2569
+ description: "Blue Alba Platform orchestrator \u2014 routes to the correct specialized skill or answers general architecture questions. Use this skill for ANY platform-related request.",
2447
2570
  type: "skill",
2448
2571
  sourceFile: "skills/ba-platform/platform.skill.md"
2449
2572
  },
2450
2573
  {
2451
2574
  name: "platform-cli",
2452
- description: "Blue Alba Platform CLI commands and usage knowledge",
2575
+ description: "Blue Alba Platform CLI commands, usage, installation, and headless mode reference",
2453
2576
  type: "skill",
2454
2577
  sourceFile: "skills/ba-platform/platform-cli.skill.md"
2455
2578
  },
2456
2579
  {
2457
2580
  name: "platform-add-operation",
2458
- description: "Defines a new authorization operation in the bootstrap service, assigns it to a role, and adds access guards in the React UI or NestJS backend",
2581
+ description: "Adds an authorization operation (permission/RBAC guard) to a bootstrap service and enforces it in UI or backend routes",
2459
2582
  type: "skill",
2460
2583
  sourceFile: "skills/ba-platform/platform-add-operation.skill.md"
2461
2584
  },
2462
2585
  {
2463
2586
  name: "platform-add-feature-flag",
2464
- description: "Declares a feature flag in the bootstrap service and adds its usage in a React UI component or NestJS service",
2587
+ description: "Declares a feature flag in a bootstrap service and gates a React component or NestJS endpoint behind it",
2465
2588
  type: "skill",
2466
2589
  sourceFile: "skills/ba-platform/platform-add-feature-flag.skill.md"
2467
2590
  },
2468
2591
  {
2469
2592
  name: "platform-add-scheduled-job",
2470
- description: "Adds a scheduled job to a Blue Alba Platform bootstrap service",
2593
+ description: "Adds a cron-scheduled job to a bootstrap service to run a backend endpoint on a recurring schedule",
2471
2594
  type: "skill",
2472
2595
  sourceFile: "skills/ba-platform/platform-add-scheduled-job.skill.md"
2473
2596
  },
2474
2597
  {
2475
2598
  name: "platform-add-menu-item",
2476
- description: "Adds a new page and menu item to a Blue Alba Platform single-spa UI module",
2599
+ description: "Creates a new page component and wires it as a menu item/route in a single-spa UI module",
2477
2600
  type: "skill",
2478
2601
  sourceFile: "skills/ba-platform/platform-add-menu-item.skill.md"
2479
2602
  },
2480
2603
  {
2481
2604
  name: "platform-extend-shell",
2482
- description: "Injects a custom component into a Blue Alba Platform shell extension point",
2605
+ description: "Injects a custom component into a shell extension point (navbar, branding, user section)",
2483
2606
  type: "skill",
2484
2607
  sourceFile: "skills/ba-platform/platform-extend-shell.skill.md"
2485
2608
  },
2486
2609
  {
2487
2610
  name: "platform-add-presence",
2488
- description: "Adds real-time user presence via the Rooms API to a Blue Alba Platform app",
2611
+ description: "Adds real-time user presence (live avatars, who-is-viewing) to a component using the Rooms API",
2489
2612
  type: "skill",
2490
2613
  sourceFile: "skills/ba-platform/platform-add-presence.skill.md"
2491
2614
  },
2492
2615
  {
2493
2616
  name: "platform-scaffold-module",
2494
- description: "Scaffolds a new UI module or service module using the platform CLI",
2617
+ description: "Scaffolds a new UI module or NestJS service module in a platform monorepo using the CLI",
2495
2618
  type: "skill",
2496
2619
  sourceFile: "skills/ba-platform/platform-scaffold-module.skill.md"
2497
2620
  }
@@ -2602,6 +2725,132 @@ var standaloneConfigListCommand = {
2602
2725
  hidden: () => false
2603
2726
  };
2604
2727
 
2728
+ // src/commands/pipelines/generate-github-pipeline.ts
2729
+ import { fileURLToPath as fileURLToPath10 } from "url";
2730
+ import { dirname as dirname13, join as join28 } from "path";
2731
+
2732
+ // src/commands/pipelines/github-secrets-checklist.ts
2733
+ var GITHUB_VARS_CHECKLIST = [
2734
+ "DOCKER_PUSH_TO_ACR \u2014 (variable) true/false, push to Azure Container Registry",
2735
+ "DOCKER_PUSH_TO_ECR \u2014 (variable) true/false, push to Amazon ECR",
2736
+ "DOCKER_PUSH_TO_BA_ARTIFACTORY \u2014 (variable) true/false, push to Blue Alba Artifactory",
2737
+ "DOCKER_REPOSITORY_URL \u2014 (variable) URL of the Docker registry",
2738
+ "DOCKER_IMAGE_ARCHS \u2014 (variable) target architectures (e.g. linux/amd64,linux/arm64)"
2739
+ ];
2740
+ var GITHUB_SECRETS_CHECKLIST = [
2741
+ "DOCKER_USER \u2014 Docker registry username",
2742
+ "DOCKER_PASSWORD \u2014 Docker registry password",
2743
+ "NPM_AUTH_TOKEN \u2014 Blue Alba private npm registry token",
2744
+ "NPMJS_AUTH_TOKEN \u2014 npmjs.org registry token",
2745
+ "WORKFLOW_REPOSITORY_RO_SSH_KEY \u2014 read-only SSH key for Blue-Alba-Platform/workflows repo",
2746
+ "WORKFLOWS_STATE_ID_PAT \u2014 PAT for reading artifacts (state file) between branches"
2747
+ ];
2748
+
2749
+ // src/commands/pipelines/generate-github-pipeline.ts
2750
+ var __dirname = dirname13(fileURLToPath10(import.meta.url));
2751
+ async function generateGithubPipeline(params, logger) {
2752
+ const templateDir4 = join28(__dirname, "..", "templates", "github-pipeline-template");
2753
+ await applyTemplate(
2754
+ {
2755
+ templateDir: templateDir4,
2756
+ outputDir: params.rootDir,
2757
+ variables: {
2758
+ stateFile: params.stateFile,
2759
+ codeFolderList: params.codeFolderList,
2760
+ packagesFolderPattern: params.packagesFolderPattern
2761
+ }
2762
+ },
2763
+ (message) => logger.log(message)
2764
+ );
2765
+ logger.log("");
2766
+ logger.log("GitHub Actions pipeline generated: .github/workflows/build-platform.yml");
2767
+ logger.log("");
2768
+ logger.log("Action required \u2014 configure the following in your GitHub repo (Settings > Secrets and variables):");
2769
+ logger.log("");
2770
+ logger.log("Variables:");
2771
+ for (const item of GITHUB_VARS_CHECKLIST) {
2772
+ logger.log(` \u2022 ${item}`);
2773
+ }
2774
+ logger.log("");
2775
+ logger.log("Secrets:");
2776
+ for (const item of GITHUB_SECRETS_CHECKLIST) {
2777
+ logger.log(` \u2022 ${item}`);
2778
+ }
2779
+ }
2780
+
2781
+ // src/commands/pipelines/generate-azure-pipeline.ts
2782
+ import { fileURLToPath as fileURLToPath11 } from "url";
2783
+ import { dirname as dirname14, join as join29 } from "path";
2784
+ var __dirname2 = dirname14(fileURLToPath11(import.meta.url));
2785
+ function buildServicesList(services) {
2786
+ if (services.length === 0) return "";
2787
+ return services.map((s) => ` - name: ${s.name}
2788
+ folder: ${s.folder}
2789
+ `).join("");
2790
+ }
2791
+ async function generateAzurePipeline(params, logger) {
2792
+ const templateDir4 = join29(__dirname2, "..", "templates", "azure-pipeline-template");
2793
+ const servicesList = buildServicesList(params.services);
2794
+ await applyTemplate(
2795
+ {
2796
+ templateDir: templateDir4,
2797
+ outputDir: params.rootDir,
2798
+ variables: {
2799
+ azureServiceConnection: params.azureServiceConnection,
2800
+ acrLoginServer: params.acrLoginServer,
2801
+ nodeVersion: params.nodeVersion,
2802
+ templatesRepoName: params.templatesRepoName,
2803
+ gitopsRepoName: params.gitopsRepoName,
2804
+ servicesList
2805
+ }
2806
+ },
2807
+ (message) => logger.log(message)
2808
+ );
2809
+ logger.log("Azure DevOps pipeline generated: .azuredevops/azure-pipeline.yml");
2810
+ }
2811
+
2812
+ // src/commands/pipelines/pipelines.command.ts
2813
+ var PIPELINES_COMMAND_NAME = "pipelines";
2814
+ var pipelinesCommand = {
2815
+ name: PIPELINES_COMMAND_NAME,
2816
+ description: "Generate CI/CD pipeline files for the platform monorepo",
2817
+ hidden: (ctx) => !ctx.platformInitialized
2818
+ };
2819
+ async function generatePipelines(params, logger) {
2820
+ for (const target of params.targets) {
2821
+ logger.log(`
2822
+ Generating ${params.provider} pipeline for ${target.name}...`);
2823
+ if (params.provider === "github") {
2824
+ await generateGithubPipeline(
2825
+ {
2826
+ rootDir: target.dir,
2827
+ stateFile: params.stateFile ?? target.name,
2828
+ codeFolderList: params.codeFolderList ?? "apps/**,services/**",
2829
+ packagesFolderPattern: params.packagesFolderPattern ?? "packages/**"
2830
+ },
2831
+ logger
2832
+ );
2833
+ continue;
2834
+ }
2835
+ if (params.provider === "azure") {
2836
+ await generateAzurePipeline(
2837
+ {
2838
+ rootDir: target.dir,
2839
+ azureServiceConnection: params.azureServiceConnection ?? "",
2840
+ acrLoginServer: params.acrLoginServer ?? "",
2841
+ nodeVersion: params.nodeVersion ?? "22.x",
2842
+ templatesRepoName: params.templatesRepoName ?? "azure-pipeline-ba-template",
2843
+ gitopsRepoName: params.gitopsRepoName ?? "portainer-deployments",
2844
+ services: target.services ?? []
2845
+ },
2846
+ logger
2847
+ );
2848
+ continue;
2849
+ }
2850
+ logger.log(`Error: Unknown provider "${params.provider}". Use "github" or "azure".`);
2851
+ }
2852
+ }
2853
+
2605
2854
  // src/commands/registry.ts
2606
2855
  var CommandRegistry = class {
2607
2856
  commands = /* @__PURE__ */ new Map();
@@ -2649,6 +2898,7 @@ registry.register(standaloneConfigShowCommand);
2649
2898
  registry.register(standaloneConfigSetCommand);
2650
2899
  registry.register(standaloneConfigDeleteCommand);
2651
2900
  registry.register(standaloneConfigListCommand);
2901
+ registry.register(pipelinesCommand);
2652
2902
 
2653
2903
  // src/app-state.ts
2654
2904
  var APP_STATE = {
@@ -2711,6 +2961,145 @@ async function initService(params, logger) {
2711
2961
  await init(params, logger);
2712
2962
  }
2713
2963
 
2964
+ // src/services/pipelines.service.ts
2965
+ async function pipelinesService(params, logger) {
2966
+ await generatePipelines(params, logger);
2967
+ }
2968
+
2969
+ // src/commands/pipelines/build-target-list.ts
2970
+ import { resolve as resolve8, join as join30 } from "path";
2971
+ function buildTargetList(rootDir, coreDirName, manifest) {
2972
+ const targets = [];
2973
+ targets.push({ name: coreDirName, dir: join30(rootDir, coreDirName) });
2974
+ for (const app of manifest.applications) {
2975
+ const appDir = resolve8(join30(rootDir, coreDirName, app.localPath));
2976
+ targets.push({ name: app.name, dir: appDir });
2977
+ }
2978
+ return targets;
2979
+ }
2980
+
2981
+ // src/commands/pipelines/detect-services.ts
2982
+ import { readdir as readdir5, access as access10 } from "fs/promises";
2983
+ import { join as join31, relative, resolve as resolve9 } from "path";
2984
+ async function detectServicesInDir(targetDir) {
2985
+ const detected = [];
2986
+ for (const subDir of ["services", "ui"]) {
2987
+ const scanDir = join31(targetDir, subDir);
2988
+ try {
2989
+ await access10(scanDir);
2990
+ } catch {
2991
+ continue;
2992
+ }
2993
+ let dirNames;
2994
+ try {
2995
+ const dirents = await readdir5(scanDir, { withFileTypes: true });
2996
+ dirNames = dirents.filter((d) => d.isDirectory()).map((d) => d.name);
2997
+ } catch {
2998
+ continue;
2999
+ }
3000
+ for (const dirName of dirNames) {
3001
+ const dockerfilePath = join31(scanDir, dirName, "Dockerfile");
3002
+ try {
3003
+ await access10(dockerfilePath);
3004
+ const folder = relative(targetDir, join31(scanDir, dirName)).replace(/\\/g, "/");
3005
+ detected.push({ name: dirName, folder });
3006
+ } catch {
3007
+ }
3008
+ }
3009
+ }
3010
+ return detected;
3011
+ }
3012
+
3013
+ // src/controllers/ui/pipelines.ui-controller.ts
3014
+ async function pipelinesUiController(ctx) {
3015
+ if (!await isPlatformInitialized()) {
3016
+ ctx.log("Error: Cannot configure pipelines \u2014 no platform initialized in this directory.");
3017
+ return;
3018
+ }
3019
+ const layout = await findPlatformLayout();
3020
+ if (!layout) return;
3021
+ const { rootDir, coreDirName } = layout;
3022
+ let manifest;
3023
+ try {
3024
+ manifest = await readManifest(rootDir, coreDirName);
3025
+ } catch {
3026
+ ctx.log("Error: Could not read product manifest.");
3027
+ return;
3028
+ }
3029
+ const allTargets = buildTargetList(rootDir, coreDirName, manifest);
3030
+ const targetOptions = allTargets.map((t) => ({
3031
+ label: t.name === coreDirName ? `${t.name} (core)` : t.name,
3032
+ value: t.name
3033
+ }));
3034
+ const selectedNames = await ctx.multiselect(
3035
+ "Select repositories to generate pipelines for:",
3036
+ targetOptions
3037
+ );
3038
+ if (selectedNames.length === 0) {
3039
+ ctx.log("No targets selected. Nothing to do.");
3040
+ return;
3041
+ }
3042
+ const selectedTargets = allTargets.filter((t) => selectedNames.includes(t.name));
3043
+ const provider = await ctx.select("CI/CD provider:", [
3044
+ { label: "GitHub Actions", value: "github" },
3045
+ { label: "Azure DevOps", value: "azure" }
3046
+ ]);
3047
+ if (provider === "github") {
3048
+ await pipelinesService(
3049
+ {
3050
+ provider: "github",
3051
+ targets: selectedTargets,
3052
+ codeFolderList: "apps/**,services/**",
3053
+ packagesFolderPattern: "packages/**"
3054
+ },
3055
+ ctx
3056
+ );
3057
+ return;
3058
+ }
3059
+ const azureServiceConnection = await ctx.prompt("Azure Service Connection name:");
3060
+ if (!azureServiceConnection.trim()) {
3061
+ ctx.log("Error: Azure Service Connection name is required.");
3062
+ return;
3063
+ }
3064
+ const acrLoginServer = await ctx.prompt("ACR Login Server (e.g. myregistry.azurecr.io):");
3065
+ if (!acrLoginServer.trim()) {
3066
+ ctx.log("Error: ACR Login Server is required.");
3067
+ return;
3068
+ }
3069
+ const targetsWithServices = [];
3070
+ for (const target of selectedTargets) {
3071
+ const detected = await detectServicesInDir(target.dir);
3072
+ let services;
3073
+ if (detected.length === 0) {
3074
+ ctx.log(` No services with Dockerfile found in ${target.name} \u2014 pipeline will have an empty services list.`);
3075
+ services = [];
3076
+ } else {
3077
+ const options = detected.map((s) => ({ label: `${s.name} (${s.folder})`, value: s.folder }));
3078
+ const selectedFolders = await ctx.multiselect(
3079
+ `Services to include for ${target.name}:`,
3080
+ options
3081
+ );
3082
+ services = selectedFolders.map((folder) => {
3083
+ const svc = detected.find((d) => d.folder === folder);
3084
+ return { name: svc.name, folder: svc.folder };
3085
+ });
3086
+ }
3087
+ targetsWithServices.push({ ...target, services });
3088
+ }
3089
+ await pipelinesService(
3090
+ {
3091
+ provider: "azure",
3092
+ targets: targetsWithServices,
3093
+ azureServiceConnection,
3094
+ acrLoginServer,
3095
+ nodeVersion: "22.x",
3096
+ templatesRepoName: "azure-pipeline-ba-template",
3097
+ gitopsRepoName: "portainer-deployments"
3098
+ },
3099
+ ctx
3100
+ );
3101
+ }
3102
+
2714
3103
  // src/controllers/ui/init.ui-controller.ts
2715
3104
  async function initUiController(ctx) {
2716
3105
  if (await isPlatformInitialized()) {
@@ -2746,6 +3135,10 @@ async function initUiController(ctx) {
2746
3135
  { organizationName, platformName, platformDisplayName, bootstrapServiceSuffix, customizationUiSuffix },
2747
3136
  ctx
2748
3137
  );
3138
+ const addPipelines = await ctx.confirm("Configure CI/CD pipelines?", false);
3139
+ if (addPipelines) {
3140
+ await pipelinesUiController(ctx);
3141
+ }
2749
3142
  }
2750
3143
 
2751
3144
  // src/services/configure-idp.service.ts
@@ -3180,20 +3573,20 @@ async function installAiPluginService(params, context) {
3180
3573
 
3181
3574
  // src/commands/install-ai-plugin/providers/claude.provider.ts
3182
3575
  import { homedir as homedir3 } from "os";
3183
- import { join as join27 } from "path";
3184
- import { readFile as readFile11, writeFile as writeFile10, mkdir as mkdir4, rm } from "fs/promises";
3576
+ import { join as join32 } from "path";
3577
+ import { readFile as readFile13, writeFile as writeFile13, mkdir as mkdir4, rm } from "fs/promises";
3185
3578
  var ClaudeProvider = class {
3186
3579
  name = "claude";
3187
- baseDir = join27(homedir3(), ".claude");
3580
+ baseDir = join32(homedir3(), ".claude");
3188
3581
  getInstallPath(skillName) {
3189
- return join27(this.baseDir, "skills", skillName, "SKILL.md");
3582
+ return join32(this.baseDir, "skills", skillName, "SKILL.md");
3190
3583
  }
3191
3584
  manifestPath(pluginName) {
3192
- return join27(this.baseDir, `${pluginName}.manifest.json`);
3585
+ return join32(this.baseDir, `${pluginName}.manifest.json`);
3193
3586
  }
3194
3587
  async readManifest(pluginName) {
3195
3588
  try {
3196
- const content = await readFile11(this.manifestPath(pluginName), "utf-8");
3589
+ const content = await readFile13(this.manifestPath(pluginName), "utf-8");
3197
3590
  return JSON.parse(content);
3198
3591
  } catch {
3199
3592
  return null;
@@ -3201,7 +3594,7 @@ var ClaudeProvider = class {
3201
3594
  }
3202
3595
  async writeManifest(manifest) {
3203
3596
  await mkdir4(this.baseDir, { recursive: true });
3204
- await writeFile10(
3597
+ await writeFile13(
3205
3598
  this.manifestPath(manifest.plugin),
3206
3599
  JSON.stringify(manifest, null, 2),
3207
3600
  "utf-8"
@@ -3221,16 +3614,16 @@ var ClaudeProvider = class {
3221
3614
  }
3222
3615
  async installSkill(skill, docsSource, logger) {
3223
3616
  const sourcePath = docsSource.resolve(skill.sourceFile);
3224
- let content = await readFile11(sourcePath, "utf-8");
3617
+ let content = await readFile13(sourcePath, "utf-8");
3225
3618
  const docsPath = docsSource.resolve("docs");
3226
3619
  content = content.replaceAll("{{docsPath}}", docsPath);
3227
3620
  const installPath = this.getInstallPath(skill.name);
3228
- await mkdir4(join27(installPath, ".."), { recursive: true });
3229
- await writeFile10(installPath, content, "utf-8");
3621
+ await mkdir4(join32(installPath, ".."), { recursive: true });
3622
+ await writeFile13(installPath, content, "utf-8");
3230
3623
  logger.log(` Installed skill: ${installPath}`);
3231
3624
  }
3232
3625
  async removeSkill(skillName, logger) {
3233
- const skillDir = join27(this.baseDir, "skills", skillName);
3626
+ const skillDir = join32(this.baseDir, "skills", skillName);
3234
3627
  try {
3235
3628
  await rm(skillDir, { recursive: true, force: true });
3236
3629
  logger.log(` Removed skill: ${skillDir}`);
@@ -3238,11 +3631,11 @@ var ClaudeProvider = class {
3238
3631
  }
3239
3632
  }
3240
3633
  async updatePermissions(docsSource, logger) {
3241
- const settingsPath = join27(this.baseDir, "settings.json");
3634
+ const settingsPath = join32(this.baseDir, "settings.json");
3242
3635
  const docsPath = docsSource.resolve("docs");
3243
3636
  let settings = {};
3244
3637
  try {
3245
- const content = await readFile11(settingsPath, "utf-8");
3638
+ const content = await readFile13(settingsPath, "utf-8");
3246
3639
  settings = JSON.parse(content);
3247
3640
  } catch {
3248
3641
  }
@@ -3254,7 +3647,7 @@ var ClaudeProvider = class {
3254
3647
  permissions.additionalDirectories = [...additionalDirectories, docsPath];
3255
3648
  settings.permissions = permissions;
3256
3649
  await mkdir4(this.baseDir, { recursive: true });
3257
- await writeFile10(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
3650
+ await writeFile13(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
3258
3651
  logger.log(` Granted read access to docs: ${docsPath}`);
3259
3652
  }
3260
3653
  };
@@ -3273,12 +3666,12 @@ function getProvider(name) {
3273
3666
  }
3274
3667
 
3275
3668
  // src/commands/install-ai-plugin/docs-source/local.docs-source.ts
3276
- import { fileURLToPath as fileURLToPath10 } from "url";
3277
- import { join as join28, dirname as dirname12 } from "path";
3278
- var packageRoot = join28(dirname12(fileURLToPath10(import.meta.url)), "..");
3669
+ import { fileURLToPath as fileURLToPath12 } from "url";
3670
+ import { join as join33, dirname as dirname15 } from "path";
3671
+ var packageRoot = join33(dirname15(fileURLToPath12(import.meta.url)), "..");
3279
3672
  var LocalDocsSource = class {
3280
3673
  resolve(relativePath) {
3281
- return join28(packageRoot, relativePath);
3674
+ return join33(packageRoot, relativePath);
3282
3675
  }
3283
3676
  };
3284
3677
 
@@ -3322,20 +3715,20 @@ async function installAiPluginUiController(ctx) {
3322
3715
 
3323
3716
  // src/controllers/ui/standalone.ui-controller.ts
3324
3717
  import { rm as rm3 } from "fs/promises";
3325
- import { join as join32 } from "path";
3718
+ import { join as join37 } from "path";
3326
3719
  import { cwd as cwd7 } from "process";
3327
3720
  import { exec } from "child_process";
3328
3721
 
3329
3722
  // src/commands/standalone/app-monorepo-check.ts
3330
- import { access as access9, readdir as readdir5 } from "fs/promises";
3331
- import { join as join29 } from "path";
3723
+ import { access as access11, readdir as readdir6 } from "fs/promises";
3724
+ import { join as join34 } from "path";
3332
3725
  import { cwd as cwd6 } from "process";
3333
3726
  async function findAppMonorepoLayout(startDir = cwd6()) {
3334
- const composePath = join29(startDir, "docker-compose.yml");
3335
- const packageJsonPath = join29(startDir, "package.json");
3727
+ const composePath = join34(startDir, "docker-compose.yml");
3728
+ const packageJsonPath = join34(startDir, "package.json");
3336
3729
  try {
3337
- await access9(composePath);
3338
- await access9(packageJsonPath);
3730
+ await access11(composePath);
3731
+ await access11(packageJsonPath);
3339
3732
  } catch {
3340
3733
  return null;
3341
3734
  }
@@ -3346,11 +3739,11 @@ async function findAppMonorepoLayout(startDir = cwd6()) {
3346
3739
  }
3347
3740
  async function hasCoreChildDir(dir) {
3348
3741
  try {
3349
- const entries = await readdir5(dir, { withFileTypes: true });
3742
+ const entries = await readdir6(dir, { withFileTypes: true });
3350
3743
  for (const entry of entries) {
3351
3744
  if (entry.isDirectory() && (entry.name.endsWith("-core") || entry.name === "core")) {
3352
3745
  try {
3353
- await access9(join29(dir, entry.name, "product.manifest.json"));
3746
+ await access11(join34(dir, entry.name, "product.manifest.json"));
3354
3747
  return true;
3355
3748
  } catch {
3356
3749
  }
@@ -3365,36 +3758,36 @@ async function isInAppMonorepo() {
3365
3758
  }
3366
3759
 
3367
3760
  // src/commands/standalone/standalone-auto-detect.ts
3368
- import { readdir as readdir6, readFile as readFile12 } from "fs/promises";
3369
- import { join as join30, basename as basename2 } from "path";
3761
+ import { readdir as readdir7, readFile as readFile14 } from "fs/promises";
3762
+ import { join as join35, basename as basename2 } from "path";
3370
3763
  async function autoDetectAppIdentity(appDir) {
3371
3764
  const fromBootstrap = await detectFromBootstrap(appDir);
3372
3765
  if (fromBootstrap) return fromBootstrap;
3373
3766
  return detectFromPackageJson(appDir);
3374
3767
  }
3375
3768
  async function detectFromBootstrap(appDir) {
3376
- const servicesDir = join30(appDir, "services");
3769
+ const servicesDir = join35(appDir, "services");
3377
3770
  let entries;
3378
3771
  try {
3379
- entries = await readdir6(servicesDir);
3772
+ entries = await readdir7(servicesDir);
3380
3773
  } catch {
3381
3774
  return null;
3382
3775
  }
3383
3776
  const bootstrapDirs = entries.filter((e) => e.endsWith("-bootstrap-service"));
3384
3777
  if (bootstrapDirs.length === 0) return null;
3385
3778
  for (const bootstrapDirName of bootstrapDirs) {
3386
- const dataDir = join30(servicesDir, bootstrapDirName, "src", "data");
3779
+ const dataDir = join35(servicesDir, bootstrapDirName, "src", "data");
3387
3780
  let dataDirEntries;
3388
3781
  try {
3389
- dataDirEntries = await readdir6(dataDir);
3782
+ dataDirEntries = await readdir7(dataDir);
3390
3783
  } catch {
3391
3784
  continue;
3392
3785
  }
3393
3786
  for (const subDir of dataDirEntries) {
3394
3787
  if (subDir === "platform") continue;
3395
- const appJsonPath = join30(dataDir, subDir, "application.json");
3788
+ const appJsonPath = join35(dataDir, subDir, "application.json");
3396
3789
  try {
3397
- const content = await readFile12(appJsonPath, "utf-8");
3790
+ const content = await readFile14(appJsonPath, "utf-8");
3398
3791
  const appData = JSON.parse(content);
3399
3792
  if (!appData.name) continue;
3400
3793
  const applicationName = appData.name;
@@ -3412,7 +3805,7 @@ async function detectFromBootstrap(appDir) {
3412
3805
  }
3413
3806
  async function detectFromPackageJson(appDir) {
3414
3807
  try {
3415
- const content = await readFile12(join30(appDir, "package.json"), "utf-8");
3808
+ const content = await readFile14(join35(appDir, "package.json"), "utf-8");
3416
3809
  const pkg = JSON.parse(content);
3417
3810
  const rawName = pkg.name ?? "";
3418
3811
  const applicationName = rawName.includes("/") ? rawName.split("/")[1] ?? rawName : rawName;
@@ -3435,23 +3828,24 @@ function inferPlatformNameFromAppDir(appDir, applicationName) {
3435
3828
  }
3436
3829
 
3437
3830
  // src/commands/standalone/standalone-orchestrator.ts
3438
- import { join as join31, dirname as dirname13 } from "path";
3439
- import { mkdir as mkdir5, rm as rm2, readdir as readdir7, access as access10, writeFile as writeFile11, readFile as readFile13 } from "fs/promises";
3831
+ import { join as join36, dirname as dirname16 } from "path";
3832
+ import { mkdir as mkdir5, rm as rm2, readdir as readdir8, access as access12, writeFile as writeFile14, readFile as readFile15, open } from "fs/promises";
3833
+ import { constants } from "fs";
3440
3834
  import { fetch as undiciFetch4, Agent as Agent4 } from "undici";
3441
3835
  var TMP_BASE = "/tmp";
3442
3836
  var STANDALONE_PREFIX = "platform-standalone-";
3443
3837
  var DETACH_MARKER = ".standalone-running";
3444
3838
  function getTmpDir(platformName) {
3445
- return join31(TMP_BASE, `${STANDALONE_PREFIX}${platformName}`);
3839
+ return join36(TMP_BASE, `${STANDALONE_PREFIX}${platformName}`);
3446
3840
  }
3447
3841
  async function findRunningSingleton() {
3448
3842
  try {
3449
- const entries = await readdir7(TMP_BASE, { withFileTypes: true });
3843
+ const entries = await readdir8(TMP_BASE, { withFileTypes: true });
3450
3844
  for (const entry of entries) {
3451
3845
  if (entry.isDirectory() && entry.name.startsWith(STANDALONE_PREFIX)) {
3452
- const candidateDir = join31(TMP_BASE, entry.name);
3846
+ const candidateDir = join36(TMP_BASE, entry.name);
3453
3847
  try {
3454
- await access10(join31(candidateDir, DETACH_MARKER));
3848
+ await access12(join36(candidateDir, DETACH_MARKER));
3455
3849
  return candidateDir;
3456
3850
  } catch {
3457
3851
  }
@@ -3495,7 +3889,7 @@ async function configureIdpFromConfig(config, localDir, logger) {
3495
3889
  logger.log("No IDP configured \u2014 run 'platform standalone-config set' to add an IDP. Skipping IDP setup.");
3496
3890
  return;
3497
3891
  }
3498
- const envPath = join31(localDir, ".env");
3892
+ const envPath = join36(localDir, ".env");
3499
3893
  let env;
3500
3894
  try {
3501
3895
  env = await readEnvFile(envPath);
@@ -3550,7 +3944,7 @@ async function configureAdminUsersFromConfig(config, localDir, logger) {
3550
3944
  if (!config.adminUsers || config.adminUsers.length === 0) {
3551
3945
  return;
3552
3946
  }
3553
- const envPath = join31(localDir, ".env");
3947
+ const envPath = join36(localDir, ".env");
3554
3948
  let env;
3555
3949
  try {
3556
3950
  env = await readEnvFile(envPath);
@@ -3593,19 +3987,19 @@ async function configureAdminUsersFromConfig(config, localDir, logger) {
3593
3987
  }
3594
3988
  }
3595
3989
  function buildLayout(tmpDir, coreDirName) {
3596
- const coreDir = join31(tmpDir, coreDirName);
3990
+ const coreDir = join36(tmpDir, coreDirName);
3597
3991
  return {
3598
3992
  rootDir: tmpDir,
3599
3993
  coreDir,
3600
3994
  coreDirName,
3601
- localDir: join31(coreDir, "local")
3995
+ localDir: join36(coreDir, "local")
3602
3996
  };
3603
3997
  }
3604
- async function generateProxyAppCompose(appDir, localDir, platformName, applicationName, logger) {
3605
- const appComposePath = join31(appDir, "docker-compose.yml");
3998
+ async function generateProxyAppCompose2(appDir, localDir, platformName, applicationName, logger) {
3999
+ const appComposePath = join36(appDir, "docker-compose.yml");
3606
4000
  let content;
3607
4001
  try {
3608
- content = await readFile13(appComposePath, "utf-8");
4002
+ content = await readFile15(appComposePath, "utf-8");
3609
4003
  } catch {
3610
4004
  logger.log(`Warning: No docker-compose.yml found at ${appComposePath} \u2014 app services won't start.`);
3611
4005
  return;
@@ -3614,10 +4008,10 @@ async function generateProxyAppCompose(appDir, localDir, platformName, applicati
3614
4008
  /^(\s+context:\s+)\.\/(.*)$/gm,
3615
4009
  `$1${appDir}/$2`
3616
4010
  );
3617
- const appParentDir = dirname13(appDir);
4011
+ const appParentDir = dirname16(appDir);
3618
4012
  const rewritten = withAbsContexts.replace(/\$\{PWD\}/g, appParentDir);
3619
- const proxyPath = join31(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
3620
- await writeFile11(proxyPath, rewritten, "utf-8");
4013
+ const proxyPath = join36(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
4014
+ await writeFile14(proxyPath, rewritten, "utf-8");
3621
4015
  logger.log(`Generated app compose with absolute build paths.`);
3622
4016
  }
3623
4017
  function buildComposeManifest(manifest) {
@@ -3643,7 +4037,23 @@ async function startStandalone(config, appDir, logger, signal, detach = false) {
3643
4037
  }
3644
4038
  logger.log(`Starting standalone platform for "${applicationDisplayName}"...`);
3645
4039
  await mkdir5(tmpDir, { recursive: true });
3646
- await writeFile11(join31(tmpDir, DETACH_MARKER), platformName, "utf-8");
4040
+ let markerFd;
4041
+ try {
4042
+ markerFd = await open(
4043
+ join36(tmpDir, DETACH_MARKER),
4044
+ constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY
4045
+ );
4046
+ await markerFd.writeFile(platformName, "utf-8");
4047
+ } catch (err) {
4048
+ if (err.code === "EEXIST") {
4049
+ logger.log(`Error: A standalone platform is already starting at ${tmpDir}.`);
4050
+ logger.log(`Run "platform standalone-stop" to stop it before starting a new one.`);
4051
+ return;
4052
+ }
4053
+ throw err;
4054
+ } finally {
4055
+ await markerFd?.close();
4056
+ }
3647
4057
  const variables = {
3648
4058
  organizationName,
3649
4059
  platformName,
@@ -3668,9 +4078,9 @@ async function startStandalone(config, appDir, logger, signal, detach = false) {
3668
4078
  }
3669
4079
  );
3670
4080
  await writeManifest(npmManifest, tmpDir, coreDirName);
3671
- const bootstrapServiceDir = join31(tmpDir, coreDirName, "services", bootstrapServiceName);
4081
+ const bootstrapServiceDir = join36(tmpDir, coreDirName, "services", bootstrapServiceName);
3672
4082
  await scaffoldPlatformBootstrap(bootstrapServiceDir, variables, logger);
3673
- const customizationUiDir = join31(tmpDir, coreDirName, "ui", customizationUiName);
4083
+ const customizationUiDir = join36(tmpDir, coreDirName, "ui", customizationUiName);
3674
4084
  await scaffoldCustomizationUi(customizationUiDir, variables, logger);
3675
4085
  await registerCustomizationModule(bootstrapServiceDir, organizationName, logger, platformName);
3676
4086
  const layout = buildLayout(tmpDir, coreDirName);
@@ -3680,7 +4090,7 @@ async function startStandalone(config, appDir, logger, signal, detach = false) {
3680
4090
  logger.log("Building...");
3681
4091
  await buildAll(layout, npmManifest, logger, signal, true);
3682
4092
  if (signal?.aborted) return;
3683
- await generateProxyAppCompose(appDir, layout.localDir, platformName, applicationName, logger);
4093
+ await generateProxyAppCompose2(appDir, layout.localDir, platformName, applicationName, logger);
3684
4094
  const composeManifest = buildComposeManifest(npmManifest);
3685
4095
  await writeManifest(composeManifest, tmpDir, coreDirName);
3686
4096
  logger.log("Resetting previous environment (removing stale containers and volumes)...");
@@ -3689,7 +4099,7 @@ async function startStandalone(config, appDir, logger, signal, detach = false) {
3689
4099
  logger.log("Starting containers...");
3690
4100
  await startEnvironment(layout, composeManifest, logger, signal);
3691
4101
  if (signal?.aborted) return;
3692
- const env = await readEnvFile(join31(layout.localDir, ".env")).catch(() => /* @__PURE__ */ new Map());
4102
+ const env = await readEnvFile(join36(layout.localDir, ".env")).catch(() => /* @__PURE__ */ new Map());
3693
4103
  const gatewayUrl = env.get("PAE_GATEWAY_HOST_URL") ?? "https://localhost:443";
3694
4104
  const gatewayReady = await waitForGateway(gatewayUrl, logger);
3695
4105
  if (!gatewayReady) {
@@ -3709,16 +4119,27 @@ Standalone platform is running in the background.`);
3709
4119
  logger.log("\nPlatform is running. Press Ctrl+C to stop and clean up.");
3710
4120
  const keepAlive = setInterval(() => {
3711
4121
  }, 60 * 6e4);
4122
+ let cleanupInProgress = false;
3712
4123
  const shutdown = () => {
4124
+ if (cleanupInProgress) {
4125
+ logger.log("\nForce exiting. Containers may need manual cleanup:");
4126
+ logger.log(` docker compose -p ${platformName}-platform down -v --remove-orphans`);
4127
+ process.exit(1);
4128
+ }
4129
+ cleanupInProgress = true;
3713
4130
  clearInterval(keepAlive);
3714
- void (async () => {
4131
+ (async () => {
3715
4132
  logger.log("\nShutting down standalone platform...");
3716
4133
  await destroyEnvironment(layout, composeManifest, logger);
3717
4134
  await cleanupTmpDir(tmpDir, logger);
3718
- })();
4135
+ process.exit(0);
4136
+ })().catch((err) => {
4137
+ logger.log(`Error during cleanup: ${err.message}`);
4138
+ process.exit(1);
4139
+ });
3719
4140
  };
3720
- process.once("SIGINT", shutdown);
3721
- process.once("SIGTERM", shutdown);
4141
+ process.on("SIGINT", shutdown);
4142
+ process.on("SIGTERM", shutdown);
3722
4143
  }
3723
4144
  async function stopStandalone(logger) {
3724
4145
  const runningDir = await findRunningSingleton();
@@ -3728,11 +4149,11 @@ async function stopStandalone(logger) {
3728
4149
  }
3729
4150
  let coreDirName = null;
3730
4151
  try {
3731
- const entries = await readdir7(runningDir, { withFileTypes: true });
4152
+ const entries = await readdir8(runningDir, { withFileTypes: true });
3732
4153
  for (const entry of entries) {
3733
4154
  if (entry.isDirectory() && entry.name.endsWith("-core")) {
3734
4155
  try {
3735
- await access10(join31(runningDir, entry.name, "product.manifest.json"));
4156
+ await access12(join36(runningDir, entry.name, "product.manifest.json"));
3736
4157
  coreDirName = entry.name;
3737
4158
  break;
3738
4159
  } catch {
@@ -3769,9 +4190,9 @@ async function stopStandalone(logger) {
3769
4190
 
3770
4191
  // src/controllers/ui/standalone.ui-controller.ts
3771
4192
  function getGitEmail() {
3772
- return new Promise((resolve9) => {
4193
+ return new Promise((resolve11) => {
3773
4194
  exec("git config user.email", (err, stdout) => {
3774
- resolve9(err ? "" : stdout.trim());
4195
+ resolve11(err ? "" : stdout.trim());
3775
4196
  });
3776
4197
  });
3777
4198
  }
@@ -3847,7 +4268,7 @@ async function standaloneUiController(ctx) {
3847
4268
  const deleteLegacy = await ctx.confirm("Delete the local standalone.json?", true);
3848
4269
  if (deleteLegacy) {
3849
4270
  try {
3850
- await rm3(join32(appDir, "standalone.json"));
4271
+ await rm3(join37(appDir, "standalone.json"));
3851
4272
  ctx.log("Deleted standalone.json.");
3852
4273
  } catch {
3853
4274
  ctx.log("Warning: Could not delete standalone.json.");
@@ -3979,7 +4400,7 @@ async function standaloneConfigShowUiController(ctx) {
3979
4400
  if (config) {
3980
4401
  ctx.log(`Standalone config for ${detected.platformName}/${detected.applicationName}:
3981
4402
  `);
3982
- ctx.log(JSON.stringify(config, null, 2));
4403
+ ctx.log(JSON.stringify(maskSensitiveFields(config), null, 2));
3983
4404
  } else {
3984
4405
  ctx.log(`No config found for ${detected.platformName}/${detected.applicationName}.`);
3985
4406
  ctx.log(`Run 'platform standalone' or 'platform standalone-config set' to configure.`);
@@ -3998,7 +4419,7 @@ async function showAll(ctx) {
3998
4419
  if (Object.keys(defaults).length === 0) {
3999
4420
  ctx.log("(none)");
4000
4421
  } else {
4001
- ctx.log(JSON.stringify(defaults, null, 2));
4422
+ ctx.log(JSON.stringify(maskSensitiveFields(defaults), null, 2));
4002
4423
  }
4003
4424
  ctx.log("\n--- Apps ---");
4004
4425
  if (apps.length === 0) {
@@ -4130,9 +4551,9 @@ async function collectIdpConfig2(ctx, current) {
4130
4551
  return { provider: providerType, name, issuer, clientId, clientSecret, extras };
4131
4552
  }
4132
4553
  function getGitEmail2() {
4133
- return new Promise((resolve9) => {
4554
+ return new Promise((resolve11) => {
4134
4555
  exec2("git config user.email", (err, stdout) => {
4135
- resolve9(err ? "" : stdout.trim());
4556
+ resolve11(err ? "" : stdout.trim());
4136
4557
  });
4137
4558
  });
4138
4559
  }
@@ -4171,7 +4592,8 @@ var uiControllers = /* @__PURE__ */ new Map([
4171
4592
  [STANDALONE_CONFIG_SHOW_COMMAND_NAME, standaloneConfigShowUiController],
4172
4593
  [STANDALONE_CONFIG_SET_COMMAND_NAME, standaloneConfigSetUiController],
4173
4594
  [STANDALONE_CONFIG_DELETE_COMMAND_NAME, standaloneConfigDeleteUiController],
4174
- [STANDALONE_CONFIG_LIST_COMMAND_NAME, standaloneConfigListUiController]
4595
+ [STANDALONE_CONFIG_LIST_COMMAND_NAME, standaloneConfigListUiController],
4596
+ [PIPELINES_COMMAND_NAME, pipelinesUiController]
4175
4597
  ]);
4176
4598
 
4177
4599
  // src/hooks/use-command-runner.ts
@@ -4205,7 +4627,7 @@ function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
4205
4627
  }
4206
4628
  },
4207
4629
  prompt(message, defaultValue) {
4208
- return new Promise((resolve9, reject) => {
4630
+ return new Promise((resolve11, reject) => {
4209
4631
  if (controller.signal.aborted) {
4210
4632
  reject(new DOMException("Aborted", "AbortError"));
4211
4633
  return;
@@ -4213,7 +4635,7 @@ function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
4213
4635
  setPromptMessage(message);
4214
4636
  setPromptValue(defaultValue ?? "");
4215
4637
  setPromptMode({ kind: "text" });
4216
- promptResolveRef.current = resolve9;
4638
+ promptResolveRef.current = resolve11;
4217
4639
  setState(APP_STATE.PROMPTING);
4218
4640
  controller.signal.addEventListener(
4219
4641
  "abort",
@@ -4223,7 +4645,7 @@ function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
4223
4645
  });
4224
4646
  },
4225
4647
  select(message, options) {
4226
- return new Promise((resolve9, reject) => {
4648
+ return new Promise((resolve11, reject) => {
4227
4649
  if (controller.signal.aborted) {
4228
4650
  reject(new DOMException("Aborted", "AbortError"));
4229
4651
  return;
@@ -4231,7 +4653,7 @@ function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
4231
4653
  setPromptMessage(message);
4232
4654
  setPromptMode({ kind: "select", options });
4233
4655
  setSelectIndex(0);
4234
- promptResolveRef.current = resolve9;
4656
+ promptResolveRef.current = resolve11;
4235
4657
  setState(APP_STATE.PROMPTING);
4236
4658
  controller.signal.addEventListener(
4237
4659
  "abort",
@@ -4241,7 +4663,7 @@ function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
4241
4663
  });
4242
4664
  },
4243
4665
  multiselect(message, options) {
4244
- return new Promise((resolve9, reject) => {
4666
+ return new Promise((resolve11, reject) => {
4245
4667
  if (controller.signal.aborted) {
4246
4668
  reject(new DOMException("Aborted", "AbortError"));
4247
4669
  return;
@@ -4251,7 +4673,7 @@ function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
4251
4673
  setSelectIndex(0);
4252
4674
  setMultiselectChecked(new Set(options.map((_, i) => i)));
4253
4675
  promptResolveRef.current = (value) => {
4254
- resolve9(value ? value.split(",") : []);
4676
+ resolve11(value ? value.split(",") : []);
4255
4677
  };
4256
4678
  setState(APP_STATE.PROMPTING);
4257
4679
  controller.signal.addEventListener(
@@ -4262,7 +4684,7 @@ function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
4262
4684
  });
4263
4685
  },
4264
4686
  confirm(message, defaultValue) {
4265
- return new Promise((resolve9, reject) => {
4687
+ return new Promise((resolve11, reject) => {
4266
4688
  if (controller.signal.aborted) {
4267
4689
  reject(new DOMException("Aborted", "AbortError"));
4268
4690
  return;
@@ -4270,7 +4692,7 @@ function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
4270
4692
  setPromptMessage(message);
4271
4693
  setPromptMode({ kind: "confirm" });
4272
4694
  setConfirmValue(defaultValue ?? true);
4273
- promptResolveRef.current = (value) => resolve9(value === "true");
4695
+ promptResolveRef.current = (value) => resolve11(value === "true");
4274
4696
  setState(APP_STATE.PROMPTING);
4275
4697
  controller.signal.addEventListener(
4276
4698
  "abort",
@@ -4304,11 +4726,11 @@ function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
4304
4726
  );
4305
4727
  const handlePromptSubmit = useCallback(
4306
4728
  (value) => {
4307
- const resolve9 = promptResolveRef.current;
4729
+ const resolve11 = promptResolveRef.current;
4308
4730
  promptResolveRef.current = null;
4309
4731
  setPromptMode({ kind: "text" });
4310
4732
  setState(APP_STATE.EXECUTING);
4311
- resolve9?.(value);
4733
+ resolve11?.(value);
4312
4734
  },
4313
4735
  [setState]
4314
4736
  );
@@ -4904,7 +5326,7 @@ async function standaloneConfigShowCliController(args2) {
4904
5326
  const [pn, an] = parts;
4905
5327
  const config = await resolveAppConfig(pn, an);
4906
5328
  if (config) {
4907
- console.log(JSON.stringify(config, null, 2));
5329
+ console.log(JSON.stringify(maskSensitiveFields(config), null, 2));
4908
5330
  } else {
4909
5331
  console.error(`No config found for ${appArg}.`);
4910
5332
  process.exit(1);
@@ -4915,12 +5337,12 @@ async function standaloneConfigShowCliController(args2) {
4915
5337
  if (detected) {
4916
5338
  const config = await resolveAppConfig(detected.platformName, detected.applicationName);
4917
5339
  if (config) {
4918
- console.log(JSON.stringify(config, null, 2));
5340
+ console.log(JSON.stringify(maskSensitiveFields(config), null, 2));
4919
5341
  return;
4920
5342
  }
4921
5343
  }
4922
5344
  const cache = await readCacheFile();
4923
- console.log(JSON.stringify(cache, null, 2));
5345
+ console.log(JSON.stringify(maskSensitiveFields(cache), null, 2));
4924
5346
  }
4925
5347
  async function standaloneConfigSetCliController(args2) {
4926
5348
  if (args2["defaults"] === "true") {
@@ -4994,6 +5416,102 @@ async function standaloneConfigListCliController(_args) {
4994
5416
  }
4995
5417
  }
4996
5418
 
5419
+ // src/controllers/cli/pipelines.cli-controller.ts
5420
+ async function pipelinesCliController(args2) {
5421
+ const {
5422
+ provider,
5423
+ targets: targetsArg,
5424
+ stateFile,
5425
+ codeFolderList,
5426
+ packagesFolderPattern,
5427
+ azureServiceConnection,
5428
+ acrLoginServer,
5429
+ nodeVersion,
5430
+ templatesRepoName,
5431
+ gitopsRepoName,
5432
+ services: servicesJson
5433
+ } = args2;
5434
+ if (!provider) {
5435
+ console.error("Error: --provider is required (github or azure)");
5436
+ process.exit(1);
5437
+ }
5438
+ if (provider !== "github" && provider !== "azure") {
5439
+ console.error('Error: --provider must be "github" or "azure"');
5440
+ process.exit(1);
5441
+ }
5442
+ if (provider === "azure") {
5443
+ if (!azureServiceConnection) {
5444
+ console.error("Error: --azureServiceConnection is required for Azure DevOps");
5445
+ process.exit(1);
5446
+ }
5447
+ if (!acrLoginServer) {
5448
+ console.error("Error: --acrLoginServer is required for Azure DevOps");
5449
+ process.exit(1);
5450
+ }
5451
+ }
5452
+ const layout = await findPlatformLayout();
5453
+ if (!layout) {
5454
+ console.error("Error: Cannot generate pipelines \u2014 no platform initialized in this directory.");
5455
+ process.exit(1);
5456
+ }
5457
+ const { rootDir, coreDirName } = layout;
5458
+ let manifest;
5459
+ try {
5460
+ manifest = await readManifest(rootDir, coreDirName);
5461
+ } catch {
5462
+ console.error("Error: Could not read product manifest.");
5463
+ process.exit(1);
5464
+ }
5465
+ const allTargets = buildTargetList(rootDir, coreDirName, manifest);
5466
+ let selectedTargets;
5467
+ if (!targetsArg || targetsArg === "all") {
5468
+ selectedTargets = allTargets;
5469
+ } else {
5470
+ const names = targetsArg.split(",").map((n) => n.trim());
5471
+ selectedTargets = allTargets.filter((t) => names.includes(t.name));
5472
+ if (selectedTargets.length === 0) {
5473
+ console.error(`Error: None of the specified targets found. Available: ${allTargets.map((t) => t.name).join(", ")}`);
5474
+ process.exit(1);
5475
+ }
5476
+ }
5477
+ let servicesMap = {};
5478
+ if (servicesJson) {
5479
+ try {
5480
+ servicesMap = JSON.parse(servicesJson);
5481
+ } catch {
5482
+ console.error(`Error: --services must be valid JSON map keyed by target name (e.g. '{"myapp":[{"name":"svc","folder":"services/svc"}]}')`);
5483
+ process.exit(1);
5484
+ }
5485
+ }
5486
+ const targetsWithServices = [];
5487
+ for (const target of selectedTargets) {
5488
+ let services;
5489
+ if (servicesMap[target.name]) {
5490
+ services = servicesMap[target.name];
5491
+ } else if (provider === "azure") {
5492
+ services = await detectServicesInDir(target.dir);
5493
+ } else {
5494
+ services = [];
5495
+ }
5496
+ targetsWithServices.push({ ...target, services });
5497
+ }
5498
+ await pipelinesService(
5499
+ {
5500
+ provider,
5501
+ targets: targetsWithServices,
5502
+ stateFile,
5503
+ codeFolderList,
5504
+ packagesFolderPattern,
5505
+ azureServiceConnection,
5506
+ acrLoginServer,
5507
+ nodeVersion,
5508
+ templatesRepoName,
5509
+ gitopsRepoName
5510
+ },
5511
+ { log: console.log }
5512
+ );
5513
+ }
5514
+
4997
5515
  // src/controllers/cli/registry.ts
4998
5516
  var cliControllers = /* @__PURE__ */ new Map([
4999
5517
  [CREATE_APPLICATION_COMMAND_NAME, createApplicationCliController],
@@ -5014,7 +5532,8 @@ var cliControllers = /* @__PURE__ */ new Map([
5014
5532
  [STANDALONE_CONFIG_SHOW_COMMAND_NAME, standaloneConfigShowCliController],
5015
5533
  [STANDALONE_CONFIG_SET_COMMAND_NAME, standaloneConfigSetCliController],
5016
5534
  [STANDALONE_CONFIG_DELETE_COMMAND_NAME, standaloneConfigDeleteCliController],
5017
- [STANDALONE_CONFIG_LIST_COMMAND_NAME, standaloneConfigListCliController]
5535
+ [STANDALONE_CONFIG_LIST_COMMAND_NAME, standaloneConfigListCliController],
5536
+ [PIPELINES_COMMAND_NAME, pipelinesCliController]
5018
5537
  ]);
5019
5538
 
5020
5539
  // src/utils/parse-args.ts