@aifabrix/builder 2.48.0 → 2.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.temp/jest-protection-convert/run-IFN2nv/.protection/hubspot-companies.json +1 -0
- package/.temp/jest-protection-convert/run-IvmKLO/.protection/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-convert/run-SdvblF/.protection/hubspot-companies.json +1 -0
- package/.temp/jest-protection-convert/run-b51TsW/.protection/hubspot-companies.json +1 -0
- package/.temp/jest-protection-convert/run-fiLmb1/.protection/hubspot-companies.json +1 -0
- package/.temp/jest-protection-convert/run-qYGHMU/.protection/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-Mq5re8/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-f3u4YL/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-iQtd1O/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-mdmz5M/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-qbKYxQ/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-yzlJy6/hubspot-companies.yaml +17 -0
- package/.temp/validation/summary.txt +5 -0
- package/jest.isolated-projects.js +3 -0
- package/jest.projects.js +12 -0
- package/lib/api/applications.api.js +23 -0
- package/lib/api/dev.api.js +1 -1
- package/lib/api/index.js +5 -2
- package/lib/api/types/dev.types.js +3 -2
- package/lib/app/deploy-config.js +6 -4
- package/lib/app/register.js +1 -1
- package/lib/app/run-container-start.js +0 -9
- package/lib/app/run-helpers.js +8 -4
- package/lib/app/run-reload-sync.js +16 -18
- package/lib/app/run-stop-containers.js +48 -5
- package/lib/app/run.js +6 -3
- package/lib/app/show.js +2 -2
- package/lib/cli/infra-guided-auth.js +42 -6
- package/lib/cli/infra-guided-footers.js +164 -0
- package/lib/cli/infra-guided-spinners.js +57 -0
- package/lib/cli/infra-guided.js +41 -179
- package/lib/cli/setup-app.js +1 -1
- package/lib/cli/setup-dev.js +3 -2
- package/lib/cli/setup-infra.js +3 -2
- package/lib/cli/setup-platform.js +3 -0
- package/lib/commands/login-device.js +6 -2
- package/lib/commands/login.js +4 -1
- package/lib/commands/setup-modes.js +19 -3
- package/lib/commands/setup-platform-auth.js +25 -0
- package/lib/commands/setup-prompts-admin.js +9 -0
- package/lib/commands/setup.js +60 -8
- package/lib/commands/up-dataplane-bootstrap.js +149 -0
- package/lib/commands/up-dataplane-credentials.js +19 -4
- package/lib/commands/up-dataplane.js +74 -11
- package/lib/core/secrets-env-declarative-expand.js +35 -23
- package/lib/core/setup-platform-mode.js +43 -0
- package/lib/deployment/deployer-status.js +26 -3
- package/lib/deployment/deployer.js +7 -14
- package/lib/external-system/download-helpers.js +10 -1
- package/lib/infrastructure/compose.js +40 -12
- package/lib/infrastructure/services.js +15 -3
- package/lib/infrastructure/traefik-tls-dynamic.js +88 -0
- package/lib/utils/app-bootstrap-api.js +53 -0
- package/lib/utils/app-run-containers.js +14 -3
- package/lib/utils/applications-config-defaults.js +19 -3
- package/lib/utils/compose-generate-docker-compose.js +16 -5
- package/lib/utils/compose-generator.js +34 -15
- package/lib/utils/compose-traefik-ingress-base.js +13 -14
- package/lib/utils/config-paths.js +6 -1
- package/lib/utils/dev-user-groups.js +1 -0
- package/lib/utils/docker-daemon-tls-ca.js +94 -57
- package/lib/utils/docker-reload-mount.js +12 -1
- package/lib/utils/external-env-template.js +53 -18
- package/lib/utils/health-check-poll-ui.js +84 -0
- package/lib/utils/health-check-url.js +54 -34
- package/lib/utils/health-check.js +43 -16
- package/lib/utils/infra-optional-service-flags.js +31 -2
- package/lib/utils/mutagen.js +7 -4
- package/lib/utils/platform-controller-url.js +23 -1
- package/lib/utils/refresh-remote-settings.js +67 -0
- package/lib/utils/remote-host-for-front-door.js +43 -0
- package/lib/utils/run-cli-flags.js +37 -1
- package/lib/utils/url-declarative-public-base.js +11 -4
- package/lib/utils/url-declarative-resolve-build-urls.js +11 -2
- package/lib/utils/url-declarative-resolve-expand-token.js +12 -1
- package/lib/utils/url-declarative-resolve-load-doc.js +30 -11
- package/lib/utils/url-declarative-resolve.js +2 -0
- package/lib/utils/urls-local-registry-scan.js +95 -10
- package/lib/utils/urls-local-registry.js +24 -1
- package/lib/utils/worker-sidecars.js +2 -2
- package/lib/validation/external-system-auth-rules.js +45 -7
- package/package.json +2 -1
- package/scripts/test-dataplane-bootstrap.js +331 -0
- package/templates/applications/dataplane/env.template +22 -5
- package/templates/applications/dataplane/rbac.yaml +11 -0
- package/templates/applications/miso-controller/application.yaml +1 -1
- package/templates/applications/miso-controller/env.template +3 -0
- package/templates/infra/compose.yaml.hbs +13 -9
- package/templates/python/docker-compose.hbs +3 -0
- package/templates/typescript/docker-compose.hbs +3 -0
|
@@ -63,56 +63,76 @@ function frontDoorPattern(appConfig) {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
* Public app URL (Traefik + frontDoor mount path), without the health path — for CLI summaries.
|
|
67
|
-
* Requires infra Traefik on, `frontDoorRouting.enabled`, host, and pattern.
|
|
68
|
-
*
|
|
69
|
-
* @async
|
|
70
|
-
* @param {string} appName
|
|
71
|
-
* @param {number} healthCheckPort
|
|
72
66
|
* @param {Object|null} appConfig
|
|
73
|
-
* @returns {
|
|
67
|
+
* @returns {{ fd: Object, mountPath: string }|null}
|
|
74
68
|
*/
|
|
75
|
-
|
|
69
|
+
function readTraefikFrontDoorMount(appConfig) {
|
|
76
70
|
if (!appConfig || !frontDoorEnabled(appConfig)) return null;
|
|
77
71
|
const pattern = frontDoorPattern(appConfig);
|
|
78
72
|
if (!pattern) return null;
|
|
79
|
-
|
|
80
|
-
const coreConfig = require('../core/config');
|
|
81
|
-
const userCfg = await coreConfig.getConfig();
|
|
82
|
-
if (!(userCfg && userCfg.traefik)) return null;
|
|
83
|
-
|
|
84
73
|
const fd = appConfig.frontDoorRouting;
|
|
85
|
-
if (!fd || typeof fd !== 'object') return null;
|
|
86
74
|
const hostTemplate = typeof fd.host === 'string' ? fd.host.trim() : '';
|
|
87
75
|
if (!hostTemplate) return null;
|
|
76
|
+
return { fd, mountPath: normalizeFrontDoorPatternForHealth(pattern) };
|
|
77
|
+
}
|
|
88
78
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const
|
|
95
|
-
const
|
|
79
|
+
/**
|
|
80
|
+
* @param {Object} appConfig
|
|
81
|
+
* @returns {Promise<Object|null>}
|
|
82
|
+
*/
|
|
83
|
+
async function loadTraefikPublicUrlContext(appConfig) {
|
|
84
|
+
const coreConfig = require('../core/config');
|
|
85
|
+
const userCfg = await coreConfig.getConfig();
|
|
86
|
+
if (!(userCfg && userCfg.traefik)) return null;
|
|
87
|
+
const [remoteServer, developerRootDomain, developerIdRaw, setupPlatformMode] = await Promise.all([
|
|
88
|
+
coreConfig.getRemoteServer(),
|
|
89
|
+
coreConfig.getDeveloperRootDomain(),
|
|
90
|
+
coreConfig.getDeveloperId(),
|
|
91
|
+
coreConfig.getSetupPlatformMode()
|
|
92
|
+
]);
|
|
93
|
+
return {
|
|
94
|
+
infraTlsEnabled: Boolean(userCfg && userCfg.tlsEnabled),
|
|
95
|
+
remoteServer,
|
|
96
|
+
developerRootDomain,
|
|
97
|
+
developerIdRaw,
|
|
98
|
+
developerIdNum: parseDeveloperIdNum(developerIdRaw),
|
|
99
|
+
setupPlatformMode,
|
|
100
|
+
listenPort: Number(appConfig?.port || 3000)
|
|
101
|
+
};
|
|
102
|
+
}
|
|
96
103
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
104
|
+
/**
|
|
105
|
+
* Public app URL (Traefik + frontDoor mount path), without the health path — for CLI summaries.
|
|
106
|
+
* Requires infra Traefik on, `frontDoorRouting.enabled`, host, and pattern.
|
|
107
|
+
*
|
|
108
|
+
* @async
|
|
109
|
+
* @param {string} appName
|
|
110
|
+
* @param {number} healthCheckPort
|
|
111
|
+
* @param {Object|null} appConfig
|
|
112
|
+
* @returns {Promise<string|null>}
|
|
113
|
+
*/
|
|
114
|
+
async function computeTraefikPublicAppUrl(_appName, _healthCheckPort, appConfig) {
|
|
115
|
+
const frontDoor = readTraefikFrontDoorMount(appConfig);
|
|
116
|
+
if (!frontDoor) return null;
|
|
117
|
+
const ctx = await loadTraefikPublicUrlContext(appConfig);
|
|
118
|
+
if (!ctx) return null;
|
|
100
119
|
|
|
101
120
|
const publicBase = computePublicUrlBaseString({
|
|
102
121
|
traefik: true,
|
|
103
122
|
pathActive: true,
|
|
104
|
-
hostTemplate: fd.host,
|
|
105
|
-
tls: fd.tls,
|
|
106
|
-
developerIdRaw,
|
|
107
|
-
remoteServer,
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
123
|
+
hostTemplate: frontDoor.fd.host,
|
|
124
|
+
tls: frontDoor.fd.tls,
|
|
125
|
+
developerIdRaw: ctx.developerIdRaw,
|
|
126
|
+
remoteServer: ctx.remoteServer,
|
|
127
|
+
developerRootDomain: ctx.developerRootDomain,
|
|
128
|
+
profile: 'local',
|
|
129
|
+
listenPort: ctx.listenPort,
|
|
130
|
+
developerIdNum: ctx.developerIdNum,
|
|
131
|
+
infraTlsEnabled: ctx.infraTlsEnabled,
|
|
132
|
+
setupPlatformMode: ctx.setupPlatformMode
|
|
113
133
|
});
|
|
114
134
|
|
|
115
|
-
return joinUrlPath(publicBase, mountPath);
|
|
135
|
+
return joinUrlPath(publicBase, frontDoor.mountPath);
|
|
116
136
|
}
|
|
117
137
|
|
|
118
138
|
/**
|
|
@@ -24,6 +24,7 @@ const {
|
|
|
24
24
|
filterTraefikUrlByDns,
|
|
25
25
|
logPublicHealthUrlWarningIfNeeded
|
|
26
26
|
} = require('./health-check-public-warn');
|
|
27
|
+
const { createHealthCheckPollHandlers } = require('./health-check-poll-ui');
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
* Compute the health check URL for an app.
|
|
@@ -228,6 +229,13 @@ function buildHealthCheckConfig(healthCheckPort, config, timeout, debug) {
|
|
|
228
229
|
return { maxAttempts };
|
|
229
230
|
}
|
|
230
231
|
|
|
232
|
+
function logHealthCheckSuccessIfAllowed() {
|
|
233
|
+
const { isSetupQuietOutput } = require('./setup-quiet-output');
|
|
234
|
+
if (!isSetupQuietOutput()) {
|
|
235
|
+
logger.log(formatSuccessLine('Application is healthy'));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
231
239
|
/**
|
|
232
240
|
* Performs a single health check attempt
|
|
233
241
|
* @async
|
|
@@ -236,18 +244,24 @@ function buildHealthCheckConfig(healthCheckPort, config, timeout, debug) {
|
|
|
236
244
|
* @param {number} attempt - Attempt number
|
|
237
245
|
* @param {number} maxAttempts - Maximum attempts
|
|
238
246
|
* @param {boolean} debug - Debug flag
|
|
247
|
+
* @param {{ suppressSuccessLog?: boolean }} [attemptOpts]
|
|
239
248
|
* @returns {Promise<boolean>} True if health check passed
|
|
240
249
|
*/
|
|
241
|
-
async function performHealthCheckAttempt(
|
|
250
|
+
async function performHealthCheckAttempt(
|
|
251
|
+
healthCheckUrl,
|
|
252
|
+
attempt,
|
|
253
|
+
maxAttempts,
|
|
254
|
+
debug,
|
|
255
|
+
attemptOpts = {}
|
|
256
|
+
) {
|
|
242
257
|
try {
|
|
243
258
|
if (debug) {
|
|
244
259
|
logger.log(chalk.gray(`[DEBUG] Health check attempt ${attempt + 1}/${maxAttempts}`));
|
|
245
260
|
}
|
|
246
261
|
const healthCheckPassed = await checkHealthEndpoint(healthCheckUrl, debug);
|
|
247
262
|
if (healthCheckPassed) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
logger.log(formatSuccessLine('Application is healthy'));
|
|
263
|
+
if (!attemptOpts.suppressSuccessLog) {
|
|
264
|
+
logHealthCheckSuccessIfAllowed();
|
|
251
265
|
}
|
|
252
266
|
if (debug) {
|
|
253
267
|
logger.log(chalk.gray(`[DEBUG] Health check passed after ${attempt + 1} attempt(s)`));
|
|
@@ -287,7 +301,7 @@ async function computePreferredHealthCheckUrls(appName, healthCheckPort, config,
|
|
|
287
301
|
skippedPublicHealthUrl = filtered.skippedPublicHealthUrl;
|
|
288
302
|
|
|
289
303
|
const urlsToTry = traefikUrl ? [traefikUrl, localhostUrl] : [localhostUrl];
|
|
290
|
-
if (urlsToTry.length > 1) {
|
|
304
|
+
if (urlsToTry.length > 1 && debug) {
|
|
291
305
|
logger.log(
|
|
292
306
|
chalk.gray(
|
|
293
307
|
`ℹ Health check order: Traefik/DNS (${urlsToTry[0]}), then localhost (${urlsToTry[1]}).`
|
|
@@ -300,10 +314,16 @@ async function computePreferredHealthCheckUrls(appName, healthCheckPort, config,
|
|
|
300
314
|
return { urlsToTry, skippedPublicHealthUrl };
|
|
301
315
|
}
|
|
302
316
|
|
|
303
|
-
async function performHealthCheckAttemptForUrls(
|
|
317
|
+
async function performHealthCheckAttemptForUrls(
|
|
318
|
+
urlsToTry,
|
|
319
|
+
attempt,
|
|
320
|
+
maxAttempts,
|
|
321
|
+
debug,
|
|
322
|
+
attemptOpts = {}
|
|
323
|
+
) {
|
|
304
324
|
for (let i = 0; i < urlsToTry.length; i++) {
|
|
305
325
|
const url = urlsToTry[i];
|
|
306
|
-
const passed = await performHealthCheckAttempt(url, attempt, maxAttempts, debug);
|
|
326
|
+
const passed = await performHealthCheckAttempt(url, attempt, maxAttempts, debug, attemptOpts);
|
|
307
327
|
if (passed) {
|
|
308
328
|
return { ok: true, resolvedIndex: i };
|
|
309
329
|
}
|
|
@@ -324,7 +344,7 @@ async function waitForHealthCheck(appName, timeout = 90, port = null, config = n
|
|
|
324
344
|
debug
|
|
325
345
|
);
|
|
326
346
|
|
|
327
|
-
if (skippedPublicHealthUrl && urlsToTry.length === 1) {
|
|
347
|
+
if (skippedPublicHealthUrl && urlsToTry.length === 1 && debug) {
|
|
328
348
|
logger.log(
|
|
329
349
|
chalk.gray(
|
|
330
350
|
`ℹ Health check: public URL not used (DNS): ${skippedPublicHealthUrl}. ` +
|
|
@@ -333,9 +353,20 @@ async function waitForHealthCheck(appName, timeout = 90, port = null, config = n
|
|
|
333
353
|
);
|
|
334
354
|
}
|
|
335
355
|
|
|
356
|
+
const poll = createHealthCheckPollHandlers(maxAttempts);
|
|
357
|
+
const pollAttemptOpts = { suppressSuccessLog: true };
|
|
358
|
+
|
|
336
359
|
for (let attempts = 0; attempts < maxAttempts; attempts++) {
|
|
337
|
-
const attemptResult = await performHealthCheckAttemptForUrls(
|
|
360
|
+
const attemptResult = await performHealthCheckAttemptForUrls(
|
|
361
|
+
urlsToTry,
|
|
362
|
+
attempts,
|
|
363
|
+
maxAttempts,
|
|
364
|
+
debug,
|
|
365
|
+
pollAttemptOpts
|
|
366
|
+
);
|
|
338
367
|
if (attemptResult.ok) {
|
|
368
|
+
poll.finish();
|
|
369
|
+
logHealthCheckSuccessIfAllowed();
|
|
339
370
|
logPublicHealthUrlWarningIfNeeded({
|
|
340
371
|
skippedPublicHealthUrl,
|
|
341
372
|
urlsToTry,
|
|
@@ -345,17 +376,13 @@ async function waitForHealthCheck(appName, timeout = 90, port = null, config = n
|
|
|
345
376
|
}
|
|
346
377
|
|
|
347
378
|
if (attempts < maxAttempts - 1) {
|
|
348
|
-
|
|
349
|
-
urlsToTry.length > 1
|
|
350
|
-
? `trying ${urlsToTry[0]}, then ${urlsToTry[1]}`
|
|
351
|
-
: (urlsToTry[0] || 'health URL');
|
|
352
|
-
logger.log(
|
|
353
|
-
chalk.yellow(`Waiting for health check… (${attempts + 1}/${maxAttempts}) (${probeHint})`)
|
|
354
|
-
);
|
|
379
|
+
poll.onWaiting(attempts);
|
|
355
380
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
356
381
|
}
|
|
357
382
|
}
|
|
358
383
|
|
|
384
|
+
poll.finish();
|
|
385
|
+
|
|
359
386
|
if (debug) {
|
|
360
387
|
logger.log(chalk.gray(`[DEBUG] Health check failed after ${maxAttempts} attempts`));
|
|
361
388
|
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
11
|
const config = require('../core/config');
|
|
12
|
+
const { flagsForSetupPlatformMode } = require('../core/setup-platform-mode');
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Resolves effective boolean from CLI option vs config.
|
|
@@ -30,14 +31,40 @@ function resolveInfraOptionalFlag(optValue, cfgValue, defaultWhenUndef = true) {
|
|
|
30
31
|
* @param {Object} [options] - Commander options (omit for setup-modes)
|
|
31
32
|
* @returns {{ traefik: boolean, pgadmin: boolean, redisCommander: boolean }}
|
|
32
33
|
*/
|
|
34
|
+
function topologyFlagsForConfig(cfg) {
|
|
35
|
+
if (!cfg || !cfg.setupPlatformMode) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return flagsForSetupPlatformMode(cfg.setupPlatformMode);
|
|
39
|
+
}
|
|
40
|
+
|
|
33
41
|
function computeEffectiveInfraOptionalFlags(cfg, options = {}) {
|
|
42
|
+
const topology = topologyFlagsForConfig(cfg);
|
|
43
|
+
const traefikCandidate = resolveInfraOptionalFlag(options.traefik, cfg.traefik, false);
|
|
44
|
+
const traefik =
|
|
45
|
+
topology && !topology.traefik && options.traefik !== true ? false : traefikCandidate;
|
|
34
46
|
return {
|
|
35
|
-
traefik
|
|
47
|
+
traefik,
|
|
36
48
|
pgadmin: resolveInfraOptionalFlag(options.pgAdmin, cfg.pgadmin, true),
|
|
37
49
|
redisCommander: resolveInfraOptionalFlag(options.redisAdmin, cfg.redisCommander, true)
|
|
38
50
|
};
|
|
39
51
|
}
|
|
40
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Effective TLS flag for infra start: setup platform mode is authoritative when Traefik/TLS mode is off.
|
|
55
|
+
*
|
|
56
|
+
* @param {Object} cfg
|
|
57
|
+
* @param {Object} [options] - Commander options (`tls` / `--no-tls`)
|
|
58
|
+
* @returns {boolean}
|
|
59
|
+
*/
|
|
60
|
+
function resolveEffectiveTlsEnabled(cfg, options = {}) {
|
|
61
|
+
if (options.tls === true) return true;
|
|
62
|
+
if (options.tls === false) return false;
|
|
63
|
+
const topology = topologyFlagsForConfig(cfg);
|
|
64
|
+
if (topology && !topology.tlsEnabled) return false;
|
|
65
|
+
return cfg.tlsEnabled === true;
|
|
66
|
+
}
|
|
67
|
+
|
|
41
68
|
/**
|
|
42
69
|
* Writes `traefik` / `pgadmin` / `redisCommander` when missing so config matches compose defaults.
|
|
43
70
|
*
|
|
@@ -65,5 +92,7 @@ async function persistMissingInfraOptionalServiceFlags(cfg, effective) {
|
|
|
65
92
|
module.exports = {
|
|
66
93
|
resolveInfraOptionalFlag,
|
|
67
94
|
computeEffectiveInfraOptionalFlags,
|
|
68
|
-
|
|
95
|
+
resolveEffectiveTlsEnabled,
|
|
96
|
+
persistMissingInfraOptionalServiceFlags,
|
|
97
|
+
topologyFlagsForConfig
|
|
69
98
|
};
|
package/lib/utils/mutagen.js
CHANGED
|
@@ -104,10 +104,13 @@ function getSyncSshUrl(syncSshUser, syncSshHost, remotePath) {
|
|
|
104
104
|
* @returns {Promise<string[]>}
|
|
105
105
|
*/
|
|
106
106
|
async function listSyncSessionNames(mutagenPath) {
|
|
107
|
-
const { stdout } = await execAsync(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
const { stdout } = await execAsync(
|
|
108
|
+
`"${mutagenPath}" sync list --template '{{range .}}{{.Name}}{{"\\n"}}{{end}}'`,
|
|
109
|
+
{
|
|
110
|
+
encoding: 'utf8',
|
|
111
|
+
timeout: 5000
|
|
112
|
+
}
|
|
113
|
+
);
|
|
111
114
|
return (stdout || '').trim().split('\n').filter(Boolean);
|
|
112
115
|
}
|
|
113
116
|
|
|
@@ -43,6 +43,25 @@ function loadBundledApplicationVariables(appName) {
|
|
|
43
43
|
return variables && typeof variables === 'object' ? variables : null;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Merge platform routing defaults from bundled template when builder copy omits them.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} appName
|
|
50
|
+
* @param {Object} variables
|
|
51
|
+
* @returns {Object}
|
|
52
|
+
*/
|
|
53
|
+
function mergeBundledPlatformRoutingDefaults(appName, variables) {
|
|
54
|
+
const bundled = loadBundledApplicationVariables(appName);
|
|
55
|
+
if (!bundled || typeof bundled !== 'object') {
|
|
56
|
+
return variables;
|
|
57
|
+
}
|
|
58
|
+
const merged = { ...variables };
|
|
59
|
+
if (!merged.frontDoorRouting && bundled.frontDoorRouting) {
|
|
60
|
+
merged.frontDoorRouting = bundled.frontDoorRouting;
|
|
61
|
+
}
|
|
62
|
+
return merged;
|
|
63
|
+
}
|
|
64
|
+
|
|
46
65
|
/**
|
|
47
66
|
* Load platform app variables from materialized builder config, or from bundled template
|
|
48
67
|
* when `aifabrix setup` runs before `up-platform` has copied templates.
|
|
@@ -55,7 +74,8 @@ function loadAppVariables(appName) {
|
|
|
55
74
|
if (fs.existsSync(builderPath)) {
|
|
56
75
|
try {
|
|
57
76
|
const configPath = pathsUtil.resolveApplicationConfigPath(builderPath);
|
|
58
|
-
|
|
77
|
+
const fromBuilder = loadConfigFile(configPath) || {};
|
|
78
|
+
return mergeBundledPlatformRoutingDefaults(appName, fromBuilder);
|
|
59
79
|
} catch (err) {
|
|
60
80
|
const msg = err && err.message ? String(err.message) : '';
|
|
61
81
|
if (!msg.includes('Application config not found')) {
|
|
@@ -102,6 +122,7 @@ async function computeAppBaseUrl(appName) {
|
|
|
102
122
|
typeof developerIdRaw === 'string' ? parseInt(developerIdRaw, 10) : developerIdRaw;
|
|
103
123
|
const userCfg = await config.getConfig();
|
|
104
124
|
const remoteServer = await config.getRemoteServer();
|
|
125
|
+
const developerRootDomain = await config.getDeveloperRootDomain();
|
|
105
126
|
const infraTlsEnabled = Boolean(userCfg && userCfg.tlsEnabled);
|
|
106
127
|
const setupPlatformMode = await config.getSetupPlatformMode();
|
|
107
128
|
|
|
@@ -117,6 +138,7 @@ async function computeAppBaseUrl(appName) {
|
|
|
117
138
|
tls: fd ? fd.tls : true,
|
|
118
139
|
developerIdRaw,
|
|
119
140
|
remoteServer,
|
|
141
|
+
developerRootDomain,
|
|
120
142
|
profile: 'docker',
|
|
121
143
|
listenPort: basePort,
|
|
122
144
|
developerIdNum,
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Refresh ~/.aifabrix/config.yaml from GET /api/dev/settings when remote dev auth is available.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Merge builder-server settings (developer-root-domain, sync paths, etc.)
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const chalk = require('chalk');
|
|
12
|
+
const config = require('../core/config');
|
|
13
|
+
const devApi = require('../api/dev.api');
|
|
14
|
+
const { getRemoteDevAuth } = require('./remote-dev-auth');
|
|
15
|
+
const { getCertDir, readClientKeyPem } = require('./dev-cert-helper');
|
|
16
|
+
const { getConfigDirForPaths } = require('./paths');
|
|
17
|
+
const logger = require('./logger');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {Object} RefreshRemoteSettingsResult
|
|
21
|
+
* @property {boolean} refreshed
|
|
22
|
+
* @property {string} [reason]
|
|
23
|
+
* @property {Object} [settings]
|
|
24
|
+
* @property {Error} [error]
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Fetch GET /api/dev/settings and merge into config when remote-server + client cert exist.
|
|
29
|
+
*
|
|
30
|
+
* @async
|
|
31
|
+
* @param {{ quiet?: boolean }} [opts]
|
|
32
|
+
* @returns {Promise<RefreshRemoteSettingsResult>}
|
|
33
|
+
*/
|
|
34
|
+
async function refreshRemoteSettingsFromBuilderIfPossible(opts = {}) {
|
|
35
|
+
const quiet = opts.quiet !== false;
|
|
36
|
+
const auth = await getRemoteDevAuth();
|
|
37
|
+
if (!auth) {
|
|
38
|
+
return { refreshed: false, reason: 'no-remote-auth' };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const devId = await config.getDeveloperId();
|
|
42
|
+
const certDir = getCertDir(getConfigDirForPaths(), devId);
|
|
43
|
+
const clientKeyPem = readClientKeyPem(certDir);
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const settings = await devApi.getSettings(
|
|
47
|
+
auth.serverUrl,
|
|
48
|
+
auth.clientCertPem,
|
|
49
|
+
clientKeyPem || undefined,
|
|
50
|
+
auth.serverCaPem || undefined
|
|
51
|
+
);
|
|
52
|
+
await config.mergeRemoteSettings(settings);
|
|
53
|
+
if (!quiet) {
|
|
54
|
+
logger.log(chalk.gray('Synced developer settings from Builder Server'));
|
|
55
|
+
}
|
|
56
|
+
return { refreshed: true, settings };
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (!quiet) {
|
|
59
|
+
logger.warn(
|
|
60
|
+
chalk.yellow(`Could not refresh settings from Builder Server: ${error.message}`)
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
return { refreshed: false, reason: 'fetch-failed', error };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = { refreshRemoteSettingsFromBuilderIfPossible };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve hostname for `${REMOTE_HOST}` in frontDoorRouting.host templates.
|
|
3
|
+
* Prefers `developer-root-domain` from builder-server settings over `remote-server` hostname.
|
|
4
|
+
*
|
|
5
|
+
* @fileoverview App public hostname root (separate from builder API host)
|
|
6
|
+
* @author AI Fabrix Team
|
|
7
|
+
* @version 1.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const { hostnameFromUrl } = require('./config-paths');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Hostname for `${REMOTE_HOST}` expansion (Traefik labels, url:// public, setup health).
|
|
16
|
+
*
|
|
17
|
+
* @param {Object} [input]
|
|
18
|
+
* @param {string|null|undefined} input.remoteServer - Builder API base URL
|
|
19
|
+
* @param {string|null|undefined} input.developerRootDomain - App hostname root from GET /api/dev/settings
|
|
20
|
+
* @returns {string}
|
|
21
|
+
*/
|
|
22
|
+
function resolveRemoteHostForFrontDoor(input = {}) {
|
|
23
|
+
const root = String(input.developerRootDomain || '').trim();
|
|
24
|
+
if (root) {
|
|
25
|
+
return root.replace(/^\.+|\.+$/g, '');
|
|
26
|
+
}
|
|
27
|
+
return hostnameFromUrl(input.remoteServer) || '';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Whether front-door host expansion can resolve `${REMOTE_HOST}` (root domain or remote-server).
|
|
32
|
+
*
|
|
33
|
+
* @param {Object} [input]
|
|
34
|
+
* @returns {boolean}
|
|
35
|
+
*/
|
|
36
|
+
function hasFrontDoorRemoteHostContext(input = {}) {
|
|
37
|
+
return Boolean(resolveRemoteHostForFrontDoor(input));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = {
|
|
41
|
+
resolveRemoteHostForFrontDoor,
|
|
42
|
+
hasFrontDoorRemoteHostContext
|
|
43
|
+
};
|
|
@@ -24,6 +24,42 @@ function isRunCliNoProxy(options) {
|
|
|
24
24
|
return options.noProxy === true;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Whether `aifabrix run` should persist `applications.<app>.proxy: true`.
|
|
29
|
+
* Proxy / front-door URL hints require infra Traefik (`traefik: true` in config).
|
|
30
|
+
* Direct-port (`setupPlatformMode: single`) always persists false even though Commander defaults `--proxy` on.
|
|
31
|
+
*
|
|
32
|
+
* @param {Object} [options] - Commander action options
|
|
33
|
+
* @param {Object|null|undefined} [userCfg] - Parsed `~/.aifabrix/config.yaml`
|
|
34
|
+
* @returns {boolean}
|
|
35
|
+
*/
|
|
36
|
+
function resolveRunProxyPersistEnabled(options, userCfg) {
|
|
37
|
+
if (isRunCliNoProxy(options)) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
return Boolean(userCfg && userCfg.traefik === true);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Effective no-proxy for env/url expansion during run.
|
|
45
|
+
*
|
|
46
|
+
* @param {Object} [options]
|
|
47
|
+
* @param {Object|null|undefined} [userCfg]
|
|
48
|
+
* @param {boolean} savedProxyHint - `applications.<app>.proxy` from config
|
|
49
|
+
* @returns {boolean}
|
|
50
|
+
*/
|
|
51
|
+
function resolveRunEffectiveNoProxy(options, userCfg, savedProxyHint) {
|
|
52
|
+
if (isRunCliNoProxy(options)) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
if (!userCfg || userCfg.traefik !== true) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
return !savedProxyHint;
|
|
59
|
+
}
|
|
60
|
+
|
|
27
61
|
module.exports = {
|
|
28
|
-
isRunCliNoProxy
|
|
62
|
+
isRunCliNoProxy,
|
|
63
|
+
resolveRunProxyPersistEnabled,
|
|
64
|
+
resolveRunEffectiveNoProxy
|
|
29
65
|
};
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
11
|
const { expandFrontDoorHostPlaceholders } = require('./compose-generator');
|
|
12
|
+
const { hasFrontDoorRemoteHostContext } = require('./remote-host-for-front-door');
|
|
12
13
|
const { publishedHostPort, localHostPort } = require('./declarative-url-ports');
|
|
13
14
|
const {
|
|
14
15
|
normalizeSetupPlatformMode,
|
|
@@ -23,11 +24,12 @@ const { isLocalhostPublicSchemeHostname } = require('./localhost-public-host');
|
|
|
23
24
|
* @param {Object} opts
|
|
24
25
|
* @param {string|number|null|undefined} opts.developerIdRaw
|
|
25
26
|
* @param {string|null|undefined} opts.remoteServer
|
|
27
|
+
* @param {string|null|undefined} [opts.developerRootDomain]
|
|
26
28
|
* @returns {string}
|
|
27
29
|
*/
|
|
28
30
|
function expandFrontDoorHostTemplateForUrls(template, opts) {
|
|
29
|
-
const { developerIdRaw, remoteServer } = opts || {};
|
|
30
|
-
return expandFrontDoorHostPlaceholders(template, developerIdRaw, remoteServer);
|
|
31
|
+
const { developerIdRaw, remoteServer, developerRootDomain } = opts || {};
|
|
32
|
+
return expandFrontDoorHostPlaceholders(template, developerIdRaw, remoteServer, developerRootDomain);
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
function hostPortForProfile(profile, listenPort, developerIdNum) {
|
|
@@ -174,6 +176,7 @@ function buildTraefikPublicBaseIfApplicable(opts) {
|
|
|
174
176
|
hostTemplate,
|
|
175
177
|
developerIdRaw,
|
|
176
178
|
remoteServer,
|
|
179
|
+
developerRootDomain,
|
|
177
180
|
profile,
|
|
178
181
|
developerIdNum
|
|
179
182
|
} = opts;
|
|
@@ -181,7 +184,10 @@ function buildTraefikPublicBaseIfApplicable(opts) {
|
|
|
181
184
|
if (!traefik || !pathActive || !hostTemplate || !String(hostTemplate).trim()) {
|
|
182
185
|
return null;
|
|
183
186
|
}
|
|
184
|
-
if (
|
|
187
|
+
if (
|
|
188
|
+
templateUsesRemoteHostPlaceholder(hostTemplate) &&
|
|
189
|
+
!hasFrontDoorRemoteHostContext({ remoteServer, developerRootDomain })
|
|
190
|
+
) {
|
|
185
191
|
// Local full-platform (no remote domain): use the single localhost front door port (3000 + devId×100).
|
|
186
192
|
return localhostFrontDoorBaseForMissingRemote({
|
|
187
193
|
profile,
|
|
@@ -190,7 +196,8 @@ function buildTraefikPublicBaseIfApplicable(opts) {
|
|
|
190
196
|
}
|
|
191
197
|
const expanded = expandFrontDoorHostTemplateForUrls(hostTemplate, {
|
|
192
198
|
developerIdRaw,
|
|
193
|
-
remoteServer
|
|
199
|
+
remoteServer,
|
|
200
|
+
developerRootDomain
|
|
194
201
|
});
|
|
195
202
|
if (!expanded) {
|
|
196
203
|
return null;
|
|
@@ -12,6 +12,7 @@ const {
|
|
|
12
12
|
computePublicUrlBaseString,
|
|
13
13
|
resolveHostPortForDeclarativePublic
|
|
14
14
|
} = require('./url-declarative-public-base');
|
|
15
|
+
const { hasFrontDoorRemoteHostContext } = require('./remote-host-for-front-door');
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Join base URL origin with path segments (no duplicate slashes).
|
|
@@ -38,6 +39,7 @@ function buildPublicUrlString(opts) {
|
|
|
38
39
|
listenPort,
|
|
39
40
|
developerIdNum,
|
|
40
41
|
remoteServer,
|
|
42
|
+
developerRootDomain,
|
|
41
43
|
pathPrefix,
|
|
42
44
|
patternPath,
|
|
43
45
|
traefik,
|
|
@@ -57,6 +59,7 @@ function buildPublicUrlString(opts) {
|
|
|
57
59
|
tls: tls !== false,
|
|
58
60
|
developerIdRaw,
|
|
59
61
|
remoteServer,
|
|
62
|
+
developerRootDomain,
|
|
60
63
|
profile,
|
|
61
64
|
listenPort,
|
|
62
65
|
developerIdNum,
|
|
@@ -90,6 +93,7 @@ function buildPublicHostOriginString(opts) {
|
|
|
90
93
|
listenPort,
|
|
91
94
|
developerIdNum,
|
|
92
95
|
remoteServer,
|
|
96
|
+
developerRootDomain,
|
|
93
97
|
traefik,
|
|
94
98
|
hostTemplate,
|
|
95
99
|
tls,
|
|
@@ -107,6 +111,7 @@ function buildPublicHostOriginString(opts) {
|
|
|
107
111
|
tls: tls !== false,
|
|
108
112
|
developerIdRaw,
|
|
109
113
|
remoteServer,
|
|
114
|
+
developerRootDomain,
|
|
110
115
|
profile,
|
|
111
116
|
listenPort,
|
|
112
117
|
developerIdNum,
|
|
@@ -140,6 +145,7 @@ function buildInternalHostOriginString(opts) {
|
|
|
140
145
|
listenPort,
|
|
141
146
|
targetAppKey,
|
|
142
147
|
remoteServer,
|
|
148
|
+
developerRootDomain,
|
|
143
149
|
pathPrefix,
|
|
144
150
|
patternPath,
|
|
145
151
|
developerIdNum,
|
|
@@ -156,12 +162,13 @@ function buildInternalHostOriginString(opts) {
|
|
|
156
162
|
if (profile === 'docker') {
|
|
157
163
|
return `http://${targetAppKey}:${listenPort}`;
|
|
158
164
|
}
|
|
159
|
-
if (remoteServer
|
|
165
|
+
if (hasFrontDoorRemoteHostContext({ remoteServer, developerRootDomain })) {
|
|
160
166
|
const pub = buildPublicUrlString({
|
|
161
167
|
profile: 'local',
|
|
162
168
|
listenPort,
|
|
163
169
|
developerIdNum,
|
|
164
170
|
remoteServer,
|
|
171
|
+
developerRootDomain,
|
|
165
172
|
pathPrefix,
|
|
166
173
|
patternPath,
|
|
167
174
|
traefik,
|
|
@@ -190,6 +197,7 @@ function buildInternalUrlString(opts) {
|
|
|
190
197
|
targetAppKey,
|
|
191
198
|
runtimeBasePath,
|
|
192
199
|
remoteServer,
|
|
200
|
+
developerRootDomain,
|
|
193
201
|
pathPrefix,
|
|
194
202
|
patternPath,
|
|
195
203
|
developerIdNum,
|
|
@@ -207,12 +215,13 @@ function buildInternalUrlString(opts) {
|
|
|
207
215
|
const origin = `http://${targetAppKey}:${listenPort}`;
|
|
208
216
|
return runtimeBasePath ? joinUrlPath(origin, runtimeBasePath) : origin;
|
|
209
217
|
}
|
|
210
|
-
if (remoteServer
|
|
218
|
+
if (hasFrontDoorRemoteHostContext({ remoteServer, developerRootDomain })) {
|
|
211
219
|
return buildPublicUrlString({
|
|
212
220
|
profile: 'local',
|
|
213
221
|
listenPort,
|
|
214
222
|
developerIdNum,
|
|
215
223
|
remoteServer,
|
|
224
|
+
developerRootDomain,
|
|
216
225
|
pathPrefix,
|
|
217
226
|
patternPath,
|
|
218
227
|
traefik,
|