@aifabrix/builder 2.31.0 → 2.32.1
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/README.md +9 -9
- package/integration/hubspot/README.md +2 -2
- package/integration/hubspot/hubspot-deploy-company.json +17 -14
- package/integration/hubspot/hubspot-deploy-contact.json +19 -16
- package/integration/hubspot/hubspot-deploy-deal.json +21 -18
- package/lib/api/types/datasources.types.js +31 -5
- package/lib/api/types/wizard.types.js +142 -0
- package/lib/api/wizard.api.js +177 -0
- package/lib/{app-config.js → app/config.js} +4 -4
- package/lib/{app-deploy.js → app/deploy.js} +8 -8
- package/lib/app/display.js +90 -0
- package/lib/{app-dockerfile.js → app/dockerfile.js} +4 -4
- package/lib/{app-down.js → app/down.js} +4 -4
- package/lib/app/helpers.js +218 -0
- package/lib/app/index.js +298 -0
- package/lib/{app-list.js → app/list.js} +6 -6
- package/lib/{app-push.js → app/push.js} +4 -4
- package/lib/{app-readme.js → app/readme.js} +34 -13
- package/lib/{app-register.js → app/register.js} +9 -9
- package/lib/{app-rotate-secret.js → app/rotate-secret.js} +123 -37
- package/lib/{app-run-helpers.js → app/run-helpers.js} +10 -10
- package/lib/{app-run.js → app/run.js} +6 -6
- package/lib/{build.js → build/index.js} +59 -32
- package/lib/build/package.json +7 -0
- package/lib/cli.js +245 -179
- package/lib/commands/app.js +3 -3
- package/lib/commands/datasource.js +4 -4
- package/lib/commands/login-credentials.js +209 -0
- package/lib/commands/login-device.js +254 -0
- package/lib/commands/login.js +67 -378
- package/lib/commands/logout.js +1 -1
- package/lib/commands/secrets-set.js +1 -1
- package/lib/commands/secure.js +2 -2
- package/lib/commands/wizard.js +498 -0
- package/lib/{audit-logger.js → core/audit-logger.js} +1 -1
- package/lib/{config.js → core/config.js} +28 -26
- package/lib/{diff.js → core/diff.js} +157 -72
- package/lib/{secrets.js → core/secrets.js} +86 -49
- package/lib/{templates.js → core/templates-env.js} +14 -222
- package/lib/core/templates.js +279 -0
- package/lib/{datasource-deploy.js → datasource/deploy.js} +6 -6
- package/lib/{datasource-diff.js → datasource/diff.js} +2 -2
- package/lib/datasource/list.js +223 -0
- package/lib/{datasource-validate.js → datasource/validate.js} +2 -2
- package/lib/{deployer.js → deployment/deployer.js} +48 -18
- package/lib/{environment-deploy.js → deployment/environment.js} +163 -84
- package/lib/{push.js → deployment/push.js} +1 -1
- package/lib/external-system/deploy-helpers.js +145 -0
- package/lib/{external-system-deploy.js → external-system/deploy.js} +156 -111
- package/lib/external-system/download-helpers.js +114 -0
- package/lib/{external-system-download.js → external-system/download.js} +92 -135
- package/lib/{external-system-generator.js → external-system/generator.js} +15 -11
- package/lib/external-system/test-auth.js +40 -0
- package/lib/external-system/test-execution.js +84 -0
- package/lib/external-system/test-helpers.js +109 -0
- package/lib/{external-system-test.js → external-system/test.js} +174 -192
- package/lib/{generator-builders.js → generator/builders.js} +87 -10
- package/lib/{generator-external.js → generator/external.js} +115 -52
- package/lib/{github-generator.js → generator/github.js} +116 -15
- package/lib/{generator-helpers.js → generator/helpers.js} +92 -42
- package/lib/{generator.js → generator/index.js} +49 -22
- package/lib/{generator-split.js → generator/split.js} +108 -55
- package/lib/generator/wizard-prompts.js +357 -0
- package/lib/generator/wizard.js +490 -0
- package/lib/{infra.js → infrastructure/index.js} +49 -22
- package/lib/schema/external-datasource.schema.json +145 -133
- package/lib/schema/external-system.schema.json +42 -0
- package/lib/utils/api.js +9 -5
- package/lib/utils/app-register-api.js +60 -32
- package/lib/utils/app-register-auth.js +172 -47
- package/lib/utils/app-register-config.js +130 -59
- package/lib/utils/app-run-containers.js +29 -8
- package/lib/utils/build-helpers.js +1 -1
- package/lib/utils/cli-utils.js +78 -30
- package/lib/utils/compose-generator.js +145 -65
- package/lib/utils/config-paths.js +2 -0
- package/lib/utils/deployment-errors.js +1 -1
- package/lib/utils/device-code.js +99 -41
- package/lib/utils/env-config-loader.js +1 -1
- package/lib/utils/env-copy.js +21 -18
- package/lib/utils/env-endpoints.js +115 -67
- package/lib/utils/env-map.js +13 -14
- package/lib/utils/env-ports.js +45 -25
- package/lib/utils/env-template.js +84 -42
- package/lib/utils/error-formatter.js +26 -9
- package/lib/utils/error-formatters/error-parser.js +90 -4
- package/lib/utils/error-formatters/http-status-errors.js +54 -17
- package/lib/utils/error-formatters/network-errors.js +103 -26
- package/lib/utils/external-system-display.js +184 -90
- package/lib/utils/external-system-validators.js +164 -42
- package/lib/utils/file-upload.js +109 -0
- package/lib/utils/health-check.js +199 -83
- package/lib/utils/infra-containers.js +1 -1
- package/lib/utils/infra-status.js +66 -15
- package/lib/utils/local-secrets.js +45 -25
- package/lib/utils/paths.js +45 -33
- package/lib/utils/schema-loader.js +42 -25
- package/lib/utils/schema-resolver.js +123 -74
- package/lib/utils/secrets-encryption.js +62 -25
- package/lib/utils/secrets-helpers.js +126 -63
- package/lib/utils/secrets-path.js +1 -1
- package/lib/utils/secrets-url.js +1 -1
- package/lib/utils/token-manager-refresh.js +181 -0
- package/lib/utils/token-manager.js +76 -123
- package/lib/utils/variable-transformer.js +154 -77
- package/lib/utils/yaml-preserve.js +41 -47
- package/lib/{template-validator.js → validation/template.js} +54 -23
- package/lib/{validate.js → validation/validate.js} +205 -125
- package/lib/{validator.js → validation/validator.js} +58 -39
- package/package.json +34 -3
- package/scripts/install-local.js +210 -0
- package/templates/external-system/deploy.ps1.hbs +34 -0
- package/templates/external-system/deploy.sh.hbs +34 -0
- package/templates/external-system/external-datasource.json.hbs +31 -12
- package/lib/app.js +0 -467
- package/lib/datasource-list.js +0 -141
- /package/lib/{app-prompts.js → app/prompts.js} +0 -0
- /package/lib/{env-reader.js → core/env-reader.js} +0 -0
- /package/lib/{key-generator.js → core/key-generator.js} +0 -0
package/lib/app/index.js
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Fabrix Builder Application Management
|
|
3
|
+
*
|
|
4
|
+
* This module handles application building, running, and deployment.
|
|
5
|
+
* Includes runtime detection, Dockerfile generation, and container management.
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview Application build and run management for AI Fabrix Builder
|
|
8
|
+
* @author AI Fabrix Team
|
|
9
|
+
* @version 2.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs').promises;
|
|
13
|
+
const { readExistingEnv } = require('../core/env-reader');
|
|
14
|
+
const build = require('../build');
|
|
15
|
+
const appRun = require('./run');
|
|
16
|
+
const { promptForOptions } = require('./prompts');
|
|
17
|
+
const { generateConfigFiles } = require('./config');
|
|
18
|
+
const { pushApp } = require('./push');
|
|
19
|
+
const { generateDockerfileForApp } = require('./dockerfile');
|
|
20
|
+
const { loadTemplateVariables, updateTemplateVariables, mergeTemplateVariables } = require('../utils/template-helpers');
|
|
21
|
+
const { validateTemplate } = require('../validation/template');
|
|
22
|
+
const auditLogger = require('../core/audit-logger');
|
|
23
|
+
const { downApp } = require('./down');
|
|
24
|
+
const { getAppPath } = require('../utils/paths');
|
|
25
|
+
const { displaySuccessMessage } = require('./display');
|
|
26
|
+
const {
|
|
27
|
+
validateAppDirectoryNotExists,
|
|
28
|
+
getBaseDirForAppType,
|
|
29
|
+
handleGitHubWorkflows,
|
|
30
|
+
validateAppCreation,
|
|
31
|
+
processTemplateFiles,
|
|
32
|
+
setupAppFiles
|
|
33
|
+
} = require('./helpers');
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Creates new application with scaffolded configuration files
|
|
37
|
+
* Prompts for configuration options and generates builder/ folder structure
|
|
38
|
+
*
|
|
39
|
+
* @async
|
|
40
|
+
* @function createApp
|
|
41
|
+
* @param {string} appName - Name of the application to create
|
|
42
|
+
* @param {Object} options - Creation options
|
|
43
|
+
* @param {number} [options.port] - Application port
|
|
44
|
+
* @param {boolean} [options.database] - Requires database
|
|
45
|
+
* @param {boolean} [options.redis] - Requires Redis
|
|
46
|
+
* @param {boolean} [options.storage] - Requires file storage
|
|
47
|
+
* @param {boolean} [options.authentication] - Requires authentication/RBAC
|
|
48
|
+
* @param {string} [options.language] - Runtime language (typescript/python)
|
|
49
|
+
* @param {string} [options.template] - Template to use (e.g., controller, keycloak)
|
|
50
|
+
* @returns {Promise<void>} Resolves when app is created
|
|
51
|
+
* @throws {Error} If creation fails
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* await createApp('myapp', { port: 3000, database: true, language: 'typescript' });
|
|
55
|
+
* // Creates builder/ with variables.yaml, env.template, rbac.yaml
|
|
56
|
+
*/
|
|
57
|
+
/**
|
|
58
|
+
* Validates app name and initial setup
|
|
59
|
+
* @function validateAppNameAndSetup
|
|
60
|
+
* @param {string} appName - Application name
|
|
61
|
+
* @param {Object} options - Options
|
|
62
|
+
* @returns {Object} Initial paths and type
|
|
63
|
+
*/
|
|
64
|
+
function validateAppNameAndSetup(appName, options) {
|
|
65
|
+
if (!appName || typeof appName !== 'string') {
|
|
66
|
+
throw new Error('Application name is required');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const initialType = options.type || 'webapp';
|
|
70
|
+
const baseDir = getBaseDirForAppType(initialType);
|
|
71
|
+
const appPath = getAppPath(appName, initialType);
|
|
72
|
+
|
|
73
|
+
return { initialType, baseDir, appPath };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Handles template validation and loading
|
|
78
|
+
* @async
|
|
79
|
+
* @function handleTemplateSetup
|
|
80
|
+
* @param {Object} options - Options
|
|
81
|
+
* @returns {Promise<Object>} Merged options with template variables
|
|
82
|
+
*/
|
|
83
|
+
async function handleTemplateSetup(options) {
|
|
84
|
+
if (options.template) {
|
|
85
|
+
await validateTemplate(options.template);
|
|
86
|
+
}
|
|
87
|
+
const templateVariables = await loadTemplateVariables(options.template);
|
|
88
|
+
return mergeTemplateVariables(options, templateVariables);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Validates and prepares final app path
|
|
93
|
+
* @async
|
|
94
|
+
* @function prepareFinalAppPath
|
|
95
|
+
* @param {string} appName - Application name
|
|
96
|
+
* @param {Object} config - Configuration
|
|
97
|
+
* @param {string} initialAppPath - Initial app path
|
|
98
|
+
* @returns {Promise<string>} Final app path
|
|
99
|
+
*/
|
|
100
|
+
async function prepareFinalAppPath(appName, config, initialAppPath) {
|
|
101
|
+
const finalBaseDir = getBaseDirForAppType(config.type);
|
|
102
|
+
const finalAppPath = getAppPath(appName, config.type);
|
|
103
|
+
|
|
104
|
+
// If path changed, validate the new path
|
|
105
|
+
if (finalAppPath !== initialAppPath) {
|
|
106
|
+
await validateAppDirectoryNotExists(finalAppPath, appName, finalBaseDir);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return finalAppPath;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Generates all application files
|
|
114
|
+
* @async
|
|
115
|
+
* @function generateApplicationFiles
|
|
116
|
+
* @param {string} finalAppPath - Final app path
|
|
117
|
+
* @param {string} appName - Application name
|
|
118
|
+
* @param {Object} config - Configuration
|
|
119
|
+
* @param {Object} options - Options
|
|
120
|
+
* @returns {Promise<string>} Environment conversion message
|
|
121
|
+
*/
|
|
122
|
+
async function generateApplicationFiles(finalAppPath, appName, config, options) {
|
|
123
|
+
await fs.mkdir(finalAppPath, { recursive: true });
|
|
124
|
+
await processTemplateFiles(options.template, finalAppPath, appName, options, config);
|
|
125
|
+
|
|
126
|
+
const existingEnv = await readExistingEnv(process.cwd());
|
|
127
|
+
const envConversionMessage = existingEnv
|
|
128
|
+
? '\n✓ Found existing .env file - sensitive values will be converted to kv:// references'
|
|
129
|
+
: '';
|
|
130
|
+
|
|
131
|
+
await generateConfigFiles(finalAppPath, appName, config, existingEnv);
|
|
132
|
+
|
|
133
|
+
// Generate external system files if type is external
|
|
134
|
+
if (config.type === 'external') {
|
|
135
|
+
const externalGenerator = require('../external-system/generator');
|
|
136
|
+
await externalGenerator.generateExternalSystemFiles(finalAppPath, appName, config);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (options.app) {
|
|
140
|
+
await setupAppFiles(appName, finalAppPath, config, options);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
await handleGitHubWorkflows(options, config);
|
|
144
|
+
return envConversionMessage;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Logs application creation for audit
|
|
149
|
+
* @async
|
|
150
|
+
* @function logApplicationCreation
|
|
151
|
+
* @param {string} appName - Application name
|
|
152
|
+
* @param {Object} config - Configuration
|
|
153
|
+
* @param {Object} options - Options
|
|
154
|
+
*/
|
|
155
|
+
async function logApplicationCreation(appName, config, options) {
|
|
156
|
+
await auditLogger.logApplicationCreation(appName, {
|
|
157
|
+
language: config.language,
|
|
158
|
+
port: config.port,
|
|
159
|
+
database: config.database,
|
|
160
|
+
redis: config.redis,
|
|
161
|
+
storage: config.storage,
|
|
162
|
+
authentication: config.authentication,
|
|
163
|
+
template: options.template,
|
|
164
|
+
api: null // Local operation, no API involved
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function createApp(appName, options = {}) {
|
|
169
|
+
try {
|
|
170
|
+
const { appPath } = validateAppNameAndSetup(appName, options);
|
|
171
|
+
await validateAppCreation(appName, options, appPath, getBaseDirForAppType(options.type || 'webapp'));
|
|
172
|
+
|
|
173
|
+
const mergedOptions = await handleTemplateSetup(options);
|
|
174
|
+
const config = await promptForOptions(appName, mergedOptions);
|
|
175
|
+
|
|
176
|
+
const finalAppPath = await prepareFinalAppPath(appName, config, appPath);
|
|
177
|
+
const envConversionMessage = await generateApplicationFiles(finalAppPath, appName, config, options);
|
|
178
|
+
|
|
179
|
+
displaySuccessMessage(appName, config, envConversionMessage, options.app, finalAppPath);
|
|
180
|
+
await logApplicationCreation(appName, config, options);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
throw new Error(`Failed to create application: ${error.message}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Builds a container image for the specified application
|
|
188
|
+
* Auto-detects runtime and generates Dockerfile if needed
|
|
189
|
+
*
|
|
190
|
+
* @async
|
|
191
|
+
* @function buildApp
|
|
192
|
+
* @param {string} appName - Name of the application to build
|
|
193
|
+
* @param {Object} options - Build options
|
|
194
|
+
* @param {string} [options.language] - Override language detection
|
|
195
|
+
* @param {boolean} [options.forceTemplate] - Force rebuild from template
|
|
196
|
+
* @param {string} [options.tag] - Image tag (default: latest)
|
|
197
|
+
* @returns {Promise<string>} Image tag that was built
|
|
198
|
+
* @throws {Error} If build fails or app configuration is invalid
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* const imageTag = await buildApp('myapp', { language: 'typescript' });
|
|
202
|
+
* // Returns: 'myapp:latest'
|
|
203
|
+
*/
|
|
204
|
+
async function buildApp(appName, options = {}) {
|
|
205
|
+
return build.buildApp(appName, options);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Detects the runtime language of an application
|
|
210
|
+
* Analyzes project files to determine TypeScript, Python, etc.
|
|
211
|
+
*
|
|
212
|
+
* @function detectLanguage
|
|
213
|
+
* @param {string} appPath - Path to application directory
|
|
214
|
+
* @returns {string} Detected language ('typescript', 'python', etc.)
|
|
215
|
+
* @throws {Error} If language cannot be detected
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* const language = detectLanguage('./myapp');
|
|
219
|
+
* // Returns: 'typescript'
|
|
220
|
+
*/
|
|
221
|
+
function detectLanguage(appPath) {
|
|
222
|
+
return build.detectLanguage(appPath);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Generates a Dockerfile from template based on detected language
|
|
227
|
+
* Uses Handlebars templates to create optimized Dockerfiles
|
|
228
|
+
*
|
|
229
|
+
* @async
|
|
230
|
+
* @function generateDockerfile
|
|
231
|
+
* @param {string} appPath - Path to application directory
|
|
232
|
+
* @param {string} language - Target language ('typescript', 'python')
|
|
233
|
+
* @param {Object} config - Application configuration from variables.yaml
|
|
234
|
+
* @returns {Promise<string>} Path to generated Dockerfile
|
|
235
|
+
* @throws {Error} If template generation fails
|
|
236
|
+
*
|
|
237
|
+
* @example
|
|
238
|
+
* const dockerfilePath = await generateDockerfile('./myapp', 'typescript', config);
|
|
239
|
+
* // Returns: './myapp/.aifabrix/Dockerfile.typescript'
|
|
240
|
+
*/
|
|
241
|
+
async function generateDockerfile(appPath, language, config) {
|
|
242
|
+
return build.generateDockerfile(appPath, language, config);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Runs the application locally using Docker
|
|
247
|
+
* Starts container with proper port mapping and environment
|
|
248
|
+
*
|
|
249
|
+
* @async
|
|
250
|
+
* @function runApp
|
|
251
|
+
* @param {string} appName - Name of the application to run
|
|
252
|
+
* @param {Object} options - Run options
|
|
253
|
+
* @param {number} [options.port] - Override local port
|
|
254
|
+
* @param {boolean} [options.debug] - Enable debug output
|
|
255
|
+
* @returns {Promise<void>} Resolves when app is running
|
|
256
|
+
* @throws {Error} If run fails or app is not built
|
|
257
|
+
*
|
|
258
|
+
* @example
|
|
259
|
+
* await runApp('myapp', { port: 3001 });
|
|
260
|
+
* // Application is now running on localhost:3001
|
|
261
|
+
*/
|
|
262
|
+
async function runApp(appName, options = {}) {
|
|
263
|
+
return appRun.runApp(appName, options);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Deploys application to controller
|
|
268
|
+
* @async
|
|
269
|
+
* @function deployApp
|
|
270
|
+
* @param {string} appName - Name of the application
|
|
271
|
+
* @param {Object} options - Deployment options
|
|
272
|
+
* @returns {Promise<void>} Resolves when deployment is complete
|
|
273
|
+
*/
|
|
274
|
+
async function deployApp(appName, options = {}) {
|
|
275
|
+
const appDeploy = require('./deploy');
|
|
276
|
+
return appDeploy.deployApp(appName, options);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
module.exports = {
|
|
280
|
+
createApp,
|
|
281
|
+
buildApp,
|
|
282
|
+
runApp,
|
|
283
|
+
downApp,
|
|
284
|
+
detectLanguage,
|
|
285
|
+
generateDockerfile,
|
|
286
|
+
generateDockerfileForApp,
|
|
287
|
+
pushApp,
|
|
288
|
+
deployApp,
|
|
289
|
+
loadTemplateVariables,
|
|
290
|
+
updateTemplateVariables,
|
|
291
|
+
mergeTemplateVariables,
|
|
292
|
+
checkImageExists: appRun.checkImageExists,
|
|
293
|
+
checkContainerRunning: appRun.checkContainerRunning,
|
|
294
|
+
stopAndRemoveContainer: appRun.stopAndRemoveContainer,
|
|
295
|
+
checkPortAvailable: appRun.checkPortAvailable,
|
|
296
|
+
generateDockerCompose: appRun.generateDockerCompose,
|
|
297
|
+
waitForHealthCheck: appRun.waitForHealthCheck
|
|
298
|
+
};
|
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
const chalk = require('chalk');
|
|
12
|
-
const { getConfig, normalizeControllerUrl } = require('
|
|
13
|
-
const { getOrRefreshDeviceToken } = require('
|
|
14
|
-
const { listEnvironmentApplications } = require('
|
|
15
|
-
const { formatApiError } = require('
|
|
16
|
-
const { formatAuthenticationError } = require('
|
|
17
|
-
const logger = require('
|
|
12
|
+
const { getConfig, normalizeControllerUrl } = require('../core/config');
|
|
13
|
+
const { getOrRefreshDeviceToken } = require('../utils/token-manager');
|
|
14
|
+
const { listEnvironmentApplications } = require('../api/environments.api');
|
|
15
|
+
const { formatApiError } = require('../utils/api-error-handler');
|
|
16
|
+
const { formatAuthenticationError } = require('../utils/error-formatters/http-status-errors');
|
|
17
|
+
const logger = require('../utils/logger');
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Extract wrapped array format: { success: true, data: { success: true, data: [...] } }
|
|
@@ -12,8 +12,8 @@ const fs = require('fs').promises;
|
|
|
12
12
|
const path = require('path');
|
|
13
13
|
const chalk = require('chalk');
|
|
14
14
|
const yaml = require('js-yaml');
|
|
15
|
-
const pushUtils = require('
|
|
16
|
-
const logger = require('
|
|
15
|
+
const pushUtils = require('../deployment/push');
|
|
16
|
+
const logger = require('../utils/logger');
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Validate application name format
|
|
@@ -70,7 +70,7 @@ function extractImageName(config, appName) {
|
|
|
70
70
|
*/
|
|
71
71
|
async function loadPushConfig(appName, options) {
|
|
72
72
|
// Detect app type and get correct path (integration or builder)
|
|
73
|
-
const { detectAppType } = require('
|
|
73
|
+
const { detectAppType } = require('../utils/paths');
|
|
74
74
|
const { appPath } = await detectAppType(appName);
|
|
75
75
|
const configPath = path.join(appPath, 'variables.yaml');
|
|
76
76
|
try {
|
|
@@ -183,7 +183,7 @@ function displayPushResults(registry, imageName, tags) {
|
|
|
183
183
|
*/
|
|
184
184
|
async function pushApp(appName, options = {}) {
|
|
185
185
|
// Check if app type is external - skip push
|
|
186
|
-
const { detectAppType } = require('
|
|
186
|
+
const { detectAppType } = require('../utils/paths');
|
|
187
187
|
try {
|
|
188
188
|
const { isExternal } = await detectAppType(appName);
|
|
189
189
|
if (isExternal) {
|
|
@@ -48,7 +48,7 @@ function formatAppDisplayName(appName) {
|
|
|
48
48
|
*/
|
|
49
49
|
function _loadReadmeTemplate() {
|
|
50
50
|
// Use getProjectRoot to reliably find templates in all environments
|
|
51
|
-
const { getProjectRoot } = require('
|
|
51
|
+
const { getProjectRoot } = require('../utils/paths');
|
|
52
52
|
const projectRoot = getProjectRoot();
|
|
53
53
|
const templatePath = path.join(projectRoot, 'templates', 'applications', 'README.md.hbs');
|
|
54
54
|
|
|
@@ -71,32 +71,51 @@ function _loadReadmeTemplate() {
|
|
|
71
71
|
* @param {Object} config - Application configuration
|
|
72
72
|
* @returns {string} README.md content
|
|
73
73
|
*/
|
|
74
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Extracts service flags from config
|
|
76
|
+
* @function extractServiceFlags
|
|
77
|
+
* @param {Object} config - Application configuration
|
|
78
|
+
* @returns {Object} Service flags object
|
|
79
|
+
*/
|
|
80
|
+
function extractServiceFlags(config) {
|
|
81
|
+
return {
|
|
82
|
+
hasDatabase: config.database || config.requires?.database || false,
|
|
83
|
+
hasRedis: config.redis || config.requires?.redis || false,
|
|
84
|
+
hasStorage: config.storage || config.requires?.storage || false,
|
|
85
|
+
hasAuthentication: config.authentication || false
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Builds template context for README generation
|
|
91
|
+
* @function buildReadmeContext
|
|
92
|
+
* @param {string} appName - Application name
|
|
93
|
+
* @param {Object} config - Application configuration
|
|
94
|
+
* @returns {Object} Template context
|
|
95
|
+
*/
|
|
96
|
+
function buildReadmeContext(appName, config) {
|
|
75
97
|
const displayName = formatAppDisplayName(appName);
|
|
76
98
|
const imageName = `aifabrix/${appName}`;
|
|
77
99
|
const port = config.port || 3000;
|
|
78
100
|
// Extract registry from nested structure (config.image.registry) or flattened (config.registry)
|
|
79
101
|
const registry = config.image?.registry || config.registry || 'myacr.azurecr.io';
|
|
80
102
|
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
const hasStorage = config.storage || config.requires?.storage || false;
|
|
84
|
-
const hasAuthentication = config.authentication || false;
|
|
85
|
-
const hasAnyService = hasDatabase || hasRedis || hasStorage || hasAuthentication;
|
|
103
|
+
const serviceFlags = extractServiceFlags(config);
|
|
104
|
+
const hasAnyService = serviceFlags.hasDatabase || serviceFlags.hasRedis || serviceFlags.hasStorage || serviceFlags.hasAuthentication;
|
|
86
105
|
|
|
87
|
-
|
|
106
|
+
return {
|
|
88
107
|
appName,
|
|
89
108
|
displayName,
|
|
90
109
|
imageName,
|
|
91
110
|
port,
|
|
92
111
|
registry,
|
|
93
|
-
|
|
94
|
-
hasRedis,
|
|
95
|
-
hasStorage,
|
|
96
|
-
hasAuthentication,
|
|
112
|
+
...serviceFlags,
|
|
97
113
|
hasAnyService
|
|
98
114
|
};
|
|
115
|
+
}
|
|
99
116
|
|
|
117
|
+
function generateReadmeMd(appName, config) {
|
|
118
|
+
const context = buildReadmeContext(appName, config);
|
|
100
119
|
// Always generate comprehensive README programmatically to ensure consistency
|
|
101
120
|
// regardless of template file content
|
|
102
121
|
return generateComprehensiveReadme(context);
|
|
@@ -220,10 +239,12 @@ For more information, see the [AI Fabrix Builder documentation](https://docs.aif
|
|
|
220
239
|
* @throws {Error} If file generation fails
|
|
221
240
|
*/
|
|
222
241
|
async function generateReadmeMdFile(appPath, appName, config) {
|
|
242
|
+
// Ensure directory exists
|
|
243
|
+
await fs.mkdir(appPath, { recursive: true });
|
|
223
244
|
const readmePath = path.join(appPath, 'README.md');
|
|
224
245
|
if (!(await fileExists(readmePath))) {
|
|
225
246
|
const readmeContent = generateReadmeMd(appName, config);
|
|
226
|
-
await fs.writeFile(readmePath, readmeContent);
|
|
247
|
+
await fs.writeFile(readmePath, readmeContent, 'utf8');
|
|
227
248
|
}
|
|
228
249
|
}
|
|
229
250
|
|
|
@@ -9,19 +9,19 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
const chalk = require('chalk');
|
|
12
|
-
const logger = require('
|
|
13
|
-
const { saveLocalSecret, isLocalhost } = require('
|
|
14
|
-
const { updateEnvTemplate } = require('
|
|
15
|
-
const { generateEnvFile } = require('
|
|
16
|
-
const { registerApplicationSchema, validateAppRegistrationData } = require('
|
|
12
|
+
const logger = require('../utils/logger');
|
|
13
|
+
const { saveLocalSecret, isLocalhost } = require('../utils/local-secrets');
|
|
14
|
+
const { updateEnvTemplate } = require('../utils/env-template');
|
|
15
|
+
const { generateEnvFile } = require('../core/secrets');
|
|
16
|
+
const { registerApplicationSchema, validateAppRegistrationData } = require('../utils/app-register-validator');
|
|
17
17
|
const {
|
|
18
18
|
loadVariablesYaml,
|
|
19
19
|
createMinimalAppIfNeeded,
|
|
20
20
|
extractAppConfiguration
|
|
21
|
-
} = require('
|
|
22
|
-
const { checkAuthentication } = require('
|
|
23
|
-
const { callRegisterApi } = require('
|
|
24
|
-
const { displayRegistrationResults, getEnvironmentPrefix } = require('
|
|
21
|
+
} = require('../utils/app-register-config');
|
|
22
|
+
const { checkAuthentication } = require('../utils/app-register-auth');
|
|
23
|
+
const { callRegisterApi } = require('../utils/app-register-api');
|
|
24
|
+
const { displayRegistrationResults, getEnvironmentPrefix } = require('../utils/app-register-display');
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Build registration data payload from app configuration
|
|
@@ -9,16 +9,16 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
const chalk = require('chalk');
|
|
12
|
-
const { getConfig, normalizeControllerUrl } = require('
|
|
13
|
-
const { getOrRefreshDeviceToken } = require('
|
|
14
|
-
const { rotateApplicationSecret } = require('
|
|
15
|
-
const { formatApiError } = require('
|
|
16
|
-
const { formatAuthenticationError } = require('
|
|
17
|
-
const logger = require('
|
|
18
|
-
const { saveLocalSecret, isLocalhost } = require('
|
|
19
|
-
const { updateEnvTemplate } = require('
|
|
20
|
-
const { getEnvironmentPrefix } = require('./
|
|
21
|
-
const { generateEnvFile } = require('
|
|
12
|
+
const { getConfig, normalizeControllerUrl } = require('../core/config');
|
|
13
|
+
const { getOrRefreshDeviceToken } = require('../utils/token-manager');
|
|
14
|
+
const { rotateApplicationSecret } = require('../api/applications.api');
|
|
15
|
+
const { formatApiError } = require('../utils/api-error-handler');
|
|
16
|
+
const { formatAuthenticationError } = require('../utils/error-formatters/http-status-errors');
|
|
17
|
+
const logger = require('../utils/logger');
|
|
18
|
+
const { saveLocalSecret, isLocalhost } = require('../utils/local-secrets');
|
|
19
|
+
const { updateEnvTemplate } = require('../utils/env-template');
|
|
20
|
+
const { getEnvironmentPrefix } = require('./register');
|
|
21
|
+
const { generateEnvFile } = require('../core/secrets');
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Find device token from config by trying each stored URL
|
|
@@ -61,20 +61,83 @@ function validateEnvironment(environment) {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Validate credentials object structure
|
|
66
|
+
* @param {Object} credentials - Credentials object to validate
|
|
67
|
+
* @returns {boolean} True if credentials are valid
|
|
68
|
+
*/
|
|
69
|
+
function isValidCredentials(credentials) {
|
|
70
|
+
return credentials &&
|
|
71
|
+
typeof credentials === 'object' &&
|
|
72
|
+
typeof credentials.clientId === 'string' &&
|
|
73
|
+
typeof credentials.clientSecret === 'string';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Extract credentials from API response
|
|
78
|
+
* Handles multiple response formats:
|
|
79
|
+
* 1. Direct format: { success: true, data: { credentials: {...} } }
|
|
80
|
+
* 2. Wrapped format: { success: true, data: { success: true, data: { credentials: {...} } } }
|
|
81
|
+
* @param {Object} response - API response from centralized API client
|
|
82
|
+
* @returns {Object|null} Object with credentials and message, or null if not found
|
|
83
|
+
*/
|
|
84
|
+
function extractCredentials(response) {
|
|
85
|
+
// Note: response.data is already validated in validateResponse
|
|
86
|
+
const apiResponse = response.data;
|
|
87
|
+
|
|
88
|
+
// Try wrapped format first: response.data.data.credentials
|
|
89
|
+
if (apiResponse.data && apiResponse.data.credentials) {
|
|
90
|
+
const credentials = apiResponse.data.credentials;
|
|
91
|
+
if (isValidCredentials(credentials)) {
|
|
92
|
+
return {
|
|
93
|
+
credentials: credentials,
|
|
94
|
+
message: apiResponse.data.message || apiResponse.message
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Try direct format: response.data.credentials
|
|
100
|
+
if (apiResponse.credentials) {
|
|
101
|
+
const credentials = apiResponse.credentials;
|
|
102
|
+
if (isValidCredentials(credentials)) {
|
|
103
|
+
return {
|
|
104
|
+
credentials: credentials,
|
|
105
|
+
message: apiResponse.message
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
64
113
|
/**
|
|
65
114
|
* Validate API response structure
|
|
66
115
|
* @param {Object} response - API response
|
|
116
|
+
* @returns {Object} Object with credentials and message
|
|
67
117
|
* @throws {Error} If response structure is invalid
|
|
68
118
|
*/
|
|
69
119
|
function validateResponse(response) {
|
|
70
120
|
if (!response.data || typeof response.data !== 'object') {
|
|
121
|
+
logger.error(chalk.red('❌ Invalid response: missing data'));
|
|
122
|
+
logger.error(chalk.gray('\nAPI response type:'), typeof response.data);
|
|
123
|
+
logger.error(chalk.gray('API response:'), JSON.stringify(response.data, null, 2));
|
|
124
|
+
logger.error(chalk.gray('\nFull response for debugging:'));
|
|
125
|
+
logger.error(chalk.gray(JSON.stringify(response, null, 2)));
|
|
71
126
|
throw new Error('Invalid response: missing data');
|
|
72
127
|
}
|
|
73
128
|
|
|
74
|
-
const
|
|
75
|
-
|
|
129
|
+
const result = extractCredentials(response);
|
|
130
|
+
|
|
131
|
+
if (!result) {
|
|
132
|
+
logger.error(chalk.red('❌ Invalid response: missing or invalid credentials'));
|
|
133
|
+
logger.error(chalk.gray('\nAPI response type:'), typeof response.data);
|
|
134
|
+
logger.error(chalk.gray('API response:'), JSON.stringify(response.data, null, 2));
|
|
135
|
+
logger.error(chalk.gray('\nFull response for debugging:'));
|
|
136
|
+
logger.error(chalk.gray(JSON.stringify(response, null, 2)));
|
|
76
137
|
throw new Error('Invalid response: missing or invalid credentials');
|
|
77
138
|
}
|
|
139
|
+
|
|
140
|
+
return result;
|
|
78
141
|
}
|
|
79
142
|
|
|
80
143
|
/**
|
|
@@ -108,6 +171,47 @@ function displayRotationResults(appKey, environment, credentials, apiUrl, messag
|
|
|
108
171
|
}
|
|
109
172
|
}
|
|
110
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Get device token from provided controller URL
|
|
176
|
+
* @async
|
|
177
|
+
* @param {string} controllerUrl - Controller URL
|
|
178
|
+
* @returns {Promise<Object|null>} Object with token and controllerUrl, or null if failed
|
|
179
|
+
*/
|
|
180
|
+
async function getTokenFromUrl(controllerUrl) {
|
|
181
|
+
try {
|
|
182
|
+
const normalizedUrl = normalizeControllerUrl(controllerUrl);
|
|
183
|
+
const deviceToken = await getOrRefreshDeviceToken(normalizedUrl);
|
|
184
|
+
if (deviceToken && deviceToken.token) {
|
|
185
|
+
return {
|
|
186
|
+
token: deviceToken.token,
|
|
187
|
+
controllerUrl: deviceToken.controller || normalizedUrl
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
} catch (error) {
|
|
191
|
+
logger.error(chalk.red(`❌ Failed to authenticate with controller: ${controllerUrl}`));
|
|
192
|
+
logger.error(chalk.gray(`Error: ${error.message}`));
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Validate and handle missing authentication token
|
|
200
|
+
* @param {string|null} token - Authentication token
|
|
201
|
+
* @param {string|null} controllerUrl - Controller URL
|
|
202
|
+
* @param {string} [providedUrl] - Original provided URL for error context
|
|
203
|
+
*/
|
|
204
|
+
function validateAuthToken(token, controllerUrl, providedUrl) {
|
|
205
|
+
if (!token || !controllerUrl) {
|
|
206
|
+
const formattedError = formatAuthenticationError({
|
|
207
|
+
controllerUrl: providedUrl || undefined,
|
|
208
|
+
message: 'No valid authentication found'
|
|
209
|
+
});
|
|
210
|
+
logger.error(formattedError);
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
111
215
|
/**
|
|
112
216
|
* Get authentication token for rotation
|
|
113
217
|
* @async
|
|
@@ -122,17 +226,10 @@ async function getRotationAuthToken(controllerUrl, config) {
|
|
|
122
226
|
|
|
123
227
|
// If controller URL provided, try to get device token
|
|
124
228
|
if (controllerUrl) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
token = deviceToken.token;
|
|
130
|
-
actualControllerUrl = deviceToken.controller || normalizedUrl;
|
|
131
|
-
}
|
|
132
|
-
} catch (error) {
|
|
133
|
-
logger.error(chalk.red(`❌ Failed to authenticate with controller: ${controllerUrl}`));
|
|
134
|
-
logger.error(chalk.gray(`Error: ${error.message}`));
|
|
135
|
-
process.exit(1);
|
|
229
|
+
const tokenResult = await getTokenFromUrl(controllerUrl);
|
|
230
|
+
if (tokenResult) {
|
|
231
|
+
token = tokenResult.token;
|
|
232
|
+
actualControllerUrl = tokenResult.controllerUrl;
|
|
136
233
|
}
|
|
137
234
|
}
|
|
138
235
|
|
|
@@ -145,15 +242,7 @@ async function getRotationAuthToken(controllerUrl, config) {
|
|
|
145
242
|
}
|
|
146
243
|
}
|
|
147
244
|
|
|
148
|
-
|
|
149
|
-
const formattedError = formatAuthenticationError({
|
|
150
|
-
controllerUrl: controllerUrl || undefined,
|
|
151
|
-
message: 'No valid authentication found'
|
|
152
|
-
});
|
|
153
|
-
logger.error(formattedError);
|
|
154
|
-
process.exit(1);
|
|
155
|
-
}
|
|
156
|
-
|
|
245
|
+
validateAuthToken(token, actualControllerUrl, controllerUrl);
|
|
157
246
|
return { token, actualControllerUrl };
|
|
158
247
|
}
|
|
159
248
|
|
|
@@ -228,11 +317,8 @@ async function rotateSecret(appKey, options) {
|
|
|
228
317
|
process.exit(1);
|
|
229
318
|
}
|
|
230
319
|
|
|
231
|
-
// Validate response structure
|
|
232
|
-
validateResponse(response);
|
|
233
|
-
|
|
234
|
-
const credentials = response.data.credentials;
|
|
235
|
-
const message = response.data.message;
|
|
320
|
+
// Validate response structure and extract credentials
|
|
321
|
+
const { credentials, message } = validateResponse(response);
|
|
236
322
|
|
|
237
323
|
// Save credentials locally
|
|
238
324
|
await saveCredentialsLocally(appKey, credentials, actualControllerUrl);
|