@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
@@ -205,6 +205,12 @@ async function getPlatformConfig(dataplaneUrl, authConfig, platformKey, config)
205
205
  });
206
206
  }
207
207
 
208
+ /**
209
+ * Wizard AI generation can exceed the default 30s HTTP timeout (OpenAPI + LLM).
210
+ * @constant {number}
211
+ */
212
+ const WIZARD_GENERATE_CONFIG_TIMEOUT_MS = 120000;
213
+
208
214
  /**
209
215
  * Generate configuration via AI (OpenAPI-based)
210
216
  * POST /api/v1/wizard/generate-config
@@ -235,7 +241,8 @@ async function getPlatformConfig(dataplaneUrl, authConfig, platformKey, config)
235
241
  async function generateConfig(dataplaneUrl, authConfig, config) {
236
242
  const client = createDataplaneApiClient(dataplaneUrl, authConfig);
237
243
  return await client.post('/api/v1/wizard/generate-config', {
238
- body: config
244
+ body: config,
245
+ timeoutMs: WIZARD_GENERATE_CONFIG_TIMEOUT_MS
239
246
  });
240
247
  }
241
248
 
@@ -254,7 +261,8 @@ async function generateConfig(dataplaneUrl, authConfig, config) {
254
261
  async function generateConfigStream(dataplaneUrl, authConfig, config) {
255
262
  const client = createDataplaneApiClient(dataplaneUrl, authConfig);
256
263
  return await client.post('/api/v1/wizard/generate-config-stream', {
257
- body: config
264
+ body: config,
265
+ timeoutMs: WIZARD_GENERATE_CONFIG_TIMEOUT_MS
258
266
  });
259
267
  }
260
268
 
package/lib/app/down.js CHANGED
@@ -18,6 +18,13 @@ const logger = require('../utils/logger');
18
18
  const config = require('../core/config');
19
19
  const helpers = require('./run-helpers');
20
20
  const { validateAppName } = require('./push');
21
+ const buildCopy = require('../utils/build-copy');
22
+ const {
23
+ getCeleryCompanionAppKeys,
24
+ loadBuilderApplicationConfig
25
+ } = require('../utils/celery-companion-apps');
26
+ const fsSync = require('fs');
27
+ const path = require('path');
21
28
 
22
29
  /**
23
30
  * Executes a shell command asynchronously.
@@ -74,6 +81,53 @@ function getAppVolumeNameCandidates(appName, developerId) {
74
81
  return suffixes.map((suffix) => `aifabrix_dev${suffix}_${appName}_data`);
75
82
  }
76
83
 
84
+ function buildVolumeRemovalCandidates(appName, developerId) {
85
+ const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
86
+ const primaryName = getAppVolumeName(appName, developerId);
87
+ const legacyDev0Name = idNum === 0 ? `aifabrix_dev0_${appName}_data` : null;
88
+ return Array.from(new Set([primaryName, legacyDev0Name].filter(Boolean)));
89
+ }
90
+
91
+ async function removeVolumeIfPresent(volumeName) {
92
+ logger.log(chalk.yellow(`Removing volume ${volumeName}...`));
93
+ try {
94
+ await execAsync(`docker volume rm -f ${volumeName}`);
95
+ logger.log(formatSuccessLine(`Volume ${volumeName} removed`));
96
+ } catch (volErr) {
97
+ logger.log(chalk.gray(`Volume ${volumeName} not found or already removed`));
98
+ }
99
+ }
100
+
101
+ async function removeAppVolumes(appName, developerId) {
102
+ for (const volumeName of buildVolumeRemovalCandidates(appName, developerId)) {
103
+ await removeVolumeIfPresent(volumeName);
104
+ }
105
+ }
106
+
107
+ async function stopAppContainers(appName, companionKeys, developerId) {
108
+ const devDir = buildCopy.getDevDirectory(appName, developerId);
109
+ const composePath = path.join(devDir, 'docker-compose.yaml');
110
+
111
+ if (companionKeys.length > 0 && fsSync.existsSync(composePath)) {
112
+ try {
113
+ await execAsync(`docker compose -f "${composePath}" down`);
114
+ return;
115
+ } catch (composeErr) {
116
+ logger.log(chalk.gray(`Compose down: ${composeErr.message}; stopping containers individually`));
117
+ for (const companionKey of companionKeys) {
118
+ await helpers.stopAndRemoveContainer(companionKey, developerId, false);
119
+ }
120
+ await helpers.stopAndRemoveContainer(appName, developerId, false);
121
+ return;
122
+ }
123
+ }
124
+
125
+ await helpers.stopAndRemoveContainer(appName, developerId, false);
126
+ for (const companionKey of companionKeys) {
127
+ await helpers.stopAndRemoveContainer(companionKey, developerId, false);
128
+ }
129
+ }
130
+
77
131
  /**
78
132
  * Stops and removes a specific application's container.
79
133
  * If --volumes is passed, attempts to remove the app's named Docker volume.
@@ -93,42 +147,20 @@ function getAppVolumeNameCandidates(appName, developerId) {
93
147
  */
94
148
  async function downApp(appName, options = {}) {
95
149
  try {
96
- // Input validation
97
150
  if (!appName || typeof appName !== 'string') {
98
151
  throw new Error('Application name is required and must be a string');
99
152
  }
100
153
  validateAppName(appName);
101
154
 
102
155
  const volumes = !!options.volumes;
103
-
104
- // Load developer ID
105
156
  const developerId = await config.getDeveloperId();
157
+ const appConfig = loadBuilderApplicationConfig(appName);
158
+ const companionKeys = getCeleryCompanionAppKeys(appName, appConfig);
106
159
 
107
- // Stop and remove container (idempotent handling in helper logs when not running)
108
- await helpers.stopAndRemoveContainer(appName, developerId, false);
160
+ await stopAppContainers(appName, companionKeys, developerId);
109
161
 
110
- // Optionally remove named Docker volume (ignore if it doesn't exist)
111
162
  if (volumes) {
112
- const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
113
- // Primary expected name
114
- const primaryName = getAppVolumeName(appName, developerId);
115
- // Legacy name used when devId \"0\" was treated as string in templates
116
- const legacyDev0Name = idNum === 0 ? `aifabrix_dev0_${appName}_data` : null;
117
- // Build list of candidates, unique and non-empty
118
- const candidates = Array.from(
119
- new Set([primaryName, legacyDev0Name].filter(Boolean))
120
- );
121
-
122
- for (const volumeName of candidates) {
123
- logger.log(chalk.yellow(`Removing volume ${volumeName}...`));
124
- try {
125
- await execAsync(`docker volume rm -f ${volumeName}`);
126
- logger.log(formatSuccessLine(`Volume ${volumeName} removed`));
127
- } catch (volErr) {
128
- // Swallow errors for missing volume; provide neutral message
129
- logger.log(chalk.gray(`Volume ${volumeName} not found or already removed`));
130
- }
131
- }
163
+ await removeAppVolumes(appName, developerId);
132
164
  }
133
165
  } catch (error) {
134
166
  // Provide meaningful error while avoiding sensitive info
package/lib/app/index.js CHANGED
@@ -93,7 +93,7 @@ function validateAppNameAndSetup(appName, options) {
93
93
 
94
94
  const initialType = options.type || 'external';
95
95
  const baseDir = getBaseDirForAppType(initialType);
96
- const appPath = paths.getAppPath(appName, initialType);
96
+ const appPath = paths.getAppCreatePath(appName, initialType);
97
97
 
98
98
  return { initialType, baseDir, appPath };
99
99
  }
@@ -124,7 +124,7 @@ async function handleTemplateSetup(options) {
124
124
  */
125
125
  async function prepareFinalAppPath(appName, config, initialAppPath) {
126
126
  const finalBaseDir = getBaseDirForAppType(config.type);
127
- const finalAppPath = paths.getAppPath(appName, config.type);
127
+ const finalAppPath = paths.getAppCreatePath(appName, config.type);
128
128
 
129
129
  // If path changed, validate the new path
130
130
  if (finalAppPath !== initialAppPath) {
@@ -10,6 +10,7 @@ const config = require('../core/config');
10
10
  const { resolveDockerImageRef } = require('../utils/resolve-docker-image-ref');
11
11
  const { checkImageExists } = require('../utils/app-run-containers');
12
12
  const { buildDevImageRepositoryPath } = require('../utils/image-name');
13
+ const { wantsLocalDockerImageDiscovery } = require('../utils/build-resolve-image');
13
14
 
14
15
  /**
15
16
  * @param {string} appName
@@ -41,7 +42,7 @@ async function resolveRunImageWithLocalFallback(appName, appConfig, runOptions =
41
42
 
42
43
  const baseRef = resolveDockerImageRef(appName, appConfig, opts);
43
44
 
44
- if (opts.base === true) {
45
+ if (opts.base === true || !wantsLocalDockerImageDiscovery(appConfig)) {
45
46
  return baseRef;
46
47
  }
47
48
 
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Stop running app containers (and celery companions) before `aifabrix run`.
3
+ *
4
+ * @fileoverview Pre-run container teardown for port reuse
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const { formatWarningLine } = require('../utils/cli-test-layout-chalk');
12
+ const config = require('../core/config');
13
+ const logger = require('../utils/logger');
14
+ const containerHelpers = require('../utils/app-run-containers');
15
+ const helpers = require('./run-helpers');
16
+ const { computeEffectiveEnvironmentScopedResources } = require('../utils/environment-scoped-resources');
17
+ const { getCeleryCompanionAppKeys } = require('../utils/celery-companion-apps');
18
+
19
+ /**
20
+ * @param {Object} appConfig
21
+ * @param {Object} options
22
+ * @returns {Promise<{ effectiveEnvironmentScopedResources: true, env: string }|null>}
23
+ */
24
+ async function resolveRunScopeOpts(appConfig, options) {
25
+ const runEnvKey = (options.env || 'dev').toLowerCase();
26
+ const userCfg = await config.getConfig();
27
+ const effectiveEnvironmentScopedResources = computeEffectiveEnvironmentScopedResources(
28
+ Boolean(userCfg.useEnvironmentScopedResources),
29
+ appConfig.environmentScopedResources === true,
30
+ runEnvKey
31
+ );
32
+ if (!effectiveEnvironmentScopedResources) {
33
+ return null;
34
+ }
35
+ return { effectiveEnvironmentScopedResources: true, env: runEnvKey };
36
+ }
37
+
38
+ /**
39
+ * @param {string[]} companionKeys
40
+ * @param {string|number} developerId
41
+ * @param {boolean} debug
42
+ * @param {Object|null} scopeOpts
43
+ * @returns {Promise<string[]>}
44
+ */
45
+ async function findRunningCompanionKeys(companionKeys, developerId, debug, scopeOpts) {
46
+ const running = [];
47
+ for (const companionKey of companionKeys) {
48
+ if (await helpers.checkContainerRunning(companionKey, developerId, debug, scopeOpts)) {
49
+ running.push(companionKey);
50
+ }
51
+ }
52
+ return running;
53
+ }
54
+
55
+ /**
56
+ * @param {string[]} companionKeys
57
+ * @param {string|number} developerId
58
+ * @param {boolean} debug
59
+ * @param {Object|null} scopeOpts
60
+ * @returns {Promise<void>}
61
+ */
62
+ async function stopCompanionContainers(companionKeys, developerId, debug, scopeOpts) {
63
+ for (const companionKey of companionKeys) {
64
+ const companionName = containerHelpers.getContainerName(companionKey, developerId, scopeOpts);
65
+ logger.log(formatWarningLine(`Stopping companion container ${companionName}`));
66
+ await helpers.stopAndRemoveContainer(companionKey, developerId, debug, scopeOpts);
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Check if container is running and stop it (and celery companions) if needed.
72
+ *
73
+ * @async
74
+ * @param {string} appName
75
+ * @param {Object} appConfig
76
+ * @param {Object} options
77
+ * @param {boolean} debug
78
+ * @returns {Promise<void>}
79
+ */
80
+ async function checkAndStopContainer(appName, appConfig, options, debug) {
81
+ const developerId = appConfig.developerId;
82
+ const scopeOpts = await resolveRunScopeOpts(appConfig, options);
83
+ const companionKeys = getCeleryCompanionAppKeys(appName, appConfig);
84
+ const companionsToStop = await findRunningCompanionKeys(
85
+ companionKeys,
86
+ developerId,
87
+ debug,
88
+ scopeOpts
89
+ );
90
+ const containerRunning = await helpers.checkContainerRunning(
91
+ appName,
92
+ developerId,
93
+ debug,
94
+ scopeOpts
95
+ );
96
+ if (!containerRunning && companionsToStop.length === 0) {
97
+ return;
98
+ }
99
+ await stopCompanionContainers(companionsToStop, developerId, debug, scopeOpts);
100
+ if (!containerRunning) {
101
+ return;
102
+ }
103
+ const containerName = containerHelpers.getContainerName(appName, developerId, scopeOpts);
104
+ logger.log(formatWarningLine(`Container ${containerName} is already running`));
105
+ await helpers.stopAndRemoveContainer(appName, developerId, debug, scopeOpts);
106
+ }
107
+
108
+ module.exports = {
109
+ checkAndStopContainer,
110
+ resolveRunScopeOpts,
111
+ findRunningCompanionKeys,
112
+ stopCompanionContainers
113
+ };
package/lib/app/run.js CHANGED
@@ -27,6 +27,7 @@ const containerHelpers = require('../utils/app-run-containers');
27
27
  const helpers = require('./run-helpers');
28
28
  const { ensureReloadSync, logReloadDevSummary } = require('./run-reload-sync');
29
29
  const { logRestartDevMountSummary } = require('./restart-display');
30
+ const { checkAndStopContainer } = require('./run-stop-containers');
30
31
  const { execWithDockerEnv } = require('../utils/docker-exec');
31
32
  const { isRunCliNoProxy } = require('../utils/run-cli-flags');
32
33
 
@@ -111,37 +112,6 @@ async function validateAppForRun(appName, _debug) {
111
112
  return true;
112
113
  }
113
114
 
114
- /**
115
- * Check if container is running and stop it if needed
116
- * @async
117
- * @param {string} appName - Application name
118
- * @param {number|string} developerId - Developer ID
119
- * @param {boolean} debug - Debug flag
120
- * @returns {Promise<void>}
121
- */
122
- async function checkAndStopContainer(appName, appConfig, options, debug) {
123
- const developerId = appConfig.developerId;
124
- const runEnvKey = (options.env || 'dev').toLowerCase();
125
- const userCfg = await config.getConfig();
126
- const { computeEffectiveEnvironmentScopedResources } = require('../utils/environment-scoped-resources');
127
- const effectiveEnvironmentScopedResources = computeEffectiveEnvironmentScopedResources(
128
- Boolean(userCfg.useEnvironmentScopedResources),
129
- appConfig.environmentScopedResources === true,
130
- runEnvKey
131
- );
132
- const scopeOpts = effectiveEnvironmentScopedResources
133
- ? { effectiveEnvironmentScopedResources: true, env: runEnvKey }
134
- : null;
135
- const containerRunning = await helpers.checkContainerRunning(appName, developerId, debug, scopeOpts);
136
- if (!containerRunning) {
137
- return;
138
- }
139
-
140
- const containerName = containerHelpers.getContainerName(appName, developerId, scopeOpts);
141
- logger.log(formatWarningLine(`Container ${containerName} is already running`));
142
- await helpers.stopAndRemoveContainer(appName, developerId, debug, scopeOpts);
143
- }
144
-
145
115
  /**
146
116
  * Calculate host port and validate it's available
147
117
  * @async
@@ -49,6 +49,40 @@ function logApplicationOptional(a) {
49
49
  });
50
50
  }
51
51
 
52
+ function localRunProfileDiffers(application, localRunProfile) {
53
+ if (!localRunProfile || !application) return false;
54
+ if (localRunProfile.url && localRunProfile.url !== application.url) return true;
55
+ if (localRunProfile.internalUrl && localRunProfile.internalUrl !== application.internalUrl) {
56
+ return true;
57
+ }
58
+ return (
59
+ localRunProfile.port !== undefined &&
60
+ localRunProfile.port !== null &&
61
+ localRunProfile.port !== application.port
62
+ );
63
+ }
64
+
65
+ function logLocalRunProfileSection(summary) {
66
+ const local = summary.localRunProfile;
67
+ if (!local || summary.source !== 'online' || !localRunProfileDiffers(summary.application, local)) {
68
+ return;
69
+ }
70
+ logger.log('');
71
+ logger.log(chalk.gray(' Local run profile (docker manifest url://):'));
72
+ if (local.url && local.url !== summary.application.url) {
73
+ logger.log(chalk.gray(` URL: ${local.url}`));
74
+ }
75
+ if (local.internalUrl && local.internalUrl !== summary.application.internalUrl) {
76
+ logger.log(chalk.gray(` Internal URL: ${local.internalUrl}`));
77
+ }
78
+ if (local.port !== undefined && local.port !== summary.application.port) {
79
+ logger.log(chalk.gray(` Port: ${local.port}`));
80
+ }
81
+ logger.log(
82
+ chalk.gray(' Controller registry values above are what identity sync and pipeline use.')
83
+ );
84
+ }
85
+
52
86
  function logApplicationFields(a) {
53
87
  logApplicationRequired(a);
54
88
  logApplicationOptional(a);
@@ -96,6 +130,7 @@ function logApplicationSection(a, summary) {
96
130
  } else {
97
131
  logApplicationFields(a);
98
132
  }
133
+ logLocalRunProfileSection(summary);
99
134
  if (summary.runReloadDefault && !summary.isExternal) {
100
135
  logger.log(` ${'Run default:'.padEnd(16)} reload on (config)`);
101
136
  }
package/lib/app/show.js CHANGED
@@ -54,10 +54,22 @@ function attachRunDefaultsFromUserConfig(summary, appKey, userCfg) {
54
54
 
55
55
  /**
56
56
  * Overlay `application.url` / `application.internalUrl` from declarative url:// rules (same profile as run `.env`, default `docker`).
57
- * Used for offline and **online** show so controller metadata does not stale local proxy/TLS hints.
57
+ * Offline: replaces URL fields for local run hints. Online: keeps controller registry on `application` and adds `localRunProfile` when it differs.
58
58
  * @param {Object} summary
59
59
  * @param {string} appKey
60
60
  */
61
+ function portFromShowUrl(url) {
62
+ if (!url || typeof url !== 'string') return undefined;
63
+ try {
64
+ const parsed = new URL(url);
65
+ if (!parsed.port) return undefined;
66
+ const port = Number(parsed.port);
67
+ return Number.isFinite(port) ? port : undefined;
68
+ } catch {
69
+ return undefined;
70
+ }
71
+ }
72
+
61
73
  async function attachDeclarativeUrlsToShowApplication(summary, appKey) {
62
74
  if (!summary || summary.isExternal) return;
63
75
  const t = summary.application && summary.application.type;
@@ -68,8 +80,24 @@ async function attachDeclarativeUrlsToShowApplication(summary, appKey) {
68
80
  const variablesPath = resolveApplicationConfigPath(appPath);
69
81
  const urls = await resolveDeclarativeShowUrlsForApp(appKey, appPath, variablesPath, 'docker');
70
82
  if (!urls) return;
83
+
84
+ const localPort = portFromShowUrl(urls.publicUrl);
85
+ const localRunProfile = {
86
+ url: urls.publicUrl,
87
+ internalUrl: urls.internalUrl,
88
+ port: localPort ?? summary.application.port
89
+ };
90
+
91
+ if (summary.source === 'online') {
92
+ summary.localRunProfile = localRunProfile;
93
+ return;
94
+ }
95
+
71
96
  summary.application.url = urls.publicUrl;
72
97
  summary.application.internalUrl = urls.internalUrl;
98
+ if (localPort !== undefined) {
99
+ summary.application.port = localPort;
100
+ }
73
101
  } catch {
74
102
  /* display-only */
75
103
  }
@@ -830,6 +858,9 @@ function outputOnlineJson(summary, permissionsOnly, manifestSource) {
830
858
  out.runReloadDefault = true;
831
859
  }
832
860
  out.runProxyDefault = Boolean(summary.runProxyDefault);
861
+ if (summary.localRunProfile) {
862
+ out.localRunProfile = summary.localRunProfile;
863
+ }
833
864
  if (summary.externalSystem !== undefined && summary.externalSystem !== null) {
834
865
  out.externalSystem = summary.externalSystem && summary.externalSystem.error
835
866
  ? { error: summary.externalSystem.error }
@@ -33,10 +33,11 @@ const logger = require('../utils/logger');
33
33
  const dockerfileUtils = require('../utils/dockerfile-utils');
34
34
  const dockerBuild = require('../utils/docker-build');
35
35
  const buildCopy = require('../utils/build-copy');
36
- const { buildDevImageName } = require('../utils/image-name');
36
+ const { resolveBuildDockerTagNames } = require('../utils/build-resolve-image');
37
37
  const buildHelpers = require('../utils/build-helpers');
38
38
  const secretsEnvWrite = require('../core/secrets-env-write');
39
39
  const { ensureRunSecretsForApp } = require('../app/run-env-compose');
40
+ const { assertMisoControllerNpmSdkDeps } = require('./miso-controller-npm-sdk-guard');
40
41
 
41
42
  /**
42
43
  * Loads application config for an application
@@ -286,8 +287,15 @@ async function prepareDevDirectory(appName, buildConfig, options) {
286
287
  const devDir = await buildCopy.copyBuilderToDevDirectory(appName, developerId);
287
288
  logger.log(formatSuccessLine(`Workspace ready: ${devDir}`));
288
289
 
289
- const { config: appConfig, imageName } = await buildHelpers.loadAndValidateConfig(appName);
290
- const effectiveImageName = buildDevImageName(imageName, developerId);
290
+ const { config: appConfig } = await buildHelpers.loadAndValidateConfig(appName);
291
+ const { buildImageName, compatBaseName } = await resolveBuildDockerTagNames(
292
+ appName,
293
+ appConfig,
294
+ developerId,
295
+ options
296
+ );
297
+ const effectiveImageName = buildImageName;
298
+ const imageName = compatBaseName || buildImageName;
291
299
 
292
300
  // Check if application source files exist, if not copy from templates
293
301
  const filesCopied = await copyApplicationSourceFiles(appName, devDir);
@@ -389,9 +397,10 @@ async function handleDockerfileGeneration(appName, params, options, buildHelpers
389
397
  /**
390
398
  * Resolve Docker build-args from resolved env map and merged BASH_* secrets.
391
399
  * @param {string} appName
400
+ * @param {string} [contextPath] Docker build context (repo root for miso-controller)
392
401
  * @returns {Promise<Object.<string, string>>}
393
402
  */
394
- async function resolveDockerBuildArgsForApp(appName) {
403
+ async function resolveDockerBuildArgsForApp(appName, contextPath) {
395
404
  const envMap = await secretsEnvWrite.resolveAndGetEnvMap(appName, { environment: 'docker' });
396
405
  const { getBashPrefixedProcessEnvOverlay } = require('../utils/bash-secret-env');
397
406
  const bashOverlay = await getBashPrefixedProcessEnvOverlay(null, appName);
@@ -406,6 +415,9 @@ async function resolveDockerBuildArgsForApp(appName) {
406
415
  const shellPypi = typeof process.env.PYPI_TOKEN === 'string' ? process.env.PYPI_TOKEN.trim() : '';
407
416
  if (shellNpm) buildArgs.NPM_TOKEN = shellNpm;
408
417
  if (shellPypi) buildArgs.PYPI_TOKEN = shellPypi;
418
+ if (appName === 'miso-controller' && contextPath) {
419
+ assertMisoControllerNpmSdkDeps(path.resolve(contextPath));
420
+ }
409
421
  return buildArgs;
410
422
  }
411
423
 
@@ -429,7 +441,7 @@ async function runStandardDockerBuild(appName, options) {
429
441
  appConfig
430
442
  }, options, buildHelpers);
431
443
  await ensureRunSecretsForApp(appName);
432
- const buildArgs = await resolveDockerBuildArgsForApp(appName);
444
+ const buildArgs = await resolveDockerBuildArgsForApp(appName, contextPath);
433
445
  const buildOptions = { ...options, buildArgs };
434
446
  const tag = options.tag || 'latest';
435
447
  if (typeof tag === 'string' && tag.includes(',')) {
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Ensure miso-controller SDK deps are npm semver before Docker build (no file: vendor hacks).
3
+ * @fileoverview miso-controller npm SDK guard for builder
4
+ */
5
+
6
+ const { execFileSync } = require('child_process');
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+
10
+ /**
11
+ * @param {string} repoRoot - aifabrix-miso repo root (Docker build context)
12
+ * @throws {Error} When guard script is missing or deps use file:
13
+ */
14
+ function assertMisoControllerNpmSdkDeps(repoRoot) {
15
+ const script = path.join(
16
+ repoRoot,
17
+ 'packages/miso-controller/scripts/assert-npm-sdk-deps.js'
18
+ );
19
+ if (!fs.existsSync(script)) {
20
+ throw new Error(`miso-controller SDK guard not found: ${script}`);
21
+ }
22
+ execFileSync('node', [script], { cwd: repoRoot, stdio: 'inherit' });
23
+ }
24
+
25
+ module.exports = { assertMisoControllerNpmSdkDeps };