@underpostnet/cyberia 3.3.77 → 3.4.0
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/.env.example +1 -0
- package/.github/workflows/coverall.cyberia.ci.yml +12 -3
- package/.github/workflows/cyberia-client.cd.yml +2 -1
- package/.github/workflows/cyberia-server.cd.yml +2 -1
- package/.github/workflows/docker-image.cyberia-client.ci.yml +1 -1
- package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +1 -1
- package/.github/workflows/docker-image.cyberia-server.ci.yml +1 -1
- package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +1 -1
- package/.github/workflows/docker-image.engine-cyberia.ci.yml +1 -1
- package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +1 -1
- package/.github/workflows/engine-cyberia.ci.yml +6 -0
- package/.github/workflows/ghpkg.ci.yml +19 -2
- package/.github/workflows/gitlab.ci.yml +8 -6
- package/.github/workflows/hardhat.ci.yml +4 -1
- package/.github/workflows/npmpkg.ci.yml +9 -5
- package/.github/workflows/publish.ci.yml +21 -12
- package/.github/workflows/publish.cyberia.ci.yml +25 -14
- package/.github/workflows/pwa-microservices-template-page.cd.yml +4 -2
- package/CHANGELOG.md +75 -140
- package/CLI-HELP.md +28 -1
- package/Dockerfile +1 -1
- package/Dockerfile.dev +1 -1
- package/Dockerfile.test +1 -1
- package/README.md +1 -1
- package/bin/build.js +2 -4
- package/bin/cyberia.js +4 -4
- package/bin/index.js +4 -4
- package/compose.env +1 -0
- package/conf.js +1 -1042
- package/deploy/cyberia-client/deploy.sh +13 -11
- package/deploy/cyberia-server/deploy.sh +13 -11
- package/deploy/dd-cyberia/sync-deploy.sh +7 -28
- package/deploy/lib/config.sh +1 -1
- package/deploy/lib/github-actions-logging.sh +3 -2
- package/deploy/pwa-microservices-template/deploy.sh +7 -1
- package/deployment.yaml +1 -1
- package/docker-compose.yml +7 -7
- package/docs/coverage/cyberia/api/cyberia-instance/cyberia-fallback-capture.js.html +1 -1
- package/docs/coverage/cyberia/api/cyberia-instance/index.html +1 -1
- package/docs/coverage/cyberia/api/cyberia-server-defaults/cyberia-server-defaults.js.html +1 -1
- package/docs/coverage/cyberia/api/cyberia-server-defaults/index.html +1 -1
- package/docs/coverage/cyberia/api/object-layer/index.html +1 -1
- package/docs/coverage/cyberia/api/object-layer/object-layer.model.js.html +1 -1
- package/docs/coverage/cyberia/index.html +1 -1
- package/docs/coverage/cyberia/projects/cyberia/atlas-sprite-sheet-generator.js.html +1 -1
- package/docs/coverage/cyberia/projects/cyberia/index.html +1 -1
- package/docs/coverage/cyberia/projects/cyberia/instance-backup.js.html +1 -1
- package/docs/coverage/cyberia/projects/cyberia/instance-data.js.html +1 -1
- package/docs/coverage/cyberia/projects/cyberia/shape-generator.js.html +1 -1
- package/docs/coverage/cyberia/projects/cyberia/stat-balance.js.html +1 -1
- package/hardhat/package-lock.json +6 -6
- package/hardhat/package.json +2 -2
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +1 -1
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +1 -1
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/package.json +19 -17
- package/scripts/test-monitor.sh +1 -1
- package/src/api/file/file.router.js +7 -1
- package/src/cli/baremetal.js +1 -1
- package/src/cli/docker-compose.js +1 -1
- package/src/cli/index.js +36 -0
- package/src/cli/kubectl.js +4 -3
- package/src/cli/release.js +2 -0
- package/src/cli/repository.js +7 -2
- package/src/cli/run.js +1 -1
- package/src/cli/secrets.js +393 -200
- package/src/cli/socketsecurity.js +260 -0
- package/src/client/components/core/CssCore.js +3 -0
- package/src/client/components/core/Modal.js +160 -36
- package/src/client/components/core/SearchBox.js +0 -1
- package/src/client/components/underpost/AppShellUnderpost.js +13 -1
- package/src/client/components/underpost/CssUnderpost.js +15 -2
- package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +8 -0
- package/src/client/public/cyberia-docs/CYBERIA.md +1 -1
- package/src/client/public/cyberia-docs/OFF-CHAIN-ECONOMY.md +54 -56
- package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
- package/src/client/public/cyberia-docs/UNDERPOST-PLATFORM.md +30 -29
- package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
- package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +54 -25
- package/src/client/ssr/head/Css.js +12 -10
- package/src/client/ssr/views/Cyberia404.js +6 -10
- package/src/client/ssr/views/NotFound.js +75 -0
- package/src/client-builder/client-bundle.js +2 -2
- package/src/index.js +13 -1
- package/src/runtime/engine-cyberia/Dockerfile +1 -1
- package/src/runtime/engine-cyberia/Dockerfile.dev +1 -1
- package/src/runtime/engine-cyberia/Dockerfile.test +1 -1
- package/src/runtime/engine-cyberia/compose.env +1 -0
- package/src/runtime/engine-cyberia/docker-compose.yml +7 -7
- package/src/server/build/catalog.js +1 -0
- package/src/server/build/execution.js +19 -5
- package/src/server/build/package.js +166 -3
- package/src/server/build/testing.js +2 -1
- package/src/server/runtime/conf.js +21 -0
- package/src/server/runtime/config-scope.js +2 -0
- package/src/server/runtime/process.js +4 -3
- package/src/server/security/socketsecurity.js +1735 -0
- package/test/integration/infra/1-security/config-scope.test.js +3 -0
- package/test/integration/infra/1-security/socketsecurity.test.js +1041 -0
- package/test/integration/infra/1-security/sops-secret-store.test.js +201 -71
- package/test/unit/build-template.test.js +24 -0
- package/test/unit/deploy-log-table.test.js +38 -0
- package/test/unit/execution-profiles.test.js +1 -0
- package/test/unit/package.test.js +118 -0
package/src/index.js
CHANGED
|
@@ -25,6 +25,7 @@ import UnderpostPackage from './cli/package.js';
|
|
|
25
25
|
import UnderpostRepository from './cli/repository.js';
|
|
26
26
|
import UnderpostRun from './cli/run.js';
|
|
27
27
|
import UnderpostSecret from './cli/secrets.js';
|
|
28
|
+
import UnderpostSocketSecurity from './cli/socketsecurity.js';
|
|
28
29
|
import UnderpostSSH from './cli/ssh.js';
|
|
29
30
|
import UnderpostState from './cli/state.js';
|
|
30
31
|
import UnderpostStatic from './cli/static.js';
|
|
@@ -52,7 +53,7 @@ class Underpost {
|
|
|
52
53
|
* @type {String}
|
|
53
54
|
* @memberof Underpost
|
|
54
55
|
*/
|
|
55
|
-
static version = 'v3.
|
|
56
|
+
static version = 'v3.4.0';
|
|
56
57
|
|
|
57
58
|
/**
|
|
58
59
|
* Required Node.js major version
|
|
@@ -384,6 +385,16 @@ class Underpost {
|
|
|
384
385
|
static get package() {
|
|
385
386
|
return UnderpostPackage.API;
|
|
386
387
|
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Socket supply-chain security cli API
|
|
391
|
+
* @static
|
|
392
|
+
* @type {UnderpostSocketSecurity.API}
|
|
393
|
+
* @memberof Underpost
|
|
394
|
+
*/
|
|
395
|
+
static get socketSecurity() {
|
|
396
|
+
return UnderpostSocketSecurity.API;
|
|
397
|
+
}
|
|
387
398
|
}
|
|
388
399
|
|
|
389
400
|
if (!process.version || !process.version.startsWith(`${Underpost.majorNodejsVersion}.`))
|
|
@@ -422,6 +433,7 @@ export {
|
|
|
422
433
|
UnderpostRepository,
|
|
423
434
|
UnderpostRun,
|
|
424
435
|
UnderpostSecret,
|
|
436
|
+
UnderpostSocketSecurity,
|
|
425
437
|
UnderpostSSH,
|
|
426
438
|
UnderpostState,
|
|
427
439
|
UnderpostDns,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# Stage 1 — builder: clone the private deploy, build it, then scrub secrets.
|
|
7
7
|
# ---------------------------------------------------------------------------
|
|
8
8
|
FROM rockylinux/rockylinux:9 AS builder
|
|
9
|
-
ARG UNDERPOST_VERSION=3.
|
|
9
|
+
ARG UNDERPOST_VERSION=3.4.0
|
|
10
10
|
# Pin Node to an exact patch: dnf's nodejs:24 module lags (24.14.1) while
|
|
11
11
|
# underpost's dependencies require >=24.15.0, so install the official binary.
|
|
12
12
|
ARG NODE_VERSION=24.15.0
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# Stage 1 — builder: clone the private deploy, build it, then scrub secrets.
|
|
7
7
|
# ---------------------------------------------------------------------------
|
|
8
8
|
FROM rockylinux/rockylinux:9 AS builder
|
|
9
|
-
ARG UNDERPOST_VERSION=3.
|
|
9
|
+
ARG UNDERPOST_VERSION=3.4.0
|
|
10
10
|
# Pin Node to an exact patch: dnf's nodejs:24 module lags (24.14.1) while
|
|
11
11
|
# underpost's dependencies require >=24.15.0, so install the official binary.
|
|
12
12
|
ARG NODE_VERSION=24.15.0
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# Stage 1 — builder: clone the private deploy, build it, then scrub secrets.
|
|
7
7
|
# ---------------------------------------------------------------------------
|
|
8
8
|
FROM rockylinux/rockylinux:9 AS builder
|
|
9
|
-
ARG UNDERPOST_VERSION=3.
|
|
9
|
+
ARG UNDERPOST_VERSION=3.4.0
|
|
10
10
|
# Pin Node to an exact patch: dnf's nodejs:24 module lags (24.14.1) while
|
|
11
11
|
# underpost's dependencies require >=24.15.0, so install the official binary.
|
|
12
12
|
ARG NODE_VERSION=24.15.0
|
|
@@ -24,6 +24,7 @@ IPFS_CLUSTER_IMAGE=ipfs/ipfs-cluster:latest
|
|
|
24
24
|
# The engine-cyberia image ships only the underpost CLI; at start it runs the
|
|
25
25
|
# `underpost start --build` bootstrap, which clones ${GITHUB_USERNAME}/engine-cyberia.
|
|
26
26
|
GITHUB_USERNAME=underpostnet
|
|
27
|
+
GITHUB_ORG_NAME=underpost
|
|
27
28
|
GITHUB_TOKEN=change_me_private_repo_token
|
|
28
29
|
|
|
29
30
|
# --- MongoDB (translation of Secret mongodb-secret) --------------------------
|
|
@@ -135,7 +135,7 @@ services:
|
|
|
135
135
|
# server starts but rejects connections until seeded). Serves REST/WS on 4005
|
|
136
136
|
# and the engine gRPC data service on 50051.
|
|
137
137
|
engine-cyberia-runtime:
|
|
138
|
-
image: ${ENGINE_CYBERIA_IMAGE:-underpost/engine-cyberia-dev}:${ENGINE_CYBERIA_TAG:-v3.
|
|
138
|
+
image: ${ENGINE_CYBERIA_IMAGE:-underpost/engine-cyberia-dev}:${ENGINE_CYBERIA_TAG:-v3.4.0}
|
|
139
139
|
container_name: dd-cyberia-engine
|
|
140
140
|
command:
|
|
141
141
|
- /bin/sh
|
|
@@ -190,7 +190,7 @@ services:
|
|
|
190
190
|
# gateway on host port 8081, so one port can route every instance path.
|
|
191
191
|
|
|
192
192
|
cyberia-server-runtime:
|
|
193
|
-
image: ${CYBERIA_SERVER_IMAGE:-underpost/cyberia-server-dev}:${CYBERIA_SERVER_TAG:-v3.
|
|
193
|
+
image: ${CYBERIA_SERVER_IMAGE:-underpost/cyberia-server-dev}:${CYBERIA_SERVER_TAG:-v3.4.0}
|
|
194
194
|
container_name: dd-cyberia-server
|
|
195
195
|
environment:
|
|
196
196
|
INSTANCE_CODE: amethyst-strata-expansion
|
|
@@ -228,7 +228,7 @@ services:
|
|
|
228
228
|
restart: unless-stopped
|
|
229
229
|
|
|
230
230
|
cyberia-server-forest:
|
|
231
|
-
image: ${CYBERIA_SERVER_IMAGE:-underpost/cyberia-server-dev}:${CYBERIA_SERVER_TAG:-v3.
|
|
231
|
+
image: ${CYBERIA_SERVER_IMAGE:-underpost/cyberia-server-dev}:${CYBERIA_SERVER_TAG:-v3.4.0}
|
|
232
232
|
container_name: dd-cyberia-server-forest
|
|
233
233
|
environment:
|
|
234
234
|
INSTANCE_CODE: FOREST
|
|
@@ -266,7 +266,7 @@ services:
|
|
|
266
266
|
restart: unless-stopped
|
|
267
267
|
|
|
268
268
|
cyberia-server-test:
|
|
269
|
-
image: ${CYBERIA_SERVER_IMAGE:-underpost/cyberia-server-dev}:${CYBERIA_SERVER_TAG:-v3.
|
|
269
|
+
image: ${CYBERIA_SERVER_IMAGE:-underpost/cyberia-server-dev}:${CYBERIA_SERVER_TAG:-v3.4.0}
|
|
270
270
|
container_name: dd-cyberia-server-test
|
|
271
271
|
environment:
|
|
272
272
|
INSTANCE_CODE: TEST
|
|
@@ -315,7 +315,7 @@ services:
|
|
|
315
315
|
# cyberia-server-test -> cyberia-client-test
|
|
316
316
|
|
|
317
317
|
cyberia-client-runtime:
|
|
318
|
-
image: ${CYBERIA_CLIENT_IMAGE:-underpost/cyberia-client-dev}:${CYBERIA_CLIENT_TAG:-v3.
|
|
318
|
+
image: ${CYBERIA_CLIENT_IMAGE:-underpost/cyberia-client-dev}:${CYBERIA_CLIENT_TAG:-v3.4.0}
|
|
319
319
|
container_name: dd-cyberia-client
|
|
320
320
|
environment:
|
|
321
321
|
CYBERIA_PORT: '8081'
|
|
@@ -340,7 +340,7 @@ services:
|
|
|
340
340
|
restart: unless-stopped
|
|
341
341
|
|
|
342
342
|
cyberia-client-forest:
|
|
343
|
-
image: ${CYBERIA_CLIENT_IMAGE:-underpost/cyberia-client-dev}:${CYBERIA_CLIENT_TAG:-v3.
|
|
343
|
+
image: ${CYBERIA_CLIENT_IMAGE:-underpost/cyberia-client-dev}:${CYBERIA_CLIENT_TAG:-v3.4.0}
|
|
344
344
|
container_name: dd-cyberia-client-forest
|
|
345
345
|
environment:
|
|
346
346
|
CYBERIA_PORT: '8081'
|
|
@@ -365,7 +365,7 @@ services:
|
|
|
365
365
|
restart: unless-stopped
|
|
366
366
|
|
|
367
367
|
cyberia-client-test:
|
|
368
|
-
image: ${CYBERIA_CLIENT_IMAGE:-underpost/cyberia-client-dev}:${CYBERIA_CLIENT_TAG:-v3.
|
|
368
|
+
image: ${CYBERIA_CLIENT_IMAGE:-underpost/cyberia-client-dev}:${CYBERIA_CLIENT_TAG:-v3.4.0}
|
|
369
369
|
container_name: dd-cyberia-client-test
|
|
370
370
|
environment:
|
|
371
371
|
CYBERIA_PORT: '8081'
|
|
@@ -71,6 +71,7 @@ const loadDeployCatalog = async (deployId) => {
|
|
|
71
71
|
*/
|
|
72
72
|
const loadProductCatalogs = async () => {
|
|
73
73
|
const catalogs = [];
|
|
74
|
+
if (!fs.existsSync('./src/projects')) return catalogs;
|
|
74
75
|
for (const file of await fs.readdir('./src/projects')) {
|
|
75
76
|
if (file === 'underpost') continue;
|
|
76
77
|
const mod = await import(`../../projects/${file}/catalog-${file}.js`);
|
|
@@ -154,10 +154,23 @@ const HOST_WRITE_BINARIES = new Set([
|
|
|
154
154
|
'haproxy',
|
|
155
155
|
]);
|
|
156
156
|
|
|
157
|
-
// `gh`
|
|
158
|
-
//
|
|
159
|
-
// to `fs` and run under HERMETIC_BUILD.
|
|
160
|
-
const NET_BINARIES = new Set([
|
|
157
|
+
// `gh` and `socket` reach a remote service and write to it (`gh secret set`, `socket scan create`),
|
|
158
|
+
// so they are a network effect rather than local filesystem work — an unclassified binary would
|
|
159
|
+
// default to `fs` and run under HERMETIC_BUILD.
|
|
160
|
+
const NET_BINARIES = new Set([
|
|
161
|
+
'curl',
|
|
162
|
+
'wget',
|
|
163
|
+
'ssh',
|
|
164
|
+
'scp',
|
|
165
|
+
'sftp',
|
|
166
|
+
'rsync',
|
|
167
|
+
'nc',
|
|
168
|
+
'ping',
|
|
169
|
+
'dig',
|
|
170
|
+
'nslookup',
|
|
171
|
+
'gh',
|
|
172
|
+
'socket',
|
|
173
|
+
]);
|
|
161
174
|
|
|
162
175
|
const CLUSTER_BINARIES = new Set(['kubectl', 'helm']);
|
|
163
176
|
|
|
@@ -200,7 +213,8 @@ const classifySegment = (segment) => {
|
|
|
200
213
|
// Leading `VAR=value` assignments are not the command.
|
|
201
214
|
let start = 0;
|
|
202
215
|
while (start < bare.length && /^[A-Z_][A-Z0-9_]*=/i.test(bare[start])) start++;
|
|
203
|
-
|
|
216
|
+
// A quoted binary path (`'/x/.bin/socket' scan`) names the same binary as the bare one.
|
|
217
|
+
const binary = nodePath.basename(bare[start] ?? '').replace(/^['"]|['"]$/g, '');
|
|
204
218
|
const rest = bare.slice(start);
|
|
205
219
|
|
|
206
220
|
if (CLUSTER_BINARIES.has(binary)) {
|
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import fs from 'fs-extra';
|
|
9
|
+
import { builtinModules } from 'node:module';
|
|
9
10
|
import os from 'node:os';
|
|
10
11
|
import nodePath from 'node:path';
|
|
11
12
|
import { fileURLToPath } from 'node:url';
|
|
12
13
|
import { loggerFactory } from '../ops/logger.js';
|
|
13
14
|
import { shellArgumentFactory, shellExec } from '../runtime/process.js';
|
|
14
|
-
import { loadDeployCatalog } from './catalog.js';
|
|
15
|
+
import { loadDeployCatalog, loadProductCatalogs } from './catalog.js';
|
|
15
16
|
|
|
16
17
|
const logger = loggerFactory(import.meta);
|
|
17
18
|
const ENGINE_PACKAGE_PATH = fileURLToPath(new URL('../../../', import.meta.url));
|
|
@@ -19,6 +20,31 @@ const STAGED_CLI_PACKAGE = 'underpost-cli.tgz';
|
|
|
19
20
|
const DEPLOY_CONF_ROOT = './engine-private/conf';
|
|
20
21
|
const DEPLOY_MANIFEST_INDENT = 4;
|
|
21
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Modules a production install executes: the CLI, the servers, the client build, and the
|
|
25
|
+
* browser bundles the build compiles. What they import statically has to resolve from
|
|
26
|
+
* `dependencies`, because the runtime images install the package with `--omit=dev`.
|
|
27
|
+
* @constant {string[]}
|
|
28
|
+
* @memberof PackageBuilder
|
|
29
|
+
*/
|
|
30
|
+
const RUNTIME_ENTRY_POINTS = [
|
|
31
|
+
'bin/index.js',
|
|
32
|
+
'src/server.js',
|
|
33
|
+
'src/api.js',
|
|
34
|
+
'src/proxy.js',
|
|
35
|
+
'src/client.build.js',
|
|
36
|
+
'src/client/*.index.js',
|
|
37
|
+
'src/client/sw/*.js',
|
|
38
|
+
// Loaded by name from a deploy's configuration, so no static import reaches them.
|
|
39
|
+
'src/api/*/*.js',
|
|
40
|
+
'src/ws/*/*.js',
|
|
41
|
+
'src/grpc/*/*.js',
|
|
42
|
+
'src/projects/*/*.js',
|
|
43
|
+
];
|
|
44
|
+
const BUILTINS = new Set(builtinModules);
|
|
45
|
+
// A dynamic import whose argument is not a string literal; esbuild would expand it into a glob.
|
|
46
|
+
const NON_LITERAL_IMPORT = /\bimport\((?!\s*(['"])[^'"\n]*\1\s*\))/g;
|
|
47
|
+
|
|
22
48
|
/**
|
|
23
49
|
* Packs an npm package into a build context under a stable file name.
|
|
24
50
|
*
|
|
@@ -195,17 +221,23 @@ const buildProductPackageJson = ({
|
|
|
195
221
|
* @param {object} params.packageJson - The repository's manifest.
|
|
196
222
|
* @param {object} [params.catalog] - That product's catalog.
|
|
197
223
|
* @param {string} [params.underpostVersion] - Engine CLI version to pin; the manifest's own otherwise.
|
|
224
|
+
* @param {string} [params.underpostPackageName] - The engine package the tarball depends on; `underpost` otherwise.
|
|
198
225
|
* @returns {object} The manifest to publish.
|
|
199
226
|
* @memberof PackageBuilder
|
|
200
227
|
*/
|
|
201
|
-
const publishedProductPackageJson = ({
|
|
228
|
+
const publishedProductPackageJson = ({
|
|
229
|
+
packageJson,
|
|
230
|
+
catalog = {},
|
|
231
|
+
underpostVersion = '',
|
|
232
|
+
underpostPackageName = 'underpost',
|
|
233
|
+
} = {}) => {
|
|
202
234
|
if (!packageJson || typeof packageJson !== 'object')
|
|
203
235
|
throw new TypeError('publishedProductPackageJson requires packageJson');
|
|
204
236
|
|
|
205
237
|
const version = `${underpostVersion || packageJson.version || ''}`.replace(/^v/, '');
|
|
206
238
|
if (!version) throw new TypeError('publishedProductPackageJson requires a version to pin the engine CLI at');
|
|
207
239
|
|
|
208
|
-
const dependencies = {
|
|
240
|
+
const dependencies = { [underpostPackageName]: `^${version}`, ...(catalog.packageDependencies ?? {}) };
|
|
209
241
|
const devDependencies = productDevDependenciesFactory({
|
|
210
242
|
engineDependencies: packageJson.dependencies,
|
|
211
243
|
engineDevDependencies: packageJson.devDependencies,
|
|
@@ -485,22 +517,153 @@ const installDeployDependencies = async (deployId, catalog) => {
|
|
|
485
517
|
return specs;
|
|
486
518
|
};
|
|
487
519
|
|
|
520
|
+
/**
|
|
521
|
+
* The package a bare import specifier names; empty for relative, absolute and builtin ones.
|
|
522
|
+
* @param {string} specifier - Import specifier.
|
|
523
|
+
* @returns {string} Package name.
|
|
524
|
+
* @memberof PackageBuilder
|
|
525
|
+
*/
|
|
526
|
+
const importPackageNameFactory = (specifier = '') => {
|
|
527
|
+
if (/^(\.|\/|node:|data:|[a-z]+:\/\/)/.test(specifier)) return '';
|
|
528
|
+
const segments = specifier.split('/');
|
|
529
|
+
const name = specifier.startsWith('@') ? segments.slice(0, 2).join('/') : segments[0];
|
|
530
|
+
return BUILTINS.has(name) ? '' : name;
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Walks the import graph from the runtime entry points and collects the packages it reaches.
|
|
535
|
+
* esbuild parses every module, so an import-shaped line inside a string or a comment is not an
|
|
536
|
+
* import. A dynamic `import('name')` is recorded as lazy: it loads on one code path only, so it
|
|
537
|
+
* does not bind a production install the way a static import does. Non-code assets stay out of
|
|
538
|
+
* the graph, and a non-literal dynamic import is not expanded: esbuild fails on a glob with no
|
|
539
|
+
* match, and the modules such imports load are entry points already.
|
|
540
|
+
* @param {object} [params]
|
|
541
|
+
* @param {string} [params.root] - Package root.
|
|
542
|
+
* @param {string[]} [params.entryPoints] - Entry point paths or glob patterns, relative to root.
|
|
543
|
+
* @returns {Promise<{files: string[], packages: Record<string, string[]>, lazyPackages: Record<string, string[]>}>} Importers by package, relative to root.
|
|
544
|
+
* @memberof PackageBuilder
|
|
545
|
+
*/
|
|
546
|
+
const runtimeImportGraphFactory = async ({ root = ENGINE_PACKAGE_PATH, entryPoints = RUNTIME_ENTRY_POINTS } = {}) => {
|
|
547
|
+
const esbuild = await import('esbuild');
|
|
548
|
+
const { metafile } = await esbuild.build({
|
|
549
|
+
absWorkingDir: nodePath.resolve(root),
|
|
550
|
+
entryPoints: entryPoints.flatMap((pattern) => fs.globSync(pattern, { cwd: root })),
|
|
551
|
+
bundle: true,
|
|
552
|
+
write: false,
|
|
553
|
+
metafile: true,
|
|
554
|
+
logLevel: 'silent',
|
|
555
|
+
platform: 'node',
|
|
556
|
+
format: 'esm',
|
|
557
|
+
packages: 'external',
|
|
558
|
+
outdir: nodePath.join(os.tmpdir(), 'underpost-runtime-graph'),
|
|
559
|
+
plugins: [
|
|
560
|
+
{
|
|
561
|
+
name: 'literal-dynamic-imports',
|
|
562
|
+
setup(build) {
|
|
563
|
+
build.onLoad({ filter: /\.[cm]?js$/ }, (args) => ({
|
|
564
|
+
contents: fs.readFileSync(args.path, 'utf8').replace(NON_LITERAL_IMPORT, 'import(__dynamic__||'),
|
|
565
|
+
loader: 'js',
|
|
566
|
+
}));
|
|
567
|
+
},
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
name: 'code-only',
|
|
571
|
+
setup(build) {
|
|
572
|
+
build.onResolve({ filter: /\.[^./\\]+$/ }, (args) =>
|
|
573
|
+
/\.(?:[cm]?js|json)$/.test(args.path) ? undefined : { path: args.path, external: true },
|
|
574
|
+
);
|
|
575
|
+
},
|
|
576
|
+
},
|
|
577
|
+
],
|
|
578
|
+
});
|
|
579
|
+
const packages = {};
|
|
580
|
+
const lazyPackages = {};
|
|
581
|
+
for (const [file, input] of Object.entries(metafile.inputs))
|
|
582
|
+
for (const { path, kind, external } of input.imports) {
|
|
583
|
+
const name = external ? importPackageNameFactory(path) : '';
|
|
584
|
+
if (!name) continue;
|
|
585
|
+
const importers = ((kind === 'dynamic-import' ? lazyPackages : packages)[name] ??= []);
|
|
586
|
+
if (!importers.includes(file)) importers.push(file);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
for (const name of Object.keys(lazyPackages)) if (packages[name]) delete lazyPackages[name];
|
|
590
|
+
const sorted = (registry) =>
|
|
591
|
+
Object.fromEntries(
|
|
592
|
+
Object.entries(registry)
|
|
593
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
594
|
+
.map(([name, importers]) => [name, importers.sort()]),
|
|
595
|
+
);
|
|
596
|
+
return {
|
|
597
|
+
files: Object.keys(metafile.inputs).sort(),
|
|
598
|
+
packages: sorted(packages),
|
|
599
|
+
lazyPackages: sorted(lazyPackages),
|
|
600
|
+
};
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Checks a manifest against the runtime import graph: every package the graph reaches
|
|
605
|
+
* statically must be declared in `dependencies`, or be pinned by a product catalog for the
|
|
606
|
+
* deploys that load its modules. A package reached only lazily is reported, not counted as a
|
|
607
|
+
* violation.
|
|
608
|
+
* @param {object} [params]
|
|
609
|
+
* @param {string} [params.root] - Package root.
|
|
610
|
+
* @param {object} [params.packageJson] - Manifest to check; read from root otherwise.
|
|
611
|
+
* @param {object[]} [params.catalogs] - Product catalogs; every one in the tree otherwise.
|
|
612
|
+
* @returns {Promise<{dependencies: number, devDependencies: number, runtime: string[], lazy: string[], misplaced: Array<{name: string, declaredIn: string, importers: string[]}>}>} Audit result.
|
|
613
|
+
* @memberof PackageBuilder
|
|
614
|
+
*/
|
|
615
|
+
const auditRuntimeDependencies = async ({
|
|
616
|
+
root = ENGINE_PACKAGE_PATH,
|
|
617
|
+
packageJson = readManifest(nodePath.join(root, 'package.json')),
|
|
618
|
+
catalogs,
|
|
619
|
+
} = {}) => {
|
|
620
|
+
const { packages, lazyPackages } = await runtimeImportGraphFactory({ root });
|
|
621
|
+
const dependencies = packageJson.dependencies ?? {};
|
|
622
|
+
const devDependencies = packageJson.devDependencies ?? {};
|
|
623
|
+
const catalogDependencies = Object.assign(
|
|
624
|
+
{},
|
|
625
|
+
...(catalogs ?? (await loadProductCatalogs())).map((catalog) => catalog.packageDependencies ?? {}),
|
|
626
|
+
);
|
|
627
|
+
const declaredIn = (name) =>
|
|
628
|
+
dependencies[name]
|
|
629
|
+
? 'dependencies'
|
|
630
|
+
: catalogDependencies[name]
|
|
631
|
+
? 'catalog'
|
|
632
|
+
: devDependencies[name]
|
|
633
|
+
? 'devDependencies'
|
|
634
|
+
: 'none';
|
|
635
|
+
const misplaced = Object.entries(packages)
|
|
636
|
+
.map(([name, importers]) => ({ name, declaredIn: declaredIn(name), importers }))
|
|
637
|
+
.filter(({ declaredIn: origin }) => origin !== 'dependencies' && origin !== 'catalog');
|
|
638
|
+
return {
|
|
639
|
+
dependencies: Object.keys(dependencies).length,
|
|
640
|
+
devDependencies: Object.keys(devDependencies).length,
|
|
641
|
+
runtime: Object.keys(packages),
|
|
642
|
+
lazy: Object.keys(lazyPackages).map((name) => `${name} (${declaredIn(name)})`),
|
|
643
|
+
misplaced,
|
|
644
|
+
};
|
|
645
|
+
};
|
|
646
|
+
|
|
488
647
|
export {
|
|
489
648
|
DEPLOY_CONF_ROOT,
|
|
490
649
|
DEPLOY_MANIFEST_INDENT,
|
|
650
|
+
RUNTIME_ENTRY_POINTS,
|
|
491
651
|
STAGED_CLI_PACKAGE,
|
|
652
|
+
auditRuntimeDependencies,
|
|
492
653
|
buildDeployPackageJson,
|
|
493
654
|
buildProductPackageJson,
|
|
494
655
|
deployDependencySpecsFactory,
|
|
495
656
|
deployPackageNameFactory,
|
|
496
657
|
deployPackagePathFactory,
|
|
497
658
|
deployStartScriptFactory,
|
|
659
|
+
importPackageNameFactory,
|
|
498
660
|
installDeployDependencies,
|
|
499
661
|
packageRepositoryFactory,
|
|
500
662
|
productDevDependenciesFactory,
|
|
501
663
|
productPackageOptionsFactory,
|
|
502
664
|
publishedProductPackageJson,
|
|
503
665
|
renamePackage,
|
|
666
|
+
runtimeImportGraphFactory,
|
|
504
667
|
setPackageRepository,
|
|
505
668
|
stageCliPackage,
|
|
506
669
|
stagePackageArchive,
|
|
@@ -128,8 +128,9 @@ const TEST_TIERS = [
|
|
|
128
128
|
'src/server/ops/systemd.js',
|
|
129
129
|
'src/server/security/container-storage.js',
|
|
130
130
|
'src/server/security/selinux.js',
|
|
131
|
+
'src/server/security/socketsecurity.js',
|
|
131
132
|
],
|
|
132
|
-
description: 'SELinux policy, systemd units
|
|
133
|
+
description: 'SELinux policy, systemd units, the SOPS secret store and the Socket supply-chain audit.',
|
|
133
134
|
},
|
|
134
135
|
{
|
|
135
136
|
name: 'infra:2-network',
|
|
@@ -3126,6 +3126,24 @@ const pruneTemplateWorkTree = (toPath, preservedEntries = []) => {
|
|
|
3126
3126
|
}
|
|
3127
3127
|
};
|
|
3128
3128
|
|
|
3129
|
+
/**
|
|
3130
|
+
* Marker in the engine `.gitignore` where the engine-only prototype rules start.
|
|
3131
|
+
* @constant {string}
|
|
3132
|
+
* @memberof ServerConfBuilder
|
|
3133
|
+
*/
|
|
3134
|
+
const GITIGNORE_PROTOTYPE_BOUNDARY = '# Ignore ERP / CRM custom prototypes src';
|
|
3135
|
+
|
|
3136
|
+
/**
|
|
3137
|
+
* The `.gitignore` a generated repository carries: the engine rules before the prototype
|
|
3138
|
+
* boundary. Without it, `git add .` after an install commits `node_modules`.
|
|
3139
|
+
* @method templateGitignoreFactory
|
|
3140
|
+
* @param {string} [source='.gitignore'] - Engine `.gitignore` path.
|
|
3141
|
+
* @returns {string} File content.
|
|
3142
|
+
* @memberof ServerConfBuilder
|
|
3143
|
+
*/
|
|
3144
|
+
const templateGitignoreFactory = (source = '.gitignore') =>
|
|
3145
|
+
fs.readFileSync(source, 'utf8').split(GITIGNORE_PROTOTYPE_BOUNDARY)[0];
|
|
3146
|
+
|
|
3129
3147
|
/**
|
|
3130
3148
|
* Rebuilds the standalone `pwa-microservices-template` from scratch out of the current
|
|
3131
3149
|
* engine source tree.
|
|
@@ -3183,6 +3201,8 @@ const buildTemplate = async ({ srcPath = './', toPath = '../pwa-microservices-te
|
|
|
3183
3201
|
|
|
3184
3202
|
fs.copySync(`./.vscode`, `${toPath}/.vscode`);
|
|
3185
3203
|
fs.copySync(`./src/client/public/default`, `${toPath}/src/client/public/default`);
|
|
3204
|
+
// The walker filter above drops every `.git*` entry, `.gitignore` included.
|
|
3205
|
+
fs.writeFileSync(`${toPath}/.gitignore`, templateGitignoreFactory(`${srcPath}/.gitignore`), 'utf8');
|
|
3186
3206
|
|
|
3187
3207
|
// Preserve the template's own README + package.json identity before merging engine metadata.
|
|
3188
3208
|
for (const checkoutPath of ['README.md', 'package.json']) shellExec(`cd ${toPath} && git checkout ${checkoutPath}`);
|
|
@@ -3480,6 +3500,7 @@ export {
|
|
|
3480
3500
|
gitOriginRepositoryName,
|
|
3481
3501
|
ensureTemplateCheckout,
|
|
3482
3502
|
pruneTemplateWorkTree,
|
|
3503
|
+
templateGitignoreFactory,
|
|
3483
3504
|
buildTemplate,
|
|
3484
3505
|
updatePrivateTemplateRepo,
|
|
3485
3506
|
updatePrivateEngineTestRepo,
|
|
@@ -60,6 +60,8 @@ const CONFIG_OWNERSHIP = Object.freeze([
|
|
|
60
60
|
{ match: /^(GITHUB_|DEFAULT_SSH_|FORWARD_PROXY_|DEFAULT_DEPLOY_)/, owner: 'host' },
|
|
61
61
|
{ match: /^(GF_SECURITY_|CLUSTER_MAILER_)/, owner: 'host' },
|
|
62
62
|
{ match: /^UNDERPOST_EVENT_TOKEN$/, owner: 'host' },
|
|
63
|
+
// The Socket audit runs on hosts and in CI, never in a workload.
|
|
64
|
+
{ match: /^SOCKET_CLI_/, owner: 'host' },
|
|
63
65
|
|
|
64
66
|
{ match: /^(DB_|MARIADB_|VALKEY_)/, owner: 'app' },
|
|
65
67
|
{ match: /^(JWT_SECRET|ACCESS_EXPIRE_MINUTES|REFRESH_EXPIRE_MINUTES|PORT|DEPLOY_ID)$/, owner: 'app' },
|
|
@@ -414,14 +414,15 @@ const getTerminalPid = () => {
|
|
|
414
414
|
};
|
|
415
415
|
/**
|
|
416
416
|
* Copies text content to the system clipboard using clipboardy.
|
|
417
|
-
* Logs the copied content
|
|
417
|
+
* Logs only the copied length: the content is often a password or a command carrying one.
|
|
418
418
|
* @memberof Process
|
|
419
419
|
* @param {string} [data='🦄'] - The data to copy. Defaults to '🦄'.
|
|
420
420
|
* @returns {void}
|
|
421
421
|
*/
|
|
422
422
|
function pbcopy(data) {
|
|
423
|
-
|
|
424
|
-
|
|
423
|
+
const content = data || '🦄';
|
|
424
|
+
clipboard.writeSync(content);
|
|
425
|
+
logger.info(`copied to clipboard`, { characters: `${content}`.length });
|
|
425
426
|
}
|
|
426
427
|
export {
|
|
427
428
|
installRootFile,
|