@aifabrix/builder 2.21.1 → 2.22.1
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/lib/app-list.js +60 -22
- package/lib/app-register.js +3 -2
- package/lib/app-rotate-secret.js +86 -48
- package/lib/commands/app.js +3 -0
- package/lib/config.js +40 -173
- package/lib/external-system-generator.js +3 -1
- package/lib/generator-external.js +229 -0
- package/lib/generator-helpers.js +205 -0
- package/lib/generator.js +2 -367
- package/lib/schema/external-system.schema.json +92 -1
- package/lib/secrets.js +10 -1
- package/lib/utils/api-error-handler.js +9 -2
- package/lib/utils/app-register-api.js +39 -29
- package/lib/utils/app-register-auth.js +103 -39
- package/lib/utils/config-paths.js +112 -0
- package/lib/utils/config-tokens.js +233 -0
- package/lib/utils/device-code.js +28 -6
- package/lib/utils/error-formatters/http-status-errors.js +78 -5
- package/lib/utils/error-formatters/network-errors.js +24 -4
- package/lib/utils/secrets-generator.js +5 -3
- package/lib/utils/secrets-utils.js +5 -3
- package/lib/validate.js +67 -7
- package/lib/validator.js +3 -1
- package/package.json +1 -1
- package/templates/external-system/external-system.json.hbs +20 -1
|
@@ -13,33 +13,85 @@ const chalk = require('chalk');
|
|
|
13
13
|
/**
|
|
14
14
|
* Formats authentication error
|
|
15
15
|
* @param {Object} errorData - Error response data
|
|
16
|
+
* @param {string} [errorData.controllerUrl] - Controller URL for example command
|
|
17
|
+
* @param {string[]} [errorData.attemptedUrls] - Array of attempted controller URLs
|
|
18
|
+
* @param {string} [errorData.message] - Error message
|
|
19
|
+
* @param {string} [errorData.error] - Error text
|
|
20
|
+
* @param {string} [errorData.detail] - Error detail
|
|
21
|
+
* @param {string} [errorData.correlationId] - Correlation ID
|
|
16
22
|
* @returns {string} Formatted authentication error message
|
|
17
23
|
*/
|
|
18
24
|
function formatAuthenticationError(errorData) {
|
|
19
25
|
const lines = [];
|
|
20
26
|
lines.push(chalk.red('❌ Authentication Failed\n'));
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
lines.push(chalk.yellow(
|
|
27
|
+
|
|
28
|
+
// Show controller URL prominently if available
|
|
29
|
+
if (errorData.controllerUrl) {
|
|
30
|
+
lines.push(chalk.yellow(`Controller URL: ${errorData.controllerUrl}`));
|
|
31
|
+
lines.push('');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Show attempted URLs if multiple were tried
|
|
35
|
+
if (errorData.attemptedUrls && errorData.attemptedUrls.length > 1) {
|
|
36
|
+
lines.push(chalk.gray('Attempted controller URLs:'));
|
|
37
|
+
errorData.attemptedUrls.forEach(url => {
|
|
38
|
+
lines.push(chalk.gray(` • ${url}`));
|
|
39
|
+
});
|
|
40
|
+
lines.push('');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Check if error message contains specific information
|
|
44
|
+
const errorMessage = errorData.message || errorData.error || errorData.detail || '';
|
|
45
|
+
const lowerMessage = errorMessage.toLowerCase();
|
|
46
|
+
|
|
47
|
+
// Only show error message if it provides useful information beyond generic messages
|
|
48
|
+
const isGenericMessage = lowerMessage.includes('authentication required') ||
|
|
49
|
+
lowerMessage.includes('unauthorized') ||
|
|
50
|
+
lowerMessage === '';
|
|
51
|
+
|
|
52
|
+
if (errorMessage && !isGenericMessage) {
|
|
53
|
+
// Show specific error message if it provides useful details
|
|
54
|
+
lines.push(chalk.yellow(errorMessage));
|
|
55
|
+
lines.push('');
|
|
25
56
|
}
|
|
57
|
+
|
|
58
|
+
// Always show general, actionable guidance
|
|
59
|
+
lines.push(chalk.gray('Your authentication token is invalid or has expired.'));
|
|
26
60
|
lines.push('');
|
|
27
|
-
lines.push(chalk.gray('
|
|
61
|
+
lines.push(chalk.gray('To authenticate, run:'));
|
|
62
|
+
|
|
63
|
+
// Use real controller URL if provided, otherwise show placeholder
|
|
64
|
+
const controllerUrl = errorData.controllerUrl;
|
|
65
|
+
if (controllerUrl && controllerUrl.trim()) {
|
|
66
|
+
lines.push(chalk.gray(` aifabrix login --method device --controller ${controllerUrl}`));
|
|
67
|
+
} else {
|
|
68
|
+
lines.push(chalk.gray(' aifabrix login --method device --controller <url>'));
|
|
69
|
+
}
|
|
70
|
+
|
|
28
71
|
if (errorData.correlationId) {
|
|
72
|
+
lines.push('');
|
|
29
73
|
lines.push(chalk.gray(`Correlation ID: ${errorData.correlationId}`));
|
|
30
74
|
}
|
|
75
|
+
|
|
31
76
|
return lines.join('\n');
|
|
32
77
|
}
|
|
33
78
|
|
|
34
79
|
/**
|
|
35
80
|
* Formats server error (500+)
|
|
36
81
|
* @param {Object} errorData - Error response data
|
|
82
|
+
* @param {string} [errorData.controllerUrl] - Controller URL
|
|
37
83
|
* @returns {string} Formatted server error message
|
|
38
84
|
*/
|
|
39
85
|
function formatServerError(errorData) {
|
|
40
86
|
const lines = [];
|
|
41
87
|
lines.push(chalk.red('❌ Server Error\n'));
|
|
42
88
|
|
|
89
|
+
// Show controller URL if available
|
|
90
|
+
if (errorData.controllerUrl) {
|
|
91
|
+
lines.push(chalk.yellow(`Controller URL: ${errorData.controllerUrl}`));
|
|
92
|
+
lines.push('');
|
|
93
|
+
}
|
|
94
|
+
|
|
43
95
|
// Check for RFC 7807 Problem Details format (detail field)
|
|
44
96
|
if (errorData.detail) {
|
|
45
97
|
lines.push(chalk.yellow(errorData.detail));
|
|
@@ -62,12 +114,19 @@ function formatServerError(errorData) {
|
|
|
62
114
|
/**
|
|
63
115
|
* Formats conflict error (409)
|
|
64
116
|
* @param {Object} errorData - Error response data
|
|
117
|
+
* @param {string} [errorData.controllerUrl] - Controller URL
|
|
65
118
|
* @returns {string} Formatted conflict error message
|
|
66
119
|
*/
|
|
67
120
|
function formatConflictError(errorData) {
|
|
68
121
|
const lines = [];
|
|
69
122
|
lines.push(chalk.red('❌ Conflict\n'));
|
|
70
123
|
|
|
124
|
+
// Show controller URL if available
|
|
125
|
+
if (errorData.controllerUrl) {
|
|
126
|
+
lines.push(chalk.yellow(`Controller URL: ${errorData.controllerUrl}`));
|
|
127
|
+
lines.push('');
|
|
128
|
+
}
|
|
129
|
+
|
|
71
130
|
// Check if it's an application already exists error
|
|
72
131
|
const detail = errorData.detail || errorData.message || '';
|
|
73
132
|
if (detail.toLowerCase().includes('application already exists')) {
|
|
@@ -124,12 +183,19 @@ function getNotFoundGuidance(detail) {
|
|
|
124
183
|
/**
|
|
125
184
|
* Formats not found error (404)
|
|
126
185
|
* @param {Object} errorData - Error response data
|
|
186
|
+
* @param {string} [errorData.controllerUrl] - Controller URL
|
|
127
187
|
* @returns {string} Formatted not found error message
|
|
128
188
|
*/
|
|
129
189
|
function formatNotFoundError(errorData) {
|
|
130
190
|
const lines = [];
|
|
131
191
|
lines.push(chalk.red('❌ Not Found\n'));
|
|
132
192
|
|
|
193
|
+
// Show controller URL if available
|
|
194
|
+
if (errorData.controllerUrl) {
|
|
195
|
+
lines.push(chalk.yellow(`Controller URL: ${errorData.controllerUrl}`));
|
|
196
|
+
lines.push('');
|
|
197
|
+
}
|
|
198
|
+
|
|
133
199
|
const detail = errorData.detail || errorData.message || '';
|
|
134
200
|
if (detail) {
|
|
135
201
|
lines.push(chalk.yellow(detail));
|
|
@@ -154,12 +220,19 @@ function formatNotFoundError(errorData) {
|
|
|
154
220
|
* Formats generic error
|
|
155
221
|
* @param {Object} errorData - Error response data
|
|
156
222
|
* @param {number} statusCode - HTTP status code
|
|
223
|
+
* @param {string} [errorData.controllerUrl] - Controller URL
|
|
157
224
|
* @returns {string} Formatted error message
|
|
158
225
|
*/
|
|
159
226
|
function formatGenericError(errorData, statusCode) {
|
|
160
227
|
const lines = [];
|
|
161
228
|
lines.push(chalk.red(`❌ Error (HTTP ${statusCode})\n`));
|
|
162
229
|
|
|
230
|
+
// Show controller URL if available
|
|
231
|
+
if (errorData.controllerUrl) {
|
|
232
|
+
lines.push(chalk.yellow(`Controller URL: ${errorData.controllerUrl}`));
|
|
233
|
+
lines.push('');
|
|
234
|
+
}
|
|
235
|
+
|
|
163
236
|
// Check for RFC 7807 Problem Details format (detail field)
|
|
164
237
|
if (errorData.detail) {
|
|
165
238
|
lines.push(chalk.yellow(errorData.detail));
|
|
@@ -14,26 +14,46 @@ const chalk = require('chalk');
|
|
|
14
14
|
* Formats network error
|
|
15
15
|
* @param {string} errorMessage - Error message
|
|
16
16
|
* @param {Object} errorData - Error response data (optional)
|
|
17
|
+
* @param {string} [errorData.controllerUrl] - Controller URL
|
|
17
18
|
* @returns {string} Formatted network error message
|
|
18
19
|
*/
|
|
19
20
|
function formatNetworkError(errorMessage, errorData) {
|
|
20
21
|
const lines = [];
|
|
21
22
|
lines.push(chalk.red('❌ Network Error\n'));
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
// Show controller URL prominently if available
|
|
25
|
+
if (errorData && errorData.controllerUrl) {
|
|
26
|
+
lines.push(chalk.yellow(`Controller URL: ${errorData.controllerUrl}`));
|
|
27
|
+
lines.push('');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Ensure errorMessage is a string
|
|
31
|
+
const message = typeof errorMessage === 'string' ? errorMessage : String(errorMessage || 'Network error');
|
|
32
|
+
|
|
33
|
+
if (message.includes('ECONNREFUSED') || message.includes('Cannot connect')) {
|
|
24
34
|
lines.push(chalk.yellow('Cannot connect to controller.'));
|
|
35
|
+
if (errorData && errorData.controllerUrl) {
|
|
36
|
+
lines.push(chalk.gray(`Controller URL: ${errorData.controllerUrl}`));
|
|
37
|
+
}
|
|
25
38
|
lines.push(chalk.gray('Check if the controller is running.'));
|
|
26
|
-
} else if (
|
|
39
|
+
} else if (message.includes('ENOTFOUND') || message.includes('hostname not found')) {
|
|
27
40
|
lines.push(chalk.yellow('Controller hostname not found.'));
|
|
41
|
+
if (errorData && errorData.controllerUrl) {
|
|
42
|
+
lines.push(chalk.gray(`Controller URL: ${errorData.controllerUrl}`));
|
|
43
|
+
}
|
|
28
44
|
lines.push(chalk.gray('Check your controller URL.'));
|
|
29
|
-
} else if (
|
|
45
|
+
} else if (message.includes('timeout') || message.includes('timed out')) {
|
|
30
46
|
lines.push(chalk.yellow('Request timed out.'));
|
|
47
|
+
if (errorData && errorData.controllerUrl) {
|
|
48
|
+
lines.push(chalk.gray(`Controller URL: ${errorData.controllerUrl}`));
|
|
49
|
+
}
|
|
31
50
|
lines.push(chalk.gray('The controller may be overloaded.'));
|
|
32
51
|
} else {
|
|
33
|
-
lines.push(chalk.yellow(
|
|
52
|
+
lines.push(chalk.yellow(message));
|
|
34
53
|
}
|
|
35
54
|
|
|
36
55
|
if (errorData && errorData.correlationId) {
|
|
56
|
+
lines.push('');
|
|
37
57
|
lines.push(chalk.gray(`Correlation ID: ${errorData.correlationId}`));
|
|
38
58
|
}
|
|
39
59
|
|
|
@@ -11,10 +11,11 @@
|
|
|
11
11
|
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
const path = require('path');
|
|
14
|
-
const yaml = require('js-yaml');
|
|
15
14
|
const os = require('os');
|
|
15
|
+
const yaml = require('js-yaml');
|
|
16
16
|
const crypto = require('crypto');
|
|
17
17
|
const logger = require('./logger');
|
|
18
|
+
const pathsUtil = require('./paths');
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Finds missing secret keys from template
|
|
@@ -123,11 +124,12 @@ function saveSecretsFile(resolvedPath, secrets) {
|
|
|
123
124
|
/**
|
|
124
125
|
* Generates missing secret keys in secrets file
|
|
125
126
|
* Scans env.template for kv:// references and adds missing keys with secure defaults
|
|
127
|
+
* Uses paths.getAifabrixHome() to respect config.yaml aifabrix-home override when path not provided
|
|
126
128
|
*
|
|
127
129
|
* @async
|
|
128
130
|
* @function generateMissingSecrets
|
|
129
131
|
* @param {string} envTemplate - Environment template content
|
|
130
|
-
* @param {string} secretsPath - Path to secrets file
|
|
132
|
+
* @param {string} [secretsPath] - Path to secrets file (optional)
|
|
131
133
|
* @returns {Promise<string[]>} Array of newly generated secret keys
|
|
132
134
|
* @throws {Error} If generation fails
|
|
133
135
|
*
|
|
@@ -136,7 +138,7 @@ function saveSecretsFile(resolvedPath, secrets) {
|
|
|
136
138
|
* // Returns: ['new-secret-key', 'another-secret']
|
|
137
139
|
*/
|
|
138
140
|
async function generateMissingSecrets(envTemplate, secretsPath) {
|
|
139
|
-
const resolvedPath = secretsPath || path.join(
|
|
141
|
+
const resolvedPath = secretsPath || path.join(pathsUtil.getAifabrixHome(), 'secrets.yaml');
|
|
140
142
|
const existingSecrets = loadExistingSecrets(resolvedPath);
|
|
141
143
|
const missingKeys = findMissingSecretKeys(envTemplate, existingSecrets);
|
|
142
144
|
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const yaml = require('js-yaml');
|
|
15
|
-
const os = require('os');
|
|
16
15
|
const logger = require('./logger');
|
|
16
|
+
const pathsUtil = require('./paths');
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Loads secrets from file with cascading lookup support
|
|
@@ -46,11 +46,12 @@ async function loadSecretsFromFile(filePath) {
|
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
48
|
* Loads user secrets from ~/.aifabrix/secrets.local.yaml
|
|
49
|
+
* Uses paths.getAifabrixHome() to respect config.yaml aifabrix-home override
|
|
49
50
|
* @function loadUserSecrets
|
|
50
51
|
* @returns {Object} Loaded secrets object or empty object
|
|
51
52
|
*/
|
|
52
53
|
function loadUserSecrets() {
|
|
53
|
-
const userSecretsPath = path.join(
|
|
54
|
+
const userSecretsPath = path.join(pathsUtil.getAifabrixHome(), 'secrets.local.yaml');
|
|
54
55
|
if (!fs.existsSync(userSecretsPath)) {
|
|
55
56
|
return {};
|
|
56
57
|
}
|
|
@@ -73,11 +74,12 @@ function loadUserSecrets() {
|
|
|
73
74
|
|
|
74
75
|
/**
|
|
75
76
|
* Loads default secrets from ~/.aifabrix/secrets.yaml
|
|
77
|
+
* Uses paths.getAifabrixHome() to respect config.yaml aifabrix-home override
|
|
76
78
|
* @function loadDefaultSecrets
|
|
77
79
|
* @returns {Object} Loaded secrets object or empty object
|
|
78
80
|
*/
|
|
79
81
|
function loadDefaultSecrets() {
|
|
80
|
-
const defaultPath = path.join(
|
|
82
|
+
const defaultPath = path.join(pathsUtil.getAifabrixHome(), 'secrets.yaml');
|
|
81
83
|
if (!fs.existsSync(defaultPath)) {
|
|
82
84
|
return {};
|
|
83
85
|
}
|
package/lib/validate.js
CHANGED
|
@@ -16,6 +16,7 @@ const validator = require('./validator');
|
|
|
16
16
|
const { resolveExternalFiles } = require('./utils/schema-resolver');
|
|
17
17
|
const { loadExternalSystemSchema, loadExternalDataSourceSchema, detectSchemaType } = require('./utils/schema-loader');
|
|
18
18
|
const { formatValidationErrors } = require('./utils/error-formatter');
|
|
19
|
+
const { detectAppType } = require('./utils/paths');
|
|
19
20
|
const logger = require('./utils/logger');
|
|
20
21
|
|
|
21
22
|
/**
|
|
@@ -56,10 +57,29 @@ async function validateExternalFile(filePath, type) {
|
|
|
56
57
|
|
|
57
58
|
const valid = validate(parsed);
|
|
58
59
|
|
|
60
|
+
const errors = valid ? [] : formatValidationErrors(validate.errors);
|
|
61
|
+
const warnings = [];
|
|
62
|
+
|
|
63
|
+
// Additional validation for external system files: check role references in permissions
|
|
64
|
+
if (type === 'system' && parsed.permissions && Array.isArray(parsed.permissions)) {
|
|
65
|
+
const roles = parsed.roles || [];
|
|
66
|
+
const roleValues = new Set(roles.map(r => r.value));
|
|
67
|
+
|
|
68
|
+
parsed.permissions.forEach((permission, index) => {
|
|
69
|
+
if (permission.roles && Array.isArray(permission.roles)) {
|
|
70
|
+
permission.roles.forEach(roleValue => {
|
|
71
|
+
if (!roleValues.has(roleValue)) {
|
|
72
|
+
errors.push(`Permission "${permission.name}" (index ${index}) references role "${roleValue}" which does not exist in roles array`);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
59
79
|
return {
|
|
60
|
-
valid,
|
|
61
|
-
errors
|
|
62
|
-
warnings
|
|
80
|
+
valid: errors.length === 0,
|
|
81
|
+
errors,
|
|
82
|
+
warnings
|
|
63
83
|
};
|
|
64
84
|
}
|
|
65
85
|
|
|
@@ -130,11 +150,28 @@ async function validateAppOrFile(appOrFile) {
|
|
|
130
150
|
// Treat as app name
|
|
131
151
|
const appName = appOrFile;
|
|
132
152
|
|
|
153
|
+
// Detect app type to support both builder/ and integration/ directories
|
|
154
|
+
const { appPath, isExternal } = await detectAppType(appName);
|
|
155
|
+
|
|
133
156
|
// Validate application
|
|
134
157
|
const appValidation = await validator.validateApplication(appName);
|
|
135
158
|
|
|
159
|
+
// Validate rbac.yaml for external systems
|
|
160
|
+
let rbacValidation = null;
|
|
161
|
+
if (isExternal) {
|
|
162
|
+
try {
|
|
163
|
+
rbacValidation = await validator.validateRbac(appName);
|
|
164
|
+
} catch (error) {
|
|
165
|
+
rbacValidation = {
|
|
166
|
+
valid: false,
|
|
167
|
+
errors: [error.message],
|
|
168
|
+
warnings: []
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
136
173
|
// Check for externalIntegration block
|
|
137
|
-
const variablesPath = path.join(
|
|
174
|
+
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
138
175
|
if (!fs.existsSync(variablesPath)) {
|
|
139
176
|
return {
|
|
140
177
|
valid: appValidation.valid,
|
|
@@ -193,20 +230,25 @@ async function validateAppOrFile(appOrFile) {
|
|
|
193
230
|
}
|
|
194
231
|
|
|
195
232
|
// Aggregate results
|
|
196
|
-
const
|
|
233
|
+
const rbacErrors = rbacValidation ? rbacValidation.errors : [];
|
|
234
|
+
const rbacWarnings = rbacValidation ? rbacValidation.warnings : [];
|
|
235
|
+
const allValid = appValidation.valid && externalValidations.every(v => v.valid) && (!rbacValidation || rbacValidation.valid);
|
|
197
236
|
const allErrors = [
|
|
198
237
|
...appValidation.errors,
|
|
199
|
-
...externalValidations.flatMap(v => v.errors.map(e => `${v.file}: ${e}`))
|
|
238
|
+
...externalValidations.flatMap(v => v.errors.map(e => `${v.file}: ${e}`)),
|
|
239
|
+
...rbacErrors.map(e => `rbac.yaml: ${e}`)
|
|
200
240
|
];
|
|
201
241
|
const allWarnings = [
|
|
202
242
|
...appValidation.warnings,
|
|
203
|
-
...externalValidations.flatMap(v => v.warnings)
|
|
243
|
+
...externalValidations.flatMap(v => v.warnings),
|
|
244
|
+
...rbacWarnings
|
|
204
245
|
];
|
|
205
246
|
|
|
206
247
|
return {
|
|
207
248
|
valid: allValid,
|
|
208
249
|
application: appValidation,
|
|
209
250
|
externalFiles: externalValidations,
|
|
251
|
+
rbac: rbacValidation,
|
|
210
252
|
errors: allErrors,
|
|
211
253
|
warnings: allWarnings
|
|
212
254
|
};
|
|
@@ -263,6 +305,24 @@ function displayValidationResults(result) {
|
|
|
263
305
|
});
|
|
264
306
|
}
|
|
265
307
|
|
|
308
|
+
// Display rbac validation (for external systems)
|
|
309
|
+
if (result.rbac) {
|
|
310
|
+
logger.log(chalk.blue('\nRBAC Configuration:'));
|
|
311
|
+
if (result.rbac.valid) {
|
|
312
|
+
logger.log(chalk.green(' ✓ RBAC configuration is valid'));
|
|
313
|
+
} else {
|
|
314
|
+
logger.log(chalk.red(' ✗ RBAC configuration has errors:'));
|
|
315
|
+
result.rbac.errors.forEach(error => {
|
|
316
|
+
logger.log(chalk.red(` • ${error}`));
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
if (result.rbac.warnings && result.rbac.warnings.length > 0) {
|
|
320
|
+
result.rbac.warnings.forEach(warning => {
|
|
321
|
+
logger.log(chalk.yellow(` ⚠ ${warning}`));
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
266
326
|
// Display file validation (for direct file validation)
|
|
267
327
|
if (result.file) {
|
|
268
328
|
logger.log(chalk.blue(`\nFile: ${result.file}`));
|
package/lib/validator.js
CHANGED
|
@@ -162,7 +162,9 @@ async function validateRbac(appName) {
|
|
|
162
162
|
throw new Error('App name is required and must be a string');
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
|
|
165
|
+
// Support both builder/ and integration/ directories using detectAppType
|
|
166
|
+
const { appPath } = await detectAppType(appName);
|
|
167
|
+
const rbacPath = path.join(appPath, 'rbac.yaml');
|
|
166
168
|
|
|
167
169
|
if (!fs.existsSync(rbacPath)) {
|
|
168
170
|
return { valid: true, errors: [], warnings: ['rbac.yaml not found - authentication disabled'] };
|
package/package.json
CHANGED
|
@@ -32,6 +32,25 @@
|
|
|
32
32
|
"serverUrl": "https://mcp.example.com",
|
|
33
33
|
"toolPrefix": "{{systemKey}}"
|
|
34
34
|
}{{/if}},
|
|
35
|
-
"tags": []
|
|
35
|
+
"tags": []{{#if roles}},
|
|
36
|
+
"roles": [
|
|
37
|
+
{{#each roles}}
|
|
38
|
+
{
|
|
39
|
+
"name": "{{name}}",
|
|
40
|
+
"value": "{{value}}",
|
|
41
|
+
"description": "{{description}}"{{#if Groups}},
|
|
42
|
+
"Groups": [{{#each Groups}}"{{this}}"{{#unless @last}}, {{/unless}}{{/each}}]{{/if}}
|
|
43
|
+
}{{#unless @last}},{{/unless}}
|
|
44
|
+
{{/each}}
|
|
45
|
+
]{{/if}}{{#if permissions}},
|
|
46
|
+
"permissions": [
|
|
47
|
+
{{#each permissions}}
|
|
48
|
+
{
|
|
49
|
+
"name": "{{name}}",
|
|
50
|
+
"roles": [{{#each roles}}"{{this}}"{{#unless @last}}, {{/unless}}{{/each}}],
|
|
51
|
+
"description": "{{description}}"
|
|
52
|
+
}{{#unless @last}},{{/unless}}
|
|
53
|
+
{{/each}}
|
|
54
|
+
]{{/if}}
|
|
36
55
|
}
|
|
37
56
|
|