@aifabrix/builder 2.36.2 → 2.37.5

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.
Files changed (43) hide show
  1. package/.cursor/rules/project-rules.mdc +19 -0
  2. package/README.md +68 -104
  3. package/integration/hubspot/test.js +1 -1
  4. package/lib/api/wizard.api.js +24 -1
  5. package/lib/app/deploy.js +43 -7
  6. package/lib/app/display.js +1 -1
  7. package/lib/app/list.js +3 -1
  8. package/lib/app/run-helpers.js +1 -1
  9. package/lib/build/index.js +3 -4
  10. package/lib/cli/index.js +45 -0
  11. package/lib/cli/setup-app.js +230 -0
  12. package/lib/cli/setup-auth.js +88 -0
  13. package/lib/cli/setup-dev.js +101 -0
  14. package/lib/cli/setup-environment.js +53 -0
  15. package/lib/cli/setup-external-system.js +87 -0
  16. package/lib/cli/setup-infra.js +219 -0
  17. package/lib/cli/setup-secrets.js +48 -0
  18. package/lib/cli/setup-utility.js +202 -0
  19. package/lib/cli.js +7 -961
  20. package/lib/commands/up-common.js +31 -1
  21. package/lib/commands/up-miso.js +6 -2
  22. package/lib/commands/wizard-core.js +32 -7
  23. package/lib/core/config.js +10 -0
  24. package/lib/core/ensure-encryption-key.js +56 -0
  25. package/lib/deployment/deployer-status.js +101 -0
  26. package/lib/deployment/deployer.js +62 -110
  27. package/lib/deployment/environment.js +133 -34
  28. package/lib/external-system/deploy.js +5 -1
  29. package/lib/external-system/test-auth.js +14 -7
  30. package/lib/generator/wizard.js +37 -41
  31. package/lib/infrastructure/helpers.js +1 -1
  32. package/lib/schema/environment-deploy-request.schema.json +64 -0
  33. package/lib/utils/help-builder.js +5 -2
  34. package/lib/utils/paths.js +22 -4
  35. package/lib/utils/secrets-generator.js +23 -8
  36. package/lib/utils/secrets-helpers.js +46 -21
  37. package/package.json +1 -1
  38. package/scripts/install-local.js +11 -2
  39. package/templates/applications/README.md.hbs +3 -3
  40. package/templates/applications/dataplane/variables.yaml +0 -2
  41. package/templates/applications/miso-controller/variables.yaml +0 -2
  42. package/templates/external-system/deploy.js.hbs +69 -0
  43. package/templates/infra/environment-dev.json +10 -0
@@ -33,7 +33,17 @@ function interpolateEnvVars(content, envVars) {
33
33
  }
34
34
 
35
35
  /**
36
- * Collect missing kv:// secrets referenced in content
36
+ * Returns true if the line is a comment or empty (should be skipped for kv:// resolution)
37
+ * @param {string} line - Single line
38
+ * @returns {boolean}
39
+ */
40
+ function isCommentOrEmptyLine(line) {
41
+ const t = line.trim();
42
+ return t === '' || t.startsWith('#');
43
+ }
44
+
45
+ /**
46
+ * Collect missing kv:// secrets referenced in content (skips commented and empty lines)
37
47
  * @function collectMissingSecrets
38
48
  * @param {string} content - Text content
39
49
  * @param {Object} secrets - Available secrets
@@ -42,11 +52,16 @@ function interpolateEnvVars(content, envVars) {
42
52
  function collectMissingSecrets(content, secrets) {
43
53
  const kvPattern = /kv:\/\/([a-zA-Z0-9-_]+)/g;
44
54
  const missing = [];
45
- let match;
46
- while ((match = kvPattern.exec(content)) !== null) {
47
- const secretKey = match[1];
48
- if (!(secretKey in secrets)) {
49
- missing.push(`kv://${secretKey}`);
55
+ const lines = content.split('\n');
56
+ for (const line of lines) {
57
+ if (isCommentOrEmptyLine(line)) continue;
58
+ let match;
59
+ kvPattern.lastIndex = 0;
60
+ while ((match = kvPattern.exec(line)) !== null) {
61
+ const secretKey = match[1];
62
+ if (!(secretKey in secrets)) {
63
+ missing.push(`kv://${secretKey}`);
64
+ }
50
65
  }
51
66
  }
52
67
  return missing;
@@ -76,7 +91,7 @@ function formatMissingSecretsFileInfo(secretsFilePaths) {
76
91
  }
77
92
 
78
93
  /**
79
- * Replace kv:// references with actual values, after also interpolating any ${VAR} within secret values
94
+ * Replace kv:// references with actual values (skips commented and empty lines)
80
95
  * @function replaceKvInContent
81
96
  * @param {string} content - Text content containing kv:// references
82
97
  * @param {Object} secrets - Secrets map
@@ -85,15 +100,20 @@ function formatMissingSecretsFileInfo(secretsFilePaths) {
85
100
  */
86
101
  function replaceKvInContent(content, secrets, envVars) {
87
102
  const kvPattern = /kv:\/\/([a-zA-Z0-9-_]+)/g;
88
- return content.replace(kvPattern, (match, secretKey) => {
89
- let value = secrets[secretKey];
90
- if (typeof value === 'string') {
91
- value = value.replace(/\$\{([A-Z_]+)\}/g, (m, envVar) => {
92
- return envVars[envVar] || m;
93
- });
94
- }
95
- return value;
103
+ const lines = content.split('\n');
104
+ const result = lines.map(line => {
105
+ if (isCommentOrEmptyLine(line)) return line;
106
+ return line.replace(kvPattern, (match, secretKey) => {
107
+ let value = secrets[secretKey];
108
+ if (typeof value === 'string') {
109
+ value = value.replace(/\$\{([A-Z_]+)\}/g, (m, envVar) => {
110
+ return envVars[envVar] || m;
111
+ });
112
+ }
113
+ return value;
114
+ });
96
115
  });
116
+ return result.join('\n');
97
117
  }
98
118
 
99
119
  /**
@@ -420,7 +440,7 @@ function ensureNonEmptySecrets(secrets) {
420
440
  }
421
441
 
422
442
  /**
423
- * Validate secrets against the env template, returning missing refs
443
+ * Validate secrets against the env template (skips commented and empty lines)
424
444
  * @function validateSecrets
425
445
  * @param {string} envTemplate - Environment template content
426
446
  * @param {Object} secrets - Available secrets
@@ -429,11 +449,16 @@ function ensureNonEmptySecrets(secrets) {
429
449
  function validateSecrets(envTemplate, secrets) {
430
450
  const kvPattern = /kv:\/\/([a-zA-Z0-9-_]+)/g;
431
451
  const missing = [];
432
- let match;
433
- while ((match = kvPattern.exec(envTemplate)) !== null) {
434
- const secretKey = match[1];
435
- if (!(secretKey in secrets)) {
436
- missing.push(`kv://${secretKey}`);
452
+ const lines = envTemplate.split('\n');
453
+ for (const line of lines) {
454
+ if (isCommentOrEmptyLine(line)) continue;
455
+ let match;
456
+ kvPattern.lastIndex = 0;
457
+ while ((match = kvPattern.exec(line)) !== null) {
458
+ const secretKey = match[1];
459
+ if (!(secretKey in secrets)) {
460
+ missing.push(`kv://${secretKey}`);
461
+ }
437
462
  }
438
463
  }
439
464
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aifabrix/builder",
3
- "version": "2.36.2",
3
+ "version": "2.37.5",
4
4
  "description": "AI Fabrix Local Fabric & Deployment SDK",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -114,10 +114,19 @@ function installLocal() {
114
114
  console.log('Linking @aifabrix/builder globally...\n');
115
115
 
116
116
  try {
117
+ const projectRoot = path.join(__dirname, '..');
117
118
  if (pm === 'pnpm') {
118
- execSync('pnpm link --global', { stdio: 'inherit' });
119
+ // Update pnpm global.
120
+ execSync('pnpm link --global', { stdio: 'inherit', cwd: projectRoot });
121
+ // Also run npm link so npm's global bin points here; often PATH has
122
+ // npm's global bin before pnpm's, so "aifabrix" would otherwise stay old.
123
+ try {
124
+ execSync('npm link', { stdio: 'inherit', cwd: projectRoot });
125
+ } catch {
126
+ // npm may not be available or may fail; pnpm link already ran
127
+ }
119
128
  } else {
120
- execSync('npm link', { stdio: 'inherit' });
129
+ execSync('npm link', { stdio: 'inherit', cwd: projectRoot });
121
130
  }
122
131
 
123
132
  // Get new version after linking
@@ -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,69 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-console */
3
+ /**
4
+ * Deploy {{systemKey}} external system and datasources using aifabrix CLI.
5
+ * Run: node deploy.js
6
+ * Flow: check auth → login if needed → deploy → run integration tests.
7
+ */
8
+
9
+ const { execSync } = require('child_process');
10
+ const path = require('path');
11
+
12
+ const scriptDir = __dirname;
13
+ const appKey = '{{systemKey}}';
14
+ const env = process.env.ENVIRONMENT || 'dev';
15
+ // Controller URL: from config (aifabrix auth config) or set CONTROLLER env before running
16
+
17
+ /**
18
+ * Run a shell command with optional options.
19
+ * @param {string} cmd - Command to run
20
+ * @param {Object} [options={}] - Options (cwd, stdio, ignoreExit)
21
+ * @returns {boolean} True if command succeeded
22
+ */
23
+ function run(cmd, options = {}) {
24
+ const opts = { cwd: scriptDir, stdio: 'inherit', ...options };
25
+ try {
26
+ execSync(cmd, opts);
27
+ return true;
28
+ } catch (err) {
29
+ if (options.ignoreExit) return false;
30
+ process.exit(err.status ?? 1);
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Check if aifabrix auth is logged in.
36
+ * @returns {boolean} True if logged in
37
+ */
38
+ function isLoggedIn() {
39
+ try {
40
+ execSync('aifabrix auth status', { cwd: scriptDir, stdio: 'pipe' });
41
+ return true;
42
+ } catch {
43
+ return false;
44
+ }
45
+ }
46
+
47
+ console.log('🔍 Checking authentication...');
48
+ if (!isLoggedIn()) {
49
+ console.log('⚠️ Not logged in. Run login (e.g. aifabrix login --controller <url> --method device --environment ' + env + ').');
50
+ run('aifabrix login --environment ' + env);
51
+ }
52
+
53
+ console.log('🔍 Validating configuration...');
54
+ {{#each allJsonFiles}}
55
+ run('aifabrix validate "' + path.join(scriptDir, '{{this}}') + '"');
56
+ {{/each}}
57
+ console.log('✅ Validation passed');
58
+
59
+ console.log('🚀 Deploying ' + appKey + '...');
60
+ run('aifabrix deploy ' + appKey);
61
+ console.log('✅ Deployment complete');
62
+
63
+ if (process.env.RUN_TESTS !== 'false') {
64
+ console.log('🧪 Running integration tests...');
65
+ run('aifabrix test-integration ' + appKey);
66
+ console.log('✅ Tests passed');
67
+ }
68
+
69
+ console.log('✅ Done.');
@@ -0,0 +1,10 @@
1
+ {
2
+ "environmentConfig": {
3
+ "key": "dev",
4
+ "environment": "dev",
5
+ "preset": "s",
6
+ "serviceName": "aifabrix",
7
+ "location": "swedencentral"
8
+ },
9
+ "dryRun": false
10
+ }