@aifabrix/builder 2.8.0 → 2.10.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/integration/hubspot/README.md +136 -0
- package/integration/hubspot/env.template +9 -0
- package/integration/hubspot/hubspot-deploy-company.json +200 -0
- package/integration/hubspot/hubspot-deploy-contact.json +228 -0
- package/integration/hubspot/hubspot-deploy-deal.json +248 -0
- package/integration/hubspot/hubspot-deploy.json +91 -0
- package/integration/hubspot/variables.yaml +17 -0
- package/lib/app-config.js +4 -3
- package/lib/app-deploy.js +8 -20
- package/lib/app-dockerfile.js +7 -9
- package/lib/app-prompts.js +6 -5
- package/lib/app-push.js +9 -9
- package/lib/app-register.js +23 -5
- package/lib/app-rotate-secret.js +10 -0
- package/lib/app-run.js +5 -11
- package/lib/app.js +42 -14
- package/lib/build.js +20 -16
- package/lib/cli.js +61 -2
- package/lib/commands/login.js +7 -1
- package/lib/datasource-deploy.js +14 -20
- package/lib/external-system-deploy.js +123 -40
- package/lib/external-system-download.js +431 -0
- package/lib/external-system-generator.js +13 -10
- package/lib/external-system-test.js +446 -0
- package/lib/generator-builders.js +323 -0
- package/lib/generator.js +200 -292
- package/lib/schema/application-schema.json +853 -852
- package/lib/schema/env-config.yaml +9 -1
- package/lib/schema/external-datasource.schema.json +823 -49
- package/lib/schema/external-system.schema.json +96 -78
- package/lib/templates.js +36 -5
- package/lib/utils/api-error-handler.js +12 -12
- package/lib/utils/cli-utils.js +4 -4
- package/lib/utils/device-code.js +65 -2
- package/lib/utils/env-template.js +5 -4
- package/lib/utils/external-system-display.js +159 -0
- package/lib/utils/external-system-validators.js +245 -0
- package/lib/utils/paths.js +151 -1
- package/lib/utils/schema-resolver.js +7 -2
- package/lib/validator.js +5 -2
- package/package.json +1 -1
- package/templates/applications/keycloak/env.template +8 -2
- package/templates/applications/keycloak/variables.yaml +3 -3
- package/templates/applications/miso-controller/env.template +23 -10
- package/templates/applications/miso-controller/rbac.yaml +263 -213
- package/templates/applications/miso-controller/variables.yaml +3 -3
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
"$id": "https://raw.githubusercontent.com/esystemsdev/aifabrix-builder/refs/heads/main/lib/schema/external-system.schema.json",
|
|
4
4
|
"title": "AI Fabrix External System Configuration Schema",
|
|
5
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
6
|
"metadata": {
|
|
8
7
|
"key": "external-system-schema",
|
|
9
8
|
"name": "External System Configuration Schema",
|
|
@@ -19,7 +18,13 @@
|
|
|
19
18
|
"maxVersion": "2.0.0",
|
|
20
19
|
"deprecated": false
|
|
21
20
|
},
|
|
22
|
-
"tags": [
|
|
21
|
+
"tags": [
|
|
22
|
+
"schema",
|
|
23
|
+
"external-system",
|
|
24
|
+
"dataplane",
|
|
25
|
+
"integration",
|
|
26
|
+
"validation"
|
|
27
|
+
],
|
|
23
28
|
"dependencies": [],
|
|
24
29
|
"changelog": [
|
|
25
30
|
{
|
|
@@ -36,9 +41,7 @@
|
|
|
36
41
|
}
|
|
37
42
|
]
|
|
38
43
|
},
|
|
39
|
-
|
|
40
44
|
"type": "object",
|
|
41
|
-
|
|
42
45
|
"required": [
|
|
43
46
|
"key",
|
|
44
47
|
"displayName",
|
|
@@ -46,7 +49,6 @@
|
|
|
46
49
|
"type",
|
|
47
50
|
"authentication"
|
|
48
51
|
],
|
|
49
|
-
|
|
50
52
|
"properties": {
|
|
51
53
|
"key": {
|
|
52
54
|
"type": "string",
|
|
@@ -55,32 +57,31 @@
|
|
|
55
57
|
"minLength": 3,
|
|
56
58
|
"maxLength": 40
|
|
57
59
|
},
|
|
58
|
-
|
|
59
60
|
"displayName": {
|
|
60
61
|
"type": "string",
|
|
61
62
|
"description": "Human-readable name for the external system",
|
|
62
63
|
"minLength": 1,
|
|
63
64
|
"maxLength": 100
|
|
64
65
|
},
|
|
65
|
-
|
|
66
66
|
"description": {
|
|
67
67
|
"type": "string",
|
|
68
68
|
"description": "Description of this external system integration",
|
|
69
69
|
"minLength": 1,
|
|
70
70
|
"maxLength": 500
|
|
71
71
|
},
|
|
72
|
-
|
|
73
72
|
"type": {
|
|
74
73
|
"type": "string",
|
|
75
|
-
"enum": [
|
|
74
|
+
"enum": [
|
|
75
|
+
"openapi",
|
|
76
|
+
"mcp",
|
|
77
|
+
"custom"
|
|
78
|
+
],
|
|
76
79
|
"description": "Integration type: OpenAPI-driven, MCP-driven, or custom Python connector."
|
|
77
80
|
},
|
|
78
|
-
|
|
79
81
|
"enabled": {
|
|
80
82
|
"type": "boolean",
|
|
81
83
|
"default": true
|
|
82
84
|
},
|
|
83
|
-
|
|
84
85
|
"environment": {
|
|
85
86
|
"type": "object",
|
|
86
87
|
"description": "Environment-level configuration values used by dataplane and external data sources.",
|
|
@@ -97,95 +98,80 @@
|
|
|
97
98
|
},
|
|
98
99
|
"additionalProperties": true
|
|
99
100
|
},
|
|
100
|
-
|
|
101
101
|
"authentication": {
|
|
102
102
|
"type": "object",
|
|
103
|
-
"description": "Authentication configuration for
|
|
104
|
-
"required": [
|
|
103
|
+
"description": "Authentication configuration for external system",
|
|
104
|
+
"required": [
|
|
105
|
+
"type"
|
|
106
|
+
],
|
|
105
107
|
"properties": {
|
|
106
108
|
"type": {
|
|
107
109
|
"type": "string",
|
|
108
|
-
"enum": [
|
|
109
|
-
|
|
110
|
+
"enum": [
|
|
111
|
+
"oauth2",
|
|
112
|
+
"apikey",
|
|
113
|
+
"basic",
|
|
114
|
+
"aad",
|
|
115
|
+
"none"
|
|
116
|
+
],
|
|
117
|
+
"description": "Authentication type"
|
|
110
118
|
},
|
|
111
|
-
|
|
112
119
|
"oauth2": {
|
|
113
120
|
"type": "object",
|
|
114
|
-
"description": "OAuth2 configuration
|
|
115
|
-
"
|
|
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
|
|
121
|
+
"description": "OAuth2 authentication configuration",
|
|
122
|
+
"additionalProperties": true
|
|
132
123
|
},
|
|
133
|
-
|
|
134
124
|
"apikey": {
|
|
135
125
|
"type": "object",
|
|
136
|
-
"
|
|
137
|
-
|
|
138
|
-
"key": { "type": "string" }
|
|
139
|
-
},
|
|
140
|
-
"additionalProperties": false
|
|
126
|
+
"description": "API key authentication configuration",
|
|
127
|
+
"additionalProperties": true
|
|
141
128
|
},
|
|
142
|
-
|
|
143
129
|
"basic": {
|
|
144
130
|
"type": "object",
|
|
145
|
-
"
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
"
|
|
131
|
+
"description": "Basic authentication configuration",
|
|
132
|
+
"additionalProperties": true
|
|
133
|
+
},
|
|
134
|
+
"aad": {
|
|
135
|
+
"type": "object",
|
|
136
|
+
"description": "Azure AD authentication configuration",
|
|
137
|
+
"additionalProperties": true
|
|
150
138
|
}
|
|
151
139
|
},
|
|
152
|
-
"additionalProperties":
|
|
140
|
+
"additionalProperties": true
|
|
153
141
|
},
|
|
154
|
-
|
|
155
142
|
"openapi": {
|
|
156
143
|
"type": "object",
|
|
157
|
-
"description": "OpenAPI integration configuration
|
|
144
|
+
"description": "OpenAPI integration configuration",
|
|
158
145
|
"properties": {
|
|
159
146
|
"documentKey": {
|
|
160
147
|
"type": "string",
|
|
161
|
-
"description": "
|
|
148
|
+
"description": "Key of the OpenAPI document in the registry"
|
|
162
149
|
},
|
|
163
|
-
"
|
|
164
|
-
"type": "
|
|
165
|
-
"
|
|
166
|
-
"
|
|
150
|
+
"specUrl": {
|
|
151
|
+
"type": "string",
|
|
152
|
+
"description": "URL to the OpenAPI specification",
|
|
153
|
+
"pattern": "^(http|https)://.*$"
|
|
167
154
|
}
|
|
168
155
|
},
|
|
169
|
-
"additionalProperties":
|
|
156
|
+
"additionalProperties": true
|
|
170
157
|
},
|
|
171
|
-
|
|
172
158
|
"mcp": {
|
|
173
159
|
"type": "object",
|
|
174
|
-
"description": "Model Context Protocol integration
|
|
160
|
+
"description": "Model Context Protocol integration configuration",
|
|
175
161
|
"properties": {
|
|
176
162
|
"serverUrl": {
|
|
177
163
|
"type": "string",
|
|
164
|
+
"description": "MCP server URL",
|
|
178
165
|
"pattern": "^(http|https)://.*$"
|
|
179
166
|
},
|
|
180
167
|
"toolPrefix": {
|
|
181
168
|
"type": "string",
|
|
182
|
-
"
|
|
183
|
-
"
|
|
169
|
+
"description": "Prefix for MCP tool names",
|
|
170
|
+
"pattern": "^[a-zA-Z0-9_-]+$"
|
|
184
171
|
}
|
|
185
172
|
},
|
|
186
|
-
"additionalProperties":
|
|
173
|
+
"additionalProperties": true
|
|
187
174
|
},
|
|
188
|
-
|
|
189
175
|
"dataSources": {
|
|
190
176
|
"type": "array",
|
|
191
177
|
"description": "List of data source keys belonging to this external system. Each must match external-datasource.schema.json.",
|
|
@@ -195,13 +181,17 @@
|
|
|
195
181
|
},
|
|
196
182
|
"uniqueItems": true
|
|
197
183
|
},
|
|
198
|
-
|
|
199
184
|
"configuration": {
|
|
200
185
|
"type": "array",
|
|
201
186
|
"description": "External system configuration variables (same pattern as application-schema).",
|
|
202
187
|
"items": {
|
|
203
188
|
"type": "object",
|
|
204
|
-
"required": [
|
|
189
|
+
"required": [
|
|
190
|
+
"name",
|
|
191
|
+
"value",
|
|
192
|
+
"location",
|
|
193
|
+
"required"
|
|
194
|
+
],
|
|
205
195
|
"properties": {
|
|
206
196
|
"name": {
|
|
207
197
|
"type": "string",
|
|
@@ -213,33 +203,61 @@
|
|
|
213
203
|
},
|
|
214
204
|
"location": {
|
|
215
205
|
"type": "string",
|
|
216
|
-
"enum": [
|
|
206
|
+
"enum": [
|
|
207
|
+
"variable",
|
|
208
|
+
"keyvault"
|
|
209
|
+
]
|
|
217
210
|
},
|
|
218
211
|
"required": {
|
|
219
212
|
"type": "boolean"
|
|
220
213
|
},
|
|
221
214
|
"portalInput": {
|
|
222
215
|
"type": "object",
|
|
223
|
-
"required": [
|
|
216
|
+
"required": [
|
|
217
|
+
"field",
|
|
218
|
+
"label"
|
|
219
|
+
],
|
|
224
220
|
"properties": {
|
|
225
221
|
"field": {
|
|
226
222
|
"type": "string",
|
|
227
|
-
"enum": [
|
|
223
|
+
"enum": [
|
|
224
|
+
"password",
|
|
225
|
+
"text",
|
|
226
|
+
"textarea",
|
|
227
|
+
"select",
|
|
228
|
+
"json"
|
|
229
|
+
]
|
|
230
|
+
},
|
|
231
|
+
"label": {
|
|
232
|
+
"type": "string"
|
|
233
|
+
},
|
|
234
|
+
"placeholder": {
|
|
235
|
+
"type": "string"
|
|
228
236
|
},
|
|
229
|
-
"label": { "type": "string" },
|
|
230
|
-
"placeholder": { "type": "string" },
|
|
231
237
|
"options": {
|
|
232
238
|
"type": "array",
|
|
233
|
-
"items": {
|
|
239
|
+
"items": {
|
|
240
|
+
"type": "string"
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
"masked": {
|
|
244
|
+
"type": "boolean"
|
|
234
245
|
},
|
|
235
|
-
"masked": { "type": "boolean" },
|
|
236
246
|
"validation": {
|
|
237
247
|
"type": "object",
|
|
238
248
|
"properties": {
|
|
239
|
-
"minLength": {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
"
|
|
249
|
+
"minLength": {
|
|
250
|
+
"type": "integer"
|
|
251
|
+
},
|
|
252
|
+
"maxLength": {
|
|
253
|
+
"type": "integer"
|
|
254
|
+
},
|
|
255
|
+
"pattern": {
|
|
256
|
+
"type": "string"
|
|
257
|
+
},
|
|
258
|
+
"required": {
|
|
259
|
+
"type": "boolean"
|
|
260
|
+
}
|
|
243
261
|
},
|
|
244
262
|
"additionalProperties": false
|
|
245
263
|
}
|
|
@@ -250,13 +268,13 @@
|
|
|
250
268
|
"additionalProperties": false
|
|
251
269
|
}
|
|
252
270
|
},
|
|
253
|
-
|
|
254
271
|
"tags": {
|
|
255
272
|
"type": "array",
|
|
256
273
|
"description": "Optional tags for search / filtering",
|
|
257
|
-
"items": {
|
|
274
|
+
"items": {
|
|
275
|
+
"type": "string"
|
|
276
|
+
}
|
|
258
277
|
}
|
|
259
278
|
},
|
|
260
|
-
|
|
261
279
|
"additionalProperties": false
|
|
262
280
|
}
|
package/lib/templates.js
CHANGED
|
@@ -32,7 +32,7 @@ function generateVariablesYaml(appName, config) {
|
|
|
32
32
|
environment: 'dev'
|
|
33
33
|
},
|
|
34
34
|
externalIntegration: {
|
|
35
|
-
schemaBasePath: './
|
|
35
|
+
schemaBasePath: './',
|
|
36
36
|
systems: [],
|
|
37
37
|
dataSources: [],
|
|
38
38
|
autopublish: true,
|
|
@@ -134,13 +134,31 @@ function generateVariablesYaml(appName, config) {
|
|
|
134
134
|
*/
|
|
135
135
|
function buildCoreEnv(config) {
|
|
136
136
|
return {
|
|
137
|
-
'NODE_ENV': '
|
|
137
|
+
'NODE_ENV': '${NODE_ENV}',
|
|
138
138
|
'PORT': config.port || 3000,
|
|
139
139
|
'APP_NAME': config.appName || 'myapp',
|
|
140
140
|
'LOG_LEVEL': 'info'
|
|
141
141
|
};
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
/**
|
|
145
|
+
* Builds Python-specific environment variables
|
|
146
|
+
* @param {Object} config - Configuration options
|
|
147
|
+
* @returns {Object} Python environment variables
|
|
148
|
+
*/
|
|
149
|
+
function buildPythonEnv(config) {
|
|
150
|
+
const language = config.language || 'typescript';
|
|
151
|
+
if (language !== 'python') {
|
|
152
|
+
return {};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
'PYTHONUNBUFFERED': '${PYTHONUNBUFFERED}',
|
|
157
|
+
'PYTHONDONTWRITEBYTECODE': '${PYTHONDONTWRITEBYTECODE}',
|
|
158
|
+
'PYTHONIOENCODING': '${PYTHONIOENCODING}'
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
144
162
|
function buildDatabaseEnv(config) {
|
|
145
163
|
if (!config.database) {
|
|
146
164
|
return {};
|
|
@@ -207,8 +225,9 @@ function buildMonitoringEnv(config) {
|
|
|
207
225
|
return {
|
|
208
226
|
'MISO_CONTROLLER_URL': config.controllerUrl || 'https://controller.aifabrix.ai',
|
|
209
227
|
'MISO_ENVIRONMENT': 'dev',
|
|
210
|
-
'MISO_CLIENTID': 'kv://miso-
|
|
211
|
-
'MISO_CLIENTSECRET': 'kv://miso-
|
|
228
|
+
'MISO_CLIENTID': 'kv://miso-controller-client-idKeyVault',
|
|
229
|
+
'MISO_CLIENTSECRET': 'kv://miso-controller-client-secretKeyVault',
|
|
230
|
+
'MISO_WEB_SERVER_URL': 'kv://miso-controller-web-server-url'
|
|
212
231
|
};
|
|
213
232
|
}
|
|
214
233
|
|
|
@@ -220,10 +239,17 @@ function buildMonitoringEnv(config) {
|
|
|
220
239
|
function addCoreVariables(lines, envVars) {
|
|
221
240
|
Object.entries(envVars).forEach(([key, value]) => {
|
|
222
241
|
if (key.startsWith('NODE_ENV') || key.startsWith('PORT') ||
|
|
223
|
-
key.startsWith('APP_NAME') || key.startsWith('LOG_LEVEL')
|
|
242
|
+
key.startsWith('APP_NAME') || key.startsWith('LOG_LEVEL') ||
|
|
243
|
+
key.startsWith('PYTHON')) {
|
|
224
244
|
lines.push(`${key}=${value}`);
|
|
225
245
|
}
|
|
226
246
|
});
|
|
247
|
+
|
|
248
|
+
// Add ALLOWED_ORIGINS and WEB_SERVER_URL after PORT variable
|
|
249
|
+
// ALLOWED_ORIGINS: My application public address
|
|
250
|
+
lines.push('ALLOWED_ORIGINS=http://localhost:*,');
|
|
251
|
+
// WEB_SERVER_URL: Miso public address (uses ${PORT} template variable)
|
|
252
|
+
lines.push('WEB_SERVER_URL=http://localhost:${PORT},');
|
|
227
253
|
}
|
|
228
254
|
|
|
229
255
|
function addMonitoringSection(lines, envVars) {
|
|
@@ -232,6 +258,10 @@ function addMonitoringSection(lines, envVars) {
|
|
|
232
258
|
lines.push(`MISO_ENVIRONMENT=${envVars['MISO_ENVIRONMENT']}`);
|
|
233
259
|
lines.push(`MISO_CLIENTID=${envVars['MISO_CLIENTID']}`);
|
|
234
260
|
lines.push(`MISO_CLIENTSECRET=${envVars['MISO_CLIENTSECRET']}`);
|
|
261
|
+
// MISO_WEB_SERVER_URL: Miso public address
|
|
262
|
+
if (envVars['MISO_WEB_SERVER_URL']) {
|
|
263
|
+
lines.push(`MISO_WEB_SERVER_URL=${envVars['MISO_WEB_SERVER_URL']}`);
|
|
264
|
+
}
|
|
235
265
|
}
|
|
236
266
|
|
|
237
267
|
function addDatabaseSection(lines, envVars) {
|
|
@@ -316,6 +346,7 @@ function addAuthenticationSection(lines, envVars) {
|
|
|
316
346
|
function generateEnvTemplate(config, existingEnv = {}) {
|
|
317
347
|
const envVars = {
|
|
318
348
|
...buildCoreEnv(config),
|
|
349
|
+
...buildPythonEnv(config),
|
|
319
350
|
...buildDatabaseEnv(config),
|
|
320
351
|
...buildRedisEnv(config),
|
|
321
352
|
...buildStorageEnv(config),
|
|
@@ -73,13 +73,11 @@ function formatPermissionError(errorData) {
|
|
|
73
73
|
|
|
74
74
|
const requiredPerms = extractRequiredPermissions(errorData);
|
|
75
75
|
addPermissionList(lines, requiredPerms, 'Required permissions');
|
|
76
|
-
|
|
77
76
|
const requestUrl = errorData.instance || errorData.url;
|
|
78
77
|
const method = errorData.method || 'POST';
|
|
79
78
|
if (requestUrl) {
|
|
80
79
|
lines.push(chalk.gray(`Request: ${method} ${requestUrl}`));
|
|
81
80
|
}
|
|
82
|
-
|
|
83
81
|
if (errorData.correlationId) {
|
|
84
82
|
lines.push(chalk.gray(`Correlation ID: ${errorData.correlationId}`));
|
|
85
83
|
}
|
|
@@ -97,18 +95,27 @@ function formatValidationError(errorData) {
|
|
|
97
95
|
lines.push(chalk.red('❌ Validation Error\n'));
|
|
98
96
|
|
|
99
97
|
// Handle RFC 7807 Problem Details format
|
|
100
|
-
// Priority: detail > title > message
|
|
98
|
+
// Priority: detail > title > errorDescription > message > error
|
|
101
99
|
if (errorData.detail) {
|
|
102
100
|
lines.push(chalk.yellow(errorData.detail));
|
|
103
101
|
lines.push('');
|
|
104
102
|
} else if (errorData.title) {
|
|
105
103
|
lines.push(chalk.yellow(errorData.title));
|
|
106
104
|
lines.push('');
|
|
105
|
+
} else if (errorData.errorDescription) {
|
|
106
|
+
// Handle Keycloak-style error format
|
|
107
|
+
lines.push(chalk.yellow(errorData.errorDescription));
|
|
108
|
+
if (errorData.error) {
|
|
109
|
+
lines.push(chalk.gray(`Error code: ${errorData.error}`));
|
|
110
|
+
}
|
|
111
|
+
lines.push('');
|
|
107
112
|
} else if (errorData.message) {
|
|
108
113
|
lines.push(chalk.yellow(errorData.message));
|
|
109
114
|
lines.push('');
|
|
115
|
+
} else if (errorData.error) {
|
|
116
|
+
lines.push(chalk.yellow(errorData.error));
|
|
117
|
+
lines.push('');
|
|
110
118
|
}
|
|
111
|
-
|
|
112
119
|
// Handle errors array - this is the most important part
|
|
113
120
|
if (errorData.errors && Array.isArray(errorData.errors) && errorData.errors.length > 0) {
|
|
114
121
|
lines.push(chalk.yellow('Validation errors:'));
|
|
@@ -124,17 +131,14 @@ function formatValidationError(errorData) {
|
|
|
124
131
|
});
|
|
125
132
|
lines.push('');
|
|
126
133
|
}
|
|
127
|
-
|
|
128
134
|
// Show instance (endpoint) if available (RFC 7807)
|
|
129
135
|
if (errorData.instance) {
|
|
130
136
|
lines.push(chalk.gray(`Endpoint: ${errorData.instance}`));
|
|
131
137
|
}
|
|
132
|
-
|
|
133
138
|
// Show correlation ID if available
|
|
134
139
|
if (errorData.correlationId) {
|
|
135
140
|
lines.push(chalk.gray(`Correlation ID: ${errorData.correlationId}`));
|
|
136
141
|
}
|
|
137
|
-
|
|
138
142
|
return lines.join('\n');
|
|
139
143
|
}
|
|
140
144
|
/**
|
|
@@ -145,7 +149,6 @@ function formatValidationError(errorData) {
|
|
|
145
149
|
function formatAuthenticationError(errorData) {
|
|
146
150
|
const lines = [];
|
|
147
151
|
lines.push(chalk.red('❌ Authentication Failed\n'));
|
|
148
|
-
|
|
149
152
|
if (errorData.message) {
|
|
150
153
|
lines.push(chalk.yellow(errorData.message));
|
|
151
154
|
} else {
|
|
@@ -153,11 +156,9 @@ function formatAuthenticationError(errorData) {
|
|
|
153
156
|
}
|
|
154
157
|
lines.push('');
|
|
155
158
|
lines.push(chalk.gray('Run: aifabrix login'));
|
|
156
|
-
|
|
157
159
|
if (errorData.correlationId) {
|
|
158
160
|
lines.push(chalk.gray(`Correlation ID: ${errorData.correlationId}`));
|
|
159
161
|
}
|
|
160
|
-
|
|
161
162
|
return lines.join('\n');
|
|
162
163
|
}
|
|
163
164
|
/**
|
|
@@ -379,7 +380,7 @@ function createErrorResult(type, message, formatted, data) {
|
|
|
379
380
|
* @returns {string} Error message
|
|
380
381
|
*/
|
|
381
382
|
function getErrorMessage(errorData, defaultMessage) {
|
|
382
|
-
return errorData.detail || errorData.title || errorData.message || defaultMessage;
|
|
383
|
+
return errorData.detail || errorData.title || errorData.errorDescription || errorData.message || errorData.error || defaultMessage;
|
|
383
384
|
}
|
|
384
385
|
|
|
385
386
|
/**
|
|
@@ -486,7 +487,6 @@ function formatApiError(apiResponse) {
|
|
|
486
487
|
const parsed = parseErrorResponse(errorResponse, statusCode, isNetworkError);
|
|
487
488
|
return parsed.formatted;
|
|
488
489
|
}
|
|
489
|
-
|
|
490
490
|
module.exports = {
|
|
491
491
|
parseErrorResponse,
|
|
492
492
|
formatApiError,
|
package/lib/utils/cli-utils.js
CHANGED
|
@@ -57,7 +57,7 @@ function formatError(error) {
|
|
|
57
57
|
} else if (errorMsg.includes('Docker') && (errorMsg.includes('not running') || errorMsg.includes('not installed') || errorMsg.includes('Cannot connect'))) {
|
|
58
58
|
messages.push(' Docker is not running or not installed.');
|
|
59
59
|
messages.push(' Please start Docker Desktop and try again.');
|
|
60
|
-
} else if (errorMsg.includes('port')) {
|
|
60
|
+
} else if (errorMsg.toLowerCase().includes('port') && (errorMsg.includes('already in use') || errorMsg.includes('in use') || errorMsg.includes('conflict'))) {
|
|
61
61
|
messages.push(' Port conflict detected.');
|
|
62
62
|
messages.push(' Run "aifabrix doctor" to check which ports are in use.');
|
|
63
63
|
} else if ((errorMsg.includes('permission denied') || errorMsg.includes('EACCES') || errorMsg.includes('Permission denied')) && !errorMsg.includes('permissions/') && !errorMsg.includes('Field "permissions')) {
|
|
@@ -69,13 +69,13 @@ function formatError(error) {
|
|
|
69
69
|
messages.push(' Azure CLI is not installed or not working properly.');
|
|
70
70
|
messages.push(' Install from: https://docs.microsoft.com/cli/azure/install-azure-cli');
|
|
71
71
|
messages.push(' Run: az login');
|
|
72
|
+
} else if (errorMsg.includes('Invalid ACR URL') || errorMsg.includes('Invalid registry URL') || errorMsg.includes('Expected format')) {
|
|
73
|
+
messages.push(' Invalid registry URL format.');
|
|
74
|
+
messages.push(' Use format: *.azurecr.io (e.g., myacr.azurecr.io)');
|
|
72
75
|
} else if (errorMsg.includes('authenticate') || errorMsg.includes('ACR') || errorMsg.includes('Authentication required')) {
|
|
73
76
|
messages.push(' Azure Container Registry authentication failed.');
|
|
74
77
|
messages.push(' Run: az acr login --name <registry-name>');
|
|
75
78
|
messages.push(' Or login to Azure: az login');
|
|
76
|
-
} else if (errorMsg.includes('Invalid ACR URL') || errorMsg.includes('Expected format')) {
|
|
77
|
-
messages.push(' Invalid registry URL format.');
|
|
78
|
-
messages.push(' Use format: *.azurecr.io (e.g., myacr.azurecr.io)');
|
|
79
79
|
} else if (errorMsg.includes('Registry URL is required')) {
|
|
80
80
|
messages.push(' Registry URL is required.');
|
|
81
81
|
messages.push(' Provide via --registry flag or configure in variables.yaml under image.registry');
|
package/lib/utils/device-code.js
CHANGED
|
@@ -140,15 +140,61 @@ function parseTokenResponse(response) {
|
|
|
140
140
|
};
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Creates a validation error with detailed information
|
|
145
|
+
* @function createValidationError
|
|
146
|
+
* @param {Object} response - Full API response object
|
|
147
|
+
* @returns {Error} Validation error with formattedError and errorData attached
|
|
148
|
+
*/
|
|
149
|
+
function createValidationError(response) {
|
|
150
|
+
const validationError = new Error('Token polling failed: Validation error');
|
|
151
|
+
|
|
152
|
+
// Attach formatted error if available (includes detailed validation info with ANSI colors)
|
|
153
|
+
if (response && response.formattedError) {
|
|
154
|
+
validationError.formattedError = response.formattedError;
|
|
155
|
+
validationError.message = `Token polling failed:\n${response.formattedError}`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Attach error data for programmatic access
|
|
159
|
+
if (response && response.errorData) {
|
|
160
|
+
validationError.errorData = response.errorData;
|
|
161
|
+
validationError.errorType = response.errorType || 'validation';
|
|
162
|
+
|
|
163
|
+
// Build detailed message if formattedError not available
|
|
164
|
+
if (!validationError.formattedError) {
|
|
165
|
+
const errorData = response.errorData;
|
|
166
|
+
const detail = errorData.detail || errorData.title || errorData.message || 'Validation error';
|
|
167
|
+
let errorMsg = `Token polling failed: ${detail}`;
|
|
168
|
+
// Add validation errors if available
|
|
169
|
+
if (errorData.errors && Array.isArray(errorData.errors) && errorData.errors.length > 0) {
|
|
170
|
+
errorMsg += '\n\nValidation errors:';
|
|
171
|
+
errorData.errors.forEach(err => {
|
|
172
|
+
const field = err.field || err.path || 'validation';
|
|
173
|
+
const message = err.message || 'Invalid value';
|
|
174
|
+
if (field === 'validation' || field === 'unknown') {
|
|
175
|
+
errorMsg += `\n • ${message}`;
|
|
176
|
+
} else {
|
|
177
|
+
errorMsg += `\n • ${field}: ${message}`;
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
validationError.message = errorMsg;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return validationError;
|
|
186
|
+
}
|
|
187
|
+
|
|
143
188
|
/**
|
|
144
189
|
* Handles polling errors
|
|
145
190
|
* @function handlePollingErrors
|
|
146
191
|
* @param {string} error - Error code
|
|
147
192
|
* @param {number} status - HTTP status code
|
|
193
|
+
* @param {Object} response - Full API response object (for accessing formattedError and errorData)
|
|
148
194
|
* @throws {Error} For fatal errors
|
|
149
195
|
* @returns {boolean} True if should continue polling
|
|
150
196
|
*/
|
|
151
|
-
function handlePollingErrors(error, status) {
|
|
197
|
+
function handlePollingErrors(error, status, response) {
|
|
152
198
|
if (error === 'authorization_pending' || status === 202) {
|
|
153
199
|
return true;
|
|
154
200
|
}
|
|
@@ -166,6 +212,11 @@ function handlePollingErrors(error, status) {
|
|
|
166
212
|
return true;
|
|
167
213
|
}
|
|
168
214
|
|
|
215
|
+
// Handle validation errors with detailed message
|
|
216
|
+
if (error === 'validation_error' || status === 400) {
|
|
217
|
+
throw createValidationError(response);
|
|
218
|
+
}
|
|
219
|
+
|
|
169
220
|
throw new Error(`Token polling failed: ${error}`);
|
|
170
221
|
}
|
|
171
222
|
|
|
@@ -187,6 +238,18 @@ async function waitForNextPoll(interval, slowDown) {
|
|
|
187
238
|
* @returns {string} Error code or 'Unknown error'
|
|
188
239
|
*/
|
|
189
240
|
function extractPollingError(response) {
|
|
241
|
+
// Check for structured error data first (from api-error-handler)
|
|
242
|
+
if (response.errorData) {
|
|
243
|
+
const errorData = response.errorData;
|
|
244
|
+
// For validation errors, return the error type so we can handle it specially
|
|
245
|
+
if (response.errorType === 'validation') {
|
|
246
|
+
return 'validation_error';
|
|
247
|
+
}
|
|
248
|
+
// Return the error message from structured error
|
|
249
|
+
return errorData.detail || errorData.title || errorData.message || errorData.error || response.error || 'Unknown error';
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Fallback to original extraction logic
|
|
190
253
|
const apiResponse = response.data || {};
|
|
191
254
|
const errorData = typeof apiResponse === 'object' ? apiResponse : {};
|
|
192
255
|
return errorData.error || response.error || 'Unknown error';
|
|
@@ -229,7 +292,7 @@ async function processPollingResponse(response, interval) {
|
|
|
229
292
|
}
|
|
230
293
|
|
|
231
294
|
const error = extractPollingError(response);
|
|
232
|
-
const shouldContinue = handlePollingErrors(error, response.status);
|
|
295
|
+
const shouldContinue = handlePollingErrors(error, response.status, response);
|
|
233
296
|
|
|
234
297
|
if (shouldContinue) {
|
|
235
298
|
const slowDown = error === 'slow_down';
|
|
@@ -20,10 +20,11 @@ const logger = require('../utils/logger');
|
|
|
20
20
|
* @param {string} appKey - Application key
|
|
21
21
|
* @param {string} clientIdKey - Secret key for client ID (e.g., 'myapp-client-idKeyVault')
|
|
22
22
|
* @param {string} clientSecretKey - Secret key for client secret (e.g., 'myapp-client-secretKeyVault')
|
|
23
|
-
* @param {string}
|
|
23
|
+
* @param {string} _controllerUrl - Controller URL (e.g., 'http://localhost:3010' or 'https://controller.aifabrix.ai')
|
|
24
|
+
* Note: This parameter is accepted for compatibility but the template format http://${MISO_HOST}:${MISO_PORT} is used instead
|
|
24
25
|
* @returns {Promise<void>} Resolves when template is updated
|
|
25
26
|
*/
|
|
26
|
-
async function updateEnvTemplate(appKey, clientIdKey, clientSecretKey,
|
|
27
|
+
async function updateEnvTemplate(appKey, clientIdKey, clientSecretKey, _controllerUrl) {
|
|
27
28
|
const envTemplatePath = path.join(process.cwd(), 'builder', appKey, 'env.template');
|
|
28
29
|
|
|
29
30
|
if (!fsSync.existsSync(envTemplatePath)) {
|
|
@@ -49,7 +50,7 @@ async function updateEnvTemplate(appKey, clientIdKey, clientSecretKey, controlle
|
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
if (hasControllerUrl) {
|
|
52
|
-
content = content.replace(/^MISO_CONTROLLER_URL\s*=.*$/m,
|
|
53
|
+
content = content.replace(/^MISO_CONTROLLER_URL\s*=.*$/m, 'MISO_CONTROLLER_URL=http://${MISO_HOST}:${MISO_PORT}');
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
// Add missing entries
|
|
@@ -62,7 +63,7 @@ async function updateEnvTemplate(appKey, clientIdKey, clientSecretKey, controlle
|
|
|
62
63
|
missingEntries.push(`MISO_CLIENTSECRET=kv://${clientSecretKey}`);
|
|
63
64
|
}
|
|
64
65
|
if (!hasControllerUrl) {
|
|
65
|
-
missingEntries.push(
|
|
66
|
+
missingEntries.push('MISO_CONTROLLER_URL=http://${MISO_HOST}:${MISO_PORT}');
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
const misoSection = `# MISO Application Client Credentials (per application)
|