@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
package/README.md
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
# AI Fabrix - Builder
|
|
1
|
+
# AI Fabrix - Builder
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@aifabrix/builder)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Install the AI Fabrix platform and test it locally. Then add external integrations or build your own applications.
|
|
7
|
+
|
|
8
|
+
← **Full documentation:** [docs/README.md](docs/README.md) (table of contents for all guides)
|
|
9
|
+
|
|
10
|
+
---
|
|
7
11
|
|
|
8
12
|
## Install
|
|
9
13
|
|
|
@@ -11,126 +15,86 @@ Local development infrastructure + Azure deployment tool.
|
|
|
11
15
|
npm install -g @aifabrix/builder
|
|
12
16
|
```
|
|
13
17
|
|
|
14
|
-
|
|
18
|
+
**Alias:** You can use `aifx` instead of `aifabrix` in any command.
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
aifabrix up # Start Postgres + Redis
|
|
18
|
-
aifabrix create myapp # Create your app
|
|
19
|
-
aifabrix build myapp # Build Docker image
|
|
20
|
-
aifabrix run myapp # Run locally
|
|
21
|
-
# Stop the app (optionally remove its data volume)
|
|
22
|
-
aifabrix down myapp
|
|
23
|
-
# aifabrix down myapp --volumes
|
|
24
|
-
```
|
|
20
|
+
---
|
|
25
21
|
|
|
26
|
-
|
|
22
|
+
## Goal 1: Start and test the AI Fabrix platform
|
|
27
23
|
|
|
28
|
-
|
|
24
|
+
Get the platform running locally so you can try it.
|
|
29
25
|
|
|
30
|
-
|
|
31
|
-
- **Auto-generated Dockerfiles** - TypeScript and Python templates
|
|
32
|
-
- **Environment variable management** - Secret resolution with kv:// references
|
|
33
|
-
- **Azure deployment pipeline** - Push to ACR and deploy via controller
|
|
26
|
+
1. **Start local infrastructure** (Postgres, Redis, optional Traefik):
|
|
34
27
|
|
|
35
|
-
|
|
28
|
+
```bash
|
|
29
|
+
aifabrix up-infra
|
|
30
|
+
```
|
|
36
31
|
|
|
37
|
-
|
|
32
|
+
2. **Start the platform** (Keycloak, Miso Controller, Dataplane) from community images:
|
|
38
33
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
aifabrix up-miso # Install Keycloak + Miso Controller from images (auto-generated secrets for testing)
|
|
43
|
-
```
|
|
34
|
+
```bash
|
|
35
|
+
aifabrix up-platform
|
|
36
|
+
```
|
|
44
37
|
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
# Keycloak for authentication
|
|
48
|
-
aifabrix create keycloak --port 8082 --database --template keycloak
|
|
49
|
-
aifabrix build keycloak
|
|
50
|
-
aifabrix run keycloak
|
|
51
|
-
|
|
52
|
-
# Miso Controller for Azure deployments
|
|
53
|
-
aifabrix create miso-controller --port 3000 --database --redis --template miso-controller
|
|
54
|
-
aifabrix build miso-controller
|
|
55
|
-
aifabrix run miso-controller
|
|
56
|
-
```
|
|
38
|
+
Or run platform apps separately: `aifabrix up-miso` then `aifabrix up-dataplane`. Infra must be up first.
|
|
57
39
|
|
|
58
|
-
**
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
40
|
+
3. **Configure secrets** – You need either **OpenAI** or **Azure OpenAI**:
|
|
41
|
+
|
|
42
|
+
- **OpenAI:** set your API key:
|
|
43
|
+
```bash
|
|
44
|
+
aifabrix secrets set secrets-openaiApiKeyVault <your-openai-secret-key>
|
|
45
|
+
```
|
|
46
|
+
- **Azure OpenAI:** set endpoint and API key:
|
|
47
|
+
```bash
|
|
48
|
+
aifabrix secrets set azure-openaiapi-urlKeyVault <your-azure-openai-endpoint-url>
|
|
49
|
+
aifabrix secrets set secrets-azureOpenaiApiKeyVault <your-azure-openai-secret-key>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Secrets are stored in `~/.aifabrix/secrets.local.yaml` or the file from `aifabrix-secrets` in your config (e.g. `builder/secrets.local.yaml`).
|
|
53
|
+
|
|
54
|
+
→ [Infrastructure guide](docs/infrastructure.md)
|
|
63
55
|
|
|
64
|
-
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Goal 2: External system integration
|
|
59
|
+
|
|
60
|
+
Create and deploy an external system (e.g. HubSpot): wizard or manual setup, then validate and deploy.
|
|
61
|
+
|
|
62
|
+
**Example: HubSpot**
|
|
63
|
+
|
|
64
|
+
- Create: `aifabrix create hubspot-test --type external` (or `aifabrix wizard` for guided setup).
|
|
65
|
+
- Configure auth and datasources under `integration/hubspot-test/`.
|
|
66
|
+
- Validate: `aifabrix validate hubspot-test`
|
|
67
|
+
- Deploy: `aifabrix deploy hubspot-test`
|
|
68
|
+
|
|
69
|
+
→ [External systems guide](docs/external-systems.md) · [Wizard](docs/wizard.md)
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Goal 3: Build your own application
|
|
74
|
+
|
|
75
|
+
Create, configure, and run your own AI Fabrix application locally or deploy it (create app → configure → build → run / deploy).
|
|
76
|
+
|
|
77
|
+
→ [Your own applications](docs/your-own-applications.md)
|
|
78
|
+
|
|
79
|
+
---
|
|
65
80
|
|
|
66
81
|
## Documentation
|
|
67
82
|
|
|
68
|
-
|
|
69
|
-
- [Infrastructure](docs/infrastructure.md) - What runs and why
|
|
70
|
-
- [Configuration](docs/configuration.md) - Config file reference
|
|
71
|
-
- [Building](docs/building.md) - Build process explained
|
|
72
|
-
- [Running](docs/running.md) - Run apps locally
|
|
73
|
-
- [Deploying](docs/deploying.md) - Deploy to Azure
|
|
74
|
-
- [CLI Reference](docs/cli-reference.md) - All commands
|
|
75
|
-
|
|
76
|
-
## How It Works
|
|
77
|
-
|
|
78
|
-
1. **Infrastructure** - Minimal baseline (Postgres + Redis)
|
|
79
|
-
2. **Create** - Generate config files for your app
|
|
80
|
-
3. **Build** - Auto-detect runtime and build Docker image
|
|
81
|
-
4. **Run** - Start locally, connected to infrastructure
|
|
82
|
-
5. **Deploy** - Push to ACR and deploy via controller
|
|
83
|
-
|
|
84
|
-
```mermaid
|
|
85
|
-
%%{init: {
|
|
86
|
-
"theme": "base",
|
|
87
|
-
"themeVariables": {
|
|
88
|
-
"fontFamily": "Poppins, Arial Rounded MT Bold, Arial, sans-serif",
|
|
89
|
-
"fontSize": "16px",
|
|
90
|
-
"background": "#FFFFFF",
|
|
91
|
-
"primaryColor": "#F8FAFC",
|
|
92
|
-
"primaryTextColor": "#0B0E15",
|
|
93
|
-
"primaryBorderColor": "#E2E8F0",
|
|
94
|
-
"lineColor": "#E2E8F0",
|
|
95
|
-
"textColor": "#0B0E15",
|
|
96
|
-
"borderRadius": 16
|
|
97
|
-
},
|
|
98
|
-
"flowchart": {
|
|
99
|
-
"curve": "linear",
|
|
100
|
-
"nodeSpacing": 34,
|
|
101
|
-
"rankSpacing": 34,
|
|
102
|
-
"padding": 10
|
|
103
|
-
}
|
|
104
|
-
}}%%
|
|
105
|
-
|
|
106
|
-
flowchart TD
|
|
107
|
-
|
|
108
|
-
%% =======================
|
|
109
|
-
%% Styles
|
|
110
|
-
%% =======================
|
|
111
|
-
classDef base fill:#FFFFFF,color:#0B0E15,stroke:#E2E8F0,stroke-width:1.5px;
|
|
112
|
-
classDef primary fill:#0062FF,color:#ffffff,stroke-width:0px;
|
|
113
|
-
|
|
114
|
-
%% =======================
|
|
115
|
-
%% Flow
|
|
116
|
-
%% =======================
|
|
117
|
-
Install[Install CLI]:::primary --> Up[Start Infrastructure]:::base
|
|
118
|
-
Up --> Create[Create App]:::base
|
|
119
|
-
Create --> Build[Build Image]:::base
|
|
120
|
-
Build --> Run[Run Locally]:::base
|
|
121
|
-
Run --> Deploy[Deploy to Azure]:::primary
|
|
122
|
-
```
|
|
83
|
+
All guides and references are listed in **[docs/README.md](docs/README.md)** (table of contents).
|
|
123
84
|
|
|
124
|
-
|
|
85
|
+
- [CLI Reference](docs/cli-reference.md) – All commands
|
|
86
|
+
- [Infrastructure](docs/infrastructure.md) – What runs and why
|
|
87
|
+
- [Configuration](docs/configuration.md) – Config files
|
|
125
88
|
|
|
126
|
-
|
|
127
|
-
- **Coverage**: `npm run test:coverage` — runs tests with coverage through the same wrapper. May take 3–5 minutes for the full suite. If the process exits with a signal after "Ran all test suites", the wrapper treats it as success and coverage is written to `coverage/`. Use `test:coverage:nyc` only if you need nyc-specific reporters.
|
|
89
|
+
---
|
|
128
90
|
|
|
129
91
|
## Requirements
|
|
130
92
|
|
|
131
|
-
- **Docker Desktop**
|
|
132
|
-
- **Node.js 18+**
|
|
133
|
-
- **Azure CLI**
|
|
93
|
+
- **Docker Desktop** – For running containers
|
|
94
|
+
- **Node.js 18+** – For running the CLI
|
|
95
|
+
- **Azure CLI** – For deploying to Azure (optional)
|
|
96
|
+
|
|
97
|
+
---
|
|
134
98
|
|
|
135
99
|
## License
|
|
136
100
|
|
|
@@ -728,7 +728,7 @@ async function runCommandWithErrorHandling(command, args, options, appName) {
|
|
|
728
728
|
const errorOutput = `${result.stdout}\n${result.stderr}`;
|
|
729
729
|
if (appName && (errorOutput.includes('not found') ||
|
|
730
730
|
(errorOutput.includes('External system') && errorOutput.includes('not found')))) {
|
|
731
|
-
|
|
731
|
+
logInfo(`System ${appName} not deployed on dataplane; download step omitted (optional).`);
|
|
732
732
|
return { skipped: true };
|
|
733
733
|
}
|
|
734
734
|
throw new Error(`${command} failed: ${result.stderr || result.stdout}`);
|
package/lib/api/pipeline.api.js
CHANGED
|
@@ -81,9 +81,33 @@ async function getPipelineHealth(controllerUrl, envKey) {
|
|
|
81
81
|
return await client.get(`/api/v1/pipeline/${envKey}/health`);
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Publish one external system via dataplane pipeline endpoint
|
|
86
|
+
* POST /api/v1/pipeline/publish
|
|
87
|
+
* Request body: external system JSON (external-system.schema.json). Optional field in body:
|
|
88
|
+
* generateMcpContract (boolean, default true). Optional: generateOpenApiContract (boolean).
|
|
89
|
+
* Do not use query parameters for MCP; use the field in the body only.
|
|
90
|
+
*
|
|
91
|
+
* @async
|
|
92
|
+
* @function publishSystemViaPipeline
|
|
93
|
+
* @param {string} dataplaneUrl - Dataplane base URL
|
|
94
|
+
* @param {Object} authConfig - Authentication configuration
|
|
95
|
+
* @param {Object} systemConfig - External system configuration (conforms to external-system.schema.json)
|
|
96
|
+
* @returns {Promise<Object>} Published external system response
|
|
97
|
+
* @throws {Error} If publish fails
|
|
98
|
+
*/
|
|
99
|
+
async function publishSystemViaPipeline(dataplaneUrl, authConfig, systemConfig) {
|
|
100
|
+
const client = new ApiClient(dataplaneUrl, authConfig);
|
|
101
|
+
return await client.post('/api/v1/pipeline/publish', {
|
|
102
|
+
body: systemConfig
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
84
106
|
/**
|
|
85
107
|
* Publish datasource via dataplane pipeline endpoint
|
|
86
108
|
* POST /api/v1/pipeline/{systemKey}/publish
|
|
109
|
+
* No generateMcpContract for this endpoint; dataplane always uses default (MCP generated).
|
|
110
|
+
*
|
|
87
111
|
* @async
|
|
88
112
|
* @function publishDatasourceViaPipeline
|
|
89
113
|
* @param {string} dataplaneUrl - Dataplane base URL
|
|
@@ -169,11 +193,15 @@ async function deployDatasourceViaPipeline(dataplaneUrl, systemKey, authConfig,
|
|
|
169
193
|
/**
|
|
170
194
|
* Upload application configuration via dataplane pipeline endpoint
|
|
171
195
|
* POST /api/v1/pipeline/upload
|
|
196
|
+
* Body: { version, application, dataSources }. Include application.generateMcpContract
|
|
197
|
+
* and/or application.generateOpenApiContract to control contract generation when
|
|
198
|
+
* publishing this upload (publish reads from stored config; no query param on publish).
|
|
199
|
+
*
|
|
172
200
|
* @async
|
|
173
201
|
* @function uploadApplicationViaPipeline
|
|
174
202
|
* @param {string} dataplaneUrl - Dataplane base URL
|
|
175
203
|
* @param {Object} authConfig - Authentication configuration
|
|
176
|
-
* @param {Object} applicationSchema -
|
|
204
|
+
* @param {Object} applicationSchema - { version, application, dataSources }; application may include generateMcpContract, generateOpenApiContract
|
|
177
205
|
* @returns {Promise<Object>} Upload response with uploadId
|
|
178
206
|
* @throws {Error} If upload fails
|
|
179
207
|
*/
|
|
@@ -203,20 +231,22 @@ async function validateUploadViaPipeline(dataplaneUrl, uploadId, authConfig) {
|
|
|
203
231
|
/**
|
|
204
232
|
* Publish upload via dataplane pipeline endpoint
|
|
205
233
|
* POST /api/v1/pipeline/upload/{uploadId}/publish
|
|
234
|
+
* No body or query parameters. MCP/OpenAPI generation is taken from the application config
|
|
235
|
+
* that was uploaded (application.generateMcpContract, application.generateOpenApiContract).
|
|
236
|
+
* To control MCP/OpenAPI, include those fields in the application object when calling
|
|
237
|
+
* uploadApplicationViaPipeline.
|
|
238
|
+
*
|
|
206
239
|
* @async
|
|
207
240
|
* @function publishUploadViaPipeline
|
|
208
241
|
* @param {string} dataplaneUrl - Dataplane base URL
|
|
209
242
|
* @param {string} uploadId - Upload ID
|
|
210
243
|
* @param {Object} authConfig - Authentication configuration
|
|
211
|
-
* @param {Object} [options] - Publish options
|
|
212
|
-
* @param {boolean} [options.generateMcpContract] - Generate MCP contract (default: true)
|
|
213
244
|
* @returns {Promise<Object>} Publish response
|
|
214
245
|
* @throws {Error} If publish fails
|
|
215
246
|
*/
|
|
216
|
-
async function publishUploadViaPipeline(dataplaneUrl, uploadId, authConfig
|
|
247
|
+
async function publishUploadViaPipeline(dataplaneUrl, uploadId, authConfig) {
|
|
217
248
|
const client = new ApiClient(dataplaneUrl, authConfig);
|
|
218
|
-
|
|
219
|
-
return await client.post(`/api/v1/pipeline/upload/${uploadId}/publish?generateMcpContract=${generateMcpContract}`);
|
|
249
|
+
return await client.post(`/api/v1/pipeline/upload/${uploadId}/publish`);
|
|
220
250
|
}
|
|
221
251
|
|
|
222
252
|
module.exports = {
|
|
@@ -224,6 +254,7 @@ module.exports = {
|
|
|
224
254
|
deployPipeline,
|
|
225
255
|
getPipelineDeployment,
|
|
226
256
|
getPipelineHealth,
|
|
257
|
+
publishSystemViaPipeline,
|
|
227
258
|
publishDatasourceViaPipeline,
|
|
228
259
|
testDatasourceViaPipeline,
|
|
229
260
|
deployExternalSystemViaPipeline,
|
package/lib/app/display.js
CHANGED
|
@@ -50,7 +50,7 @@ function displayWebappSuccess(appName, config, envConversionMessage) {
|
|
|
50
50
|
|
|
51
51
|
logger.log(chalk.green('\nNext steps:'));
|
|
52
52
|
logger.log(chalk.white('1. Copy env.template to .env and fill in your values'));
|
|
53
|
-
logger.log(chalk.white('2. Run: aifabrix up'));
|
|
53
|
+
logger.log(chalk.white('2. Run: aifabrix up-infra'));
|
|
54
54
|
logger.log(chalk.white('3. Run: aifabrix build ' + appName));
|
|
55
55
|
logger.log(chalk.white('4. Run: aifabrix run ' + appName));
|
|
56
56
|
}
|
package/lib/app/list.js
CHANGED
|
@@ -168,7 +168,7 @@ function displayApplications(applications, environment, controllerUrl) {
|
|
|
168
168
|
const urlAndPort = formatUrlAndPort(app);
|
|
169
169
|
logger.log(`${hasPipeline} ${chalk.cyan(app.key)} - ${app.displayName} (${app.status || 'unknown'})${urlAndPort}`);
|
|
170
170
|
});
|
|
171
|
-
logger.log('');
|
|
171
|
+
logger.log(chalk.gray(' To show details for an app: aifabrix app show <appKey>\n'));
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
/**
|
package/lib/app/run-helpers.js
CHANGED
|
@@ -177,7 +177,7 @@ async function checkPrerequisites(appName, appConfig, debug = false, skipInfraCh
|
|
|
177
177
|
.map(([service, _]) => service);
|
|
178
178
|
|
|
179
179
|
if (unhealthyServices.length > 0) {
|
|
180
|
-
throw new Error(`Infrastructure services not healthy: ${unhealthyServices.join(', ')}\nRun 'aifabrix up' first`);
|
|
180
|
+
throw new Error(`Infrastructure services not healthy: ${unhealthyServices.join(', ')}\nRun 'aifabrix up-infra' first`);
|
|
181
181
|
}
|
|
182
182
|
logger.log(chalk.green('✓ Infrastructure is running'));
|
|
183
183
|
}
|
package/lib/cli/index.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Fabrix Builder CLI Command Definitions
|
|
3
|
+
*
|
|
4
|
+
* This module wires all CLI command setup and re-exports setupCommands,
|
|
5
|
+
* validateCommand, and handleCommandError for backward compatibility.
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview CLI entry for AI Fabrix Builder; command setup orchestration
|
|
8
|
+
* @author AI Fabrix Team
|
|
9
|
+
* @version 2.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { validateCommand, handleCommandError } = require('../utils/cli-utils');
|
|
13
|
+
const { setupAuthCommands } = require('./setup-auth');
|
|
14
|
+
const { setupInfraCommands } = require('./setup-infra');
|
|
15
|
+
const { setupAppCommands } = require('./setup-app');
|
|
16
|
+
const { setupEnvironmentCommands } = require('./setup-environment');
|
|
17
|
+
const { setupUtilityCommands } = require('./setup-utility');
|
|
18
|
+
const { setupDevCommands } = require('./setup-dev');
|
|
19
|
+
const { setupSecretsCommands } = require('./setup-secrets');
|
|
20
|
+
const { setupExternalSystemCommands } = require('./setup-external-system');
|
|
21
|
+
const { setupAppCommands: setupAppManagementCommands } = require('../commands/app');
|
|
22
|
+
const { setupDatasourceCommands } = require('../commands/datasource');
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Sets up all CLI commands on the Commander program instance
|
|
26
|
+
* @param {Command} program - Commander program instance
|
|
27
|
+
*/
|
|
28
|
+
function setupCommands(program) {
|
|
29
|
+
setupInfraCommands(program);
|
|
30
|
+
setupAuthCommands(program);
|
|
31
|
+
setupAppCommands(program);
|
|
32
|
+
setupEnvironmentCommands(program);
|
|
33
|
+
setupAppManagementCommands(program);
|
|
34
|
+
setupDatasourceCommands(program);
|
|
35
|
+
setupUtilityCommands(program);
|
|
36
|
+
setupExternalSystemCommands(program);
|
|
37
|
+
setupDevCommands(program);
|
|
38
|
+
setupSecretsCommands(program);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = {
|
|
42
|
+
setupCommands,
|
|
43
|
+
validateCommand,
|
|
44
|
+
handleCommandError
|
|
45
|
+
};
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI application lifecycle command setup (create, wizard, build, run, push, deploy, dockerfile).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Application command definitions for AI Fabrix Builder CLI
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const app = require('../app');
|
|
12
|
+
const logger = require('../utils/logger');
|
|
13
|
+
const { handleCommandError } = require('../utils/cli-utils');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Normalize options for external system creation
|
|
17
|
+
* @param {Object} options - Raw CLI options
|
|
18
|
+
* @returns {Object} Normalized options
|
|
19
|
+
*/
|
|
20
|
+
function normalizeExternalOptions(options) {
|
|
21
|
+
const normalized = { ...options };
|
|
22
|
+
if (options.displayName) normalized.systemDisplayName = options.displayName;
|
|
23
|
+
if (options.description) normalized.systemDescription = options.description;
|
|
24
|
+
if (options.systemType) normalized.systemType = options.systemType;
|
|
25
|
+
if (options.authType) normalized.authType = options.authType;
|
|
26
|
+
if (options.datasources !== undefined) {
|
|
27
|
+
const parsedCount = parseInt(options.datasources, 10);
|
|
28
|
+
if (Number.isNaN(parsedCount) || parsedCount < 1 || parsedCount > 10) {
|
|
29
|
+
throw new Error('Datasources count must be a number between 1 and 10');
|
|
30
|
+
}
|
|
31
|
+
normalized.datasourceCount = parsedCount;
|
|
32
|
+
}
|
|
33
|
+
if (options.controller) {
|
|
34
|
+
normalized.controller = true;
|
|
35
|
+
normalized.controllerUrl = options.controller;
|
|
36
|
+
}
|
|
37
|
+
return normalized;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Validate required options for non-interactive external creation
|
|
42
|
+
* @param {Object} normalizedOptions - Normalized options
|
|
43
|
+
* @throws {Error} If required options are missing
|
|
44
|
+
*/
|
|
45
|
+
function validateNonInteractiveExternalOptions(normalizedOptions) {
|
|
46
|
+
const missing = [];
|
|
47
|
+
if (!normalizedOptions.systemDisplayName) missing.push('--display-name');
|
|
48
|
+
if (!normalizedOptions.systemDescription) missing.push('--description');
|
|
49
|
+
if (!normalizedOptions.systemType) missing.push('--system-type');
|
|
50
|
+
if (!normalizedOptions.authType) missing.push('--auth-type');
|
|
51
|
+
if (!normalizedOptions.datasourceCount) missing.push('--datasources');
|
|
52
|
+
if (missing.length > 0) {
|
|
53
|
+
throw new Error(`Missing required options for non-interactive external create: ${missing.join(', ')}`);
|
|
54
|
+
}
|
|
55
|
+
if (!Object.prototype.hasOwnProperty.call(normalizedOptions, 'github')) {
|
|
56
|
+
normalizedOptions.github = false;
|
|
57
|
+
}
|
|
58
|
+
if (!Object.prototype.hasOwnProperty.call(normalizedOptions, 'controller')) {
|
|
59
|
+
normalizedOptions.controller = false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Handle create command execution
|
|
65
|
+
* @async
|
|
66
|
+
* @param {string} appName - Application name
|
|
67
|
+
* @param {Object} options - CLI options
|
|
68
|
+
*/
|
|
69
|
+
async function handleCreateCommand(appName, options) {
|
|
70
|
+
const validTypes = ['webapp', 'api', 'service', 'functionapp', 'external'];
|
|
71
|
+
if (options.type && !validTypes.includes(options.type)) {
|
|
72
|
+
throw new Error(`Invalid type: ${options.type}. Must be one of: ${validTypes.join(', ')}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const wizardOptions = { app: appName, ...options };
|
|
76
|
+
const normalizedOptions = normalizeExternalOptions(options);
|
|
77
|
+
|
|
78
|
+
const isExternalType = options.type === 'external';
|
|
79
|
+
const isNonInteractive = process.stdin && process.stdin.isTTY === false;
|
|
80
|
+
|
|
81
|
+
if (isExternalType && !options.wizard && isNonInteractive) {
|
|
82
|
+
validateNonInteractiveExternalOptions(normalizedOptions);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const shouldUseWizard = options.wizard && (options.type === 'external' || (!options.type && validTypes.includes('external')));
|
|
86
|
+
if (shouldUseWizard) {
|
|
87
|
+
const { handleWizard } = require('../commands/wizard');
|
|
88
|
+
await handleWizard(wizardOptions);
|
|
89
|
+
} else {
|
|
90
|
+
await app.createApp(appName, normalizedOptions);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Sets up application lifecycle commands
|
|
96
|
+
* @param {Command} program - Commander program instance
|
|
97
|
+
*/
|
|
98
|
+
function setupAppCommands(program) {
|
|
99
|
+
program.command('create <app>')
|
|
100
|
+
.description('Create new application with configuration files')
|
|
101
|
+
.option('-p, --port <port>', 'Application port', '3000')
|
|
102
|
+
.option('-d, --database', 'Requires database')
|
|
103
|
+
.option('-r, --redis', 'Requires Redis')
|
|
104
|
+
.option('-s, --storage', 'Requires file storage')
|
|
105
|
+
.option('-a, --authentication', 'Requires authentication/RBAC')
|
|
106
|
+
.option('-l, --language <lang>', 'Runtime language (typescript/python)')
|
|
107
|
+
.option('-t, --template <name>', 'Template to use (e.g., miso-controller, keycloak)')
|
|
108
|
+
.option('--type <type>', 'Application type (webapp, api, service, functionapp, external)', 'webapp')
|
|
109
|
+
.option('--app', 'Generate minimal application files (package.json, index.ts or requirements.txt, main.py)')
|
|
110
|
+
.option('-g, --github', 'Generate GitHub Actions workflows')
|
|
111
|
+
.option('--github-steps <steps>', 'Extra GitHub workflow steps (comma-separated, e.g., npm,test)')
|
|
112
|
+
.option('--main-branch <branch>', 'Main branch name for workflows', 'main')
|
|
113
|
+
.option('--wizard', 'Use interactive wizard for external system creation')
|
|
114
|
+
.option('--display-name <name>', 'External system display name')
|
|
115
|
+
.option('--description <desc>', 'External system description')
|
|
116
|
+
.option('--system-type <type>', 'External system type (openapi, mcp, custom)')
|
|
117
|
+
.option('--auth-type <type>', 'External system auth type (oauth2, apikey, basic)')
|
|
118
|
+
.option('--datasources <count>', 'Number of datasources to create')
|
|
119
|
+
.action(async(appName, options) => {
|
|
120
|
+
try {
|
|
121
|
+
await handleCreateCommand(appName, options);
|
|
122
|
+
} catch (error) {
|
|
123
|
+
handleCommandError(error, 'create');
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
program.command('wizard [appName]')
|
|
129
|
+
.description('Create or extend external systems (OpenAPI, MCP, or known platforms like HubSpot) via guided steps or a config file')
|
|
130
|
+
.option('-a, --app <app>', 'Application name (synonym for positional appName)')
|
|
131
|
+
.option('--config <file>', 'Run headless using a wizard.yaml file (appName, mode, source, credential, preferences)')
|
|
132
|
+
.option('--silent', 'Run with saved integration/<app>/wizard.yaml only; no prompts (requires app name and existing wizard.yaml)')
|
|
133
|
+
.addHelpText('after', `
|
|
134
|
+
Examples:
|
|
135
|
+
$ aifabrix wizard Run interactively (mode first, then prompts)
|
|
136
|
+
$ aifabrix wizard my-integration Load wizard.yaml if present → show summary → "Run with saved config?" or start from step 1
|
|
137
|
+
$ aifabrix wizard my-integration --silent Run headless with integration/my-integration/wizard.yaml (no prompts)
|
|
138
|
+
$ aifabrix wizard -a my-integration Same as above (app name set)
|
|
139
|
+
$ aifabrix wizard --config wizard.yaml Run headless from a wizard config file
|
|
140
|
+
|
|
141
|
+
Config path: When appName is provided, integration/<appName>/wizard.yaml is used for load/save and error.log.
|
|
142
|
+
To change settings after a run, edit that file and run "aifabrix wizard <app>" again.
|
|
143
|
+
Headless config must include: appName, mode (create-system|add-datasource), source (type + filePath/url/platform).
|
|
144
|
+
See integration/hubspot/wizard-hubspot-e2e.yaml for an example.`)
|
|
145
|
+
.action(async(positionalAppName, options) => {
|
|
146
|
+
try {
|
|
147
|
+
const appName = positionalAppName || options.app;
|
|
148
|
+
const configPath = appName ? path.join(process.cwd(), 'integration', appName, 'wizard.yaml') : null;
|
|
149
|
+
const { handleWizard } = require('../commands/wizard');
|
|
150
|
+
await handleWizard({ ...options, app: appName, config: options.config, configPath });
|
|
151
|
+
} catch (error) {
|
|
152
|
+
handleCommandError(error, 'wizard');
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
program.command('build <app>')
|
|
158
|
+
.description('Build container image (auto-detects runtime)')
|
|
159
|
+
.option('-l, --language <lang>', 'Override language detection')
|
|
160
|
+
.option('-f, --force-template', 'Force rebuild from template')
|
|
161
|
+
.option('-t, --tag <tag>', 'Image tag (default: latest). Set image.tag in variables.yaml to match for deploy.')
|
|
162
|
+
.action(async(appName, options) => {
|
|
163
|
+
try {
|
|
164
|
+
const imageTag = await app.buildApp(appName, options);
|
|
165
|
+
logger.log(`✅ Built image: ${imageTag}`);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
handleCommandError(error, 'build');
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
program.command('run <app>')
|
|
173
|
+
.description('Run application locally')
|
|
174
|
+
.option('-p, --port <port>', 'Override local port')
|
|
175
|
+
.option('-d, --debug', 'Enable debug output with detailed container information')
|
|
176
|
+
.action(async(appName, options) => {
|
|
177
|
+
try {
|
|
178
|
+
await app.runApp(appName, options);
|
|
179
|
+
} catch (error) {
|
|
180
|
+
handleCommandError(error, 'run');
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
program.command('push <app>')
|
|
186
|
+
.description('Push image to Azure Container Registry')
|
|
187
|
+
.option('-r, --registry <registry>', 'ACR registry URL (overrides variables.yaml)')
|
|
188
|
+
.option('-t, --tag <tag>', 'Image tag(s) - comma-separated for multiple (default: latest)')
|
|
189
|
+
.action(async(appName, options) => {
|
|
190
|
+
try {
|
|
191
|
+
await app.pushApp(appName, options);
|
|
192
|
+
} catch (error) {
|
|
193
|
+
handleCommandError(error, 'push');
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
program.command('deploy <app>')
|
|
199
|
+
.description('Deploy to Azure via Miso Controller')
|
|
200
|
+
.option('--client-id <id>', 'Client ID (overrides config)')
|
|
201
|
+
.option('--client-secret <secret>', 'Client Secret (overrides config)')
|
|
202
|
+
.option('--poll', 'Poll for deployment status', true)
|
|
203
|
+
.option('--no-poll', 'Do not poll for status')
|
|
204
|
+
.action(async(appName, options) => {
|
|
205
|
+
try {
|
|
206
|
+
await app.deployApp(appName, options);
|
|
207
|
+
} catch (error) {
|
|
208
|
+
handleCommandError(error, 'deploy');
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
program.command('dockerfile <app>')
|
|
214
|
+
.description('Generate Dockerfile for an application')
|
|
215
|
+
.option('-l, --language <lang>', 'Override language detection')
|
|
216
|
+
.option('-f, --force', 'Overwrite existing Dockerfile')
|
|
217
|
+
.action(async(appName, options) => {
|
|
218
|
+
try {
|
|
219
|
+
const dockerfilePath = await app.generateDockerfileForApp(appName, options);
|
|
220
|
+
logger.log(chalk.green('\n✅ Dockerfile generated successfully!'));
|
|
221
|
+
logger.log(chalk.gray(`Location: ${dockerfilePath}`));
|
|
222
|
+
} catch (error) {
|
|
223
|
+
handleCommandError(error, 'dockerfile');
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
module.exports = { setupAppCommands };
|