@aifabrix/builder 2.40.2 → 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 +7 -5
- 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/integration/hubspot/test.js +1 -1
- package/jest.config.manual.js +2 -1
- package/lib/api/credential.api.js +40 -0
- package/lib/api/dev.api.js +423 -0
- 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/credential.types.js +23 -0
- package/lib/api/types/dev.types.js +140 -0
- 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 +44 -11
- package/lib/app/down.js +2 -1
- package/lib/app/index.js +12 -1
- package/lib/app/prompts.js +44 -29
- package/lib/app/push.js +36 -12
- package/lib/app/readme.js +9 -6
- package/lib/app/run-env-compose.js +264 -0
- package/lib/app/run-helpers.js +121 -118
- package/lib/app/run.js +148 -28
- package/lib/app/show-display.js +1 -1
- package/lib/app/show.js +5 -2
- package/lib/build/index.js +11 -3
- package/lib/cli/setup-app.js +172 -15
- package/lib/cli/setup-credential-deployment.js +31 -6
- package/lib/cli/setup-dev.js +206 -16
- package/lib/cli/setup-environment.js +16 -6
- package/lib/cli/setup-external-system.js +89 -24
- package/lib/cli/setup-infra.js +82 -15
- package/lib/cli/setup-secrets.js +52 -5
- package/lib/cli/setup-utility.js +129 -24
- package/lib/commands/app-install.js +172 -0
- package/lib/commands/app-shell.js +75 -0
- package/lib/commands/app-test.js +282 -0
- package/lib/commands/app.js +1 -1
- 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-cli-handlers.js +141 -0
- package/lib/commands/dev-down.js +114 -0
- package/lib/commands/dev-init.js +347 -0
- 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/secrets-list.js +118 -0
- package/lib/commands/secrets-remove.js +97 -0
- package/lib/commands/secrets-set.js +30 -17
- package/lib/commands/secrets-validate.js +50 -0
- package/lib/commands/test-e2e-external.js +165 -0
- package/lib/commands/up-dataplane.js +2 -2
- package/lib/commands/up-miso.js +0 -25
- package/lib/commands/upload.js +96 -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/admin-secrets.js +96 -0
- package/lib/core/config.js +7 -1
- package/lib/core/secrets-ensure.js +378 -0
- package/lib/core/secrets-env-write.js +157 -0
- package/lib/core/secrets.js +176 -89
- package/lib/datasource/deploy.js +12 -3
- package/lib/datasource/field-reference-validator.js +91 -0
- package/lib/datasource/test-e2e.js +219 -0
- package/lib/datasource/test-integration.js +154 -0
- package/lib/datasource/validate.js +21 -3
- package/lib/deployment/deployer.js +7 -5
- package/lib/deployment/environment-config.js +137 -0
- package/lib/deployment/environment.js +21 -98
- package/lib/deployment/push.js +32 -2
- package/lib/external-system/download.js +188 -203
- package/lib/external-system/generator.js +204 -56
- package/lib/external-system/test-auth.js +7 -3
- 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 +56 -19
- 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 +177 -25
- 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 +155 -158
- package/lib/infrastructure/compose.js +11 -1
- package/lib/infrastructure/helpers.js +103 -20
- package/lib/infrastructure/index.js +98 -12
- package/lib/infrastructure/services.js +88 -22
- package/lib/schema/application-schema.json +32 -8
- package/lib/schema/external-datasource.schema.json +49 -26
- package/lib/schema/external-system.schema.json +509 -411
- package/lib/schema/wizard-config.schema.json +16 -0
- package/lib/utils/api.js +41 -13
- package/lib/utils/app-register-auth.js +25 -3
- package/lib/utils/auth-headers.js +8 -7
- package/lib/utils/cli-utils.js +20 -0
- package/lib/utils/compose-generator.js +77 -76
- package/lib/utils/compose-handlebars-helpers.js +54 -0
- package/lib/utils/compose-vector-helper.js +18 -0
- package/lib/utils/config-format-preference.js +51 -0
- package/lib/utils/config-format.js +36 -0
- package/lib/utils/config-paths.js +127 -2
- package/lib/utils/configuration-env-resolver.js +179 -0
- package/lib/utils/credential-display.js +83 -0
- package/lib/utils/credential-secrets-env.js +357 -0
- 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/dev-cert-helper.js +122 -0
- package/lib/utils/device-code-helpers.js +224 -0
- package/lib/utils/device-code.js +37 -336
- package/lib/utils/docker-build.js +40 -8
- package/lib/utils/env-copy.js +103 -13
- package/lib/utils/env-map.js +35 -5
- package/lib/utils/env-template.js +6 -5
- package/lib/utils/error-formatters/http-status-errors.js +20 -2
- 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 +16 -2
- package/lib/utils/infra-status.js +80 -45
- package/lib/utils/local-secrets.js +7 -52
- package/lib/utils/mutagen-install.js +195 -0
- package/lib/utils/mutagen.js +146 -0
- package/lib/utils/paths.js +128 -37
- package/lib/utils/port-resolver.js +28 -16
- package/lib/utils/remote-dev-auth.js +38 -0
- package/lib/utils/remote-docker-env.js +43 -0
- package/lib/utils/remote-secrets-loader.js +60 -0
- package/lib/utils/secrets-canonical.js +93 -0
- package/lib/utils/secrets-generator.js +114 -6
- package/lib/utils/secrets-helpers.js +108 -114
- package/lib/utils/secrets-path.js +2 -2
- package/lib/utils/secrets-utils.js +52 -1
- package/lib/utils/secrets-validation.js +84 -0
- package/lib/utils/ssh-key-helper.js +116 -0
- package/lib/utils/test-log-writer.js +56 -0
- package/lib/utils/token-manager-messages.js +90 -0
- package/lib/utils/token-manager.js +29 -36
- package/lib/utils/variable-transformer.js +3 -3
- 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 +72 -9
- package/lib/validation/wizard-datasource-validation.js +50 -0
- package/package.json +8 -3
- package/scripts/install-local.js +34 -15
- package/templates/README.md +0 -1
- package/templates/applications/README.md.hbs +4 -4
- package/templates/applications/dataplane/application.yaml +6 -5
- package/templates/applications/dataplane/env.template +15 -10
- package/templates/applications/dataplane/rbac.yaml +2 -2
- package/templates/applications/keycloak/env.template +2 -0
- package/templates/applications/miso-controller/application.yaml +1 -0
- package/templates/applications/miso-controller/env.template +12 -10
- 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 +49 -23
- package/templates/typescript/docker-compose.hbs +48 -22
- package/integration/hubspot/application.yaml +0 -37
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
const fs = require('fs').promises;
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const { testDatasourceViaPipeline } = require('../api/pipeline.api');
|
|
15
|
-
|
|
15
|
+
|
|
16
|
+
/** Pipeline test endpoints accept client credentials; do not enforce Bearer-only */
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Retry API call with exponential backoff
|
|
@@ -40,26 +41,31 @@ async function retryApiCall(fn, maxRetries = 3, backoffMs = 1000) {
|
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
/**
|
|
43
|
-
* Calls pipeline test endpoint using centralized API client
|
|
44
|
+
* Calls pipeline test endpoint using centralized API client.
|
|
45
|
+
* Pipeline test accepts Bearer, API_KEY, or client credentials (x-client-id/x-client-secret) for CI/CD.
|
|
44
46
|
* @async
|
|
45
47
|
* @param {Object} params - Function parameters
|
|
46
48
|
* @param {string} params.systemKey - System key
|
|
47
49
|
* @param {string} params.datasourceKey - Datasource key
|
|
48
50
|
* @param {Object} params.payloadTemplate - Test payload template
|
|
49
51
|
* @param {string} params.dataplaneUrl - Dataplane URL
|
|
50
|
-
* @param {Object} params.authConfig - Authentication configuration
|
|
52
|
+
* @param {Object} params.authConfig - Authentication configuration (token or client credentials)
|
|
51
53
|
* @param {number} [params.timeout] - Request timeout in milliseconds (default: 30000)
|
|
54
|
+
* @param {boolean} [params.includeDebug] - Include debug output in response
|
|
52
55
|
* @returns {Promise<Object>} Test response
|
|
53
56
|
*/
|
|
54
|
-
async function callPipelineTestEndpoint({ systemKey, datasourceKey, payloadTemplate, dataplaneUrl, authConfig, timeout = 30000 }) {
|
|
55
|
-
|
|
57
|
+
async function callPipelineTestEndpoint({ systemKey, datasourceKey, payloadTemplate, dataplaneUrl, authConfig, timeout = 30000, includeDebug = false }) {
|
|
58
|
+
const testData = { payloadTemplate };
|
|
59
|
+
if (includeDebug) {
|
|
60
|
+
testData.includeDebug = true;
|
|
61
|
+
}
|
|
56
62
|
const response = await retryApiCall(async() => {
|
|
57
63
|
return await testDatasourceViaPipeline({
|
|
58
64
|
dataplaneUrl,
|
|
59
65
|
systemKey,
|
|
60
66
|
datasourceKey,
|
|
61
67
|
authConfig,
|
|
62
|
-
testData
|
|
68
|
+
testData,
|
|
63
69
|
options: { timeout }
|
|
64
70
|
});
|
|
65
71
|
});
|
|
@@ -67,6 +73,11 @@ async function callPipelineTestEndpoint({ systemKey, datasourceKey, payloadTempl
|
|
|
67
73
|
if (!response.success || !response.data) {
|
|
68
74
|
throw new Error(`Test endpoint failed: ${response.error || response.formattedError || 'Unknown error'}`);
|
|
69
75
|
}
|
|
76
|
+
// When 200 with success: false in body, pass through; caller interprets via data.success
|
|
77
|
+
if (response.data?.success === false) {
|
|
78
|
+
const errMsg = response.data?.error || response.data?.formattedError || 'Test failed';
|
|
79
|
+
throw new Error(`Test endpoint failed: ${errMsg}`);
|
|
80
|
+
}
|
|
70
81
|
|
|
71
82
|
return response.data.data || response.data;
|
|
72
83
|
}
|
|
@@ -114,16 +125,18 @@ function determinePayloadTemplate(datasource, datasourceKey, customPayload) {
|
|
|
114
125
|
* @param {string} params.dataplaneUrl - Dataplane URL
|
|
115
126
|
* @param {Object} params.authConfig - Authentication configuration
|
|
116
127
|
* @param {number} params.timeout - Request timeout
|
|
128
|
+
* @param {boolean} [params.includeDebug] - Include debug in response
|
|
117
129
|
* @returns {Promise<Object>} Test result
|
|
118
130
|
*/
|
|
119
|
-
async function testSingleDatasource({ systemKey, datasourceKey, payloadTemplate, dataplaneUrl, authConfig, timeout }) {
|
|
131
|
+
async function testSingleDatasource({ systemKey, datasourceKey, payloadTemplate, dataplaneUrl, authConfig, timeout, includeDebug = false }) {
|
|
120
132
|
const testResponse = await callPipelineTestEndpoint({
|
|
121
133
|
systemKey,
|
|
122
134
|
datasourceKey,
|
|
123
135
|
payloadTemplate,
|
|
124
136
|
dataplaneUrl,
|
|
125
137
|
authConfig,
|
|
126
|
-
timeout
|
|
138
|
+
timeout,
|
|
139
|
+
includeDebug
|
|
127
140
|
});
|
|
128
141
|
|
|
129
142
|
return {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
const Ajv = require('ajv');
|
|
12
|
+
const addFormats = require('ajv-formats');
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Validates field mapping expression syntax (pipe-based DSL)
|
|
@@ -263,6 +264,7 @@ function validateMetadataSchema(datasource, testPayload) {
|
|
|
263
264
|
|
|
264
265
|
try {
|
|
265
266
|
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
267
|
+
addFormats(ajv);
|
|
266
268
|
const validate = ajv.compile(datasource.metadataSchema);
|
|
267
269
|
const valid = validate(payloadTemplate);
|
|
268
270
|
|
|
@@ -319,6 +321,7 @@ function validateAgainstSchema(data, schema) {
|
|
|
319
321
|
allowUnionTypes: true,
|
|
320
322
|
validateSchema: false
|
|
321
323
|
});
|
|
324
|
+
addFormats(ajv);
|
|
322
325
|
// Remove $schema for draft-2020-12 to avoid AJV issues
|
|
323
326
|
const schemaCopy = { ...schema };
|
|
324
327
|
if (schemaCopy.$schema && schemaCopy.$schema.includes('2020-12')) {
|
package/lib/utils/file-upload.js
CHANGED
|
@@ -1,29 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview File upload utilities for multipart/form-data requests
|
|
3
|
+
* All API calls go via ApiClient (lib/api/index.js); no duplicate auth logic.
|
|
3
4
|
* @author AI Fabrix Team
|
|
4
5
|
* @version 2.0.0
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
const fs = require('fs').promises;
|
|
8
9
|
const path = require('path');
|
|
9
|
-
const {
|
|
10
|
+
const { ApiClient } = require('../api');
|
|
10
11
|
|
|
11
|
-
/**
|
|
12
|
-
* Upload a file using multipart/form-data
|
|
13
|
-
* @async
|
|
14
|
-
* @function uploadFile
|
|
15
|
-
* @param {string} url - API endpoint URL
|
|
16
|
-
* @param {string} filePath - Path to file to upload
|
|
17
|
-
* @param {string} fieldName - Form field name for the file (default: 'file')
|
|
18
|
-
* @param {Object} [authConfig] - Authentication configuration
|
|
19
|
-
* @param {string} [authConfig.type] - Auth type ('bearer' | 'client-credentials')
|
|
20
|
-
* @param {string} [authConfig.token] - Bearer token
|
|
21
|
-
* @param {string} [authConfig.clientId] - Client ID
|
|
22
|
-
* @param {string} [authConfig.clientSecret] - Client secret
|
|
23
|
-
* @param {Object} [additionalFields] - Additional form fields to include
|
|
24
|
-
* @returns {Promise<Object>} API response
|
|
25
|
-
* @throws {Error} If file upload fails
|
|
26
|
-
*/
|
|
27
12
|
/**
|
|
28
13
|
* Validates file exists
|
|
29
14
|
* @async
|
|
@@ -63,47 +48,32 @@ async function buildFormData(filePath, fieldName, additionalFields) {
|
|
|
63
48
|
}
|
|
64
49
|
|
|
65
50
|
/**
|
|
66
|
-
*
|
|
67
|
-
* @
|
|
68
|
-
* @
|
|
69
|
-
* @
|
|
51
|
+
* Upload a file using multipart/form-data via ApiClient (single place for auth and API calls).
|
|
52
|
+
* @async
|
|
53
|
+
* @function uploadFile
|
|
54
|
+
* @param {string} url - Full API endpoint URL (e.g. https://dataplane.example.com/api/v1/wizard/parse-openapi)
|
|
55
|
+
* @param {string} filePath - Path to file to upload
|
|
56
|
+
* @param {string} fieldName - Form field name for the file (default: 'file')
|
|
57
|
+
* @param {Object} [authConfig] - Authentication configuration (token-only for app endpoints)
|
|
58
|
+
* @param {string} [authConfig.type] - Auth type ('bearer' | 'client-token')
|
|
59
|
+
* @param {string} [authConfig.token] - Token (Bearer user token or x-client-token application token)
|
|
60
|
+
* @param {Object} [additionalFields] - Additional form fields to include
|
|
61
|
+
* @returns {Promise<Object>} API response
|
|
62
|
+
* @throws {Error} If file upload fails
|
|
70
63
|
*/
|
|
71
|
-
function buildAuthHeaders(authConfig) {
|
|
72
|
-
const headers = {};
|
|
73
|
-
if (authConfig.type === 'bearer' && authConfig.token) {
|
|
74
|
-
headers['Authorization'] = `Bearer ${authConfig.token}`;
|
|
75
|
-
} else if (authConfig.type === 'client-credentials') {
|
|
76
|
-
if (authConfig.clientId) {
|
|
77
|
-
headers['x-client-id'] = authConfig.clientId;
|
|
78
|
-
}
|
|
79
|
-
if (authConfig.clientSecret) {
|
|
80
|
-
headers['x-client-secret'] = authConfig.clientSecret;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return headers;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
64
|
async function uploadFile(url, filePath, fieldName = 'file', authConfig = {}, additionalFields = {}) {
|
|
87
65
|
await validateFileExists(filePath);
|
|
88
66
|
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
const options = {
|
|
93
|
-
method: 'POST',
|
|
94
|
-
headers,
|
|
95
|
-
body: formData
|
|
96
|
-
};
|
|
67
|
+
const parsed = new URL(url);
|
|
68
|
+
const baseUrl = parsed.origin;
|
|
69
|
+
const endpointPath = parsed.pathname + parsed.search;
|
|
97
70
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return await authenticatedApiCall(url, options, authConfig.token);
|
|
101
|
-
}
|
|
71
|
+
const formData = await buildFormData(filePath, fieldName, additionalFields);
|
|
72
|
+
const client = new ApiClient(baseUrl, authConfig);
|
|
102
73
|
|
|
103
|
-
return await
|
|
74
|
+
return await client.postFormData(endpointPath, formData);
|
|
104
75
|
}
|
|
105
76
|
|
|
106
77
|
module.exports = {
|
|
107
78
|
uploadFile
|
|
108
79
|
};
|
|
109
|
-
|
|
@@ -45,6 +45,13 @@ const CATEGORIES = [
|
|
|
45
45
|
{ name: 'wizard' },
|
|
46
46
|
{ name: 'build', term: 'build <app>' },
|
|
47
47
|
{ name: 'run', term: 'run <app>' },
|
|
48
|
+
{ name: 'shell', term: 'shell <app>' },
|
|
49
|
+
{ name: 'test', term: 'test <app>' },
|
|
50
|
+
{ name: 'install', term: 'install <app>' },
|
|
51
|
+
{ name: 'test-e2e', term: 'test-e2e <app>' },
|
|
52
|
+
{ name: 'lint', term: 'lint <app>' },
|
|
53
|
+
{ name: 'logs', term: 'logs <app>' },
|
|
54
|
+
{ name: 'stop', term: 'stop <app>' },
|
|
48
55
|
{ name: 'dockerfile', term: 'dockerfile <app>' }
|
|
49
56
|
]
|
|
50
57
|
},
|
|
@@ -66,7 +73,10 @@ const CATEGORIES = [
|
|
|
66
73
|
name: 'Application & Datasource Management',
|
|
67
74
|
commands: [
|
|
68
75
|
{ name: 'app' },
|
|
69
|
-
{ name: 'datasource' }
|
|
76
|
+
{ name: 'datasource' },
|
|
77
|
+
{ name: 'credential' },
|
|
78
|
+
{ name: 'deployment' },
|
|
79
|
+
{ name: 'service-user' }
|
|
70
80
|
]
|
|
71
81
|
},
|
|
72
82
|
{
|
|
@@ -75,6 +85,8 @@ const CATEGORIES = [
|
|
|
75
85
|
{ name: 'resolve', term: 'resolve <app>' },
|
|
76
86
|
{ name: 'json', term: 'json <app>' },
|
|
77
87
|
{ name: 'split-json', term: 'split-json <app>' },
|
|
88
|
+
{ name: 'convert', term: 'convert <app>' },
|
|
89
|
+
{ name: 'show', term: 'show <appKey>' },
|
|
78
90
|
{ name: 'validate', term: 'validate <appOrFile>' },
|
|
79
91
|
{ name: 'diff', term: 'diff <file1> <file2>' }
|
|
80
92
|
]
|
|
@@ -83,7 +95,9 @@ const CATEGORIES = [
|
|
|
83
95
|
name: 'External Systems',
|
|
84
96
|
commands: [
|
|
85
97
|
{ name: 'download', term: 'download <system-key>' },
|
|
98
|
+
{ name: 'upload', term: 'upload <system-key>' },
|
|
86
99
|
{ name: 'delete', term: 'delete <system-key>' },
|
|
100
|
+
{ name: 'repair', term: 'repair <app>' },
|
|
87
101
|
{ name: 'test', term: 'test <app>' },
|
|
88
102
|
{ name: 'test-integration', term: 'test-integration <app>' }
|
|
89
103
|
]
|
|
@@ -92,7 +106,7 @@ const CATEGORIES = [
|
|
|
92
106
|
name: 'Developer & Secrets',
|
|
93
107
|
commands: [
|
|
94
108
|
{ name: 'dev' },
|
|
95
|
-
{ name: '
|
|
109
|
+
{ name: 'secret' },
|
|
96
110
|
{ name: 'secure' }
|
|
97
111
|
]
|
|
98
112
|
}
|
|
@@ -17,9 +17,53 @@ const containerUtils = require('./infra-containers');
|
|
|
17
17
|
|
|
18
18
|
const execAsync = promisify(exec);
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Builds services config map from ports and config flags.
|
|
22
|
+
* @param {Object} ports - Port configuration
|
|
23
|
+
* @param {Object} cfg - Config (pgadmin, redisCommander, traefik)
|
|
24
|
+
* @returns {Object} Map of serviceName -> { port, url }
|
|
25
|
+
*/
|
|
26
|
+
function buildServicesConfig(ports, cfg) {
|
|
27
|
+
const services = {
|
|
28
|
+
postgres: { port: ports.postgres, url: `localhost:${ports.postgres}` },
|
|
29
|
+
redis: { port: ports.redis, url: `localhost:${ports.redis}` }
|
|
30
|
+
};
|
|
31
|
+
if (cfg.pgadmin !== false) services.pgadmin = { port: ports.pgadmin, url: `http://localhost:${ports.pgadmin}` };
|
|
32
|
+
if (cfg.redisCommander !== false) {
|
|
33
|
+
services['redis-commander'] = { port: ports.redisCommander, url: `http://localhost:${ports.redisCommander}` };
|
|
34
|
+
}
|
|
35
|
+
if (cfg.traefik) {
|
|
36
|
+
services.traefik = {
|
|
37
|
+
port: `${ports.traefikHttp}, ${ports.traefikHttps}`,
|
|
38
|
+
url: `http://localhost:${ports.traefikHttp}, https://localhost:${ports.traefikHttps}`
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return services;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Gets status for a single service.
|
|
46
|
+
* @param {string} serviceName - Service name
|
|
47
|
+
* @param {Object} serviceConfig - { port, url }
|
|
48
|
+
* @param {string} devId - Developer ID
|
|
49
|
+
* @returns {Promise<Object>} Status entry
|
|
50
|
+
*/
|
|
51
|
+
async function getServiceStatus(serviceName, serviceConfig, devId) {
|
|
52
|
+
try {
|
|
53
|
+
const containerName = await containerUtils.findContainer(serviceName, devId, { strict: true });
|
|
54
|
+
const rawStatus = containerName
|
|
55
|
+
? (await execAsync(`docker inspect --format='{{.State.Status}}' ${containerName}`)).stdout.trim().replace(/['"]/g, '')
|
|
56
|
+
: 'not running';
|
|
57
|
+
return { status: rawStatus, port: serviceConfig.port, url: serviceConfig.url };
|
|
58
|
+
} catch {
|
|
59
|
+
return { status: 'not running', port: serviceConfig.port, url: serviceConfig.url };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
20
63
|
/**
|
|
21
64
|
* Gets the status of infrastructure services
|
|
22
|
-
* Returns detailed information about running containers
|
|
65
|
+
* Returns detailed information about running containers.
|
|
66
|
+
* Only includes pgAdmin, Redis Commander, and Traefik when enabled in config.
|
|
23
67
|
*
|
|
24
68
|
* @async
|
|
25
69
|
* @function getInfraStatus
|
|
@@ -31,51 +75,13 @@ const execAsync = promisify(exec);
|
|
|
31
75
|
*/
|
|
32
76
|
async function getInfraStatus() {
|
|
33
77
|
const devId = await config.getDeveloperId();
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
const services = {
|
|
38
|
-
postgres: { port: ports.postgres, url: `localhost:${ports.postgres}` },
|
|
39
|
-
redis: { port: ports.redis, url: `localhost:${ports.redis}` },
|
|
40
|
-
pgadmin: { port: ports.pgadmin, url: `http://localhost:${ports.pgadmin}` },
|
|
41
|
-
'redis-commander': { port: ports.redisCommander, url: `http://localhost:${ports.redisCommander}` },
|
|
42
|
-
traefik: {
|
|
43
|
-
port: `${ports.traefikHttp}, ${ports.traefikHttps}`,
|
|
44
|
-
url: `http://localhost:${ports.traefikHttp}, https://localhost:${ports.traefikHttps}`
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
|
|
78
|
+
const cfg = await config.getConfig();
|
|
79
|
+
const ports = devConfig.getDevPorts(parseInt(devId, 10));
|
|
80
|
+
const services = buildServicesConfig(ports, cfg);
|
|
48
81
|
const status = {};
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
try {
|
|
52
|
-
// Strict: only this developer's infra (no fallback to dev 0), so status reflects reality
|
|
53
|
-
const containerName = await containerUtils.findContainer(serviceName, devId, { strict: true });
|
|
54
|
-
if (containerName) {
|
|
55
|
-
const { stdout } = await execAsync(`docker inspect --format='{{.State.Status}}' ${containerName}`);
|
|
56
|
-
// Normalize status value (trim whitespace and remove quotes)
|
|
57
|
-
const normalizedStatus = stdout.trim().replace(/['"]/g, '');
|
|
58
|
-
status[serviceName] = {
|
|
59
|
-
status: normalizedStatus,
|
|
60
|
-
port: serviceConfig.port,
|
|
61
|
-
url: serviceConfig.url
|
|
62
|
-
};
|
|
63
|
-
} else {
|
|
64
|
-
status[serviceName] = {
|
|
65
|
-
status: 'not running',
|
|
66
|
-
port: serviceConfig.port,
|
|
67
|
-
url: serviceConfig.url
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
} catch (error) {
|
|
71
|
-
status[serviceName] = {
|
|
72
|
-
status: 'not running',
|
|
73
|
-
port: serviceConfig.port,
|
|
74
|
-
url: serviceConfig.url
|
|
75
|
-
};
|
|
76
|
-
}
|
|
82
|
+
for (const [name, svc] of Object.entries(services)) {
|
|
83
|
+
status[name] = await getServiceStatus(name, svc, devId);
|
|
77
84
|
}
|
|
78
|
-
|
|
79
85
|
return status;
|
|
80
86
|
}
|
|
81
87
|
|
|
@@ -186,8 +192,37 @@ async function getAppStatus() {
|
|
|
186
192
|
return apps;
|
|
187
193
|
}
|
|
188
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Lists app container names for a developer (excludes infra containers).
|
|
197
|
+
* Used by down-infra to stop/remove all app-related containers on the same network.
|
|
198
|
+
* When includeExited is true, includes stopped/exited containers (e.g. db-init one-offs).
|
|
199
|
+
*
|
|
200
|
+
* @async
|
|
201
|
+
* @function listAppContainerNamesForDeveloper
|
|
202
|
+
* @param {string} devId - Developer ID
|
|
203
|
+
* @param {Object} [options] - Options
|
|
204
|
+
* @param {boolean} [options.includeExited=false] - If true, use docker ps -a to include exited containers
|
|
205
|
+
* @returns {Promise<string[]>} Container names (e.g. aifabrix-myapp, aifabrix-keycloak-db-init)
|
|
206
|
+
*/
|
|
207
|
+
async function listAppContainerNamesForDeveloper(devId, options = {}) {
|
|
208
|
+
const devIdNum = parseInt(devId, 10);
|
|
209
|
+
const filterPattern = devIdNum === 0 ? 'aifabrix-' : `aifabrix-dev${devId}-`;
|
|
210
|
+
const infraContainers = getInfraContainerNames(devIdNum, devId);
|
|
211
|
+
const includeExited = !!options.includeExited;
|
|
212
|
+
try {
|
|
213
|
+
const allFlag = includeExited ? ' -a' : '';
|
|
214
|
+
const { stdout } = await execAsync(`docker ps${allFlag} --filter "name=${filterPattern}" --format "{{.Names}}"`);
|
|
215
|
+
const names = (stdout || '').trim().split('\n').filter(Boolean);
|
|
216
|
+
return names.filter(n => !infraContainers.includes(n));
|
|
217
|
+
} catch {
|
|
218
|
+
return [];
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
189
222
|
module.exports = {
|
|
190
223
|
getInfraStatus,
|
|
191
|
-
getAppStatus
|
|
224
|
+
getAppStatus,
|
|
225
|
+
extractAppName,
|
|
226
|
+
listAppContainerNamesForDeveloper
|
|
192
227
|
};
|
|
193
228
|
|
|
@@ -13,11 +13,12 @@ const path = require('path');
|
|
|
13
13
|
const yaml = require('js-yaml');
|
|
14
14
|
const logger = require('../utils/logger');
|
|
15
15
|
const pathsUtil = require('./paths');
|
|
16
|
+
const { mergeSecretsIntoFile } = require('./secrets-generator');
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Saves a secret to ~/.aifabrix/secrets.local.yaml
|
|
19
20
|
* Uses paths.getAifabrixHome() to respect config.yaml aifabrix-home override
|
|
20
|
-
* Merges
|
|
21
|
+
* Merges the key into the file (updates in place if key already exists, e.g. after rotate-secret)
|
|
21
22
|
*
|
|
22
23
|
* @async
|
|
23
24
|
* @function saveLocalSecret
|
|
@@ -39,48 +40,12 @@ async function saveLocalSecret(key, value) {
|
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
const secretsPath = path.join(pathsUtil.getAifabrixHome(), 'secrets.local.yaml');
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
// Create directory if needed
|
|
45
|
-
if (!fs.existsSync(secretsDir)) {
|
|
46
|
-
fs.mkdirSync(secretsDir, { recursive: true, mode: 0o700 });
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Load existing secrets
|
|
50
|
-
let existingSecrets = {};
|
|
51
|
-
if (fs.existsSync(secretsPath)) {
|
|
52
|
-
try {
|
|
53
|
-
const content = fs.readFileSync(secretsPath, 'utf8');
|
|
54
|
-
existingSecrets = yaml.load(content) || {};
|
|
55
|
-
if (typeof existingSecrets !== 'object') {
|
|
56
|
-
existingSecrets = {};
|
|
57
|
-
}
|
|
58
|
-
} catch (error) {
|
|
59
|
-
logger.warn(`Warning: Could not read existing secrets file: ${error.message}`);
|
|
60
|
-
existingSecrets = {};
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Merge with new secret
|
|
65
|
-
const updatedSecrets = {
|
|
66
|
-
...existingSecrets,
|
|
67
|
-
[key]: value
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
// Save to file
|
|
71
|
-
const yamlContent = yaml.dump(updatedSecrets, {
|
|
72
|
-
indent: 2,
|
|
73
|
-
lineWidth: 120,
|
|
74
|
-
noRefs: true,
|
|
75
|
-
sortKeys: false
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
fs.writeFileSync(secretsPath, yamlContent, { mode: 0o600 });
|
|
43
|
+
mergeSecretsIntoFile(secretsPath, { [key]: value });
|
|
79
44
|
}
|
|
80
45
|
|
|
81
46
|
/**
|
|
82
47
|
* Saves a secret to a specified secrets file path
|
|
83
|
-
* Merges
|
|
48
|
+
* Merges the key into the file (updates in place if key already exists)
|
|
84
49
|
*
|
|
85
50
|
* @async
|
|
86
51
|
* @function saveSecret
|
|
@@ -134,11 +99,11 @@ function resolveAndPrepareSecretsPath(secretsPath) {
|
|
|
134
99
|
|
|
135
100
|
/**
|
|
136
101
|
* Loads existing secrets from file
|
|
137
|
-
* @function
|
|
102
|
+
* @function _loadExistingSecrets
|
|
138
103
|
* @param {string} resolvedPath - Resolved secrets path
|
|
139
104
|
* @returns {Object} Existing secrets object
|
|
140
105
|
*/
|
|
141
|
-
function
|
|
106
|
+
function _loadExistingSecrets(resolvedPath) {
|
|
142
107
|
if (!fs.existsSync(resolvedPath)) {
|
|
143
108
|
return {};
|
|
144
109
|
}
|
|
@@ -157,17 +122,7 @@ async function saveSecret(key, value, secretsPath) {
|
|
|
157
122
|
validateSaveSecretParams(key, value, secretsPath);
|
|
158
123
|
|
|
159
124
|
const resolvedPath = resolveAndPrepareSecretsPath(secretsPath);
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const updatedSecrets = { ...existingSecrets, [key]: value };
|
|
163
|
-
const yamlContent = yaml.dump(updatedSecrets, {
|
|
164
|
-
indent: 2,
|
|
165
|
-
lineWidth: 120,
|
|
166
|
-
noRefs: true,
|
|
167
|
-
sortKeys: false
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
fs.writeFileSync(resolvedPath, yamlContent, { mode: 0o600 });
|
|
125
|
+
mergeSecretsIntoFile(resolvedPath, { [key]: value });
|
|
171
126
|
}
|
|
172
127
|
|
|
173
128
|
/**
|