@aifabrix/builder 2.8.0 → 2.9.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/integration/hubspot/README.md +136 -0
- package/integration/hubspot/env.template +9 -0
- package/integration/hubspot/hubspot-deploy-company.json +200 -0
- package/integration/hubspot/hubspot-deploy-contact.json +228 -0
- package/integration/hubspot/hubspot-deploy-deal.json +248 -0
- package/integration/hubspot/hubspot-deploy.json +91 -0
- package/integration/hubspot/variables.yaml +17 -0
- package/lib/app-config.js +4 -3
- package/lib/app-deploy.js +8 -20
- package/lib/app-dockerfile.js +7 -9
- package/lib/app-prompts.js +6 -5
- package/lib/app-push.js +9 -9
- package/lib/app-register.js +23 -5
- package/lib/app-rotate-secret.js +10 -0
- package/lib/app-run.js +5 -11
- package/lib/app.js +42 -14
- package/lib/build.js +20 -16
- package/lib/cli.js +61 -2
- package/lib/datasource-deploy.js +14 -20
- package/lib/external-system-deploy.js +123 -40
- package/lib/external-system-download.js +431 -0
- package/lib/external-system-generator.js +13 -10
- package/lib/external-system-test.js +446 -0
- package/lib/generator-builders.js +323 -0
- package/lib/generator.js +200 -292
- package/lib/schema/application-schema.json +853 -852
- package/lib/schema/external-datasource.schema.json +823 -49
- package/lib/schema/external-system.schema.json +96 -78
- package/lib/templates.js +1 -1
- package/lib/utils/cli-utils.js +4 -4
- package/lib/utils/external-system-display.js +159 -0
- package/lib/utils/external-system-validators.js +245 -0
- package/lib/utils/paths.js +151 -1
- package/lib/utils/schema-resolver.js +7 -2
- package/lib/validator.js +5 -2
- package/package.json +1 -1
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
{
|
|
2
|
+
"key": "hubspot-deal",
|
|
3
|
+
"displayName": "HubSpot Deal",
|
|
4
|
+
"description": "HubSpot deals datasource with field mappings for CRM deal data",
|
|
5
|
+
"systemKey": "hubspot",
|
|
6
|
+
"entityKey": "deal",
|
|
7
|
+
"resourceType": "deal",
|
|
8
|
+
"enabled": true,
|
|
9
|
+
"version": "1.0.0",
|
|
10
|
+
"metadataSchema": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"properties": {
|
|
13
|
+
"id": {
|
|
14
|
+
"type": "string"
|
|
15
|
+
},
|
|
16
|
+
"properties": {
|
|
17
|
+
"type": "object",
|
|
18
|
+
"properties": {
|
|
19
|
+
"dealname": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"properties": {
|
|
22
|
+
"value": {
|
|
23
|
+
"type": "string"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"amount": {
|
|
28
|
+
"type": "object",
|
|
29
|
+
"properties": {
|
|
30
|
+
"value": {
|
|
31
|
+
"type": "string"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"deal_currency_code": {
|
|
36
|
+
"type": "object",
|
|
37
|
+
"properties": {
|
|
38
|
+
"value": {
|
|
39
|
+
"type": "string"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"dealstage": {
|
|
44
|
+
"type": "object",
|
|
45
|
+
"properties": {
|
|
46
|
+
"value": {
|
|
47
|
+
"type": "string"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"pipeline": {
|
|
52
|
+
"type": "object",
|
|
53
|
+
"properties": {
|
|
54
|
+
"value": {
|
|
55
|
+
"type": "string"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"closedate": {
|
|
60
|
+
"type": "object",
|
|
61
|
+
"properties": {
|
|
62
|
+
"value": {
|
|
63
|
+
"type": "string"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"dealtype": {
|
|
68
|
+
"type": "object",
|
|
69
|
+
"properties": {
|
|
70
|
+
"value": {
|
|
71
|
+
"type": "string"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"createdate": {
|
|
76
|
+
"type": "object",
|
|
77
|
+
"properties": {
|
|
78
|
+
"value": {
|
|
79
|
+
"type": "string"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"hs_lastmodifieddate": {
|
|
84
|
+
"type": "object",
|
|
85
|
+
"properties": {
|
|
86
|
+
"value": {
|
|
87
|
+
"type": "string"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
"associations": {
|
|
94
|
+
"type": "object",
|
|
95
|
+
"properties": {
|
|
96
|
+
"companies": {
|
|
97
|
+
"type": "object",
|
|
98
|
+
"properties": {
|
|
99
|
+
"results": {
|
|
100
|
+
"type": "array",
|
|
101
|
+
"items": {
|
|
102
|
+
"type": "object",
|
|
103
|
+
"properties": {
|
|
104
|
+
"id": {
|
|
105
|
+
"type": "string"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"contacts": {
|
|
113
|
+
"type": "object",
|
|
114
|
+
"properties": {
|
|
115
|
+
"results": {
|
|
116
|
+
"type": "array",
|
|
117
|
+
"items": {
|
|
118
|
+
"type": "object",
|
|
119
|
+
"properties": {
|
|
120
|
+
"id": {
|
|
121
|
+
"type": "string"
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
"required": ["id", "properties"]
|
|
132
|
+
},
|
|
133
|
+
"fieldMappings": {
|
|
134
|
+
"accessFields": ["stage", "pipeline"],
|
|
135
|
+
"fields": {
|
|
136
|
+
"id": {
|
|
137
|
+
"expression": "{{id}}",
|
|
138
|
+
"type": "string",
|
|
139
|
+
"description": "Unique deal identifier",
|
|
140
|
+
"required": true
|
|
141
|
+
},
|
|
142
|
+
"dealName": {
|
|
143
|
+
"expression": "{{properties.dealname.value}} | trim",
|
|
144
|
+
"type": "string",
|
|
145
|
+
"description": "Deal name",
|
|
146
|
+
"required": false
|
|
147
|
+
},
|
|
148
|
+
"amount": {
|
|
149
|
+
"expression": "{{properties.amount.value}}",
|
|
150
|
+
"type": "string",
|
|
151
|
+
"description": "Deal amount",
|
|
152
|
+
"required": false
|
|
153
|
+
},
|
|
154
|
+
"currency": {
|
|
155
|
+
"expression": "{{properties.deal_currency_code.value}} | toUpper | trim",
|
|
156
|
+
"type": "string",
|
|
157
|
+
"description": "Currency code",
|
|
158
|
+
"required": false
|
|
159
|
+
},
|
|
160
|
+
"stage": {
|
|
161
|
+
"expression": "{{properties.dealstage.value}} | trim",
|
|
162
|
+
"type": "string",
|
|
163
|
+
"description": "Deal stage for ABAC filtering",
|
|
164
|
+
"required": false
|
|
165
|
+
},
|
|
166
|
+
"pipeline": {
|
|
167
|
+
"expression": "{{properties.pipeline.value}} | trim",
|
|
168
|
+
"type": "string",
|
|
169
|
+
"description": "Pipeline name for ABAC filtering",
|
|
170
|
+
"required": false
|
|
171
|
+
},
|
|
172
|
+
"closeDate": {
|
|
173
|
+
"expression": "{{properties.closedate.value}}",
|
|
174
|
+
"type": "string",
|
|
175
|
+
"description": "Expected close date",
|
|
176
|
+
"required": false
|
|
177
|
+
},
|
|
178
|
+
"dealType": {
|
|
179
|
+
"expression": "{{properties.dealtype.value}} | trim",
|
|
180
|
+
"type": "string",
|
|
181
|
+
"description": "Deal type",
|
|
182
|
+
"required": false
|
|
183
|
+
},
|
|
184
|
+
"associatedCompany": {
|
|
185
|
+
"expression": "{{associations.companies.results[0].id}}",
|
|
186
|
+
"type": "string",
|
|
187
|
+
"description": "Associated company ID",
|
|
188
|
+
"required": false
|
|
189
|
+
},
|
|
190
|
+
"associatedContacts": {
|
|
191
|
+
"expression": "{{associations.contacts.results}}",
|
|
192
|
+
"type": "array",
|
|
193
|
+
"description": "Array of associated contact IDs",
|
|
194
|
+
"required": false
|
|
195
|
+
},
|
|
196
|
+
"createdAt": {
|
|
197
|
+
"expression": "{{properties.createdate.value}}",
|
|
198
|
+
"type": "string",
|
|
199
|
+
"description": "Creation timestamp",
|
|
200
|
+
"required": false
|
|
201
|
+
},
|
|
202
|
+
"updatedAt": {
|
|
203
|
+
"expression": "{{properties.hs_lastmodifieddate.value}}",
|
|
204
|
+
"type": "string",
|
|
205
|
+
"description": "Last modification timestamp",
|
|
206
|
+
"required": false
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
"exposed": {
|
|
211
|
+
"fields": ["id", "dealName", "amount", "currency", "stage", "pipeline", "closeDate", "dealType", "associatedCompany", "associatedContacts", "createdAt", "updatedAt"],
|
|
212
|
+
"description": "Exposed fields for HubSpot deals"
|
|
213
|
+
},
|
|
214
|
+
"openapi": {
|
|
215
|
+
"enabled": true,
|
|
216
|
+
"documentKey": "hubspot-v3",
|
|
217
|
+
"baseUrl": "https://api.hubapi.com",
|
|
218
|
+
"operations": {
|
|
219
|
+
"list": {
|
|
220
|
+
"operationId": "getDeals",
|
|
221
|
+
"method": "GET",
|
|
222
|
+
"path": "/crm/v3/objects/deals"
|
|
223
|
+
},
|
|
224
|
+
"get": {
|
|
225
|
+
"operationId": "getDeal",
|
|
226
|
+
"method": "GET",
|
|
227
|
+
"path": "/crm/v3/objects/deals/{dealId}"
|
|
228
|
+
},
|
|
229
|
+
"create": {
|
|
230
|
+
"operationId": "createDeal",
|
|
231
|
+
"method": "POST",
|
|
232
|
+
"path": "/crm/v3/objects/deals"
|
|
233
|
+
},
|
|
234
|
+
"update": {
|
|
235
|
+
"operationId": "updateDeal",
|
|
236
|
+
"method": "PATCH",
|
|
237
|
+
"path": "/crm/v3/objects/deals/{dealId}"
|
|
238
|
+
},
|
|
239
|
+
"delete": {
|
|
240
|
+
"operationId": "deleteDeal",
|
|
241
|
+
"method": "DELETE",
|
|
242
|
+
"path": "/crm/v3/objects/deals/{dealId}"
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
"autoRbac": true
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
{
|
|
2
|
+
"key": "hubspot",
|
|
3
|
+
"displayName": "HubSpot CRM",
|
|
4
|
+
"description": "HubSpot CRM integration with OpenAPI support for companies, contacts, and deals",
|
|
5
|
+
"type": "openapi",
|
|
6
|
+
"enabled": true,
|
|
7
|
+
"environment": {
|
|
8
|
+
"baseUrl": "https://api.hubapi.com"
|
|
9
|
+
},
|
|
10
|
+
"authentication": {
|
|
11
|
+
"type": "oauth2",
|
|
12
|
+
"mode": "oauth2",
|
|
13
|
+
"oauth2": {
|
|
14
|
+
"tokenUrl": "{{TOKENURL}}",
|
|
15
|
+
"clientId": "{{CLIENTID}}",
|
|
16
|
+
"clientSecret": "{{CLIENTSECRET}}",
|
|
17
|
+
"scopes": [
|
|
18
|
+
"crm.objects.companies.read",
|
|
19
|
+
"crm.objects.companies.write",
|
|
20
|
+
"crm.objects.contacts.read",
|
|
21
|
+
"crm.objects.contacts.write",
|
|
22
|
+
"crm.objects.deals.read",
|
|
23
|
+
"crm.objects.deals.write"
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"configuration": [
|
|
28
|
+
{
|
|
29
|
+
"name": "CLIENTID",
|
|
30
|
+
"value": "hubspot-clientidKeyVault",
|
|
31
|
+
"location": "keyvault",
|
|
32
|
+
"required": true
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "CLIENTSECRET",
|
|
36
|
+
"value": "hubspot-clientsecretKeyVault",
|
|
37
|
+
"location": "keyvault",
|
|
38
|
+
"required": true
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"name": "TOKENURL",
|
|
42
|
+
"value": "https://api.hubapi.com/oauth/v1/token",
|
|
43
|
+
"location": "variable",
|
|
44
|
+
"required": true
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"name": "REDIRECT_URI",
|
|
48
|
+
"value": "hubspot-redirect-uriKeyVault",
|
|
49
|
+
"location": "keyvault",
|
|
50
|
+
"required": false
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "HUBSPOT_API_VERSION",
|
|
54
|
+
"value": "v3",
|
|
55
|
+
"location": "variable",
|
|
56
|
+
"required": false,
|
|
57
|
+
"portalInput": {
|
|
58
|
+
"field": "select",
|
|
59
|
+
"label": "HubSpot API Version",
|
|
60
|
+
"placeholder": "Select API version",
|
|
61
|
+
"options": ["v1", "v2", "v3"],
|
|
62
|
+
"validation": {
|
|
63
|
+
"required": false
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "MAX_PAGE_SIZE",
|
|
69
|
+
"value": "100",
|
|
70
|
+
"location": "variable",
|
|
71
|
+
"required": false,
|
|
72
|
+
"portalInput": {
|
|
73
|
+
"field": "text",
|
|
74
|
+
"label": "Maximum Page Size",
|
|
75
|
+
"placeholder": "100",
|
|
76
|
+
"validation": {
|
|
77
|
+
"required": false,
|
|
78
|
+
"pattern": "^[0-9]+$",
|
|
79
|
+
"min": 1,
|
|
80
|
+
"max": 1000
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
"openapi": {
|
|
86
|
+
"documentKey": "hubspot-v3",
|
|
87
|
+
"autoDiscoverEntities": false
|
|
88
|
+
},
|
|
89
|
+
"tags": ["crm", "sales", "marketing", "hubspot"]
|
|
90
|
+
}
|
|
91
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
app:
|
|
2
|
+
key: hubspot
|
|
3
|
+
displayName: "HubSpot CRM Integration"
|
|
4
|
+
description: "HubSpot CRM external system integration with companies, contacts, and deals"
|
|
5
|
+
type: external
|
|
6
|
+
|
|
7
|
+
externalIntegration:
|
|
8
|
+
schemaBasePath: ./
|
|
9
|
+
systems:
|
|
10
|
+
- hubspot-deploy.json
|
|
11
|
+
dataSources:
|
|
12
|
+
- hubspot-deploy-company.json
|
|
13
|
+
- hubspot-deploy-contact.json
|
|
14
|
+
- hubspot-deploy-deal.json
|
|
15
|
+
autopublish: true
|
|
16
|
+
version: 1.0.0
|
|
17
|
+
|
package/lib/app-config.js
CHANGED
|
@@ -99,14 +99,14 @@ async function generateRbacYamlFile(appPath, appName, config) {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
/**
|
|
102
|
-
* Generates
|
|
102
|
+
* Generates <app-name>-deploy.json file (consistent naming for all apps)
|
|
103
103
|
* @async
|
|
104
104
|
* @param {string} appPath - Path to application directory
|
|
105
105
|
* @param {string} appName - Application name
|
|
106
106
|
* @param {Object} config - Application configuration
|
|
107
107
|
*/
|
|
108
108
|
async function generateDeployJsonFile(appPath, appName, config) {
|
|
109
|
-
// Skip
|
|
109
|
+
// Skip for external type (external system JSON is generated separately)
|
|
110
110
|
if (config.type === 'external') {
|
|
111
111
|
return;
|
|
112
112
|
}
|
|
@@ -138,8 +138,9 @@ async function generateDeployJsonFile(appPath, appName, config) {
|
|
|
138
138
|
}
|
|
139
139
|
};
|
|
140
140
|
|
|
141
|
+
// Use consistent naming: <app-name>-deploy.json
|
|
141
142
|
await fs.writeFile(
|
|
142
|
-
path.join(appPath,
|
|
143
|
+
path.join(appPath, `${appName}-deploy.json`),
|
|
143
144
|
JSON.stringify(deployJson, null, 2)
|
|
144
145
|
);
|
|
145
146
|
}
|
package/lib/app-deploy.js
CHANGED
|
@@ -17,6 +17,7 @@ const pushUtils = require('./push');
|
|
|
17
17
|
const logger = require('./utils/logger');
|
|
18
18
|
const config = require('./config');
|
|
19
19
|
const { getDeploymentAuth } = require('./utils/token-manager');
|
|
20
|
+
const { detectAppType } = require('./utils/paths');
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Validate application name format
|
|
@@ -215,10 +216,11 @@ function validateDeploymentConfig(deploymentConfig) {
|
|
|
215
216
|
* @throws {Error} If configuration is invalid
|
|
216
217
|
*/
|
|
217
218
|
async function loadDeploymentConfig(appName, options) {
|
|
218
|
-
|
|
219
|
-
await
|
|
219
|
+
// Detect app type and get correct path (integration or builder)
|
|
220
|
+
const { appPath } = await detectAppType(appName);
|
|
221
|
+
await validateAppDirectory(appPath, appName);
|
|
220
222
|
|
|
221
|
-
const variablesPath = path.join(
|
|
223
|
+
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
222
224
|
const variables = await loadVariablesFile(variablesPath);
|
|
223
225
|
|
|
224
226
|
const deploymentConfig = extractDeploymentConfig(options, variables);
|
|
@@ -366,23 +368,9 @@ async function deployApp(appName, options = {}) {
|
|
|
366
368
|
|
|
367
369
|
validateAppName(appName);
|
|
368
370
|
|
|
369
|
-
// 2. Check if app type is external -
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
const path = require('path');
|
|
373
|
-
const variablesPath = path.join(process.cwd(), 'builder', appName, 'variables.yaml');
|
|
374
|
-
try {
|
|
375
|
-
const variablesContent = await fs.readFile(variablesPath, 'utf8');
|
|
376
|
-
const variables = yaml.load(variablesContent);
|
|
377
|
-
if (variables.app && variables.app.type === 'external') {
|
|
378
|
-
const externalDeploy = require('./external-system-deploy');
|
|
379
|
-
await externalDeploy.deployExternalSystem(appName, options);
|
|
380
|
-
return { success: true, type: 'external' };
|
|
381
|
-
}
|
|
382
|
-
} catch (error) {
|
|
383
|
-
// If variables.yaml doesn't exist or can't be read, continue with normal deployment
|
|
384
|
-
// The error will be properly handled in loadDeploymentConfig
|
|
385
|
-
}
|
|
371
|
+
// 2. Check if app type is external - use normal deployment flow with application-schema.json
|
|
372
|
+
// External systems now deploy via miso controller as normal application (full application file)
|
|
373
|
+
// The json command generates application-schema.json which is used for deployment
|
|
386
374
|
|
|
387
375
|
// 2. Load deployment configuration
|
|
388
376
|
config = await loadDeploymentConfig(appName, options);
|
package/lib/app-dockerfile.js
CHANGED
|
@@ -85,25 +85,23 @@ async function generateAndCopyDockerfile(appPath, dockerfilePath, config) {
|
|
|
85
85
|
*/
|
|
86
86
|
async function generateDockerfileForApp(appName, options = {}) {
|
|
87
87
|
// Check if app type is external - skip Dockerfile generation
|
|
88
|
-
const
|
|
88
|
+
const { detectAppType } = require('./utils/paths');
|
|
89
89
|
try {
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
if (variables.app && variables.app.type === 'external') {
|
|
90
|
+
const { isExternal } = await detectAppType(appName);
|
|
91
|
+
if (isExternal) {
|
|
93
92
|
logger.log(chalk.yellow('⚠️ External systems don\'t require Dockerfiles. Skipping...'));
|
|
94
93
|
return null;
|
|
95
94
|
}
|
|
96
95
|
} catch (error) {
|
|
97
|
-
// If
|
|
98
|
-
|
|
99
|
-
throw error;
|
|
100
|
-
}
|
|
96
|
+
// If detection fails, continue with normal generation
|
|
97
|
+
// (detectAppType throws if app doesn't exist, which is fine for dockerfile command)
|
|
101
98
|
}
|
|
102
99
|
try {
|
|
103
100
|
// Validate app name
|
|
104
101
|
validateAppName(appName);
|
|
105
102
|
|
|
106
|
-
|
|
103
|
+
// Detect app type and get correct path (integration or builder)
|
|
104
|
+
const { appPath } = await detectAppType(appName);
|
|
107
105
|
const dockerfilePath = path.join(appPath, 'Dockerfile');
|
|
108
106
|
|
|
109
107
|
// Check if Dockerfile already exists
|
package/lib/app-prompts.js
CHANGED
|
@@ -364,11 +364,6 @@ function mergePromptAnswers(appName, options, answers) {
|
|
|
364
364
|
* @returns {Promise<Object>} Complete configuration
|
|
365
365
|
*/
|
|
366
366
|
async function promptForOptions(appName, options) {
|
|
367
|
-
// Default github to false if not provided (make it truly optional)
|
|
368
|
-
if (!Object.prototype.hasOwnProperty.call(options, 'github')) {
|
|
369
|
-
options.github = false;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
367
|
// Get app type from options (default to webapp)
|
|
373
368
|
const appType = options.type || 'webapp';
|
|
374
369
|
|
|
@@ -398,6 +393,12 @@ async function promptForOptions(appName, options) {
|
|
|
398
393
|
// Add type to merged config
|
|
399
394
|
merged.type = appType;
|
|
400
395
|
|
|
396
|
+
// For external type, remove port and language as they're not applicable
|
|
397
|
+
if (appType === 'external') {
|
|
398
|
+
delete merged.port;
|
|
399
|
+
delete merged.language;
|
|
400
|
+
}
|
|
401
|
+
|
|
401
402
|
return merged;
|
|
402
403
|
}
|
|
403
404
|
|
package/lib/app-push.js
CHANGED
|
@@ -69,7 +69,10 @@ function extractImageName(config, appName) {
|
|
|
69
69
|
* @throws {Error} If configuration cannot be loaded
|
|
70
70
|
*/
|
|
71
71
|
async function loadPushConfig(appName, options) {
|
|
72
|
-
|
|
72
|
+
// Detect app type and get correct path (integration or builder)
|
|
73
|
+
const { detectAppType } = require('./utils/paths');
|
|
74
|
+
const { appPath } = await detectAppType(appName);
|
|
75
|
+
const configPath = path.join(appPath, 'variables.yaml');
|
|
73
76
|
try {
|
|
74
77
|
const config = yaml.load(await fs.readFile(configPath, 'utf8'));
|
|
75
78
|
const registry = options.registry || config.image?.registry;
|
|
@@ -180,19 +183,16 @@ function displayPushResults(registry, imageName, tags) {
|
|
|
180
183
|
*/
|
|
181
184
|
async function pushApp(appName, options = {}) {
|
|
182
185
|
// Check if app type is external - skip push
|
|
183
|
-
const
|
|
186
|
+
const { detectAppType } = require('./utils/paths');
|
|
184
187
|
try {
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
if (config.app && config.app.type === 'external') {
|
|
188
|
+
const { isExternal } = await detectAppType(appName);
|
|
189
|
+
if (isExternal) {
|
|
188
190
|
logger.log(chalk.yellow('⚠️ External systems don\'t require Docker images. Skipping push...'));
|
|
189
191
|
return;
|
|
190
192
|
}
|
|
191
193
|
} catch (error) {
|
|
192
|
-
// If
|
|
193
|
-
|
|
194
|
-
throw error;
|
|
195
|
-
}
|
|
194
|
+
// If detection fails, continue with normal push
|
|
195
|
+
// (detectAppType throws if app doesn't exist, which is fine for push command)
|
|
196
196
|
}
|
|
197
197
|
try {
|
|
198
198
|
// Validate app name
|
package/lib/app-register.js
CHANGED
|
@@ -19,6 +19,8 @@ const logger = require('./utils/logger');
|
|
|
19
19
|
const { saveLocalSecret, isLocalhost } = require('./utils/local-secrets');
|
|
20
20
|
const { updateEnvTemplate } = require('./utils/env-template');
|
|
21
21
|
const { getOrRefreshDeviceToken } = require('./utils/token-manager');
|
|
22
|
+
const { detectAppType } = require('./utils/paths');
|
|
23
|
+
const { generateEnvFile } = require('./secrets');
|
|
22
24
|
|
|
23
25
|
// Import createApp to auto-generate config if missing
|
|
24
26
|
let createApp;
|
|
@@ -86,7 +88,9 @@ const registerApplicationSchema = {
|
|
|
86
88
|
* @returns {Promise<{variables: Object, created: boolean}>} Variables and creation flag
|
|
87
89
|
*/
|
|
88
90
|
async function loadVariablesYaml(appKey) {
|
|
89
|
-
|
|
91
|
+
// Detect app type and get correct path (integration or builder)
|
|
92
|
+
const { appPath } = await detectAppType(appKey);
|
|
93
|
+
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
90
94
|
|
|
91
95
|
try {
|
|
92
96
|
const variablesContent = await fs.readFile(variablesPath, 'utf-8');
|
|
@@ -122,7 +126,9 @@ async function createMinimalAppIfNeeded(appKey, options) {
|
|
|
122
126
|
authentication: false
|
|
123
127
|
});
|
|
124
128
|
|
|
125
|
-
|
|
129
|
+
// Detect app type and get correct path (integration or builder)
|
|
130
|
+
const { appPath } = await detectAppType(appKey);
|
|
131
|
+
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
126
132
|
const variablesContent = await fs.readFile(variablesPath, 'utf-8');
|
|
127
133
|
return yaml.load(variablesContent);
|
|
128
134
|
}
|
|
@@ -170,11 +176,12 @@ function extractAppConfiguration(variables, appKey, options) {
|
|
|
170
176
|
|
|
171
177
|
/**
|
|
172
178
|
* Validate application registration data
|
|
179
|
+
* @async
|
|
173
180
|
* @param {Object} config - Application configuration
|
|
174
181
|
* @param {string} originalAppKey - Original app key for error messages
|
|
175
182
|
* @throws {Error} If validation fails
|
|
176
183
|
*/
|
|
177
|
-
function validateAppRegistrationData(config, originalAppKey) {
|
|
184
|
+
async function validateAppRegistrationData(config, originalAppKey) {
|
|
178
185
|
const missingFields = [];
|
|
179
186
|
if (!config.appKey) missingFields.push('app.key');
|
|
180
187
|
if (!config.displayName) missingFields.push('app.name');
|
|
@@ -182,7 +189,10 @@ function validateAppRegistrationData(config, originalAppKey) {
|
|
|
182
189
|
if (missingFields.length > 0) {
|
|
183
190
|
logger.error(chalk.red('❌ Missing required fields in variables.yaml:'));
|
|
184
191
|
missingFields.forEach(field => logger.error(chalk.red(` - ${field}`)));
|
|
185
|
-
|
|
192
|
+
// Detect app type to show correct path
|
|
193
|
+
const { appPath } = await detectAppType(originalAppKey);
|
|
194
|
+
const relativePath = path.relative(process.cwd(), appPath);
|
|
195
|
+
logger.error(chalk.red(`\n Please update ${relativePath}/variables.yaml and try again.`));
|
|
186
196
|
process.exit(1);
|
|
187
197
|
}
|
|
188
198
|
|
|
@@ -382,7 +392,7 @@ async function registerApplication(appKey, options) {
|
|
|
382
392
|
const appConfig = extractAppConfiguration(finalVariables, appKey, options);
|
|
383
393
|
|
|
384
394
|
// Validate configuration (pass original appKey for error messages)
|
|
385
|
-
validateAppRegistrationData(appConfig, appKey);
|
|
395
|
+
await validateAppRegistrationData(appConfig, appKey);
|
|
386
396
|
|
|
387
397
|
// Get controller URL from variables.yaml if available
|
|
388
398
|
const controllerUrl = finalVariables?.deployment?.controllerUrl;
|
|
@@ -434,6 +444,14 @@ async function registerApplication(appKey, options) {
|
|
|
434
444
|
// Update env.template
|
|
435
445
|
await updateEnvTemplate(registeredAppKey, clientIdKey, clientSecretKey, authConfig.apiUrl);
|
|
436
446
|
|
|
447
|
+
// Regenerate .env file with updated credentials
|
|
448
|
+
try {
|
|
449
|
+
await generateEnvFile(registeredAppKey, null, 'local');
|
|
450
|
+
logger.log(chalk.green('✓ .env file updated with new credentials'));
|
|
451
|
+
} catch (error) {
|
|
452
|
+
logger.warn(chalk.yellow(`⚠️ Could not regenerate .env file: ${error.message}`));
|
|
453
|
+
}
|
|
454
|
+
|
|
437
455
|
logger.log(chalk.green('\n✓ Credentials saved to ~/.aifabrix/secrets.local.yaml'));
|
|
438
456
|
logger.log(chalk.green('✓ env.template updated with MISO_CLIENTID, MISO_CLIENTSECRET, and MISO_CONTROLLER_URL\n'));
|
|
439
457
|
} catch (error) {
|
package/lib/app-rotate-secret.js
CHANGED
|
@@ -17,6 +17,7 @@ const logger = require('./utils/logger');
|
|
|
17
17
|
const { saveLocalSecret, isLocalhost } = require('./utils/local-secrets');
|
|
18
18
|
const { updateEnvTemplate } = require('./utils/env-template');
|
|
19
19
|
const { getEnvironmentPrefix } = require('./app-register');
|
|
20
|
+
const { generateEnvFile } = require('./secrets');
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Validate environment parameter
|
|
@@ -147,6 +148,15 @@ async function rotateSecret(appKey, options) {
|
|
|
147
148
|
// Update env.template if localhost
|
|
148
149
|
if (isLocalhost(controllerUrl)) {
|
|
149
150
|
await updateEnvTemplate(appKey, clientIdKey, clientSecretKey, controllerUrl);
|
|
151
|
+
|
|
152
|
+
// Regenerate .env file with updated credentials
|
|
153
|
+
try {
|
|
154
|
+
await generateEnvFile(appKey, null, 'local');
|
|
155
|
+
logger.log(chalk.green('✓ .env file updated with new credentials'));
|
|
156
|
+
} catch (error) {
|
|
157
|
+
logger.warn(chalk.yellow(`⚠️ Could not regenerate .env file: ${error.message}`));
|
|
158
|
+
}
|
|
159
|
+
|
|
150
160
|
logger.log(chalk.green('\n✓ Credentials saved to ~/.aifabrix/secrets.local.yaml'));
|
|
151
161
|
logger.log(chalk.green('✓ env.template updated with MISO_CLIENTID, MISO_CLIENTSECRET, and MISO_CONTROLLER_URL\n'));
|
|
152
162
|
} else {
|
package/lib/app-run.js
CHANGED
|
@@ -49,23 +49,17 @@ async function runApp(appName, options = {}) {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
// Check if app type is external - skip Docker run
|
|
52
|
-
const
|
|
53
|
-
const fs = require('fs').promises;
|
|
54
|
-
const path = require('path');
|
|
55
|
-
const variablesPath = path.join(process.cwd(), 'builder', appName, 'variables.yaml');
|
|
52
|
+
const { detectAppType } = require('./utils/paths');
|
|
56
53
|
try {
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
if (variables.app && variables.app.type === 'external') {
|
|
54
|
+
const { isExternal } = await detectAppType(appName);
|
|
55
|
+
if (isExternal) {
|
|
60
56
|
logger.log(chalk.yellow('⚠️ External systems don\'t run as Docker containers.'));
|
|
61
57
|
logger.log(chalk.blue('Use "aifabrix build" to deploy to dataplane, then test via OpenAPI endpoints.'));
|
|
62
58
|
return;
|
|
63
59
|
}
|
|
64
60
|
} catch (error) {
|
|
65
|
-
// If
|
|
66
|
-
|
|
67
|
-
throw error;
|
|
68
|
-
}
|
|
61
|
+
// If detection fails, continue with normal run
|
|
62
|
+
// (detectAppType throws if app doesn't exist, which is fine for run command)
|
|
69
63
|
}
|
|
70
64
|
|
|
71
65
|
// Validate app name and load configuration
|