@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.
Files changed (105) hide show
  1. package/.env.example +1 -0
  2. package/.github/workflows/coverall.cyberia.ci.yml +12 -3
  3. package/.github/workflows/cyberia-client.cd.yml +2 -1
  4. package/.github/workflows/cyberia-server.cd.yml +2 -1
  5. package/.github/workflows/docker-image.cyberia-client.ci.yml +1 -1
  6. package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +1 -1
  7. package/.github/workflows/docker-image.cyberia-server.ci.yml +1 -1
  8. package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +1 -1
  9. package/.github/workflows/docker-image.engine-cyberia.ci.yml +1 -1
  10. package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +1 -1
  11. package/.github/workflows/engine-cyberia.ci.yml +6 -0
  12. package/.github/workflows/ghpkg.ci.yml +19 -2
  13. package/.github/workflows/gitlab.ci.yml +8 -6
  14. package/.github/workflows/hardhat.ci.yml +4 -1
  15. package/.github/workflows/npmpkg.ci.yml +9 -5
  16. package/.github/workflows/publish.ci.yml +21 -12
  17. package/.github/workflows/publish.cyberia.ci.yml +25 -14
  18. package/.github/workflows/pwa-microservices-template-page.cd.yml +4 -2
  19. package/CHANGELOG.md +75 -140
  20. package/CLI-HELP.md +28 -1
  21. package/Dockerfile +1 -1
  22. package/Dockerfile.dev +1 -1
  23. package/Dockerfile.test +1 -1
  24. package/README.md +1 -1
  25. package/bin/build.js +2 -4
  26. package/bin/cyberia.js +4 -4
  27. package/bin/index.js +4 -4
  28. package/compose.env +1 -0
  29. package/conf.js +1 -1042
  30. package/deploy/cyberia-client/deploy.sh +13 -11
  31. package/deploy/cyberia-server/deploy.sh +13 -11
  32. package/deploy/dd-cyberia/sync-deploy.sh +7 -28
  33. package/deploy/lib/config.sh +1 -1
  34. package/deploy/lib/github-actions-logging.sh +3 -2
  35. package/deploy/pwa-microservices-template/deploy.sh +7 -1
  36. package/deployment.yaml +1 -1
  37. package/docker-compose.yml +7 -7
  38. package/docs/coverage/cyberia/api/cyberia-instance/cyberia-fallback-capture.js.html +1 -1
  39. package/docs/coverage/cyberia/api/cyberia-instance/index.html +1 -1
  40. package/docs/coverage/cyberia/api/cyberia-server-defaults/cyberia-server-defaults.js.html +1 -1
  41. package/docs/coverage/cyberia/api/cyberia-server-defaults/index.html +1 -1
  42. package/docs/coverage/cyberia/api/object-layer/index.html +1 -1
  43. package/docs/coverage/cyberia/api/object-layer/object-layer.model.js.html +1 -1
  44. package/docs/coverage/cyberia/index.html +1 -1
  45. package/docs/coverage/cyberia/projects/cyberia/atlas-sprite-sheet-generator.js.html +1 -1
  46. package/docs/coverage/cyberia/projects/cyberia/index.html +1 -1
  47. package/docs/coverage/cyberia/projects/cyberia/instance-backup.js.html +1 -1
  48. package/docs/coverage/cyberia/projects/cyberia/instance-data.js.html +1 -1
  49. package/docs/coverage/cyberia/projects/cyberia/shape-generator.js.html +1 -1
  50. package/docs/coverage/cyberia/projects/cyberia/stat-balance.js.html +1 -1
  51. package/hardhat/package-lock.json +6 -6
  52. package/hardhat/package.json +2 -2
  53. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
  54. package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +1 -1
  55. package/manifests/deployment/dd-cyberia-development/deployment.yaml +1 -1
  56. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  57. package/package.json +19 -17
  58. package/scripts/test-monitor.sh +1 -1
  59. package/src/api/file/file.router.js +7 -1
  60. package/src/cli/baremetal.js +1 -1
  61. package/src/cli/docker-compose.js +1 -1
  62. package/src/cli/index.js +36 -0
  63. package/src/cli/kubectl.js +4 -3
  64. package/src/cli/release.js +2 -0
  65. package/src/cli/repository.js +7 -2
  66. package/src/cli/run.js +1 -1
  67. package/src/cli/secrets.js +393 -200
  68. package/src/cli/socketsecurity.js +260 -0
  69. package/src/client/components/core/CssCore.js +3 -0
  70. package/src/client/components/core/Modal.js +160 -36
  71. package/src/client/components/core/SearchBox.js +0 -1
  72. package/src/client/components/underpost/AppShellUnderpost.js +13 -1
  73. package/src/client/components/underpost/CssUnderpost.js +15 -2
  74. package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +8 -0
  75. package/src/client/public/cyberia-docs/CYBERIA.md +1 -1
  76. package/src/client/public/cyberia-docs/OFF-CHAIN-ECONOMY.md +54 -56
  77. package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
  78. package/src/client/public/cyberia-docs/UNDERPOST-PLATFORM.md +30 -29
  79. package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
  80. package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +54 -25
  81. package/src/client/ssr/head/Css.js +12 -10
  82. package/src/client/ssr/views/Cyberia404.js +6 -10
  83. package/src/client/ssr/views/NotFound.js +75 -0
  84. package/src/client-builder/client-bundle.js +2 -2
  85. package/src/index.js +13 -1
  86. package/src/runtime/engine-cyberia/Dockerfile +1 -1
  87. package/src/runtime/engine-cyberia/Dockerfile.dev +1 -1
  88. package/src/runtime/engine-cyberia/Dockerfile.test +1 -1
  89. package/src/runtime/engine-cyberia/compose.env +1 -0
  90. package/src/runtime/engine-cyberia/docker-compose.yml +7 -7
  91. package/src/server/build/catalog.js +1 -0
  92. package/src/server/build/execution.js +19 -5
  93. package/src/server/build/package.js +166 -3
  94. package/src/server/build/testing.js +2 -1
  95. package/src/server/runtime/conf.js +21 -0
  96. package/src/server/runtime/config-scope.js +2 -0
  97. package/src/server/runtime/process.js +4 -3
  98. package/src/server/security/socketsecurity.js +1735 -0
  99. package/test/integration/infra/1-security/config-scope.test.js +3 -0
  100. package/test/integration/infra/1-security/socketsecurity.test.js +1041 -0
  101. package/test/integration/infra/1-security/sops-secret-store.test.js +201 -71
  102. package/test/unit/build-template.test.js +24 -0
  103. package/test/unit/deploy-log-table.test.js +38 -0
  104. package/test/unit/execution-profiles.test.js +1 -0
  105. 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.3.77';
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.3.77
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.3.77
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.3.77
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.3.77}
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.3.77}
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.3.77}
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.3.77}
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.3.77}
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.3.77}
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.3.77}
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` reaches a remote service and writes to it (`gh secret set`, `gh release create`), so it
158
- // is a network effect rather than local filesystem work — an unclassified binary would default
159
- // to `fs` and run under HERMETIC_BUILD.
160
- const NET_BINARIES = new Set(['curl', 'wget', 'ssh', 'scp', 'sftp', 'rsync', 'nc', 'ping', 'dig', 'nslookup', 'gh']);
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
- const binary = nodePath.basename(bare[start] ?? '');
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 = ({ packageJson, catalog = {}, underpostVersion = '' } = {}) => {
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 = { underpost: `^${version}`, ...(catalog.packageDependencies ?? {}) };
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 and the SOPS secret store.',
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 for confirmation.
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
- clipboard.writeSync(data || '🦄');
424
- logger.info(`copied to clipboard`, clipboard.readSync());
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,