@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.
- package/README.md +9 -9
- package/integration/hubspot/README.md +2 -2
- package/integration/hubspot/hubspot-deploy-company.json +17 -14
- package/integration/hubspot/hubspot-deploy-contact.json +19 -16
- package/integration/hubspot/hubspot-deploy-deal.json +21 -18
- package/lib/api/types/datasources.types.js +31 -5
- package/lib/api/types/wizard.types.js +142 -0
- package/lib/api/wizard.api.js +177 -0
- package/lib/{app-config.js → app/config.js} +4 -4
- package/lib/{app-deploy.js → app/deploy.js} +8 -8
- package/lib/app/display.js +90 -0
- package/lib/{app-dockerfile.js → app/dockerfile.js} +4 -4
- package/lib/{app-down.js → app/down.js} +4 -4
- package/lib/app/helpers.js +218 -0
- package/lib/app/index.js +298 -0
- package/lib/{app-list.js → app/list.js} +6 -6
- package/lib/{app-push.js → app/push.js} +4 -4
- package/lib/{app-readme.js → app/readme.js} +34 -13
- package/lib/{app-register.js → app/register.js} +9 -9
- package/lib/{app-rotate-secret.js → app/rotate-secret.js} +10 -10
- package/lib/{app-run-helpers.js → app/run-helpers.js} +10 -10
- package/lib/{app-run.js → app/run.js} +6 -6
- package/lib/{build.js → build/index.js} +59 -32
- package/lib/build/package.json +7 -0
- package/lib/cli.js +245 -179
- package/lib/commands/app.js +3 -3
- package/lib/commands/datasource.js +4 -4
- package/lib/commands/login-credentials.js +209 -0
- package/lib/commands/login-device.js +254 -0
- package/lib/commands/login.js +67 -378
- package/lib/commands/logout.js +1 -1
- package/lib/commands/secrets-set.js +1 -1
- package/lib/commands/secure.js +2 -2
- package/lib/commands/wizard.js +498 -0
- package/lib/{audit-logger.js → core/audit-logger.js} +1 -1
- package/lib/{config.js → core/config.js} +28 -26
- package/lib/{diff.js → core/diff.js} +157 -72
- package/lib/{secrets.js → core/secrets.js} +86 -49
- package/lib/{templates.js → core/templates-env.js} +14 -222
- package/lib/core/templates.js +279 -0
- package/lib/{datasource-deploy.js → datasource/deploy.js} +6 -6
- package/lib/{datasource-diff.js → datasource/diff.js} +2 -2
- package/lib/datasource/list.js +223 -0
- package/lib/{datasource-validate.js → datasource/validate.js} +2 -2
- package/lib/{deployer.js → deployment/deployer.js} +48 -18
- package/lib/{environment-deploy.js → deployment/environment.js} +163 -84
- package/lib/{push.js → deployment/push.js} +1 -1
- package/lib/external-system/deploy-helpers.js +145 -0
- package/lib/{external-system-deploy.js → external-system/deploy.js} +156 -111
- package/lib/external-system/download-helpers.js +114 -0
- package/lib/{external-system-download.js → external-system/download.js} +92 -135
- package/lib/{external-system-generator.js → external-system/generator.js} +15 -11
- package/lib/external-system/test-auth.js +40 -0
- package/lib/external-system/test-execution.js +84 -0
- package/lib/external-system/test-helpers.js +109 -0
- package/lib/{external-system-test.js → external-system/test.js} +174 -192
- package/lib/{generator-builders.js → generator/builders.js} +87 -10
- package/lib/{generator-external.js → generator/external.js} +115 -52
- package/lib/{github-generator.js → generator/github.js} +116 -15
- package/lib/{generator-helpers.js → generator/helpers.js} +92 -42
- package/lib/{generator.js → generator/index.js} +49 -22
- package/lib/{generator-split.js → generator/split.js} +108 -55
- package/lib/generator/wizard-prompts.js +357 -0
- package/lib/generator/wizard.js +490 -0
- package/lib/{infra.js → infrastructure/index.js} +49 -22
- package/lib/schema/external-datasource.schema.json +158 -136
- package/lib/schema/external-system.schema.json +43 -1
- package/lib/utils/api.js +9 -5
- package/lib/utils/app-register-api.js +60 -32
- package/lib/utils/app-register-auth.js +172 -47
- package/lib/utils/app-register-config.js +130 -59
- package/lib/utils/app-run-containers.js +29 -8
- package/lib/utils/build-helpers.js +1 -1
- package/lib/utils/cli-utils.js +78 -30
- package/lib/utils/compose-generator.js +145 -65
- package/lib/utils/config-paths.js +2 -0
- package/lib/utils/deployment-errors.js +1 -1
- package/lib/utils/device-code.js +99 -41
- package/lib/utils/env-config-loader.js +1 -1
- package/lib/utils/env-copy.js +21 -18
- package/lib/utils/env-endpoints.js +115 -67
- package/lib/utils/env-map.js +13 -14
- package/lib/utils/env-ports.js +45 -25
- package/lib/utils/env-template.js +84 -42
- package/lib/utils/error-formatter.js +26 -9
- package/lib/utils/error-formatters/error-parser.js +90 -4
- package/lib/utils/error-formatters/http-status-errors.js +54 -17
- package/lib/utils/error-formatters/network-errors.js +103 -26
- package/lib/utils/external-system-display.js +184 -90
- package/lib/utils/external-system-validators.js +164 -42
- package/lib/utils/file-upload.js +109 -0
- package/lib/utils/health-check.js +199 -83
- package/lib/utils/infra-containers.js +1 -1
- package/lib/utils/infra-status.js +66 -15
- package/lib/utils/local-secrets.js +45 -25
- package/lib/utils/paths.js +45 -33
- package/lib/utils/schema-loader.js +42 -25
- package/lib/utils/schema-resolver.js +123 -74
- package/lib/utils/secrets-encryption.js +62 -25
- package/lib/utils/secrets-helpers.js +126 -63
- package/lib/utils/secrets-path.js +1 -1
- package/lib/utils/secrets-url.js +1 -1
- package/lib/utils/token-manager-refresh.js +181 -0
- package/lib/utils/token-manager.js +76 -123
- package/lib/utils/variable-transformer.js +154 -77
- package/lib/utils/yaml-preserve.js +41 -47
- package/lib/{template-validator.js → validation/template.js} +54 -23
- package/lib/{validate.js → validation/validate.js} +205 -125
- package/lib/{validator.js → validation/validator.js} +58 -39
- package/package.json +31 -2
- package/templates/external-system/deploy.ps1.hbs +34 -0
- package/templates/external-system/deploy.sh.hbs +34 -0
- package/templates/external-system/external-datasource.json.hbs +31 -12
- package/lib/app.js +0 -467
- package/lib/datasource-list.js +0 -141
- /package/lib/{app-prompts.js → app/prompts.js} +0 -0
- /package/lib/{env-reader.js → core/env-reader.js} +0 -0
- /package/lib/{key-generator.js → core/key-generator.js} +0 -0
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
const fs = require('fs').promises;
|
|
12
12
|
const fsSync = require('fs');
|
|
13
13
|
const path = require('path');
|
|
14
|
-
const { getProjectRoot } = require('
|
|
14
|
+
const { getProjectRoot } = require('../utils/paths');
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Validates that a template exists and contains files
|
|
@@ -112,17 +112,14 @@ async function copyTemplateFiles(templateName, appPath) {
|
|
|
112
112
|
* @returns {Promise<string[]>} Array of copied file paths
|
|
113
113
|
* @throws {Error} If language template doesn't exist or copying fails
|
|
114
114
|
*/
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const languageTemplatePath = path.join(projectRoot, 'templates', normalizedLanguage);
|
|
124
|
-
|
|
125
|
-
// Check if language template folder exists
|
|
115
|
+
/**
|
|
116
|
+
* Validates language template path
|
|
117
|
+
* @function validateLanguageTemplatePath
|
|
118
|
+
* @param {string} languageTemplatePath - Template path
|
|
119
|
+
* @param {string} normalizedLanguage - Normalized language name
|
|
120
|
+
* @throws {Error} If path is invalid
|
|
121
|
+
*/
|
|
122
|
+
function validateLanguageTemplatePath(languageTemplatePath, normalizedLanguage) {
|
|
126
123
|
if (!fsSync.existsSync(languageTemplatePath)) {
|
|
127
124
|
throw new Error(`Language template '${normalizedLanguage}' not found. Expected folder: templates/${normalizedLanguage}/`);
|
|
128
125
|
}
|
|
@@ -131,14 +128,17 @@ async function copyAppFiles(language, appPath) {
|
|
|
131
128
|
if (!stats.isDirectory()) {
|
|
132
129
|
throw new Error(`Language template '${normalizedLanguage}' exists but is not a directory`);
|
|
133
130
|
}
|
|
131
|
+
}
|
|
134
132
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
133
|
+
/**
|
|
134
|
+
* Filters application files from entries
|
|
135
|
+
* @function filterAppFiles
|
|
136
|
+
* @param {string[]} entries - Directory entries
|
|
137
|
+
* @returns {string[]} Filtered application files
|
|
138
|
+
*/
|
|
139
|
+
function filterAppFiles(entries) {
|
|
140
|
+
return entries.filter(entry => {
|
|
140
141
|
const lowerEntry = entry.toLowerCase();
|
|
141
|
-
// Include .gitignore, exclude .hbs files and docker-related files
|
|
142
142
|
if (entry === '.gitignore') {
|
|
143
143
|
return true;
|
|
144
144
|
}
|
|
@@ -153,15 +153,46 @@ async function copyAppFiles(language, appPath) {
|
|
|
153
153
|
}
|
|
154
154
|
return true;
|
|
155
155
|
});
|
|
156
|
+
}
|
|
156
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Copies a single file
|
|
160
|
+
* @async
|
|
161
|
+
* @function copySingleFile
|
|
162
|
+
* @param {string} sourcePath - Source file path
|
|
163
|
+
* @param {string} targetPath - Target file path
|
|
164
|
+
* @returns {Promise<string|null>} Target path if copied, null otherwise
|
|
165
|
+
*/
|
|
166
|
+
async function copySingleFile(sourcePath, targetPath) {
|
|
167
|
+
const entryStats = await fs.stat(sourcePath);
|
|
168
|
+
if (entryStats.isFile()) {
|
|
169
|
+
await fs.copyFile(sourcePath, targetPath);
|
|
170
|
+
return targetPath;
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function copyAppFiles(language, appPath) {
|
|
176
|
+
if (!language || typeof language !== 'string') {
|
|
177
|
+
throw new Error('Language is required and must be a string');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const normalizedLanguage = language.toLowerCase();
|
|
181
|
+
const projectRoot = getProjectRoot();
|
|
182
|
+
const languageTemplatePath = path.join(projectRoot, 'templates', normalizedLanguage);
|
|
183
|
+
|
|
184
|
+
validateLanguageTemplatePath(languageTemplatePath, normalizedLanguage);
|
|
185
|
+
|
|
186
|
+
const entries = await fs.readdir(languageTemplatePath);
|
|
187
|
+
const appFiles = filterAppFiles(entries);
|
|
188
|
+
|
|
189
|
+
const copiedFiles = [];
|
|
157
190
|
for (const entry of appFiles) {
|
|
158
191
|
const sourcePath = path.join(languageTemplatePath, entry);
|
|
159
192
|
const targetPath = path.join(appPath, entry);
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
await fs.copyFile(sourcePath, targetPath);
|
|
164
|
-
copiedFiles.push(targetPath);
|
|
193
|
+
const copied = await copySingleFile(sourcePath, targetPath);
|
|
194
|
+
if (copied) {
|
|
195
|
+
copiedFiles.push(copied);
|
|
165
196
|
}
|
|
166
197
|
}
|
|
167
198
|
|
|
@@ -13,11 +13,11 @@ const fs = require('fs');
|
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const chalk = require('chalk');
|
|
15
15
|
const validator = require('./validator');
|
|
16
|
-
const { resolveExternalFiles } = require('
|
|
17
|
-
const { loadExternalSystemSchema, loadExternalDataSourceSchema, detectSchemaType } = require('
|
|
18
|
-
const { formatValidationErrors } = require('
|
|
19
|
-
const { detectAppType } = require('
|
|
20
|
-
const logger = require('
|
|
16
|
+
const { resolveExternalFiles } = require('../utils/schema-resolver');
|
|
17
|
+
const { loadExternalSystemSchema, loadExternalDataSourceSchema, detectSchemaType } = require('../utils/schema-loader');
|
|
18
|
+
const { formatValidationErrors } = require('../utils/error-formatter');
|
|
19
|
+
const { detectAppType } = require('../utils/paths');
|
|
20
|
+
const logger = require('../utils/logger');
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Validates a file path (detects type and validates)
|
|
@@ -49,10 +49,10 @@ async function validateExternalFilesForApp(appName) {
|
|
|
49
49
|
for (const file of files) {
|
|
50
50
|
const result = await validateExternalFile(file.path, file.type);
|
|
51
51
|
validations.push({
|
|
52
|
+
...result,
|
|
52
53
|
file: file.fileName,
|
|
53
54
|
path: file.path,
|
|
54
|
-
type: file.type
|
|
55
|
-
...result
|
|
55
|
+
type: file.type
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
|
|
@@ -103,54 +103,89 @@ function aggregateValidationResults(appValidation, externalValidations, rbacVali
|
|
|
103
103
|
* @param {string} type - File type: 'system' | 'datasource'
|
|
104
104
|
* @returns {Promise<Object>} Validation result
|
|
105
105
|
*/
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Parses JSON file content
|
|
108
|
+
* @function parseJsonFileContent
|
|
109
|
+
* @param {string} filePath - File path
|
|
110
|
+
* @returns {Object} Parse result with parsed object or error
|
|
111
|
+
*/
|
|
112
|
+
function parseJsonFileContent(filePath) {
|
|
111
113
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
112
|
-
let parsed;
|
|
113
|
-
|
|
114
114
|
try {
|
|
115
|
-
parsed
|
|
115
|
+
return { parsed: JSON.parse(content), error: null };
|
|
116
116
|
} catch (error) {
|
|
117
117
|
return {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
parsed: null,
|
|
119
|
+
error: {
|
|
120
|
+
valid: false,
|
|
121
|
+
errors: [`Invalid JSON syntax: ${error.message}`],
|
|
122
|
+
warnings: []
|
|
123
|
+
}
|
|
121
124
|
};
|
|
122
125
|
}
|
|
126
|
+
}
|
|
123
127
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
128
|
+
/**
|
|
129
|
+
* Gets validator function for file type
|
|
130
|
+
* @function getValidatorForType
|
|
131
|
+
* @param {string} normalizedType - Normalized file type
|
|
132
|
+
* @returns {Function} Validator function
|
|
133
|
+
* @throws {Error} If type is unknown
|
|
134
|
+
*/
|
|
135
|
+
function getValidatorForType(normalizedType) {
|
|
127
136
|
if (normalizedType === 'system') {
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
throw new Error(`Unknown file type: ${type}`);
|
|
137
|
+
return loadExternalSystemSchema();
|
|
138
|
+
}
|
|
139
|
+
if (normalizedType === 'datasource') {
|
|
140
|
+
return loadExternalDataSourceSchema();
|
|
133
141
|
}
|
|
142
|
+
throw new Error(`Unknown file type: ${normalizedType}`);
|
|
143
|
+
}
|
|
134
144
|
|
|
135
|
-
|
|
145
|
+
/**
|
|
146
|
+
* Validates role references in permissions
|
|
147
|
+
* @function validateRoleReferences
|
|
148
|
+
* @param {Object} parsed - Parsed JSON object
|
|
149
|
+
* @param {string[]} errors - Errors array to append to
|
|
150
|
+
*/
|
|
151
|
+
function validateRoleReferences(parsed, errors) {
|
|
152
|
+
if (!parsed.permissions || !Array.isArray(parsed.permissions)) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const roles = parsed.roles || [];
|
|
157
|
+
const roleValues = new Set(roles.map(r => r.value));
|
|
158
|
+
|
|
159
|
+
parsed.permissions.forEach((permission, index) => {
|
|
160
|
+
if (permission.roles && Array.isArray(permission.roles)) {
|
|
161
|
+
permission.roles.forEach(roleValue => {
|
|
162
|
+
if (!roleValues.has(roleValue)) {
|
|
163
|
+
errors.push(`Permission "${permission.name}" (index ${index}) references role "${roleValue}" which does not exist in roles array`);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function validateExternalFile(filePath, type) {
|
|
171
|
+
if (!fs.existsSync(filePath)) {
|
|
172
|
+
throw new Error(`File not found: ${filePath}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const parseResult = parseJsonFileContent(filePath);
|
|
176
|
+
if (parseResult.error) {
|
|
177
|
+
return parseResult.error;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const normalizedType = type === 'external-system' ? 'system' : (type === 'external-datasource' ? 'datasource' : type);
|
|
181
|
+
const validate = getValidatorForType(normalizedType);
|
|
182
|
+
const valid = validate(parseResult.parsed);
|
|
136
183
|
|
|
137
184
|
const errors = valid ? [] : formatValidationErrors(validate.errors);
|
|
138
185
|
const warnings = [];
|
|
139
186
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const roles = parsed.roles || [];
|
|
143
|
-
const roleValues = new Set(roles.map(r => r.value));
|
|
144
|
-
|
|
145
|
-
parsed.permissions.forEach((permission, index) => {
|
|
146
|
-
if (permission.roles && Array.isArray(permission.roles)) {
|
|
147
|
-
permission.roles.forEach(roleValue => {
|
|
148
|
-
if (!roleValues.has(roleValue)) {
|
|
149
|
-
errors.push(`Permission "${permission.name}" (index ${index}) references role "${roleValue}" which does not exist in roles array`);
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
});
|
|
187
|
+
if (normalizedType === 'system') {
|
|
188
|
+
validateRoleReferences(parseResult.parsed, errors);
|
|
154
189
|
}
|
|
155
190
|
|
|
156
191
|
return {
|
|
@@ -176,43 +211,38 @@ async function validateExternalFile(filePath, type) {
|
|
|
176
211
|
* const result = await validateAppOrFile('myapp');
|
|
177
212
|
* // Returns: { valid: true, application: {...}, externalFiles: [...] }
|
|
178
213
|
*/
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
214
|
+
/**
|
|
215
|
+
* Validates RBAC for external systems
|
|
216
|
+
* @async
|
|
217
|
+
* @function validateRbacForExternalSystem
|
|
218
|
+
* @param {boolean} isExternal - Whether app is external system
|
|
219
|
+
* @param {string} appName - Application name
|
|
220
|
+
* @returns {Promise<Object|null>} RBAC validation result or null
|
|
221
|
+
*/
|
|
222
|
+
async function validateRbacForExternalSystem(isExternal, appName) {
|
|
223
|
+
if (!isExternal) {
|
|
224
|
+
return null;
|
|
189
225
|
}
|
|
190
226
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
// Validate rbac.yaml for external systems
|
|
201
|
-
let rbacValidation = null;
|
|
202
|
-
if (isExternal) {
|
|
203
|
-
try {
|
|
204
|
-
rbacValidation = await validator.validateRbac(appName);
|
|
205
|
-
} catch (error) {
|
|
206
|
-
rbacValidation = {
|
|
207
|
-
valid: false,
|
|
208
|
-
errors: [error.message],
|
|
209
|
-
warnings: []
|
|
210
|
-
};
|
|
211
|
-
}
|
|
227
|
+
try {
|
|
228
|
+
return await validator.validateRbac(appName);
|
|
229
|
+
} catch (error) {
|
|
230
|
+
return {
|
|
231
|
+
valid: false,
|
|
232
|
+
errors: [error.message],
|
|
233
|
+
warnings: []
|
|
234
|
+
};
|
|
212
235
|
}
|
|
236
|
+
}
|
|
213
237
|
|
|
214
|
-
|
|
215
|
-
|
|
238
|
+
/**
|
|
239
|
+
* Loads and checks variables.yaml for externalIntegration
|
|
240
|
+
* @function loadVariablesAndCheckExternalIntegration
|
|
241
|
+
* @param {string} variablesPath - Path to variables.yaml
|
|
242
|
+
* @param {Object} appValidation - Application validation result
|
|
243
|
+
* @returns {Object|null} Variables object or null if no externalIntegration
|
|
244
|
+
*/
|
|
245
|
+
function loadVariablesAndCheckExternalIntegration(variablesPath, appValidation) {
|
|
216
246
|
if (!fs.existsSync(variablesPath)) {
|
|
217
247
|
return {
|
|
218
248
|
valid: appValidation.valid,
|
|
@@ -236,7 +266,6 @@ async function validateAppOrFile(appOrFile) {
|
|
|
236
266
|
};
|
|
237
267
|
}
|
|
238
268
|
|
|
239
|
-
// If no externalIntegration block, return app validation only
|
|
240
269
|
if (!variables.externalIntegration) {
|
|
241
270
|
return {
|
|
242
271
|
valid: appValidation.valid,
|
|
@@ -245,10 +274,31 @@ async function validateAppOrFile(appOrFile) {
|
|
|
245
274
|
};
|
|
246
275
|
}
|
|
247
276
|
|
|
248
|
-
|
|
249
|
-
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async function validateAppOrFile(appOrFile) {
|
|
281
|
+
if (!appOrFile || typeof appOrFile !== 'string') {
|
|
282
|
+
throw new Error('App name or file path is required');
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const isFilePath = fs.existsSync(appOrFile) && fs.statSync(appOrFile).isFile();
|
|
286
|
+
if (isFilePath) {
|
|
287
|
+
return await validateFilePath(appOrFile);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const appName = appOrFile;
|
|
291
|
+
const { appPath, isExternal } = await detectAppType(appName);
|
|
292
|
+
const appValidation = await validator.validateApplication(appName);
|
|
293
|
+
const rbacValidation = await validateRbacForExternalSystem(isExternal, appName);
|
|
294
|
+
|
|
295
|
+
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
296
|
+
const earlyReturn = loadVariablesAndCheckExternalIntegration(variablesPath, appValidation);
|
|
297
|
+
if (earlyReturn) {
|
|
298
|
+
return earlyReturn;
|
|
299
|
+
}
|
|
250
300
|
|
|
251
|
-
|
|
301
|
+
const externalValidations = await validateExternalFilesForApp(appName);
|
|
252
302
|
return aggregateValidationResults(appValidation, externalValidations, rbacValidation);
|
|
253
303
|
}
|
|
254
304
|
|
|
@@ -258,69 +308,97 @@ async function validateAppOrFile(appOrFile) {
|
|
|
258
308
|
* @function displayValidationResults
|
|
259
309
|
* @param {Object} result - Validation result from validateAppOrFile
|
|
260
310
|
*/
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
311
|
+
/**
|
|
312
|
+
* Displays application validation results
|
|
313
|
+
* @function displayApplicationValidation
|
|
314
|
+
* @param {Object} application - Application validation result
|
|
315
|
+
*/
|
|
316
|
+
function displayApplicationValidation(application) {
|
|
317
|
+
if (!application) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
logger.log(chalk.blue('\nApplication:'));
|
|
322
|
+
if (application.valid) {
|
|
323
|
+
logger.log(chalk.green(' ✓ Application configuration is valid'));
|
|
264
324
|
} else {
|
|
265
|
-
logger.log(chalk.red('
|
|
325
|
+
logger.log(chalk.red(' ✗ Application configuration has errors:'));
|
|
326
|
+
application.errors.forEach(error => {
|
|
327
|
+
logger.log(chalk.red(` • ${error}`));
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
if (application.warnings && application.warnings.length > 0) {
|
|
331
|
+
application.warnings.forEach(warning => {
|
|
332
|
+
logger.log(chalk.yellow(` ⚠ ${warning}`));
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Displays external files validation results
|
|
339
|
+
* @function displayExternalFilesValidation
|
|
340
|
+
* @param {Array} externalFiles - External files validation results
|
|
341
|
+
*/
|
|
342
|
+
function displayExternalFilesValidation(externalFiles) {
|
|
343
|
+
if (!externalFiles || externalFiles.length === 0) {
|
|
344
|
+
return;
|
|
266
345
|
}
|
|
267
346
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
logger.log(chalk.green(' ✓ Application configuration is valid'));
|
|
347
|
+
logger.log(chalk.blue('\nExternal Integration Files:'));
|
|
348
|
+
externalFiles.forEach(file => {
|
|
349
|
+
if (file.valid) {
|
|
350
|
+
logger.log(chalk.green(` ✓ ${file.file} (${file.type})`));
|
|
273
351
|
} else {
|
|
274
|
-
logger.log(chalk.red(
|
|
275
|
-
|
|
352
|
+
logger.log(chalk.red(` ✗ ${file.file} (${file.type}):`));
|
|
353
|
+
file.errors.forEach(error => {
|
|
276
354
|
logger.log(chalk.red(` • ${error}`));
|
|
277
355
|
});
|
|
278
356
|
}
|
|
279
|
-
if (
|
|
280
|
-
|
|
357
|
+
if (file.warnings && file.warnings.length > 0) {
|
|
358
|
+
file.warnings.forEach(warning => {
|
|
281
359
|
logger.log(chalk.yellow(` ⚠ ${warning}`));
|
|
282
360
|
});
|
|
283
361
|
}
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Displays RBAC validation results
|
|
367
|
+
* @function displayRbacValidation
|
|
368
|
+
* @param {Object} rbac - RBAC validation result
|
|
369
|
+
*/
|
|
370
|
+
function displayRbacValidation(rbac) {
|
|
371
|
+
if (!rbac) {
|
|
372
|
+
return;
|
|
284
373
|
}
|
|
285
374
|
|
|
286
|
-
|
|
287
|
-
if (
|
|
288
|
-
logger.log(chalk.
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
logger.log(chalk.red(` ✗ ${file.file} (${file.type}):`));
|
|
294
|
-
file.errors.forEach(error => {
|
|
295
|
-
logger.log(chalk.red(` • ${error}`));
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
if (file.warnings && file.warnings.length > 0) {
|
|
299
|
-
file.warnings.forEach(warning => {
|
|
300
|
-
logger.log(chalk.yellow(` ⚠ ${warning}`));
|
|
301
|
-
});
|
|
302
|
-
}
|
|
375
|
+
logger.log(chalk.blue('\nRBAC Configuration:'));
|
|
376
|
+
if (rbac.valid) {
|
|
377
|
+
logger.log(chalk.green(' ✓ RBAC configuration is valid'));
|
|
378
|
+
} else {
|
|
379
|
+
logger.log(chalk.red(' ✗ RBAC configuration has errors:'));
|
|
380
|
+
rbac.errors.forEach(error => {
|
|
381
|
+
logger.log(chalk.red(` • ${error}`));
|
|
303
382
|
});
|
|
304
383
|
}
|
|
384
|
+
if (rbac.warnings && rbac.warnings.length > 0) {
|
|
385
|
+
rbac.warnings.forEach(warning => {
|
|
386
|
+
logger.log(chalk.yellow(` ⚠ ${warning}`));
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
305
390
|
|
|
306
|
-
|
|
307
|
-
if (result.
|
|
308
|
-
logger.log(chalk.
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
} else {
|
|
312
|
-
logger.log(chalk.red(' ✗ RBAC configuration has errors:'));
|
|
313
|
-
result.rbac.errors.forEach(error => {
|
|
314
|
-
logger.log(chalk.red(` • ${error}`));
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
if (result.rbac.warnings && result.rbac.warnings.length > 0) {
|
|
318
|
-
result.rbac.warnings.forEach(warning => {
|
|
319
|
-
logger.log(chalk.yellow(` ⚠ ${warning}`));
|
|
320
|
-
});
|
|
321
|
-
}
|
|
391
|
+
function displayValidationResults(result) {
|
|
392
|
+
if (result.valid) {
|
|
393
|
+
logger.log(chalk.green('\n✓ Validation passed!'));
|
|
394
|
+
} else {
|
|
395
|
+
logger.log(chalk.red('\n✗ Validation failed!'));
|
|
322
396
|
}
|
|
323
397
|
|
|
398
|
+
displayApplicationValidation(result.application);
|
|
399
|
+
displayExternalFilesValidation(result.externalFiles);
|
|
400
|
+
displayRbacValidation(result.rbac);
|
|
401
|
+
|
|
324
402
|
// Display file validation (for direct file validation)
|
|
325
403
|
if (result.file) {
|
|
326
404
|
logger.log(chalk.blue(`\nFile: ${result.file}`));
|
|
@@ -352,6 +430,8 @@ function displayValidationResults(result) {
|
|
|
352
430
|
module.exports = {
|
|
353
431
|
validateAppOrFile,
|
|
354
432
|
displayValidationResults,
|
|
355
|
-
validateExternalFile
|
|
433
|
+
validateExternalFile,
|
|
434
|
+
validateExternalFilesForApp,
|
|
435
|
+
validateFilePath
|
|
356
436
|
};
|
|
357
437
|
|
|
@@ -13,13 +13,13 @@ const fs = require('fs');
|
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const yaml = require('js-yaml');
|
|
15
15
|
const Ajv = require('ajv');
|
|
16
|
-
const applicationSchema = require('
|
|
17
|
-
const externalSystemSchema = require('
|
|
18
|
-
const externalDataSourceSchema = require('
|
|
19
|
-
const { transformVariablesForValidation } = require('
|
|
20
|
-
const { checkEnvironment } = require('
|
|
21
|
-
const { formatValidationErrors } = require('
|
|
22
|
-
const { detectAppType } = require('
|
|
16
|
+
const applicationSchema = require('../schema/application-schema.json');
|
|
17
|
+
const externalSystemSchema = require('../schema/external-system.schema.json');
|
|
18
|
+
const externalDataSourceSchema = require('../schema/external-datasource.schema.json');
|
|
19
|
+
const { transformVariablesForValidation } = require('../utils/variable-transformer');
|
|
20
|
+
const { checkEnvironment } = require('../utils/environment-checker');
|
|
21
|
+
const { formatValidationErrors } = require('../utils/error-formatter');
|
|
22
|
+
const { detectAppType } = require('../utils/paths');
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Validates variables.yaml file against application schema
|
|
@@ -35,12 +35,15 @@ const { detectAppType } = require('./utils/paths');
|
|
|
35
35
|
* const result = await validateVariables('myapp');
|
|
36
36
|
* // Returns: { valid: true, errors: [], warnings: [] }
|
|
37
37
|
*/
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Loads and parses variables.yaml
|
|
40
|
+
* @async
|
|
41
|
+
* @function loadVariablesYaml
|
|
42
|
+
* @param {string} appName - Application name
|
|
43
|
+
* @returns {Promise<Object>} Variables object
|
|
44
|
+
* @throws {Error} If file not found or invalid
|
|
45
|
+
*/
|
|
46
|
+
async function loadVariablesYaml(appName) {
|
|
44
47
|
const { appPath } = await detectAppType(appName);
|
|
45
48
|
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
46
49
|
|
|
@@ -49,51 +52,67 @@ async function validateVariables(appName) {
|
|
|
49
52
|
}
|
|
50
53
|
|
|
51
54
|
const content = fs.readFileSync(variablesPath, 'utf8');
|
|
52
|
-
let variables;
|
|
53
|
-
|
|
54
55
|
try {
|
|
55
|
-
|
|
56
|
+
return yaml.load(content);
|
|
56
57
|
} catch (error) {
|
|
57
58
|
throw new Error(`Invalid YAML syntax in variables.yaml: ${error.message}`);
|
|
58
59
|
}
|
|
60
|
+
}
|
|
59
61
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Sets up AJV validator with external schemas
|
|
64
|
+
* @function setupAjvValidator
|
|
65
|
+
* @returns {Function} Compiled validator function
|
|
66
|
+
*/
|
|
67
|
+
function setupAjvValidator() {
|
|
63
68
|
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
64
|
-
// Register external schemas with their $id (GitHub raw URLs)
|
|
65
|
-
// Create copies to avoid modifying the original schemas
|
|
66
69
|
const externalSystemSchemaCopy = { ...externalSystemSchema };
|
|
67
70
|
const externalDataSourceSchemaCopy = { ...externalDataSourceSchema };
|
|
68
|
-
|
|
71
|
+
|
|
69
72
|
if (externalDataSourceSchemaCopy.$schema && externalDataSourceSchemaCopy.$schema.includes('2020-12')) {
|
|
70
73
|
delete externalDataSourceSchemaCopy.$schema;
|
|
71
74
|
}
|
|
75
|
+
|
|
72
76
|
ajv.addSchema(externalSystemSchemaCopy, externalSystemSchema.$id);
|
|
73
77
|
ajv.addSchema(externalDataSourceSchemaCopy, externalDataSourceSchema.$id);
|
|
74
|
-
|
|
78
|
+
return ajv.compile(applicationSchema);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Validates external integration block
|
|
83
|
+
* @function validateExternalIntegrationBlock
|
|
84
|
+
* @param {Object} variables - Variables object
|
|
85
|
+
* @param {string[]} errors - Errors array to append to
|
|
86
|
+
*/
|
|
87
|
+
function validateExternalIntegrationBlock(variables, errors) {
|
|
88
|
+
if (!variables.externalIntegration) {
|
|
89
|
+
errors.push('externalIntegration block is required when app.type is "external"');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!variables.externalIntegration.schemaBasePath) {
|
|
94
|
+
errors.push('externalIntegration.schemaBasePath is required');
|
|
95
|
+
}
|
|
96
|
+
if (!variables.externalIntegration.systems || !Array.isArray(variables.externalIntegration.systems) || variables.externalIntegration.systems.length === 0) {
|
|
97
|
+
errors.push('externalIntegration.systems must be a non-empty array');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function validateVariables(appName) {
|
|
102
|
+
if (!appName || typeof appName !== 'string') {
|
|
103
|
+
throw new Error('App name is required and must be a string');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const variables = await loadVariablesYaml(appName);
|
|
107
|
+
const transformed = transformVariablesForValidation(variables, appName);
|
|
108
|
+
const validate = setupAjvValidator();
|
|
75
109
|
const valid = validate(transformed);
|
|
76
110
|
|
|
77
|
-
// Additional explicit validation for external type
|
|
78
111
|
const errors = valid ? [] : formatValidationErrors(validate.errors);
|
|
79
112
|
const warnings = [];
|
|
80
113
|
|
|
81
|
-
// If type is external, perform additional checks
|
|
82
114
|
if (variables.app && variables.app.type === 'external') {
|
|
83
|
-
|
|
84
|
-
if (!variables.externalIntegration) {
|
|
85
|
-
errors.push('externalIntegration block is required when app.type is "external"');
|
|
86
|
-
} else {
|
|
87
|
-
// Validate externalIntegration structure
|
|
88
|
-
if (!variables.externalIntegration.schemaBasePath) {
|
|
89
|
-
errors.push('externalIntegration.schemaBasePath is required');
|
|
90
|
-
}
|
|
91
|
-
if (!variables.externalIntegration.systems || !Array.isArray(variables.externalIntegration.systems) || variables.externalIntegration.systems.length === 0) {
|
|
92
|
-
errors.push('externalIntegration.systems must be a non-empty array');
|
|
93
|
-
}
|
|
94
|
-
// Note: dataSources can be empty, so we don't validate that here
|
|
95
|
-
// File existence is validated during build/deploy, not during schema validation
|
|
96
|
-
}
|
|
115
|
+
validateExternalIntegrationBlock(variables, errors);
|
|
97
116
|
}
|
|
98
117
|
|
|
99
118
|
return {
|