underpost 3.2.9 → 3.2.10

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 (81) hide show
  1. package/.github/workflows/npmpkg.ci.yml +1 -0
  2. package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
  3. package/.github/workflows/release.cd.yml +1 -0
  4. package/.vscode/settings.json +10 -5
  5. package/CHANGELOG.md +122 -1
  6. package/CLI-HELP.md +22 -7
  7. package/README.md +37 -8
  8. package/bin/build.js +26 -9
  9. package/bin/deploy.js +20 -21
  10. package/bin/file.js +31 -13
  11. package/bin/index.js +2 -1
  12. package/bin/vs.js +1 -1
  13. package/bump.config.js +26 -0
  14. package/conf.js +20 -4
  15. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
  16. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
  17. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  18. package/manifests/deployment/dd-test-development/deployment.yaml +4 -2
  19. package/manifests/kind-config-dev.yaml +8 -0
  20. package/manifests/mongodb/pv-pvc.yaml +44 -8
  21. package/manifests/mongodb/statefulset.yaml +55 -68
  22. package/package.json +27 -12
  23. package/scripts/k3s-node-setup.sh +28 -9
  24. package/src/api/core/core.router.js +19 -14
  25. package/src/api/core/core.service.js +5 -5
  26. package/src/api/default/default.router.js +22 -18
  27. package/src/api/default/default.service.js +5 -5
  28. package/src/api/document/document.router.js +28 -23
  29. package/src/api/document/document.service.js +100 -23
  30. package/src/api/file/file.router.js +19 -13
  31. package/src/api/file/file.service.js +9 -7
  32. package/src/api/test/test.router.js +17 -12
  33. package/src/api/types.js +24 -0
  34. package/src/api/user/guest.service.js +5 -4
  35. package/src/api/user/user.router.js +297 -288
  36. package/src/api/user/user.service.js +100 -35
  37. package/src/cli/baremetal.js +20 -11
  38. package/src/cli/cluster.js +196 -55
  39. package/src/cli/db.js +59 -60
  40. package/src/cli/deploy.js +273 -159
  41. package/src/cli/fs.js +3 -1
  42. package/src/cli/index.js +16 -9
  43. package/src/cli/ipfs.js +4 -6
  44. package/src/cli/kubectl.js +4 -1
  45. package/src/cli/lxd.js +217 -135
  46. package/src/cli/release.js +289 -131
  47. package/src/cli/repository.js +58 -7
  48. package/src/cli/run.js +152 -25
  49. package/src/cli/test.js +9 -3
  50. package/src/client/Default.index.js +9 -3
  51. package/src/client/components/core/Auth.js +4 -0
  52. package/src/client/components/core/PanelForm.js +56 -52
  53. package/src/client/components/core/Worker.js +162 -363
  54. package/src/client/sw/core.sw.js +174 -112
  55. package/src/db/DataBaseProvider.js +120 -20
  56. package/src/db/mongo/MongoBootstrap.js +587 -0
  57. package/src/db/mongo/MongooseDB.js +126 -22
  58. package/src/index.js +1 -1
  59. package/src/runtime/express/Express.js +2 -2
  60. package/src/runtime/wp/Wp.js +8 -5
  61. package/src/server/auth.js +2 -2
  62. package/src/server/client-build-docs.js +1 -1
  63. package/src/server/client-build.js +94 -129
  64. package/src/server/conf.js +20 -65
  65. package/src/server/process.js +180 -19
  66. package/src/server/runtime.js +1 -1
  67. package/src/server/start.js +12 -4
  68. package/src/ws/IoInterface.js +16 -16
  69. package/src/ws/core/channels/core.ws.chat.js +11 -11
  70. package/src/ws/core/channels/core.ws.mailer.js +29 -29
  71. package/src/ws/core/channels/core.ws.stream.js +19 -19
  72. package/src/ws/core/core.ws.connection.js +8 -8
  73. package/src/ws/core/core.ws.server.js +6 -5
  74. package/src/ws/default/channels/default.ws.main.js +10 -10
  75. package/src/ws/default/default.ws.connection.js +4 -4
  76. package/src/ws/default/default.ws.server.js +4 -3
  77. package/src/client/ssr/email/DefaultRecoverEmail.js +0 -21
  78. package/src/client/ssr/email/DefaultVerifyEmail.js +0 -17
  79. /package/src/client/ssr/{offline → views}/Maintenance.js +0 -0
  80. /package/src/client/ssr/{offline → views}/NoNetworkConnection.js +0 -0
  81. /package/src/client/ssr/{pages → views}/Test.js +0 -0
@@ -10,6 +10,7 @@
10
10
  */
11
11
 
12
12
  import fs from 'fs-extra';
13
+ import path from 'path';
13
14
  import dotenv from 'dotenv';
14
15
  import { pbcopy, shellCd, shellExec } from '../server/process.js';
15
16
  import { loggerFactory } from '../server/logger.js';
@@ -18,6 +19,222 @@ import Underpost from '../index.js';
18
19
 
19
20
  const logger = loggerFactory(import.meta);
20
21
 
22
+ /**
23
+ * Files that must never be touched by a version bump, even if they happen to contain a
24
+ * literal version string. CHANGELOG is historical; logs/ and node_modules/ are runtime
25
+ * outputs; engine-private/.env.* and secrets must not be rewritten by regex.
26
+ */
27
+ const VERSION_BUMP_SKIP = [
28
+ /(^|\/)CHANGELOG\.md$/i,
29
+ /(^|\/)logs\//,
30
+ /(^|\/)node_modules\//,
31
+ /(^|\/)\.git\//,
32
+ /(^|\/)engine-private\/.*\.env(\..*)?$/,
33
+ ];
34
+
35
+ const isSkipped = (filePath) => VERSION_BUMP_SKIP.some((re) => re.test(filePath));
36
+
37
+ /**
38
+ * Declarative regex-based version bump targets for files bumpp cannot handle natively
39
+ * (image tags, doc strings, README badges, build-args, etc.).
40
+ *
41
+ * Each target is one of:
42
+ * - { file: 'path/to/file', patterns: RegExp | RegExp[] }
43
+ * - { dir: 'dir', match: RegExp, patterns: RegExp | RegExp[], recursive?: boolean }
44
+ *
45
+ * Every pattern MUST have exactly one capture group: the prefix to preserve. The replacement
46
+ * is `$1<newVersion>`. Use a lookahead `(?=...)` to anchor a trailing context (closing quote,
47
+ * backtick, etc.) without consuming it. This keeps the callback signature trivial.
48
+ *
49
+ * The walker iterates targets, applies patterns to each matching file, and reports the
50
+ * number of substitutions per file. Files that match nothing are silently skipped.
51
+ */
52
+ const buildVersionBumpTargets = () => [
53
+ // ── src/index.js — anchored to the static class field, never a bare replaceAll. ──
54
+ {
55
+ file: 'src/index.js',
56
+ patterns: /(static version = ['"]v)\d+\.\d+\.\d+(?=['"])/g,
57
+ },
58
+
59
+ // ── README + CLI-HELP fallbacks. CLI-HELP is regenerated by `deploy cli-docs`, but bump
60
+ // it too so the file is consistent if the regeneration step is skipped. ──
61
+ {
62
+ file: 'README.md',
63
+ patterns: [
64
+ /(socket\.dev\/api\/badge\/npm\/package\/underpost\/)\d+\.\d+\.\d+/g,
65
+ /(socket\.dev\/npm\/package\/underpost\/overview\/)\d+\.\d+\.\d+/g,
66
+ /(ci\/cd cli v)\d+\.\d+\.\d+/gi,
67
+ ],
68
+ },
69
+ {
70
+ file: 'CLI-HELP.md',
71
+ patterns: /(ci\/cd cli v)\d+\.\d+\.\d+/gi,
72
+ optional: true,
73
+ },
74
+
75
+ // ── Cyberia + nexodev docs. ──
76
+ {
77
+ dir: 'src/client/public/cyberia-docs',
78
+ match: /\.md$/,
79
+ patterns: [
80
+ /(\*\*(?:Current )?[Vv]ersion:\*\* )\d+\.\d+\.\d+/g,
81
+ /(underpost\/[a-z0-9-]+:v)\d+\.\d+\.\d+/g,
82
+ ],
83
+ recursive: true,
84
+ },
85
+ {
86
+ dir: 'src/client/public/nexodev/docs/references',
87
+ match: /\.md$/,
88
+ patterns: [
89
+ /(underpost\/[a-z0-9-]+:v)\d+\.\d+\.\d+/g,
90
+ /(UNDERPOST_VERSION=)\d+\.\d+\.\d+/g,
91
+ /(ci\/cd cli v)\d+\.\d+\.\d+/gi,
92
+ // version tag bumps inside markdown narrative, e.g. `v3.2.9`, `v3.2.10`, …
93
+ /(`v)\d+\.\d+\.\d+(?=`)/g,
94
+ ],
95
+ recursive: true,
96
+ },
97
+
98
+ // ── Kubernetes manifests. Covers deployment.yaml + cronjob yamls under dd-cron. ──
99
+ {
100
+ dir: 'manifests/deployment',
101
+ match: /deployment\.yaml$/,
102
+ patterns: [
103
+ /(underpost\/[a-z0-9-]+:v)\d+\.\d+\.\d+/g,
104
+ /(engine\.version: )\d+\.\d+\.\d+/g,
105
+ ],
106
+ recursive: true,
107
+ },
108
+ {
109
+ dir: 'manifests/cronjobs',
110
+ match: /\.yaml$/,
111
+ patterns: /(underpost\/[a-z0-9-]+:v)\d+\.\d+\.\d+/g,
112
+ recursive: true,
113
+ },
114
+
115
+ // ── GitHub Actions workflows. Single sweep across docker-image.*.ci.yml, engine-*.cd.yml,
116
+ // and the root docker-image.ci.yml — replaces previous three separate loops. ──
117
+ {
118
+ dir: '.github/workflows',
119
+ match: /\.(yml|yaml)$/,
120
+ patterns: [
121
+ /(underpost\/[a-z0-9-]+:v)\d+\.\d+\.\d+/g,
122
+ /(underpost-engine:v)\d+\.\d+\.\d+/g,
123
+ /(type=raw,value=v)\d+\.\d+\.\d+/g,
124
+ /(UNDERPOST_VERSION=)\d+\.\d+\.\d+/g,
125
+ ],
126
+ },
127
+
128
+ // ── engine-private confs (gitignored — bumped only if present). ──
129
+ {
130
+ dir: 'engine-private/conf',
131
+ match: /(?:deployment\.yaml|conf\.instances\.json)$/,
132
+ patterns: [
133
+ /(underpost\/[a-z0-9-]+:v)\d+\.\d+\.\d+/g,
134
+ /(underpost-engine:v)\d+\.\d+\.\d+/g,
135
+ /(engine\.version: )\d+\.\d+\.\d+/g,
136
+ /(v)\d+\.\d+\.\d+/g,
137
+ ],
138
+ recursive: true,
139
+ optional: true,
140
+ },
141
+ ];
142
+
143
+ /**
144
+ * Walks a directory (optionally recursively) and returns paths matching `match`.
145
+ */
146
+ const walkDir = (dir, match, recursive) => {
147
+ if (!fs.existsSync(dir)) return [];
148
+ const out = [];
149
+ const stack = [dir];
150
+ while (stack.length) {
151
+ const cur = stack.pop();
152
+ let entries;
153
+ try {
154
+ entries = fs.readdirSync(cur, { withFileTypes: true });
155
+ } catch {
156
+ continue;
157
+ }
158
+ for (const ent of entries) {
159
+ const full = path.join(cur, ent.name);
160
+ if (ent.isDirectory()) {
161
+ if (recursive) stack.push(full);
162
+ } else if (ent.isFile() && match.test(ent.name) && !isSkipped(full)) {
163
+ out.push(full);
164
+ }
165
+ }
166
+ }
167
+ return out;
168
+ };
169
+
170
+ /**
171
+ * Applies an array of regex patterns to a single file in place. Substitutes only when the
172
+ * matched version literally equals `oldVersion` — narrative references to other versions
173
+ * (e.g. example tags `(v3.2.9, v3.2.10, …)`) are left untouched.
174
+ *
175
+ * Returns the substitution count (0 if nothing matched).
176
+ */
177
+ const bumpFile = (filePath, patterns, oldVersion, newVersion, { dryRun }) => {
178
+ if (isSkipped(filePath)) return 0;
179
+ if (!fs.existsSync(filePath)) return 0;
180
+ const original = fs.readFileSync(filePath, 'utf8');
181
+ const regexes = Array.isArray(patterns) ? patterns : [patterns];
182
+ let updated = original;
183
+ let count = 0;
184
+ for (const re of regexes) {
185
+ updated = updated.replace(re, (m, prefix) => {
186
+ const matchedVersion = m.match(/\d+\.\d+\.\d+/)?.[0];
187
+ if (matchedVersion !== oldVersion) return m;
188
+ count += 1;
189
+ return `${prefix}${newVersion}`;
190
+ });
191
+ }
192
+ if (count > 0 && updated !== original && !dryRun) {
193
+ fs.writeFileSync(filePath, updated, 'utf8');
194
+ }
195
+ return count;
196
+ };
197
+
198
+ /**
199
+ * Bumps every non-canonical file declared in VERSION_BUMP_TARGETS.
200
+ * Returns a per-file report so callers can print a summary.
201
+ *
202
+ * @param {string} oldVersion - The version currently in the files (pre-bump).
203
+ * @param {string} newVersion - The version to write.
204
+ * @param {{dryRun?: boolean}} [opts]
205
+ * @returns {Array<{file: string, count: number}>}
206
+ */
207
+ const bumpAuxiliaryFiles = (oldVersion, newVersion, { dryRun = false } = {}) => {
208
+ const report = [];
209
+ if (oldVersion === newVersion) return report;
210
+ for (const target of buildVersionBumpTargets()) {
211
+ const files = target.file ? [target.file] : walkDir(target.dir, target.match, target.recursive ?? false);
212
+ for (const file of files) {
213
+ if (target.optional && !fs.existsSync(file)) continue;
214
+ const count = bumpFile(file, target.patterns, oldVersion, newVersion, { dryRun });
215
+ if (count > 0) report.push({ file, count });
216
+ }
217
+ }
218
+ return report;
219
+ };
220
+
221
+ /**
222
+ * Prints a per-file change summary table.
223
+ */
224
+ const printBumpReport = (report, { dryRun }) => {
225
+ const header = dryRun ? 'Version bump (dry-run) — would change:' : 'Version bump — files updated:';
226
+ logger.info(header);
227
+ if (!report.length) {
228
+ console.log(' (no files matched)');
229
+ return;
230
+ }
231
+ const maxLen = Math.max(...report.map((r) => r.file.length));
232
+ for (const { file, count } of report) {
233
+ console.log(` ${file.padEnd(maxLen + 2)} ${count} match${count === 1 ? '' : 'es'}`);
234
+ }
235
+ console.log(` Total: ${report.length} file(s), ${report.reduce((s, r) => s + r.count, 0)} substitution(s)`);
236
+ };
237
+
21
238
  /**
22
239
  * Kills any Node.js dev-server or nodemon processes that may hold file locks
23
240
  * (e.g. overwriting package.json). Skips VSCode internals and the current process.
@@ -46,154 +263,94 @@ class UnderpostRelease {
46
263
  * Builds a new version of the Underpost engine.
47
264
  *
48
265
  * Performs the full version build pipeline:
49
- * 1. Loads production environment and pulls latest code.
50
- * 2. Kills running dev servers on ports 4001-4003.
51
- * 3. Builds and tests the pwa-microservices-template.
52
- * 4. Bumps version in package.json, package-lock.json, and all conf package files.
53
- * 5. Updates deployment YAML manifests and Docker image CI workflow with new version.
54
- * 6. Updates src/index.js version string.
55
- * 7. Rebuilds CLI docs, dependencies, client builds, deploy manifests, and default confs.
56
- * 8. Syncs cron setup-start scripts and builds changelog.
266
+ * 1. Loads production env and pulls latest code; kills dev servers on ports 4001-4003.
267
+ * 2. Builds and smoke-tests the pwa-microservices-template (aborts on error).
268
+ * 3. Canonical version files: delegates to `bumpp` (driven by `bump.config.js`). Bumps
269
+ * package.json, package-lock.json (root + packages['']), and every
270
+ * engine-private/conf/**\/package.json.
271
+ * 4. Auxiliary files: anchored-regex walker over VERSION_BUMP_TARGETS — covers
272
+ * src/index.js, README, cyberia-docs, nexodev docs, manifests (deployment + cronjobs),
273
+ * .github/workflows (image tags, type=raw, UNDERPOST_VERSION build-args), and
274
+ * engine-private confs (deployment.yaml + conf.instances.json). Files in
275
+ * VERSION_BUMP_SKIP (CHANGELOG, logs/, .env.*, node_modules/) are never touched.
276
+ * The walker only rewrites occurrences whose version literally equals the pre-bump
277
+ * version — narrative references like `(v3.2.9, v3.2.10, …)` are preserved.
278
+ * 5. Rebuilds CLI docs, deps, client bundle, deploy manifests, default confs.
279
+ * 6. Syncs cron setup-start scripts and builds the changelog.
280
+ *
281
+ * With `options.dryRun: true`, steps 3–4 print a per-file substitution report and exit
282
+ * without writing files or running steps 5–6.
57
283
  *
58
284
  * @method build
59
285
  * @param {string} [newVersion] - The new version string to set. Defaults to current version if not provided.
60
- * @param {object} [options] - Commander options object (unused, reserved for future flags).
286
+ * @param {{dryRun?: boolean}} [options] - Commander options. `--dry-run` previews changes.
61
287
  * @memberof UnderpostRelease
62
288
  */
63
- async build(newVersion, options) {
289
+ async build(newVersion, options = {}) {
290
+ const dryRun = !!options.dryRun;
64
291
  dotenv.config({ path: `./engine-private/conf/dd-cron/.env.production`, override: true });
65
292
  shellCd(`/home/dd/engine`);
66
- killDevServers();
67
- Underpost.repo.clean({ paths: ['/home/dd/engine', '/home/dd/engine/engine-private '] });
68
- shellExec(`node bin pull . ${process.env.GITHUB_USERNAME}/engine`);
69
- shellExec(`npm run update:template`);
70
- shellExec(`cd ../pwa-microservices-template && npm install && npm run build`);
71
- console.log(fs.existsSync(`../pwa-microservices-template/engine-private/conf/dd-default`));
72
- shellExec(`cd ../pwa-microservices-template && ENABLE_FILE_LOGS=true timeout 5s npm run dev`, {
73
- async: true,
74
- });
75
- await timer(5500);
76
- const templateRunnerResult = fs.readFileSync(`../pwa-microservices-template/logs/start.js/all.log`, 'utf8');
77
- logger.info('Test template runner result');
78
- console.log(templateRunnerResult);
79
- if (!templateRunnerResult || templateRunnerResult.toLowerCase().match('error')) {
80
- logger.error('Test template runner result failed');
81
- return;
82
- }
83
- killDevServers();
84
- shellCd(`/home/dd/engine`);
85
- Underpost.repo.clean({ paths: ['/home/dd/engine', '/home/dd/engine/engine-private '] });
293
+
294
+ // ── Resolve from/to versions up front. Used by every bump step + downstream commands. ──
86
295
  const originPackageJson = JSON.parse(fs.readFileSync(`package.json`, 'utf8'));
87
- if (!newVersion) newVersion = originPackageJson.version;
88
296
  const { version } = originPackageJson;
89
- originPackageJson.version = newVersion;
90
- fs.writeFileSync(`package.json`, JSON.stringify(originPackageJson, null, 4), 'utf8');
91
-
92
- const originPackageLockJson = JSON.parse(fs.readFileSync(`package-lock.json`, 'utf8'));
93
- originPackageLockJson.version = newVersion;
94
- originPackageLockJson.packages[''].version = newVersion;
95
- fs.writeFileSync(`package-lock.json`, JSON.stringify(originPackageLockJson, null, 4), 'utf8');
297
+ if (!newVersion) newVersion = version;
298
+ logger.info(`Release build bumping ${version} → ${newVersion}${dryRun ? ' (dry-run)' : ''}`);
96
299
 
97
- if (fs.existsSync(`./engine-private/conf`)) {
98
- const files = await fs.readdir(`./engine-private/conf`, { recursive: true });
99
- for (const relativePath of files) {
100
- const filePah = `./engine-private/conf/${relativePath.replaceAll(`\\`, '/')}`;
101
- if (filePah.split('/').pop() === 'package.json') {
102
- const originPackage = JSON.parse(fs.readFileSync(filePah, 'utf8'));
103
- originPackage.version = newVersion;
104
- fs.writeFileSync(filePah, JSON.stringify(originPackage, null, 4), 'utf8');
105
- }
106
- if (filePah.split('/').pop() === 'deployment.yaml') {
107
- fs.writeFileSync(
108
- filePah,
109
- fs
110
- .readFileSync(filePah, 'utf8')
111
- .replaceAll(`v${version}`, `v${newVersion}`)
112
- .replaceAll(`engine.version: ${version}`, `engine.version: ${newVersion}`),
113
- 'utf8',
114
- );
115
- }
300
+ if (!dryRun) {
301
+ killDevServers();
302
+ Underpost.repo.clean({ paths: ['/home/dd/engine', '/home/dd/engine/engine-private '] });
303
+ shellExec(`node bin pull . ${process.env.GITHUB_USERNAME}/engine`);
304
+ shellExec(`npm run update:template`);
305
+ shellExec(`cd ../pwa-microservices-template && npm install && npm run build`);
306
+ console.log(fs.existsSync(`../pwa-microservices-template/engine-private/conf/dd-default`));
307
+ shellExec(`cd ../pwa-microservices-template && ENABLE_FILE_LOGS=true timeout 5s npm run dev`, {
308
+ async: true,
309
+ });
310
+ await timer(5500);
311
+ const templateRunnerResult = fs.readFileSync(`../pwa-microservices-template/logs/start.js/all.log`, 'utf8');
312
+ logger.info('Test template runner result');
313
+ console.log(templateRunnerResult);
314
+ if (!templateRunnerResult || templateRunnerResult.toLowerCase().match('error')) {
315
+ logger.error('Test template runner result failed');
316
+ return;
116
317
  }
318
+ killDevServers();
319
+ shellCd(`/home/dd/engine`);
320
+ Underpost.repo.clean({ paths: ['/home/dd/engine', '/home/dd/engine/engine-private '] });
117
321
  }
118
322
 
119
- fs.writeFileSync(
120
- `./manifests/deployment/dd-default-development/deployment.yaml`,
121
- fs
122
- .readFileSync(`./manifests/deployment/dd-default-development/deployment.yaml`, 'utf8')
123
- .replaceAll(`underpost-engine:v${version}`, `underpost-engine:v${newVersion}`),
124
- 'utf8',
125
- );
126
-
127
- if (fs.existsSync(`./.github/workflows/docker-image.ci.yml`))
128
- fs.writeFileSync(
129
- `./.github/workflows/docker-image.ci.yml`,
130
- fs
131
- .readFileSync(`./.github/workflows/docker-image.ci.yml`, 'utf8')
132
- .replaceAll(`underpost-engine:v${version}`, `underpost-engine:v${newVersion}`),
133
- 'utf8',
134
- );
135
-
136
- // Update underpost/* image versions in all engine-*.cd.yml workflows.
137
- for (const wf of fs.readdirSync(`./.github/workflows`)) {
138
- if (!wf.match(/^engine-.+\.cd\.yml$/)) continue;
139
- const wfPath = `./.github/workflows/${wf}`;
140
- const updated = fs
141
- .readFileSync(wfPath, 'utf8')
142
- .replace(/underpost\/([^:'"]+):v[0-9]+\.[0-9]+\.[0-9]+/g, `underpost/$1:v${newVersion}`);
143
- fs.writeFileSync(wfPath, updated, 'utf8');
144
- }
145
-
146
- // Update version tag in all runtime docker image workflows (type=raw,value=v<version>).
147
- for (const wf of fs.readdirSync(`./.github/workflows`)) {
148
- if (!wf.match(/^docker-image\..+\.ci\.yml$/) || wf === 'docker-image.ci.yml') continue;
149
- const wfPath = `./.github/workflows/${wf}`;
150
- fs.writeFileSync(
151
- wfPath,
152
- fs.readFileSync(wfPath, 'utf8').replaceAll(`type=raw,value=v${version}`, `type=raw,value=v${newVersion}`),
153
- 'utf8',
154
- );
155
- }
156
-
157
- // Update image version in all conf.instances.json files for underpost/* images.
158
- if (fs.existsSync(`./engine-private/conf`)) {
159
- const confFiles = await fs.readdir(`./engine-private/conf`, { recursive: true });
160
- for (const relativePath of confFiles) {
161
- const filePath = `./engine-private/conf/${relativePath.replaceAll('\\', '/')}`;
162
- if (filePath.split('/').pop() !== 'conf.instances.json' || !fs.existsSync(filePath)) continue;
163
-
164
- let instances;
165
- try {
166
- instances = JSON.parse(fs.readFileSync(filePath, 'utf8'));
167
- } catch {
168
- logger.warn(`Skipping invalid JSON file: ${filePath}`);
169
- continue;
170
- }
171
-
172
- if (!Array.isArray(instances)) continue;
173
-
174
- let updated = false;
175
- for (const instance of instances) {
176
- if (!instance || typeof instance !== 'object') continue;
177
- if (!instance.image || typeof instance.image !== 'string') continue;
178
- if (!instance.image.startsWith('underpost/')) continue;
323
+ // ── Canonical version files: delegate to bumpp (package.json, package-lock.json,
324
+ // engine-private/conf/**/package.json). bumpp owns lockfile semantics (root version
325
+ // + packages[''].version) so we don't reimplement them. ──
326
+ const { versionBump } = await import('bumpp');
327
+ const bumppResult = await versionBump({
328
+ release: newVersion,
329
+ files: [
330
+ 'package.json',
331
+ 'package-lock.json',
332
+ ...(fs.existsSync('./engine-private/conf') ? ['engine-private/conf/**/package.json'] : []),
333
+ ],
334
+ commit: false,
335
+ tag: false,
336
+ push: false,
337
+ confirm: false,
338
+ recursive: false,
339
+ ignoreScripts: true,
340
+ dry: dryRun,
341
+ });
342
+ logger.info(`bumpp updated ${bumppResult?.files?.length ?? 0} canonical file(s).`);
179
343
 
180
- const baseImage = instance.image.split('@')[0].split(':')[0];
181
- const nextImage = `${baseImage}:v${newVersion}`;
182
- if (instance.image !== nextImage) {
183
- instance.image = nextImage;
184
- updated = true;
185
- }
186
- }
344
+ // ── Auxiliary files: anchored-regex walker over VERSION_BUMP_TARGETS. ──
345
+ const report = bumpAuxiliaryFiles(version, newVersion, { dryRun });
346
+ printBumpReport(report, { dryRun });
187
347
 
188
- if (updated) fs.writeFileSync(filePath, JSON.stringify(instances, null, 2), 'utf8');
189
- }
348
+ if (dryRun) {
349
+ logger.info('Dry-run complete. No files modified. Re-run without --dry-run to apply.');
350
+ return { from: version, to: newVersion, files: report.map((r) => r.file), dryRun: true };
190
351
  }
191
352
 
192
- fs.writeFileSync(
193
- `./src/index.js`,
194
- fs.readFileSync(`./src/index.js`, 'utf8').replaceAll(`${version}`, `${newVersion}`),
195
- 'utf8',
196
- );
353
+ // ── Downstream regenerations and manifest sync (unchanged from previous flow). ──
197
354
  shellExec(`node bin/deploy cli-docs ${version} ${newVersion}`);
198
355
  shellExec(`node bin/deploy update-dependencies`);
199
356
  shellExec(`node bin/build dd`);
@@ -206,6 +363,7 @@ class UnderpostRelease {
206
363
  shellExec(`sudo rm -rf ./engine-private/conf/dd-default`);
207
364
  shellExec(`node bin cron --kubeadm --setup-start --git`); // --apply
208
365
  shellExec(`node bin cmt --changelog-build`);
366
+ return { from: version, to: newVersion, files: report.map((r) => r.file), dryRun: false };
209
367
  },
210
368
 
211
369
  /**
@@ -1008,11 +1008,14 @@ Prevent build private config repo.`,
1008
1008
  const host = 'default.net';
1009
1009
  const path = '/';
1010
1010
  DefaultConf.server[host][path].valkey = {
1011
- port: 6379,
1012
- host: 'valkey-service.default.svc.cluster.local',
1011
+ port: 'env:VALKEY_PORT:int:6379',
1012
+ host: 'env:VALKEY_HOST:127.0.0.1',
1013
1013
  };
1014
- // mongodb-0.mongodb-service
1015
- DefaultConf.server[host][path].db.host = 'mongodb://mongodb-service:27017';
1014
+ DefaultConf.server[host][path].db.host = 'env:DB_HOST:mongodb://127.0.0.1:27017';
1015
+ DefaultConf.server[host][path].db.replicaSet = 'env:DB_REPLICA_SET:rs0';
1016
+ DefaultConf.server[host][path].db.authSource = 'env:DB_AUTH_SOURCE:admin';
1017
+ DefaultConf.server[host][path].db.user = 'env:DB_USER:';
1018
+ DefaultConf.server[host][path].db.password = 'env:DB_PASSWORD:';
1016
1019
  defaultConf = true;
1017
1020
  break;
1018
1021
  }
@@ -1295,7 +1298,7 @@ Prevent build private config repo.`,
1295
1298
  },
1296
1299
  /**
1297
1300
  * Checks whether a remote Git repository URL is reachable.
1298
- * Uses `git ls-remote` with `|| true` so the process always exits 0.
1301
+ * Uses `silentOnError` so a non-reachable remote returns false instead of throwing.
1299
1302
  * Injects `GITHUB_TOKEN` into GitHub HTTPS URLs when available.
1300
1303
  * @param {string} url - Full HTTPS clone URL to test (e.g. "https://github.com/org/repo.git").
1301
1304
  * @returns {boolean} `true` when the remote responded with at least one ref hash.
@@ -1305,10 +1308,11 @@ Prevent build private config repo.`,
1305
1308
  if (!url) return false;
1306
1309
  const authUrl = Underpost.repo.resolveAuthUrl(url);
1307
1310
  // GIT_TERMINAL_PROMPT=0 prevents git from hanging on credential prompts inside containers.
1308
- const raw = shellExec(`GIT_TERMINAL_PROMPT=0 git ls-remote "${authUrl}" HEAD 2>&1 || true`, {
1311
+ const raw = shellExec(`GIT_TERMINAL_PROMPT=0 git ls-remote "${authUrl}" HEAD 2>&1`, {
1309
1312
  stdout: true,
1310
1313
  silent: true,
1311
1314
  disableLog: true,
1315
+ silentOnError: true,
1312
1316
  });
1313
1317
  logger.info('isRemoteRepo', { url, raw: (raw || '').slice(0, 120) });
1314
1318
  return typeof raw === 'string' && /^[0-9a-f]{40}\t/m.test(raw);
@@ -1382,9 +1386,10 @@ Prevent build private config repo.`,
1382
1386
  shellExec(`cd "${repoPath}" && git config user.email '${gitEmail}'`);
1383
1387
 
1384
1388
  if (origin) {
1385
- const currentRemote = shellExec(`cd "${repoPath}" && git remote get-url origin 2>/dev/null || true`, {
1389
+ const currentRemote = shellExec(`cd "${repoPath}" && git remote get-url origin`, {
1386
1390
  stdout: true,
1387
1391
  silent: true,
1392
+ silentOnError: true,
1388
1393
  }).trim();
1389
1394
  if (!currentRemote) {
1390
1395
  shellExec(`cd "${repoPath}" && git remote add origin "${origin}"`);
@@ -1608,6 +1613,52 @@ Prevent build private config repo.`,
1608
1613
  logger.info('engine-private in /home/dd removed');
1609
1614
  }
1610
1615
  },
1616
+
1617
+ /**
1618
+ * Resolves the GitHub repository for a given instance runtime by scanning
1619
+ * every `conf.instances.json` listed in `./engine-private/deploy/dd.router`.
1620
+ *
1621
+ * Resolution order:
1622
+ * 1. If `runtime` is falsy, returns `${GITHUB_USERNAME}/engine`.
1623
+ * 2. Iterates each deploy ID found in `dd.router` and looks for an instance
1624
+ * whose `runtime` field matches the supplied value.
1625
+ * 3. When a match is found, returns `instance.metadata.repository`.
1626
+ * 4. Falls back to `${GITHUB_USERNAME}/engine` when no match is found.
1627
+ *
1628
+ * @param {string} [runtime=''] - The runtime identifier to look up (e.g. `'cyberia-server'`, `'cyberia-client'`).
1629
+ * @returns {string} The resolved `owner/repo` string.
1630
+ * @memberof UnderpostRepository
1631
+ */
1632
+ resolveInstanceRepo(runtime = '') {
1633
+ const fallback = `${process.env.GITHUB_USERNAME}/engine`;
1634
+ if (!runtime) return fallback;
1635
+ const ddRouter = './engine-private/deploy/dd.router';
1636
+ const deployIds = fs.existsSync(ddRouter)
1637
+ ? fs
1638
+ .readFileSync(ddRouter, 'utf8')
1639
+ .split(',')
1640
+ .map((s) => s.trim())
1641
+ .filter(Boolean)
1642
+ : [];
1643
+ for (const deployId of deployIds) {
1644
+ const confPath = `./engine-private/conf/${deployId}/conf.instances.json`;
1645
+ if (!fs.existsSync(confPath)) continue;
1646
+ try {
1647
+ const instances = JSON.parse(fs.readFileSync(confPath, 'utf8'));
1648
+ const match = instances.find((i) => i && i.runtime === runtime);
1649
+ if (match && match.metadata && match.metadata.repository) {
1650
+ logger.info(`[resolveInstanceRepo] resolved from ${confPath}`, {
1651
+ runtime,
1652
+ repo: match.metadata.repository,
1653
+ });
1654
+ return match.metadata.repository;
1655
+ }
1656
+ } catch (err) {
1657
+ logger.warn(`[resolveInstanceRepo] failed to parse ${confPath}: ${err.message}`);
1658
+ }
1659
+ }
1660
+ return fallback;
1661
+ },
1611
1662
  };
1612
1663
  }
1613
1664