@aifabrix/builder 2.45.5 → 2.47.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 (355) hide show
  1. package/.cursor/rules/cli-layout.mdc +2 -0
  2. package/.cursor/rules/cli-product-neutral.mdc +43 -0
  3. package/.cursor/rules/project-rules.mdc +1 -0
  4. package/.npmrc.token +1 -0
  5. package/.temp/jest-protection-convert/run-IFN2nv/.protection/hubspot-companies.json +1 -0
  6. package/.temp/jest-protection-convert/run-IvmKLO/.protection/hubspot-companies.yaml +17 -0
  7. package/.temp/jest-protection-convert/run-SdvblF/.protection/hubspot-companies.json +1 -0
  8. package/.temp/jest-protection-convert/run-b51TsW/.protection/hubspot-companies.json +1 -0
  9. package/.temp/jest-protection-convert/run-fiLmb1/.protection/hubspot-companies.json +1 -0
  10. package/.temp/jest-protection-convert/run-qYGHMU/.protection/hubspot-companies.yaml +17 -0
  11. package/.temp/jest-protection-fixtures/hubspot-Mq5re8/hubspot-companies.yaml +17 -0
  12. package/.temp/jest-protection-fixtures/hubspot-f3u4YL/hubspot-companies.yaml +17 -0
  13. package/.temp/jest-protection-fixtures/hubspot-iQtd1O/hubspot-companies.yaml +17 -0
  14. package/.temp/jest-protection-fixtures/hubspot-mdmz5M/hubspot-companies.yaml +17 -0
  15. package/.temp/jest-protection-fixtures/hubspot-qbKYxQ/hubspot-companies.yaml +17 -0
  16. package/.temp/jest-protection-fixtures/hubspot-yzlJy6/hubspot-companies.yaml +17 -0
  17. package/.temp/validation/summary.txt +5 -0
  18. package/README.md +43 -17
  19. package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +1 -1
  20. package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +1 -1
  21. package/jest.config.default.js +2 -1
  22. package/jest.config.isolated.js +2 -1
  23. package/jest.global-live-fabrix-hooks.js +36 -0
  24. package/jest.isolated-projects.js +146 -0
  25. package/jest.projects.js +65 -136
  26. package/lib/api/api-response-errors.js +124 -0
  27. package/lib/api/dataplane-health.api.js +19 -7
  28. package/lib/api/governance-scenario-pack.api.js +159 -0
  29. package/lib/api/index.js +43 -3
  30. package/lib/api/lifecycle.api.js +98 -0
  31. package/lib/api/pipeline.api.js +3 -1
  32. package/lib/api/records-bulk.api.js +14 -1
  33. package/lib/api/resource-types.api.js +107 -0
  34. package/lib/api/runtime-configuration.api.js +72 -0
  35. package/lib/api/types/resource-types.types.js +45 -0
  36. package/lib/api/user-groups.api.js +9 -9
  37. package/lib/api/users.api.js +8 -8
  38. package/lib/api/validation-runner.js +3 -1
  39. package/lib/api/wizard.api.js +10 -2
  40. package/lib/app/deploy-config.js +4 -1
  41. package/lib/app/down.js +78 -27
  42. package/lib/app/index.js +2 -2
  43. package/lib/app/push.js +4 -5
  44. package/lib/app/register.js +7 -4
  45. package/lib/app/run-env-compose.js +84 -30
  46. package/lib/app/run-resolve-image.js +2 -1
  47. package/lib/app/run-stop-containers.js +113 -0
  48. package/lib/app/run.js +1 -31
  49. package/lib/app/show-display.js +35 -0
  50. package/lib/app/show.js +32 -1
  51. package/lib/build/index.js +22 -5
  52. package/lib/build/miso-controller-npm-sdk-guard.js +25 -0
  53. package/lib/certification/sync-system-certification.js +13 -6
  54. package/lib/channels/add-channel.js +348 -0
  55. package/lib/channels/channel-artifact-generator.js +141 -0
  56. package/lib/channels/channel-audit.js +101 -0
  57. package/lib/channels/channel-auth-context.js +37 -0
  58. package/lib/channels/channel-credential.js +200 -0
  59. package/lib/channels/channel-display-name.js +64 -0
  60. package/lib/channels/channel-external-system.js +106 -0
  61. package/lib/channels/channel-integration-generator.js +274 -0
  62. package/lib/channels/channel-probe.js +89 -0
  63. package/lib/channels/channel-runtime-resolver.js +152 -0
  64. package/lib/channels/channel-status-display.js +141 -0
  65. package/lib/channels/channel-targets.js +115 -0
  66. package/lib/cli/index.js +20 -4
  67. package/lib/cli/infra-guided-auth.js +109 -0
  68. package/lib/cli/infra-guided.js +84 -81
  69. package/lib/cli/setup-app.help.js +0 -31
  70. package/lib/cli/setup-app.js +82 -5
  71. package/lib/cli/setup-app.test-commands.js +5 -57
  72. package/lib/cli/setup-auth.js +25 -0
  73. package/lib/cli/setup-certification-commands.js +147 -0
  74. package/lib/cli/setup-certification.help.js +141 -0
  75. package/lib/cli/setup-environment.js +19 -2
  76. package/lib/cli/setup-external-system-help.js +62 -0
  77. package/lib/cli/setup-external-system.js +25 -3
  78. package/lib/cli/setup-infra-up-dataplane-action.js +2 -2
  79. package/lib/cli/setup-infra-up-platform-action.js +2 -1
  80. package/lib/cli/setup-infra.js +9 -1
  81. package/lib/cli/setup-platform.js +6 -1
  82. package/lib/cli/setup-secrets.js +33 -1
  83. package/lib/cli/setup-utility-help-after.js +25 -2
  84. package/lib/cli/setup-utility-repair.js +198 -0
  85. package/lib/cli/setup-utility-resolve.js +44 -5
  86. package/lib/cli/setup-utility-validate.js +197 -0
  87. package/lib/cli/setup-utility.js +4 -239
  88. package/lib/commands/app-logs.js +54 -26
  89. package/lib/commands/app.js +41 -0
  90. package/lib/commands/auth-status-client-tokens.js +214 -0
  91. package/lib/commands/auth-status-dataplane-version.js +142 -68
  92. package/lib/commands/auth-status.js +43 -54
  93. package/lib/commands/channel.js +58 -0
  94. package/lib/commands/datasource-test-trust-cli.js +27 -23
  95. package/lib/commands/datasource-unified-test-cli.js +6 -3
  96. package/lib/commands/datasource-unified-test-cli.options.js +5 -1
  97. package/lib/commands/datasource.js +8 -8
  98. package/lib/commands/dev-show-display.js +8 -1
  99. package/lib/commands/governance-cli-shared.js +94 -0
  100. package/lib/commands/governance-command-action.js +132 -0
  101. package/lib/commands/governance-download-external.js +34 -0
  102. package/lib/commands/governance-generate-external.js +71 -0
  103. package/lib/commands/governance-upload-external.js +36 -0
  104. package/lib/commands/governance-verify-external.js +64 -0
  105. package/lib/commands/governance.js +144 -0
  106. package/lib/commands/identity-apply-sync.js +49 -8
  107. package/lib/commands/identity-group-cli.js +66 -26
  108. package/lib/commands/identity-membership-role-cache.js +58 -37
  109. package/lib/commands/identity-shared.js +22 -1
  110. package/lib/commands/identity-user-cli.js +80 -34
  111. package/lib/commands/lifecycle-command-action.js +123 -0
  112. package/lib/commands/lifecycle-external.js +268 -0
  113. package/lib/commands/login.js +11 -2
  114. package/lib/commands/repair-auth-apply.js +153 -0
  115. package/lib/commands/repair-auth-config.js +8 -13
  116. package/lib/commands/repair-changed-files.js +41 -0
  117. package/lib/commands/repair-datasource-com-v2.js +15 -0
  118. package/lib/commands/repair-datasource-keys.js +4 -1
  119. package/lib/commands/repair-datasource-run.js +59 -0
  120. package/lib/commands/repair-datasource-service-contract.js +206 -0
  121. package/lib/commands/repair-datasource-test-payload.js +259 -0
  122. package/lib/commands/repair-datasource.js +49 -64
  123. package/lib/commands/repair-env-template.js +76 -25
  124. package/lib/commands/repair-external-run.js +216 -0
  125. package/lib/commands/repair-openapi-sync.js +49 -4
  126. package/lib/commands/repair-persist.js +4 -0
  127. package/lib/commands/repair-rbac.js +52 -1
  128. package/lib/commands/repair-steps.js +160 -0
  129. package/lib/commands/repair-system-alignment.js +7 -4
  130. package/lib/commands/repair.js +29 -296
  131. package/lib/commands/resource-type.js +371 -0
  132. package/lib/commands/secrets-get.js +184 -0
  133. package/lib/commands/secrets-remove-all.js +3 -1
  134. package/lib/commands/setup-image-refresh.js +146 -0
  135. package/lib/commands/setup-modes.js +158 -162
  136. package/lib/commands/setup-platform-auth.js +76 -0
  137. package/lib/commands/setup-prompts-admin.js +244 -0
  138. package/lib/commands/setup-prompts-platform-mode.js +70 -0
  139. package/lib/commands/setup-prompts-validators.js +36 -0
  140. package/lib/commands/setup-prompts.js +24 -76
  141. package/lib/commands/setup-run-context.js +45 -0
  142. package/lib/commands/setup-spinners.js +39 -0
  143. package/lib/commands/setup.js +27 -5
  144. package/lib/commands/teardown.js +39 -0
  145. package/lib/commands/test-e2e-external.js +24 -8
  146. package/lib/commands/test-governance-external.js +3 -24
  147. package/lib/commands/test-trust-external.js +48 -100
  148. package/lib/commands/up-common.js +57 -13
  149. package/lib/commands/up-dataplane.js +56 -15
  150. package/lib/commands/upload-publish-context.js +75 -0
  151. package/lib/commands/upload-sidecar-followups.js +89 -0
  152. package/lib/commands/upload.js +113 -110
  153. package/lib/commands/verify-governance-combined-external.js +61 -0
  154. package/lib/commands/verify-governance-command-action.js +64 -0
  155. package/lib/commands/verify-governance-db.js +102 -0
  156. package/lib/commands/verify-governance-external.js +77 -0
  157. package/lib/commands/verify-governance-result-builders.js +71 -0
  158. package/lib/commands/verify-operations-command-action.js +68 -0
  159. package/lib/commands/verify-operations-external.js +268 -0
  160. package/lib/commands/verify-operations-persist.js +75 -0
  161. package/lib/commands/verify-operations-step-plan.js +68 -0
  162. package/lib/commands/verify-operations-steps.js +111 -0
  163. package/lib/commands/verify-trust-command-action.js +73 -0
  164. package/lib/commands/verify-trust-external.js +59 -0
  165. package/lib/commands/wizard-config-normalizer.js +43 -23
  166. package/lib/commands/wizard-core.js +2 -0
  167. package/lib/core/admin-password-generator.js +38 -0
  168. package/lib/core/admin-secrets-bundle.js +111 -0
  169. package/lib/core/admin-secrets-env-overlay.js +62 -0
  170. package/lib/core/admin-secrets.js +32 -4
  171. package/lib/core/config-attach-extensions.js +3 -0
  172. package/lib/core/config-platform-controller.js +44 -0
  173. package/lib/core/config-setup-installation-profile.js +42 -0
  174. package/lib/core/config-setup-platform-mode.js +46 -0
  175. package/lib/core/config.js +46 -36
  176. package/lib/core/secrets-admin-env.js +6 -2
  177. package/lib/core/secrets-ensure.js +4 -0
  178. package/lib/core/secrets-env-content.js +54 -48
  179. package/lib/core/secrets-env-write.js +10 -1
  180. package/lib/core/secrets-infra-placeholder-sync.js +2 -0
  181. package/lib/core/setup-installation-profile.js +43 -0
  182. package/lib/core/setup-platform-mode.js +90 -0
  183. package/lib/datasource/agent-trust-debug-log.js +1 -1
  184. package/lib/datasource/agent-trust-run.js +5 -3
  185. package/lib/datasource/audit-evidence-extract-debug.js +30 -18
  186. package/lib/datasource/audit-evidence-extract.js +7 -4
  187. package/lib/datasource/audit-evidence-verify-matrix.js +54 -26
  188. package/lib/datasource/bulk-loader-run-batches.js +15 -3
  189. package/lib/datasource/capability/capability-diff-slice.js +2 -2
  190. package/lib/datasource/capability/capability-manifest.js +254 -0
  191. package/lib/datasource/capability/capability-resolve.js +3 -4
  192. package/lib/datasource/capability/copy-operations.js +5 -3
  193. package/lib/datasource/capability/remove-operations.js +2 -1
  194. package/lib/datasource/capability/run-capability-copy.js +32 -4
  195. package/lib/datasource/capability/run-capability-diff.js +3 -3
  196. package/lib/datasource/capability/run-capability-dimension.js +2 -4
  197. package/lib/datasource/capability/run-capability-edit.js +4 -7
  198. package/lib/datasource/capability/run-capability-relate.js +2 -4
  199. package/lib/datasource/capability/run-capability-remove.js +2 -4
  200. package/lib/datasource/capability/validate-capability-slice.js +2 -1
  201. package/lib/datasource/datasource-identifier.js +2 -1
  202. package/lib/datasource/datasource-load-export-context.js +3 -0
  203. package/lib/datasource/datasource-validate-display.js +1 -1
  204. package/lib/datasource/field-reference-validator.js +24 -3
  205. package/lib/datasource/log-cleaner.js +2 -2
  206. package/lib/datasource/log-viewer.js +8 -8
  207. package/lib/datasource/orphan-profiles-validator.js +98 -0
  208. package/lib/datasource/test-e2e.js +4 -1
  209. package/lib/datasource/unified-validation-run-body.js +3 -1
  210. package/lib/datasource/unified-validation-run.js +5 -2
  211. package/lib/datasource/validate.js +41 -23
  212. package/lib/deployment/environment-poll-status.js +138 -0
  213. package/lib/deployment/environment.js +42 -34
  214. package/lib/external-system/download-helpers.js +87 -0
  215. package/lib/external-system/download.js +78 -138
  216. package/lib/external-system/generator.js +1 -55
  217. package/lib/external-system/integration-auth-context.js +45 -0
  218. package/lib/external-system/test-auth.js +12 -5
  219. package/lib/external-system/test.js +16 -21
  220. package/lib/generator/builders.js +11 -1
  221. package/lib/generator/external-controller-manifest.js +18 -0
  222. package/lib/generator/split.js +3 -1
  223. package/lib/generator/wizard.js +1 -28
  224. package/lib/governance/governance-cli-errors.js +96 -0
  225. package/lib/governance/governance-download-display.js +38 -0
  226. package/lib/governance/governance-run-display.js +215 -25
  227. package/lib/identity/fk-reference.js +65 -0
  228. package/lib/identity/identity-apply-core.js +13 -58
  229. package/lib/identity/identity-apply-phases.js +5 -1
  230. package/lib/identity/identity-display.js +243 -0
  231. package/lib/identity/identity-response-helpers.js +52 -0
  232. package/lib/identity/identity-sync-errors.js +62 -0
  233. package/lib/identity/resolve-user-id.js +129 -0
  234. package/lib/identity/username-default.js +31 -0
  235. package/lib/infrastructure/admin-secrets-apply.js +84 -0
  236. package/lib/infrastructure/admin-secrets-ensure.js +130 -0
  237. package/lib/infrastructure/admin-secrets-resolve.js +95 -0
  238. package/lib/infrastructure/admin-secrets-sync.js +91 -0
  239. package/lib/infrastructure/helpers.js +23 -129
  240. package/lib/infrastructure/index.js +5 -6
  241. package/lib/infrastructure/orphan-infra-docker-teardown.js +11 -5
  242. package/lib/lifecycle/certification-progress.js +231 -0
  243. package/lib/lifecycle/dataplane-ui-url.js +145 -0
  244. package/lib/lifecycle/exit-codes.js +24 -0
  245. package/lib/lifecycle/json-envelope.js +25 -0
  246. package/lib/lifecycle/lifecycle-governance-options.js +51 -0
  247. package/lib/lifecycle/logger-quiet.js +29 -0
  248. package/lib/lifecycle/open-api-published.js +59 -0
  249. package/lib/lifecycle/operations-readiness-metrics.js +101 -0
  250. package/lib/lifecycle/product-model.js +196 -0
  251. package/lib/lifecycle/report-display-trust.js +70 -0
  252. package/lib/lifecycle/report-display.js +472 -0
  253. package/lib/lifecycle/scenario-upload.js +191 -0
  254. package/lib/lifecycle/verify-step-progress.js +87 -0
  255. package/lib/parameters/infra-kv-discovery.js +27 -2
  256. package/lib/protection/auth-context.js +10 -4
  257. package/lib/resolvers/datasource-resolver.js +2 -3
  258. package/lib/resolvers/resource-type-file.js +159 -0
  259. package/lib/schema/application-schema.json +17 -1
  260. package/lib/schema/datasource-test-run.schema.json +89 -6
  261. package/lib/schema/deployment-rules.yaml +2 -1
  262. package/lib/schema/external-datasource.schema.json +2947 -2619
  263. package/lib/schema/external-system.schema.json +1418 -761
  264. package/lib/schema/infra.parameter.yaml +45 -0
  265. package/lib/schema/type/credential-field-registry.json +103 -97
  266. package/lib/schema/type/document-storage.json +9 -5
  267. package/lib/utils/aifabrix-test-runtime-guard.js +125 -0
  268. package/lib/utils/api.js +55 -31
  269. package/lib/utils/app-relative-path-env.js +112 -0
  270. package/lib/utils/app-run-containers.js +25 -2
  271. package/lib/utils/authentication-shape.js +224 -0
  272. package/lib/utils/build-copy.js +36 -5
  273. package/lib/utils/build-resolve-image.js +66 -12
  274. package/lib/utils/celery-companion-apps.js +53 -0
  275. package/lib/utils/cli-secrets-error-format.js +83 -21
  276. package/lib/utils/cli-utils.js +20 -4
  277. package/lib/utils/compose-celery-worker.js +23 -0
  278. package/lib/utils/compose-generate-docker-compose.js +13 -1
  279. package/lib/utils/compose-traefik-ingress-base.js +16 -1
  280. package/lib/utils/config-secrets-path.js +46 -0
  281. package/lib/utils/configuration-env-resolver.js +36 -0
  282. package/lib/utils/controller-url.js +88 -1
  283. package/lib/utils/credential-secrets-env-kv.js +216 -0
  284. package/lib/utils/credential-secrets-env.js +175 -257
  285. package/lib/utils/dataplane-resolver.js +18 -2
  286. package/lib/utils/declarative-url-ports.js +27 -4
  287. package/lib/utils/deployment-auth-mode.js +41 -0
  288. package/lib/utils/developer-volume-id.js +33 -0
  289. package/lib/utils/docker-build.js +1 -4
  290. package/lib/utils/env-copy.js +14 -9
  291. package/lib/utils/env-kv-orphan-lines.js +43 -0
  292. package/lib/utils/error-formatters/network-errors.js +13 -4
  293. package/lib/utils/external-env-template.js +11 -3
  294. package/lib/utils/external-system-readiness-core.js +10 -0
  295. package/lib/utils/external-system-readiness-display.js +80 -23
  296. package/lib/utils/external-system-validators.js +8 -2
  297. package/lib/utils/health-check.js +4 -1
  298. package/lib/utils/help-builder.js +13 -8
  299. package/lib/utils/keycloak-realm-ready.js +101 -0
  300. package/lib/utils/load-cip-capacity-display-config.js +61 -15
  301. package/lib/utils/manifest-source-emit.js +2 -0
  302. package/lib/utils/paths.js +25 -0
  303. package/lib/utils/pipeline-upload-auth-retry.js +82 -0
  304. package/lib/utils/platform-controller-url.js +157 -0
  305. package/lib/utils/platform-kv-ref.js +57 -0
  306. package/lib/utils/postgres-platform-bootstrap.js +199 -0
  307. package/lib/utils/postgres-wipe.js +9 -2
  308. package/lib/utils/resolve-env-mode.js +47 -0
  309. package/lib/utils/runtime-configuration-download.js +189 -0
  310. package/lib/utils/runtime-configuration-values.js +119 -0
  311. package/lib/utils/schema-loader.js +19 -4
  312. package/lib/utils/secrets-generator.js +31 -25
  313. package/lib/utils/secrets-missing-error.js +164 -0
  314. package/lib/utils/service-url-label.js +81 -0
  315. package/lib/utils/setup-quiet-output.js +25 -0
  316. package/lib/utils/template-helpers.js +15 -0
  317. package/lib/utils/token-manager-client-credentials.js +201 -0
  318. package/lib/utils/token-manager-messages.js +8 -0
  319. package/lib/utils/token-manager-refresh.js +12 -2
  320. package/lib/utils/token-manager.js +86 -105
  321. package/lib/utils/upload-pipeline-ui.js +12 -5
  322. package/lib/utils/upload-sync-options.js +42 -0
  323. package/lib/utils/url-declarative-public-base.js +21 -3
  324. package/lib/utils/url-declarative-vdir-inactive-env.js +14 -4
  325. package/lib/utils/validation-poll-ui.js +8 -0
  326. package/lib/utils/validation-run-stage.js +45 -0
  327. package/lib/utils/worker-sidecars.js +142 -0
  328. package/lib/validation/external-manifest-validator.js +2 -0
  329. package/lib/validation/runtime-configuration-rules.js +98 -0
  330. package/lib/validation/validate-display-dimensions.js +154 -0
  331. package/lib/validation/validate-display-sections.js +276 -0
  332. package/lib/validation/validate-display-steps.js +67 -0
  333. package/lib/validation/validate-display.js +24 -402
  334. package/package.json +1 -1
  335. package/templates/applications/dataplane/application.yaml +7 -1
  336. package/templates/applications/dataplane/env.template +50 -14
  337. package/templates/applications/dataplane/rbac.yaml +108 -1
  338. package/templates/applications/miso-controller/application.yaml +2 -2
  339. package/templates/applications/miso-controller/env.template +53 -122
  340. package/templates/applications/miso-controller/rbac.yaml +28 -13
  341. package/templates/channels/chatgpt/install.template.md.hbs +49 -0
  342. package/templates/channels/chatgpt/manifest.template.json.hbs +9 -0
  343. package/templates/channels/microsoft-copilot/install.template.md.hbs +49 -0
  344. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +8 -0
  345. package/templates/channels/slack/install.template.md.hbs +49 -0
  346. package/templates/channels/slack/manifest.template.json.hbs +66 -0
  347. package/templates/channels/teams-bot/install.template.md.hbs +49 -0
  348. package/templates/channels/teams-bot/manifest.template.json.hbs +13 -0
  349. package/templates/channels/web-embed/install.template.md.hbs +49 -0
  350. package/templates/channels/web-embed/manifest.template.json.hbs +8 -0
  351. package/templates/external-system/README.md.hbs +74 -17
  352. package/templates/python/docker-compose.hbs +75 -18
  353. package/templates/typescript/docker-compose.hbs +70 -17
  354. package/lib/commands/test-governance-command-action.js +0 -55
  355. package/lib/commands/test-trust-command-action.js +0 -118
package/lib/utils/api.js CHANGED
@@ -10,7 +10,8 @@
10
10
  * @version 2.0.0
11
11
  */
12
12
 
13
- const { parseErrorResponse } = require('./api-error-handler');
13
+ const { parseErrorResponse, formatNetworkError } = require('./api-error-handler');
14
+ const { inferServiceLabelFromUrl } = require('./service-url-label');
14
15
  const auditLogger = require('../core/audit-logger');
15
16
 
16
17
  /** Default timeout for HTTP requests (ms). Prevents hanging when the controller is unreachable. 30s allows Azure Web App cold start to complete. */
@@ -172,7 +173,7 @@ async function handleSuccessResponse(response, url, options, duration) {
172
173
  * @throws {Error} If URL is empty, null, undefined, whitespace-only, or malformed
173
174
  */
174
175
  function validateUrl(url, urlType = 'URL') {
175
- if (!url || typeof url !== 'string') {
176
+ if (url === null || url === undefined || typeof url !== 'string') {
176
177
  throw new Error(`${urlType} is required and must be a string (received: ${JSON.stringify(url)})`);
177
178
  }
178
179
  const trimmedUrl = url.trim();
@@ -248,20 +249,18 @@ async function handleNetworkError(error, url, options, duration) {
248
249
  const flat = flattenNetworkErrorDetail(error);
249
250
  const errorMessage = withUrlContextForNetworkError(flat || error.message, url);
250
251
 
251
- const parsedError = parseErrorResponse(errorMessage, 0, true);
252
-
253
- // Extract controller URL from full URL for error data
254
- let controllerUrl = null;
252
+ let serviceBaseUrl = null;
255
253
  const endpointUrl = url;
256
254
  if (url && typeof url === 'string' && url.trim()) {
257
255
  try {
258
256
  const urlObj = new URL(url);
259
- controllerUrl = `${urlObj.protocol}//${urlObj.host}`;
257
+ serviceBaseUrl = `${urlObj.protocol}//${urlObj.host}`;
260
258
  } catch {
261
- // If URL parsing fails, use the full URL as endpoint
262
- controllerUrl = null;
259
+ serviceBaseUrl = null;
263
260
  }
264
261
  }
262
+ const serviceLabel = inferServiceLabelFromUrl(endpointUrl);
263
+ const parsedError = parseErrorResponse(errorMessage, 0, true);
265
264
 
266
265
  await logApiPerformance({
267
266
  url,
@@ -276,24 +275,61 @@ async function handleNetworkError(error, url, options, duration) {
276
275
  }
277
276
  });
278
277
 
279
- // Include both controller URL and full endpoint URL in error data
280
278
  const errorData = {
281
- ...parsedError.data,
282
- controllerUrl: controllerUrl,
283
- endpointUrl: endpointUrl
279
+ controllerUrl: serviceBaseUrl,
280
+ endpointUrl: endpointUrl,
281
+ serviceLabel
284
282
  };
283
+ const formattedError = formatNetworkError(errorMessage, errorData);
285
284
 
286
285
  return {
287
286
  success: false,
288
- error: parsedError.message,
287
+ error: errorMessage,
289
288
  errorData: errorData,
290
- errorType: parsedError.type,
291
- formattedError: parsedError.formatted,
289
+ errorType: 'network',
290
+ formattedError,
292
291
  network: true,
293
292
  originalError: error
294
293
  };
295
294
  }
296
295
 
296
+ /**
297
+ * @param {Object} options - Fetch options passed to makeApiCall
298
+ * @returns {{ fetchOptions: Object, singleRequestTimeoutMs: number }}
299
+ */
300
+ function prepareFetchOptionsForRequest(options) {
301
+ const fetchOptions = { ...options };
302
+ const singleRequestTimeoutMs = resolveSingleRequestTimeoutMs(fetchOptions);
303
+ if (!fetchOptions.signal) {
304
+ fetchOptions.signal = AbortSignal.timeout(singleRequestTimeoutMs);
305
+ }
306
+ delete fetchOptions.timeoutMs;
307
+ delete fetchOptions.requestTimeoutMs;
308
+ return { fetchOptions, singleRequestTimeoutMs };
309
+ }
310
+
311
+ /**
312
+ * @param {Error} err
313
+ * @param {string} url
314
+ * @param {number} singleRequestTimeoutMs
315
+ * @returns {Error}
316
+ */
317
+ function normalizeFetchNetworkError(err, url, singleRequestTimeoutMs) {
318
+ if (err?.name !== 'AbortError') {
319
+ return err;
320
+ }
321
+ const serviceLabel = inferServiceLabelFromUrl(url);
322
+ const serviceName =
323
+ serviceLabel === 'dataplane'
324
+ ? 'dataplane'
325
+ : serviceLabel === 'controller'
326
+ ? 'controller'
327
+ : 'service';
328
+ return new Error(
329
+ `Request timed out after ${Math.round(singleRequestTimeoutMs / 1000)} seconds. The ${serviceName} may be unreachable. Check the URL and network.`
330
+ );
331
+ }
332
+
297
333
  /**
298
334
  * Make an API call with proper error handling
299
335
  * Uses a 30s timeout by default. Pass ``options.timeoutMs`` or ``options.requestTimeoutMs`` for
@@ -304,22 +340,14 @@ async function handleNetworkError(error, url, options, duration) {
304
340
  * @returns {Promise<Object>} Response object with success flag
305
341
  */
306
342
  async function makeApiCall(url, options = {}) {
307
- // Validate URL before attempting request
308
343
  try {
309
344
  validateUrl(url, 'API endpoint URL');
310
345
  } catch (error) {
311
- const duration = 0;
312
- return await handleNetworkError(error, url || '', options, duration);
346
+ return await handleNetworkError(error, url || '', options, 0);
313
347
  }
314
348
 
315
349
  const startTime = Date.now();
316
- const fetchOptions = { ...options };
317
- const singleRequestTimeoutMs = resolveSingleRequestTimeoutMs(fetchOptions);
318
- if (!fetchOptions.signal) {
319
- fetchOptions.signal = AbortSignal.timeout(singleRequestTimeoutMs);
320
- }
321
- delete fetchOptions.timeoutMs;
322
- delete fetchOptions.requestTimeoutMs;
350
+ const { fetchOptions, singleRequestTimeoutMs } = prepareFetchOptionsForRequest(options);
323
351
 
324
352
  try {
325
353
  const response = await fetch(url, fetchOptions);
@@ -332,11 +360,7 @@ async function makeApiCall(url, options = {}) {
332
360
  return await handleSuccessResponse(response, url, options, duration);
333
361
  } catch (err) {
334
362
  const duration = Date.now() - startTime;
335
- const error = err?.name === 'AbortError'
336
- ? new Error(
337
- `Request timed out after ${Math.round(singleRequestTimeoutMs / 1000)} seconds. The controller may be unreachable. Check the URL and network.`
338
- )
339
- : err;
363
+ const error = normalizeFetchNetworkError(err, url, singleRequestTimeoutMs);
340
364
  return await handleNetworkError(error, url, options, duration);
341
365
  }
342
366
  }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Per-app virtual-directory env var names ({@code <NAME>_RELATIVE_PATH}) for Builder CLI.
3
+ *
4
+ * @fileoverview {@code app.key} → {@code DATAPLANE_RELATIVE_PATH}, {@code MYAPP_RELATIVE_PATH}, etc.
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ /** Must match {@link module:lib/utils/url-declarative-vdir-inactive-env} token (avoid circular require). */
12
+ const URL_DECLARATIVE_VDIR_PUBLIC_TOKEN = 'url://vdir-public';
13
+
14
+ /** Suffix for runtime mount vars derived from {@code application.yaml} {@code app.key}. */
15
+ const APP_RELATIVE_PATH_ENV_SUFFIX = '_RELATIVE_PATH';
16
+
17
+ /**
18
+ * Apps whose runtime vdir env name is not {@code UPPER(appKey)_RELATIVE_PATH}.
19
+ * @type {Readonly<Record<string, string>>}
20
+ */
21
+ const APP_RELATIVE_PATH_ENV_KEY_OVERRIDES = Object.freeze({
22
+ keycloak: 'KC_HTTP_RELATIVE_PATH',
23
+ 'miso-controller': 'MISO_RELATIVE_PATH'
24
+ });
25
+
26
+ /** Always rewritten to "/" when front-door path is inactive (shared infra apps). */
27
+ const INACTIVE_VDIR_PUBLIC_STATIC_ENV_KEYS = Object.freeze([
28
+ 'KC_HTTP_RELATIVE_PATH',
29
+ 'MISO_RELATIVE_PATH'
30
+ ]);
31
+
32
+ /**
33
+ * @param {string} appKey - {@code application.yaml} {@code app.key}
34
+ * @returns {string} e.g. {@code dataplane} → {@code DATAPLANE_RELATIVE_PATH}, {@code myapp} → {@code MYAPP_RELATIVE_PATH}
35
+ */
36
+ function appKeyToRelativePathEnvKey(appKey) {
37
+ const key = String(appKey || '').trim();
38
+ if (!key) {
39
+ return '';
40
+ }
41
+ const override = APP_RELATIVE_PATH_ENV_KEY_OVERRIDES[key];
42
+ if (override) {
43
+ return override;
44
+ }
45
+ const upper = key.replace(/-/g, '_').toUpperCase();
46
+ return `${upper}${APP_RELATIVE_PATH_ENV_SUFFIX}`;
47
+ }
48
+
49
+ /**
50
+ * Whether {@code key} is a runtime vdir env name (static Keycloak/Miso or {@code NAME_RELATIVE_PATH}).
51
+ * @param {string} key
52
+ * @returns {boolean}
53
+ */
54
+ function isAppRelativePathEnvKey(key) {
55
+ const name = String(key || '').trim();
56
+ if (!name) {
57
+ return false;
58
+ }
59
+ if (INACTIVE_VDIR_PUBLIC_STATIC_ENV_KEYS.includes(name)) {
60
+ return true;
61
+ }
62
+ return name.endsWith(APP_RELATIVE_PATH_ENV_SUFFIX);
63
+ }
64
+
65
+ /**
66
+ * Env keys to rewrite when path is inactive: static Keycloak/Miso + current app vdir key.
67
+ * @param {string|null|undefined} appKey - {@code application.yaml} {@code app.key}
68
+ * @returns {ReadonlySet<string>}
69
+ */
70
+ function resolveInactiveVdirRewriteEnvKeys(appKey) {
71
+ const keys = new Set(INACTIVE_VDIR_PUBLIC_STATIC_ENV_KEYS);
72
+ const envKey = appKeyToRelativePathEnvKey(appKey);
73
+ if (envKey) {
74
+ keys.add(envKey);
75
+ }
76
+ return keys;
77
+ }
78
+
79
+ /**
80
+ * When an app is created from a product template, rename the template's {@code *_RELATIVE_PATH}
81
+ * line to match {@code app.key} (e.g. {@code DATAPLANE_RELATIVE_PATH} → {@code MYAPP_RELATIVE_PATH}).
82
+ * @param {string} content - {@code env.template} body
83
+ * @param {string} appKey
84
+ * @returns {string}
85
+ */
86
+ function renameAppRelativePathEnvKeyInTemplate(content, appKey) {
87
+ const targetKey = appKeyToRelativePathEnvKey(appKey);
88
+ if (!targetKey || !content || !content.includes(URL_DECLARATIVE_VDIR_PUBLIC_TOKEN)) {
89
+ return content;
90
+ }
91
+ const escapedToken = URL_DECLARATIVE_VDIR_PUBLIC_TOKEN.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
92
+ const lineRe = new RegExp(
93
+ `^([ \\t]*)([A-Z][A-Z0-9_]*${APP_RELATIVE_PATH_ENV_SUFFIX})([ \\t]*=\\s*)${escapedToken}[ \\t]*$`,
94
+ 'gm'
95
+ );
96
+ return content.replace(lineRe, (match, indent, currentKey, eq) => {
97
+ if (currentKey === targetKey) {
98
+ return match;
99
+ }
100
+ return `${indent}${targetKey}${eq}${URL_DECLARATIVE_VDIR_PUBLIC_TOKEN}`;
101
+ });
102
+ }
103
+
104
+ module.exports = {
105
+ APP_RELATIVE_PATH_ENV_SUFFIX,
106
+ APP_RELATIVE_PATH_ENV_KEY_OVERRIDES,
107
+ INACTIVE_VDIR_PUBLIC_STATIC_ENV_KEYS,
108
+ appKeyToRelativePathEnvKey,
109
+ isAppRelativePathEnvKey,
110
+ resolveInactiveVdirRewriteEnvKeys,
111
+ renameAppRelativePathEnvKeyInTemplate
112
+ };
@@ -85,6 +85,27 @@ async function logContainerDebugInfo(containerName) {
85
85
  logger.log(chalk.gray(`[DEBUG] Container ports: ${ports.trim()}`));
86
86
  }
87
87
 
88
+ /**
89
+ * @param {string} stdout - `docker ps --format "{{.Names}}"` output
90
+ * @returns {string[]}
91
+ */
92
+ function parseDockerPsNameLines(stdout) {
93
+ return String(stdout || '')
94
+ .split('\n')
95
+ .map((line) => line.trim())
96
+ .filter(Boolean);
97
+ }
98
+
99
+ /**
100
+ * True when an exact container name appears in docker ps output (not a substring sibling).
101
+ * @param {string} containerName
102
+ * @param {string} stdout
103
+ * @returns {boolean}
104
+ */
105
+ function isExactContainerNameInPsOutput(containerName, stdout) {
106
+ return parseDockerPsNameLines(stdout).includes(containerName);
107
+ }
108
+
88
109
  async function checkContainerRunning(appName, developerId, debug = false, scopeOpts = null) {
89
110
  try {
90
111
  const containerName = getContainerName(appName, developerId, scopeOpts);
@@ -93,7 +114,7 @@ async function checkContainerRunning(appName, developerId, debug = false, scopeO
93
114
  logger.log(chalk.gray(`[DEBUG] Executing: ${cmd}`));
94
115
  }
95
116
  const { stdout } = await execWithDockerEnv(cmd);
96
- const isRunning = stdout.trim() === containerName;
117
+ const isRunning = isExactContainerNameInPsOutput(containerName, stdout);
97
118
  if (debug) {
98
119
  logger.log(chalk.gray(`[DEBUG] Container ${containerName} running: ${isRunning}`));
99
120
  if (isRunning) {
@@ -161,6 +182,8 @@ module.exports = {
161
182
  checkContainerRunning,
162
183
  stopAndRemoveContainer,
163
184
  logContainerStatus,
164
- getContainerName
185
+ getContainerName,
186
+ parseDockerPsNameLines,
187
+ isExactContainerNameInPsOutput
165
188
  };
166
189
 
@@ -0,0 +1,224 @@
1
+ /**
2
+ * External system authentication shape helpers (schema 2.4.x nested credential).
3
+ *
4
+ * @fileoverview Normalize flat legacy auth blocks to authType + credential template
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const { getKvPathSegmentForSecurityKey } = require('./credential-secrets-env');
12
+
13
+ /**
14
+ * @param {Object|null|undefined} authentication
15
+ * @returns {Object|null} Credential template (method, variables, security) or null
16
+ */
17
+ function getAuthCredentialTemplate(authentication) {
18
+ if (!authentication || typeof authentication !== 'object') {
19
+ return null;
20
+ }
21
+ if (authentication.authType === 'credential' && authentication.credential) {
22
+ return authentication.credential;
23
+ }
24
+ if (authentication.method) {
25
+ return authentication;
26
+ }
27
+ return null;
28
+ }
29
+
30
+ /**
31
+ * @param {Object|null|undefined} authentication
32
+ * @returns {string}
33
+ */
34
+ function getAuthMethodFromAuthentication(authentication) {
35
+ const credential = getAuthCredentialTemplate(authentication);
36
+ return String(credential?.method || authentication?.method || '').trim();
37
+ }
38
+
39
+ /**
40
+ * Builds flat credential template fields (method, variables, security) for a method.
41
+ * @param {string} systemKey
42
+ * @param {string} authType
43
+ * @returns {{ method: string, variables: Object, security: Object }}
44
+ */
45
+ function buildFlatCredentialTemplate(systemKey, authType) {
46
+ const kvPath = (securityKey) => {
47
+ const segment = getKvPathSegmentForSecurityKey(securityKey);
48
+ return segment ? `kv://${systemKey}/${segment}` : null;
49
+ };
50
+ const method = authType || 'apikey';
51
+ const base = 'https://api.example.com';
52
+
53
+ const authMap = {
54
+ oauth2: {
55
+ variables: { baseUrl: base, tokenUrl: `${base}/oauth/token`, authorizationUrl: `${base}/oauth/authorize` },
56
+ security: { clientId: kvPath('clientId'), clientSecret: kvPath('clientSecret') }
57
+ },
58
+ aad: {
59
+ variables: {
60
+ baseUrl: base,
61
+ tokenUrl: 'https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token',
62
+ tenantId: '{tenant-id}'
63
+ },
64
+ security: { clientId: kvPath('clientId'), clientSecret: kvPath('clientSecret') }
65
+ },
66
+ apikey: {
67
+ variables: { baseUrl: base, headerName: 'X-API-Key' },
68
+ security: { apiKey: kvPath('apiKey') }
69
+ },
70
+ bearerToken: {
71
+ variables: { baseUrl: base, headerName: 'Authorization', prefix: 'Bearer' },
72
+ security: { token: kvPath('token') }
73
+ },
74
+ basic: {
75
+ variables: { baseUrl: base },
76
+ security: { username: kvPath('username'), password: kvPath('password') }
77
+ },
78
+ queryParam: {
79
+ variables: { baseUrl: base, paramName: 'api_key' },
80
+ security: { paramValue: kvPath('paramValue') }
81
+ },
82
+ oidc: {
83
+ variables: { openIdConfigUrl: 'https://example.com/.well-known/openid-configuration', clientId: 'app-id' },
84
+ security: {}
85
+ },
86
+ hmac: {
87
+ variables: { baseUrl: base, algorithm: 'sha256', signatureHeader: 'X-Signature' },
88
+ security: { signingSecret: kvPath('signingSecret') }
89
+ },
90
+ none: {
91
+ variables: {},
92
+ security: {}
93
+ }
94
+ };
95
+
96
+ const auth = authMap[method] || authMap.apikey;
97
+ return { method, variables: auth.variables, security: auth.security };
98
+ }
99
+
100
+ /**
101
+ * Builds schema-compliant authentication block for external-system.schema.json.
102
+ * @param {string} systemKey
103
+ * @param {string} authType - oauth2, apikey, bearerToken, none, …
104
+ * @returns {Object}
105
+ */
106
+ function buildAuthenticationFromMethod(systemKey, authType) {
107
+ const method = authType || 'apikey';
108
+ if (method === 'none') {
109
+ return { authType: 'none' };
110
+ }
111
+ const flat = buildFlatCredentialTemplate(systemKey, method);
112
+ return {
113
+ authType: 'credential',
114
+ credential: {
115
+ credentialKey: `${systemKey}-cred`,
116
+ method: flat.method,
117
+ variables: flat.variables,
118
+ security: flat.security,
119
+ writeOnce: true
120
+ }
121
+ };
122
+ }
123
+
124
+ /**
125
+ * @param {Object} authentication
126
+ * @param {string} systemKey
127
+ * @returns {{ authentication: Object, changed: boolean }}
128
+ */
129
+ function normalizeNestedCredentialBlock(authentication, systemKey) {
130
+ const credential = { ...authentication.credential };
131
+ let changed = Object.keys(authentication).some((k) => !['authType', 'credential'].includes(k));
132
+ if (!credential.credentialKey) {
133
+ credential.credentialKey = `${systemKey}-cred`;
134
+ changed = true;
135
+ }
136
+ if (credential.writeOnce === undefined) {
137
+ credential.writeOnce = true;
138
+ changed = true;
139
+ }
140
+ return { authentication: { authType: 'credential', credential }, changed };
141
+ }
142
+
143
+ /**
144
+ * @param {Object} flatAuth
145
+ * @param {string} systemKey
146
+ * @returns {{ authentication: Object, changed: boolean }}
147
+ */
148
+ function wrapFlatAuthAsNested(flatAuth, systemKey) {
149
+ const credential = {
150
+ credentialKey: flatAuth.credentialKey || `${systemKey}-cred`,
151
+ method: flatAuth.method,
152
+ variables: flatAuth.variables && typeof flatAuth.variables === 'object' ? flatAuth.variables : {},
153
+ security: flatAuth.security && typeof flatAuth.security === 'object' ? flatAuth.security : {},
154
+ writeOnce: flatAuth.writeOnce !== false
155
+ };
156
+ if (flatAuth.displayName) {
157
+ credential.displayName = flatAuth.displayName;
158
+ }
159
+ return { authentication: { authType: 'credential', credential }, changed: true };
160
+ }
161
+
162
+ /**
163
+ * Migrates legacy flat authentication to nested authType + credential.
164
+ * @param {Object|null|undefined} authentication
165
+ * @param {string} systemKey
166
+ * @returns {{ authentication: Object, changed: boolean }}
167
+ */
168
+ function migrateAuthenticationToNestedSchema(authentication, systemKey) {
169
+ if (!authentication || typeof authentication !== 'object') {
170
+ return { authentication: authentication || { authType: 'none' }, changed: false };
171
+ }
172
+ if (authentication.authType === 'none') {
173
+ return { authentication: { authType: 'none' }, changed: false };
174
+ }
175
+ if (authentication.authType === 'apiProvider' && authentication.apiProvider) {
176
+ return { authentication, changed: false };
177
+ }
178
+ if (authentication.authType === 'credential' && authentication.credential) {
179
+ return normalizeNestedCredentialBlock(authentication, systemKey);
180
+ }
181
+ if (authentication.method) {
182
+ return wrapFlatAuthAsNested(authentication, systemKey);
183
+ }
184
+ return { authentication, changed: false };
185
+ }
186
+
187
+ /**
188
+ * Align credential kv:// paths and credentialKey with the integration system key (wizard output).
189
+ * @param {Object|null|undefined} authentication
190
+ * @param {string} systemKey
191
+ * @param {string} [authDisplayName]
192
+ */
193
+ function normalizeAuthenticationToSystemKey(authentication, systemKey, authDisplayName) {
194
+ if (!authentication || typeof authentication !== 'object') return;
195
+ const migrated = migrateAuthenticationToNestedSchema(authentication, systemKey);
196
+ Object.assign(authentication, migrated.authentication);
197
+ const credential = authentication.authType === 'credential' ? authentication.credential : null;
198
+ if (!credential) return;
199
+ credential.credentialKey = `${systemKey}-cred`;
200
+ const security = credential.security;
201
+ if (security && typeof security === 'object') {
202
+ for (const k of Object.keys(security)) {
203
+ const v = security[k];
204
+ if (typeof v === 'string' && v.startsWith('kv://')) {
205
+ const rest = v.slice(5);
206
+ const idx = rest.indexOf('/');
207
+ const suffix = idx >= 0 ? rest.slice(idx + 1) : '';
208
+ security[k] = suffix ? `kv://${systemKey}/${suffix}` : `kv://${systemKey}`;
209
+ }
210
+ }
211
+ }
212
+ if (authDisplayName) {
213
+ credential.displayName = authDisplayName;
214
+ }
215
+ }
216
+
217
+ module.exports = {
218
+ getAuthCredentialTemplate,
219
+ getAuthMethodFromAuthentication,
220
+ buildFlatCredentialTemplate,
221
+ buildAuthenticationFromMethod,
222
+ migrateAuthenticationToNestedSchema,
223
+ normalizeAuthenticationToSystemKey
224
+ };
@@ -14,6 +14,11 @@ const fs = require('fs').promises;
14
14
  const fsSync = require('fs');
15
15
  const path = require('path');
16
16
  const paths = require('./paths');
17
+ const { formatWarningLine } = require('./cli-test-layout-chalk');
18
+ const logger = require('./logger');
19
+
20
+ /** Builder `.env` is not copied into the dev workspace (plan 139: resolve in-memory; run uses `.env.run`). */
21
+ const BUILD_WORKSPACE_SKIP_FILES = new Set(['.env']);
17
22
 
18
23
  /**
19
24
  * Copies all files from builder directory to developer-specific directory
@@ -81,8 +86,11 @@ async function copyDirectory(sourceDir, targetDir) {
81
86
  const entries = await fs.readdir(sourceDir);
82
87
 
83
88
  for (const entry of entries) {
84
- // Skip hidden files and directories (but allow .env, .gitignore, etc.)
85
- if (entry.startsWith('.') && entry !== '.env' && entry !== '.gitignore') {
89
+ if (BUILD_WORKSPACE_SKIP_FILES.has(entry)) {
90
+ continue;
91
+ }
92
+ // Skip hidden files and directories (allow .gitignore)
93
+ if (entry.startsWith('.') && entry !== '.gitignore') {
86
94
  continue;
87
95
  }
88
96
 
@@ -92,12 +100,35 @@ async function copyDirectory(sourceDir, targetDir) {
92
100
  const stats = await fs.stat(sourcePath);
93
101
 
94
102
  if (stats.isDirectory()) {
95
- // Recursively copy subdirectories
96
103
  await copyDirectory(sourcePath, targetPath);
97
104
  } else if (stats.isFile()) {
98
- // Copy file
99
- await fs.copyFile(sourcePath, targetPath);
105
+ await copyFileToWorkspace(sourcePath, targetPath, entry);
106
+ }
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Copy a single file into the dev workspace; skip unreadable files instead of failing the build.
112
+ * @async
113
+ * @param {string} sourcePath
114
+ * @param {string} targetPath
115
+ * @param {string} entryName
116
+ */
117
+ async function copyFileToWorkspace(sourcePath, targetPath, entryName) {
118
+ try {
119
+ await fs.copyFile(sourcePath, targetPath);
120
+ } catch (err) {
121
+ const code = err && err.code ? String(err.code) : '';
122
+ if (code === 'EACCES' || code === 'EPERM') {
123
+ logger.log(
124
+ formatWarningLine(
125
+ `Skipped ${entryName} during workspace copy (permission denied). ` +
126
+ 'Use "aifabrix resolve <app>" for a persistent .env when needed.'
127
+ )
128
+ );
129
+ return;
100
130
  }
131
+ throw err;
101
132
  }
102
133
  }
103
134