@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
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview aifabrix secret list – list secret keys and values (user file, shared file, or remote API)
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 2.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const yaml = require('js-yaml');
|
|
10
|
+
const chalk = require('chalk');
|
|
11
|
+
const logger = require('../utils/logger');
|
|
12
|
+
const { getAifabrixSecretsPath } = require('../core/config');
|
|
13
|
+
const pathsUtil = require('../utils/paths');
|
|
14
|
+
const { isRemoteSecretsUrl, getRemoteDevAuth } = require('../utils/remote-dev-auth');
|
|
15
|
+
const devApi = require('../api/dev.api');
|
|
16
|
+
|
|
17
|
+
const REMOTE_NOT_CONFIGURED_MSG = 'Remote server is not configured. Set remote-server and run "aifabrix dev init" first.';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* List secret keys and values from a YAML file.
|
|
21
|
+
* @param {string} filePath - Absolute path to secrets file
|
|
22
|
+
* @returns {Array<{ key: string, value: string }>} Key-value pairs (value stringified)
|
|
23
|
+
*/
|
|
24
|
+
function listKeysAndValuesFromFile(filePath) {
|
|
25
|
+
if (!fs.existsSync(filePath)) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
30
|
+
const data = yaml.load(content) || {};
|
|
31
|
+
if (typeof data !== 'object' || Array.isArray(data)) return [];
|
|
32
|
+
return Object.entries(data).map(([key, val]) => ({
|
|
33
|
+
key,
|
|
34
|
+
value: (val !== null && val !== undefined) ? String(val) : ''
|
|
35
|
+
}));
|
|
36
|
+
} catch {
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const KEY_COL_WIDTH = 45;
|
|
42
|
+
const TABLE_SEPARATOR_LENGTH = 120;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Log a list of secret keys and values as a table (header, column headers, separator, rows).
|
|
46
|
+
* Keys are sorted alphabetically. Matches datasource list style.
|
|
47
|
+
* @param {string} emptyMessage - Message when items.length === 0
|
|
48
|
+
* @param {string} title - Table title (e.g. "User secrets")
|
|
49
|
+
* @param {Array<{ key: string, value: string }>} items - Key-value pairs
|
|
50
|
+
*/
|
|
51
|
+
function logKeyValueList(emptyMessage, title, items) {
|
|
52
|
+
if (items.length === 0) {
|
|
53
|
+
logger.log(chalk.gray(emptyMessage));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
logger.log(chalk.bold(`\n📋 ${title}:\n`));
|
|
57
|
+
logger.log(chalk.gray('Key'.padEnd(KEY_COL_WIDTH) + 'Value'));
|
|
58
|
+
logger.log(chalk.gray('-'.repeat(TABLE_SEPARATOR_LENGTH)));
|
|
59
|
+
const sorted = [...items].sort((a, b) => a.key.localeCompare(b.key, undefined, { sensitivity: 'base' }));
|
|
60
|
+
sorted.forEach(({ key, value }) => {
|
|
61
|
+
const keyCol = key.padEnd(KEY_COL_WIDTH);
|
|
62
|
+
logger.log(`${keyCol}${value}`);
|
|
63
|
+
});
|
|
64
|
+
logger.log('');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* List shared secrets (remote API or file) and log key and value.
|
|
69
|
+
* @param {string} generalSecretsPath - Path or URL for shared secrets
|
|
70
|
+
* @returns {Promise<void>}
|
|
71
|
+
*/
|
|
72
|
+
async function listSharedSecrets(generalSecretsPath) {
|
|
73
|
+
if (isRemoteSecretsUrl(generalSecretsPath)) {
|
|
74
|
+
const auth = await getRemoteDevAuth();
|
|
75
|
+
if (!auth) {
|
|
76
|
+
throw new Error(REMOTE_NOT_CONFIGURED_MSG);
|
|
77
|
+
}
|
|
78
|
+
const items = await devApi.listSecrets(auth.serverUrl, auth.clientCertPem);
|
|
79
|
+
const keyValues = items.map(i => ({ key: i.name || i.key || '', value: (i.value !== null && i.value !== undefined) ? String(i.value) : '' }));
|
|
80
|
+
logKeyValueList('No shared secrets (remote).', 'Shared secrets (remote)', keyValues);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const resolvedPath = path.isAbsolute(generalSecretsPath)
|
|
84
|
+
? generalSecretsPath
|
|
85
|
+
: path.resolve(process.cwd(), generalSecretsPath);
|
|
86
|
+
const keyValues = listKeysAndValuesFromFile(resolvedPath);
|
|
87
|
+
const fileTitle = `Shared secrets (file: ${resolvedPath})`;
|
|
88
|
+
logKeyValueList('No shared secrets in file.', fileTitle, keyValues);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** List user secrets and log key and value. */
|
|
92
|
+
function listUserSecrets() {
|
|
93
|
+
const userSecretsPath = path.join(pathsUtil.getAifabrixHome(), 'secrets.local.yaml');
|
|
94
|
+
const keyValues = listKeysAndValuesFromFile(userSecretsPath);
|
|
95
|
+
logKeyValueList('No user secrets.', 'User secrets', keyValues);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Handle secret list command. Lists key and value for each secret.
|
|
100
|
+
* @param {Object} options - Command options
|
|
101
|
+
* @param {boolean} [options.shared] - If true, list shared secrets (file or remote API)
|
|
102
|
+
* @returns {Promise<void>}
|
|
103
|
+
*/
|
|
104
|
+
async function handleSecretsList(options) {
|
|
105
|
+
const isShared = options.shared || options['shared'] || false;
|
|
106
|
+
|
|
107
|
+
if (isShared) {
|
|
108
|
+
const generalSecretsPath = await getAifabrixSecretsPath();
|
|
109
|
+
if (!generalSecretsPath) {
|
|
110
|
+
throw new Error('Shared secrets not configured. Set aifabrix-secrets in config.yaml.');
|
|
111
|
+
}
|
|
112
|
+
await listSharedSecrets(generalSecretsPath);
|
|
113
|
+
} else {
|
|
114
|
+
listUserSecrets();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = { handleSecretsList };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview aifabrix secret remove – remove a secret (user file, shared file, or remote API)
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 2.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const yaml = require('js-yaml');
|
|
10
|
+
const chalk = require('chalk');
|
|
11
|
+
const logger = require('../utils/logger');
|
|
12
|
+
const { getAifabrixSecretsPath } = require('../core/config');
|
|
13
|
+
const pathsUtil = require('../utils/paths');
|
|
14
|
+
const { isRemoteSecretsUrl, getRemoteDevAuth } = require('../utils/remote-dev-auth');
|
|
15
|
+
const devApi = require('../api/dev.api');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Remove a key from a YAML secrets file.
|
|
19
|
+
* @param {string} key - Secret key
|
|
20
|
+
* @param {string} filePath - Absolute path to secrets file
|
|
21
|
+
* @throws {Error} If file cannot be read or written
|
|
22
|
+
*/
|
|
23
|
+
function removeKeyFromFile(key, filePath) {
|
|
24
|
+
let data = {};
|
|
25
|
+
if (fs.existsSync(filePath)) {
|
|
26
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
27
|
+
data = yaml.load(content) || {};
|
|
28
|
+
if (typeof data !== 'object' || Array.isArray(data)) {
|
|
29
|
+
data = {};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
|
33
|
+
throw new Error(`Secret '${key}' not found.`);
|
|
34
|
+
}
|
|
35
|
+
delete data[key];
|
|
36
|
+
const yamlContent = yaml.dump(data, { indent: 2, lineWidth: 120, noRefs: true, sortKeys: false });
|
|
37
|
+
fs.writeFileSync(filePath, yamlContent, { mode: 0o600 });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Remove secret from shared store (remote API or file).
|
|
42
|
+
* @param {string} key - Secret key
|
|
43
|
+
* @param {string} generalSecretsPath - Path or URL for shared secrets
|
|
44
|
+
* @returns {Promise<void>}
|
|
45
|
+
*/
|
|
46
|
+
async function removeSharedSecret(key, generalSecretsPath) {
|
|
47
|
+
if (isRemoteSecretsUrl(generalSecretsPath)) {
|
|
48
|
+
const auth = await getRemoteDevAuth();
|
|
49
|
+
if (!auth) {
|
|
50
|
+
throw new Error('Remote server not configured or certificate missing. Run "aifabrix dev init" first.');
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
await devApi.deleteSecret(auth.serverUrl, auth.clientCertPem, key);
|
|
54
|
+
} catch (err) {
|
|
55
|
+
if (err.status === 404) {
|
|
56
|
+
throw new Error(`Secret '${key}' not found.`);
|
|
57
|
+
}
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
60
|
+
logger.log(chalk.green(`✓ Secret '${key}' removed from remote shared secrets.`));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const resolvedPath = path.isAbsolute(generalSecretsPath)
|
|
64
|
+
? generalSecretsPath
|
|
65
|
+
: path.resolve(process.cwd(), generalSecretsPath);
|
|
66
|
+
removeKeyFromFile(key, resolvedPath);
|
|
67
|
+
logger.log(chalk.green(`✓ Secret '${key}' removed from shared secrets file.`));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Handle secret remove command.
|
|
72
|
+
* @param {string} key - Secret key to remove
|
|
73
|
+
* @param {Object} options - Command options
|
|
74
|
+
* @param {boolean} [options.shared] - If true, remove from shared secrets (file or remote API)
|
|
75
|
+
* @returns {Promise<void>}
|
|
76
|
+
*/
|
|
77
|
+
async function handleSecretsRemove(key, options) {
|
|
78
|
+
if (!key || typeof key !== 'string') {
|
|
79
|
+
throw new Error('Secret key is required.');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const isShared = options.shared || options['shared'] || false;
|
|
83
|
+
|
|
84
|
+
if (isShared) {
|
|
85
|
+
const generalSecretsPath = await getAifabrixSecretsPath();
|
|
86
|
+
if (!generalSecretsPath) {
|
|
87
|
+
throw new Error('Shared secrets not configured. Set aifabrix-secrets in config.yaml.');
|
|
88
|
+
}
|
|
89
|
+
await removeSharedSecret(key, generalSecretsPath);
|
|
90
|
+
} else {
|
|
91
|
+
const userSecretsPath = path.join(pathsUtil.getAifabrixHome(), 'secrets.local.yaml');
|
|
92
|
+
removeKeyFromFile(key, userSecretsPath);
|
|
93
|
+
logger.log(chalk.green(`✓ Secret '${key}' removed from user secrets.`));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = { handleSecretsRemove };
|
|
@@ -15,24 +15,46 @@ const logger = require('../utils/logger');
|
|
|
15
15
|
const { getAifabrixSecretsPath } = require('../core/config');
|
|
16
16
|
const { saveLocalSecret, saveSecret } = require('../utils/local-secrets');
|
|
17
17
|
const pathsUtil = require('../utils/paths');
|
|
18
|
+
const { isRemoteSecretsUrl, getRemoteDevAuth } = require('../utils/remote-dev-auth');
|
|
19
|
+
const devApi = require('../api/dev.api');
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
|
-
* Handle
|
|
21
|
-
* Sets a secret value in either user secrets or
|
|
22
|
+
* Handle secret set command action
|
|
23
|
+
* Sets a secret value in either user secrets, general secrets file, or remote API (when aifabrix-secrets is http(s) URL).
|
|
22
24
|
*
|
|
23
25
|
* @async
|
|
24
26
|
* @function handleSecretsSet
|
|
25
27
|
* @param {string} key - Secret key name
|
|
26
28
|
* @param {string} value - Secret value (supports full URLs or environment variable interpolation)
|
|
27
29
|
* @param {Object} options - Command options
|
|
28
|
-
* @param {boolean} [options.shared] - If true, save to general secrets file
|
|
30
|
+
* @param {boolean} [options.shared] - If true, save to general secrets file or remote API
|
|
29
31
|
* @returns {Promise<void>} Resolves when secret is saved
|
|
30
32
|
* @throws {Error} If save fails or validation fails
|
|
31
|
-
*
|
|
32
|
-
* @example
|
|
33
|
-
* await handleSecretsSet('keycloak-public-server-urlKeyVault', 'https://mydomain.com/keycloak', { shared: false });
|
|
34
|
-
* await handleSecretsSet('keycloak-public-server-urlKeyVault', 'https://${VAR}:8182', { shared: true });
|
|
35
33
|
*/
|
|
34
|
+
/**
|
|
35
|
+
* Save secret to shared store (remote API or file).
|
|
36
|
+
* @param {string} key - Secret key
|
|
37
|
+
* @param {string} value - Secret value
|
|
38
|
+
* @param {string} generalSecretsPath - Path or URL for shared secrets
|
|
39
|
+
* @returns {Promise<void>}
|
|
40
|
+
*/
|
|
41
|
+
async function setSharedSecret(key, value, generalSecretsPath) {
|
|
42
|
+
if (isRemoteSecretsUrl(generalSecretsPath)) {
|
|
43
|
+
const auth = await getRemoteDevAuth();
|
|
44
|
+
if (!auth) {
|
|
45
|
+
throw new Error('Remote server not configured or certificate missing. Run "aifabrix dev init" first.');
|
|
46
|
+
}
|
|
47
|
+
await devApi.addSecret(auth.serverUrl, auth.clientCertPem, { key, value });
|
|
48
|
+
logger.log(chalk.green(`✓ Secret '${key}' saved to remote secrets (shared).`));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const resolvedPath = path.isAbsolute(generalSecretsPath)
|
|
52
|
+
? generalSecretsPath
|
|
53
|
+
: path.resolve(process.cwd(), generalSecretsPath);
|
|
54
|
+
await saveSecret(key, value, resolvedPath);
|
|
55
|
+
logger.log(chalk.green(`✓ Secret '${key}' saved to general secrets file: ${resolvedPath}`));
|
|
56
|
+
}
|
|
57
|
+
|
|
36
58
|
async function handleSecretsSet(key, value, options) {
|
|
37
59
|
if (!key || typeof key !== 'string') {
|
|
38
60
|
throw new Error('Secret key is required and must be a string');
|
|
@@ -45,21 +67,12 @@ async function handleSecretsSet(key, value, options) {
|
|
|
45
67
|
const isShared = options.shared || options['shared'] || false;
|
|
46
68
|
|
|
47
69
|
if (isShared) {
|
|
48
|
-
// Save to general secrets file
|
|
49
70
|
const generalSecretsPath = await getAifabrixSecretsPath();
|
|
50
71
|
if (!generalSecretsPath) {
|
|
51
72
|
throw new Error('General secrets file not configured. Set aifabrix-secrets in config.yaml or use without --shared flag for user secrets.');
|
|
52
73
|
}
|
|
53
|
-
|
|
54
|
-
// Resolve path (handle absolute vs relative)
|
|
55
|
-
const resolvedPath = path.isAbsolute(generalSecretsPath)
|
|
56
|
-
? generalSecretsPath
|
|
57
|
-
: path.resolve(process.cwd(), generalSecretsPath);
|
|
58
|
-
|
|
59
|
-
await saveSecret(key, value, resolvedPath);
|
|
60
|
-
logger.log(chalk.green(`✓ Secret '${key}' saved to general secrets file: ${resolvedPath}`));
|
|
74
|
+
await setSharedSecret(key, value, generalSecretsPath);
|
|
61
75
|
} else {
|
|
62
|
-
// Save to user secrets file
|
|
63
76
|
await saveLocalSecret(key, value);
|
|
64
77
|
const userSecretsPath = path.join(pathsUtil.getAifabrixHome(), 'secrets.local.yaml');
|
|
65
78
|
logger.log(chalk.green(`✓ Secret '${key}' saved to user secrets file: ${userSecretsPath}`));
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Fabrix Builder – Secrets validate command
|
|
3
|
+
*
|
|
4
|
+
* Validates a secrets file (structure and optional naming convention).
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview Secrets validate command implementation
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 2.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const chalk = require('chalk');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const logger = require('../utils/logger');
|
|
14
|
+
const { validateSecretsFile } = require('../utils/secrets-validation');
|
|
15
|
+
const pathsUtil = require('../utils/paths');
|
|
16
|
+
const secretsEnsure = require('../core/secrets-ensure');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Handle secret validate command action.
|
|
20
|
+
* Validates secrets file at given path or at resolved write target from config.
|
|
21
|
+
*
|
|
22
|
+
* @async
|
|
23
|
+
* @function handleSecretsValidate
|
|
24
|
+
* @param {string} [pathArg] - Optional path to secrets file
|
|
25
|
+
* @param {Object} options - Command options
|
|
26
|
+
* @param {boolean} [options.naming] - Check key names against *KeyVault convention
|
|
27
|
+
* @returns {Promise<{ valid: boolean, errors: string[] }>}
|
|
28
|
+
*/
|
|
29
|
+
async function handleSecretsValidate(pathArg, options = {}) {
|
|
30
|
+
let filePath = pathArg;
|
|
31
|
+
if (!filePath) {
|
|
32
|
+
const target = await secretsEnsure.resolveWriteTarget();
|
|
33
|
+
if (target.type === 'file' && target.filePath) {
|
|
34
|
+
filePath = target.filePath;
|
|
35
|
+
} else {
|
|
36
|
+
filePath = path.join(pathsUtil.getAifabrixHome(), 'secrets.local.yaml');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const result = validateSecretsFile(filePath, { checkNaming: Boolean(options.naming) });
|
|
41
|
+
if (result.valid) {
|
|
42
|
+
logger.log(chalk.green(`✓ Secrets file is valid: ${result.path}`));
|
|
43
|
+
return { valid: true, errors: [] };
|
|
44
|
+
}
|
|
45
|
+
logger.log(chalk.red(`✗ Validation failed: ${result.path}`));
|
|
46
|
+
result.errors.forEach((err) => logger.log(chalk.yellow(` • ${err}`)));
|
|
47
|
+
return { valid: false, errors: result.errors };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = { handleSecretsValidate };
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run E2E tests for all datasources of an external system.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview test-e2e <external system> – run E2E for every datasource
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const chalk = require('chalk');
|
|
14
|
+
const logger = require('../utils/logger');
|
|
15
|
+
const { getIntegrationPath } = require('../utils/paths');
|
|
16
|
+
const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
|
|
17
|
+
const { loadConfigFile } = require('../utils/config-format');
|
|
18
|
+
const { discoverIntegrationFiles, buildEffectiveDatasourceFiles } = require('./repair-internal');
|
|
19
|
+
const { runDatasourceTestE2E } = require('../datasource/test-e2e');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Derives datasource key from filename when file has no key (same logic as repair).
|
|
23
|
+
* @param {string} fileName - Datasource file name
|
|
24
|
+
* @param {string} systemKey - System key
|
|
25
|
+
* @returns {string}
|
|
26
|
+
*/
|
|
27
|
+
function deriveDatasourceKeyFromFileName(fileName, systemKey) {
|
|
28
|
+
const base = path.basename(fileName, path.extname(fileName));
|
|
29
|
+
if (/^datasource-/.test(base)) {
|
|
30
|
+
const suffix = base.slice('datasource-'.length);
|
|
31
|
+
return systemKey && typeof systemKey === 'string' ? `${systemKey}-${suffix}` : base;
|
|
32
|
+
}
|
|
33
|
+
return base.replace(/-datasource-/, '-');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* eslint-disable max-statements -- Key resolution from system or files */
|
|
37
|
+
/**
|
|
38
|
+
* Resolves the list of datasource keys for an external system (from system file or discovered files).
|
|
39
|
+
* @param {string} appPath - Integration app path
|
|
40
|
+
* @param {string} configPath - Application config path
|
|
41
|
+
* @param {Object} variables - Loaded application variables (externalIntegration.dataSources = filenames)
|
|
42
|
+
* @param {string} systemKey - System key from system file
|
|
43
|
+
* @param {Object} systemParsed - Parsed system config (may have dataSources array of keys)
|
|
44
|
+
* @param {string[]} datasourceFiles - Discovered datasource filenames
|
|
45
|
+
* @returns {string[]} Sorted list of datasource keys
|
|
46
|
+
*/
|
|
47
|
+
function getDatasourceKeys(appPath, configPath, variables, systemKey, systemParsed, datasourceFiles) {
|
|
48
|
+
const fromSystem = Array.isArray(systemParsed.dataSources) && systemParsed.dataSources.length > 0
|
|
49
|
+
? systemParsed.dataSources
|
|
50
|
+
: null;
|
|
51
|
+
const keys = [];
|
|
52
|
+
const seen = new Set();
|
|
53
|
+
if (fromSystem) {
|
|
54
|
+
fromSystem.forEach(k => {
|
|
55
|
+
if (k && typeof k === 'string' && !seen.has(k)) {
|
|
56
|
+
keys.push(k.trim());
|
|
57
|
+
seen.add(k.trim());
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
keys.sort();
|
|
61
|
+
return keys;
|
|
62
|
+
}
|
|
63
|
+
for (const fileName of datasourceFiles) {
|
|
64
|
+
const filePath = path.join(appPath, fileName);
|
|
65
|
+
if (!fs.existsSync(filePath)) continue;
|
|
66
|
+
try {
|
|
67
|
+
const parsed = loadConfigFile(filePath);
|
|
68
|
+
const key = parsed && typeof parsed.key === 'string' && parsed.key.trim()
|
|
69
|
+
? parsed.key.trim()
|
|
70
|
+
: deriveDatasourceKeyFromFileName(fileName, systemKey);
|
|
71
|
+
if (key && !seen.has(key)) {
|
|
72
|
+
keys.push(key);
|
|
73
|
+
seen.add(key);
|
|
74
|
+
}
|
|
75
|
+
} catch {
|
|
76
|
+
const key = deriveDatasourceKeyFromFileName(fileName, systemKey);
|
|
77
|
+
if (key && !seen.has(key)) {
|
|
78
|
+
keys.push(key);
|
|
79
|
+
seen.add(key);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
keys.sort();
|
|
84
|
+
return keys;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* eslint-disable max-lines-per-function, max-statements -- Load context, then loop over keys */
|
|
88
|
+
/**
|
|
89
|
+
* Runs E2E for all datasources of an external system. Uses each datasource's payloadTemplate (no extra params required).
|
|
90
|
+
*
|
|
91
|
+
* @async
|
|
92
|
+
* @param {string} externalSystem - System key (e.g. hubspot-demo)
|
|
93
|
+
* @param {Object} options - Options passed to each runDatasourceTestE2E
|
|
94
|
+
* @param {string} [options.env] - Environment (dev, tst, pro)
|
|
95
|
+
* @param {boolean} [options.debug] - Include debug, write log
|
|
96
|
+
* @param {boolean} [options.verbose] - Verbose output
|
|
97
|
+
* @param {boolean} [options.async] - If false, sync mode (default true)
|
|
98
|
+
* @returns {Promise<{ success: boolean, results: Array<{ key: string, success: boolean, error?: string }> }>}
|
|
99
|
+
*/
|
|
100
|
+
async function runTestE2EForExternalSystem(externalSystem, options = {}) {
|
|
101
|
+
if (!externalSystem || typeof externalSystem !== 'string') {
|
|
102
|
+
throw new Error('External system name is required');
|
|
103
|
+
}
|
|
104
|
+
const appPath = getIntegrationPath(externalSystem);
|
|
105
|
+
if (!fs.existsSync(appPath)) {
|
|
106
|
+
throw new Error(`Integration path not found: ${appPath}`);
|
|
107
|
+
}
|
|
108
|
+
const configPath = resolveApplicationConfigPath(appPath);
|
|
109
|
+
let variables = {};
|
|
110
|
+
if (fs.existsSync(configPath)) {
|
|
111
|
+
variables = loadConfigFile(configPath);
|
|
112
|
+
}
|
|
113
|
+
const { systemFiles, datasourceFiles: discovered } = discoverIntegrationFiles(appPath);
|
|
114
|
+
if (systemFiles.length === 0) {
|
|
115
|
+
throw new Error(`No system file found in ${appPath}. Expected *-system.yaml or *-system.json`);
|
|
116
|
+
}
|
|
117
|
+
const datasourceFiles = buildEffectiveDatasourceFiles(
|
|
118
|
+
appPath,
|
|
119
|
+
discovered,
|
|
120
|
+
variables.externalIntegration?.dataSources
|
|
121
|
+
);
|
|
122
|
+
const systemPath = path.join(appPath, systemFiles[0]);
|
|
123
|
+
const systemParsed = loadConfigFile(systemPath);
|
|
124
|
+
const systemKey = systemParsed.key ||
|
|
125
|
+
path.basename(systemFiles[0], path.extname(systemFiles[0])).replace(/-system$/, '');
|
|
126
|
+
|
|
127
|
+
const keys = getDatasourceKeys(
|
|
128
|
+
appPath,
|
|
129
|
+
configPath,
|
|
130
|
+
variables,
|
|
131
|
+
systemKey,
|
|
132
|
+
systemParsed,
|
|
133
|
+
datasourceFiles
|
|
134
|
+
);
|
|
135
|
+
if (keys.length === 0) {
|
|
136
|
+
logger.log(chalk.yellow(`No datasources found for ${externalSystem}. Add datasource files and run aifabrix repair.`));
|
|
137
|
+
return { success: true, results: [] };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const results = [];
|
|
141
|
+
const opts = {
|
|
142
|
+
app: externalSystem,
|
|
143
|
+
environment: options.env,
|
|
144
|
+
debug: options.debug,
|
|
145
|
+
verbose: options.verbose,
|
|
146
|
+
async: options.async !== false
|
|
147
|
+
};
|
|
148
|
+
for (const key of keys) {
|
|
149
|
+
try {
|
|
150
|
+
const data = await runDatasourceTestE2E(key, opts);
|
|
151
|
+
const steps = data.steps || data.completedActions || [];
|
|
152
|
+
const failed = data.success === false || steps.some(s => s.success === false || s.error);
|
|
153
|
+
results.push({ key, success: !failed });
|
|
154
|
+
} catch (err) {
|
|
155
|
+
results.push({ key, success: false, error: err.message });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const success = results.every(r => r.success);
|
|
159
|
+
return { success, results };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
module.exports = {
|
|
163
|
+
runTestE2EForExternalSystem,
|
|
164
|
+
getDatasourceKeys
|
|
165
|
+
};
|
|
@@ -86,7 +86,7 @@ async function handleUpDataplane(options = {}) {
|
|
|
86
86
|
logger.log(chalk.blue('Starting up-dataplane (register/rotate, deploy, then run dataplane locally)...\n'));
|
|
87
87
|
|
|
88
88
|
const [controllerUrl, environmentKey] = await Promise.all([resolveControllerUrl(), resolveEnvironment()]);
|
|
89
|
-
const authConfig = await checkAuthentication(controllerUrl, environmentKey);
|
|
89
|
+
const authConfig = await checkAuthentication(controllerUrl, environmentKey, { throwOnFailure: true });
|
|
90
90
|
|
|
91
91
|
const cfg = await config.getConfig();
|
|
92
92
|
const environment = (cfg && cfg.environment) ? cfg.environment : 'dev';
|
|
@@ -108,7 +108,7 @@ async function handleUpDataplane(options = {}) {
|
|
|
108
108
|
|
|
109
109
|
await app.deployApp('dataplane', deployOpts);
|
|
110
110
|
logger.log('');
|
|
111
|
-
await app.runApp('dataplane', {});
|
|
111
|
+
await app.runApp('dataplane', { skipEnvOutputPath: true });
|
|
112
112
|
|
|
113
113
|
logger.log(chalk.green('\n✓ up-dataplane complete. Dataplane is registered, deployed in dev, and running locally.'));
|
|
114
114
|
}
|
package/lib/commands/up-miso.js
CHANGED
|
@@ -14,16 +14,10 @@ const pathsUtil = require('../utils/paths');
|
|
|
14
14
|
const { loadConfigFile } = require('../utils/config-format');
|
|
15
15
|
const logger = require('../utils/logger');
|
|
16
16
|
const config = require('../core/config');
|
|
17
|
-
const secrets = require('../core/secrets');
|
|
18
17
|
const infra = require('../infrastructure');
|
|
19
18
|
const app = require('../app');
|
|
20
|
-
const { saveLocalSecret } = require('../utils/local-secrets');
|
|
21
19
|
const { ensureAppFromTemplate, patchEnvOutputPathForDeployOnly, validateEnvOutputPathFolderOrNull } = require('./up-common');
|
|
22
20
|
|
|
23
|
-
/** Keycloak base port (from templates/applications/keycloak application config) */
|
|
24
|
-
const KEYCLOAK_BASE_PORT = 8082;
|
|
25
|
-
/** Miso controller base port (dev-config app base) */
|
|
26
|
-
const MISO_BASE_PORT = 3000;
|
|
27
21
|
/**
|
|
28
22
|
* Parse --image options array into map { keycloak?: string, 'miso-controller'?: string }
|
|
29
23
|
* @param {string[]|string} imageOpts - Option value(s) e.g. ['keycloak=reg/k:v1', 'miso-controller=reg/m:v1']
|
|
@@ -64,22 +58,6 @@ function buildImageRefFromRegistry(appName, registry) {
|
|
|
64
58
|
}
|
|
65
59
|
}
|
|
66
60
|
|
|
67
|
-
/**
|
|
68
|
-
* Set URL secrets and resolve keycloak + miso-controller (no force; existing .env preserved)
|
|
69
|
-
* @async
|
|
70
|
-
* @param {number} devIdNum - Developer ID number
|
|
71
|
-
*/
|
|
72
|
-
async function setMisoSecretsAndResolve(devIdNum) {
|
|
73
|
-
const keycloakPort = KEYCLOAK_BASE_PORT + (devIdNum === 0 ? 0 : devIdNum * 100);
|
|
74
|
-
const misoPort = MISO_BASE_PORT + (devIdNum === 0 ? 0 : devIdNum * 100);
|
|
75
|
-
await saveLocalSecret('keycloak-public-server-urlKeyVault', `http://localhost:${keycloakPort}`);
|
|
76
|
-
await saveLocalSecret('miso-controller-web-server-url', `http://localhost:${misoPort}`);
|
|
77
|
-
logger.log(chalk.green('✓ Set keycloak and miso-controller URL secrets'));
|
|
78
|
-
await secrets.generateEnvFile('keycloak', undefined, 'docker', false, true);
|
|
79
|
-
await secrets.generateEnvFile('miso-controller', undefined, 'docker', false, true);
|
|
80
|
-
logger.log(chalk.green('✓ Resolved keycloak and miso-controller'));
|
|
81
|
-
}
|
|
82
|
-
|
|
83
61
|
/**
|
|
84
62
|
* Build run options and run keycloak, then miso-controller
|
|
85
63
|
* @async
|
|
@@ -130,9 +108,6 @@ async function handleUpMiso(options = {}) {
|
|
|
130
108
|
// Deploy-only: do not copy .env to repo paths; patch variables so envOutputPath is null
|
|
131
109
|
patchEnvOutputPathForDeployOnly('keycloak');
|
|
132
110
|
patchEnvOutputPathForDeployOnly('miso-controller');
|
|
133
|
-
const developerId = await config.getDeveloperId();
|
|
134
|
-
const devIdNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
|
|
135
|
-
await setMisoSecretsAndResolve(devIdNum);
|
|
136
111
|
await runMisoApps(options);
|
|
137
112
|
logger.log(chalk.green('\n✓ up-miso complete. Keycloak and miso-controller are running.') +
|
|
138
113
|
chalk.gray('\n Run onboarding and register Keycloak from the miso-controller repo if needed. Use \'aifabrix up-dataplane\' for dataplane.'));
|