@aifabrix/builder 2.6.3 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/rules/project-rules.mdc +680 -0
- package/bin/aifabrix.js +4 -0
- package/lib/app-config.js +10 -0
- package/lib/app-deploy.js +18 -0
- package/lib/app-dockerfile.js +15 -0
- package/lib/app-prompts.js +172 -9
- package/lib/app-push.js +15 -0
- package/lib/app-register.js +14 -0
- package/lib/app-run.js +25 -0
- package/lib/app.js +30 -13
- package/lib/audit-logger.js +9 -4
- package/lib/build.js +8 -0
- package/lib/cli.js +99 -2
- package/lib/commands/datasource.js +94 -0
- package/lib/commands/login.js +40 -3
- package/lib/config.js +121 -114
- package/lib/datasource-deploy.js +182 -0
- package/lib/datasource-diff.js +73 -0
- package/lib/datasource-list.js +138 -0
- package/lib/datasource-validate.js +63 -0
- package/lib/diff.js +266 -0
- package/lib/environment-deploy.js +305 -0
- package/lib/external-system-deploy.js +262 -0
- package/lib/external-system-generator.js +187 -0
- package/lib/schema/application-schema.json +869 -698
- package/lib/schema/external-datasource.schema.json +512 -0
- package/lib/schema/external-system.schema.json +262 -0
- package/lib/schema/infrastructure-schema.json +1 -1
- package/lib/secrets.js +20 -1
- package/lib/templates.js +32 -1
- package/lib/utils/device-code.js +10 -2
- package/lib/utils/env-copy.js +24 -0
- package/lib/utils/env-endpoints.js +50 -11
- package/lib/utils/schema-loader.js +220 -0
- package/lib/utils/schema-resolver.js +174 -0
- package/lib/utils/secrets-helpers.js +65 -17
- package/lib/utils/token-encryption.js +68 -0
- package/lib/validate.js +299 -0
- package/lib/validator.js +47 -3
- package/package.json +1 -1
- package/tatus +181 -0
- package/templates/external-system/external-datasource.json.hbs +55 -0
- package/templates/external-system/external-system.json.hbs +37 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema Loading Utilities
|
|
3
|
+
*
|
|
4
|
+
* Loads and compiles JSON schemas for validation.
|
|
5
|
+
* Provides schema type detection and cached validators.
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview Schema loading utilities for AI Fabrix Builder
|
|
8
|
+
* @author AI Fabrix Team
|
|
9
|
+
* @version 2.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const Ajv = require('ajv');
|
|
15
|
+
|
|
16
|
+
// Cache for compiled validators
|
|
17
|
+
// These are reset when module is reloaded (for testing)
|
|
18
|
+
let externalSystemValidator = null;
|
|
19
|
+
let externalDataSourceValidator = null;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Reset validators cache (for testing)
|
|
23
|
+
* @function resetValidators
|
|
24
|
+
*/
|
|
25
|
+
function resetValidators() {
|
|
26
|
+
externalSystemValidator = null;
|
|
27
|
+
externalDataSourceValidator = null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Loads and compiles external-system schema
|
|
32
|
+
* Caches the compiled validator for performance
|
|
33
|
+
*
|
|
34
|
+
* @function loadExternalSystemSchema
|
|
35
|
+
* @returns {Function} Compiled AJV validator function
|
|
36
|
+
* @throws {Error} If schema file cannot be loaded or compiled
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* const validate = loadExternalSystemSchema();
|
|
40
|
+
* const valid = validate(data);
|
|
41
|
+
*/
|
|
42
|
+
function loadExternalSystemSchema() {
|
|
43
|
+
if (externalSystemValidator) {
|
|
44
|
+
return externalSystemValidator;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const schemaPath = path.join(__dirname, '..', 'schema', 'external-system.schema.json');
|
|
48
|
+
|
|
49
|
+
if (!fs.existsSync(schemaPath)) {
|
|
50
|
+
throw new Error(`External system schema not found: ${schemaPath}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const schemaContent = fs.readFileSync(schemaPath, 'utf8');
|
|
54
|
+
let schema;
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
schema = JSON.parse(schemaContent);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
throw new Error(`Invalid JSON in external-system.schema.json: ${error.message}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
63
|
+
externalSystemValidator = ajv.compile(schema);
|
|
64
|
+
|
|
65
|
+
return externalSystemValidator;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Loads and compiles external-datasource schema
|
|
70
|
+
* Caches the compiled validator for performance
|
|
71
|
+
*
|
|
72
|
+
* @function loadExternalDataSourceSchema
|
|
73
|
+
* @returns {Function} Compiled AJV validator function
|
|
74
|
+
* @throws {Error} If schema file cannot be loaded or compiled
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* const validate = loadExternalDataSourceSchema();
|
|
78
|
+
* const valid = validate(data);
|
|
79
|
+
*/
|
|
80
|
+
function loadExternalDataSourceSchema() {
|
|
81
|
+
if (externalDataSourceValidator) {
|
|
82
|
+
return externalDataSourceValidator;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const schemaPath = path.join(__dirname, '..', 'schema', 'external-datasource.schema.json');
|
|
86
|
+
|
|
87
|
+
if (!fs.existsSync(schemaPath)) {
|
|
88
|
+
throw new Error(`External datasource schema not found: ${schemaPath}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const schemaContent = fs.readFileSync(schemaPath, 'utf8');
|
|
92
|
+
let schema;
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
schema = JSON.parse(schemaContent);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
throw new Error(`Invalid JSON in external-datasource.schema.json: ${error.message}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// For draft-2020-12 schemas, we need to handle $schema differently
|
|
101
|
+
// Remove $schema if it's draft-2020-12 to avoid AJV issues
|
|
102
|
+
const schemaToCompile = { ...schema };
|
|
103
|
+
if (schemaToCompile.$schema && schemaToCompile.$schema.includes('2020-12')) {
|
|
104
|
+
// AJV v8 supports draft-2020-12 but may need the schema without $schema for compilation
|
|
105
|
+
delete schemaToCompile.$schema;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const ajv = new Ajv({ allErrors: true, strict: false, strictSchema: false });
|
|
109
|
+
externalDataSourceValidator = ajv.compile(schemaToCompile);
|
|
110
|
+
|
|
111
|
+
return externalDataSourceValidator;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Detects schema type from file content or path
|
|
116
|
+
* Attempts to identify if file is application, external-system, or external-datasource
|
|
117
|
+
*
|
|
118
|
+
* @function detectSchemaType
|
|
119
|
+
* @param {string} filePath - Path to the file
|
|
120
|
+
* @param {string} [content] - Optional file content (if not provided, will be read from file)
|
|
121
|
+
* @returns {string} Schema type: 'application' | 'external-system' | 'external-datasource'
|
|
122
|
+
* @throws {Error} If file cannot be read or parsed
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* const type = detectSchemaType('./hubspot.json');
|
|
126
|
+
* // Returns: 'external-system'
|
|
127
|
+
*/
|
|
128
|
+
function detectSchemaType(filePath, content) {
|
|
129
|
+
let fileContent = content;
|
|
130
|
+
|
|
131
|
+
// Read file if content not provided
|
|
132
|
+
if (!fileContent) {
|
|
133
|
+
if (!fs.existsSync(filePath)) {
|
|
134
|
+
throw new Error(`File not found: ${filePath}`);
|
|
135
|
+
}
|
|
136
|
+
fileContent = fs.readFileSync(filePath, 'utf8');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Try to parse JSON
|
|
140
|
+
let parsed;
|
|
141
|
+
try {
|
|
142
|
+
parsed = JSON.parse(fileContent);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
throw new Error(`Invalid JSON in file: ${error.message}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Check for schema type indicators
|
|
148
|
+
// Check $id for schema type
|
|
149
|
+
if (parsed.$id) {
|
|
150
|
+
if (parsed.$id.includes('external-system')) {
|
|
151
|
+
return 'external-system';
|
|
152
|
+
}
|
|
153
|
+
if (parsed.$id.includes('external-datasource')) {
|
|
154
|
+
return 'external-datasource';
|
|
155
|
+
}
|
|
156
|
+
if (parsed.$id.includes('application-schema')) {
|
|
157
|
+
return 'application';
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Check title for schema type (works even without $id or $schema)
|
|
162
|
+
if (parsed.title) {
|
|
163
|
+
const titleLower = parsed.title.toLowerCase();
|
|
164
|
+
if (titleLower.includes('external system') || titleLower.includes('external-system') || titleLower.includes('external system configuration')) {
|
|
165
|
+
return 'external-system';
|
|
166
|
+
}
|
|
167
|
+
if (titleLower.includes('external data source') || titleLower.includes('external datasource') || titleLower.includes('external-datasource')) {
|
|
168
|
+
return 'external-datasource';
|
|
169
|
+
}
|
|
170
|
+
if (titleLower.includes('application')) {
|
|
171
|
+
return 'application';
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Check for required fields to determine type
|
|
176
|
+
// External system requires: key, displayName, description, type, authentication
|
|
177
|
+
if (parsed.key && parsed.displayName && parsed.type && parsed.authentication) {
|
|
178
|
+
// Check if it has systemKey (datasource) or not (system)
|
|
179
|
+
if (parsed.systemKey) {
|
|
180
|
+
return 'external-datasource';
|
|
181
|
+
}
|
|
182
|
+
// Check if type is one of external-system types
|
|
183
|
+
if (['openapi', 'mcp', 'custom'].includes(parsed.type)) {
|
|
184
|
+
return 'external-system';
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Check for datasource-specific fields
|
|
189
|
+
if (parsed.systemKey && parsed.entityKey && parsed.fieldMappings) {
|
|
190
|
+
return 'external-datasource';
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Check for application-specific fields
|
|
194
|
+
if (parsed.deploymentKey || (parsed.image && parsed.registryMode && parsed.port)) {
|
|
195
|
+
return 'application';
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Fallback: check filename pattern
|
|
199
|
+
const fileName = path.basename(filePath).toLowerCase();
|
|
200
|
+
if (fileName.includes('external-system') || fileName.includes('external_system')) {
|
|
201
|
+
return 'external-system';
|
|
202
|
+
}
|
|
203
|
+
if (fileName.includes('external-datasource') || fileName.includes('external_datasource') || fileName.includes('datasource')) {
|
|
204
|
+
return 'external-datasource';
|
|
205
|
+
}
|
|
206
|
+
if (fileName.includes('application') || fileName.includes('variables') || fileName.includes('deploy')) {
|
|
207
|
+
return 'application';
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Default to application if cannot determine
|
|
211
|
+
return 'application';
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
module.exports = {
|
|
215
|
+
loadExternalSystemSchema,
|
|
216
|
+
loadExternalDataSourceSchema,
|
|
217
|
+
detectSchemaType,
|
|
218
|
+
resetValidators
|
|
219
|
+
};
|
|
220
|
+
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema Resolution Utilities
|
|
3
|
+
*
|
|
4
|
+
* Resolves paths for external integration schemas from application configuration.
|
|
5
|
+
* Handles schemaBasePath resolution and external file discovery.
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview Schema path resolution utilities for AI Fabrix Builder
|
|
8
|
+
* @author AI Fabrix Team
|
|
9
|
+
* @version 2.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const yaml = require('js-yaml');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Resolves schemaBasePath from application variables.yaml
|
|
18
|
+
* Supports both absolute and relative paths
|
|
19
|
+
*
|
|
20
|
+
* @async
|
|
21
|
+
* @function resolveSchemaBasePath
|
|
22
|
+
* @param {string} appName - Application name
|
|
23
|
+
* @returns {Promise<string>} Resolved absolute path to schema base directory
|
|
24
|
+
* @throws {Error} If variables.yaml not found, externalIntegration missing, or path invalid
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* const basePath = await resolveSchemaBasePath('myapp');
|
|
28
|
+
* // Returns: '/path/to/builder/myapp/schemas'
|
|
29
|
+
*/
|
|
30
|
+
async function resolveSchemaBasePath(appName) {
|
|
31
|
+
if (!appName || typeof appName !== 'string') {
|
|
32
|
+
throw new Error('App name is required and must be a string');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const variablesPath = path.join(process.cwd(), 'builder', appName, 'variables.yaml');
|
|
36
|
+
|
|
37
|
+
if (!fs.existsSync(variablesPath)) {
|
|
38
|
+
throw new Error(`variables.yaml not found: ${variablesPath}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const content = fs.readFileSync(variablesPath, 'utf8');
|
|
42
|
+
let variables;
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
variables = yaml.load(content);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
throw new Error(`Invalid YAML syntax in variables.yaml: ${error.message}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Check if externalIntegration block exists
|
|
51
|
+
if (!variables.externalIntegration) {
|
|
52
|
+
throw new Error(`externalIntegration block not found in variables.yaml for app: ${appName}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!variables.externalIntegration.schemaBasePath) {
|
|
56
|
+
throw new Error(`schemaBasePath not found in externalIntegration block for app: ${appName}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const schemaBasePath = variables.externalIntegration.schemaBasePath;
|
|
60
|
+
const variablesDir = path.dirname(variablesPath);
|
|
61
|
+
|
|
62
|
+
// Resolve path (absolute or relative to variables.yaml location)
|
|
63
|
+
let resolvedPath;
|
|
64
|
+
if (path.isAbsolute(schemaBasePath)) {
|
|
65
|
+
resolvedPath = schemaBasePath;
|
|
66
|
+
} else {
|
|
67
|
+
resolvedPath = path.resolve(variablesDir, schemaBasePath);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Normalize path
|
|
71
|
+
resolvedPath = path.normalize(resolvedPath);
|
|
72
|
+
|
|
73
|
+
// Validate path exists
|
|
74
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
75
|
+
throw new Error(`Schema base path does not exist: ${resolvedPath}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!fs.statSync(resolvedPath).isDirectory()) {
|
|
79
|
+
throw new Error(`Schema base path is not a directory: ${resolvedPath}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return resolvedPath;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Resolves all external system and datasource files from application configuration
|
|
87
|
+
* Returns array of file paths with metadata
|
|
88
|
+
*
|
|
89
|
+
* @async
|
|
90
|
+
* @function resolveExternalFiles
|
|
91
|
+
* @param {string} appName - Application name
|
|
92
|
+
* @returns {Promise<Array<{path: string, type: 'system'|'datasource', fileName: string}>>} Array of resolved file paths with metadata
|
|
93
|
+
* @throws {Error} If files cannot be resolved or do not exist
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* const files = await resolveExternalFiles('myapp');
|
|
97
|
+
* // Returns: [
|
|
98
|
+
* // { path: '/path/to/hubspot.json', type: 'system', fileName: 'hubspot.json' },
|
|
99
|
+
* // { path: '/path/to/hubspot-deal.json', type: 'datasource', fileName: 'hubspot-deal.json' }
|
|
100
|
+
* // ]
|
|
101
|
+
*/
|
|
102
|
+
async function resolveExternalFiles(appName) {
|
|
103
|
+
if (!appName || typeof appName !== 'string') {
|
|
104
|
+
throw new Error('App name is required and must be a string');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const variablesPath = path.join(process.cwd(), 'builder', appName, 'variables.yaml');
|
|
108
|
+
|
|
109
|
+
if (!fs.existsSync(variablesPath)) {
|
|
110
|
+
throw new Error(`variables.yaml not found: ${variablesPath}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const content = fs.readFileSync(variablesPath, 'utf8');
|
|
114
|
+
let variables;
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
variables = yaml.load(content);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
throw new Error(`Invalid YAML syntax in variables.yaml: ${error.message}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Check if externalIntegration block exists
|
|
123
|
+
if (!variables.externalIntegration) {
|
|
124
|
+
return []; // No external integration, return empty array
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Resolve schema base path
|
|
128
|
+
const schemaBasePath = await resolveSchemaBasePath(appName);
|
|
129
|
+
const resolvedFiles = [];
|
|
130
|
+
|
|
131
|
+
// Resolve systems files
|
|
132
|
+
if (variables.externalIntegration.systems && Array.isArray(variables.externalIntegration.systems)) {
|
|
133
|
+
for (const systemFile of variables.externalIntegration.systems) {
|
|
134
|
+
const systemPath = path.join(schemaBasePath, systemFile);
|
|
135
|
+
const normalizedPath = path.normalize(systemPath);
|
|
136
|
+
|
|
137
|
+
if (!fs.existsSync(normalizedPath)) {
|
|
138
|
+
throw new Error(`External system file not found: ${normalizedPath}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
resolvedFiles.push({
|
|
142
|
+
path: normalizedPath,
|
|
143
|
+
type: 'system',
|
|
144
|
+
fileName: systemFile
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Resolve datasources files
|
|
150
|
+
if (variables.externalIntegration.dataSources && Array.isArray(variables.externalIntegration.dataSources)) {
|
|
151
|
+
for (const datasourceFile of variables.externalIntegration.dataSources) {
|
|
152
|
+
const datasourcePath = path.join(schemaBasePath, datasourceFile);
|
|
153
|
+
const normalizedPath = path.normalize(datasourcePath);
|
|
154
|
+
|
|
155
|
+
if (!fs.existsSync(normalizedPath)) {
|
|
156
|
+
throw new Error(`External datasource file not found: ${normalizedPath}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
resolvedFiles.push({
|
|
160
|
+
path: normalizedPath,
|
|
161
|
+
type: 'datasource',
|
|
162
|
+
fileName: datasourceFile
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return resolvedFiles;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
module.exports = {
|
|
171
|
+
resolveSchemaBasePath,
|
|
172
|
+
resolveExternalFiles
|
|
173
|
+
};
|
|
174
|
+
|
|
@@ -13,10 +13,10 @@ const path = require('path');
|
|
|
13
13
|
const yaml = require('js-yaml');
|
|
14
14
|
const config = require('../config');
|
|
15
15
|
const { buildHostnameToServiceMap, resolveUrlPort } = require('./secrets-utils');
|
|
16
|
-
const { rewriteInfraEndpoints, getEnvHosts } = require('./env-endpoints');
|
|
16
|
+
const { rewriteInfraEndpoints, getEnvHosts, getServicePort, getServiceHost, getLocalhostOverride } = require('./env-endpoints');
|
|
17
17
|
const { loadEnvConfig } = require('./env-config-loader');
|
|
18
|
-
const { processEnvVariables } = require('./env-copy');
|
|
19
18
|
const { updateContainerPortInEnvFile } = require('./env-ports');
|
|
19
|
+
const { buildEnvVarMap } = require('./env-map');
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Interpolate ${VAR} occurrences with values from envVars map
|
|
@@ -262,6 +262,24 @@ async function adjustLocalEnvPortsInContent(envContent, variablesPath) {
|
|
|
262
262
|
// Update infra endpoints with developer-id adjusted ports for local context
|
|
263
263
|
updated = await rewriteInfraEndpoints(updated, 'local');
|
|
264
264
|
|
|
265
|
+
// Interpolate ${VAR} references created by rewriteInfraEndpoints
|
|
266
|
+
// Get the ports that were just set by rewriteInfraEndpoints for interpolation
|
|
267
|
+
const hostsForPorts = await getEnvHosts('local');
|
|
268
|
+
const redisPort = await getServicePort('REDIS_PORT', 'redis', hostsForPorts, 'local');
|
|
269
|
+
const dbPort = await getServicePort('DB_PORT', 'postgres', hostsForPorts, 'local');
|
|
270
|
+
const localhostOverride = getLocalhostOverride('local');
|
|
271
|
+
const redisHost = getServiceHost(hostsForPorts.REDIS_HOST, 'local', 'localhost', localhostOverride);
|
|
272
|
+
const dbHost = getServiceHost(hostsForPorts.DB_HOST, 'local', 'localhost', localhostOverride);
|
|
273
|
+
|
|
274
|
+
// Build envVars map and ensure it has the correct values
|
|
275
|
+
const envVars = await buildEnvVarMap('local', null, devIdNum);
|
|
276
|
+
// Override with the actual values that were just set by rewriteInfraEndpoints
|
|
277
|
+
envVars.REDIS_HOST = redisHost;
|
|
278
|
+
envVars.REDIS_PORT = String(redisPort);
|
|
279
|
+
envVars.DB_HOST = dbHost;
|
|
280
|
+
envVars.DB_PORT = String(dbPort);
|
|
281
|
+
updated = interpolateEnvVars(updated, envVars);
|
|
282
|
+
|
|
265
283
|
return updated;
|
|
266
284
|
}
|
|
267
285
|
|
|
@@ -276,6 +294,29 @@ function readYamlAtPath(filePath) {
|
|
|
276
294
|
return yaml.load(content);
|
|
277
295
|
}
|
|
278
296
|
|
|
297
|
+
/**
|
|
298
|
+
* Merge a single secret value from canonical into result
|
|
299
|
+
* @function mergeSecretValue
|
|
300
|
+
* @param {Object} result - Result object to merge into
|
|
301
|
+
* @param {string} key - Secret key
|
|
302
|
+
* @param {*} canonicalValue - Value from canonical secrets
|
|
303
|
+
*/
|
|
304
|
+
function mergeSecretValue(result, key, canonicalValue) {
|
|
305
|
+
const currentValue = result[key];
|
|
306
|
+
// Fill missing, empty, or undefined values
|
|
307
|
+
if (!(key in result) || currentValue === undefined || currentValue === null || currentValue === '') {
|
|
308
|
+
result[key] = canonicalValue;
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
// Only replace values that are encrypted (have secure:// prefix)
|
|
312
|
+
// Plaintext values (no secure://) are used as-is
|
|
313
|
+
if (typeof currentValue === 'string' && typeof canonicalValue === 'string') {
|
|
314
|
+
if (currentValue.startsWith('secure://')) {
|
|
315
|
+
result[key] = canonicalValue;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
279
320
|
/**
|
|
280
321
|
* Apply canonical secrets path override if configured and file exists
|
|
281
322
|
* @async
|
|
@@ -287,21 +328,29 @@ async function applyCanonicalSecretsOverride(currentSecrets) {
|
|
|
287
328
|
let mergedSecrets = currentSecrets || {};
|
|
288
329
|
try {
|
|
289
330
|
const canonicalPath = await config.getSecretsPath();
|
|
290
|
-
if (canonicalPath) {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
331
|
+
if (!canonicalPath) {
|
|
332
|
+
return mergedSecrets;
|
|
333
|
+
}
|
|
334
|
+
const resolvedCanonical = path.isAbsolute(canonicalPath)
|
|
335
|
+
? canonicalPath
|
|
336
|
+
: path.resolve(process.cwd(), canonicalPath);
|
|
337
|
+
if (!fs.existsSync(resolvedCanonical)) {
|
|
338
|
+
return mergedSecrets;
|
|
339
|
+
}
|
|
340
|
+
const configSecrets = readYamlAtPath(resolvedCanonical);
|
|
341
|
+
if (!configSecrets || typeof configSecrets !== 'object') {
|
|
342
|
+
return mergedSecrets;
|
|
343
|
+
}
|
|
344
|
+
// Apply canonical secrets as a fallback source:
|
|
345
|
+
// - Do NOT override any existing keys from user/build
|
|
346
|
+
// - Add only missing keys from canonical path
|
|
347
|
+
// - Also fill in empty/undefined values from canonical path
|
|
348
|
+
// - Replace encrypted values (secure://) with canonical plaintext
|
|
349
|
+
const result = { ...mergedSecrets };
|
|
350
|
+
for (const [key, canonicalValue] of Object.entries(configSecrets)) {
|
|
351
|
+
mergeSecretValue(result, key, canonicalValue);
|
|
304
352
|
}
|
|
353
|
+
mergedSecrets = result;
|
|
305
354
|
} catch {
|
|
306
355
|
// ignore and fall through
|
|
307
356
|
}
|
|
@@ -351,7 +400,6 @@ module.exports = {
|
|
|
351
400
|
replaceKvInContent,
|
|
352
401
|
resolveServicePortsInEnvContent,
|
|
353
402
|
loadEnvTemplate,
|
|
354
|
-
processEnvVariables,
|
|
355
403
|
updateContainerPortInEnvFile,
|
|
356
404
|
adjustLocalEnvPortsInContent,
|
|
357
405
|
readYamlAtPath,
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Fabrix Builder Token Encryption Utilities
|
|
3
|
+
*
|
|
4
|
+
* This module provides encryption and decryption functions for authentication tokens
|
|
5
|
+
* using AES-256-GCM algorithm for ISO 27001 compliance.
|
|
6
|
+
* Reuses the same encryption infrastructure as secrets encryption.
|
|
7
|
+
*
|
|
8
|
+
* @fileoverview Token encryption utilities for AI Fabrix Builder
|
|
9
|
+
* @author AI Fabrix Team
|
|
10
|
+
* @version 2.0.0
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { encryptSecret, decryptSecret, isEncrypted } = require('./secrets-encryption');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Encrypts a token value using AES-256-GCM
|
|
17
|
+
* Returns encrypted value in format: secure://<iv>:<ciphertext>:<authTag>
|
|
18
|
+
* All components are base64 encoded
|
|
19
|
+
*
|
|
20
|
+
* @function encryptToken
|
|
21
|
+
* @param {string} value - Plaintext token value to encrypt
|
|
22
|
+
* @param {string} key - Encryption key (hex or base64, 32 bytes)
|
|
23
|
+
* @returns {string} Encrypted value with secure:// prefix
|
|
24
|
+
* @throws {Error} If encryption fails or key is invalid
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* const encrypted = encryptToken('my-token', 'a1b2c3...');
|
|
28
|
+
* // Returns: 'secure://<iv>:<ciphertext>:<authTag>'
|
|
29
|
+
*/
|
|
30
|
+
function encryptToken(value, key) {
|
|
31
|
+
return encryptSecret(value, key);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Decrypts an encrypted token value
|
|
36
|
+
* Handles secure:// prefixed values and extracts IV, ciphertext, and auth tag
|
|
37
|
+
*
|
|
38
|
+
* @function decryptToken
|
|
39
|
+
* @param {string} encryptedValue - Encrypted value with secure:// prefix
|
|
40
|
+
* @param {string} key - Encryption key (hex or base64, 32 bytes)
|
|
41
|
+
* @returns {string} Decrypted plaintext value
|
|
42
|
+
* @throws {Error} If decryption fails, key is invalid, or format is incorrect
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* const decrypted = decryptToken('secure://<iv>:<ciphertext>:<authTag>', 'a1b2c3...');
|
|
46
|
+
* // Returns: 'my-token'
|
|
47
|
+
*/
|
|
48
|
+
function decryptToken(encryptedValue, key) {
|
|
49
|
+
return decryptSecret(encryptedValue, key);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Checks if a token value is encrypted (starts with secure://)
|
|
54
|
+
*
|
|
55
|
+
* @function isTokenEncrypted
|
|
56
|
+
* @param {string} value - Value to check
|
|
57
|
+
* @returns {boolean} True if value is encrypted
|
|
58
|
+
*/
|
|
59
|
+
function isTokenEncrypted(value) {
|
|
60
|
+
return isEncrypted(value);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = {
|
|
64
|
+
encryptToken,
|
|
65
|
+
decryptToken,
|
|
66
|
+
isTokenEncrypted
|
|
67
|
+
};
|
|
68
|
+
|