@aifabrix/builder 2.42.0 → 2.43.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/README.md +1 -1
- package/bin/aifabrix.js +1 -1
- package/integration/hubspot-test/README.md +126 -0
- package/integration/{hubspot → hubspot-test}/application.json +6 -6
- package/integration/{hubspot → hubspot-test}/create-hubspot.js +5 -5
- package/integration/hubspot-test/env.template +4 -0
- package/integration/{hubspot/hubspot-datasource-company.json → hubspot-test/hubspot-test-datasource-company.json} +3 -2
- package/integration/{hubspot/hubspot-datasource-contact.json → hubspot-test/hubspot-test-datasource-contact.json} +3 -2
- package/integration/{hubspot/hubspot-datasource-deal.json → hubspot-test/hubspot-test-datasource-deal.json} +3 -2
- package/integration/{hubspot/hubspot-datasource-users.json → hubspot-test/hubspot-test-datasource-users.json} +3 -2
- package/integration/{hubspot/hubspot-deploy.json → hubspot-test/hubspot-test-deploy.json} +198 -21
- package/integration/{hubspot/hubspot-system.json → hubspot-test/hubspot-test-system.json} +8 -7
- package/integration/hubspot-test/rbac.json +166 -0
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-hubspot-credential-real.yaml +3 -3
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-hubspot-env-vars.yaml +2 -2
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-add-datasource.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-credential-create.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-credential-select.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-known-platform.yaml +1 -1
- package/integration/hubspot-test/test-artifacts/wizard-invalid-missing-source.yaml +2 -0
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-mode.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-openapi-file.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-openapi-url.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-source.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-dimension-array-test.yaml +1 -1
- package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-key-test.yaml +5 -0
- package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-path-test.yaml +5 -0
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-dimension-test.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-rbac-test.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test.js +102 -59
- package/integration/{hubspot → hubspot-test}/wizard-hubspot-e2e.yaml +2 -2
- package/integration/{hubspot → hubspot-test}/wizard-hubspot-platform.yaml +1 -1
- package/lib/api/external-test.api.js +1 -1
- package/lib/api/service-users.api.js +111 -2
- package/lib/api/types/service-users.types.js +41 -0
- package/lib/api/wizard.api.js +2 -1
- package/lib/app/index.js +2 -2
- package/lib/app/prompts.js +2 -2
- package/lib/app/readme.js +3 -1
- package/lib/app/register.js +3 -1
- package/lib/app/rotate-secret.js +3 -0
- package/lib/cli/setup-app.js +5 -5
- package/lib/cli/setup-auth.js +19 -11
- package/lib/cli/setup-dev.js +62 -32
- package/lib/cli/setup-environment.js +6 -21
- package/lib/cli/setup-infra.js +13 -0
- package/lib/cli/setup-secrets.js +45 -6
- package/lib/cli/setup-service-user.js +146 -20
- package/lib/cli/setup-utility.js +12 -0
- package/lib/commands/auth-config.js +25 -19
- package/lib/commands/datasource.js +46 -1
- package/lib/commands/dev-init.js +1 -1
- package/lib/commands/repair-env-template.js +14 -8
- package/lib/commands/repair-rbac.js +25 -19
- package/lib/commands/repair.js +108 -31
- package/lib/commands/secrets-remove.js +1 -1
- package/lib/commands/secrets-set.js +6 -0
- package/lib/commands/secrets-validate.js +17 -4
- package/lib/commands/service-user.js +231 -2
- package/lib/commands/up-common.js +25 -0
- package/lib/commands/up-dataplane.js +91 -7
- package/lib/commands/wizard-core-helpers.js +5 -2
- package/lib/commands/wizard-core.js +2 -1
- package/lib/commands/wizard-headless.js +6 -1
- package/lib/commands/wizard.js +13 -6
- package/lib/core/admin-secrets.js +2 -0
- package/lib/core/config.js +7 -5
- package/lib/core/ensure-encryption-key.js +1 -3
- package/lib/core/secrets.js +32 -9
- package/lib/core/templates.js +1 -1
- package/lib/datasource/abac-validator.js +157 -0
- package/lib/datasource/field-reference-validator.js +74 -36
- package/lib/datasource/log-viewer.js +221 -0
- package/lib/datasource/resolve-app.js +109 -0
- package/lib/datasource/test-e2e.js +11 -20
- package/lib/datasource/test-integration.js +42 -22
- package/lib/datasource/validate.js +5 -2
- package/lib/external-system/download-helpers.js +3 -1
- package/lib/external-system/generator.js +12 -8
- package/lib/external-system/test-system-level.js +1 -1
- package/lib/generator/external-controller-manifest.js +3 -3
- package/lib/generator/external-schema-utils.js +3 -1
- package/lib/generator/external.js +7 -7
- package/lib/generator/helpers.js +13 -9
- package/lib/generator/index.js +4 -4
- package/lib/generator/split.js +45 -10
- package/lib/generator/wizard-prompts-secondary.js +39 -7
- package/lib/generator/wizard-readme.js +4 -1
- package/lib/generator/wizard.js +68 -53
- package/lib/infrastructure/helpers.js +50 -35
- package/lib/infrastructure/index.js +39 -23
- package/lib/schema/env-config.yaml +19 -2
- package/lib/schema/external-datasource.schema.json +11 -1
- package/lib/schema/wizard-config.schema.json +7 -1
- package/lib/utils/app-config-resolver.js +23 -1
- package/lib/utils/config-paths.js +48 -4
- package/lib/utils/credential-secrets-env.js +16 -1
- package/lib/utils/env-map.js +7 -3
- package/lib/utils/error-formatter.js +37 -0
- package/lib/utils/external-env-template.js +180 -0
- package/lib/utils/external-readme.js +33 -1
- package/lib/utils/external-system-display.js +43 -0
- package/lib/utils/external-system-validators.js +2 -2
- package/lib/utils/help-builder.js +3 -5
- package/lib/utils/local-secrets.js +26 -3
- package/lib/utils/paths.js +2 -1
- package/lib/utils/secrets-generator.js +2 -2
- package/lib/utils/secrets-utils.js +4 -0
- package/lib/utils/secure-file-permissions.js +91 -0
- package/lib/utils/token-manager.js +36 -3
- package/lib/utils/yaml-preserve.js +59 -1
- package/lib/validation/env-template-auth.js +50 -2
- package/lib/validation/external-manifest-validator.js +8 -0
- package/lib/validation/validate.js +8 -0
- package/lib/validation/validator.js +10 -13
- package/package.json +6 -2
- package/templates/applications/dataplane/env.template +5 -1
- package/templates/applications/miso-controller/application.yaml +1 -1
- package/templates/applications/miso-controller/env.template +13 -2
- package/templates/external-system/README.md.hbs +18 -5
- package/templates/external-system/env.template.hbs +22 -0
- package/integration/hubspot/README.md +0 -100
- package/integration/hubspot/env.template +0 -4
- package/integration/hubspot/test-artifacts/wizard-invalid-missing-source.yaml +0 -2
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-key-test.yaml +0 -5
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-path-test.yaml +0 -5
- /package/integration/{hubspot → hubspot-test}/companies.json +0 -0
- /package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-app-name.yaml +0 -0
- /package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-missing-app.yaml +0 -0
- /package/integration/{hubspot → hubspot-test}/test-dataplane-down-helpers.js +0 -0
- /package/integration/{hubspot → hubspot-test}/test-dataplane-down-tests.js +0 -0
- /package/integration/{hubspot → hubspot-test}/test-dataplane-down.js +0 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log viewer for E2E and integration test logs - format and display JSON logs.
|
|
3
|
+
* @fileoverview Read and format test-e2e / test-integration debug logs for terminal
|
|
4
|
+
* @author AI Fabrix Team
|
|
5
|
+
* @version 2.0.0
|
|
6
|
+
*/
|
|
7
|
+
/* eslint-disable max-statements, complexity, max-depth -- Formatter functions; display branches by design */
|
|
8
|
+
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const fs = require('fs').promises;
|
|
11
|
+
const chalk = require('chalk');
|
|
12
|
+
const logger = require('../utils/logger');
|
|
13
|
+
const { resolveAppKeyForDatasource } = require('./resolve-app');
|
|
14
|
+
const { getIntegrationPath } = require('../utils/paths');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get the path to the latest log file in a directory matching a glob-like pattern
|
|
18
|
+
* @param {string} logsDir - Directory containing log files
|
|
19
|
+
* @param {string} pattern - Prefix pattern (e.g. 'test-e2e' matches test-e2e-*.json)
|
|
20
|
+
* @returns {Promise<string|null>} Full path to latest file or null if none
|
|
21
|
+
*/
|
|
22
|
+
async function getLatestLogPath(logsDir, pattern) {
|
|
23
|
+
let entries;
|
|
24
|
+
try {
|
|
25
|
+
entries = await fs.readdir(logsDir, { withFileTypes: true });
|
|
26
|
+
} catch (err) {
|
|
27
|
+
if (err.code === 'ENOENT') return null;
|
|
28
|
+
throw err;
|
|
29
|
+
}
|
|
30
|
+
const prefix = pattern.replace(/\*$/, '');
|
|
31
|
+
const files = entries
|
|
32
|
+
.filter(e => e.isFile() && e.name.startsWith(prefix) && e.name.endsWith('.json'))
|
|
33
|
+
.map(e => path.join(logsDir, e.name));
|
|
34
|
+
if (files.length === 0) return null;
|
|
35
|
+
const withStats = await Promise.all(
|
|
36
|
+
files.map(async f => ({ path: f, mtime: (await fs.stat(f)).mtimeMs }))
|
|
37
|
+
);
|
|
38
|
+
withStats.sort((a, b) => b.mtime - a.mtime);
|
|
39
|
+
return withStats[0].path;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Truncate string for display
|
|
44
|
+
* @param {string} s - String
|
|
45
|
+
* @param {number} maxLen - Max length
|
|
46
|
+
* @returns {string}
|
|
47
|
+
*/
|
|
48
|
+
function truncate(s, maxLen = 60) {
|
|
49
|
+
if (typeof s !== 'string') return String(s);
|
|
50
|
+
return s.length <= maxLen ? s : `${s.slice(0, maxLen - 1)}…`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Format E2E log content for terminal display
|
|
55
|
+
* @param {Object} data - Parsed log JSON (request, response, error)
|
|
56
|
+
* @param {string} [fileName] - Log file name for header
|
|
57
|
+
*/
|
|
58
|
+
function formatE2ELog(data, fileName) {
|
|
59
|
+
logger.log(chalk.blue('\n——— E2E Log') + (fileName ? chalk.gray(` ${fileName}`) : ''));
|
|
60
|
+
const req = data.request || {};
|
|
61
|
+
logger.log(chalk.cyan('Request:'));
|
|
62
|
+
logger.log(chalk.gray(` sourceIdOrKey: ${req.sourceIdOrKey ?? '—'}`));
|
|
63
|
+
if (req.includeDebug !== undefined) logger.log(chalk.gray(` includeDebug: ${req.includeDebug}`));
|
|
64
|
+
if (req.cleanup !== undefined) logger.log(chalk.gray(` cleanup: ${req.cleanup}`));
|
|
65
|
+
if (req.primaryKeyValue !== undefined) logger.log(chalk.gray(` primaryKeyValue: ${truncate(JSON.stringify(req.primaryKeyValue))}`));
|
|
66
|
+
if (data.error) {
|
|
67
|
+
logger.log(chalk.red('Error: ') + data.error);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const res = data.response || {};
|
|
71
|
+
logger.log(chalk.cyan('Response:'));
|
|
72
|
+
logger.log(chalk.gray(` success: ${res.success}`));
|
|
73
|
+
if (res.status) logger.log(chalk.gray(` status: ${res.status}`));
|
|
74
|
+
if (res.error) logger.log(chalk.red(` error: ${res.error}`));
|
|
75
|
+
const steps = res.steps || res.completedActions || [];
|
|
76
|
+
if (steps.length > 0) {
|
|
77
|
+
logger.log(chalk.cyan('Steps:'));
|
|
78
|
+
for (const step of steps) {
|
|
79
|
+
const name = step.name || step.step || 'unknown';
|
|
80
|
+
const ok = step.success !== false && !step.error;
|
|
81
|
+
logger.log(` ${ok ? chalk.green('✓') : chalk.red('✗')} ${name}`);
|
|
82
|
+
if (step.error) logger.log(chalk.red(` ${step.error}`));
|
|
83
|
+
if (step.message && ok) logger.log(chalk.gray(` ${step.message}`));
|
|
84
|
+
if ((name === 'sync' || step.step === 'sync') && step.evidence && step.evidence.jobs) {
|
|
85
|
+
for (const job of step.evidence.jobs) {
|
|
86
|
+
const audit = job.audit || {};
|
|
87
|
+
const parts = [];
|
|
88
|
+
if (job.recordsProcessed !== undefined && job.recordsProcessed !== null) parts.push(`${job.recordsProcessed} processed`);
|
|
89
|
+
if (job.totalRecords !== undefined && job.totalRecords !== null) parts.push(`total: ${job.totalRecords}`);
|
|
90
|
+
if (audit.inserted !== undefined && audit.inserted !== null || audit.updated !== undefined && audit.updated !== null || audit.deleted !== undefined && audit.deleted !== null) {
|
|
91
|
+
parts.push(`(inserted: ${audit.inserted ?? 0}, updated: ${audit.updated ?? 0}, deleted: ${audit.deleted ?? 0})`);
|
|
92
|
+
}
|
|
93
|
+
if (parts.length) logger.log(chalk.gray(` Job: ${parts.join(' ')}`));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (res.auditLog && Array.isArray(res.auditLog) && res.auditLog.length > 0) {
|
|
99
|
+
logger.log(chalk.cyan('CIP execution trace(s): ') + chalk.gray(`${res.auditLog.length}`));
|
|
100
|
+
const baseUrl = (req.dataplaneUrl || '').toString().replace(/\/$/, '');
|
|
101
|
+
const sourceIdOrKey = req.sourceIdOrKey || '';
|
|
102
|
+
res.auditLog.slice(0, 3).forEach((trace, i) => {
|
|
103
|
+
const id = trace.executionId || trace.id || trace.traceId;
|
|
104
|
+
if (id) {
|
|
105
|
+
const idStr = String(id);
|
|
106
|
+
logger.log(chalk.gray(` ${i + 1}. executionId: ${idStr}`));
|
|
107
|
+
if (baseUrl && sourceIdOrKey) {
|
|
108
|
+
const executionUrl = `${baseUrl}/api/v1/external/${sourceIdOrKey}/executions/${idStr}`;
|
|
109
|
+
logger.log(chalk.gray(` Link: ${executionUrl}`));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Format integration test log content for terminal display
|
|
118
|
+
* @param {Object} data - Parsed log JSON
|
|
119
|
+
* @param {string} [fileName] - Log file name for header
|
|
120
|
+
*/
|
|
121
|
+
function formatIntegrationLog(data, fileName) {
|
|
122
|
+
logger.log(chalk.blue('\n——— Integration Log') + (fileName ? chalk.gray(` ${fileName}`) : ''));
|
|
123
|
+
const req = data.request || {};
|
|
124
|
+
logger.log(chalk.cyan('Request:'));
|
|
125
|
+
logger.log(chalk.gray(` systemKey: ${req.systemKey ?? '—'}, datasourceKey: ${req.datasourceKey ?? '—'}`));
|
|
126
|
+
if (req.includeDebug !== undefined) logger.log(chalk.gray(` includeDebug: ${req.includeDebug}`));
|
|
127
|
+
if (data.error) {
|
|
128
|
+
logger.log(chalk.red('Error: ') + data.error);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const res = data.response || {};
|
|
132
|
+
logger.log(chalk.cyan('Response:'));
|
|
133
|
+
logger.log(chalk.gray(` success: ${res.success}`));
|
|
134
|
+
if (res.error) logger.log(chalk.red(` error: ${res.error}`));
|
|
135
|
+
const vr = res.validationResults || {};
|
|
136
|
+
logger.log(chalk.cyan('Validation:'));
|
|
137
|
+
logger.log(chalk.gray(` isValid: ${vr.isValid}`));
|
|
138
|
+
if (vr.errors && vr.errors.length) vr.errors.forEach(e => logger.log(chalk.red(` - ${e}`)));
|
|
139
|
+
const fmr = res.fieldMappingResults || {};
|
|
140
|
+
if (Object.keys(fmr).length) {
|
|
141
|
+
logger.log(chalk.cyan('Field mapping:'));
|
|
142
|
+
logger.log(chalk.gray(` mappingCount: ${fmr.mappingCount ?? '—'}`));
|
|
143
|
+
if (fmr.dimensions) logger.log(chalk.gray(` dimensions: ${Object.keys(fmr.dimensions).join(', ')}`));
|
|
144
|
+
}
|
|
145
|
+
const etr = res.endpointTestResults || {};
|
|
146
|
+
if (Object.keys(etr).length) {
|
|
147
|
+
logger.log(chalk.cyan('Endpoint:'));
|
|
148
|
+
logger.log(chalk.gray(` endpointConfigured: ${etr.endpointConfigured}`));
|
|
149
|
+
}
|
|
150
|
+
if (res.normalizedOutput || res.normalizedMetadata) {
|
|
151
|
+
const out = res.normalizedOutput || res.normalizedMetadata;
|
|
152
|
+
const keys = typeof out === 'object' && out !== null ? Object.keys(out) : [];
|
|
153
|
+
logger.log(chalk.cyan('Normalized output: ') + chalk.gray(keys.length ? `${keys.length} fields` : '—'));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Format log content by type
|
|
159
|
+
* @param {Object} parsed - Parsed JSON log
|
|
160
|
+
* @param {'test-e2e'|'test-integration'} logType - Log type
|
|
161
|
+
* @param {string} [fileName] - File name for header
|
|
162
|
+
*/
|
|
163
|
+
function formatLogContent(parsed, logType, fileName) {
|
|
164
|
+
if (logType === 'test-e2e') {
|
|
165
|
+
formatE2ELog(parsed, fileName);
|
|
166
|
+
} else {
|
|
167
|
+
formatIntegrationLog(parsed, fileName);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Run log viewer: resolve log file, read, parse, format and print
|
|
173
|
+
* @async
|
|
174
|
+
* @param {string} datasourceKey - Datasource key (used when no --file)
|
|
175
|
+
* @param {Object} options - Options
|
|
176
|
+
* @param {string} [options.app] - App key (optional, resolved from key if omitted)
|
|
177
|
+
* @param {string} [options.file] - Path to log file (overrides app resolution)
|
|
178
|
+
* @param {'test-e2e'|'test-integration'} options.logType - Log type
|
|
179
|
+
* @throws {Error} When file not found or invalid JSON
|
|
180
|
+
*/
|
|
181
|
+
/* eslint-disable-next-line max-statements -- Resolve path, read, parse, format */
|
|
182
|
+
async function runLogViewer(datasourceKey, options) {
|
|
183
|
+
const { app, file, logType } = options;
|
|
184
|
+
let logPath;
|
|
185
|
+
let fileName;
|
|
186
|
+
if (file && typeof file === 'string' && file.trim()) {
|
|
187
|
+
logPath = path.isAbsolute(file) ? file : path.resolve(process.cwd(), file.trim());
|
|
188
|
+
fileName = path.basename(logPath);
|
|
189
|
+
} else {
|
|
190
|
+
if (!datasourceKey || typeof datasourceKey !== 'string') {
|
|
191
|
+
throw new Error('Datasource key is required when --file is not provided');
|
|
192
|
+
}
|
|
193
|
+
const { appKey } = await resolveAppKeyForDatasource(datasourceKey.trim(), app);
|
|
194
|
+
const appPath = getIntegrationPath(appKey);
|
|
195
|
+
const logsDir = path.join(appPath, 'logs');
|
|
196
|
+
const pattern = logType === 'test-e2e' ? 'test-e2e-' : 'test-integration-';
|
|
197
|
+
logPath = await getLatestLogPath(logsDir, pattern);
|
|
198
|
+
if (!logPath) {
|
|
199
|
+
throw new Error(
|
|
200
|
+
`No ${logType} log found in ${logsDir}. Run the test with --debug first.`
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
fileName = path.basename(logPath);
|
|
204
|
+
}
|
|
205
|
+
const content = await fs.readFile(logPath, 'utf8');
|
|
206
|
+
let parsed;
|
|
207
|
+
try {
|
|
208
|
+
parsed = JSON.parse(content);
|
|
209
|
+
} catch (err) {
|
|
210
|
+
throw new Error(`Invalid JSON in ${logPath}: ${err.message}`);
|
|
211
|
+
}
|
|
212
|
+
formatLogContent(parsed, logType, fileName);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
module.exports = {
|
|
216
|
+
getLatestLogPath,
|
|
217
|
+
formatLogContent,
|
|
218
|
+
formatE2ELog,
|
|
219
|
+
formatIntegrationLog,
|
|
220
|
+
runLogViewer
|
|
221
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve app key for datasource commands from explicit --app, cwd, scan, or key parse.
|
|
3
|
+
* @fileoverview App resolution for datasource test-e2e, test-integration, log-e2e, log-integration
|
|
4
|
+
* @author AI Fabrix Team
|
|
5
|
+
* @version 2.0.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const {
|
|
11
|
+
getIntegrationPath,
|
|
12
|
+
listIntegrationAppNames,
|
|
13
|
+
resolveIntegrationAppKeyFromCwd
|
|
14
|
+
} = require('../utils/paths');
|
|
15
|
+
const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
|
|
16
|
+
const { loadConfigFile } = require('../utils/config-format');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* For one app, check if any of its datasource files has the given key
|
|
20
|
+
* @param {string} appKey - Integration app key
|
|
21
|
+
* @param {string} datasourceKey - Datasource key to match
|
|
22
|
+
* @returns {boolean} True if this app has a datasource with that key
|
|
23
|
+
*/
|
|
24
|
+
function appHasDatasourceKey(appKey, datasourceKey) {
|
|
25
|
+
const appPath = getIntegrationPath(appKey);
|
|
26
|
+
let config;
|
|
27
|
+
try {
|
|
28
|
+
const configPath = resolveApplicationConfigPath(appPath);
|
|
29
|
+
config = loadConfigFile(configPath);
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
const schemaBasePath = config.externalIntegration?.schemaBasePath || './';
|
|
34
|
+
const datasourceFiles = config.externalIntegration?.dataSources || [];
|
|
35
|
+
for (const f of datasourceFiles) {
|
|
36
|
+
if (!f || typeof f !== 'string') continue;
|
|
37
|
+
const fullPath = path.isAbsolute(schemaBasePath)
|
|
38
|
+
? path.join(schemaBasePath, f)
|
|
39
|
+
: path.join(appPath, schemaBasePath, f);
|
|
40
|
+
if (!fs.existsSync(fullPath)) continue;
|
|
41
|
+
try {
|
|
42
|
+
const parsed = loadConfigFile(fullPath);
|
|
43
|
+
if (parsed && parsed.key === datasourceKey) return true;
|
|
44
|
+
} catch {
|
|
45
|
+
// skip unreadable or invalid files
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Resolve app key for a datasource: explicit --app, cwd, scan by key, or parse key convention.
|
|
53
|
+
* @async
|
|
54
|
+
* @param {string} datasourceKey - Datasource key (e.g. hubspot-test-company)
|
|
55
|
+
* @param {string} [explicitApp] - Explicit app key from --app
|
|
56
|
+
* @returns {Promise<{appKey: string}>} Resolved app key
|
|
57
|
+
* @throws {Error} When app cannot be determined or multiple apps match
|
|
58
|
+
*/
|
|
59
|
+
/* eslint-disable-next-line max-statements -- Resolution order: explicit, cwd, scan, parse */
|
|
60
|
+
async function resolveAppKeyForDatasource(datasourceKey, explicitApp) {
|
|
61
|
+
if (!datasourceKey || typeof datasourceKey !== 'string') {
|
|
62
|
+
throw new Error('Datasource key is required');
|
|
63
|
+
}
|
|
64
|
+
const key = String(datasourceKey).trim();
|
|
65
|
+
if (key.length === 0) {
|
|
66
|
+
throw new Error('Datasource key cannot be empty');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (explicitApp && typeof explicitApp === 'string' && explicitApp.trim()) {
|
|
70
|
+
const appPath = getIntegrationPath(explicitApp.trim());
|
|
71
|
+
if (fs.existsSync(appPath)) {
|
|
72
|
+
return { appKey: explicitApp.trim() };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const fromCwd = resolveIntegrationAppKeyFromCwd();
|
|
77
|
+
if (fromCwd && appHasDatasourceKey(fromCwd, key)) {
|
|
78
|
+
return { appKey: fromCwd };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const appNames = listIntegrationAppNames();
|
|
82
|
+
const matches = appNames.filter(appName => appHasDatasourceKey(appName, key));
|
|
83
|
+
if (matches.length === 1) {
|
|
84
|
+
return { appKey: matches[0] };
|
|
85
|
+
}
|
|
86
|
+
if (matches.length > 1) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`More than one app has this datasource; add --app <appKey>. Apps: ${matches.join(', ')}`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const segments = key.split('-');
|
|
93
|
+
if (segments.length >= 2) {
|
|
94
|
+
const candidate = segments.slice(0, -1).join('-');
|
|
95
|
+
const candidatePath = getIntegrationPath(candidate);
|
|
96
|
+
if (fs.existsSync(candidatePath) && appHasDatasourceKey(candidate, key)) {
|
|
97
|
+
return { appKey: candidate };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
throw new Error(
|
|
102
|
+
'Could not determine app context. Use --app <appKey> or run from integration/<appKey>/ directory.'
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = {
|
|
107
|
+
resolveAppKeyForDatasource,
|
|
108
|
+
appHasDatasourceKey
|
|
109
|
+
};
|
|
@@ -10,7 +10,8 @@ const path = require('path');
|
|
|
10
10
|
const fs = require('fs').promises;
|
|
11
11
|
const chalk = require('chalk');
|
|
12
12
|
const logger = require('../utils/logger');
|
|
13
|
-
const { getIntegrationPath
|
|
13
|
+
const { getIntegrationPath } = require('../utils/paths');
|
|
14
|
+
const { resolveAppKeyForDatasource } = require('./resolve-app');
|
|
14
15
|
const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
|
|
15
16
|
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
16
17
|
const { getDeviceOnlyAuth } = require('../utils/token-manager');
|
|
@@ -20,20 +21,6 @@ const { writeTestLog } = require('../utils/test-log-writer');
|
|
|
20
21
|
const DEFAULT_POLL_INTERVAL_MS = 2500;
|
|
21
22
|
const DEFAULT_POLL_TIMEOUT_MS = 15 * 60 * 1000;
|
|
22
23
|
|
|
23
|
-
/**
|
|
24
|
-
* Resolve appKey for datasource test-e2e
|
|
25
|
-
* @param {string} [appKey] - Explicit app key from --app
|
|
26
|
-
* @returns {string}
|
|
27
|
-
*/
|
|
28
|
-
function resolveAppKey(appKey) {
|
|
29
|
-
if (appKey) return appKey;
|
|
30
|
-
const fromCwd = resolveIntegrationAppKeyFromCwd();
|
|
31
|
-
if (fromCwd) return fromCwd;
|
|
32
|
-
throw new Error(
|
|
33
|
-
'Could not determine app context. Use --app <appKey> or run from integration/<appKey>/ directory.'
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
24
|
/**
|
|
38
25
|
* Resolve primaryKeyValue for request body: string as-is, or read and parse JSON from @path
|
|
39
26
|
* @param {string} [value] - Literal value or path prefixed with @ (e.g. @pk.json)
|
|
@@ -58,6 +45,7 @@ async function resolvePrimaryKeyValue(value) {
|
|
|
58
45
|
async function buildE2EBody(options) {
|
|
59
46
|
const body = {};
|
|
60
47
|
if (options.debug) body.includeDebug = true;
|
|
48
|
+
if (options.verbose) body.audit = true;
|
|
61
49
|
if (options.testCrud === true) body.testCrud = true;
|
|
62
50
|
if (options.recordId !== undefined && options.recordId !== null && options.recordId !== '') body.recordId = String(options.recordId);
|
|
63
51
|
if (options.cleanup === false) body.cleanup = false;
|
|
@@ -125,7 +113,7 @@ async function runDatasourceTestE2E(datasourceKey, options = {}) {
|
|
|
125
113
|
if (!datasourceKey || typeof datasourceKey !== 'string') {
|
|
126
114
|
throw new Error('Datasource key is required');
|
|
127
115
|
}
|
|
128
|
-
const appKey =
|
|
116
|
+
const { appKey } = await resolveAppKeyForDatasource(datasourceKey, options.app);
|
|
129
117
|
const controllerUrl = await resolveControllerUrl();
|
|
130
118
|
const { resolveEnvironment } = require('../core/config');
|
|
131
119
|
const environment = options.environment || await resolveEnvironment();
|
|
@@ -138,6 +126,7 @@ async function runDatasourceTestE2E(datasourceKey, options = {}) {
|
|
|
138
126
|
const useAsync = options.async !== false;
|
|
139
127
|
const requestMeta = {
|
|
140
128
|
sourceIdOrKey: datasourceKey,
|
|
129
|
+
dataplaneUrl,
|
|
141
130
|
includeDebug: options.debug,
|
|
142
131
|
testCrud: options.testCrud,
|
|
143
132
|
recordId: options.recordId,
|
|
@@ -197,11 +186,14 @@ async function executeE2EWithOptionalPoll(opts) {
|
|
|
197
186
|
asyncRun: useAsync
|
|
198
187
|
});
|
|
199
188
|
let data = response.data || response;
|
|
200
|
-
|
|
189
|
+
const runId = (data?.testRunId !== null && data?.testRunId !== undefined)
|
|
190
|
+
? (typeof data.testRunId === 'string' ? data.testRunId : data.testRunId.id || data.testRunId.key)
|
|
191
|
+
: null;
|
|
192
|
+
if (useAsync && runId) {
|
|
201
193
|
data = await pollE2ETestRun(
|
|
202
194
|
dataplaneUrl,
|
|
203
195
|
datasourceKey,
|
|
204
|
-
|
|
196
|
+
runId,
|
|
205
197
|
authConfig,
|
|
206
198
|
{
|
|
207
199
|
intervalMs: pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,
|
|
@@ -214,6 +206,5 @@ async function executeE2EWithOptionalPoll(opts) {
|
|
|
214
206
|
}
|
|
215
207
|
|
|
216
208
|
module.exports = {
|
|
217
|
-
runDatasourceTestE2E
|
|
218
|
-
resolveAppKey
|
|
209
|
+
runDatasourceTestE2E
|
|
219
210
|
};
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const chalk = require('chalk');
|
|
11
11
|
const logger = require('../utils/logger');
|
|
12
|
-
const { getIntegrationPath
|
|
12
|
+
const { getIntegrationPath } = require('../utils/paths');
|
|
13
|
+
const { resolveAppKeyForDatasource } = require('./resolve-app');
|
|
13
14
|
const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
|
|
14
15
|
const { loadConfigFile } = require('../utils/config-format');
|
|
15
16
|
const { setupIntegrationTestAuth } = require('../external-system/test-auth');
|
|
@@ -20,21 +21,12 @@ const testHelpers = require('../utils/external-system-test-helpers');
|
|
|
20
21
|
const fs = require('fs').promises;
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
|
-
*
|
|
24
|
-
* @param {string}
|
|
25
|
-
* @returns {Promise<
|
|
24
|
+
* Get systemKey for an integration app (from application config and first system file)
|
|
25
|
+
* @param {string} appKey - Integration app key
|
|
26
|
+
* @returns {Promise<string>} systemKey
|
|
26
27
|
*/
|
|
27
|
-
async function
|
|
28
|
-
|
|
29
|
-
if (!resolvedAppKey) {
|
|
30
|
-
resolvedAppKey = resolveIntegrationAppKeyFromCwd();
|
|
31
|
-
}
|
|
32
|
-
if (!resolvedAppKey) {
|
|
33
|
-
throw new Error(
|
|
34
|
-
'Could not determine app context. Use --app <appKey> or run from integration/<appKey>/ directory.'
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
const appPath = getIntegrationPath(resolvedAppKey);
|
|
28
|
+
async function getSystemKeyFromAppKey(appKey) {
|
|
29
|
+
const appPath = getIntegrationPath(appKey);
|
|
38
30
|
const configPath = resolveApplicationConfigPath(appPath);
|
|
39
31
|
const config = loadConfigFile(configPath);
|
|
40
32
|
if (!config.externalIntegration || !config.externalIntegration.systems || config.externalIntegration.systems.length === 0) {
|
|
@@ -47,8 +39,33 @@ async function resolveSystemKey(appKey) {
|
|
|
47
39
|
const systemContent = await fs.readFile(systemPath, 'utf8');
|
|
48
40
|
const yaml = require('js-yaml');
|
|
49
41
|
const systemConfig = yaml.load(systemContent);
|
|
50
|
-
|
|
51
|
-
|
|
42
|
+
return systemConfig?.key || path.basename(systemFile, '-system.yaml').replace('-system', '');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Find a datasource filename by matching the key inside the file (fallback when filename-base match fails).
|
|
47
|
+
* @param {string} appPath - Integration app directory path
|
|
48
|
+
* @param {string} schemaBasePath - Schema base path (relative or absolute)
|
|
49
|
+
* @param {string[]} datasourceFiles - List of datasource filenames from application config
|
|
50
|
+
* @param {string} datasourceKey - Datasource key to find
|
|
51
|
+
* @returns {string|null} Filename if found, null otherwise
|
|
52
|
+
*/
|
|
53
|
+
function findDatasourceFileByKey(appPath, schemaBasePath, datasourceFiles, datasourceKey) {
|
|
54
|
+
const fsSync = require('fs');
|
|
55
|
+
for (const f of datasourceFiles) {
|
|
56
|
+
if (!f || typeof f !== 'string') continue;
|
|
57
|
+
const fullPath = path.isAbsolute(schemaBasePath)
|
|
58
|
+
? path.join(schemaBasePath, f)
|
|
59
|
+
: path.join(appPath, schemaBasePath, f);
|
|
60
|
+
if (!fsSync.existsSync(fullPath)) continue;
|
|
61
|
+
try {
|
|
62
|
+
const parsed = loadConfigFile(fullPath);
|
|
63
|
+
if (parsed && parsed.key === datasourceKey) return f;
|
|
64
|
+
} catch {
|
|
65
|
+
// skip unreadable or invalid files
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
52
69
|
}
|
|
53
70
|
|
|
54
71
|
/**
|
|
@@ -67,23 +84,26 @@ async function runDatasourceTestIntegration(datasourceKey, options = {}) {
|
|
|
67
84
|
if (!datasourceKey || typeof datasourceKey !== 'string') {
|
|
68
85
|
throw new Error('Datasource key is required');
|
|
69
86
|
}
|
|
70
|
-
const { appKey
|
|
87
|
+
const { appKey } = await resolveAppKeyForDatasource(datasourceKey, options.app);
|
|
88
|
+
const systemKey = await getSystemKeyFromAppKey(appKey);
|
|
71
89
|
const appPath = getIntegrationPath(appKey);
|
|
72
90
|
const config = loadConfigFile(resolveApplicationConfigPath(appPath));
|
|
73
91
|
const schemaBasePath = config.externalIntegration?.schemaBasePath || './';
|
|
74
92
|
const datasourceFiles = config.externalIntegration?.dataSources || [];
|
|
75
|
-
|
|
93
|
+
let datasourceFile = datasourceFiles.find(f => {
|
|
76
94
|
const base = path.basename(f, path.extname(f));
|
|
77
95
|
return base === datasourceKey || base.includes(datasourceKey);
|
|
78
96
|
});
|
|
97
|
+
if (!datasourceFile) {
|
|
98
|
+
datasourceFile = findDatasourceFileByKey(appPath, schemaBasePath, datasourceFiles, datasourceKey);
|
|
99
|
+
}
|
|
79
100
|
if (!datasourceFile) {
|
|
80
101
|
throw new Error(`Datasource '${datasourceKey}' not found in application config`);
|
|
81
102
|
}
|
|
82
103
|
const datasourcePath = path.isAbsolute(schemaBasePath)
|
|
83
104
|
? path.join(schemaBasePath, datasourceFile)
|
|
84
105
|
: path.join(appPath, schemaBasePath, datasourceFile);
|
|
85
|
-
const
|
|
86
|
-
const datasource = JSON.parse(datasourceContent);
|
|
106
|
+
const datasource = loadConfigFile(datasourcePath);
|
|
87
107
|
if (datasource.key !== datasourceKey) {
|
|
88
108
|
throw new Error(`Datasource key mismatch: file has '${datasource.key}', expected '${datasourceKey}'`);
|
|
89
109
|
}
|
|
@@ -150,5 +170,5 @@ async function runDatasourceTestIntegration(datasourceKey, options = {}) {
|
|
|
150
170
|
|
|
151
171
|
module.exports = {
|
|
152
172
|
runDatasourceTestIntegration,
|
|
153
|
-
|
|
173
|
+
getSystemKeyFromAppKey
|
|
154
174
|
};
|
|
@@ -12,6 +12,7 @@ const fs = require('fs');
|
|
|
12
12
|
const { loadExternalDataSourceSchema } = require('../utils/schema-loader');
|
|
13
13
|
const { formatValidationErrors } = require('../utils/error-formatter');
|
|
14
14
|
const { validateFieldReferences } = require('./field-reference-validator');
|
|
15
|
+
const { validateAbac } = require('./abac-validator');
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Validates a datasource file against external-datasource schema
|
|
@@ -60,10 +61,12 @@ async function validateDatasourceFile(filePath) {
|
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
const fieldRefErrors = validateFieldReferences(parsed);
|
|
63
|
-
|
|
64
|
+
const abacErrors = validateAbac(parsed);
|
|
65
|
+
const postSchemaErrors = [...fieldRefErrors, ...abacErrors];
|
|
66
|
+
if (postSchemaErrors.length > 0) {
|
|
64
67
|
return {
|
|
65
68
|
valid: false,
|
|
66
|
-
errors:
|
|
69
|
+
errors: postSchemaErrors,
|
|
67
70
|
warnings: []
|
|
68
71
|
};
|
|
69
72
|
}
|
|
@@ -77,13 +77,15 @@ function generateReadme(systemKey, application, dataSources) {
|
|
|
77
77
|
};
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
+
const authType = application.authentication?.type || application.authentication?.method;
|
|
80
81
|
return generateExternalReadmeContent({
|
|
81
82
|
appName: systemKey,
|
|
82
83
|
systemKey,
|
|
83
84
|
systemType: application.type,
|
|
84
85
|
displayName: application.displayName,
|
|
85
86
|
description: application.description,
|
|
86
|
-
datasources
|
|
87
|
+
datasources,
|
|
88
|
+
authType
|
|
87
89
|
});
|
|
88
90
|
}
|
|
89
91
|
|
|
@@ -16,6 +16,7 @@ const chalk = require('chalk');
|
|
|
16
16
|
const logger = require('../utils/logger');
|
|
17
17
|
const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
|
|
18
18
|
const { loadConfigFile, writeConfigFile } = require('../utils/config-format');
|
|
19
|
+
const { getKvPathSegmentForSecurityKey } = require('../utils/credential-secrets-env');
|
|
19
20
|
|
|
20
21
|
// Register Handlebars helper for equality check
|
|
21
22
|
handlebars.registerHelper('eq', (a, b) => a === b);
|
|
@@ -23,36 +24,39 @@ handlebars.registerHelper('json', (obj) => JSON.stringify(obj));
|
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* Build authentication object per schema authenticationVariablesByMethod.
|
|
26
|
-
* Security values use kv://<systemKey>/<
|
|
27
|
+
* Security values use canonical kv://<systemKey>/<segment> paths (segment from getKvPathSegmentForSecurityKey).
|
|
27
28
|
* @param {string} systemKey - External system key
|
|
28
29
|
* @param {string} authType - Auth method (oauth2, aad, apikey, basic, queryParam, oidc, hmac, none)
|
|
29
30
|
* @returns {{ method: string, variables: Object, security: Object }} Authentication object
|
|
30
31
|
*/
|
|
31
32
|
function buildAuthenticationFromMethod(systemKey, authType) {
|
|
32
|
-
const
|
|
33
|
+
const kvPath = (securityKey) => {
|
|
34
|
+
const segment = getKvPathSegmentForSecurityKey(securityKey);
|
|
35
|
+
return segment ? `kv://${systemKey}/${segment}` : null;
|
|
36
|
+
};
|
|
33
37
|
const method = authType || 'apikey';
|
|
34
38
|
const base = 'https://api.example.com';
|
|
35
39
|
|
|
36
40
|
const authMap = {
|
|
37
41
|
oauth2: {
|
|
38
42
|
variables: { baseUrl: base, tokenUrl: `${base}/oauth/token`, authorizationUrl: `${base}/oauth/authorize` },
|
|
39
|
-
security: { clientId:
|
|
43
|
+
security: { clientId: kvPath('clientId'), clientSecret: kvPath('clientSecret') }
|
|
40
44
|
},
|
|
41
45
|
aad: {
|
|
42
46
|
variables: { baseUrl: base, tokenUrl: 'https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token', tenantId: '{tenant-id}' },
|
|
43
|
-
security: { clientId:
|
|
47
|
+
security: { clientId: kvPath('clientId'), clientSecret: kvPath('clientSecret') }
|
|
44
48
|
},
|
|
45
49
|
apikey: {
|
|
46
50
|
variables: { baseUrl: base, headerName: 'X-API-Key' },
|
|
47
|
-
security: { apiKey:
|
|
51
|
+
security: { apiKey: kvPath('apiKey') }
|
|
48
52
|
},
|
|
49
53
|
basic: {
|
|
50
54
|
variables: { baseUrl: base },
|
|
51
|
-
security: { username:
|
|
55
|
+
security: { username: kvPath('username'), password: kvPath('password') }
|
|
52
56
|
},
|
|
53
57
|
queryParam: {
|
|
54
58
|
variables: { baseUrl: base, paramName: 'api_key' },
|
|
55
|
-
security: { paramValue:
|
|
59
|
+
security: { paramValue: kvPath('paramValue') }
|
|
56
60
|
},
|
|
57
61
|
oidc: {
|
|
58
62
|
variables: { openIdConfigUrl: 'https://example.com/.well-known/openid-configuration', clientId: 'app-id' },
|
|
@@ -60,7 +64,7 @@ function buildAuthenticationFromMethod(systemKey, authType) {
|
|
|
60
64
|
},
|
|
61
65
|
hmac: {
|
|
62
66
|
variables: { baseUrl: base, algorithm: 'sha256', signatureHeader: 'X-Signature' },
|
|
63
|
-
security: { signingSecret:
|
|
67
|
+
security: { signingSecret: kvPath('signingSecret') }
|
|
64
68
|
},
|
|
65
69
|
none: {
|
|
66
70
|
variables: {},
|
|
@@ -42,7 +42,7 @@ async function runSystemLevelTest({ appName, systemKey, authConfig, dataplaneUrl
|
|
|
42
42
|
let success = true;
|
|
43
43
|
|
|
44
44
|
for (const r of rawResults) {
|
|
45
|
-
const dsKey = r.key || r.datasourceKey;
|
|
45
|
+
const dsKey = r.key || r.sourceKey || r.name || r.datasourceKey;
|
|
46
46
|
const dsResult = {
|
|
47
47
|
key: dsKey,
|
|
48
48
|
success: r.success !== false,
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
const path = require('path');
|
|
13
13
|
const { detectAppType } = require('../utils/paths');
|
|
14
|
-
const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
|
|
14
|
+
const { resolveApplicationConfigPath, resolveRbacPath } = require('../utils/app-config-resolver');
|
|
15
15
|
const { loadSystemFile, loadDatasourceFiles } = require('./external');
|
|
16
16
|
const { loadVariables, loadRbac } = require('./helpers');
|
|
17
17
|
|
|
@@ -77,8 +77,8 @@ function extractAppMetadata(variables, appName) {
|
|
|
77
77
|
*/
|
|
78
78
|
async function loadSystemWithRbac(appPath, schemaBasePath, systemFile) {
|
|
79
79
|
const systemJson = await loadSystemFile(appPath, schemaBasePath, systemFile);
|
|
80
|
-
const rbacPath =
|
|
81
|
-
const rbac = loadRbac(rbacPath);
|
|
80
|
+
const rbacPath = resolveRbacPath(appPath);
|
|
81
|
+
const rbac = rbacPath ? loadRbac(rbacPath) : null;
|
|
82
82
|
mergeRbacIntoSystemJson(systemJson, rbac);
|
|
83
83
|
return systemJson;
|
|
84
84
|
}
|
|
@@ -196,13 +196,15 @@ async function writeSplitExternalSchemaFiles({ outputDir, systemKey, application
|
|
|
196
196
|
await writeYamlFile(rbacPath, rbac, { indent: 2, lineWidth: -1 });
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
const authType = application.authentication?.type || application.authentication?.method;
|
|
199
200
|
const readmeContent = generateExternalReadmeContent({
|
|
200
201
|
appName: systemKey,
|
|
201
202
|
systemKey,
|
|
202
203
|
systemType: application.type,
|
|
203
204
|
displayName: application.displayName,
|
|
204
205
|
description: application.description,
|
|
205
|
-
datasources: dataSources
|
|
206
|
+
datasources: dataSources,
|
|
207
|
+
authType
|
|
206
208
|
});
|
|
207
209
|
const readmePath = path.join(outputDir, 'README.md');
|
|
208
210
|
await fs.promises.writeFile(readmePath, readmeContent, 'utf8');
|