@aifabrix/builder 2.10.1 → 2.20.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 +194 -0
- package/README.md +12 -0
- package/integration/hubspot/README.md +2 -2
- package/integration/hubspot/hubspot-deploy.json +12 -4
- package/lib/api/applications.api.js +164 -0
- package/lib/api/auth.api.js +304 -0
- package/lib/api/datasources-core.api.js +87 -0
- package/lib/api/datasources-extended.api.js +117 -0
- package/lib/api/datasources.api.js +13 -0
- package/lib/api/deployments.api.js +126 -0
- package/lib/api/environments.api.js +245 -0
- package/lib/api/external-systems.api.js +251 -0
- package/lib/api/index.js +221 -0
- package/lib/api/pipeline.api.js +234 -0
- package/lib/api/types/applications.types.js +136 -0
- package/lib/api/types/auth.types.js +218 -0
- package/lib/api/types/datasources.types.js +272 -0
- package/lib/api/types/deployments.types.js +184 -0
- package/lib/api/types/environments.types.js +197 -0
- package/lib/api/types/external-systems.types.js +244 -0
- package/lib/api/types/pipeline.types.js +125 -0
- package/lib/app-list.js +5 -7
- package/lib/app-register.js +70 -403
- package/lib/app-rotate-secret.js +4 -10
- package/lib/commands/login.js +19 -12
- package/lib/datasource-deploy.js +7 -30
- package/lib/datasource-list.js +9 -6
- package/lib/deployer.js +103 -135
- package/lib/environment-deploy.js +15 -26
- package/lib/external-system-deploy.js +12 -39
- package/lib/external-system-download.js +5 -13
- package/lib/external-system-test.js +9 -12
- package/lib/utils/api-error-handler.js +11 -453
- package/lib/utils/app-register-api.js +66 -0
- package/lib/utils/app-register-auth.js +72 -0
- package/lib/utils/app-register-config.js +205 -0
- package/lib/utils/app-register-display.js +69 -0
- package/lib/utils/app-register-validator.js +143 -0
- package/lib/utils/deployment-errors.js +88 -6
- package/lib/utils/device-code.js +1 -1
- package/lib/utils/error-formatters/error-parser.js +150 -0
- package/lib/utils/error-formatters/http-status-errors.js +189 -0
- package/lib/utils/error-formatters/network-errors.js +46 -0
- package/lib/utils/error-formatters/permission-errors.js +94 -0
- package/lib/utils/error-formatters/validation-errors.js +133 -0
- package/package.json +1 -1
- package/templates/applications/README.md.hbs +1 -1
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Fabrix Builder - App Register Configuration Utilities
|
|
3
|
+
*
|
|
4
|
+
* Configuration extraction and loading for application registration
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview Configuration utilities for app registration
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 2.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs').promises;
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const chalk = require('chalk');
|
|
14
|
+
const yaml = require('js-yaml');
|
|
15
|
+
const logger = require('./logger');
|
|
16
|
+
const { detectAppType } = require('./paths');
|
|
17
|
+
|
|
18
|
+
// Import createApp to auto-generate config if missing
|
|
19
|
+
let createApp;
|
|
20
|
+
try {
|
|
21
|
+
createApp = require('../app').createApp;
|
|
22
|
+
} catch {
|
|
23
|
+
createApp = null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Load variables.yaml file for an application
|
|
28
|
+
* @async
|
|
29
|
+
* @param {string} appKey - Application key
|
|
30
|
+
* @returns {Promise<{variables: Object, created: boolean}>} Variables and creation flag
|
|
31
|
+
*/
|
|
32
|
+
async function loadVariablesYaml(appKey) {
|
|
33
|
+
// Detect app type and get correct path (integration or builder)
|
|
34
|
+
const { appPath } = await detectAppType(appKey);
|
|
35
|
+
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const variablesContent = await fs.readFile(variablesPath, 'utf-8');
|
|
39
|
+
return { variables: yaml.load(variablesContent), created: false };
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (error.code === 'ENOENT') {
|
|
42
|
+
logger.log(chalk.yellow(`⚠️ variables.yaml not found for ${appKey}`));
|
|
43
|
+
logger.log(chalk.yellow('📝 Creating minimal configuration...\n'));
|
|
44
|
+
return { variables: null, created: true };
|
|
45
|
+
}
|
|
46
|
+
throw new Error(`Failed to read variables.yaml: ${error.message}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Create minimal application configuration if needed
|
|
52
|
+
* @async
|
|
53
|
+
* @param {string} appKey - Application key
|
|
54
|
+
* @param {Object} options - Registration options
|
|
55
|
+
* @returns {Promise<Object>} Variables after creation
|
|
56
|
+
*/
|
|
57
|
+
async function createMinimalAppIfNeeded(appKey, options) {
|
|
58
|
+
if (!createApp) {
|
|
59
|
+
throw new Error('Cannot auto-create application: createApp function not available');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
await createApp(appKey, {
|
|
63
|
+
port: options.port,
|
|
64
|
+
language: 'typescript',
|
|
65
|
+
database: false,
|
|
66
|
+
redis: false,
|
|
67
|
+
storage: false,
|
|
68
|
+
authentication: false
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Detect app type and get correct path (integration or builder)
|
|
72
|
+
const { appPath } = await detectAppType(appKey);
|
|
73
|
+
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
74
|
+
const variablesContent = await fs.readFile(variablesPath, 'utf-8');
|
|
75
|
+
return yaml.load(variablesContent);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Builds image reference string from variables
|
|
80
|
+
* Format: repository:tag (e.g., aifabrix/miso-controller:latest or myregistry.azurecr.io/miso-controller:v1.0.0)
|
|
81
|
+
* @param {Object} variables - Variables from YAML file
|
|
82
|
+
* @param {string} appKey - Application key (fallback)
|
|
83
|
+
* @returns {string} Image reference string
|
|
84
|
+
*/
|
|
85
|
+
function buildImageReference(variables, appKey) {
|
|
86
|
+
const imageName = variables.image?.name || variables.app?.key || appKey;
|
|
87
|
+
const registry = variables.image?.registry;
|
|
88
|
+
const tag = variables.image?.tag || 'latest';
|
|
89
|
+
return registry ? `${registry}/${imageName}:${tag}` : `${imageName}:${tag}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Extract URL from external system JSON file for registration
|
|
94
|
+
* @async
|
|
95
|
+
* @param {string} appKey - Application key
|
|
96
|
+
* @param {Object} externalIntegration - External integration config from variables.yaml
|
|
97
|
+
* @returns {Promise<{url: string, apiKey?: string}>} URL and optional API key
|
|
98
|
+
*/
|
|
99
|
+
async function extractExternalIntegrationUrl(appKey, externalIntegration) {
|
|
100
|
+
if (!externalIntegration || !externalIntegration.systems || externalIntegration.systems.length === 0) {
|
|
101
|
+
throw new Error('externalIntegration.systems is required for external type applications');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Detect app type and get correct path (integration or builder)
|
|
105
|
+
const { appPath } = await detectAppType(appKey);
|
|
106
|
+
const schemaBasePath = externalIntegration.schemaBasePath || './';
|
|
107
|
+
const systemFileName = externalIntegration.systems[0];
|
|
108
|
+
|
|
109
|
+
// Resolve system file path (handle both relative and absolute paths)
|
|
110
|
+
const systemFilePath = path.isAbsolute(schemaBasePath)
|
|
111
|
+
? path.join(schemaBasePath, systemFileName)
|
|
112
|
+
: path.join(appPath, schemaBasePath, systemFileName);
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const systemContent = await fs.readFile(systemFilePath, 'utf-8');
|
|
116
|
+
const systemJson = JSON.parse(systemContent);
|
|
117
|
+
|
|
118
|
+
// Extract URL from environment.baseUrl
|
|
119
|
+
const url = systemJson.environment?.baseUrl;
|
|
120
|
+
if (!url) {
|
|
121
|
+
throw new Error(`Missing environment.baseUrl in ${systemFileName}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Extract optional API key from authentication if present
|
|
125
|
+
let apiKey;
|
|
126
|
+
if (systemJson.authentication?.apikey?.key) {
|
|
127
|
+
// If it's a kv:// reference, we can't resolve it here, so leave it undefined
|
|
128
|
+
// The API will handle kv:// references
|
|
129
|
+
const keyValue = systemJson.authentication.apikey.key;
|
|
130
|
+
if (!keyValue.startsWith('kv://') && !keyValue.startsWith('{{')) {
|
|
131
|
+
apiKey = keyValue;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return { url, apiKey };
|
|
136
|
+
} catch (error) {
|
|
137
|
+
if (error.code === 'ENOENT') {
|
|
138
|
+
throw new Error(`External system file not found: ${systemFilePath}`);
|
|
139
|
+
}
|
|
140
|
+
throw new Error(`Failed to read external system file: ${error.message}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Extract application configuration from variables.yaml
|
|
146
|
+
* @async
|
|
147
|
+
* @param {Object} variables - Variables from YAML file
|
|
148
|
+
* @param {string} appKey - Application key
|
|
149
|
+
* @param {Object} options - Registration options
|
|
150
|
+
* @returns {Promise<Object>} Extracted configuration
|
|
151
|
+
*/
|
|
152
|
+
async function extractAppConfiguration(variables, appKey, options) {
|
|
153
|
+
const appKeyFromFile = variables.app?.key || appKey;
|
|
154
|
+
const displayName = variables.app?.name || options.name || appKey;
|
|
155
|
+
const description = variables.app?.description || '';
|
|
156
|
+
|
|
157
|
+
// Handle external type
|
|
158
|
+
if (variables.app?.type === 'external') {
|
|
159
|
+
// Extract URL from external system JSON file
|
|
160
|
+
const { url, apiKey } = await extractExternalIntegrationUrl(appKey, variables.externalIntegration);
|
|
161
|
+
|
|
162
|
+
// Build simplified externalIntegration object for registration API
|
|
163
|
+
const externalIntegration = { url };
|
|
164
|
+
if (apiKey) {
|
|
165
|
+
externalIntegration.apiKey = apiKey;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
appKey: appKeyFromFile,
|
|
170
|
+
displayName,
|
|
171
|
+
description,
|
|
172
|
+
appType: 'external',
|
|
173
|
+
externalIntegration,
|
|
174
|
+
port: null, // External systems don't need ports
|
|
175
|
+
image: null, // External systems don't need images
|
|
176
|
+
language: null // External systems don't need language
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const appType = variables.build?.language === 'typescript' ? 'webapp' : 'service';
|
|
181
|
+
const registryMode = variables.image?.registryMode || 'external';
|
|
182
|
+
const port = variables.build?.port || options.port || 3000;
|
|
183
|
+
const language = variables.build?.language || 'typescript';
|
|
184
|
+
const image = buildImageReference(variables, appKeyFromFile);
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
appKey: appKeyFromFile,
|
|
188
|
+
displayName,
|
|
189
|
+
description,
|
|
190
|
+
appType,
|
|
191
|
+
registryMode,
|
|
192
|
+
port,
|
|
193
|
+
image,
|
|
194
|
+
language
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
module.exports = {
|
|
199
|
+
loadVariablesYaml,
|
|
200
|
+
createMinimalAppIfNeeded,
|
|
201
|
+
buildImageReference,
|
|
202
|
+
extractAppConfiguration,
|
|
203
|
+
extractExternalIntegrationUrl
|
|
204
|
+
};
|
|
205
|
+
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Fabrix Builder - App Register Display Utilities
|
|
3
|
+
*
|
|
4
|
+
* Display and formatting utilities for application registration results
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview Display utilities for app registration
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 2.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const chalk = require('chalk');
|
|
12
|
+
const logger = require('./logger');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get environment prefix for GitHub Secrets
|
|
16
|
+
* @param {string} environment - Environment key (e.g., 'dev', 'tst', 'pro', 'miso')
|
|
17
|
+
* @returns {string} Uppercase prefix (e.g., 'DEV', 'TST', 'PRO', 'MISO')
|
|
18
|
+
*/
|
|
19
|
+
function getEnvironmentPrefix(environment) {
|
|
20
|
+
if (!environment) {
|
|
21
|
+
return 'DEV';
|
|
22
|
+
}
|
|
23
|
+
// Convert to uppercase and handle common variations
|
|
24
|
+
const env = environment.toLowerCase();
|
|
25
|
+
if (env === 'dev' || env === 'development') {
|
|
26
|
+
return 'DEV';
|
|
27
|
+
}
|
|
28
|
+
if (env === 'tst' || env === 'test' || env === 'staging') {
|
|
29
|
+
return 'TST';
|
|
30
|
+
}
|
|
31
|
+
if (env === 'pro' || env === 'prod' || env === 'production') {
|
|
32
|
+
return 'PRO';
|
|
33
|
+
}
|
|
34
|
+
// For other environments (e.g., 'miso'), uppercase the entire string
|
|
35
|
+
// Use full string if 4 characters or less, otherwise use first 4 characters
|
|
36
|
+
const upper = environment.toUpperCase();
|
|
37
|
+
return upper.length <= 4 ? upper : upper.substring(0, 4);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Display registration success and credentials
|
|
42
|
+
* @param {Object} data - Registration response data
|
|
43
|
+
* @param {string} apiUrl - API URL
|
|
44
|
+
* @param {string} environment - Environment key
|
|
45
|
+
*/
|
|
46
|
+
function displayRegistrationResults(data, apiUrl, environment) {
|
|
47
|
+
logger.log(chalk.green('✅ Application registered successfully!\n'));
|
|
48
|
+
logger.log(chalk.bold('📋 Application Details:'));
|
|
49
|
+
logger.log(` ID: ${data.application.id}`);
|
|
50
|
+
logger.log(` Key: ${data.application.key}`);
|
|
51
|
+
logger.log(` Display Name: ${data.application.displayName}\n`);
|
|
52
|
+
|
|
53
|
+
logger.log(chalk.bold.yellow('🔑 CREDENTIALS (save these immediately):'));
|
|
54
|
+
logger.log(chalk.yellow(` Client ID: ${data.credentials.clientId}`));
|
|
55
|
+
logger.log(chalk.yellow(` Client Secret: ${data.credentials.clientSecret}\n`));
|
|
56
|
+
|
|
57
|
+
logger.log(chalk.red('⚠️ IMPORTANT: Client Secret will not be shown again!\n'));
|
|
58
|
+
|
|
59
|
+
const envPrefix = getEnvironmentPrefix(environment);
|
|
60
|
+
logger.log(chalk.bold('📝 Add to GitHub Secrets:'));
|
|
61
|
+
logger.log(chalk.cyan(' Repository level:'));
|
|
62
|
+
logger.log(chalk.cyan(` MISO_CONTROLLER_URL = ${apiUrl}`));
|
|
63
|
+
logger.log(chalk.cyan(`\n Environment level (${environment}):`));
|
|
64
|
+
logger.log(chalk.cyan(` ${envPrefix}_MISO_CLIENTID = ${data.credentials.clientId}`));
|
|
65
|
+
logger.log(chalk.cyan(` ${envPrefix}_MISO_CLIENTSECRET = ${data.credentials.clientSecret}\n`));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = { displayRegistrationResults, getEnvironmentPrefix };
|
|
69
|
+
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Fabrix Builder - App Register Validation Utilities
|
|
3
|
+
*
|
|
4
|
+
* Validation logic for application registration
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview Validation utilities for app registration
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 2.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const chalk = require('chalk');
|
|
13
|
+
const logger = require('./logger');
|
|
14
|
+
const { detectAppType } = require('./paths');
|
|
15
|
+
const applicationSchema = require('../schema/application-schema.json');
|
|
16
|
+
|
|
17
|
+
// Extract valid enum values from application schema
|
|
18
|
+
const validTypes = applicationSchema.properties.type.enum || [];
|
|
19
|
+
const validRegistryModes = applicationSchema.properties.registryMode.enum || [];
|
|
20
|
+
const portConstraints = {
|
|
21
|
+
minimum: applicationSchema.properties.port?.minimum || 1,
|
|
22
|
+
maximum: applicationSchema.properties.port?.maximum || 65535
|
|
23
|
+
};
|
|
24
|
+
const imagePattern = applicationSchema.properties.image?.pattern || /^[a-zA-Z0-9._/-]+:[a-zA-Z0-9._-]+$/;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Validation schema for application registration
|
|
28
|
+
* Validates according to application-schema.json
|
|
29
|
+
*/
|
|
30
|
+
const registerApplicationSchema = {
|
|
31
|
+
environmentId: (val) => {
|
|
32
|
+
if (!val || val.length < 1) {
|
|
33
|
+
throw new Error('Invalid environment ID format');
|
|
34
|
+
}
|
|
35
|
+
return val;
|
|
36
|
+
},
|
|
37
|
+
key: (val) => {
|
|
38
|
+
if (!val || val.length < 1) {
|
|
39
|
+
throw new Error('Application key is required');
|
|
40
|
+
}
|
|
41
|
+
const keyPattern = applicationSchema.properties.key.pattern;
|
|
42
|
+
const keyMaxLength = applicationSchema.properties.key.maxLength || 50;
|
|
43
|
+
if (val.length > keyMaxLength) {
|
|
44
|
+
throw new Error(`Application key must be at most ${keyMaxLength} characters`);
|
|
45
|
+
}
|
|
46
|
+
if (keyPattern && !new RegExp(keyPattern).test(val)) {
|
|
47
|
+
throw new Error('Application key must contain only lowercase letters, numbers, and hyphens');
|
|
48
|
+
}
|
|
49
|
+
return val;
|
|
50
|
+
},
|
|
51
|
+
displayName: (val) => {
|
|
52
|
+
if (!val || val.length < 1) {
|
|
53
|
+
throw new Error('Display name is required');
|
|
54
|
+
}
|
|
55
|
+
const displayNameMaxLength = applicationSchema.properties.displayName.maxLength || 100;
|
|
56
|
+
if (val.length > displayNameMaxLength) {
|
|
57
|
+
throw new Error(`Display name must be at most ${displayNameMaxLength} characters`);
|
|
58
|
+
}
|
|
59
|
+
return val;
|
|
60
|
+
},
|
|
61
|
+
description: (val) => val || undefined,
|
|
62
|
+
image: (val, appType) => {
|
|
63
|
+
// Image is required for non-external types
|
|
64
|
+
if (appType !== 'external') {
|
|
65
|
+
if (!val || typeof val !== 'string') {
|
|
66
|
+
throw new Error('Image is required for non-external application types');
|
|
67
|
+
}
|
|
68
|
+
// Validate image format: repository:tag
|
|
69
|
+
const pattern = typeof imagePattern === 'string' ? new RegExp(imagePattern) : imagePattern;
|
|
70
|
+
if (!pattern.test(val)) {
|
|
71
|
+
throw new Error('Image must be in format repository:tag (e.g., aifabrix/miso-controller:latest or myregistry.azurecr.io/miso-controller:v1.0.0)');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return val || undefined;
|
|
75
|
+
},
|
|
76
|
+
configuration: (val) => {
|
|
77
|
+
if (!val || !val.type || !validTypes.includes(val.type)) {
|
|
78
|
+
throw new Error(`Configuration type must be one of: ${validTypes.join(', ')}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// For external type: require externalIntegration, skip registryMode/port validation
|
|
82
|
+
if (val.type === 'external') {
|
|
83
|
+
if (!val.externalIntegration) {
|
|
84
|
+
throw new Error('externalIntegration is required for external application type');
|
|
85
|
+
}
|
|
86
|
+
// External type should not have registryMode, port, or image
|
|
87
|
+
return val;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// For non-external types: require registryMode, port, image
|
|
91
|
+
if (!val.registryMode || !validRegistryModes.includes(val.registryMode)) {
|
|
92
|
+
throw new Error(`Registry mode must be one of: ${validRegistryModes.join(', ')}`);
|
|
93
|
+
}
|
|
94
|
+
if (val.port === undefined || val.port === null) {
|
|
95
|
+
throw new Error('Port is required for non-external application types');
|
|
96
|
+
}
|
|
97
|
+
if (!Number.isInteger(val.port) || val.port < portConstraints.minimum || val.port > portConstraints.maximum) {
|
|
98
|
+
throw new Error(`Port must be an integer between ${portConstraints.minimum} and ${portConstraints.maximum}`);
|
|
99
|
+
}
|
|
100
|
+
return val;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Validate application registration data
|
|
106
|
+
* @async
|
|
107
|
+
* @param {Object} config - Application configuration
|
|
108
|
+
* @param {string} originalAppKey - Original app key for error messages
|
|
109
|
+
* @throws {Error} If validation fails
|
|
110
|
+
*/
|
|
111
|
+
async function validateAppRegistrationData(config, originalAppKey) {
|
|
112
|
+
const missingFields = [];
|
|
113
|
+
if (!config.appKey) missingFields.push('app.key');
|
|
114
|
+
if (!config.displayName) missingFields.push('app.name');
|
|
115
|
+
|
|
116
|
+
if (missingFields.length > 0) {
|
|
117
|
+
logger.error(chalk.red('❌ Missing required fields in variables.yaml:'));
|
|
118
|
+
missingFields.forEach(field => logger.error(chalk.red(` - ${field}`)));
|
|
119
|
+
// Detect app type to show correct path
|
|
120
|
+
const { appPath } = await detectAppType(originalAppKey);
|
|
121
|
+
const relativePath = path.relative(process.cwd(), appPath);
|
|
122
|
+
logger.error(chalk.red(`\n Please update ${relativePath}/variables.yaml and try again.`));
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
registerApplicationSchema.key(config.appKey);
|
|
128
|
+
registerApplicationSchema.displayName(config.displayName);
|
|
129
|
+
registerApplicationSchema.image(config.image, config.appType);
|
|
130
|
+
registerApplicationSchema.configuration({
|
|
131
|
+
type: config.appType,
|
|
132
|
+
registryMode: config.registryMode,
|
|
133
|
+
port: config.port,
|
|
134
|
+
externalIntegration: config.externalIntegration
|
|
135
|
+
});
|
|
136
|
+
} catch (error) {
|
|
137
|
+
logger.error(chalk.red(`❌ Invalid configuration: ${error.message}`));
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
module.exports = { registerApplicationSchema, validateAppRegistrationData };
|
|
143
|
+
|
|
@@ -18,8 +18,17 @@ const { parseErrorResponse } = require('./api-error-handler');
|
|
|
18
18
|
* @returns {Object} Structured error information
|
|
19
19
|
*/
|
|
20
20
|
function handleDeploymentError(error) {
|
|
21
|
+
if (!error) {
|
|
22
|
+
return {
|
|
23
|
+
message: 'Unknown error',
|
|
24
|
+
code: 'UNKNOWN',
|
|
25
|
+
timeout: false,
|
|
26
|
+
status: undefined,
|
|
27
|
+
data: undefined
|
|
28
|
+
};
|
|
29
|
+
}
|
|
21
30
|
const safeError = {
|
|
22
|
-
message: error.message,
|
|
31
|
+
message: error.message || 'Unknown error',
|
|
23
32
|
code: error.code || 'UNKNOWN',
|
|
24
33
|
timeout: error.code === 'ECONNABORTED',
|
|
25
34
|
status: error.status || error.response?.status,
|
|
@@ -42,6 +51,38 @@ function handleDeploymentError(error) {
|
|
|
42
51
|
* @throws {Error} User-friendly error message
|
|
43
52
|
*/
|
|
44
53
|
async function handleDeploymentErrors(error, appName, url, alreadyLogged = false) {
|
|
54
|
+
// For validation errors (like URL validation), just re-throw them directly
|
|
55
|
+
// They already have user-friendly messages
|
|
56
|
+
// Handle both Error objects and strings
|
|
57
|
+
let errorMessage = '';
|
|
58
|
+
if (error instanceof Error) {
|
|
59
|
+
errorMessage = error.message || '';
|
|
60
|
+
} else if (typeof error === 'string') {
|
|
61
|
+
errorMessage = error;
|
|
62
|
+
} else if (error && typeof error === 'object' && error.message) {
|
|
63
|
+
errorMessage = error.message;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (errorMessage && (
|
|
67
|
+
errorMessage.includes('Controller URL must use HTTPS') ||
|
|
68
|
+
errorMessage.includes('Invalid environment key') ||
|
|
69
|
+
errorMessage.includes('Environment key is required') ||
|
|
70
|
+
errorMessage.includes('Authentication configuration is required') ||
|
|
71
|
+
errorMessage.includes('Invalid controller URL format') ||
|
|
72
|
+
errorMessage.includes('Controller URL is required')
|
|
73
|
+
)) {
|
|
74
|
+
// If error is a string, convert to Error object
|
|
75
|
+
if (typeof error === 'string') {
|
|
76
|
+
throw new Error(error);
|
|
77
|
+
}
|
|
78
|
+
// If it's already an Error object, re-throw it directly
|
|
79
|
+
if (error instanceof Error) {
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
// Otherwise, create a new Error with the message
|
|
83
|
+
throw new Error(errorMessage);
|
|
84
|
+
}
|
|
85
|
+
|
|
45
86
|
// Log to audit log if not already logged
|
|
46
87
|
if (!alreadyLogged) {
|
|
47
88
|
try {
|
|
@@ -63,7 +104,17 @@ async function handleDeploymentErrors(error, appName, url, alreadyLogged = false
|
|
|
63
104
|
|
|
64
105
|
// Ensure errorData is not undefined before parsing
|
|
65
106
|
// If errorData is undefined, use the error message instead
|
|
66
|
-
|
|
107
|
+
let errorResponse = errorData !== undefined ? errorData : safeError.message;
|
|
108
|
+
|
|
109
|
+
// Ensure errorResponse is a string or object, not an Error object
|
|
110
|
+
if (errorResponse instanceof Error) {
|
|
111
|
+
errorResponse = errorResponse.message || 'Unknown error occurred';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Ensure errorResponse is not null or undefined
|
|
115
|
+
if (errorResponse === null || errorResponse === undefined) {
|
|
116
|
+
errorResponse = safeError.message || 'Unknown error occurred';
|
|
117
|
+
}
|
|
67
118
|
|
|
68
119
|
// Determine if this is a network error
|
|
69
120
|
const isNetworkError = safeError.code === 'ECONNREFUSED' ||
|
|
@@ -72,13 +123,44 @@ async function handleDeploymentErrors(error, appName, url, alreadyLogged = false
|
|
|
72
123
|
safeError.timeout;
|
|
73
124
|
|
|
74
125
|
// Parse error using error handler
|
|
75
|
-
|
|
126
|
+
let parsedError;
|
|
127
|
+
try {
|
|
128
|
+
parsedError = parseErrorResponse(errorResponse, safeError.status || 0, isNetworkError);
|
|
129
|
+
// Ensure parsedError is a valid object
|
|
130
|
+
if (!parsedError || typeof parsedError !== 'object') {
|
|
131
|
+
throw new Error('parseErrorResponse returned invalid result');
|
|
132
|
+
}
|
|
133
|
+
} catch (parseErr) {
|
|
134
|
+
// If parsing fails, use the safe error message
|
|
135
|
+
parsedError = {
|
|
136
|
+
message: safeError.message || 'Unknown error occurred',
|
|
137
|
+
formatted: safeError.message || 'Unknown error occurred',
|
|
138
|
+
data: undefined
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Ensure parsedError is always a valid object with required properties
|
|
143
|
+
if (!parsedError || typeof parsedError !== 'object') {
|
|
144
|
+
parsedError = {
|
|
145
|
+
message: safeError.message || 'Unknown error occurred',
|
|
146
|
+
formatted: safeError.message || 'Unknown error occurred',
|
|
147
|
+
data: undefined
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Ensure we have a message - handle case where parsedError.message might be undefined
|
|
152
|
+
const finalErrorMessage = (parsedError && parsedError.message) ? parsedError.message : (safeError.message || 'Unknown error occurred');
|
|
153
|
+
|
|
154
|
+
// Validate finalErrorMessage is a string
|
|
155
|
+
if (typeof finalErrorMessage !== 'string') {
|
|
156
|
+
throw new Error(`Invalid error message type: ${typeof finalErrorMessage}. Error: ${JSON.stringify(error)}`);
|
|
157
|
+
}
|
|
76
158
|
|
|
77
159
|
// Throw clean error message (without emoji) - CLI will format it
|
|
78
|
-
const formattedError = new Error(
|
|
79
|
-
formattedError.formatted = parsedError
|
|
160
|
+
const formattedError = new Error(finalErrorMessage);
|
|
161
|
+
formattedError.formatted = parsedError?.formatted || finalErrorMessage;
|
|
80
162
|
formattedError.status = safeError.status;
|
|
81
|
-
formattedError.data = parsedError
|
|
163
|
+
formattedError.data = parsedError?.data;
|
|
82
164
|
formattedError._logged = true; // Mark as logged to prevent double-logging
|
|
83
165
|
throw formattedError;
|
|
84
166
|
}
|
package/lib/utils/device-code.js
CHANGED
|
@@ -388,7 +388,7 @@ async function refreshDeviceToken(controllerUrl, refreshToken) {
|
|
|
388
388
|
}
|
|
389
389
|
|
|
390
390
|
const url = `${controllerUrl}/api/v1/auth/login/device/refresh`;
|
|
391
|
-
const response = await
|
|
391
|
+
const response = await getMakeApiCall()(url, {
|
|
392
392
|
method: 'POST',
|
|
393
393
|
headers: {
|
|
394
394
|
'Content-Type': 'application/json'
|