@bluealba/platform-cli 1.2.0-alpha.3 → 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
|
@@ -1290,7 +1290,7 @@ async function configureIdp(params, logger) {
|
|
|
1290
1290
|
|
|
1291
1291
|
// src/commands/create-service-module/create-service-module.command.ts
|
|
1292
1292
|
import { join as join22, resolve as resolve3 } from "path";
|
|
1293
|
-
import { access as
|
|
1293
|
+
import { access as access5 } from "fs/promises";
|
|
1294
1294
|
|
|
1295
1295
|
// src/commands/create-service-module/scaffold-service-module.ts
|
|
1296
1296
|
import { fileURLToPath as fileURLToPath9 } from "url";
|
|
@@ -1354,6 +1354,46 @@ async function appendServiceToDockerCompose(dockerComposePath, serviceName, plat
|
|
|
1354
1354
|
}
|
|
1355
1355
|
}
|
|
1356
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
|
+
|
|
1357
1397
|
// src/commands/create-service-module/create-service-module.command.ts
|
|
1358
1398
|
var CREATE_SERVICE_MODULE_COMMAND_NAME = "create-service-module";
|
|
1359
1399
|
var createServiceModuleCommand = {
|
|
@@ -1384,7 +1424,7 @@ async function createServiceModule(params, logger) {
|
|
|
1384
1424
|
}
|
|
1385
1425
|
const applicationDir = resolve3(join22(rootDir, coreDirName), appEntry.localPath);
|
|
1386
1426
|
try {
|
|
1387
|
-
await
|
|
1427
|
+
await access5(applicationDir);
|
|
1388
1428
|
} catch {
|
|
1389
1429
|
logger.log(`Error: The specified application "${applicationName}" does not exist in the platform.`);
|
|
1390
1430
|
return;
|
|
@@ -1393,7 +1433,7 @@ async function createServiceModule(params, logger) {
|
|
|
1393
1433
|
const fullServiceName = suffix ? `${platformName}-${serviceName}-${suffix}` : `${platformName}-${serviceName}`;
|
|
1394
1434
|
const serviceDir = join22(applicationDir, "services", fullServiceName);
|
|
1395
1435
|
try {
|
|
1396
|
-
await
|
|
1436
|
+
await access5(serviceDir);
|
|
1397
1437
|
logger.log(`Error: A service named "${fullServiceName}" already exists in application "${applicationName}".`);
|
|
1398
1438
|
return;
|
|
1399
1439
|
} catch {
|
|
@@ -1418,15 +1458,17 @@ async function createServiceModule(params, logger) {
|
|
|
1418
1458
|
const dockerComposePath = join22(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
|
|
1419
1459
|
const port = await getNextAvailablePort(localDir);
|
|
1420
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);
|
|
1421
1463
|
logger.log(`Done! Service module "${fullServiceName}" added to application "${applicationName}".`);
|
|
1422
1464
|
}
|
|
1423
1465
|
|
|
1424
1466
|
// src/commands/create-ui-module/create-ui-module.command.ts
|
|
1425
1467
|
import { join as join23, resolve as resolve4 } from "path";
|
|
1426
|
-
import { access as
|
|
1468
|
+
import { access as access6, readdir as readdir4 } from "fs/promises";
|
|
1427
1469
|
|
|
1428
1470
|
// src/commands/create-ui-module/append-ui-docker-compose.ts
|
|
1429
|
-
import { readFile as
|
|
1471
|
+
import { readFile as readFile11, writeFile as writeFile11 } from "fs/promises";
|
|
1430
1472
|
async function appendUiToDockerCompose(dockerComposePath, platformName, applicationName, port, logger, uiNameOverride) {
|
|
1431
1473
|
const uiName = uiNameOverride ?? `${platformName}-${applicationName}-ui`;
|
|
1432
1474
|
const block = `
|
|
@@ -1440,8 +1482,8 @@ async function appendUiToDockerCompose(dockerComposePath, platformName, applicat
|
|
|
1440
1482
|
- \${PWD}/:/app/out
|
|
1441
1483
|
`;
|
|
1442
1484
|
try {
|
|
1443
|
-
const existing = await
|
|
1444
|
-
await
|
|
1485
|
+
const existing = await readFile11(dockerComposePath, "utf-8");
|
|
1486
|
+
await writeFile11(dockerComposePath, existing + block, "utf-8");
|
|
1445
1487
|
logger.log(`Updated docker-compose: ${dockerComposePath}`);
|
|
1446
1488
|
} catch (err) {
|
|
1447
1489
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -1479,7 +1521,7 @@ async function createUiModule(params, logger) {
|
|
|
1479
1521
|
}
|
|
1480
1522
|
const applicationDir = resolve4(join23(rootDir, coreDirName), appEntry.localPath);
|
|
1481
1523
|
try {
|
|
1482
|
-
await
|
|
1524
|
+
await access6(applicationDir);
|
|
1483
1525
|
} catch {
|
|
1484
1526
|
logger.log(`Error: The specified application "${applicationName}" does not exist in the platform.`);
|
|
1485
1527
|
return;
|
|
@@ -1510,18 +1552,20 @@ async function createUiModule(params, logger) {
|
|
|
1510
1552
|
const dockerComposePath = join23(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
|
|
1511
1553
|
const port = await getNextAvailablePort(localDir);
|
|
1512
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);
|
|
1513
1557
|
logger.log(`Done! UI module "${uiName}" added to application "${applicationName}".`);
|
|
1514
1558
|
}
|
|
1515
1559
|
|
|
1516
1560
|
// src/commands/status/status-checks.ts
|
|
1517
1561
|
import { spawn as spawn3 } from "child_process";
|
|
1518
|
-
import { access as
|
|
1562
|
+
import { access as access9 } from "fs/promises";
|
|
1519
1563
|
import { join as join26, resolve as resolve7 } from "path";
|
|
1520
1564
|
import { fetch as undiciFetch2, Agent as Agent2 } from "undici";
|
|
1521
1565
|
|
|
1522
1566
|
// src/commands/local-scripts/docker-compose-orchestrator.ts
|
|
1523
1567
|
import { spawn } from "child_process";
|
|
1524
|
-
import { access as
|
|
1568
|
+
import { access as access7, readFile as readFile12, writeFile as writeFile12 } from "fs/promises";
|
|
1525
1569
|
import { dirname as dirname12, join as join24, resolve as resolve5 } from "path";
|
|
1526
1570
|
function runDockerCompose(args2, logger, rootDir, signal) {
|
|
1527
1571
|
return new Promise((resolvePromise) => {
|
|
@@ -1587,7 +1631,7 @@ function captureDockerCompose(args2, rootDir) {
|
|
|
1587
1631
|
}
|
|
1588
1632
|
async function generateProxyAppCompose(appDir, localDir, platformName, applicationName) {
|
|
1589
1633
|
const appComposePath = join24(appDir, "docker-compose.yml");
|
|
1590
|
-
const content = await
|
|
1634
|
+
const content = await readFile12(appComposePath, "utf-8");
|
|
1591
1635
|
const withAbsContexts = content.replace(
|
|
1592
1636
|
/^(\s+context:\s+)\.\/(.*)$/gm,
|
|
1593
1637
|
`$1${appDir}/$2`
|
|
@@ -1595,7 +1639,7 @@ async function generateProxyAppCompose(appDir, localDir, platformName, applicati
|
|
|
1595
1639
|
const appParentDir = dirname12(appDir);
|
|
1596
1640
|
const rewritten = withAbsContexts.replace(/\$\{PWD\}/g, appParentDir);
|
|
1597
1641
|
const proxyPath = join24(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
|
|
1598
|
-
await
|
|
1642
|
+
await writeFile12(proxyPath, rewritten, "utf-8");
|
|
1599
1643
|
return proxyPath;
|
|
1600
1644
|
}
|
|
1601
1645
|
async function getAppComposePaths(localDir, coreDir, platformName, manifest, logger) {
|
|
@@ -1607,16 +1651,16 @@ async function getAppComposePaths(localDir, coreDir, platformName, manifest, log
|
|
|
1607
1651
|
const legacyUnprefixedPath = join24(localDir, `${app.name}-docker-compose.yml`);
|
|
1608
1652
|
let resolved = null;
|
|
1609
1653
|
try {
|
|
1610
|
-
await
|
|
1654
|
+
await access7(newPath);
|
|
1611
1655
|
resolved = await generateProxyAppCompose(appDir, localDir, platformName, app.name);
|
|
1612
1656
|
} catch {
|
|
1613
1657
|
try {
|
|
1614
|
-
await
|
|
1658
|
+
await access7(legacyPrefixedPath);
|
|
1615
1659
|
resolved = legacyPrefixedPath;
|
|
1616
1660
|
logger?.log(`Warning: docker-compose for "${app.name}" found in legacy location (core/local/). Consider moving it to the app monorepo root.`);
|
|
1617
1661
|
} catch {
|
|
1618
1662
|
try {
|
|
1619
|
-
await
|
|
1663
|
+
await access7(legacyUnprefixedPath);
|
|
1620
1664
|
resolved = legacyUnprefixedPath;
|
|
1621
1665
|
logger?.log(`Warning: docker-compose for "${app.name}" found in legacy location (core/local/). Consider moving it to the app monorepo root.`);
|
|
1622
1666
|
} catch {
|
|
@@ -1636,7 +1680,7 @@ async function buildFullComposeArgs(layout, manifest, logger) {
|
|
|
1636
1680
|
const unprefixedCoreCompose = join24(localDir, "core-docker-compose.yml");
|
|
1637
1681
|
let coreComposePath;
|
|
1638
1682
|
try {
|
|
1639
|
-
await
|
|
1683
|
+
await access7(prefixedCoreCompose);
|
|
1640
1684
|
coreComposePath = prefixedCoreCompose;
|
|
1641
1685
|
} catch {
|
|
1642
1686
|
coreComposePath = unprefixedCoreCompose;
|
|
@@ -1667,7 +1711,7 @@ async function buildSelectedComposeFiles(layout, selectedManifest, includeCore)
|
|
|
1667
1711
|
const unprefixedCoreCompose = join24(localDir, "core-docker-compose.yml");
|
|
1668
1712
|
let coreComposePath;
|
|
1669
1713
|
try {
|
|
1670
|
-
await
|
|
1714
|
+
await access7(prefixedCoreCompose);
|
|
1671
1715
|
coreComposePath = prefixedCoreCompose;
|
|
1672
1716
|
} catch {
|
|
1673
1717
|
coreComposePath = unprefixedCoreCompose;
|
|
@@ -1794,7 +1838,7 @@ async function destroyEnvironment(layout, manifest, logger, signal, includeCore
|
|
|
1794
1838
|
|
|
1795
1839
|
// src/commands/local-scripts/npm-orchestrator.ts
|
|
1796
1840
|
import { spawn as spawn2 } from "child_process";
|
|
1797
|
-
import { access as
|
|
1841
|
+
import { access as access8 } from "fs/promises";
|
|
1798
1842
|
import { resolve as resolve6, join as join25 } from "path";
|
|
1799
1843
|
function runCommand(command, args2, workDir, logger, signal) {
|
|
1800
1844
|
return new Promise((resolvePromise) => {
|
|
@@ -1842,7 +1886,7 @@ function runCommand(command, args2, workDir, logger, signal) {
|
|
|
1842
1886
|
}
|
|
1843
1887
|
async function dirExists(dirPath) {
|
|
1844
1888
|
try {
|
|
1845
|
-
await
|
|
1889
|
+
await access8(dirPath);
|
|
1846
1890
|
return true;
|
|
1847
1891
|
} catch {
|
|
1848
1892
|
return false;
|
|
@@ -2015,7 +2059,7 @@ function spawnCapture(cmd, args2, cwd11) {
|
|
|
2015
2059
|
}
|
|
2016
2060
|
async function pathExists(p) {
|
|
2017
2061
|
try {
|
|
2018
|
-
await
|
|
2062
|
+
await access9(p);
|
|
2019
2063
|
return true;
|
|
2020
2064
|
} catch {
|
|
2021
2065
|
return false;
|
|
@@ -2681,6 +2725,132 @@ var standaloneConfigListCommand = {
|
|
|
2681
2725
|
hidden: () => false
|
|
2682
2726
|
};
|
|
2683
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
|
+
|
|
2684
2854
|
// src/commands/registry.ts
|
|
2685
2855
|
var CommandRegistry = class {
|
|
2686
2856
|
commands = /* @__PURE__ */ new Map();
|
|
@@ -2728,6 +2898,7 @@ registry.register(standaloneConfigShowCommand);
|
|
|
2728
2898
|
registry.register(standaloneConfigSetCommand);
|
|
2729
2899
|
registry.register(standaloneConfigDeleteCommand);
|
|
2730
2900
|
registry.register(standaloneConfigListCommand);
|
|
2901
|
+
registry.register(pipelinesCommand);
|
|
2731
2902
|
|
|
2732
2903
|
// src/app-state.ts
|
|
2733
2904
|
var APP_STATE = {
|
|
@@ -2790,6 +2961,145 @@ async function initService(params, logger) {
|
|
|
2790
2961
|
await init(params, logger);
|
|
2791
2962
|
}
|
|
2792
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
|
+
|
|
2793
3103
|
// src/controllers/ui/init.ui-controller.ts
|
|
2794
3104
|
async function initUiController(ctx) {
|
|
2795
3105
|
if (await isPlatformInitialized()) {
|
|
@@ -2825,6 +3135,10 @@ async function initUiController(ctx) {
|
|
|
2825
3135
|
{ organizationName, platformName, platformDisplayName, bootstrapServiceSuffix, customizationUiSuffix },
|
|
2826
3136
|
ctx
|
|
2827
3137
|
);
|
|
3138
|
+
const addPipelines = await ctx.confirm("Configure CI/CD pipelines?", false);
|
|
3139
|
+
if (addPipelines) {
|
|
3140
|
+
await pipelinesUiController(ctx);
|
|
3141
|
+
}
|
|
2828
3142
|
}
|
|
2829
3143
|
|
|
2830
3144
|
// src/services/configure-idp.service.ts
|
|
@@ -3259,20 +3573,20 @@ async function installAiPluginService(params, context) {
|
|
|
3259
3573
|
|
|
3260
3574
|
// src/commands/install-ai-plugin/providers/claude.provider.ts
|
|
3261
3575
|
import { homedir as homedir3 } from "os";
|
|
3262
|
-
import { join as
|
|
3263
|
-
import { readFile as
|
|
3576
|
+
import { join as join32 } from "path";
|
|
3577
|
+
import { readFile as readFile13, writeFile as writeFile13, mkdir as mkdir4, rm } from "fs/promises";
|
|
3264
3578
|
var ClaudeProvider = class {
|
|
3265
3579
|
name = "claude";
|
|
3266
|
-
baseDir =
|
|
3580
|
+
baseDir = join32(homedir3(), ".claude");
|
|
3267
3581
|
getInstallPath(skillName) {
|
|
3268
|
-
return
|
|
3582
|
+
return join32(this.baseDir, "skills", skillName, "SKILL.md");
|
|
3269
3583
|
}
|
|
3270
3584
|
manifestPath(pluginName) {
|
|
3271
|
-
return
|
|
3585
|
+
return join32(this.baseDir, `${pluginName}.manifest.json`);
|
|
3272
3586
|
}
|
|
3273
3587
|
async readManifest(pluginName) {
|
|
3274
3588
|
try {
|
|
3275
|
-
const content = await
|
|
3589
|
+
const content = await readFile13(this.manifestPath(pluginName), "utf-8");
|
|
3276
3590
|
return JSON.parse(content);
|
|
3277
3591
|
} catch {
|
|
3278
3592
|
return null;
|
|
@@ -3280,7 +3594,7 @@ var ClaudeProvider = class {
|
|
|
3280
3594
|
}
|
|
3281
3595
|
async writeManifest(manifest) {
|
|
3282
3596
|
await mkdir4(this.baseDir, { recursive: true });
|
|
3283
|
-
await
|
|
3597
|
+
await writeFile13(
|
|
3284
3598
|
this.manifestPath(manifest.plugin),
|
|
3285
3599
|
JSON.stringify(manifest, null, 2),
|
|
3286
3600
|
"utf-8"
|
|
@@ -3300,16 +3614,16 @@ var ClaudeProvider = class {
|
|
|
3300
3614
|
}
|
|
3301
3615
|
async installSkill(skill, docsSource, logger) {
|
|
3302
3616
|
const sourcePath = docsSource.resolve(skill.sourceFile);
|
|
3303
|
-
let content = await
|
|
3617
|
+
let content = await readFile13(sourcePath, "utf-8");
|
|
3304
3618
|
const docsPath = docsSource.resolve("docs");
|
|
3305
3619
|
content = content.replaceAll("{{docsPath}}", docsPath);
|
|
3306
3620
|
const installPath = this.getInstallPath(skill.name);
|
|
3307
|
-
await mkdir4(
|
|
3308
|
-
await
|
|
3621
|
+
await mkdir4(join32(installPath, ".."), { recursive: true });
|
|
3622
|
+
await writeFile13(installPath, content, "utf-8");
|
|
3309
3623
|
logger.log(` Installed skill: ${installPath}`);
|
|
3310
3624
|
}
|
|
3311
3625
|
async removeSkill(skillName, logger) {
|
|
3312
|
-
const skillDir =
|
|
3626
|
+
const skillDir = join32(this.baseDir, "skills", skillName);
|
|
3313
3627
|
try {
|
|
3314
3628
|
await rm(skillDir, { recursive: true, force: true });
|
|
3315
3629
|
logger.log(` Removed skill: ${skillDir}`);
|
|
@@ -3317,11 +3631,11 @@ var ClaudeProvider = class {
|
|
|
3317
3631
|
}
|
|
3318
3632
|
}
|
|
3319
3633
|
async updatePermissions(docsSource, logger) {
|
|
3320
|
-
const settingsPath =
|
|
3634
|
+
const settingsPath = join32(this.baseDir, "settings.json");
|
|
3321
3635
|
const docsPath = docsSource.resolve("docs");
|
|
3322
3636
|
let settings = {};
|
|
3323
3637
|
try {
|
|
3324
|
-
const content = await
|
|
3638
|
+
const content = await readFile13(settingsPath, "utf-8");
|
|
3325
3639
|
settings = JSON.parse(content);
|
|
3326
3640
|
} catch {
|
|
3327
3641
|
}
|
|
@@ -3333,7 +3647,7 @@ var ClaudeProvider = class {
|
|
|
3333
3647
|
permissions.additionalDirectories = [...additionalDirectories, docsPath];
|
|
3334
3648
|
settings.permissions = permissions;
|
|
3335
3649
|
await mkdir4(this.baseDir, { recursive: true });
|
|
3336
|
-
await
|
|
3650
|
+
await writeFile13(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
3337
3651
|
logger.log(` Granted read access to docs: ${docsPath}`);
|
|
3338
3652
|
}
|
|
3339
3653
|
};
|
|
@@ -3352,12 +3666,12 @@ function getProvider(name) {
|
|
|
3352
3666
|
}
|
|
3353
3667
|
|
|
3354
3668
|
// src/commands/install-ai-plugin/docs-source/local.docs-source.ts
|
|
3355
|
-
import { fileURLToPath as
|
|
3356
|
-
import { join as
|
|
3357
|
-
var packageRoot =
|
|
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)), "..");
|
|
3358
3672
|
var LocalDocsSource = class {
|
|
3359
3673
|
resolve(relativePath) {
|
|
3360
|
-
return
|
|
3674
|
+
return join33(packageRoot, relativePath);
|
|
3361
3675
|
}
|
|
3362
3676
|
};
|
|
3363
3677
|
|
|
@@ -3401,20 +3715,20 @@ async function installAiPluginUiController(ctx) {
|
|
|
3401
3715
|
|
|
3402
3716
|
// src/controllers/ui/standalone.ui-controller.ts
|
|
3403
3717
|
import { rm as rm3 } from "fs/promises";
|
|
3404
|
-
import { join as
|
|
3718
|
+
import { join as join37 } from "path";
|
|
3405
3719
|
import { cwd as cwd7 } from "process";
|
|
3406
3720
|
import { exec } from "child_process";
|
|
3407
3721
|
|
|
3408
3722
|
// src/commands/standalone/app-monorepo-check.ts
|
|
3409
|
-
import { access as
|
|
3410
|
-
import { join as
|
|
3723
|
+
import { access as access11, readdir as readdir6 } from "fs/promises";
|
|
3724
|
+
import { join as join34 } from "path";
|
|
3411
3725
|
import { cwd as cwd6 } from "process";
|
|
3412
3726
|
async function findAppMonorepoLayout(startDir = cwd6()) {
|
|
3413
|
-
const composePath =
|
|
3414
|
-
const packageJsonPath =
|
|
3727
|
+
const composePath = join34(startDir, "docker-compose.yml");
|
|
3728
|
+
const packageJsonPath = join34(startDir, "package.json");
|
|
3415
3729
|
try {
|
|
3416
|
-
await
|
|
3417
|
-
await
|
|
3730
|
+
await access11(composePath);
|
|
3731
|
+
await access11(packageJsonPath);
|
|
3418
3732
|
} catch {
|
|
3419
3733
|
return null;
|
|
3420
3734
|
}
|
|
@@ -3425,11 +3739,11 @@ async function findAppMonorepoLayout(startDir = cwd6()) {
|
|
|
3425
3739
|
}
|
|
3426
3740
|
async function hasCoreChildDir(dir) {
|
|
3427
3741
|
try {
|
|
3428
|
-
const entries = await
|
|
3742
|
+
const entries = await readdir6(dir, { withFileTypes: true });
|
|
3429
3743
|
for (const entry of entries) {
|
|
3430
3744
|
if (entry.isDirectory() && (entry.name.endsWith("-core") || entry.name === "core")) {
|
|
3431
3745
|
try {
|
|
3432
|
-
await
|
|
3746
|
+
await access11(join34(dir, entry.name, "product.manifest.json"));
|
|
3433
3747
|
return true;
|
|
3434
3748
|
} catch {
|
|
3435
3749
|
}
|
|
@@ -3444,36 +3758,36 @@ async function isInAppMonorepo() {
|
|
|
3444
3758
|
}
|
|
3445
3759
|
|
|
3446
3760
|
// src/commands/standalone/standalone-auto-detect.ts
|
|
3447
|
-
import { readdir as
|
|
3448
|
-
import { join as
|
|
3761
|
+
import { readdir as readdir7, readFile as readFile14 } from "fs/promises";
|
|
3762
|
+
import { join as join35, basename as basename2 } from "path";
|
|
3449
3763
|
async function autoDetectAppIdentity(appDir) {
|
|
3450
3764
|
const fromBootstrap = await detectFromBootstrap(appDir);
|
|
3451
3765
|
if (fromBootstrap) return fromBootstrap;
|
|
3452
3766
|
return detectFromPackageJson(appDir);
|
|
3453
3767
|
}
|
|
3454
3768
|
async function detectFromBootstrap(appDir) {
|
|
3455
|
-
const servicesDir =
|
|
3769
|
+
const servicesDir = join35(appDir, "services");
|
|
3456
3770
|
let entries;
|
|
3457
3771
|
try {
|
|
3458
|
-
entries = await
|
|
3772
|
+
entries = await readdir7(servicesDir);
|
|
3459
3773
|
} catch {
|
|
3460
3774
|
return null;
|
|
3461
3775
|
}
|
|
3462
3776
|
const bootstrapDirs = entries.filter((e) => e.endsWith("-bootstrap-service"));
|
|
3463
3777
|
if (bootstrapDirs.length === 0) return null;
|
|
3464
3778
|
for (const bootstrapDirName of bootstrapDirs) {
|
|
3465
|
-
const dataDir =
|
|
3779
|
+
const dataDir = join35(servicesDir, bootstrapDirName, "src", "data");
|
|
3466
3780
|
let dataDirEntries;
|
|
3467
3781
|
try {
|
|
3468
|
-
dataDirEntries = await
|
|
3782
|
+
dataDirEntries = await readdir7(dataDir);
|
|
3469
3783
|
} catch {
|
|
3470
3784
|
continue;
|
|
3471
3785
|
}
|
|
3472
3786
|
for (const subDir of dataDirEntries) {
|
|
3473
3787
|
if (subDir === "platform") continue;
|
|
3474
|
-
const appJsonPath =
|
|
3788
|
+
const appJsonPath = join35(dataDir, subDir, "application.json");
|
|
3475
3789
|
try {
|
|
3476
|
-
const content = await
|
|
3790
|
+
const content = await readFile14(appJsonPath, "utf-8");
|
|
3477
3791
|
const appData = JSON.parse(content);
|
|
3478
3792
|
if (!appData.name) continue;
|
|
3479
3793
|
const applicationName = appData.name;
|
|
@@ -3491,7 +3805,7 @@ async function detectFromBootstrap(appDir) {
|
|
|
3491
3805
|
}
|
|
3492
3806
|
async function detectFromPackageJson(appDir) {
|
|
3493
3807
|
try {
|
|
3494
|
-
const content = await
|
|
3808
|
+
const content = await readFile14(join35(appDir, "package.json"), "utf-8");
|
|
3495
3809
|
const pkg = JSON.parse(content);
|
|
3496
3810
|
const rawName = pkg.name ?? "";
|
|
3497
3811
|
const applicationName = rawName.includes("/") ? rawName.split("/")[1] ?? rawName : rawName;
|
|
@@ -3514,24 +3828,24 @@ function inferPlatformNameFromAppDir(appDir, applicationName) {
|
|
|
3514
3828
|
}
|
|
3515
3829
|
|
|
3516
3830
|
// src/commands/standalone/standalone-orchestrator.ts
|
|
3517
|
-
import { join as
|
|
3518
|
-
import { mkdir as mkdir5, rm as rm2, readdir as
|
|
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";
|
|
3519
3833
|
import { constants } from "fs";
|
|
3520
3834
|
import { fetch as undiciFetch4, Agent as Agent4 } from "undici";
|
|
3521
3835
|
var TMP_BASE = "/tmp";
|
|
3522
3836
|
var STANDALONE_PREFIX = "platform-standalone-";
|
|
3523
3837
|
var DETACH_MARKER = ".standalone-running";
|
|
3524
3838
|
function getTmpDir(platformName) {
|
|
3525
|
-
return
|
|
3839
|
+
return join36(TMP_BASE, `${STANDALONE_PREFIX}${platformName}`);
|
|
3526
3840
|
}
|
|
3527
3841
|
async function findRunningSingleton() {
|
|
3528
3842
|
try {
|
|
3529
|
-
const entries = await
|
|
3843
|
+
const entries = await readdir8(TMP_BASE, { withFileTypes: true });
|
|
3530
3844
|
for (const entry of entries) {
|
|
3531
3845
|
if (entry.isDirectory() && entry.name.startsWith(STANDALONE_PREFIX)) {
|
|
3532
|
-
const candidateDir =
|
|
3846
|
+
const candidateDir = join36(TMP_BASE, entry.name);
|
|
3533
3847
|
try {
|
|
3534
|
-
await
|
|
3848
|
+
await access12(join36(candidateDir, DETACH_MARKER));
|
|
3535
3849
|
return candidateDir;
|
|
3536
3850
|
} catch {
|
|
3537
3851
|
}
|
|
@@ -3575,7 +3889,7 @@ async function configureIdpFromConfig(config, localDir, logger) {
|
|
|
3575
3889
|
logger.log("No IDP configured \u2014 run 'platform standalone-config set' to add an IDP. Skipping IDP setup.");
|
|
3576
3890
|
return;
|
|
3577
3891
|
}
|
|
3578
|
-
const envPath =
|
|
3892
|
+
const envPath = join36(localDir, ".env");
|
|
3579
3893
|
let env;
|
|
3580
3894
|
try {
|
|
3581
3895
|
env = await readEnvFile(envPath);
|
|
@@ -3630,7 +3944,7 @@ async function configureAdminUsersFromConfig(config, localDir, logger) {
|
|
|
3630
3944
|
if (!config.adminUsers || config.adminUsers.length === 0) {
|
|
3631
3945
|
return;
|
|
3632
3946
|
}
|
|
3633
|
-
const envPath =
|
|
3947
|
+
const envPath = join36(localDir, ".env");
|
|
3634
3948
|
let env;
|
|
3635
3949
|
try {
|
|
3636
3950
|
env = await readEnvFile(envPath);
|
|
@@ -3673,19 +3987,19 @@ async function configureAdminUsersFromConfig(config, localDir, logger) {
|
|
|
3673
3987
|
}
|
|
3674
3988
|
}
|
|
3675
3989
|
function buildLayout(tmpDir, coreDirName) {
|
|
3676
|
-
const coreDir =
|
|
3990
|
+
const coreDir = join36(tmpDir, coreDirName);
|
|
3677
3991
|
return {
|
|
3678
3992
|
rootDir: tmpDir,
|
|
3679
3993
|
coreDir,
|
|
3680
3994
|
coreDirName,
|
|
3681
|
-
localDir:
|
|
3995
|
+
localDir: join36(coreDir, "local")
|
|
3682
3996
|
};
|
|
3683
3997
|
}
|
|
3684
3998
|
async function generateProxyAppCompose2(appDir, localDir, platformName, applicationName, logger) {
|
|
3685
|
-
const appComposePath =
|
|
3999
|
+
const appComposePath = join36(appDir, "docker-compose.yml");
|
|
3686
4000
|
let content;
|
|
3687
4001
|
try {
|
|
3688
|
-
content = await
|
|
4002
|
+
content = await readFile15(appComposePath, "utf-8");
|
|
3689
4003
|
} catch {
|
|
3690
4004
|
logger.log(`Warning: No docker-compose.yml found at ${appComposePath} \u2014 app services won't start.`);
|
|
3691
4005
|
return;
|
|
@@ -3694,10 +4008,10 @@ async function generateProxyAppCompose2(appDir, localDir, platformName, applicat
|
|
|
3694
4008
|
/^(\s+context:\s+)\.\/(.*)$/gm,
|
|
3695
4009
|
`$1${appDir}/$2`
|
|
3696
4010
|
);
|
|
3697
|
-
const appParentDir =
|
|
4011
|
+
const appParentDir = dirname16(appDir);
|
|
3698
4012
|
const rewritten = withAbsContexts.replace(/\$\{PWD\}/g, appParentDir);
|
|
3699
|
-
const proxyPath =
|
|
3700
|
-
await
|
|
4013
|
+
const proxyPath = join36(localDir, `${platformName}-${applicationName}-docker-compose.yml`);
|
|
4014
|
+
await writeFile14(proxyPath, rewritten, "utf-8");
|
|
3701
4015
|
logger.log(`Generated app compose with absolute build paths.`);
|
|
3702
4016
|
}
|
|
3703
4017
|
function buildComposeManifest(manifest) {
|
|
@@ -3726,7 +4040,7 @@ async function startStandalone(config, appDir, logger, signal, detach = false) {
|
|
|
3726
4040
|
let markerFd;
|
|
3727
4041
|
try {
|
|
3728
4042
|
markerFd = await open(
|
|
3729
|
-
|
|
4043
|
+
join36(tmpDir, DETACH_MARKER),
|
|
3730
4044
|
constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY
|
|
3731
4045
|
);
|
|
3732
4046
|
await markerFd.writeFile(platformName, "utf-8");
|
|
@@ -3764,9 +4078,9 @@ async function startStandalone(config, appDir, logger, signal, detach = false) {
|
|
|
3764
4078
|
}
|
|
3765
4079
|
);
|
|
3766
4080
|
await writeManifest(npmManifest, tmpDir, coreDirName);
|
|
3767
|
-
const bootstrapServiceDir =
|
|
4081
|
+
const bootstrapServiceDir = join36(tmpDir, coreDirName, "services", bootstrapServiceName);
|
|
3768
4082
|
await scaffoldPlatformBootstrap(bootstrapServiceDir, variables, logger);
|
|
3769
|
-
const customizationUiDir =
|
|
4083
|
+
const customizationUiDir = join36(tmpDir, coreDirName, "ui", customizationUiName);
|
|
3770
4084
|
await scaffoldCustomizationUi(customizationUiDir, variables, logger);
|
|
3771
4085
|
await registerCustomizationModule(bootstrapServiceDir, organizationName, logger, platformName);
|
|
3772
4086
|
const layout = buildLayout(tmpDir, coreDirName);
|
|
@@ -3785,7 +4099,7 @@ async function startStandalone(config, appDir, logger, signal, detach = false) {
|
|
|
3785
4099
|
logger.log("Starting containers...");
|
|
3786
4100
|
await startEnvironment(layout, composeManifest, logger, signal);
|
|
3787
4101
|
if (signal?.aborted) return;
|
|
3788
|
-
const env = await readEnvFile(
|
|
4102
|
+
const env = await readEnvFile(join36(layout.localDir, ".env")).catch(() => /* @__PURE__ */ new Map());
|
|
3789
4103
|
const gatewayUrl = env.get("PAE_GATEWAY_HOST_URL") ?? "https://localhost:443";
|
|
3790
4104
|
const gatewayReady = await waitForGateway(gatewayUrl, logger);
|
|
3791
4105
|
if (!gatewayReady) {
|
|
@@ -3835,11 +4149,11 @@ async function stopStandalone(logger) {
|
|
|
3835
4149
|
}
|
|
3836
4150
|
let coreDirName = null;
|
|
3837
4151
|
try {
|
|
3838
|
-
const entries = await
|
|
4152
|
+
const entries = await readdir8(runningDir, { withFileTypes: true });
|
|
3839
4153
|
for (const entry of entries) {
|
|
3840
4154
|
if (entry.isDirectory() && entry.name.endsWith("-core")) {
|
|
3841
4155
|
try {
|
|
3842
|
-
await
|
|
4156
|
+
await access12(join36(runningDir, entry.name, "product.manifest.json"));
|
|
3843
4157
|
coreDirName = entry.name;
|
|
3844
4158
|
break;
|
|
3845
4159
|
} catch {
|
|
@@ -3876,9 +4190,9 @@ async function stopStandalone(logger) {
|
|
|
3876
4190
|
|
|
3877
4191
|
// src/controllers/ui/standalone.ui-controller.ts
|
|
3878
4192
|
function getGitEmail() {
|
|
3879
|
-
return new Promise((
|
|
4193
|
+
return new Promise((resolve11) => {
|
|
3880
4194
|
exec("git config user.email", (err, stdout) => {
|
|
3881
|
-
|
|
4195
|
+
resolve11(err ? "" : stdout.trim());
|
|
3882
4196
|
});
|
|
3883
4197
|
});
|
|
3884
4198
|
}
|
|
@@ -3954,7 +4268,7 @@ async function standaloneUiController(ctx) {
|
|
|
3954
4268
|
const deleteLegacy = await ctx.confirm("Delete the local standalone.json?", true);
|
|
3955
4269
|
if (deleteLegacy) {
|
|
3956
4270
|
try {
|
|
3957
|
-
await rm3(
|
|
4271
|
+
await rm3(join37(appDir, "standalone.json"));
|
|
3958
4272
|
ctx.log("Deleted standalone.json.");
|
|
3959
4273
|
} catch {
|
|
3960
4274
|
ctx.log("Warning: Could not delete standalone.json.");
|
|
@@ -4237,9 +4551,9 @@ async function collectIdpConfig2(ctx, current) {
|
|
|
4237
4551
|
return { provider: providerType, name, issuer, clientId, clientSecret, extras };
|
|
4238
4552
|
}
|
|
4239
4553
|
function getGitEmail2() {
|
|
4240
|
-
return new Promise((
|
|
4554
|
+
return new Promise((resolve11) => {
|
|
4241
4555
|
exec2("git config user.email", (err, stdout) => {
|
|
4242
|
-
|
|
4556
|
+
resolve11(err ? "" : stdout.trim());
|
|
4243
4557
|
});
|
|
4244
4558
|
});
|
|
4245
4559
|
}
|
|
@@ -4278,7 +4592,8 @@ var uiControllers = /* @__PURE__ */ new Map([
|
|
|
4278
4592
|
[STANDALONE_CONFIG_SHOW_COMMAND_NAME, standaloneConfigShowUiController],
|
|
4279
4593
|
[STANDALONE_CONFIG_SET_COMMAND_NAME, standaloneConfigSetUiController],
|
|
4280
4594
|
[STANDALONE_CONFIG_DELETE_COMMAND_NAME, standaloneConfigDeleteUiController],
|
|
4281
|
-
[STANDALONE_CONFIG_LIST_COMMAND_NAME, standaloneConfigListUiController]
|
|
4595
|
+
[STANDALONE_CONFIG_LIST_COMMAND_NAME, standaloneConfigListUiController],
|
|
4596
|
+
[PIPELINES_COMMAND_NAME, pipelinesUiController]
|
|
4282
4597
|
]);
|
|
4283
4598
|
|
|
4284
4599
|
// src/hooks/use-command-runner.ts
|
|
@@ -4312,7 +4627,7 @@ function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
|
|
|
4312
4627
|
}
|
|
4313
4628
|
},
|
|
4314
4629
|
prompt(message, defaultValue) {
|
|
4315
|
-
return new Promise((
|
|
4630
|
+
return new Promise((resolve11, reject) => {
|
|
4316
4631
|
if (controller.signal.aborted) {
|
|
4317
4632
|
reject(new DOMException("Aborted", "AbortError"));
|
|
4318
4633
|
return;
|
|
@@ -4320,7 +4635,7 @@ function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
|
|
|
4320
4635
|
setPromptMessage(message);
|
|
4321
4636
|
setPromptValue(defaultValue ?? "");
|
|
4322
4637
|
setPromptMode({ kind: "text" });
|
|
4323
|
-
promptResolveRef.current =
|
|
4638
|
+
promptResolveRef.current = resolve11;
|
|
4324
4639
|
setState(APP_STATE.PROMPTING);
|
|
4325
4640
|
controller.signal.addEventListener(
|
|
4326
4641
|
"abort",
|
|
@@ -4330,7 +4645,7 @@ function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
|
|
|
4330
4645
|
});
|
|
4331
4646
|
},
|
|
4332
4647
|
select(message, options) {
|
|
4333
|
-
return new Promise((
|
|
4648
|
+
return new Promise((resolve11, reject) => {
|
|
4334
4649
|
if (controller.signal.aborted) {
|
|
4335
4650
|
reject(new DOMException("Aborted", "AbortError"));
|
|
4336
4651
|
return;
|
|
@@ -4338,7 +4653,7 @@ function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
|
|
|
4338
4653
|
setPromptMessage(message);
|
|
4339
4654
|
setPromptMode({ kind: "select", options });
|
|
4340
4655
|
setSelectIndex(0);
|
|
4341
|
-
promptResolveRef.current =
|
|
4656
|
+
promptResolveRef.current = resolve11;
|
|
4342
4657
|
setState(APP_STATE.PROMPTING);
|
|
4343
4658
|
controller.signal.addEventListener(
|
|
4344
4659
|
"abort",
|
|
@@ -4348,7 +4663,7 @@ function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
|
|
|
4348
4663
|
});
|
|
4349
4664
|
},
|
|
4350
4665
|
multiselect(message, options) {
|
|
4351
|
-
return new Promise((
|
|
4666
|
+
return new Promise((resolve11, reject) => {
|
|
4352
4667
|
if (controller.signal.aborted) {
|
|
4353
4668
|
reject(new DOMException("Aborted", "AbortError"));
|
|
4354
4669
|
return;
|
|
@@ -4358,7 +4673,7 @@ function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
|
|
|
4358
4673
|
setSelectIndex(0);
|
|
4359
4674
|
setMultiselectChecked(new Set(options.map((_, i) => i)));
|
|
4360
4675
|
promptResolveRef.current = (value) => {
|
|
4361
|
-
|
|
4676
|
+
resolve11(value ? value.split(",") : []);
|
|
4362
4677
|
};
|
|
4363
4678
|
setState(APP_STATE.PROMPTING);
|
|
4364
4679
|
controller.signal.addEventListener(
|
|
@@ -4369,7 +4684,7 @@ function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
|
|
|
4369
4684
|
});
|
|
4370
4685
|
},
|
|
4371
4686
|
confirm(message, defaultValue) {
|
|
4372
|
-
return new Promise((
|
|
4687
|
+
return new Promise((resolve11, reject) => {
|
|
4373
4688
|
if (controller.signal.aborted) {
|
|
4374
4689
|
reject(new DOMException("Aborted", "AbortError"));
|
|
4375
4690
|
return;
|
|
@@ -4377,7 +4692,7 @@ function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
|
|
|
4377
4692
|
setPromptMessage(message);
|
|
4378
4693
|
setPromptMode({ kind: "confirm" });
|
|
4379
4694
|
setConfirmValue(defaultValue ?? true);
|
|
4380
|
-
promptResolveRef.current = (value) =>
|
|
4695
|
+
promptResolveRef.current = (value) => resolve11(value === "true");
|
|
4381
4696
|
setState(APP_STATE.PROMPTING);
|
|
4382
4697
|
controller.signal.addEventListener(
|
|
4383
4698
|
"abort",
|
|
@@ -4411,11 +4726,11 @@ function useCommandRunner({ appendStaticItem, setState, onCommandComplete }) {
|
|
|
4411
4726
|
);
|
|
4412
4727
|
const handlePromptSubmit = useCallback(
|
|
4413
4728
|
(value) => {
|
|
4414
|
-
const
|
|
4729
|
+
const resolve11 = promptResolveRef.current;
|
|
4415
4730
|
promptResolveRef.current = null;
|
|
4416
4731
|
setPromptMode({ kind: "text" });
|
|
4417
4732
|
setState(APP_STATE.EXECUTING);
|
|
4418
|
-
|
|
4733
|
+
resolve11?.(value);
|
|
4419
4734
|
},
|
|
4420
4735
|
[setState]
|
|
4421
4736
|
);
|
|
@@ -5101,6 +5416,102 @@ async function standaloneConfigListCliController(_args) {
|
|
|
5101
5416
|
}
|
|
5102
5417
|
}
|
|
5103
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
|
+
|
|
5104
5515
|
// src/controllers/cli/registry.ts
|
|
5105
5516
|
var cliControllers = /* @__PURE__ */ new Map([
|
|
5106
5517
|
[CREATE_APPLICATION_COMMAND_NAME, createApplicationCliController],
|
|
@@ -5121,7 +5532,8 @@ var cliControllers = /* @__PURE__ */ new Map([
|
|
|
5121
5532
|
[STANDALONE_CONFIG_SHOW_COMMAND_NAME, standaloneConfigShowCliController],
|
|
5122
5533
|
[STANDALONE_CONFIG_SET_COMMAND_NAME, standaloneConfigSetCliController],
|
|
5123
5534
|
[STANDALONE_CONFIG_DELETE_COMMAND_NAME, standaloneConfigDeleteCliController],
|
|
5124
|
-
[STANDALONE_CONFIG_LIST_COMMAND_NAME, standaloneConfigListCliController]
|
|
5535
|
+
[STANDALONE_CONFIG_LIST_COMMAND_NAME, standaloneConfigListCliController],
|
|
5536
|
+
[PIPELINES_COMMAND_NAME, pipelinesCliController]
|
|
5125
5537
|
]);
|
|
5126
5538
|
|
|
5127
5539
|
// src/utils/parse-args.ts
|
package/package.json
CHANGED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
trigger:
|
|
2
|
+
branches:
|
|
3
|
+
include:
|
|
4
|
+
- develop
|
|
5
|
+
- release/*
|
|
6
|
+
- rc/*
|
|
7
|
+
- hotfix/*
|
|
8
|
+
- feature/*
|
|
9
|
+
paths:
|
|
10
|
+
exclude:
|
|
11
|
+
- '**/*.md'
|
|
12
|
+
- docs/**
|
|
13
|
+
|
|
14
|
+
pr:
|
|
15
|
+
branches:
|
|
16
|
+
include:
|
|
17
|
+
- develop
|
|
18
|
+
paths:
|
|
19
|
+
exclude:
|
|
20
|
+
- '**/*.md'
|
|
21
|
+
- docs/**
|
|
22
|
+
|
|
23
|
+
name: '$(Date:yyyyMMdd).$(Rev:r)'
|
|
24
|
+
|
|
25
|
+
resources:
|
|
26
|
+
repositories:
|
|
27
|
+
- repository: templates
|
|
28
|
+
type: git
|
|
29
|
+
name: {{templatesRepoName}}
|
|
30
|
+
ref: refs/heads/main
|
|
31
|
+
- repository: gitops
|
|
32
|
+
type: git
|
|
33
|
+
name: {{gitopsRepoName}}
|
|
34
|
+
|
|
35
|
+
stages:
|
|
36
|
+
|
|
37
|
+
- template: azure-pipeline-monorepo-feature-build.yml@templates
|
|
38
|
+
parameters:
|
|
39
|
+
nodeVersion: '{{nodeVersion}}'
|
|
40
|
+
npmScopeRegistries: ''
|
|
41
|
+
npmAuthServiceConnections: ''
|
|
42
|
+
|
|
43
|
+
- template: azure-pipeline-monorepo-build.yml@templates
|
|
44
|
+
parameters:
|
|
45
|
+
azureServiceConnection: '{{azureServiceConnection}}'
|
|
46
|
+
acrLoginServer: '{{acrLoginServer}}'
|
|
47
|
+
nodeVersion: '{{nodeVersion}}'
|
|
48
|
+
npmScopeRegistries: ''
|
|
49
|
+
npmAuthServiceConnections: ''
|
|
50
|
+
services:
|
|
51
|
+
# platform-cli:managed-services-start
|
|
52
|
+
{{servicesList}} # platform-cli:managed-services-end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: Build Platform
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
paths:
|
|
5
|
+
- "apps/**"
|
|
6
|
+
- "services/**"
|
|
7
|
+
- "packages/**"
|
|
8
|
+
- "package-lock.json"
|
|
9
|
+
- ".github/workflows/**"
|
|
10
|
+
branches-ignore:
|
|
11
|
+
- "dependabot/**"
|
|
12
|
+
workflow_dispatch:
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
build_platform:
|
|
16
|
+
name: Build, test and publish container images for all monorepo services
|
|
17
|
+
uses: Blue-Alba-Platform/workflows/.github/workflows/monorepo-turbo-npm-build.yml@main
|
|
18
|
+
with:
|
|
19
|
+
code_folder_list: {{codeFolderList}}
|
|
20
|
+
packages_folder: "{{packagesFolderPattern}}"
|
|
21
|
+
full_rebuild: ${{ contains(github.ref , 'refs/heads/develop') || contains(github.ref , 'refs/heads/feature/') }}
|
|
22
|
+
publish: ${{ ! contains(github.ref , 'refs/heads/feature') }}
|
|
23
|
+
state_file: {{stateFile}}
|
|
24
|
+
secrets: inherit
|