@aifabrix/builder 2.36.2 → 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.
@@ -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');
@@ -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 };
@@ -360,54 +360,39 @@ async function generateEnvTemplate(appPath, systemConfig) {
360
360
  }
361
361
 
362
362
  /**
363
- * Generate deployment scripts (deploy.sh and deploy.ps1) from templates
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 script file paths
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
- // Load and compile deploy.sh template
378
- const deployShTemplatePath = path.join(__dirname, '..', '..', 'templates', 'external-system', 'deploy.sh.hbs');
379
- const deployShTemplateContent = await fs.readFile(deployShTemplatePath, 'utf8');
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
@@ -20,8 +20,11 @@ const CATEGORIES = [
20
20
  {
21
21
  name: 'Infrastructure (Local Development)',
22
22
  commands: [
23
- { name: 'up' },
24
- { name: 'down', term: 'down [app]' },
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>' }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aifabrix/builder",
3
- "version": "2.36.2",
3
+ "version": "2.37.0",
4
4
  "description": "AI Fabrix Local Fabric & Deployment SDK",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -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
 
@@ -43,8 +43,6 @@ authentication:
43
43
  type: azure
44
44
  enableSSO: true
45
45
  requiredRoles: ["aifabrix-user"]
46
- endpoints:
47
- local: "https://dataplane.aifabrix.ai/auth/callback"
48
46
 
49
47
  # Build Configuration
50
48
  build:
@@ -38,8 +38,6 @@ authentication:
38
38
  enableSSO: true
39
39
  requiredRoles:
40
40
  - aifabrix-user
41
- endpoints:
42
- local: http://localhost:3000/auth/callback
43
41
 
44
42
  # Build Configuration
45
43
  build:
@@ -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.');