@aifabrix/builder 2.47.4 → 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/jest.isolated-projects.js +7 -0
- package/jest.projects.js +16 -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 +4 -3
- package/lib/app/deploy-config.js +6 -4
- package/lib/app/register.js +21 -19
- package/lib/app/rotate-secret.js +39 -16
- 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/build/index.js +12 -49
- package/lib/build/integration-docker-staging.js +35 -0
- package/lib/build/standard-docker-build.js +113 -0
- 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 +5 -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/dev-init.js +18 -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-prompts-platform-mode.js +11 -1
- package/lib/commands/setup.js +60 -7
- package/lib/commands/up-dataplane-bootstrap.js +149 -0
- package/lib/commands/up-dataplane-credentials.js +104 -6
- 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 +59 -9
- 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/parameters/infra-kv-discovery.js +4 -3
- package/lib/utils/aifabrix-runtime-config-dir.js +2 -1
- 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 +18 -6
- package/lib/utils/compose-generator.js +34 -15
- package/lib/utils/compose-traefik-ingress-base.js +13 -14
- package/lib/utils/config-paths.js +8 -3
- package/lib/utils/dev-ca-docker-augment.js +54 -0
- package/lib/utils/dev-user-groups.js +1 -0
- package/lib/utils/docker-daemon-tls-ca.js +363 -0
- package/lib/utils/docker-reload-mount.js +12 -1
- package/lib/utils/ensure-dev-certs-for-remote-docker.js +5 -0
- 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/promote-dev-docker-image.js +170 -0
- package/lib/utils/refresh-remote-settings.js +67 -0
- package/lib/utils/remote-docker-env.js +22 -7
- package/lib/utils/remote-host-for-front-door.js +43 -0
- package/lib/utils/run-cli-flags.js +37 -1
- package/lib/utils/secrets-helpers.js +9 -14
- package/lib/utils/secrets-kv-refs.js +47 -3
- package/lib/utils/secrets-missing-error.js +1 -9
- package/lib/utils/token-manager-client-credentials.js +11 -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 +28 -1
- 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
|
@@ -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,
|
|
@@ -15,6 +15,7 @@ const {
|
|
|
15
15
|
buildInternalUrlString
|
|
16
16
|
} = require('./url-declarative-resolve-build-urls');
|
|
17
17
|
const { normalizeRuntimeBasePath } = require('./url-declarative-runtime-base-path');
|
|
18
|
+
const { hasFrontDoorRemoteHostContext } = require('./remote-host-for-front-door');
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Published/browser origin port basis for url:// public surfaces (manifest `port`), not container listen.
|
|
@@ -39,6 +40,7 @@ function expandHostSurfacePublic(r) {
|
|
|
39
40
|
listenPort: publishedOriginPortBasis(r),
|
|
40
41
|
developerIdNum: r.devNum,
|
|
41
42
|
remoteServer: r.remoteServer,
|
|
43
|
+
developerRootDomain: r.developerRootDomain,
|
|
42
44
|
traefik: r.traefik,
|
|
43
45
|
hostTemplate: r.hostTemplate,
|
|
44
46
|
tls: r.tls,
|
|
@@ -59,7 +61,13 @@ function expandHostSurfacePublic(r) {
|
|
|
59
61
|
* @returns {boolean}
|
|
60
62
|
*/
|
|
61
63
|
function isLocalProfileWithoutRemoteServer(r) {
|
|
62
|
-
|
|
64
|
+
if (r.profile !== 'local') {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
return !hasFrontDoorRemoteHostContext({
|
|
68
|
+
remoteServer: r.remoteServer,
|
|
69
|
+
developerRootDomain: r.developerRootDomain
|
|
70
|
+
});
|
|
63
71
|
}
|
|
64
72
|
|
|
65
73
|
/**
|
|
@@ -75,6 +83,7 @@ function expandHostSurfaceInternal(r) {
|
|
|
75
83
|
listenPort: r.listenPort,
|
|
76
84
|
targetAppKey: r.appKey,
|
|
77
85
|
remoteServer: r.remoteServer,
|
|
86
|
+
developerRootDomain: r.developerRootDomain,
|
|
78
87
|
pathPrefix: r.pathPrefix,
|
|
79
88
|
patternPath: r.patternPath,
|
|
80
89
|
developerIdNum: r.devNum,
|
|
@@ -101,6 +110,7 @@ function expandFullSurfacePublic(r) {
|
|
|
101
110
|
listenPort: publishedOriginPortBasis(r),
|
|
102
111
|
developerIdNum: r.devNum,
|
|
103
112
|
remoteServer: r.remoteServer,
|
|
113
|
+
developerRootDomain: r.developerRootDomain,
|
|
104
114
|
pathPrefix: r.pathPrefix,
|
|
105
115
|
patternPath: r.patternPath,
|
|
106
116
|
traefik: r.traefik,
|
|
@@ -134,6 +144,7 @@ function expandFullSurfaceInternal(r) {
|
|
|
134
144
|
targetAppKey: r.appKey,
|
|
135
145
|
runtimeBasePath: runtimePath,
|
|
136
146
|
remoteServer: r.remoteServer,
|
|
147
|
+
developerRootDomain: r.developerRootDomain,
|
|
137
148
|
pathPrefix: r.pathPrefix,
|
|
138
149
|
patternPath: r.patternPath,
|
|
139
150
|
developerIdNum: r.devNum,
|
|
@@ -26,29 +26,47 @@ function tryReadApplicationYamlAt(cfgPath) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
29
|
+
* Candidate paths for cross-app url:// resolution (low → high priority).
|
|
30
|
+
* Platform system apps prefer materialized `~/.aifabrix-work|home/builder` over CLI package `builder/` stubs.
|
|
31
|
+
*
|
|
30
32
|
* @param {string} appKey
|
|
31
33
|
* @param {object} pathsUtil
|
|
32
|
-
* @param {string|null|undefined} [projectRootOverride] - When set (e.g. `ctx.projectRoot`), used instead of {@link pathsUtil.getProjectRoot} for the
|
|
34
|
+
* @param {string|null|undefined} [projectRootOverride] - When set (e.g. `ctx.projectRoot`), used instead of {@link pathsUtil.getProjectRoot} for the project-root candidate.
|
|
33
35
|
* @returns {string[]}
|
|
34
36
|
*/
|
|
35
37
|
function collectApplicationYamlPathsForUrlResolve(appKey, pathsUtil, projectRootOverride) {
|
|
36
38
|
const list = [];
|
|
39
|
+
const seen = new Set();
|
|
40
|
+
const push = (cfgPath) => {
|
|
41
|
+
const resolved = path.resolve(cfgPath);
|
|
42
|
+
if (seen.has(resolved)) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
seen.add(resolved);
|
|
46
|
+
list.push(resolved);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
if (pathsUtil.isSystemBuilderAppName && pathsUtil.isSystemBuilderAppName(appKey)) {
|
|
50
|
+
try {
|
|
51
|
+
push(path.join(pathsUtil.getSystemBuilderRoot(), appKey, 'application.yaml'));
|
|
52
|
+
} catch {
|
|
53
|
+
/* ignore */
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
push(path.join(pathsUtil.getBuilderPath(appKey), 'application.yaml'));
|
|
59
|
+
} catch {
|
|
60
|
+
/* ignore getBuilderPath errors */
|
|
61
|
+
}
|
|
62
|
+
|
|
37
63
|
const trimmed =
|
|
38
64
|
projectRootOverride !== undefined && projectRootOverride !== null
|
|
39
65
|
? String(projectRootOverride).trim()
|
|
40
66
|
: '';
|
|
41
67
|
const root = trimmed || pathsUtil.getProjectRoot();
|
|
42
68
|
if (root) {
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
try {
|
|
46
|
-
const viaBuilder = path.resolve(path.join(pathsUtil.getBuilderPath(appKey), 'application.yaml'));
|
|
47
|
-
if (!list.length || path.resolve(list[0]) !== viaBuilder) {
|
|
48
|
-
list.push(viaBuilder);
|
|
49
|
-
}
|
|
50
|
-
} catch {
|
|
51
|
-
/* ignore getBuilderPath errors */
|
|
69
|
+
push(path.join(root, 'builder', appKey, 'application.yaml'));
|
|
52
70
|
}
|
|
53
71
|
return list;
|
|
54
72
|
}
|
|
@@ -86,5 +104,6 @@ function loadApplicationYamlDocForUrlResolve(appKey, ctx, pathsUtil) {
|
|
|
86
104
|
}
|
|
87
105
|
|
|
88
106
|
module.exports = {
|
|
107
|
+
collectApplicationYamlPathsForUrlResolve,
|
|
89
108
|
loadApplicationYamlDocForUrlResolve
|
|
90
109
|
};
|
|
@@ -172,6 +172,7 @@ function replaceUrlRefToken(token, ctx, profile, devNum, derivedEnvKey, registry
|
|
|
172
172
|
patternPath,
|
|
173
173
|
pathPrefix,
|
|
174
174
|
remoteServer: ctx.remoteServer,
|
|
175
|
+
developerRootDomain: ctx.developerRootDomain,
|
|
175
176
|
devNum,
|
|
176
177
|
derivedEnvKey,
|
|
177
178
|
traefik: traefikForToken,
|
|
@@ -197,6 +198,7 @@ function replaceUrlRefToken(token, ctx, profile, devNum, derivedEnvKey, registry
|
|
|
197
198
|
* @param {boolean} ctx.useEnvironmentScopedResources
|
|
198
199
|
* @param {boolean} ctx.appEnvironmentScopedResources
|
|
199
200
|
* @param {string|null|undefined} ctx.remoteServer
|
|
201
|
+
* @param {string|null|undefined} [ctx.developerRootDomain]
|
|
200
202
|
* @param {string|number|null|undefined} ctx.developerIdRaw
|
|
201
203
|
* @param {boolean} [ctx.traefik] - When `ctx.userCfg` is absent: global Traefik-style URL flag. When `ctx.userCfg` is set: `ctx.traefik` is ignored for per-token Traefik; see {@link resolveDeclarativeUrlSurfaceState}.
|
|
202
204
|
* @param {boolean|undefined} [ctx.declarativePublicUrlsUseLocalhost] - When `ctx.userCfg` is absent: **`undefined`** (default) skips `remote-server` when Traefik is off; **`true`** forces localhost; **`false`** allows remote authority without Traefik. When `ctx.userCfg` is set and root **`traefik` is not** `false`: ignored for public bases; derived per target app from `applications.<target>.proxy`. When **`ctx.userCfg.traefik === false`**, public bases always use localhost (per-app `proxy` does not opt into `remote-server` without infra Traefik).
|
|
@@ -13,6 +13,7 @@ const path = require('path');
|
|
|
13
13
|
const fsRealSync = require('../internal/fs-real-sync');
|
|
14
14
|
const { resolveApplicationConfigPath } = require('./app-config-resolver');
|
|
15
15
|
const { loadConfigFile } = require('./config-format');
|
|
16
|
+
const pathsUtil = require('./paths');
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* @param {string} appDir - Absolute path to a builder app folder (directory containing application config)
|
|
@@ -44,9 +45,10 @@ function tryGetApplicationDirConfigForRegistry(appDir) {
|
|
|
44
45
|
* @param {string} builderDir
|
|
45
46
|
* @param {import('fs').Dirent} ent
|
|
46
47
|
* @param {number} dirIndex
|
|
47
|
-
* @param {Map<string, { folderName: string, doc: object, dirIndex: number }>} best
|
|
48
|
+
* @param {Map<string, { folderName: string, doc: object, dirIndex: number, tier: number }>} best
|
|
49
|
+
* @param {{ cwdBuilderDir?: string|null, systemMaterializedDir?: string|null }} scanCtx
|
|
48
50
|
*/
|
|
49
|
-
function maybeUpdateBestFromAppFolder(builderDir, ent, dirIndex, best) {
|
|
51
|
+
function maybeUpdateBestFromAppFolder(builderDir, ent, dirIndex, best, scanCtx) {
|
|
50
52
|
const appDir = path.join(builderDir, ent.name);
|
|
51
53
|
const entry = tryGetApplicationDirConfigForRegistry(appDir);
|
|
52
54
|
if (!entry) {
|
|
@@ -54,18 +56,82 @@ function maybeUpdateBestFromAppFolder(builderDir, ent, dirIndex, best) {
|
|
|
54
56
|
}
|
|
55
57
|
const { doc } = entry;
|
|
56
58
|
const appKey = (doc.app && doc.app.key) || ent.name;
|
|
59
|
+
const tier = resolveRegistryScanTierForDir(builderDir, scanCtx);
|
|
57
60
|
const cur = best.get(appKey);
|
|
61
|
+
const next = { folderName: ent.name, doc, dirIndex, tier };
|
|
62
|
+
|
|
63
|
+
if (pathsUtil.isSystemBuilderAppName(appKey)) {
|
|
64
|
+
if (!cur || tier > cur.tier || (tier === cur.tier && dirIndex > cur.dirIndex)) {
|
|
65
|
+
best.set(appKey, next);
|
|
66
|
+
}
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
58
70
|
if (!cur || dirIndex > cur.dirIndex) {
|
|
59
|
-
best.set(appKey,
|
|
71
|
+
best.set(appKey, next);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param {string} resolved
|
|
77
|
+
* @param {{ cwdBuilderDir?: string|null, projectRoot?: string|null }} scanCtx
|
|
78
|
+
* @returns {number|null}
|
|
79
|
+
*/
|
|
80
|
+
function resolveCwdCheckoutRegistryTier(resolved, scanCtx) {
|
|
81
|
+
const cwdDir =
|
|
82
|
+
scanCtx && scanCtx.cwdBuilderDir ? path.resolve(String(scanCtx.cwdBuilderDir)) : null;
|
|
83
|
+
if (!cwdDir || resolved !== cwdDir) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
const projectRoot =
|
|
87
|
+
scanCtx && scanCtx.projectRoot ? path.resolve(String(scanCtx.projectRoot)) : null;
|
|
88
|
+
const projectBuilder = projectRoot ? path.resolve(projectRoot, 'builder') : null;
|
|
89
|
+
if (projectBuilder && resolved === projectBuilder) {
|
|
90
|
+
return 0;
|
|
60
91
|
}
|
|
92
|
+
return 2;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @param {string} resolved
|
|
97
|
+
* @param {{ systemMaterializedDir?: string|null, effectiveBuilderDir?: string|null }} scanCtx
|
|
98
|
+
* @returns {boolean}
|
|
99
|
+
*/
|
|
100
|
+
function isMaterializedRegistryBuilderDir(resolved, scanCtx) {
|
|
101
|
+
const tierOneDirs = [];
|
|
102
|
+
if (scanCtx && scanCtx.systemMaterializedDir) {
|
|
103
|
+
tierOneDirs.push(path.resolve(String(scanCtx.systemMaterializedDir)));
|
|
104
|
+
}
|
|
105
|
+
if (scanCtx && scanCtx.effectiveBuilderDir) {
|
|
106
|
+
const eff = path.resolve(String(scanCtx.effectiveBuilderDir));
|
|
107
|
+
if (!tierOneDirs.includes(eff)) {
|
|
108
|
+
tierOneDirs.push(eff);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return tierOneDirs.includes(resolved);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @param {string} builderDir
|
|
116
|
+
* @param {{ cwdBuilderDir?: string|null, systemMaterializedDir?: string|null, effectiveBuilderDir?: string|null, projectRoot?: string|null }} scanCtx
|
|
117
|
+
* @returns {number}
|
|
118
|
+
*/
|
|
119
|
+
function resolveRegistryScanTierForDir(builderDir, scanCtx) {
|
|
120
|
+
const resolved = path.resolve(builderDir);
|
|
121
|
+
const cwdTier = resolveCwdCheckoutRegistryTier(resolved, scanCtx);
|
|
122
|
+
if (cwdTier !== null) {
|
|
123
|
+
return cwdTier;
|
|
124
|
+
}
|
|
125
|
+
return isMaterializedRegistryBuilderDir(resolved, scanCtx) ? 1 : 0;
|
|
61
126
|
}
|
|
62
127
|
|
|
63
128
|
/**
|
|
64
129
|
* @param {string} builderDir
|
|
65
130
|
* @param {number} dirIndex
|
|
66
|
-
* @param {Map<string, { folderName: string, doc: object, dirIndex: number }>} best
|
|
131
|
+
* @param {Map<string, { folderName: string, doc: object, dirIndex: number, tier: number }>} best
|
|
132
|
+
* @param {{ cwdBuilderDir?: string|null, systemMaterializedDir?: string|null }} scanCtx
|
|
67
133
|
*/
|
|
68
|
-
function mergeBuilderDirScanIntoBestMap(builderDir, dirIndex, best) {
|
|
134
|
+
function mergeBuilderDirScanIntoBestMap(builderDir, dirIndex, best, scanCtx) {
|
|
69
135
|
if (!builderDir || typeof builderDir !== 'string') {
|
|
70
136
|
return;
|
|
71
137
|
}
|
|
@@ -76,22 +142,41 @@ function mergeBuilderDirScanIntoBestMap(builderDir, dirIndex, best) {
|
|
|
76
142
|
if (!ent.isDirectory()) {
|
|
77
143
|
continue;
|
|
78
144
|
}
|
|
79
|
-
maybeUpdateBestFromAppFolder(builderDir, ent, dirIndex, best);
|
|
145
|
+
maybeUpdateBestFromAppFolder(builderDir, ent, dirIndex, best, scanCtx);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @param {{ cwdBuilderDir?: string|null, systemMaterializedDir?: string|null, effectiveBuilderDir?: string|null }} [scanCtx]
|
|
151
|
+
* @returns {{ cwdBuilderDir?: string|null, systemMaterializedDir?: string|null, effectiveBuilderDir?: string|null }}
|
|
152
|
+
*/
|
|
153
|
+
function buildRegistryScanContext(scanCtx) {
|
|
154
|
+
const ctx = scanCtx && typeof scanCtx === 'object' ? { ...scanCtx } : {};
|
|
155
|
+
if (!ctx.systemMaterializedDir) {
|
|
156
|
+
try {
|
|
157
|
+
ctx.systemMaterializedDir = pathsUtil.getSystemBuilderRoot();
|
|
158
|
+
} catch {
|
|
159
|
+
ctx.systemMaterializedDir = null;
|
|
160
|
+
}
|
|
80
161
|
}
|
|
162
|
+
return ctx;
|
|
81
163
|
}
|
|
82
164
|
|
|
83
165
|
/**
|
|
84
166
|
* When the same app exists under multiple builder roots, the **last** directory in `scanDirs`
|
|
85
|
-
* wins (highest `dirIndex`)
|
|
167
|
+
* wins (highest `dirIndex`) for user apps. Platform system apps use tiered precedence:
|
|
168
|
+
* cwd checkout builder (tier 2) > materialized system builder (tier 1) > CLI/package stubs (tier 0).
|
|
86
169
|
*
|
|
87
170
|
* @param {string[]} scanDirs - Builder or `packages` directories, low → high priority
|
|
171
|
+
* @param {{ cwdBuilderDir?: string|null, systemMaterializedDir?: string|null }} [scanCtx]
|
|
88
172
|
* @returns {Array<{ folderName: string, doc: object }>}
|
|
89
173
|
*/
|
|
90
|
-
function collectLatestApplicationYamlEntriesPerApp(scanDirs) {
|
|
91
|
-
|
|
174
|
+
function collectLatestApplicationYamlEntriesPerApp(scanDirs, scanCtx) {
|
|
175
|
+
const ctx = buildRegistryScanContext(scanCtx);
|
|
176
|
+
/** @type {Map<string, { folderName: string, doc: object, dirIndex: number, tier: number }>} */
|
|
92
177
|
const best = new Map();
|
|
93
178
|
scanDirs.forEach((builderDir, dirIndex) => {
|
|
94
|
-
mergeBuilderDirScanIntoBestMap(builderDir, dirIndex, best);
|
|
179
|
+
mergeBuilderDirScanIntoBestMap(builderDir, dirIndex, best, ctx);
|
|
95
180
|
});
|
|
96
181
|
return [...best.entries()]
|
|
97
182
|
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
@@ -266,6 +266,25 @@ function buildCanonicalRegistryScanDirs(root, effectiveBuilderDir, opts = {}) {
|
|
|
266
266
|
return scanDirs;
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
+
/**
|
|
270
|
+
* @param {{ excludeCwdBuilderScan?: boolean }} opts
|
|
271
|
+
* @returns {string|null}
|
|
272
|
+
*/
|
|
273
|
+
function resolveCwdBuilderDirForRegistryScan(opts) {
|
|
274
|
+
if (opts.excludeCwdBuilderScan) {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
try {
|
|
278
|
+
const cwdRoot = pathsUtil.findProjectRootFromCwd();
|
|
279
|
+
if (cwdRoot && typeof cwdRoot === 'string') {
|
|
280
|
+
return path.join(cwdRoot, 'builder');
|
|
281
|
+
}
|
|
282
|
+
} catch {
|
|
283
|
+
// ignore
|
|
284
|
+
}
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
|
|
269
288
|
/**
|
|
270
289
|
* Merge scan results into registry (does not remove stale keys).
|
|
271
290
|
* @param {string|null} projectRoot - getProjectRoot() or null (same semantics as projectRoot || getProjectRoot())
|
|
@@ -289,7 +308,11 @@ function refreshUrlsLocalRegistryFromBuilder(projectRoot, opts) {
|
|
|
289
308
|
effectiveBuilderDir = null;
|
|
290
309
|
}
|
|
291
310
|
const scanDirs = buildCanonicalRegistryScanDirs(root, effectiveBuilderDir, o);
|
|
292
|
-
const entries = collectLatestApplicationYamlEntriesPerApp(scanDirs
|
|
311
|
+
const entries = collectLatestApplicationYamlEntriesPerApp(scanDirs, {
|
|
312
|
+
cwdBuilderDir: resolveCwdBuilderDirForRegistryScan(o),
|
|
313
|
+
effectiveBuilderDir,
|
|
314
|
+
projectRoot: root
|
|
315
|
+
});
|
|
293
316
|
for (const { folderName, doc } of entries) {
|
|
294
317
|
mergeDocIntoRegistry(merged, doc, folderName);
|
|
295
318
|
}
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
'use strict';
|
|
8
8
|
|
|
9
|
+
const fsRealSync = require('../internal/fs-real-sync');
|
|
10
|
+
|
|
9
11
|
const { buildDefaultLocalContainerName } = require('./environment-scoped-resources');
|
|
10
12
|
|
|
11
13
|
/**
|
|
@@ -88,10 +90,31 @@ function getWorkerCompanionAppKeys(appKey, appConfig) {
|
|
|
88
90
|
return resolveWorkerSidecars(appKey, appConfig).companions.map((entry) => entry.key);
|
|
89
91
|
}
|
|
90
92
|
|
|
93
|
+
/**
|
|
94
|
+
* @param {string|undefined|null} envFilePath
|
|
95
|
+
* @returns {boolean}
|
|
96
|
+
*/
|
|
97
|
+
function isLocalModeFromEnvFile(envFilePath) {
|
|
98
|
+
if (!envFilePath || typeof envFilePath !== 'string') {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const content = fsRealSync.readFileSync(envFilePath, 'utf8');
|
|
103
|
+
const match = content.match(/^LOCAL_MODE=(.+)$/m);
|
|
104
|
+
if (!match) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
const value = match[1].trim().replace(/^["']|["']$/g, '').toLowerCase();
|
|
108
|
+
return value === 'true' || value === '1' || value === 'yes';
|
|
109
|
+
} catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
91
114
|
/**
|
|
92
115
|
* @param {string} appKey
|
|
93
116
|
* @param {object} appConfig
|
|
94
|
-
* @param {{ devId: string|number, idNum: number }} ids
|
|
117
|
+
* @param {{ devId: string|number, idNum: number, envFilePath?: string }} ids
|
|
95
118
|
* @returns {{
|
|
96
119
|
* enabled: boolean,
|
|
97
120
|
* worker?: { key: string, containerName: string, command: string },
|
|
@@ -99,6 +122,9 @@ function getWorkerCompanionAppKeys(appKey, appConfig) {
|
|
|
99
122
|
* }}
|
|
100
123
|
*/
|
|
101
124
|
function buildWorkerSidecarComposeContext(appKey, appConfig, ids) {
|
|
125
|
+
if (isLocalModeFromEnvFile(ids.envFilePath)) {
|
|
126
|
+
return { enabled: false };
|
|
127
|
+
}
|
|
102
128
|
const sidecars = resolveWorkerSidecars(appKey, appConfig);
|
|
103
129
|
if (!sidecars.enabled) {
|
|
104
130
|
return { enabled: false };
|
|
@@ -133,6 +159,7 @@ function imageRunToStartupCommand(imageRun) {
|
|
|
133
159
|
}
|
|
134
160
|
|
|
135
161
|
module.exports = {
|
|
162
|
+
isLocalModeFromEnvFile,
|
|
136
163
|
readImageRunCommand,
|
|
137
164
|
isWorkerStackEnabled,
|
|
138
165
|
resolveWorkerSidecars,
|
|
@@ -14,6 +14,8 @@ const STANDARD_AUTH_VAR_NAMES = new Set([
|
|
|
14
14
|
'baseurl', 'clientid', 'clientsecret', 'tokenurl', 'apikey', 'username', 'password'
|
|
15
15
|
]);
|
|
16
16
|
|
|
17
|
+
const { getAuthCredentialTemplate } = require('../utils/authentication-shape');
|
|
18
|
+
|
|
17
19
|
function trimVar(value) {
|
|
18
20
|
return (value !== undefined && value !== null ? String(value).trim() : '');
|
|
19
21
|
}
|
|
@@ -23,6 +25,39 @@ function isOAuth2OrAad(method) {
|
|
|
23
25
|
return m === 'oauth2' || m === 'aad';
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Resolves credential method from nested or legacy flat authentication.
|
|
30
|
+
* @param {Object|null|undefined} auth
|
|
31
|
+
* @returns {string}
|
|
32
|
+
*/
|
|
33
|
+
function resolveAuthMethod(auth) {
|
|
34
|
+
if (!auth || typeof auth !== 'object') {
|
|
35
|
+
return '';
|
|
36
|
+
}
|
|
37
|
+
if (auth.authType === 'none') {
|
|
38
|
+
return 'none';
|
|
39
|
+
}
|
|
40
|
+
if (auth.authType === 'apiProvider') {
|
|
41
|
+
return 'apiprovider';
|
|
42
|
+
}
|
|
43
|
+
const credential = getAuthCredentialTemplate(auth);
|
|
44
|
+
return String(credential?.method || auth.method || '').toLowerCase();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Resolves non-secret auth variables from nested or legacy flat authentication.
|
|
49
|
+
* @param {Object|null|undefined} auth
|
|
50
|
+
* @returns {Object|null}
|
|
51
|
+
*/
|
|
52
|
+
function resolveAuthVariables(auth) {
|
|
53
|
+
if (!auth || typeof auth !== 'object') {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
const credential = getAuthCredentialTemplate(auth);
|
|
57
|
+
const variables = credential?.variables || auth.variables;
|
|
58
|
+
return variables && typeof variables === 'object' ? variables : null;
|
|
59
|
+
}
|
|
60
|
+
|
|
26
61
|
/**
|
|
27
62
|
* Validates OAuth2/AAD grantType and conditional authorizationUrl for external system files.
|
|
28
63
|
* When method is oauth2 or aad: grantType (if present) must be client_credentials or authorization_code;
|
|
@@ -34,20 +69,23 @@ function isOAuth2OrAad(method) {
|
|
|
34
69
|
*/
|
|
35
70
|
function validateOAuth2GrantTypeAndAuthorizationUrl(parsed, errors) {
|
|
36
71
|
const auth = parsed?.authentication;
|
|
37
|
-
const variables = auth
|
|
38
|
-
|
|
72
|
+
const variables = resolveAuthVariables(auth);
|
|
73
|
+
const method = resolveAuthMethod(auth);
|
|
74
|
+
if (!variables || !isOAuth2OrAad(method)) {
|
|
39
75
|
return;
|
|
40
76
|
}
|
|
41
77
|
|
|
42
78
|
const grantType = trimVar(variables.grantType);
|
|
43
79
|
if (grantType !== '' && !VALID_GRANT_TYPES.includes(grantType)) {
|
|
44
|
-
errors.push('authentication.variables.grantType must be one of: client_credentials, authorization_code');
|
|
80
|
+
errors.push('authentication.credential.variables.grantType must be one of: client_credentials, authorization_code');
|
|
45
81
|
return;
|
|
46
82
|
}
|
|
47
83
|
|
|
48
84
|
const effectiveGrant = grantType || 'authorization_code';
|
|
49
85
|
if (effectiveGrant === 'authorization_code' && trimVar(variables.authorizationUrl) === '') {
|
|
50
|
-
errors.push(
|
|
86
|
+
errors.push(
|
|
87
|
+
'authentication.credential.variables.authorizationUrl is required when grantType is authorization_code or omitted'
|
|
88
|
+
);
|
|
51
89
|
}
|
|
52
90
|
}
|
|
53
91
|
|
|
@@ -55,7 +93,7 @@ function validateOAuth2GrantTypeAndAuthorizationUrl(parsed, errors) {
|
|
|
55
93
|
* Validates that the external system configuration array does not contain standard auth variable names.
|
|
56
94
|
* BASEURL, CLIENTID, CLIENTSECRET, TOKENURL, APIKEY, USERNAME, PASSWORD are credential parameters
|
|
57
95
|
* supplied from the selected credential at runtime and must not be in configuration. Exception:
|
|
58
|
-
* BASEURL is only allowed in configuration when authentication
|
|
96
|
+
* BASEURL is only allowed in configuration when authentication auth method is 'none'.
|
|
59
97
|
*
|
|
60
98
|
* @param {Object} parsed - Parsed external system object
|
|
61
99
|
* @param {string[]} errors - Array to push validation error messages into
|
|
@@ -63,7 +101,7 @@ function validateOAuth2GrantTypeAndAuthorizationUrl(parsed, errors) {
|
|
|
63
101
|
function validateConfigurationNoStandardAuthVariables(parsed, errors) {
|
|
64
102
|
const config = parsed?.configuration;
|
|
65
103
|
if (!Array.isArray(config) || config.length === 0) return;
|
|
66
|
-
const method = (parsed?.authentication
|
|
104
|
+
const method = resolveAuthMethod(parsed?.authentication);
|
|
67
105
|
const authNone = method === 'none';
|
|
68
106
|
const allowedWhenNone = new Set(['baseurl']);
|
|
69
107
|
for (const item of config) {
|
|
@@ -75,7 +113,7 @@ function validateConfigurationNoStandardAuthVariables(parsed, errors) {
|
|
|
75
113
|
errors.push(
|
|
76
114
|
`configuration must not contain standard auth variable '${name}'. ` +
|
|
77
115
|
'Standard auth variables (BASEURL, CLIENTID, CLIENTSECRET, TOKENURL, APIKEY, USERNAME, PASSWORD) are supplied from the selected credential at runtime. ' +
|
|
78
|
-
'BASEURL is only allowed in configuration when authentication
|
|
116
|
+
'BASEURL is only allowed in configuration when authentication authType is none.'
|
|
79
117
|
);
|
|
80
118
|
}
|
|
81
119
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aifabrix/builder",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.49.0",
|
|
4
4
|
"description": "AI Fabrix Local Fabric & Deployment SDK",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -95,6 +95,7 @@
|
|
|
95
95
|
"yaml": "^2.4.0"
|
|
96
96
|
},
|
|
97
97
|
"devDependencies": {
|
|
98
|
+
"@babel/plugin-syntax-optional-chaining": "^7.8.3",
|
|
98
99
|
"@babel/preset-env": "^7.29.0",
|
|
99
100
|
"@bcoe/v8-coverage": "^1.0.2",
|
|
100
101
|
"@types/node": "^20.10.0",
|