@aifabrix/builder 2.44.5 → 2.45.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.
Files changed (249) hide show
  1. package/.cursor/rules/cli-layout.mdc +8 -4
  2. package/.cursor/rules/project-rules.mdc +1 -1
  3. package/README.md +15 -23
  4. package/integration/hubspot-test/README.md +2 -0
  5. package/integration/hubspot-test/test.js +5 -3
  6. package/jest.projects.js +104 -2
  7. package/lib/api/controller-health.api.js +49 -0
  8. package/lib/api/dimension-values.api.js +82 -0
  9. package/lib/api/dimensions.api.js +114 -0
  10. package/lib/api/external-systems.api.js +1 -0
  11. package/lib/api/integration-clients.api.js +168 -0
  12. package/lib/api/types/dimension-values.types.js +28 -0
  13. package/lib/api/types/dimensions.types.js +31 -0
  14. package/lib/api/types/integration-clients.types.js +45 -0
  15. package/lib/api/validation-runner.js +46 -25
  16. package/lib/app/deploy-config.js +11 -1
  17. package/lib/app/deploy-status-display.js +3 -3
  18. package/lib/app/deploy.js +36 -14
  19. package/lib/app/display.js +15 -11
  20. package/lib/app/helpers.js +3 -3
  21. package/lib/app/index.js +3 -3
  22. package/lib/app/push.js +46 -23
  23. package/lib/app/register.js +7 -6
  24. package/lib/app/restart-display.js +126 -0
  25. package/lib/app/rotate-secret.js +7 -6
  26. package/lib/app/run-container-start.js +12 -6
  27. package/lib/app/run-env-compose.js +30 -1
  28. package/lib/app/run-helpers.js +58 -19
  29. package/lib/app/run-reload-sync.js +148 -0
  30. package/lib/app/run-resolve-image.js +51 -1
  31. package/lib/app/run.js +148 -74
  32. package/lib/app/show-display.js +7 -0
  33. package/lib/app/show.js +87 -5
  34. package/lib/build/index.js +83 -49
  35. package/lib/cli/doctor-check.js +117 -0
  36. package/lib/cli/index.js +8 -2
  37. package/lib/cli/infra-guided.js +460 -0
  38. package/lib/cli/installation-log-command.js +73 -0
  39. package/lib/cli/setup-app.js +31 -3
  40. package/lib/cli/setup-auth.js +98 -27
  41. package/lib/cli/setup-dev-path-commands.js +50 -3
  42. package/lib/cli/setup-infra-up-dataplane-action.js +111 -0
  43. package/lib/cli/setup-infra-up-platform-action.js +131 -0
  44. package/lib/cli/setup-infra.js +132 -118
  45. package/lib/cli/setup-integration-client.js +182 -0
  46. package/lib/cli/setup-parameters.js +21 -2
  47. package/lib/cli/setup-platform.js +102 -0
  48. package/lib/cli/setup-secrets.js +18 -6
  49. package/lib/cli/setup-utility-resolve.js +132 -0
  50. package/lib/cli/setup-utility.js +143 -84
  51. package/lib/commands/app-logs.js +81 -33
  52. package/lib/commands/auth-config.js +116 -18
  53. package/lib/commands/datasource-capability-dimension-cli.js +128 -0
  54. package/lib/commands/datasource-capability-output.js +29 -0
  55. package/lib/commands/datasource-capability-relate-cli.js +140 -0
  56. package/lib/commands/datasource-capability.js +411 -0
  57. package/lib/commands/datasource-unified-test-cli.options.js +1 -1
  58. package/lib/commands/datasource.js +53 -13
  59. package/lib/commands/dev-down.js +3 -3
  60. package/lib/commands/dev-infra-gate.js +32 -0
  61. package/lib/commands/dev-init.js +13 -7
  62. package/lib/commands/dimension-value.js +179 -0
  63. package/lib/commands/dimension.js +330 -0
  64. package/lib/commands/integration-client.js +430 -0
  65. package/lib/commands/login-device.js +65 -30
  66. package/lib/commands/login.js +21 -10
  67. package/lib/commands/parameters-validate.js +78 -13
  68. package/lib/commands/repair-datasource-auto-rbac.js +166 -0
  69. package/lib/commands/repair-datasource-keys.js +10 -5
  70. package/lib/commands/repair-datasource.js +19 -7
  71. package/lib/commands/repair-env-template.js +4 -1
  72. package/lib/commands/repair-openapi-sync.js +172 -0
  73. package/lib/commands/repair-persist.js +102 -0
  74. package/lib/commands/repair-rbac-extract.js +27 -0
  75. package/lib/commands/repair-rbac-migrate.js +186 -0
  76. package/lib/commands/repair-rbac.js +214 -31
  77. package/lib/commands/repair-system-alignment.js +246 -0
  78. package/lib/commands/repair-system-permissions.js +168 -0
  79. package/lib/commands/repair.js +120 -338
  80. package/lib/commands/secure.js +1 -1
  81. package/lib/commands/setup-modes.js +468 -0
  82. package/lib/commands/setup-prompts.js +421 -0
  83. package/lib/commands/setup.js +254 -0
  84. package/lib/commands/teardown.js +277 -0
  85. package/lib/commands/up-common.js +113 -19
  86. package/lib/commands/up-dataplane.js +44 -19
  87. package/lib/commands/up-miso.js +18 -18
  88. package/lib/commands/upload.js +111 -23
  89. package/lib/commands/wizard-core-helpers.js +14 -11
  90. package/lib/commands/wizard-core.js +6 -5
  91. package/lib/commands/wizard-dataplane.js +2 -2
  92. package/lib/commands/wizard-entity-selection.js +4 -3
  93. package/lib/commands/wizard-headless.js +2 -1
  94. package/lib/commands/wizard.js +2 -1
  95. package/lib/constants/infra-compose-service-names.js +40 -0
  96. package/lib/core/audit-logger.js +1 -34
  97. package/lib/core/config-admin-email.js +56 -0
  98. package/lib/core/config-normalize.js +60 -0
  99. package/lib/core/config-registered-controller-urls.js +54 -0
  100. package/lib/core/config.js +33 -50
  101. package/lib/core/env-reader.js +16 -3
  102. package/lib/core/secrets-admin-env.js +101 -0
  103. package/lib/core/secrets-ensure-infra.js +34 -1
  104. package/lib/core/secrets-ensure.js +88 -66
  105. package/lib/core/secrets-env-content.js +428 -0
  106. package/lib/core/secrets-env-declarative-expand.js +170 -0
  107. package/lib/core/secrets-env-write.js +29 -1
  108. package/lib/core/secrets-load.js +252 -0
  109. package/lib/core/secrets-names.js +32 -0
  110. package/lib/core/secrets.js +17 -757
  111. package/lib/datasource/capability/basic-exposure.js +76 -0
  112. package/lib/datasource/capability/capability-diff-slice.js +41 -0
  113. package/lib/datasource/capability/capability-key.js +34 -0
  114. package/lib/datasource/capability/capability-resolve.js +172 -0
  115. package/lib/datasource/capability/capability-storage-keys.js +22 -0
  116. package/lib/datasource/capability/copy-operations.js +348 -0
  117. package/lib/datasource/capability/copy-test-payload.js +139 -0
  118. package/lib/datasource/capability/create-operations.js +235 -0
  119. package/lib/datasource/capability/dimension-operations.js +151 -0
  120. package/lib/datasource/capability/dimension-validate.js +219 -0
  121. package/lib/datasource/capability/json-pointer.js +31 -0
  122. package/lib/datasource/capability/reference-rewrite.js +51 -0
  123. package/lib/datasource/capability/relate-operations.js +325 -0
  124. package/lib/datasource/capability/relate-validate.js +219 -0
  125. package/lib/datasource/capability/remove-operations.js +275 -0
  126. package/lib/datasource/capability/run-capability-copy.js +152 -0
  127. package/lib/datasource/capability/run-capability-diff.js +135 -0
  128. package/lib/datasource/capability/run-capability-dimension.js +291 -0
  129. package/lib/datasource/capability/run-capability-edit.js +377 -0
  130. package/lib/datasource/capability/run-capability-relate.js +193 -0
  131. package/lib/datasource/capability/run-capability-remove.js +105 -0
  132. package/lib/datasource/capability/templates/minimal-fetch.json +18 -0
  133. package/lib/datasource/capability/validate-capability-slice.js +35 -0
  134. package/lib/datasource/list.js +136 -23
  135. package/lib/datasource/log-viewer.js +2 -4
  136. package/lib/datasource/unified-validation-run.js +51 -16
  137. package/lib/datasource/validate.js +53 -1
  138. package/lib/deployment/deploy-poll-ui.js +60 -0
  139. package/lib/deployment/deployer-status.js +29 -3
  140. package/lib/deployment/deployer.js +48 -30
  141. package/lib/deployment/environment.js +7 -2
  142. package/lib/deployment/poll-interval.js +72 -0
  143. package/lib/deployment/push.js +11 -9
  144. package/lib/external-system/deploy.js +9 -2
  145. package/lib/external-system/download.js +61 -32
  146. package/lib/external-system/sync-deploy-manifest.js +33 -0
  147. package/lib/infrastructure/index.js +49 -19
  148. package/lib/infrastructure/orphan-infra-docker-teardown.js +177 -0
  149. package/lib/internal/node-fs.js +2 -0
  150. package/lib/parameters/infra-kv-discovery.js +29 -4
  151. package/lib/parameters/infra-parameter-catalog.js +6 -3
  152. package/lib/parameters/infra-parameter-validate.js +67 -19
  153. package/lib/resolvers/datasource-resolver.js +53 -0
  154. package/lib/resolvers/dimension-file.js +52 -0
  155. package/lib/resolvers/manifest-resolver.js +133 -0
  156. package/lib/schema/application-schema.json +4 -0
  157. package/lib/schema/external-datasource.schema.json +183 -53
  158. package/lib/schema/external-system.schema.json +23 -10
  159. package/lib/schema/infra.parameter.yaml +26 -1
  160. package/lib/schema/wizard-config.schema.json +1 -1
  161. package/lib/utils/aifabrix-config-dir-walk.js +40 -0
  162. package/lib/utils/aifabrix-runtime-config-dir.js +26 -3
  163. package/lib/utils/app-config-resolver.js +24 -1
  164. package/lib/utils/app-run-containers.js +2 -2
  165. package/lib/utils/applications-config-defaults.js +206 -0
  166. package/lib/utils/auth-config-validator.js +2 -12
  167. package/lib/utils/bash-secret-env.js +59 -0
  168. package/lib/utils/cli-secrets-error-format.js +78 -0
  169. package/lib/utils/cli-test-layout-chalk.js +31 -9
  170. package/lib/utils/cli-utils.js +4 -36
  171. package/lib/utils/compose-generate-docker-compose.js +111 -6
  172. package/lib/utils/compose-generator.js +17 -8
  173. package/lib/utils/controller-url.js +50 -7
  174. package/lib/utils/datasource-test-run-display.js +8 -0
  175. package/lib/utils/dev-hosts-helper.js +3 -2
  176. package/lib/utils/dev-init-ssh-merge.js +2 -1
  177. package/lib/utils/docker-build.js +17 -9
  178. package/lib/utils/docker-reload-mount.js +127 -0
  179. package/lib/utils/env-copy.js +99 -14
  180. package/lib/utils/env-template.js +5 -1
  181. package/lib/utils/external-readme.js +71 -2
  182. package/lib/utils/external-system-local-test-tty.js +3 -2
  183. package/lib/utils/external-system-readiness-core.js +45 -12
  184. package/lib/utils/external-system-readiness-deploy-display.js +3 -3
  185. package/lib/utils/external-system-readiness-display-internals.js +33 -3
  186. package/lib/utils/external-system-readiness-display.js +10 -1
  187. package/lib/utils/file-upload.js +40 -3
  188. package/lib/utils/health-check-db-init.js +107 -0
  189. package/lib/utils/health-check-public-warn.js +69 -0
  190. package/lib/utils/health-check-url.js +28 -10
  191. package/lib/utils/health-check.js +139 -107
  192. package/lib/utils/help-builder.js +5 -1
  193. package/lib/utils/image-name.js +34 -7
  194. package/lib/utils/infra-optional-service-flags.js +69 -0
  195. package/lib/utils/installation-log-core.js +282 -0
  196. package/lib/utils/installation-log-record.js +237 -0
  197. package/lib/utils/installation-log.js +123 -0
  198. package/lib/utils/integration-file-backup.js +74 -0
  199. package/lib/utils/log-redaction.js +105 -0
  200. package/lib/utils/manifest-location.js +164 -0
  201. package/lib/utils/manifest-source-emit.js +162 -0
  202. package/lib/utils/mutagen-install.js +30 -3
  203. package/lib/utils/paths.js +308 -76
  204. package/lib/utils/postgres-wipe.js +212 -0
  205. package/lib/utils/register-aifabrix-shell-env.js +15 -0
  206. package/lib/utils/remote-dev-auth.js +21 -5
  207. package/lib/utils/remote-docker-env.js +9 -1
  208. package/lib/utils/remote-secrets-loader.js +49 -4
  209. package/lib/utils/resolve-docker-image-ref.js +9 -3
  210. package/lib/utils/run-cli-flags.js +29 -0
  211. package/lib/utils/secrets-ancestor-paths.js +47 -0
  212. package/lib/utils/secrets-canonical.js +10 -3
  213. package/lib/utils/secrets-helpers.js +17 -10
  214. package/lib/utils/secrets-kv-refs.js +42 -0
  215. package/lib/utils/secrets-kv-scope.js +19 -2
  216. package/lib/utils/secrets-materialize-local.js +134 -0
  217. package/lib/utils/secrets-path.js +26 -13
  218. package/lib/utils/secrets-utils.js +20 -10
  219. package/lib/utils/system-builder-root.js +42 -0
  220. package/lib/utils/url-declarative-public-base.js +80 -12
  221. package/lib/utils/url-declarative-resolve-build-urls.js +238 -0
  222. package/lib/utils/url-declarative-resolve-build.js +24 -388
  223. package/lib/utils/url-declarative-resolve-expand-token.js +189 -0
  224. package/lib/utils/url-declarative-resolve-load-doc.js +12 -3
  225. package/lib/utils/url-declarative-resolve-surface-state.js +102 -0
  226. package/lib/utils/url-declarative-resolve.js +47 -7
  227. package/lib/utils/url-declarative-runtime-base-path.js +52 -0
  228. package/lib/utils/url-declarative-vdir-inactive-env.js +2 -1
  229. package/lib/utils/urls-local-registry-scan.js +103 -0
  230. package/lib/utils/urls-local-registry.js +158 -76
  231. package/lib/utils/validation-poll-ui.js +81 -0
  232. package/lib/utils/validation-run-poll.js +29 -5
  233. package/lib/utils/with-muted-logger.js +53 -0
  234. package/package.json +3 -1
  235. package/templates/applications/dataplane/application.yaml +5 -1
  236. package/templates/applications/dataplane/rbac.yaml +10 -10
  237. package/templates/applications/keycloak/env.template +8 -6
  238. package/templates/applications/miso-controller/application.yaml +9 -0
  239. package/templates/applications/miso-controller/env.template +27 -29
  240. package/templates/applications/miso-controller/rbac.yaml +9 -9
  241. package/templates/external-system/README.md.hbs +83 -123
  242. package/.npmrc.token +0 -1
  243. package/.nyc_output/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  244. package/.nyc_output/processinfo/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  245. package/.nyc_output/processinfo/index.json +0 -1
  246. package/lib/api/service-users.api.js +0 -150
  247. package/lib/api/types/service-users.types.js +0 -65
  248. package/lib/cli/setup-service-user.js +0 -187
  249. package/lib/commands/service-user.js +0 -429
@@ -17,6 +17,7 @@ const {
17
17
  getAifabrixRuntimeConfigDir,
18
18
  resolveAifabrixHomeLikePath
19
19
  } = require('./aifabrix-runtime-config-dir');
20
+ const { resolveSystemBuilderParentDir } = require('./system-builder-root');
20
21
 
21
22
  function safeHomedir() {
22
23
  try {
@@ -43,9 +44,10 @@ function getConfigDirForPaths() {
43
44
  }
44
45
 
45
46
  /**
46
- * User-owned `secrets.local.yaml` beside effective `config.yaml` (see {@link getConfigDirForPaths}).
47
- * When `AIFABRIX_HOME` is `$HOME` but config is only at `$HOME/.aifabrix/config.yaml`, uses that folder.
48
- * Keeps `secret list` / `secret set` aligned with resolve merge (`loadPrimaryUserSecrets`).
47
+ * User-owned `secrets.local.yaml` beside `config.yaml` (same directory as {@link getConfigDirForPaths}).
48
+ * When `aifabrix-home` is the POSIX home (e.g. `/home/user`), secrets stay under `~/.aifabrix/`, not
49
+ * in the home root. {@link getAifabrixHome} remains for applications base, builder parent, and
50
+ * legacy secrets migration reads in {@link module:lib/utils/secrets-utils}.
49
51
  *
50
52
  * @returns {string} Absolute path to secrets.local.yaml
51
53
  */
@@ -315,7 +317,8 @@ function getDevDirectory(appName, developerId) {
315
317
 
316
318
  /**
317
319
  * Gets the application path (builder or integration folder).
318
- * Matches getBuilderPath / getIntegrationPath: respects AIFABRIX_BUILDER_DIR and project-root vs cwd base.
320
+ * Matches {@link getBuilderPath} / {@link getIntegrationPath} (cwd `integration/` / `builder/`, then
321
+ * material `(aifabrix-work | aifabrix-home)` trees).
319
322
  * @param {string} appName - Application name
320
323
  * @param {string} [appType] - Application type ('external' or other)
321
324
  * @returns {string} Absolute path to application directory
@@ -325,59 +328,161 @@ function getAppPath(appName, appType) {
325
328
  throw new Error('App name is required and must be a string');
326
329
  }
327
330
  if (appType === 'external') {
328
- return getIntegrationPath(appName);
331
+ return module.exports.getIntegrationPath(appName);
329
332
  }
330
- return path.join(getBuilderRoot(), appName);
333
+ return module.exports.getBuilderPath(appName);
331
334
  }
332
335
 
333
336
  /**
334
- * Base directory for integration/builder: project root when cwd is inside project, else cwd.
335
- * When `aifabrix-work` / `AIFABRIX_WORK` points at a repo that contains `integration/`, use that
336
- * root even if cwd is elsewhere (e.g. global CLI install + cwd under `integration/<app>/`).
337
- * @returns {string} Directory to resolve integration/ and builder/ from
337
+ * Apps materialization / default repo root: **`aifabrix-work`** (or `AIFABRIX_WORK`) when set, else
338
+ * {@link getAifabrixHome}. Used for `integration/` and `builder/` under that parent (not the CLI
339
+ * install tree). Aligns with setup / `up-platform` / `up-miso` / `up-dataplane` template targets.
340
+ *
341
+ * @returns {string} Absolute directory (no trailing `integration/` or `builder/`)
338
342
  */
339
- function getIntegrationBuilderBaseDir() {
343
+ function getAppsMaterializationParent() {
340
344
  const work = getAifabrixWork();
341
345
  if (work) {
342
- const workNorm = path.resolve(work);
343
- const integrationUnderWork = path.join(workNorm, 'integration');
344
- try {
345
- if (fs.existsSync(integrationUnderWork)) {
346
- return workNorm;
347
- }
348
- } catch {
349
- // ignore fs errors
350
- }
351
- }
352
- const root = getProjectRoot();
353
- const cwd = path.resolve(process.cwd());
354
- const rootNorm = path.resolve(root);
355
- if (cwd === rootNorm || cwd.startsWith(rootNorm + path.sep)) {
356
- return rootNorm;
346
+ return path.resolve(work);
357
347
  }
358
- return cwd;
348
+ return path.resolve(getAifabrixHome());
359
349
  }
360
350
 
361
351
  /**
362
- * Returns the integration root directory (used for listing apps).
352
+ * Base directory for legacy callers that meant “non-cwd app tree”: same as {@link getAppsMaterializationParent}.
353
+ *
354
+ * @returns {string}
355
+ */
356
+ function getIntegrationBuilderBaseDir() {
357
+ return getAppsMaterializationParent();
358
+ }
359
+
360
+ /**
361
+ * Returns the default integration root under {@link getAppsMaterializationParent} (listing may also
362
+ * scan {@link getCwdIntegrationRoot}; see {@link listIntegrationAppNames}).
363
+ *
363
364
  * @returns {string} Absolute path to integration/ directory
364
365
  */
365
366
  function getIntegrationRoot() {
366
- return path.join(getIntegrationBuilderBaseDir(), 'integration');
367
+ return path.join(getAppsMaterializationParent(), 'integration');
368
+ }
369
+
370
+ /**
371
+ * Absolute `integration/` next to {@link process.cwd} when that directory exists.
372
+ *
373
+ * @returns {string|null}
374
+ */
375
+ function getCwdIntegrationRoot() {
376
+ const p = path.join(path.resolve(process.cwd()), 'integration');
377
+ try {
378
+ if (nodeFs().existsSync(p)) {
379
+ const st = nodeFs().statSync(p);
380
+ if (st && typeof st.isDirectory === 'function' && st.isDirectory()) {
381
+ return p;
382
+ }
383
+ }
384
+ } catch {
385
+ // ignore
386
+ }
387
+ return null;
367
388
  }
368
389
 
369
390
  /**
370
- * Returns the builder root directory. Uses AIFABRIX_BUILDER_DIR when set, else project/cwd + builder.
391
+ * Absolute `builder/` next to {@link process.cwd} when that directory exists.
392
+ *
393
+ * @returns {string|null}
394
+ */
395
+ function getCwdBuilderRoot() {
396
+ const p = path.join(path.resolve(process.cwd()), 'builder');
397
+ try {
398
+ if (nodeFs().existsSync(p)) {
399
+ const st = nodeFs().statSync(p);
400
+ if (st && typeof st.isDirectory === 'function' && st.isDirectory()) {
401
+ return p;
402
+ }
403
+ }
404
+ } catch {
405
+ // ignore
406
+ }
407
+ return null;
408
+ }
409
+
410
+ /**
411
+ * Returns the material `builder/` root: {@link getAppsMaterializationParent}`/builder`
412
+ * (same as {@link getSystemBuilderRoot}). Does not use `AIFABRIX_BUILDER_DIR` — app paths follow
413
+ * cwd + `integration/` / `builder/` or this root only.
414
+ *
371
415
  * @returns {string} Absolute path to builder/ directory
372
416
  */
373
417
  function getBuilderRoot() {
374
- const envDir = process.env.AIFABRIX_BUILDER_DIR && typeof process.env.AIFABRIX_BUILDER_DIR === 'string'
375
- ? process.env.AIFABRIX_BUILDER_DIR.trim()
376
- : null;
377
- if (envDir) {
378
- return path.resolve(envDir);
418
+ return path.join(getAppsMaterializationParent(), 'builder');
419
+ }
420
+
421
+ /**
422
+ * Platform system apps (`up-platform` / `up-miso` / `up-dataplane` / `setup`): materialize under
423
+ * {@link getSystemBuilderRoot} (= {@link getAppsMaterializationParent}`/builder`), never the global CLI package tree.
424
+ * @readonly
425
+ */
426
+ const SYSTEM_BUILDER_APP_KEYS = Object.freeze(['keycloak', 'miso-controller', 'dataplane']);
427
+
428
+ /**
429
+ * @param {string} appName
430
+ * @returns {boolean}
431
+ */
432
+ function isSystemBuilderAppName(appName) {
433
+ if (!appName || typeof appName !== 'string') return false;
434
+ return SYSTEM_BUILDER_APP_KEYS.includes(appName);
435
+ }
436
+
437
+ /**
438
+ * `builder/<appName>` under {@link getAppsMaterializationParent} (same tree as {@link getSystemBuilderRoot}).
439
+ *
440
+ * @param {string} appName
441
+ * @returns {string}
442
+ */
443
+ function getProjectBuilderAppPath(appName) {
444
+ return path.join(getIntegrationBuilderBaseDir(), 'builder', appName);
445
+ }
446
+
447
+ /**
448
+ * Same parent as {@link getAppsMaterializationParent} (exported for diagnostics / allowlist checks).
449
+ *
450
+ * @returns {string} Absolute path (no trailing `builder/`)
451
+ */
452
+ function getSystemPlatformMaterializationParent() {
453
+ return getAppsMaterializationParent();
454
+ }
455
+
456
+ /**
457
+ * Default `builder/` root for materialized platform apps and Tier‑2 manifest discovery:
458
+ * {@link getAppsMaterializationParent}`/builder`.
459
+ *
460
+ * @returns {string}
461
+ */
462
+ function getSystemBuilderRoot() {
463
+ return path.join(getAppsMaterializationParent(), 'builder');
464
+ }
465
+
466
+ /**
467
+ * True when `projectAppPath` is a directory that contains a resolvable application config
468
+ * (`application.yaml` / `.json` / legacy `variables.yaml`). Empty `builder/<platformApp>`
469
+ * stubs must not win over {@link getSystemBuilderRoot} or secrets/run would read the wrong tree
470
+ * (missing `env.template` → skipped ensure → "Missing secrets" on first platform boot).
471
+ *
472
+ * @param {string} projectAppPath - Absolute `.../builder/<app>`
473
+ * @returns {boolean}
474
+ */
475
+ function isProjectBuilderAppDirectory(projectAppPath) {
476
+ try {
477
+ if (!projectAppPath || !nodeFs().existsSync(projectAppPath)) return false;
478
+ const st = nodeFs().statSync(projectAppPath);
479
+ if (!st || typeof st.isDirectory !== 'function' || !st.isDirectory()) return false;
480
+ const { resolveApplicationConfigPath } = require('./app-config-resolver');
481
+ resolveApplicationConfigPath(projectAppPath);
482
+ return true;
483
+ } catch {
484
+ return false;
379
485
  }
380
- return path.join(getIntegrationBuilderBaseDir(), 'builder');
381
486
  }
382
487
 
383
488
  /**
@@ -404,49 +509,67 @@ function isAppSubdirSync(root, name) {
404
509
  */
405
510
  function listIntegrationAppNames() {
406
511
  const disk = nodeFs();
407
- const root = getIntegrationRoot();
408
- if (!disk.existsSync(root)) {
409
- return [];
512
+ const names = new Set();
513
+ const cwdRoot = getCwdIntegrationRoot();
514
+ if (cwdRoot) {
515
+ addBuilderSubdirNamesToSet(disk, cwdRoot, names, null);
410
516
  }
411
- let rootStat;
517
+ const matInt = getIntegrationRoot();
518
+ addBuilderSubdirNamesToSet(disk, matInt, names, null);
519
+ return [...names].sort();
520
+ }
521
+
522
+ /**
523
+ * Adds immediate subdirectory names under a builder root into `names` (real disk via {@link nodeFs}).
524
+ * @param {ReturnType<typeof nodeFs>} disk
525
+ * @param {string} builderRootDir
526
+ * @param {Set<string>} names
527
+ * @param {(name: string) => boolean} [nameFilter] - When set, only names passing this filter (after dir check)
528
+ */
529
+ function addBuilderSubdirNamesToSet(disk, builderRootDir, names, nameFilter) {
530
+ if (!builderRootDir || !disk.existsSync(builderRootDir)) return;
531
+ let st;
412
532
  try {
413
- rootStat = disk.statSync(root);
533
+ st = disk.statSync(builderRootDir);
414
534
  } catch {
415
- return [];
535
+ return;
416
536
  }
417
- if (!rootStat || typeof rootStat.isDirectory !== 'function' || !rootStat.isDirectory()) {
418
- return [];
537
+ if (!st || typeof st.isDirectory !== 'function' || !st.isDirectory()) return;
538
+ try {
539
+ for (const name of disk.readdirSync(builderRootDir)) {
540
+ if (!isAppSubdirSync(builderRootDir, name)) continue;
541
+ if (nameFilter && !nameFilter(name)) continue;
542
+ names.add(name);
543
+ }
544
+ } catch {
545
+ // ignore
419
546
  }
420
- const entries = disk.readdirSync(root);
421
- return entries.filter((name) => isAppSubdirSync(root, name)).sort();
422
547
  }
423
548
 
424
549
  /**
425
- * Lists app names (directories) under builder root. Excludes dot-prefixed entries.
426
- * Returns [] if root does not exist.
550
+ * Lists app names (directories) under builder roots. Excludes dot-prefixed entries.
551
+ * Merges `cwd/builder` and material `(aifabrix-work | aifabrix-home)/builder` (deduped).
427
552
  * @returns {string[]} Sorted list of app directory names
428
553
  */
429
554
  function listBuilderAppNames() {
430
555
  const disk = nodeFs();
431
- const root = getBuilderRoot();
432
- if (!disk.existsSync(root)) {
433
- return [];
434
- }
435
- let rootStat;
436
- try {
437
- rootStat = disk.statSync(root);
438
- } catch {
439
- return [];
556
+ const names = new Set();
557
+ const roots = new Set();
558
+ const cwdRoot = getCwdBuilderRoot();
559
+ if (cwdRoot) {
560
+ roots.add(path.resolve(cwdRoot));
440
561
  }
441
- if (!rootStat || typeof rootStat.isDirectory !== 'function' || !rootStat.isDirectory()) {
442
- return [];
562
+ roots.add(path.resolve(getSystemBuilderRoot()));
563
+ for (const r of roots) {
564
+ addBuilderSubdirNamesToSet(disk, r, names, null);
443
565
  }
444
- const entries = disk.readdirSync(root);
445
- return entries.filter((name) => isAppSubdirSync(root, name)).sort();
566
+ return [...names].sort();
446
567
  }
447
568
 
448
569
  /**
449
- * Gets the integration folder path for external systems.
570
+ * Gets the integration folder path: **`cwd/integration/<appName>`** when that directory exists, else
571
+ * **`aifabrix-work` or `aifabrix-home` + `/integration/<appName>`**.
572
+ *
450
573
  * @param {string} appName - Application name
451
574
  * @returns {string} Absolute path to integration directory
452
575
  */
@@ -454,8 +577,18 @@ function getIntegrationPath(appName) {
454
577
  if (!appName || typeof appName !== 'string') {
455
578
  throw new Error('App name is required and must be a string');
456
579
  }
457
- const base = getIntegrationBuilderBaseDir();
458
- return path.join(base, 'integration', appName);
580
+ const cwdInt = path.join(path.resolve(process.cwd()), 'integration', appName);
581
+ try {
582
+ if (nodeFs().existsSync(cwdInt)) {
583
+ const st = nodeFs().statSync(cwdInt);
584
+ if (st && typeof st.isDirectory === 'function' && st.isDirectory()) {
585
+ return cwdInt;
586
+ }
587
+ }
588
+ } catch {
589
+ // ignore
590
+ }
591
+ return path.join(getAppsMaterializationParent(), 'integration', appName);
459
592
  }
460
593
 
461
594
  /**
@@ -473,7 +606,39 @@ function resolveBuildContext(configDir, buildContext) {
473
606
  }
474
607
 
475
608
  /**
476
- * Gets the builder folder path. Uses AIFABRIX_BUILDER_DIR when set, else project root.
609
+ * `cwd/builder/<appName>` when present as a directory and allowed (platform empty stubs skipped).
610
+ *
611
+ * @param {string} appName
612
+ * @returns {string|null}
613
+ */
614
+ function tryCwdBuilderPathOrNull(appName) {
615
+ const cwdApp = path.join(path.resolve(process.cwd()), 'builder', appName);
616
+ try {
617
+ if (!nodeFs().existsSync(cwdApp)) {
618
+ return null;
619
+ }
620
+ const st = nodeFs().statSync(cwdApp);
621
+ if (!st || typeof st.isDirectory !== 'function' || !st.isDirectory()) {
622
+ return null;
623
+ }
624
+ if (isSystemBuilderAppName(appName) && !isProjectBuilderAppDirectory(cwdApp)) {
625
+ return null;
626
+ }
627
+ return cwdApp;
628
+ } catch {
629
+ return null;
630
+ }
631
+ }
632
+
633
+ /**
634
+ * Gets the builder folder path:
635
+ * 1. Plan 141 Tier‑1: `cwd/builder/<app>` or `cwd/integration/<app>` when a resolvable application manifest exists.
636
+ * 2. Else **`cwd/builder/<app>`** when that directory exists. For **platform** apps (`keycloak`,
637
+ * `miso-controller`, `dataplane`), an **empty** cwd stub is ignored so materialization under
638
+ * `(work|home)/builder` still wins.
639
+ * 3. Else **`(aifabrix-work or aifabrix-home)/builder/<appName>`** — used by setup / `up-platform` /
640
+ * `up-miso` / `up-dataplane` for template materialization.
641
+ *
477
642
  * @param {string} appName - Application name
478
643
  * @returns {string} Absolute path to builder directory
479
644
  */
@@ -481,14 +646,20 @@ function getBuilderPath(appName) {
481
646
  if (!appName || typeof appName !== 'string') {
482
647
  throw new Error('App name is required and must be a string');
483
648
  }
484
- const builderRoot = process.env.AIFABRIX_BUILDER_DIR && typeof process.env.AIFABRIX_BUILDER_DIR === 'string'
485
- ? process.env.AIFABRIX_BUILDER_DIR.trim()
486
- : null;
487
- if (builderRoot) {
488
- return path.join(path.resolve(builderRoot), appName);
649
+ const { resolveApplicationManifestPathSync } = require('./manifest-location');
650
+ const manifestHit = resolveApplicationManifestPathSync({
651
+ targetKey: appName,
652
+ mode: 'auto',
653
+ cwd: process.cwd()
654
+ });
655
+ if (manifestHit && (manifestHit.tier === 'cwd-builder' || manifestHit.tier === 'cwd-integration')) {
656
+ return manifestHit.absolutePath;
489
657
  }
490
- const base = getIntegrationBuilderBaseDir();
491
- return path.join(base, 'builder', appName);
658
+ const cwdHit = tryCwdBuilderPathOrNull(appName);
659
+ if (cwdHit) {
660
+ return cwdHit;
661
+ }
662
+ return path.join(getAppsMaterializationParent(), 'builder', appName);
492
663
  }
493
664
 
494
665
  /**
@@ -639,16 +810,66 @@ async function getResolveAppPath(appName) {
639
810
  return { appPath: integrationPath, envOnly: true };
640
811
  }
641
812
  }
813
+ const { resolveApplicationManifestPathSync } = require('./manifest-location');
814
+ const manifestHit = resolveApplicationManifestPathSync({
815
+ targetKey: appName,
816
+ mode: 'auto',
817
+ cwd: process.cwd()
818
+ });
819
+ if (manifestHit) {
820
+ return { appPath: manifestHit.absolutePath, envOnly: false };
821
+ }
642
822
  const result = await detectAppType(appName);
643
823
  return { appPath: result.appPath, envOnly: false };
644
824
  }
645
825
 
646
- /** Resolve app folder name when cwd is inside integration/<systemKey>/. */
826
+ /**
827
+ * @param {string} walkDir - Candidate workspace root (walk upward from cwd)
828
+ * @param {string} cwd - Resolved process.cwd()
829
+ * @param {ReturnType<typeof nodeFs>} disk
830
+ * @returns {string|null}
831
+ */
832
+ function tryIntegrationAppKeyAtWalkStep(walkDir, cwd, disk) {
833
+ const integrationDir = path.join(walkDir, 'integration');
834
+ try {
835
+ if (!disk.existsSync(integrationDir)) {
836
+ return null;
837
+ }
838
+ const intNorm = path.resolve(integrationDir);
839
+ if (cwd !== intNorm && !cwd.startsWith(intNorm + path.sep)) {
840
+ return null;
841
+ }
842
+ const rel = path.relative(intNorm, cwd);
843
+ if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) {
844
+ return null;
845
+ }
846
+ return rel.split(path.sep)[0] || null;
847
+ } catch {
848
+ return null;
849
+ }
850
+ }
851
+
852
+ /**
853
+ * Resolve app folder name when cwd is under some ancestor's `integration/<systemKey>/`.
854
+ * Walks up from cwd (does not use {@link getIntegrationBuilderBaseDir}) so detection still works
855
+ * when the canonical base is config/home but the shell is inside a checkout's integration tree.
856
+ */
647
857
  function resolveIntegrationAppKeyFromCwd() {
648
- const integrationNorm = path.resolve(path.join(getIntegrationBuilderBaseDir(), 'integration'));
649
858
  const cwd = path.resolve(process.cwd());
650
- if (cwd !== integrationNorm && !cwd.startsWith(integrationNorm + path.sep)) return null;
651
- return path.relative(integrationNorm, cwd).split(path.sep)[0] || null;
859
+ const disk = nodeFs();
860
+ let p = cwd;
861
+ for (let i = 0; i < 64; i += 1) {
862
+ const key = tryIntegrationAppKeyAtWalkStep(p, cwd, disk);
863
+ if (key) {
864
+ return key;
865
+ }
866
+ const parent = path.dirname(p);
867
+ if (parent === p) {
868
+ break;
869
+ }
870
+ p = parent;
871
+ }
872
+ return null;
652
873
  }
653
874
 
654
875
  module.exports = {
@@ -660,11 +881,22 @@ module.exports = {
660
881
  getApplicationsBaseDir,
661
882
  getDevDirectory,
662
883
  getAppPath,
884
+ getAppsMaterializationParent,
885
+ getCwdIntegrationRoot,
886
+ getCwdBuilderRoot,
663
887
  getProjectRoot,
888
+ findProjectRootFromCwd,
664
889
  getIntegrationPath,
665
890
  getBuilderPath,
666
891
  getIntegrationRoot,
667
892
  getBuilderRoot,
893
+ getIntegrationBuilderBaseDir,
894
+ SYSTEM_BUILDER_APP_KEYS,
895
+ isSystemBuilderAppName,
896
+ getProjectBuilderAppPath,
897
+ getSystemPlatformMaterializationParent,
898
+ getSystemBuilderRoot,
899
+ resolveSystemBuilderParentDir,
668
900
  listIntegrationAppNames,
669
901
  listBuilderAppNames,
670
902
  resolveBuildContext,