@aifabrix/builder 2.22.2 → 2.31.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.
- package/jest.config.coverage.js +37 -0
- package/lib/api/pipeline.api.js +10 -9
- package/lib/app-deploy.js +36 -14
- package/lib/app-list.js +191 -71
- package/lib/app-prompts.js +77 -26
- package/lib/app-readme.js +123 -5
- package/lib/app-rotate-secret.js +210 -80
- package/lib/app-run-helpers.js +200 -172
- package/lib/app-run.js +137 -68
- package/lib/audit-logger.js +8 -7
- package/lib/build.js +161 -250
- package/lib/cli.js +73 -65
- package/lib/commands/login.js +45 -31
- package/lib/commands/logout.js +181 -0
- package/lib/commands/secure.js +59 -24
- package/lib/config.js +79 -45
- package/lib/datasource-deploy.js +89 -29
- package/lib/deployer.js +164 -129
- package/lib/diff.js +63 -21
- package/lib/environment-deploy.js +36 -19
- package/lib/external-system-deploy.js +134 -66
- package/lib/external-system-download.js +244 -171
- package/lib/external-system-test.js +199 -164
- package/lib/generator-external.js +145 -72
- package/lib/generator-helpers.js +49 -17
- package/lib/generator-split.js +105 -58
- package/lib/infra.js +101 -131
- package/lib/schema/application-schema.json +895 -896
- package/lib/schema/env-config.yaml +11 -4
- package/lib/template-validator.js +13 -4
- package/lib/utils/api.js +8 -8
- package/lib/utils/app-register-auth.js +36 -18
- package/lib/utils/app-run-containers.js +140 -0
- package/lib/utils/auth-headers.js +6 -6
- package/lib/utils/build-copy.js +60 -2
- package/lib/utils/build-helpers.js +94 -0
- package/lib/utils/cli-utils.js +177 -76
- package/lib/utils/compose-generator.js +12 -2
- package/lib/utils/config-tokens.js +151 -9
- package/lib/utils/deployment-errors.js +137 -69
- package/lib/utils/deployment-validation-helpers.js +103 -0
- package/lib/utils/docker-build.js +57 -0
- package/lib/utils/dockerfile-utils.js +13 -3
- package/lib/utils/env-copy.js +163 -94
- package/lib/utils/env-map.js +226 -86
- package/lib/utils/error-formatters/network-errors.js +0 -1
- package/lib/utils/external-system-display.js +14 -19
- package/lib/utils/external-system-env-helpers.js +107 -0
- package/lib/utils/external-system-test-helpers.js +144 -0
- package/lib/utils/health-check.js +10 -8
- package/lib/utils/infra-status.js +123 -0
- package/lib/utils/paths.js +228 -49
- package/lib/utils/schema-loader.js +125 -57
- package/lib/utils/token-manager.js +3 -3
- package/lib/utils/yaml-preserve.js +55 -16
- package/lib/validate.js +87 -89
- package/package.json +7 -5
- package/scripts/ci-fix.sh +19 -0
- package/scripts/ci-simulate.sh +19 -0
- package/scripts/install-local.js +210 -0
- package/templates/applications/miso-controller/test.yaml +1 -0
- package/templates/python/Dockerfile.hbs +8 -45
- package/templates/typescript/Dockerfile.hbs +8 -42
package/lib/utils/paths.js
CHANGED
|
@@ -57,6 +57,153 @@ function getAifabrixHome() {
|
|
|
57
57
|
return path.join(safeHomedir(), '.aifabrix');
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
// Cache project root to avoid repeated filesystem lookups
|
|
61
|
+
let cachedProjectRoot = null;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Clears the cached project root
|
|
65
|
+
* Useful in tests when global.PROJECT_ROOT changes
|
|
66
|
+
*/
|
|
67
|
+
function clearProjectRootCache() {
|
|
68
|
+
cachedProjectRoot = null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Checks if a directory contains package.json
|
|
73
|
+
* @param {string} dirPath - Directory path to check
|
|
74
|
+
* @returns {boolean} True if package.json exists
|
|
75
|
+
*/
|
|
76
|
+
function hasPackageJson(dirPath) {
|
|
77
|
+
const packageJsonPath = path.join(dirPath, 'package.json');
|
|
78
|
+
return fs.existsSync(packageJsonPath);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Strategy 1: Walk up from a starting directory to find package.json
|
|
83
|
+
* @param {string} startDir - Starting directory
|
|
84
|
+
* @param {number} maxDepth - Maximum depth to search
|
|
85
|
+
* @returns {string|null} Project root path or null if not found
|
|
86
|
+
*/
|
|
87
|
+
function findProjectRootByWalkingUp(startDir, maxDepth = 10) {
|
|
88
|
+
let currentDir = startDir;
|
|
89
|
+
for (let i = 0; i < maxDepth; i++) {
|
|
90
|
+
if (hasPackageJson(currentDir)) {
|
|
91
|
+
return currentDir;
|
|
92
|
+
}
|
|
93
|
+
const parentDir = path.dirname(currentDir);
|
|
94
|
+
if (parentDir === currentDir) {
|
|
95
|
+
// Reached filesystem root
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
currentDir = parentDir;
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Strategy 2: Try from process.cwd()
|
|
105
|
+
* @returns {string|null} Project root path or null if not found
|
|
106
|
+
*/
|
|
107
|
+
function findProjectRootFromCwd() {
|
|
108
|
+
try {
|
|
109
|
+
const cwd = process.cwd();
|
|
110
|
+
if (cwd && cwd !== '/' && hasPackageJson(cwd)) {
|
|
111
|
+
return cwd;
|
|
112
|
+
}
|
|
113
|
+
} catch {
|
|
114
|
+
// Ignore errors
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Strategy 3: Check if Jest rootDir is available
|
|
121
|
+
* @returns {string|null} Project root path or null if not found
|
|
122
|
+
*/
|
|
123
|
+
// eslint-disable-next-line no-unused-vars
|
|
124
|
+
function _findProjectRootFromJest() {
|
|
125
|
+
if (typeof jest !== 'undefined' && jest.config && jest.config.rootDir) {
|
|
126
|
+
const jestRoot = jest.config.rootDir;
|
|
127
|
+
if (hasPackageJson(jestRoot)) {
|
|
128
|
+
return jestRoot;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Strategy 4: Fallback to __dirname relative path
|
|
136
|
+
* @returns {string} Fallback project root path
|
|
137
|
+
*/
|
|
138
|
+
function getFallbackProjectRoot() {
|
|
139
|
+
return path.resolve(__dirname, '..', '..');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Gets the project root directory by finding package.json
|
|
144
|
+
* Works reliably in all environments including Jest tests and CI
|
|
145
|
+
* @returns {string} Absolute path to project root
|
|
146
|
+
*/
|
|
147
|
+
function getProjectRoot() {
|
|
148
|
+
// SIMPLIFIED: Always use __dirname walking - most reliable in all environments
|
|
149
|
+
// This works in: local dev, CI, tests, temp directories, etc.
|
|
150
|
+
// __dirname is lib/utils/, so we walk up to find package.json
|
|
151
|
+
|
|
152
|
+
// Return cached value if available and valid
|
|
153
|
+
if (cachedProjectRoot && hasPackageJson(cachedProjectRoot)) {
|
|
154
|
+
return cachedProjectRoot;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Strategy 1: Check global.PROJECT_ROOT if set and valid (for test isolation)
|
|
158
|
+
// BUT: In CI simulation, the project is copied, so global.PROJECT_ROOT might point to original
|
|
159
|
+
// We need to verify it's actually the correct root by checking if __dirname is within it
|
|
160
|
+
if (typeof global !== 'undefined' && global.PROJECT_ROOT) {
|
|
161
|
+
const globalRoot = global.PROJECT_ROOT;
|
|
162
|
+
if (hasPackageJson(globalRoot)) {
|
|
163
|
+
// Verify that __dirname is actually within globalRoot (or they're the same)
|
|
164
|
+
// This ensures we're using the correct project root in CI simulation
|
|
165
|
+
const dirnameNormalized = path.resolve(__dirname);
|
|
166
|
+
const globalRootNormalized = path.resolve(globalRoot);
|
|
167
|
+
const isWithinGlobalRoot = dirnameNormalized.startsWith(globalRootNormalized + path.sep) ||
|
|
168
|
+
dirnameNormalized === globalRootNormalized;
|
|
169
|
+
|
|
170
|
+
if (isWithinGlobalRoot) {
|
|
171
|
+
cachedProjectRoot = globalRoot;
|
|
172
|
+
return cachedProjectRoot;
|
|
173
|
+
}
|
|
174
|
+
// If global.PROJECT_ROOT doesn't contain __dirname, it's wrong (e.g., original project in CI)
|
|
175
|
+
// Clear it and continue with other strategies
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Strategy 2: Walk up from __dirname (lib/utils/) - MOST RELIABLE
|
|
180
|
+
// This always works because __dirname is always correct relative to the code
|
|
181
|
+
const foundRoot = findProjectRootByWalkingUp(__dirname);
|
|
182
|
+
if (foundRoot && hasPackageJson(foundRoot)) {
|
|
183
|
+
cachedProjectRoot = foundRoot;
|
|
184
|
+
return cachedProjectRoot;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Strategy 3: Try process.cwd() (works in most cases)
|
|
188
|
+
const cwdRoot = findProjectRootFromCwd();
|
|
189
|
+
if (cwdRoot && hasPackageJson(cwdRoot)) {
|
|
190
|
+
cachedProjectRoot = cwdRoot;
|
|
191
|
+
return cachedProjectRoot;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Strategy 4: Fallback to __dirname relative (lib/utils/ -> project root)
|
|
195
|
+
const fallbackRoot = getFallbackProjectRoot();
|
|
196
|
+
if (hasPackageJson(fallbackRoot)) {
|
|
197
|
+
cachedProjectRoot = fallbackRoot;
|
|
198
|
+
return cachedProjectRoot;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Last resort: return fallback even if package.json not found
|
|
202
|
+
// This prevents crashes but should rarely happen
|
|
203
|
+
cachedProjectRoot = fallbackRoot;
|
|
204
|
+
return cachedProjectRoot;
|
|
205
|
+
}
|
|
206
|
+
|
|
60
207
|
/**
|
|
61
208
|
* Returns the applications base directory for a developer.
|
|
62
209
|
* Dev 0: <home>/applications
|
|
@@ -169,65 +316,73 @@ function getDeployJsonPath(appName, appType, preferNew = false) {
|
|
|
169
316
|
}
|
|
170
317
|
|
|
171
318
|
/**
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
* @param {string} appName - Application name
|
|
176
|
-
* @returns {Promise<{isExternal: boolean, appPath: string, appType: string}>}
|
|
319
|
+
* Reads and parses variables.yaml file
|
|
320
|
+
* @param {string} variablesPath - Path to variables.yaml file
|
|
321
|
+
* @returns {Object|null} Parsed variables object or null if error
|
|
177
322
|
*/
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
323
|
+
function readVariablesFile(variablesPath) {
|
|
324
|
+
try {
|
|
325
|
+
if (!fs.existsSync(variablesPath)) {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
const content = fs.readFileSync(variablesPath, 'utf8');
|
|
329
|
+
return yaml.load(content);
|
|
330
|
+
} catch {
|
|
331
|
+
return null;
|
|
181
332
|
}
|
|
333
|
+
}
|
|
182
334
|
|
|
183
|
-
|
|
335
|
+
/**
|
|
336
|
+
* Checks if app type is external from variables object
|
|
337
|
+
* @param {Object} variables - Parsed variables.yaml object
|
|
338
|
+
* @returns {boolean} True if app type is external
|
|
339
|
+
*/
|
|
340
|
+
function isExternalAppType(variables) {
|
|
341
|
+
return variables && variables.app && variables.app.type === 'external';
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Checks integration folder for external app type
|
|
346
|
+
* @param {string} appName - Application name
|
|
347
|
+
* @returns {Object|null} App type info or null if not found
|
|
348
|
+
*/
|
|
349
|
+
function checkIntegrationFolder(appName) {
|
|
184
350
|
const integrationPath = getIntegrationPath(appName);
|
|
185
|
-
const
|
|
351
|
+
const variablesPath = path.join(integrationPath, 'variables.yaml');
|
|
352
|
+
const variables = readVariablesFile(variablesPath);
|
|
186
353
|
|
|
187
|
-
if (
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
appPath: integrationPath,
|
|
195
|
-
appType: 'external',
|
|
196
|
-
baseDir: 'integration'
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
} catch {
|
|
200
|
-
// Ignore errors, continue to check builder folder
|
|
201
|
-
}
|
|
354
|
+
if (variables && isExternalAppType(variables)) {
|
|
355
|
+
return {
|
|
356
|
+
isExternal: true,
|
|
357
|
+
appPath: integrationPath,
|
|
358
|
+
appType: 'external',
|
|
359
|
+
baseDir: 'integration'
|
|
360
|
+
};
|
|
202
361
|
}
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
203
364
|
|
|
204
|
-
|
|
365
|
+
/**
|
|
366
|
+
* Checks builder folder for app type
|
|
367
|
+
* @param {string} appName - Application name
|
|
368
|
+
* @returns {Object} App type info
|
|
369
|
+
*/
|
|
370
|
+
function checkBuilderFolder(appName) {
|
|
205
371
|
const builderPath = getBuilderPath(appName);
|
|
206
|
-
const
|
|
372
|
+
const variablesPath = path.join(builderPath, 'variables.yaml');
|
|
373
|
+
const variables = readVariablesFile(variablesPath);
|
|
207
374
|
|
|
208
|
-
if (
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
appType: isExternal ? 'external' : 'regular',
|
|
217
|
-
baseDir: 'builder'
|
|
218
|
-
};
|
|
219
|
-
} catch {
|
|
220
|
-
// If we can't read it, assume regular app in builder folder
|
|
221
|
-
return {
|
|
222
|
-
isExternal: false,
|
|
223
|
-
appPath: builderPath,
|
|
224
|
-
appType: 'regular',
|
|
225
|
-
baseDir: 'builder'
|
|
226
|
-
};
|
|
227
|
-
}
|
|
375
|
+
if (variables) {
|
|
376
|
+
const isExternal = isExternalAppType(variables);
|
|
377
|
+
return {
|
|
378
|
+
isExternal,
|
|
379
|
+
appPath: builderPath,
|
|
380
|
+
appType: isExternal ? 'external' : 'regular',
|
|
381
|
+
baseDir: 'builder'
|
|
382
|
+
};
|
|
228
383
|
}
|
|
229
384
|
|
|
230
|
-
// Default to
|
|
385
|
+
// Default to regular app in builder folder
|
|
231
386
|
return {
|
|
232
387
|
isExternal: false,
|
|
233
388
|
appPath: builderPath,
|
|
@@ -236,14 +391,38 @@ async function detectAppType(appName) {
|
|
|
236
391
|
};
|
|
237
392
|
}
|
|
238
393
|
|
|
394
|
+
/**
|
|
395
|
+
* Detects if an app is external type by checking variables.yaml
|
|
396
|
+
* Checks both integration/ and builder/ folders for backward compatibility
|
|
397
|
+
*
|
|
398
|
+
* @param {string} appName - Application name
|
|
399
|
+
* @returns {Promise<{isExternal: boolean, appPath: string, appType: string}>}
|
|
400
|
+
*/
|
|
401
|
+
async function detectAppType(appName) {
|
|
402
|
+
if (!appName || typeof appName !== 'string') {
|
|
403
|
+
throw new Error('App name is required and must be a string');
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Check integration folder first (new structure)
|
|
407
|
+
const integrationResult = checkIntegrationFolder(appName);
|
|
408
|
+
if (integrationResult) {
|
|
409
|
+
return integrationResult;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Check builder folder (backward compatibility)
|
|
413
|
+
return checkBuilderFolder(appName);
|
|
414
|
+
}
|
|
415
|
+
|
|
239
416
|
module.exports = {
|
|
240
417
|
getAifabrixHome,
|
|
241
418
|
getApplicationsBaseDir,
|
|
242
419
|
getDevDirectory,
|
|
243
420
|
getAppPath,
|
|
421
|
+
getProjectRoot,
|
|
244
422
|
getIntegrationPath,
|
|
245
423
|
getBuilderPath,
|
|
246
424
|
getDeployJsonPath,
|
|
247
|
-
detectAppType
|
|
425
|
+
detectAppType,
|
|
426
|
+
clearProjectRootCache
|
|
248
427
|
};
|
|
249
428
|
|
|
@@ -111,6 +111,118 @@ function loadExternalDataSourceSchema() {
|
|
|
111
111
|
return externalDataSourceValidator;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Detect schema type from $id field
|
|
116
|
+
* @param {Object} parsed - Parsed JSON object
|
|
117
|
+
* @returns {string|null} Schema type or null if not detected
|
|
118
|
+
*/
|
|
119
|
+
function detectFromId(parsed) {
|
|
120
|
+
if (!parsed.$id) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (parsed.$id.includes('external-system')) {
|
|
125
|
+
return 'external-system';
|
|
126
|
+
}
|
|
127
|
+
if (parsed.$id.includes('external-datasource')) {
|
|
128
|
+
return 'external-datasource';
|
|
129
|
+
}
|
|
130
|
+
if (parsed.$id.includes('application-schema')) {
|
|
131
|
+
return 'application';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Detect schema type from title field
|
|
139
|
+
* @param {Object} parsed - Parsed JSON object
|
|
140
|
+
* @returns {string|null} Schema type or null if not detected
|
|
141
|
+
*/
|
|
142
|
+
function detectFromTitle(parsed) {
|
|
143
|
+
if (!parsed.title) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const titleLower = parsed.title.toLowerCase();
|
|
148
|
+
if (titleLower.includes('external system') || titleLower.includes('external-system') || titleLower.includes('external system configuration')) {
|
|
149
|
+
return 'external-system';
|
|
150
|
+
}
|
|
151
|
+
if (titleLower.includes('external data source') || titleLower.includes('external datasource') || titleLower.includes('external-datasource')) {
|
|
152
|
+
return 'external-datasource';
|
|
153
|
+
}
|
|
154
|
+
if (titleLower.includes('application')) {
|
|
155
|
+
return 'application';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Detect schema type from required fields
|
|
163
|
+
* @param {Object} parsed - Parsed JSON object
|
|
164
|
+
* @returns {string|null} Schema type or null if not detected
|
|
165
|
+
*/
|
|
166
|
+
function detectFromRequiredFields(parsed) {
|
|
167
|
+
if (!parsed.key || !parsed.displayName || !parsed.type || !parsed.authentication) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Check if it has systemKey (datasource) or not (system)
|
|
172
|
+
if (parsed.systemKey) {
|
|
173
|
+
return 'external-datasource';
|
|
174
|
+
}
|
|
175
|
+
// Check if type is one of external-system types
|
|
176
|
+
if (['openapi', 'mcp', 'custom'].includes(parsed.type)) {
|
|
177
|
+
return 'external-system';
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Detect schema type from datasource-specific fields
|
|
185
|
+
* @param {Object} parsed - Parsed JSON object
|
|
186
|
+
* @returns {string|null} Schema type or null if not detected
|
|
187
|
+
*/
|
|
188
|
+
function detectFromDatasourceFields(parsed) {
|
|
189
|
+
if (parsed.systemKey && parsed.entityKey && parsed.fieldMappings) {
|
|
190
|
+
return 'external-datasource';
|
|
191
|
+
}
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Detect schema type from application-specific fields
|
|
197
|
+
* @param {Object} parsed - Parsed JSON object
|
|
198
|
+
* @returns {string|null} Schema type or null if not detected
|
|
199
|
+
*/
|
|
200
|
+
function detectFromApplicationFields(parsed) {
|
|
201
|
+
if (parsed.deploymentKey || (parsed.image && parsed.registryMode && parsed.port)) {
|
|
202
|
+
return 'application';
|
|
203
|
+
}
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Detect schema type from filename pattern
|
|
209
|
+
* @param {string} filePath - File path
|
|
210
|
+
* @returns {string|null} Schema type or null if not detected
|
|
211
|
+
*/
|
|
212
|
+
function detectFromFilename(filePath) {
|
|
213
|
+
const fileName = path.basename(filePath).toLowerCase();
|
|
214
|
+
if (fileName.includes('external-system') || fileName.includes('external_system')) {
|
|
215
|
+
return 'external-system';
|
|
216
|
+
}
|
|
217
|
+
if (fileName.includes('external-datasource') || fileName.includes('external_datasource') || fileName.includes('datasource')) {
|
|
218
|
+
return 'external-datasource';
|
|
219
|
+
}
|
|
220
|
+
if (fileName.includes('application') || fileName.includes('variables') || fileName.includes('deploy')) {
|
|
221
|
+
return 'application';
|
|
222
|
+
}
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
|
|
114
226
|
/**
|
|
115
227
|
* Detects schema type from file content or path
|
|
116
228
|
* Attempts to identify if file is application, external-system, or external-datasource
|
|
@@ -144,68 +256,24 @@ function detectSchemaType(filePath, content) {
|
|
|
144
256
|
throw new Error(`Invalid JSON in file: ${error.message}`);
|
|
145
257
|
}
|
|
146
258
|
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
if (
|
|
150
|
-
if (parsed.$id.includes('external-system')) {
|
|
151
|
-
return 'external-system';
|
|
152
|
-
}
|
|
153
|
-
if (parsed.$id.includes('external-datasource')) {
|
|
154
|
-
return 'external-datasource';
|
|
155
|
-
}
|
|
156
|
-
if (parsed.$id.includes('application-schema')) {
|
|
157
|
-
return 'application';
|
|
158
|
-
}
|
|
159
|
-
}
|
|
259
|
+
// Try different detection methods in order
|
|
260
|
+
const idType = detectFromId(parsed);
|
|
261
|
+
if (idType) return idType;
|
|
160
262
|
|
|
161
|
-
|
|
162
|
-
if (
|
|
163
|
-
const titleLower = parsed.title.toLowerCase();
|
|
164
|
-
if (titleLower.includes('external system') || titleLower.includes('external-system') || titleLower.includes('external system configuration')) {
|
|
165
|
-
return 'external-system';
|
|
166
|
-
}
|
|
167
|
-
if (titleLower.includes('external data source') || titleLower.includes('external datasource') || titleLower.includes('external-datasource')) {
|
|
168
|
-
return 'external-datasource';
|
|
169
|
-
}
|
|
170
|
-
if (titleLower.includes('application')) {
|
|
171
|
-
return 'application';
|
|
172
|
-
}
|
|
173
|
-
}
|
|
263
|
+
const titleType = detectFromTitle(parsed);
|
|
264
|
+
if (titleType) return titleType;
|
|
174
265
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if (parsed.key && parsed.displayName && parsed.type && parsed.authentication) {
|
|
178
|
-
// Check if it has systemKey (datasource) or not (system)
|
|
179
|
-
if (parsed.systemKey) {
|
|
180
|
-
return 'external-datasource';
|
|
181
|
-
}
|
|
182
|
-
// Check if type is one of external-system types
|
|
183
|
-
if (['openapi', 'mcp', 'custom'].includes(parsed.type)) {
|
|
184
|
-
return 'external-system';
|
|
185
|
-
}
|
|
186
|
-
}
|
|
266
|
+
const requiredFieldsType = detectFromRequiredFields(parsed);
|
|
267
|
+
if (requiredFieldsType) return requiredFieldsType;
|
|
187
268
|
|
|
188
|
-
|
|
189
|
-
if (
|
|
190
|
-
return 'external-datasource';
|
|
191
|
-
}
|
|
269
|
+
const datasourceType = detectFromDatasourceFields(parsed);
|
|
270
|
+
if (datasourceType) return datasourceType;
|
|
192
271
|
|
|
193
|
-
|
|
194
|
-
if (
|
|
195
|
-
return 'application';
|
|
196
|
-
}
|
|
272
|
+
const applicationType = detectFromApplicationFields(parsed);
|
|
273
|
+
if (applicationType) return applicationType;
|
|
197
274
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
if (fileName.includes('external-system') || fileName.includes('external_system')) {
|
|
201
|
-
return 'external-system';
|
|
202
|
-
}
|
|
203
|
-
if (fileName.includes('external-datasource') || fileName.includes('external_datasource') || fileName.includes('datasource')) {
|
|
204
|
-
return 'external-datasource';
|
|
205
|
-
}
|
|
206
|
-
if (fileName.includes('application') || fileName.includes('variables') || fileName.includes('deploy')) {
|
|
207
|
-
return 'application';
|
|
208
|
-
}
|
|
275
|
+
const filenameType = detectFromFilename(filePath);
|
|
276
|
+
if (filenameType) return filenameType;
|
|
209
277
|
|
|
210
278
|
// Default to application if cannot determine
|
|
211
279
|
return 'application';
|
|
@@ -301,7 +301,7 @@ async function getOrRefreshDeviceToken(controllerUrl) {
|
|
|
301
301
|
* @param {string} controllerUrl - Controller URL
|
|
302
302
|
* @param {string} environment - Environment key
|
|
303
303
|
* @param {string} appName - Application name
|
|
304
|
-
* @returns {Promise<{type: 'bearer'|'credentials', token?: string, clientId?: string, clientSecret?: string, controller: string}>} Auth configuration
|
|
304
|
+
* @returns {Promise<{type: 'bearer'|'client-credentials', token?: string, clientId?: string, clientSecret?: string, controller: string}>} Auth configuration
|
|
305
305
|
* @throws {Error} If no authentication method is available
|
|
306
306
|
*/
|
|
307
307
|
async function getDeploymentAuth(controllerUrl, environment, appName) {
|
|
@@ -344,7 +344,7 @@ async function getDeploymentAuth(controllerUrl, environment, appName) {
|
|
|
344
344
|
const credentials = await loadClientCredentials(appName);
|
|
345
345
|
if (credentials && credentials.clientId && credentials.clientSecret) {
|
|
346
346
|
return {
|
|
347
|
-
type: 'credentials',
|
|
347
|
+
type: 'client-credentials',
|
|
348
348
|
clientId: credentials.clientId,
|
|
349
349
|
clientSecret: credentials.clientSecret,
|
|
350
350
|
controller: controllerUrl
|
|
@@ -366,7 +366,7 @@ async function getDeploymentAuth(controllerUrl, environment, appName) {
|
|
|
366
366
|
* @throws {Error} If credentials cannot be obtained
|
|
367
367
|
*/
|
|
368
368
|
async function extractClientCredentials(authConfig, appKey, envKey, _options = {}) {
|
|
369
|
-
if (authConfig.type === 'credentials') {
|
|
369
|
+
if (authConfig.type === 'client-credentials') {
|
|
370
370
|
if (!authConfig.clientId || !authConfig.clientSecret) {
|
|
371
371
|
throw new Error('Client ID and Client Secret are required');
|
|
372
372
|
}
|
|
@@ -11,6 +11,57 @@
|
|
|
11
11
|
|
|
12
12
|
const { encryptSecret, isEncrypted } = require('./secrets-encryption');
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Check if value is null
|
|
16
|
+
* @param {string} trimmed - Trimmed value
|
|
17
|
+
* @returns {boolean} True if null
|
|
18
|
+
*/
|
|
19
|
+
function isNullValue(trimmed) {
|
|
20
|
+
return trimmed === 'null' || trimmed === '~' || trimmed === '';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Check if value is boolean
|
|
25
|
+
* @param {string} trimmed - Trimmed value
|
|
26
|
+
* @returns {boolean} True if boolean
|
|
27
|
+
*/
|
|
28
|
+
function isBooleanValue(trimmed) {
|
|
29
|
+
const booleanValues = [
|
|
30
|
+
'true', 'false', 'True', 'False', 'TRUE', 'FALSE',
|
|
31
|
+
'yes', 'no', 'Yes', 'No', 'YES', 'NO',
|
|
32
|
+
'on', 'off', 'On', 'Off', 'ON', 'OFF'
|
|
33
|
+
];
|
|
34
|
+
return booleanValues.includes(trimmed);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Check if value is number
|
|
39
|
+
* @param {string} trimmed - Trimmed value
|
|
40
|
+
* @returns {boolean} True if number
|
|
41
|
+
*/
|
|
42
|
+
function isNumberValue(trimmed) {
|
|
43
|
+
return /^[+-]?\d+$/.test(trimmed) ||
|
|
44
|
+
/^[+-]?\d*\.\d+([eE][+-]?\d+)?$/.test(trimmed) ||
|
|
45
|
+
/^[+-]?\.\d+([eE][+-]?\d+)?$/.test(trimmed) ||
|
|
46
|
+
/^0x[0-9a-fA-F]+$/.test(trimmed) ||
|
|
47
|
+
/^0o[0-7]+$/.test(trimmed) ||
|
|
48
|
+
/^0b[01]+$/.test(trimmed);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Check if value is YAML special value
|
|
53
|
+
* @param {string} trimmed - Trimmed value
|
|
54
|
+
* @returns {boolean} True if YAML special value
|
|
55
|
+
*/
|
|
56
|
+
function isYamlSpecialValue(trimmed) {
|
|
57
|
+
const specialValues = [
|
|
58
|
+
'.inf', '.Inf', '.INF',
|
|
59
|
+
'-.inf', '-.Inf', '-.INF',
|
|
60
|
+
'.nan', '.NaN', '.NAN'
|
|
61
|
+
];
|
|
62
|
+
return specialValues.includes(trimmed);
|
|
63
|
+
}
|
|
64
|
+
|
|
14
65
|
/**
|
|
15
66
|
* Checks if a string value represents a YAML primitive (number, boolean, null)
|
|
16
67
|
* When parsing line-by-line, these appear as strings but should not be encrypted
|
|
@@ -22,31 +73,19 @@ const { encryptSecret, isEncrypted } = require('./secrets-encryption');
|
|
|
22
73
|
function isYamlPrimitive(value) {
|
|
23
74
|
const trimmed = value.trim();
|
|
24
75
|
|
|
25
|
-
|
|
26
|
-
if (trimmed === 'null' || trimmed === '~' || trimmed === '') {
|
|
76
|
+
if (isNullValue(trimmed)) {
|
|
27
77
|
return true;
|
|
28
78
|
}
|
|
29
79
|
|
|
30
|
-
|
|
31
|
-
if (trimmed === 'true' || trimmed === 'false' || trimmed === 'True' || trimmed === 'False' ||
|
|
32
|
-
trimmed === 'TRUE' || trimmed === 'FALSE' || trimmed === 'yes' || trimmed === 'no' ||
|
|
33
|
-
trimmed === 'Yes' || trimmed === 'No' || trimmed === 'YES' || trimmed === 'NO' ||
|
|
34
|
-
trimmed === 'on' || trimmed === 'off' || trimmed === 'On' || trimmed === 'Off' ||
|
|
35
|
-
trimmed === 'ON' || trimmed === 'OFF') {
|
|
80
|
+
if (isBooleanValue(trimmed)) {
|
|
36
81
|
return true;
|
|
37
82
|
}
|
|
38
83
|
|
|
39
|
-
|
|
40
|
-
if (/^[+-]?\d+$/.test(trimmed) || /^[+-]?\d*\.\d+([eE][+-]?\d+)?$/.test(trimmed) ||
|
|
41
|
-
/^[+-]?\.\d+([eE][+-]?\d+)?$/.test(trimmed) || /^0x[0-9a-fA-F]+$/.test(trimmed) ||
|
|
42
|
-
/^0o[0-7]+$/.test(trimmed) || /^0b[01]+$/.test(trimmed)) {
|
|
84
|
+
if (isNumberValue(trimmed)) {
|
|
43
85
|
return true;
|
|
44
86
|
}
|
|
45
87
|
|
|
46
|
-
|
|
47
|
-
if (trimmed === '.inf' || trimmed === '.Inf' || trimmed === '.INF' ||
|
|
48
|
-
trimmed === '-.inf' || trimmed === '-.Inf' || trimmed === '-.INF' ||
|
|
49
|
-
trimmed === '.nan' || trimmed === '.NaN' || trimmed === '.NAN') {
|
|
88
|
+
if (isYamlSpecialValue(trimmed)) {
|
|
50
89
|
return true;
|
|
51
90
|
}
|
|
52
91
|
|