@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,262 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://raw.githubusercontent.com/esystemsdev/aifabrix-builder/refs/heads/main/lib/schema/external-system.schema.json",
|
|
4
|
+
"title": "AI Fabrix External System Configuration Schema",
|
|
5
|
+
"description": "Schema for configuring an external system connected to the AI Fabrix Dataplane. This defines authentication, OpenAPI/MCP bindings, field mappings defaults, metadata handling and portal inputs.",
|
|
6
|
+
|
|
7
|
+
"metadata": {
|
|
8
|
+
"key": "external-system-schema",
|
|
9
|
+
"name": "External System Configuration Schema",
|
|
10
|
+
"description": "JSON schema for validating ExternalSystem configuration files",
|
|
11
|
+
"version": "1.0.0",
|
|
12
|
+
"type": "schema",
|
|
13
|
+
"category": "integration",
|
|
14
|
+
"author": "AI Fabrix Team",
|
|
15
|
+
"createdAt": "2024-01-01T00:00:00Z",
|
|
16
|
+
"updatedAt": "2024-01-01T00:00:00Z",
|
|
17
|
+
"compatibility": {
|
|
18
|
+
"minVersion": "1.0.0",
|
|
19
|
+
"maxVersion": "2.0.0",
|
|
20
|
+
"deprecated": false
|
|
21
|
+
},
|
|
22
|
+
"tags": ["schema", "external-system", "dataplane", "integration", "validation"],
|
|
23
|
+
"dependencies": [],
|
|
24
|
+
"changelog": [
|
|
25
|
+
{
|
|
26
|
+
"version": "1.0.0",
|
|
27
|
+
"date": "2024-01-01T00:00:00Z",
|
|
28
|
+
"changes": [
|
|
29
|
+
"Initial schema for External Systems",
|
|
30
|
+
"Added OpenAPI and MCP contract configuration",
|
|
31
|
+
"Added authentication and environment variables",
|
|
32
|
+
"Added portalInput for UI-driven onboarding",
|
|
33
|
+
"Compatible with field-mappings.schema and security-model.schema"
|
|
34
|
+
],
|
|
35
|
+
"breaking": false
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
"type": "object",
|
|
41
|
+
|
|
42
|
+
"required": [
|
|
43
|
+
"key",
|
|
44
|
+
"displayName",
|
|
45
|
+
"description",
|
|
46
|
+
"type",
|
|
47
|
+
"authentication"
|
|
48
|
+
],
|
|
49
|
+
|
|
50
|
+
"properties": {
|
|
51
|
+
"key": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"description": "Unique external system identifier (cannot be changed after creation). Example: 'hubspot', 'salesforce', 'teams'.",
|
|
54
|
+
"pattern": "^[a-z0-9-]+$",
|
|
55
|
+
"minLength": 3,
|
|
56
|
+
"maxLength": 40
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
"displayName": {
|
|
60
|
+
"type": "string",
|
|
61
|
+
"description": "Human-readable name for the external system",
|
|
62
|
+
"minLength": 1,
|
|
63
|
+
"maxLength": 100
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
"description": {
|
|
67
|
+
"type": "string",
|
|
68
|
+
"description": "Description of this external system integration",
|
|
69
|
+
"minLength": 1,
|
|
70
|
+
"maxLength": 500
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
"type": {
|
|
74
|
+
"type": "string",
|
|
75
|
+
"enum": ["openapi", "mcp", "custom"],
|
|
76
|
+
"description": "Integration type: OpenAPI-driven, MCP-driven, or custom Python connector."
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
"enabled": {
|
|
80
|
+
"type": "boolean",
|
|
81
|
+
"default": true
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
"environment": {
|
|
85
|
+
"type": "object",
|
|
86
|
+
"description": "Environment-level configuration values used by dataplane and external data sources.",
|
|
87
|
+
"properties": {
|
|
88
|
+
"baseUrl": {
|
|
89
|
+
"type": "string",
|
|
90
|
+
"description": "Base API URL or MCP server URL",
|
|
91
|
+
"pattern": "^(http|https)://.*$"
|
|
92
|
+
},
|
|
93
|
+
"region": {
|
|
94
|
+
"type": "string",
|
|
95
|
+
"description": "Optional region setting for API routing"
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
"additionalProperties": true
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
"authentication": {
|
|
102
|
+
"type": "object",
|
|
103
|
+
"description": "Authentication configuration for the external system.",
|
|
104
|
+
"required": ["type"],
|
|
105
|
+
"properties": {
|
|
106
|
+
"type": {
|
|
107
|
+
"type": "string",
|
|
108
|
+
"enum": ["oauth2", "apikey", "basic", "aad", "none"],
|
|
109
|
+
"description": "Authentication method used for API access."
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
"oauth2": {
|
|
113
|
+
"type": "object",
|
|
114
|
+
"description": "OAuth2 configuration used when type == 'oauth2'. All secrets are stored in Key Vault.",
|
|
115
|
+
"properties": {
|
|
116
|
+
"tokenUrl": {
|
|
117
|
+
"type": "string",
|
|
118
|
+
"pattern": "^(http|https)://.*$"
|
|
119
|
+
},
|
|
120
|
+
"clientId": {
|
|
121
|
+
"type": "string"
|
|
122
|
+
},
|
|
123
|
+
"clientSecret": {
|
|
124
|
+
"type": "string"
|
|
125
|
+
},
|
|
126
|
+
"scopes": {
|
|
127
|
+
"type": "array",
|
|
128
|
+
"items": { "type": "string" }
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
"additionalProperties": false
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
"apikey": {
|
|
135
|
+
"type": "object",
|
|
136
|
+
"properties": {
|
|
137
|
+
"headerName": { "type": "string" },
|
|
138
|
+
"key": { "type": "string" }
|
|
139
|
+
},
|
|
140
|
+
"additionalProperties": false
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
"basic": {
|
|
144
|
+
"type": "object",
|
|
145
|
+
"properties": {
|
|
146
|
+
"username": { "type": "string" },
|
|
147
|
+
"password": { "type": "string" }
|
|
148
|
+
},
|
|
149
|
+
"additionalProperties": false
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
"additionalProperties": false
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
"openapi": {
|
|
156
|
+
"type": "object",
|
|
157
|
+
"description": "OpenAPI integration configuration.",
|
|
158
|
+
"properties": {
|
|
159
|
+
"documentKey": {
|
|
160
|
+
"type": "string",
|
|
161
|
+
"description": "Reference to OpenAPI spec registered via builder. Example: 'hubspot-v3'."
|
|
162
|
+
},
|
|
163
|
+
"autoDiscoverEntities": {
|
|
164
|
+
"type": "boolean",
|
|
165
|
+
"default": false,
|
|
166
|
+
"description": "Automatically discover resources/entities from OpenAPI schema."
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
"additionalProperties": false
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
"mcp": {
|
|
173
|
+
"type": "object",
|
|
174
|
+
"description": "Model Context Protocol integration config.",
|
|
175
|
+
"properties": {
|
|
176
|
+
"serverUrl": {
|
|
177
|
+
"type": "string",
|
|
178
|
+
"pattern": "^(http|https)://.*$"
|
|
179
|
+
},
|
|
180
|
+
"toolPrefix": {
|
|
181
|
+
"type": "string",
|
|
182
|
+
"pattern": "^[a-z0-9-]+$",
|
|
183
|
+
"description": "Prefix for MCP tool names generated from this system."
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
"additionalProperties": false
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
"dataSources": {
|
|
190
|
+
"type": "array",
|
|
191
|
+
"description": "List of data source keys belonging to this external system. Each must match external-datasource.schema.json.",
|
|
192
|
+
"items": {
|
|
193
|
+
"type": "string",
|
|
194
|
+
"pattern": "^[a-z0-9-]+$"
|
|
195
|
+
},
|
|
196
|
+
"uniqueItems": true
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
"configuration": {
|
|
200
|
+
"type": "array",
|
|
201
|
+
"description": "External system configuration variables (same pattern as application-schema).",
|
|
202
|
+
"items": {
|
|
203
|
+
"type": "object",
|
|
204
|
+
"required": ["name", "value", "location", "required"],
|
|
205
|
+
"properties": {
|
|
206
|
+
"name": {
|
|
207
|
+
"type": "string",
|
|
208
|
+
"pattern": "^[A-Z_][A-Z0-9_]*$"
|
|
209
|
+
},
|
|
210
|
+
"value": {
|
|
211
|
+
"type": "string",
|
|
212
|
+
"description": "Literal value or parameter reference ({{parameter}})"
|
|
213
|
+
},
|
|
214
|
+
"location": {
|
|
215
|
+
"type": "string",
|
|
216
|
+
"enum": ["variable", "keyvault"]
|
|
217
|
+
},
|
|
218
|
+
"required": {
|
|
219
|
+
"type": "boolean"
|
|
220
|
+
},
|
|
221
|
+
"portalInput": {
|
|
222
|
+
"type": "object",
|
|
223
|
+
"required": ["field", "label"],
|
|
224
|
+
"properties": {
|
|
225
|
+
"field": {
|
|
226
|
+
"type": "string",
|
|
227
|
+
"enum": ["password", "text", "textarea", "select", "json"]
|
|
228
|
+
},
|
|
229
|
+
"label": { "type": "string" },
|
|
230
|
+
"placeholder": { "type": "string" },
|
|
231
|
+
"options": {
|
|
232
|
+
"type": "array",
|
|
233
|
+
"items": { "type": "string" }
|
|
234
|
+
},
|
|
235
|
+
"masked": { "type": "boolean" },
|
|
236
|
+
"validation": {
|
|
237
|
+
"type": "object",
|
|
238
|
+
"properties": {
|
|
239
|
+
"minLength": { "type": "integer" },
|
|
240
|
+
"maxLength": { "type": "integer" },
|
|
241
|
+
"pattern": { "type": "string" },
|
|
242
|
+
"required": { "type": "boolean" }
|
|
243
|
+
},
|
|
244
|
+
"additionalProperties": false
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
"additionalProperties": false
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
"additionalProperties": false
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
"tags": {
|
|
255
|
+
"type": "array",
|
|
256
|
+
"description": "Optional tags for search / filtering",
|
|
257
|
+
"items": { "type": "string" }
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
"additionalProperties": false
|
|
262
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
-
"$id": "https://
|
|
3
|
+
"$id": "https://raw.githubusercontent.com/esystemsdev/aifabrix-builder/refs/heads/main/lib/schema/infrastructure-schema.json",
|
|
4
4
|
"title": "AI Fabrix Infrastructure Configuration Schema",
|
|
5
5
|
"description": "Infrastructure-level configuration schema for AI Fabrix deployment",
|
|
6
6
|
"metadata": {
|
package/lib/secrets.js
CHANGED
|
@@ -20,7 +20,6 @@ const {
|
|
|
20
20
|
formatMissingSecretsFileInfo,
|
|
21
21
|
replaceKvInContent,
|
|
22
22
|
loadEnvTemplate,
|
|
23
|
-
processEnvVariables,
|
|
24
23
|
adjustLocalEnvPortsInContent,
|
|
25
24
|
rewriteInfraEndpoints,
|
|
26
25
|
readYamlAtPath,
|
|
@@ -28,6 +27,7 @@ const {
|
|
|
28
27
|
ensureNonEmptySecrets,
|
|
29
28
|
validateSecrets
|
|
30
29
|
} = require('./utils/secrets-helpers');
|
|
30
|
+
const { processEnvVariables } = require('./utils/env-copy');
|
|
31
31
|
const { buildEnvVarMap } = require('./utils/env-map');
|
|
32
32
|
const { resolveServicePortsInEnvContent } = require('./utils/secrets-url');
|
|
33
33
|
const {
|
|
@@ -291,6 +291,25 @@ async function applyEnvironmentTransformations(resolved, environment, variablesP
|
|
|
291
291
|
if (environment === 'docker') {
|
|
292
292
|
resolved = await resolveServicePortsInEnvContent(resolved, environment);
|
|
293
293
|
resolved = await rewriteInfraEndpoints(resolved, 'docker');
|
|
294
|
+
// Interpolate ${VAR} references created by rewriteInfraEndpoints
|
|
295
|
+
// Get the actual host and port values from env-endpoints.js directly
|
|
296
|
+
// to ensure they are correctly populated in envVars for interpolation
|
|
297
|
+
const { getEnvHosts, getServiceHost, getServicePort, getLocalhostOverride } = require('./utils/env-endpoints');
|
|
298
|
+
const hosts = await getEnvHosts('docker');
|
|
299
|
+
const localhostOverride = getLocalhostOverride('docker');
|
|
300
|
+
const redisHost = getServiceHost(hosts.REDIS_HOST, 'docker', 'redis', localhostOverride);
|
|
301
|
+
const redisPort = await getServicePort('REDIS_PORT', 'redis', hosts, 'docker', null);
|
|
302
|
+
const dbHost = getServiceHost(hosts.DB_HOST, 'docker', 'postgres', localhostOverride);
|
|
303
|
+
const dbPort = await getServicePort('DB_PORT', 'postgres', hosts, 'docker', null);
|
|
304
|
+
|
|
305
|
+
// Build envVars map and ensure it has the correct values
|
|
306
|
+
const envVars = await buildEnvVarMap('docker');
|
|
307
|
+
// Override with the actual values that were just set by rewriteInfraEndpoints
|
|
308
|
+
envVars.REDIS_HOST = redisHost;
|
|
309
|
+
envVars.REDIS_PORT = String(redisPort);
|
|
310
|
+
envVars.DB_HOST = dbHost;
|
|
311
|
+
envVars.DB_PORT = String(dbPort);
|
|
312
|
+
resolved = interpolateEnvVars(resolved, envVars);
|
|
294
313
|
resolved = await updatePortForDocker(resolved, variablesPath);
|
|
295
314
|
} else if (environment === 'local') {
|
|
296
315
|
// adjustLocalEnvPortsInContent handles both PORT and infra endpoints
|
package/lib/templates.js
CHANGED
|
@@ -16,6 +16,37 @@ const yaml = require('js-yaml');
|
|
|
16
16
|
*/
|
|
17
17
|
function generateVariablesYaml(appName, config) {
|
|
18
18
|
const displayName = appName.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
|
|
19
|
+
const appType = config.type || 'webapp';
|
|
20
|
+
|
|
21
|
+
// For external type, create minimal variables.yaml
|
|
22
|
+
if (appType === 'external') {
|
|
23
|
+
const variables = {
|
|
24
|
+
app: {
|
|
25
|
+
key: appName,
|
|
26
|
+
displayName: displayName,
|
|
27
|
+
description: `${appName.replace(/-/g, ' ')} external system`,
|
|
28
|
+
type: 'external'
|
|
29
|
+
},
|
|
30
|
+
deployment: {
|
|
31
|
+
controllerUrl: '',
|
|
32
|
+
environment: 'dev'
|
|
33
|
+
},
|
|
34
|
+
externalIntegration: {
|
|
35
|
+
schemaBasePath: './schemas',
|
|
36
|
+
systems: [],
|
|
37
|
+
dataSources: [],
|
|
38
|
+
autopublish: true,
|
|
39
|
+
version: '1.0.0'
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return yaml.dump(variables, {
|
|
44
|
+
indent: 2,
|
|
45
|
+
lineWidth: 120,
|
|
46
|
+
noRefs: true,
|
|
47
|
+
sortKeys: false
|
|
48
|
+
});
|
|
49
|
+
}
|
|
19
50
|
|
|
20
51
|
// Parse image name to separate name and tag
|
|
21
52
|
let imageName = appName;
|
|
@@ -32,7 +63,7 @@ function generateVariablesYaml(appName, config) {
|
|
|
32
63
|
key: appName,
|
|
33
64
|
displayName: displayName,
|
|
34
65
|
description: `${appName.replace(/-/g, ' ')} application`,
|
|
35
|
-
type:
|
|
66
|
+
type: appType
|
|
36
67
|
},
|
|
37
68
|
image: {
|
|
38
69
|
name: imageName,
|
package/lib/utils/device-code.js
CHANGED
|
@@ -61,20 +61,28 @@ function parseDeviceCodeResponse(response) {
|
|
|
61
61
|
* @function initiateDeviceCodeFlow
|
|
62
62
|
* @param {string} controllerUrl - Base URL of the controller
|
|
63
63
|
* @param {string} environment - Environment key (e.g., 'miso', 'dev', 'tst', 'pro')
|
|
64
|
+
* @param {string} [scope] - OAuth2 scope string (default: 'openid profile email')
|
|
64
65
|
* @returns {Promise<Object>} Device code response with device_code, user_code, verification_uri, expires_in, interval
|
|
65
66
|
* @throws {Error} If initiation fails
|
|
66
67
|
*/
|
|
67
|
-
async function initiateDeviceCodeFlow(controllerUrl, environment) {
|
|
68
|
+
async function initiateDeviceCodeFlow(controllerUrl, environment, scope) {
|
|
68
69
|
if (!environment || typeof environment !== 'string') {
|
|
69
70
|
throw new Error('Environment key is required');
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
// Default scope for backward compatibility
|
|
74
|
+
const defaultScope = 'openid profile email';
|
|
75
|
+
const requestScope = scope || defaultScope;
|
|
76
|
+
|
|
72
77
|
const url = `${controllerUrl}/api/v1/auth/login?environment=${encodeURIComponent(environment)}`;
|
|
73
78
|
const response = await getMakeApiCall()(url, {
|
|
74
79
|
method: 'POST',
|
|
75
80
|
headers: {
|
|
76
81
|
'Content-Type': 'application/json'
|
|
77
|
-
}
|
|
82
|
+
},
|
|
83
|
+
body: JSON.stringify({
|
|
84
|
+
scope: requestScope
|
|
85
|
+
})
|
|
78
86
|
});
|
|
79
87
|
|
|
80
88
|
if (!response.success) {
|
package/lib/utils/env-copy.js
CHANGED
|
@@ -16,6 +16,8 @@ const logger = require('./logger');
|
|
|
16
16
|
const config = require('../config');
|
|
17
17
|
const devConfig = require('../utils/dev-config');
|
|
18
18
|
const { rewriteInfraEndpoints } = require('./env-endpoints');
|
|
19
|
+
const { buildEnvVarMap } = require('./env-map');
|
|
20
|
+
const { interpolateEnvVars } = require('./secrets-helpers');
|
|
19
21
|
|
|
20
22
|
/**
|
|
21
23
|
* Process and optionally copy env file to envOutputPath if configured
|
|
@@ -120,6 +122,28 @@ async function processEnvVariables(envPath, variablesPath, appName, secretsPath)
|
|
|
120
122
|
});
|
|
121
123
|
// Rewrite infra endpoints using env-config mapping for local context
|
|
122
124
|
envContent = await rewriteInfraEndpoints(envContent, 'local', infraPorts);
|
|
125
|
+
// Interpolate ${VAR} references created by rewriteInfraEndpoints
|
|
126
|
+
// Extract actual values from updated content to use for interpolation
|
|
127
|
+
const envVars = await buildEnvVarMap('local', null, devIdNum);
|
|
128
|
+
// Extract REDIS_HOST, REDIS_PORT, DB_HOST, DB_PORT from updated content if present
|
|
129
|
+
// Only extract if the value doesn't contain ${VAR} references (to avoid circular interpolation)
|
|
130
|
+
const redisHostMatch = envContent.match(/^REDIS_HOST\s*=\s*([^\r\n$]+)/m);
|
|
131
|
+
const redisPortMatch = envContent.match(/^REDIS_PORT\s*=\s*([^\r\n$]+)/m);
|
|
132
|
+
const dbHostMatch = envContent.match(/^DB_HOST\s*=\s*([^\r\n$]+)/m);
|
|
133
|
+
const dbPortMatch = envContent.match(/^DB_PORT\s*=\s*([^\r\n$]+)/m);
|
|
134
|
+
if (redisHostMatch && redisHostMatch[1] && !redisHostMatch[1].includes('${')) {
|
|
135
|
+
envVars.REDIS_HOST = redisHostMatch[1].trim();
|
|
136
|
+
}
|
|
137
|
+
if (redisPortMatch && redisPortMatch[1] && !redisPortMatch[1].includes('${')) {
|
|
138
|
+
envVars.REDIS_PORT = redisPortMatch[1].trim();
|
|
139
|
+
}
|
|
140
|
+
if (dbHostMatch && dbHostMatch[1] && !dbHostMatch[1].includes('${')) {
|
|
141
|
+
envVars.DB_HOST = dbHostMatch[1].trim();
|
|
142
|
+
}
|
|
143
|
+
if (dbPortMatch && dbPortMatch[1] && !dbPortMatch[1].includes('${')) {
|
|
144
|
+
envVars.DB_PORT = dbPortMatch[1].trim();
|
|
145
|
+
}
|
|
146
|
+
envContent = interpolateEnvVars(envContent, envVars);
|
|
123
147
|
fs.writeFileSync(outputPath, envContent, { mode: 0o600 });
|
|
124
148
|
logger.log(chalk.green(`✓ Copied .env to: ${variables.build.envOutputPath}`));
|
|
125
149
|
}
|
|
@@ -149,27 +149,26 @@ function updateEndpointVariables(envContent, redisHost, redisPort, dbHost, dbPor
|
|
|
149
149
|
|
|
150
150
|
// Update REDIS_URL if present
|
|
151
151
|
if (/^REDIS_URL\s*=.*$/m.test(updated)) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
updated = updated.replace(
|
|
157
|
-
/^REDIS_URL\s*=\s*.*$/m,
|
|
158
|
-
`REDIS_URL=redis://${targetHost}:${redisPort}`
|
|
159
|
-
);
|
|
160
|
-
}
|
|
152
|
+
updated = updated.replace(
|
|
153
|
+
/^REDIS_URL\s*=\s*.*$/m,
|
|
154
|
+
'REDIS_URL=redis://${REDIS_HOST}:${REDIS_PORT}'
|
|
155
|
+
);
|
|
161
156
|
}
|
|
162
157
|
|
|
163
158
|
// Update REDIS_HOST if present
|
|
159
|
+
// If the original has host:port pattern, use ${VAR} references
|
|
160
|
+
// Otherwise, just set the host value
|
|
164
161
|
if (/^REDIS_HOST\s*=.*$/m.test(updated)) {
|
|
165
162
|
const hostPortMatch = updated.match(/^REDIS_HOST\s*=\s*([a-zA-Z0-9_.-]+):\d+$/m);
|
|
166
163
|
const hasPortPattern = !!hostPortMatch;
|
|
167
164
|
if (hasPortPattern) {
|
|
165
|
+
// Original had host:port pattern, use ${VAR} references
|
|
168
166
|
updated = updated.replace(
|
|
169
167
|
/^REDIS_HOST\s*=\s*.*$/m,
|
|
170
|
-
|
|
168
|
+
'REDIS_HOST=${REDIS_HOST}:${REDIS_PORT}'
|
|
171
169
|
);
|
|
172
170
|
} else {
|
|
171
|
+
// Just host, set actual value
|
|
173
172
|
updated = updated.replace(/^REDIS_HOST\s*=\s*.*$/m, `REDIS_HOST=${redisHost}`);
|
|
174
173
|
}
|
|
175
174
|
}
|
|
@@ -195,6 +194,31 @@ function updateEndpointVariables(envContent, redisHost, redisPort, dbHost, dbPor
|
|
|
195
194
|
);
|
|
196
195
|
}
|
|
197
196
|
|
|
197
|
+
// Update DATABASE_URL and other database URL variables (postgresql:// or postgres:// URLs)
|
|
198
|
+
// Handles DATABASE_URL, DATABASELOG_URL, and any other *_URL variables with postgresql:// or postgres://
|
|
199
|
+
// First, collect all matches to avoid issues with modifying string during iteration
|
|
200
|
+
const dbUrlPattern = /^(DATABASE[^=]*_URL)\s*=\s*(postgresql?:\/\/)([^:]+):([^@]+)@([^:/]+):(\d+)(\/[^\s]*)?/gm;
|
|
201
|
+
const matches = [];
|
|
202
|
+
let dbUrlMatch;
|
|
203
|
+
// Reset regex lastIndex to ensure we start from beginning
|
|
204
|
+
dbUrlPattern.lastIndex = 0;
|
|
205
|
+
while ((dbUrlMatch = dbUrlPattern.exec(updated)) !== null) {
|
|
206
|
+
matches.push({
|
|
207
|
+
varName: dbUrlMatch[1], // DATABASE_URL, DATABASELOG_URL, etc.
|
|
208
|
+
protocol: dbUrlMatch[2], // postgresql:// or postgres://
|
|
209
|
+
user: dbUrlMatch[3], // username
|
|
210
|
+
password: dbUrlMatch[4], // password
|
|
211
|
+
path: dbUrlMatch[7] || '' // /database path
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
// Now apply all replacements
|
|
215
|
+
for (const match of matches) {
|
|
216
|
+
updated = updated.replace(
|
|
217
|
+
new RegExp(`^${match.varName}\\s*=\\s*.*$`, 'm'),
|
|
218
|
+
`${match.varName}=${match.protocol}${match.user}:${match.password}@\${DB_HOST}:\${DB_PORT}${match.path}`
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
198
222
|
// Update DATABASE_PORT if present (some templates use DATABASE_PORT instead of DB_PORT)
|
|
199
223
|
if (/^DATABASE_PORT\s*=.*$/m.test(updated)) {
|
|
200
224
|
updated = updated.replace(
|
|
@@ -203,6 +227,20 @@ function updateEndpointVariables(envContent, redisHost, redisPort, dbHost, dbPor
|
|
|
203
227
|
);
|
|
204
228
|
}
|
|
205
229
|
|
|
230
|
+
// Update Keycloak database variables if present
|
|
231
|
+
// KC_DB_URL_HOST is used by Keycloak to connect to the database
|
|
232
|
+
if (/^KC_DB_URL_HOST\s*=.*$/m.test(updated)) {
|
|
233
|
+
updated = updated.replace(/^KC_DB_URL_HOST\s*=\s*.*$/m, `KC_DB_URL_HOST=${dbHost}`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// KC_DB_URL_PORT is used by Keycloak for database port
|
|
237
|
+
if (/^KC_DB_URL_PORT\s*=.*$/m.test(updated)) {
|
|
238
|
+
updated = updated.replace(
|
|
239
|
+
/^KC_DB_URL_PORT\s*=\s*.*$/m,
|
|
240
|
+
`KC_DB_URL_PORT=${dbPort}`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
206
244
|
return updated;
|
|
207
245
|
}
|
|
208
246
|
|
|
@@ -259,6 +297,7 @@ module.exports = {
|
|
|
259
297
|
splitHost,
|
|
260
298
|
getServicePort,
|
|
261
299
|
getServiceHost,
|
|
262
|
-
updateEndpointVariables
|
|
300
|
+
updateEndpointVariables,
|
|
301
|
+
getLocalhostOverride
|
|
263
302
|
};
|
|
264
303
|
|