@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.
- package/.cursor/rules/project-rules.mdc +8 -0
- package/README.md +36 -8
- package/bin/aifabrix.js +6 -8
- package/integration/hubspot/README.md +8 -7
- package/integration/hubspot/companies.json +2048 -0
- package/integration/hubspot/create-hubspot.js +665 -0
- package/integration/hubspot/{hubspot-deploy-company.json → hubspot-datasource-company.json} +1 -1
- package/integration/hubspot/{hubspot-deploy-contact.json → hubspot-datasource-contact.json} +1 -1
- package/integration/hubspot/{hubspot-deploy-deal.json → hubspot-datasource-deal.json} +1 -1
- package/integration/hubspot/hubspot-deploy.json +832 -81
- package/integration/hubspot/hubspot-system.json +99 -0
- package/integration/hubspot/test-artifacts/wizard-hubspot-credential-real.yaml +20 -0
- package/integration/hubspot/test-artifacts/wizard-hubspot-env-vars.yaml +9 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-add-datasource.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-app-name.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-credential-create.yaml +7 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-credential-select.yaml +7 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-known-platform.yaml +4 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-missing-app.yaml +4 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-missing-source.yaml +2 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-mode.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-openapi-file.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-openapi-url.yaml +4 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-source.yaml +4 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-array-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-key-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-path-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-rbac-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +5 -0
- package/integration/hubspot/test-dataplane-down-helpers.js +246 -0
- package/integration/hubspot/test-dataplane-down-tests.js +419 -0
- package/integration/hubspot/test-dataplane-down.js +157 -0
- package/integration/hubspot/test.js +1517 -0
- package/integration/hubspot/variables.yaml +4 -4
- package/integration/hubspot/wizard-hubspot-e2e.yaml +16 -0
- package/integration/hubspot/wizard-hubspot-platform.yaml +8 -0
- package/lib/api/applications.api.js +1 -0
- package/lib/api/index.js +10 -5
- package/lib/api/types/wizard.types.js +176 -38
- package/lib/api/wizard.api.js +207 -38
- package/lib/app/deploy.js +116 -54
- package/lib/app/display.js +6 -5
- package/lib/app/dockerfile.js +2 -1
- package/lib/app/list.js +78 -37
- package/lib/app/prompts.js +9 -5
- package/lib/app/readme.js +41 -112
- package/lib/app/register.js +44 -9
- package/lib/app/rotate-secret.js +50 -32
- package/lib/cli.js +243 -65
- package/lib/commands/app.js +4 -9
- package/lib/commands/auth-config.js +125 -0
- package/lib/commands/auth-status.js +261 -0
- package/lib/commands/datasource.js +3 -6
- package/lib/commands/login-credentials.js +4 -4
- package/lib/commands/login-device.js +43 -29
- package/lib/commands/login.js +22 -13
- package/lib/commands/wizard-config-normalizer.js +92 -0
- package/lib/commands/wizard-core.js +515 -0
- package/lib/commands/wizard-dataplane.js +122 -0
- package/lib/commands/wizard-headless.js +115 -0
- package/lib/commands/wizard.js +129 -357
- package/lib/core/config.js +46 -0
- package/lib/core/secrets.js +3 -22
- package/lib/core/templates-env.js +1 -1
- package/lib/datasource/deploy.js +34 -23
- package/lib/datasource/list.js +8 -6
- package/lib/deployment/deployer.js +25 -0
- package/lib/deployment/environment.js +10 -13
- package/lib/external-system/delete.js +151 -0
- package/lib/external-system/deploy.js +54 -378
- package/lib/external-system/download-helpers.js +45 -65
- package/lib/external-system/download.js +34 -13
- package/lib/external-system/generator.js +11 -7
- package/lib/external-system/test-auth.js +5 -3
- package/lib/generator/builders.js +3 -1
- package/lib/generator/external-controller-manifest.js +157 -0
- package/lib/generator/external-schema-utils.js +236 -0
- package/lib/generator/external.js +55 -3
- package/lib/generator/index.js +22 -10
- package/lib/generator/wizard-prompts.js +33 -10
- package/lib/generator/wizard.js +69 -86
- package/lib/infrastructure/compose.js +100 -0
- package/lib/infrastructure/helpers.js +139 -0
- package/lib/infrastructure/index.js +52 -311
- package/lib/infrastructure/services.js +168 -0
- package/lib/schema/application-schema.json +24 -5
- package/lib/schema/external-datasource.schema.json +303 -17
- package/lib/schema/external-system.schema.json +1 -1
- package/lib/schema/wizard-config.schema.json +234 -0
- package/lib/utils/api.js +37 -42
- package/lib/utils/app-existence.js +42 -0
- package/lib/utils/app-register-config.js +7 -2
- package/lib/utils/app-register-display.js +2 -1
- package/lib/utils/auth-config-validator.js +92 -0
- package/lib/utils/cli-utils.js +3 -1
- package/lib/utils/command-header.js +43 -0
- package/lib/utils/compose-generator.js +113 -70
- package/lib/utils/controller-url.js +115 -0
- package/lib/utils/dataplane-health.js +115 -0
- package/lib/utils/dataplane-resolver.js +29 -0
- package/lib/utils/dev-config.js +6 -2
- package/lib/utils/env-copy.js +2 -1
- package/lib/utils/env-map.js +2 -1
- package/lib/utils/env-ports.js +2 -1
- package/lib/utils/env-template.js +1 -1
- package/lib/utils/error-formatter.js +149 -28
- package/lib/utils/external-readme.js +125 -0
- package/lib/utils/help-builder.js +190 -0
- package/lib/utils/infra-status.js +13 -3
- package/lib/utils/paths.js +17 -2
- package/lib/utils/port-resolver.js +111 -0
- package/lib/utils/secrets-helpers.js +3 -15
- package/lib/utils/secrets-utils.js +2 -2
- package/lib/utils/token-manager.js +69 -4
- package/lib/utils/variable-transformer.js +7 -2
- package/lib/validation/external-manifest-validator.js +202 -0
- package/lib/validation/validate-display.js +406 -0
- package/lib/validation/validate.js +159 -123
- package/lib/validation/validator.js +38 -4
- package/lib/validation/wizard-config-validator.js +267 -0
- package/package.json +4 -2
- package/templates/applications/README.md.hbs +19 -17
- package/templates/applications/miso-controller/env.template +1 -1
- package/templates/applications/miso-controller/rbac.yaml +7 -7
- package/templates/external-system/README.md.hbs +99 -0
- package/templates/external-system/external-system.json.hbs +1 -1
- package/templates/infra/compose.yaml.hbs +35 -0
- package/templates/python/docker-compose.hbs +26 -0
- 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.
|
|
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 --
|
|
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
|
|
25
|
-
aifabrix app register {{appName}}
|
|
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:{{
|
|
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
|
|
64
|
-
aifabrix deploy {{appName}}
|
|
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}}
|
|
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}}
|
|
98
|
+
aifabrix deploy {{appName}} # Deploy (controller/env from config)
|
|
99
99
|
|
|
100
100
|
# Management
|
|
101
|
-
aifabrix app register {{appName}}
|
|
102
|
-
aifabrix app list
|
|
103
|
-
aifabrix app rotate-secret {{appName}}
|
|
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 {{
|
|
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}}
|
|
137
|
-
aifabrix deploy {{appName}} --
|
|
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
|
|
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**: {{
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
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:
|