@aifabrix/builder 2.8.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/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 +4 -3
- package/lib/app-deploy.js +8 -20
- package/lib/app-dockerfile.js +7 -9
- package/lib/app-prompts.js +6 -5
- package/lib/app-push.js +9 -9
- package/lib/app-register.js +23 -5
- package/lib/app-rotate-secret.js +10 -0
- package/lib/app-run.js +5 -11
- package/lib/app.js +42 -14
- package/lib/build.js +20 -16
- package/lib/cli.js +61 -2
- package/lib/datasource-deploy.js +14 -20
- package/lib/external-system-deploy.js +123 -40
- package/lib/external-system-download.js +431 -0
- package/lib/external-system-generator.js +13 -10
- 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 +853 -852
- package/lib/schema/external-datasource.schema.json +823 -49
- package/lib/schema/external-system.schema.json +96 -78
- package/lib/templates.js +1 -1
- package/lib/utils/cli-utils.js +4 -4
- 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/validator.js +5 -2
- package/package.json +1 -1
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External System Validation Helpers
|
|
3
|
+
*
|
|
4
|
+
* Provides validation functions for external system field mappings and schemas.
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview Validation helpers for external system testing
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 2.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const Ajv = require('ajv');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Validates field mapping expression syntax (pipe-based DSL)
|
|
15
|
+
* @param {string} expression - Field mapping expression
|
|
16
|
+
* @returns {Object} Validation result with isValid and error message
|
|
17
|
+
*/
|
|
18
|
+
function validateFieldMappingExpression(expression) {
|
|
19
|
+
if (!expression || typeof expression !== 'string') {
|
|
20
|
+
return { isValid: false, error: 'Expression must be a non-empty string' };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Pattern: {{path.to.field}} | transformation1 | transformation2
|
|
24
|
+
const expressionPattern = /^\s*\{\{[^}]+\}\}(\s*\|\s*[a-zA-Z0-9_]+(\s*\([^)]*\))?)*\s*$/;
|
|
25
|
+
|
|
26
|
+
if (!expressionPattern.test(expression)) {
|
|
27
|
+
return {
|
|
28
|
+
isValid: false,
|
|
29
|
+
error: 'Invalid expression format. Expected: {{path.to.field}} | toUpper | trim'
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Extract path and transformations
|
|
34
|
+
const pathMatch = expression.match(/\{\{([^}]+)\}\}/);
|
|
35
|
+
if (!pathMatch) {
|
|
36
|
+
return { isValid: false, error: 'Path must be wrapped in {{}}' };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Validate transformations (optional)
|
|
40
|
+
const transformations = expression.split('|').slice(1).map(t => t.trim());
|
|
41
|
+
const validTransformations = ['toUpper', 'toLower', 'trim', 'default', 'toNumber', 'toString'];
|
|
42
|
+
for (const trans of transformations) {
|
|
43
|
+
const transName = trans.split('(')[0].trim();
|
|
44
|
+
if (!validTransformations.includes(transName)) {
|
|
45
|
+
return {
|
|
46
|
+
isValid: false,
|
|
47
|
+
error: `Unknown transformation: ${transName}. Valid: ${validTransformations.join(', ')}`
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return { isValid: true, error: null };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Validates field mappings against test payload
|
|
57
|
+
* @param {Object} datasource - Datasource configuration
|
|
58
|
+
* @param {Object} testPayload - Test payload object
|
|
59
|
+
* @returns {Object} Validation results
|
|
60
|
+
*/
|
|
61
|
+
function validateFieldMappings(datasource, testPayload) {
|
|
62
|
+
const results = {
|
|
63
|
+
valid: true,
|
|
64
|
+
errors: [],
|
|
65
|
+
warnings: [],
|
|
66
|
+
mappedFields: {}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
if (!datasource.fieldMappings || !datasource.fieldMappings.fields) {
|
|
70
|
+
results.warnings.push('No field mappings defined');
|
|
71
|
+
return results;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const fields = datasource.fieldMappings.fields;
|
|
75
|
+
const payloadTemplate = testPayload.payloadTemplate || testPayload;
|
|
76
|
+
|
|
77
|
+
// Validate each field mapping expression
|
|
78
|
+
for (const [fieldName, fieldConfig] of Object.entries(fields)) {
|
|
79
|
+
if (!fieldConfig.expression) {
|
|
80
|
+
results.errors.push(`Field '${fieldName}' missing expression`);
|
|
81
|
+
results.valid = false;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Validate expression syntax
|
|
86
|
+
const exprValidation = validateFieldMappingExpression(fieldConfig.expression);
|
|
87
|
+
if (!exprValidation.isValid) {
|
|
88
|
+
results.errors.push(`Field '${fieldName}': ${exprValidation.error}`);
|
|
89
|
+
results.valid = false;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Try to extract path from expression
|
|
94
|
+
const pathMatch = fieldConfig.expression.match(/\{\{([^}]+)\}\}/);
|
|
95
|
+
if (pathMatch) {
|
|
96
|
+
const fieldPath = pathMatch[1].trim();
|
|
97
|
+
// Check if path exists in payload (simple check)
|
|
98
|
+
const pathParts = fieldPath.split('.');
|
|
99
|
+
let current = payloadTemplate;
|
|
100
|
+
let pathExists = true;
|
|
101
|
+
|
|
102
|
+
for (const part of pathParts) {
|
|
103
|
+
if (current && typeof current === 'object' && part in current) {
|
|
104
|
+
current = current[part];
|
|
105
|
+
} else {
|
|
106
|
+
pathExists = false;
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!pathExists) {
|
|
112
|
+
results.warnings.push(`Field '${fieldName}': Path '${fieldPath}' may not exist in payload`);
|
|
113
|
+
} else {
|
|
114
|
+
results.mappedFields[fieldName] = fieldConfig.expression;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return results;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Validates metadata schema against test payload
|
|
124
|
+
* @param {Object} datasource - Datasource configuration
|
|
125
|
+
* @param {Object} testPayload - Test payload object
|
|
126
|
+
* @returns {Object} Validation results
|
|
127
|
+
*/
|
|
128
|
+
function validateMetadataSchema(datasource, testPayload) {
|
|
129
|
+
const results = {
|
|
130
|
+
valid: true,
|
|
131
|
+
errors: [],
|
|
132
|
+
warnings: []
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
if (!datasource.metadataSchema) {
|
|
136
|
+
results.warnings.push('No metadata schema defined');
|
|
137
|
+
return results;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const payloadTemplate = testPayload.payloadTemplate || testPayload;
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
144
|
+
const validate = ajv.compile(datasource.metadataSchema);
|
|
145
|
+
const valid = validate(payloadTemplate);
|
|
146
|
+
|
|
147
|
+
if (!valid) {
|
|
148
|
+
results.valid = false;
|
|
149
|
+
results.errors = validate.errors.map(err => {
|
|
150
|
+
const path = err.instancePath || err.schemaPath;
|
|
151
|
+
return `${path} ${err.message}`;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
} catch (error) {
|
|
155
|
+
results.valid = false;
|
|
156
|
+
results.errors.push(`Schema validation error: ${error.message}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return results;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Normalizes schema to handle nullable without type
|
|
164
|
+
* @param {Object} schema - Schema to normalize
|
|
165
|
+
* @returns {Object} Normalized schema
|
|
166
|
+
*/
|
|
167
|
+
function normalizeSchema(schema) {
|
|
168
|
+
if (typeof schema !== 'object' || schema === null) {
|
|
169
|
+
return schema;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const normalized = Array.isArray(schema) ? [...schema] : { ...schema };
|
|
173
|
+
|
|
174
|
+
if (normalized.nullable === true && !normalized.type) {
|
|
175
|
+
normalized.type = ['null', 'string', 'number', 'boolean', 'object', 'array'];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
for (const key in normalized) {
|
|
179
|
+
if (typeof normalized[key] === 'object' && normalized[key] !== null) {
|
|
180
|
+
normalized[key] = normalizeSchema(normalized[key]);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return normalized;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Validates JSON against schema
|
|
189
|
+
* @param {Object} data - Data to validate
|
|
190
|
+
* @param {Object} schema - JSON schema
|
|
191
|
+
* @returns {Object} Validation results
|
|
192
|
+
*/
|
|
193
|
+
function validateAgainstSchema(data, schema) {
|
|
194
|
+
const ajv = new Ajv({
|
|
195
|
+
allErrors: true,
|
|
196
|
+
strict: false,
|
|
197
|
+
allowUnionTypes: true,
|
|
198
|
+
validateSchema: false
|
|
199
|
+
});
|
|
200
|
+
// Remove $schema for draft-2020-12 to avoid AJV issues
|
|
201
|
+
const schemaCopy = { ...schema };
|
|
202
|
+
if (schemaCopy.$schema && schemaCopy.$schema.includes('2020-12')) {
|
|
203
|
+
delete schemaCopy.$schema;
|
|
204
|
+
}
|
|
205
|
+
// Normalize schema to handle nullable without type
|
|
206
|
+
const normalizedSchema = normalizeSchema(schemaCopy);
|
|
207
|
+
const validate = ajv.compile(normalizedSchema);
|
|
208
|
+
const valid = validate(data);
|
|
209
|
+
|
|
210
|
+
if (!valid) {
|
|
211
|
+
// Filter out additionalProperties errors for required properties that aren't defined in schema
|
|
212
|
+
// This handles schema inconsistencies where authentication is required but not defined in properties
|
|
213
|
+
const filteredErrors = validate.errors.filter(err => {
|
|
214
|
+
if (err.keyword === 'additionalProperties' && err.params?.additionalProperty === 'authentication') {
|
|
215
|
+
// Check if authentication is in required array
|
|
216
|
+
const required = normalizedSchema.required || [];
|
|
217
|
+
if (required.includes('authentication')) {
|
|
218
|
+
return false; // Ignore this error since authentication is required but not defined
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return true;
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
valid: filteredErrors.length === 0,
|
|
226
|
+
errors: filteredErrors.map(err => {
|
|
227
|
+
const path = err.instancePath || err.schemaPath;
|
|
228
|
+
return `${path} ${err.message}`;
|
|
229
|
+
})
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
valid: true,
|
|
235
|
+
errors: []
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
module.exports = {
|
|
240
|
+
validateFieldMappingExpression,
|
|
241
|
+
validateFieldMappings,
|
|
242
|
+
validateMetadataSchema,
|
|
243
|
+
validateAgainstSchema
|
|
244
|
+
};
|
|
245
|
+
|
package/lib/utils/paths.js
CHANGED
|
@@ -91,9 +91,159 @@ function getDevDirectory(appName, developerId) {
|
|
|
91
91
|
return baseDir;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Gets the application path (builder or integration folder)
|
|
96
|
+
* @param {string} appName - Application name
|
|
97
|
+
* @param {string} [appType] - Application type ('external' or other)
|
|
98
|
+
* @returns {string} Absolute path to application directory
|
|
99
|
+
*/
|
|
100
|
+
function getAppPath(appName, appType) {
|
|
101
|
+
if (!appName || typeof appName !== 'string') {
|
|
102
|
+
throw new Error('App name is required and must be a string');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const baseDir = appType === 'external' ? 'integration' : 'builder';
|
|
106
|
+
return path.join(process.cwd(), baseDir, appName);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Gets the integration folder path for external systems
|
|
111
|
+
* @param {string} appName - Application name
|
|
112
|
+
* @returns {string} Absolute path to integration directory
|
|
113
|
+
*/
|
|
114
|
+
function getIntegrationPath(appName) {
|
|
115
|
+
if (!appName || typeof appName !== 'string') {
|
|
116
|
+
throw new Error('App name is required and must be a string');
|
|
117
|
+
}
|
|
118
|
+
return path.join(process.cwd(), 'integration', appName);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Gets the builder folder path for regular applications
|
|
123
|
+
* @param {string} appName - Application name
|
|
124
|
+
* @returns {string} Absolute path to builder directory
|
|
125
|
+
*/
|
|
126
|
+
function getBuilderPath(appName) {
|
|
127
|
+
if (!appName || typeof appName !== 'string') {
|
|
128
|
+
throw new Error('App name is required and must be a string');
|
|
129
|
+
}
|
|
130
|
+
return path.join(process.cwd(), 'builder', appName);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Resolves the deployment JSON file path for an application
|
|
135
|
+
* Uses consistent naming: <app-name>-deploy.json
|
|
136
|
+
* Supports backward compatibility with aifabrix-deploy.json
|
|
137
|
+
*
|
|
138
|
+
* @param {string} appName - Application name
|
|
139
|
+
* @param {string} [appType] - Application type ('external' or other)
|
|
140
|
+
* @param {boolean} [preferNew] - If true, only return new naming (no backward compat)
|
|
141
|
+
* @returns {string} Absolute path to deployment JSON file
|
|
142
|
+
*/
|
|
143
|
+
function getDeployJsonPath(appName, appType, preferNew = false) {
|
|
144
|
+
if (!appName || typeof appName !== 'string') {
|
|
145
|
+
throw new Error('App name is required and must be a string');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const appPath = getAppPath(appName, appType);
|
|
149
|
+
const newPath = path.join(appPath, `${appName}-deploy.json`);
|
|
150
|
+
|
|
151
|
+
// If preferNew is true, always return new naming
|
|
152
|
+
if (preferNew) {
|
|
153
|
+
return newPath;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Check if new naming exists, otherwise fall back to old naming for backward compatibility
|
|
157
|
+
const oldPath = path.join(appPath, 'aifabrix-deploy.json');
|
|
158
|
+
if (fs.existsSync(newPath)) {
|
|
159
|
+
return newPath;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Fall back to old naming for backward compatibility
|
|
163
|
+
if (fs.existsSync(oldPath)) {
|
|
164
|
+
return oldPath;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// If neither exists, return new naming (for generation)
|
|
168
|
+
return newPath;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Detects if an app is external type by checking variables.yaml
|
|
173
|
+
* Checks both integration/ and builder/ folders for backward compatibility
|
|
174
|
+
*
|
|
175
|
+
* @param {string} appName - Application name
|
|
176
|
+
* @returns {Promise<{isExternal: boolean, appPath: string, appType: string}>}
|
|
177
|
+
*/
|
|
178
|
+
async function detectAppType(appName) {
|
|
179
|
+
if (!appName || typeof appName !== 'string') {
|
|
180
|
+
throw new Error('App name is required and must be a string');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Check integration folder first (new structure)
|
|
184
|
+
const integrationPath = getIntegrationPath(appName);
|
|
185
|
+
const integrationVariablesPath = path.join(integrationPath, 'variables.yaml');
|
|
186
|
+
|
|
187
|
+
if (fs.existsSync(integrationVariablesPath)) {
|
|
188
|
+
try {
|
|
189
|
+
const content = fs.readFileSync(integrationVariablesPath, 'utf8');
|
|
190
|
+
const variables = yaml.load(content);
|
|
191
|
+
if (variables.app && variables.app.type === 'external') {
|
|
192
|
+
return {
|
|
193
|
+
isExternal: true,
|
|
194
|
+
appPath: integrationPath,
|
|
195
|
+
appType: 'external',
|
|
196
|
+
baseDir: 'integration'
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
} catch {
|
|
200
|
+
// Ignore errors, continue to check builder folder
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Check builder folder (backward compatibility)
|
|
205
|
+
const builderPath = getBuilderPath(appName);
|
|
206
|
+
const builderVariablesPath = path.join(builderPath, 'variables.yaml');
|
|
207
|
+
|
|
208
|
+
if (fs.existsSync(builderVariablesPath)) {
|
|
209
|
+
try {
|
|
210
|
+
const content = fs.readFileSync(builderVariablesPath, 'utf8');
|
|
211
|
+
const variables = yaml.load(content);
|
|
212
|
+
const isExternal = variables.app && variables.app.type === 'external';
|
|
213
|
+
return {
|
|
214
|
+
isExternal,
|
|
215
|
+
appPath: builderPath,
|
|
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
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Default to builder folder if neither exists
|
|
231
|
+
return {
|
|
232
|
+
isExternal: false,
|
|
233
|
+
appPath: builderPath,
|
|
234
|
+
appType: 'regular',
|
|
235
|
+
baseDir: 'builder'
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
94
239
|
module.exports = {
|
|
95
240
|
getAifabrixHome,
|
|
96
241
|
getApplicationsBaseDir,
|
|
97
|
-
getDevDirectory
|
|
242
|
+
getDevDirectory,
|
|
243
|
+
getAppPath,
|
|
244
|
+
getIntegrationPath,
|
|
245
|
+
getBuilderPath,
|
|
246
|
+
getDeployJsonPath,
|
|
247
|
+
detectAppType
|
|
98
248
|
};
|
|
99
249
|
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const yaml = require('js-yaml');
|
|
15
|
+
const { detectAppType } = require('./paths');
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Resolves schemaBasePath from application variables.yaml
|
|
@@ -32,7 +33,9 @@ async function resolveSchemaBasePath(appName) {
|
|
|
32
33
|
throw new Error('App name is required and must be a string');
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
// Detect app type and get correct path (integration or builder)
|
|
37
|
+
const { appPath } = await detectAppType(appName);
|
|
38
|
+
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
36
39
|
|
|
37
40
|
if (!fs.existsSync(variablesPath)) {
|
|
38
41
|
throw new Error(`variables.yaml not found: ${variablesPath}`);
|
|
@@ -104,7 +107,9 @@ async function resolveExternalFiles(appName) {
|
|
|
104
107
|
throw new Error('App name is required and must be a string');
|
|
105
108
|
}
|
|
106
109
|
|
|
107
|
-
|
|
110
|
+
// Detect app type and get correct path (integration or builder)
|
|
111
|
+
const { appPath } = await detectAppType(appName);
|
|
112
|
+
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
108
113
|
|
|
109
114
|
if (!fs.existsSync(variablesPath)) {
|
|
110
115
|
throw new Error(`variables.yaml not found: ${variablesPath}`);
|
package/lib/validator.js
CHANGED
|
@@ -19,6 +19,7 @@ const externalDataSourceSchema = require('./schema/external-datasource.schema.js
|
|
|
19
19
|
const { transformVariablesForValidation } = require('./utils/variable-transformer');
|
|
20
20
|
const { checkEnvironment } = require('./utils/environment-checker');
|
|
21
21
|
const { formatValidationErrors } = require('./utils/error-formatter');
|
|
22
|
+
const { detectAppType } = require('./utils/paths');
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* Validates variables.yaml file against application schema
|
|
@@ -39,7 +40,9 @@ async function validateVariables(appName) {
|
|
|
39
40
|
throw new Error('App name is required and must be a string');
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
|
|
43
|
+
// Detect app type and get correct path (integration or builder)
|
|
44
|
+
const { appPath } = await detectAppType(appName);
|
|
45
|
+
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
43
46
|
|
|
44
47
|
if (!fs.existsSync(variablesPath)) {
|
|
45
48
|
throw new Error(`variables.yaml not found: ${variablesPath}`);
|
|
@@ -254,7 +257,7 @@ async function validateEnvTemplate(appName) {
|
|
|
254
257
|
|
|
255
258
|
/**
|
|
256
259
|
* Validates deployment JSON against application schema
|
|
257
|
-
* Ensures generated
|
|
260
|
+
* Ensures generated <app-name>-deploy.json matches the schema structure
|
|
258
261
|
*
|
|
259
262
|
* @function validateDeploymentJson
|
|
260
263
|
* @param {Object} deployment - Deployment JSON object to validate
|