@aifabrix/builder 2.36.1 → 2.37.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/README.md +68 -104
- package/integration/hubspot/test.js +1 -1
- package/lib/api/pipeline.api.js +37 -6
- package/lib/app/display.js +1 -1
- package/lib/app/list.js +1 -1
- package/lib/app/run-helpers.js +1 -1
- package/lib/cli/index.js +45 -0
- package/lib/cli/setup-app.js +229 -0
- package/lib/cli/setup-auth.js +88 -0
- package/lib/cli/setup-dev.js +101 -0
- package/lib/cli/setup-environment.js +53 -0
- package/lib/cli/setup-external-system.js +86 -0
- package/lib/cli/setup-infra.js +219 -0
- package/lib/cli/setup-secrets.js +48 -0
- package/lib/cli/setup-utility.js +202 -0
- package/lib/cli.js +7 -961
- package/lib/commands/auth-status.js +39 -9
- package/lib/commands/up-miso.js +1 -1
- package/lib/core/config.js +10 -0
- package/lib/core/ensure-encryption-key.js +56 -0
- package/lib/generator/wizard.js +19 -34
- package/lib/infrastructure/helpers.js +1 -1
- package/lib/schema/external-system.schema.json +24 -1
- package/lib/utils/help-builder.js +5 -2
- package/lib/utils/token-manager.js +2 -3
- package/package.json +1 -1
- package/templates/applications/README.md.hbs +3 -3
- package/templates/applications/dataplane/variables.yaml +0 -2
- package/templates/applications/miso-controller/variables.yaml +0 -2
- package/templates/external-system/deploy.js.hbs +58 -0
|
@@ -197,15 +197,15 @@ function displayTokenInfo(tokenInfo) {
|
|
|
197
197
|
const statusIcon = tokenInfo.authenticated ? chalk.green('✓') : chalk.red('✗');
|
|
198
198
|
const statusText = tokenInfo.authenticated ? 'Authenticated' : 'Not authenticated';
|
|
199
199
|
|
|
200
|
-
logger.log(`Status: ${statusIcon} ${statusText}`);
|
|
201
|
-
logger.log(`Token Type: ${chalk.cyan(tokenInfo.type)}`);
|
|
200
|
+
logger.log(` Status: ${statusIcon} ${statusText}`);
|
|
201
|
+
logger.log(` Token Type: ${chalk.cyan(tokenInfo.type)}`);
|
|
202
202
|
|
|
203
203
|
if (tokenInfo.appName) {
|
|
204
|
-
logger.log(`Application: ${chalk.cyan(tokenInfo.appName)}`);
|
|
204
|
+
logger.log(` Application: ${chalk.cyan(tokenInfo.appName)}`);
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
if (tokenInfo.expiresAt) {
|
|
208
|
-
logger.log(`Expires: ${chalk.gray(formatExpiration(tokenInfo.expiresAt))}`);
|
|
208
|
+
logger.log(` Expires: ${chalk.gray(formatExpiration(tokenInfo.expiresAt))}`);
|
|
209
209
|
}
|
|
210
210
|
|
|
211
211
|
if (tokenInfo.error) {
|
|
@@ -241,14 +241,43 @@ async function resolveDataplaneUrlSilent(controllerUrl, environment, authConfig)
|
|
|
241
241
|
* @param {boolean} dataplaneConnected - Whether dataplane health check passed
|
|
242
242
|
*/
|
|
243
243
|
function displayDataplaneSection(dataplaneUrl, dataplaneConnected) {
|
|
244
|
+
logger.log('');
|
|
244
245
|
if (dataplaneUrl) {
|
|
245
246
|
logger.log(`Dataplane: ${chalk.cyan(dataplaneUrl)}`);
|
|
246
247
|
const statusIcon = dataplaneConnected ? chalk.green('✓') : chalk.red('✗');
|
|
247
248
|
const statusText = dataplaneConnected ? 'Connected' : 'Not reachable';
|
|
248
|
-
|
|
249
|
+
displayOpenApiDocs(null, dataplaneUrl);
|
|
250
|
+
logger.log('');
|
|
251
|
+
logger.log(` Status: ${statusIcon} ${statusText}`);
|
|
249
252
|
} else {
|
|
250
253
|
logger.log(`Dataplane: ${chalk.gray('—')}`);
|
|
251
|
-
logger.log(
|
|
254
|
+
logger.log('');
|
|
255
|
+
logger.log(` Status: ${chalk.gray('Not discovered')}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Normalize base URL (no trailing slash) for docs path
|
|
261
|
+
* @param {string} url - Base URL
|
|
262
|
+
* @returns {string} URL without trailing slash
|
|
263
|
+
*/
|
|
264
|
+
function normalizeBaseUrl(url) {
|
|
265
|
+
return (url || '').replace(/\/$/, '');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Display Open API documentation links (Controller and Dataplane)
|
|
270
|
+
* @param {string} controllerUrl - Controller URL
|
|
271
|
+
* @param {string|null} dataplaneUrl - Dataplane URL or null
|
|
272
|
+
*/
|
|
273
|
+
function displayOpenApiDocs(controllerUrl, dataplaneUrl) {
|
|
274
|
+
const controllerBase = normalizeBaseUrl(controllerUrl);
|
|
275
|
+
if (controllerBase) {
|
|
276
|
+
logger.log(` Open API docs: ${chalk.cyan(controllerBase + '/api/docs')}`);
|
|
277
|
+
}
|
|
278
|
+
if (dataplaneUrl) {
|
|
279
|
+
const dataplaneBase = normalizeBaseUrl(dataplaneUrl);
|
|
280
|
+
logger.log(` Open API docs: ${chalk.cyan(dataplaneBase + '/api/docs')}`);
|
|
252
281
|
}
|
|
253
282
|
}
|
|
254
283
|
|
|
@@ -264,11 +293,12 @@ function displayDataplaneSection(dataplaneUrl, dataplaneConnected) {
|
|
|
264
293
|
function displayStatus(controllerUrl, environment, tokenInfo, dataplaneInfo) {
|
|
265
294
|
logger.log(chalk.bold('\n🔐 Authentication Status\n'));
|
|
266
295
|
logger.log(`Controller: ${chalk.cyan(controllerUrl)}`);
|
|
267
|
-
|
|
296
|
+
displayOpenApiDocs(controllerUrl, null);
|
|
297
|
+
logger.log(` Environment: ${chalk.cyan(environment || 'Not specified')}\n`);
|
|
268
298
|
|
|
269
299
|
if (!tokenInfo) {
|
|
270
|
-
logger.log(`Status: ${chalk.red('✗ Not authenticated')}`);
|
|
271
|
-
logger.log(`Token Type: ${chalk.gray('None')}\n`);
|
|
300
|
+
logger.log(` Status: ${chalk.red('✗ Not authenticated')}`);
|
|
301
|
+
logger.log(` Token Type: ${chalk.gray('None')}\n`);
|
|
272
302
|
logger.log(chalk.yellow('💡 Run "aifabrix login" to authenticate\n'));
|
|
273
303
|
return;
|
|
274
304
|
}
|
package/lib/commands/up-miso.js
CHANGED
|
@@ -126,7 +126,7 @@ async function handleUpMiso(options = {}) {
|
|
|
126
126
|
const health = await infra.checkInfraHealth(undefined, { strict: true });
|
|
127
127
|
const allHealthy = Object.values(health).every(status => status === 'healthy');
|
|
128
128
|
if (!allHealthy) {
|
|
129
|
-
throw new Error('Infrastructure is not up. Run \'aifabrix up\' first.');
|
|
129
|
+
throw new Error('Infrastructure is not up. Run \'aifabrix up-infra\' first.');
|
|
130
130
|
}
|
|
131
131
|
logger.log(chalk.green('✓ Infrastructure is up'));
|
|
132
132
|
await ensureAppFromTemplate('keycloak');
|
package/lib/core/config.js
CHANGED
|
@@ -396,6 +396,15 @@ async function setSecretsEncryptionKey(key) {
|
|
|
396
396
|
await saveConfig(config);
|
|
397
397
|
}
|
|
398
398
|
|
|
399
|
+
/**
|
|
400
|
+
* Ensure secrets encryption key exists (empty install). Delegates to ensure-encryption-key module.
|
|
401
|
+
* @returns {Promise<void>}
|
|
402
|
+
*/
|
|
403
|
+
async function ensureSecretsEncryptionKey() {
|
|
404
|
+
const { ensureSecretsEncryptionKey: run } = require('./ensure-encryption-key');
|
|
405
|
+
await run({ getSecretsEncryptionKey, setSecretsEncryptionKey, getSecretsPath });
|
|
406
|
+
}
|
|
407
|
+
|
|
399
408
|
async function getSecretsPath() {
|
|
400
409
|
const config = await getConfig();
|
|
401
410
|
return config['aifabrix-secrets'] || config['secrets-path'] || null;
|
|
@@ -427,6 +436,7 @@ const exportsObj = {
|
|
|
427
436
|
decryptTokenValue,
|
|
428
437
|
getSecretsEncryptionKey,
|
|
429
438
|
setSecretsEncryptionKey,
|
|
439
|
+
ensureSecretsEncryptionKey,
|
|
430
440
|
getSecretsPath,
|
|
431
441
|
setSecretsPath,
|
|
432
442
|
normalizeControllerUrl,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensure secrets encryption key exists on empty install.
|
|
3
|
+
* If missing from config and from user/project secrets, generates and saves one. Never logs the key.
|
|
4
|
+
*
|
|
5
|
+
* @fileoverview Encryption key bootstrap for empty installation
|
|
6
|
+
* @author AI Fabrix Team
|
|
7
|
+
* @version 2.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const yaml = require('js-yaml');
|
|
13
|
+
const crypto = require('crypto');
|
|
14
|
+
const pathsUtil = require('../utils/paths');
|
|
15
|
+
const { saveLocalSecret } = require('../utils/local-secrets');
|
|
16
|
+
|
|
17
|
+
const ENCRYPTION_KEY = 'secrets-encryptionKeyVault';
|
|
18
|
+
|
|
19
|
+
function readKeyFromFile(filePath) {
|
|
20
|
+
try {
|
|
21
|
+
if (!fs.existsSync(filePath)) return null;
|
|
22
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
23
|
+
const data = yaml.load(content);
|
|
24
|
+
if (data && typeof data[ENCRYPTION_KEY] === 'string') return data[ENCRYPTION_KEY];
|
|
25
|
+
} catch {
|
|
26
|
+
// Ignore
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Ensure secrets encryption key exists. If config already has it, do nothing.
|
|
33
|
+
* If key exists in user or project secrets file, set config. Otherwise generate, write to user secrets, set config.
|
|
34
|
+
* @param {Object} config - Config module (getSecretsEncryptionKey, setSecretsEncryptionKey, getSecretsPath)
|
|
35
|
+
* @returns {Promise<void>}
|
|
36
|
+
*/
|
|
37
|
+
async function ensureSecretsEncryptionKey(config) {
|
|
38
|
+
const existing = await config.getSecretsEncryptionKey();
|
|
39
|
+
if (existing) return;
|
|
40
|
+
|
|
41
|
+
const userSecretsPath = path.join(pathsUtil.getAifabrixHome(), 'secrets.local.yaml');
|
|
42
|
+
const projectSecretsPath = await config.getSecretsPath();
|
|
43
|
+
|
|
44
|
+
let key = readKeyFromFile(userSecretsPath);
|
|
45
|
+
if (!key && projectSecretsPath) key = readKeyFromFile(path.resolve(projectSecretsPath));
|
|
46
|
+
if (key) {
|
|
47
|
+
await config.setSecretsEncryptionKey(key);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const newKey = crypto.randomBytes(32).toString('hex');
|
|
52
|
+
await saveLocalSecret(ENCRYPTION_KEY, newKey);
|
|
53
|
+
await config.setSecretsEncryptionKey(newKey);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = { ensureSecretsEncryptionKey };
|
package/lib/generator/wizard.js
CHANGED
|
@@ -360,54 +360,39 @@ async function generateEnvTemplate(appPath, systemConfig) {
|
|
|
360
360
|
}
|
|
361
361
|
|
|
362
362
|
/**
|
|
363
|
-
* Generate deployment scripts (deploy.sh
|
|
363
|
+
* Generate deployment scripts (deploy.sh, deploy.ps1, deploy.js) from templates
|
|
364
364
|
* @async
|
|
365
365
|
* @function generateDeployScripts
|
|
366
366
|
* @param {string} appPath - Application directory path
|
|
367
367
|
* @param {string} systemKey - System key
|
|
368
368
|
* @param {string} systemFileName - System file name
|
|
369
369
|
* @param {string[]} datasourceFileNames - Array of datasource file names
|
|
370
|
-
* @returns {Promise<Object>} Object with
|
|
370
|
+
* @returns {Promise<Object>} Object with deployShPath, deployPs1Path, deployJsPath
|
|
371
371
|
* @throws {Error} If generation fails
|
|
372
372
|
*/
|
|
373
|
+
const templatesExternalDir = path.join(__dirname, '..', '..', 'templates', 'external-system');
|
|
374
|
+
|
|
375
|
+
async function writeDeployScriptFromTemplate(templateName, outputPath, context, executable) {
|
|
376
|
+
const templatePath = path.join(templatesExternalDir, templateName);
|
|
377
|
+
const content = Handlebars.compile(await fs.readFile(templatePath, 'utf8'))(context);
|
|
378
|
+
await fs.writeFile(outputPath, content, 'utf8');
|
|
379
|
+
if (executable) await fs.chmod(outputPath, 0o755);
|
|
380
|
+
logger.log(chalk.green(`✓ Generated ${path.basename(outputPath)}`));
|
|
381
|
+
}
|
|
382
|
+
|
|
373
383
|
async function generateDeployScripts(appPath, systemKey, systemFileName, datasourceFileNames) {
|
|
374
384
|
try {
|
|
375
385
|
const allJsonFiles = [systemFileName, ...datasourceFileNames];
|
|
386
|
+
const context = { systemKey, allJsonFiles, datasourceFileNames };
|
|
376
387
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
const deployShTemplate = Handlebars.compile(deployShTemplateContent);
|
|
381
|
-
|
|
382
|
-
// Generate deploy.sh
|
|
383
|
-
const deployShPath = path.join(appPath, 'deploy.sh');
|
|
384
|
-
const deployShContent = deployShTemplate({
|
|
385
|
-
systemKey,
|
|
386
|
-
allJsonFiles,
|
|
387
|
-
datasourceFileNames
|
|
388
|
-
});
|
|
389
|
-
await fs.writeFile(deployShPath, deployShContent, 'utf8');
|
|
390
|
-
await fs.chmod(deployShPath, 0o755); // Make executable
|
|
391
|
-
logger.log(chalk.green('✓ Generated deploy.sh'));
|
|
392
|
-
|
|
393
|
-
// Load and compile deploy.ps1 template
|
|
394
|
-
const deployPs1TemplatePath = path.join(__dirname, '..', '..', 'templates', 'external-system', 'deploy.ps1.hbs');
|
|
395
|
-
const deployPs1TemplateContent = await fs.readFile(deployPs1TemplatePath, 'utf8');
|
|
396
|
-
const deployPs1Template = Handlebars.compile(deployPs1TemplateContent);
|
|
397
|
-
|
|
398
|
-
// Generate deploy.ps1
|
|
399
|
-
const deployPs1Path = path.join(appPath, 'deploy.ps1');
|
|
400
|
-
const deployPs1Content = deployPs1Template({
|
|
401
|
-
systemKey,
|
|
402
|
-
allJsonFiles,
|
|
403
|
-
datasourceFileNames
|
|
404
|
-
});
|
|
405
|
-
await fs.writeFile(deployPs1Path, deployPs1Content, 'utf8');
|
|
406
|
-
logger.log(chalk.green('✓ Generated deploy.ps1'));
|
|
388
|
+
await writeDeployScriptFromTemplate('deploy.sh.hbs', path.join(appPath, 'deploy.sh'), context, true);
|
|
389
|
+
await writeDeployScriptFromTemplate('deploy.ps1.hbs', path.join(appPath, 'deploy.ps1'), context, false);
|
|
390
|
+
await writeDeployScriptFromTemplate('deploy.js.hbs', path.join(appPath, 'deploy.js'), context, false);
|
|
407
391
|
|
|
408
392
|
return {
|
|
409
|
-
deployShPath,
|
|
410
|
-
deployPs1Path
|
|
393
|
+
deployShPath: path.join(appPath, 'deploy.sh'),
|
|
394
|
+
deployPs1Path: path.join(appPath, 'deploy.ps1'),
|
|
395
|
+
deployJsPath: path.join(appPath, 'deploy.js')
|
|
411
396
|
};
|
|
412
397
|
} catch (error) {
|
|
413
398
|
throw new Error(`Failed to generate deployment scripts: ${error.message}`);
|
|
@@ -119,7 +119,7 @@ function extractPasswordFromUrlOrValue(urlOrPassword) {
|
|
|
119
119
|
* Ensures Postgres init script exists for miso-controller app (database miso, user miso_user).
|
|
120
120
|
* Uses password from secrets (databases-miso-controller-0-passwordKeyVault) or default miso_pass123.
|
|
121
121
|
* Init scripts run only when the Postgres data volume is first created. If infra was already
|
|
122
|
-
* started before this script existed, run `aifabrix down -v` then `aifabrix up` to re-init, or
|
|
122
|
+
* started before this script existed, run `aifabrix down-infra -v` then `aifabrix up-infra` to re-init, or
|
|
123
123
|
* create the database and user manually (e.g. via pgAdmin or psql).
|
|
124
124
|
*
|
|
125
125
|
* @async
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"key": "external-system-schema",
|
|
8
8
|
"name": "External System Configuration Schema",
|
|
9
9
|
"description": "JSON schema for validating ExternalSystem configuration files",
|
|
10
|
-
"version": "1.
|
|
10
|
+
"version": "1.2.0",
|
|
11
11
|
"type": "schema",
|
|
12
12
|
"category": "integration",
|
|
13
13
|
"author": "AI Fabrix Team",
|
|
@@ -27,6 +27,15 @@
|
|
|
27
27
|
],
|
|
28
28
|
"dependencies": [],
|
|
29
29
|
"changelog": [
|
|
30
|
+
{
|
|
31
|
+
"version": "1.2.0",
|
|
32
|
+
"date": "2025-02-01T00:00:00Z",
|
|
33
|
+
"changes": [
|
|
34
|
+
"Added generateMcpContract (boolean, default true): config-only control for MCP contract generation on publish",
|
|
35
|
+
"Added generateOpenApiContract (boolean, default true): reserved for future use"
|
|
36
|
+
],
|
|
37
|
+
"breaking": false
|
|
38
|
+
},
|
|
30
39
|
{
|
|
31
40
|
"version": "1.1.0",
|
|
32
41
|
"date": "2025-12-01T00:00:00Z",
|
|
@@ -407,6 +416,20 @@
|
|
|
407
416
|
"type": "boolean",
|
|
408
417
|
"description": "Master switch for all endpoints in this system. If false, no endpoints are registered regardless of individual endpoint active flags.",
|
|
409
418
|
"default": true
|
|
419
|
+
},
|
|
420
|
+
"credentialIdOrKey": {
|
|
421
|
+
"type": "string",
|
|
422
|
+
"description": "Credential identifier (ID or key) to use for authenticating with this external system."
|
|
423
|
+
},
|
|
424
|
+
"generateMcpContract": {
|
|
425
|
+
"type": "boolean",
|
|
426
|
+
"description": "Whether to generate MCP contract on publish. Config only (no query parameter); default true when absent.",
|
|
427
|
+
"default": true
|
|
428
|
+
},
|
|
429
|
+
"generateOpenApiContract": {
|
|
430
|
+
"type": "boolean",
|
|
431
|
+
"description": "Reserved: whether to generate or expose OpenAPI contract on publish. Not yet implemented.",
|
|
432
|
+
"default": true
|
|
410
433
|
}
|
|
411
434
|
},
|
|
412
435
|
"additionalProperties": false
|
|
@@ -20,8 +20,11 @@ const CATEGORIES = [
|
|
|
20
20
|
{
|
|
21
21
|
name: 'Infrastructure (Local Development)',
|
|
22
22
|
commands: [
|
|
23
|
-
{ name: 'up' },
|
|
24
|
-
{ name: '
|
|
23
|
+
{ name: 'up-infra' },
|
|
24
|
+
{ name: 'up-platform' },
|
|
25
|
+
{ name: 'up-miso' },
|
|
26
|
+
{ name: 'up-dataplane' },
|
|
27
|
+
{ name: 'down-infra', term: 'down-infra [app]' },
|
|
25
28
|
{ name: 'doctor' },
|
|
26
29
|
{ name: 'status' },
|
|
27
30
|
{ name: 'restart', term: 'restart <service>' }
|
|
@@ -251,9 +251,8 @@ async function tryClientTokenAuth(environment, appName, controllerUrl) {
|
|
|
251
251
|
controller: clientToken.controller
|
|
252
252
|
};
|
|
253
253
|
}
|
|
254
|
-
} catch
|
|
255
|
-
// Client token unavailable
|
|
256
|
-
logger.warn(`Client token unavailable: ${error.message}`);
|
|
254
|
+
} catch {
|
|
255
|
+
// Client token unavailable; getDeploymentAuth will try client credentials next (no warning here to avoid misleading output when env credentials succeed)
|
|
257
256
|
}
|
|
258
257
|
return null;
|
|
259
258
|
}
|
package/package.json
CHANGED
|
@@ -47,8 +47,8 @@ docker logs aifabrix-{{appName}} -f
|
|
|
47
47
|
|
|
48
48
|
**Stop:**
|
|
49
49
|
```bash
|
|
50
|
-
aifabrix down {{appName}}
|
|
51
|
-
# aifabrix down {{appName}} --volumes # also remove data volume
|
|
50
|
+
aifabrix down-infra {{appName}}
|
|
51
|
+
# aifabrix down-infra {{appName}} --volumes # also remove data volume
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
### 4. Deploy to Azure
|
|
@@ -87,7 +87,7 @@ aifabrix app rotate-secret {{appName}}
|
|
|
87
87
|
# Development
|
|
88
88
|
aifabrix build {{appName}} # Build app
|
|
89
89
|
aifabrix run {{appName}} # Run locally
|
|
90
|
-
aifabrix down {{appName}} [--volumes] # Stop app (optionally remove volume)
|
|
90
|
+
aifabrix down-infra {{appName}} [--volumes] # Stop app (optionally remove volume)
|
|
91
91
|
aifabrix dockerfile {{appName}} --force # Generate Dockerfile
|
|
92
92
|
aifabrix resolve {{appName}} # Generate .env file
|
|
93
93
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Deploy {{systemKey}} external system and datasources using aifabrix CLI.
|
|
4
|
+
* Run: node deploy.js
|
|
5
|
+
* Flow: check auth → login if needed → deploy → run integration tests.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { execSync } = require('child_process');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
const scriptDir = __dirname;
|
|
12
|
+
const appKey = '{{systemKey}}';
|
|
13
|
+
const env = process.env.ENVIRONMENT || 'dev';
|
|
14
|
+
// Controller URL: from config (aifabrix auth config) or set CONTROLLER env before running
|
|
15
|
+
|
|
16
|
+
function run(cmd, options = {}) {
|
|
17
|
+
const opts = { cwd: scriptDir, stdio: 'inherit', ...options };
|
|
18
|
+
try {
|
|
19
|
+
execSync(cmd, opts);
|
|
20
|
+
return true;
|
|
21
|
+
} catch (err) {
|
|
22
|
+
if (options.ignoreExit) return false;
|
|
23
|
+
process.exit(err.status ?? 1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isLoggedIn() {
|
|
28
|
+
try {
|
|
29
|
+
execSync('aifabrix auth status', { cwd: scriptDir, stdio: 'pipe' });
|
|
30
|
+
return true;
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
console.log('🔍 Checking authentication...');
|
|
37
|
+
if (!isLoggedIn()) {
|
|
38
|
+
console.log('⚠️ Not logged in. Run login (e.g. aifabrix login --controller <url> --method device --environment ' + env + ').');
|
|
39
|
+
run('aifabrix login --environment ' + env);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log('🔍 Validating configuration...');
|
|
43
|
+
{{#each allJsonFiles}}
|
|
44
|
+
run('aifabrix validate "' + path.join(scriptDir, '{{this}}') + '"');
|
|
45
|
+
{{/each}}
|
|
46
|
+
console.log('✅ Validation passed');
|
|
47
|
+
|
|
48
|
+
console.log('🚀 Deploying ' + appKey + '...');
|
|
49
|
+
run('aifabrix deploy ' + appKey);
|
|
50
|
+
console.log('✅ Deployment complete');
|
|
51
|
+
|
|
52
|
+
if (process.env.RUN_TESTS !== 'false') {
|
|
53
|
+
console.log('🧪 Running integration tests...');
|
|
54
|
+
run('aifabrix test-integration ' + appKey);
|
|
55
|
+
console.log('✅ Tests passed');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
console.log('✅ Done.');
|