underpost 3.2.22 → 3.2.30
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 -1
- package/.github/workflows/gitlab.ci.yml +1 -1
- package/.github/workflows/npmpkg.ci.yml +1 -1
- package/.github/workflows/publish.ci.yml +2 -2
- package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -1
- package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
- package/CHANGELOG.md +181 -1
- package/CLI-HELP.md +61 -2
- package/README.md +3 -2
- package/baremetal/commission-workflows.json +1 -0
- package/bin/build.js +1 -0
- package/docker-compose.yml +224 -0
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/package.json +27 -15
- package/scripts/kubeadm-node-setup.sh +317 -0
- package/scripts/rocky-kickstart.sh +877 -185
- package/scripts/rpmfusion-ffmpeg-setup.sh +26 -50
- package/scripts/test-monitor.sh +3 -5
- package/src/cli/baremetal.js +717 -16
- package/src/cli/cluster.js +3 -1
- package/src/cli/deploy.js +118 -9
- package/src/cli/docker-compose.js +591 -0
- package/src/cli/env.js +11 -2
- package/src/cli/fs.js +35 -11
- package/src/cli/index.js +89 -0
- package/src/cli/kickstart.js +142 -20
- package/src/cli/repository.js +153 -11
- package/src/cli/run.js +285 -8
- package/src/cli/secrets.js +7 -2
- package/src/cli/ssh.js +234 -0
- package/src/cli/static.js +2 -2
- package/src/{server → client-builder}/client-build-docs.js +15 -5
- package/src/{server → client-builder}/client-build-live.js +3 -3
- package/src/{server → client-builder}/client-build.js +25 -22
- package/src/{server → client-builder}/client-dev-server.js +3 -3
- package/src/{server → client-builder}/client-icons.js +2 -2
- package/src/{server → client-builder}/ssr.js +5 -5
- package/src/client.build.js +1 -3
- package/src/client.dev.js +1 -1
- package/src/db/mongo/MongoBootstrap.js +12 -12
- package/src/index.js +12 -1
- package/src/mailer/EmailRender.js +1 -1
- package/src/{server → projects/underpost}/catalog-underpost.js +1 -2
- package/src/runtime/express/Express.js +2 -2
- package/src/runtime/nginx/Nginx.js +250 -0
- package/src/server/catalog.js +7 -14
- package/src/server/conf.js +3 -3
- package/src/server/runtime-status.js +18 -1
- package/src/server/start.js +12 -2
- package/src/server.js +6 -2
- package/test/deploy-monitor.test.js +26 -10
- package/typedoc.json +3 -1
- package/src/server/ipfs-client.js +0 -599
- /package/src/client/ssr/{Render.js → RootDocument.js} +0 -0
- /package/src/{server → client-builder}/client-formatted.js +0 -0
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Module for building project documentation (JSDoc, Swagger, Coverage).
|
|
5
|
-
* @module src/
|
|
5
|
+
* @module src/client-builder/client-build-docs.js
|
|
6
6
|
* @namespace clientBuildDocs
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import fs from 'fs-extra';
|
|
10
|
-
import { shellExec } from '
|
|
11
|
-
import { loggerFactory } from '
|
|
10
|
+
import { shellExec } from '../server/process.js';
|
|
11
|
+
import { loggerFactory } from '../server/logger.js';
|
|
12
12
|
import { JSONweb } from './client-formatted.js';
|
|
13
13
|
import { ssrFactory } from './ssr.js';
|
|
14
14
|
|
|
@@ -396,10 +396,20 @@ const buildCoverage = async ({ docs, docsDestination }) => {
|
|
|
396
396
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
397
397
|
if (pkg.scripts && pkg.scripts.coverage) {
|
|
398
398
|
logger.info('generating coverage report', coveragePath);
|
|
399
|
-
|
|
399
|
+
try {
|
|
400
|
+
await shellExec(`cd ${coveragePath} && npm run coverage`, { silent: true });
|
|
401
|
+
} catch (err) {
|
|
402
|
+
logger.warn('coverage generation failed (non-fatal), skipping:', err.message);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
400
405
|
} else if (pkg.scripts && pkg.scripts.test) {
|
|
401
406
|
logger.info('generating coverage via test', coveragePath);
|
|
402
|
-
|
|
407
|
+
try {
|
|
408
|
+
await shellExec(`cd ${coveragePath} && npm test`, { silent: true, silentOnError: true });
|
|
409
|
+
} catch (err) {
|
|
410
|
+
logger.warn('coverage generation failed (non-fatal), skipping:', err.message);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
403
413
|
}
|
|
404
414
|
}
|
|
405
415
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Module for live building client-side code
|
|
3
|
-
* @module src/
|
|
3
|
+
* @module src/client-builder/client-build-live.js
|
|
4
4
|
* @namespace clientLiveBuild
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import fs from 'fs-extra';
|
|
8
|
-
import { Config, loadConf, readConfJson } from '
|
|
9
|
-
import { loggerFactory } from '
|
|
8
|
+
import { Config, loadConf, readConfJson } from '../server/conf.js';
|
|
9
|
+
import { loggerFactory } from '../server/logger.js';
|
|
10
10
|
import { buildClient } from './client-build.js';
|
|
11
11
|
|
|
12
12
|
const logger = loggerFactory(import.meta);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Manages the client-side build process, including full builds and incremental builds.
|
|
3
|
-
* @module
|
|
3
|
+
* @module src/client-builder/client-build.js
|
|
4
4
|
* @namespace clientBuild
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -8,18 +8,18 @@
|
|
|
8
8
|
|
|
9
9
|
import fs from 'fs-extra';
|
|
10
10
|
import { transformClientJs, JSONweb } from './client-formatted.js';
|
|
11
|
-
import { loggerFactory } from '
|
|
11
|
+
import { loggerFactory } from '../server/logger.js';
|
|
12
12
|
import {
|
|
13
13
|
getCapVariableName,
|
|
14
14
|
newInstance,
|
|
15
15
|
orderArrayFromAttrInt,
|
|
16
16
|
uniqueArray,
|
|
17
17
|
} from '../client/components/core/CommonJs.js';
|
|
18
|
-
import { readConfJson } from '
|
|
18
|
+
import { readConfJson } from '../server/conf.js';
|
|
19
19
|
import { minify } from 'html-minifier-terser';
|
|
20
20
|
import AdmZip from 'adm-zip';
|
|
21
21
|
import * as dir from 'path';
|
|
22
|
-
import { shellExec } from '
|
|
22
|
+
import { shellExec } from '../server/process.js';
|
|
23
23
|
import { SitemapStream, streamToPromise } from 'sitemap';
|
|
24
24
|
import { Readable } from 'stream';
|
|
25
25
|
import { buildIcons } from './client-icons.js';
|
|
@@ -751,8 +751,9 @@ const buildClient = async (
|
|
|
751
751
|
)
|
|
752
752
|
)
|
|
753
753
|
for (const view of views) {
|
|
754
|
-
const buildPath = `${
|
|
755
|
-
|
|
754
|
+
const buildPath = `${
|
|
755
|
+
rootClientPath[rootClientPath.length - 1] === '/' ? rootClientPath.slice(0, -1) : rootClientPath
|
|
756
|
+
}${view.path === '/' ? view.path : `${view.path}/`}`;
|
|
756
757
|
|
|
757
758
|
if (!fs.existsSync(buildPath)) fs.mkdirSync(buildPath, { recursive: true });
|
|
758
759
|
|
|
@@ -768,8 +769,9 @@ const buildClient = async (
|
|
|
768
769
|
fs.writeFileSync(`${buildPath}${buildId}.js`, jsSrc, 'utf8');
|
|
769
770
|
const title = metadata.title ? metadata.title : title;
|
|
770
771
|
|
|
771
|
-
const canonicalURL = `https://${host}${path}${
|
|
772
|
-
}
|
|
772
|
+
const canonicalURL = `https://${host}${path}${
|
|
773
|
+
view.path === '/' ? (path === '/' ? '' : '/') : path === '/' ? `${view.path.slice(1)}/` : `${view.path}/`
|
|
774
|
+
}`;
|
|
773
775
|
|
|
774
776
|
let ssrHeadComponents = ``;
|
|
775
777
|
let ssrBodyComponents = ``;
|
|
@@ -903,12 +905,12 @@ const buildClient = async (
|
|
|
903
905
|
`${buildPath}index.html`,
|
|
904
906
|
minifyBuild
|
|
905
907
|
? await minify(htmlSrc, {
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
908
|
+
minifyCSS: true,
|
|
909
|
+
minifyJS: true,
|
|
910
|
+
collapseBooleanAttributes: true,
|
|
911
|
+
collapseInlineTagWhitespace: true,
|
|
912
|
+
collapseWhitespace: true,
|
|
913
|
+
})
|
|
912
914
|
: htmlSrc,
|
|
913
915
|
'utf8',
|
|
914
916
|
);
|
|
@@ -997,8 +999,9 @@ Sitemap: ${sitemapBaseUrl}/sitemap.xml`,
|
|
|
997
999
|
renderApi: { JSONweb },
|
|
998
1000
|
});
|
|
999
1001
|
|
|
1000
|
-
const buildPath = `${
|
|
1001
|
-
|
|
1002
|
+
const buildPath = `${
|
|
1003
|
+
rootClientPath[rootClientPath.length - 1] === '/' ? rootClientPath.slice(0, -1) : rootClientPath
|
|
1004
|
+
}${view.path === '/' ? view.path : `${view.path}/`}`;
|
|
1002
1005
|
|
|
1003
1006
|
const indexUrl = buildIndexUrl(view.path);
|
|
1004
1007
|
if (view.offlineDefault) {
|
|
@@ -1018,12 +1021,12 @@ Sitemap: ${sitemapBaseUrl}/sitemap.xml`,
|
|
|
1018
1021
|
buildHtmlPath,
|
|
1019
1022
|
minifyBuild
|
|
1020
1023
|
? await minify(htmlSrc, {
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1024
|
+
minifyCSS: true,
|
|
1025
|
+
minifyJS: true,
|
|
1026
|
+
collapseBooleanAttributes: true,
|
|
1027
|
+
collapseInlineTagWhitespace: true,
|
|
1028
|
+
collapseWhitespace: true,
|
|
1029
|
+
})
|
|
1027
1030
|
: htmlSrc,
|
|
1028
1031
|
'utf8',
|
|
1029
1032
|
);
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Module for creating a client-side development server
|
|
3
|
-
* @module src/
|
|
3
|
+
* @module src/client-builder/client-dev-server.js
|
|
4
4
|
* @namespace clientDevServer
|
|
5
5
|
*/
|
|
6
6
|
import fs from 'fs-extra';
|
|
7
7
|
import nodemon from 'nodemon';
|
|
8
8
|
import dotenv from 'dotenv';
|
|
9
|
-
import { shellExec } from '
|
|
10
|
-
import { loggerFactory } from '
|
|
9
|
+
import { shellExec } from '../server/process.js';
|
|
10
|
+
import { loggerFactory } from '../server/logger.js';
|
|
11
11
|
import Underpost from '../index.js';
|
|
12
12
|
|
|
13
13
|
const logger = loggerFactory(import.meta);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Module for building client-side icons
|
|
3
|
-
* @module src/
|
|
3
|
+
* @module src/client-builder/client-icons.js
|
|
4
4
|
* @namespace clientIcons
|
|
5
5
|
*/
|
|
6
6
|
import { favicons } from 'favicons';
|
|
7
|
-
import { loggerFactory } from '
|
|
7
|
+
import { loggerFactory } from '../server/logger.js';
|
|
8
8
|
import fs from 'fs-extra';
|
|
9
9
|
import { getCapVariableName } from '../client/components/core/CommonJs.js';
|
|
10
10
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Module for managing server side rendering
|
|
3
|
-
* @module src/
|
|
3
|
+
* @module src/client-builder/ssr.js
|
|
4
4
|
* @namespace ServerSideRendering
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -10,19 +10,19 @@ import vm from 'node:vm';
|
|
|
10
10
|
import Underpost from '../index.js';
|
|
11
11
|
|
|
12
12
|
import { srcFormatted, JSONweb } from './client-formatted.js';
|
|
13
|
-
import { loggerFactory } from '
|
|
14
|
-
import { getRootDirectory } from '
|
|
13
|
+
import { loggerFactory } from '../server/logger.js';
|
|
14
|
+
import { getRootDirectory } from '../server/process.js';
|
|
15
15
|
|
|
16
16
|
const logger = loggerFactory(import.meta);
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Creates a server-side rendering component function from a given file path.
|
|
20
20
|
* It reads the component file, formats it, and executes it in a sandboxed Node.js VM context to extract the component.
|
|
21
|
-
* @param {string} [componentPath='./src/client/ssr/
|
|
21
|
+
* @param {string} [componentPath='./src/client/ssr/RootDocument.js'] - The path to the SSR component file.
|
|
22
22
|
* @returns {Promise<Function>} A promise that resolves to the SSR component function.
|
|
23
23
|
* @memberof ServerSideRendering
|
|
24
24
|
*/
|
|
25
|
-
const ssrFactory = async (componentPath = `./src/client/ssr/
|
|
25
|
+
const ssrFactory = async (componentPath = `./src/client/ssr/RootDocument.js`) => {
|
|
26
26
|
const context = { SrrComponent: () => {}, npm_package_version: Underpost.version };
|
|
27
27
|
vm.createContext(context);
|
|
28
28
|
vm.runInContext(await srcFormatted(fs.readFileSync(componentPath, 'utf8')), context);
|
package/src/client.build.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { loggerFactory } from './server/logger.js';
|
|
7
7
|
import { Config } from './server/conf.js';
|
|
8
8
|
import { ProcessController } from './server/process.js';
|
|
9
|
-
import { clientLiveBuild } from './
|
|
9
|
+
import { clientLiveBuild } from './client-builder/client-build-live.js';
|
|
10
10
|
|
|
11
11
|
await Config.build();
|
|
12
12
|
|
|
@@ -15,5 +15,3 @@ const logger = loggerFactory(import.meta);
|
|
|
15
15
|
await logger.setUpInfo();
|
|
16
16
|
|
|
17
17
|
await clientLiveBuild();
|
|
18
|
-
|
|
19
|
-
// ProcessController.init(logger);
|
package/src/client.dev.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { loggerFactory } from './server/logger.js';
|
|
7
7
|
import { ProcessController } from './server/process.js';
|
|
8
8
|
import { Config, buildClientStaticConf } from './server/conf.js';
|
|
9
|
-
import { createClientDevServer } from './
|
|
9
|
+
import { createClientDevServer } from './client-builder/client-dev-server.js';
|
|
10
10
|
|
|
11
11
|
const logger = loggerFactory(import.meta);
|
|
12
12
|
|
|
@@ -290,19 +290,16 @@ class MongoBootstrap {
|
|
|
290
290
|
* @returns {Promise<number>} Number of pods that failed to become ready (0 = all good).
|
|
291
291
|
*/
|
|
292
292
|
static async waitForPods(namespace, replicaCount) {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
const failed = results.filter((r) => !r.ready).map((r) => `${MONGODB_STATEFULSET_NAME}-${r.index}`);
|
|
302
|
-
if (failed.length > 0) {
|
|
303
|
-
logger.error('MongoDB pods did not become ready', { failed });
|
|
293
|
+
let failedCount = 0;
|
|
294
|
+
for (let i = 0; i < replicaCount; i++) {
|
|
295
|
+
const podName = `${MONGODB_STATEFULSET_NAME}-${i}`;
|
|
296
|
+
const result = shellExec(`kubectl wait --for=condition=Ready pod/${podName} -n ${namespace} --timeout=60s`);
|
|
297
|
+
if (result.code !== 0) {
|
|
298
|
+
logger.error(`Pod ${podName} did not become ready`);
|
|
299
|
+
failedCount++;
|
|
300
|
+
}
|
|
304
301
|
}
|
|
305
|
-
return
|
|
302
|
+
return failedCount;
|
|
306
303
|
}
|
|
307
304
|
|
|
308
305
|
/**
|
|
@@ -620,6 +617,9 @@ class MongoBootstrap {
|
|
|
620
617
|
process.env.DB_PASSWORD ||
|
|
621
618
|
readTrimmedFile('./engine-private/mongodb-password');
|
|
622
619
|
|
|
620
|
+
// Ensure the pod is ready before querying
|
|
621
|
+
shellExec(`kubectl wait --for=condition=Ready pod/${podName} -n ${namespace} --timeout=60s`);
|
|
622
|
+
|
|
623
623
|
const evalExpr = 'rs.status().members.filter(m=>m.stateStr=="PRIMARY").map(m=>m.name)';
|
|
624
624
|
|
|
625
625
|
const cli = disableAuth ? 'mongo' : 'mongosh';
|
package/src/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import UnderpostKickStart from './cli/kickstart.js';
|
|
|
10
10
|
import UnderpostCluster from './cli/cluster.js';
|
|
11
11
|
import UnderpostDB from './cli/db.js';
|
|
12
12
|
import UnderpostDeploy from './cli/deploy.js';
|
|
13
|
+
import UnderpostDockerCompose from './cli/docker-compose.js';
|
|
13
14
|
import UnderpostKubectl from './cli/kubectl.js';
|
|
14
15
|
import UnderpostRootEnv from './cli/env.js';
|
|
15
16
|
import UnderpostFileStorage from './cli/fs.js';
|
|
@@ -44,7 +45,7 @@ class Underpost {
|
|
|
44
45
|
* @type {String}
|
|
45
46
|
* @memberof Underpost
|
|
46
47
|
*/
|
|
47
|
-
static version = 'v3.2.
|
|
48
|
+
static version = 'v3.2.30';
|
|
48
49
|
|
|
49
50
|
/**
|
|
50
51
|
* Required Node.js major version
|
|
@@ -146,6 +147,15 @@ class Underpost {
|
|
|
146
147
|
static get deploy() {
|
|
147
148
|
return UnderpostDeploy.API;
|
|
148
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* Docker Compose pipeline cli API
|
|
152
|
+
* @static
|
|
153
|
+
* @type {UnderpostDockerCompose.API}
|
|
154
|
+
* @memberof Underpost
|
|
155
|
+
*/
|
|
156
|
+
static get dockerCompose() {
|
|
157
|
+
return UnderpostDockerCompose.API;
|
|
158
|
+
}
|
|
149
159
|
/**
|
|
150
160
|
* File Storage cli API
|
|
151
161
|
* @static
|
|
@@ -321,6 +331,7 @@ export {
|
|
|
321
331
|
UnderpostCluster,
|
|
322
332
|
UnderpostDB,
|
|
323
333
|
UnderpostDeploy,
|
|
334
|
+
UnderpostDockerCompose,
|
|
324
335
|
UnderpostKubectl,
|
|
325
336
|
UnderpostRootEnv,
|
|
326
337
|
UnderpostFileStorage,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Underpost platform content catalog — the base `pwa-microservices-template`.
|
|
3
3
|
*
|
|
4
|
-
* @module src/
|
|
4
|
+
* @module src/projects/underpost/catalog-underpost.js
|
|
5
5
|
* @namespace UnderpostCatalog
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
* @memberof UnderpostCatalog
|
|
12
12
|
*/
|
|
13
13
|
const TEMPLATE_RESTORE_PATHS = [
|
|
14
|
-
`./src/server/catalog-underpost.js`,
|
|
15
14
|
`./.github/workflows/pwa-microservices-template-page.cd.yml`,
|
|
16
15
|
`./.github/workflows/pwa-microservices-template-test.ci.yml`,
|
|
17
16
|
`./.github/workflows/npmpkg.ci.yml`,
|
|
@@ -19,8 +19,8 @@ import { DataBaseProviderService } from '../../db/DataBaseProvider.js';
|
|
|
19
19
|
import { createPeerServer } from '../../server/peer.js';
|
|
20
20
|
import { createValkeyConnection } from '../../server/valkey.js';
|
|
21
21
|
import { applySecurity, authMiddlewareFactory } from '../../server/auth.js';
|
|
22
|
-
import { ssrMiddlewareFactory } from '../../
|
|
23
|
-
import { buildSwaggerUiOptions } from '../../
|
|
22
|
+
import { ssrMiddlewareFactory } from '../../client-builder/ssr.js';
|
|
23
|
+
import { buildSwaggerUiOptions } from '../../client-builder/client-build-docs.js';
|
|
24
24
|
|
|
25
25
|
import { shellExec } from '../../server/process.js';
|
|
26
26
|
import { devProxyHostFactory, isDevProxyContext, isTlsDevProxy } from '../../server/conf.js';
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exported singleton instance of the NginxService class.
|
|
3
|
+
* Manages dynamic generation of nginx reverse-proxy router configuration used
|
|
4
|
+
* by the Docker Compose development stack. Mirrors the conventions of
|
|
5
|
+
* {@link module:src/runtime/lampp/Lampp.js LamppService}.
|
|
6
|
+
* @module src/runtime/nginx/Nginx.js
|
|
7
|
+
* @namespace NginxService
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'fs-extra';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import { loggerFactory } from '../../server/logger.js';
|
|
13
|
+
|
|
14
|
+
const logger = loggerFactory(import.meta);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @class NginxService
|
|
18
|
+
* @description Builds nginx `server` blocks (the router) for fronting upstream
|
|
19
|
+
* application services and writes the rendered configuration to disk. The
|
|
20
|
+
* router is accumulated in memory via {@link NginxService#createApp} and
|
|
21
|
+
* flushed with {@link NginxService#writeConf}, keeping config generation
|
|
22
|
+
* decoupled from the filesystem location it is written to.
|
|
23
|
+
* @memberof NginxService
|
|
24
|
+
*/
|
|
25
|
+
class NginxService {
|
|
26
|
+
/**
|
|
27
|
+
* @type {string}
|
|
28
|
+
* @description Accumulated nginx `server { ... }` blocks (the router definition).
|
|
29
|
+
* @memberof NginxService
|
|
30
|
+
*/
|
|
31
|
+
router = '';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @type {Set<string>}
|
|
35
|
+
* @description Upstream blocks keyed by upstream name to avoid duplicates.
|
|
36
|
+
* @memberof NginxService
|
|
37
|
+
*/
|
|
38
|
+
upstreams;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @type {boolean}
|
|
42
|
+
* @description Whether a default_server catch-all block has been emitted.
|
|
43
|
+
* @memberof NginxService
|
|
44
|
+
*/
|
|
45
|
+
hasDefaultServer;
|
|
46
|
+
|
|
47
|
+
constructor() {
|
|
48
|
+
this.reset();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Resets the in-memory router, upstreams, and default-server flag.
|
|
53
|
+
* @method reset
|
|
54
|
+
* @returns {void}
|
|
55
|
+
* @memberof NginxService
|
|
56
|
+
*/
|
|
57
|
+
reset() {
|
|
58
|
+
this.router = '';
|
|
59
|
+
this.upstreams = new Map();
|
|
60
|
+
this.hasDefaultServer = false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Appends a raw render fragment to the router string.
|
|
65
|
+
* @method appendRouter
|
|
66
|
+
* @param {string} render - The configuration fragment to append.
|
|
67
|
+
* @returns {string} The complete, updated router configuration string.
|
|
68
|
+
* @memberof NginxService
|
|
69
|
+
*/
|
|
70
|
+
appendRouter(render) {
|
|
71
|
+
if (!this.router) return (this.router = render);
|
|
72
|
+
return (this.router += render);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Clears the in-memory router configuration.
|
|
77
|
+
* @method removeRouter
|
|
78
|
+
* @returns {void}
|
|
79
|
+
* @memberof NginxService
|
|
80
|
+
*/
|
|
81
|
+
removeRouter() {
|
|
82
|
+
this.reset();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Registers a named upstream pointing at a container service:port.
|
|
87
|
+
* Idempotent: repeated names with the same target are collapsed.
|
|
88
|
+
* @method addUpstream
|
|
89
|
+
* @param {string} name - The upstream identifier.
|
|
90
|
+
* @param {string} service - The Docker service name (resolved via service discovery).
|
|
91
|
+
* @param {number} port - The upstream container port.
|
|
92
|
+
* @returns {string} The upstream name.
|
|
93
|
+
* @memberof NginxService
|
|
94
|
+
*/
|
|
95
|
+
addUpstream(name, service, port) {
|
|
96
|
+
this.upstreams.set(name, `upstream ${name} { server ${service}:${port}; }`);
|
|
97
|
+
return name;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Renders the standard proxy directive block shared by all locations,
|
|
102
|
+
* including websocket upgrade headers and forwarded headers.
|
|
103
|
+
* @method proxyLocation
|
|
104
|
+
* @param {string} location - The location path (e.g. '/', '/peer').
|
|
105
|
+
* @param {string} upstream - The upstream name to proxy to.
|
|
106
|
+
* @returns {string} The rendered `location { ... }` block.
|
|
107
|
+
* @memberof NginxService
|
|
108
|
+
*/
|
|
109
|
+
proxyLocation(location, upstream) {
|
|
110
|
+
return `
|
|
111
|
+
location ${location} {
|
|
112
|
+
proxy_pass http://${upstream};
|
|
113
|
+
proxy_set_header Host $host;
|
|
114
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
115
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
116
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
117
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
118
|
+
proxy_set_header Connection $connection_upgrade;
|
|
119
|
+
proxy_read_timeout 3600s;
|
|
120
|
+
}
|
|
121
|
+
`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Renders the `/healthz` location used by the proxy container healthcheck.
|
|
126
|
+
* @method healthLocation
|
|
127
|
+
* @returns {string} The rendered health-check location block.
|
|
128
|
+
* @memberof NginxService
|
|
129
|
+
*/
|
|
130
|
+
healthLocation() {
|
|
131
|
+
return `
|
|
132
|
+
location = /healthz {
|
|
133
|
+
access_log off;
|
|
134
|
+
return 200 "ok\\n";
|
|
135
|
+
add_header Content-Type text/plain;
|
|
136
|
+
}
|
|
137
|
+
`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Creates an nginx virtual-host (`server`) entry for a host and appends it to
|
|
142
|
+
* the router. Each route maps a URL prefix to an upstream service:port with
|
|
143
|
+
* websocket support, mirroring the Contour HTTPProxy route model.
|
|
144
|
+
*
|
|
145
|
+
* @method createApp
|
|
146
|
+
* @param {object} options - Virtual host options.
|
|
147
|
+
* @param {string} options.host - The `server_name` (e.g. 'default.net').
|
|
148
|
+
* @param {number} [options.listen=80] - The listen port.
|
|
149
|
+
* @param {Array<{location: string, service: string, port: number}>} options.routes
|
|
150
|
+
* - Route table. Longer prefixes should precede '/' for correct matching.
|
|
151
|
+
* @param {boolean} [options.resetRouter] - Clear the router before appending.
|
|
152
|
+
* @returns {string} The complete, updated router configuration string.
|
|
153
|
+
* @memberof NginxService
|
|
154
|
+
*/
|
|
155
|
+
createApp({ host, listen = 80, routes = [], resetRouter = false }) {
|
|
156
|
+
if (resetRouter) this.removeRouter();
|
|
157
|
+
|
|
158
|
+
const safeHost = host.replace(/[^a-zA-Z0-9]/g, '_');
|
|
159
|
+
const locationBlocks = routes
|
|
160
|
+
.map(({ location, service, port }) => {
|
|
161
|
+
const upstreamName = this.addUpstream(`up_${safeHost}_${port}`, service, port);
|
|
162
|
+
return this.proxyLocation(location, upstreamName);
|
|
163
|
+
})
|
|
164
|
+
.join('');
|
|
165
|
+
|
|
166
|
+
this.appendRouter(`
|
|
167
|
+
server {
|
|
168
|
+
listen ${listen};
|
|
169
|
+
server_name ${host};
|
|
170
|
+
${this.healthLocation()}${locationBlocks}}
|
|
171
|
+
`);
|
|
172
|
+
|
|
173
|
+
return this.router;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Emits a `default_server` catch-all that forwards to the given upstream and
|
|
178
|
+
* answers the health probe for unmatched Host headers. Safe to call once.
|
|
179
|
+
*
|
|
180
|
+
* @method createDefaultServer
|
|
181
|
+
* @param {object} options - Default server options.
|
|
182
|
+
* @param {string} options.service - The Docker service name to fall back to.
|
|
183
|
+
* @param {number} options.port - The upstream container port.
|
|
184
|
+
* @param {number} [options.listen=80] - The listen port.
|
|
185
|
+
* @returns {string} The complete, updated router configuration string.
|
|
186
|
+
* @memberof NginxService
|
|
187
|
+
*/
|
|
188
|
+
createDefaultServer({ service, port, listen = 80 }) {
|
|
189
|
+
if (this.hasDefaultServer) return this.router;
|
|
190
|
+
this.hasDefaultServer = true;
|
|
191
|
+
const upstreamName = this.addUpstream('up_default', service, port);
|
|
192
|
+
this.appendRouter(`
|
|
193
|
+
server {
|
|
194
|
+
listen ${listen} default_server;
|
|
195
|
+
server_name _;
|
|
196
|
+
${this.healthLocation()}${this.proxyLocation('/', upstreamName)}}
|
|
197
|
+
`);
|
|
198
|
+
return this.router;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Renders the full nginx config document: the websocket connection map, all
|
|
203
|
+
* upstream blocks, the global `proxy_http_version`, and the accumulated router.
|
|
204
|
+
* @method render
|
|
205
|
+
* @returns {string} The complete nginx configuration file content.
|
|
206
|
+
* @memberof NginxService
|
|
207
|
+
*/
|
|
208
|
+
render() {
|
|
209
|
+
const upstreamBlocks = Array.from(this.upstreams.values()).join('\n');
|
|
210
|
+
return `# Generated by src/runtime/nginx/Nginx.js — do not hand-edit.
|
|
211
|
+
# Reverse proxy derived from manifests/deployment/*/proxy.yaml (Contour HTTPProxy).
|
|
212
|
+
# Upstreams resolve container services via the Docker internal network.
|
|
213
|
+
|
|
214
|
+
map $http_upgrade $connection_upgrade {
|
|
215
|
+
default upgrade;
|
|
216
|
+
'' close;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
${upstreamBlocks}
|
|
220
|
+
|
|
221
|
+
proxy_http_version 1.1;
|
|
222
|
+
${this.router}`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Writes the rendered configuration to the given path, creating parent
|
|
227
|
+
* directories as needed. Idempotent and safe to rerun.
|
|
228
|
+
* @method writeConf
|
|
229
|
+
* @param {string} confPath - Absolute or relative destination path.
|
|
230
|
+
* @returns {string} The path written.
|
|
231
|
+
* @memberof NginxService
|
|
232
|
+
*/
|
|
233
|
+
writeConf(confPath) {
|
|
234
|
+
const target = path.resolve(confPath);
|
|
235
|
+
fs.mkdirpSync(path.dirname(target));
|
|
236
|
+
fs.writeFileSync(target, this.render(), 'utf8');
|
|
237
|
+
logger.info(`nginx config written`, { path: target, upstreams: this.upstreams.size });
|
|
238
|
+
return target;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* @description Exported singleton instance of the NginxService class.
|
|
244
|
+
* @type {NginxService}
|
|
245
|
+
* @memberof NginxService
|
|
246
|
+
*/
|
|
247
|
+
const Nginx = new NginxService();
|
|
248
|
+
|
|
249
|
+
export { Nginx, NginxService };
|
|
250
|
+
export default Nginx;
|
package/src/server/catalog.js
CHANGED
|
@@ -18,8 +18,6 @@ import fs from 'fs-extra';
|
|
|
18
18
|
import { fileURLToPath } from 'node:url';
|
|
19
19
|
import * as path from 'node:path';
|
|
20
20
|
|
|
21
|
-
const catalogDir = path.dirname(fileURLToPath(import.meta.url));
|
|
22
|
-
|
|
23
21
|
/** Empty product catalog returned for deploy ids without a dedicated module. */
|
|
24
22
|
const EMPTY_CATALOG = {
|
|
25
23
|
sourceMoves: [],
|
|
@@ -43,12 +41,11 @@ const EMPTY_CATALOG = {
|
|
|
43
41
|
const loadDeployCatalog = async (deployId) => {
|
|
44
42
|
const suffix = (deployId ?? '').split('dd-')[1];
|
|
45
43
|
if (!suffix) return EMPTY_CATALOG;
|
|
46
|
-
|
|
47
|
-
const mod = await import(
|
|
44
|
+
if (fs.existsSync(`./src/projects/${suffix}/catalog-${suffix}.js`)) {
|
|
45
|
+
const mod = await import(`../projects/${suffix}/catalog-${suffix}.js`);
|
|
48
46
|
return { ...EMPTY_CATALOG, ...(mod.default ?? {}) };
|
|
49
|
-
} catch {
|
|
50
|
-
return EMPTY_CATALOG;
|
|
51
47
|
}
|
|
48
|
+
return EMPTY_CATALOG;
|
|
52
49
|
};
|
|
53
50
|
|
|
54
51
|
/**
|
|
@@ -62,14 +59,10 @@ const loadDeployCatalog = async (deployId) => {
|
|
|
62
59
|
*/
|
|
63
60
|
const loadProductCatalogs = async () => {
|
|
64
61
|
const catalogs = [];
|
|
65
|
-
for (const file of fs.
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
}
|
|
62
|
+
for (const file of await fs.readdir('./src/projects')) {
|
|
63
|
+
if (file === 'underpost') continue;
|
|
64
|
+
const mod = await import(`../projects/${file}/catalog-${file}.js`);
|
|
65
|
+
if (mod.default) catalogs.push({ ...EMPTY_CATALOG, ...mod.default });
|
|
73
66
|
}
|
|
74
67
|
return catalogs;
|
|
75
68
|
};
|