@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
@@ -17,6 +17,10 @@ const path = require('path');
17
17
  * `builder/`) beside `config.yaml` (e.g. `$HOME/.aifabrix/builder` when `AIFABRIX_HOME=$HOME`).
18
18
  * When `aifabrix-home` relocates home outside the config tree, materialize under that home instead.
19
19
  *
20
+ * Uses a resolved-path prefix check (not `path.relative`) so "nested under home" is stable across
21
+ * platforms (e.g. sibling paths like `/var/aifabrix` vs `/var/aifabrix-config` must not be treated
22
+ * as nested).
23
+ *
20
24
  * @param {string} systemDir - Absolute config/state directory (same as `getAifabrixSystemDir()`).
21
25
  * @param {string} homeDir - Absolute AI Fabrix home (`getAifabrixHome()`).
22
26
  * @returns {string} Absolute directory whose `builder/` subdir is used
@@ -24,8 +28,12 @@ const path = require('path');
24
28
  function resolveSystemBuilderParentDir(systemDir, homeDir) {
25
29
  const s = path.resolve(systemDir);
26
30
  const h = path.resolve(homeDir);
27
- const rel = path.relative(h, s);
28
- const configUnderHome = rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
31
+ if (s === h) {
32
+ return s;
33
+ }
34
+ const sep = path.sep;
35
+ const homePrefix = h.endsWith(sep) ? h : `${h}${sep}`;
36
+ const configUnderHome = s.startsWith(homePrefix);
29
37
  return configUnderHome ? s : h;
30
38
  }
31
39
 
@@ -11,6 +11,26 @@
11
11
  const { expandFrontDoorHostPlaceholders } = require('./compose-generator');
12
12
  const { publishedHostPort, localHostPort } = require('./declarative-url-ports');
13
13
 
14
+ /**
15
+ * Public URL authorities that must use `http://` (browsers hit loopback without TLS in practice).
16
+ *
17
+ * @param {string} hostname - Parsed URL hostname
18
+ * @returns {boolean}
19
+ */
20
+ function isLocalhostPublicSchemeHostname(hostname) {
21
+ const h = String(hostname || '').trim().toLowerCase();
22
+ if (!h) {
23
+ return false;
24
+ }
25
+ if (h === 'localhost') {
26
+ return true;
27
+ }
28
+ if (h === '127.0.0.1') {
29
+ return true;
30
+ }
31
+ return h === '::1';
32
+ }
33
+
14
34
  /**
15
35
  * Expand frontDoorRouting.host placeholders for url:// (same rules as Traefik labels in compose-generator).
16
36
  *
@@ -31,6 +51,17 @@ function hostPortForProfile(profile, listenPort, developerIdNum) {
31
51
  : localHostPort(listenPort, developerIdNum);
32
52
  }
33
53
 
54
+ /**
55
+ * Scheme for `declarativePublicUrlsUseLocalhost` bases: always **http** for loopback reachability
56
+ * (ignore `tlsEnabled` / `remote-server` https — localhost URLs are not served with TLS in dev).
57
+ * @param {Object} opts - Kept for call-site compatibility (same shape as {@link computePublicUrlBaseString})
58
+ * @returns {'http'}
59
+ */
60
+ function schemeForDeclarativeLocalhostPublicBase(opts) {
61
+ void opts;
62
+ return 'http';
63
+ }
64
+
34
65
  /**
35
66
  * Local profile: workstation `+10` applies only to the app being resolved (`currentAppKey`).
36
67
  * Cross-app tokens (e.g. `url://keycloak-public` from miso-controller) use `publishedHostPort`
@@ -62,10 +93,10 @@ function resolveHostPortForDeclarativePublic(opts) {
62
93
  }
63
94
 
64
95
  /**
65
- * Without Traefik, `remote-server` is the dev machine host. Apps bind published host ports, not 443 + path.
66
- * If the URL already has an explicit port, keep host:port but **scheme follows `infraTlsEnabled`**, not the
67
- * literal `https://` in `remote-server` when TLS is off (`up-infra` without `--tls`).
68
- * If `remote-server` omits a port, append the profile-specific published/listen-derived port.
96
+ * Public authority from `remote-server` when expansion opts in (Traefik on, or per-app `proxy: true`
97
+ * `declarativePublicUrlsUseLocalhost === false`). If the URL already has an explicit port, keep host:port but
98
+ * **scheme follows `infraTlsEnabled`**, not the literal `https://` in `remote-server` when TLS is off
99
+ * (`up-infra` without `--tls`). If `remote-server` omits a port, append the profile-specific published/listen-derived port.
69
100
  *
70
101
  * @param {string} rawRemote
71
102
  * @param {'docker'|'local'} profile
@@ -85,7 +116,10 @@ function remotePublicBaseWithoutTraefik(
85
116
  const raw = String(rawRemote || '').trim().replace(/\/+$/, '');
86
117
  const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(raw) ? raw : (infraTlsEnabled ? `https://${raw}` : `http://${raw}`);
87
118
  const u = new URL(withScheme);
88
- const scheme = infraTlsEnabled ? 'https' : 'http';
119
+ let scheme = infraTlsEnabled ? 'https' : 'http';
120
+ if (isLocalhostPublicSchemeHostname(u.hostname)) {
121
+ scheme = 'http';
122
+ }
89
123
 
90
124
  if (u.port !== '') {
91
125
  return `${scheme}://${u.host}`;
@@ -105,17 +139,31 @@ function remotePublicBaseWithoutTraefik(
105
139
  return `${scheme}://${u.hostname}:${hostPort}`;
106
140
  }
107
141
 
142
+ /**
143
+ * True when `frontDoorRouting.host` depends on `remote-server` for expansion (`${REMOTE_HOST}`).
144
+ * Without a remote, expansion yields a bare `devNN` label (no DNS) — public `url://` bases must fall back to localhost.
145
+ *
146
+ * @param {string|null|undefined} hostTemplate
147
+ * @returns {boolean}
148
+ */
149
+ function templateUsesRemoteHostPlaceholder(hostTemplate) {
150
+ return /\$\{REMOTE_HOST\}/.test(String(hostTemplate || ''));
151
+ }
152
+
108
153
  /**
109
154
  * @param {Object} opts - same shape as computePublicUrlBaseString
110
155
  * @returns {string|null}
111
156
  */
112
157
  function buildTraefikPublicBaseIfApplicable(opts) {
113
- const { traefik, pathActive, hostTemplate, tls, developerIdRaw, remoteServer, infraTlsEnabled } =
158
+ const { traefik, pathActive, hostTemplate, developerIdRaw, remoteServer, infraTlsEnabled } =
114
159
  opts;
115
160
  // Plan 124: Traefik host authority only when pathActive (traefik ∧ frontDoorRouting.enabled)
116
161
  if (!traefik || !pathActive || !hostTemplate || !String(hostTemplate).trim()) {
117
162
  return null;
118
163
  }
164
+ if (templateUsesRemoteHostPlaceholder(hostTemplate) && !String(remoteServer || '').trim()) {
165
+ return null;
166
+ }
119
167
  const expanded = expandFrontDoorHostTemplateForUrls(hostTemplate, {
120
168
  developerIdRaw,
121
169
  remoteServer
@@ -123,8 +171,12 @@ function buildTraefikPublicBaseIfApplicable(opts) {
123
171
  if (!expanded) {
124
172
  return null;
125
173
  }
126
- const useHttps = Boolean(infraTlsEnabled) || tls !== false;
127
- const scheme = useHttps ? 'https' : 'http';
174
+ // Scheme follows global `tlsEnabled` (`up-infra --tls`) only — not `frontDoorRouting.tls` when infra TLS is off.
175
+ const useHttps = Boolean(infraTlsEnabled);
176
+ let scheme = useHttps ? 'https' : 'http';
177
+ if (isLocalhostPublicSchemeHostname(expanded)) {
178
+ scheme = 'http';
179
+ }
128
180
  return `${scheme}://${expanded}`.replace(/\/+$/, '');
129
181
  }
130
182
 
@@ -140,8 +192,9 @@ function buildTraefikPublicBaseIfApplicable(opts) {
140
192
  * @param {'docker'|'local'} opts.profile
141
193
  * @param {number} opts.listenPort
142
194
  * @param {number} opts.developerIdNum
143
- * @param {boolean} [opts.infraTlsEnabled] - `tlsEnabled` from ~/.aifabrix/config.yaml (`up-infra --tls`); when true, Traefik front-door public URLs use https even if application.yaml has `frontDoorRouting.tls: false`
195
+ * @param {boolean} [opts.infraTlsEnabled] - `tlsEnabled` from ~/.aifabrix/config.yaml (`up-infra --tls`); Traefik front-door public bases use **`https`** only when this is **true** (even if `frontDoorRouting.tls` is **false**). When **false**, front-door bases use **`http`** regardless of `frontDoorRouting.tls`. Loopback hostnames stay **http** always.
144
196
  * @param {boolean} [opts.pathActive] - traefik ∧ frontDoorRouting.enabled; required for Traefik host branch (plan 124)
197
+ * @param {boolean|undefined} [opts.declarativePublicUrlsUseLocalhost] - **`true`**: force localhost authority (+ port rules below). **`false`**: allow `remote-server` as authority when Traefik is on, or when global `traefik` is not `false` and per-app `proxy` opts in without Traefik. **`undefined`**: default — omit `remote-server` when Traefik is off so published services use `http://localhost:<port>` (e.g. `af setup` / device login URLs). When user config has **`traefik: false`**, expansion forces localhost for public bases regardless of per-app `proxy`.
145
198
  * @returns {string}
146
199
  */
147
200
  function computePublicUrlBaseString(opts) {
@@ -159,7 +212,23 @@ function computePublicUrlBaseString(opts) {
159
212
  return traefikBase;
160
213
  }
161
214
 
162
- if (remoteServer && String(remoteServer).trim()) {
215
+ if (opts.declarativePublicUrlsUseLocalhost) {
216
+ const hostPort = resolveHostPortForDeclarativePublic({
217
+ profile,
218
+ listenPort,
219
+ developerIdNum,
220
+ declarativeTargetAppKey: declarativePortOpts.declarativeTargetAppKey,
221
+ declarativeCurrentAppKey: declarativePortOpts.declarativeCurrentAppKey
222
+ });
223
+ const scheme = schemeForDeclarativeLocalhostPublicBase(opts);
224
+ return `${scheme}://localhost:${hostPort}`;
225
+ }
226
+
227
+ const remoteTrimmed = remoteServer && String(remoteServer).trim();
228
+ const useRemotePublicAuthority =
229
+ Boolean(remoteTrimmed) &&
230
+ (Boolean(opts.traefik) || opts.declarativePublicUrlsUseLocalhost === false);
231
+ if (useRemotePublicAuthority) {
163
232
  return remotePublicBaseWithoutTraefik(
164
233
  remoteServer,
165
234
  profile,
@@ -177,8 +246,7 @@ function computePublicUrlBaseString(opts) {
177
246
  declarativeTargetAppKey: declarativePortOpts.declarativeTargetAppKey,
178
247
  declarativeCurrentAppKey: declarativePortOpts.declarativeCurrentAppKey
179
248
  });
180
- const scheme = infraTlsEnabled ? 'https' : 'http';
181
- return `${scheme}://localhost:${hostPort}`;
249
+ return `http://localhost:${hostPort}`;
182
250
  }
183
251
 
184
252
  module.exports = {
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Public/internal URL string builders for declarative url:// (join + build*).
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
+ computePublicUrlBaseString,
13
+ resolveHostPortForDeclarativePublic
14
+ } = require('./url-declarative-public-base');
15
+
16
+ /**
17
+ * Join base URL origin with path segments (no duplicate slashes).
18
+ * @param {string} originOrBase - https://host or https://host:port
19
+ * @param {string} suffixPath - /dev/data
20
+ * @returns {string}
21
+ */
22
+ function joinUrlPath(originOrBase, suffixPath) {
23
+ const base = String(originOrBase || '').replace(/\/+$/, '');
24
+ let suf = suffixPath || '';
25
+ if (!suf.startsWith('/')) {
26
+ suf = `/${suf}`;
27
+ }
28
+ return `${base}${suf}`.replace(/([^:]\/)\/+/g, '$1');
29
+ }
30
+
31
+ /**
32
+ * @param {Object} opts
33
+ * @returns {string}
34
+ */
35
+ function buildPublicUrlString(opts) {
36
+ const {
37
+ profile,
38
+ listenPort,
39
+ developerIdNum,
40
+ remoteServer,
41
+ pathPrefix,
42
+ patternPath,
43
+ traefik,
44
+ hostTemplate,
45
+ tls,
46
+ developerIdRaw,
47
+ infraTlsEnabled,
48
+ frontDoorIngressActive,
49
+ declarativeTargetAppKey,
50
+ declarativeCurrentAppKey,
51
+ declarativePublicUrlsUseLocalhost
52
+ } = opts;
53
+ const base = computePublicUrlBaseString({
54
+ traefik: Boolean(traefik),
55
+ pathActive: Boolean(frontDoorIngressActive),
56
+ hostTemplate: hostTemplate || null,
57
+ tls: tls !== false,
58
+ developerIdRaw,
59
+ remoteServer,
60
+ profile,
61
+ listenPort,
62
+ developerIdNum,
63
+ infraTlsEnabled: Boolean(infraTlsEnabled),
64
+ declarativeTargetAppKey,
65
+ declarativeCurrentAppKey,
66
+ declarativePublicUrlsUseLocalhost
67
+ });
68
+ const patternSegment =
69
+ frontDoorIngressActive === false
70
+ ? ''
71
+ : patternPath === '/' || patternPath === ''
72
+ ? ''
73
+ : patternPath;
74
+ const rawSuffix = `${pathPrefix}${patternSegment}`.replace(/\/{2,}/g, '/');
75
+ if (!rawSuffix || rawSuffix === '/') {
76
+ return String(base).replace(/\/+$/, '');
77
+ }
78
+ const normalizedSuffix = rawSuffix.startsWith('/') ? rawSuffix : `/${rawSuffix}`;
79
+ return joinUrlPath(base, normalizedSuffix);
80
+ }
81
+
82
+ /**
83
+ * Public reachability origin only (no env path prefix, no front-door pattern path).
84
+ * @param {Object} opts
85
+ * @returns {string}
86
+ */
87
+ function buildPublicHostOriginString(opts) {
88
+ const {
89
+ profile,
90
+ listenPort,
91
+ developerIdNum,
92
+ remoteServer,
93
+ traefik,
94
+ hostTemplate,
95
+ tls,
96
+ developerIdRaw,
97
+ infraTlsEnabled,
98
+ frontDoorIngressActive,
99
+ declarativeTargetAppKey,
100
+ declarativeCurrentAppKey,
101
+ declarativePublicUrlsUseLocalhost
102
+ } = opts;
103
+ const base = computePublicUrlBaseString({
104
+ traefik: Boolean(traefik),
105
+ pathActive: Boolean(frontDoorIngressActive),
106
+ hostTemplate: hostTemplate || null,
107
+ tls: tls !== false,
108
+ developerIdRaw,
109
+ remoteServer,
110
+ profile,
111
+ listenPort,
112
+ developerIdNum,
113
+ infraTlsEnabled: Boolean(infraTlsEnabled),
114
+ declarativeTargetAppKey,
115
+ declarativeCurrentAppKey,
116
+ declarativePublicUrlsUseLocalhost
117
+ });
118
+ try {
119
+ return new URL(base).origin;
120
+ } catch {
121
+ const hostPort = resolveHostPortForDeclarativePublic({
122
+ profile,
123
+ listenPort,
124
+ developerIdNum,
125
+ declarativeTargetAppKey,
126
+ declarativeCurrentAppKey
127
+ });
128
+ return `http://localhost:${hostPort}`;
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Internal service origin only (scheme + host + port), no path suffix.
134
+ * @param {Object} opts
135
+ * @returns {string}
136
+ */
137
+ function buildInternalHostOriginString(opts) {
138
+ const {
139
+ profile,
140
+ listenPort,
141
+ targetAppKey,
142
+ remoteServer,
143
+ pathPrefix,
144
+ patternPath,
145
+ developerIdNum,
146
+ traefik,
147
+ hostTemplate,
148
+ tls,
149
+ developerIdRaw,
150
+ infraTlsEnabled,
151
+ frontDoorIngressActive,
152
+ declarativeTargetAppKey,
153
+ declarativeCurrentAppKey,
154
+ declarativePublicUrlsUseLocalhost
155
+ } = opts;
156
+ if (profile === 'docker') {
157
+ return `http://${targetAppKey}:${listenPort}`;
158
+ }
159
+ if (remoteServer && String(remoteServer).trim()) {
160
+ const pub = buildPublicUrlString({
161
+ profile: 'local',
162
+ listenPort,
163
+ developerIdNum,
164
+ remoteServer,
165
+ pathPrefix,
166
+ patternPath,
167
+ traefik,
168
+ hostTemplate,
169
+ tls,
170
+ developerIdRaw,
171
+ infraTlsEnabled,
172
+ frontDoorIngressActive,
173
+ declarativeTargetAppKey: declarativeTargetAppKey || targetAppKey,
174
+ declarativeCurrentAppKey: declarativeCurrentAppKey,
175
+ declarativePublicUrlsUseLocalhost
176
+ });
177
+ try {
178
+ return new URL(pub).origin;
179
+ } catch {
180
+ return `http://${targetAppKey}:${listenPort}`;
181
+ }
182
+ }
183
+ return `http://${targetAppKey}:${listenPort}`;
184
+ }
185
+
186
+ function buildInternalUrlString(opts) {
187
+ const {
188
+ profile,
189
+ listenPort,
190
+ targetAppKey,
191
+ runtimeBasePath,
192
+ remoteServer,
193
+ pathPrefix,
194
+ patternPath,
195
+ developerIdNum,
196
+ traefik,
197
+ hostTemplate,
198
+ tls,
199
+ developerIdRaw,
200
+ infraTlsEnabled,
201
+ frontDoorIngressActive,
202
+ declarativeTargetAppKey,
203
+ declarativeCurrentAppKey,
204
+ declarativePublicUrlsUseLocalhost
205
+ } = opts;
206
+ if (profile === 'docker') {
207
+ const origin = `http://${targetAppKey}:${listenPort}`;
208
+ return runtimeBasePath ? joinUrlPath(origin, runtimeBasePath) : origin;
209
+ }
210
+ if (remoteServer && String(remoteServer).trim()) {
211
+ return buildPublicUrlString({
212
+ profile: 'local',
213
+ listenPort,
214
+ developerIdNum,
215
+ remoteServer,
216
+ pathPrefix,
217
+ patternPath,
218
+ traefik,
219
+ hostTemplate,
220
+ tls,
221
+ developerIdRaw,
222
+ infraTlsEnabled,
223
+ frontDoorIngressActive,
224
+ declarativeTargetAppKey: declarativeTargetAppKey || targetAppKey,
225
+ declarativeCurrentAppKey: declarativeCurrentAppKey,
226
+ declarativePublicUrlsUseLocalhost
227
+ });
228
+ }
229
+ return `http://${targetAppKey}:${listenPort}`;
230
+ }
231
+
232
+ module.exports = {
233
+ joinUrlPath,
234
+ buildPublicUrlString,
235
+ buildPublicHostOriginString,
236
+ buildInternalUrlString,
237
+ buildInternalHostOriginString
238
+ };