@aifabrix/builder 2.44.6 → 2.45.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 (86) hide show
  1. package/.cursor/rules/cli-layout.mdc +7 -3
  2. package/jest.projects.js +56 -0
  3. package/lib/app/helpers.js +3 -3
  4. package/lib/app/index.js +3 -3
  5. package/lib/app/register.js +7 -6
  6. package/lib/app/restart-display.js +52 -21
  7. package/lib/app/rotate-secret.js +7 -6
  8. package/lib/app/run-helpers.js +15 -8
  9. package/lib/app/run.js +57 -9
  10. package/lib/app/show-display.js +7 -0
  11. package/lib/app/show.js +87 -5
  12. package/lib/build/index.js +9 -5
  13. package/lib/cli/infra-guided.js +42 -27
  14. package/lib/cli/installation-log-command.js +73 -0
  15. package/lib/cli/setup-app.js +11 -1
  16. package/lib/cli/setup-auth.js +94 -49
  17. package/lib/cli/setup-infra-up-dataplane-action.js +111 -0
  18. package/lib/cli/setup-infra-up-platform-action.js +131 -0
  19. package/lib/cli/setup-infra.js +60 -119
  20. package/lib/cli/setup-platform.js +1 -1
  21. package/lib/cli/setup-utility-resolve.js +132 -0
  22. package/lib/cli/setup-utility.js +65 -51
  23. package/lib/commands/app-logs.js +81 -33
  24. package/lib/commands/auth-config.js +116 -18
  25. package/lib/commands/setup-modes.js +19 -6
  26. package/lib/commands/setup-prompts.js +41 -8
  27. package/lib/commands/setup.js +114 -9
  28. package/lib/commands/teardown.js +54 -5
  29. package/lib/commands/up-common.js +48 -14
  30. package/lib/commands/up-dataplane.js +21 -18
  31. package/lib/commands/up-miso.js +12 -8
  32. package/lib/commands/upload.js +5 -3
  33. package/lib/core/audit-logger.js +1 -34
  34. package/lib/core/config-admin-email.js +56 -0
  35. package/lib/core/config-normalize.js +60 -0
  36. package/lib/core/config-registered-controller-urls.js +54 -0
  37. package/lib/core/config.js +33 -50
  38. package/lib/core/secrets-ensure-infra.js +1 -1
  39. package/lib/core/secrets-env-content.js +86 -90
  40. package/lib/core/secrets-env-declarative-expand.js +170 -0
  41. package/lib/core/secrets-env-write.js +2 -0
  42. package/lib/core/secrets-load.js +106 -102
  43. package/lib/external-system/deploy.js +5 -1
  44. package/lib/internal/node-fs.js +2 -0
  45. package/lib/schema/application-schema.json +4 -0
  46. package/lib/schema/infra.parameter.yaml +10 -0
  47. package/lib/utils/app-config-resolver.js +24 -1
  48. package/lib/utils/applications-config-defaults.js +206 -0
  49. package/lib/utils/auth-config-validator.js +2 -12
  50. package/lib/utils/bash-secret-env.js +1 -1
  51. package/lib/utils/compose-generate-docker-compose.js +111 -6
  52. package/lib/utils/compose-generator.js +17 -8
  53. package/lib/utils/controller-url.js +50 -7
  54. package/lib/utils/env-copy.js +99 -14
  55. package/lib/utils/env-template.js +5 -1
  56. package/lib/utils/health-check-url.js +18 -15
  57. package/lib/utils/health-check.js +7 -5
  58. package/lib/utils/infra-optional-service-flags.js +69 -0
  59. package/lib/utils/installation-log-core.js +282 -0
  60. package/lib/utils/installation-log-record.js +237 -0
  61. package/lib/utils/installation-log.js +123 -0
  62. package/lib/utils/log-redaction.js +105 -0
  63. package/lib/utils/manifest-location.js +164 -0
  64. package/lib/utils/manifest-source-emit.js +162 -0
  65. package/lib/utils/paths.js +238 -89
  66. package/lib/utils/remote-secrets-loader.js +7 -1
  67. package/lib/utils/run-cli-flags.js +29 -0
  68. package/lib/utils/secrets-canonical.js +10 -3
  69. package/lib/utils/secrets-path.js +3 -4
  70. package/lib/utils/secrets-utils.js +20 -10
  71. package/lib/utils/system-builder-root.js +10 -2
  72. package/lib/utils/url-declarative-public-base.js +80 -12
  73. package/lib/utils/url-declarative-resolve-build-urls.js +238 -0
  74. package/lib/utils/url-declarative-resolve-build.js +24 -393
  75. package/lib/utils/url-declarative-resolve-expand-token.js +189 -0
  76. package/lib/utils/url-declarative-resolve-load-doc.js +12 -3
  77. package/lib/utils/url-declarative-resolve-surface-state.js +102 -0
  78. package/lib/utils/url-declarative-resolve.js +47 -7
  79. package/lib/utils/url-declarative-runtime-base-path.js +21 -1
  80. package/lib/utils/urls-local-registry-scan.js +103 -0
  81. package/lib/utils/urls-local-registry.js +161 -90
  82. package/package.json +3 -1
  83. package/templates/applications/dataplane/application.yaml +4 -0
  84. package/templates/applications/miso-controller/application.yaml +2 -0
  85. package/templates/applications/miso-controller/env.template +27 -29
  86. package/.npmrc.token +0 -1
@@ -8,18 +8,16 @@
8
8
 
9
9
  'use strict';
10
10
 
11
- const { getRegistryEntryForApp } = require('./urls-local-registry');
11
+ const { getRegistryEntryForApp, readRegistryInternalDockerUseOriginOnly } = require('./urls-local-registry');
12
12
  const { DECLARATIVE_URL_INFRA_DEFAULTS } = require('./infra-env-defaults');
13
13
  const { getContainerPort, getLocalPort } = require('./port-resolver');
14
14
  const pathsUtil = require('./paths');
15
- const {
16
- computePublicUrlBaseString,
17
- resolveHostPortForDeclarativePublic
18
- } = require('./url-declarative-public-base');
19
- const { computePathActive } = require('./url-declarative-url-flags');
15
+ const { resolveDeclarativeUrlSurfaceState } = require('./url-declarative-resolve-surface-state');
20
16
  const { loadApplicationYamlDocForUrlResolve } = require('./url-declarative-resolve-load-doc');
21
17
  const { parseUrlToken } = require('./url-declarative-token-parse');
22
- const runtimeBasePath = require('./url-declarative-runtime-base-path');
18
+ const { readFrontDoorHostTlsFromDoc } = require('./url-declarative-runtime-base-path');
19
+ const buildUrls = require('./url-declarative-resolve-build-urls');
20
+ const expandToken = require('./url-declarative-resolve-expand-token');
23
21
 
24
22
  /**
25
23
  * Plan 122: **Developer subdomain `devNN` + remote hostname is not derived from envKey `tst`.** It comes only from
@@ -41,228 +39,6 @@ function applyTstRemoteDeveloperHostToOrigin(origin) {
41
39
  return origin;
42
40
  }
43
41
 
44
- /**
45
- * Join base URL origin with path segments (no duplicate slashes).
46
- * @param {string} originOrBase - https://host or https://host:port
47
- * @param {string} suffixPath - /dev/data
48
- * @returns {string}
49
- */
50
- function joinUrlPath(originOrBase, suffixPath) {
51
- const base = String(originOrBase || '').replace(/\/+$/, '');
52
- let suf = suffixPath || '';
53
- if (!suf.startsWith('/')) {
54
- suf = `/${suf}`;
55
- }
56
- return `${base}${suf}`.replace(/([^:]\/)\/+/g, '$1');
57
- }
58
-
59
- /**
60
- * @param {Object} opts
61
- * @returns {string}
62
- */
63
- function buildPublicUrlString(opts) {
64
- const {
65
- profile,
66
- listenPort,
67
- developerIdNum,
68
- remoteServer,
69
- pathPrefix,
70
- patternPath,
71
- traefik,
72
- hostTemplate,
73
- tls,
74
- developerIdRaw,
75
- infraTlsEnabled,
76
- frontDoorIngressActive,
77
- declarativeTargetAppKey,
78
- declarativeCurrentAppKey
79
- } = opts;
80
- const base = computePublicUrlBaseString({
81
- traefik: Boolean(traefik),
82
- pathActive: Boolean(frontDoorIngressActive),
83
- hostTemplate: hostTemplate || null,
84
- tls: tls !== false,
85
- developerIdRaw,
86
- remoteServer,
87
- profile,
88
- listenPort,
89
- developerIdNum,
90
- infraTlsEnabled: Boolean(infraTlsEnabled),
91
- declarativeTargetAppKey,
92
- declarativeCurrentAppKey
93
- });
94
- const patternSegment =
95
- frontDoorIngressActive === false
96
- ? ''
97
- : patternPath === '/' || patternPath === ''
98
- ? ''
99
- : patternPath;
100
- const rawSuffix = `${pathPrefix}${patternSegment}`.replace(/\/{2,}/g, '/');
101
- if (!rawSuffix || rawSuffix === '/') {
102
- return String(base).replace(/\/+$/, '');
103
- }
104
- const normalizedSuffix = rawSuffix.startsWith('/') ? rawSuffix : `/${rawSuffix}`;
105
- return joinUrlPath(base, normalizedSuffix);
106
- }
107
-
108
- /**
109
- * Public reachability origin only (no env path prefix, no front-door pattern path).
110
- * @param {Object} opts
111
- * @returns {string}
112
- */
113
- function buildPublicHostOriginString(opts) {
114
- const {
115
- profile,
116
- listenPort,
117
- developerIdNum,
118
- remoteServer,
119
- traefik,
120
- hostTemplate,
121
- tls,
122
- developerIdRaw,
123
- infraTlsEnabled,
124
- frontDoorIngressActive,
125
- declarativeTargetAppKey,
126
- declarativeCurrentAppKey
127
- } = opts;
128
- const base = computePublicUrlBaseString({
129
- traefik: Boolean(traefik),
130
- pathActive: Boolean(frontDoorIngressActive),
131
- hostTemplate: hostTemplate || null,
132
- tls: tls !== false,
133
- developerIdRaw,
134
- remoteServer,
135
- profile,
136
- listenPort,
137
- developerIdNum,
138
- infraTlsEnabled: Boolean(infraTlsEnabled),
139
- declarativeTargetAppKey,
140
- declarativeCurrentAppKey
141
- });
142
- try {
143
- return new URL(base).origin;
144
- } catch {
145
- const hostPort = resolveHostPortForDeclarativePublic({
146
- profile,
147
- listenPort,
148
- developerIdNum,
149
- declarativeTargetAppKey,
150
- declarativeCurrentAppKey
151
- });
152
- return `http://localhost:${hostPort}`;
153
- }
154
- }
155
-
156
- /**
157
- * Internal service origin only (scheme + host + port), no path suffix.
158
- * @param {Object} opts
159
- * @returns {string}
160
- */
161
- function buildInternalHostOriginString(opts) {
162
- const {
163
- profile,
164
- listenPort,
165
- targetAppKey,
166
- remoteServer,
167
- pathPrefix,
168
- patternPath,
169
- developerIdNum,
170
- traefik,
171
- hostTemplate,
172
- tls,
173
- developerIdRaw,
174
- infraTlsEnabled,
175
- frontDoorIngressActive,
176
- declarativeTargetAppKey,
177
- declarativeCurrentAppKey
178
- } = opts;
179
- if (profile === 'docker') {
180
- return `http://${targetAppKey}:${listenPort}`;
181
- }
182
- if (remoteServer && String(remoteServer).trim()) {
183
- const pub = buildPublicUrlString({
184
- profile: 'local',
185
- listenPort,
186
- developerIdNum,
187
- remoteServer,
188
- pathPrefix,
189
- patternPath,
190
- traefik,
191
- hostTemplate,
192
- tls,
193
- developerIdRaw,
194
- infraTlsEnabled,
195
- frontDoorIngressActive,
196
- declarativeTargetAppKey: declarativeTargetAppKey || targetAppKey,
197
- declarativeCurrentAppKey: declarativeCurrentAppKey
198
- });
199
- try {
200
- return new URL(pub).origin;
201
- } catch {
202
- return `http://${targetAppKey}:${listenPort}`;
203
- }
204
- }
205
- return `http://${targetAppKey}:${listenPort}`;
206
- }
207
-
208
- function buildInternalUrlString(opts) {
209
- const {
210
- profile,
211
- listenPort,
212
- targetAppKey,
213
- runtimeBasePath,
214
- remoteServer,
215
- pathPrefix,
216
- patternPath,
217
- developerIdNum,
218
- traefik,
219
- hostTemplate,
220
- tls,
221
- developerIdRaw,
222
- infraTlsEnabled,
223
- frontDoorIngressActive,
224
- declarativeTargetAppKey,
225
- declarativeCurrentAppKey
226
- } = opts;
227
- if (profile === 'docker') {
228
- const origin = `http://${targetAppKey}:${listenPort}`;
229
- return runtimeBasePath ? joinUrlPath(origin, runtimeBasePath) : origin;
230
- }
231
- if (remoteServer && String(remoteServer).trim()) {
232
- return buildPublicUrlString({
233
- profile: 'local',
234
- listenPort,
235
- developerIdNum,
236
- remoteServer,
237
- pathPrefix,
238
- patternPath,
239
- traefik,
240
- hostTemplate,
241
- tls,
242
- developerIdRaw,
243
- infraTlsEnabled,
244
- frontDoorIngressActive,
245
- declarativeTargetAppKey: declarativeTargetAppKey || targetAppKey,
246
- declarativeCurrentAppKey: declarativeCurrentAppKey
247
- });
248
- }
249
- return `http://${targetAppKey}:${listenPort}`;
250
- }
251
-
252
- /**
253
- * @param {object|null|undefined} doc
254
- * @returns {{ hostTemplate: string|null, tls: boolean }}
255
- */
256
- function readFrontDoorHostTlsFromDoc(doc) {
257
- if (!doc || !doc.frontDoorRouting) {
258
- return { hostTemplate: null, tls: true };
259
- }
260
- const fd = doc.frontDoorRouting;
261
- const hostTemplate =
262
- typeof fd.host === 'string' && fd.host.trim() ? fd.host.trim() : null;
263
- return { hostTemplate, tls: fd.tls !== false };
264
- }
265
-
266
42
  /**
267
43
  * Ports from application.yaml: `listenPort` = container/process listen ({@link getContainerPort});
268
44
  * `publicPortBasis` = manifest root `port` ({@link getLocalPort}) for browser-published host URLs
@@ -296,10 +72,11 @@ function resolveListenPortAndPatternFromDoc(doc, entry) {
296
72
 
297
73
  /**
298
74
  * Port + pattern + optional Traefik host template for a url:// token.
75
+ * `frontDoorIngressActive` and `perTokenTraefik` come from {@link resolveDeclarativeUrlSurfaceState}.
299
76
  * @param {string} token
300
77
  * @param {Object} ctx
301
78
  * @param {Object} registry
302
- * @returns {{ appKey: string, listenPort: number, publicPortBasis: number, patternStr: string, hostTemplate: string|null, tls: boolean, frontDoorIngressActive: boolean }|null}
79
+ * @returns {{ appKey: string, listenPort: number, publicPortBasis: number, patternStr: string, hostTemplate: string|null, tls: boolean, frontDoorIngressActive: boolean, perTokenTraefik: boolean, declarativeUrlSurfacePhase: string, internalDockerUseOriginOnly: boolean }|null}
303
80
  */
304
81
  function resolveListenPortPatternForToken(token, ctx, registry) {
305
82
  const { targetKey } = parseUrlToken(token);
@@ -316,10 +93,18 @@ function resolveListenPortPatternForToken(token, ctx, registry) {
316
93
  }
317
94
  const { listenPort, publicPortBasis, patternStr } = portPattern;
318
95
  const meta = readFrontDoorHostTlsFromDoc(doc);
319
- const frontDoorIngressActive = computePathActive(
320
- Boolean(ctx.traefik),
321
- Boolean(doc && doc.frontDoorRouting && doc.frontDoorRouting.enabled === true)
96
+ const fromRegistry = readRegistryInternalDockerUseOriginOnly(appKey, registry);
97
+ const internalDockerUseOriginOnly =
98
+ fromRegistry !== undefined ? fromRegistry : meta.internalDockerUseOriginOnly;
99
+ const frontDoorRoutingEnabled = Boolean(
100
+ doc && doc.frontDoorRouting && doc.frontDoorRouting.enabled === true
322
101
  );
102
+ const { phase, perTokenTraefik, frontDoorIngressActive } = resolveDeclarativeUrlSurfaceState({
103
+ userCfg: ctx.userCfg,
104
+ appKey,
105
+ ctxTraefik: ctx.traefik,
106
+ frontDoorRoutingEnabled
107
+ });
323
108
  return {
324
109
  appKey,
325
110
  listenPort,
@@ -327,172 +112,18 @@ function resolveListenPortPatternForToken(token, ctx, registry) {
327
112
  patternStr,
328
113
  hostTemplate: meta.hostTemplate,
329
114
  tls: meta.tls,
330
- frontDoorIngressActive
115
+ frontDoorIngressActive,
116
+ perTokenTraefik,
117
+ declarativeUrlSurfacePhase: phase,
118
+ internalDockerUseOriginOnly
331
119
  };
332
120
  }
333
121
 
334
- /**
335
- * Published/browser origin port basis for url:// public surfaces (manifest `port`), not container listen.
336
- * @param {Object} r
337
- * @returns {number}
338
- */
339
- function publishedOriginPortBasis(r) {
340
- const b = r.publicPortBasis;
341
- if (b !== undefined && b !== null && Number.isFinite(Number(b))) {
342
- return Number(b);
343
- }
344
- return r.listenPort;
345
- }
346
-
347
- /**
348
- * @param {Object} r - resolved app + patternPath + pathPrefix + remoteServer + profile + devNum + derivedEnvKey
349
- * @returns {string}
350
- */
351
- function expandHostSurfacePublic(r) {
352
- return buildPublicHostOriginString({
353
- profile: r.profile,
354
- listenPort: publishedOriginPortBasis(r),
355
- developerIdNum: r.devNum,
356
- remoteServer: r.remoteServer,
357
- traefik: r.traefik,
358
- hostTemplate: r.hostTemplate,
359
- tls: r.tls,
360
- developerIdRaw: r.developerIdRaw,
361
- infraTlsEnabled: r.infraTlsEnabled,
362
- frontDoorIngressActive: r.frontDoorIngressActive,
363
- declarativeTargetAppKey: r.appKey,
364
- declarativeCurrentAppKey: r.currentAppKey
365
- });
366
- }
367
-
368
- /**
369
- * Local workstation `.env` (no remote-server): browser-reachable URLs only — internal tokens match public.
370
- * Remote dev keeps internal = public mirroring via {@link buildInternalUrlString} when `remoteServer` is set.
371
- *
372
- * @param {Object} r
373
- * @returns {boolean}
374
- */
375
- function isLocalProfileWithoutRemoteServer(r) {
376
- return r.profile === 'local' && !String(r.remoteServer || '').trim();
377
- }
378
-
379
- /**
380
- * @param {Object} r
381
- * @returns {string}
382
- */
383
- function expandHostSurfaceInternal(r) {
384
- if (isLocalProfileWithoutRemoteServer(r)) {
385
- return expandHostSurfacePublic(r);
386
- }
387
- return buildInternalHostOriginString({
388
- profile: r.profile,
389
- listenPort: r.listenPort,
390
- targetAppKey: r.appKey,
391
- remoteServer: r.remoteServer,
392
- pathPrefix: r.pathPrefix,
393
- patternPath: r.patternPath,
394
- developerIdNum: r.devNum,
395
- derivedEnvKey: r.derivedEnvKey,
396
- traefik: r.traefik,
397
- hostTemplate: r.hostTemplate,
398
- tls: r.tls,
399
- developerIdRaw: r.developerIdRaw,
400
- infraTlsEnabled: r.infraTlsEnabled,
401
- frontDoorIngressActive: r.frontDoorIngressActive,
402
- declarativeTargetAppKey: r.appKey,
403
- declarativeCurrentAppKey: r.currentAppKey
404
- });
405
- }
406
-
407
- /**
408
- * @param {Object} r
409
- * @returns {string}
410
- */
411
- function expandFullSurfacePublic(r) {
412
- return buildPublicUrlString({
413
- profile: r.profile,
414
- listenPort: publishedOriginPortBasis(r),
415
- developerIdNum: r.devNum,
416
- remoteServer: r.remoteServer,
417
- pathPrefix: r.pathPrefix,
418
- patternPath: r.patternPath,
419
- traefik: r.traefik,
420
- hostTemplate: r.hostTemplate,
421
- tls: r.tls,
422
- developerIdRaw: r.developerIdRaw,
423
- infraTlsEnabled: r.infraTlsEnabled,
424
- frontDoorIngressActive: r.frontDoorIngressActive,
425
- declarativeTargetAppKey: r.appKey,
426
- declarativeCurrentAppKey: r.currentAppKey
427
- });
428
- }
429
-
430
- /**
431
- * @param {Object} r
432
- * @returns {string}
433
- */
434
- function expandFullSurfaceInternal(r) {
435
- if (isLocalProfileWithoutRemoteServer(r)) {
436
- return expandFullSurfacePublic(r);
437
- }
438
- const runtimePath = runtimeBasePath.normalizeRuntimeBasePath(r.patternPath, r);
439
- return buildInternalUrlString({
440
- profile: r.profile,
441
- listenPort: r.listenPort,
442
- targetAppKey: r.appKey,
443
- runtimeBasePath: runtimePath,
444
- remoteServer: r.remoteServer,
445
- pathPrefix: r.pathPrefix,
446
- patternPath: r.patternPath,
447
- developerIdNum: r.devNum,
448
- derivedEnvKey: r.derivedEnvKey,
449
- traefik: r.traefik,
450
- hostTemplate: r.hostTemplate,
451
- tls: r.tls,
452
- developerIdRaw: r.developerIdRaw,
453
- infraTlsEnabled: r.infraTlsEnabled,
454
- frontDoorIngressActive: r.frontDoorIngressActive,
455
- declarativeTargetAppKey: r.appKey,
456
- declarativeCurrentAppKey: r.currentAppKey
457
- });
458
- }
459
-
460
- /**
461
- * @param {{ kind: string, surface: string }} parsed
462
- * @param {Object} r
463
- * @returns {string}
464
- */
465
- function expandResolvedUrlToken(parsed, r) {
466
- if (parsed.surface === 'vdir') {
467
- if (!r.frontDoorIngressActive) {
468
- return '';
469
- }
470
- // Plan 124: docker PRIVATEVDIR is always empty; public vdir still carries pattern
471
- if (parsed.kind === 'internal' && r.profile === 'docker') {
472
- return '';
473
- }
474
- const prefix = String(r.pathPrefix || '');
475
- const pat = r.patternPath || '/';
476
- if (!pat || pat === '/') {
477
- return prefix || '';
478
- }
479
- const joined = `${prefix}${pat}`.replace(/\/{2,}/g, '/');
480
- return joined.startsWith('/') ? joined : `/${joined}`;
481
- }
482
- if (parsed.surface === 'host') {
483
- return parsed.kind === 'public' ? expandHostSurfacePublic(r) : expandHostSurfaceInternal(r);
484
- }
485
- return parsed.kind === 'public' ? expandFullSurfacePublic(r) : expandFullSurfaceInternal(r);
486
- }
487
-
488
122
  module.exports = {
489
123
  applyTstRemoteDeveloperHost,
490
124
  applyTstRemoteDeveloperHostToOrigin,
491
- buildPublicUrlString,
492
- buildPublicHostOriginString,
493
- buildInternalUrlString,
494
- buildInternalHostOriginString,
125
+ ...buildUrls,
126
+ ...expandToken,
495
127
  parseUrlToken,
496
- expandResolvedUrlToken,
497
128
  resolveListenPortPatternForToken
498
129
  };
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Surface expansion + {@link expandResolvedUrlToken} for declarative url:// tokens.
3
+ *
4
+ * @fileoverview Split from url-declarative-resolve-build.js for ESLint max-lines
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const {
12
+ buildPublicUrlString,
13
+ buildPublicHostOriginString,
14
+ buildInternalHostOriginString,
15
+ buildInternalUrlString
16
+ } = require('./url-declarative-resolve-build-urls');
17
+ const { normalizeRuntimeBasePath } = require('./url-declarative-runtime-base-path');
18
+
19
+ /**
20
+ * Published/browser origin port basis for url:// public surfaces (manifest `port`), not container listen.
21
+ * @param {Object} r
22
+ * @returns {number}
23
+ */
24
+ function publishedOriginPortBasis(r) {
25
+ const b = r.publicPortBasis;
26
+ if (b !== undefined && b !== null && Number.isFinite(Number(b))) {
27
+ return Number(b);
28
+ }
29
+ return r.listenPort;
30
+ }
31
+
32
+ /**
33
+ * @param {Object} r - resolved app + patternPath + pathPrefix + remoteServer + profile + devNum + derivedEnvKey
34
+ * @returns {string}
35
+ */
36
+ function expandHostSurfacePublic(r) {
37
+ return buildPublicHostOriginString({
38
+ profile: r.profile,
39
+ listenPort: publishedOriginPortBasis(r),
40
+ developerIdNum: r.devNum,
41
+ remoteServer: r.remoteServer,
42
+ traefik: r.traefik,
43
+ hostTemplate: r.hostTemplate,
44
+ tls: r.tls,
45
+ developerIdRaw: r.developerIdRaw,
46
+ infraTlsEnabled: r.infraTlsEnabled,
47
+ frontDoorIngressActive: r.frontDoorIngressActive,
48
+ declarativeTargetAppKey: r.appKey,
49
+ declarativeCurrentAppKey: r.currentAppKey,
50
+ declarativePublicUrlsUseLocalhost: r.declarativePublicUrlsUseLocalhost
51
+ });
52
+ }
53
+
54
+ /**
55
+ * Local workstation `.env` (no remote-server): browser-reachable URLs only — internal tokens match public.
56
+ * **Docker** profile must keep Docker DNS / service hostnames so resolved `.env` works **inside** containers.
57
+ *
58
+ * @param {Object} r
59
+ * @returns {boolean}
60
+ */
61
+ function isLocalProfileWithoutRemoteServer(r) {
62
+ return r.profile === 'local' && !String(r.remoteServer || '').trim();
63
+ }
64
+
65
+ /**
66
+ * @param {Object} r
67
+ * @returns {string}
68
+ */
69
+ function expandHostSurfaceInternal(r) {
70
+ if (isLocalProfileWithoutRemoteServer(r)) {
71
+ return expandHostSurfacePublic(r);
72
+ }
73
+ return buildInternalHostOriginString({
74
+ profile: r.profile,
75
+ listenPort: r.listenPort,
76
+ targetAppKey: r.appKey,
77
+ remoteServer: r.remoteServer,
78
+ pathPrefix: r.pathPrefix,
79
+ patternPath: r.patternPath,
80
+ developerIdNum: r.devNum,
81
+ derivedEnvKey: r.derivedEnvKey,
82
+ traefik: r.traefik,
83
+ hostTemplate: r.hostTemplate,
84
+ tls: r.tls,
85
+ developerIdRaw: r.developerIdRaw,
86
+ infraTlsEnabled: r.infraTlsEnabled,
87
+ frontDoorIngressActive: r.frontDoorIngressActive,
88
+ declarativeTargetAppKey: r.appKey,
89
+ declarativeCurrentAppKey: r.currentAppKey,
90
+ declarativePublicUrlsUseLocalhost: r.declarativePublicUrlsUseLocalhost
91
+ });
92
+ }
93
+
94
+ /**
95
+ * @param {Object} r
96
+ * @returns {string}
97
+ */
98
+ function expandFullSurfacePublic(r) {
99
+ return buildPublicUrlString({
100
+ profile: r.profile,
101
+ listenPort: publishedOriginPortBasis(r),
102
+ developerIdNum: r.devNum,
103
+ remoteServer: r.remoteServer,
104
+ pathPrefix: r.pathPrefix,
105
+ patternPath: r.patternPath,
106
+ traefik: r.traefik,
107
+ hostTemplate: r.hostTemplate,
108
+ tls: r.tls,
109
+ developerIdRaw: r.developerIdRaw,
110
+ infraTlsEnabled: r.infraTlsEnabled,
111
+ frontDoorIngressActive: r.frontDoorIngressActive,
112
+ declarativeTargetAppKey: r.appKey,
113
+ declarativeCurrentAppKey: r.currentAppKey,
114
+ declarativePublicUrlsUseLocalhost: r.declarativePublicUrlsUseLocalhost
115
+ });
116
+ }
117
+
118
+ /**
119
+ * @param {Object} r
120
+ * @returns {string}
121
+ */
122
+ function expandFullSurfaceInternal(r) {
123
+ if (isLocalProfileWithoutRemoteServer(r)) {
124
+ return expandFullSurfacePublic(r);
125
+ }
126
+ const useOriginOnly =
127
+ r.profile === 'docker' && Boolean(r.internalDockerUseOriginOnly);
128
+ const runtimePath = useOriginOnly
129
+ ? ''
130
+ : normalizeRuntimeBasePath(r.patternPath, r);
131
+ return buildInternalUrlString({
132
+ profile: r.profile,
133
+ listenPort: r.listenPort,
134
+ targetAppKey: r.appKey,
135
+ runtimeBasePath: runtimePath,
136
+ remoteServer: r.remoteServer,
137
+ pathPrefix: r.pathPrefix,
138
+ patternPath: r.patternPath,
139
+ developerIdNum: r.devNum,
140
+ derivedEnvKey: r.derivedEnvKey,
141
+ traefik: r.traefik,
142
+ hostTemplate: r.hostTemplate,
143
+ tls: r.tls,
144
+ developerIdRaw: r.developerIdRaw,
145
+ infraTlsEnabled: r.infraTlsEnabled,
146
+ frontDoorIngressActive: r.frontDoorIngressActive,
147
+ declarativeTargetAppKey: r.appKey,
148
+ declarativeCurrentAppKey: r.currentAppKey,
149
+ declarativePublicUrlsUseLocalhost: r.declarativePublicUrlsUseLocalhost
150
+ });
151
+ }
152
+
153
+ /**
154
+ * @param {{ kind: string, surface: string }} parsed
155
+ * @param {Object} r
156
+ * @returns {string}
157
+ */
158
+ function expandResolvedUrlToken(parsed, r) {
159
+ if (parsed.surface === 'vdir') {
160
+ if (!r.frontDoorIngressActive) {
161
+ return '';
162
+ }
163
+ // Plan 124: docker PRIVATEVDIR is always empty; public vdir still carries pattern
164
+ if (parsed.kind === 'internal' && r.profile === 'docker') {
165
+ return '';
166
+ }
167
+ const prefix = String(r.pathPrefix || '');
168
+ const pat = r.patternPath || '/';
169
+ if (!pat || pat === '/') {
170
+ return prefix || '';
171
+ }
172
+ const joined = `${prefix}${pat}`.replace(/\/{2,}/g, '/');
173
+ return joined.startsWith('/') ? joined : `/${joined}`;
174
+ }
175
+ if (parsed.surface === 'host') {
176
+ return parsed.kind === 'public' ? expandHostSurfacePublic(r) : expandHostSurfaceInternal(r);
177
+ }
178
+ return parsed.kind === 'public' ? expandFullSurfacePublic(r) : expandFullSurfaceInternal(r);
179
+ }
180
+
181
+ module.exports = {
182
+ publishedOriginPortBasis,
183
+ isLocalProfileWithoutRemoteServer,
184
+ expandHostSurfacePublic,
185
+ expandHostSurfaceInternal,
186
+ expandFullSurfacePublic,
187
+ expandFullSurfaceInternal,
188
+ expandResolvedUrlToken
189
+ };
@@ -29,11 +29,16 @@ function tryReadApplicationYamlAt(cfgPath) {
29
29
  * Prefer projectRoot/builder (fixtures, tests); then {@link pathsUtil.getBuilderPath} (global npm).
30
30
  * @param {string} appKey
31
31
  * @param {object} pathsUtil
32
+ * @param {string|null|undefined} [projectRootOverride] - When set (e.g. `ctx.projectRoot`), used instead of {@link pathsUtil.getProjectRoot} for the first candidate path (avoids flaky cross-test `paths` mocks).
32
33
  * @returns {string[]}
33
34
  */
34
- function collectApplicationYamlPathsForUrlResolve(appKey, pathsUtil) {
35
+ function collectApplicationYamlPathsForUrlResolve(appKey, pathsUtil, projectRootOverride) {
35
36
  const list = [];
36
- const root = pathsUtil.getProjectRoot();
37
+ const trimmed =
38
+ projectRootOverride !== undefined && projectRootOverride !== null
39
+ ? String(projectRootOverride).trim()
40
+ : '';
41
+ const root = trimmed || pathsUtil.getProjectRoot();
37
42
  if (root) {
38
43
  list.push(path.resolve(path.join(root, 'builder', appKey, 'application.yaml')));
39
44
  }
@@ -64,7 +69,11 @@ function loadApplicationYamlDocForUrlResolve(appKey, ctx, pathsUtil) {
64
69
  return fromVars;
65
70
  }
66
71
  }
67
- for (const cfgPath of collectApplicationYamlPathsForUrlResolve(appKey, pathsUtil)) {
72
+ const pr =
73
+ ctx && ctx.projectRoot !== undefined && ctx.projectRoot !== null
74
+ ? String(ctx.projectRoot).trim()
75
+ : '';
76
+ for (const cfgPath of collectApplicationYamlPathsForUrlResolve(appKey, pathsUtil, pr || undefined)) {
68
77
  const doc = tryReadApplicationYamlAt(cfgPath);
69
78
  if (doc) {
70
79
  return doc;