@aifabrix/builder 2.57.0 → 2.59.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/lib/agent-kit/codex-app-server.js +44 -0
- package/lib/agent-kit/init.js +4 -17
- package/lib/agent-kit/session-report.js +15 -10
- package/lib/agent-kit/setup-auth.js +131 -0
- package/lib/agent-kit/setup-context.js +118 -0
- package/lib/agent-kit/setup-github.js +70 -0
- package/lib/agent-kit/setup-managed.js +79 -0
- package/lib/agent-kit/setup-process.js +42 -0
- package/lib/agent-kit/setup-session.js +53 -0
- package/lib/agent-kit/setup-state.js +52 -0
- package/lib/agent-kit/setup-workspace.js +113 -0
- package/lib/agent-kit/setup.js +84 -0
- package/lib/agent-kit/start.js +4 -3
- package/lib/agent-kit/tmux-sessions.js +20 -3
- package/lib/api/dev.api.js +1 -1
- package/lib/api/miso-health.api.js +26 -0
- package/lib/api/types/dev.types.js +3 -3
- package/lib/app/deploy.js +0 -102
- package/lib/app/push.js +98 -41
- package/lib/build/standard-docker-build.js +13 -11
- package/lib/cli/index.js +4 -0
- package/lib/cli/setup-app.js +16 -5
- package/lib/cli/setup-dev-path-commands.js +53 -2
- package/lib/cli/setup-dev.js +7 -4
- package/lib/cli/setup-environment.js +17 -4
- package/lib/cli/setup-help.js +86 -0
- package/lib/cli/setup-onboarding.js +37 -0
- package/lib/cli/setup-utility-repair.js +7 -5
- package/lib/commands/agent-kit-help.js +21 -7
- package/lib/commands/agent-kit-sessions.js +126 -0
- package/lib/commands/agent-kit-setup.js +44 -0
- package/lib/commands/agent-kit.js +5 -79
- package/lib/commands/dev-init.js +21 -23
- package/lib/commands/dev-refresh.js +2 -2
- package/lib/commands/dev-registry.js +155 -0
- package/lib/commands/dev-show-display.js +10 -2
- package/lib/commands/governance-verify-external.js +12 -5
- package/lib/commands/repair-datasource.js +9 -9
- package/lib/commands/repair.js +45 -8
- package/lib/commands/setup-image-refresh.js +26 -0
- package/lib/commands/upload-qualification-client.js +56 -11
- package/lib/commands/upload-qualification-poll.js +51 -22
- package/lib/commands/wizard-dataplane.js +16 -52
- package/lib/core/config-attach-extensions.js +4 -1
- package/lib/datasource/datasource-validate-summary.js +8 -5
- package/lib/datasource/sync-defaults.js +53 -0
- package/lib/deployment/installation/azure-infra-stage.js +50 -9
- package/lib/deployment/installation/azure-preflight.js +63 -7
- package/lib/deployment/installation/azure-readiness.js +59 -7
- package/lib/deployment/installation/environment-auth.js +60 -20
- package/lib/deployment/installation/environment-stage.js +9 -5
- package/lib/deployment/installation/infra-catalog.js +57 -5
- package/lib/deployment/installation/infra-manifest.js +22 -2
- package/lib/deployment/installation/infra-projection.js +14 -3
- package/lib/deployment/installation/registry-auth-mode.js +121 -0
- package/lib/deployment/installation/stage-input.js +21 -6
- package/lib/deployment/marketplace-environment.js +26 -1
- package/lib/generator/builders.js +8 -9
- package/lib/generator/index.js +23 -12
- package/lib/integration-definition/apply.js +2 -2
- package/lib/internal/fs-real-sync.js +4 -1
- package/lib/programmatic/bearer-auth.js +3 -62
- package/lib/programmatic/builder-api-startup.js +51 -0
- package/lib/programmatic/builder-miso-runtime.js +167 -0
- package/lib/programmatic/openapi-descriptions.js +3 -3
- package/lib/programmatic/openapi-schema-descriptions-core.js +3 -3
- package/lib/programmatic/openapi-spec.js +2 -1
- package/lib/programmatic/route-handler-map.js +3 -2
- package/lib/schema/environment-deploy-request.schema.json +1 -1
- package/lib/schema/external-datasource.schema.json +20 -5
- package/lib/schema/infra-parameter.schema.json +1 -1
- package/lib/schema/infra.parameter.yaml +2 -1
- package/lib/schema/infrastructure-schema.json +200 -3
- package/lib/schema/installation-environment.schema.json +1 -1
- package/lib/schema/wizard-config.schema.json +1 -1
- package/lib/utils/app-register-config.js +2 -4
- package/lib/utils/build-resolve-image.js +2 -21
- package/lib/utils/cli-utils.js +8 -1
- package/lib/utils/config-paths.js +24 -29
- package/lib/utils/config-registry-preference.js +113 -0
- package/lib/utils/dev-init-ssh-merge.js +5 -4
- package/lib/utils/dev-user-groups.js +2 -1
- package/lib/utils/docker-build.js +53 -19
- package/lib/utils/docker-daemon-tls-ca.js +2 -2
- package/lib/utils/env-map.js +23 -6
- package/lib/utils/error-formatters/validation-errors.js +25 -5
- package/lib/utils/help-builder.js +4 -3
- package/lib/utils/image-tags.js +88 -0
- package/lib/utils/promote-dev-docker-image.js +6 -0
- package/lib/utils/registry-credentials.js +236 -0
- package/lib/utils/resolve-docker-image-ref.js +78 -9
- package/lib/utils/secrets-helpers.js +9 -1
- package/lib/utils/token-manager.js +29 -36
- package/lib/utils/variable-transformer.js +2 -4
- package/package.json +4 -2
- package/templates/agent-kit/instructions/AGENTKIT.md +39 -2
- package/templates/agent-kit/instructions/root.AGENTS.md +40 -1
- package/templates/agent-kit/skills/shared/hosts.md +23 -5
- package/templates/agent-kit/workspace/README.md +14 -1
- package/templates/applications/builder-api/application.yaml +1 -1
- package/templates/applications/dataplane/application.yaml +1 -1
- package/templates/applications/dataplane/env.template +1 -1
- package/templates/applications/miso-controller/application.yaml +1 -1
- package/templates/applications/miso-controller/env.template +1 -1
- package/templates/external-system/external-datasource.yaml.hbs +7 -6
- package/templates/marketplace/main.json +440 -82
|
@@ -80,10 +80,8 @@ async function createMinimalAppIfNeeded(appKey, options) {
|
|
|
80
80
|
* @returns {string} Image reference string
|
|
81
81
|
*/
|
|
82
82
|
function buildImageReference(variables, appKey) {
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
const tag = variables.image?.tag || 'latest';
|
|
86
|
-
return registry ? `${registry}/${imageName}:${tag}` : `${imageName}:${tag}`;
|
|
83
|
+
const { buildManifestImageReference } = require('./resolve-docker-image-ref');
|
|
84
|
+
return buildManifestImageReference(variables, appKey);
|
|
87
85
|
}
|
|
88
86
|
|
|
89
87
|
/**
|
|
@@ -14,7 +14,8 @@ const { execWithDockerEnv } = require('./docker-exec');
|
|
|
14
14
|
const { buildDevImageName, buildDevImageRepositoryPath } = require('./image-name');
|
|
15
15
|
const {
|
|
16
16
|
getRepositoryPathFromConfig,
|
|
17
|
-
resolveDockerImageRef
|
|
17
|
+
resolveDockerImageRef,
|
|
18
|
+
stripRegistryHostFromRepository
|
|
18
19
|
} = require('./resolve-docker-image-ref');
|
|
19
20
|
|
|
20
21
|
/**
|
|
@@ -48,26 +49,6 @@ function repositoryTail(repoPath) {
|
|
|
48
49
|
return parts[parts.length - 1] || repoPath;
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
/**
|
|
52
|
-
* Remove registry host prefix from a Docker repository path (local build only).
|
|
53
|
-
* @param {string} repository
|
|
54
|
-
* @returns {string}
|
|
55
|
-
*/
|
|
56
|
-
function stripRegistryHostFromRepository(repository) {
|
|
57
|
-
if (!repository || typeof repository !== 'string') {
|
|
58
|
-
return repository;
|
|
59
|
-
}
|
|
60
|
-
const parts = repository.split('/');
|
|
61
|
-
if (parts.length <= 1) {
|
|
62
|
-
return repository;
|
|
63
|
-
}
|
|
64
|
-
const host = parts[0];
|
|
65
|
-
if (host.includes('.') || host.includes(':') || host === 'localhost') {
|
|
66
|
-
return parts.slice(1).join('/');
|
|
67
|
-
}
|
|
68
|
-
return repository;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
52
|
/**
|
|
72
53
|
* Strip dev-scoped or -extra suffix from a Docker repository path.
|
|
73
54
|
* @param {string} repo
|
package/lib/utils/cli-utils.js
CHANGED
|
@@ -198,6 +198,9 @@ function isAzureCliToolingError(errorMsg) {
|
|
|
198
198
|
return false;
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
+
/** Registry-credential and per-tag push errors that already name the host and the next step. */
|
|
202
|
+
const REGISTRY_ACTIONABLE_ERROR = /dev set-registry|Push failed for |No registry credentials are available/;
|
|
203
|
+
|
|
201
204
|
/**
|
|
202
205
|
* Format Azure-related errors
|
|
203
206
|
* @param {string} errorMsg - Error message
|
|
@@ -221,6 +224,10 @@ function formatAzureError(errorMsg) {
|
|
|
221
224
|
if (errorMsg.includes('Docker build failed') || errorMsg.includes('Build failed: Docker')) {
|
|
222
225
|
return null;
|
|
223
226
|
}
|
|
227
|
+
// Registry-credential and per-tag push errors already carry host-specific next steps.
|
|
228
|
+
if (REGISTRY_ACTIONABLE_ERROR.test(errorMsg)) {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
224
231
|
const lowerMsg = String(errorMsg || '').toLowerCase();
|
|
225
232
|
const hasAcrDomain = /\b[a-z0-9.-]+\.azurecr\.io\b/i.test(errorMsg);
|
|
226
233
|
const hasAcrKeyword = /\bacr\b/.test(lowerMsg);
|
|
@@ -238,7 +245,7 @@ function formatAzureError(errorMsg) {
|
|
|
238
245
|
if (errorMsg.includes('Registry URL is required')) {
|
|
239
246
|
return [
|
|
240
247
|
' Registry URL is required.',
|
|
241
|
-
' Provide via --registry
|
|
248
|
+
' Provide via --registry, set a default with aifabrix dev set-registry <host>, or configure image.registry in application.yaml'
|
|
242
249
|
];
|
|
243
250
|
}
|
|
244
251
|
return null;
|
|
@@ -101,13 +101,8 @@ function createHomeAndSecretsPathFunctions(getConfigFn, saveConfigFn) {
|
|
|
101
101
|
return;
|
|
102
102
|
}
|
|
103
103
|
const resolved = path.resolve(trimmed);
|
|
104
|
-
await setPathConfig(
|
|
105
|
-
|
|
106
|
-
saveConfigFn,
|
|
107
|
-
'aifabrix-work',
|
|
108
|
-
resolved,
|
|
109
|
-
'Work path must be a non-empty string'
|
|
110
|
-
);
|
|
104
|
+
await setPathConfig(getConfigFn, saveConfigFn, 'aifabrix-work', resolved,
|
|
105
|
+
'Work path must be a non-empty string');
|
|
111
106
|
},
|
|
112
107
|
async getAifabrixSecretsPath() {
|
|
113
108
|
return getPathConfig(getConfigFn, 'aifabrix-secrets');
|
|
@@ -168,6 +163,17 @@ async function resolveEnvConfigPathToAbsolute(raw, getConfigFn) {
|
|
|
168
163
|
return path.normalize(path.resolve(base, trimmed));
|
|
169
164
|
}
|
|
170
165
|
|
|
166
|
+
function findBuilderDirectory(base) {
|
|
167
|
+
if (!base) return null;
|
|
168
|
+
const candidate = path.join(base, 'builder');
|
|
169
|
+
try {
|
|
170
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) return candidate;
|
|
171
|
+
} catch {
|
|
172
|
+
/* Missing or inaccessible builder directory. */
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
171
177
|
function createEnvConfigPathFunctions(getConfigFn, saveConfigFn) {
|
|
172
178
|
return {
|
|
173
179
|
/**
|
|
@@ -199,30 +205,16 @@ function createEnvConfigPathFunctions(getConfigFn, saveConfigFn) {
|
|
|
199
205
|
return path.dirname(absolute);
|
|
200
206
|
}
|
|
201
207
|
const pathsMod = require('./paths');
|
|
202
|
-
const
|
|
203
|
-
if (!base) {
|
|
204
|
-
return null;
|
|
205
|
-
}
|
|
206
|
-
const b = path.join(base, 'builder');
|
|
207
|
-
try {
|
|
208
|
-
if (fs.existsSync(b) && fs.statSync(b).isDirectory()) {
|
|
209
|
-
return b;
|
|
210
|
-
}
|
|
211
|
-
} catch {
|
|
212
|
-
/* ignore */
|
|
213
|
-
}
|
|
214
|
-
return null;
|
|
215
|
-
};
|
|
216
|
-
const fromProject = tryDir(pathsMod.getProjectRoot());
|
|
208
|
+
const fromProject = findBuilderDirectory(pathsMod.getProjectRoot());
|
|
217
209
|
if (fromProject) {
|
|
218
210
|
return fromProject;
|
|
219
211
|
}
|
|
220
|
-
const fromEffective =
|
|
212
|
+
const fromEffective = findBuilderDirectory(pathsMod.getBuilderRoot());
|
|
221
213
|
if (fromEffective) {
|
|
222
214
|
return fromEffective;
|
|
223
215
|
}
|
|
224
216
|
const work = pathsMod.getAifabrixWork();
|
|
225
|
-
return
|
|
217
|
+
return findBuilderDirectory(work);
|
|
226
218
|
}
|
|
227
219
|
};
|
|
228
220
|
}
|
|
@@ -368,11 +360,10 @@ function applySecretsUrlFromRemote(config) {
|
|
|
368
360
|
config['aifabrix-secrets'] = `${base}/api/dev/secrets`;
|
|
369
361
|
}
|
|
370
362
|
|
|
371
|
-
function
|
|
363
|
+
function applySyncHost(config) {
|
|
372
364
|
const host = hostnameFromUrl(config['remote-server']);
|
|
373
365
|
if (!host) return;
|
|
374
366
|
if (!config['sync-ssh-host']) config['sync-ssh-host'] = host;
|
|
375
|
-
if (!config['docker-endpoint']) config['docker-endpoint'] = `tcp://${host}:2376`;
|
|
376
367
|
}
|
|
377
368
|
|
|
378
369
|
async function mergeRemoteSettingsImpl(getConfigFn, saveConfigFn, settings) {
|
|
@@ -386,8 +377,13 @@ async function mergeRemoteSettingsImpl(getConfigFn, saveConfigFn, settings) {
|
|
|
386
377
|
if (value === '') continue;
|
|
387
378
|
config[key] = value;
|
|
388
379
|
}
|
|
380
|
+
// Current settings are authoritative: never inherit Docker access from another identity.
|
|
381
|
+
if (typeof settings['docker-endpoint'] !== 'string' || !settings['docker-endpoint'].trim()) {
|
|
382
|
+
delete config['docker-endpoint'];
|
|
383
|
+
delete config['docker-tls-skip-verify'];
|
|
384
|
+
}
|
|
389
385
|
applySecretsUrlFromRemote(config);
|
|
390
|
-
|
|
386
|
+
applySyncHost(config);
|
|
391
387
|
await saveConfigFn(config);
|
|
392
388
|
}
|
|
393
389
|
|
|
@@ -428,7 +424,6 @@ module.exports = {
|
|
|
428
424
|
resolveEnvConfigPathToAbsolute,
|
|
429
425
|
SETTINGS_RESPONSE_KEYS,
|
|
430
426
|
applySecretsUrlFromRemote,
|
|
431
|
-
applySyncAndDockerFromHost,
|
|
427
|
+
applySyncAndDockerFromHost: applySyncHost,
|
|
432
428
|
hostnameFromUrl
|
|
433
429
|
};
|
|
434
|
-
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Installation-level default container registry (`registry` in the Builder config.yaml).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Registry host validation plus get/set/clear preference helpers
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const yaml = require('js-yaml');
|
|
12
|
+
const fs = require('../internal/fs-real-sync');
|
|
13
|
+
const { getRuntimeConfigFile } = require('../core/config-runtime-paths');
|
|
14
|
+
|
|
15
|
+
const REGISTRY_CONFIG_KEY = 'registry';
|
|
16
|
+
const HOST_LABEL = '[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?';
|
|
17
|
+
const REGISTRY_HOST_RE = new RegExp(`^${HOST_LABEL}(?:\\.${HOST_LABEL})*$`);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Normalizes and validates a registry host.
|
|
21
|
+
* Rejects schemes, paths, embedded credentials, query/fragment text and whitespace. A port is
|
|
22
|
+
* rejected because the deployment `image` contract (application-schema.json, shared with the
|
|
23
|
+
* Controller) accepts only one colon, which separates the tag.
|
|
24
|
+
* @param {string} value - Raw registry value
|
|
25
|
+
* @returns {string} Lower-case host
|
|
26
|
+
* @throws {Error} When the value is empty or not a plain host
|
|
27
|
+
*/
|
|
28
|
+
function normalizeRegistryHost(value) {
|
|
29
|
+
const raw = typeof value === 'string' ? value.trim() : '';
|
|
30
|
+
const withoutSlash = raw.replace(/\/+$/, '');
|
|
31
|
+
if (!withoutSlash) {
|
|
32
|
+
throw new Error('Registry is required (for example myregistry.azurecr.io)');
|
|
33
|
+
}
|
|
34
|
+
if (/[\s@?#/\\]|:\/\//.test(withoutSlash)) {
|
|
35
|
+
throw new Error(`Invalid registry "${raw}": use a host name such as myregistry.azurecr.io (no scheme, path or credentials)`);
|
|
36
|
+
}
|
|
37
|
+
const host = withoutSlash.toLowerCase();
|
|
38
|
+
if (host.includes(':')) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`Invalid registry "${raw}": a port is not supported because the deployed image reference keeps one colon for the tag. ` +
|
|
41
|
+
'Use a host name such as myregistry.azurecr.io.'
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
if (!REGISTRY_HOST_RE.test(host)) {
|
|
45
|
+
throw new Error(`Invalid registry "${raw}": use a host name such as myregistry.azurecr.io`);
|
|
46
|
+
}
|
|
47
|
+
return host;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Reads the installation registry synchronously (used by the shared image resolver).
|
|
52
|
+
* An unreadable or invalid value is treated as unset so image resolution keeps working.
|
|
53
|
+
* @returns {string} Normalized host or '' when unset
|
|
54
|
+
*/
|
|
55
|
+
function readInstallationRegistrySync() {
|
|
56
|
+
try {
|
|
57
|
+
const file = getRuntimeConfigFile();
|
|
58
|
+
if (!fs.existsSync(file)) return '';
|
|
59
|
+
const cfg = yaml.load(fs.readFileSync(file, 'utf8')) || {};
|
|
60
|
+
const value = cfg[REGISTRY_CONFIG_KEY];
|
|
61
|
+
return typeof value === 'string' && value.trim() ? normalizeRegistryHost(value) : '';
|
|
62
|
+
} catch {
|
|
63
|
+
return '';
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @param {Function} getConfigFn - async () => config object
|
|
69
|
+
* @param {Function} saveConfigFn - async (config) => void
|
|
70
|
+
* @returns {{ getRegistry: Function, setRegistry: Function, clearRegistry: Function }}
|
|
71
|
+
*/
|
|
72
|
+
function createRegistryPreferenceFunctions(getConfigFn, saveConfigFn) {
|
|
73
|
+
return {
|
|
74
|
+
/**
|
|
75
|
+
* @returns {Promise<string|null>} Saved registry host or null
|
|
76
|
+
*/
|
|
77
|
+
async getRegistry() {
|
|
78
|
+
const cfg = await getConfigFn();
|
|
79
|
+
const value = cfg[REGISTRY_CONFIG_KEY];
|
|
80
|
+
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @param {string} registry - Registry host
|
|
85
|
+
* @returns {Promise<string>} Normalized saved value
|
|
86
|
+
*/
|
|
87
|
+
async setRegistry(registry) {
|
|
88
|
+
const host = normalizeRegistryHost(registry);
|
|
89
|
+
const cfg = await getConfigFn();
|
|
90
|
+
cfg[REGISTRY_CONFIG_KEY] = host;
|
|
91
|
+
await saveConfigFn(cfg);
|
|
92
|
+
return host;
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Removes the saved registry (idempotent).
|
|
97
|
+
* @returns {Promise<void>}
|
|
98
|
+
*/
|
|
99
|
+
async clearRegistry() {
|
|
100
|
+
const cfg = await getConfigFn();
|
|
101
|
+
if (!(REGISTRY_CONFIG_KEY in cfg)) return;
|
|
102
|
+
delete cfg[REGISTRY_CONFIG_KEY];
|
|
103
|
+
await saveConfigFn(cfg);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = {
|
|
109
|
+
REGISTRY_CONFIG_KEY,
|
|
110
|
+
normalizeRegistryHost,
|
|
111
|
+
readInstallationRegistrySync,
|
|
112
|
+
createRegistryPreferenceFunctions
|
|
113
|
+
};
|
|
@@ -163,16 +163,17 @@ async function mergeDevSshConfigBlock(p) {
|
|
|
163
163
|
* Update ~/.ssh/config with Host <user>.<host> for interactive SSH; log result.
|
|
164
164
|
* @param {string} baseUrl - Builder Server base URL
|
|
165
165
|
* @param {string} devId - Developer ID (digits)
|
|
166
|
+
* @param {Object} [freshSettings] - Current authenticated server settings; never mix with saved identity
|
|
166
167
|
* @returns {Promise<{ hostAlias: string|null, syncUser: string, syncHost: string|null }>}
|
|
167
168
|
*/
|
|
168
|
-
async function mergeDevSshConfigAfterInit(baseUrl, devId) {
|
|
169
|
-
const syncHost = (await config.getSyncSshHost()) || hostnameFromBaseUrl(baseUrl);
|
|
170
|
-
const syncUser = (await config.getSyncSshUser()) || `dev${devId}`;
|
|
169
|
+
async function mergeDevSshConfigAfterInit(baseUrl, devId, freshSettings) {
|
|
170
|
+
const syncHost = (freshSettings ? freshSettings['sync-ssh-host'] : await config.getSyncSshHost()) || hostnameFromBaseUrl(baseUrl);
|
|
171
|
+
const syncUser = (freshSettings ? freshSettings['sync-ssh-user'] : await config.getSyncSshUser()) || `dev${devId}`;
|
|
171
172
|
if (!syncHost) {
|
|
172
173
|
return { hostAlias: null, syncUser, syncHost: null };
|
|
173
174
|
}
|
|
174
175
|
try {
|
|
175
|
-
const rootTrimmed = trimDeveloperRootDomain(await config.getDeveloperRootDomain());
|
|
176
|
+
const rootTrimmed = trimDeveloperRootDomain(freshSettings ? freshSettings['developer-root-domain'] : await config.getDeveloperRootDomain());
|
|
176
177
|
return await mergeDevSshConfigBlock({
|
|
177
178
|
baseUrl,
|
|
178
179
|
devId,
|
|
@@ -252,13 +252,44 @@ async function executeBuild(imageName, dockerfilePath, contextPath, tag, options
|
|
|
252
252
|
(options.cache === false || options.noCache === true || options['no-cache'] === true)
|
|
253
253
|
);
|
|
254
254
|
await executeDockerBuild(imageName, dockerfilePath, contextPath, tag, buildArgs, noCache);
|
|
255
|
+
await applyTagAliases(imageName, tag, resolveTagAliases(tag, options));
|
|
256
|
+
}
|
|
255
257
|
|
|
256
|
-
|
|
258
|
+
/**
|
|
259
|
+
* Aliases for the built image: explicit `options.tagAliases` from the shared tag plan, else the
|
|
260
|
+
* legacy rule (a single non-floating `options.tag` also gets `latest`).
|
|
261
|
+
* @param {string} tag - Built tag
|
|
262
|
+
* @param {Object} [options] - Build options
|
|
263
|
+
* @returns {string[]}
|
|
264
|
+
*/
|
|
265
|
+
function resolveTagAliases(tag, options) {
|
|
266
|
+
if (options && Array.isArray(options.tagAliases)) {
|
|
267
|
+
return options.tagAliases.filter((alias) => alias !== tag);
|
|
268
|
+
}
|
|
257
269
|
if (options && options.tag && options.tag !== 'latest' && options.tag !== 'main') {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
270
|
+
return ['latest'];
|
|
271
|
+
}
|
|
272
|
+
return [];
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Tag the built image with every alias (argument arrays; no shell interpolation).
|
|
277
|
+
* @param {string} imageName - Repository
|
|
278
|
+
* @param {string} tag - Built tag
|
|
279
|
+
* @param {string[]} aliases - Additional tags
|
|
280
|
+
* @returns {Promise<void>}
|
|
281
|
+
*/
|
|
282
|
+
async function applyTagAliases(imageName, tag, aliases) {
|
|
283
|
+
if (!aliases.length) return;
|
|
284
|
+
const { execFileWithDockerEnv } = require('./docker-exec');
|
|
285
|
+
const { getDockerExecEnv } = require('./remote-docker-env');
|
|
286
|
+
const env = await getDockerExecEnv();
|
|
287
|
+
for (const alias of aliases) {
|
|
288
|
+
try {
|
|
289
|
+
await execFileWithDockerEnv('docker', ['tag', `${imageName}:${tag}`, `${imageName}:${alias}`], { env });
|
|
290
|
+
} catch (error) {
|
|
291
|
+
throw new Error(`Failed to tag ${imageName}:${alias}: ${error.message}`);
|
|
292
|
+
}
|
|
262
293
|
}
|
|
263
294
|
}
|
|
264
295
|
|
|
@@ -286,26 +317,29 @@ async function executeDockerBuildWithTag(effectiveImageName, imageName, dockerfi
|
|
|
286
317
|
return;
|
|
287
318
|
}
|
|
288
319
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
320
|
+
const { execFileWithDockerEnv } = require('./docker-exec');
|
|
321
|
+
const { getDockerExecEnv } = require('./remote-docker-env');
|
|
322
|
+
const env = await getDockerExecEnv();
|
|
323
|
+
for (const compatTag of [tag, ...resolveTagAliases(tag, options)]) {
|
|
324
|
+
try {
|
|
325
|
+
await execFileWithDockerEnv(
|
|
326
|
+
'docker',
|
|
327
|
+
['tag', `${effectiveImageName}:${compatTag}`, `${imageName}:${compatTag}`],
|
|
328
|
+
{ env }
|
|
329
|
+
);
|
|
330
|
+
logger.log(formatSuccessLine(`Tagged image: ${imageName}:${compatTag}`));
|
|
331
|
+
} catch (err) {
|
|
332
|
+
logger.log(
|
|
333
|
+
formatWarningLine(`Could not create compatibility tag ${imageName}:${compatTag} - ${err.message}`)
|
|
334
|
+
);
|
|
335
|
+
}
|
|
303
336
|
}
|
|
304
337
|
}
|
|
305
338
|
|
|
306
339
|
module.exports = {
|
|
307
340
|
executeDockerBuild,
|
|
308
341
|
executeBuild,
|
|
342
|
+
resolveTagAliases,
|
|
309
343
|
executeDockerBuildWithTag,
|
|
310
344
|
isDockerNotAvailableError
|
|
311
345
|
};
|
|
@@ -146,7 +146,7 @@ function dockerEndpointFromRemoteServer(remoteServerUrl) {
|
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
/**
|
|
149
|
-
* Resolve docker-endpoint
|
|
149
|
+
* Resolve only an explicitly advertised docker-endpoint; a server hostname is not Docker access.
|
|
150
150
|
* @param {{ settings?: Object|null, remoteServerUrl?: string|null }} ctx
|
|
151
151
|
* @returns {string|null}
|
|
152
152
|
*/
|
|
@@ -156,7 +156,7 @@ function resolveDockerEndpointForCaAugment(ctx) {
|
|
|
156
156
|
if (typeof fromSettings === 'string' && fromSettings.trim()) {
|
|
157
157
|
return fromSettings.trim();
|
|
158
158
|
}
|
|
159
|
-
return
|
|
159
|
+
return null;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
/**
|
package/lib/utils/env-map.js
CHANGED
|
@@ -12,6 +12,8 @@ const fs = require('fs');
|
|
|
12
12
|
const yaml = require('js-yaml');
|
|
13
13
|
const { loadEnvConfig, loadSchemaEnvConfig } = require('./env-config-loader');
|
|
14
14
|
const config = require('../core/config');
|
|
15
|
+
const { buildDevUsernameForFrontDoorHost } = require('./compose-traefik-ingress-base');
|
|
16
|
+
const { resolveRemoteHostForFrontDoor } = require('./remote-host-for-front-door');
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
19
|
* Load base environment variables from env-config.yaml
|
|
@@ -309,19 +311,34 @@ function applyPublicPortAdjustmentsForContext(result, context, devIdNum) {
|
|
|
309
311
|
}
|
|
310
312
|
|
|
311
313
|
/**
|
|
312
|
-
*
|
|
314
|
+
* Add config-derived interpolation values used by env.template.
|
|
313
315
|
* @param {Object} result - Env map (mutated)
|
|
316
|
+
* @param {number} devIdNum - Normalized developer ID
|
|
314
317
|
*/
|
|
315
|
-
async function
|
|
318
|
+
async function applyRuntimeConfigVars(result, devIdNum) {
|
|
319
|
+
let userCfg = {};
|
|
316
320
|
let tlsOn = false;
|
|
317
321
|
try {
|
|
318
|
-
|
|
322
|
+
userCfg = await config.getConfig();
|
|
319
323
|
tlsOn = Boolean(userCfg && userCfg.tlsEnabled === true);
|
|
320
324
|
} catch {
|
|
321
|
-
|
|
325
|
+
userCfg = {};
|
|
322
326
|
}
|
|
323
327
|
result.TLS_ENABLED = tlsOn ? 'true' : 'false';
|
|
324
328
|
result.HTTP_ENABLED = tlsOn ? 'false' : 'true';
|
|
329
|
+
const devUsername = buildDevUsernameForFrontDoorHost(devIdNum);
|
|
330
|
+
const remoteServerHost = resolveRemoteHostForFrontDoor({
|
|
331
|
+
remoteServer: userCfg && userCfg['remote-server']
|
|
332
|
+
});
|
|
333
|
+
const remoteHost = remoteServerHost ? remoteServerHost.split('.')[0] : '';
|
|
334
|
+
const devRootDomain = resolveRemoteHostForFrontDoor({
|
|
335
|
+
developerRootDomain: userCfg && userCfg['developer-root-domain']
|
|
336
|
+
});
|
|
337
|
+
if (devUsername || remoteHost) {
|
|
338
|
+
result.DEV_USERNAME = devUsername;
|
|
339
|
+
result.REMOTE_HOST = remoteHost;
|
|
340
|
+
}
|
|
341
|
+
if (devRootDomain) result.DEV_ROOT_DOMAIN = devRootDomain;
|
|
325
342
|
}
|
|
326
343
|
|
|
327
344
|
/**
|
|
@@ -333,6 +350,7 @@ async function applyTlsHttpManifestFlags(result) {
|
|
|
333
350
|
* - Applies developer-id adjustment to port variables for local context
|
|
334
351
|
* - Calculates *_PUBLIC_PORT for both local and docker context (basePort + developer-id * 100)
|
|
335
352
|
* - Adds TLS_ENABLED and HTTP_ENABLED ("true" / "false"); HTTP_ENABLED = !TLS_ENABLED for manifest ${...} substitution
|
|
353
|
+
* - Adds DEV_USERNAME, REMOTE_HOST, and DEV_ROOT_DOMAIN from Builder configuration
|
|
336
354
|
* @async
|
|
337
355
|
* @function buildEnvVarMap
|
|
338
356
|
* @param {'docker'|'local'} context - Environment context
|
|
@@ -358,7 +376,7 @@ async function buildEnvVarMap(context, osModule = null, developerId = null, opti
|
|
|
358
376
|
applyLocalPortAdjustment(result, devIdNum);
|
|
359
377
|
}
|
|
360
378
|
applyPublicPortAdjustmentsForContext(result, context, devIdNum);
|
|
361
|
-
await
|
|
379
|
+
await applyRuntimeConfigVars(result, devIdNum);
|
|
362
380
|
return result;
|
|
363
381
|
}
|
|
364
382
|
|
|
@@ -367,4 +385,3 @@ module.exports = {
|
|
|
367
385
|
getDeveloperIdNumber,
|
|
368
386
|
resolvePortInEnvValues
|
|
369
387
|
};
|
|
370
|
-
|
|
@@ -11,6 +11,26 @@ const { formatBlockingError } = require('../cli-test-layout-chalk');
|
|
|
11
11
|
|
|
12
12
|
const chalk = require('chalk');
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Resolves legacy application lookup codes without replacing API explanations.
|
|
16
|
+
* @param {Object|string} error - Validation error entry
|
|
17
|
+
* @returns {{message: string, applicationNotFound: boolean}} Display information
|
|
18
|
+
*/
|
|
19
|
+
function getValidationMessage(error) {
|
|
20
|
+
const message = typeof error === 'string' ? error : error.msg || error.message;
|
|
21
|
+
const code = typeof error === 'object' ? error.code : undefined;
|
|
22
|
+
const applicationNotFound = message === 'APPLICATION_NOT_FOUND' || code === 'APPLICATION_NOT_FOUND';
|
|
23
|
+
if (applicationNotFound && (!message || message === 'APPLICATION_NOT_FOUND')) {
|
|
24
|
+
return {
|
|
25
|
+
message: 'The controller could not find the application. ' +
|
|
26
|
+
'Check that it is registered on the selected controller and that its application key matches. ' +
|
|
27
|
+
'(Error code: APPLICATION_NOT_FOUND)',
|
|
28
|
+
applicationNotFound
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return { message: message || 'Invalid value', applicationNotFound };
|
|
32
|
+
}
|
|
33
|
+
|
|
14
34
|
/**
|
|
15
35
|
* Adds main error message to lines array
|
|
16
36
|
* @param {Array<string>} lines - Lines array to append to
|
|
@@ -52,7 +72,7 @@ function addValidationErrorsList(lines, errors) {
|
|
|
52
72
|
lines.push(chalk.yellow('Validation errors:'));
|
|
53
73
|
errors.forEach(err => {
|
|
54
74
|
const field = err.field || err.path || err.loc?.join('.') || 'validation';
|
|
55
|
-
const message = err
|
|
75
|
+
const { message } = getValidationMessage(err);
|
|
56
76
|
const value = err.value !== undefined ? ` (value: ${JSON.stringify(err.value)})` : '';
|
|
57
77
|
if (field === 'validation' || field === 'unknown') {
|
|
58
78
|
lines.push(chalk.gray(` • ${message}${value}`));
|
|
@@ -91,10 +111,10 @@ function addConfigurationErrors(lines, configuration) {
|
|
|
91
111
|
/**
|
|
92
112
|
* Adds actionable guidance tips to lines
|
|
93
113
|
* @param {Array<string>} lines - Lines array to append to
|
|
94
|
-
* @param {
|
|
114
|
+
* @param {Array<Object|string>} errors - Validation errors
|
|
95
115
|
*/
|
|
96
|
-
function addValidationGuidance(lines,
|
|
97
|
-
if (!
|
|
116
|
+
function addValidationGuidance(lines, errors) {
|
|
117
|
+
if (!Array.isArray(errors) || !errors.some(err => !getValidationMessage(err).applicationNotFound)) {
|
|
98
118
|
return;
|
|
99
119
|
}
|
|
100
120
|
lines.push(chalk.gray('Tips:'));
|
|
@@ -117,7 +137,7 @@ function formatValidationError(errorData) {
|
|
|
117
137
|
addValidationErrorMessage(lines, errorData);
|
|
118
138
|
addValidationErrorsList(lines, errorData.errors);
|
|
119
139
|
addConfigurationErrors(lines, errorData.configuration);
|
|
120
|
-
addValidationGuidance(lines, errorData.errors
|
|
140
|
+
addValidationGuidance(lines, errorData.errors);
|
|
121
141
|
|
|
122
142
|
if (errorData.instance) {
|
|
123
143
|
lines.push(chalk.gray(`Endpoint: ${errorData.instance}`));
|
|
@@ -17,6 +17,7 @@ const { Help } = require('commander');
|
|
|
17
17
|
* @type {Array<{ name: string, commands: Array<{ name: string, term?: string }> }>}
|
|
18
18
|
*/
|
|
19
19
|
const CATEGORIES = [
|
|
20
|
+
{ name: 'Getting Started', commands: [{ name: 'help', term: 'help [command]' }, { name: 'onboarding' }] },
|
|
20
21
|
{
|
|
21
22
|
name: 'Infrastructure (Local Development)',
|
|
22
23
|
commands: [
|
|
@@ -209,9 +210,9 @@ function formatCommandCategories(helper, program) {
|
|
|
209
210
|
for (const { term, desc: d } of lines) out.push(` ${term.padEnd(pad)}${d}`);
|
|
210
211
|
out.push('');
|
|
211
212
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
213
|
+
if (!program.commands.some(command => command.name() === 'help')) {
|
|
214
|
+
out.push('Help:', ` ${'help [command]'.padEnd(pad)}display help for command`, '');
|
|
215
|
+
}
|
|
215
216
|
out.push(` ${''.padEnd(pad)}Tip: aifabrix <command> --help for flags and examples`);
|
|
216
217
|
out.push('');
|
|
217
218
|
return out;
|