@aifabrix/builder 2.41.0 → 2.42.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/docs-rules.mdc +30 -0
- package/README.md +1 -1
- package/integration/hubspot/README.md +8 -4
- package/integration/hubspot/application.json +54 -0
- package/integration/hubspot/create-hubspot.js +9 -136
- package/integration/hubspot/env.template +3 -4
- package/integration/hubspot/hubspot-datasource-company.json +343 -5
- package/integration/hubspot/hubspot-datasource-contact.json +413 -5
- package/integration/hubspot/hubspot-datasource-deal.json +341 -4
- package/integration/hubspot/hubspot-datasource-users.json +116 -0
- package/integration/hubspot/hubspot-deploy.json +1250 -108
- package/integration/hubspot/hubspot-system.json +15 -32
- package/integration/hubspot/test-dataplane-down-tests.js +17 -16
- package/integration/hubspot/test-dataplane-down.js +2 -2
- package/jest.config.manual.js +2 -1
- package/lib/api/external-test.api.js +111 -0
- package/lib/api/index.js +42 -19
- package/lib/api/pipeline.api.js +66 -120
- package/lib/api/types/pipeline.types.js +37 -0
- package/lib/api/wizard-platform.api.js +61 -0
- package/lib/api/wizard.api.js +34 -1
- package/lib/app/config.js +23 -11
- package/lib/app/index.js +3 -1
- package/lib/app/prompts.js +44 -29
- package/lib/app/readme.js +8 -3
- package/lib/app/run-env-compose.js +64 -1
- package/lib/app/run-helpers.js +1 -1
- package/lib/app/show-display.js +1 -1
- package/lib/cli/setup-app.js +42 -11
- package/lib/cli/setup-credential-deployment.js +31 -6
- package/lib/cli/setup-dev.js +27 -0
- package/lib/cli/setup-environment.js +12 -4
- package/lib/cli/setup-external-system.js +19 -4
- package/lib/cli/setup-infra.js +54 -14
- package/lib/cli/setup-utility.js +117 -21
- package/lib/commands/credential-env.js +162 -0
- package/lib/commands/credential-list.js +17 -22
- package/lib/commands/credential-push.js +96 -0
- package/lib/commands/datasource.js +77 -6
- package/lib/commands/dev-init.js +39 -1
- package/lib/commands/repair-auth-config.js +99 -0
- package/lib/commands/repair-datasource-keys.js +208 -0
- package/lib/commands/repair-datasource.js +235 -0
- package/lib/commands/repair-env-template.js +348 -0
- package/lib/commands/repair-internal.js +85 -0
- package/lib/commands/repair-rbac.js +158 -0
- package/lib/commands/repair.js +507 -0
- package/lib/commands/test-e2e-external.js +165 -0
- package/lib/commands/upload.js +71 -40
- package/lib/commands/wizard-core-helpers.js +226 -4
- package/lib/commands/wizard-core.js +67 -29
- package/lib/commands/wizard-dataplane.js +1 -1
- package/lib/commands/wizard-entity-selection.js +43 -0
- package/lib/commands/wizard-headless.js +44 -5
- package/lib/commands/wizard-helpers.js +7 -3
- package/lib/commands/wizard.js +86 -64
- package/lib/core/config.js +7 -1
- package/lib/core/secrets.js +33 -12
- package/lib/datasource/deploy.js +12 -3
- package/lib/datasource/test-e2e.js +219 -0
- package/lib/datasource/test-integration.js +154 -0
- package/lib/deployment/deployer.js +7 -5
- package/lib/external-system/download.js +182 -204
- package/lib/external-system/generator.js +204 -56
- package/lib/external-system/test-execution.js +2 -1
- package/lib/external-system/test-system-level.js +73 -0
- package/lib/external-system/test.js +51 -18
- package/lib/generator/external-controller-manifest.js +29 -2
- package/lib/generator/external-schema-utils.js +1 -1
- package/lib/generator/external.js +10 -3
- package/lib/generator/index.js +4 -1
- package/lib/generator/split-readme.js +1 -0
- package/lib/generator/split-variables.js +7 -1
- package/lib/generator/split.js +194 -54
- package/lib/generator/wizard-prompts-secondary.js +294 -0
- package/lib/generator/wizard-prompts.js +105 -106
- package/lib/generator/wizard-readme.js +88 -0
- package/lib/generator/wizard.js +147 -158
- package/lib/infrastructure/compose.js +11 -1
- package/lib/infrastructure/index.js +11 -3
- package/lib/infrastructure/services.js +22 -11
- package/lib/schema/application-schema.json +8 -5
- package/lib/schema/external-datasource.schema.json +49 -26
- package/lib/schema/external-system.schema.json +82 -6
- package/lib/schema/wizard-config.schema.json +16 -0
- package/lib/utils/api.js +38 -10
- package/lib/utils/auth-headers.js +8 -7
- package/lib/utils/compose-generator.js +1 -1
- package/lib/utils/compose-handlebars-helpers.js +11 -0
- package/lib/utils/config-format-preference.js +51 -0
- package/lib/utils/config-format.js +36 -0
- package/lib/utils/configuration-env-resolver.js +179 -0
- package/lib/utils/credential-display.js +83 -0
- package/lib/utils/credential-secrets-env.js +115 -25
- package/lib/utils/dataplane-pipeline-warning.js +28 -0
- package/lib/utils/deployment-validation-helpers.js +4 -4
- package/lib/utils/dev-ca-install.js +139 -0
- package/lib/utils/env-copy.js +23 -3
- package/lib/utils/error-formatters/http-status-errors.js +0 -1
- package/lib/utils/error-formatters/permission-errors.js +0 -1
- package/lib/utils/error-formatters/validation-errors.js +0 -1
- package/lib/utils/external-readme.js +56 -29
- package/lib/utils/external-system-display.js +59 -1
- package/lib/utils/external-system-test-helpers.js +21 -8
- package/lib/utils/external-system-validators.js +3 -0
- package/lib/utils/file-upload.js +20 -50
- package/lib/utils/help-builder.js +1 -0
- package/lib/utils/infra-status.js +50 -44
- package/lib/utils/local-secrets.js +5 -5
- package/lib/utils/paths.js +85 -4
- package/lib/utils/secrets-canonical.js +93 -0
- package/lib/utils/secrets-generator.js +20 -0
- package/lib/utils/secrets-helpers.js +75 -89
- package/lib/utils/test-log-writer.js +56 -0
- package/lib/utils/token-manager.js +24 -32
- package/lib/validation/env-template-auth.js +157 -0
- package/lib/validation/env-template-kv.js +41 -0
- package/lib/validation/external-manifest-validator.js +25 -0
- package/lib/validation/external-system-auth-rules.js +86 -0
- package/lib/validation/validate-batch.js +149 -0
- package/lib/validation/validate-datasource-keys-api.js +33 -0
- package/lib/validation/validate-display.js +94 -16
- package/lib/validation/validate.js +25 -12
- package/lib/validation/validator.js +7 -9
- package/lib/validation/wizard-datasource-validation.js +50 -0
- package/package.json +7 -2
- package/templates/applications/dataplane/application.yaml +1 -1
- package/templates/applications/dataplane/env.template +5 -5
- package/templates/applications/dataplane/rbac.yaml +2 -2
- package/templates/applications/miso-controller/env.template +1 -1
- package/templates/external-system/README.md.hbs +65 -25
- package/templates/external-system/deploy.js.hbs +4 -2
- package/templates/external-system/external-datasource.yaml.hbs +217 -0
- package/templates/external-system/external-system.json.hbs +1 -18
- package/templates/infra/compose.yaml.hbs +6 -0
- package/templates/python/docker-compose.hbs +4 -4
- package/templates/typescript/docker-compose.hbs +4 -4
- package/integration/hubspot/application.yaml +0 -37
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* env.template authentication kv:// validation helpers
|
|
3
|
+
*
|
|
4
|
+
* Collects required kv paths from system configs and extracts kv paths from env.template
|
|
5
|
+
* for validating that external integrations have all authentication secrets in env.template.
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview Auth kv validation for env.template
|
|
8
|
+
* @author AI Fabrix Team
|
|
9
|
+
* @version 2.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { loadExternalIntegrationConfig, loadSystemFile } = require('../generator/external');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Extracts all kv:// paths from env.template content (RHS of VAR=value lines).
|
|
16
|
+
* Uses same regex as validateKvReferencesInLines.
|
|
17
|
+
*
|
|
18
|
+
* @function extractKvPathsFromEnvTemplate
|
|
19
|
+
* @param {string} content - env.template file content
|
|
20
|
+
* @returns {Set<string>} Set of kv:// paths found (e.g. kv://hubspot/client-id)
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* const paths = extractKvPathsFromEnvTemplate('CLIENT_ID=kv://hubspot/client-id\nPORT=3000');
|
|
24
|
+
* // paths has 'kv://hubspot/client-id'
|
|
25
|
+
*/
|
|
26
|
+
function extractKvPathsFromEnvTemplate(content) {
|
|
27
|
+
const paths = new Set();
|
|
28
|
+
const lines = content.split('\n');
|
|
29
|
+
for (const line of lines) {
|
|
30
|
+
const trimmed = line.trim();
|
|
31
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
32
|
+
if (!trimmed.includes('=')) continue;
|
|
33
|
+
const [_key, value] = trimmed.split('=', 2);
|
|
34
|
+
const val = (value || '').trim();
|
|
35
|
+
const matches = val.match(/kv:\/\/[^\s]*/g) || [];
|
|
36
|
+
for (const fullRef of matches) {
|
|
37
|
+
paths.add(fullRef);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return paths;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Extracts kv:// paths from commented lines in env.template (e.g. # KEY=kv://path or # kv://path).
|
|
45
|
+
* Used to treat commented-out keys as intentionally disabled for auth-coverage validation.
|
|
46
|
+
* Scans the whole line after '#' so both key=value and bare/commented refs are recognized.
|
|
47
|
+
*
|
|
48
|
+
* @function extractKvPathsFromCommentedLines
|
|
49
|
+
* @param {string} content - env.template file content
|
|
50
|
+
* @returns {Set<string>} Set of kv:// paths found in commented lines (e.g. kv://avoma/apikey)
|
|
51
|
+
*/
|
|
52
|
+
function extractKvPathsFromCommentedLines(content) {
|
|
53
|
+
const paths = new Set();
|
|
54
|
+
const lines = content.split('\n');
|
|
55
|
+
for (const line of lines) {
|
|
56
|
+
const trimmed = line.trim();
|
|
57
|
+
if (!trimmed.startsWith('#')) continue;
|
|
58
|
+
const afterHash = trimmed.slice(1).trim();
|
|
59
|
+
const matches = afterHash.match(/kv:\/\/[^\s]*/g) || [];
|
|
60
|
+
for (const fullRef of matches) {
|
|
61
|
+
paths.add(fullRef);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return paths;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Returns true if the required path is present in the set or matches any entry case-insensitively.
|
|
69
|
+
* Used so commented-out keys match required paths regardless of kv path casing (e.g. apiKey vs apikey).
|
|
70
|
+
*
|
|
71
|
+
* @function setHasPathIgnoreCase
|
|
72
|
+
* @param {Set<string>} pathSet - Set of kv paths (e.g. from commented lines)
|
|
73
|
+
* @param {string} requiredPath - Required path from authentication.security
|
|
74
|
+
* @returns {boolean} True if requiredPath is in pathSet or matches any element when lowercased
|
|
75
|
+
*/
|
|
76
|
+
function setHasPathIgnoreCase(pathSet, requiredPath) {
|
|
77
|
+
if (pathSet.has(requiredPath)) return true;
|
|
78
|
+
const requiredLower = requiredPath.toLowerCase();
|
|
79
|
+
for (const p of pathSet) {
|
|
80
|
+
if (p.toLowerCase() === requiredLower) return true;
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Collects required kv:// paths from authentication.security of all system configs.
|
|
87
|
+
* For external integrations only. On config load failure, returns empty set and optional warning.
|
|
88
|
+
*
|
|
89
|
+
* @async
|
|
90
|
+
* @function collectRequiredAuthKvPaths
|
|
91
|
+
* @param {string} appPath - Application directory path
|
|
92
|
+
* @param {Object} [options] - Options (reserved)
|
|
93
|
+
* @returns {Promise<{ requiredPaths: Set<string>, warning?: string }>} Required kv paths and optional warning
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* const { requiredPaths } = await collectRequiredAuthKvPaths('/path/to/integration/hubspot');
|
|
97
|
+
* // requiredPaths has kv:// paths from authentication.security
|
|
98
|
+
*/
|
|
99
|
+
async function collectRequiredAuthKvPaths(appPath, _options = {}) {
|
|
100
|
+
const requiredPaths = new Set();
|
|
101
|
+
try {
|
|
102
|
+
const { schemaBasePath, systemFiles } = await loadExternalIntegrationConfig(appPath);
|
|
103
|
+
for (const systemFileName of systemFiles) {
|
|
104
|
+
const systemJson = await loadSystemFile(appPath, schemaBasePath, systemFileName);
|
|
105
|
+
const security = systemJson.authentication?.security;
|
|
106
|
+
if (!security || typeof security !== 'object') continue;
|
|
107
|
+
for (const val of Object.values(security)) {
|
|
108
|
+
if (typeof val === 'string' && /^kv:\/\/.+/.test(val)) {
|
|
109
|
+
requiredPaths.add(val);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return { requiredPaths };
|
|
114
|
+
} catch (error) {
|
|
115
|
+
return {
|
|
116
|
+
requiredPaths: new Set(),
|
|
117
|
+
warning: `Could not validate auth kv coverage (skip auth check): ${error.message}`
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Validates that env.template covers all authentication.security kv paths for external apps.
|
|
124
|
+
* Modifies errors and warnings in place.
|
|
125
|
+
*
|
|
126
|
+
* @async
|
|
127
|
+
* @function validateAuthKvCoverage
|
|
128
|
+
* @param {string} appPath - Application path
|
|
129
|
+
* @param {string} content - env.template content
|
|
130
|
+
* @param {string[]} errors - Errors array to push to
|
|
131
|
+
* @param {string[]} warnings - Warnings array to push to
|
|
132
|
+
* @param {Object} [options] - Options
|
|
133
|
+
*/
|
|
134
|
+
async function validateAuthKvCoverage(appPath, content, errors, warnings, options = {}) {
|
|
135
|
+
const authResult = await collectRequiredAuthKvPaths(appPath, options);
|
|
136
|
+
if (authResult.warning) warnings.push(authResult.warning);
|
|
137
|
+
if (authResult.requiredPaths.size === 0) return;
|
|
138
|
+
const actualPaths = extractKvPathsFromEnvTemplate(content);
|
|
139
|
+
const commentedPaths = extractKvPathsFromCommentedLines(content);
|
|
140
|
+
for (const requiredPath of authResult.requiredPaths) {
|
|
141
|
+
const inActive = actualPaths.has(requiredPath);
|
|
142
|
+
const inCommented = setHasPathIgnoreCase(commentedPaths, requiredPath);
|
|
143
|
+
if (!inActive && !inCommented) {
|
|
144
|
+
errors.push(
|
|
145
|
+
`env.template: Missing required authentication secret (required by authentication.security): add a variable with value ${requiredPath}`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
module.exports = {
|
|
152
|
+
extractKvPathsFromEnvTemplate,
|
|
153
|
+
extractKvPathsFromCommentedLines,
|
|
154
|
+
setHasPathIgnoreCase,
|
|
155
|
+
collectRequiredAuthKvPaths,
|
|
156
|
+
validateAuthKvCoverage
|
|
157
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* env.template kv:// reference validation (syntax only).
|
|
3
|
+
* Skips comment and empty lines. Used by validator.validateEnvTemplate.
|
|
4
|
+
*
|
|
5
|
+
* @fileoverview Kv reference validation for env.template lines
|
|
6
|
+
* @author AI Fabrix Team
|
|
7
|
+
* @version 2.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Validates kv:// references in env.template lines; pushes errors into the given array.
|
|
12
|
+
* Skips empty and comment (#) lines.
|
|
13
|
+
*
|
|
14
|
+
* @function validateKvReferencesInLines
|
|
15
|
+
* @param {string[]} lines - Lines of env.template content
|
|
16
|
+
* @param {string[]} errors - Array to push error messages into
|
|
17
|
+
*/
|
|
18
|
+
function validateKvReferencesInLines(lines, errors) {
|
|
19
|
+
lines.forEach((line, index) => {
|
|
20
|
+
const trimmed = line.trim();
|
|
21
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const matches = line.match(/kv:\/\/[^\s]*/g) || [];
|
|
25
|
+
for (const fullRef of matches) {
|
|
26
|
+
const pathMatch = fullRef.match(/^kv:\/\/(.*)$/);
|
|
27
|
+
const pathStr = pathMatch ? pathMatch[1] : '';
|
|
28
|
+
const invalid = !pathStr || pathStr.startsWith('/') || pathStr.endsWith('/');
|
|
29
|
+
if (invalid) {
|
|
30
|
+
const hint = !pathStr
|
|
31
|
+
? 'path is empty (use kv://secret-key)'
|
|
32
|
+
: pathStr.startsWith('/')
|
|
33
|
+
? 'path must not start with / (use kv://secret-key not kv:///secret-key)'
|
|
34
|
+
: 'path must not end with / (use kv://secret-key not kv://secret-key/)';
|
|
35
|
+
errors.push(`env.template line ${index + 1}: Invalid kv:// reference "${fullRef}" - ${hint}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = { validateKvReferencesInLines };
|
|
@@ -156,6 +156,30 @@ function validateRequiredFields(manifest, errors) {
|
|
|
156
156
|
});
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Validates that each datasource's systemKey matches the application system key.
|
|
161
|
+
* Matches dataplane upload API wording so integrators recognize the error.
|
|
162
|
+
*
|
|
163
|
+
* @function validateDatasourceSystemKeyAlignment
|
|
164
|
+
* @param {Object} manifest - Manifest object with system and dataSources
|
|
165
|
+
* @param {Array} errors - Errors array to append to
|
|
166
|
+
* @returns {void}
|
|
167
|
+
*/
|
|
168
|
+
function validateDatasourceSystemKeyAlignment(manifest, errors) {
|
|
169
|
+
const systemKey = manifest.system?.key;
|
|
170
|
+
if (!manifest.dataSources || !Array.isArray(manifest.dataSources) || systemKey === null || systemKey === undefined || systemKey === '') {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
manifest.dataSources.forEach((datasource) => {
|
|
174
|
+
if (datasource.systemKey !== systemKey) {
|
|
175
|
+
const dsKey = datasource.key || 'unknown';
|
|
176
|
+
errors.push(
|
|
177
|
+
`Data source '${dsKey}' systemKey does not match application system key (expected '${systemKey}', got '${datasource.systemKey}')`
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
159
183
|
/**
|
|
160
184
|
* Validates controller deployment manifest for external systems
|
|
161
185
|
* Validates manifest structure and inline system/dataSources against their schemas
|
|
@@ -187,6 +211,7 @@ async function validateControllerManifest(manifest) {
|
|
|
187
211
|
validateManifestStructure(manifest, ajv, applicationSchema, errors);
|
|
188
212
|
validateInlineSystem(manifest, ajv, externalSystemSchema, errors);
|
|
189
213
|
validateDatasources(manifest, ajv, externalDatasourceSchema, errors, warnings);
|
|
214
|
+
validateDatasourceSystemKeyAlignment(manifest, errors);
|
|
190
215
|
validateConditionalRequirements(manifest, errors, warnings);
|
|
191
216
|
validateRequiredFields(manifest, errors);
|
|
192
217
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External system authentication rules (OAuth2/AAD grantType, authorizationUrl, configuration).
|
|
3
|
+
* Used after schema validation for external system files.
|
|
4
|
+
*
|
|
5
|
+
* @fileoverview OAuth2/AAD and configuration rules for external system configs
|
|
6
|
+
* @author AI Fabrix Team
|
|
7
|
+
* @version 2.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const VALID_GRANT_TYPES = ['client_credentials', 'authorization_code'];
|
|
11
|
+
|
|
12
|
+
/** Standard auth variable names (credential parameters supplied at runtime). Not allowed in configuration except BASEURL when auth is none. */
|
|
13
|
+
const STANDARD_AUTH_VAR_NAMES = new Set([
|
|
14
|
+
'baseurl', 'clientid', 'clientsecret', 'tokenurl', 'apikey', 'username', 'password'
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
function trimVar(value) {
|
|
18
|
+
return (value !== undefined && value !== null ? String(value).trim() : '');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isOAuth2OrAad(method) {
|
|
22
|
+
const m = (method && String(method).toLowerCase()) || '';
|
|
23
|
+
return m === 'oauth2' || m === 'aad';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Validates OAuth2/AAD grantType and conditional authorizationUrl for external system files.
|
|
28
|
+
* When method is oauth2 or aad: grantType (if present) must be client_credentials or authorization_code;
|
|
29
|
+
* when effective grant is authorization_code (explicit or default), authorizationUrl is required.
|
|
30
|
+
*
|
|
31
|
+
* @function validateOAuth2GrantTypeAndAuthorizationUrl
|
|
32
|
+
* @param {Object} parsed - Parsed external system object (must have authentication.variables when method is oauth2/aad)
|
|
33
|
+
* @param {string[]} errors - Array to push validation error messages into
|
|
34
|
+
*/
|
|
35
|
+
function validateOAuth2GrantTypeAndAuthorizationUrl(parsed, errors) {
|
|
36
|
+
const auth = parsed?.authentication;
|
|
37
|
+
const variables = auth?.variables;
|
|
38
|
+
if (!variables || typeof variables !== 'object' || !isOAuth2OrAad(auth.method)) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const grantType = trimVar(variables.grantType);
|
|
43
|
+
if (grantType !== '' && !VALID_GRANT_TYPES.includes(grantType)) {
|
|
44
|
+
errors.push('authentication.variables.grantType must be one of: client_credentials, authorization_code');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const effectiveGrant = grantType || 'authorization_code';
|
|
49
|
+
if (effectiveGrant === 'authorization_code' && trimVar(variables.authorizationUrl) === '') {
|
|
50
|
+
errors.push('authentication.variables.authorizationUrl is required when grantType is authorization_code or omitted');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Validates that the external system configuration array does not contain standard auth variable names.
|
|
56
|
+
* BASEURL, CLIENTID, CLIENTSECRET, TOKENURL, APIKEY, USERNAME, PASSWORD are credential parameters
|
|
57
|
+
* supplied from the selected credential at runtime and must not be in configuration. Exception:
|
|
58
|
+
* BASEURL is only allowed in configuration when authentication.method is 'none'.
|
|
59
|
+
*
|
|
60
|
+
* @param {Object} parsed - Parsed external system object
|
|
61
|
+
* @param {string[]} errors - Array to push validation error messages into
|
|
62
|
+
*/
|
|
63
|
+
function validateConfigurationNoStandardAuthVariables(parsed, errors) {
|
|
64
|
+
const config = parsed?.configuration;
|
|
65
|
+
if (!Array.isArray(config) || config.length === 0) return;
|
|
66
|
+
const method = (parsed?.authentication?.method && String(parsed.authentication.method).toLowerCase()) || '';
|
|
67
|
+
const authNone = method === 'none';
|
|
68
|
+
const allowedWhenNone = new Set(['baseurl']);
|
|
69
|
+
for (const item of config) {
|
|
70
|
+
const name = (item?.name && String(item.name).trim()) || '';
|
|
71
|
+
if (!name) continue;
|
|
72
|
+
const nameLower = name.toLowerCase();
|
|
73
|
+
if (!STANDARD_AUTH_VAR_NAMES.has(nameLower)) continue;
|
|
74
|
+
if (authNone && allowedWhenNone.has(nameLower)) continue;
|
|
75
|
+
errors.push(
|
|
76
|
+
`configuration must not contain standard auth variable '${name}'. ` +
|
|
77
|
+
'Standard auth variables (BASEURL, CLIENTID, CLIENTSECRET, TOKENURL, APIKEY, USERNAME, PASSWORD) are supplied from the selected credential at runtime. ' +
|
|
78
|
+
'BASEURL is only allowed in configuration when authentication.method is \'none\'.'
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = {
|
|
84
|
+
validateOAuth2GrantTypeAndAuthorizationUrl,
|
|
85
|
+
validateConfigurationNoStandardAuthVariables
|
|
86
|
+
};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batch validation helpers for validate --integration / --builder.
|
|
3
|
+
* @fileoverview Builds batch results and runs validation across multiple apps
|
|
4
|
+
* @author AI Fabrix Team
|
|
5
|
+
* @version 2.0.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { listIntegrationAppNames, listBuilderAppNames } = require('../utils/paths');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Collects error strings from a single validation result (various shapes).
|
|
12
|
+
* @param {Object} result - Single-app validation result
|
|
13
|
+
* @returns {string[]} Error messages
|
|
14
|
+
*/
|
|
15
|
+
function collectResultErrors(result) {
|
|
16
|
+
const errs = [];
|
|
17
|
+
if (result.errors && Array.isArray(result.errors)) {
|
|
18
|
+
errs.push(...result.errors);
|
|
19
|
+
}
|
|
20
|
+
if (result.application && result.application.errors && Array.isArray(result.application.errors)) {
|
|
21
|
+
errs.push(...result.application.errors);
|
|
22
|
+
}
|
|
23
|
+
if (result.steps) {
|
|
24
|
+
['application', 'components', 'manifest'].forEach(step => {
|
|
25
|
+
const s = result.steps[step];
|
|
26
|
+
if (s && s.errors && Array.isArray(s.errors)) {
|
|
27
|
+
errs.push(...s.errors);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return errs;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Collects warning strings from a single validation result.
|
|
36
|
+
* @param {Object} result - Single-app validation result
|
|
37
|
+
* @returns {string[]} Warning messages
|
|
38
|
+
*/
|
|
39
|
+
function collectResultWarnings(result) {
|
|
40
|
+
const w = [];
|
|
41
|
+
if (result.warnings && Array.isArray(result.warnings)) {
|
|
42
|
+
w.push(...result.warnings);
|
|
43
|
+
}
|
|
44
|
+
if (result.application && result.application.warnings && Array.isArray(result.application.warnings)) {
|
|
45
|
+
w.push(...result.application.warnings);
|
|
46
|
+
}
|
|
47
|
+
if (result.steps) {
|
|
48
|
+
['application', 'components', 'manifest'].forEach(step => {
|
|
49
|
+
const s = result.steps[step];
|
|
50
|
+
if (s && s.warnings && Array.isArray(s.warnings)) {
|
|
51
|
+
w.push(...s.warnings);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return w;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Builds batch result from per-app results. Each item has appName and either result or error.
|
|
60
|
+
* @param {Array<{appName: string, result?: Object, error?: string}>} items - Per-app results
|
|
61
|
+
* @returns {{ batch: true, valid: boolean, results: Array, errors: string[], warnings: string[] }}
|
|
62
|
+
*/
|
|
63
|
+
function buildBatchResult(items) {
|
|
64
|
+
const errors = [];
|
|
65
|
+
const warnings = [];
|
|
66
|
+
items.forEach(item => {
|
|
67
|
+
if (item.error) {
|
|
68
|
+
errors.push(`${item.appName}: ${item.error}`);
|
|
69
|
+
} else if (item.result) {
|
|
70
|
+
collectResultErrors(item.result).forEach(e => errors.push(`${item.appName}: ${e}`));
|
|
71
|
+
collectResultWarnings(item.result).forEach(w => warnings.push(`${item.appName}: ${w}`));
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
const valid = items.every(item => item.result && item.result.valid);
|
|
75
|
+
return {
|
|
76
|
+
batch: true,
|
|
77
|
+
valid,
|
|
78
|
+
results: items,
|
|
79
|
+
errors,
|
|
80
|
+
warnings
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Validates all apps under integration/ (each as external system).
|
|
86
|
+
* @async
|
|
87
|
+
* @param {Function} validateAppOrFile - validateAppOrFile(appName, options) from validate.js
|
|
88
|
+
* @param {Object} [options] - Validation options
|
|
89
|
+
* @returns {Promise<{ batch: true, valid: boolean, results: Array, errors: string[], warnings: string[] }>}
|
|
90
|
+
*/
|
|
91
|
+
async function validateAllIntegrations(validateAppOrFile, options = {}) {
|
|
92
|
+
const names = listIntegrationAppNames();
|
|
93
|
+
const items = [];
|
|
94
|
+
for (const appName of names) {
|
|
95
|
+
try {
|
|
96
|
+
const result = await validateAppOrFile(appName, options);
|
|
97
|
+
items.push({ appName, result });
|
|
98
|
+
} catch (error) {
|
|
99
|
+
items.push({ appName, error: error.message || String(error) });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return buildBatchResult(items);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Validates all apps under builder/.
|
|
107
|
+
* @async
|
|
108
|
+
* @param {Function} validateAppOrFile - validateAppOrFile(appName, options) from validate.js
|
|
109
|
+
* @param {Object} [options] - Validation options
|
|
110
|
+
* @returns {Promise<{ batch: true, valid: boolean, results: Array, errors: string[], warnings: string[] }>}
|
|
111
|
+
*/
|
|
112
|
+
async function validateAllBuilderApps(validateAppOrFile, options = {}) {
|
|
113
|
+
const names = listBuilderAppNames();
|
|
114
|
+
const items = [];
|
|
115
|
+
for (const appName of names) {
|
|
116
|
+
try {
|
|
117
|
+
const result = await validateAppOrFile(appName, options);
|
|
118
|
+
items.push({ appName, result });
|
|
119
|
+
} catch (error) {
|
|
120
|
+
items.push({ appName, error: error.message || String(error) });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return buildBatchResult(items);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Validates all integration and builder apps in one run.
|
|
128
|
+
* @async
|
|
129
|
+
* @param {Function} validateAppOrFile - validateAppOrFile(appName, options) from validate.js
|
|
130
|
+
* @param {Object} [options] - Validation options
|
|
131
|
+
* @returns {Promise<{ batch: true, valid: boolean, results: Array, errors: string[], warnings: string[] }>}
|
|
132
|
+
*/
|
|
133
|
+
async function validateAll(validateAppOrFile, options = {}) {
|
|
134
|
+
const [integrationResult, builderResult] = await Promise.all([
|
|
135
|
+
validateAllIntegrations(validateAppOrFile, options),
|
|
136
|
+
validateAllBuilderApps(validateAppOrFile, options)
|
|
137
|
+
]);
|
|
138
|
+
const mergedResults = [...integrationResult.results, ...builderResult.results];
|
|
139
|
+
return buildBatchResult(mergedResults);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
module.exports = {
|
|
143
|
+
buildBatchResult,
|
|
144
|
+
collectResultErrors,
|
|
145
|
+
collectResultWarnings,
|
|
146
|
+
validateAllIntegrations,
|
|
147
|
+
validateAllBuilderApps,
|
|
148
|
+
validateAll
|
|
149
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Async validation of datasourceKeys against dataplane API
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 2.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { getPlatformDetails } = require('../api/wizard.api');
|
|
8
|
+
const { validateDatasourceKeysForPlatform } = require('./wizard-datasource-validation');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Validate datasourceKeys against platform's available datasources; throws if invalid
|
|
12
|
+
* @async
|
|
13
|
+
* @param {string} dataplaneUrl - Dataplane URL
|
|
14
|
+
* @param {Object} authConfig - Auth config
|
|
15
|
+
* @param {string} platformKey - Platform key
|
|
16
|
+
* @param {string[]} datasourceKeys - Datasource keys to validate
|
|
17
|
+
* @throws {Error} If any key is invalid
|
|
18
|
+
*/
|
|
19
|
+
async function validateDatasourceKeysBeforePlatformConfig(dataplaneUrl, authConfig, platformKey, datasourceKeys) {
|
|
20
|
+
if (!Array.isArray(datasourceKeys) || datasourceKeys.length === 0) return;
|
|
21
|
+
const platformDetails = await getPlatformDetails(dataplaneUrl, authConfig, platformKey);
|
|
22
|
+
const datasources = platformDetails?.data?.datasources ?? platformDetails?.datasources ?? [];
|
|
23
|
+
const { valid, invalidKeys } = validateDatasourceKeysForPlatform(datasourceKeys, datasources);
|
|
24
|
+
if (!valid) {
|
|
25
|
+
const availableKeys = datasources.map(d => d.key).filter(Boolean);
|
|
26
|
+
throw new Error(
|
|
27
|
+
`Invalid datasource keys: [${invalidKeys.join(', ')}]. ` +
|
|
28
|
+
`Available for platform '${platformKey}': [${availableKeys.join(', ')}].`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = { validateDatasourceKeysBeforePlatformConfig };
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* @version 2.0.0
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
const path = require('path');
|
|
11
12
|
const chalk = require('chalk');
|
|
12
13
|
const logger = require('../utils/logger');
|
|
13
14
|
const { loadConfigFile } = require('../utils/config-format');
|
|
@@ -242,22 +243,25 @@ function displayApplicationStep(application) {
|
|
|
242
243
|
* @returns {void}
|
|
243
244
|
*/
|
|
244
245
|
function displayComponentsStep(components) {
|
|
245
|
-
|
|
246
|
+
const hasFiles = components.files && components.files.length > 0;
|
|
247
|
+
const hasErrors = components.errors && components.errors.length > 0;
|
|
248
|
+
if (!hasFiles && !hasErrors) {
|
|
246
249
|
return;
|
|
247
250
|
}
|
|
248
251
|
|
|
249
252
|
logger.log(chalk.blue('\nExternal Integration Files:'));
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
253
|
+
if (hasFiles) {
|
|
254
|
+
components.files.forEach(file => {
|
|
255
|
+
if (file.valid) {
|
|
256
|
+
logger.log(chalk.green(` ✓ ${file.file} (${file.type})`));
|
|
257
|
+
} else {
|
|
258
|
+
logger.log(chalk.red(` ✗ ${file.file} (${file.type})`));
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
if (hasErrors) {
|
|
259
263
|
components.errors.forEach(error => {
|
|
260
|
-
logger.log(chalk.red(`
|
|
264
|
+
logger.log(chalk.red(` • ${error}`));
|
|
261
265
|
});
|
|
262
266
|
}
|
|
263
267
|
}
|
|
@@ -301,7 +305,9 @@ function displayDimensionsStep(datasourceFiles) {
|
|
|
301
305
|
*/
|
|
302
306
|
function displayManifestStep(manifest, componentFiles) {
|
|
303
307
|
logger.log(chalk.blue('\nDeployment Manifest:'));
|
|
304
|
-
if (manifest.
|
|
308
|
+
if (manifest.skipped) {
|
|
309
|
+
logger.log(chalk.yellow(' ⊘ Skipped (fix errors above first)'));
|
|
310
|
+
} else if (manifest.valid) {
|
|
305
311
|
logger.log(chalk.green(' ✓ Full deployment manifest is valid'));
|
|
306
312
|
if (componentFiles) {
|
|
307
313
|
const datasourceFiles = componentFiles.filter(f => f.type === 'datasource' || f.type === 'external-datasource');
|
|
@@ -311,10 +317,14 @@ function displayManifestStep(manifest, componentFiles) {
|
|
|
311
317
|
}
|
|
312
318
|
} else {
|
|
313
319
|
logger.log(chalk.red(' ✗ Full deployment manifest validation failed:'));
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
320
|
+
const errs = manifest.errors && manifest.errors.length > 0 ? manifest.errors : [];
|
|
321
|
+
if (errs.length > 0) {
|
|
322
|
+
errs.forEach(error => {
|
|
323
|
+
const msg = typeof error === 'string' ? error : String(error);
|
|
324
|
+
logger.log(chalk.red(` • ${msg}`));
|
|
317
325
|
});
|
|
326
|
+
} else {
|
|
327
|
+
logger.log(chalk.red(' • No error details available (check schema and manifest structure).'));
|
|
318
328
|
}
|
|
319
329
|
}
|
|
320
330
|
if (manifest.warnings && manifest.warnings.length > 0) {
|
|
@@ -338,7 +348,7 @@ function displayStepByStepValidation(result) {
|
|
|
338
348
|
|
|
339
349
|
displayApplicationStep(result.steps.application);
|
|
340
350
|
|
|
341
|
-
if (result.steps.components.files && result.steps.components.files.length > 0) {
|
|
351
|
+
if (!result.steps.components.valid || (result.steps.components.files && result.steps.components.files.length > 0)) {
|
|
342
352
|
displayComponentsStep(result.steps.components);
|
|
343
353
|
}
|
|
344
354
|
|
|
@@ -356,6 +366,68 @@ function displayStepByStepValidation(result) {
|
|
|
356
366
|
if (result.warnings && result.warnings.length > 0) {
|
|
357
367
|
displayAggregatedWarnings(result.warnings);
|
|
358
368
|
}
|
|
369
|
+
|
|
370
|
+
displayOverallStatus(result);
|
|
371
|
+
if (result.appPath) {
|
|
372
|
+
logger.log(chalk.gray(`\n${path.resolve(result.appPath)}`));
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Displays batch validation results (per-app blocks then summary).
|
|
378
|
+
* Expects batchResult.batch === true and batchResult.results.
|
|
379
|
+
* @function displayBatchValidationResults
|
|
380
|
+
* @param {Object} batchResult - Batch result from validateAllIntegrations / validateAllBuilderApps / validateAll
|
|
381
|
+
*/
|
|
382
|
+
function displayBatchValidationResults(batchResult) {
|
|
383
|
+
if (!batchResult || batchResult.batch !== true || !Array.isArray(batchResult.results)) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const results = batchResult.results;
|
|
388
|
+
results.forEach(item => {
|
|
389
|
+
logger.log(chalk.blue(`\n--- ${item.appName} ---`));
|
|
390
|
+
if (item.error) {
|
|
391
|
+
logger.log(chalk.red(` ✗ ${item.error}`));
|
|
392
|
+
} else if (item.result) {
|
|
393
|
+
displayValidationResults(item.result);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
const passed = results.filter(r => r.result && r.result.valid).length;
|
|
398
|
+
const failed = results.length - passed;
|
|
399
|
+
logger.log(chalk.blue('\n--- Summary ---'));
|
|
400
|
+
if (failed === 0) {
|
|
401
|
+
logger.log(chalk.green(`✓ ${passed} passed, 0 failed`));
|
|
402
|
+
logger.log(chalk.green('Overall: Passed'));
|
|
403
|
+
} else {
|
|
404
|
+
logger.log(chalk.red(`✗ ${passed} passed, ${failed} failed`));
|
|
405
|
+
logger.log(chalk.red('Overall: Failed'));
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Displays overall validation status
|
|
411
|
+
* @param {Object} result - Validation result
|
|
412
|
+
*/
|
|
413
|
+
function displayOverallStatus(result) {
|
|
414
|
+
let hasErrors = result.errors && result.errors.length > 0;
|
|
415
|
+
if (!hasErrors && result.steps) {
|
|
416
|
+
const stepErrors = [result.steps.application, result.steps.components, result.steps.manifest]
|
|
417
|
+
.filter(Boolean)
|
|
418
|
+
.some(s => s.errors && s.errors.length > 0);
|
|
419
|
+
if (stepErrors) {
|
|
420
|
+
hasErrors = true;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
const hasWarnings = result.warnings && result.warnings.length > 0;
|
|
424
|
+
if (hasErrors) {
|
|
425
|
+
logger.log(chalk.red('\nOverall: Failed'));
|
|
426
|
+
} else if (hasWarnings) {
|
|
427
|
+
logger.log(chalk.yellow('\nOverall: Passed with warnings'));
|
|
428
|
+
} else {
|
|
429
|
+
logger.log(chalk.green('\nOverall: Passed'));
|
|
430
|
+
}
|
|
359
431
|
}
|
|
360
432
|
|
|
361
433
|
/**
|
|
@@ -390,10 +462,16 @@ function displayValidationResults(result) {
|
|
|
390
462
|
// Combine all warnings
|
|
391
463
|
const allWarnings = [...(result.warnings || []), ...dimensionWarnings];
|
|
392
464
|
displayAggregatedWarnings(allWarnings);
|
|
465
|
+
|
|
466
|
+
displayOverallStatus(result);
|
|
467
|
+
if (result.appPath) {
|
|
468
|
+
logger.log(chalk.gray(`\n${path.resolve(result.appPath)}`));
|
|
469
|
+
}
|
|
393
470
|
}
|
|
394
471
|
|
|
395
472
|
module.exports = {
|
|
396
473
|
displayValidationResults,
|
|
474
|
+
displayBatchValidationResults,
|
|
397
475
|
displayStepByStepValidation,
|
|
398
476
|
displayApplicationValidation,
|
|
399
477
|
displayExternalFilesValidation,
|