@aifabrix/builder 2.31.1 → 2.32.2

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 (118) hide show
  1. package/README.md +9 -9
  2. package/integration/hubspot/README.md +2 -2
  3. package/integration/hubspot/hubspot-deploy-company.json +17 -14
  4. package/integration/hubspot/hubspot-deploy-contact.json +19 -16
  5. package/integration/hubspot/hubspot-deploy-deal.json +21 -18
  6. package/lib/api/types/datasources.types.js +31 -5
  7. package/lib/api/types/wizard.types.js +142 -0
  8. package/lib/api/wizard.api.js +177 -0
  9. package/lib/{app-config.js → app/config.js} +4 -4
  10. package/lib/{app-deploy.js → app/deploy.js} +8 -8
  11. package/lib/app/display.js +90 -0
  12. package/lib/{app-dockerfile.js → app/dockerfile.js} +4 -4
  13. package/lib/{app-down.js → app/down.js} +4 -4
  14. package/lib/app/helpers.js +218 -0
  15. package/lib/app/index.js +298 -0
  16. package/lib/{app-list.js → app/list.js} +6 -6
  17. package/lib/{app-push.js → app/push.js} +4 -4
  18. package/lib/{app-readme.js → app/readme.js} +34 -13
  19. package/lib/{app-register.js → app/register.js} +9 -9
  20. package/lib/{app-rotate-secret.js → app/rotate-secret.js} +10 -10
  21. package/lib/{app-run-helpers.js → app/run-helpers.js} +10 -10
  22. package/lib/{app-run.js → app/run.js} +6 -6
  23. package/lib/{build.js → build/index.js} +59 -32
  24. package/lib/build/package.json +7 -0
  25. package/lib/cli.js +245 -179
  26. package/lib/commands/app.js +3 -3
  27. package/lib/commands/datasource.js +4 -4
  28. package/lib/commands/login-credentials.js +209 -0
  29. package/lib/commands/login-device.js +254 -0
  30. package/lib/commands/login.js +67 -378
  31. package/lib/commands/logout.js +1 -1
  32. package/lib/commands/secrets-set.js +1 -1
  33. package/lib/commands/secure.js +2 -2
  34. package/lib/commands/wizard.js +498 -0
  35. package/lib/{audit-logger.js → core/audit-logger.js} +1 -1
  36. package/lib/{config.js → core/config.js} +28 -26
  37. package/lib/{diff.js → core/diff.js} +157 -72
  38. package/lib/{secrets.js → core/secrets.js} +86 -49
  39. package/lib/{templates.js → core/templates-env.js} +14 -222
  40. package/lib/core/templates.js +279 -0
  41. package/lib/{datasource-deploy.js → datasource/deploy.js} +6 -6
  42. package/lib/{datasource-diff.js → datasource/diff.js} +2 -2
  43. package/lib/datasource/list.js +223 -0
  44. package/lib/{datasource-validate.js → datasource/validate.js} +2 -2
  45. package/lib/{deployer.js → deployment/deployer.js} +48 -18
  46. package/lib/{environment-deploy.js → deployment/environment.js} +163 -84
  47. package/lib/{push.js → deployment/push.js} +1 -1
  48. package/lib/external-system/deploy-helpers.js +145 -0
  49. package/lib/{external-system-deploy.js → external-system/deploy.js} +156 -111
  50. package/lib/external-system/download-helpers.js +114 -0
  51. package/lib/{external-system-download.js → external-system/download.js} +92 -135
  52. package/lib/{external-system-generator.js → external-system/generator.js} +15 -11
  53. package/lib/external-system/test-auth.js +40 -0
  54. package/lib/external-system/test-execution.js +84 -0
  55. package/lib/external-system/test-helpers.js +109 -0
  56. package/lib/{external-system-test.js → external-system/test.js} +174 -192
  57. package/lib/{generator-builders.js → generator/builders.js} +87 -10
  58. package/lib/{generator-external.js → generator/external.js} +115 -52
  59. package/lib/{github-generator.js → generator/github.js} +116 -15
  60. package/lib/{generator-helpers.js → generator/helpers.js} +92 -42
  61. package/lib/{generator.js → generator/index.js} +49 -22
  62. package/lib/{generator-split.js → generator/split.js} +108 -55
  63. package/lib/generator/wizard-prompts.js +357 -0
  64. package/lib/generator/wizard.js +490 -0
  65. package/lib/{infra.js → infrastructure/index.js} +49 -22
  66. package/lib/schema/external-datasource.schema.json +158 -136
  67. package/lib/schema/external-system.schema.json +43 -1
  68. package/lib/utils/api.js +9 -5
  69. package/lib/utils/app-register-api.js +60 -32
  70. package/lib/utils/app-register-auth.js +172 -47
  71. package/lib/utils/app-register-config.js +130 -59
  72. package/lib/utils/app-run-containers.js +29 -8
  73. package/lib/utils/build-helpers.js +1 -1
  74. package/lib/utils/cli-utils.js +78 -30
  75. package/lib/utils/compose-generator.js +145 -65
  76. package/lib/utils/config-paths.js +2 -0
  77. package/lib/utils/deployment-errors.js +1 -1
  78. package/lib/utils/device-code.js +99 -41
  79. package/lib/utils/env-config-loader.js +1 -1
  80. package/lib/utils/env-copy.js +21 -18
  81. package/lib/utils/env-endpoints.js +115 -67
  82. package/lib/utils/env-map.js +13 -14
  83. package/lib/utils/env-ports.js +45 -25
  84. package/lib/utils/env-template.js +84 -42
  85. package/lib/utils/error-formatter.js +26 -9
  86. package/lib/utils/error-formatters/error-parser.js +90 -4
  87. package/lib/utils/error-formatters/http-status-errors.js +54 -17
  88. package/lib/utils/error-formatters/network-errors.js +103 -26
  89. package/lib/utils/external-system-display.js +184 -90
  90. package/lib/utils/external-system-validators.js +164 -42
  91. package/lib/utils/file-upload.js +109 -0
  92. package/lib/utils/health-check.js +199 -83
  93. package/lib/utils/infra-containers.js +1 -1
  94. package/lib/utils/infra-status.js +66 -15
  95. package/lib/utils/local-secrets.js +45 -25
  96. package/lib/utils/paths.js +45 -33
  97. package/lib/utils/schema-loader.js +42 -25
  98. package/lib/utils/schema-resolver.js +123 -74
  99. package/lib/utils/secrets-encryption.js +62 -25
  100. package/lib/utils/secrets-helpers.js +126 -63
  101. package/lib/utils/secrets-path.js +1 -1
  102. package/lib/utils/secrets-url.js +1 -1
  103. package/lib/utils/token-manager-refresh.js +181 -0
  104. package/lib/utils/token-manager.js +76 -123
  105. package/lib/utils/variable-transformer.js +154 -77
  106. package/lib/utils/yaml-preserve.js +41 -47
  107. package/lib/{template-validator.js → validation/template.js} +54 -23
  108. package/lib/{validate.js → validation/validate.js} +205 -125
  109. package/lib/{validator.js → validation/validator.js} +58 -39
  110. package/package.json +31 -2
  111. package/templates/external-system/deploy.ps1.hbs +34 -0
  112. package/templates/external-system/deploy.sh.hbs +34 -0
  113. package/templates/external-system/external-datasource.json.hbs +31 -12
  114. package/lib/app.js +0 -467
  115. package/lib/datasource-list.js +0 -141
  116. /package/lib/{app-prompts.js → app/prompts.js} +0 -0
  117. /package/lib/{env-reader.js → core/env-reader.js} +0 -0
  118. /package/lib/{key-generator.js → core/key-generator.js} +0 -0
@@ -13,7 +13,7 @@ const path = require('path');
13
13
  const yaml = require('js-yaml');
14
14
  const chalk = require('chalk');
15
15
  const logger = require('./logger');
16
- const config = require('../config');
16
+ const config = require('../core/config');
17
17
  const devConfig = require('../utils/dev-config');
18
18
  const { rewriteInfraEndpoints } = require('./env-endpoints');
19
19
  const { buildEnvVarMap } = require('./env-map');
@@ -126,23 +126,26 @@ function updateLocalhostUrls(envContent, baseAppPort, appPort) {
126
126
  * @param {Object} envVars - Existing env vars map
127
127
  * @returns {Object} Updated env vars map
128
128
  */
129
- function extractEnvVarsFromContent(envContent, envVars) {
130
- const redisHostMatch = envContent.match(/^REDIS_HOST\s*=\s*([^\r\n$]+)/m);
131
- const redisPortMatch = envContent.match(/^REDIS_PORT\s*=\s*([^\r\n$]+)/m);
132
- const dbHostMatch = envContent.match(/^DB_HOST\s*=\s*([^\r\n$]+)/m);
133
- const dbPortMatch = envContent.match(/^DB_PORT\s*=\s*([^\r\n$]+)/m);
134
- if (redisHostMatch && redisHostMatch[1] && !redisHostMatch[1].includes('${')) {
135
- envVars.REDIS_HOST = redisHostMatch[1].trim();
136
- }
137
- if (redisPortMatch && redisPortMatch[1] && !redisPortMatch[1].includes('${')) {
138
- envVars.REDIS_PORT = redisPortMatch[1].trim();
139
- }
140
- if (dbHostMatch && dbHostMatch[1] && !dbHostMatch[1].includes('${')) {
141
- envVars.DB_HOST = dbHostMatch[1].trim();
142
- }
143
- if (dbPortMatch && dbPortMatch[1] && !dbPortMatch[1].includes('${')) {
144
- envVars.DB_PORT = dbPortMatch[1].trim();
129
+ /**
130
+ * Extracts a single environment variable from content
131
+ * @function extractSingleEnvVar
132
+ * @param {string} envContent - Environment file content
133
+ * @param {string} varName - Variable name
134
+ * @param {Object} envVars - Environment variables object
135
+ */
136
+ function extractSingleEnvVar(envContent, varName, envVars) {
137
+ const pattern = new RegExp(`^${varName}\\s*=\\s*([^\\r\\n$]+)`, 'm');
138
+ const match = envContent.match(pattern);
139
+ if (match && match[1] && !match[1].includes('${')) {
140
+ envVars[varName] = match[1].trim();
145
141
  }
142
+ }
143
+
144
+ function extractEnvVarsFromContent(envContent, envVars) {
145
+ extractSingleEnvVar(envContent, 'REDIS_HOST', envVars);
146
+ extractSingleEnvVar(envContent, 'REDIS_PORT', envVars);
147
+ extractSingleEnvVar(envContent, 'DB_HOST', envVars);
148
+ extractSingleEnvVar(envContent, 'DB_PORT', envVars);
146
149
  return envVars;
147
150
  }
148
151
 
@@ -205,7 +208,7 @@ async function processEnvVariables(envPath, variablesPath, appName, secretsPath)
205
208
 
206
209
  // Regenerate .env file with env=local instead of copying docker-generated file
207
210
  if (appName) {
208
- const { generateEnvContent } = require('../secrets');
211
+ const { generateEnvContent } = require('../core/secrets');
209
212
  const localEnvContent = await generateEnvContent(appName, secretsPath, 'local', false);
210
213
  fs.writeFileSync(outputPath, localEnvContent, { mode: 0o600 });
211
214
  logger.log(chalk.green(`✓ Generated local .env at: ${variables.build.envOutputPath}`));
@@ -11,7 +11,7 @@
11
11
  const fs = require('fs');
12
12
  const path = require('path');
13
13
  const yaml = require('js-yaml');
14
- const config = require('../config');
14
+ const config = require('../core/config');
15
15
  const devConfig = require('../utils/dev-config');
16
16
  const { loadEnvConfig } = require('./env-config-loader');
17
17
 
@@ -75,22 +75,58 @@ function getLocalhostOverride(context) {
75
75
  * @param {Object} [devPorts] - Optional devPorts object with pre-adjusted ports
76
76
  * @returns {Promise<number>} Service port with developer-id adjustment (for local context only)
77
77
  */
78
- async function getServicePort(portKey, serviceName, hosts, context, devPorts) {
79
- // If devPorts provided, use it (already has developer-id adjustment)
80
- if (devPorts && typeof devPorts[serviceName] === 'number') {
81
- return devPorts[serviceName];
82
- }
83
-
84
- // Get base port from config
85
- let basePort = null;
78
+ /**
79
+ * Gets base port from config or fallback
80
+ * @function getBasePortFromConfig
81
+ * @param {string} portKey - Port key
82
+ * @param {string} serviceName - Service name
83
+ * @param {Object} hosts - Hosts configuration
84
+ * @returns {number|null} Base port or null
85
+ */
86
+ function getBasePortFromConfig(portKey, serviceName, hosts) {
86
87
  if (hosts[portKey] !== undefined && hosts[portKey] !== null) {
87
88
  const portVal = typeof hosts[portKey] === 'number' ? hosts[portKey] : parseInt(hosts[portKey], 10);
88
89
  if (!Number.isNaN(portVal)) {
89
- basePort = portVal;
90
+ return portVal;
90
91
  }
91
92
  }
92
93
 
93
94
  // Last resort fallback to devConfig (only if not in config)
95
+ const basePorts = devConfig.getBasePorts();
96
+ return basePorts[serviceName] || null;
97
+ }
98
+
99
+ /**
100
+ * Applies developer-id adjustment to port
101
+ * @async
102
+ * @function applyDeveloperIdAdjustment
103
+ * @param {number} basePort - Base port
104
+ * @returns {Promise<number>} Adjusted port
105
+ */
106
+ async function applyDeveloperIdAdjustment(basePort) {
107
+ try {
108
+ const devId = await config.getDeveloperId();
109
+ let devIdNum = 0;
110
+ if (devId !== null && devId !== undefined) {
111
+ const parsed = parseInt(devId, 10);
112
+ if (!Number.isNaN(parsed)) {
113
+ devIdNum = parsed;
114
+ }
115
+ }
116
+ return devIdNum === 0 ? basePort : (basePort + (devIdNum * 100));
117
+ } catch {
118
+ return basePort;
119
+ }
120
+ }
121
+
122
+ async function getServicePort(portKey, serviceName, hosts, context, devPorts) {
123
+ // If devPorts provided, use it (already has developer-id adjustment)
124
+ if (devPorts && typeof devPorts[serviceName] === 'number') {
125
+ return devPorts[serviceName];
126
+ }
127
+
128
+ // Get base port from config
129
+ let basePort = getBasePortFromConfig(portKey, serviceName, hosts);
94
130
  if (basePort === null || basePort === undefined) {
95
131
  const basePorts = devConfig.getBasePorts();
96
132
  basePort = basePorts[serviceName];
@@ -98,19 +134,7 @@ async function getServicePort(portKey, serviceName, hosts, context, devPorts) {
98
134
 
99
135
  // Apply developer-id adjustment only for local context
100
136
  if (context === 'local') {
101
- try {
102
- const devId = await config.getDeveloperId();
103
- let devIdNum = 0;
104
- if (devId !== null && devId !== undefined) {
105
- const parsed = parseInt(devId, 10);
106
- if (!Number.isNaN(parsed)) {
107
- devIdNum = parsed;
108
- }
109
- }
110
- return devIdNum === 0 ? basePort : (basePort + (devIdNum * 100));
111
- } catch {
112
- return basePort;
113
- }
137
+ return await applyDeveloperIdAdjustment(basePort);
114
138
  }
115
139
 
116
140
  // For docker context, return base port without adjustment
@@ -144,9 +168,14 @@ function getServiceHost(host, context, defaultHost, localhostOverride) {
144
168
  * @param {number} dbPort - Database port
145
169
  * @returns {string} Updated content
146
170
  */
147
- function updateEndpointVariables(envContent, redisHost, redisPort, dbHost, dbPort) {
148
- let updated = envContent;
149
-
171
+ /**
172
+ * Updates Redis-related variables
173
+ * @function updateRedisVariables
174
+ * @param {string} updated - Current content
175
+ * @param {string} redisHost - Redis host
176
+ * @returns {string} Updated content
177
+ */
178
+ function updateRedisVariables(updated, redisHost) {
150
179
  // Update REDIS_URL if present
151
180
  if (/^REDIS_URL\s*=.*$/m.test(updated)) {
152
181
  updated = updated.replace(
@@ -156,31 +185,31 @@ function updateEndpointVariables(envContent, redisHost, redisPort, dbHost, dbPor
156
185
  }
157
186
 
158
187
  // Update REDIS_HOST if present
159
- // If the original has host:port pattern, use ${VAR} references
160
- // Otherwise, just set the host value
161
188
  if (/^REDIS_HOST\s*=.*$/m.test(updated)) {
162
189
  const hostPortMatch = updated.match(/^REDIS_HOST\s*=\s*([a-zA-Z0-9_.-]+):\d+$/m);
163
190
  const hasPortPattern = !!hostPortMatch;
164
191
  if (hasPortPattern) {
165
- // Original had host:port pattern, use ${VAR} references
166
192
  updated = updated.replace(
167
193
  /^REDIS_HOST\s*=\s*.*$/m,
168
194
  'REDIS_HOST=${REDIS_HOST}:${REDIS_PORT}'
169
195
  );
170
196
  } else {
171
- // Just host, set actual value
172
197
  updated = updated.replace(/^REDIS_HOST\s*=\s*.*$/m, `REDIS_HOST=${redisHost}`);
173
198
  }
174
199
  }
175
200
 
176
- // Update REDIS_PORT if present
177
- if (/^REDIS_PORT\s*=.*$/m.test(updated)) {
178
- updated = updated.replace(
179
- /^REDIS_PORT\s*=\s*.*$/m,
180
- `REDIS_PORT=${redisPort}`
181
- );
182
- }
201
+ return updated;
202
+ }
183
203
 
204
+ /**
205
+ * Updates database host/port variables
206
+ * @function updateDatabaseVariables
207
+ * @param {string} updated - Current content
208
+ * @param {string} dbHost - Database host
209
+ * @param {number} dbPort - Database port
210
+ * @returns {string} Updated content
211
+ */
212
+ function updateDatabaseVariables(updated, dbHost, dbPort) {
184
213
  // Update DB_HOST if present
185
214
  if (/^DB_HOST\s*=.*$/m.test(updated)) {
186
215
  updated = updated.replace(/^DB_HOST\s*=\s*.*$/m, `DB_HOST=${dbHost}`);
@@ -188,58 +217,77 @@ function updateEndpointVariables(envContent, redisHost, redisPort, dbHost, dbPor
188
217
 
189
218
  // Update DB_PORT if present
190
219
  if (/^DB_PORT\s*=.*$/m.test(updated)) {
191
- updated = updated.replace(
192
- /^DB_PORT\s*=\s*.*$/m,
193
- `DB_PORT=${dbPort}`
194
- );
220
+ updated = updated.replace(/^DB_PORT\s*=\s*.*$/m, `DB_PORT=${dbPort}`);
221
+ }
222
+
223
+ // Update DATABASE_PORT if present
224
+ if (/^DATABASE_PORT\s*=.*$/m.test(updated)) {
225
+ updated = updated.replace(/^DATABASE_PORT\s*=\s*.*$/m, `DATABASE_PORT=${dbPort}`);
195
226
  }
196
227
 
197
- // Update DATABASE_URL and other database URL variables (postgresql:// or postgres:// URLs)
198
- // Handles DATABASE_URL, DATABASELOG_URL, and any other *_URL variables with postgresql:// or postgres://
199
- // First, collect all matches to avoid issues with modifying string during iteration
228
+ return updated;
229
+ }
230
+
231
+ /**
232
+ * Updates database URL variables
233
+ * @function updateDatabaseUrlVariables
234
+ * @param {string} updated - Current content
235
+ * @returns {string} Updated content
236
+ */
237
+ function updateDatabaseUrlVariables(updated) {
200
238
  const dbUrlPattern = /^(DATABASE[^=]*_URL)\s*=\s*(postgresql?:\/\/)([^:]+):([^@]+)@([^:/]+):(\d+)(\/[^\s]*)?/gm;
201
239
  const matches = [];
202
240
  let dbUrlMatch;
203
- // Reset regex lastIndex to ensure we start from beginning
204
241
  dbUrlPattern.lastIndex = 0;
205
242
  while ((dbUrlMatch = dbUrlPattern.exec(updated)) !== null) {
206
243
  matches.push({
207
- varName: dbUrlMatch[1], // DATABASE_URL, DATABASELOG_URL, etc.
208
- protocol: dbUrlMatch[2], // postgresql:// or postgres://
209
- user: dbUrlMatch[3], // username
210
- password: dbUrlMatch[4], // password
211
- path: dbUrlMatch[7] || '' // /database path
244
+ varName: dbUrlMatch[1],
245
+ protocol: dbUrlMatch[2],
246
+ user: dbUrlMatch[3],
247
+ password: dbUrlMatch[4],
248
+ path: dbUrlMatch[7] || ''
212
249
  });
213
250
  }
214
- // Now apply all replacements
215
251
  for (const match of matches) {
216
252
  updated = updated.replace(
217
253
  new RegExp(`^${match.varName}\\s*=\\s*.*$`, 'm'),
218
254
  `${match.varName}=${match.protocol}${match.user}:${match.password}@\${DB_HOST}:\${DB_PORT}${match.path}`
219
255
  );
220
256
  }
257
+ return updated;
258
+ }
221
259
 
222
- // Update DATABASE_PORT if present (some templates use DATABASE_PORT instead of DB_PORT)
223
- if (/^DATABASE_PORT\s*=.*$/m.test(updated)) {
224
- updated = updated.replace(
225
- /^DATABASE_PORT\s*=\s*.*$/m,
226
- `DATABASE_PORT=${dbPort}`
227
- );
228
- }
229
-
230
- // Update Keycloak database variables if present
231
- // KC_DB_URL_HOST is used by Keycloak to connect to the database
260
+ /**
261
+ * Updates Keycloak database variables
262
+ * @function updateKeycloakVariables
263
+ * @param {string} updated - Current content
264
+ * @param {string} dbHost - Database host
265
+ * @param {number} dbPort - Database port
266
+ * @returns {string} Updated content
267
+ */
268
+ function updateKeycloakVariables(updated, dbHost, dbPort) {
232
269
  if (/^KC_DB_URL_HOST\s*=.*$/m.test(updated)) {
233
270
  updated = updated.replace(/^KC_DB_URL_HOST\s*=\s*.*$/m, `KC_DB_URL_HOST=${dbHost}`);
234
271
  }
235
-
236
- // KC_DB_URL_PORT is used by Keycloak for database port
237
272
  if (/^KC_DB_URL_PORT\s*=.*$/m.test(updated)) {
238
- updated = updated.replace(
239
- /^KC_DB_URL_PORT\s*=\s*.*$/m,
240
- `KC_DB_URL_PORT=${dbPort}`
241
- );
273
+ updated = updated.replace(/^KC_DB_URL_PORT\s*=\s*.*$/m, `KC_DB_URL_PORT=${dbPort}`);
242
274
  }
275
+ return updated;
276
+ }
277
+
278
+ function updateEndpointVariables(envContent, redisHost, redisPort, dbHost, dbPort) {
279
+ let updated = envContent;
280
+
281
+ updated = updateRedisVariables(updated, redisHost);
282
+
283
+ // Update REDIS_PORT if present
284
+ if (/^REDIS_PORT\s*=.*$/m.test(updated)) {
285
+ updated = updated.replace(/^REDIS_PORT\s*=\s*.*$/m, `REDIS_PORT=${redisPort}`);
286
+ }
287
+
288
+ updated = updateDatabaseVariables(updated, dbHost, dbPort);
289
+ updated = updateDatabaseUrlVariables(updated);
290
+ updated = updateKeycloakVariables(updated, dbHost, dbPort);
243
291
 
244
292
  return updated;
245
293
  }
@@ -12,7 +12,7 @@ const fs = require('fs');
12
12
  const path = require('path');
13
13
  const yaml = require('js-yaml');
14
14
  const { loadEnvConfig } = require('./env-config-loader');
15
- const config = require('../config');
15
+ const config = require('../core/config');
16
16
 
17
17
  /**
18
18
  * Load base environment variables from env-config.yaml
@@ -246,19 +246,18 @@ function calculateDockerPublicPorts(result, devIdNum) {
246
246
  // Match any variable ending with _PORT (e.g., MISO_PORT, KEYCLOAK_PORT, DB_PORT)
247
247
  if (/_PORT$/.test(key) && !/_PUBLIC_PORT$/.test(key)) {
248
248
  const publicPortKey = key.replace(/_PORT$/, '_PUBLIC_PORT');
249
- // Skip if public port already exists (allow manual override)
250
- if (result[publicPortKey] === undefined) {
251
- let portVal;
252
- if (typeof value === 'string') {
253
- portVal = parseInt(value, 10);
254
- } else if (typeof value === 'number') {
255
- portVal = value;
256
- } else {
257
- continue;
258
- }
259
- if (!Number.isNaN(portVal)) {
260
- result[publicPortKey] = String(portVal + (devIdNum * 100));
261
- }
249
+ // Always recalculate public port based on base port, even if manually set
250
+ // This ensures developer-id offset is always applied correctly
251
+ let portVal;
252
+ if (typeof value === 'string') {
253
+ portVal = parseInt(value, 10);
254
+ } else if (typeof value === 'number') {
255
+ portVal = value;
256
+ } else {
257
+ continue;
258
+ }
259
+ if (!Number.isNaN(portVal)) {
260
+ result[publicPortKey] = String(portVal + (devIdNum * 100));
262
261
  }
263
262
  }
264
263
  }
@@ -10,7 +10,7 @@
10
10
 
11
11
  const fs = require('fs');
12
12
  const yaml = require('js-yaml');
13
- const config = require('../config');
13
+ const config = require('../core/config');
14
14
 
15
15
  /**
16
16
  * Update PORT in the container's .env file to use variables.port (+offset)
@@ -18,37 +18,57 @@ const config = require('../config');
18
18
  * @param {string} envPath - Path to .env
19
19
  * @param {string} variablesPath - Path to variables.yaml
20
20
  */
21
+ /**
22
+ * Gets developer ID from environment variable or config file
23
+ * @function getDeveloperIdFromEnvOrConfig
24
+ * @returns {number} Developer ID number
25
+ */
26
+ function getDeveloperIdFromEnvOrConfig() {
27
+ const devIdRaw = process.env.AIFABRIX_DEVELOPERID;
28
+ if (devIdRaw && /^[0-9]+$/.test(devIdRaw)) {
29
+ return parseInt(devIdRaw, 10);
30
+ }
31
+
32
+ try {
33
+ const cfgPath = config && config.CONFIG_FILE ? config.CONFIG_FILE : null;
34
+ if (cfgPath && fs.existsSync(cfgPath)) {
35
+ const cfgContent = fs.readFileSync(cfgPath, 'utf8');
36
+ const cfg = yaml.load(cfgContent) || {};
37
+ const raw = cfg['developer-id'];
38
+ if (typeof raw === 'number') {
39
+ return raw;
40
+ }
41
+ if (typeof raw === 'string' && /^[0-9]+$/.test(raw)) {
42
+ return parseInt(raw, 10);
43
+ }
44
+ }
45
+ } catch {
46
+ // ignore, will use 0
47
+ }
48
+
49
+ return 0;
50
+ }
51
+
52
+ /**
53
+ * Calculates port with developer ID adjustment
54
+ * @function calculatePortWithDevId
55
+ * @param {number} basePort - Base port
56
+ * @param {number} devIdNum - Developer ID number
57
+ * @returns {number} Adjusted port
58
+ */
59
+ function calculatePortWithDevId(basePort, devIdNum) {
60
+ return devIdNum > 0 ? (basePort + devIdNum * 100) : basePort;
61
+ }
62
+
21
63
  function updateContainerPortInEnvFile(envPath, variablesPath) {
22
64
  if (!fs.existsSync(variablesPath)) {
23
65
  return;
24
66
  }
25
67
  const variablesContent = fs.readFileSync(variablesPath, 'utf8');
26
68
  const variables = yaml.load(variablesContent);
27
- // Base port from variables
28
69
  const basePort = variables?.port || 3000;
29
- // Determine developer-id (prefer env var for sync context, fallback to config file)
30
- let devIdNum = 0;
31
- const devIdRaw = process.env.AIFABRIX_DEVELOPERID;
32
- if (devIdRaw && /^[0-9]+$/.test(devIdRaw)) {
33
- devIdNum = parseInt(devIdRaw, 10);
34
- } else {
35
- try {
36
- const cfgPath = config && config.CONFIG_FILE ? config.CONFIG_FILE : null;
37
- if (cfgPath && fs.existsSync(cfgPath)) {
38
- const cfgContent = fs.readFileSync(cfgPath, 'utf8');
39
- const cfg = yaml.load(cfgContent) || {};
40
- const raw = cfg['developer-id'];
41
- if (typeof raw === 'number') {
42
- devIdNum = raw;
43
- } else if (typeof raw === 'string' && /^[0-9]+$/.test(raw)) {
44
- devIdNum = parseInt(raw, 10);
45
- }
46
- }
47
- } catch {
48
- // ignore, will use 0
49
- }
50
- }
51
- const port = devIdNum > 0 ? (basePort + devIdNum * 100) : basePort;
70
+ const devIdNum = getDeveloperIdFromEnvOrConfig();
71
+ const port = calculatePortWithDevId(basePort, devIdNum);
52
72
  let envContent = fs.readFileSync(envPath, 'utf8');
53
73
  envContent = envContent.replace(/^PORT\s*=\s*.*$/m, `PORT=${port}`);
54
74
  fs.writeFileSync(envPath, envContent, { mode: 0o600 });
@@ -24,6 +24,85 @@ const logger = require('../utils/logger');
24
24
  * Note: This parameter is accepted for compatibility but the template format http://${MISO_HOST}:${MISO_PORT} is used instead
25
25
  * @returns {Promise<void>} Resolves when template is updated
26
26
  */
27
+ /**
28
+ * Checks which MISO entries exist in content
29
+ * @function checkMisoEntries
30
+ * @param {string} content - File content
31
+ * @returns {Object} Object with boolean flags for each entry
32
+ */
33
+ function checkMisoEntries(content) {
34
+ return {
35
+ hasClientId: /^MISO_CLIENTID\s*=/m.test(content),
36
+ hasClientSecret: /^MISO_CLIENTSECRET\s*=/m.test(content),
37
+ hasControllerUrl: /^MISO_CONTROLLER_URL\s*=/m.test(content)
38
+ };
39
+ }
40
+
41
+ /**
42
+ * Updates existing MISO entries
43
+ * @function updateExistingMisoEntries
44
+ * @param {string} content - File content
45
+ * @param {string} clientIdKey - Client ID key
46
+ * @param {string} clientSecretKey - Client secret key
47
+ * @param {Object} entries - Entry flags
48
+ * @returns {string} Updated content
49
+ */
50
+ function updateExistingMisoEntries(content, clientIdKey, clientSecretKey, entries) {
51
+ if (entries.hasClientId) {
52
+ content = content.replace(/^MISO_CLIENTID\s*=.*$/m, `MISO_CLIENTID=kv://${clientIdKey}`);
53
+ }
54
+ if (entries.hasClientSecret) {
55
+ content = content.replace(/^MISO_CLIENTSECRET\s*=.*$/m, `MISO_CLIENTSECRET=kv://${clientSecretKey}`);
56
+ }
57
+ if (entries.hasControllerUrl) {
58
+ content = content.replace(/^MISO_CONTROLLER_URL\s*=.*$/m, 'MISO_CONTROLLER_URL=http://${MISO_HOST}:${MISO_PORT}');
59
+ }
60
+ return content;
61
+ }
62
+
63
+ /**
64
+ * Builds missing MISO entries
65
+ * @function buildMissingMisoEntries
66
+ * @param {string} clientIdKey - Client ID key
67
+ * @param {string} clientSecretKey - Client secret key
68
+ * @param {Object} entries - Entry flags
69
+ * @returns {string[]} Array of missing entries
70
+ */
71
+ function buildMissingMisoEntries(clientIdKey, clientSecretKey, entries) {
72
+ const missingEntries = [];
73
+ if (!entries.hasClientId) {
74
+ missingEntries.push(`MISO_CLIENTID=kv://${clientIdKey}`);
75
+ }
76
+ if (!entries.hasClientSecret) {
77
+ missingEntries.push(`MISO_CLIENTSECRET=kv://${clientSecretKey}`);
78
+ }
79
+ if (!entries.hasControllerUrl) {
80
+ missingEntries.push('MISO_CONTROLLER_URL=http://${MISO_HOST}:${MISO_PORT}');
81
+ }
82
+ return missingEntries;
83
+ }
84
+
85
+ /**
86
+ * Inserts MISO section into content
87
+ * @function insertMisoSection
88
+ * @param {string} content - File content
89
+ * @param {string[]} missingEntries - Missing entries to add
90
+ * @returns {string} Updated content
91
+ */
92
+ function insertMisoSection(content, missingEntries) {
93
+ const misoSection = `# MISO Application Client Credentials (per application)
94
+ ${missingEntries.join('\n')}
95
+ `;
96
+
97
+ const lastSectionMatch = content.match(/# =+.*$/gm);
98
+ if (lastSectionMatch && lastSectionMatch.length > 0) {
99
+ const lastSectionIndex = content.lastIndexOf(lastSectionMatch[lastSectionMatch.length - 1]);
100
+ const insertIndex = content.indexOf('\n', lastSectionIndex) + 1;
101
+ return content.slice(0, insertIndex) + '\n' + misoSection + content.slice(insertIndex);
102
+ }
103
+ return content + '\n' + misoSection;
104
+ }
105
+
27
106
  async function updateEnvTemplate(appKey, clientIdKey, clientSecretKey, _controllerUrl) {
28
107
  const envTemplatePath = path.join(process.cwd(), 'builder', appKey, 'env.template');
29
108
 
@@ -35,50 +114,13 @@ async function updateEnvTemplate(appKey, clientIdKey, clientSecretKey, _controll
35
114
  try {
36
115
  let content = await fs.readFile(envTemplatePath, 'utf8');
37
116
 
38
- // Check if entries already exist
39
- const hasClientId = /^MISO_CLIENTID\s*=/m.test(content);
40
- const hasClientSecret = /^MISO_CLIENTSECRET\s*=/m.test(content);
41
- const hasControllerUrl = /^MISO_CONTROLLER_URL\s*=/m.test(content);
42
-
43
- // Update existing entries
44
- if (hasClientId) {
45
- content = content.replace(/^MISO_CLIENTID\s*=.*$/m, `MISO_CLIENTID=kv://${clientIdKey}`);
46
- }
47
-
48
- if (hasClientSecret) {
49
- content = content.replace(/^MISO_CLIENTSECRET\s*=.*$/m, `MISO_CLIENTSECRET=kv://${clientSecretKey}`);
50
- }
51
-
52
- if (hasControllerUrl) {
53
- content = content.replace(/^MISO_CONTROLLER_URL\s*=.*$/m, 'MISO_CONTROLLER_URL=http://${MISO_HOST}:${MISO_PORT}');
54
- }
117
+ const entries = checkMisoEntries(content);
118
+ content = updateExistingMisoEntries(content, clientIdKey, clientSecretKey, entries);
55
119
 
56
120
  // Add missing entries
57
- if (!hasClientId || !hasClientSecret || !hasControllerUrl) {
58
- const missingEntries = [];
59
- if (!hasClientId) {
60
- missingEntries.push(`MISO_CLIENTID=kv://${clientIdKey}`);
61
- }
62
- if (!hasClientSecret) {
63
- missingEntries.push(`MISO_CLIENTSECRET=kv://${clientSecretKey}`);
64
- }
65
- if (!hasControllerUrl) {
66
- missingEntries.push('MISO_CONTROLLER_URL=http://${MISO_HOST}:${MISO_PORT}');
67
- }
68
-
69
- const misoSection = `# MISO Application Client Credentials (per application)
70
- ${missingEntries.join('\n')}
71
- `;
72
-
73
- // Try to find a good place to insert (after last section or at end)
74
- const lastSectionMatch = content.match(/# =+.*$/gm);
75
- if (lastSectionMatch && lastSectionMatch.length > 0) {
76
- const lastSectionIndex = content.lastIndexOf(lastSectionMatch[lastSectionMatch.length - 1]);
77
- const insertIndex = content.indexOf('\n', lastSectionIndex) + 1;
78
- content = content.slice(0, insertIndex) + '\n' + misoSection + content.slice(insertIndex);
79
- } else {
80
- content = content + '\n' + misoSection;
81
- }
121
+ if (!entries.hasClientId || !entries.hasClientSecret || !entries.hasControllerUrl) {
122
+ const missingEntries = buildMissingMisoEntries(clientIdKey, clientSecretKey, entries);
123
+ content = insertMisoSection(content, missingEntries);
82
124
  }
83
125
 
84
126
  await fs.writeFile(envTemplatePath, content, 'utf8');
@@ -17,21 +17,38 @@
17
17
  * @returns {string} Formatted error message
18
18
  */
19
19
  function formatSingleError(error) {
20
- const path = error.instancePath ? error.instancePath.slice(1) : 'root';
20
+ // Handle empty or missing instancePath - use 'Configuration' for root level errors
21
+ const instancePath = error.instancePath || '';
22
+ const path = instancePath ? instancePath.slice(1) : '';
21
23
  const field = path ? `Field "${path}"` : 'Configuration';
22
24
 
25
+ // Check if params exists before accessing it
23
26
  const errorMessages = {
24
- required: `${field}: Missing required property "${error.params.missingProperty}"`,
25
- type: `${field}: Expected ${error.params.type}, got ${typeof error.data}`,
26
- minimum: `${field}: Value must be at least ${error.params.limit}`,
27
- maximum: `${field}: Value must be at most ${error.params.limit}`,
28
- minLength: `${field}: Must be at least ${error.params.limit} characters`,
29
- maxLength: `${field}: Must be at most ${error.params.limit} characters`,
27
+ required: error.params?.missingProperty
28
+ ? `${field}: Missing required property "${error.params.missingProperty}"`
29
+ : `${field}: Missing required property`,
30
+ type: error.params?.type
31
+ ? `${field}: Expected ${error.params.type}, got ${typeof error.data}`
32
+ : `${field}: Type error`,
33
+ minimum: error.params?.limit
34
+ ? `${field}: Value must be at least ${error.params.limit}`
35
+ : `${field}: Value below minimum`,
36
+ maximum: error.params?.limit
37
+ ? `${field}: Value must be at most ${error.params.limit}`
38
+ : `${field}: Value above maximum`,
39
+ minLength: error.params?.limit
40
+ ? `${field}: Must be at least ${error.params.limit} characters`
41
+ : `${field}: Too short`,
42
+ maxLength: error.params?.limit
43
+ ? `${field}: Must be at most ${error.params.limit} characters`
44
+ : `${field}: Too long`,
30
45
  pattern: `${field}: Invalid format`,
31
- enum: `${field}: Must be one of: ${error.params.allowedValues?.join(', ') || 'unknown'}`
46
+ enum: error.params?.allowedValues && error.params.allowedValues.length > 0
47
+ ? `${field}: Must be one of: ${error.params.allowedValues.join(', ')}`
48
+ : `${field}: Must be one of: unknown`
32
49
  };
33
50
 
34
- return errorMessages[error.keyword] || `${field}: ${error.message}`;
51
+ return errorMessages[error.keyword] || `${field}: ${error.message || 'Validation error'}`;
35
52
  }
36
53
 
37
54
  /**