@aifabrix/builder 2.7.0 → 2.9.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.
- package/.cursor/rules/project-rules.mdc +680 -0
- package/integration/hubspot/README.md +136 -0
- package/integration/hubspot/env.template +9 -0
- package/integration/hubspot/hubspot-deploy-company.json +200 -0
- package/integration/hubspot/hubspot-deploy-contact.json +228 -0
- package/integration/hubspot/hubspot-deploy-deal.json +248 -0
- package/integration/hubspot/hubspot-deploy.json +91 -0
- package/integration/hubspot/variables.yaml +17 -0
- package/lib/app-config.js +13 -2
- package/lib/app-deploy.js +9 -3
- package/lib/app-dockerfile.js +14 -1
- package/lib/app-prompts.js +177 -13
- package/lib/app-push.js +16 -1
- package/lib/app-register.js +37 -5
- package/lib/app-rotate-secret.js +10 -0
- package/lib/app-run.js +19 -0
- package/lib/app.js +70 -25
- package/lib/audit-logger.js +9 -4
- package/lib/build.js +25 -13
- package/lib/cli.js +109 -2
- package/lib/commands/login.js +40 -3
- package/lib/config.js +121 -114
- package/lib/datasource-deploy.js +14 -20
- package/lib/environment-deploy.js +305 -0
- package/lib/external-system-deploy.js +345 -0
- package/lib/external-system-download.js +431 -0
- package/lib/external-system-generator.js +190 -0
- package/lib/external-system-test.js +446 -0
- package/lib/generator-builders.js +323 -0
- package/lib/generator.js +200 -292
- package/lib/schema/application-schema.json +830 -800
- package/lib/schema/external-datasource.schema.json +868 -46
- package/lib/schema/external-system.schema.json +98 -80
- package/lib/schema/infrastructure-schema.json +1 -1
- package/lib/templates.js +32 -1
- package/lib/utils/cli-utils.js +4 -4
- package/lib/utils/device-code.js +10 -2
- package/lib/utils/external-system-display.js +159 -0
- package/lib/utils/external-system-validators.js +245 -0
- package/lib/utils/paths.js +151 -1
- package/lib/utils/schema-resolver.js +7 -2
- package/lib/utils/token-encryption.js +68 -0
- package/lib/validator.js +52 -5
- package/package.json +1 -1
- package/tatus +181 -0
- package/templates/external-system/external-datasource.json.hbs +55 -0
- package/templates/external-system/external-system.json.hbs +37 -0
package/lib/generator.js
CHANGED
|
@@ -12,25 +12,12 @@
|
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const yaml = require('js-yaml');
|
|
15
|
+
const Ajv = require('ajv');
|
|
15
16
|
const _secrets = require('./secrets');
|
|
16
17
|
const _keyGenerator = require('./key-generator');
|
|
17
18
|
const _validator = require('./validator');
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
* Sanitizes authentication type - map keycloak to azure (schema allows: azure, local, none)
|
|
21
|
-
* @function sanitizeAuthType
|
|
22
|
-
* @param {string} authType - Authentication type
|
|
23
|
-
* @returns {string} Sanitized authentication type
|
|
24
|
-
*/
|
|
25
|
-
function sanitizeAuthType(authType) {
|
|
26
|
-
if (authType === 'keycloak') {
|
|
27
|
-
return 'azure';
|
|
28
|
-
}
|
|
29
|
-
if (authType && !['azure', 'local', 'none'].includes(authType)) {
|
|
30
|
-
return 'azure'; // Default to azure if invalid type
|
|
31
|
-
}
|
|
32
|
-
return authType;
|
|
33
|
-
}
|
|
19
|
+
const builders = require('./generator-builders');
|
|
20
|
+
const { detectAppType, getDeployJsonPath } = require('./utils/paths');
|
|
34
21
|
|
|
35
22
|
/**
|
|
36
23
|
* Loads variables.yaml file
|
|
@@ -84,229 +71,61 @@ function loadRbac(rbacPath) {
|
|
|
84
71
|
}
|
|
85
72
|
|
|
86
73
|
/**
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* @
|
|
90
|
-
* @
|
|
91
|
-
* @param {string}
|
|
92
|
-
* @
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (registryMode !== 'external') {
|
|
96
|
-
return configuration;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const allowedDockerRegistryVars = [
|
|
100
|
-
'DOCKER_REGISTRY_SERVER_URL',
|
|
101
|
-
'DOCKER_REGISTRY_SERVER_USERNAME',
|
|
102
|
-
'DOCKER_REGISTRY_SERVER_PASSWORD'
|
|
103
|
-
];
|
|
104
|
-
return configuration.filter(config => allowedDockerRegistryVars.includes(config.name));
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Builds base deployment structure
|
|
109
|
-
* @function buildBaseDeployment
|
|
110
|
-
* @param {string} appName - Application name
|
|
111
|
-
* @param {Object} variables - Variables configuration
|
|
112
|
-
* @param {Array} filteredConfiguration - Filtered environment configuration
|
|
113
|
-
* @returns {Object} Base deployment structure
|
|
114
|
-
*/
|
|
115
|
-
function buildBaseDeployment(appName, variables, filteredConfiguration) {
|
|
116
|
-
const requires = variables.requires || {};
|
|
117
|
-
return {
|
|
118
|
-
key: variables.app?.key || appName,
|
|
119
|
-
displayName: variables.app?.displayName || appName,
|
|
120
|
-
description: variables.app?.description || '',
|
|
121
|
-
type: variables.app?.type || 'webapp',
|
|
122
|
-
image: buildImageReference(variables),
|
|
123
|
-
registryMode: variables.image?.registryMode || 'external',
|
|
124
|
-
port: variables.port || 3000,
|
|
125
|
-
requiresDatabase: requires.database || false,
|
|
126
|
-
requiresRedis: requires.redis || false,
|
|
127
|
-
requiresStorage: requires.storage || false,
|
|
128
|
-
databases: requires.databases || (requires.database ? [{ name: variables.app?.key || 'app' }] : []),
|
|
129
|
-
configuration: filteredConfiguration
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Builds authentication configuration from variables or RBAC
|
|
135
|
-
* @function buildAuthenticationConfig
|
|
136
|
-
* @param {Object} variables - Variables configuration
|
|
137
|
-
* @param {Object|null} rbac - RBAC configuration
|
|
138
|
-
* @returns {Object} Authentication configuration
|
|
139
|
-
*/
|
|
140
|
-
function buildAuthenticationConfig(variables, rbac) {
|
|
141
|
-
if (variables.authentication) {
|
|
142
|
-
const auth = {
|
|
143
|
-
enableSSO: variables.authentication.enableSSO !== undefined ? variables.authentication.enableSSO : true
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
// When enableSSO is false, default type to 'none' and requiredRoles to []
|
|
147
|
-
// When enableSSO is true, require type and requiredRoles
|
|
148
|
-
// Sanitize auth type (e.g., map keycloak to azure)
|
|
149
|
-
if (auth.enableSSO === false) {
|
|
150
|
-
auth.type = sanitizeAuthType(variables.authentication.type || 'none');
|
|
151
|
-
auth.requiredRoles = variables.authentication.requiredRoles || [];
|
|
152
|
-
} else {
|
|
153
|
-
auth.type = sanitizeAuthType(variables.authentication.type || 'azure');
|
|
154
|
-
auth.requiredRoles = variables.authentication.requiredRoles || [];
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (variables.authentication.endpoints) {
|
|
158
|
-
auth.endpoints = variables.authentication.endpoints;
|
|
159
|
-
}
|
|
160
|
-
return auth;
|
|
161
|
-
}
|
|
162
|
-
return buildAuthentication(rbac);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Validates and transforms repository configuration
|
|
167
|
-
* @function validateRepositoryConfig
|
|
168
|
-
* @param {Object} repository - Repository configuration
|
|
169
|
-
* @returns {Object|null} Validated repository config or null
|
|
74
|
+
* Generates external system <app-name>-deploy.json by loading the system JSON file
|
|
75
|
+
* For external systems, the system JSON file is already created and we just need to reference it
|
|
76
|
+
* @async
|
|
77
|
+
* @function generateExternalSystemDeployJson
|
|
78
|
+
* @param {string} appName - Name of the application
|
|
79
|
+
* @param {string} appPath - Path to application directory (integration or builder)
|
|
80
|
+
* @returns {Promise<string>} Path to generated <app-name>-deploy.json file
|
|
81
|
+
* @throws {Error} If generation fails
|
|
170
82
|
*/
|
|
171
|
-
function
|
|
172
|
-
if (!
|
|
173
|
-
|
|
83
|
+
async function generateExternalSystemDeployJson(appName, appPath) {
|
|
84
|
+
if (!appName || typeof appName !== 'string') {
|
|
85
|
+
throw new Error('App name is required and must be a string');
|
|
174
86
|
}
|
|
175
87
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
enabled: repository.enabled || false,
|
|
179
|
-
repositoryUrl: repository.repositoryUrl
|
|
180
|
-
};
|
|
181
|
-
}
|
|
88
|
+
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
89
|
+
const { parsed: variables } = loadVariables(variablesPath);
|
|
182
90
|
|
|
183
|
-
if (
|
|
184
|
-
|
|
91
|
+
if (!variables.externalIntegration) {
|
|
92
|
+
throw new Error('externalIntegration block not found in variables.yaml');
|
|
185
93
|
}
|
|
186
94
|
|
|
187
|
-
|
|
188
|
-
|
|
95
|
+
// For external systems, the system JSON file should be in the same folder
|
|
96
|
+
// Check if it already exists (should be <app-name>-deploy.json)
|
|
97
|
+
const deployJsonPath = getDeployJsonPath(appName, 'external', true);
|
|
98
|
+
const systemFileName = variables.externalIntegration.systems && variables.externalIntegration.systems.length > 0
|
|
99
|
+
? variables.externalIntegration.systems[0]
|
|
100
|
+
: `${appName}-deploy.json`;
|
|
189
101
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
*/
|
|
196
|
-
function validateBuildFields(build) {
|
|
197
|
-
if (!build) {
|
|
198
|
-
return null;
|
|
199
|
-
}
|
|
102
|
+
// Resolve system file path (schemaBasePath is usually './' for same folder)
|
|
103
|
+
const schemaBasePath = variables.externalIntegration.schemaBasePath || './';
|
|
104
|
+
const systemFilePath = path.isAbsolute(schemaBasePath)
|
|
105
|
+
? path.join(schemaBasePath, systemFileName)
|
|
106
|
+
: path.join(appPath, schemaBasePath, systemFileName);
|
|
200
107
|
|
|
201
|
-
|
|
202
|
-
if (
|
|
203
|
-
|
|
108
|
+
// If system file doesn't exist, throw error (it should be created manually or via external-system-generator)
|
|
109
|
+
if (!fs.existsSync(systemFilePath)) {
|
|
110
|
+
throw new Error(`External system file not found: ${systemFilePath}. Please create it first.`);
|
|
204
111
|
}
|
|
205
|
-
if (build.dockerfile && build.dockerfile.trim()) {
|
|
206
|
-
buildConfig.dockerfile = build.dockerfile;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
return Object.keys(buildConfig).length > 0 ? buildConfig : null;
|
|
210
|
-
}
|
|
211
112
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
* @param {Object} deployment - Deployment configuration
|
|
216
|
-
* @returns {Object|null} Validated deployment config or null
|
|
217
|
-
*/
|
|
218
|
-
function validateDeploymentFields(deployment) {
|
|
219
|
-
if (!deployment) {
|
|
220
|
-
return null;
|
|
221
|
-
}
|
|
113
|
+
// Read the system JSON file
|
|
114
|
+
const systemContent = await fs.promises.readFile(systemFilePath, 'utf8');
|
|
115
|
+
const systemJson = JSON.parse(systemContent);
|
|
222
116
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
117
|
+
// Write it as <app-name>-deploy.json (consistent naming)
|
|
118
|
+
const jsonContent = JSON.stringify(systemJson, null, 2);
|
|
119
|
+
await fs.promises.writeFile(deployJsonPath, jsonContent, { mode: 0o644, encoding: 'utf8' });
|
|
227
120
|
|
|
228
|
-
return
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Adds optional fields to deployment manifest
|
|
233
|
-
* @function buildOptionalFields
|
|
234
|
-
* @param {Object} deployment - Deployment manifest
|
|
235
|
-
* @param {Object} variables - Variables configuration
|
|
236
|
-
* @param {Object|null} rbac - RBAC configuration
|
|
237
|
-
* @returns {Object} Deployment manifest with optional fields
|
|
238
|
-
*/
|
|
239
|
-
function buildOptionalFields(deployment, variables, rbac) {
|
|
240
|
-
if (variables.healthCheck) {
|
|
241
|
-
deployment.healthCheck = buildHealthCheck(variables);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
deployment.authentication = buildAuthenticationConfig(variables, rbac);
|
|
245
|
-
|
|
246
|
-
// Add roles and permissions (from variables.yaml or rbac.yaml)
|
|
247
|
-
// Priority: variables.yaml > rbac.yaml
|
|
248
|
-
if (variables.roles) {
|
|
249
|
-
deployment.roles = variables.roles;
|
|
250
|
-
} else if (rbac && rbac.roles) {
|
|
251
|
-
deployment.roles = rbac.roles;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (variables.permissions) {
|
|
255
|
-
deployment.permissions = variables.permissions;
|
|
256
|
-
} else if (rbac && rbac.permissions) {
|
|
257
|
-
deployment.permissions = rbac.permissions;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const repository = validateRepositoryConfig(variables.repository);
|
|
261
|
-
if (repository) {
|
|
262
|
-
deployment.repository = repository;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const build = validateBuildFields(variables.build);
|
|
266
|
-
if (build) {
|
|
267
|
-
deployment.build = build;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const deploymentConfig = validateDeploymentFields(variables.deployment);
|
|
271
|
-
if (deploymentConfig) {
|
|
272
|
-
deployment.deployment = deploymentConfig;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (variables.startupCommand) {
|
|
276
|
-
deployment.startupCommand = variables.startupCommand;
|
|
277
|
-
}
|
|
278
|
-
if (variables.runtimeVersion) {
|
|
279
|
-
deployment.runtimeVersion = variables.runtimeVersion;
|
|
280
|
-
}
|
|
281
|
-
if (variables.scaling) {
|
|
282
|
-
deployment.scaling = variables.scaling;
|
|
283
|
-
}
|
|
284
|
-
if (variables.frontDoorRouting) {
|
|
285
|
-
deployment.frontDoorRouting = variables.frontDoorRouting;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
return deployment;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* Builds deployment manifest structure
|
|
293
|
-
* @param {string} appName - Application name
|
|
294
|
-
* @param {Object} variables - Variables configuration
|
|
295
|
-
* @param {string} deploymentKey - Deployment key
|
|
296
|
-
* @param {Array} configuration - Environment configuration
|
|
297
|
-
* @param {Object|null} rbac - RBAC configuration
|
|
298
|
-
* @returns {Object} Deployment manifest
|
|
299
|
-
*/
|
|
300
|
-
function buildManifestStructure(appName, variables, deploymentKey, configuration, rbac) {
|
|
301
|
-
const registryMode = variables.image?.registryMode || 'external';
|
|
302
|
-
const filteredConfiguration = filterConfigurationByRegistryMode(configuration, registryMode);
|
|
303
|
-
const deployment = buildBaseDeployment(appName, variables, filteredConfiguration);
|
|
304
|
-
return buildOptionalFields(deployment, variables, rbac);
|
|
121
|
+
return deployJsonPath;
|
|
305
122
|
}
|
|
306
123
|
|
|
307
124
|
/**
|
|
308
125
|
* Generates deployment JSON from application configuration files
|
|
309
|
-
* Creates
|
|
126
|
+
* Creates <app-name>-deploy.json for all apps (consistent naming)
|
|
127
|
+
* For external systems, loads the system JSON file
|
|
128
|
+
* For regular apps, generates deployment manifest from variables.yaml, env.template, rbac.yaml
|
|
310
129
|
*
|
|
311
130
|
* @async
|
|
312
131
|
* @function generateDeployJson
|
|
@@ -316,18 +135,26 @@ function buildManifestStructure(appName, variables, deploymentKey, configuration
|
|
|
316
135
|
*
|
|
317
136
|
* @example
|
|
318
137
|
* const jsonPath = await generateDeployJson('myapp');
|
|
319
|
-
* // Returns: './builder/myapp/
|
|
138
|
+
* // Returns: './builder/myapp/myapp-deploy.json' or './integration/hubspot/hubspot-deploy.json'
|
|
320
139
|
*/
|
|
321
140
|
async function generateDeployJson(appName) {
|
|
322
141
|
if (!appName || typeof appName !== 'string') {
|
|
323
142
|
throw new Error('App name is required and must be a string');
|
|
324
143
|
}
|
|
325
144
|
|
|
326
|
-
|
|
327
|
-
const
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
145
|
+
// Detect app type and get correct path (integration or builder)
|
|
146
|
+
const { isExternal, appPath, appType } = await detectAppType(appName);
|
|
147
|
+
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
148
|
+
|
|
149
|
+
// Check if app type is external
|
|
150
|
+
if (isExternal) {
|
|
151
|
+
return await generateExternalSystemDeployJson(appName, appPath);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Regular app: generate deployment manifest
|
|
155
|
+
const templatePath = path.join(appPath, 'env.template');
|
|
156
|
+
const rbacPath = path.join(appPath, 'rbac.yaml');
|
|
157
|
+
const jsonPath = getDeployJsonPath(appName, appType, true); // Use new naming
|
|
331
158
|
|
|
332
159
|
// Load configuration files
|
|
333
160
|
const { parsed: variables } = loadVariables(variablesPath);
|
|
@@ -338,7 +165,7 @@ async function generateDeployJson(appName) {
|
|
|
338
165
|
const configuration = parseEnvironmentVariables(envTemplate);
|
|
339
166
|
|
|
340
167
|
// Build deployment manifest WITHOUT deploymentKey initially
|
|
341
|
-
const deployment = buildManifestStructure(appName, variables, null, configuration, rbac);
|
|
168
|
+
const deployment = builders.buildManifestStructure(appName, variables, null, configuration, rbac);
|
|
342
169
|
|
|
343
170
|
// Generate deploymentKey from the manifest object (excluding deploymentKey field)
|
|
344
171
|
const deploymentKey = _keyGenerator.generateDeploymentKeyFromJson(deployment);
|
|
@@ -411,89 +238,170 @@ function parseEnvironmentVariables(envTemplate) {
|
|
|
411
238
|
return configuration;
|
|
412
239
|
}
|
|
413
240
|
|
|
414
|
-
function
|
|
415
|
-
const
|
|
416
|
-
const
|
|
417
|
-
const
|
|
241
|
+
async function generateDeployJsonWithValidation(appName) {
|
|
242
|
+
const jsonPath = await generateDeployJson(appName);
|
|
243
|
+
const jsonContent = fs.readFileSync(jsonPath, 'utf8');
|
|
244
|
+
const deployment = JSON.parse(jsonContent);
|
|
418
245
|
|
|
419
|
-
if
|
|
420
|
-
|
|
421
|
-
}
|
|
246
|
+
// Detect if this is an external system
|
|
247
|
+
const { isExternal } = await detectAppType(appName);
|
|
422
248
|
|
|
423
|
-
|
|
424
|
-
|
|
249
|
+
// For external systems, skip deployment JSON validation (they use external system JSON structure)
|
|
250
|
+
if (isExternal) {
|
|
251
|
+
return {
|
|
252
|
+
success: true,
|
|
253
|
+
path: jsonPath,
|
|
254
|
+
validation: { valid: true, errors: [], warnings: [] },
|
|
255
|
+
deployment
|
|
256
|
+
};
|
|
257
|
+
}
|
|
425
258
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
259
|
+
const validation = _validator.validateDeploymentJson(deployment);
|
|
260
|
+
return {
|
|
261
|
+
success: validation.valid,
|
|
262
|
+
path: jsonPath,
|
|
263
|
+
validation,
|
|
264
|
+
deployment
|
|
430
265
|
};
|
|
266
|
+
}
|
|
431
267
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
268
|
+
/**
|
|
269
|
+
* Generates application-schema.json structure for external systems
|
|
270
|
+
* Combines system and datasource JSONs into application-level deployment format
|
|
271
|
+
* @async
|
|
272
|
+
* @function generateExternalSystemApplicationSchema
|
|
273
|
+
* @param {string} appName - Application name
|
|
274
|
+
* @returns {Promise<Object>} Application schema object
|
|
275
|
+
* @throws {Error} If generation fails
|
|
276
|
+
*/
|
|
277
|
+
async function generateExternalSystemApplicationSchema(appName) {
|
|
278
|
+
if (!appName || typeof appName !== 'string') {
|
|
279
|
+
throw new Error('App name is required and must be a string');
|
|
435
280
|
}
|
|
436
|
-
|
|
437
|
-
|
|
281
|
+
|
|
282
|
+
const { appPath } = await detectAppType(appName);
|
|
283
|
+
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
284
|
+
|
|
285
|
+
// Load variables.yaml
|
|
286
|
+
const { parsed: variables } = loadVariables(variablesPath);
|
|
287
|
+
|
|
288
|
+
if (!variables.externalIntegration) {
|
|
289
|
+
throw new Error('externalIntegration block not found in variables.yaml');
|
|
438
290
|
}
|
|
439
|
-
|
|
440
|
-
|
|
291
|
+
|
|
292
|
+
// Load system file
|
|
293
|
+
const schemaBasePath = variables.externalIntegration.schemaBasePath || './';
|
|
294
|
+
const systemFiles = variables.externalIntegration.systems || [];
|
|
295
|
+
|
|
296
|
+
if (systemFiles.length === 0) {
|
|
297
|
+
throw new Error('No system files specified in externalIntegration.systems');
|
|
441
298
|
}
|
|
442
|
-
|
|
443
|
-
|
|
299
|
+
|
|
300
|
+
const systemFileName = systemFiles[0];
|
|
301
|
+
const systemFilePath = path.isAbsolute(schemaBasePath)
|
|
302
|
+
? path.join(schemaBasePath, systemFileName)
|
|
303
|
+
: path.join(appPath, schemaBasePath, systemFileName);
|
|
304
|
+
|
|
305
|
+
if (!fs.existsSync(systemFilePath)) {
|
|
306
|
+
throw new Error(`System file not found: ${systemFilePath}`);
|
|
444
307
|
}
|
|
445
308
|
|
|
446
|
-
|
|
447
|
-
|
|
309
|
+
const systemContent = await fs.promises.readFile(systemFilePath, 'utf8');
|
|
310
|
+
const systemJson = JSON.parse(systemContent);
|
|
448
311
|
|
|
449
|
-
|
|
450
|
-
const
|
|
312
|
+
// Load datasource files
|
|
313
|
+
const datasourceFiles = variables.externalIntegration.dataSources || [];
|
|
314
|
+
const datasourceJsons = [];
|
|
451
315
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
storage: requires.storage || false,
|
|
457
|
-
storageSize: requires.storageSize || '1Gi'
|
|
458
|
-
};
|
|
459
|
-
}
|
|
316
|
+
for (const datasourceFile of datasourceFiles) {
|
|
317
|
+
const datasourcePath = path.isAbsolute(schemaBasePath)
|
|
318
|
+
? path.join(schemaBasePath, datasourceFile)
|
|
319
|
+
: path.join(appPath, schemaBasePath, datasourceFile);
|
|
460
320
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
321
|
+
if (!fs.existsSync(datasourcePath)) {
|
|
322
|
+
throw new Error(`Datasource file not found: ${datasourcePath}`);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const datasourceContent = await fs.promises.readFile(datasourcePath, 'utf8');
|
|
326
|
+
const datasourceJson = JSON.parse(datasourceContent);
|
|
327
|
+
datasourceJsons.push(datasourceJson);
|
|
468
328
|
}
|
|
469
329
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
330
|
+
// Build application-schema.json structure
|
|
331
|
+
const applicationSchema = {
|
|
332
|
+
version: variables.externalIntegration.version || '1.0.0',
|
|
333
|
+
application: systemJson,
|
|
334
|
+
dataSources: datasourceJsons
|
|
474
335
|
};
|
|
475
|
-
}
|
|
476
336
|
|
|
477
|
-
|
|
478
|
-
const
|
|
479
|
-
const
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
};
|
|
337
|
+
// Validate individual components against their schemas
|
|
338
|
+
const externalSystemSchema = require('./schema/external-system.schema.json');
|
|
339
|
+
const externalDatasourceSchema = require('./schema/external-datasource.schema.json');
|
|
340
|
+
|
|
341
|
+
// For draft-2020-12 schemas, remove $schema to avoid AJV issues (similar to schema-loader.js)
|
|
342
|
+
const datasourceSchemaToAdd = { ...externalDatasourceSchema };
|
|
343
|
+
if (datasourceSchemaToAdd.$schema && datasourceSchemaToAdd.$schema.includes('2020-12')) {
|
|
344
|
+
delete datasourceSchemaToAdd.$schema;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const ajv = new Ajv({ allErrors: true, strict: false, removeAdditional: false });
|
|
348
|
+
|
|
349
|
+
// Validate application (system) against external-system schema
|
|
350
|
+
const externalSystemSchemaId = externalSystemSchema.$id || 'https://raw.githubusercontent.com/esystemsdev/aifabrix-builder/refs/heads/main/lib/schema/external-system.schema.json';
|
|
351
|
+
ajv.addSchema(externalSystemSchema, externalSystemSchemaId);
|
|
352
|
+
const validateSystem = ajv.compile(externalSystemSchema);
|
|
353
|
+
const systemValid = validateSystem(systemJson);
|
|
354
|
+
|
|
355
|
+
if (!systemValid) {
|
|
356
|
+
// Filter out additionalProperties errors for required properties that aren't defined in schema
|
|
357
|
+
// This handles schema inconsistencies where authentication is required but not defined in properties
|
|
358
|
+
const filteredErrors = validateSystem.errors.filter(err => {
|
|
359
|
+
if (err.keyword === 'additionalProperties' && err.params?.additionalProperty === 'authentication') {
|
|
360
|
+
// Check if authentication is in required array
|
|
361
|
+
const required = externalSystemSchema.required || [];
|
|
362
|
+
if (required.includes('authentication')) {
|
|
363
|
+
return false; // Ignore this error since authentication is required but not defined
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return true;
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
if (filteredErrors.length > 0) {
|
|
370
|
+
const errors = filteredErrors.map(err => {
|
|
371
|
+
const path = err.instancePath || err.schemaPath;
|
|
372
|
+
return `${path} ${err.message}`;
|
|
373
|
+
}).join(', ');
|
|
374
|
+
throw new Error(`System JSON does not match external-system schema: ${errors}`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Validate each datasource against external-datasource schema
|
|
379
|
+
const externalDatasourceSchemaId = datasourceSchemaToAdd.$id || 'https://raw.githubusercontent.com/esystemsdev/aifabrix-builder/refs/heads/main/lib/schema/external-datasource.schema.json';
|
|
380
|
+
ajv.addSchema(datasourceSchemaToAdd, externalDatasourceSchemaId);
|
|
381
|
+
const validateDatasource = ajv.compile(datasourceSchemaToAdd);
|
|
382
|
+
|
|
383
|
+
for (let i = 0; i < datasourceJsons.length; i++) {
|
|
384
|
+
const datasourceValid = validateDatasource(datasourceJsons[i]);
|
|
385
|
+
if (!datasourceValid) {
|
|
386
|
+
const errors = validateDatasource.errors.map(err => {
|
|
387
|
+
const path = err.instancePath || err.schemaPath;
|
|
388
|
+
return `${path} ${err.message}`;
|
|
389
|
+
}).join(', ');
|
|
390
|
+
throw new Error(`Datasource ${i + 1} (${datasourceJsons[i].key || 'unknown'}) does not match external-datasource schema: ${errors}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return applicationSchema;
|
|
488
395
|
}
|
|
489
396
|
|
|
490
397
|
module.exports = {
|
|
491
398
|
generateDeployJson,
|
|
492
399
|
generateDeployJsonWithValidation,
|
|
400
|
+
generateExternalSystemApplicationSchema,
|
|
493
401
|
parseEnvironmentVariables,
|
|
494
|
-
buildImageReference,
|
|
495
|
-
buildHealthCheck,
|
|
496
|
-
buildRequirements,
|
|
497
|
-
buildAuthentication,
|
|
498
|
-
buildAuthenticationConfig
|
|
402
|
+
buildImageReference: builders.buildImageReference,
|
|
403
|
+
buildHealthCheck: builders.buildHealthCheck,
|
|
404
|
+
buildRequirements: builders.buildRequirements,
|
|
405
|
+
buildAuthentication: builders.buildAuthentication,
|
|
406
|
+
buildAuthenticationConfig: builders.buildAuthenticationConfig
|
|
499
407
|
};
|