@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.
Files changed (83) hide show
  1. package/.cursor/skills/README.md +14 -0
  2. package/jest.isolated-projects.js +3 -0
  3. package/jest.projects.js +12 -0
  4. package/lib/api/applications.api.js +23 -0
  5. package/lib/api/dev.api.js +1 -1
  6. package/lib/api/index.js +5 -2
  7. package/lib/api/types/dev.types.js +3 -2
  8. package/lib/app/deploy-config.js +6 -4
  9. package/lib/app/register.js +1 -1
  10. package/lib/app/run-container-start.js +0 -9
  11. package/lib/app/run-helpers.js +8 -4
  12. package/lib/app/run-reload-sync.js +16 -18
  13. package/lib/app/run-stop-containers.js +48 -5
  14. package/lib/app/run.js +6 -3
  15. package/lib/app/show.js +2 -2
  16. package/lib/cli/infra-guided-auth.js +50 -12
  17. package/lib/cli/infra-guided-footers.js +164 -0
  18. package/lib/cli/infra-guided-spinners.js +57 -0
  19. package/lib/cli/infra-guided.js +52 -181
  20. package/lib/cli/setup-app.js +1 -1
  21. package/lib/cli/setup-dev.js +3 -2
  22. package/lib/cli/setup-infra.js +3 -2
  23. package/lib/cli/setup-platform.js +3 -0
  24. package/lib/commands/login-device.js +6 -2
  25. package/lib/commands/login.js +4 -1
  26. package/lib/commands/setup-modes.js +19 -3
  27. package/lib/commands/setup-platform-auth.js +25 -0
  28. package/lib/commands/setup-prompts-admin.js +9 -0
  29. package/lib/commands/setup.js +60 -8
  30. package/lib/commands/teardown.js +3 -5
  31. package/lib/commands/up-common.js +8 -11
  32. package/lib/commands/up-dataplane-bootstrap.js +149 -0
  33. package/lib/commands/up-dataplane-credentials.js +19 -4
  34. package/lib/commands/up-dataplane.js +155 -24
  35. package/lib/commands/up-miso.js +6 -2
  36. package/lib/core/secrets-env-declarative-expand.js +35 -23
  37. package/lib/core/setup-platform-mode.js +43 -0
  38. package/lib/deployment/deployer-status.js +26 -3
  39. package/lib/deployment/deployer.js +7 -14
  40. package/lib/external-system/download-helpers.js +10 -1
  41. package/lib/infrastructure/compose.js +40 -12
  42. package/lib/infrastructure/services.js +15 -3
  43. package/lib/infrastructure/traefik-tls-dynamic.js +88 -0
  44. package/lib/utils/app-bootstrap-api.js +53 -0
  45. package/lib/utils/app-run-containers.js +14 -3
  46. package/lib/utils/applications-config-defaults.js +19 -3
  47. package/lib/utils/compose-generate-docker-compose.js +16 -5
  48. package/lib/utils/compose-generator.js +34 -15
  49. package/lib/utils/compose-traefik-ingress-base.js +13 -14
  50. package/lib/utils/config-paths.js +6 -1
  51. package/lib/utils/dev-user-groups.js +1 -0
  52. package/lib/utils/docker-daemon-tls-ca.js +94 -57
  53. package/lib/utils/docker-reload-mount.js +12 -1
  54. package/lib/utils/external-env-template.js +53 -18
  55. package/lib/utils/health-check-poll-ui.js +119 -0
  56. package/lib/utils/health-check-url.js +54 -34
  57. package/lib/utils/health-check.js +91 -35
  58. package/lib/utils/infra-optional-service-flags.js +31 -2
  59. package/lib/utils/mutagen.js +7 -4
  60. package/lib/utils/paths.js +26 -36
  61. package/lib/utils/platform-controller-url.js +23 -1
  62. package/lib/utils/refresh-remote-settings.js +67 -0
  63. package/lib/utils/remote-host-for-front-door.js +43 -0
  64. package/lib/utils/run-cli-flags.js +37 -1
  65. package/lib/utils/url-declarative-public-base.js +11 -4
  66. package/lib/utils/url-declarative-resolve-build-urls.js +11 -2
  67. package/lib/utils/url-declarative-resolve-expand-token.js +12 -1
  68. package/lib/utils/url-declarative-resolve-load-doc.js +30 -11
  69. package/lib/utils/url-declarative-resolve.js +2 -0
  70. package/lib/utils/urls-local-registry-scan.js +95 -10
  71. package/lib/utils/urls-local-registry.js +24 -1
  72. package/lib/utils/worker-sidecars.js +2 -2
  73. package/lib/validation/external-system-auth-rules.js +45 -7
  74. package/package.json +2 -1
  75. package/scripts/test-dataplane-bootstrap.js +331 -0
  76. package/templates/applications/dataplane/application.yaml +1 -1
  77. package/templates/applications/dataplane/env.template +24 -6
  78. package/templates/applications/dataplane/rbac.yaml +11 -0
  79. package/templates/applications/miso-controller/application.yaml +1 -1
  80. package/templates/applications/miso-controller/env.template +5 -0
  81. package/templates/infra/compose.yaml.hbs +13 -9
  82. package/templates/python/docker-compose.hbs +3 -0
  83. 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 && String(remoteServer).trim()) {
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 && String(remoteServer).trim()) {
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
- return r.profile === 'local' && !String(r.remoteServer || '').trim();
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
- * Prefer projectRoot/builder (fixtures, tests); then {@link pathsUtil.getBuilderPath} (global npm).
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 first candidate path (avoids flaky cross-test `paths` mocks).
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
- list.push(path.resolve(path.join(root, 'builder', appKey, 'application.yaml')));
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, { folderName: ent.name, doc, dirIndex });
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`). Matches plan 141 canonical precedence (checkout / cwd after system materialization).
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
- /** @type {Map<string, { folderName: string, doc: object, dirIndex: number }>} */
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,7 +6,7 @@
6
6
 
7
7
  'use strict';
8
8
 
9
- const fs = require('fs');
9
+ const fsRealSync = require('../internal/fs-real-sync');
10
10
 
11
11
  const { buildDefaultLocalContainerName } = require('./environment-scoped-resources');
12
12
 
@@ -99,7 +99,7 @@ function isLocalModeFromEnvFile(envFilePath) {
99
99
  return false;
100
100
  }
101
101
  try {
102
- const content = fs.readFileSync(envFilePath, 'utf8');
102
+ const content = fsRealSync.readFileSync(envFilePath, 'utf8');
103
103
  const match = content.match(/^LOCAL_MODE=(.+)$/m);
104
104
  if (!match) {
105
105
  return false;
@@ -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?.variables;
38
- if (!variables || typeof variables !== 'object' || !isOAuth2OrAad(auth.method)) {
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('authentication.variables.authorizationUrl is required when grantType is authorization_code or omitted');
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.method is 'none'.
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?.method && String(parsed.authentication.method).toLowerCase()) || '';
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.method is \'none\'.'
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.48.0",
3
+ "version": "2.49.1",
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",