@aifabrix/builder 2.48.0 → 2.49.1
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/.cursor/skills/README.md +14 -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 +50 -12
- package/lib/cli/infra-guided-footers.js +164 -0
- package/lib/cli/infra-guided-spinners.js +57 -0
- package/lib/cli/infra-guided.js +52 -181
- 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/teardown.js +3 -5
- package/lib/commands/up-common.js +8 -11
- package/lib/commands/up-dataplane-bootstrap.js +149 -0
- package/lib/commands/up-dataplane-credentials.js +19 -4
- package/lib/commands/up-dataplane.js +155 -24
- package/lib/commands/up-miso.js +6 -2
- 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 +119 -0
- package/lib/utils/health-check-url.js +54 -34
- package/lib/utils/health-check.js +91 -35
- package/lib/utils/infra-optional-service-flags.js +31 -2
- package/lib/utils/mutagen.js +7 -4
- package/lib/utils/paths.js +26 -36
- 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/application.yaml +1 -1
- package/templates/applications/dataplane/env.template +24 -6
- package/templates/applications/dataplane/rbac.yaml +11 -0
- package/templates/applications/miso-controller/application.yaml +1 -1
- package/templates/applications/miso-controller/env.template +5 -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
|
@@ -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
|
}
|
|
@@ -311,31 +331,39 @@ async function performHealthCheckAttemptForUrls(urlsToTry, attempt, maxAttempts,
|
|
|
311
331
|
return { ok: false, resolvedIndex: -1 };
|
|
312
332
|
}
|
|
313
333
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
healthCheckPort,
|
|
322
|
-
config,
|
|
323
|
-
runOptions,
|
|
324
|
-
debug
|
|
325
|
-
);
|
|
326
|
-
|
|
327
|
-
if (skippedPublicHealthUrl && urlsToTry.length === 1) {
|
|
328
|
-
logger.log(
|
|
329
|
-
chalk.gray(
|
|
330
|
-
`ℹ Health check: public URL not used (DNS): ${skippedPublicHealthUrl}. ` +
|
|
331
|
-
`Probing ${urlsToTry[0]} only until the app responds.`
|
|
332
|
-
)
|
|
333
|
-
);
|
|
334
|
+
function resolveHealthCheckPollOptions(runOptions = {}) {
|
|
335
|
+
const pollOptions = {};
|
|
336
|
+
if (runOptions.silentPoll === true) {
|
|
337
|
+
pollOptions.silent = true;
|
|
338
|
+
}
|
|
339
|
+
if (typeof runOptions.onPollText === 'function') {
|
|
340
|
+
pollOptions.onPollText = runOptions.onPollText;
|
|
334
341
|
}
|
|
342
|
+
return pollOptions;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function pollUntilHealthCheckPasses({
|
|
346
|
+
urlsToTry,
|
|
347
|
+
maxAttempts,
|
|
348
|
+
timeout,
|
|
349
|
+
debug,
|
|
350
|
+
skippedPublicHealthUrl,
|
|
351
|
+
pollOptions = {}
|
|
352
|
+
}) {
|
|
353
|
+
const poll = createHealthCheckPollHandlers(maxAttempts, pollOptions);
|
|
354
|
+
const pollAttemptOpts = { suppressSuccessLog: true };
|
|
335
355
|
|
|
336
356
|
for (let attempts = 0; attempts < maxAttempts; attempts++) {
|
|
337
|
-
const attemptResult = await performHealthCheckAttemptForUrls(
|
|
357
|
+
const attemptResult = await performHealthCheckAttemptForUrls(
|
|
358
|
+
urlsToTry,
|
|
359
|
+
attempts,
|
|
360
|
+
maxAttempts,
|
|
361
|
+
debug,
|
|
362
|
+
pollAttemptOpts
|
|
363
|
+
);
|
|
338
364
|
if (attemptResult.ok) {
|
|
365
|
+
poll.finish();
|
|
366
|
+
logHealthCheckSuccessIfAllowed();
|
|
339
367
|
logPublicHealthUrlWarningIfNeeded({
|
|
340
368
|
skippedPublicHealthUrl,
|
|
341
369
|
urlsToTry,
|
|
@@ -345,23 +373,51 @@ async function waitForHealthCheck(appName, timeout = 90, port = null, config = n
|
|
|
345
373
|
}
|
|
346
374
|
|
|
347
375
|
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
|
-
);
|
|
376
|
+
poll.onWaiting(attempts);
|
|
355
377
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
356
378
|
}
|
|
357
379
|
}
|
|
358
380
|
|
|
381
|
+
poll.finish();
|
|
382
|
+
|
|
359
383
|
if (debug) {
|
|
360
384
|
logger.log(chalk.gray(`[DEBUG] Health check failed after ${maxAttempts} attempts`));
|
|
361
385
|
}
|
|
362
386
|
throw new Error(`Health check timeout after ${timeout} seconds`);
|
|
363
387
|
}
|
|
364
388
|
|
|
389
|
+
async function waitForHealthCheck(appName, timeout = 90, port = null, config = null, debug = false, runOptions = {}) {
|
|
390
|
+
await waitForDbInit(appName);
|
|
391
|
+
|
|
392
|
+
const healthCheckPort = await determineHealthCheckPort(port, appName, debug);
|
|
393
|
+
const { maxAttempts } = buildHealthCheckConfig(healthCheckPort, config, timeout, debug);
|
|
394
|
+
const { urlsToTry, skippedPublicHealthUrl } = await computePreferredHealthCheckUrls(
|
|
395
|
+
appName,
|
|
396
|
+
healthCheckPort,
|
|
397
|
+
config,
|
|
398
|
+
runOptions,
|
|
399
|
+
debug
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
if (skippedPublicHealthUrl && urlsToTry.length === 1 && debug) {
|
|
403
|
+
logger.log(
|
|
404
|
+
chalk.gray(
|
|
405
|
+
`ℹ Health check: public URL not used (DNS): ${skippedPublicHealthUrl}. ` +
|
|
406
|
+
`Probing ${urlsToTry[0]} only until the app responds.`
|
|
407
|
+
)
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
await pollUntilHealthCheckPasses({
|
|
412
|
+
urlsToTry,
|
|
413
|
+
maxAttempts,
|
|
414
|
+
timeout,
|
|
415
|
+
debug,
|
|
416
|
+
skippedPublicHealthUrl,
|
|
417
|
+
pollOptions: resolveHealthCheckPollOptions(runOptions)
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
365
421
|
/**
|
|
366
422
|
* Checks if port is available
|
|
367
423
|
* @param {number} port - Port number to check
|
|
@@ -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
|
|
package/lib/utils/paths.js
CHANGED
|
@@ -459,13 +459,23 @@ function isSystemBuilderAppName(appName) {
|
|
|
459
459
|
}
|
|
460
460
|
|
|
461
461
|
/**
|
|
462
|
-
*
|
|
462
|
+
* Home / work materialization path: `(aifabrix-work | aifabrix-home)/builder/<appName>`.
|
|
463
|
+
* Used by setup, teardown, and template copy — never the product-repo `cwd/builder` tree.
|
|
463
464
|
*
|
|
464
465
|
* @param {string} appName
|
|
465
466
|
* @returns {string}
|
|
466
467
|
*/
|
|
468
|
+
function getHomeBuilderAppPath(appName) {
|
|
469
|
+
return path.join(getAppsMaterializationParent(), 'builder', appName);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* @deprecated Prefer {@link getHomeBuilderAppPath}; same path.
|
|
474
|
+
* @param {string} appName
|
|
475
|
+
* @returns {string}
|
|
476
|
+
*/
|
|
467
477
|
function getProjectBuilderAppPath(appName) {
|
|
468
|
-
return
|
|
478
|
+
return getHomeBuilderAppPath(appName);
|
|
469
479
|
}
|
|
470
480
|
|
|
471
481
|
/**
|
|
@@ -630,38 +640,25 @@ function resolveBuildContext(configDir, buildContext) {
|
|
|
630
640
|
}
|
|
631
641
|
|
|
632
642
|
/**
|
|
633
|
-
* `cwd/builder/<appName>` when
|
|
643
|
+
* `cwd/builder/<appName>` when the running folder has a resolvable application manifest.
|
|
634
644
|
*
|
|
635
645
|
* @param {string} appName
|
|
636
646
|
* @returns {string|null}
|
|
637
647
|
*/
|
|
638
|
-
function
|
|
639
|
-
|
|
640
|
-
try {
|
|
641
|
-
if (!nodeFs().existsSync(cwdApp)) {
|
|
642
|
-
return null;
|
|
643
|
-
}
|
|
644
|
-
const st = nodeFs().statSync(cwdApp);
|
|
645
|
-
if (!st || typeof st.isDirectory !== 'function' || !st.isDirectory()) {
|
|
646
|
-
return null;
|
|
647
|
-
}
|
|
648
|
-
if (isSystemBuilderAppName(appName) && !isProjectBuilderAppDirectory(cwdApp)) {
|
|
649
|
-
return null;
|
|
650
|
-
}
|
|
651
|
-
return cwdApp;
|
|
652
|
-
} catch {
|
|
648
|
+
function getCwdBuilderAppPath(appName) {
|
|
649
|
+
if (!appName || typeof appName !== 'string') {
|
|
653
650
|
return null;
|
|
654
651
|
}
|
|
652
|
+
const cwdApp = path.join(path.resolve(process.cwd()), 'builder', appName);
|
|
653
|
+
return isProjectBuilderAppDirectory(cwdApp) ? cwdApp : null;
|
|
655
654
|
}
|
|
656
655
|
|
|
657
656
|
/**
|
|
658
|
-
* Gets the builder folder path:
|
|
659
|
-
* 1.
|
|
660
|
-
* 2. Else **`
|
|
661
|
-
*
|
|
662
|
-
*
|
|
663
|
-
* 3. Else **`(aifabrix-work or aifabrix-home)/builder/<appName>`** — used by setup / `up-platform` /
|
|
664
|
-
* `up-miso` / `up-dataplane` for template materialization.
|
|
657
|
+
* Gets the builder folder path for run/deploy/register:
|
|
658
|
+
* 1. **`cwd/builder/<app>`** when a resolvable application manifest exists in the running folder.
|
|
659
|
+
* 2. Else **`(aifabrix-work | aifabrix-home)/builder/<app>`** (setup materializes templates there).
|
|
660
|
+
*
|
|
661
|
+
* Teardown and setup `--force` clean use {@link getHomeBuilderAppPath} only (never product-repo trees).
|
|
665
662
|
*
|
|
666
663
|
* @param {string} appName - Application name
|
|
667
664
|
* @returns {string} Absolute path to builder directory
|
|
@@ -670,20 +667,11 @@ function getBuilderPath(appName) {
|
|
|
670
667
|
if (!appName || typeof appName !== 'string') {
|
|
671
668
|
throw new Error('App name is required and must be a string');
|
|
672
669
|
}
|
|
673
|
-
const
|
|
674
|
-
const manifestHit = resolveApplicationManifestPathSync({
|
|
675
|
-
targetKey: appName,
|
|
676
|
-
mode: 'auto',
|
|
677
|
-
cwd: process.cwd()
|
|
678
|
-
});
|
|
679
|
-
if (manifestHit && (manifestHit.tier === 'cwd-builder' || manifestHit.tier === 'cwd-integration')) {
|
|
680
|
-
return manifestHit.absolutePath;
|
|
681
|
-
}
|
|
682
|
-
const cwdHit = tryCwdBuilderPathOrNull(appName);
|
|
670
|
+
const cwdHit = getCwdBuilderAppPath(appName);
|
|
683
671
|
if (cwdHit) {
|
|
684
672
|
return cwdHit;
|
|
685
673
|
}
|
|
686
|
-
return
|
|
674
|
+
return getHomeBuilderAppPath(appName);
|
|
687
675
|
}
|
|
688
676
|
|
|
689
677
|
/**
|
|
@@ -918,6 +906,8 @@ module.exports = {
|
|
|
918
906
|
getIntegrationBuilderBaseDir,
|
|
919
907
|
SYSTEM_BUILDER_APP_KEYS,
|
|
920
908
|
isSystemBuilderAppName,
|
|
909
|
+
getHomeBuilderAppPath,
|
|
910
|
+
getCwdBuilderAppPath,
|
|
921
911
|
getProjectBuilderAppPath,
|
|
922
912
|
getSystemPlatformMaterializationParent,
|
|
923
913
|
getSystemBuilderRoot,
|
|
@@ -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;
|