underpost 3.2.12 → 3.2.14
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/.github/workflows/ghpkg.ci.yml +1 -0
- package/.github/workflows/npmpkg.ci.yml +9 -5
- package/CHANGELOG.md +58 -1
- package/CLI-HELP.md +906 -1130
- package/README.md +47 -41
- package/bin/build.js +88 -137
- package/bin/build.template.js +23 -179
- package/bin/deploy.js +4 -1
- package/bin/index.js +2 -2
- package/conf.js +11 -37
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +2 -2
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/package.json +9 -14
- package/src/cli/deploy.js +0 -228
- package/src/cli/image.js +58 -4
- package/src/cli/monitor.js +190 -6
- package/src/cli/release.js +5 -5
- package/src/cli/repository.js +80 -3
- package/src/cli/run.js +115 -69
- package/src/index.js +1 -1
- package/src/runtime/wp/Dockerfile +3 -3
- package/src/server/catalog-underpost.js +61 -0
- package/src/server/catalog.js +77 -0
- package/src/server/conf.js +334 -49
- package/src/server/start.js +7 -3
- package/test/deploy-monitor.test.js +188 -0
- package/manifests/deployment/dd-test-development/deployment.yaml +0 -256
- package/manifests/deployment/dd-test-development/proxy.yaml +0 -102
package/src/cli/run.js
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
getNpmRootPath,
|
|
16
16
|
isDeployRunnerContext,
|
|
17
17
|
loadConfServerJson,
|
|
18
|
+
loadReplicas,
|
|
18
19
|
writeEnv,
|
|
19
20
|
} from '../server/conf.js';
|
|
20
21
|
import { actionInitLog, loggerFactory } from '../server/logger.js';
|
|
@@ -422,6 +423,7 @@ class UnderpostRun {
|
|
|
422
423
|
return;
|
|
423
424
|
}
|
|
424
425
|
shellExec(`${baseCommand} run pull`);
|
|
426
|
+
shellExec(`${baseCommand} run shared-dir`);
|
|
425
427
|
|
|
426
428
|
// Capture last N commit messages for propagation.
|
|
427
429
|
// When --from-n-commit is not set, auto-detect unpushed commit count (same as --unpush flag).
|
|
@@ -502,6 +504,7 @@ class UnderpostRun {
|
|
|
502
504
|
return;
|
|
503
505
|
}
|
|
504
506
|
shellExec(`${baseCommand} run pull`);
|
|
507
|
+
shellExec(`${baseCommand} run shared-dir`);
|
|
505
508
|
|
|
506
509
|
// Capture last N commit messages from the engine repo.
|
|
507
510
|
// When --from-n-commit is not set, auto-detect unpushed commit count (same as --unpush flag).
|
|
@@ -713,6 +716,11 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
713
716
|
let targetTraffic = currentTraffic ? (currentTraffic === 'blue' ? 'green' : 'blue') : 'green';
|
|
714
717
|
if (targetTraffic) versions = versions ? versions : targetTraffic;
|
|
715
718
|
|
|
719
|
+
const ignorePods =
|
|
720
|
+
isDeployRunnerContext(path, options) && targetTraffic
|
|
721
|
+
? Underpost.kubectl.get(`${deployId}-${env}-${targetTraffic}`, 'pods', options.namespace).map((p) => p.NAME)
|
|
722
|
+
: [];
|
|
723
|
+
|
|
716
724
|
const timeoutFlags = Underpost.deploy.timeoutFlagsFactory(options);
|
|
717
725
|
const cmdString = options.cmd
|
|
718
726
|
? ' --cmd ' + (options.cmd.find((c) => c.match('"')) ? '"' + options.cmd + '"' : "'" + options.cmd + "'")
|
|
@@ -743,7 +751,7 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
743
751
|
);
|
|
744
752
|
if (!targetTraffic)
|
|
745
753
|
targetTraffic = Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace });
|
|
746
|
-
await Underpost.
|
|
754
|
+
await Underpost.monitor.monitorReadyRunner(deployId, env, targetTraffic, ignorePods, options.namespace);
|
|
747
755
|
Underpost.deploy.switchTraffic(deployId, env, targetTraffic, replicas, options.namespace, options);
|
|
748
756
|
} else
|
|
749
757
|
logger.info('current traffic', Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace }));
|
|
@@ -1154,7 +1162,7 @@ EOF
|
|
|
1154
1162
|
`,
|
|
1155
1163
|
{ disableLog: true },
|
|
1156
1164
|
);
|
|
1157
|
-
const { ready, readyPods } = await Underpost.
|
|
1165
|
+
const { ready, readyPods } = await Underpost.monitor.monitorReadyRunner(
|
|
1158
1166
|
_deployId,
|
|
1159
1167
|
env,
|
|
1160
1168
|
targetTraffic,
|
|
@@ -1436,8 +1444,8 @@ EOF`);
|
|
|
1436
1444
|
const baseClusterCommand = options.dev ? ' --dev' : '';
|
|
1437
1445
|
const currentImage = options.imageName
|
|
1438
1446
|
? options.imageName
|
|
1439
|
-
: Underpost.
|
|
1440
|
-
.
|
|
1447
|
+
: Underpost.image
|
|
1448
|
+
.getCurrentLoaded(options.nodeName ? options.nodeName : 'kind-worker', false)
|
|
1441
1449
|
.find((o) => o.IMAGE.match('underpost'));
|
|
1442
1450
|
const podName = options.podName || `underpost-dev-container`;
|
|
1443
1451
|
const volumeHostPath = options.claimName || '/home/dd';
|
|
@@ -1714,20 +1722,13 @@ EOF`);
|
|
|
1714
1722
|
const currentTraffic = Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace });
|
|
1715
1723
|
const targetTraffic = currentTraffic === 'blue' ? 'green' : 'blue';
|
|
1716
1724
|
const env = options.dev ? 'development' : 'production';
|
|
1717
|
-
const ignorePods = Underpost.
|
|
1725
|
+
const ignorePods = Underpost.kubectl
|
|
1718
1726
|
.get(`${deployId}-${env}-${targetTraffic}`, 'pods', options.namespace)
|
|
1719
1727
|
.map((p) => p.NAME);
|
|
1720
1728
|
|
|
1721
1729
|
shellExec(`sudo kubectl rollout restart deployment/${deployId}-${env}-${targetTraffic} -n ${options.namespace}`);
|
|
1722
1730
|
|
|
1723
|
-
await Underpost.
|
|
1724
|
-
deployId,
|
|
1725
|
-
env,
|
|
1726
|
-
targetTraffic,
|
|
1727
|
-
ignorePods,
|
|
1728
|
-
options.namespace,
|
|
1729
|
-
'underpost',
|
|
1730
|
-
);
|
|
1731
|
+
await Underpost.monitor.monitorReadyRunner(deployId, env, targetTraffic, ignorePods, options.namespace);
|
|
1731
1732
|
|
|
1732
1733
|
Underpost.deploy.switchTraffic(deployId, env, targetTraffic, options.replicas, options.namespace, options);
|
|
1733
1734
|
},
|
|
@@ -1819,7 +1820,7 @@ EOF`);
|
|
|
1819
1820
|
}`;
|
|
1820
1821
|
shellExec(cmd, { async: true });
|
|
1821
1822
|
}
|
|
1822
|
-
await awaitDeployMonitor();
|
|
1823
|
+
if ((await awaitDeployMonitor()) !== true) return;
|
|
1823
1824
|
{
|
|
1824
1825
|
const cmd = `npm run dev:client ${deployId} ${subConf} ${host} ${_path} proxy${options.tls ? ' tls' : ''}`;
|
|
1825
1826
|
|
|
@@ -1827,7 +1828,7 @@ EOF`);
|
|
|
1827
1828
|
async: true,
|
|
1828
1829
|
});
|
|
1829
1830
|
}
|
|
1830
|
-
await awaitDeployMonitor();
|
|
1831
|
+
if ((await awaitDeployMonitor()) !== true) return;
|
|
1831
1832
|
shellExec(
|
|
1832
1833
|
`NODE_ENV=development node src/proxy proxy ${deployId} ${subConf} ${host} ${_path}${options.tls ? ' tls' : ''}`,
|
|
1833
1834
|
);
|
|
@@ -2504,7 +2505,8 @@ EOF`;
|
|
|
2504
2505
|
/**
|
|
2505
2506
|
* @method push-bundle
|
|
2506
2507
|
* @description Builds the client zip for the specified deployment, splits it into parts, and uploads to file storage.
|
|
2507
|
-
* Steps: set env, build+split zip,
|
|
2508
|
+
* Steps: set env, build+split zip, upload only the zip parts belonging to the deploy-id's hosts (from conf.server.json).
|
|
2509
|
+
* Only files matching `<host>-<route>.zip.part*` or `<host>-<route>.zip` for each non-skipped route are uploaded.
|
|
2508
2510
|
* @param {string} path - Optional `fsPath.splitOption` string.
|
|
2509
2511
|
* Examples: `build` (default split 8), `build.16` (split 16 MB), `build.none-split` (no split flag).
|
|
2510
2512
|
* @param {Object} options - The default underpost runner options for customizing workflow.
|
|
@@ -2513,7 +2515,7 @@ EOF`;
|
|
|
2513
2515
|
* @memberof UnderpostRun
|
|
2514
2516
|
*/
|
|
2515
2517
|
'push-bundle': (path = '', options = DEFAULT_OPTION) => {
|
|
2516
|
-
const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
2518
|
+
const baseCommand = 'node bin'; // options.dev ? 'node bin' : 'underpost';
|
|
2517
2519
|
const env = options.dev ? 'development' : 'production';
|
|
2518
2520
|
const deployId = options.deployId || 'dd-default';
|
|
2519
2521
|
const pathParts = (path || '').split('.');
|
|
@@ -2537,11 +2539,54 @@ EOF`;
|
|
|
2537
2539
|
}
|
|
2538
2540
|
}
|
|
2539
2541
|
|
|
2542
|
+
const confServerPath = `./engine-private/conf/${deployId}/conf.server.json`;
|
|
2543
|
+
const confServer = fs.existsSync(confServerPath)
|
|
2544
|
+
? loadReplicas(deployId, loadConfServerJson(confServerPath))
|
|
2545
|
+
: {};
|
|
2546
|
+
const storageFilePath = `engine-private/conf/${deployId}/storage.bundle.json`;
|
|
2547
|
+
|
|
2540
2548
|
shellExec(`${baseCommand} env ${deployId} ${env}`);
|
|
2541
2549
|
shellExec(`${baseCommand} client ${deployId} --build-zip${splitFlag ? ` ${splitFlag}` : ''}`);
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2550
|
+
|
|
2551
|
+
const pushBundleFiles = (host, routePath) => {
|
|
2552
|
+
const buildId = `${host}-${routePath.replaceAll('/', '')}`;
|
|
2553
|
+
const buildDir = `./${fsPath}`;
|
|
2554
|
+
if (!fs.existsSync(buildDir)) return;
|
|
2555
|
+
const partFiles = fs
|
|
2556
|
+
.readdirSync(buildDir)
|
|
2557
|
+
.filter(
|
|
2558
|
+
(name) =>
|
|
2559
|
+
name.startsWith(`${buildId}.zip.part`) ||
|
|
2560
|
+
name.startsWith(`${buildId}.zip-part`) ||
|
|
2561
|
+
name === `${buildId}.zip`,
|
|
2562
|
+
)
|
|
2563
|
+
.map((name) => `${fsPath}/${name}`);
|
|
2564
|
+
if (partFiles.length === 0) {
|
|
2565
|
+
logger.warn(`push-bundle: no bundle files found for '${host}${routePath}'`, { buildId });
|
|
2566
|
+
return;
|
|
2567
|
+
}
|
|
2568
|
+
for (const partFile of partFiles) {
|
|
2569
|
+
shellExec(
|
|
2570
|
+
`${baseCommand} fs ${partFile} --deploy-id ${deployId} --storage-file-path ${storageFilePath} --force`,
|
|
2571
|
+
);
|
|
2572
|
+
}
|
|
2573
|
+
};
|
|
2574
|
+
|
|
2575
|
+
for (const host of Object.keys(confServer)) {
|
|
2576
|
+
for (const routePath of Object.keys(confServer[host])) {
|
|
2577
|
+
const routeConf = confServer[host][routePath] || {};
|
|
2578
|
+
if (routeConf.redirect || routeConf.disabledRebuild) continue;
|
|
2579
|
+
if (routeConf.singleReplica) {
|
|
2580
|
+
if (routeConf.replicas) {
|
|
2581
|
+
for (const replica of routeConf.replicas) {
|
|
2582
|
+
pushBundleFiles(host, replica);
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
continue;
|
|
2586
|
+
}
|
|
2587
|
+
pushBundleFiles(host, routePath);
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2545
2590
|
},
|
|
2546
2591
|
|
|
2547
2592
|
/**
|
|
@@ -2558,11 +2603,13 @@ EOF`;
|
|
|
2558
2603
|
* @memberof UnderpostRun
|
|
2559
2604
|
*/
|
|
2560
2605
|
'pull-bundle': (path = '', options = DEFAULT_OPTION) => {
|
|
2561
|
-
const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
2606
|
+
const baseCommand = 'node bin'; // options.dev ? 'node bin' : 'underpost';
|
|
2562
2607
|
const env = options.dev ? 'development' : 'production';
|
|
2563
2608
|
const deployId = options.deployId || 'dd-default';
|
|
2564
2609
|
const confServerPath = `./engine-private/conf/${deployId}/conf.server.json`;
|
|
2565
|
-
const confServer = fs.existsSync(confServerPath)
|
|
2610
|
+
const confServer = fs.existsSync(confServerPath)
|
|
2611
|
+
? loadReplicas(deployId, loadConfServerJson(confServerPath))
|
|
2612
|
+
: {};
|
|
2566
2613
|
const hostsArg = path
|
|
2567
2614
|
? path
|
|
2568
2615
|
.split(',')
|
|
@@ -2581,60 +2628,59 @@ EOF`;
|
|
|
2581
2628
|
`${baseCommand} fs build --recursive --deploy-id ${deployId} --storage-file-path engine-private/conf/${deployId}/storage.bundle.json --pull --omit-unzip`,
|
|
2582
2629
|
);
|
|
2583
2630
|
|
|
2631
|
+
const pullBundleRoute = (host, routePath) => {
|
|
2632
|
+
const buildId = `${host}-${routePath.replaceAll('/', '')}`;
|
|
2633
|
+
const zipPath = `build/${buildId}.zip`;
|
|
2634
|
+
const buildDir = './build';
|
|
2635
|
+
const hasZip = fs.existsSync(zipPath);
|
|
2636
|
+
const hasParts =
|
|
2637
|
+
fs.existsSync(buildDir) &&
|
|
2638
|
+
fs
|
|
2639
|
+
.readdirSync(buildDir)
|
|
2640
|
+
.some((name) => name.startsWith(`${buildId}.zip.part`) || name.startsWith(`${buildId}.zip-part`));
|
|
2641
|
+
|
|
2642
|
+
if (!hasZip && !hasParts) {
|
|
2643
|
+
logger.warn(`Bundle not found for '${host}${routePath}'. Skipping.`, { zipPath, deployId });
|
|
2644
|
+
return;
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
if (hasParts) shellExec(`${baseCommand} client --merge-zip ${zipPath}`);
|
|
2648
|
+
shellExec(`${baseCommand} client --unzip ${zipPath}`);
|
|
2649
|
+
shellExec(`sudo rm -rf ${zipPath}`);
|
|
2650
|
+
|
|
2651
|
+
if (fs.existsSync(buildDir)) {
|
|
2652
|
+
fs.readdirSync(buildDir)
|
|
2653
|
+
.filter((name) => name.startsWith(`${buildId}.zip.part`) || name.startsWith(`${buildId}.zip-part`))
|
|
2654
|
+
.forEach((partFile) => shellExec(`sudo rm -rf ${buildDir}/${partFile}`));
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
const extractedDir = `build/${buildId.replace(/-$/, '')}`;
|
|
2658
|
+
if (!fs.existsSync(extractedDir)) {
|
|
2659
|
+
logger.warn(`Extracted build dir not found: ${extractedDir}. Skipping move for '${host}${routePath}'.`);
|
|
2660
|
+
return;
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2663
|
+
const publicDestPath = routePath === '/' ? `public/${host}` : `public/${host}${routePath}`;
|
|
2664
|
+
if (fs.existsSync(publicDestPath)) shellExec(`sudo rm -rf ${publicDestPath}`);
|
|
2665
|
+
if (routePath !== '/') shellExec(`sudo mkdir -p public/${host}`);
|
|
2666
|
+
fs.copySync(`${extractedDir}`, `${publicDestPath}`);
|
|
2667
|
+
};
|
|
2668
|
+
|
|
2584
2669
|
for (const host of hostsArg) {
|
|
2585
|
-
// Gather all routes for this host; fall back to root '/' when host is not in confServer
|
|
2586
|
-
// (e.g. when hosts were provided explicitly via the path argument).
|
|
2587
2670
|
const routePaths = confServer[host] ? Object.keys(confServer[host]) : ['/'];
|
|
2588
2671
|
|
|
2589
2672
|
for (const routePath of routePaths) {
|
|
2590
2673
|
const routeConf = confServer[host] ? confServer[host][routePath] || {} : {};
|
|
2591
|
-
|
|
2592
|
-
if (routeConf.singleReplica
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
const zipPath = `build/${buildId}.zip`;
|
|
2599
|
-
const buildDir = './build';
|
|
2600
|
-
const hasZip = fs.existsSync(zipPath);
|
|
2601
|
-
const hasParts =
|
|
2602
|
-
fs.existsSync(buildDir) &&
|
|
2603
|
-
fs
|
|
2604
|
-
.readdirSync(buildDir)
|
|
2605
|
-
.some((name) => name.startsWith(`${buildId}.zip.part`) || name.startsWith(`${buildId}.zip-part`));
|
|
2606
|
-
|
|
2607
|
-
if (!hasZip && !hasParts) {
|
|
2608
|
-
logger.warn(`Bundle not found for '${host}${routePath}'. Skipping.`, { zipPath, deployId });
|
|
2609
|
-
continue;
|
|
2610
|
-
}
|
|
2611
|
-
|
|
2612
|
-
if (hasParts) shellExec(`${baseCommand} client --merge-zip ${zipPath}`);
|
|
2613
|
-
shellExec(`${baseCommand} client --unzip ${zipPath}`);
|
|
2614
|
-
shellExec(`sudo rm -rf ${zipPath}`);
|
|
2615
|
-
|
|
2616
|
-
// Clean up downloaded part wrapper zips left by --omit-unzip pull
|
|
2617
|
-
if (fs.existsSync(buildDir)) {
|
|
2618
|
-
fs.readdirSync(buildDir)
|
|
2619
|
-
.filter((name) => name.startsWith(`${buildId}.zip.part`) || name.startsWith(`${buildId}.zip-part`))
|
|
2620
|
-
.forEach((partFile) => shellExec(`sudo rm -rf ${buildDir}/${partFile}`));
|
|
2621
|
-
}
|
|
2622
|
-
|
|
2623
|
-
// unzipClientBuild extracts to buildId with trailing '-' stripped
|
|
2624
|
-
// e.g. "build/underpost.net-" → "build/underpost.net"
|
|
2625
|
-
// e.g. "build/app.net-admin" → "build/app.net-admin" (no trailing dash, no change)
|
|
2626
|
-
const extractedDir = `build/${buildId.replace(/-$/, '')}`;
|
|
2627
|
-
if (!fs.existsSync(extractedDir)) {
|
|
2628
|
-
logger.warn(`Extracted build dir not found: ${extractedDir}. Skipping move for '${host}${routePath}'.`);
|
|
2674
|
+
if (routeConf.redirect || routeConf.disabledRebuild) continue;
|
|
2675
|
+
if (routeConf.singleReplica) {
|
|
2676
|
+
if (routeConf.replicas) {
|
|
2677
|
+
for (const replica of routeConf.replicas) {
|
|
2678
|
+
pullBundleRoute(host, replica);
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2629
2681
|
continue;
|
|
2630
2682
|
}
|
|
2631
|
-
|
|
2632
|
-
// Destination mirrors the public directory layout used by the server
|
|
2633
|
-
const publicDestPath = routePath === '/' ? `public/${host}` : `public/${host}${routePath}`;
|
|
2634
|
-
if (fs.existsSync(publicDestPath)) shellExec(`sudo rm -rf ${publicDestPath}`);
|
|
2635
|
-
// Ensure parent directory exists for sub-paths
|
|
2636
|
-
if (routePath !== '/') shellExec(`sudo mkdir -p public/${host}`);
|
|
2637
|
-
shellExec(`sudo mv ${extractedDir} ${publicDestPath}`);
|
|
2683
|
+
pullBundleRoute(host, routePath);
|
|
2638
2684
|
}
|
|
2639
2685
|
}
|
|
2640
2686
|
},
|
package/src/index.js
CHANGED
|
@@ -3,7 +3,7 @@ FROM rockylinux:9
|
|
|
3
3
|
# System packages
|
|
4
4
|
RUN dnf -y update && \
|
|
5
5
|
dnf -y install epel-release && \
|
|
6
|
-
dnf -y install --allowerasing \
|
|
6
|
+
dnf -y install --allowerasing --nobest \
|
|
7
7
|
bzip2 \
|
|
8
8
|
sudo \
|
|
9
9
|
curl \
|
|
@@ -20,8 +20,8 @@ RUN dnf -y update && \
|
|
|
20
20
|
perl && \
|
|
21
21
|
dnf clean all
|
|
22
22
|
|
|
23
|
-
# Download and install XAMPP (PHP 8.2)
|
|
24
|
-
RUN curl -L -o /tmp/xampp-linux-installer.run "https://sourceforge.net/projects/xampp/files/XAMPP%20Linux/8.2.12/xampp-linux-x64-8.2.12-0-installer.run" && \
|
|
23
|
+
# Download and install XAMPP (PHP 8.2) with retry logic for flaky SourceForge transfers
|
|
24
|
+
RUN curl -L --retry 5 --retry-delay 10 --retry-max-time 180 -o /tmp/xampp-linux-installer.run "https://sourceforge.net/projects/xampp/files/XAMPP%20Linux/8.2.12/xampp-linux-x64-8.2.12-0-installer.run" && \
|
|
25
25
|
chmod +x /tmp/xampp-linux-installer.run && \
|
|
26
26
|
bash -c "/tmp/xampp-linux-installer.run --mode unattended" && \
|
|
27
27
|
ln -sf /opt/lampp/lampp /usr/bin/lampp
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Underpost platform content catalog — the base `pwa-microservices-template`.
|
|
3
|
+
*
|
|
4
|
+
* @module src/server/catalog-underpost.js
|
|
5
|
+
* @namespace UnderpostCatalog
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Workflow + service files re-added to the template after the engine-only strip.
|
|
10
|
+
* @constant {string[]}
|
|
11
|
+
* @memberof UnderpostCatalog
|
|
12
|
+
*/
|
|
13
|
+
const TEMPLATE_RESTORE_PATHS = [
|
|
14
|
+
`./src/server/catalog-underpost.js`,
|
|
15
|
+
`./.github/workflows/pwa-microservices-template-page.cd.yml`,
|
|
16
|
+
`./.github/workflows/pwa-microservices-template-test.ci.yml`,
|
|
17
|
+
`./.github/workflows/npmpkg.ci.yml`,
|
|
18
|
+
`./.github/workflows/ghpkg.ci.yml`,
|
|
19
|
+
`./.github/workflows/gitlab.ci.yml`,
|
|
20
|
+
`./.github/workflows/publish.ci.yml`,
|
|
21
|
+
`./.github/workflows/release.cd.yml`,
|
|
22
|
+
`./src/client/services/user/guest.service.js`,
|
|
23
|
+
'./src/api/user/guest.service.js',
|
|
24
|
+
'./src/ws/IoInterface.js',
|
|
25
|
+
'./src/ws/IoServer.js',
|
|
26
|
+
'./manifests/deployment/dd-default-development',
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* npm keywords for the standalone Underpost platform / template package.
|
|
31
|
+
* @constant {string[]}
|
|
32
|
+
* @memberof UnderpostCatalog
|
|
33
|
+
*/
|
|
34
|
+
const TEMPLATE_KEYWORDS = [
|
|
35
|
+
'underpost',
|
|
36
|
+
'underpost-platform',
|
|
37
|
+
'cli',
|
|
38
|
+
'toolchain',
|
|
39
|
+
'ci-cd',
|
|
40
|
+
'devops',
|
|
41
|
+
'kubernetes',
|
|
42
|
+
'k3s',
|
|
43
|
+
'kubeadm',
|
|
44
|
+
'lxd',
|
|
45
|
+
'baremetal',
|
|
46
|
+
'container-orchestration',
|
|
47
|
+
'image-management',
|
|
48
|
+
'pwa',
|
|
49
|
+
'workbox',
|
|
50
|
+
'microservices',
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* npm description for the standalone Underpost platform / template package.
|
|
55
|
+
* @constant {string}
|
|
56
|
+
* @memberof UnderpostCatalog
|
|
57
|
+
*/
|
|
58
|
+
const TEMPLATE_DESCRIPTION =
|
|
59
|
+
'Underpost Platform — end-to-end CI/CD and application-delivery toolchain CLI. Covers bare metal, Kubernetes, K3s, kubeadm, LXD, container/image orchestration, secrets, databases, cron jobs, monitoring, SSH, runners, PWA + Workbox delivery, and release orchestration. Extensible via downstream CLIs.';
|
|
60
|
+
|
|
61
|
+
export { TEMPLATE_RESTORE_PATHS, TEMPLATE_KEYWORDS, TEMPLATE_DESCRIPTION };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dynamic product-catalog resolver.
|
|
3
|
+
*
|
|
4
|
+
* Product catalogs (`catalog-<suffix>.js`, e.g. `catalog-cyberia`, `catalog-prototype`)
|
|
5
|
+
* are loaded lazily by deploy id via ES dynamic `import()` so the base build
|
|
6
|
+
* (`bin/build`) and template assembly (`bin/build.template`) never statically
|
|
7
|
+
* depend on any product module. Removing a product catalog simply makes its
|
|
8
|
+
* deploy id resolve to the empty catalog — nothing else breaks.
|
|
9
|
+
*
|
|
10
|
+
* Each product catalog default-exports the uniform shape documented in
|
|
11
|
+
* {@link module:src/server/catalog-cyberia}.
|
|
12
|
+
*
|
|
13
|
+
* @module src/server/catalog.js
|
|
14
|
+
* @namespace Catalog
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'fs-extra';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import * as path from 'node:path';
|
|
20
|
+
|
|
21
|
+
const catalogDir = path.dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
|
|
23
|
+
/** Empty product catalog returned for deploy ids without a dedicated module. */
|
|
24
|
+
const EMPTY_CATALOG = {
|
|
25
|
+
sourceMoves: [],
|
|
26
|
+
privateConfPaths: [],
|
|
27
|
+
templatePaths: [],
|
|
28
|
+
stripPaths: [],
|
|
29
|
+
keywords: [],
|
|
30
|
+
description: '',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Loads a single deploy id's product catalog. The suffix after `dd-` selects the
|
|
35
|
+
* module (`dd-cyberia` → `catalog-cyberia.js`). Returns {@link EMPTY_CATALOG} when
|
|
36
|
+
* the deploy id has no dedicated catalog or the module cannot be loaded.
|
|
37
|
+
*
|
|
38
|
+
* @method loadDeployCatalog
|
|
39
|
+
* @param {string} deployId - A concrete deploy id (e.g. `dd-cyberia`).
|
|
40
|
+
* @returns {Promise<object>} The product catalog (uniform shape).
|
|
41
|
+
* @memberof Catalog
|
|
42
|
+
*/
|
|
43
|
+
const loadDeployCatalog = async (deployId) => {
|
|
44
|
+
const suffix = (deployId ?? '').split('dd-')[1];
|
|
45
|
+
if (!suffix) return EMPTY_CATALOG;
|
|
46
|
+
try {
|
|
47
|
+
const mod = await import(`./catalog-${suffix}.js`);
|
|
48
|
+
return { ...EMPTY_CATALOG, ...(mod.default ?? {}) };
|
|
49
|
+
} catch {
|
|
50
|
+
return EMPTY_CATALOG;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Loads every product catalog present alongside this module (`catalog-*.js`,
|
|
56
|
+
* excluding the base `catalog-underpost` and this resolver). Used to aggregate
|
|
57
|
+
* product `stripPaths` for the base template without naming any product.
|
|
58
|
+
*
|
|
59
|
+
* @method loadProductCatalogs
|
|
60
|
+
* @returns {Promise<object[]>} Loaded product catalogs (uniform shape).
|
|
61
|
+
* @memberof Catalog
|
|
62
|
+
*/
|
|
63
|
+
const loadProductCatalogs = async () => {
|
|
64
|
+
const catalogs = [];
|
|
65
|
+
for (const file of fs.readdirSync(catalogDir)) {
|
|
66
|
+
if (!/^catalog-.+\.js$/.test(file) || file === 'catalog-underpost.js') continue;
|
|
67
|
+
try {
|
|
68
|
+
const mod = await import(`./${file}`);
|
|
69
|
+
if (mod.default) catalogs.push({ ...EMPTY_CATALOG, ...mod.default });
|
|
70
|
+
} catch {
|
|
71
|
+
/* a malformed/removed product catalog must not break the base build */
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return catalogs;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export { loadDeployCatalog, loadProductCatalogs, EMPTY_CATALOG };
|