@aifabrix/builder 2.22.1 ā 2.31.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/jest.config.coverage.js +37 -0
- package/lib/api/pipeline.api.js +10 -9
- package/lib/app-deploy.js +36 -14
- package/lib/app-list.js +191 -71
- package/lib/app-prompts.js +77 -26
- package/lib/app-readme.js +123 -5
- package/lib/app-rotate-secret.js +101 -57
- package/lib/app-run-helpers.js +200 -172
- package/lib/app-run.js +137 -68
- package/lib/audit-logger.js +8 -7
- package/lib/build.js +161 -250
- package/lib/cli.js +73 -65
- package/lib/commands/login.js +45 -31
- package/lib/commands/logout.js +181 -0
- package/lib/commands/secrets-set.js +2 -2
- package/lib/commands/secure.js +61 -26
- package/lib/config.js +79 -45
- package/lib/datasource-deploy.js +89 -29
- package/lib/deployer.js +164 -129
- package/lib/diff.js +63 -21
- package/lib/environment-deploy.js +36 -19
- package/lib/external-system-deploy.js +134 -66
- package/lib/external-system-download.js +244 -171
- package/lib/external-system-test.js +199 -164
- package/lib/generator-external.js +145 -72
- package/lib/generator-helpers.js +49 -17
- package/lib/generator-split.js +105 -58
- package/lib/infra.js +101 -131
- package/lib/schema/application-schema.json +895 -896
- package/lib/schema/env-config.yaml +11 -4
- package/lib/template-validator.js +13 -4
- package/lib/utils/api.js +8 -8
- package/lib/utils/app-register-auth.js +36 -18
- package/lib/utils/app-run-containers.js +140 -0
- package/lib/utils/auth-headers.js +6 -6
- package/lib/utils/build-copy.js +60 -2
- package/lib/utils/build-helpers.js +94 -0
- package/lib/utils/cli-utils.js +177 -76
- package/lib/utils/compose-generator.js +12 -2
- package/lib/utils/config-tokens.js +151 -9
- package/lib/utils/deployment-errors.js +137 -69
- package/lib/utils/deployment-validation-helpers.js +103 -0
- package/lib/utils/docker-build.js +57 -0
- package/lib/utils/dockerfile-utils.js +13 -3
- package/lib/utils/env-copy.js +163 -94
- package/lib/utils/env-map.js +226 -86
- package/lib/utils/environment-checker.js +2 -2
- package/lib/utils/error-formatters/network-errors.js +0 -1
- package/lib/utils/external-system-display.js +14 -19
- package/lib/utils/external-system-env-helpers.js +107 -0
- package/lib/utils/external-system-test-helpers.js +144 -0
- package/lib/utils/health-check.js +10 -8
- package/lib/utils/infra-status.js +123 -0
- package/lib/utils/local-secrets.js +3 -2
- package/lib/utils/paths.js +228 -49
- package/lib/utils/schema-loader.js +125 -57
- package/lib/utils/token-manager.js +10 -7
- package/lib/utils/yaml-preserve.js +55 -16
- package/lib/validate.js +87 -89
- package/package.json +4 -4
- package/scripts/ci-fix.sh +19 -0
- package/scripts/ci-simulate.sh +19 -0
- package/templates/applications/miso-controller/test.yaml +1 -0
- package/templates/python/Dockerfile.hbs +8 -45
- package/templates/typescript/Dockerfile.hbs +8 -42
|
@@ -126,6 +126,39 @@ async function sendEnvironmentDeployment(controllerUrl, envKey, authConfig, opti
|
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Process environment status response
|
|
131
|
+
* @param {Object} response - API response
|
|
132
|
+
* @param {string} validatedEnvKey - Validated environment key
|
|
133
|
+
* @returns {Object|null} Status result if ready, null if needs to continue polling
|
|
134
|
+
* @throws {Error} If deployment failed
|
|
135
|
+
*/
|
|
136
|
+
function processEnvironmentStatusResponse(response, validatedEnvKey) {
|
|
137
|
+
if (!response.success || !response.data) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const responseData = response.data.data || response.data;
|
|
142
|
+
const status = responseData.status || responseData.ready;
|
|
143
|
+
const isReady = status === 'ready' || status === 'completed' || responseData.ready === true;
|
|
144
|
+
|
|
145
|
+
if (isReady) {
|
|
146
|
+
return {
|
|
147
|
+
success: true,
|
|
148
|
+
environment: validatedEnvKey,
|
|
149
|
+
status: 'ready',
|
|
150
|
+
message: 'Environment is ready for application deployments'
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Check for terminal failure states
|
|
155
|
+
if (status === 'failed' || status === 'error') {
|
|
156
|
+
throw new Error(`Environment deployment failed: ${responseData.message || 'Unknown error'}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
129
162
|
/**
|
|
130
163
|
* Polls environment deployment status
|
|
131
164
|
* @async
|
|
@@ -153,25 +186,9 @@ async function pollEnvironmentStatus(deploymentId, controllerUrl, envKey, authCo
|
|
|
153
186
|
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
154
187
|
|
|
155
188
|
const response = await getEnvironmentStatus(validatedUrl, validatedEnvKey, apiAuthConfig);
|
|
156
|
-
|
|
157
|
-
if (
|
|
158
|
-
|
|
159
|
-
const status = responseData.status || responseData.ready;
|
|
160
|
-
const isReady = status === 'ready' || status === 'completed' || responseData.ready === true;
|
|
161
|
-
|
|
162
|
-
if (isReady) {
|
|
163
|
-
return {
|
|
164
|
-
success: true,
|
|
165
|
-
environment: validatedEnvKey,
|
|
166
|
-
status: 'ready',
|
|
167
|
-
message: 'Environment is ready for application deployments'
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Check for terminal failure states
|
|
172
|
-
if (status === 'failed' || status === 'error') {
|
|
173
|
-
throw new Error(`Environment deployment failed: ${responseData.message || 'Unknown error'}`);
|
|
174
|
-
}
|
|
189
|
+
const statusResult = processEnvironmentStatusResponse(response, validatedEnvKey);
|
|
190
|
+
if (statusResult) {
|
|
191
|
+
return statusResult;
|
|
175
192
|
}
|
|
176
193
|
} catch (error) {
|
|
177
194
|
// If it's a terminal error (not a timeout), throw it
|
|
@@ -194,6 +194,132 @@ async function buildExternalSystem(appName, options = {}) {
|
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
+
/**
|
|
198
|
+
* Validate deployment prerequisites
|
|
199
|
+
* @async
|
|
200
|
+
* @param {string} appName - Application name
|
|
201
|
+
* @returns {Promise<Object>} Validation result with systemFiles, datasourceFiles, and systemKey
|
|
202
|
+
*/
|
|
203
|
+
async function validateDeploymentPrerequisites(appName) {
|
|
204
|
+
const { systemFiles: _systemFiles, datasourceFiles, systemKey } = await validateExternalSystemFiles(appName);
|
|
205
|
+
return { systemFiles: _systemFiles, datasourceFiles, systemKey };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Prepare deployment files and get authentication
|
|
210
|
+
* @async
|
|
211
|
+
* @param {string} appName - Application name
|
|
212
|
+
* @param {Object} options - Deployment options
|
|
213
|
+
* @returns {Promise<Object>} Object with applicationSchema, authConfig, controllerUrl, environment, and systemKey
|
|
214
|
+
*/
|
|
215
|
+
async function prepareDeploymentFiles(appName, options) {
|
|
216
|
+
logger.log(chalk.blue('š Generating application schema...'));
|
|
217
|
+
const applicationSchema = await generateExternalSystemApplicationSchema(appName);
|
|
218
|
+
logger.log(chalk.green('ā Application schema generated'));
|
|
219
|
+
|
|
220
|
+
const config = await getConfig();
|
|
221
|
+
const environment = options.environment || 'dev';
|
|
222
|
+
const controllerUrl = options.controller || config.deployment?.controllerUrl || 'http://localhost:3000';
|
|
223
|
+
const authConfig = await getDeploymentAuth(controllerUrl, environment, appName);
|
|
224
|
+
|
|
225
|
+
if (!authConfig.token && !authConfig.clientId) {
|
|
226
|
+
throw new Error('Authentication required. Run "aifabrix login" or "aifabrix app register" first.');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const { systemKey } = await validateDeploymentPrerequisites(appName);
|
|
230
|
+
|
|
231
|
+
return { applicationSchema, authConfig, controllerUrl, environment, systemKey };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Upload application and get upload ID
|
|
236
|
+
* @async
|
|
237
|
+
* @param {string} dataplaneUrl - Dataplane URL
|
|
238
|
+
* @param {Object} authConfig - Authentication configuration
|
|
239
|
+
* @param {Object} applicationSchema - Application schema
|
|
240
|
+
* @returns {Promise<string>} Upload ID
|
|
241
|
+
* @throws {Error} If upload fails
|
|
242
|
+
*/
|
|
243
|
+
async function uploadApplication(dataplaneUrl, authConfig, applicationSchema) {
|
|
244
|
+
logger.log(chalk.blue('š¤ Uploading application configuration...'));
|
|
245
|
+
const uploadResponse = await uploadApplicationViaPipeline(dataplaneUrl, authConfig, applicationSchema);
|
|
246
|
+
|
|
247
|
+
if (!uploadResponse.success || !uploadResponse.data) {
|
|
248
|
+
throw new Error(`Failed to upload application: ${uploadResponse.error || uploadResponse.formattedError || 'Unknown error'}`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const uploadData = uploadResponse.data.data || uploadResponse.data;
|
|
252
|
+
const uploadId = uploadData.uploadId || uploadData.id;
|
|
253
|
+
|
|
254
|
+
if (!uploadId) {
|
|
255
|
+
throw new Error('Upload ID not found in upload response');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
logger.log(chalk.green(`ā Upload successful (ID: ${uploadId})`));
|
|
259
|
+
return uploadId;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Validate upload and display changes
|
|
264
|
+
* @async
|
|
265
|
+
* @param {string} dataplaneUrl - Dataplane URL
|
|
266
|
+
* @param {string} uploadId - Upload ID
|
|
267
|
+
* @param {Object} authConfig - Authentication configuration
|
|
268
|
+
* @returns {Promise<void>}
|
|
269
|
+
* @throws {Error} If validation fails
|
|
270
|
+
*/
|
|
271
|
+
async function validateUpload(dataplaneUrl, uploadId, authConfig) {
|
|
272
|
+
logger.log(chalk.blue('š Validating upload...'));
|
|
273
|
+
const validateResponse = await validateUploadViaPipeline(dataplaneUrl, uploadId, authConfig);
|
|
274
|
+
|
|
275
|
+
if (!validateResponse.success || !validateResponse.data) {
|
|
276
|
+
throw new Error(`Validation failed: ${validateResponse.error || validateResponse.formattedError || 'Unknown error'}`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const validateData = validateResponse.data.data || validateResponse.data;
|
|
280
|
+
|
|
281
|
+
// Display changes
|
|
282
|
+
if (validateData.changes && validateData.changes.length > 0) {
|
|
283
|
+
logger.log(chalk.blue('\nš Changes to be published:'));
|
|
284
|
+
for (const change of validateData.changes) {
|
|
285
|
+
const changeType = change.type || 'unknown';
|
|
286
|
+
const changeEntity = change.entity || change.key || 'unknown';
|
|
287
|
+
const emoji = changeType === 'new' ? 'ā' : changeType === 'modified' ? 'āļø' : 'šļø';
|
|
288
|
+
logger.log(chalk.gray(` ${emoji} ${changeType}: ${changeEntity}`));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (validateData.summary) {
|
|
293
|
+
logger.log(chalk.blue(`\nš Summary: ${validateData.summary}`));
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
logger.log(chalk.green('ā Validation successful'));
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Publish application
|
|
301
|
+
* @async
|
|
302
|
+
* @param {string} dataplaneUrl - Dataplane URL
|
|
303
|
+
* @param {string} uploadId - Upload ID
|
|
304
|
+
* @param {Object} authConfig - Authentication configuration
|
|
305
|
+
* @param {Object} options - Publish options
|
|
306
|
+
* @param {boolean} [options.generateMcpContract] - Generate MCP contract (default: true)
|
|
307
|
+
* @returns {Promise<Object>} Publish response data
|
|
308
|
+
* @throws {Error} If publish fails
|
|
309
|
+
*/
|
|
310
|
+
async function publishApplication(dataplaneUrl, uploadId, authConfig, options) {
|
|
311
|
+
const generateMcpContract = options.generateMcpContract !== false; // Default to true
|
|
312
|
+
logger.log(chalk.blue(`š¢ Publishing application (MCP contract: ${generateMcpContract ? 'enabled' : 'disabled'})...`));
|
|
313
|
+
|
|
314
|
+
const publishResponse = await publishUploadViaPipeline(dataplaneUrl, uploadId, authConfig, { generateMcpContract });
|
|
315
|
+
|
|
316
|
+
if (!publishResponse.success || !publishResponse.data) {
|
|
317
|
+
throw new Error(`Failed to publish application: ${publishResponse.error || publishResponse.formattedError || 'Unknown error'}`);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return publishResponse.data.data || publishResponse.data;
|
|
321
|
+
}
|
|
322
|
+
|
|
197
323
|
/**
|
|
198
324
|
* Publishes external system to dataplane using application-level workflow
|
|
199
325
|
* Uses upload ā validate ā publish workflow for atomic deployment
|
|
@@ -212,23 +338,11 @@ async function deployExternalSystem(appName, options = {}) {
|
|
|
212
338
|
try {
|
|
213
339
|
logger.log(chalk.blue(`\nš Publishing external system: ${appName}`));
|
|
214
340
|
|
|
215
|
-
// Validate
|
|
216
|
-
const {
|
|
217
|
-
|
|
218
|
-
// Generate application-schema.json structure
|
|
219
|
-
logger.log(chalk.blue('š Generating application schema...'));
|
|
220
|
-
const applicationSchema = await generateExternalSystemApplicationSchema(appName);
|
|
221
|
-
logger.log(chalk.green('ā Application schema generated'));
|
|
341
|
+
// Validate prerequisites
|
|
342
|
+
const { datasourceFiles } = await validateDeploymentPrerequisites(appName);
|
|
222
343
|
|
|
223
|
-
//
|
|
224
|
-
const
|
|
225
|
-
const environment = options.environment || 'dev';
|
|
226
|
-
const controllerUrl = options.controller || config.deployment?.controllerUrl || 'http://localhost:3000';
|
|
227
|
-
const authConfig = await getDeploymentAuth(controllerUrl, environment, appName);
|
|
228
|
-
|
|
229
|
-
if (!authConfig.token && !authConfig.clientId) {
|
|
230
|
-
throw new Error('Authentication required. Run "aifabrix login" or "aifabrix app register" first.');
|
|
231
|
-
}
|
|
344
|
+
// Prepare deployment files and get authentication
|
|
345
|
+
const { applicationSchema, authConfig, controllerUrl, environment, systemKey } = await prepareDeploymentFiles(appName, options);
|
|
232
346
|
|
|
233
347
|
// Get dataplane URL from controller
|
|
234
348
|
logger.log(chalk.blue('š Getting dataplane URL from controller...'));
|
|
@@ -236,65 +350,19 @@ async function deployExternalSystem(appName, options = {}) {
|
|
|
236
350
|
logger.log(chalk.green(`ā Dataplane URL: ${dataplaneUrl}`));
|
|
237
351
|
|
|
238
352
|
// Step 1: Upload application
|
|
239
|
-
|
|
240
|
-
const uploadResponse = await uploadApplicationViaPipeline(dataplaneUrl, authConfig, applicationSchema);
|
|
241
|
-
|
|
242
|
-
if (!uploadResponse.success || !uploadResponse.data) {
|
|
243
|
-
throw new Error(`Failed to upload application: ${uploadResponse.error || uploadResponse.formattedError || 'Unknown error'}`);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
const uploadData = uploadResponse.data.data || uploadResponse.data;
|
|
247
|
-
const uploadId = uploadData.uploadId || uploadData.id;
|
|
248
|
-
|
|
249
|
-
if (!uploadId) {
|
|
250
|
-
throw new Error('Upload ID not found in upload response');
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
logger.log(chalk.green(`ā Upload successful (ID: ${uploadId})`));
|
|
353
|
+
const uploadId = await uploadApplication(dataplaneUrl, authConfig, applicationSchema);
|
|
254
354
|
|
|
255
355
|
// Step 2: Validate upload (optional, can be skipped)
|
|
256
356
|
if (!options.skipValidation) {
|
|
257
|
-
|
|
258
|
-
const validateResponse = await validateUploadViaPipeline(dataplaneUrl, uploadId, authConfig);
|
|
259
|
-
|
|
260
|
-
if (!validateResponse.success || !validateResponse.data) {
|
|
261
|
-
throw new Error(`Validation failed: ${validateResponse.error || validateResponse.formattedError || 'Unknown error'}`);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
const validateData = validateResponse.data.data || validateResponse.data;
|
|
265
|
-
|
|
266
|
-
// Display changes
|
|
267
|
-
if (validateData.changes && validateData.changes.length > 0) {
|
|
268
|
-
logger.log(chalk.blue('\nš Changes to be published:'));
|
|
269
|
-
for (const change of validateData.changes) {
|
|
270
|
-
const changeType = change.type || 'unknown';
|
|
271
|
-
const changeEntity = change.entity || change.key || 'unknown';
|
|
272
|
-
const emoji = changeType === 'new' ? 'ā' : changeType === 'modified' ? 'āļø' : 'šļø';
|
|
273
|
-
logger.log(chalk.gray(` ${emoji} ${changeType}: ${changeEntity}`));
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
if (validateData.summary) {
|
|
278
|
-
logger.log(chalk.blue(`\nš Summary: ${validateData.summary}`));
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
logger.log(chalk.green('ā Validation successful'));
|
|
357
|
+
await validateUpload(dataplaneUrl, uploadId, authConfig);
|
|
282
358
|
} else {
|
|
283
359
|
logger.log(chalk.yellow('ā Skipping validation step'));
|
|
284
360
|
}
|
|
285
361
|
|
|
286
362
|
// Step 3: Publish application
|
|
287
|
-
const
|
|
288
|
-
logger.log(chalk.blue(`š¢ Publishing application (MCP contract: ${generateMcpContract ? 'enabled' : 'disabled'})...`));
|
|
289
|
-
|
|
290
|
-
const publishResponse = await publishUploadViaPipeline(dataplaneUrl, uploadId, authConfig, { generateMcpContract });
|
|
291
|
-
|
|
292
|
-
if (!publishResponse.success || !publishResponse.data) {
|
|
293
|
-
throw new Error(`Failed to publish application: ${publishResponse.error || publishResponse.formattedError || 'Unknown error'}`);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
const publishData = publishResponse.data.data || publishResponse.data;
|
|
363
|
+
const publishData = await publishApplication(dataplaneUrl, uploadId, authConfig, options);
|
|
297
364
|
|
|
365
|
+
// Display success summary
|
|
298
366
|
logger.log(chalk.green('\nā
External system published successfully!'));
|
|
299
367
|
logger.log(chalk.blue(`System: ${systemKey}`));
|
|
300
368
|
if (publishData.systems && publishData.systems.length > 0) {
|