@aifabrix/builder 2.44.1 → 2.44.2

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/.npmrc.token CHANGED
@@ -1 +1 @@
1
- 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
1
+ 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
@@ -224,6 +224,52 @@ async function loadMergedConfigAndUserSecrets() {
224
224
  }
225
225
  }
226
226
 
227
+ /**
228
+ * @returns {string[]}
229
+ */
230
+ function collectBuilderSecretsYamlPaths() {
231
+ const projectRoot = pathsUtil.getProjectRoot();
232
+ const candidates = [];
233
+ if (projectRoot) {
234
+ candidates.push(path.join(projectRoot, 'builder', 'secrets.local.yaml'));
235
+ }
236
+ try {
237
+ const alt = path.join(pathsUtil.getBuilderRoot(), 'secrets.local.yaml');
238
+ if (!candidates.length || path.resolve(candidates[0]) !== path.resolve(alt)) {
239
+ candidates.push(alt);
240
+ }
241
+ } catch {
242
+ /* ignore */
243
+ }
244
+ return candidates;
245
+ }
246
+
247
+ /**
248
+ * Merge `builder/secrets.local.yaml` from project root and from {@link pathsUtil.getBuilderRoot} when distinct.
249
+ * @param {Object|null|undefined} merged
250
+ * @returns {Object|null|undefined}
251
+ */
252
+ function mergeBuilderSecretsLocalFiles(merged) {
253
+ try {
254
+ const seen = new Set();
255
+ let out = merged;
256
+ for (const builderPath of collectBuilderSecretsYamlPaths()) {
257
+ if (!builderPath || seen.has(path.resolve(builderPath))) {
258
+ continue;
259
+ }
260
+ seen.add(path.resolve(builderPath));
261
+ if (fs.existsSync(builderPath)) {
262
+ ensureSecureFilePermissions(builderPath);
263
+ const builderSecrets = mergeUserWithConfigFile(out || {}, builderPath);
264
+ if (builderSecrets) out = builderSecrets;
265
+ }
266
+ }
267
+ return out;
268
+ } catch {
269
+ return merged;
270
+ }
271
+ }
272
+
227
273
  /**
228
274
  * Loads merged secrets using config/user cascade, builder file merge, and default fallback.
229
275
  * @async
@@ -238,19 +284,7 @@ async function loadSecretsWithFallbacks() {
238
284
  }
239
285
  merged = await applyCanonicalSecretsOverride(merged);
240
286
  }
241
- try {
242
- const projectRoot = pathsUtil.getProjectRoot();
243
- if (projectRoot) {
244
- const builderPath = path.join(projectRoot, 'builder', 'secrets.local.yaml');
245
- if (fs.existsSync(builderPath)) {
246
- ensureSecureFilePermissions(builderPath);
247
- const builderSecrets = mergeUserWithConfigFile(merged || {}, builderPath);
248
- if (builderSecrets) merged = builderSecrets;
249
- }
250
- }
251
- } catch {
252
- // Ignore (e.g. no project root or read error)
253
- }
287
+ merged = mergeBuilderSecretsLocalFiles(merged);
254
288
  if (Object.keys(merged).length === 0) {
255
289
  merged = loadDefaultSecrets();
256
290
  }
@@ -278,6 +278,34 @@ function formatLogContent(parsed, logType, fileName) {
278
278
  }
279
279
  }
280
280
 
281
+ /**
282
+ * Parse saved debug log JSON (strict). Always throws an Error whose message starts with
283
+ * "Invalid JSON" so CLI/tests get a stable contract.
284
+ * @param {string} content
285
+ * @param {string} logPath
286
+ * @returns {Object}
287
+ */
288
+ function parseDatasourceLogJson(content, logPath) {
289
+ if (content === undefined || content === null) {
290
+ throw new Error(`Invalid JSON in ${logPath}: empty content`);
291
+ }
292
+ const text = String(content).replace(/^\uFEFF/, '').trim();
293
+ if (!text) {
294
+ throw new Error(`Invalid JSON in ${logPath}: empty file`);
295
+ }
296
+ let parsed;
297
+ try {
298
+ parsed = JSON.parse(text);
299
+ } catch (err) {
300
+ const detail = err instanceof Error ? err.message : String(err);
301
+ throw new Error(`Invalid JSON in ${logPath}: ${detail}`);
302
+ }
303
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
304
+ throw new Error(`Invalid JSON in ${logPath}: expected a JSON object at the root`);
305
+ }
306
+ return parsed;
307
+ }
308
+
281
309
  /**
282
310
  * Run log viewer: resolve log file, read, parse, format and print
283
311
  * @async
@@ -322,12 +350,7 @@ async function runLogViewer(datasourceKey, options) {
322
350
  fileName = path.basename(logPath);
323
351
  }
324
352
  const content = await fsp.readFile(logPath, 'utf8');
325
- let parsed;
326
- try {
327
- parsed = JSON.parse(content);
328
- } catch (err) {
329
- throw new Error(`Invalid JSON in ${logPath}: ${err.message}`);
330
- }
353
+ const parsed = parseDatasourceLogJson(content, logPath);
331
354
  formatLogContent(parsed, logType, fileName);
332
355
  }
333
356
 
@@ -339,5 +362,6 @@ module.exports = {
339
362
  formatE2ELog,
340
363
  formatIntegrationLog,
341
364
  formatStructuralTestLog,
365
+ parseDatasourceLogJson,
342
366
  runLogViewer
343
367
  };
@@ -12,8 +12,21 @@
12
12
  const path = require('path');
13
13
  const yaml = require('js-yaml');
14
14
  const fsRealSync = require('../internal/fs-real-sync');
15
+ const pathsUtil = require('./paths');
15
16
  const { localHostPort } = require('./declarative-url-ports');
16
17
 
18
+ /**
19
+ * @param {string} p
20
+ * @returns {boolean}
21
+ */
22
+ function isExistingDirSync(p) {
23
+ try {
24
+ return Boolean(p && fsRealSync.existsSync(p) && fsRealSync.statSync(p).isDirectory());
25
+ } catch {
26
+ return false;
27
+ }
28
+ }
29
+
17
30
  /**
18
31
  * Maps application.yaml `app.key` to env var prefix (MISO_HOST, DATAPLANE_PORT, …).
19
32
  * Generic keys not listed are skipped (no guessed PREFIX).
@@ -110,12 +123,11 @@ function mergeDocIntoOverlay(overlayDocker, overlayLocal, doc, folderName) {
110
123
  }
111
124
 
112
125
  /**
113
- * @param {string} projectRoot
126
+ * @param {string} builderDir
114
127
  * @returns {Array<{ doc: object, folderName: string }>}
115
128
  */
116
- function listBuilderApplicationDocs(projectRoot) {
117
- const builderDir = path.join(projectRoot, 'builder');
118
- if (!fsRealSync.existsSync(builderDir) || !fsRealSync.statSync(builderDir).isDirectory()) {
129
+ function listBuilderApplicationDocsInDir(builderDir) {
130
+ if (!isExistingDirSync(builderDir)) {
119
131
  return [];
120
132
  }
121
133
  const out = [];
@@ -140,6 +152,33 @@ function listBuilderApplicationDocs(projectRoot) {
140
152
  return out;
141
153
  }
142
154
 
155
+ /**
156
+ * Prefer projectRoot/builder when present (unit tests, in-repo runs). When missing but projectRoot
157
+ * is the detected CLI package root (global npm install omits builder/), fall back to
158
+ * {@link pathsUtil.getBuilderRoot}.
159
+ *
160
+ * @param {string} projectRoot
161
+ * @returns {string[]}
162
+ */
163
+ function collectBuilderScanDirs(projectRoot) {
164
+ const legacy = path.join(projectRoot, 'builder');
165
+ if (isExistingDirSync(legacy)) {
166
+ return [legacy];
167
+ }
168
+ try {
169
+ const detected = pathsUtil.getProjectRoot();
170
+ if (detected && path.resolve(projectRoot) === path.resolve(detected)) {
171
+ const br = pathsUtil.getBuilderRoot();
172
+ if (isExistingDirSync(br)) {
173
+ return [br];
174
+ }
175
+ }
176
+ } catch {
177
+ /* ignore */
178
+ }
179
+ return [];
180
+ }
181
+
143
182
  /**
144
183
  * @param {string|null|undefined} projectRoot
145
184
  * @returns {{ docker: Record<string, unknown>, local: Record<string, unknown> }}
@@ -150,8 +189,10 @@ function buildAppServiceEnvOverlay(projectRoot) {
150
189
  if (!projectRoot || !fsRealSync.existsSync(projectRoot)) {
151
190
  return { docker: overlayDocker, local: overlayLocal };
152
191
  }
153
- for (const { doc, folderName } of listBuilderApplicationDocs(projectRoot)) {
154
- mergeDocIntoOverlay(overlayDocker, overlayLocal, doc, folderName);
192
+ for (const builderDir of collectBuilderScanDirs(projectRoot)) {
193
+ for (const { doc, folderName } of listBuilderApplicationDocsInDir(builderDir)) {
194
+ mergeDocIntoOverlay(overlayDocker, overlayLocal, doc, folderName);
195
+ }
155
196
  }
156
197
  return { docker: overlayDocker, local: overlayLocal };
157
198
  }
@@ -216,6 +216,10 @@ function createEnvConfigPathFunctions(getConfigFn, saveConfigFn) {
216
216
  if (fromProject) {
217
217
  return fromProject;
218
218
  }
219
+ const fromEffective = tryDir(pathsMod.getBuilderRoot());
220
+ if (fromEffective) {
221
+ return fromEffective;
222
+ }
219
223
  const work = pathsMod.getAifabrixWork();
220
224
  return tryDir(work);
221
225
  }
@@ -8,10 +8,50 @@ const path = require('path');
8
8
  const yaml = require('js-yaml');
9
9
  const fsRealSync = require('../internal/fs-real-sync');
10
10
 
11
+ /**
12
+ * @param {string} cfgPath
13
+ * @returns {object|null}
14
+ */
15
+ function tryReadApplicationYamlAt(cfgPath) {
16
+ try {
17
+ if (!fsRealSync.existsSync(cfgPath)) {
18
+ return null;
19
+ }
20
+ const raw = fsRealSync.readFileSync(cfgPath, 'utf8');
21
+ const doc = yaml.load(raw);
22
+ return doc && typeof doc === 'object' ? doc : null;
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Prefer projectRoot/builder (fixtures, tests); then {@link pathsUtil.getBuilderPath} (global npm).
30
+ * @param {string} appKey
31
+ * @param {object} pathsUtil
32
+ * @returns {string[]}
33
+ */
34
+ function collectApplicationYamlPathsForUrlResolve(appKey, pathsUtil) {
35
+ const list = [];
36
+ const root = pathsUtil.getProjectRoot();
37
+ if (root) {
38
+ list.push(path.resolve(path.join(root, 'builder', appKey, 'application.yaml')));
39
+ }
40
+ try {
41
+ const viaBuilder = path.resolve(path.join(pathsUtil.getBuilderPath(appKey), 'application.yaml'));
42
+ if (!list.length || path.resolve(list[0]) !== viaBuilder) {
43
+ list.push(viaBuilder);
44
+ }
45
+ } catch {
46
+ /* ignore getBuilderPath errors */
47
+ }
48
+ return list;
49
+ }
50
+
11
51
  /**
12
52
  * @param {string} appKey
13
53
  * @param {Object} ctx
14
- * @param {object} pathsUtil - paths module (getProjectRoot)
54
+ * @param {object} pathsUtil - paths module (getProjectRoot, getBuilderPath)
15
55
  * @returns {object|null}
16
56
  */
17
57
  function loadApplicationYamlDocForUrlResolve(appKey, ctx, pathsUtil) {
@@ -19,28 +59,18 @@ function loadApplicationYamlDocForUrlResolve(appKey, ctx, pathsUtil) {
19
59
  const current = ctx.currentAppKey || '';
20
60
  if (appKey === current && ctx.variablesPath) {
21
61
  const vp = path.resolve(String(ctx.variablesPath));
22
- try {
23
- const raw = fsRealSync.readFileSync(vp, 'utf8');
24
- const doc = yaml.load(raw);
25
- if (doc && typeof doc === 'object') {
26
- return doc;
27
- }
28
- } catch {
29
- // Fall through to builder-relative resolution
62
+ const fromVars = tryReadApplicationYamlAt(vp);
63
+ if (fromVars) {
64
+ return fromVars;
30
65
  }
31
66
  }
32
- const root = pathsUtil.getProjectRoot();
33
- if (!root) {
34
- return null;
35
- }
36
- const cfgPath = path.resolve(path.join(root, 'builder', appKey, 'application.yaml'));
37
- try {
38
- const raw = fsRealSync.readFileSync(cfgPath, 'utf8');
39
- const doc = yaml.load(raw);
40
- return doc && typeof doc === 'object' ? doc : null;
41
- } catch {
42
- return null;
67
+ for (const cfgPath of collectApplicationYamlPathsForUrlResolve(appKey, pathsUtil)) {
68
+ const doc = tryReadApplicationYamlAt(cfgPath);
69
+ if (doc) {
70
+ return doc;
71
+ }
43
72
  }
73
+ return null;
44
74
  } catch {
45
75
  return null;
46
76
  }
@@ -156,19 +156,12 @@ function mergeDocIntoRegistry(merged, doc, folderName) {
156
156
  }
157
157
 
158
158
  /**
159
- * Merge scan results into registry (does not remove stale keys).
160
- * @param {string|null} projectRoot - getProjectRoot() or null
161
- * @returns {Object} Updated registry
159
+ * @param {Object} merged
160
+ * @param {string} builderDir
162
161
  */
163
- function refreshUrlsLocalRegistryFromBuilder(projectRoot) {
164
- const root = projectRoot || pathsUtil.getProjectRoot();
165
- const merged = { ...readUrlsLocalRegistrySync() };
166
- if (!root) {
167
- return writeMergedRegistry(merged);
168
- }
169
- const builderDir = path.join(root, 'builder');
170
- if (!fsRealSync.existsSync(builderDir) || !fsRealSync.statSync(builderDir).isDirectory()) {
171
- return writeMergedRegistry(merged);
162
+ function mergeBuilderDirIntoRegistry(merged, builderDir) {
163
+ if (!builderDir || !fsRealSync.existsSync(builderDir) || !fsRealSync.statSync(builderDir).isDirectory()) {
164
+ return;
172
165
  }
173
166
  for (const ent of fsRealSync.readdirSync(builderDir, { withFileTypes: true })) {
174
167
  if (!ent.isDirectory()) {
@@ -180,6 +173,37 @@ function refreshUrlsLocalRegistryFromBuilder(projectRoot) {
180
173
  }
181
174
  mergeDocIntoRegistry(merged, doc, ent.name);
182
175
  }
176
+ }
177
+
178
+ /**
179
+ * Merge scan results into registry (does not remove stale keys).
180
+ * @param {string|null} projectRoot - getProjectRoot() or null (same semantics as projectRoot || getProjectRoot())
181
+ * @returns {Object} Updated registry
182
+ */
183
+ function refreshUrlsLocalRegistryFromBuilder(projectRoot) {
184
+ const root = projectRoot || pathsUtil.getProjectRoot();
185
+ const merged = { ...readUrlsLocalRegistrySync() };
186
+ if (!root) {
187
+ return writeMergedRegistry(merged);
188
+ }
189
+ // Published npm tarball omits builder/ under the package root (.npmignore). Global installs must
190
+ // still refresh from the real builder tree (AIFABRIX_BUILDER_DIR or integration base + builder).
191
+ const legacyBuilderDir = path.join(root, 'builder');
192
+ const effectiveBuilderDir = pathsUtil.getBuilderRoot();
193
+ const builderDirs = [legacyBuilderDir];
194
+ try {
195
+ if (
196
+ effectiveBuilderDir &&
197
+ path.resolve(effectiveBuilderDir) !== path.resolve(legacyBuilderDir)
198
+ ) {
199
+ builderDirs.push(effectiveBuilderDir);
200
+ }
201
+ } catch {
202
+ /* ignore path resolution errors */
203
+ }
204
+ for (const builderDir of builderDirs) {
205
+ mergeBuilderDirIntoRegistry(merged, builderDir);
206
+ }
183
207
  return writeMergedRegistry(merged);
184
208
  }
185
209
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aifabrix/builder",
3
- "version": "2.44.1",
3
+ "version": "2.44.2",
4
4
  "description": "AI Fabrix Local Fabric & Deployment SDK",
5
5
  "main": "lib/index.js",
6
6
  "bin": {