@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,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test command – run tests inside app container (dev: running container; tst: ephemeral).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview App test command for builder apps (plan 65: dev = in container, tst = ephemeral)
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { spawn } = require('child_process');
|
|
10
|
+
const chalk = require('chalk');
|
|
11
|
+
const logger = require('../utils/logger');
|
|
12
|
+
const config = require('../core/config');
|
|
13
|
+
const containerHelpers = require('../utils/app-run-containers');
|
|
14
|
+
const composeGenerator = require('../utils/compose-generator');
|
|
15
|
+
const pathsUtil = require('../utils/paths');
|
|
16
|
+
const { loadConfigFile } = require('../utils/config-format');
|
|
17
|
+
const secretsEnvWrite = require('../core/secrets-env-write');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Load application config for builder app.
|
|
21
|
+
* @param {string} appName - Application name
|
|
22
|
+
* @returns {Object} Application config
|
|
23
|
+
*/
|
|
24
|
+
function loadAppConfig(appName) {
|
|
25
|
+
const builderPath = pathsUtil.getBuilderPath(appName);
|
|
26
|
+
const configPath = pathsUtil.resolveApplicationConfigPath(builderPath);
|
|
27
|
+
return loadConfigFile(configPath);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Resolve test command from application config (language or build.scripts).
|
|
32
|
+
* @param {Object} appConfig - Application config
|
|
33
|
+
* @returns {string} Shell command to run tests (e.g. "pnpm test" or "make test")
|
|
34
|
+
*/
|
|
35
|
+
function getTestCommand(appConfig) {
|
|
36
|
+
const scripts = appConfig.build?.scripts || appConfig.scripts;
|
|
37
|
+
if (scripts && typeof scripts.test === 'string' && scripts.test.trim()) {
|
|
38
|
+
return scripts.test.trim();
|
|
39
|
+
}
|
|
40
|
+
const lang = (appConfig.build?.language || appConfig.language || 'typescript').toLowerCase();
|
|
41
|
+
return lang === 'python' ? 'make test' : 'pnpm test';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Resolve test:e2e command from application config.
|
|
46
|
+
* @param {Object} appConfig - Application config
|
|
47
|
+
* @returns {string} Shell command (e.g. "pnpm test:e2e" or "make test:e2e")
|
|
48
|
+
*/
|
|
49
|
+
function getTestE2eCommand(appConfig) {
|
|
50
|
+
const scripts = appConfig.build?.scripts || appConfig.scripts;
|
|
51
|
+
const cmd = scripts?.['test:e2e'] || scripts?.testE2e;
|
|
52
|
+
if (typeof cmd === 'string' && cmd.trim()) return cmd.trim();
|
|
53
|
+
const lang = (appConfig.build?.language || appConfig.language || 'typescript').toLowerCase();
|
|
54
|
+
return lang === 'python' ? 'make test:e2e' : 'pnpm test:e2e';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Resolve test:integration command from application config (aifabrix test-integration <app>).
|
|
59
|
+
* Defaults to test:e2e when not set so builder apps can run integration-style tests.
|
|
60
|
+
* @param {Object} appConfig - Application config
|
|
61
|
+
* @returns {string} Shell command (e.g. "pnpm test:integration" or "make test-integration")
|
|
62
|
+
*/
|
|
63
|
+
function getTestIntegrationCommand(appConfig) {
|
|
64
|
+
const scripts = appConfig.build?.scripts || appConfig.scripts;
|
|
65
|
+
const cmd = scripts?.['test:integration'] || scripts?.testIntegration;
|
|
66
|
+
if (typeof cmd === 'string' && cmd.trim()) return cmd.trim();
|
|
67
|
+
return getTestE2eCommand(appConfig);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Resolve lint command from application config.
|
|
72
|
+
* @param {Object} appConfig - Application config
|
|
73
|
+
* @returns {string} Shell command (e.g. "pnpm lint" or "make lint")
|
|
74
|
+
*/
|
|
75
|
+
function getLintCommand(appConfig) {
|
|
76
|
+
const scripts = appConfig.build?.scripts || appConfig.scripts;
|
|
77
|
+
if (scripts && typeof scripts.lint === 'string' && scripts.lint.trim()) {
|
|
78
|
+
return scripts.lint.trim();
|
|
79
|
+
}
|
|
80
|
+
const lang = (appConfig.build?.language || appConfig.language || 'typescript').toLowerCase();
|
|
81
|
+
return lang === 'python' ? 'make lint' : 'pnpm lint';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Run tests in dev (exec in running container).
|
|
86
|
+
* Resolves app .env (including NPM_TOKEN/PYPI_TOKEN) and passes it so tests have registry tokens.
|
|
87
|
+
* @param {string} appName - Application name
|
|
88
|
+
* @param {string|number} developerId - Developer ID
|
|
89
|
+
* @param {string} testCmd - Test command
|
|
90
|
+
* @returns {Promise<number>} Exit code
|
|
91
|
+
*/
|
|
92
|
+
async function runTestsInDev(appName, developerId, testCmd) {
|
|
93
|
+
const containerName = containerHelpers.getContainerName(appName, developerId);
|
|
94
|
+
const isRunning = await containerHelpers.checkContainerRunning(appName, developerId);
|
|
95
|
+
if (!isRunning) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`Container ${containerName} is not running.\nRun 'aifabrix run ${appName}' first.`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
logger.log(chalk.blue(`Running tests in container ${containerName}: ${testCmd}\n`));
|
|
101
|
+
const envFilePath = await secretsEnvWrite.resolveAndWriteEnvFile(appName, {});
|
|
102
|
+
return runDockerExec(containerName, testCmd, envFilePath);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Run tests for a builder app. DEV: exec in running container; TST: ephemeral container.
|
|
107
|
+
* @param {string} appName - Application name
|
|
108
|
+
* @param {Object} [options] - { env: 'dev'|'tst' }
|
|
109
|
+
* @returns {Promise<void>}
|
|
110
|
+
*/
|
|
111
|
+
async function runAppTest(appName, options = {}) {
|
|
112
|
+
const env = (options.env || 'dev').toLowerCase();
|
|
113
|
+
if (env !== 'dev' && env !== 'tst') {
|
|
114
|
+
throw new Error('--env must be dev or tst');
|
|
115
|
+
}
|
|
116
|
+
const appConfig = loadAppConfig(appName);
|
|
117
|
+
const testCmd = getTestCommand(appConfig);
|
|
118
|
+
const developerId = await config.getDeveloperId();
|
|
119
|
+
const imageName = composeGenerator.getImageName(appConfig, appName);
|
|
120
|
+
const imageTag = appConfig.image?.tag || 'latest';
|
|
121
|
+
const fullImage = `${imageName}:${imageTag}`;
|
|
122
|
+
|
|
123
|
+
if (env === 'dev') {
|
|
124
|
+
const code = await runTestsInDev(appName, developerId, testCmd);
|
|
125
|
+
if (code !== 0) process.exit(code);
|
|
126
|
+
logger.log(chalk.green('✓ Tests completed'));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
logger.log(chalk.blue(`Running tests in ephemeral container (${fullImage}): ${testCmd}\n`));
|
|
130
|
+
const envFilePath = await secretsEnvWrite.resolveAndWriteEnvFile(appName, {});
|
|
131
|
+
const code = await runDockerRunEphemeral(fullImage, testCmd, envFilePath);
|
|
132
|
+
if (code !== 0) process.exit(code);
|
|
133
|
+
logger.log(chalk.green('✓ Tests completed'));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Run command in container via docker exec; stream output. Returns exit code.
|
|
138
|
+
* When envFilePath is set, passes --env-file so NPM_TOKEN/PYPI_TOKEN (from kv://) are available.
|
|
139
|
+
* @param {string} containerName - Container name
|
|
140
|
+
* @param {string} testCmd - Shell command
|
|
141
|
+
* @param {string|null} [envFilePath] - Path to resolved .env for --env-file (optional)
|
|
142
|
+
* @returns {Promise<number>} Exit code
|
|
143
|
+
*/
|
|
144
|
+
function runDockerExec(containerName, testCmd, envFilePath = null) {
|
|
145
|
+
return new Promise((resolve) => {
|
|
146
|
+
const args = ['exec'];
|
|
147
|
+
if (envFilePath) args.push('--env-file', envFilePath);
|
|
148
|
+
args.push(containerName, 'sh', '-c', testCmd);
|
|
149
|
+
const proc = spawn('docker', args, {
|
|
150
|
+
stdio: 'inherit',
|
|
151
|
+
shell: false
|
|
152
|
+
});
|
|
153
|
+
proc.on('close', code => resolve(code !== null ? code : 1));
|
|
154
|
+
proc.on('error', () => resolve(1));
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Run command in ephemeral container; optional --env-file. Returns exit code.
|
|
160
|
+
* @param {string} fullImage - Image:tag
|
|
161
|
+
* @param {string} testCmd - Shell command
|
|
162
|
+
* @param {string|null} [envFilePath] - Path to .env for docker run --env-file (optional)
|
|
163
|
+
* @returns {Promise<number>} Exit code
|
|
164
|
+
*/
|
|
165
|
+
function runDockerRunEphemeral(fullImage, testCmd, envFilePath = null) {
|
|
166
|
+
return new Promise((resolve) => {
|
|
167
|
+
const args = ['run', '--rm'];
|
|
168
|
+
if (envFilePath) args.push('--env-file', envFilePath);
|
|
169
|
+
args.push(fullImage, 'sh', '-c', testCmd);
|
|
170
|
+
const proc = spawn('docker', args, {
|
|
171
|
+
stdio: 'inherit',
|
|
172
|
+
shell: false
|
|
173
|
+
});
|
|
174
|
+
proc.on('close', code => resolve(code !== null ? code : 1));
|
|
175
|
+
proc.on('error', () => resolve(1));
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Run test-e2e for a builder app. DEV: exec in running container; TST: ephemeral with .env.
|
|
181
|
+
* @param {string} appName - Application name
|
|
182
|
+
* @param {Object} [options] - { env: 'dev'|'tst' }
|
|
183
|
+
* @returns {Promise<void>}
|
|
184
|
+
*/
|
|
185
|
+
async function runAppTestE2e(appName, options = {}) {
|
|
186
|
+
const env = (options.env || 'dev').toLowerCase();
|
|
187
|
+
if (env !== 'dev' && env !== 'tst') {
|
|
188
|
+
throw new Error('--env must be dev or tst');
|
|
189
|
+
}
|
|
190
|
+
const appConfig = loadAppConfig(appName);
|
|
191
|
+
const cmd = getTestE2eCommand(appConfig);
|
|
192
|
+
const developerId = await config.getDeveloperId();
|
|
193
|
+
const imageName = composeGenerator.getImageName(appConfig, appName);
|
|
194
|
+
const imageTag = appConfig.image?.tag || 'latest';
|
|
195
|
+
const fullImage = `${imageName}:${imageTag}`;
|
|
196
|
+
|
|
197
|
+
if (env === 'dev') {
|
|
198
|
+
const code = await runTestsInDev(appName, developerId, cmd);
|
|
199
|
+
if (code !== 0) process.exit(code);
|
|
200
|
+
logger.log(chalk.green('✓ Test-e2e completed'));
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
logger.log(chalk.blue(`Running test-e2e in ephemeral container (${fullImage}): ${cmd}\n`));
|
|
204
|
+
const envFilePath = await secretsEnvWrite.resolveAndWriteEnvFile(appName, {});
|
|
205
|
+
const code = await runDockerRunEphemeral(fullImage, cmd, envFilePath);
|
|
206
|
+
if (code !== 0) process.exit(code);
|
|
207
|
+
logger.log(chalk.green('✓ Test-e2e completed'));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Run test-integration for a builder app (integration tests in container). DEV: exec in running container; TST: ephemeral with .env.
|
|
212
|
+
* Uses build.scripts.testIntegration or test:integration; falls back to test:e2e when not set.
|
|
213
|
+
* @param {string} appName - Application name
|
|
214
|
+
* @param {Object} [options] - { env: 'dev'|'tst' }
|
|
215
|
+
* @returns {Promise<void>}
|
|
216
|
+
*/
|
|
217
|
+
async function runAppTestIntegration(appName, options = {}) {
|
|
218
|
+
const env = (options.env || 'dev').toLowerCase();
|
|
219
|
+
if (env !== 'dev' && env !== 'tst') {
|
|
220
|
+
throw new Error('--env must be dev or tst');
|
|
221
|
+
}
|
|
222
|
+
const appConfig = loadAppConfig(appName);
|
|
223
|
+
const cmd = getTestIntegrationCommand(appConfig);
|
|
224
|
+
const developerId = await config.getDeveloperId();
|
|
225
|
+
const imageName = composeGenerator.getImageName(appConfig, appName);
|
|
226
|
+
const imageTag = appConfig.image?.tag || 'latest';
|
|
227
|
+
const fullImage = `${imageName}:${imageTag}`;
|
|
228
|
+
|
|
229
|
+
if (env === 'dev') {
|
|
230
|
+
const code = await runTestsInDev(appName, developerId, cmd);
|
|
231
|
+
if (code !== 0) process.exit(code);
|
|
232
|
+
logger.log(chalk.green('✓ Test-integration completed'));
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
logger.log(chalk.blue(`Running test-integration in ephemeral container (${fullImage}): ${cmd}\n`));
|
|
236
|
+
const envFilePath = await secretsEnvWrite.resolveAndWriteEnvFile(appName, {});
|
|
237
|
+
const code = await runDockerRunEphemeral(fullImage, cmd, envFilePath);
|
|
238
|
+
if (code !== 0) process.exit(code);
|
|
239
|
+
logger.log(chalk.green('✓ Test-integration completed'));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Run lint for a builder app. DEV: exec in running container; TST: ephemeral with .env.
|
|
244
|
+
* @param {string} appName - Application name
|
|
245
|
+
* @param {Object} [options] - { env: 'dev'|'tst' }
|
|
246
|
+
* @returns {Promise<void>}
|
|
247
|
+
*/
|
|
248
|
+
async function runAppLint(appName, options = {}) {
|
|
249
|
+
const env = (options.env || 'dev').toLowerCase();
|
|
250
|
+
if (env !== 'dev' && env !== 'tst') {
|
|
251
|
+
throw new Error('--env must be dev or tst');
|
|
252
|
+
}
|
|
253
|
+
const appConfig = loadAppConfig(appName);
|
|
254
|
+
const cmd = getLintCommand(appConfig);
|
|
255
|
+
const developerId = await config.getDeveloperId();
|
|
256
|
+
const imageName = composeGenerator.getImageName(appConfig, appName);
|
|
257
|
+
const imageTag = appConfig.image?.tag || 'latest';
|
|
258
|
+
const fullImage = `${imageName}:${imageTag}`;
|
|
259
|
+
|
|
260
|
+
if (env === 'dev') {
|
|
261
|
+
const code = await runTestsInDev(appName, developerId, cmd);
|
|
262
|
+
if (code !== 0) process.exit(code);
|
|
263
|
+
logger.log(chalk.green('✓ Lint completed'));
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
logger.log(chalk.blue(`Running lint in ephemeral container (${fullImage}): ${cmd}\n`));
|
|
267
|
+
const envFilePath = await secretsEnvWrite.resolveAndWriteEnvFile(appName, {});
|
|
268
|
+
const code = await runDockerRunEphemeral(fullImage, cmd, envFilePath);
|
|
269
|
+
if (code !== 0) process.exit(code);
|
|
270
|
+
logger.log(chalk.green('✓ Lint completed'));
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
module.exports = {
|
|
274
|
+
runAppTest,
|
|
275
|
+
getTestCommand,
|
|
276
|
+
getTestE2eCommand,
|
|
277
|
+
getTestIntegrationCommand,
|
|
278
|
+
getLintCommand,
|
|
279
|
+
runAppTestE2e,
|
|
280
|
+
runAppTestIntegration,
|
|
281
|
+
runAppLint
|
|
282
|
+
};
|
package/lib/commands/app.js
CHANGED
|
@@ -21,7 +21,7 @@ function setupAppRegisterCommand(app) {
|
|
|
21
21
|
app.command('register <appKey>')
|
|
22
22
|
.description('Register application and get pipeline credentials')
|
|
23
23
|
.option('-p, --port <port>', 'Application port (default: from application.yaml)')
|
|
24
|
-
.option('-u, --url <url>', 'Application URL. If omitted: app.url, deployment.dataplaneUrl or deployment.appUrl in application.yaml; else http://localhost:{
|
|
24
|
+
.option('-u, --url <url>', 'Application URL. If omitted: app.url, deployment.dataplaneUrl or deployment.appUrl in application.yaml; else http://localhost:{port})')
|
|
25
25
|
.option('-n, --name <name>', 'Override display name')
|
|
26
26
|
.option('-d, --description <desc>', 'Override description')
|
|
27
27
|
.action(async(appKey, options) => {
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential env command – prompts for KV_* values and writes .env.
|
|
3
|
+
* Used by `aifabrix credential env <system-key>`.
|
|
4
|
+
*
|
|
5
|
+
* @fileoverview Credential env command – interactive credential capture to .env
|
|
6
|
+
* @author AI Fabrix Team
|
|
7
|
+
* @version 2.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const chalk = require('chalk');
|
|
13
|
+
const logger = require('../utils/logger');
|
|
14
|
+
const { getIntegrationPath } = require('../utils/paths');
|
|
15
|
+
const { kvEnvKeyToPath } = require('../utils/credential-secrets-env');
|
|
16
|
+
const { parseEnvToMap } = require('../utils/credential-secrets-env');
|
|
17
|
+
|
|
18
|
+
const KV_PREFIX = 'KV_';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Secret var suffixes (use password prompt).
|
|
22
|
+
* @type {Set<string>}
|
|
23
|
+
*/
|
|
24
|
+
const SECRET_SUFFIXES = new Set([
|
|
25
|
+
'CLIENTID', 'CLIENTSECRET', 'APIKEY', 'USERNAME', 'PASSWORD', 'PARAMVALUE',
|
|
26
|
+
'SIGNINGSECRET', 'BEARERTOKEN'
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Validates system-key format.
|
|
31
|
+
* @param {string} systemKey - System key
|
|
32
|
+
* @throws {Error} If invalid
|
|
33
|
+
*/
|
|
34
|
+
function validateSystemKeyFormat(systemKey) {
|
|
35
|
+
if (!systemKey || typeof systemKey !== 'string') {
|
|
36
|
+
throw new Error('System key is required and must be a string');
|
|
37
|
+
}
|
|
38
|
+
if (!/^[a-z0-9-_]+$/.test(systemKey)) {
|
|
39
|
+
throw new Error('System key must contain only lowercase letters, numbers, hyphens, and underscores');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Extracts KV_* variable names from env.template content.
|
|
45
|
+
* @param {string} content - env.template content
|
|
46
|
+
* @returns {Array<{ key: string, isSecret: boolean }>} KV_* vars to prompt
|
|
47
|
+
*/
|
|
48
|
+
function extractKvVarsFromTemplate(content) {
|
|
49
|
+
if (!content || typeof content !== 'string') return [];
|
|
50
|
+
const vars = [];
|
|
51
|
+
const seen = new Set();
|
|
52
|
+
const lines = content.split(/\r?\n/);
|
|
53
|
+
for (const line of lines) {
|
|
54
|
+
const trimmed = line.trim();
|
|
55
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
56
|
+
const eq = trimmed.indexOf('=');
|
|
57
|
+
if (eq <= 0) continue;
|
|
58
|
+
const key = trimmed.substring(0, eq).trim();
|
|
59
|
+
if (!key.toUpperCase().startsWith(KV_PREFIX)) continue;
|
|
60
|
+
if (kvEnvKeyToPath(key) && !seen.has(key)) {
|
|
61
|
+
seen.add(key);
|
|
62
|
+
const suffix = key.slice(KV_PREFIX.length).split('_').pop() || '';
|
|
63
|
+
vars.push({ key, isSecret: SECRET_SUFFIXES.has(suffix.toUpperCase()) });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return vars;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Prompts for KV_* values using inquirer.
|
|
71
|
+
* @async
|
|
72
|
+
* @param {Array<{ key: string, isSecret: boolean }>} vars - Variables to prompt
|
|
73
|
+
* @param {Object} existingMap - Existing .env key-value map (for default values)
|
|
74
|
+
* @returns {Promise<Object.<string, string>>} Key-value map from prompts
|
|
75
|
+
*/
|
|
76
|
+
async function promptForKvValues(vars, existingMap) {
|
|
77
|
+
if (vars.length === 0) return {};
|
|
78
|
+
const inquirer = require('inquirer');
|
|
79
|
+
const questions = vars.map(({ key, isSecret }) => ({
|
|
80
|
+
type: isSecret ? 'password' : 'input',
|
|
81
|
+
name: key,
|
|
82
|
+
message: key,
|
|
83
|
+
default: existingMap[key] || undefined
|
|
84
|
+
}));
|
|
85
|
+
return await inquirer.prompt(questions);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Builds .env content: template lines with KV_* values replaced/merged from prompts.
|
|
90
|
+
* Preserves comments, non-KV lines, and structure; updates KV_* with prompted values.
|
|
91
|
+
* @param {string} templateContent - env.template content
|
|
92
|
+
* @param {Object.<string, string>} promptValues - Values from prompts
|
|
93
|
+
* @returns {string} Final .env content
|
|
94
|
+
*/
|
|
95
|
+
function buildEnvContent(templateContent, promptValues) {
|
|
96
|
+
if (!templateContent || typeof templateContent !== 'string') return '';
|
|
97
|
+
const lines = templateContent.split(/\r?\n/);
|
|
98
|
+
const result = [];
|
|
99
|
+
for (const line of lines) {
|
|
100
|
+
const trimmed = line.trim();
|
|
101
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
102
|
+
result.push(line);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const eq = trimmed.indexOf('=');
|
|
106
|
+
if (eq <= 0) {
|
|
107
|
+
result.push(line);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const key = trimmed.substring(0, eq).trim();
|
|
111
|
+
if (key.toUpperCase().startsWith(KV_PREFIX) && key in promptValues) {
|
|
112
|
+
result.push(`${key}=${promptValues[key]}`);
|
|
113
|
+
} else {
|
|
114
|
+
result.push(line);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return result.join('\n');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Runs credential env command: prompt for KV_* values and write .env.
|
|
122
|
+
* @async
|
|
123
|
+
* @param {string} systemKey - External system key (integration/<system-key>/)
|
|
124
|
+
* @returns {Promise<string>} Path to written .env file
|
|
125
|
+
* @throws {Error} If env.template missing or write fails
|
|
126
|
+
*/
|
|
127
|
+
function loadExistingEnvMap(envPath) {
|
|
128
|
+
if (!fs.existsSync(envPath)) return {};
|
|
129
|
+
return parseEnvToMap(fs.readFileSync(envPath, 'utf8'));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function runCredentialEnv(systemKey) {
|
|
133
|
+
validateSystemKeyFormat(systemKey);
|
|
134
|
+
const appPath = getIntegrationPath(systemKey);
|
|
135
|
+
const envTemplatePath = path.join(appPath, 'env.template');
|
|
136
|
+
const envPath = path.join(appPath, '.env');
|
|
137
|
+
|
|
138
|
+
if (!fs.existsSync(envTemplatePath)) {
|
|
139
|
+
throw new Error(`env.template not found at ${envTemplatePath}. Create the integration first (e.g. aifabrix wizard or download).`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const templateContent = fs.readFileSync(envTemplatePath, 'utf8');
|
|
143
|
+
const vars = extractKvVarsFromTemplate(templateContent);
|
|
144
|
+
|
|
145
|
+
if (vars.length === 0) {
|
|
146
|
+
logger.log(chalk.yellow('No KV_* variables in env.template. Nothing to prompt.'));
|
|
147
|
+
return envPath;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const existingMap = loadExistingEnvMap(envPath);
|
|
151
|
+
logger.log(chalk.blue(`\nEnter credential values for ${systemKey} (integration/${systemKey}/):`));
|
|
152
|
+
const promptValues = await promptForKvValues(vars, existingMap);
|
|
153
|
+
const content = buildEnvContent(templateContent, promptValues);
|
|
154
|
+
|
|
155
|
+
const dir = path.dirname(envPath);
|
|
156
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
157
|
+
fs.writeFileSync(envPath, content, { mode: 0o600 });
|
|
158
|
+
logger.log(chalk.green(`✓ Wrote ${envPath}`));
|
|
159
|
+
return envPath;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
module.exports = { runCredentialEnv, validateSystemKeyFormat, extractKvVarsFromTemplate, buildEnvContent };
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Credential list command – list credentials from Dataplane
|
|
3
3
|
* GET /api/v1/credential. Used by `aifabrix credential list`.
|
|
4
|
-
* The Controller does not expose this endpoint; credentials are listed from the Dataplane.
|
|
5
4
|
*
|
|
6
5
|
* @fileoverview Credential list command implementation
|
|
7
6
|
* @author AI Fabrix Team
|
|
@@ -10,6 +9,7 @@
|
|
|
10
9
|
|
|
11
10
|
const chalk = require('chalk');
|
|
12
11
|
const logger = require('../utils/logger');
|
|
12
|
+
const { formatCredentialWithStatus } = require('../utils/credential-display');
|
|
13
13
|
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
14
14
|
const { getOrRefreshDeviceToken } = require('../utils/token-manager');
|
|
15
15
|
const { normalizeControllerUrl, resolveEnvironment } = require('../core/config');
|
|
@@ -37,12 +37,14 @@ async function getCredentialListAuth(controllerUrl) {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
* Extract credentials array from API response
|
|
41
|
-
*
|
|
40
|
+
* Extract credentials array from API response.
|
|
41
|
+
* Handles: { data: { meta, data: [...] } } (paginated), { data: [...] }, { credentials: [...] }, { items: [...] }.
|
|
42
|
+
* @param {Object} response - API response (e.g. { success, data } from API client, or raw body)
|
|
42
43
|
* @returns {Array}
|
|
43
44
|
*/
|
|
44
45
|
function extractCredentials(response) {
|
|
45
|
-
const
|
|
46
|
+
const body = response?.data ?? response;
|
|
47
|
+
const data = body?.data ?? body;
|
|
46
48
|
const items = data?.credentials ?? data?.items ?? (Array.isArray(data) ? data : []);
|
|
47
49
|
return Array.isArray(items) ? items : [];
|
|
48
50
|
}
|
|
@@ -59,9 +61,10 @@ function displayCredentialList(list, baseUrl) {
|
|
|
59
61
|
return;
|
|
60
62
|
}
|
|
61
63
|
list.forEach((c) => {
|
|
62
|
-
const key
|
|
63
|
-
const
|
|
64
|
-
|
|
64
|
+
const { key, name, statusFormatted, statusLabel } = formatCredentialWithStatus(c);
|
|
65
|
+
const prefix = statusFormatted ? `${statusFormatted} ` : ' ';
|
|
66
|
+
const line = `${prefix}${chalk.cyan(key)} - ${name}${statusLabel}`;
|
|
67
|
+
logger.log(line);
|
|
65
68
|
});
|
|
66
69
|
logger.log('');
|
|
67
70
|
}
|
|
@@ -69,10 +72,10 @@ function displayCredentialList(list, baseUrl) {
|
|
|
69
72
|
/**
|
|
70
73
|
* Ensure controller URL and auth; exit on failure. Returns { controllerUrl, authConfig } when valid.
|
|
71
74
|
* @async
|
|
72
|
-
* @param {Object} options - CLI options
|
|
75
|
+
* @param {Object} options - CLI options
|
|
73
76
|
* @returns {Promise<{controllerUrl: string, authConfig: Object}>}
|
|
74
77
|
*/
|
|
75
|
-
async function ensureControllerAndAuth(options) {
|
|
78
|
+
async function ensureControllerAndAuth(options = {}) {
|
|
76
79
|
const controllerUrl = options.controller || (await resolveControllerUrl());
|
|
77
80
|
if (!controllerUrl) {
|
|
78
81
|
logger.error(chalk.red('❌ Controller URL is required. Run "aifabrix login" first.'));
|
|
@@ -91,19 +94,14 @@ async function ensureControllerAndAuth(options) {
|
|
|
91
94
|
}
|
|
92
95
|
|
|
93
96
|
/**
|
|
94
|
-
* Resolve Dataplane URL for credential list (
|
|
97
|
+
* Resolve Dataplane URL for credential list (discover from controller + environment)
|
|
95
98
|
* @async
|
|
96
99
|
* @param {string} controllerUrl - Controller base URL
|
|
97
100
|
* @param {Object} authConfig - Auth config with token
|
|
98
|
-
* @param {Object} options - CLI options
|
|
99
|
-
* @param {string} [options.dataplane] - Optional Dataplane URL override
|
|
100
101
|
* @returns {Promise<string>} Dataplane base URL
|
|
101
102
|
* @throws {Error} When resolution fails (caller should exit)
|
|
102
103
|
*/
|
|
103
|
-
async function resolveCredentialListDataplaneUrl(controllerUrl, authConfig
|
|
104
|
-
if (options.dataplane) {
|
|
105
|
-
return options.dataplane.replace(/\/$/, '');
|
|
106
|
-
}
|
|
104
|
+
async function resolveCredentialListDataplaneUrl(controllerUrl, authConfig) {
|
|
107
105
|
const environment = await resolveEnvironment();
|
|
108
106
|
return await resolveDataplaneUrl(controllerUrl, environment, authConfig);
|
|
109
107
|
}
|
|
@@ -127,12 +125,11 @@ async function fetchAndDisplayCredentials(dataplaneUrl, authConfig, listOptions)
|
|
|
127
125
|
* @param {Object} options - CLI options
|
|
128
126
|
* @returns {Promise<string>} Dataplane URL (never returns on failure; process.exit(1))
|
|
129
127
|
*/
|
|
130
|
-
async function resolveDataplaneUrlOrExit(controllerUrl, authConfig
|
|
128
|
+
async function resolveDataplaneUrlOrExit(controllerUrl, authConfig) {
|
|
131
129
|
try {
|
|
132
|
-
return await resolveCredentialListDataplaneUrl(controllerUrl, authConfig
|
|
130
|
+
return await resolveCredentialListDataplaneUrl(controllerUrl, authConfig);
|
|
133
131
|
} catch (err) {
|
|
134
132
|
logger.error(chalk.red(`❌ Could not resolve Dataplane URL: ${err.message}`));
|
|
135
|
-
logger.error(chalk.gray('Use --dataplane <url> to specify the Dataplane URL directly.'));
|
|
136
133
|
process.exit(1);
|
|
137
134
|
}
|
|
138
135
|
}
|
|
@@ -141,15 +138,13 @@ async function resolveDataplaneUrlOrExit(controllerUrl, authConfig, options) {
|
|
|
141
138
|
* Run credential list command: call GET /api/v1/credential on Dataplane and display results
|
|
142
139
|
* @async
|
|
143
140
|
* @param {Object} options - CLI options
|
|
144
|
-
* @param {string} [options.controller] - Controller URL override
|
|
145
|
-
* @param {string} [options.dataplane] - Dataplane URL override (default: resolved from controller + environment)
|
|
146
141
|
* @param {boolean} [options.activeOnly] - List only active credentials
|
|
147
142
|
* @param {number} [options.pageSize] - Items per page
|
|
148
143
|
* @returns {Promise<void>}
|
|
149
144
|
*/
|
|
150
145
|
async function runCredentialList(options = {}) {
|
|
151
146
|
const { controllerUrl, authConfig } = await ensureControllerAndAuth(options);
|
|
152
|
-
const dataplaneUrl = await resolveDataplaneUrlOrExit(controllerUrl, authConfig
|
|
147
|
+
const dataplaneUrl = await resolveDataplaneUrlOrExit(controllerUrl, authConfig);
|
|
153
148
|
const listOptions = {
|
|
154
149
|
pageSize: options.pageSize || DEFAULT_PAGE_SIZE,
|
|
155
150
|
activeOnly: options.activeOnly
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential push command – pushes KV_* from .env to dataplane.
|
|
3
|
+
* Used by `aifabrix credential push <system-key>`.
|
|
4
|
+
*
|
|
5
|
+
* @fileoverview Credential push command – push credential secrets to dataplane
|
|
6
|
+
* @author AI Fabrix Team
|
|
7
|
+
* @version 2.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const chalk = require('chalk');
|
|
12
|
+
const logger = require('../utils/logger');
|
|
13
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
14
|
+
const { getDeploymentAuth, requireBearerForDataplanePipeline } = require('../utils/token-manager');
|
|
15
|
+
const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
|
|
16
|
+
const { getIntegrationPath } = require('../utils/paths');
|
|
17
|
+
const { pushCredentialSecrets } = require('../utils/credential-secrets-env');
|
|
18
|
+
const { generateControllerManifest } = require('../generator/external-controller-manifest');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Validates system-key format.
|
|
22
|
+
* @param {string} systemKey - System key
|
|
23
|
+
* @throws {Error} If invalid
|
|
24
|
+
*/
|
|
25
|
+
function validateSystemKeyFormat(systemKey) {
|
|
26
|
+
if (!systemKey || typeof systemKey !== 'string') {
|
|
27
|
+
throw new Error('System key is required and must be a string');
|
|
28
|
+
}
|
|
29
|
+
if (!/^[a-z0-9-_]+$/.test(systemKey)) {
|
|
30
|
+
throw new Error('System key must contain only lowercase letters, numbers, hyphens, and underscores');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Builds upload payload for credential push (same shape as upload).
|
|
36
|
+
* @param {string} systemKey - System key
|
|
37
|
+
* @returns {Promise<Object>} { version, application, dataSources }
|
|
38
|
+
*/
|
|
39
|
+
async function buildPayload(systemKey) {
|
|
40
|
+
const manifest = await generateControllerManifest(systemKey, { type: 'external' });
|
|
41
|
+
return {
|
|
42
|
+
version: manifest.version || '1.0.0',
|
|
43
|
+
application: manifest.system,
|
|
44
|
+
dataSources: manifest.dataSources || []
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Runs credential push: push KV_* from .env to dataplane.
|
|
50
|
+
* @async
|
|
51
|
+
* @param {string} systemKey - External system key
|
|
52
|
+
* @returns {Promise<{ pushed: number }>} Count of secrets pushed
|
|
53
|
+
* @throws {Error} If auth or push fails
|
|
54
|
+
*/
|
|
55
|
+
function logPushResult(pushResult) {
|
|
56
|
+
if (pushResult.pushed > 0) {
|
|
57
|
+
const keyList = pushResult.keys?.length ? ` (${pushResult.keys.join(', ')})` : '';
|
|
58
|
+
logger.log(chalk.green(`✓ Pushed ${pushResult.pushed} credential secret(s) to dataplane${keyList}.`));
|
|
59
|
+
} else if (pushResult.skipped) {
|
|
60
|
+
logger.log(chalk.yellow('No credential secrets to push (empty .env or no KV_* vars with values).'));
|
|
61
|
+
} else {
|
|
62
|
+
logger.log(chalk.yellow('Secret push skipped'));
|
|
63
|
+
}
|
|
64
|
+
if (pushResult.warning) logger.log(chalk.yellow(`Warning: ${pushResult.warning}`));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function runCredentialPush(systemKey) {
|
|
68
|
+
validateSystemKeyFormat(systemKey);
|
|
69
|
+
const appPath = getIntegrationPath(systemKey);
|
|
70
|
+
const envFilePath = path.join(appPath, '.env');
|
|
71
|
+
|
|
72
|
+
const { resolveEnvironment } = require('../core/config');
|
|
73
|
+
const environment = await resolveEnvironment();
|
|
74
|
+
const controllerUrl = await resolveControllerUrl();
|
|
75
|
+
const authConfig = await getDeploymentAuth(controllerUrl, environment, systemKey);
|
|
76
|
+
|
|
77
|
+
if (!authConfig.token && !authConfig.clientId) {
|
|
78
|
+
throw new Error('Authentication required. Run "aifabrix login" or "aifabrix app register <system-key>" first.');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
requireBearerForDataplanePipeline(authConfig);
|
|
82
|
+
logger.log(chalk.blue('Resolving dataplane URL...'));
|
|
83
|
+
const dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig);
|
|
84
|
+
|
|
85
|
+
const payload = await buildPayload(systemKey);
|
|
86
|
+
const pushResult = await pushCredentialSecrets(dataplaneUrl, authConfig, {
|
|
87
|
+
envFilePath,
|
|
88
|
+
appName: systemKey,
|
|
89
|
+
payload
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
logPushResult(pushResult);
|
|
93
|
+
return { pushed: pushResult.pushed || 0 };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = { runCredentialPush, validateSystemKeyFormat, buildPayload };
|