@aifabrix/builder 2.44.3 → 2.44.5
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/.npmrc.token +1 -1
- package/integration/roundtrip-test-local/README.md +1 -2
- package/integration/roundtrip-test-local2/README.md +1 -2
- package/jest.projects.js +31 -15
- package/lib/api/certificates.api.js +21 -3
- package/lib/api/types/wizard.types.js +2 -1
- package/lib/certification/post-unified-cert-sync.js +13 -2
- package/lib/certification/sync-after-external-command.js +6 -3
- package/lib/certification/sync-system-certification.js +60 -14
- package/lib/cli/setup-app.help.js +1 -1
- package/lib/cli/setup-app.test-commands.js +75 -39
- package/lib/cli/setup-infra.js +6 -2
- package/lib/cli/setup-utility.js +20 -1
- package/lib/commands/datasource-unified-test-cli.js +81 -46
- package/lib/commands/datasource-unified-test-cli.options.js +4 -2
- package/lib/commands/datasource.js +3 -31
- package/lib/commands/repair-datasource-keys.js +1 -1
- package/lib/commands/repair-datasource-openapi.js +57 -0
- package/lib/commands/repair-datasource.js +5 -0
- package/lib/commands/repair-internal.js +2 -4
- package/lib/commands/repair-rbac.js +25 -2
- package/lib/commands/repair.js +2 -19
- package/lib/commands/test-e2e-external.js +9 -9
- package/lib/commands/up-common.js +25 -0
- package/lib/commands/upload.js +18 -4
- package/lib/commands/wizard-core.js +53 -11
- package/lib/commands/wizard-dataplane.js +14 -6
- package/lib/commands/wizard-entity-selection.js +71 -14
- package/lib/commands/wizard-headless.js +5 -2
- package/lib/commands/wizard-helpers.js +13 -1
- package/lib/commands/wizard.js +208 -60
- package/lib/datasource/datasource-validate-display.js +162 -0
- package/lib/datasource/datasource-validate-summary.js +194 -0
- package/lib/datasource/test-e2e.js +65 -37
- package/lib/datasource/unified-validation-run-body.js +1 -2
- package/lib/datasource/validate.js +14 -6
- package/lib/external-system/test.js +12 -8
- package/lib/generator/external-controller-manifest.js +12 -2
- package/lib/generator/wizard-prompts.js +7 -1
- package/lib/generator/wizard.js +34 -0
- package/lib/schema/cip-capacity-display.fallback.json +7 -0
- package/lib/schema/datasource-test-run.schema.json +79 -1
- package/lib/schema/external-datasource.schema.json +94 -2
- package/lib/schema/flag-map-validation-run.json +1 -2
- package/lib/schema/type/document-storage.json +83 -3
- package/lib/schema/wizard-config.schema.json +1 -1
- package/lib/utils/configuration-env-resolver.js +38 -0
- package/lib/utils/dataplane-resolver.js +3 -2
- package/lib/utils/datasource-test-run-capacity-operations.js +149 -0
- package/lib/utils/datasource-test-run-debug-display.js +143 -1
- package/lib/utils/datasource-test-run-display.js +46 -33
- package/lib/utils/datasource-test-run-tty-log.js +6 -2
- package/lib/utils/datasource-test-run-tty-meta-lines.js +123 -0
- package/lib/utils/error-formatter.js +32 -2
- package/lib/utils/external-readme.js +47 -3
- package/lib/utils/external-system-readiness-core.js +39 -0
- package/lib/utils/external-system-readiness-deploy-display.js +2 -3
- package/lib/utils/external-system-readiness-display-internals.js +3 -2
- package/lib/utils/external-system-system-test-tty.js +33 -9
- package/lib/utils/external-system-validators.js +62 -5
- package/lib/utils/load-cip-capacity-display-config.js +130 -0
- package/lib/utils/paths.js +10 -3
- package/lib/utils/schema-resolver.js +98 -2
- package/lib/utils/urls-local-registry.js +52 -10
- package/lib/utils/validation-run-poll.js +15 -4
- package/lib/utils/validation-run-request.js +4 -6
- package/lib/validation/dimension-display-helpers.js +60 -0
- package/lib/validation/validate-display-log-helpers.js +39 -0
- package/lib/validation/validate-display.js +89 -83
- package/package.json +1 -1
- package/templates/applications/miso-controller/env.template +6 -6
- package/templates/external-system/README.md.hbs +58 -32
|
@@ -94,7 +94,7 @@ async function runDatasourceUnifiedTestOnceForWatch(datasourceKey, runOpts, disp
|
|
|
94
94
|
try {
|
|
95
95
|
const result = await runUnifiedDatasourceValidation(datasourceKey, runOpts);
|
|
96
96
|
const exitCode = finalizeUnifiedValidationResult(result, displayOpts);
|
|
97
|
-
await afterUnifiedValidationCertSync(exitCode, datasourceKey, certCliOptions, 'datasource test');
|
|
97
|
+
await afterUnifiedValidationCertSync(exitCode, datasourceKey, certCliOptions, 'datasource test', result.envelope);
|
|
98
98
|
return {
|
|
99
99
|
exitCode,
|
|
100
100
|
envelope: result.envelope
|
|
@@ -121,7 +121,7 @@ async function datasourceTestCommandAction(datasourceKey, options) {
|
|
|
121
121
|
const result = await runUnifiedDatasourceValidation(datasourceKey, runOpts);
|
|
122
122
|
await writeDatasourceTestDebugLogIfRequested(appKey, datasourceKey, result, options);
|
|
123
123
|
const exitCode = finalizeUnifiedValidationResult(result, displayOpts);
|
|
124
|
-
await afterUnifiedValidationCertSync(exitCode, datasourceKey, options, 'datasource test (watch)');
|
|
124
|
+
await afterUnifiedValidationCertSync(exitCode, datasourceKey, options, 'datasource test (watch)', result.envelope);
|
|
125
125
|
return {
|
|
126
126
|
exitCode,
|
|
127
127
|
envelope: result.envelope
|
|
@@ -140,7 +140,7 @@ async function datasourceTestCommandAction(datasourceKey, options) {
|
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
const exitCode = finalizeUnifiedValidationResult(result, displayOpts);
|
|
143
|
-
await afterUnifiedValidationCertSync(exitCode, datasourceKey, options, 'datasource test');
|
|
143
|
+
await afterUnifiedValidationCertSync(exitCode, datasourceKey, options, 'datasource test', result.envelope);
|
|
144
144
|
process.exit(exitCode);
|
|
145
145
|
} catch (error) {
|
|
146
146
|
logger.error(formatBlockingError('Datasource test failed:'), error.message);
|
|
@@ -195,7 +195,13 @@ async function runIntegrationOnceForWatch(datasourceKey, integOpts, options, uni
|
|
|
195
195
|
if (unifiedModes) {
|
|
196
196
|
const uni = unifiedCliResultFromIntegrationReturn(result);
|
|
197
197
|
const exitCode = finalizeUnifiedValidationResult(uni, unifiedDisplayOpts);
|
|
198
|
-
await afterUnifiedValidationCertSync(
|
|
198
|
+
await afterUnifiedValidationCertSync(
|
|
199
|
+
exitCode,
|
|
200
|
+
datasourceKey,
|
|
201
|
+
options,
|
|
202
|
+
'datasource test-integration (watch)',
|
|
203
|
+
uni.envelope
|
|
204
|
+
);
|
|
199
205
|
return { exitCode, envelope: uni.envelope };
|
|
200
206
|
}
|
|
201
207
|
displayIntegrationTestResults(
|
|
@@ -211,7 +217,13 @@ async function runIntegrationOnceForWatch(datasourceKey, integOpts, options, uni
|
|
|
211
217
|
warningsAsErrors: unifiedDisplayOpts.warningsAsErrors === true,
|
|
212
218
|
requireCert: unifiedDisplayOpts.requireCert === true
|
|
213
219
|
});
|
|
214
|
-
await afterUnifiedValidationCertSync(
|
|
220
|
+
await afterUnifiedValidationCertSync(
|
|
221
|
+
exitCode,
|
|
222
|
+
datasourceKey,
|
|
223
|
+
options,
|
|
224
|
+
'datasource test-integration (watch)',
|
|
225
|
+
result.datasourceTestRun || null
|
|
226
|
+
);
|
|
215
227
|
return { exitCode, envelope: result.datasourceTestRun || null };
|
|
216
228
|
} catch (err) {
|
|
217
229
|
logger.error(formatBlockingError('Integration test failed:'), err.message);
|
|
@@ -219,49 +231,67 @@ async function runIntegrationOnceForWatch(datasourceKey, integOpts, options, uni
|
|
|
219
231
|
}
|
|
220
232
|
}
|
|
221
233
|
|
|
234
|
+
async function integrationTestCommandActionWatch(datasourceKey, options, integOpts, unifiedDisplayOpts) {
|
|
235
|
+
const { appKey } = await resolveAppKeyForDatasource(datasourceKey, options.app);
|
|
236
|
+
await runDatasourceValidationWatchLoop({
|
|
237
|
+
appKey,
|
|
238
|
+
extraPaths: options.watchPath || [],
|
|
239
|
+
includeApplicationYaml: options.watchApplicationYaml === true,
|
|
240
|
+
watchCi: options.watchCi === true,
|
|
241
|
+
watchFullDiff: options.watchFullDiff === true,
|
|
242
|
+
runOnce: () => runIntegrationOnceForWatch(datasourceKey, integOpts, options, unifiedDisplayOpts)
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function integrationTestCommandActionNonWatch(datasourceKey, integOpts, options, unifiedDisplayOpts) {
|
|
247
|
+
const result = await runDatasourceTestIntegration(datasourceKey, integOpts);
|
|
248
|
+
const unifiedModes =
|
|
249
|
+
options.json || options.summary || options.warningsAsErrors || options.requireCert;
|
|
250
|
+
if (unifiedModes) {
|
|
251
|
+
const uni = unifiedCliResultFromIntegrationReturn(result);
|
|
252
|
+
const exitCode = finalizeUnifiedValidationResult(uni, unifiedDisplayOpts);
|
|
253
|
+
await afterUnifiedValidationCertSync(
|
|
254
|
+
exitCode,
|
|
255
|
+
datasourceKey,
|
|
256
|
+
options,
|
|
257
|
+
'datasource test-integration',
|
|
258
|
+
uni.envelope
|
|
259
|
+
);
|
|
260
|
+
process.exit(exitCode);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
displayIntegrationTestResults(
|
|
264
|
+
{
|
|
265
|
+
systemKey: result.systemKey || 'unknown',
|
|
266
|
+
datasourceResults: [result],
|
|
267
|
+
success: result.success
|
|
268
|
+
},
|
|
269
|
+
options.verbose,
|
|
270
|
+
{ debug: options.debug, runType: 'integration' }
|
|
271
|
+
);
|
|
272
|
+
const integExit = finalizeAfterIntegrationDisplay(result, {
|
|
273
|
+
warningsAsErrors: unifiedDisplayOpts.warningsAsErrors === true,
|
|
274
|
+
requireCert: unifiedDisplayOpts.requireCert === true
|
|
275
|
+
});
|
|
276
|
+
await afterUnifiedValidationCertSync(
|
|
277
|
+
integExit,
|
|
278
|
+
datasourceKey,
|
|
279
|
+
options,
|
|
280
|
+
'datasource test-integration',
|
|
281
|
+
result.datasourceTestRun || null
|
|
282
|
+
);
|
|
283
|
+
process.exit(integExit);
|
|
284
|
+
}
|
|
285
|
+
|
|
222
286
|
async function integrationTestCommandAction(datasourceKey, options) {
|
|
223
287
|
const integOpts = buildIntegrationTestOpts(options);
|
|
224
288
|
const unifiedDisplayOpts = buildIntegrationUnifiedDisplayOpts(options);
|
|
225
289
|
try {
|
|
226
290
|
if (options.watch) {
|
|
227
|
-
|
|
228
|
-
await runDatasourceValidationWatchLoop({
|
|
229
|
-
appKey,
|
|
230
|
-
extraPaths: options.watchPath || [],
|
|
231
|
-
includeApplicationYaml: options.watchApplicationYaml === true,
|
|
232
|
-
watchCi: options.watchCi === true,
|
|
233
|
-
watchFullDiff: options.watchFullDiff === true,
|
|
234
|
-
runOnce: () => runIntegrationOnceForWatch(datasourceKey, integOpts, options, unifiedDisplayOpts)
|
|
235
|
-
});
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
const result = await runDatasourceTestIntegration(datasourceKey, integOpts);
|
|
239
|
-
const unifiedModes =
|
|
240
|
-
options.json || options.summary || options.warningsAsErrors || options.requireCert;
|
|
241
|
-
if (unifiedModes) {
|
|
242
|
-
const exitCode = finalizeUnifiedValidationResult(
|
|
243
|
-
unifiedCliResultFromIntegrationReturn(result),
|
|
244
|
-
unifiedDisplayOpts
|
|
245
|
-
);
|
|
246
|
-
await afterUnifiedValidationCertSync(exitCode, datasourceKey, options, 'datasource test-integration');
|
|
247
|
-
process.exit(exitCode);
|
|
291
|
+
await integrationTestCommandActionWatch(datasourceKey, options, integOpts, unifiedDisplayOpts);
|
|
248
292
|
return;
|
|
249
293
|
}
|
|
250
|
-
|
|
251
|
-
{
|
|
252
|
-
systemKey: result.systemKey || 'unknown',
|
|
253
|
-
datasourceResults: [result],
|
|
254
|
-
success: result.success
|
|
255
|
-
},
|
|
256
|
-
options.verbose,
|
|
257
|
-
{ debug: options.debug, runType: 'integration' }
|
|
258
|
-
);
|
|
259
|
-
const integExit = finalizeAfterIntegrationDisplay(result, {
|
|
260
|
-
warningsAsErrors: unifiedDisplayOpts.warningsAsErrors === true,
|
|
261
|
-
requireCert: unifiedDisplayOpts.requireCert === true
|
|
262
|
-
});
|
|
263
|
-
await afterUnifiedValidationCertSync(integExit, datasourceKey, options, 'datasource test-integration');
|
|
264
|
-
process.exit(integExit);
|
|
294
|
+
await integrationTestCommandActionNonWatch(datasourceKey, integOpts, options, unifiedDisplayOpts);
|
|
265
295
|
} catch (error) {
|
|
266
296
|
logger.error(formatBlockingError('Integration test failed:'), error.message);
|
|
267
297
|
process.exit(4);
|
|
@@ -296,9 +326,8 @@ async function runDatasourceTestE2ECliOnce(datasourceKey, options) {
|
|
|
296
326
|
debug: options.debug,
|
|
297
327
|
verbose: options.verbose,
|
|
298
328
|
async: options.async !== false,
|
|
299
|
-
testCrud: options.testCrud,
|
|
300
|
-
recordId: options.recordId,
|
|
301
329
|
cleanup: options.cleanup,
|
|
330
|
+
runScenarios: options.runScenarios,
|
|
302
331
|
primaryKeyValue: options.primaryKeyValue,
|
|
303
332
|
minVectorHits: options.minVectorHits,
|
|
304
333
|
minProcessed: options.minProcessed,
|
|
@@ -340,8 +369,8 @@ async function runDatasourceTestE2ECliOnce(datasourceKey, options) {
|
|
|
340
369
|
}
|
|
341
370
|
|
|
342
371
|
async function runDatasourceTestE2ECliAction(datasourceKey, options) {
|
|
343
|
-
const { exitCode } = await runDatasourceTestE2ECliOnce(datasourceKey, options);
|
|
344
|
-
await afterUnifiedValidationCertSync(exitCode, datasourceKey, options, 'datasource test-e2e');
|
|
372
|
+
const { exitCode, envelope } = await runDatasourceTestE2ECliOnce(datasourceKey, options);
|
|
373
|
+
await afterUnifiedValidationCertSync(exitCode, datasourceKey, options, 'datasource test-e2e', envelope);
|
|
345
374
|
process.exit(exitCode);
|
|
346
375
|
}
|
|
347
376
|
|
|
@@ -371,7 +400,13 @@ async function e2eTestCommandAction(datasourceKey, capabilityKey, options) {
|
|
|
371
400
|
runOnce: async() => {
|
|
372
401
|
try {
|
|
373
402
|
const r = await runDatasourceTestE2ECliOnce(datasourceKey, mergedOptions);
|
|
374
|
-
await afterUnifiedValidationCertSync(
|
|
403
|
+
await afterUnifiedValidationCertSync(
|
|
404
|
+
r.exitCode,
|
|
405
|
+
datasourceKey,
|
|
406
|
+
mergedOptions,
|
|
407
|
+
'datasource test-e2e (watch)',
|
|
408
|
+
r.envelope
|
|
409
|
+
);
|
|
375
410
|
return r;
|
|
376
411
|
} catch (err) {
|
|
377
412
|
logger.error(formatBlockingError('E2E test failed:'), err.message);
|
|
@@ -166,8 +166,10 @@ function attachDatasourceTestE2eExclusiveOptions(cmd) {
|
|
|
166
166
|
'Minimum record count assertion (e2eOptions.minRecordCount)',
|
|
167
167
|
(v) => parseInt(String(v), 10)
|
|
168
168
|
)
|
|
169
|
-
.option(
|
|
170
|
-
|
|
169
|
+
.option(
|
|
170
|
+
'--no-run-scenarios',
|
|
171
|
+
'Do not expand testPayload.scenarios in capacity step (e2eOptions.runScenarios: false)'
|
|
172
|
+
)
|
|
171
173
|
.option('--no-cleanup', 'Disable cleanup after test (e2eOptions.cleanup: false)')
|
|
172
174
|
.option(
|
|
173
175
|
'--primary-key-value <value|@path>',
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
const path = require('path');
|
|
13
|
-
const chalk = require('chalk');
|
|
14
13
|
const logger = require('../utils/logger');
|
|
15
|
-
const {
|
|
14
|
+
const { formatBlockingError } = require('../utils/cli-test-layout-chalk');
|
|
16
15
|
const { validateDatasourceFile } = require('../datasource/validate');
|
|
16
|
+
const { logDatasourceValidateOutcome } = require('../datasource/datasource-validate-display');
|
|
17
17
|
const { listDatasources } = require('../datasource/list');
|
|
18
18
|
const { compareDatasources } = require('../datasource/diff');
|
|
19
19
|
const { deployDatasource } = require('../datasource/deploy');
|
|
@@ -53,34 +53,6 @@ Examples:
|
|
|
53
53
|
$ af ds upload ../integration/hubspot/hubspot-datasource-deals.json
|
|
54
54
|
`;
|
|
55
55
|
|
|
56
|
-
/**
|
|
57
|
-
* TTY layout for local datasource JSON validation (aligned with cli-test-layout-chalk).
|
|
58
|
-
* @param {{ valid: boolean, errors: string[], resolvedPath: string }} result
|
|
59
|
-
* @param {string} trimmed - original CLI argument
|
|
60
|
-
* @param {boolean} showMapping - show Key + File when key resolved to a path
|
|
61
|
-
*/
|
|
62
|
-
function logDatasourceValidateOutcome(result, trimmed, showMapping) {
|
|
63
|
-
logger.log('');
|
|
64
|
-
logger.log(sectionTitle('Datasource validation'));
|
|
65
|
-
logger.log(metadata('Offline — JSON schema and integration wiring'));
|
|
66
|
-
logger.log('');
|
|
67
|
-
if (!result.valid) {
|
|
68
|
-
logger.log(headerKeyValue('File:', result.resolvedPath));
|
|
69
|
-
logger.log('');
|
|
70
|
-
logger.log(formatBlockingError('Datasource file has errors:'));
|
|
71
|
-
result.errors.forEach(error => logger.log(chalk.red(` • ${error}`)));
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
if (showMapping) {
|
|
75
|
-
logger.log(headerKeyValue('Key:', trimmed));
|
|
76
|
-
logger.log(headerKeyValue('File:', result.resolvedPath));
|
|
77
|
-
} else {
|
|
78
|
-
logger.log(headerKeyValue('File:', result.resolvedPath));
|
|
79
|
-
}
|
|
80
|
-
logger.log('');
|
|
81
|
-
logger.log(formatSuccessLine('Datasource file is valid.'));
|
|
82
|
-
}
|
|
83
|
-
|
|
84
56
|
function setupDatasourceValidateCommand(datasource) {
|
|
85
57
|
datasource.command('validate <file-or-key>')
|
|
86
58
|
.description('Validate datasource JSON (file path or datasource key under integration/<app>/)')
|
|
@@ -97,7 +69,7 @@ function setupDatasourceValidateCommand(datasource) {
|
|
|
97
69
|
process.exit(1);
|
|
98
70
|
}
|
|
99
71
|
} catch (error) {
|
|
100
|
-
logger.error(formatBlockingError(
|
|
72
|
+
logger.error(formatBlockingError(`Validation failed: ${error.message}`));
|
|
101
73
|
process.exit(1);
|
|
102
74
|
}
|
|
103
75
|
});
|
|
@@ -193,7 +193,7 @@ function normalizeDatasourceKeysAndFilenames(appPath, datasourceFiles, systemKey
|
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
if (updated && variables.externalIntegration && Array.isArray(variables.externalIntegration.dataSources)) {
|
|
196
|
-
variables.externalIntegration.dataSources = [...newDatasourceFiles]
|
|
196
|
+
variables.externalIntegration.dataSources = [...newDatasourceFiles];
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
return { updated, datasourceFiles: newDatasourceFiles };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Repair OpenAPI block on external datasource JSON.
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
function hasNonEmptyObject(v) {
|
|
10
|
+
return !!v && typeof v === 'object' && !Array.isArray(v) && Object.keys(v).length > 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Repair `openapi` section:
|
|
15
|
+
* - If operations exist, ensure `openapi.enabled=true`
|
|
16
|
+
* - If enabled/operations and missing documentKey/fileId, default documentKey to datasource key (common convention)
|
|
17
|
+
* - If enabled+operations and autoRbac missing, default to true (wizard/fixtures expectation)
|
|
18
|
+
*
|
|
19
|
+
* @param {Object} parsed - Parsed datasource (mutated)
|
|
20
|
+
* @param {string[]} changes - Change log
|
|
21
|
+
* @returns {boolean} True if updated
|
|
22
|
+
*/
|
|
23
|
+
function repairOpenapiSection(parsed, changes) {
|
|
24
|
+
const openapi = parsed?.openapi;
|
|
25
|
+
if (!openapi || typeof openapi !== 'object') {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const hasOps = hasNonEmptyObject(openapi.operations);
|
|
30
|
+
const enabled = openapi.enabled === true;
|
|
31
|
+
let updated = false;
|
|
32
|
+
|
|
33
|
+
if (hasOps && openapi.enabled !== true) {
|
|
34
|
+
openapi.enabled = true;
|
|
35
|
+
changes.push('Set openapi.enabled=true (operations present)');
|
|
36
|
+
updated = true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if ((enabled || hasOps) && !openapi.documentKey && !openapi.fileId) {
|
|
40
|
+
if (typeof parsed.key === 'string' && parsed.key.trim()) {
|
|
41
|
+
openapi.documentKey = parsed.key.trim();
|
|
42
|
+
changes.push(`Set openapi.documentKey=${openapi.documentKey}`);
|
|
43
|
+
updated = true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if ((enabled || hasOps) && hasOps && openapi.autoRbac === undefined) {
|
|
48
|
+
openapi.autoRbac = true;
|
|
49
|
+
changes.push('Set openapi.autoRbac=true (enabled operations, default)');
|
|
50
|
+
updated = true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return updated;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = { repairOpenapiSection };
|
|
57
|
+
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
'use strict';
|
|
11
11
|
|
|
12
|
+
const { repairOpenapiSection } = require('./repair-datasource-openapi');
|
|
13
|
+
|
|
12
14
|
const DEFAULT_SYNC = {
|
|
13
15
|
mode: 'pull',
|
|
14
16
|
batchSize: 500
|
|
@@ -446,6 +448,8 @@ function repairDatasourceFile(parsed, options = {}, changes = []) {
|
|
|
446
448
|
updated = repairMetadataSchemaFromAttributes(parsed, out) || updated;
|
|
447
449
|
}
|
|
448
450
|
|
|
451
|
+
updated = repairOpenapiSection(parsed, out) || updated;
|
|
452
|
+
|
|
449
453
|
if (options.expose) {
|
|
450
454
|
updated = repairExposeFromAttributes(parsed, out) || updated;
|
|
451
455
|
}
|
|
@@ -472,6 +476,7 @@ module.exports = {
|
|
|
472
476
|
repairExposeFromAttributes,
|
|
473
477
|
repairSyncSection,
|
|
474
478
|
repairTestPayload,
|
|
479
|
+
repairOpenapiSection,
|
|
475
480
|
sanitizeTestPayloadTopLevel,
|
|
476
481
|
repairDatasourceFile,
|
|
477
482
|
DEFAULT_SYNC,
|
|
@@ -40,7 +40,6 @@ function discoverIntegrationFiles(appPath) {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
systemFiles.sort();
|
|
43
|
-
datasourceFiles.sort();
|
|
44
43
|
return { systemFiles, datasourceFiles };
|
|
45
44
|
}
|
|
46
45
|
|
|
@@ -48,8 +47,8 @@ function discoverIntegrationFiles(appPath) {
|
|
|
48
47
|
* Builds the effective datasource file list from application.yaml and discovered files.
|
|
49
48
|
* @param {string} appPath - Application directory path
|
|
50
49
|
* @param {string[]} discoveredDatasourceFiles - Filenames from discoverIntegrationFiles
|
|
51
|
-
* @param {string[]} [existingDataSources] - externalIntegration.dataSources from application
|
|
52
|
-
* @returns {string[]}
|
|
50
|
+
* @param {string[]} [existingDataSources] - externalIntegration.dataSources from application config
|
|
51
|
+
* @returns {string[]} Deduplicated list of datasource filenames (application order first, then remaining discovered files in directory order)
|
|
53
52
|
*/
|
|
54
53
|
function buildEffectiveDatasourceFiles(appPath, discoveredDatasourceFiles, existingDataSources) {
|
|
55
54
|
const existing = Array.isArray(existingDataSources) ? existingDataSources : [];
|
|
@@ -75,7 +74,6 @@ function buildEffectiveDatasourceFiles(appPath, discoveredDatasourceFiles, exist
|
|
|
75
74
|
seen.add(name);
|
|
76
75
|
}
|
|
77
76
|
}
|
|
78
|
-
result.sort();
|
|
79
77
|
return result;
|
|
80
78
|
}
|
|
81
79
|
|
|
@@ -17,6 +17,22 @@ const { resolveRbacPath } = require('../utils/app-config-resolver');
|
|
|
17
17
|
|
|
18
18
|
const DEFAULT_CAPABILITIES = ['list', 'get', 'create', 'update', 'delete'];
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Extracts roles and permissions from external system JSON for rbac.yaml (same rules as repair).
|
|
22
|
+
* @param {Object} system - Parsed system config
|
|
23
|
+
* @returns {Object|null} RBAC object or null
|
|
24
|
+
*/
|
|
25
|
+
function extractRbacFromSystem(system) {
|
|
26
|
+
if (!system || typeof system !== 'object') return null;
|
|
27
|
+
const hasRoles = system.roles && Array.isArray(system.roles) && system.roles.length > 0;
|
|
28
|
+
const hasPermissions = system.permissions && Array.isArray(system.permissions) && system.permissions.length > 0;
|
|
29
|
+
if (!hasRoles && !hasPermissions) return null;
|
|
30
|
+
const rbac = {};
|
|
31
|
+
if (hasRoles) rbac.roles = system.roles;
|
|
32
|
+
if (hasPermissions) rbac.permissions = system.permissions;
|
|
33
|
+
return rbac;
|
|
34
|
+
}
|
|
35
|
+
|
|
20
36
|
/**
|
|
21
37
|
* Resolves capabilities from datasource (array or legacy object).
|
|
22
38
|
* @param {Object} parsed - Parsed datasource
|
|
@@ -24,9 +40,15 @@ const DEFAULT_CAPABILITIES = ['list', 'get', 'create', 'update', 'delete'];
|
|
|
24
40
|
*/
|
|
25
41
|
function getCapabilitiesFromDatasource(parsed) {
|
|
26
42
|
const cap = parsed?.capabilities;
|
|
27
|
-
if (Array.isArray(cap))
|
|
43
|
+
if (Array.isArray(cap)) {
|
|
44
|
+
const arr = cap.filter(c => typeof c === 'string');
|
|
45
|
+
if (arr.length > 0) return arr;
|
|
46
|
+
return [...DEFAULT_CAPABILITIES];
|
|
47
|
+
}
|
|
28
48
|
if (cap && typeof cap === 'object') {
|
|
29
|
-
|
|
49
|
+
const keys = Object.keys(cap).filter(k => cap[k] === true);
|
|
50
|
+
if (keys.length > 0) return keys;
|
|
51
|
+
return [...DEFAULT_CAPABILITIES];
|
|
30
52
|
}
|
|
31
53
|
return [...DEFAULT_CAPABILITIES];
|
|
32
54
|
}
|
|
@@ -159,6 +181,7 @@ function mergeRbacFromDatasources(appPath, systemParsed, datasourceFiles, extrac
|
|
|
159
181
|
}
|
|
160
182
|
|
|
161
183
|
module.exports = {
|
|
184
|
+
extractRbacFromSystem,
|
|
162
185
|
getCapabilitiesFromDatasource,
|
|
163
186
|
mergeRbacFromDatasources
|
|
164
187
|
};
|
package/lib/commands/repair.js
CHANGED
|
@@ -26,7 +26,7 @@ const generator = require('../generator');
|
|
|
26
26
|
const { repairEnvTemplate, normalizeSystemFileAuthAndConfig } = require('./repair-env-template');
|
|
27
27
|
const { generateReadmeFromDeployJson } = require('../generator/split-readme');
|
|
28
28
|
const { repairDatasourceFile } = require('./repair-datasource');
|
|
29
|
-
const { mergeRbacFromDatasources } = require('./repair-rbac');
|
|
29
|
+
const { mergeRbacFromDatasources, extractRbacFromSystem } = require('./repair-rbac');
|
|
30
30
|
const { discoverIntegrationFiles, buildEffectiveDatasourceFiles } = require('./repair-internal');
|
|
31
31
|
const { normalizeDatasourceKeysAndFilenames } = require('./repair-datasource-keys');
|
|
32
32
|
|
|
@@ -49,22 +49,6 @@ function inferExternalReadmeFileExt(appPath) {
|
|
|
49
49
|
return '.json';
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
/**
|
|
53
|
-
* Extracts roles and permissions from system object for rbac.yaml
|
|
54
|
-
* @param {Object} system - Parsed system config
|
|
55
|
-
* @returns {Object|null} RBAC object or null
|
|
56
|
-
*/
|
|
57
|
-
function extractRbacFromSystem(system) {
|
|
58
|
-
if (!system || typeof system !== 'object') return null;
|
|
59
|
-
const hasRoles = system.roles && Array.isArray(system.roles) && system.roles.length > 0;
|
|
60
|
-
const hasPermissions = system.permissions && Array.isArray(system.permissions) && system.permissions.length > 0;
|
|
61
|
-
if (!hasRoles && !hasPermissions) return null;
|
|
62
|
-
const rbac = {};
|
|
63
|
-
if (hasRoles) rbac.roles = system.roles;
|
|
64
|
-
if (hasPermissions) rbac.permissions = system.permissions;
|
|
65
|
-
return rbac;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
52
|
/**
|
|
69
53
|
* Loads first system file and returns parsed object with key
|
|
70
54
|
* @param {string} appPath - Application path
|
|
@@ -204,8 +188,7 @@ function alignSystemFileDataSources(appPath, systemParsed, datasourceFiles, syst
|
|
|
204
188
|
keys.push(deriveDatasourceKeyFromFileName(fileName, systemKey));
|
|
205
189
|
}
|
|
206
190
|
}
|
|
207
|
-
|
|
208
|
-
const prev = Array.isArray(systemParsed.dataSources) ? [...systemParsed.dataSources].sort() : [];
|
|
191
|
+
const prev = Array.isArray(systemParsed.dataSources) ? [...systemParsed.dataSources] : [];
|
|
209
192
|
if (JSON.stringify(prev) === JSON.stringify(keys)) return false;
|
|
210
193
|
systemParsed.dataSources = keys;
|
|
211
194
|
changes.push(`dataSources: [${prev.join(', ') || '(none)'}] → [${keys.join(', ')}] (keys from each datasource file's "key" or filename)`);
|
|
@@ -42,8 +42,8 @@ function deriveDatasourceKeyFromFileName(fileName, systemKey) {
|
|
|
42
42
|
* @param {Object} variables - Loaded application variables (externalIntegration.dataSources = filenames)
|
|
43
43
|
* @param {string} systemKey - System key from system file
|
|
44
44
|
* @param {Object} systemParsed - Parsed system config (may have dataSources array of keys)
|
|
45
|
-
* @param {string[]} datasourceFiles - Discovered datasource filenames
|
|
46
|
-
* @returns {string[]}
|
|
45
|
+
* @param {string[]} datasourceFiles - Discovered datasource filenames (application order when built via repair)
|
|
46
|
+
* @returns {string[]} Datasource keys in declaration order (system JSON or file list — never sort; FK targets must run first)
|
|
47
47
|
*/
|
|
48
48
|
function getDatasourceKeys(appPath, configPath, variables, systemKey, systemParsed, datasourceFiles) {
|
|
49
49
|
const fromSystem = Array.isArray(systemParsed.dataSources) && systemParsed.dataSources.length > 0
|
|
@@ -54,11 +54,11 @@ function getDatasourceKeys(appPath, configPath, variables, systemKey, systemPars
|
|
|
54
54
|
if (fromSystem) {
|
|
55
55
|
fromSystem.forEach(k => {
|
|
56
56
|
if (k && typeof k === 'string' && !seen.has(k)) {
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
const trimmed = k.trim();
|
|
58
|
+
keys.push(trimmed);
|
|
59
|
+
seen.add(trimmed);
|
|
59
60
|
}
|
|
60
61
|
});
|
|
61
|
-
keys.sort();
|
|
62
62
|
return keys;
|
|
63
63
|
}
|
|
64
64
|
for (const fileName of datasourceFiles) {
|
|
@@ -81,18 +81,18 @@ function getDatasourceKeys(appPath, configPath, variables, systemKey, systemPars
|
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
|
-
keys.sort();
|
|
85
84
|
return keys;
|
|
86
85
|
}
|
|
87
86
|
|
|
88
87
|
/**
|
|
89
|
-
*
|
|
88
|
+
* Publish local integration files to the dataplane before E2E (same path as `aifabrix upload <systemKey>`),
|
|
89
|
+
* unless ``options.noSync`` is true.
|
|
90
90
|
* @param {string} systemKey
|
|
91
91
|
* @param {Object} options
|
|
92
92
|
* @returns {Promise<void>}
|
|
93
93
|
*/
|
|
94
94
|
async function syncLocalIfRequested(systemKey, options) {
|
|
95
|
-
if (options.
|
|
95
|
+
if (options.noSync === true) return;
|
|
96
96
|
logger.log(chalk.cyan('Syncing local config to dataplane…'));
|
|
97
97
|
const { uploadExternalSystem } = require('./upload');
|
|
98
98
|
await uploadExternalSystem(systemKey, {
|
|
@@ -113,7 +113,7 @@ async function syncLocalIfRequested(systemKey, options) {
|
|
|
113
113
|
* @param {boolean} [options.debug] - Include debug, write log
|
|
114
114
|
* @param {boolean} [options.verbose] - Verbose output
|
|
115
115
|
* @param {boolean} [options.async] - If false, sync mode (default true)
|
|
116
|
-
* @param {boolean} [options.
|
|
116
|
+
* @param {boolean} [options.noSync] - When true, skip upload (E2E uses dataplane config already deployed)
|
|
117
117
|
* @returns {Promise<{ success: boolean, results: Array<{ key: string, success: boolean, error?: string }> }>}
|
|
118
118
|
*/
|
|
119
119
|
async function runTestE2EForExternalSystem(externalSystem, options = {}) {
|
|
@@ -13,6 +13,8 @@ const path = require('path');
|
|
|
13
13
|
const fs = require('fs');
|
|
14
14
|
const chalk = require('chalk');
|
|
15
15
|
const logger = require('../utils/logger');
|
|
16
|
+
const config = require('../core/config');
|
|
17
|
+
const { getDefaultControllerUrl } = require('../utils/controller-url');
|
|
16
18
|
const pathsUtil = require('../utils/paths');
|
|
17
19
|
const { loadConfigFile, writeConfigFile } = require('../utils/config-format');
|
|
18
20
|
const { isYamlPath } = require('../utils/config-format');
|
|
@@ -166,6 +168,28 @@ function patchEnvOutputPathForDeployOnly(appName) {
|
|
|
166
168
|
}
|
|
167
169
|
}
|
|
168
170
|
|
|
171
|
+
/**
|
|
172
|
+
* For ``up-platform --force`` only: clear all stored auth tokens, set ``environment`` to ``dev``,
|
|
173
|
+
* set ``controller`` to the default URL for the current ``developer-id`` (``http://localhost`` +
|
|
174
|
+
* app port = 3000 + developerId × 100), and persist config. Does not change ``developer-id``.
|
|
175
|
+
* Builder dirs (keycloak, miso-controller, dataplane) are cleaned separately.
|
|
176
|
+
*
|
|
177
|
+
* @returns {Promise<void>}
|
|
178
|
+
*/
|
|
179
|
+
async function applyUpPlatformForceConfig() {
|
|
180
|
+
const deviceCleared = await config.clearAllDeviceTokens();
|
|
181
|
+
const clientCleared = await config.clearAllClientTokens();
|
|
182
|
+
await config.setCurrentEnvironment('dev');
|
|
183
|
+
const defaultControllerUrl = await getDefaultControllerUrl();
|
|
184
|
+
await config.setControllerUrl(defaultControllerUrl);
|
|
185
|
+
logger.log(
|
|
186
|
+
chalk.blue(
|
|
187
|
+
`--force: cleared ${deviceCleared} device token(s) and ${clientCleared} client token(s); ` +
|
|
188
|
+
`environment set to dev; default controller set to ${defaultControllerUrl} (run aifabrix login after platform is up)`
|
|
189
|
+
)
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
169
193
|
/**
|
|
170
194
|
* Removes builder app directories for the given app names. Only removes paths under the builder root
|
|
171
195
|
* to prevent path traversal. Uses getBuilderPath for each app and validates before removal.
|
|
@@ -231,6 +255,7 @@ async function ensureAppFromTemplate(appName) {
|
|
|
231
255
|
}
|
|
232
256
|
|
|
233
257
|
module.exports = {
|
|
258
|
+
applyUpPlatformForceConfig,
|
|
234
259
|
cleanBuilderAppDirs,
|
|
235
260
|
ensureAppFromTemplate,
|
|
236
261
|
patchEnvOutputPathForDeployOnly,
|
package/lib/commands/upload.js
CHANGED
|
@@ -17,7 +17,8 @@ const { getIntegrationPath } = require('../utils/paths');
|
|
|
17
17
|
const { pushCredentialSecrets } = require('../utils/credential-secrets-env');
|
|
18
18
|
const {
|
|
19
19
|
buildResolvedEnvMapForIntegration,
|
|
20
|
-
resolveConfigurationValues
|
|
20
|
+
resolveConfigurationValues,
|
|
21
|
+
collectResolvedVariableConfigurationNames
|
|
21
22
|
} = require('../utils/configuration-env-resolver');
|
|
22
23
|
const { validateExternalSystemComplete } = require('../validation/validate');
|
|
23
24
|
const { displayValidationResults } = require('../validation/validate-display');
|
|
@@ -70,9 +71,11 @@ function buildUploadPayload(manifest) {
|
|
|
70
71
|
/**
|
|
71
72
|
* Resolves dataplane URL and auth (same pattern as download).
|
|
72
73
|
* @param {string} systemKey - System key
|
|
74
|
+
* @param {{ silent?: boolean }} [opts] - silent: omit “Resolving dataplane URL…” and discovery success lines
|
|
73
75
|
* @returns {Promise<{ dataplaneUrl: string, authConfig: Object, environment: string }>}
|
|
74
76
|
*/
|
|
75
|
-
async function resolveDataplaneAndAuth(systemKey) {
|
|
77
|
+
async function resolveDataplaneAndAuth(systemKey, opts = {}) {
|
|
78
|
+
const silent = opts.silent === true;
|
|
76
79
|
const { resolveEnvironment } = require('../core/config');
|
|
77
80
|
const environment = await resolveEnvironment();
|
|
78
81
|
const controllerUrl = await resolveControllerUrl();
|
|
@@ -82,8 +85,10 @@ async function resolveDataplaneAndAuth(systemKey) {
|
|
|
82
85
|
throw new Error('Authentication required. Run "aifabrix login" or "aifabrix app register <systemKey>" first.');
|
|
83
86
|
}
|
|
84
87
|
|
|
85
|
-
|
|
86
|
-
|
|
88
|
+
if (!silent) {
|
|
89
|
+
logger.log(chalk.gray('Resolving dataplane URL...'));
|
|
90
|
+
}
|
|
91
|
+
const dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig, { silent });
|
|
87
92
|
return { dataplaneUrl, authConfig, environment };
|
|
88
93
|
}
|
|
89
94
|
|
|
@@ -167,6 +172,15 @@ async function pushAndLogCredentialSecrets(dataplaneUrl, authConfig, systemKey,
|
|
|
167
172
|
} else {
|
|
168
173
|
logger.log(chalk.yellow('Secret push skipped'));
|
|
169
174
|
}
|
|
175
|
+
const resolvedParamNames = collectResolvedVariableConfigurationNames(payload);
|
|
176
|
+
if (resolvedParamNames.length > 0) {
|
|
177
|
+
const nameList = ` (${resolvedParamNames.join(', ')})`;
|
|
178
|
+
logger.log(
|
|
179
|
+
formatSuccessLine(
|
|
180
|
+
`Resolved ${resolvedParamNames.length} integration parameter(s) from .env for publish${nameList}.`
|
|
181
|
+
)
|
|
182
|
+
);
|
|
183
|
+
}
|
|
170
184
|
if (pushResult.warning) {
|
|
171
185
|
logger.log(chalk.yellow(`Warning: ${pushResult.warning}`));
|
|
172
186
|
}
|