@aifabrix/builder 2.39.3 → 2.40.2
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 +6 -6
- package/README.md +3 -3
- package/babel.config.js +6 -0
- package/integration/hubspot/README.md +53 -141
- package/integration/hubspot/application.yaml +37 -0
- package/integration/hubspot/env.template +2 -11
- package/integration/hubspot/hubspot-deploy.json +1 -0
- package/integration/hubspot/test.js +5 -5
- package/jest.config.manual.js +29 -0
- package/lib/api/credentials.api.js +5 -5
- package/lib/api/deployments.api.js +2 -2
- package/lib/api/pipeline.api.js +17 -17
- package/lib/api/wizard.api.js +2 -2
- package/lib/app/config.js +11 -6
- package/lib/app/deploy-config.js +13 -16
- package/lib/app/deploy.js +29 -22
- package/lib/app/display.js +1 -1
- package/lib/app/dockerfile.js +11 -12
- package/lib/app/helpers.js +51 -13
- package/lib/app/index.js +14 -2
- package/lib/app/prompts.js +37 -45
- package/lib/app/push.js +8 -11
- package/lib/app/readme.js +16 -12
- package/lib/app/register.js +1 -1
- package/lib/app/run-helpers.js +31 -22
- package/lib/app/run.js +44 -5
- package/lib/app/show-display.js +104 -44
- package/lib/app/show.js +123 -43
- package/lib/build/index.js +11 -18
- package/lib/cli/setup-app.js +36 -29
- package/lib/cli/setup-auth.js +19 -15
- package/lib/cli/setup-credential-deployment.js +3 -1
- package/lib/cli/setup-external-system.js +35 -16
- package/lib/cli/setup-infra.js +45 -23
- package/lib/cli/setup-utility.js +85 -31
- package/lib/commands/app-logs.js +28 -20
- package/lib/commands/app.js +30 -26
- package/lib/commands/auth-status.js +36 -3
- package/lib/commands/convert.js +202 -0
- package/lib/commands/credential-list.js +78 -17
- package/lib/commands/datasource.js +24 -24
- package/lib/commands/deployment-list.js +13 -6
- package/lib/commands/up-common.js +80 -42
- package/lib/commands/up-dataplane.js +15 -14
- package/lib/commands/up-miso.js +15 -14
- package/lib/commands/upload.js +163 -0
- package/lib/commands/wizard-core.js +5 -4
- package/lib/core/diff.js +84 -9
- package/lib/core/key-generator.js +9 -12
- package/lib/core/secrets-docker-env.js +2 -2
- package/lib/core/secrets.js +3 -2
- package/lib/core/templates.js +2 -2
- package/lib/datasource/deploy.js +2 -1
- package/lib/deployment/deployer.js +76 -48
- package/lib/external-system/delete.js +0 -1
- package/lib/external-system/deploy-helpers.js +5 -6
- package/lib/external-system/deploy.js +7 -2
- package/lib/external-system/download-helpers.js +4 -4
- package/lib/external-system/download.js +11 -10
- package/lib/external-system/generator.js +19 -17
- package/lib/external-system/test.js +10 -15
- package/lib/generator/builders.js +1 -1
- package/lib/generator/external-controller-manifest.js +26 -29
- package/lib/generator/external-schema-utils.js +6 -18
- package/lib/generator/external.js +32 -27
- package/lib/generator/github.js +1 -1
- package/lib/generator/helpers.js +12 -19
- package/lib/generator/index.js +15 -15
- package/lib/generator/parse-image.js +35 -0
- package/lib/generator/split-readme.js +105 -0
- package/lib/generator/split-variables.js +149 -0
- package/lib/generator/split.js +86 -246
- package/lib/generator/wizard.js +51 -70
- package/lib/schema/application-schema.json +4 -4
- package/lib/schema/external-datasource.schema.json +5 -0
- package/lib/schema/external-system.schema.json +10 -0
- package/lib/utils/app-config-resolver.js +52 -0
- package/lib/utils/app-register-api.js +1 -1
- package/lib/utils/app-register-auth.js +1 -1
- package/lib/utils/app-register-config.js +16 -23
- package/lib/utils/app-register-validator.js +2 -2
- package/lib/utils/cli-utils.js +47 -3
- package/lib/utils/config-format.js +154 -0
- package/lib/utils/config-paths.js +19 -52
- package/lib/utils/config-tokens.js +1 -0
- package/lib/utils/docker-build.js +71 -94
- package/lib/utils/dockerfile-utils.js +1 -1
- package/lib/utils/env-copy.js +4 -4
- package/lib/utils/env-ports.js +2 -2
- package/lib/utils/error-formatter.js +1 -1
- package/lib/utils/error-formatters/validation-errors.js +1 -1
- package/lib/utils/external-readme.js +12 -5
- package/lib/utils/external-system-test-helpers.js +2 -0
- package/lib/utils/health-check.js +55 -66
- package/lib/utils/image-version.js +12 -21
- package/lib/utils/paths.js +45 -66
- package/lib/utils/port-resolver.js +8 -8
- package/lib/utils/schema-loader.js +22 -0
- package/lib/utils/schema-resolver.js +23 -33
- package/lib/utils/secrets-helpers.js +7 -7
- package/lib/utils/secrets-utils.js +10 -12
- package/lib/utils/template-helpers.js +13 -13
- package/lib/utils/token-manager.js +20 -2
- package/lib/utils/variable-transformer.js +2 -2
- package/lib/validation/validate-display.js +3 -4
- package/lib/validation/validate.js +34 -28
- package/lib/validation/validator.js +50 -30
- package/package.json +4 -1
- package/templates/README.md +1 -1
- package/templates/applications/README.md.hbs +3 -3
- package/templates/applications/miso-controller/env.template +3 -1
- package/templates/external-system/README.md.hbs +4 -4
- package/templates/external-system/external-system.json.hbs +1 -16
- package/integration/hubspot/variables.yaml +0 -17
- /package/templates/applications/dataplane/{variables.yaml → application.yaml} +0 -0
- /package/templates/applications/keycloak/{variables.yaml → application.yaml} +0 -0
- /package/templates/applications/miso-controller/{variables.yaml → application.yaml} +0 -0
package/lib/generator/wizard.js
CHANGED
|
@@ -2,14 +2,19 @@
|
|
|
2
2
|
* @fileoverview Wizard file generator - saves dataplane-generated configurations to files
|
|
3
3
|
* @author AI Fabrix Team
|
|
4
4
|
* @version 2.0.0
|
|
5
|
+
*
|
|
6
|
+
* Standard credential-backed variable names (supplied at runtime from the selected credential;
|
|
7
|
+
* do not list in configuration array): CLIENTID, CLIENTSECRET, TOKENURL, APIKEY, USERNAME,
|
|
8
|
+
* PASSWORD, BASEURL. See docs/external-systems.md and docs/wizard.md.
|
|
5
9
|
*/
|
|
6
10
|
|
|
7
11
|
const fs = require('fs').promises;
|
|
8
12
|
const path = require('path');
|
|
9
|
-
const yaml = require('js-yaml');
|
|
10
13
|
const Handlebars = require('handlebars');
|
|
11
14
|
const chalk = require('chalk');
|
|
12
15
|
const logger = require('../utils/logger');
|
|
16
|
+
const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
|
|
17
|
+
const { loadConfigFile, writeConfigFile } = require('../utils/config-format');
|
|
13
18
|
const { generateExternalReadmeContent } = require('../utils/external-readme');
|
|
14
19
|
|
|
15
20
|
/**
|
|
@@ -39,30 +44,30 @@ function toKeySegment(str) {
|
|
|
39
44
|
/**
|
|
40
45
|
* Writes system JSON file
|
|
41
46
|
* @async
|
|
42
|
-
* @function
|
|
47
|
+
* @function writeSystemYamlFile
|
|
43
48
|
* @param {string} appPath - Application path
|
|
44
49
|
* @param {string} finalSystemKey - Final system key
|
|
45
50
|
* @param {Object} systemConfig - System configuration
|
|
46
51
|
* @returns {Promise<string>} System file path
|
|
47
52
|
*/
|
|
48
|
-
async function
|
|
49
|
-
const systemFileName = `${finalSystemKey}-system.
|
|
53
|
+
async function writeSystemYamlFile(appPath, finalSystemKey, systemConfig) {
|
|
54
|
+
const systemFileName = `${finalSystemKey}-system.yaml`;
|
|
50
55
|
const systemFilePath = path.join(appPath, systemFileName);
|
|
51
|
-
|
|
56
|
+
writeConfigFile(systemFilePath, systemConfig);
|
|
52
57
|
logger.log(chalk.green(`✓ Generated system file: ${systemFileName}`));
|
|
53
58
|
return systemFilePath;
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
/**
|
|
57
|
-
* Writes datasource
|
|
62
|
+
* Writes datasource YAML files
|
|
58
63
|
* @async
|
|
59
|
-
* @function
|
|
64
|
+
* @function writeDatasourceYamlFiles
|
|
60
65
|
* @param {string} appPath - Application path
|
|
61
66
|
* @param {string} finalSystemKey - Final system key
|
|
62
67
|
* @param {Object[]} datasourceConfigs - Array of datasource configurations
|
|
63
68
|
* @returns {Promise<string[]>} Array of datasource file names
|
|
64
69
|
*/
|
|
65
|
-
async function
|
|
70
|
+
async function writeDatasourceYamlFiles(appPath, finalSystemKey, datasourceConfigs) {
|
|
66
71
|
const datasourceFileNames = [];
|
|
67
72
|
for (const datasourceConfig of datasourceConfigs) {
|
|
68
73
|
const entityType = datasourceConfig.entityType || datasourceConfig.entityKey || datasourceConfig.key?.split('-').pop() || 'default';
|
|
@@ -72,9 +77,9 @@ async function writeDatasourceJsonFiles(appPath, finalSystemKey, datasourceConfi
|
|
|
72
77
|
const datasourceKeyOnly = datasourceKey.includes('-') && datasourceKey.startsWith(`${finalSystemKey}-`)
|
|
73
78
|
? datasourceKey.substring(finalSystemKey.length + 1)
|
|
74
79
|
: keySegment;
|
|
75
|
-
const datasourceFileName = `${finalSystemKey}-datasource-${datasourceKeyOnly}.
|
|
80
|
+
const datasourceFileName = `${finalSystemKey}-datasource-${datasourceKeyOnly}.yaml`;
|
|
76
81
|
const datasourceFilePath = path.join(appPath, datasourceFileName);
|
|
77
|
-
|
|
82
|
+
writeConfigFile(datasourceFilePath, datasourceConfig);
|
|
78
83
|
datasourceFileNames.push(datasourceFileName);
|
|
79
84
|
logger.log(chalk.green(`✓ Generated datasource file: ${datasourceFileName}`));
|
|
80
85
|
}
|
|
@@ -99,8 +104,8 @@ async function writeDatasourceJsonFiles(appPath, finalSystemKey, datasourceConfi
|
|
|
99
104
|
async function generateConfigFilesForWizard(params) {
|
|
100
105
|
const { appPath, appName, finalSystemKey, systemFileName, datasourceFileNames, systemConfig, datasourceConfigs, aiGeneratedReadme } = params;
|
|
101
106
|
|
|
102
|
-
// Generate or update
|
|
103
|
-
await generateOrUpdateVariablesYaml({
|
|
107
|
+
// Generate or update application.yaml with externalIntegration block
|
|
108
|
+
const configPath = await generateOrUpdateVariablesYaml({
|
|
104
109
|
appPath,
|
|
105
110
|
appName,
|
|
106
111
|
systemKey: finalSystemKey,
|
|
@@ -126,7 +131,7 @@ async function generateConfigFilesForWizard(params) {
|
|
|
126
131
|
logger.log(chalk.green(`✓ Generated deployment manifest: ${finalSystemKey}-deploy.json`));
|
|
127
132
|
|
|
128
133
|
return {
|
|
129
|
-
variablesPath:
|
|
134
|
+
variablesPath: configPath,
|
|
130
135
|
envTemplatePath: path.join(appPath, 'env.template'),
|
|
131
136
|
readmePath: path.join(appPath, 'README.md'),
|
|
132
137
|
applicationSchemaPath: deployManifestPath,
|
|
@@ -134,48 +139,28 @@ async function generateConfigFilesForWizard(params) {
|
|
|
134
139
|
};
|
|
135
140
|
}
|
|
136
141
|
|
|
142
|
+
async function prepareWizardContext(appName, systemConfig, datasourceConfigs) {
|
|
143
|
+
const appPath = path.join(process.cwd(), 'integration', appName);
|
|
144
|
+
await fs.mkdir(appPath, { recursive: true });
|
|
145
|
+
const finalSystemKey = appName;
|
|
146
|
+
const appDisplayName = appName.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
|
|
147
|
+
const updatedSystemConfig = { ...systemConfig, key: finalSystemKey, displayName: appDisplayName };
|
|
148
|
+
const updatedDatasourceConfigs = datasourceConfigs.map(ds => {
|
|
149
|
+
const entityType = ds.entityType || ds.entityKey || ds.key?.split('-').pop() || 'default';
|
|
150
|
+
const keySegment = toKeySegment(entityType);
|
|
151
|
+
const entityDisplayName = entityType.charAt(0).toUpperCase() + entityType.slice(1).replace(/-/g, ' ');
|
|
152
|
+
return { ...ds, key: `${finalSystemKey}-${keySegment}`, systemKey: finalSystemKey, displayName: `${appDisplayName} ${entityDisplayName}` };
|
|
153
|
+
});
|
|
154
|
+
return { appPath, finalSystemKey, updatedSystemConfig, updatedDatasourceConfigs, appDisplayName };
|
|
155
|
+
}
|
|
156
|
+
|
|
137
157
|
async function generateWizardFiles(appName, systemConfig, datasourceConfigs, systemKey, options = {}) {
|
|
138
158
|
try {
|
|
139
159
|
const { aiGeneratedReadme } = options || {};
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
await fs.mkdir(appPath, { recursive: true });
|
|
145
|
-
|
|
146
|
-
// Use appName as the system key to ensure consistent naming
|
|
147
|
-
// Priority: appName > systemKey parameter > systemConfig.key
|
|
148
|
-
const finalSystemKey = appName;
|
|
149
|
-
|
|
150
|
-
// Generate displayName from appName (e.g., "my-hubspot" -> "My Hubspot")
|
|
151
|
-
const appDisplayName = appName.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
|
|
152
|
-
|
|
153
|
-
// Update system config to use the appName as key and displayName
|
|
154
|
-
const updatedSystemConfig = {
|
|
155
|
-
...systemConfig,
|
|
156
|
-
key: finalSystemKey,
|
|
157
|
-
displayName: appDisplayName
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
// Update datasource configs to use appName-based keys and systemKey (key must match ^[a-z0-9-]+$)
|
|
161
|
-
const updatedDatasourceConfigs = datasourceConfigs.map(ds => {
|
|
162
|
-
const entityType = ds.entityType || ds.entityKey || ds.key?.split('-').pop() || 'default';
|
|
163
|
-
const keySegment = toKeySegment(entityType);
|
|
164
|
-
const entityDisplayName = entityType.charAt(0).toUpperCase() + entityType.slice(1).replace(/-/g, ' ');
|
|
165
|
-
return {
|
|
166
|
-
...ds,
|
|
167
|
-
key: `${finalSystemKey}-${keySegment}`,
|
|
168
|
-
systemKey: finalSystemKey,
|
|
169
|
-
displayName: `${appDisplayName} ${entityDisplayName}`
|
|
170
|
-
};
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
// Write system and datasource JSON files
|
|
174
|
-
const systemFilePath = await writeSystemJsonFile(appPath, finalSystemKey, updatedSystemConfig);
|
|
175
|
-
const datasourceFileNames = await writeDatasourceJsonFiles(appPath, finalSystemKey, updatedDatasourceConfigs);
|
|
176
|
-
|
|
177
|
-
// Generate configuration files
|
|
178
|
-
const systemFileName = `${finalSystemKey}-system.json`;
|
|
160
|
+
const { appPath, finalSystemKey, updatedSystemConfig, updatedDatasourceConfigs } = await prepareWizardContext(appName, systemConfig, datasourceConfigs);
|
|
161
|
+
const systemFilePath = await writeSystemYamlFile(appPath, finalSystemKey, updatedSystemConfig);
|
|
162
|
+
const datasourceFileNames = await writeDatasourceYamlFiles(appPath, finalSystemKey, updatedDatasourceConfigs);
|
|
163
|
+
const systemFileName = `${finalSystemKey}-system.yaml`;
|
|
179
164
|
const configFiles = await generateConfigFilesForWizard({
|
|
180
165
|
appPath,
|
|
181
166
|
appName,
|
|
@@ -186,7 +171,6 @@ async function generateWizardFiles(appName, systemConfig, datasourceConfigs, sys
|
|
|
186
171
|
datasourceConfigs: updatedDatasourceConfigs,
|
|
187
172
|
aiGeneratedReadme
|
|
188
173
|
});
|
|
189
|
-
|
|
190
174
|
return {
|
|
191
175
|
appPath,
|
|
192
176
|
systemFilePath,
|
|
@@ -199,7 +183,7 @@ async function generateWizardFiles(appName, systemConfig, datasourceConfigs, sys
|
|
|
199
183
|
}
|
|
200
184
|
|
|
201
185
|
/**
|
|
202
|
-
* Generate or update
|
|
186
|
+
* Generate or update application.yaml with externalIntegration block
|
|
203
187
|
* @async
|
|
204
188
|
* @function generateOrUpdateVariablesYaml
|
|
205
189
|
* @param {Object} params - Parameters object
|
|
@@ -209,23 +193,19 @@ async function generateWizardFiles(appName, systemConfig, datasourceConfigs, sys
|
|
|
209
193
|
* @param {string} params.systemFileName - System file name
|
|
210
194
|
* @param {string[]} params.datasourceFileNames - Array of datasource file names
|
|
211
195
|
* @param {Object} params.systemConfig - System configuration
|
|
196
|
+
* @returns {Promise<string>} Path to application config file
|
|
212
197
|
* @throws {Error} If generation fails
|
|
213
198
|
*/
|
|
214
199
|
async function generateOrUpdateVariablesYaml(params) {
|
|
215
200
|
const { appPath, appName, systemFileName, datasourceFileNames, systemConfig } = params;
|
|
201
|
+
let configPath;
|
|
202
|
+
let variables = {};
|
|
216
203
|
try {
|
|
217
|
-
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
218
|
-
let variables = {};
|
|
219
|
-
|
|
220
|
-
// Try to read existing variables.yaml
|
|
221
204
|
try {
|
|
222
|
-
|
|
223
|
-
variables =
|
|
224
|
-
} catch
|
|
225
|
-
|
|
226
|
-
if (error.code !== 'ENOENT') {
|
|
227
|
-
throw error;
|
|
228
|
-
}
|
|
205
|
+
configPath = resolveApplicationConfigPath(appPath);
|
|
206
|
+
variables = loadConfigFile(configPath) || {};
|
|
207
|
+
} catch {
|
|
208
|
+
configPath = path.join(appPath, 'application.yaml');
|
|
229
209
|
}
|
|
230
210
|
|
|
231
211
|
// Set basic app info if not present
|
|
@@ -258,10 +238,11 @@ async function generateOrUpdateVariablesYaml(params) {
|
|
|
258
238
|
version: systemConfig.version || '1.0.0'
|
|
259
239
|
};
|
|
260
240
|
|
|
261
|
-
|
|
262
|
-
logger.log(chalk.green('✓ Generated/updated
|
|
241
|
+
writeConfigFile(configPath, variables);
|
|
242
|
+
logger.log(chalk.green('✓ Generated/updated application.yaml'));
|
|
243
|
+
return configPath;
|
|
263
244
|
} catch (error) {
|
|
264
|
-
throw new Error(`Failed to generate
|
|
245
|
+
throw new Error(`Failed to generate application config: ${error.message}`);
|
|
265
246
|
}
|
|
266
247
|
}
|
|
267
248
|
|
|
@@ -345,7 +326,7 @@ function addAuthenticationLines(lines, auth, authType) {
|
|
|
345
326
|
function addBaseUrlLines(lines, systemConfig) {
|
|
346
327
|
if (systemConfig.baseUrl || systemConfig.baseURL) {
|
|
347
328
|
lines.push('# API Base URL');
|
|
348
|
-
lines.push(`
|
|
329
|
+
lines.push(`BASEURL=${systemConfig.baseUrl || systemConfig.baseURL}`);
|
|
349
330
|
lines.push('');
|
|
350
331
|
}
|
|
351
332
|
}
|
|
@@ -445,7 +426,7 @@ async function generateReadme(appPath, appName, systemKey, systemConfig, datasou
|
|
|
445
426
|
return {
|
|
446
427
|
entityType,
|
|
447
428
|
displayName: ds.displayName || ds.name || ds.key || `Datasource ${index + 1}`,
|
|
448
|
-
fileName: `${systemKey}-datasource-${datasourceKeyOnly}.
|
|
429
|
+
fileName: `${systemKey}-datasource-${datasourceKeyOnly}.yaml`
|
|
449
430
|
};
|
|
450
431
|
});
|
|
451
432
|
|
|
@@ -799,19 +799,19 @@
|
|
|
799
799
|
},
|
|
800
800
|
"systems": {
|
|
801
801
|
"type": "array",
|
|
802
|
-
"description": "List of external-system
|
|
802
|
+
"description": "List of external-system files to deploy via pipeline (.json, .yaml, or .yml).",
|
|
803
803
|
"items": {
|
|
804
804
|
"type": "string",
|
|
805
|
-
"pattern": "^[^ ].+\\.json$"
|
|
805
|
+
"pattern": "^[^ ].+\\.(json|yaml|yml)$"
|
|
806
806
|
},
|
|
807
807
|
"uniqueItems": true
|
|
808
808
|
},
|
|
809
809
|
"dataSources": {
|
|
810
810
|
"type": "array",
|
|
811
|
-
"description": "List of external-datasource
|
|
811
|
+
"description": "List of external-datasource files belonging to this app (.json, .yaml, or .yml).",
|
|
812
812
|
"items": {
|
|
813
813
|
"type": "string",
|
|
814
|
-
"pattern": "^[^ ].+\\.json$"
|
|
814
|
+
"pattern": "^[^ ].+\\.(json|yaml|yml)$"
|
|
815
815
|
},
|
|
816
816
|
"uniqueItems": true
|
|
817
817
|
},
|
|
@@ -1587,6 +1587,11 @@
|
|
|
1587
1587
|
}
|
|
1588
1588
|
},
|
|
1589
1589
|
"additionalProperties":false
|
|
1590
|
+
},
|
|
1591
|
+
"triggerPathsHash":{
|
|
1592
|
+
"type":"string",
|
|
1593
|
+
"description":"SHA256 hash of triggerPaths payload (64-char hex). Used to detect structural changes. Optional; Dataplane computes when absent.",
|
|
1594
|
+
"pattern":"^[a-f0-9]{64}$"
|
|
1590
1595
|
}
|
|
1591
1596
|
},
|
|
1592
1597
|
"additionalProperties":false
|
|
@@ -102,6 +102,11 @@
|
|
|
102
102
|
"type": "boolean",
|
|
103
103
|
"default": true
|
|
104
104
|
},
|
|
105
|
+
"internal": {
|
|
106
|
+
"type": "boolean",
|
|
107
|
+
"description": "When true, this integration is deployed on dataplane startup (internal integration). When false or absent, deployed via pipeline only.",
|
|
108
|
+
"default": false
|
|
109
|
+
},
|
|
105
110
|
"authentication": {
|
|
106
111
|
"type": "object",
|
|
107
112
|
"description": "Authentication configuration for external system",
|
|
@@ -414,6 +419,11 @@
|
|
|
414
419
|
"type": "boolean",
|
|
415
420
|
"description": "Reserved: whether to generate or expose OpenAPI contract on publish. Not yet implemented.",
|
|
416
421
|
"default": true
|
|
422
|
+
},
|
|
423
|
+
"triggerPathsHash": {
|
|
424
|
+
"type": "string",
|
|
425
|
+
"description": "SHA256 hash of triggerPaths payload (64-char hex). Used to detect structural changes. Optional; Dataplane computes when absent.",
|
|
426
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
417
427
|
}
|
|
418
428
|
},
|
|
419
429
|
"additionalProperties": false
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application config path resolution
|
|
3
|
+
*
|
|
4
|
+
* Single entry point for resolving path to application config file
|
|
5
|
+
* (application.yaml, application.json, or legacy variables.yaml).
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview Resolve application config file path with legacy migration
|
|
8
|
+
* @author AI Fabrix Team
|
|
9
|
+
* @version 2.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Resolves path to application config file (application.yaml, application.json, or legacy variables.yaml).
|
|
19
|
+
* If only variables.yaml exists, renames it to application.yaml and returns the new path.
|
|
20
|
+
*
|
|
21
|
+
* @param {string} appPath - Absolute path to application directory
|
|
22
|
+
* @returns {string} Absolute path to application config file
|
|
23
|
+
* @throws {Error} If no config file found
|
|
24
|
+
*/
|
|
25
|
+
function resolveApplicationConfigPath(appPath) {
|
|
26
|
+
if (!appPath || typeof appPath !== 'string') {
|
|
27
|
+
throw new Error('App path is required and must be a string');
|
|
28
|
+
}
|
|
29
|
+
const applicationYaml = path.join(appPath, 'application.yaml');
|
|
30
|
+
const applicationYml = path.join(appPath, 'application.yml');
|
|
31
|
+
const applicationJson = path.join(appPath, 'application.json');
|
|
32
|
+
const variablesYaml = path.join(appPath, 'variables.yaml');
|
|
33
|
+
|
|
34
|
+
if (fs.existsSync(applicationYaml)) {
|
|
35
|
+
return applicationYaml;
|
|
36
|
+
}
|
|
37
|
+
if (fs.existsSync(applicationYml)) {
|
|
38
|
+
return applicationYml;
|
|
39
|
+
}
|
|
40
|
+
if (fs.existsSync(applicationJson)) {
|
|
41
|
+
return applicationJson;
|
|
42
|
+
}
|
|
43
|
+
if (fs.existsSync(variablesYaml)) {
|
|
44
|
+
fs.renameSync(variablesYaml, applicationYaml);
|
|
45
|
+
return applicationYaml;
|
|
46
|
+
}
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Application config not found in ${appPath}. Expected application.yaml, application.yml, application.json, or variables.yaml.`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = { resolveApplicationConfigPath };
|
|
@@ -39,7 +39,7 @@ function handleRegistrationError(response, apiUrl, registrationData) {
|
|
|
39
39
|
logger.error(chalk.gray('\nRequest payload:'));
|
|
40
40
|
logger.error(chalk.gray(JSON.stringify(registrationData, null, 2)));
|
|
41
41
|
logger.error('');
|
|
42
|
-
logger.error(chalk.gray('Check your
|
|
42
|
+
logger.error(chalk.gray('Check your application.yaml file and ensure all required fields are correctly set.'));
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
process.exit(1);
|
|
@@ -82,7 +82,7 @@ async function findDeviceTokenFromConfig(deviceConfig, attemptedUrls) {
|
|
|
82
82
|
/**
|
|
83
83
|
* Check if user is authenticated and get token
|
|
84
84
|
* @async
|
|
85
|
-
* @param {string} [controllerUrl] - Optional controller URL from
|
|
85
|
+
* @param {string} [controllerUrl] - Optional controller URL from application.yaml or --controller flag
|
|
86
86
|
* @param {string} [environment] - Optional environment key
|
|
87
87
|
* @returns {Promise<{apiUrl: string, token: string, controllerUrl: string}>} Configuration with API URL, token, and controller URL
|
|
88
88
|
*/
|
|
@@ -8,37 +8,35 @@
|
|
|
8
8
|
* @version 2.0.0
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
const fs = require('fs').promises;
|
|
12
11
|
const path = require('path');
|
|
13
12
|
const chalk = require('chalk');
|
|
14
|
-
const yaml = require('js-yaml');
|
|
15
13
|
const logger = require('./logger');
|
|
16
|
-
const { detectAppType } = require('./paths');
|
|
14
|
+
const { detectAppType, resolveApplicationConfigPath } = require('./paths');
|
|
15
|
+
const { loadConfigFile } = require('./config-format');
|
|
17
16
|
const { getContainerPort, getLocalPort } = require('./port-resolver');
|
|
18
17
|
|
|
19
18
|
// createApp is imported dynamically in createMinimalAppIfNeeded to handle test mocking
|
|
20
19
|
|
|
21
20
|
/**
|
|
22
|
-
* Load
|
|
21
|
+
* Load application config for an application (application.yaml, application.json, or legacy).
|
|
23
22
|
* @async
|
|
24
23
|
* @param {string} appKey - Application key
|
|
25
24
|
* @returns {Promise<{variables: Object, created: boolean}>} Variables and creation flag
|
|
26
25
|
*/
|
|
27
26
|
async function loadVariablesYaml(appKey) {
|
|
28
|
-
// Detect app type and get correct path (integration or builder)
|
|
29
27
|
const { appPath } = await detectAppType(appKey);
|
|
30
|
-
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
31
|
-
|
|
32
28
|
try {
|
|
33
|
-
const
|
|
34
|
-
|
|
29
|
+
const configPath = resolveApplicationConfigPath(appPath);
|
|
30
|
+
const variables = loadConfigFile(configPath);
|
|
31
|
+
return { variables, created: false };
|
|
35
32
|
} catch (error) {
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
const isNotFound = error.code === 'ENOENT' || (error.message && error.message.includes('not found'));
|
|
34
|
+
if (isNotFound) {
|
|
35
|
+
logger.log(chalk.yellow(`⚠️ Application config not found for ${appKey}`));
|
|
38
36
|
logger.log(chalk.yellow('📝 Creating minimal configuration...\n'));
|
|
39
37
|
return { variables: null, created: true };
|
|
40
38
|
}
|
|
41
|
-
throw new Error(`Failed to read
|
|
39
|
+
throw new Error(`Failed to read application config: ${error.message}`);
|
|
42
40
|
}
|
|
43
41
|
}
|
|
44
42
|
|
|
@@ -65,21 +63,19 @@ async function createMinimalAppIfNeeded(appKey, options) {
|
|
|
65
63
|
authentication: false
|
|
66
64
|
});
|
|
67
65
|
|
|
68
|
-
// Detect app type and get correct path (integration or builder)
|
|
69
66
|
const appTypeResult = await detectAppType(appKey);
|
|
70
67
|
if (!appTypeResult || !appTypeResult.appPath) {
|
|
71
68
|
throw new Error('Failed to detect app type after creation');
|
|
72
69
|
}
|
|
73
70
|
const { appPath } = appTypeResult;
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
return yaml.load(variablesContent);
|
|
71
|
+
const configPath = resolveApplicationConfigPath(appPath);
|
|
72
|
+
return loadConfigFile(configPath);
|
|
77
73
|
}
|
|
78
74
|
|
|
79
75
|
/**
|
|
80
76
|
* Builds image reference string from variables
|
|
81
77
|
* Format: repository:tag (e.g., aifabrix/miso-controller:latest or myregistry.azurecr.io/miso-controller:v1.0.0)
|
|
82
|
-
* @param {Object} variables - Variables from
|
|
78
|
+
* @param {Object} variables - Variables from application config
|
|
83
79
|
* @param {string} appKey - Application key (fallback)
|
|
84
80
|
* @returns {string} Image reference string
|
|
85
81
|
*/
|
|
@@ -94,7 +90,7 @@ function buildImageReference(variables, appKey) {
|
|
|
94
90
|
* Extract URL from external system JSON file for registration
|
|
95
91
|
* @async
|
|
96
92
|
* @param {string} appKey - Application key
|
|
97
|
-
* @param {Object} externalIntegration - External integration config from
|
|
93
|
+
* @param {Object} externalIntegration - External integration config from application config
|
|
98
94
|
* @returns {Promise<{url: string, apiKey?: string}>} URL and optional API key
|
|
99
95
|
*/
|
|
100
96
|
/**
|
|
@@ -171,12 +167,9 @@ async function extractExternalIntegrationUrl(appKey, externalIntegration) {
|
|
|
171
167
|
const systemFilePath = resolveSystemFilePath(appPath, schemaBasePath, systemFileName);
|
|
172
168
|
|
|
173
169
|
try {
|
|
174
|
-
const
|
|
175
|
-
const systemJson = JSON.parse(systemContent);
|
|
176
|
-
|
|
170
|
+
const systemJson = loadConfigFile(systemFilePath);
|
|
177
171
|
const url = extractUrlFromSystemJson(systemJson, systemFileName);
|
|
178
172
|
const apiKey = extractApiKeyFromSystemJson(systemJson);
|
|
179
|
-
|
|
180
173
|
return { url, apiKey };
|
|
181
174
|
} catch (error) {
|
|
182
175
|
handleFileReadError(error, systemFilePath);
|
|
@@ -184,7 +177,7 @@ async function extractExternalIntegrationUrl(appKey, externalIntegration) {
|
|
|
184
177
|
}
|
|
185
178
|
|
|
186
179
|
/**
|
|
187
|
-
* Extract application configuration from
|
|
180
|
+
* Extract application configuration from application config
|
|
188
181
|
* @async
|
|
189
182
|
* @param {Object} variables - Variables from YAML file
|
|
190
183
|
* @param {string} appKey - Application key
|
|
@@ -114,12 +114,12 @@ async function validateAppRegistrationData(config, originalAppKey) {
|
|
|
114
114
|
if (!config.displayName) missingFields.push('app.name');
|
|
115
115
|
|
|
116
116
|
if (missingFields.length > 0) {
|
|
117
|
-
logger.error(chalk.red('❌ Missing required fields in
|
|
117
|
+
logger.error(chalk.red('❌ Missing required fields in application.yaml:'));
|
|
118
118
|
missingFields.forEach(field => logger.error(chalk.red(` - ${field}`)));
|
|
119
119
|
// Detect app type to show correct path
|
|
120
120
|
const { appPath } = await detectAppType(originalAppKey);
|
|
121
121
|
const relativePath = path.relative(process.cwd(), appPath);
|
|
122
|
-
logger.error(chalk.red(`\n Please update ${relativePath}/
|
|
122
|
+
logger.error(chalk.red(`\n Please update ${relativePath}/application.yaml and try again.`));
|
|
123
123
|
process.exit(1);
|
|
124
124
|
}
|
|
125
125
|
|
package/lib/utils/cli-utils.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
const path = require('path');
|
|
12
|
+
const chalk = require('chalk');
|
|
12
13
|
const fs = require('fs').promises;
|
|
13
14
|
const logger = require('./logger');
|
|
14
15
|
|
|
@@ -85,6 +86,19 @@ function isPermissionDeniedError(errorMsg) {
|
|
|
85
86
|
!errorMsg.includes('Field "permissions');
|
|
86
87
|
}
|
|
87
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Checks if permission denied error is Docker-related (daemon socket / CLI), not API auth.
|
|
91
|
+
* Used to avoid showing Docker hints when the error is from Controller/Dataplane "Permission denied".
|
|
92
|
+
* @function isDockerPermissionDeniedError
|
|
93
|
+
* @param {string} errorMsg - Error message
|
|
94
|
+
* @returns {boolean} True if Docker permission denied error
|
|
95
|
+
*/
|
|
96
|
+
function isDockerPermissionDeniedError(errorMsg) {
|
|
97
|
+
if (!isPermissionDeniedError(errorMsg)) return false;
|
|
98
|
+
const lower = errorMsg.toLowerCase();
|
|
99
|
+
return lower.includes('docker') || lower.includes('socket') || errorMsg.includes('EACCES');
|
|
100
|
+
}
|
|
101
|
+
|
|
88
102
|
/**
|
|
89
103
|
* Format Docker-related errors
|
|
90
104
|
* @param {string} errorMsg - Error message
|
|
@@ -109,7 +123,7 @@ function formatDockerError(errorMsg) {
|
|
|
109
123
|
' Run "aifabrix doctor" to check which ports are in use.'
|
|
110
124
|
];
|
|
111
125
|
}
|
|
112
|
-
if (
|
|
126
|
+
if (isDockerPermissionDeniedError(errorMsg)) {
|
|
113
127
|
return [
|
|
114
128
|
' Permission denied.',
|
|
115
129
|
' Make sure you have the necessary permissions to run Docker commands.'
|
|
@@ -149,7 +163,7 @@ function formatAzureError(errorMsg) {
|
|
|
149
163
|
if (errorMsg.includes('Registry URL is required')) {
|
|
150
164
|
return [
|
|
151
165
|
' Registry URL is required.',
|
|
152
|
-
' Provide via --registry flag or configure in
|
|
166
|
+
' Provide via --registry flag or configure in application.yaml under image.registry'
|
|
153
167
|
];
|
|
154
168
|
}
|
|
155
169
|
return null;
|
|
@@ -222,6 +236,21 @@ function formatValidationError(errorMsg) {
|
|
|
222
236
|
return null;
|
|
223
237
|
}
|
|
224
238
|
|
|
239
|
+
/**
|
|
240
|
+
* Format API/Controller/Dataplane permission errors (403-style "Permission denied").
|
|
241
|
+
* Keeps the real message and adds a hint; avoids mis-classifying as Docker.
|
|
242
|
+
* @param {string} errorMsg - Error message
|
|
243
|
+
* @returns {string[]|null} Array of error message lines or null if not an API permission error
|
|
244
|
+
*/
|
|
245
|
+
function formatApiPermissionError(errorMsg) {
|
|
246
|
+
if (!isPermissionDeniedError(errorMsg)) return null;
|
|
247
|
+
if (isDockerPermissionDeniedError(errorMsg)) return null;
|
|
248
|
+
return [
|
|
249
|
+
` ${errorMsg}`,
|
|
250
|
+
' Ensure your token has the required permission (e.g. external-system:delete for delete).'
|
|
251
|
+
];
|
|
252
|
+
}
|
|
253
|
+
|
|
225
254
|
/**
|
|
226
255
|
* Formats error message based on error type
|
|
227
256
|
* @function formatError
|
|
@@ -236,6 +265,7 @@ function formatValidationError(errorMsg) {
|
|
|
236
265
|
*/
|
|
237
266
|
function tryFormatErrorWithFormatters(errorMsg) {
|
|
238
267
|
const formatters = [
|
|
268
|
+
formatApiPermissionError,
|
|
239
269
|
formatDockerError,
|
|
240
270
|
formatAzureError,
|
|
241
271
|
formatSecretsError,
|
|
@@ -283,6 +313,19 @@ function logError(command, errorMessages) {
|
|
|
283
313
|
logger.error('\n💡 Run "aifabrix doctor" for environment diagnostics.\n');
|
|
284
314
|
}
|
|
285
315
|
|
|
316
|
+
/**
|
|
317
|
+
* Logs the resolved app path so the user can see which directory (integration/<app> or builder/<app>) is used.
|
|
318
|
+
* Path resolution order is always integration first, then builder; no CLI flag overrides this.
|
|
319
|
+
*
|
|
320
|
+
* @param {string} appPath - Resolved application directory path
|
|
321
|
+
* @param {Object} [_options] - Reserved for backward compatibility; ignored
|
|
322
|
+
*/
|
|
323
|
+
function logOfflinePathWhenType(appPath, options) {
|
|
324
|
+
if (!appPath || !options || (options.type !== 'app' && options.type !== 'external')) return;
|
|
325
|
+
const displayPath = path.relative(process.cwd(), appPath) || appPath;
|
|
326
|
+
logger.log(chalk.gray(`Using: ${displayPath}`));
|
|
327
|
+
}
|
|
328
|
+
|
|
286
329
|
/**
|
|
287
330
|
* Handles command errors with user-friendly messages
|
|
288
331
|
* @param {Error} error - The error that occurred
|
|
@@ -332,6 +375,7 @@ async function appendWizardError(appKey, error) {
|
|
|
332
375
|
module.exports = {
|
|
333
376
|
validateCommand,
|
|
334
377
|
handleCommandError,
|
|
335
|
-
appendWizardError
|
|
378
|
+
appendWizardError,
|
|
379
|
+
logOfflinePathWhenType
|
|
336
380
|
};
|
|
337
381
|
|