@aifabrix/builder 2.32.2 → 2.33.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.
Files changed (130) hide show
  1. package/.cursor/rules/project-rules.mdc +8 -0
  2. package/README.md +36 -8
  3. package/bin/aifabrix.js +6 -8
  4. package/integration/hubspot/README.md +8 -7
  5. package/integration/hubspot/companies.json +2048 -0
  6. package/integration/hubspot/create-hubspot.js +665 -0
  7. package/integration/hubspot/{hubspot-deploy-company.json → hubspot-datasource-company.json} +1 -1
  8. package/integration/hubspot/{hubspot-deploy-contact.json → hubspot-datasource-contact.json} +1 -1
  9. package/integration/hubspot/{hubspot-deploy-deal.json → hubspot-datasource-deal.json} +1 -1
  10. package/integration/hubspot/hubspot-deploy.json +832 -81
  11. package/integration/hubspot/hubspot-system.json +99 -0
  12. package/integration/hubspot/test-artifacts/wizard-hubspot-credential-real.yaml +20 -0
  13. package/integration/hubspot/test-artifacts/wizard-hubspot-env-vars.yaml +9 -0
  14. package/integration/hubspot/test-artifacts/wizard-invalid-add-datasource.yaml +5 -0
  15. package/integration/hubspot/test-artifacts/wizard-invalid-app-name.yaml +5 -0
  16. package/integration/hubspot/test-artifacts/wizard-invalid-credential-create.yaml +7 -0
  17. package/integration/hubspot/test-artifacts/wizard-invalid-credential-select.yaml +7 -0
  18. package/integration/hubspot/test-artifacts/wizard-invalid-known-platform.yaml +4 -0
  19. package/integration/hubspot/test-artifacts/wizard-invalid-missing-app.yaml +4 -0
  20. package/integration/hubspot/test-artifacts/wizard-invalid-missing-source.yaml +2 -0
  21. package/integration/hubspot/test-artifacts/wizard-invalid-mode.yaml +5 -0
  22. package/integration/hubspot/test-artifacts/wizard-invalid-openapi-file.yaml +5 -0
  23. package/integration/hubspot/test-artifacts/wizard-invalid-openapi-url.yaml +4 -0
  24. package/integration/hubspot/test-artifacts/wizard-invalid-source.yaml +4 -0
  25. package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-array-test.yaml +5 -0
  26. package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-key-test.yaml +5 -0
  27. package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-path-test.yaml +5 -0
  28. package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-test.yaml +5 -0
  29. package/integration/hubspot/test-artifacts/wizard-valid-for-rbac-test.yaml +5 -0
  30. package/integration/hubspot/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +5 -0
  31. package/integration/hubspot/test-dataplane-down-helpers.js +246 -0
  32. package/integration/hubspot/test-dataplane-down-tests.js +419 -0
  33. package/integration/hubspot/test-dataplane-down.js +157 -0
  34. package/integration/hubspot/test.js +1517 -0
  35. package/integration/hubspot/variables.yaml +4 -4
  36. package/integration/hubspot/wizard-hubspot-e2e.yaml +16 -0
  37. package/integration/hubspot/wizard-hubspot-platform.yaml +8 -0
  38. package/lib/api/applications.api.js +1 -0
  39. package/lib/api/index.js +10 -5
  40. package/lib/api/types/wizard.types.js +176 -38
  41. package/lib/api/wizard.api.js +207 -38
  42. package/lib/app/deploy.js +116 -54
  43. package/lib/app/display.js +6 -5
  44. package/lib/app/dockerfile.js +2 -1
  45. package/lib/app/list.js +78 -37
  46. package/lib/app/prompts.js +9 -5
  47. package/lib/app/readme.js +41 -112
  48. package/lib/app/register.js +44 -9
  49. package/lib/app/rotate-secret.js +50 -32
  50. package/lib/cli.js +243 -65
  51. package/lib/commands/app.js +4 -9
  52. package/lib/commands/auth-config.js +125 -0
  53. package/lib/commands/auth-status.js +261 -0
  54. package/lib/commands/datasource.js +3 -6
  55. package/lib/commands/login-credentials.js +4 -4
  56. package/lib/commands/login-device.js +43 -29
  57. package/lib/commands/login.js +22 -13
  58. package/lib/commands/wizard-config-normalizer.js +92 -0
  59. package/lib/commands/wizard-core.js +515 -0
  60. package/lib/commands/wizard-dataplane.js +122 -0
  61. package/lib/commands/wizard-headless.js +115 -0
  62. package/lib/commands/wizard.js +129 -357
  63. package/lib/core/config.js +46 -0
  64. package/lib/core/secrets.js +3 -22
  65. package/lib/core/templates-env.js +1 -1
  66. package/lib/datasource/deploy.js +34 -23
  67. package/lib/datasource/list.js +8 -6
  68. package/lib/deployment/deployer.js +25 -0
  69. package/lib/deployment/environment.js +10 -13
  70. package/lib/external-system/delete.js +151 -0
  71. package/lib/external-system/deploy.js +54 -378
  72. package/lib/external-system/download-helpers.js +45 -65
  73. package/lib/external-system/download.js +34 -13
  74. package/lib/external-system/generator.js +11 -7
  75. package/lib/external-system/test-auth.js +5 -3
  76. package/lib/generator/builders.js +3 -1
  77. package/lib/generator/external-controller-manifest.js +157 -0
  78. package/lib/generator/external-schema-utils.js +236 -0
  79. package/lib/generator/external.js +55 -3
  80. package/lib/generator/index.js +22 -10
  81. package/lib/generator/wizard-prompts.js +33 -10
  82. package/lib/generator/wizard.js +69 -86
  83. package/lib/infrastructure/compose.js +100 -0
  84. package/lib/infrastructure/helpers.js +139 -0
  85. package/lib/infrastructure/index.js +52 -311
  86. package/lib/infrastructure/services.js +168 -0
  87. package/lib/schema/application-schema.json +24 -5
  88. package/lib/schema/external-datasource.schema.json +303 -17
  89. package/lib/schema/external-system.schema.json +1 -1
  90. package/lib/schema/wizard-config.schema.json +234 -0
  91. package/lib/utils/api.js +37 -42
  92. package/lib/utils/app-existence.js +42 -0
  93. package/lib/utils/app-register-config.js +7 -2
  94. package/lib/utils/app-register-display.js +2 -1
  95. package/lib/utils/auth-config-validator.js +92 -0
  96. package/lib/utils/cli-utils.js +3 -1
  97. package/lib/utils/command-header.js +43 -0
  98. package/lib/utils/compose-generator.js +113 -70
  99. package/lib/utils/controller-url.js +115 -0
  100. package/lib/utils/dataplane-health.js +115 -0
  101. package/lib/utils/dataplane-resolver.js +29 -0
  102. package/lib/utils/dev-config.js +6 -2
  103. package/lib/utils/env-copy.js +2 -1
  104. package/lib/utils/env-map.js +2 -1
  105. package/lib/utils/env-ports.js +2 -1
  106. package/lib/utils/env-template.js +1 -1
  107. package/lib/utils/error-formatter.js +149 -28
  108. package/lib/utils/external-readme.js +125 -0
  109. package/lib/utils/help-builder.js +190 -0
  110. package/lib/utils/infra-status.js +13 -3
  111. package/lib/utils/paths.js +17 -2
  112. package/lib/utils/port-resolver.js +111 -0
  113. package/lib/utils/secrets-helpers.js +3 -15
  114. package/lib/utils/secrets-utils.js +2 -2
  115. package/lib/utils/token-manager.js +69 -4
  116. package/lib/utils/variable-transformer.js +7 -2
  117. package/lib/validation/external-manifest-validator.js +202 -0
  118. package/lib/validation/validate-display.js +406 -0
  119. package/lib/validation/validate.js +159 -123
  120. package/lib/validation/validator.js +38 -4
  121. package/lib/validation/wizard-config-validator.js +267 -0
  122. package/package.json +4 -2
  123. package/templates/applications/README.md.hbs +19 -17
  124. package/templates/applications/miso-controller/env.template +1 -1
  125. package/templates/applications/miso-controller/rbac.yaml +7 -7
  126. package/templates/external-system/README.md.hbs +99 -0
  127. package/templates/external-system/external-system.json.hbs +1 -1
  128. package/templates/infra/compose.yaml.hbs +35 -0
  129. package/templates/python/docker-compose.hbs +26 -0
  130. package/templates/typescript/docker-compose.hbs +26 -0
@@ -0,0 +1,267 @@
1
+ /**
2
+ * @fileoverview Wizard configuration validator for wizard.yaml files
3
+ * @author AI Fabrix Team
4
+ * @version 2.0.0
5
+ */
6
+
7
+ const fs = require('fs').promises;
8
+ const path = require('path');
9
+ const yaml = require('js-yaml');
10
+ const Ajv = require('ajv');
11
+ const wizardConfigSchema = require('../schema/wizard-config.schema.json');
12
+
13
+ /**
14
+ * Resolve environment variable references in a value
15
+ * Supports ${VAR_NAME} syntax
16
+ * @function resolveEnvVar
17
+ * @param {string} value - Value that may contain env var references
18
+ * @returns {string} Resolved value
19
+ */
20
+ function resolveEnvVar(value) {
21
+ if (typeof value !== 'string') {
22
+ return value;
23
+ }
24
+ return value.replace(/\$\{([^}]+)\}/g, (match, varName) => {
25
+ const envValue = process.env[varName];
26
+ if (envValue === undefined) {
27
+ throw new Error(`Environment variable '${varName}' is not defined`);
28
+ }
29
+ return envValue;
30
+ });
31
+ }
32
+
33
+ /**
34
+ * Recursively resolve environment variables in an object
35
+ * @function resolveEnvVarsInObject
36
+ * @param {Object} obj - Object to process
37
+ * @returns {Object} Object with resolved env vars
38
+ */
39
+ function resolveEnvVarsInObject(obj) {
40
+ if (obj === null || obj === undefined) {
41
+ return obj;
42
+ }
43
+ if (typeof obj === 'string') {
44
+ return resolveEnvVar(obj);
45
+ }
46
+ if (Array.isArray(obj)) {
47
+ return obj.map(item => resolveEnvVarsInObject(item));
48
+ }
49
+ if (typeof obj === 'object') {
50
+ const result = {};
51
+ for (const [key, value] of Object.entries(obj)) {
52
+ result[key] = resolveEnvVarsInObject(value);
53
+ }
54
+ return result;
55
+ }
56
+ return obj;
57
+ }
58
+
59
+ /**
60
+ * Format AJV validation errors into user-friendly messages
61
+ * @function formatValidationErrors
62
+ * @param {Object[]} errors - AJV validation errors
63
+ * @returns {string[]} Formatted error messages
64
+ */
65
+ function formatValidationErrors(errors) {
66
+ if (!errors || errors.length === 0) {
67
+ return [];
68
+ }
69
+ return errors.map(error => {
70
+ const path = error.instancePath || '/';
71
+ const message = error.message || 'Unknown validation error';
72
+ if (error.keyword === 'required') {
73
+ return `Missing required field: ${error.params.missingProperty}`;
74
+ }
75
+ if (error.keyword === 'enum') {
76
+ return `${path}: ${message}. Allowed values: ${error.params.allowedValues.join(', ')}`;
77
+ }
78
+ if (error.keyword === 'pattern') {
79
+ return `${path}: ${message}`;
80
+ }
81
+ if (error.keyword === 'additionalProperties') {
82
+ return `${path}: Unknown property '${error.params.additionalProperty}'`;
83
+ }
84
+ return `${path}: ${message}`;
85
+ });
86
+ }
87
+
88
+ /**
89
+ * Load and parse wizard.yaml file
90
+ * @async
91
+ * @function loadWizardConfig
92
+ * @param {string} configPath - Path to wizard.yaml file
93
+ * @returns {Promise<Object>} Parsed configuration object
94
+ * @throws {Error} If file cannot be read or parsed
95
+ */
96
+ async function loadWizardConfig(configPath) {
97
+ const resolvedPath = path.resolve(configPath);
98
+ try {
99
+ await fs.access(resolvedPath);
100
+ const content = await fs.readFile(resolvedPath, 'utf8');
101
+ const config = yaml.load(content);
102
+ if (!config || typeof config !== 'object') {
103
+ throw new Error('Configuration file is empty or invalid');
104
+ }
105
+ return config;
106
+ } catch (error) {
107
+ if (error.code === 'ENOENT') {
108
+ throw new Error(`Configuration file not found: ${resolvedPath}`);
109
+ }
110
+ if (error.name === 'YAMLException') {
111
+ throw new Error(`Invalid YAML syntax: ${error.message}`);
112
+ }
113
+ throw error;
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Validate wizard configuration against schema
119
+ * @function validateWizardConfigSchema
120
+ * @param {Object} config - Configuration object to validate
121
+ * @returns {Object} Validation result with valid flag and errors
122
+ */
123
+ function validateWizardConfigSchema(config) {
124
+ const ajv = new Ajv({ allErrors: true, strict: false });
125
+ const validate = ajv.compile(wizardConfigSchema);
126
+ const valid = validate(config);
127
+ return {
128
+ valid,
129
+ errors: valid ? [] : formatValidationErrors(validate.errors)
130
+ };
131
+ }
132
+
133
+ /**
134
+ * Validate file path exists (for openapi-file source type)
135
+ * @function validateFilePath
136
+ * @param {string} filePath - Path to validate
137
+ * @param {string} basePath - Base path for relative paths
138
+ * @returns {Promise<Object>} Validation result with valid flag and errors
139
+ */
140
+ async function validateFilePath(filePath, basePath) {
141
+ const baseDir = path.resolve(basePath);
142
+ const resolvedPath = path.isAbsolute(filePath)
143
+ ? path.resolve(filePath)
144
+ : path.resolve(baseDir, filePath);
145
+ if (!path.isAbsolute(filePath)) {
146
+ const baseWithSep = baseDir.endsWith(path.sep) ? baseDir : `${baseDir}${path.sep}`;
147
+ if (!resolvedPath.startsWith(baseWithSep)) {
148
+ return {
149
+ valid: false,
150
+ errors: [`OpenAPI file path must be within: ${baseDir}`]
151
+ };
152
+ }
153
+ }
154
+ try {
155
+ await fs.access(resolvedPath);
156
+ } catch (error) {
157
+ return {
158
+ valid: false,
159
+ errors: [`OpenAPI file not found: ${resolvedPath}`]
160
+ };
161
+ }
162
+ return { valid: true, errors: [] };
163
+ }
164
+
165
+ /**
166
+ * Validate schema and resolve environment variables
167
+ * @function validateSchemaAndResolveEnvVars
168
+ * @param {Object} config - Configuration object
169
+ * @param {boolean} shouldResolveEnvVars - Whether to resolve env vars
170
+ * @returns {Object} Result with valid flag, errors, and config
171
+ */
172
+ function validateSchemaAndResolveEnvVars(config, shouldResolveEnvVars) {
173
+ if (shouldResolveEnvVars) {
174
+ try {
175
+ config = resolveEnvVarsInObject(config);
176
+ } catch (error) {
177
+ return { valid: false, errors: [error.message], config };
178
+ }
179
+ }
180
+ const schemaResult = validateWizardConfigSchema(config);
181
+ if (!schemaResult.valid) {
182
+ return { valid: false, errors: schemaResult.errors, config };
183
+ }
184
+ return { valid: true, errors: [], config };
185
+ }
186
+
187
+ /**
188
+ * Perform additional semantic validations
189
+ * @function performSemanticValidations
190
+ * @param {Object} config - Configuration object
191
+ * @param {string} configPath - Path to config file
192
+ * @param {boolean} validateFilePaths - Whether to validate file paths
193
+ * @returns {Promise<Object>} Validation result with errors array
194
+ */
195
+ async function performSemanticValidations(config, configPath, validateFilePaths) {
196
+ const errors = [];
197
+ if (validateFilePaths && config.source?.type === 'openapi-file' && config.source?.filePath) {
198
+ const basePath = path.dirname(path.resolve(configPath));
199
+ const fileResult = await validateFilePath(config.source.filePath, basePath);
200
+ if (!fileResult.valid) {
201
+ errors.push(...fileResult.errors);
202
+ }
203
+ }
204
+ if (config.mode === 'add-datasource' && !config.systemIdOrKey) {
205
+ errors.push('\'systemIdOrKey\' is required when mode is \'add-datasource\'');
206
+ }
207
+ return errors;
208
+ }
209
+
210
+ /**
211
+ * Validate wizard configuration with all checks
212
+ * @async
213
+ * @function validateWizardConfig
214
+ * @param {string} configPath - Path to wizard.yaml file
215
+ * @param {Object} [options] - Validation options
216
+ * @param {boolean} [options.resolveEnvVars=true] - Whether to resolve env vars
217
+ * @param {boolean} [options.validateFilePaths=true] - Whether to validate file paths
218
+ * @returns {Promise<Object>} Validation result with valid flag, errors, and config
219
+ */
220
+ async function validateWizardConfig(configPath, options = {}) {
221
+ const { resolveEnvVars: shouldResolveEnvVars = true, validateFilePaths = true } = options;
222
+ let config;
223
+ try {
224
+ config = await loadWizardConfig(configPath);
225
+ } catch (error) {
226
+ return { valid: false, errors: [error.message], config: null };
227
+ }
228
+ const schemaResult = validateSchemaAndResolveEnvVars(config, shouldResolveEnvVars);
229
+ if (!schemaResult.valid) {
230
+ return schemaResult;
231
+ }
232
+ config = schemaResult.config;
233
+ const errors = await performSemanticValidations(config, configPath, validateFilePaths);
234
+ return { valid: errors.length === 0, errors, config };
235
+ }
236
+
237
+ /**
238
+ * Display validation results to console
239
+ * @function displayValidationResults
240
+ * @param {Object} result - Validation result
241
+ * @param {boolean} result.valid - Whether validation passed
242
+ * @param {string[]} result.errors - Array of error messages
243
+ */
244
+ function displayValidationResults(result) {
245
+ const chalk = require('chalk');
246
+ if (result.valid) {
247
+ // eslint-disable-next-line no-console
248
+ console.log(chalk.green('Wizard configuration is valid'));
249
+ } else {
250
+ // eslint-disable-next-line no-console
251
+ console.log(chalk.red('✗ Wizard configuration validation failed:'));
252
+ result.errors.forEach(error => {
253
+ // eslint-disable-next-line no-console
254
+ console.log(chalk.red(` - ${error}`));
255
+ });
256
+ }
257
+ }
258
+
259
+ module.exports = {
260
+ loadWizardConfig,
261
+ validateWizardConfig,
262
+ validateWizardConfigSchema,
263
+ resolveEnvVar,
264
+ resolveEnvVarsInObject,
265
+ formatValidationErrors,
266
+ displayValidationResults
267
+ };
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "@aifabrix/builder",
3
- "version": "2.32.2",
3
+ "version": "2.33.0",
4
4
  "description": "AI Fabrix Local Fabric & Deployment SDK",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
7
- "aifabrix": "bin/aifabrix.js"
7
+ "aifabrix": "bin/aifabrix.js",
8
+ "aifx": "bin/aifabrix.js"
8
9
  },
9
10
  "scripts": {
10
11
  "test": "node tests/scripts/test-wrapper.js",
12
+ "test:ci": "bash tests/scripts/ci-simulate.sh",
11
13
  "test:coverage": "jest --config jest.config.coverage.js --coverage --runInBand",
12
14
  "test:coverage:nyc": "nyc --reporter=text --reporter=lcov --reporter=html jest --config jest.config.coverage.js --runInBand",
13
15
  "test:watch": "jest --watch",
@@ -18,11 +18,11 @@ npm install -g @aifabrix/builder
18
18
  # Check your environment
19
19
  aifabrix doctor
20
20
 
21
- # Login to controller
22
- aifabrix login --method device --environment dev --offline --controller http://localhost:3000
21
+ # Login to controller (offline tokens are default; sets controller and environment in config)
22
+ aifabrix login --method device --environment dev --controller http://localhost:3000
23
23
 
24
- # Register your application (gets you credentials automatically)
25
- aifabrix app register {{appName}} --environment dev
24
+ # Register your application (gets you credentials; uses controller and environment from config)
25
+ aifabrix app register {{appName}}
26
26
  ```
27
27
 
28
28
  ### 3. Build & Run Locally
@@ -38,7 +38,7 @@ aifabrix resolve {{appName}}
38
38
  aifabrix run {{appName}}
39
39
  ```
40
40
 
41
- **Access your app:** http://localhost:{{port}}
41
+ **Access your app:** http://localhost:{{localPort}}
42
42
 
43
43
  **View logs:**
44
44
  ```bash
@@ -60,8 +60,8 @@ aifabrix build {{appName}} --tag v1.0.0
60
60
  # Push to registry
61
61
  aifabrix push {{appName}} --registry {{registry}} --tag "v1.0.0,latest"
62
62
 
63
- # Deploy to miso-controller
64
- aifabrix deploy {{appName}} --controller https://controller.aifabrix.ai --environment dev
63
+ # Deploy (controller and environment from config; set via aifabrix login or aifabrix auth config)
64
+ aifabrix deploy {{appName}}
65
65
  ```
66
66
 
67
67
  ---
@@ -74,7 +74,7 @@ After registering your app, you automatically get credentials in your secret fil
74
74
 
75
75
  **Rotate credentials if needed:**
76
76
  ```bash
77
- aifabrix app rotate-secret {{appName}} --environment dev
77
+ aifabrix app rotate-secret {{appName}}
78
78
  ```
79
79
 
80
80
  ---
@@ -95,12 +95,12 @@ aifabrix resolve {{appName}} # Generate .env file
95
95
  aifabrix json {{appName}} # Preview deployment JSON
96
96
  aifabrix genkey {{appName}} # Generate deployment key
97
97
  aifabrix push {{appName}} --registry {{registry}} # Push to ACR
98
- aifabrix deploy {{appName}} --controller <url> # Deploy to Azure
98
+ aifabrix deploy {{appName}} # Deploy (controller/env from config)
99
99
 
100
100
  # Management
101
- aifabrix app register {{appName}} --environment dev
102
- aifabrix app list --environment dev
103
- aifabrix app rotate-secret {{appName}} --environment dev
101
+ aifabrix app register {{appName}}
102
+ aifabrix app list
103
+ aifabrix app rotate-secret {{appName}}
104
104
 
105
105
  # Utilities
106
106
  aifabrix doctor # Check environment
@@ -119,7 +119,7 @@ aifabrix build {{appName}} --language typescript # Override language detection
119
119
  ### Run Options
120
120
 
121
121
  ```bash
122
- aifabrix run {{appName}} --port {{port}} # Custom port
122
+ aifabrix run {{appName}} --port {{localPort}} # Override local port
123
123
  aifabrix run {{appName}} --debug # Debug output
124
124
  ```
125
125
 
@@ -133,8 +133,8 @@ aifabrix push {{appName}} --registry {{registry}} --tag "v1.0.0,latest,stable"
133
133
  ### Deploy Options
134
134
 
135
135
  ```bash
136
- aifabrix deploy {{appName}} --controller <url> --environment dev
137
- aifabrix deploy {{appName}} --controller <url> --environment dev --no-poll
136
+ aifabrix deploy {{appName}} # Uses controller and environment from config
137
+ aifabrix deploy {{appName}} --no-poll # Deploy without polling for status
138
138
  ```
139
139
 
140
140
  ### Login Methods
@@ -159,13 +159,15 @@ aifabrix-home: "/custom/path"
159
159
  aifabrix-secrets: "/path/to/secrets.yaml"
160
160
  ```
161
161
 
162
+ Controller URL and environment (for `deploy`, `app register`, etc.) are set via `aifabrix login` or `aifabrix auth config --set-controller <url> --set-environment <env>`.
163
+
162
164
  ---
163
165
 
164
166
  ## Troubleshooting
165
167
 
166
168
  - **"Docker not running"** → Start Docker Desktop
167
169
  - **"Not logged in"** → Run `aifabrix login` first
168
- - **"Port already in use"** → Use `--port` flag or change `build.localPort` in `variables.yaml` (default: {{port}})
170
+ - **"Port already in use"** → Use `aifabrix run {{appName}} --port <port>` or set `build.localPort` in `variables.yaml` (default: {{localPort}})
169
171
  - **"Authentication failed"** → Run `aifabrix login` again
170
172
  - **"Build fails"** → Check Docker is running and `aifabrix-secrets` in `config.yaml` is configured correctly
171
173
  - **"Can't connect"** → Verify infrastructure is running{{#if hasDatabase}} and PostgreSQL is accessible{{/if}}
@@ -203,4 +205,4 @@ aifabrix genkey {{appName}}
203
205
 
204
206
  ---
205
207
 
206
- **Application**: {{appName}} | **Port**: {{port}} | **Registry**: {{registry}} | **Image**: {{imageName}}:latest
208
+ **Application**: {{appName}} | **Port**: {{localPort}} | **Registry**: {{registry}} | **Image**: {{imageName}}:latest
@@ -21,7 +21,7 @@ ONBOARDING_INFRASTRUCTURE_NAME=
21
21
  # Password for the initial administrator user (username: admin)
22
22
  ONBOARDING_ADMIN_PASSWORD=kv://miso-controller-admin-passwordKeyVault
23
23
 
24
- # Optional admin email for onboarding (default: admin@aifabrix.ai)
24
+ # Optional admin email for onboarding (default: admin@aifabrix.dev)
25
25
  ONBOARDING_ADMIN_EMAIL=kv://miso-controller-admin-emailKeyVault
26
26
 
27
27
  # =============================================================================
@@ -2,37 +2,37 @@ roles:
2
2
  - name: 'AI Fabrix Platform Admin'
3
3
  value: 'aifabrix-platform-admin'
4
4
  description: 'Full platform infrastructure management and enterprise controller access'
5
- Groups: ['AI-Fabrix-Platform-Admins']
5
+ groups: ['AI-Fabrix-Platform-Admins']
6
6
 
7
7
  - name: 'AI Fabrix Security Admin'
8
8
  value: 'aifabrix-security-admin'
9
9
  description: 'Security and compliance management for enterprise controller'
10
- Groups: ['AI-Fabrix-Security-Admins']
10
+ groups: ['AI-Fabrix-Security-Admins']
11
11
 
12
12
  - name: 'AI Fabrix Infrastructure Admin'
13
13
  value: 'aifabrix-infrastructure-admin'
14
14
  description: 'Infrastructure deployment and management across environments'
15
- Groups: ['AI-Fabrix-Infrastructure-Admins']
15
+ groups: ['AI-Fabrix-Infrastructure-Admins']
16
16
 
17
17
  - name: 'AI Fabrix Deployment Admin'
18
18
  value: 'aifabrix-deployment-admin'
19
19
  description: 'Application deployment orchestration and environment management'
20
- Groups: ['AI-Fabrix-Deployment-Admins']
20
+ groups: ['AI-Fabrix-Deployment-Admins']
21
21
 
22
22
  - name: 'AI Fabrix Compliance Admin'
23
23
  value: 'aifabrix-compliance-admin'
24
24
  description: 'ISO 27001 compliance monitoring and audit management'
25
- Groups: ['AI-Fabrix-Compliance-Admins']
25
+ groups: ['AI-Fabrix-Compliance-Admins']
26
26
 
27
27
  - name: 'AI Fabrix Developer'
28
28
  value: 'aifabrix-developer'
29
29
  description: 'Developer access to deploy applications via GitHub Actions'
30
- Groups: ['AI-Fabrix-Developers']
30
+ groups: ['AI-Fabrix-Developers']
31
31
 
32
32
  - name: 'AI Fabrix Observer'
33
33
  value: 'aifabrix-observer'
34
34
  description: 'Read-only access to monitoring, logs, and compliance reports'
35
- Groups: ['AI-Fabrix-Observers']
35
+ groups: ['AI-Fabrix-Observers']
36
36
 
37
37
  permissions:
38
38
  # Service User Management
@@ -0,0 +1,99 @@
1
+ # {{displayName}}
2
+
3
+ {{description}}
4
+
5
+ ## System Information
6
+
7
+ - **System Key**: `{{systemKey}}`
8
+ - **System Type**: `{{systemType}}`
9
+ - **Datasources**: {{datasourceCount}}
10
+
11
+ ## Files
12
+
13
+ - `variables.yaml` – Application configuration with `app` and `externalIntegration` blocks
14
+ - `{{systemKey}}-system.json` – External system definition (authentication, OpenAPI/MCP, RBAC)
15
+ {{#each datasources}}
16
+ - `{{fileName}}` – Datasource: {{displayName}}
17
+ {{/each}}
18
+ - `env.template` – Environment variables template (secrets, API keys)
19
+ - `{{systemKey}}-deploy.json` – Deployment manifest (generated by `aifabrix json`)
20
+
21
+ Optional: `rbac.yaml` – Roles and permissions merged into the system when present.
22
+
23
+ ## Quick Start
24
+
25
+ ### 1. Create External System
26
+
27
+ ```bash
28
+ aifabrix create {{appName}} --type external
29
+ ```
30
+
31
+ Or use the interactive wizard:
32
+
33
+ ```bash
34
+ aifabrix wizard --app {{appName}}
35
+ ```
36
+
37
+ ### 2. Configure Authentication and Datasources
38
+
39
+ Edit files in `integration/{{appName}}/`:
40
+
41
+ - **Authentication**: `{{systemKey}}-system.json` (auth type, credentials placeholders)
42
+ - **Field mappings**: `{{systemKey}}-datasource-*.json` (dimensions, attributes, operations)
43
+
44
+ ### 3. Validate Configuration
45
+
46
+ ```bash
47
+ aifabrix validate {{appName}} --type external
48
+ ```
49
+
50
+ ### 4. Generate Deployment Manifest
51
+
52
+ ```bash
53
+ aifabrix json {{appName}} --type external
54
+ ```
55
+
56
+ This creates `{{systemKey}}-deploy.json` in `integration/{{appName}}/`.
57
+
58
+ ### 5. Deploy
59
+
60
+ Controller URL and environment are read from config. Configure and log in first:
61
+
62
+ ```bash
63
+ aifabrix auth config --set-controller <url> --set-environment dev
64
+ aifabrix login
65
+ ```
66
+
67
+ Then deploy:
68
+
69
+ ```bash
70
+ aifabrix deploy {{appName}}
71
+ ```
72
+
73
+ ## Testing
74
+
75
+ ### Unit Tests (Local Validation, No API)
76
+
77
+ ```bash
78
+ aifabrix test {{appName}}
79
+ ```
80
+
81
+ ### Integration Tests (Via Dataplane)
82
+
83
+ ```bash
84
+ aifabrix test-integration {{appName}}
85
+ ```
86
+
87
+ ## Deployment
88
+
89
+ Deploy via miso-controller pipeline (same as regular apps). Auth and controller come from `aifabrix login` and `aifabrix auth config`:
90
+
91
+ ```bash
92
+ aifabrix deploy {{appName}}
93
+ ```
94
+
95
+ ## Troubleshooting
96
+
97
+ - **Validation errors**: Run `aifabrix validate {{appName}} --type external` to see schema and manifest errors.
98
+ - **Deployment / auth**: Run `aifabrix auth config --set-controller <url> --set-environment <env>` and `aifabrix login` before `aifabrix deploy`.
99
+ - **File not found**: Run commands from the project root (where `package.json` and `integration/` live).
@@ -39,7 +39,7 @@
39
39
  "name": "{{name}}",
40
40
  "value": "{{value}}",
41
41
  "description": "{{description}}"{{#if Groups}},
42
- "Groups": [{{#each Groups}}"{{this}}"{{#unless @last}}, {{/unless}}{{/each}}]{{/if}}
42
+ "groups": [{{#each Groups}}"{{this}}"{{#unless @last}}, {{/unless}}{{/each}}]{{/if}}
43
43
  }{{#unless @last}},{{/unless}}
44
44
  {{/each}}
45
45
  ]{{/if}}{{#if permissions}},
@@ -96,6 +96,41 @@ services:
96
96
  networks:
97
97
  - {{networkName}}
98
98
 
99
+ {{#if traefik.enabled}}
100
+ # Traefik Reverse Proxy
101
+ traefik:
102
+ image: traefik:v3.6
103
+ container_name: {{#if (eq devId 0)}}aifabrix-traefik{{else}}aifabrix-dev{{devId}}-traefik{{/if}}
104
+ command:
105
+ - "--providers.docker=true"
106
+ - "--providers.docker.exposedbydefault=false"
107
+ - "--providers.docker.network={{networkName}}"
108
+ - "--entrypoints.web.address=:80"
109
+ - "--entrypoints.websecure.address=:443"
110
+ {{#if traefik.certStore}}
111
+ {{#if traefik.certFile}}
112
+ - "--certificatesstores.{{traefik.certStore}}.defaultcertificate.certfile={{traefik.certFile}}"
113
+ {{/if}}
114
+ {{#if traefik.keyFile}}
115
+ - "--certificatesstores.{{traefik.certStore}}.defaultcertificate.keyfile={{traefik.keyFile}}"
116
+ {{/if}}
117
+ {{/if}}
118
+ ports:
119
+ - "{{traefikHttpPort}}:80"
120
+ - "{{traefikHttpsPort}}:443"
121
+ volumes:
122
+ - /var/run/docker.sock:/var/run/docker.sock:ro
123
+ {{#if traefik.certFile}}
124
+ - {{traefik.certFile}}:{{traefik.certFile}}:ro
125
+ {{/if}}
126
+ {{#if traefik.keyFile}}
127
+ - {{traefik.keyFile}}:{{traefik.keyFile}}:ro
128
+ {{/if}}
129
+ networks:
130
+ - {{networkName}}
131
+ restart: unless-stopped
132
+ {{/if}}
133
+
99
134
  volumes:
100
135
  {{#if (eq devId 0)}}postgres_data{{else}}dev{{devId}}_postgres_data{{/if}}:
101
136
  name: {{#if (eq devId 0)}}infra_postgres_data{{else}}infra_dev{{devId}}_postgres_data{{/if}}
@@ -6,6 +6,32 @@ services:
6
6
  {{app.key}}:
7
7
  image: {{image.name}}:{{image.tag}}
8
8
  container_name: {{containerName}}
9
+ {{#if traefik.enabled}}
10
+ labels:
11
+ - "traefik.enable=true"
12
+ - "traefik.http.routers.{{app.key}}.rule=Host(`{{traefik.host}}`) && PathPrefix(`{{traefik.path}}`)"
13
+ {{#if traefik.tls}}
14
+ - "traefik.http.routers.{{app.key}}.entrypoints=websecure"
15
+ - "traefik.http.routers.{{app.key}}.tls=true"
16
+ {{#if traefik.certStore}}
17
+ - "traefik.http.routers.{{app.key}}.tls.certstore={{traefik.certStore}}"
18
+ {{/if}}
19
+ {{else}}
20
+ - "traefik.http.routers.{{app.key}}.entrypoints=web"
21
+ {{/if}}
22
+ - "traefik.http.services.{{app.key}}.loadbalancer.server.port={{containerPort}}"
23
+ {{#unless (eq traefik.path "/")}}
24
+ - "traefik.http.middlewares.{{app.key}}-stripprefix.stripprefix.prefixes={{traefik.path}}"
25
+ - "traefik.http.routers.{{app.key}}.middlewares={{app.key}}-stripprefix"
26
+ {{/unless}}
27
+ {{/if}}
28
+ {{#if traefik.enabled}}
29
+ {{#unless (eq traefik.path "/")}}
30
+ environment:
31
+ - BASE_PATH={{traefik.path}}
32
+ - X_FORWARDED_PREFIX={{traefik.path}}
33
+ {{/unless}}
34
+ {{/if}}
9
35
  env_file:
10
36
  - {{envFile}}
11
37
  ports:
@@ -6,6 +6,32 @@ services:
6
6
  {{app.key}}:
7
7
  image: {{image.name}}:{{image.tag}}
8
8
  container_name: {{containerName}}
9
+ {{#if traefik.enabled}}
10
+ labels:
11
+ - "traefik.enable=true"
12
+ - "traefik.http.routers.{{app.key}}.rule=Host(`{{traefik.host}}`) && PathPrefix(`{{traefik.path}}`)"
13
+ {{#if traefik.tls}}
14
+ - "traefik.http.routers.{{app.key}}.entrypoints=websecure"
15
+ - "traefik.http.routers.{{app.key}}.tls=true"
16
+ {{#if traefik.certStore}}
17
+ - "traefik.http.routers.{{app.key}}.tls.certstore={{traefik.certStore}}"
18
+ {{/if}}
19
+ {{else}}
20
+ - "traefik.http.routers.{{app.key}}.entrypoints=web"
21
+ {{/if}}
22
+ - "traefik.http.services.{{app.key}}.loadbalancer.server.port={{containerPort}}"
23
+ {{#unless (eq traefik.path "/")}}
24
+ - "traefik.http.middlewares.{{app.key}}-stripprefix.stripprefix.prefixes={{traefik.path}}"
25
+ - "traefik.http.routers.{{app.key}}.middlewares={{app.key}}-stripprefix"
26
+ {{/unless}}
27
+ {{/if}}
28
+ {{#if traefik.enabled}}
29
+ {{#unless (eq traefik.path "/")}}
30
+ environment:
31
+ - BASE_PATH={{traefik.path}}
32
+ - X_FORWARDED_PREFIX={{traefik.path}}
33
+ {{/unless}}
34
+ {{/if}}
9
35
  env_file:
10
36
  - {{envFile}}
11
37
  ports: