@aifabrix/builder 2.46.0 → 2.47.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/.npmrc.token +1 -0
  2. package/.temp/jest-protection-convert/run-IFN2nv/.protection/hubspot-companies.json +1 -0
  3. package/.temp/jest-protection-convert/run-IvmKLO/.protection/hubspot-companies.yaml +17 -0
  4. package/.temp/jest-protection-convert/run-SdvblF/.protection/hubspot-companies.json +1 -0
  5. package/.temp/jest-protection-convert/run-b51TsW/.protection/hubspot-companies.json +1 -0
  6. package/.temp/jest-protection-convert/run-fiLmb1/.protection/hubspot-companies.json +1 -0
  7. package/.temp/jest-protection-convert/run-qYGHMU/.protection/hubspot-companies.yaml +17 -0
  8. package/.temp/jest-protection-fixtures/hubspot-Mq5re8/hubspot-companies.yaml +17 -0
  9. package/.temp/jest-protection-fixtures/hubspot-f3u4YL/hubspot-companies.yaml +17 -0
  10. package/.temp/jest-protection-fixtures/hubspot-iQtd1O/hubspot-companies.yaml +17 -0
  11. package/.temp/jest-protection-fixtures/hubspot-mdmz5M/hubspot-companies.yaml +17 -0
  12. package/.temp/jest-protection-fixtures/hubspot-qbKYxQ/hubspot-companies.yaml +17 -0
  13. package/.temp/jest-protection-fixtures/hubspot-yzlJy6/hubspot-companies.yaml +17 -0
  14. package/.temp/validation/summary.txt +5 -0
  15. package/jest.isolated-projects.js +18 -2
  16. package/jest.projects.js +24 -0
  17. package/lib/api/dataplane-health.api.js +19 -7
  18. package/lib/api/lifecycle.api.js +22 -1
  19. package/lib/api/resource-types.api.js +107 -0
  20. package/lib/api/runtime-configuration.api.js +72 -0
  21. package/lib/api/types/resource-types.types.js +45 -0
  22. package/lib/api/user-groups.api.js +9 -9
  23. package/lib/api/users.api.js +8 -8
  24. package/lib/api/wizard.api.js +10 -2
  25. package/lib/app/down.js +58 -26
  26. package/lib/app/index.js +2 -2
  27. package/lib/app/run-resolve-image.js +2 -1
  28. package/lib/app/run-stop-containers.js +113 -0
  29. package/lib/app/run.js +1 -31
  30. package/lib/app/show-display.js +35 -0
  31. package/lib/app/show.js +32 -1
  32. package/lib/build/index.js +17 -5
  33. package/lib/build/miso-controller-npm-sdk-guard.js +25 -0
  34. package/lib/channels/add-channel.js +348 -0
  35. package/lib/channels/channel-artifact-generator.js +141 -0
  36. package/lib/channels/channel-audit.js +101 -0
  37. package/lib/channels/channel-auth-context.js +37 -0
  38. package/lib/channels/channel-credential.js +200 -0
  39. package/lib/channels/channel-display-name.js +64 -0
  40. package/lib/channels/channel-external-system.js +106 -0
  41. package/lib/channels/channel-integration-generator.js +274 -0
  42. package/lib/channels/channel-probe.js +89 -0
  43. package/lib/channels/channel-runtime-resolver.js +152 -0
  44. package/lib/channels/channel-status-display.js +141 -0
  45. package/lib/channels/channel-targets.js +115 -0
  46. package/lib/cli/index.js +18 -4
  47. package/lib/cli/infra-guided-auth.js +36 -12
  48. package/lib/cli/setup-app.js +54 -3
  49. package/lib/cli/setup-infra-up-dataplane-action.js +2 -2
  50. package/lib/cli/setup-infra-up-platform-action.js +2 -1
  51. package/lib/cli/setup-npm.js +59 -0
  52. package/lib/cli/setup-platform.js +1 -1
  53. package/lib/cli/setup-secrets.js +1 -1
  54. package/lib/cli/setup-utility-help-after.js +4 -1
  55. package/lib/cli/setup-utility-resolve.js +8 -5
  56. package/lib/commands/app-logs.js +54 -26
  57. package/lib/commands/app.js +41 -0
  58. package/lib/commands/auth-status-dataplane-version.js +97 -61
  59. package/lib/commands/auth-status.js +2 -7
  60. package/lib/commands/channel.js +58 -0
  61. package/lib/commands/datasource.js +1 -1
  62. package/lib/commands/identity-apply-sync.js +49 -8
  63. package/lib/commands/identity-group-cli.js +66 -26
  64. package/lib/commands/identity-membership-role-cache.js +58 -37
  65. package/lib/commands/identity-shared.js +22 -1
  66. package/lib/commands/identity-user-cli.js +80 -34
  67. package/lib/commands/login-device.js +11 -3
  68. package/lib/commands/login.js +8 -3
  69. package/lib/commands/npm-config.js +103 -0
  70. package/lib/commands/repair-auth-apply.js +30 -4
  71. package/lib/commands/repair-auth-config.js +8 -13
  72. package/lib/commands/repair-datasource-com-v2.js +15 -0
  73. package/lib/commands/repair-datasource-service-contract.js +206 -0
  74. package/lib/commands/repair-datasource.js +53 -18
  75. package/lib/commands/repair-env-template.js +12 -12
  76. package/lib/commands/repair-openapi-sync.js +26 -4
  77. package/lib/commands/repair-steps.js +28 -13
  78. package/lib/commands/repair-system-alignment.js +7 -4
  79. package/lib/commands/resource-type.js +371 -0
  80. package/lib/commands/setup-modes.js +1 -1
  81. package/lib/commands/setup-prompts-platform-mode.js +24 -21
  82. package/lib/commands/test-trust-external.js +39 -90
  83. package/lib/commands/up-common.js +33 -1
  84. package/lib/commands/up-dataplane.js +8 -4
  85. package/lib/commands/upload.js +24 -3
  86. package/lib/commands/verify-trust-command-action.js +4 -1
  87. package/lib/commands/verify-trust-external.js +12 -20
  88. package/lib/core/config-setup-platform-mode.js +3 -3
  89. package/lib/core/setup-platform-mode.js +41 -6
  90. package/lib/datasource/capability/capability-diff-slice.js +2 -2
  91. package/lib/datasource/capability/capability-manifest.js +254 -0
  92. package/lib/datasource/capability/capability-resolve.js +3 -4
  93. package/lib/datasource/capability/copy-operations.js +5 -3
  94. package/lib/datasource/capability/remove-operations.js +2 -1
  95. package/lib/datasource/capability/run-capability-copy.js +32 -4
  96. package/lib/datasource/capability/run-capability-diff.js +3 -3
  97. package/lib/datasource/capability/run-capability-dimension.js +2 -4
  98. package/lib/datasource/capability/run-capability-edit.js +4 -7
  99. package/lib/datasource/capability/run-capability-relate.js +2 -4
  100. package/lib/datasource/capability/run-capability-remove.js +2 -4
  101. package/lib/datasource/capability/validate-capability-slice.js +2 -1
  102. package/lib/datasource/datasource-identifier.js +2 -1
  103. package/lib/datasource/datasource-validate-display.js +1 -1
  104. package/lib/datasource/field-reference-validator.js +24 -3
  105. package/lib/datasource/orphan-profiles-validator.js +98 -0
  106. package/lib/datasource/validate.js +41 -23
  107. package/lib/external-system/download-helpers.js +87 -0
  108. package/lib/external-system/download.js +61 -122
  109. package/lib/external-system/generator.js +1 -59
  110. package/lib/external-system/test.js +10 -15
  111. package/lib/generator/builders.js +11 -1
  112. package/lib/generator/external-controller-manifest.js +5 -2
  113. package/lib/generator/split.js +3 -1
  114. package/lib/generator/wizard.js +1 -28
  115. package/lib/identity/fk-reference.js +65 -0
  116. package/lib/identity/identity-apply-core.js +13 -58
  117. package/lib/identity/identity-apply-phases.js +5 -1
  118. package/lib/identity/identity-display.js +243 -0
  119. package/lib/identity/identity-response-helpers.js +52 -0
  120. package/lib/identity/identity-sync-errors.js +62 -0
  121. package/lib/identity/resolve-user-id.js +129 -0
  122. package/lib/identity/username-default.js +31 -0
  123. package/lib/lifecycle/certification-progress.js +2 -2
  124. package/lib/lifecycle/report-display-trust.js +70 -0
  125. package/lib/lifecycle/report-display.js +14 -13
  126. package/lib/resolvers/datasource-resolver.js +2 -3
  127. package/lib/resolvers/resource-type-file.js +159 -0
  128. package/lib/schema/application-schema.json +16 -0
  129. package/lib/schema/deployment-rules.yaml +2 -1
  130. package/lib/schema/external-datasource.schema.json +372 -58
  131. package/lib/schema/external-system.schema.json +331 -2
  132. package/lib/schema/type/document-storage.json +9 -5
  133. package/lib/utils/api.js +2 -1
  134. package/lib/utils/app-run-containers.js +25 -2
  135. package/lib/utils/authentication-shape.js +224 -0
  136. package/lib/utils/build-copy.js +36 -5
  137. package/lib/utils/build-resolve-image.js +66 -12
  138. package/lib/utils/celery-companion-apps.js +53 -0
  139. package/lib/utils/cli-utils.js +4 -0
  140. package/lib/utils/compose-celery-worker.js +23 -0
  141. package/lib/utils/compose-generate-docker-compose.js +9 -1
  142. package/lib/utils/configuration-env-resolver.js +36 -0
  143. package/lib/utils/controller-url.js +4 -1
  144. package/lib/utils/credential-secrets-env-kv.js +216 -0
  145. package/lib/utils/credential-secrets-env.js +52 -262
  146. package/lib/utils/dataplane-resolver.js +4 -115
  147. package/lib/utils/declarative-url-ports.js +24 -1
  148. package/lib/utils/dev-platform-tls.js +148 -0
  149. package/lib/utils/docker-build.js +1 -4
  150. package/lib/utils/external-env-template.js +10 -3
  151. package/lib/utils/external-system-validators.js +8 -2
  152. package/lib/utils/health-check-request.js +114 -0
  153. package/lib/utils/health-check-url.js +3 -1
  154. package/lib/utils/health-check.js +1 -113
  155. package/lib/utils/help-builder.js +3 -0
  156. package/lib/utils/keycloak-realm-ready.js +5 -1
  157. package/lib/utils/localhost-public-host.js +31 -0
  158. package/lib/utils/npm-user-config.js +145 -0
  159. package/lib/utils/paths.js +25 -0
  160. package/lib/utils/platform-controller-url.js +58 -4
  161. package/lib/utils/platform-kv-ref.js +57 -0
  162. package/lib/utils/runtime-configuration-download.js +189 -0
  163. package/lib/utils/runtime-configuration-values.js +119 -0
  164. package/lib/utils/schema-loader.js +19 -4
  165. package/lib/utils/url-declarative-public-base.js +33 -25
  166. package/lib/utils/worker-sidecars.js +142 -0
  167. package/lib/validation/external-manifest-validator.js +2 -0
  168. package/lib/validation/runtime-configuration-rules.js +98 -0
  169. package/lib/validation/validate-display-dimensions.js +154 -0
  170. package/lib/validation/validate-display-sections.js +276 -0
  171. package/lib/validation/validate-display-steps.js +67 -0
  172. package/lib/validation/validate-display.js +24 -402
  173. package/package.json +1 -1
  174. package/templates/applications/dataplane/application.yaml +5 -0
  175. package/templates/applications/dataplane/env.template +26 -13
  176. package/templates/applications/dataplane/rbac.yaml +108 -1
  177. package/templates/applications/miso-controller/application.yaml +2 -2
  178. package/templates/applications/miso-controller/env.template +37 -122
  179. package/templates/applications/miso-controller/rbac.yaml +28 -13
  180. package/templates/channels/chatgpt/install.template.md.hbs +49 -0
  181. package/templates/channels/chatgpt/manifest.template.json.hbs +9 -0
  182. package/templates/channels/microsoft-copilot/install.template.md.hbs +49 -0
  183. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +8 -0
  184. package/templates/channels/slack/install.template.md.hbs +49 -0
  185. package/templates/channels/slack/manifest.template.json.hbs +66 -0
  186. package/templates/channels/teams-bot/install.template.md.hbs +49 -0
  187. package/templates/channels/teams-bot/manifest.template.json.hbs +13 -0
  188. package/templates/channels/web-embed/install.template.md.hbs +49 -0
  189. package/templates/channels/web-embed/manifest.template.json.hbs +8 -0
  190. package/templates/python/docker-compose.hbs +56 -0
  191. package/templates/typescript/docker-compose.hbs +52 -0
@@ -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
 
@@ -11,9 +11,10 @@
11
11
  const chalk = require('chalk');
12
12
  const logger = require('./logger');
13
13
  const { execWithDockerEnv } = require('./docker-exec');
14
+ const { buildDevImageName } = require('./image-name');
14
15
  const {
15
- resolveDockerImageRef,
16
- getRepositoryPathFromConfig
16
+ getRepositoryPathFromConfig,
17
+ resolveDockerImageRef
17
18
  } = require('./resolve-docker-image-ref');
18
19
 
19
20
  /**
@@ -47,20 +48,44 @@ function repositoryTail(repoPath) {
47
48
  return parts[parts.length - 1] || repoPath;
48
49
  }
49
50
 
51
+ /**
52
+ * Remove registry host prefix from a Docker repository path (local build only).
53
+ * @param {string} repository
54
+ * @returns {string}
55
+ */
56
+ function stripRegistryHostFromRepository(repository) {
57
+ if (!repository || typeof repository !== 'string') {
58
+ return repository;
59
+ }
60
+ const parts = repository.split('/');
61
+ if (parts.length <= 1) {
62
+ return repository;
63
+ }
64
+ const host = parts[0];
65
+ if (host.includes('.') || host.includes(':') || host === 'localhost') {
66
+ return parts.slice(1).join('/');
67
+ }
68
+ return repository;
69
+ }
70
+
50
71
  /**
51
72
  * Strip dev-scoped or -extra suffix from a Docker repository path.
52
73
  * @param {string} repo
53
74
  * @returns {string}
54
75
  */
55
76
  function baseRepositoryFromDevScoped(repo) {
56
- const devMatch = repo.match(/^(.*)-dev\d+$/);
77
+ const localRepo = stripRegistryHostFromRepository(repo);
78
+ const tail = repositoryTail(localRepo);
79
+ const devMatch = tail.match(/^(.*)-dev\d+$/);
57
80
  if (devMatch) {
58
- return devMatch[1];
81
+ const prefix = localRepo.slice(0, localRepo.length - tail.length);
82
+ return `${prefix}${devMatch[1]}`;
59
83
  }
60
- if (repo.endsWith('-extra')) {
61
- return repo.slice(0, -'-extra'.length);
84
+ if (tail.endsWith('-extra')) {
85
+ const prefix = localRepo.slice(0, localRepo.length - tail.length);
86
+ return `${prefix}${tail.slice(0, -'-extra'.length)}`;
62
87
  }
63
- return repo;
88
+ return localRepo;
64
89
  }
65
90
 
66
91
  /**
@@ -140,7 +165,33 @@ async function discoverLocalBaseImageRepository(appName, developerId) {
140
165
  }
141
166
 
142
167
  /**
143
- * Repository path (may include registry prefix) used for `docker build -t`.
168
+ * Resolve local `docker build -t` repository path(s) from the manifest or discovery.
169
+ * When `application.yaml` declares `image`, the manifest ref is used exactly (registry + name).
170
+ * Developer-scoped `-devN` suffixes apply only when the manifest omits `image` and Docker is scanned.
171
+ *
172
+ * @param {string} appName
173
+ * @param {Object} variables - application manifest
174
+ * @param {string|number} developerId
175
+ * @param {Object} [options] - CLI build options (`base`, `tag`, `registry`)
176
+ * @returns {Promise<{ buildImageName: string, compatBaseName: string|null }>}
177
+ */
178
+ async function resolveBuildDockerTagNames(appName, variables, developerId, options = {}) {
179
+ if (!wantsLocalDockerImageDiscovery(variables)) {
180
+ const { imageName } = resolveDockerImageRef(appName, variables, options);
181
+ return { buildImageName: imageName, compatBaseName: null };
182
+ }
183
+ const baseRepo = await resolveBuildImageRepositoryName(appName, variables, developerId);
184
+ const buildImageName = buildDevImageName(baseRepo, developerId);
185
+ const wantCompat =
186
+ buildImageName !== baseRepo && options && options.base === true;
187
+ return {
188
+ buildImageName,
189
+ compatBaseName: wantCompat ? baseRepo : null
190
+ };
191
+ }
192
+
193
+ /**
194
+ * Repository path for local `docker build -t` when manifest omits `image` (discovery / app key).
144
195
  * @param {string} appName
145
196
  * @param {Object} variables - application manifest
146
197
  * @param {string|number} developerId
@@ -148,18 +199,21 @@ async function discoverLocalBaseImageRepository(appName, developerId) {
148
199
  */
149
200
  async function resolveBuildImageRepositoryName(appName, variables, developerId) {
150
201
  if (!wantsLocalDockerImageDiscovery(variables)) {
151
- return resolveDockerImageRef(appName, variables, {}).imageName;
202
+ return getRepositoryPathFromConfig(variables, appName);
152
203
  }
153
204
  const discovered = await discoverLocalBaseImageRepository(appName, developerId);
154
205
  if (discovered) {
155
- logger.log(chalk.blue(`Using image repository from local Docker: ${discovered}`));
156
- return discovered;
206
+ const localRepo = stripRegistryHostFromRepository(discovered);
207
+ logger.log(chalk.blue(`Using image repository from local Docker: ${localRepo}`));
208
+ return localRepo;
157
209
  }
158
210
  return getRepositoryPathFromConfig(variables, appName);
159
211
  }
160
212
 
161
213
  module.exports = {
214
+ resolveBuildDockerTagNames,
162
215
  resolveBuildImageRepositoryName,
163
216
  wantsLocalDockerImageDiscovery,
164
- pickBaseFromDockerLines
217
+ pickBaseFromDockerLines,
218
+ stripRegistryHostFromRepository
165
219
  };
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Worker companion helpers for run/logs/down lifecycle.
3
+ *
4
+ * @fileoverview Loads application.yaml and resolves worker sidecar app keys
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const yaml = require('js-yaml');
12
+ const paths = require('./paths');
13
+ const {
14
+ getWorkerCompanionAppKeys,
15
+ resolveWorkerSidecars
16
+ } = require('./worker-sidecars');
17
+
18
+ /**
19
+ * @param {string} appName
20
+ * @param {object|null|undefined} appConfig
21
+ * @returns {boolean}
22
+ */
23
+ function isCeleryWorkerStackEnabled(appName, appConfig) {
24
+ return resolveWorkerSidecars(appName, appConfig).enabled;
25
+ }
26
+
27
+ /**
28
+ * @param {string} appName
29
+ * @param {object|null|undefined} appConfig
30
+ * @returns {string[]}
31
+ */
32
+ function getCeleryCompanionAppKeys(appName, appConfig) {
33
+ return getWorkerCompanionAppKeys(appName, appConfig);
34
+ }
35
+
36
+ /**
37
+ * @param {string} appName
38
+ * @returns {object|null}
39
+ */
40
+ function loadBuilderApplicationConfig(appName) {
41
+ const configPath = path.join(paths.getBuilderPath(appName), 'application.yaml');
42
+ if (!fs.existsSync(configPath)) {
43
+ return null;
44
+ }
45
+ return yaml.load(fs.readFileSync(configPath, 'utf8'));
46
+ }
47
+
48
+ module.exports = {
49
+ isCeleryWorkerStackEnabled,
50
+ getCeleryCompanionAppKeys,
51
+ loadBuilderApplicationConfig,
52
+ resolveWorkerSidecars
53
+ };
@@ -215,6 +215,10 @@ function formatAzureError(errorMsg) {
215
215
  ' Use format: *.azurecr.io (e.g., myacr.azurecr.io)'
216
216
  ];
217
217
  }
218
+ // Docker build failures may mention azurecr.io in the local tag; show the real build error instead.
219
+ if (errorMsg.includes('Docker build failed') || errorMsg.includes('Build failed: Docker')) {
220
+ return null;
221
+ }
218
222
  // Only match ACR-specific authentication errors, not general authentication failures
219
223
  if (errorMsg.includes('ACR') || errorMsg.includes('azurecr.io') ||
220
224
  (errorMsg.includes('authenticate') && (errorMsg.includes('registry') || errorMsg.includes('container')))) {
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Worker/beat compose fragment for local runs.
3
+ *
4
+ * @fileoverview Compose context for optional worker and beat services from application.yaml
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const { buildWorkerSidecarComposeContext } = require('./worker-sidecars');
10
+
11
+ /**
12
+ * @param {string} appKey
13
+ * @param {object} appConfig
14
+ * @param {{ devId: string|number, idNum: number }} ids
15
+ * @returns {ReturnType<typeof buildWorkerSidecarComposeContext>}
16
+ */
17
+ function buildCeleryWorkerComposeContext(appKey, appConfig, ids) {
18
+ return buildWorkerSidecarComposeContext(appKey, appConfig, ids);
19
+ }
20
+
21
+ module.exports = {
22
+ buildCeleryWorkerComposeContext
23
+ };
@@ -14,6 +14,7 @@ const buildCopy = require('./build-copy');
14
14
  const paths = require('./paths');
15
15
  const { getInfraDirName } = require('../infrastructure/helpers');
16
16
  const { resolveMisoEnvironment } = require('./compose-miso-env');
17
+ const { buildCeleryWorkerComposeContext } = require('./compose-celery-worker');
17
18
 
18
19
  /**
19
20
  * When running a pulled Python image (no bind-mounted source), the image CMD may invoke the
@@ -149,7 +150,11 @@ function buildComposeTemplateContext(ctx) {
149
150
  devMountPath: ctx.devMountPath,
150
151
  reloadStartCommand: ctx.reloadStartCommand,
151
152
  infraPgpassPath: ctx.useInfraPgpass ? ctx.infraPgpassPath : null,
152
- useInfraPgpass: !!ctx.useInfraPgpass
153
+ useInfraPgpass: !!ctx.useInfraPgpass,
154
+ workerSidecars: buildCeleryWorkerComposeContext(ctx.appName, ctx.appConfig, {
155
+ devId: ctx.devId,
156
+ idNum: ctx.idNum
157
+ })
153
158
  };
154
159
  }
155
160
 
@@ -303,6 +308,9 @@ async function generateDockerComposeImpl(deps, appName, appConfig, options) {
303
308
  ...layouts,
304
309
  ...side,
305
310
  idNum: head.idNum,
311
+ appName,
312
+ appConfig,
313
+ devId: head.devId,
306
314
  options
307
315
  })
308
316
  );
@@ -190,6 +190,40 @@ function retemplateConfigurationFromEnvTemplate(configArray, envTemplateVariable
190
190
  }
191
191
  }
192
192
 
193
+ /**
194
+ * Re-templates every system variable definition to {{NAME}} (download/export safe).
195
+ *
196
+ * @param {Array<{ name?: string, location?: string }>} configArray - Configuration array (mutated)
197
+ */
198
+ function retemplateAllVariableDefinitions(configArray) {
199
+ if (!Array.isArray(configArray)) return;
200
+ for (const item of configArray) {
201
+ if (!item || String(item.location || 'variable').toLowerCase() !== 'variable') continue;
202
+ const name = item.name && String(item.name).trim();
203
+ if (name) {
204
+ item.value = `{{${name}}}`;
205
+ }
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Re-templates datasource override literals to {{NAME}} placeholders.
211
+ *
212
+ * @param {Array<{ name?: string, value?: string }>} configArray - Datasource configuration (mutated)
213
+ */
214
+ function retemplateDatasourceConfigurationOverrides(configArray) {
215
+ if (!Array.isArray(configArray)) return;
216
+ for (const item of configArray) {
217
+ if (!item || !item.name) continue;
218
+ const name = String(item.name).trim();
219
+ const raw = item.value;
220
+ if (typeof raw !== 'string') continue;
221
+ const value = raw.trim();
222
+ if (!value || value.startsWith('kv://') || value.includes('{{')) continue;
223
+ item.value = `{{${name}}}`;
224
+ }
225
+ }
226
+
193
227
  /**
194
228
  * Reads env.template at integration path, re-templates the given configuration array,
195
229
  * and returns the updated array (mutates in place). If env.template is missing, does nothing.
@@ -215,6 +249,8 @@ module.exports = {
215
249
  collectResolvedVariableConfigurationNames,
216
250
  getEnvTemplateVariableNames,
217
251
  retemplateConfigurationFromEnvTemplate,
252
+ retemplateAllVariableDefinitions,
253
+ retemplateDatasourceConfigurationOverrides,
218
254
  retemplateConfigurationForDownload,
219
255
  substituteVarPlaceholders
220
256
  };
@@ -145,7 +145,10 @@ async function isControllerHealthReachable(controllerUrl) {
145
145
  return false;
146
146
  }
147
147
  try {
148
- const res = await fetch(`${base}/health`, { signal: AbortSignal.timeout(2500) });
148
+ const { fetchWithOptionalDevPlatformTls } = require('./dev-platform-tls');
149
+ const res = await fetchWithOptionalDevPlatformTls(`${base}/health`, {
150
+ signal: AbortSignal.timeout(2500)
151
+ });
149
152
  return res.ok;
150
153
  } catch {
151
154
  return false;
@@ -0,0 +1,216 @@
1
+ /**
2
+ * KV_* env key ↔ kv:// path mapping and payload reference scanning.
3
+ *
4
+ * @fileoverview KV path helpers for credential-secrets-env
5
+ * @author AI Fabrix Team
6
+ * @version 2.0.0
7
+ */
8
+
9
+ const KV_PREFIX = 'KV_';
10
+ const KV_REF_PATTERN = /kv:\/\/([a-zA-Z0-9_\-/]+)/g;
11
+
12
+ /**
13
+ * Converts systemKey to KV_* prefix (e.g. hubspot -> HUBSPOT, my-hubspot -> MY_HUBSPOT).
14
+ * @param {string} systemKey - System key
15
+ * @returns {string}
16
+ */
17
+ function systemKeyToKvPrefix(systemKey) {
18
+ if (!systemKey || typeof systemKey !== 'string') return '';
19
+ return systemKey.replace(/-/g, '_').toUpperCase();
20
+ }
21
+
22
+ /**
23
+ * Maps authentication security key (camelCase) to env VAR (UPPERCASE, no underscores).
24
+ * @param {string} securityKey - Security key (e.g. 'clientId', 'clientSecret')
25
+ * @returns {string}
26
+ */
27
+ function securityKeyToVar(securityKey) {
28
+ if (!securityKey || typeof securityKey !== 'string') return '';
29
+ return securityKey.replace(/_/g, '').toUpperCase();
30
+ }
31
+
32
+ /** Known single-segment variable suffixes (uppercase) for inferring var vs namespace in env keys */
33
+ const VAR_SUFFIXES = new Set(['ID', 'SECRET', 'KEY', 'TOKEN', 'URL', 'USERNAME', 'PASSWORD']);
34
+
35
+ /**
36
+ * Converts var segment(s) from env key to path-style camelCase.
37
+ * @param {string[]} varSegments - One or two segments (e.g. ['CLIENT', 'ID'], ['CLIENTID'])
38
+ * @returns {string}
39
+ */
40
+ function varSegmentsToCamelCase(varSegments) {
41
+ if (!varSegments || varSegments.length === 0) return '';
42
+ if (varSegments.length === 1) {
43
+ const s = varSegments[0].toLowerCase();
44
+ if (s.endsWith('id') && s.length > 2) return s.slice(0, -2) + 'Id';
45
+ if (s.endsWith('secret') && s.length > 6) return s.slice(0, -6) + 'Secret';
46
+ if (s.endsWith('key') && s.length > 3) return s.slice(0, -3) + 'Key';
47
+ if (s.endsWith('token') && s.length > 5) return s.slice(0, -5) + 'Token';
48
+ if (s.endsWith('url') && s.length > 3) return s.slice(0, -3) + 'Url';
49
+ return s;
50
+ }
51
+ return varSegments.map((seg, i) => {
52
+ const lower = seg.toLowerCase();
53
+ return i === 0 ? lower : lower.charAt(0).toUpperCase() + lower.slice(1);
54
+ }).join('');
55
+ }
56
+
57
+ /**
58
+ * @param {string[]} segments - Parsed segments after KV_ prefix
59
+ * @param {string} systemKey - System key (e.g. 'microsoft-teams')
60
+ * @returns {string|null}
61
+ */
62
+ function kvPathWithSystemKey(segments, systemKey) {
63
+ const prefixInKey = systemKey.replace(/-/g, '_').toUpperCase();
64
+ const prefixSegs = prefixInKey.split('_').filter(Boolean);
65
+ if (segments.length <= prefixSegs.length) return null;
66
+ const prefixMatch = prefixSegs.every((p, i) => segments[i] === p);
67
+ if (!prefixMatch) return null;
68
+ const varSegments = segments.slice(prefixSegs.length);
69
+ const pathVar = varSegmentsToCamelCase(varSegments);
70
+ return pathVar ? `kv://${systemKey}/${pathVar}` : null;
71
+ }
72
+
73
+ /**
74
+ * @param {string[]} segments - Parsed segments after KV_ prefix
75
+ * @returns {string|null}
76
+ */
77
+ function kvPathInferred(segments) {
78
+ if (segments.length === 1) return `kv://${segments[0].toLowerCase()}`;
79
+ const varSegmentCount = (segments.length >= 2 && VAR_SUFFIXES.has(segments[segments.length - 1])) ? 2 : 1;
80
+ const namespace = segments.slice(0, -varSegmentCount).map(s => s.toLowerCase()).join('-');
81
+ const varSegments = segments.slice(-varSegmentCount);
82
+ const pathVar = varSegmentsToCamelCase(varSegments);
83
+ return (namespace && pathVar) ? `kv://${namespace}/${pathVar}` : null;
84
+ }
85
+
86
+ /**
87
+ * @param {string} securityKey - Security key (e.g. 'apiKey', 'clientId')
88
+ * @returns {string}
89
+ */
90
+ function getKvPathSegmentForSecurityKey(securityKey) {
91
+ if (!securityKey || typeof securityKey !== 'string') return '';
92
+ return varSegmentsToCamelCase([securityKeyToVar(securityKey)]);
93
+ }
94
+
95
+ /**
96
+ * @param {string} envKey - Env var name (e.g. KV_MICROSOFT_TEAMS_CLIENT_ID)
97
+ * @param {string} [systemKey] - Optional system key
98
+ * @returns {string|null}
99
+ */
100
+ function kvEnvKeyToPath(envKey, systemKey) {
101
+ if (!envKey || typeof envKey !== 'string' || !envKey.toUpperCase().startsWith(KV_PREFIX)) return null;
102
+ const rest = envKey.slice(KV_PREFIX.length).trim();
103
+ if (!rest) return null;
104
+ const segments = rest.split('_').filter(Boolean);
105
+ if (segments.length === 0) return null;
106
+
107
+ const hasSystemKey = typeof systemKey === 'string' && systemKey.length > 0;
108
+ if (hasSystemKey) return kvPathWithSystemKey(segments, systemKey);
109
+ return kvPathInferred(segments);
110
+ }
111
+
112
+ /**
113
+ * @param {Object.<string, string>} envMap - Key-value map from .env
114
+ * @param {string} [systemKey]
115
+ * @returns {Array<{ key: string, value: string }>}
116
+ */
117
+ function collectKvEnvVarsAsSecretItems(envMap, systemKey) {
118
+ if (!envMap || typeof envMap !== 'object') {
119
+ return [];
120
+ }
121
+ const items = [];
122
+ for (const [envKey, rawValue] of Object.entries(envMap)) {
123
+ const value = typeof rawValue === 'string' ? rawValue.trim() : '';
124
+ if (value === '') continue;
125
+ let kvPath = null;
126
+ if (value.startsWith('kv://') && isValidKvPath(value)) {
127
+ kvPath = value;
128
+ }
129
+ if (!kvPath) kvPath = kvEnvKeyToPath(envKey, systemKey) || kvEnvKeyToPath(envKey);
130
+ if (!kvPath) continue;
131
+ items.push({ key: kvPath, value });
132
+ }
133
+ return items;
134
+ }
135
+
136
+ /**
137
+ * @param {Object} secrets - Loaded secrets object
138
+ * @param {string} value - Value (may be "kv://..." or plain)
139
+ * @returns {string|null}
140
+ */
141
+ function resolveKvValue(secrets, value) {
142
+ if (typeof value !== 'string') return null;
143
+ const trimmed = value.trim();
144
+ if (!trimmed.startsWith('kv://')) {
145
+ return trimmed;
146
+ }
147
+ const pathMatch = trimmed.match(/^kv:\/\/([a-zA-Z0-9_\-/]+)$/);
148
+ if (!pathMatch) return null;
149
+ const pathKey = pathMatch[1];
150
+ const resolved = secrets[pathKey];
151
+ if (resolved === undefined) return null;
152
+ return typeof resolved === 'string' ? resolved : String(resolved);
153
+ }
154
+
155
+ /**
156
+ * @param {Object|Array|string|number|boolean|null} obj
157
+ * @param {Set<string>} [acc]
158
+ * @returns {string[]}
159
+ */
160
+ function collectKvRefsFromPayload(obj, acc = new Set()) {
161
+ if (obj === null || obj === undefined) {
162
+ return Array.from(acc);
163
+ }
164
+ if (typeof obj === 'string') {
165
+ let m;
166
+ KV_REF_PATTERN.lastIndex = 0;
167
+ while ((m = KV_REF_PATTERN.exec(obj)) !== null) {
168
+ acc.add(`kv://${m[1]}`);
169
+ }
170
+ return Array.from(acc);
171
+ }
172
+ if (Array.isArray(obj)) {
173
+ for (const item of obj) {
174
+ collectKvRefsFromPayload(item, acc);
175
+ }
176
+ return Array.from(acc);
177
+ }
178
+ if (typeof obj === 'object') {
179
+ for (const v of Object.values(obj)) {
180
+ collectKvRefsFromPayload(v, acc);
181
+ }
182
+ return Array.from(acc);
183
+ }
184
+ return Array.from(acc);
185
+ }
186
+
187
+ /**
188
+ * @param {string} key - kv:// path
189
+ * @returns {boolean}
190
+ */
191
+ function isValidKvPath(key) {
192
+ return typeof key === 'string' && /^kv:\/\/[a-z0-9][a-z0-9\-/]*$/i.test(key.trim());
193
+ }
194
+
195
+ /**
196
+ * @param {unknown} resolved
197
+ * @returns {boolean}
198
+ */
199
+ function isNonEmptySecretValue(resolved) {
200
+ if (resolved === null || resolved === undefined) {
201
+ return false;
202
+ }
203
+ return String(resolved).trim() !== '';
204
+ }
205
+
206
+ module.exports = {
207
+ collectKvEnvVarsAsSecretItems,
208
+ collectKvRefsFromPayload,
209
+ getKvPathSegmentForSecurityKey,
210
+ isNonEmptySecretValue,
211
+ isValidKvPath,
212
+ kvEnvKeyToPath,
213
+ resolveKvValue,
214
+ securityKeyToVar,
215
+ systemKeyToKvPrefix
216
+ };