@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
|
@@ -51,6 +51,68 @@ function collectIssuerChainPems(peerCert) {
|
|
|
51
51
|
return pems;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* @param {string} rest - Host[:port] after tcp://
|
|
56
|
+
* @returns {{ host: string, port: number }|null}
|
|
57
|
+
*/
|
|
58
|
+
function parseBracketedTcpHostPort(rest) {
|
|
59
|
+
if (!rest.startsWith('[')) return null;
|
|
60
|
+
const end = rest.indexOf(']');
|
|
61
|
+
if (end === -1) return null;
|
|
62
|
+
const host = rest.slice(1, end);
|
|
63
|
+
let port = DEFAULT_DOCKER_TLS_PORT;
|
|
64
|
+
const after = rest.slice(end + 1);
|
|
65
|
+
if (after.startsWith(':')) {
|
|
66
|
+
const p = parseInt(after.slice(1), 10);
|
|
67
|
+
if (!Number.isNaN(p)) port = p;
|
|
68
|
+
}
|
|
69
|
+
return { host, port };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @param {string} rest - Host[:port] after tcp://
|
|
74
|
+
* @returns {{ host: string, port: number }}
|
|
75
|
+
*/
|
|
76
|
+
function parsePlainTcpHostPort(rest) {
|
|
77
|
+
const colonIdx = rest.lastIndexOf(':');
|
|
78
|
+
if (colonIdx > 0 && /^\d+$/.test(rest.slice(colonIdx + 1))) {
|
|
79
|
+
return {
|
|
80
|
+
host: rest.slice(0, colonIdx),
|
|
81
|
+
port: parseInt(rest.slice(colonIdx + 1), 10)
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return { host: rest, port: DEFAULT_DOCKER_TLS_PORT };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @param {string} trimmed
|
|
89
|
+
* @returns {{ host: string, port: number }|null}
|
|
90
|
+
*/
|
|
91
|
+
function parseTcpSchemeEndpoint(trimmed) {
|
|
92
|
+
const rest = trimmed.slice(6).split('/')[0];
|
|
93
|
+
return parseBracketedTcpHostPort(rest) || parsePlainTcpHostPort(rest);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @param {string} trimmed
|
|
98
|
+
* @returns {{ host: string, port: number }|null}
|
|
99
|
+
*/
|
|
100
|
+
function parseDockerEndpointViaUrl(trimmed) {
|
|
101
|
+
try {
|
|
102
|
+
const u = new URL(trimmed.includes('://') ? trimmed : `tcp://${trimmed}`);
|
|
103
|
+
const host = String(u.hostname || '').trim();
|
|
104
|
+
if (!host) return null;
|
|
105
|
+
let port = DEFAULT_DOCKER_TLS_PORT;
|
|
106
|
+
if (u.port) {
|
|
107
|
+
const p = parseInt(u.port, 10);
|
|
108
|
+
if (!Number.isNaN(p)) port = p;
|
|
109
|
+
}
|
|
110
|
+
return { host, port };
|
|
111
|
+
} catch {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
54
116
|
/**
|
|
55
117
|
* Parse docker-endpoint (tcp://host:port) into TLS connect parameters.
|
|
56
118
|
* @param {string} endpoint
|
|
@@ -62,45 +124,11 @@ function parseDockerTlsEndpoint(endpoint) {
|
|
|
62
124
|
const lower = trimmed.toLowerCase();
|
|
63
125
|
if (lower.startsWith('unix:')) return null;
|
|
64
126
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
if (rest.startsWith('[')) {
|
|
71
|
-
const end = rest.indexOf(']');
|
|
72
|
-
if (end === -1) return null;
|
|
73
|
-
host = rest.slice(1, end);
|
|
74
|
-
const after = rest.slice(end + 1);
|
|
75
|
-
if (after.startsWith(':')) {
|
|
76
|
-
const p = parseInt(after.slice(1), 10);
|
|
77
|
-
if (!Number.isNaN(p)) port = p;
|
|
78
|
-
}
|
|
79
|
-
} else {
|
|
80
|
-
const colonIdx = rest.lastIndexOf(':');
|
|
81
|
-
if (colonIdx > 0 && /^\d+$/.test(rest.slice(colonIdx + 1))) {
|
|
82
|
-
port = parseInt(rest.slice(colonIdx + 1), 10);
|
|
83
|
-
host = rest.slice(0, colonIdx);
|
|
84
|
-
} else {
|
|
85
|
-
host = rest;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
} else {
|
|
89
|
-
try {
|
|
90
|
-
const u = new URL(trimmed.includes('://') ? trimmed : `tcp://${trimmed}`);
|
|
91
|
-
host = u.hostname;
|
|
92
|
-
if (u.port) {
|
|
93
|
-
const p = parseInt(u.port, 10);
|
|
94
|
-
if (!Number.isNaN(p)) port = p;
|
|
95
|
-
}
|
|
96
|
-
} catch {
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
host = String(host || '').trim();
|
|
102
|
-
if (!host) return null;
|
|
103
|
-
return { host, port, servername: host };
|
|
127
|
+
const parsed = lower.startsWith('tcp://')
|
|
128
|
+
? parseTcpSchemeEndpoint(trimmed)
|
|
129
|
+
: parseDockerEndpointViaUrl(trimmed);
|
|
130
|
+
if (!parsed || !parsed.host) return null;
|
|
131
|
+
return { host: parsed.host, port: parsed.port, servername: parsed.host };
|
|
104
132
|
}
|
|
105
133
|
|
|
106
134
|
/**
|
|
@@ -262,6 +290,32 @@ async function writeDockerDaemonCaMarker(certDir) {
|
|
|
262
290
|
await fs.writeFile(markerPath, `augmentedAt=${new Date().toISOString()}\n`, { mode: 0o600 });
|
|
263
291
|
}
|
|
264
292
|
|
|
293
|
+
/**
|
|
294
|
+
* @param {string} certDir
|
|
295
|
+
* @param {string} existingCaPem
|
|
296
|
+
* @param {string} dockerEndpoint
|
|
297
|
+
* @param {{ timeoutMs?: number }} fetchOpts
|
|
298
|
+
* @returns {Promise<boolean>}
|
|
299
|
+
*/
|
|
300
|
+
async function augmentCaPemForDockerVerify(certDir, existingCaPem, dockerEndpoint, fetchOpts) {
|
|
301
|
+
const fs = require('fs');
|
|
302
|
+
const path = require('path');
|
|
303
|
+
if (await caPemTrustsDockerDaemon(existingCaPem, dockerEndpoint, fetchOpts)) {
|
|
304
|
+
await writeDockerDaemonCaMarker(certDir);
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
const augmented = await augmentCaPemWithDockerDaemonChain(existingCaPem, dockerEndpoint, fetchOpts);
|
|
308
|
+
if (!augmented || augmented === existingCaPem) return false;
|
|
309
|
+
const { normalizePemNewlines } = require('./dev-cert-helper');
|
|
310
|
+
await fs.promises.writeFile(
|
|
311
|
+
path.join(certDir, 'ca.pem'),
|
|
312
|
+
normalizePemNewlines(augmented),
|
|
313
|
+
{ mode: 0o600 }
|
|
314
|
+
);
|
|
315
|
+
await writeDockerDaemonCaMarker(certDir);
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
|
|
265
319
|
/**
|
|
266
320
|
* Decide whether Docker CLI should verify the daemon certificate (DOCKER_TLS_VERIFY=1).
|
|
267
321
|
* When ca.pem holds only the internal issue-cert CA but the daemon uses a public/wildcard cert,
|
|
@@ -288,24 +342,7 @@ async function resolveRemoteDockerDaemonVerify(ctx) {
|
|
|
288
342
|
if (!dockerEndpoint) return true;
|
|
289
343
|
|
|
290
344
|
const fetchOpts = { timeoutMs: DEFAULT_FETCH_TIMEOUT_MS };
|
|
291
|
-
|
|
292
|
-
await writeDockerDaemonCaMarker(certDir);
|
|
293
|
-
return true;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
const augmented = await augmentCaPemWithDockerDaemonChain(existingCaPem, dockerEndpoint, fetchOpts);
|
|
297
|
-
if (augmented && augmented !== existingCaPem) {
|
|
298
|
-
const { normalizePemNewlines } = require('./dev-cert-helper');
|
|
299
|
-
await fs.promises.writeFile(
|
|
300
|
-
path.join(certDir, 'ca.pem'),
|
|
301
|
-
normalizePemNewlines(augmented),
|
|
302
|
-
{ mode: 0o600 }
|
|
303
|
-
);
|
|
304
|
-
await writeDockerDaemonCaMarker(certDir);
|
|
305
|
-
return true;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
return false;
|
|
345
|
+
return augmentCaPemForDockerVerify(certDir, existingCaPem, dockerEndpoint, fetchOpts);
|
|
309
346
|
}
|
|
310
347
|
|
|
311
348
|
module.exports = {
|
|
@@ -101,6 +101,7 @@ function hostnameMatchesDockerHost(dockerHost) {
|
|
|
101
101
|
const hnShort = hn.split('.')[0];
|
|
102
102
|
const hShort = h.split('.')[0];
|
|
103
103
|
if (h === hnShort || hn === hShort) return true;
|
|
104
|
+
if (hShort && hnShort && hShort === hnShort) return true;
|
|
104
105
|
return false;
|
|
105
106
|
}
|
|
106
107
|
|
|
@@ -120,8 +121,18 @@ function isReloadBindMountOnEngineHost(dockerEndpoint) {
|
|
|
120
121
|
return hostnameMatchesDockerHost(host);
|
|
121
122
|
}
|
|
122
123
|
|
|
124
|
+
/**
|
|
125
|
+
* True when host refers to this machine (loopback, FQDN, or short hostname match).
|
|
126
|
+
* @param {string} host
|
|
127
|
+
* @returns {boolean}
|
|
128
|
+
*/
|
|
129
|
+
function isHostOnThisMachine(host) {
|
|
130
|
+
return hostnameMatchesDockerHost(host);
|
|
131
|
+
}
|
|
132
|
+
|
|
123
133
|
module.exports = {
|
|
124
134
|
extractHostFromDockerEndpoint,
|
|
125
135
|
isReloadBindMountOnEngineHost,
|
|
126
|
-
isLocalhostSyncSshHost
|
|
136
|
+
isLocalhostSyncSshHost,
|
|
137
|
+
isHostOnThisMachine
|
|
127
138
|
};
|
|
@@ -54,6 +54,36 @@ const FALLBACK_SECURITY_BY_AUTH = {
|
|
|
54
54
|
none: []
|
|
55
55
|
};
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* @param {Array<{name: string, value: string}>} authSecureVars
|
|
59
|
+
* @param {Record<string, unknown>} security
|
|
60
|
+
* @param {string} prefix
|
|
61
|
+
* @param {string} systemKey
|
|
62
|
+
*/
|
|
63
|
+
function pushSecureVarsFromSecurityMap(authSecureVars, security, prefix, systemKey) {
|
|
64
|
+
for (const key of Object.keys(security)) {
|
|
65
|
+
const envName = `KV_${prefix}_${securityKeyToVar(key)}`;
|
|
66
|
+
const pathVal = kvEnvKeyToPath(envName, systemKey);
|
|
67
|
+
authSecureVars.push({ name: envName, value: pathVal || `kv://${systemKey}/${key}` });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @param {Array<{name: string, value: string}>} authSecureVars
|
|
73
|
+
* @param {string} authMethod
|
|
74
|
+
* @param {string} prefix
|
|
75
|
+
* @param {string} systemKey
|
|
76
|
+
*/
|
|
77
|
+
function pushFallbackSecureVars(authSecureVars, authMethod, prefix, systemKey) {
|
|
78
|
+
const keys = FALLBACK_SECURITY_BY_AUTH[authMethod] || FALLBACK_SECURITY_BY_AUTH.apikey;
|
|
79
|
+
for (const key of keys) {
|
|
80
|
+
authSecureVars.push({
|
|
81
|
+
name: `KV_${prefix}_${securityKeyToVar(key)}`,
|
|
82
|
+
value: `kv://${systemKey}/${key}`
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
57
87
|
/**
|
|
58
88
|
* Builds authSecureVars array from system authentication.security (or fallback by auth type).
|
|
59
89
|
* @param {Object} system - System object with key and authentication
|
|
@@ -65,6 +95,9 @@ function buildAuthSecureVarsFromSystem(system) {
|
|
|
65
95
|
const prefix = systemKeyToKvPrefix(systemKey);
|
|
66
96
|
if (!prefix) return authSecureVars;
|
|
67
97
|
const authBlock = system?.authentication || system?.auth;
|
|
98
|
+
if (authBlock?.authType === 'apiProvider' || authBlock?.authType === 'none') {
|
|
99
|
+
return authSecureVars;
|
|
100
|
+
}
|
|
68
101
|
const credential = getAuthCredentialTemplate(authBlock);
|
|
69
102
|
const security = credential?.security || authBlock?.security;
|
|
70
103
|
const authMethod = (
|
|
@@ -74,19 +107,9 @@ function buildAuthSecureVarsFromSystem(system) {
|
|
|
74
107
|
'apikey'
|
|
75
108
|
).toLowerCase();
|
|
76
109
|
if (security && typeof security === 'object' && Object.keys(security).length > 0) {
|
|
77
|
-
|
|
78
|
-
const envName = `KV_${prefix}_${securityKeyToVar(key)}`;
|
|
79
|
-
const pathVal = kvEnvKeyToPath(envName, systemKey);
|
|
80
|
-
authSecureVars.push({ name: envName, value: pathVal || `kv://${systemKey}/${key}` });
|
|
81
|
-
}
|
|
110
|
+
pushSecureVarsFromSecurityMap(authSecureVars, security, prefix, systemKey);
|
|
82
111
|
} else {
|
|
83
|
-
|
|
84
|
-
for (const key of keys) {
|
|
85
|
-
authSecureVars.push({
|
|
86
|
-
name: `KV_${prefix}_${securityKeyToVar(key)}`,
|
|
87
|
-
value: `kv://${systemKey}/${key}`
|
|
88
|
-
});
|
|
89
|
-
}
|
|
112
|
+
pushFallbackSecureVars(authSecureVars, authMethod, prefix, systemKey);
|
|
90
113
|
}
|
|
91
114
|
return authSecureVars;
|
|
92
115
|
}
|
|
@@ -116,13 +139,25 @@ function buildConfigurationEntries(system) {
|
|
|
116
139
|
* @returns {{ authMethod: string, authSecureVars: Array<{name: string, value: string}>, authNonSecureVarNames: string[], configuration: Array<{name: string, value: string, comment: string}> }}
|
|
117
140
|
*/
|
|
118
141
|
function buildExternalEnvTemplateContext(system) {
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
'
|
|
142
|
+
const authBlock = system?.authentication || system?.auth;
|
|
143
|
+
const credential = getAuthCredentialTemplate(authBlock);
|
|
144
|
+
let authMethod = 'apikey';
|
|
145
|
+
if (authBlock?.authType === 'apiProvider') {
|
|
146
|
+
authMethod = 'apiprovider';
|
|
147
|
+
} else if (authBlock?.authType === 'none') {
|
|
148
|
+
authMethod = 'none';
|
|
149
|
+
} else {
|
|
150
|
+
authMethod = (
|
|
151
|
+
credential?.method ||
|
|
152
|
+
authBlock?.method ||
|
|
153
|
+
authBlock?.type ||
|
|
154
|
+
'apikey'
|
|
155
|
+
).toLowerCase();
|
|
156
|
+
}
|
|
124
157
|
const authSecureVars = buildAuthSecureVarsFromSystem(system);
|
|
125
|
-
const authVars =
|
|
158
|
+
const authVars =
|
|
159
|
+
(credential?.variables && typeof credential.variables === 'object' ? credential.variables : null) ||
|
|
160
|
+
(authBlock?.variables && typeof authBlock.variables === 'object' ? authBlock.variables : {});
|
|
126
161
|
const authNonSecureVarNames = Object.keys(authVars);
|
|
127
162
|
const configuration = buildConfigurationEntries(system);
|
|
128
163
|
return {
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-place health-check wait line (TTY ora or carriage-return refresh).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Health check poll presentation helpers
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const chalk = require('chalk');
|
|
12
|
+
const logger = require('./logger');
|
|
13
|
+
|
|
14
|
+
function shouldUseHealthCheckPollSpinner() {
|
|
15
|
+
return Boolean(process && process.stdout && process.stdout.isTTY);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {number} attemptIndex - Zero-based attempt index after a failed probe
|
|
20
|
+
* @param {number} maxAttempts
|
|
21
|
+
* @returns {string}
|
|
22
|
+
*/
|
|
23
|
+
function buildHealthCheckPollText(attemptIndex, maxAttempts) {
|
|
24
|
+
return chalk.yellow(
|
|
25
|
+
`Waiting for health check… (${attemptIndex + 1}/${maxAttempts})`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function createSilentHealthCheckPollHandlers() {
|
|
30
|
+
return {
|
|
31
|
+
usesSpinner: false,
|
|
32
|
+
onWaiting: () => {},
|
|
33
|
+
finish: () => {}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function createDelegatedHealthCheckPollHandlers(maxAttempts, onPollText) {
|
|
38
|
+
return {
|
|
39
|
+
usesSpinner: false,
|
|
40
|
+
onWaiting: (attemptIndex) => {
|
|
41
|
+
onPollText(buildHealthCheckPollText(attemptIndex, maxAttempts));
|
|
42
|
+
},
|
|
43
|
+
finish: () => {}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function createOraHealthCheckPollHandlers(maxAttempts) {
|
|
48
|
+
const ora = require('ora');
|
|
49
|
+
const spinner = ora({
|
|
50
|
+
text: buildHealthCheckPollText(0, maxAttempts),
|
|
51
|
+
spinner: 'dots'
|
|
52
|
+
});
|
|
53
|
+
let started = false;
|
|
54
|
+
return {
|
|
55
|
+
usesSpinner: true,
|
|
56
|
+
onWaiting: (attemptIndex) => {
|
|
57
|
+
if (!started) {
|
|
58
|
+
spinner.start();
|
|
59
|
+
started = true;
|
|
60
|
+
}
|
|
61
|
+
spinner.text = buildHealthCheckPollText(attemptIndex, maxAttempts);
|
|
62
|
+
},
|
|
63
|
+
finish: () => {
|
|
64
|
+
if (started) {
|
|
65
|
+
spinner.stop();
|
|
66
|
+
process.stdout.write('\n');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function createCarriageReturnHealthCheckPollHandlers(maxAttempts) {
|
|
73
|
+
let lastLine = '';
|
|
74
|
+
return {
|
|
75
|
+
usesSpinner: false,
|
|
76
|
+
onWaiting: (attemptIndex) => {
|
|
77
|
+
const line = buildHealthCheckPollText(attemptIndex, maxAttempts);
|
|
78
|
+
if (process.stdout && process.stdout.isTTY) {
|
|
79
|
+
process.stdout.write(`\r${line}`);
|
|
80
|
+
lastLine = line;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
logger.log(line);
|
|
84
|
+
},
|
|
85
|
+
finish: () => {
|
|
86
|
+
if (lastLine && process.stdout && process.stdout.isTTY) {
|
|
87
|
+
process.stdout.write('\n');
|
|
88
|
+
lastLine = '';
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @param {number} maxAttempts
|
|
96
|
+
* @param {{ silent?: boolean, onPollText?: (text: string) => void }} [options]
|
|
97
|
+
* @returns {{ usesSpinner: boolean, onWaiting: (attemptIndex: number) => void, finish: () => void }}
|
|
98
|
+
*/
|
|
99
|
+
function createHealthCheckPollHandlers(maxAttempts, options = {}) {
|
|
100
|
+
const silent = options && options.silent === true;
|
|
101
|
+
const onPollText = options && typeof options.onPollText === 'function' ? options.onPollText : null;
|
|
102
|
+
|
|
103
|
+
if (silent) {
|
|
104
|
+
return createSilentHealthCheckPollHandlers();
|
|
105
|
+
}
|
|
106
|
+
if (onPollText) {
|
|
107
|
+
return createDelegatedHealthCheckPollHandlers(maxAttempts, onPollText);
|
|
108
|
+
}
|
|
109
|
+
if (shouldUseHealthCheckPollSpinner()) {
|
|
110
|
+
return createOraHealthCheckPollHandlers(maxAttempts);
|
|
111
|
+
}
|
|
112
|
+
return createCarriageReturnHealthCheckPollHandlers(maxAttempts);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = {
|
|
116
|
+
buildHealthCheckPollText,
|
|
117
|
+
createHealthCheckPollHandlers,
|
|
118
|
+
shouldUseHealthCheckPollSpinner
|
|
119
|
+
};
|
|
@@ -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
|
/**
|