@aifabrix/builder 2.41.0 → 2.42.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/docs-rules.mdc +30 -0
- package/README.md +1 -1
- package/integration/hubspot/README.md +8 -4
- package/integration/hubspot/application.json +54 -0
- package/integration/hubspot/create-hubspot.js +9 -136
- package/integration/hubspot/env.template +3 -4
- package/integration/hubspot/hubspot-datasource-company.json +343 -5
- package/integration/hubspot/hubspot-datasource-contact.json +413 -5
- package/integration/hubspot/hubspot-datasource-deal.json +341 -4
- package/integration/hubspot/hubspot-datasource-users.json +116 -0
- package/integration/hubspot/hubspot-deploy.json +1250 -108
- package/integration/hubspot/hubspot-system.json +15 -32
- package/integration/hubspot/test-dataplane-down-tests.js +17 -16
- package/integration/hubspot/test-dataplane-down.js +2 -2
- package/jest.config.manual.js +2 -1
- package/lib/api/external-test.api.js +111 -0
- package/lib/api/index.js +42 -19
- package/lib/api/pipeline.api.js +66 -120
- package/lib/api/types/pipeline.types.js +37 -0
- package/lib/api/wizard-platform.api.js +61 -0
- package/lib/api/wizard.api.js +34 -1
- package/lib/app/config.js +23 -11
- package/lib/app/index.js +3 -1
- package/lib/app/prompts.js +44 -29
- package/lib/app/readme.js +8 -3
- package/lib/app/run-env-compose.js +64 -1
- package/lib/app/run-helpers.js +1 -1
- package/lib/app/show-display.js +1 -1
- package/lib/cli/setup-app.js +42 -11
- package/lib/cli/setup-credential-deployment.js +31 -6
- package/lib/cli/setup-dev.js +27 -0
- package/lib/cli/setup-environment.js +12 -4
- package/lib/cli/setup-external-system.js +19 -4
- package/lib/cli/setup-infra.js +54 -14
- package/lib/cli/setup-utility.js +117 -21
- package/lib/commands/credential-env.js +162 -0
- package/lib/commands/credential-list.js +17 -22
- package/lib/commands/credential-push.js +96 -0
- package/lib/commands/datasource.js +77 -6
- package/lib/commands/dev-init.js +39 -1
- package/lib/commands/repair-auth-config.js +99 -0
- package/lib/commands/repair-datasource-keys.js +208 -0
- package/lib/commands/repair-datasource.js +235 -0
- package/lib/commands/repair-env-template.js +348 -0
- package/lib/commands/repair-internal.js +85 -0
- package/lib/commands/repair-rbac.js +158 -0
- package/lib/commands/repair.js +507 -0
- package/lib/commands/test-e2e-external.js +165 -0
- package/lib/commands/upload.js +71 -40
- package/lib/commands/wizard-core-helpers.js +226 -4
- package/lib/commands/wizard-core.js +67 -29
- package/lib/commands/wizard-dataplane.js +1 -1
- package/lib/commands/wizard-entity-selection.js +43 -0
- package/lib/commands/wizard-headless.js +44 -5
- package/lib/commands/wizard-helpers.js +7 -3
- package/lib/commands/wizard.js +86 -64
- package/lib/core/config.js +7 -1
- package/lib/core/secrets.js +33 -12
- package/lib/datasource/deploy.js +12 -3
- package/lib/datasource/test-e2e.js +219 -0
- package/lib/datasource/test-integration.js +154 -0
- package/lib/deployment/deployer.js +7 -5
- package/lib/external-system/download.js +182 -204
- package/lib/external-system/generator.js +204 -56
- package/lib/external-system/test-execution.js +2 -1
- package/lib/external-system/test-system-level.js +73 -0
- package/lib/external-system/test.js +51 -18
- package/lib/generator/external-controller-manifest.js +29 -2
- package/lib/generator/external-schema-utils.js +1 -1
- package/lib/generator/external.js +10 -3
- package/lib/generator/index.js +4 -1
- package/lib/generator/split-readme.js +1 -0
- package/lib/generator/split-variables.js +7 -1
- package/lib/generator/split.js +194 -54
- package/lib/generator/wizard-prompts-secondary.js +294 -0
- package/lib/generator/wizard-prompts.js +105 -106
- package/lib/generator/wizard-readme.js +88 -0
- package/lib/generator/wizard.js +147 -158
- package/lib/infrastructure/compose.js +11 -1
- package/lib/infrastructure/index.js +11 -3
- package/lib/infrastructure/services.js +22 -11
- package/lib/schema/application-schema.json +8 -5
- package/lib/schema/external-datasource.schema.json +49 -26
- package/lib/schema/external-system.schema.json +82 -6
- package/lib/schema/wizard-config.schema.json +16 -0
- package/lib/utils/api.js +38 -10
- package/lib/utils/auth-headers.js +8 -7
- package/lib/utils/compose-generator.js +1 -1
- package/lib/utils/compose-handlebars-helpers.js +11 -0
- package/lib/utils/config-format-preference.js +51 -0
- package/lib/utils/config-format.js +36 -0
- package/lib/utils/configuration-env-resolver.js +179 -0
- package/lib/utils/credential-display.js +83 -0
- package/lib/utils/credential-secrets-env.js +115 -25
- package/lib/utils/dataplane-pipeline-warning.js +28 -0
- package/lib/utils/deployment-validation-helpers.js +4 -4
- package/lib/utils/dev-ca-install.js +139 -0
- package/lib/utils/env-copy.js +23 -3
- package/lib/utils/error-formatters/http-status-errors.js +0 -1
- package/lib/utils/error-formatters/permission-errors.js +0 -1
- package/lib/utils/error-formatters/validation-errors.js +0 -1
- package/lib/utils/external-readme.js +56 -29
- package/lib/utils/external-system-display.js +59 -1
- package/lib/utils/external-system-test-helpers.js +21 -8
- package/lib/utils/external-system-validators.js +3 -0
- package/lib/utils/file-upload.js +20 -50
- package/lib/utils/help-builder.js +1 -0
- package/lib/utils/infra-status.js +50 -44
- package/lib/utils/local-secrets.js +5 -5
- package/lib/utils/paths.js +85 -4
- package/lib/utils/secrets-canonical.js +93 -0
- package/lib/utils/secrets-generator.js +20 -0
- package/lib/utils/secrets-helpers.js +75 -89
- package/lib/utils/test-log-writer.js +56 -0
- package/lib/utils/token-manager.js +24 -32
- package/lib/validation/env-template-auth.js +157 -0
- package/lib/validation/env-template-kv.js +41 -0
- package/lib/validation/external-manifest-validator.js +25 -0
- package/lib/validation/external-system-auth-rules.js +86 -0
- package/lib/validation/validate-batch.js +149 -0
- package/lib/validation/validate-datasource-keys-api.js +33 -0
- package/lib/validation/validate-display.js +94 -16
- package/lib/validation/validate.js +25 -12
- package/lib/validation/validator.js +7 -9
- package/lib/validation/wizard-datasource-validation.js +50 -0
- package/package.json +7 -2
- package/templates/applications/dataplane/application.yaml +1 -1
- package/templates/applications/dataplane/env.template +5 -5
- package/templates/applications/dataplane/rbac.yaml +2 -2
- package/templates/applications/miso-controller/env.template +1 -1
- package/templates/external-system/README.md.hbs +65 -25
- package/templates/external-system/deploy.js.hbs +4 -2
- package/templates/external-system/external-datasource.yaml.hbs +217 -0
- package/templates/external-system/external-system.json.hbs +1 -18
- package/templates/infra/compose.yaml.hbs +6 -0
- package/templates/python/docker-compose.hbs +4 -4
- package/templates/typescript/docker-compose.hbs +4 -4
- package/integration/hubspot/application.yaml +0 -37
|
@@ -115,11 +115,6 @@
|
|
|
115
115
|
"minimum": 1,
|
|
116
116
|
"maximum": 65535
|
|
117
117
|
},
|
|
118
|
-
"deploymentKey": {
|
|
119
|
-
"type": "string",
|
|
120
|
-
"description": "SHA256 hash of deployment manifest (excluding deploymentKey field)",
|
|
121
|
-
"pattern": "^[a-f0-9]{64}$"
|
|
122
|
-
},
|
|
123
118
|
"requiresDatabase": {
|
|
124
119
|
"type": "boolean",
|
|
125
120
|
"description": "Whether application requires database"
|
|
@@ -137,6 +132,14 @@
|
|
|
137
132
|
"type": "string",
|
|
138
133
|
"description": "Database name",
|
|
139
134
|
"pattern": "^[a-z0-9_-]+$"
|
|
135
|
+
},
|
|
136
|
+
"extensions": {
|
|
137
|
+
"type": "array",
|
|
138
|
+
"description": "PostgreSQL extension names to create in this database during db-init (e.g. pgcrypto, uuid-ossp, vector, btree_gin, btree_gist). If the database name ends with 'vector', the vector extension is still added automatically if not listed.",
|
|
139
|
+
"items": {
|
|
140
|
+
"type": "string",
|
|
141
|
+
"pattern": "^[a-z0-9_-]+$"
|
|
142
|
+
}
|
|
140
143
|
}
|
|
141
144
|
},
|
|
142
145
|
"additionalProperties": false
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
"$id":"https://raw.githubusercontent.com/esystemsdev/aifabrix-builder/refs/heads/main/lib/schema/external-datasource.schema.json",
|
|
4
4
|
"title":"External Data Source",
|
|
5
5
|
"description":"Configuration for AI Fabrix ExternalDataSource entities. Includes metadata schema, data dimensions, transformation mappings, OpenAPI/MCP exposure, execution logic, and sync behavior.",
|
|
6
|
-
|
|
6
|
+
"metadata":{
|
|
7
7
|
"key":"external-datasource-schema",
|
|
8
8
|
"name":"External Data Source Configuration Schema",
|
|
9
9
|
"description":"JSON schema for validating ExternalDataSource configuration files",
|
|
10
|
-
"version":"2.
|
|
10
|
+
"version":"2.3.0",
|
|
11
11
|
"type":"schema",
|
|
12
12
|
"category":"integration",
|
|
13
13
|
"author":"AI Fabrix Team",
|
|
@@ -91,6 +91,22 @@
|
|
|
91
91
|
"Added contract versioning configuration to datasource root for CI/CD safety and agent stability"
|
|
92
92
|
],
|
|
93
93
|
"breaking":false
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"version":"2.2.0",
|
|
97
|
+
"date":"2026-02-26T00:00:00Z",
|
|
98
|
+
"changes":[
|
|
99
|
+
"capabilities: preferred format is array [\"list\",\"get\",...]. Schema oneOf accepts both array and legacy object; runtime accepts both for backward compatibility."
|
|
100
|
+
],
|
|
101
|
+
"breaking":false
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"version":"2.3.0",
|
|
105
|
+
"date":"2026-03-08T00:00:00Z",
|
|
106
|
+
"changes":[
|
|
107
|
+
"BREAKING: Added required primaryKey (array of normalized attribute names). Used for get/update/delete and table indexing. Migration: add primaryKey array (e.g. [\"id\"] or [\"externalId\"]) to each datasource config."
|
|
108
|
+
],
|
|
109
|
+
"breaking":true
|
|
94
110
|
}
|
|
95
111
|
]
|
|
96
112
|
},
|
|
@@ -101,7 +117,8 @@
|
|
|
101
117
|
"systemKey",
|
|
102
118
|
"entityType",
|
|
103
119
|
"resourceType",
|
|
104
|
-
"fieldMappings"
|
|
120
|
+
"fieldMappings",
|
|
121
|
+
"primaryKey"
|
|
105
122
|
],
|
|
106
123
|
"properties":{
|
|
107
124
|
"key":{
|
|
@@ -147,6 +164,16 @@
|
|
|
147
164
|
"description":"Subset of JSON Schema used to validate raw input metadata.",
|
|
148
165
|
"additionalProperties":true
|
|
149
166
|
},
|
|
167
|
+
"primaryKey":{
|
|
168
|
+
"type":"array",
|
|
169
|
+
"description":"Normalized field names that uniquely identify a record (used for get/update/delete and table indexing). Each element must exist in fieldMappings.dimensions or fieldMappings.attributes.",
|
|
170
|
+
"minItems":1,
|
|
171
|
+
"items":{
|
|
172
|
+
"type":"string",
|
|
173
|
+
"pattern":"^[a-zA-Z0-9_]+$"
|
|
174
|
+
},
|
|
175
|
+
"uniqueItems":true
|
|
176
|
+
},
|
|
150
177
|
"fieldMappings":{
|
|
151
178
|
"type":"object",
|
|
152
179
|
"description":"Transformation rules and data dimensions. Maps canonical dimensions to system attributes.",
|
|
@@ -764,31 +791,27 @@
|
|
|
764
791
|
"additionalProperties":false
|
|
765
792
|
},
|
|
766
793
|
"capabilities":{
|
|
767
|
-
"
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
"type":"
|
|
772
|
-
"
|
|
773
|
-
},
|
|
774
|
-
"get":{
|
|
775
|
-
"type":"boolean",
|
|
776
|
-
"default":false
|
|
777
|
-
},
|
|
778
|
-
"create":{
|
|
779
|
-
"type":"boolean",
|
|
780
|
-
"default":false
|
|
781
|
-
},
|
|
782
|
-
"update":{
|
|
783
|
-
"type":"boolean",
|
|
784
|
-
"default":false
|
|
794
|
+
"oneOf":[
|
|
795
|
+
{
|
|
796
|
+
"type":"array",
|
|
797
|
+
"description":"Preferred: list of supported operation names. Values: list, get, create, update, delete.",
|
|
798
|
+
"items":{"type":"string","enum":["list","get","create","update","delete"]},
|
|
799
|
+
"uniqueItems":true
|
|
785
800
|
},
|
|
786
|
-
|
|
787
|
-
"type":"
|
|
788
|
-
"
|
|
801
|
+
{
|
|
802
|
+
"type":"object",
|
|
803
|
+
"description":"Legacy: object with boolean flags per operation. Accepted for backward compatibility.",
|
|
804
|
+
"properties":{
|
|
805
|
+
"list":{"type":"boolean"},
|
|
806
|
+
"get":{"type":"boolean"},
|
|
807
|
+
"create":{"type":"boolean"},
|
|
808
|
+
"update":{"type":"boolean"},
|
|
809
|
+
"delete":{"type":"boolean"}
|
|
810
|
+
},
|
|
811
|
+
"additionalProperties":false
|
|
789
812
|
}
|
|
790
|
-
|
|
791
|
-
"
|
|
813
|
+
],
|
|
814
|
+
"description":"Supported operations. When omitted, derived from execution.engine (CIP operations or list-only for Python)."
|
|
792
815
|
},
|
|
793
816
|
"execution":{
|
|
794
817
|
"type":"object",
|
|
@@ -3,18 +3,18 @@
|
|
|
3
3
|
"$id":"https://raw.githubusercontent.com/esystemsdev/aifabrix-builder/refs/heads/main/lib/schema/external-system.schema.json",
|
|
4
4
|
"title":"AI Fabrix External System Configuration Schema",
|
|
5
5
|
"description":"Schema for configuring an external system connected to the AI Fabrix Dataplane. This defines authentication, OpenAPI/MCP bindings, field mappings defaults, metadata handling and portal inputs.",
|
|
6
|
-
|
|
6
|
+
"metadata":{
|
|
7
7
|
"key":"external-system-schema",
|
|
8
8
|
"name":"External System Configuration Schema",
|
|
9
9
|
"description":"JSON schema for validating ExternalSystem configuration files",
|
|
10
|
-
"version":"1.
|
|
10
|
+
"version":"1.5.0",
|
|
11
11
|
"type":"schema",
|
|
12
12
|
"category":"integration",
|
|
13
13
|
"author":"AI Fabrix Team",
|
|
14
14
|
"createdAt":"2024-01-01T00:00:00Z",
|
|
15
|
-
"updatedAt":"2026-
|
|
15
|
+
"updatedAt":"2026-03-07T00:00:00Z",
|
|
16
16
|
"compatibility":{
|
|
17
|
-
"minVersion":"1.
|
|
17
|
+
"minVersion":"1.4.0",
|
|
18
18
|
"maxVersion":"2.0.0",
|
|
19
19
|
"deprecated":false
|
|
20
20
|
},
|
|
@@ -29,6 +29,20 @@
|
|
|
29
29
|
|
|
30
30
|
],
|
|
31
31
|
"changelog":[
|
|
32
|
+
{
|
|
33
|
+
"version":"1.5.0",
|
|
34
|
+
"date":"2026-03-07T00:00:00Z",
|
|
35
|
+
"changes":[
|
|
36
|
+
"Optional rateLimit: outbound per-system rate limit (requestsPerWindow/windowSeconds or requestsPerSecond/burstSize); dataplane enforces and handles 429"
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"version":"1.4.0",
|
|
41
|
+
"date":"2026-03-07T00:00:00Z",
|
|
42
|
+
"changes":[
|
|
43
|
+
"Optional testEndpoint in authentication.variables for apikey: full URL or path (path resolved against baseUrl); credential test URL for E2E/credential step"
|
|
44
|
+
]
|
|
45
|
+
},
|
|
32
46
|
{
|
|
33
47
|
"version":"1.3.0",
|
|
34
48
|
"date":"2026-02-18T00:00:00Z",
|
|
@@ -71,6 +85,18 @@
|
|
|
71
85
|
}
|
|
72
86
|
]
|
|
73
87
|
},
|
|
88
|
+
"$defs":{
|
|
89
|
+
"authenticationVariablesByMethod":{
|
|
90
|
+
"oauth2":{"variables":[{"key":"baseUrl","required":true,"description":"API base URL"},{"key":"tokenUrl","required":true,"description":"OAuth token endpoint. Full URL (https://...) or path (e.g. /oauth/v2/token); path is resolved against baseUrl."},{"key":"authorizationUrl","required":false,"description":"OAuth authorization endpoint. Full URL or path; path is resolved against baseUrl. Required when grantType is authorization_code or omitted."},{"key":"grantType","required":false,"description":"OAuth 2.0 grant type. One of: client_credentials, authorization_code. Default: authorization_code."},{"key":"scope","required":false},{"key":"tenantId","required":false},{"key":"testEndpoint","required":false,"description":"Optional URL used when testing the credential (GET). Full URL or path (path resolved against baseUrl). If omitted, baseUrl + /health is used."}],"security":[{"key":"clientId","required":true},{"key":"clientSecret","required":true}]},
|
|
91
|
+
"aad":{"variables":[{"key":"baseUrl","required":true},{"key":"tokenUrl","required":true,"description":"Token endpoint. Full URL or path (path resolved against baseUrl)."},{"key":"authorizationUrl","required":false,"description":"Authorization endpoint. Full URL or path (path resolved against baseUrl). Required when grantType is authorization_code or omitted."},{"key":"grantType","required":false,"description":"OAuth 2.0 grant type. One of: client_credentials, authorization_code. Default: authorization_code."},{"key":"tenantId","required":false},{"key":"testEndpoint","required":false,"description":"Optional URL used when testing the credential (GET). Full URL or path (path resolved against baseUrl). If omitted, baseUrl + /health is used."}],"security":[{"key":"clientId","required":true},{"key":"clientSecret","required":true}]},
|
|
92
|
+
"apikey":{"variables":[{"key":"baseUrl","required":true},{"key":"headerName","required":false},{"key":"prefix","required":false},{"key":"testEndpoint","required":false,"description":"Optional URL used when testing the credential (GET). Full URL (https://...) or path (e.g. /crm/v3/objects/contacts?limit=1); path is resolved against baseUrl. If omitted, baseUrl + /health is used."}],"security":[{"key":"apiKey","required":true}]},
|
|
93
|
+
"basic":{"variables":[{"key":"baseUrl","required":true},{"key":"testEndpoint","required":false,"description":"Optional URL used when testing the credential (GET). Full URL or path (path resolved against baseUrl). If omitted, baseUrl + /health is used."}],"security":[{"key":"username","required":true},{"key":"password","required":true}]},
|
|
94
|
+
"queryParam":{"variables":[{"key":"baseUrl","required":true},{"key":"paramName","required":true},{"key":"testEndpoint","required":false,"description":"Optional URL used when testing the credential (GET). Full URL or path (path resolved against baseUrl). If omitted, baseUrl + /health is used."}],"security":[{"key":"paramValue","required":true}]},
|
|
95
|
+
"oidc":{"variables":[{"key":"openIdConfigUrl","required":true},{"key":"clientId","required":true},{"key":"expectedIssuer","required":false},{"key":"algorithms","required":false},{"key":"validateSignature","required":false},{"key":"clockSkewSeconds","required":false},{"key":"testEndpoint","required":false,"description":"Optional URL used when testing the credential (GET). Full URL or path. For OIDC, discovery URL is typically used if testEndpoint omitted."}],"security":[]},
|
|
96
|
+
"hmac":{"variables":[{"key":"baseUrl","required":false},{"key":"algorithm","required":false},{"key":"signatureHeader","required":false},{"key":"timestampHeader","required":false},{"key":"signaturePrefix","required":false},{"key":"testEndpoint","required":false,"description":"Optional URL used when testing the credential (GET). Full URL or path (path resolved against baseUrl when baseUrl present)."}],"security":[{"key":"signingSecret","required":true}]},
|
|
97
|
+
"none":{"variables":[],"security":[]}
|
|
98
|
+
}
|
|
99
|
+
},
|
|
74
100
|
"type":"object",
|
|
75
101
|
"required":[
|
|
76
102
|
"key",
|
|
@@ -153,14 +179,14 @@
|
|
|
153
179
|
},
|
|
154
180
|
"variables":{
|
|
155
181
|
"type":"object",
|
|
156
|
-
"description":"Non-secret config.
|
|
182
|
+
"description":"Non-secret config. See $defs.authenticationVariablesByMethod for per-method keys. oauth2/aad: baseUrl, tokenUrl, authorizationUrl (optional; required when grantType is authorization_code or omitted), grantType (optional, default authorization_code); apikey: baseUrl, headerName (optional), prefix (optional), testEndpoint (optional); basic: baseUrl; queryParam: baseUrl, paramName; oidc: openIdConfigUrl, clientId; hmac: optional; none: empty. URL-valued variables (tokenUrl, authorizationUrl, testEndpoint) accept full URL or path; paths are resolved against baseUrl by the backend.",
|
|
157
183
|
"additionalProperties":{
|
|
158
184
|
"type":"string"
|
|
159
185
|
}
|
|
160
186
|
},
|
|
161
187
|
"security":{
|
|
162
188
|
"type":"object",
|
|
163
|
-
"description":"Secret-bearing keys only.
|
|
189
|
+
"description":"Secret-bearing keys only. Values must be kv:// references. See $defs.authenticationVariablesByMethod. oauth2/aad: clientId, clientSecret; apikey: apiKey; basic: username, password; queryParam: paramValue; oidc: none; hmac: signingSecret.",
|
|
164
190
|
"additionalProperties":{
|
|
165
191
|
"type":"string",
|
|
166
192
|
"pattern":"^kv://.+$"
|
|
@@ -172,6 +198,17 @@
|
|
|
172
198
|
"default":true
|
|
173
199
|
}
|
|
174
200
|
},
|
|
201
|
+
"examples":[
|
|
202
|
+
{"method":"oauth2","variables":{"baseUrl":"https://api.example.com","tokenUrl":"https://api.example.com/oauth/token","authorizationUrl":"https://api.example.com/oauth/authorize"},"security":{"clientId":"kv://example/clientId","clientSecret":"kv://example/clientSecret"}},
|
|
203
|
+
{"method":"oauth2","variables":{"baseUrl":"https://api.example.com","tokenUrl":"https://api.example.com/oauth/token","grantType":"client_credentials"},"security":{"clientId":"kv://example/clientId","clientSecret":"kv://example/clientSecret"}},
|
|
204
|
+
{"method":"apikey","variables":{"baseUrl":"https://api.example.com","headerName":"X-API-Key","testEndpoint":"https://api.example.com/health"},"security":{"apiKey":"kv://example/apiKey"}},
|
|
205
|
+
{"method":"apikey","variables":{"baseUrl":"https://api.example.com","headerName":"Authorization","prefix":"Bearer","testEndpoint":"/crm/v3/objects/contacts?limit=1"},"security":{"apiKey":"kv://example/apiKey"}},
|
|
206
|
+
{"method":"basic","variables":{"baseUrl":"https://api.example.com"},"security":{"username":"kv://example/username","password":"kv://example/password"}},
|
|
207
|
+
{"method":"queryParam","variables":{"baseUrl":"https://api.example.com","paramName":"api_key"},"security":{"paramValue":"kv://example/apiKey"}},
|
|
208
|
+
{"method":"oidc","variables":{"openIdConfigUrl":"https://example.com/.well-known/openid-configuration","clientId":"app-id"}},
|
|
209
|
+
{"method":"hmac","variables":{"signatureHeader":"X-Signature"},"security":{"signingSecret":"kv://example/signingSecret"}},
|
|
210
|
+
{"method":"none","variables":{}}
|
|
211
|
+
],
|
|
175
212
|
"additionalProperties":false
|
|
176
213
|
},
|
|
177
214
|
"openapi":{
|
|
@@ -446,6 +483,45 @@
|
|
|
446
483
|
"type":"string",
|
|
447
484
|
"description":"SHA256 hash of triggerPaths payload (64-char hex). Used to detect structural changes. Optional; Dataplane computes when absent.",
|
|
448
485
|
"pattern":"^[a-f0-9]{64}$"
|
|
486
|
+
},
|
|
487
|
+
"rateLimit":{
|
|
488
|
+
"type":"object",
|
|
489
|
+
"description":"Outbound rate limit for requests from the dataplane to this external system. When set, the dataplane enforces the limit per base URL and handles HTTP 429 (wait and retry). When absent, global env defaults apply (CIP_EXECUTION_RATE_LIMIT_REQUESTS_PER_SECOND, CIP_EXECUTION_RATE_LIMIT_BURST_SIZE). Supports window-based (e.g. HubSpot 100/10s) or token-bucket style (requestsPerSecond + burstSize).",
|
|
490
|
+
"properties":{
|
|
491
|
+
"requestsPerWindow":{
|
|
492
|
+
"type":"integer",
|
|
493
|
+
"minimum":1,
|
|
494
|
+
"description":"Maximum requests allowed in the time window (window-based limit). Example: 100 for HubSpot's 100 requests per 10 seconds."
|
|
495
|
+
},
|
|
496
|
+
"windowSeconds":{
|
|
497
|
+
"type":"integer",
|
|
498
|
+
"minimum":1,
|
|
499
|
+
"description":"Time window in seconds. Used with requestsPerWindow. Example: 10 for HubSpot (100 requests per 10 seconds)."
|
|
500
|
+
},
|
|
501
|
+
"requestsPerSecond":{
|
|
502
|
+
"type":"number",
|
|
503
|
+
"minimum":0.1,
|
|
504
|
+
"description":"Sustained request rate (token-bucket style). When used with burstSize, allows short bursts up to burstSize while refilling at this rate."
|
|
505
|
+
},
|
|
506
|
+
"burstSize":{
|
|
507
|
+
"type":"integer",
|
|
508
|
+
"minimum":1,
|
|
509
|
+
"description":"Maximum burst size in tokens (token-bucket style). Used with requestsPerSecond."
|
|
510
|
+
}
|
|
511
|
+
},
|
|
512
|
+
"additionalProperties":false,
|
|
513
|
+
"oneOf":[
|
|
514
|
+
{
|
|
515
|
+
"required":["requestsPerWindow","windowSeconds"]
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
"required":["requestsPerSecond","burstSize"]
|
|
519
|
+
}
|
|
520
|
+
],
|
|
521
|
+
"examples":[
|
|
522
|
+
{"requestsPerWindow":100,"windowSeconds":10},
|
|
523
|
+
{"requestsPerSecond":10,"burstSize":100}
|
|
524
|
+
]
|
|
449
525
|
}
|
|
450
526
|
},
|
|
451
527
|
"additionalProperties":false
|
|
@@ -59,6 +59,17 @@
|
|
|
59
59
|
"type": "string",
|
|
60
60
|
"description": "Known platform identifier (for known-platform type)",
|
|
61
61
|
"enum": ["hubspot", "salesforce", "zendesk", "slack", "microsoft365"]
|
|
62
|
+
},
|
|
63
|
+
"datasourceKeys": {
|
|
64
|
+
"type": "array",
|
|
65
|
+
"description": "Datasource keys to include (validated against platform; omit for all)",
|
|
66
|
+
"items": { "type": "string", "minLength": 1 },
|
|
67
|
+
"minItems": 1
|
|
68
|
+
},
|
|
69
|
+
"entityName": {
|
|
70
|
+
"type": "string",
|
|
71
|
+
"description": "Entity for multi-entity OpenAPI (validated against discover-entities; for openapi-file and openapi-url)",
|
|
72
|
+
"minLength": 1
|
|
62
73
|
}
|
|
63
74
|
},
|
|
64
75
|
"allOf": [
|
|
@@ -194,6 +205,11 @@
|
|
|
194
205
|
"type": "boolean",
|
|
195
206
|
"description": "Enable Role-Based Access Control",
|
|
196
207
|
"default": false
|
|
208
|
+
},
|
|
209
|
+
"debug": {
|
|
210
|
+
"type": "boolean",
|
|
211
|
+
"description": "When true, capture detailed generation steps and save to debug.log (dataplane returns debugLog)",
|
|
212
|
+
"default": false
|
|
197
213
|
}
|
|
198
214
|
}
|
|
199
215
|
},
|
package/lib/utils/api.js
CHANGED
|
@@ -121,10 +121,22 @@ async function handleSuccessResponse(response, url, options, duration) {
|
|
|
121
121
|
success: true
|
|
122
122
|
});
|
|
123
123
|
|
|
124
|
+
// 204 No Content or empty body: nothing to parse (avoids "Unexpected end of JSON input")
|
|
125
|
+
if (response.status === 204) {
|
|
126
|
+
return { success: true, data: null, status: response.status };
|
|
127
|
+
}
|
|
128
|
+
|
|
124
129
|
const contentType = response.headers.get('content-type');
|
|
125
130
|
if (contentType && contentType.includes('application/json')) {
|
|
126
|
-
|
|
127
|
-
|
|
131
|
+
try {
|
|
132
|
+
const data = await response.json();
|
|
133
|
+
return { success: true, data, status: response.status };
|
|
134
|
+
} catch (e) {
|
|
135
|
+
if (e instanceof SyntaxError && e.message && e.message.includes('JSON')) {
|
|
136
|
+
return { success: true, data: null, status: response.status };
|
|
137
|
+
}
|
|
138
|
+
throw e;
|
|
139
|
+
}
|
|
128
140
|
}
|
|
129
141
|
|
|
130
142
|
const text = await response.text();
|
|
@@ -286,12 +298,28 @@ function extractControllerUrl(url) {
|
|
|
286
298
|
}
|
|
287
299
|
|
|
288
300
|
/**
|
|
289
|
-
*
|
|
290
|
-
*
|
|
301
|
+
* Set auth header on headers object: Bearer for user token, x-client-token for application token.
|
|
302
|
+
* @param {Object} headers - Headers object to mutate
|
|
303
|
+
* @param {string} token - Token value
|
|
304
|
+
* @param {string} authType - 'bearer' or 'client-token'
|
|
305
|
+
*/
|
|
306
|
+
function setAuthHeader(headers, token, authType) {
|
|
307
|
+
if (!token) return;
|
|
308
|
+
if (authType === 'client-token') {
|
|
309
|
+
headers['x-client-token'] = token;
|
|
310
|
+
} else {
|
|
311
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Make an authenticated API call with user token (Bearer) or application token (x-client-token).
|
|
317
|
+
* Automatically refreshes device token on 401 when user Bearer was used.
|
|
291
318
|
* @param {string} url - API endpoint URL
|
|
292
319
|
* @param {Object} options - Fetch options
|
|
293
|
-
* @param {string|Object} tokenOrAuthConfig -
|
|
294
|
-
* @param {string} [tokenOrAuthConfig.
|
|
320
|
+
* @param {string|Object} tokenOrAuthConfig - User token string (Bearer), or authConfig object with type 'bearer'|'client-token'
|
|
321
|
+
* @param {string} [tokenOrAuthConfig.type] - 'bearer' (user token) or 'client-token' (application token)
|
|
322
|
+
* @param {string} [tokenOrAuthConfig.token] - Token (if object)
|
|
295
323
|
* @param {string} [tokenOrAuthConfig.controller] - Controller URL for token refresh (if object)
|
|
296
324
|
* @returns {Promise<Object>} Response object
|
|
297
325
|
*/
|
|
@@ -299,22 +327,22 @@ function extractControllerUrl(url) {
|
|
|
299
327
|
async function authenticatedApiCall(url, options = {}, tokenOrAuthConfig) {
|
|
300
328
|
const isStringToken = typeof tokenOrAuthConfig === 'string';
|
|
301
329
|
const token = isStringToken ? tokenOrAuthConfig : tokenOrAuthConfig?.token;
|
|
330
|
+
const authType = isStringToken ? 'bearer' : tokenOrAuthConfig?.type;
|
|
302
331
|
const authControllerUrl = isStringToken ? null : tokenOrAuthConfig?.controller;
|
|
303
332
|
const isFormData = typeof FormData !== 'undefined' && options.body instanceof FormData;
|
|
304
333
|
const headers = { ...options.headers };
|
|
305
334
|
if (!isFormData && !headers['Content-Type']) {
|
|
306
335
|
headers['Content-Type'] = 'application/json';
|
|
307
336
|
}
|
|
308
|
-
|
|
309
|
-
headers['Authorization'] = `Bearer ${token}`;
|
|
310
|
-
}
|
|
337
|
+
setAuthHeader(headers, token, authType);
|
|
311
338
|
|
|
312
339
|
const response = await makeApiCall(url, {
|
|
313
340
|
...options,
|
|
314
341
|
headers
|
|
315
342
|
});
|
|
316
343
|
|
|
317
|
-
|
|
344
|
+
// Only attempt device token refresh on 401 when user Bearer token was used (not for client-token)
|
|
345
|
+
if (!response.success && response.status === 401 && authType !== 'client-token') {
|
|
318
346
|
try {
|
|
319
347
|
const { forceRefreshDeviceToken } = require('./token-manager');
|
|
320
348
|
const refreshedToken = await forceRefreshDeviceToken(authControllerUrl || extractControllerUrl(url));
|
|
@@ -25,11 +25,12 @@ function createBearerTokenHeaders(token) {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
* Creates authentication headers for
|
|
28
|
+
* Creates authentication headers for the token-issuing endpoint only (e.g. POST /api/v1/auth/token).
|
|
29
|
+
* Do not use for Controller or Dataplane app endpoints—those require Bearer token (use createBearerTokenHeaders).
|
|
29
30
|
*
|
|
30
31
|
* @param {string} clientId - Application client ID
|
|
31
32
|
* @param {string} clientSecret - Application client secret
|
|
32
|
-
* @returns {Object} Headers object with
|
|
33
|
+
* @returns {Object} Headers object with x-client-id and x-client-secret
|
|
33
34
|
* @throws {Error} If credentials are missing
|
|
34
35
|
*/
|
|
35
36
|
function createClientCredentialsHeaders(clientId, clientSecret) {
|
|
@@ -43,14 +44,14 @@ function createClientCredentialsHeaders(clientId, clientSecret) {
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
/**
|
|
46
|
-
* Creates authentication headers based on auth configuration
|
|
47
|
-
*
|
|
47
|
+
* Creates authentication headers based on auth configuration.
|
|
48
|
+
* For app endpoints use type 'bearer' only. Use 'client-credentials' only when calling the token-issuing endpoint (e.g. /api/v1/auth/token).
|
|
48
49
|
*
|
|
49
50
|
* @param {Object} authConfig - Authentication configuration
|
|
50
|
-
* @param {string} authConfig.type - Auth type: 'bearer' or 'client-credentials'
|
|
51
|
+
* @param {string} authConfig.type - Auth type: 'bearer' (for app endpoints) or 'client-credentials' (token endpoint only)
|
|
51
52
|
* @param {string} [authConfig.token] - Bearer token (for type 'bearer')
|
|
52
|
-
* @param {string} [authConfig.clientId] - Client ID (for type 'client-credentials')
|
|
53
|
-
* @param {string} [authConfig.clientSecret] - Client secret (for type 'client-credentials')
|
|
53
|
+
* @param {string} [authConfig.clientId] - Client ID (for type 'client-credentials', token endpoint only)
|
|
54
|
+
* @param {string} [authConfig.clientSecret] - Client secret (for type 'client-credentials', token endpoint only)
|
|
54
55
|
* @returns {Object} Headers object with authentication
|
|
55
56
|
* @throws {Error} If auth config is invalid
|
|
56
57
|
*/
|
|
@@ -224,7 +224,7 @@ function buildVolumesConfig(appName) {
|
|
|
224
224
|
/**
|
|
225
225
|
* Builds networks configuration for template data
|
|
226
226
|
* @param {Object} config - Application configuration
|
|
227
|
-
* @returns {Object} Networks configuration
|
|
227
|
+
* @returns {Object} Networks configuration with databases array
|
|
228
228
|
*/
|
|
229
229
|
function buildNetworksConfig(config) {
|
|
230
230
|
return { databases: config.requires?.databases || config.databases || [] };
|
|
@@ -38,6 +38,17 @@ function registerComposeHelpers() {
|
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
handlebars.registerHelper('isVectorDatabase', (name) => isVectorDatabaseName(name));
|
|
41
|
+
|
|
42
|
+
/** Returns list of extension names for this database (config extensions + vector if name ends with "vector"). */
|
|
43
|
+
handlebars.registerHelper('extensionsForDb', (db) => {
|
|
44
|
+
if (!db) return [];
|
|
45
|
+
const explicit = Array.isArray(db.extensions) ? db.extensions : [];
|
|
46
|
+
const list = [...explicit];
|
|
47
|
+
if (isVectorDatabaseName(db.name) && !list.includes('vector')) {
|
|
48
|
+
list.push('vector');
|
|
49
|
+
}
|
|
50
|
+
return list;
|
|
51
|
+
});
|
|
41
52
|
}
|
|
42
53
|
|
|
43
54
|
module.exports = { registerComposeHelpers };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config format preference utilities (json/yaml)
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Format preference get/set for config
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Validate and normalize format (json or yaml)
|
|
11
|
+
* @param {*} format - Format value
|
|
12
|
+
* @returns {string} Normalized format ('json' or 'yaml')
|
|
13
|
+
* @throws {Error} If format is invalid
|
|
14
|
+
*/
|
|
15
|
+
function validateAndNormalizeFormat(format) {
|
|
16
|
+
if (!format || typeof format !== 'string') {
|
|
17
|
+
throw new Error('Option --format must be \'json\' or \'yaml\'');
|
|
18
|
+
}
|
|
19
|
+
const normalized = format.trim().toLowerCase();
|
|
20
|
+
if (normalized !== 'json' && normalized !== 'yaml') {
|
|
21
|
+
throw new Error('Option --format must be \'json\' or \'yaml\'');
|
|
22
|
+
}
|
|
23
|
+
return normalized;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Create format preference functions
|
|
28
|
+
* @param {Function} getConfigFn - Async function to get config
|
|
29
|
+
* @param {Function} saveConfigFn - Async function to save config
|
|
30
|
+
* @returns {{ getFormat: Function, setFormat: Function, validateAndNormalizeFormat: Function }}
|
|
31
|
+
*/
|
|
32
|
+
function createFormatFunctions(getConfigFn, saveConfigFn) {
|
|
33
|
+
return {
|
|
34
|
+
async getFormat() {
|
|
35
|
+
const config = await getConfigFn();
|
|
36
|
+
const raw = config.format;
|
|
37
|
+
if (!raw || typeof raw !== 'string') return null;
|
|
38
|
+
const normalized = raw.trim().toLowerCase();
|
|
39
|
+
return normalized === 'json' || normalized === 'yaml' ? normalized : null;
|
|
40
|
+
},
|
|
41
|
+
async setFormat(format) {
|
|
42
|
+
const normalized = validateAndNormalizeFormat(format);
|
|
43
|
+
const config = await getConfigFn();
|
|
44
|
+
config.format = normalized;
|
|
45
|
+
await saveConfigFn(config);
|
|
46
|
+
},
|
|
47
|
+
validateAndNormalizeFormat
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = { createFormatFunctions, validateAndNormalizeFormat };
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
const fs = require('fs');
|
|
15
15
|
const path = require('path');
|
|
16
16
|
const yaml = require('js-yaml');
|
|
17
|
+
const YAML = require('yaml');
|
|
17
18
|
|
|
18
19
|
const YAML_EXTENSIONS = ['.yaml', '.yml'];
|
|
19
20
|
const JSON_EXTENSIONS = ['.json'];
|
|
@@ -144,11 +145,46 @@ function writeConfigFile(filePath, object, format) {
|
|
|
144
145
|
fs.writeFileSync(filePath, content, 'utf8');
|
|
145
146
|
}
|
|
146
147
|
|
|
148
|
+
/**
|
|
149
|
+
* Writes application config YAML by updating only repaired keys in the original content,
|
|
150
|
+
* so comments and formatting on other keys are preserved. Use for repair flows that
|
|
151
|
+
* only change externalIntegration and/or app.key.
|
|
152
|
+
*
|
|
153
|
+
* @param {string} filePath - Absolute path to the YAML file
|
|
154
|
+
* @param {string} originalContent - Original file content (with comments)
|
|
155
|
+
* @param {Object} repairedVariables - Repaired config object; only externalIntegration and app are written
|
|
156
|
+
* @throws {Error} If parsing or write fails
|
|
157
|
+
*/
|
|
158
|
+
function writeYamlPreservingComments(filePath, originalContent, repairedVariables) {
|
|
159
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
160
|
+
throw new Error('writeYamlPreservingComments requires a non-empty file path');
|
|
161
|
+
}
|
|
162
|
+
if (typeof originalContent !== 'string') {
|
|
163
|
+
throw new Error('writeYamlPreservingComments requires original content string');
|
|
164
|
+
}
|
|
165
|
+
const doc = YAML.parseDocument(originalContent);
|
|
166
|
+
if (doc.errors && doc.errors.length > 0) {
|
|
167
|
+
const first = doc.errors[0];
|
|
168
|
+
throw new Error(`Invalid YAML: ${first.message}`);
|
|
169
|
+
}
|
|
170
|
+
if (!doc.contents) {
|
|
171
|
+
doc.contents = doc.createNode({});
|
|
172
|
+
}
|
|
173
|
+
if (repairedVariables.externalIntegration !== undefined) {
|
|
174
|
+
doc.set('externalIntegration', doc.createNode(repairedVariables.externalIntegration));
|
|
175
|
+
}
|
|
176
|
+
if (repairedVariables.app !== undefined) {
|
|
177
|
+
doc.set('app', doc.createNode(repairedVariables.app));
|
|
178
|
+
}
|
|
179
|
+
fs.writeFileSync(filePath, String(doc), 'utf8');
|
|
180
|
+
}
|
|
181
|
+
|
|
147
182
|
module.exports = {
|
|
148
183
|
yamlToJson,
|
|
149
184
|
jsonToYaml,
|
|
150
185
|
loadConfigFile,
|
|
151
186
|
writeConfigFile,
|
|
187
|
+
writeYamlPreservingComments,
|
|
152
188
|
isYamlPath,
|
|
153
189
|
isJsonPath
|
|
154
190
|
};
|