@aifabrix/builder 2.37.9 → 2.39.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 (74) hide show
  1. package/.cursor/rules/project-rules.mdc +3 -0
  2. package/README.md +19 -0
  3. package/integration/hubspot/hubspot-deploy.json +1 -5
  4. package/integration/hubspot/hubspot-system.json +0 -3
  5. package/lib/api/applications.api.js +29 -1
  6. package/lib/api/auth.api.js +14 -0
  7. package/lib/api/credentials.api.js +34 -0
  8. package/lib/api/datasources-core.api.js +16 -1
  9. package/lib/api/datasources-extended.api.js +18 -1
  10. package/lib/api/deployments.api.js +32 -0
  11. package/lib/api/environments.api.js +11 -0
  12. package/lib/api/external-systems.api.js +16 -1
  13. package/lib/api/pipeline.api.js +12 -4
  14. package/lib/api/service-users.api.js +41 -0
  15. package/lib/api/types/applications.types.js +1 -1
  16. package/lib/api/types/deployments.types.js +1 -1
  17. package/lib/api/types/pipeline.types.js +1 -1
  18. package/lib/api/types/service-users.types.js +24 -0
  19. package/lib/api/wizard.api.js +40 -1
  20. package/lib/app/deploy.js +86 -21
  21. package/lib/app/rotate-secret.js +3 -1
  22. package/lib/app/run-helpers.js +35 -2
  23. package/lib/app/show-display.js +30 -11
  24. package/lib/app/show.js +34 -8
  25. package/lib/cli/index.js +4 -0
  26. package/lib/cli/setup-app.js +40 -0
  27. package/lib/cli/setup-credential-deployment.js +72 -0
  28. package/lib/cli/setup-infra.js +3 -3
  29. package/lib/cli/setup-service-user.js +52 -0
  30. package/lib/cli/setup-utility.js +1 -25
  31. package/lib/commands/app-down.js +80 -0
  32. package/lib/commands/app-logs.js +146 -0
  33. package/lib/commands/app.js +24 -1
  34. package/lib/commands/credential-list.js +104 -0
  35. package/lib/commands/deployment-list.js +184 -0
  36. package/lib/commands/service-user.js +193 -0
  37. package/lib/commands/up-common.js +74 -5
  38. package/lib/commands/up-dataplane.js +13 -7
  39. package/lib/commands/up-miso.js +17 -24
  40. package/lib/core/templates.js +2 -2
  41. package/lib/external-system/deploy.js +79 -15
  42. package/lib/generator/builders.js +8 -27
  43. package/lib/generator/external-controller-manifest.js +5 -4
  44. package/lib/generator/index.js +16 -14
  45. package/lib/generator/split.js +1 -0
  46. package/lib/generator/wizard.js +4 -1
  47. package/lib/schema/application-schema.json +6 -14
  48. package/lib/schema/deployment-rules.yaml +121 -0
  49. package/lib/schema/external-system.schema.json +0 -16
  50. package/lib/utils/app-register-config.js +10 -12
  51. package/lib/utils/app-run-containers.js +2 -1
  52. package/lib/utils/compose-generator.js +2 -1
  53. package/lib/utils/deployment-errors.js +10 -0
  54. package/lib/utils/environment-checker.js +25 -6
  55. package/lib/utils/help-builder.js +0 -1
  56. package/lib/utils/image-version.js +209 -0
  57. package/lib/utils/schema-loader.js +1 -1
  58. package/lib/utils/variable-transformer.js +7 -33
  59. package/lib/validation/external-manifest-validator.js +1 -1
  60. package/package.json +1 -1
  61. package/templates/applications/README.md.hbs +1 -3
  62. package/templates/applications/dataplane/Dockerfile +2 -2
  63. package/templates/applications/dataplane/README.md +20 -6
  64. package/templates/applications/dataplane/env.template +31 -2
  65. package/templates/applications/dataplane/rbac.yaml +1 -1
  66. package/templates/applications/dataplane/variables.yaml +7 -4
  67. package/templates/applications/keycloak/Dockerfile +3 -3
  68. package/templates/applications/keycloak/README.md +14 -4
  69. package/templates/applications/keycloak/env.template +17 -2
  70. package/templates/applications/keycloak/variables.yaml +2 -1
  71. package/templates/applications/miso-controller/README.md +1 -3
  72. package/templates/applications/miso-controller/env.template +85 -25
  73. package/templates/applications/miso-controller/rbac.yaml +15 -0
  74. package/templates/applications/miso-controller/variables.yaml +24 -23
@@ -57,8 +57,7 @@
57
57
  "key",
58
58
  "displayName",
59
59
  "description",
60
- "type",
61
- "deploymentKey"
60
+ "type"
62
61
  ],
63
62
  "properties": {
64
63
  "key": {
@@ -91,6 +90,11 @@
91
90
  "external"
92
91
  ]
93
92
  },
93
+ "version": {
94
+ "type": "string",
95
+ "description": "Application version (semantic); default 1.0.0 when empty",
96
+ "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(-[a-zA-Z0-9.-]+)?$"
97
+ },
94
98
  "image": {
95
99
  "type": "string",
96
100
  "description": "Container image reference",
@@ -768,18 +772,6 @@
768
772
  },
769
773
  "additionalProperties": false
770
774
  },
771
- "deployment": {
772
- "type": "object",
773
- "description": "Deployment configuration for pipeline API",
774
- "properties": {
775
- "controllerUrl": {
776
- "type": "string",
777
- "description": "Controller API URL for deployment",
778
- "pattern": "^https://.*$"
779
- }
780
- },
781
- "additionalProperties": false
782
- },
783
775
  "system": {
784
776
  "type": "object",
785
777
  "description": "Optional: Inline external system configuration for atomic deployment. Uses external-system.schema.json structure via $ref. Alternative to externalIntegration.systems file-based approach.",
@@ -0,0 +1,121 @@
1
+ # Deployment rules – Central mapping for Controller
2
+ #
3
+ # Defines which manifest paths trigger deployment and which can differ per environment.
4
+ # Controller uses this file (or equivalent) for deployment key computation and value merge.
5
+ # Schemas (application, external-system, external-datasource) remain clean; no x-* annotations.
6
+ #
7
+ # Semantics:
8
+ # triggerPaths: Change affects deployment key / requires deploy
9
+ # overridablePaths: Value can differ per environment (preserve on promote)
10
+ # A path may appear in both (e.g. authentication.endpoints triggers deploy and is overridable).
11
+ #
12
+ # Path format: Dot notation. Child paths override parent when both match.
13
+ # Schema keys: application | externalSystem | externalDataSource
14
+
15
+ application:
16
+ triggerPaths:
17
+ - key
18
+ - displayName
19
+ - description
20
+ - type
21
+ - version
22
+ - image
23
+ - registryMode
24
+ - port
25
+ - requiresDatabase
26
+ - databases
27
+ - requiresRedis
28
+ - requiresStorage
29
+ - configuration
30
+ - configuration.items
31
+ - configuration.items.required
32
+ - configuration.items.portalInput
33
+ - healthCheck
34
+ - healthCheck.path
35
+ - healthCheck.probePath
36
+ - healthCheck.probeRequestType
37
+ - healthCheck.probeProtocol
38
+ - frontDoorRouting
39
+ - authentication
40
+ - roles
41
+ - permissions
42
+ - repository
43
+ - startupCommand
44
+ - runtimeVersion
45
+ - scaling
46
+ - build
47
+ - deployment
48
+ - externalIntegration
49
+ overridablePaths:
50
+ - configuration.items.value
51
+ - authentication.endpoints
52
+ - deployment.controllerUrl
53
+ - healthCheck.interval
54
+ - healthCheck.probeIntervalInSeconds
55
+
56
+ externalSystem:
57
+ triggerPaths:
58
+ - key
59
+ - displayName
60
+ - description
61
+ - type
62
+ - enabled
63
+ - environment
64
+ - authentication
65
+ - openapi
66
+ - mcp
67
+ - dataSources
68
+ - configuration
69
+ - configuration.items
70
+ - tags
71
+ - roles
72
+ - permissions
73
+ - endpoints
74
+ - endpointsActive
75
+ - generateMcpContract
76
+ - generateOpenApiContract
77
+ overridablePaths:
78
+ - environment.baseUrl
79
+ - environment.region
80
+ - authentication.oauth2
81
+ - authentication.apikey
82
+ - authentication.basic
83
+ - authentication.aad
84
+ - openapi.specUrl
85
+ - openapi.documentKey
86
+ - mcp.serverUrl
87
+ - mcp.toolPrefix
88
+ - configuration.items.value
89
+ - credentialIdOrKey
90
+
91
+ externalDataSource:
92
+ triggerPaths:
93
+ - key
94
+ - displayName
95
+ - description
96
+ - enabled
97
+ - systemKey
98
+ - entityType
99
+ - resourceType
100
+ - version
101
+ - metadataSchema
102
+ - fieldMappings
103
+ - exposed
104
+ - validation
105
+ - quality
106
+ - indexing
107
+ - context
108
+ - documentStorage
109
+ - portalInput
110
+ - capabilities
111
+ - execution
112
+ - config
113
+ - openapi
114
+ overridablePaths:
115
+ - sync
116
+ - sync.mode
117
+ - sync.schedule
118
+ - sync.batchSize
119
+ - sync.maxParallelRequests
120
+ - openapi.baseUrl
121
+ - openapi.resourcePath
@@ -102,22 +102,6 @@
102
102
  "type": "boolean",
103
103
  "default": true
104
104
  },
105
- "environment": {
106
- "type": "object",
107
- "description": "Environment-level configuration values used by dataplane and external data sources.",
108
- "properties": {
109
- "baseUrl": {
110
- "type": "string",
111
- "description": "Base API URL or MCP server URL",
112
- "pattern": "^(http|https)://.*$"
113
- },
114
- "region": {
115
- "type": "string",
116
- "description": "Optional region setting for API routing"
117
- }
118
- },
119
- "additionalProperties": true
120
- },
121
105
  "authentication": {
122
106
  "type": "object",
123
107
  "description": "Authentication configuration for external system",
@@ -112,19 +112,14 @@ function resolveSystemFilePath(appPath, schemaBasePath, systemFileName) {
112
112
  }
113
113
 
114
114
  /**
115
- * Extracts URL from system JSON
115
+ * Base URL is no longer read from manifest; Controller resolves from configuration at deploy time.
116
116
  * @function extractUrlFromSystemJson
117
- * @param {Object} systemJson - System JSON object
118
- * @param {string} systemFileName - System file name
119
- * @returns {string} Base URL
120
- * @throws {Error} If URL is missing
117
+ * @param {Object} _systemJson - System JSON object (unused; kept for call-site compatibility)
118
+ * @param {string} _systemFileName - System file name (unused)
119
+ * @returns {undefined} URL is not supplied from manifest
121
120
  */
122
- function extractUrlFromSystemJson(systemJson, systemFileName) {
123
- const url = systemJson.environment?.baseUrl;
124
- if (!url) {
125
- throw new Error(`Missing environment.baseUrl in ${systemFileName}`);
126
- }
127
- return url;
121
+ function extractUrlFromSystemJson(_systemJson, _systemFileName) {
122
+ return undefined;
128
123
  }
129
124
 
130
125
  /**
@@ -209,7 +204,10 @@ async function extractExternalIntegrationUrl(appKey, externalIntegration) {
209
204
  */
210
205
  async function extractExternalAppConfiguration(appKey, variables, appKeyFromFile, displayName, description) {
211
206
  const { url, apiKey } = await extractExternalIntegrationUrl(appKey, variables.externalIntegration);
212
- const externalIntegration = { url };
207
+ const externalIntegration = {};
208
+ if (url !== undefined && url !== null) {
209
+ externalIntegration.url = url;
210
+ }
213
211
  if (apiKey) {
214
212
  externalIntegration.apiKey = apiKey;
215
213
  }
@@ -156,6 +156,7 @@ module.exports = {
156
156
  checkImageExists,
157
157
  checkContainerRunning,
158
158
  stopAndRemoveContainer,
159
- logContainerStatus
159
+ logContainerStatus,
160
+ getContainerName
160
161
  };
161
162
 
@@ -458,7 +458,8 @@ async function generateDockerCompose(appName, appConfig, options) {
458
458
  const language = appConfig.build?.language || appConfig.language || 'typescript';
459
459
  const template = loadDockerComposeTemplate(language);
460
460
  const port = options.port || appConfig.port || 3000;
461
- const imageOverride = options.image || options.imageOverride;
461
+ const imageOverride = options.image || options.imageOverride ||
462
+ (options.tag ? `${getImageName(appConfig, appName)}:${options.tag}` : null);
462
463
  const { devId, idNum } = await getDeveloperIdAndNumeric();
463
464
  const { networkName, containerName } = buildNetworkAndContainerNames(appName, devId, idNum);
464
465
  const serviceConfig = buildServiceConfig(appName, appConfig, port, devId, imageOverride);
@@ -206,6 +206,16 @@ async function handleDeploymentErrors(error, appName, url, alreadyLogged = false
206
206
  // Extract error message
207
207
  const errorMessage = extractErrorMessage(error);
208
208
 
209
+ // Deployment failed/cancelled (from deploy.js): show message as-is, do not treat as HTTP error
210
+ if (errorMessage.startsWith('Deployment failed:') || errorMessage.startsWith('Deployment cancelled:')) {
211
+ if (!alreadyLogged) {
212
+ await logDeploymentFailure(appName, url, error);
213
+ }
214
+ const err = new Error(errorMessage);
215
+ err.formatted = (error.formatted || errorMessage).trim();
216
+ throw err;
217
+ }
218
+
209
219
  // For validation errors (like URL validation), just re-throw them directly
210
220
  if (isValidationError(errorMessage)) {
211
221
  throwValidationError(error, errorMessage);
@@ -13,6 +13,8 @@ const fs = require('fs');
13
13
  const path = require('path');
14
14
  const dockerUtils = require('./docker');
15
15
  const { getActualSecretsPath } = require('./secrets-path');
16
+ const config = require('../core/config');
17
+ const devConfig = require('./dev-config');
16
18
 
17
19
  /**
18
20
  * Checks if Docker is installed and available
@@ -33,17 +35,28 @@ async function checkDocker() {
33
35
 
34
36
  /**
35
37
  * Checks if required ports are available
38
+ * Uses developer-specific ports when developer-id greater than 0 (basePort + developerId * 100)
36
39
  *
37
40
  * @async
38
41
  * @function checkPorts
42
+ * @param {number[]} [requiredPorts] - Ports to check. If omitted, uses ports from config developer-id
39
43
  * @returns {Promise<string>} 'ok' if all ports are available, 'warning' otherwise
40
44
  */
41
- async function checkPorts() {
42
- const requiredPorts = [5432, 6379, 5050, 8081];
45
+ async function checkPorts(requiredPorts) {
43
46
  const netstat = require('net');
47
+ let portsToCheck = requiredPorts;
48
+
49
+ if (!portsToCheck || portsToCheck.length === 0) {
50
+ const devId = await config.getDeveloperId();
51
+ const idNum = typeof devId === 'string' ? parseInt(devId, 10) : devId;
52
+ const id = Number.isNaN(idNum) || idNum < 0 ? 0 : idNum;
53
+ const ports = devConfig.getDevPorts(id);
54
+ portsToCheck = [ports.postgres, ports.redis, ports.pgadmin, ports.redisCommander];
55
+ }
56
+
44
57
  let portIssues = 0;
45
58
 
46
- for (const port of requiredPorts) {
59
+ for (const port of portsToCheck) {
47
60
  try {
48
61
  await new Promise((resolve, reject) => {
49
62
  const server = netstat.createServer();
@@ -128,10 +141,16 @@ async function checkEnvironment() {
128
141
  result.recommendations.push('Install Docker and Docker Compose');
129
142
  }
130
143
 
131
- // Check ports
132
- result.ports = await checkPorts();
144
+ // Check ports (developer-specific: dev 0 = base ports, dev N = base + N*100)
145
+ const devId = await config.getDeveloperId();
146
+ const idNum = typeof devId === 'string' ? parseInt(devId, 10) : devId;
147
+ const id = Number.isNaN(idNum) || idNum < 0 ? 0 : idNum;
148
+ const ports = devConfig.getDevPorts(id);
149
+ const requiredPorts = [ports.postgres, ports.redis, ports.pgadmin, ports.redisCommander];
150
+
151
+ result.ports = await checkPorts(requiredPorts);
133
152
  if (result.ports === 'warning') {
134
- result.recommendations.push('Some required ports (5432, 6379, 5050, 8081) are in use');
153
+ result.recommendations.push(`Some required ports (${requiredPorts.join(', ')}) are in use`);
135
154
  }
136
155
 
137
156
  // Check secrets
@@ -75,7 +75,6 @@ const CATEGORIES = [
75
75
  { name: 'resolve', term: 'resolve <app>' },
76
76
  { name: 'json', term: 'json <app>' },
77
77
  { name: 'split-json', term: 'split-json <app>' },
78
- { name: 'genkey', term: 'genkey <app>' },
79
78
  { name: 'validate', term: 'validate <appOrFile>' },
80
79
  { name: 'diff', term: 'diff <file1> <file2>' }
81
80
  ]
@@ -0,0 +1,209 @@
1
+ /**
2
+ * AI Fabrix Builder - Image Version Resolution
3
+ *
4
+ * Resolves application version from Docker image (OCI label or semver tag).
5
+ * When template is empty or image version is greater, uses image version.
6
+ *
7
+ * @fileoverview Image version resolution utilities
8
+ * @author AI Fabrix Team
9
+ * @version 2.0.0
10
+ */
11
+
12
+ const { exec } = require('child_process');
13
+ const { promisify } = require('util');
14
+ const fs = require('fs').promises;
15
+ const fsSync = require('fs');
16
+ const path = require('path');
17
+ const yaml = require('js-yaml');
18
+ const { getBuilderPath } = require('./paths');
19
+ const composeGenerator = require('./compose-generator');
20
+ const containerHelpers = require('./app-run-containers');
21
+
22
+ const execAsync = promisify(exec);
23
+
24
+ const OCI_VERSION_LABEL = 'org.opencontainers.image.version';
25
+ const SEMVER_REGEX = /^v?(\d+\.\d+\.\d+)(?:-[-.\w]+)?(?:\+[-.\w]+)?$/i;
26
+
27
+ /**
28
+ * Gets version from Docker image via OCI label or semver tag
29
+ * @async
30
+ * @param {string} imageName - Image name (e.g. aifabrix/dataplane)
31
+ * @param {string} imageTag - Image tag (e.g. v1.0.0, latest)
32
+ * @returns {Promise<string|null>} Version string or null if not found
33
+ */
34
+ async function getVersionFromImage(imageName, imageTag) {
35
+ if (!imageName || typeof imageName !== 'string') {
36
+ return null;
37
+ }
38
+ const tag = imageTag || 'latest';
39
+ const fullImage = `${imageName}:${tag}`;
40
+
41
+ try {
42
+ const labelFormat = `{{index .Config.Labels "${OCI_VERSION_LABEL}"}}`;
43
+ const { stdout } = await execAsync(
44
+ `docker inspect --format '${labelFormat}' "${fullImage}" 2>/dev/null || true`,
45
+ { timeout: 10000 }
46
+ );
47
+ const labelValue = (stdout || '').trim();
48
+ if (labelValue && labelValue !== '<no value>') {
49
+ return labelValue;
50
+ }
51
+
52
+ const tagMatch = tag.match(SEMVER_REGEX);
53
+ if (tagMatch) {
54
+ return tagMatch[1];
55
+ }
56
+ return null;
57
+ } catch {
58
+ return null;
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Compares two semantic versions
64
+ * @param {string} a - First version
65
+ * @param {string} b - Second version
66
+ * @returns {number} -1 if a < b, 0 if a === b, 1 if a > b
67
+ */
68
+ function compareSemver(a, b) {
69
+ if (!a || !b) {
70
+ return 0;
71
+ }
72
+ const parse = (v) => {
73
+ const m = String(v).match(SEMVER_REGEX);
74
+ if (!m) return null;
75
+ const parts = m[1].split('.').map(Number);
76
+ return { major: parts[0] || 0, minor: parts[1] || 0, patch: parts[2] || 0 };
77
+ };
78
+ const pa = parse(a);
79
+ const pb = parse(b);
80
+ if (!pa || !pb) return 0;
81
+ if (pa.major !== pb.major) return pa.major > pb.major ? 1 : -1;
82
+ if (pa.minor !== pb.minor) return pa.minor > pb.minor ? 1 : -1;
83
+ if (pa.patch !== pb.patch) return pa.patch > pb.patch ? 1 : -1;
84
+ return 0;
85
+ }
86
+
87
+ /**
88
+ * Resolves version for external app (app.version or externalIntegration.version)
89
+ * @param {Object} variables - Parsed variables.yaml
90
+ * @returns {string}
91
+ */
92
+ function resolveExternalVersion(variables) {
93
+ const version =
94
+ variables.app?.version ||
95
+ variables.externalIntegration?.version ||
96
+ '1.0.0';
97
+ return String(version).trim() || '1.0.0';
98
+ }
99
+
100
+ /**
101
+ * Resolves version for regular app when image exists
102
+ * @async
103
+ * @param {string} imageName - Image name
104
+ * @param {string} imageTag - Image tag
105
+ * @param {string} templateVersion - Template version (may be empty)
106
+ * @returns {Promise<{ version: string, fromImage: boolean }>}
107
+ */
108
+ async function resolveRegularVersion(imageName, imageTag, templateVersion) {
109
+ const templateEmpty =
110
+ templateVersion === undefined ||
111
+ templateVersion === null ||
112
+ String(templateVersion).trim() === '';
113
+ const templateStr = String(templateVersion || '').trim();
114
+
115
+ const imageVersion = await getVersionFromImage(imageName, imageTag);
116
+ const useImage =
117
+ imageVersion &&
118
+ (templateEmpty || compareSemver(imageVersion, templateStr) >= 0);
119
+
120
+ const version = useImage ? imageVersion : (templateEmpty ? '1.0.0' : templateStr);
121
+ return { version, fromImage: Boolean(useImage) };
122
+ }
123
+
124
+ /**
125
+ * Resolves version for an app: from image when image exists and template empty or smaller
126
+ * @async
127
+ * @param {string} appName - Application name
128
+ * @param {Object} variables - Parsed variables.yaml
129
+ * @param {Object} [options] - Options
130
+ * @param {boolean} [options.updateBuilder] - When true, update builder/variables.yaml if fromImage
131
+ * @param {string} [options.builderPath] - Builder path (defaults to getBuilderPath(appName))
132
+ * @returns {Promise<{ version: string, fromImage: boolean, updated: boolean }>}
133
+ */
134
+ async function resolveVersionForApp(appName, variables, options = {}) {
135
+ if (!appName || typeof appName !== 'string') {
136
+ return { version: '1.0.0', fromImage: false, updated: false };
137
+ }
138
+
139
+ if (variables?.externalIntegration) {
140
+ const version = resolveExternalVersion(variables);
141
+ return { version, fromImage: false, updated: false };
142
+ }
143
+
144
+ const imageName = composeGenerator.getImageName(variables, appName);
145
+ const imageTag = variables?.image?.tag || 'latest';
146
+ const imageExists = await containerHelpers.checkImageExists(imageName, imageTag);
147
+
148
+ if (!imageExists) {
149
+ const templateVersion = variables?.app?.version;
150
+ const templateEmpty =
151
+ templateVersion === undefined ||
152
+ templateVersion === null ||
153
+ String(templateVersion).trim() === '';
154
+ const fallback = templateEmpty ? '1.0.0' : String(templateVersion).trim();
155
+ return { version: fallback, fromImage: false, updated: false };
156
+ }
157
+
158
+ const { version, fromImage } = await resolveRegularVersion(
159
+ imageName,
160
+ imageTag,
161
+ variables?.app?.version
162
+ );
163
+
164
+ let updated = false;
165
+ if (fromImage && options.updateBuilder) {
166
+ const builderPath = options.builderPath || getBuilderPath(appName);
167
+ updated = await updateAppVersionInVariablesYaml(builderPath, version);
168
+ }
169
+
170
+ return { version, fromImage, updated };
171
+ }
172
+
173
+ /**
174
+ * Updates app.version in builder variables.yaml
175
+ * @async
176
+ * @param {string} builderPath - Path to builder app directory
177
+ * @param {string} version - Version to set
178
+ * @returns {Promise<boolean>} True if file was updated
179
+ */
180
+ async function updateAppVersionInVariablesYaml(builderPath, version) {
181
+ if (!builderPath || !version || typeof version !== 'string') {
182
+ return false;
183
+ }
184
+ const variablesPath = path.join(builderPath, 'variables.yaml');
185
+ if (!fsSync.existsSync(variablesPath)) {
186
+ return false;
187
+ }
188
+
189
+ try {
190
+ const content = await fs.readFile(variablesPath, 'utf8');
191
+ const parsed = yaml.load(content) || {};
192
+ if (!parsed.app) {
193
+ parsed.app = {};
194
+ }
195
+ parsed.app.version = version;
196
+ const dumped = yaml.dump(parsed, { lineWidth: -1 });
197
+ await fs.writeFile(variablesPath, dumped, { mode: 0o644, encoding: 'utf8' });
198
+ return true;
199
+ } catch {
200
+ return false;
201
+ }
202
+ }
203
+
204
+ module.exports = {
205
+ getVersionFromImage,
206
+ compareSemver,
207
+ resolveVersionForApp,
208
+ updateAppVersionInVariablesYaml
209
+ };
@@ -199,7 +199,7 @@ function detectFromDatasourceFields(parsed) {
199
199
  * @returns {string|null} Schema type or null if not detected
200
200
  */
201
201
  function detectFromApplicationFields(parsed) {
202
- if (parsed.deploymentKey || (parsed.image && parsed.registryMode && parsed.port)) {
202
+ if (parsed.image && parsed.registryMode && parsed.port) {
203
203
  return 'application';
204
204
  }
205
205
  return null;
@@ -96,20 +96,6 @@ function handlePartialAuthentication(authentication) {
96
96
  return auth;
97
97
  }
98
98
 
99
- /**
100
- * Adds placeholder deployment key if missing
101
- * @function addPlaceholderDeploymentKey
102
- * @param {Object} result - Result object
103
- * @returns {Object} Result object with deployment key
104
- */
105
- function addPlaceholderDeploymentKey(result) {
106
- if (!result.deploymentKey) {
107
- // This is a 64-character hex string matching the SHA256 pattern
108
- result.deploymentKey = '0000000000000000000000000000000000000000000000000000000000000000';
109
- }
110
- return result;
111
- }
112
-
113
99
  /**
114
100
  * Transforms flat structure to schema-compatible format
115
101
  * @function transformFlatStructure
@@ -126,9 +112,6 @@ function transformFlatStructure(variables, appName) {
126
112
  result.authentication = handlePartialAuthentication(result.authentication);
127
113
  }
128
114
 
129
- // Add placeholder deploymentKey for validation
130
- addPlaceholderDeploymentKey(result);
131
-
132
115
  return result;
133
116
  }
134
117
 
@@ -215,22 +198,14 @@ function validateBuildConfig(build) {
215
198
  }
216
199
 
217
200
  /**
218
- * Validates and transforms deployment configuration
201
+ * Validates and transforms deployment configuration.
202
+ * Manifest is generic: deployment URL/env are resolved outside the manifest (user/config).
219
203
  * @function validateDeploymentConfig
220
- * @param {Object} deployment - Deployment configuration
221
- * @returns {Object|null} Validated deployment config or null
204
+ * @param {Object} _deployment - Deployment configuration (unused; manifest is generic)
205
+ * @returns {null} No deployment block emitted from variables
222
206
  */
223
- function validateDeploymentConfig(deployment) {
224
- if (!deployment) {
225
- return null;
226
- }
227
-
228
- const deploymentConfig = {};
229
- if (deployment.controllerUrl && deployment.controllerUrl.trim() !== '' && /^https:\/\/.*$/.test(deployment.controllerUrl)) {
230
- deploymentConfig.controllerUrl = deployment.controllerUrl;
231
- }
232
-
233
- return Object.keys(deploymentConfig).length > 0 ? deploymentConfig : null;
207
+ function validateDeploymentConfig(_deployment) {
208
+ return null;
234
209
  }
235
210
 
236
211
  /**
@@ -379,8 +354,7 @@ function transformVariablesForValidation(variables, appName) {
379
354
 
380
355
  // Nested structure - transform it
381
356
  const transformed = buildBaseTransformedStructure(variables, appName);
382
- const result = transformOptionalFields(variables, transformed);
383
- return addPlaceholderDeploymentKey(result);
357
+ return transformOptionalFields(variables, transformed);
384
358
  }
385
359
 
386
360
  module.exports = {
@@ -148,7 +148,7 @@ function validateConditionalRequirements(manifest, errors, warnings) {
148
148
  * @returns {void}
149
149
  */
150
150
  function validateRequiredFields(manifest, errors) {
151
- const requiredFields = ['key', 'displayName', 'description', 'type', 'deploymentKey'];
151
+ const requiredFields = ['key', 'displayName', 'description', 'type'];
152
152
  requiredFields.forEach(field => {
153
153
  if (!manifest[field]) {
154
154
  errors.push(`Required field "${field}" is missing`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aifabrix/builder",
3
- "version": "2.37.9",
3
+ "version": "2.39.0",
4
4
  "description": "AI Fabrix Local Fabric & Deployment SDK",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -92,8 +92,7 @@ aifabrix dockerfile {{appName}} --force # Generate Dockerfile
92
92
  aifabrix resolve {{appName}} # Generate .env file
93
93
 
94
94
  # Deployment
95
- aifabrix json {{appName}} # Preview deployment JSON
96
- aifabrix genkey {{appName}} # Generate deployment key
95
+ aifabrix json {{appName}} # Generate deployment manifest
97
96
  aifabrix push {{appName}} --registry {{registry}} # Push to ACR
98
97
  aifabrix deploy {{appName}} # Deploy (controller/env from config)
99
98
 
@@ -176,7 +175,6 @@ Controller URL and environment (for `deploy`, `app register`, etc.) are set via
176
175
  ```bash
177
176
  aifabrix resolve {{appName}} --force
178
177
  aifabrix json {{appName}}
179
- aifabrix genkey {{appName}}
180
178
  ```
181
179
 
182
180
  ---
@@ -9,8 +9,8 @@ FROM aifabrix/dataplane:latest
9
9
  EXPOSE 3001
10
10
 
11
11
  # Health check (documentation; base image may already set this)
12
- HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
13
- CMD curl -f http://localhost:3001/health || exit 1
12
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
13
+ CMD wget --no-verbose --tries=1 --spider http://localhost:3001/health || exit 1
14
14
 
15
15
  # CMD inherited from base image; override only if needed
16
16
  # CMD inherited: uvicorn app.main:app --host 0.0.0.0 --port 3001