@aifabrix/builder 2.44.5 → 2.45.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/cli-layout.mdc +8 -4
- package/.cursor/rules/project-rules.mdc +1 -1
- package/README.md +15 -23
- package/integration/hubspot-test/README.md +2 -0
- package/integration/hubspot-test/test.js +5 -3
- package/jest.projects.js +104 -2
- package/lib/api/controller-health.api.js +49 -0
- package/lib/api/dimension-values.api.js +82 -0
- package/lib/api/dimensions.api.js +114 -0
- package/lib/api/external-systems.api.js +1 -0
- package/lib/api/integration-clients.api.js +168 -0
- package/lib/api/types/dimension-values.types.js +28 -0
- package/lib/api/types/dimensions.types.js +31 -0
- package/lib/api/types/integration-clients.types.js +45 -0
- package/lib/api/validation-runner.js +46 -25
- package/lib/app/deploy-config.js +11 -1
- package/lib/app/deploy-status-display.js +3 -3
- package/lib/app/deploy.js +36 -14
- package/lib/app/display.js +15 -11
- package/lib/app/helpers.js +3 -3
- package/lib/app/index.js +3 -3
- package/lib/app/push.js +46 -23
- package/lib/app/register.js +7 -6
- package/lib/app/restart-display.js +126 -0
- package/lib/app/rotate-secret.js +7 -6
- package/lib/app/run-container-start.js +12 -6
- package/lib/app/run-env-compose.js +30 -1
- package/lib/app/run-helpers.js +58 -19
- package/lib/app/run-reload-sync.js +148 -0
- package/lib/app/run-resolve-image.js +51 -1
- package/lib/app/run.js +148 -74
- package/lib/app/show-display.js +7 -0
- package/lib/app/show.js +87 -5
- package/lib/build/index.js +83 -49
- package/lib/cli/doctor-check.js +117 -0
- package/lib/cli/index.js +8 -2
- package/lib/cli/infra-guided.js +460 -0
- package/lib/cli/installation-log-command.js +73 -0
- package/lib/cli/setup-app.js +31 -3
- package/lib/cli/setup-auth.js +98 -27
- package/lib/cli/setup-dev-path-commands.js +50 -3
- package/lib/cli/setup-infra-up-dataplane-action.js +111 -0
- package/lib/cli/setup-infra-up-platform-action.js +131 -0
- package/lib/cli/setup-infra.js +132 -118
- package/lib/cli/setup-integration-client.js +182 -0
- package/lib/cli/setup-parameters.js +21 -2
- package/lib/cli/setup-platform.js +102 -0
- package/lib/cli/setup-secrets.js +18 -6
- package/lib/cli/setup-utility-resolve.js +132 -0
- package/lib/cli/setup-utility.js +143 -84
- package/lib/commands/app-logs.js +81 -33
- package/lib/commands/auth-config.js +116 -18
- package/lib/commands/datasource-capability-dimension-cli.js +128 -0
- package/lib/commands/datasource-capability-output.js +29 -0
- package/lib/commands/datasource-capability-relate-cli.js +140 -0
- package/lib/commands/datasource-capability.js +411 -0
- package/lib/commands/datasource-unified-test-cli.options.js +1 -1
- package/lib/commands/datasource.js +53 -13
- package/lib/commands/dev-down.js +3 -3
- package/lib/commands/dev-infra-gate.js +32 -0
- package/lib/commands/dev-init.js +13 -7
- package/lib/commands/dimension-value.js +179 -0
- package/lib/commands/dimension.js +330 -0
- package/lib/commands/integration-client.js +430 -0
- package/lib/commands/login-device.js +65 -30
- package/lib/commands/login.js +21 -10
- package/lib/commands/parameters-validate.js +78 -13
- package/lib/commands/repair-datasource-auto-rbac.js +166 -0
- package/lib/commands/repair-datasource-keys.js +10 -5
- package/lib/commands/repair-datasource.js +19 -7
- package/lib/commands/repair-env-template.js +4 -1
- package/lib/commands/repair-openapi-sync.js +172 -0
- package/lib/commands/repair-persist.js +102 -0
- package/lib/commands/repair-rbac-extract.js +27 -0
- package/lib/commands/repair-rbac-migrate.js +186 -0
- package/lib/commands/repair-rbac.js +214 -31
- package/lib/commands/repair-system-alignment.js +246 -0
- package/lib/commands/repair-system-permissions.js +168 -0
- package/lib/commands/repair.js +120 -338
- package/lib/commands/secure.js +1 -1
- package/lib/commands/setup-modes.js +468 -0
- package/lib/commands/setup-prompts.js +421 -0
- package/lib/commands/setup.js +254 -0
- package/lib/commands/teardown.js +277 -0
- package/lib/commands/up-common.js +113 -19
- package/lib/commands/up-dataplane.js +44 -19
- package/lib/commands/up-miso.js +18 -18
- package/lib/commands/upload.js +111 -23
- package/lib/commands/wizard-core-helpers.js +14 -11
- package/lib/commands/wizard-core.js +6 -5
- package/lib/commands/wizard-dataplane.js +2 -2
- package/lib/commands/wizard-entity-selection.js +4 -3
- package/lib/commands/wizard-headless.js +2 -1
- package/lib/commands/wizard.js +2 -1
- package/lib/constants/infra-compose-service-names.js +40 -0
- package/lib/core/audit-logger.js +1 -34
- package/lib/core/config-admin-email.js +56 -0
- package/lib/core/config-normalize.js +60 -0
- package/lib/core/config-registered-controller-urls.js +54 -0
- package/lib/core/config.js +33 -50
- package/lib/core/env-reader.js +16 -3
- package/lib/core/secrets-admin-env.js +101 -0
- package/lib/core/secrets-ensure-infra.js +34 -1
- package/lib/core/secrets-ensure.js +88 -66
- package/lib/core/secrets-env-content.js +428 -0
- package/lib/core/secrets-env-declarative-expand.js +170 -0
- package/lib/core/secrets-env-write.js +29 -1
- package/lib/core/secrets-load.js +252 -0
- package/lib/core/secrets-names.js +32 -0
- package/lib/core/secrets.js +17 -757
- package/lib/datasource/capability/basic-exposure.js +76 -0
- package/lib/datasource/capability/capability-diff-slice.js +41 -0
- package/lib/datasource/capability/capability-key.js +34 -0
- package/lib/datasource/capability/capability-resolve.js +172 -0
- package/lib/datasource/capability/capability-storage-keys.js +22 -0
- package/lib/datasource/capability/copy-operations.js +348 -0
- package/lib/datasource/capability/copy-test-payload.js +139 -0
- package/lib/datasource/capability/create-operations.js +235 -0
- package/lib/datasource/capability/dimension-operations.js +151 -0
- package/lib/datasource/capability/dimension-validate.js +219 -0
- package/lib/datasource/capability/json-pointer.js +31 -0
- package/lib/datasource/capability/reference-rewrite.js +51 -0
- package/lib/datasource/capability/relate-operations.js +325 -0
- package/lib/datasource/capability/relate-validate.js +219 -0
- package/lib/datasource/capability/remove-operations.js +275 -0
- package/lib/datasource/capability/run-capability-copy.js +152 -0
- package/lib/datasource/capability/run-capability-diff.js +135 -0
- package/lib/datasource/capability/run-capability-dimension.js +291 -0
- package/lib/datasource/capability/run-capability-edit.js +377 -0
- package/lib/datasource/capability/run-capability-relate.js +193 -0
- package/lib/datasource/capability/run-capability-remove.js +105 -0
- package/lib/datasource/capability/templates/minimal-fetch.json +18 -0
- package/lib/datasource/capability/validate-capability-slice.js +35 -0
- package/lib/datasource/list.js +136 -23
- package/lib/datasource/log-viewer.js +2 -4
- package/lib/datasource/unified-validation-run.js +51 -16
- package/lib/datasource/validate.js +53 -1
- package/lib/deployment/deploy-poll-ui.js +60 -0
- package/lib/deployment/deployer-status.js +29 -3
- package/lib/deployment/deployer.js +48 -30
- package/lib/deployment/environment.js +7 -2
- package/lib/deployment/poll-interval.js +72 -0
- package/lib/deployment/push.js +11 -9
- package/lib/external-system/deploy.js +9 -2
- package/lib/external-system/download.js +61 -32
- package/lib/external-system/sync-deploy-manifest.js +33 -0
- package/lib/infrastructure/index.js +49 -19
- package/lib/infrastructure/orphan-infra-docker-teardown.js +177 -0
- package/lib/internal/node-fs.js +2 -0
- package/lib/parameters/infra-kv-discovery.js +29 -4
- package/lib/parameters/infra-parameter-catalog.js +6 -3
- package/lib/parameters/infra-parameter-validate.js +67 -19
- package/lib/resolvers/datasource-resolver.js +53 -0
- package/lib/resolvers/dimension-file.js +52 -0
- package/lib/resolvers/manifest-resolver.js +133 -0
- package/lib/schema/application-schema.json +4 -0
- package/lib/schema/external-datasource.schema.json +183 -53
- package/lib/schema/external-system.schema.json +23 -10
- package/lib/schema/infra.parameter.yaml +26 -1
- package/lib/schema/wizard-config.schema.json +1 -1
- package/lib/utils/aifabrix-config-dir-walk.js +40 -0
- package/lib/utils/aifabrix-runtime-config-dir.js +26 -3
- package/lib/utils/app-config-resolver.js +24 -1
- package/lib/utils/app-run-containers.js +2 -2
- package/lib/utils/applications-config-defaults.js +206 -0
- package/lib/utils/auth-config-validator.js +2 -12
- package/lib/utils/bash-secret-env.js +59 -0
- package/lib/utils/cli-secrets-error-format.js +78 -0
- package/lib/utils/cli-test-layout-chalk.js +31 -9
- package/lib/utils/cli-utils.js +4 -36
- package/lib/utils/compose-generate-docker-compose.js +111 -6
- package/lib/utils/compose-generator.js +17 -8
- package/lib/utils/controller-url.js +50 -7
- package/lib/utils/datasource-test-run-display.js +8 -0
- package/lib/utils/dev-hosts-helper.js +3 -2
- package/lib/utils/dev-init-ssh-merge.js +2 -1
- package/lib/utils/docker-build.js +17 -9
- package/lib/utils/docker-reload-mount.js +127 -0
- package/lib/utils/env-copy.js +99 -14
- package/lib/utils/env-template.js +5 -1
- package/lib/utils/external-readme.js +71 -2
- package/lib/utils/external-system-local-test-tty.js +3 -2
- package/lib/utils/external-system-readiness-core.js +45 -12
- package/lib/utils/external-system-readiness-deploy-display.js +3 -3
- package/lib/utils/external-system-readiness-display-internals.js +33 -3
- package/lib/utils/external-system-readiness-display.js +10 -1
- package/lib/utils/file-upload.js +40 -3
- package/lib/utils/health-check-db-init.js +107 -0
- package/lib/utils/health-check-public-warn.js +69 -0
- package/lib/utils/health-check-url.js +28 -10
- package/lib/utils/health-check.js +139 -107
- package/lib/utils/help-builder.js +5 -1
- package/lib/utils/image-name.js +34 -7
- package/lib/utils/infra-optional-service-flags.js +69 -0
- package/lib/utils/installation-log-core.js +282 -0
- package/lib/utils/installation-log-record.js +237 -0
- package/lib/utils/installation-log.js +123 -0
- package/lib/utils/integration-file-backup.js +74 -0
- package/lib/utils/log-redaction.js +105 -0
- package/lib/utils/manifest-location.js +164 -0
- package/lib/utils/manifest-source-emit.js +162 -0
- package/lib/utils/mutagen-install.js +30 -3
- package/lib/utils/paths.js +308 -76
- package/lib/utils/postgres-wipe.js +212 -0
- package/lib/utils/register-aifabrix-shell-env.js +15 -0
- package/lib/utils/remote-dev-auth.js +21 -5
- package/lib/utils/remote-docker-env.js +9 -1
- package/lib/utils/remote-secrets-loader.js +49 -4
- package/lib/utils/resolve-docker-image-ref.js +9 -3
- package/lib/utils/run-cli-flags.js +29 -0
- package/lib/utils/secrets-ancestor-paths.js +47 -0
- package/lib/utils/secrets-canonical.js +10 -3
- package/lib/utils/secrets-helpers.js +17 -10
- package/lib/utils/secrets-kv-refs.js +42 -0
- package/lib/utils/secrets-kv-scope.js +19 -2
- package/lib/utils/secrets-materialize-local.js +134 -0
- package/lib/utils/secrets-path.js +26 -13
- package/lib/utils/secrets-utils.js +20 -10
- package/lib/utils/system-builder-root.js +42 -0
- package/lib/utils/url-declarative-public-base.js +80 -12
- package/lib/utils/url-declarative-resolve-build-urls.js +238 -0
- package/lib/utils/url-declarative-resolve-build.js +24 -388
- package/lib/utils/url-declarative-resolve-expand-token.js +189 -0
- package/lib/utils/url-declarative-resolve-load-doc.js +12 -3
- package/lib/utils/url-declarative-resolve-surface-state.js +102 -0
- package/lib/utils/url-declarative-resolve.js +47 -7
- package/lib/utils/url-declarative-runtime-base-path.js +52 -0
- package/lib/utils/url-declarative-vdir-inactive-env.js +2 -1
- package/lib/utils/urls-local-registry-scan.js +103 -0
- package/lib/utils/urls-local-registry.js +158 -76
- package/lib/utils/validation-poll-ui.js +81 -0
- package/lib/utils/validation-run-poll.js +29 -5
- package/lib/utils/with-muted-logger.js +53 -0
- package/package.json +3 -1
- package/templates/applications/dataplane/application.yaml +5 -1
- package/templates/applications/dataplane/rbac.yaml +10 -10
- package/templates/applications/keycloak/env.template +8 -6
- package/templates/applications/miso-controller/application.yaml +9 -0
- package/templates/applications/miso-controller/env.template +27 -29
- package/templates/applications/miso-controller/rbac.yaml +9 -9
- package/templates/external-system/README.md.hbs +83 -123
- package/.npmrc.token +0 -1
- package/.nyc_output/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
- package/.nyc_output/processinfo/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
- package/.nyc_output/processinfo/index.json +0 -1
- package/lib/api/service-users.api.js +0 -150
- package/lib/api/types/service-users.types.js +0 -65
- package/lib/cli/setup-service-user.js +0 -187
- package/lib/commands/service-user.js +0 -429
package/lib/utils/env-copy.js
CHANGED
|
@@ -11,6 +11,7 @@ const { formatSuccessLine } = require('./cli-test-layout-chalk');
|
|
|
11
11
|
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
const fsp = require('fs').promises;
|
|
14
|
+
const fsRealSync = require('../internal/fs-real-sync');
|
|
14
15
|
const path = require('path');
|
|
15
16
|
const yaml = require('js-yaml');
|
|
16
17
|
const logger = require('./logger');
|
|
@@ -98,22 +99,37 @@ function resolveEnvOutputPath(rawOutputPath, variablesPath) {
|
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
/**
|
|
101
|
-
* Writes .env to envOutputPath for reload
|
|
102
|
+
* Writes .env to envOutputPath for `--reload`: merge container `.env.run` into the host file.
|
|
103
|
+
* Preserves comments when a file already exists (e.g. after `aifabrix resolve`). Keys present only
|
|
104
|
+
* in `.env.run` (compose-only such as DB_0_NAME) are **not** appended unless they already exist as
|
|
105
|
+
* `KEY=` lines in the base file. If the output file is missing, seeds from `generateEnvContent`
|
|
106
|
+
* (`local`) so the host file keeps template comments and localhost-oriented URLs.
|
|
107
|
+
*
|
|
102
108
|
* @async
|
|
103
109
|
* @param {string} outputPath - Resolved output path
|
|
104
110
|
* @param {string} runEnvPath - Path to .env.run
|
|
111
|
+
* @param {string} appName - Application name (for template seed when output is absent)
|
|
105
112
|
*/
|
|
106
|
-
async function writeEnvOutputForReload(outputPath, runEnvPath) {
|
|
107
|
-
const { parseEnvContentToMap, mergeEnvMapIntoContent } = require('../core/secrets');
|
|
113
|
+
async function writeEnvOutputForReload(outputPath, runEnvPath, appName) {
|
|
114
|
+
const { parseEnvContentToMap, mergeEnvMapIntoContent, generateEnvContent } = require('../core/secrets');
|
|
108
115
|
const runContent = await fsp.readFile(runEnvPath, 'utf8');
|
|
109
116
|
const runMap = parseEnvContentToMap(runContent);
|
|
110
|
-
let
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
|
|
117
|
+
let baseContent;
|
|
118
|
+
if (fsRealSync.existsSync(outputPath)) {
|
|
119
|
+
baseContent = await fsp.readFile(outputPath, 'utf8');
|
|
120
|
+
} else if (appName) {
|
|
121
|
+
baseContent = await generateEnvContent(appName, null, 'local', false);
|
|
122
|
+
baseContent = substituteMntDataForLocal(baseContent, outputPath);
|
|
123
|
+
} else {
|
|
124
|
+
baseContent = runContent;
|
|
114
125
|
}
|
|
126
|
+
const toWrite = mergeEnvMapIntoContent(baseContent, runMap, { appendMissingFromNewMap: false });
|
|
115
127
|
await fsp.writeFile(outputPath, toWrite, { mode: 0o600 });
|
|
116
|
-
logger.log(
|
|
128
|
+
logger.log(
|
|
129
|
+
formatSuccessLine(
|
|
130
|
+
`Wrote .env to envOutputPath (--reload: merged container env; no extra keys): ${outputPath}`
|
|
131
|
+
)
|
|
132
|
+
);
|
|
117
133
|
}
|
|
118
134
|
|
|
119
135
|
/**
|
|
@@ -245,6 +261,30 @@ async function patchEnvContentForLocal(envContent, variables) {
|
|
|
245
261
|
return envContent;
|
|
246
262
|
}
|
|
247
263
|
|
|
264
|
+
/**
|
|
265
|
+
* Copy the just-written builder `<appPath>/.env` to `build.envOutputPath` so both files
|
|
266
|
+
* share the same resolution pass (no second `generateEnvContent` with a different environment).
|
|
267
|
+
* Applies {@link substituteMntDataForLocal} for host paths; merges into an existing output file
|
|
268
|
+
* when present (preserves comments).
|
|
269
|
+
*
|
|
270
|
+
* @async
|
|
271
|
+
* @param {string} envPath - Path to `<appPath>/.env` (already written)
|
|
272
|
+
* @param {string} outputPath - Resolved `build.envOutputPath`
|
|
273
|
+
* @param {string} envOutputPathLabel - Label for log (e.g. raw `build.envOutputPath` value)
|
|
274
|
+
*/
|
|
275
|
+
async function syncWrittenBuilderEnvToOutputPath(envPath, outputPath, envOutputPathLabel) {
|
|
276
|
+
const { parseEnvContentToMap, mergeEnvMapIntoContent } = require('../core/secrets');
|
|
277
|
+
const raw = fs.readFileSync(envPath, 'utf8');
|
|
278
|
+
const synced = substituteMntDataForLocal(raw, outputPath);
|
|
279
|
+
let toWrite = synced;
|
|
280
|
+
if (fs.existsSync(outputPath)) {
|
|
281
|
+
const existingContent = fs.readFileSync(outputPath, 'utf8');
|
|
282
|
+
toWrite = mergeEnvMapIntoContent(existingContent, parseEnvContentToMap(synced));
|
|
283
|
+
}
|
|
284
|
+
fs.writeFileSync(outputPath, toWrite, { mode: 0o600 });
|
|
285
|
+
logger.log(formatSuccessLine(`Copied resolved .env to envOutputPath (${envOutputPathLabel})`));
|
|
286
|
+
}
|
|
287
|
+
|
|
248
288
|
/**
|
|
249
289
|
* Write regenerated local .env to output path (merge with existing if present).
|
|
250
290
|
* @async
|
|
@@ -252,10 +292,12 @@ async function patchEnvContentForLocal(envContent, variables) {
|
|
|
252
292
|
* @param {string} appName - Application name
|
|
253
293
|
* @param {string} [secretsPath] - Path to secrets file (optional)
|
|
254
294
|
* @param {string} envOutputPathLabel - Label for log message (e.g. variables.build.envOutputPath)
|
|
295
|
+
* @param {Object} [extraOpts] - Optional: `appPath` for {@link module:lib/core/secrets-env-content.generateEnvContent}
|
|
255
296
|
*/
|
|
256
|
-
async function writeLocalEnvToOutputPath(outputPath, appName, secretsPath, envOutputPathLabel) {
|
|
297
|
+
async function writeLocalEnvToOutputPath(outputPath, appName, secretsPath, envOutputPathLabel, extraOpts = {}) {
|
|
257
298
|
const { generateEnvContent, parseEnvContentToMap, mergeEnvMapIntoContent } = require('../core/secrets');
|
|
258
|
-
|
|
299
|
+
const genOpts = extraOpts.appPath ? { appPath: extraOpts.appPath } : {};
|
|
300
|
+
let localEnvContent = await generateEnvContent(appName, secretsPath, 'local', false, genOpts);
|
|
259
301
|
localEnvContent = substituteMntDataForLocal(localEnvContent, outputPath);
|
|
260
302
|
let toWrite = localEnvContent;
|
|
261
303
|
if (fs.existsSync(outputPath)) {
|
|
@@ -284,16 +326,45 @@ async function writePatchedEnvToOutputPath(envPath, outputPath, variables, envOu
|
|
|
284
326
|
}
|
|
285
327
|
|
|
286
328
|
/**
|
|
287
|
-
* Process and optionally copy env file to envOutputPath if configured
|
|
288
|
-
*
|
|
329
|
+
* Process and optionally copy env file to envOutputPath if configured.
|
|
330
|
+
* When `appName` is set and `preferLocalEnvOutputPath` is true, regenerates **local**-flavored env at
|
|
331
|
+
* `build.envOutputPath` (IDE/host). **False** only when `remote-server` and `applications.<app>.reload` are both set
|
|
332
|
+
* (docker flavor at env output). Otherwise, when `appName`
|
|
333
|
+
* is set and `envPath` exists, copies the same resolved `<appPath>/.env` content to the output path.
|
|
334
|
+
* Falls back to local regeneration when `envPath` is missing, or patched copy when `appName` is omitted.
|
|
335
|
+
*
|
|
289
336
|
* @async
|
|
290
337
|
* @function processEnvVariables
|
|
291
338
|
* @param {string} envPath - Path to generated .env file
|
|
292
339
|
* @param {string} variablesPath - Path to application config
|
|
293
340
|
* @param {string} appName - Application name (for regenerating with local env)
|
|
294
341
|
* @param {string} [secretsPath] - Path to secrets file (optional, for regenerating)
|
|
342
|
+
* @param {Object} [copyOptions] - Optional: `preferLocalEnvOutputPath` (IDE/host file gets **local** flavor
|
|
343
|
+
* while `<appPath>/.env` may be docker); `appPath` overrides app root for local regeneration
|
|
295
344
|
*/
|
|
296
|
-
async function
|
|
345
|
+
async function copyOrRegenerateEnvForNamedApp(opts) {
|
|
346
|
+
const {
|
|
347
|
+
envPath,
|
|
348
|
+
outputPath,
|
|
349
|
+
appName,
|
|
350
|
+
secretsPath,
|
|
351
|
+
label,
|
|
352
|
+
preferLocal,
|
|
353
|
+
appPathForLocal
|
|
354
|
+
} = opts;
|
|
355
|
+
const localOpts = { appPath: appPathForLocal || undefined };
|
|
356
|
+
if (preferLocal) {
|
|
357
|
+
await writeLocalEnvToOutputPath(outputPath, appName, secretsPath, label, localOpts);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (fs.existsSync(envPath)) {
|
|
361
|
+
await syncWrittenBuilderEnvToOutputPath(envPath, outputPath, label);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
await writeLocalEnvToOutputPath(outputPath, appName, secretsPath, label, localOpts);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async function processEnvVariables(envPath, variablesPath, appName, secretsPath, copyOptions = {}) {
|
|
297
368
|
if (!variablesPath || !fs.existsSync(variablesPath)) {
|
|
298
369
|
return;
|
|
299
370
|
}
|
|
@@ -310,8 +381,21 @@ async function processEnvVariables(envPath, variablesPath, appName, secretsPath)
|
|
|
310
381
|
}
|
|
311
382
|
|
|
312
383
|
const label = variables.build.envOutputPath;
|
|
384
|
+
const appPathForLocal =
|
|
385
|
+
(copyOptions && copyOptions.appPath) ||
|
|
386
|
+
(variablesPath ? path.dirname(variablesPath) : null);
|
|
387
|
+
const preferLocal =
|
|
388
|
+
copyOptions && typeof copyOptions === 'object' && copyOptions.preferLocalEnvOutputPath === true;
|
|
313
389
|
if (appName) {
|
|
314
|
-
await
|
|
390
|
+
await copyOrRegenerateEnvForNamedApp({
|
|
391
|
+
envPath,
|
|
392
|
+
outputPath,
|
|
393
|
+
appName,
|
|
394
|
+
secretsPath,
|
|
395
|
+
label,
|
|
396
|
+
preferLocal,
|
|
397
|
+
appPathForLocal
|
|
398
|
+
});
|
|
315
399
|
} else {
|
|
316
400
|
await writePatchedEnvToOutputPath(envPath, outputPath, variables, label);
|
|
317
401
|
}
|
|
@@ -321,6 +405,7 @@ module.exports = {
|
|
|
321
405
|
processEnvVariables,
|
|
322
406
|
resolveEnvOutputPath,
|
|
323
407
|
substituteMntDataForLocal,
|
|
408
|
+
syncWrittenBuilderEnvToOutputPath,
|
|
324
409
|
writeEnvOutputForReload,
|
|
325
410
|
writeEnvOutputForLocal
|
|
326
411
|
};
|
|
@@ -13,6 +13,7 @@ const fsSync = require('fs');
|
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const chalk = require('chalk');
|
|
15
15
|
const logger = require('../utils/logger');
|
|
16
|
+
const pathsUtil = require('./paths');
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Updates env.template to add MISO_CLIENTID, MISO_CLIENTSECRET, and optionally MISO_CONTROLLER_URL.
|
|
@@ -105,7 +106,10 @@ ${missingEntries.join('\n')}
|
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
async function updateEnvTemplate(appKey, clientIdKey, clientSecretKey, _controllerUrl) {
|
|
108
|
-
|
|
109
|
+
// Use paths.getBuilderPath so the system builder root (e.g. ~/.aifabrix/builder/<app>) and
|
|
110
|
+
// AIFABRIX_BUILDER_DIR are respected. Falling back to process.cwd() previously made the binary
|
|
111
|
+
// CLI skip env.template updates whenever cwd was a non-builder repo (e.g. aifabrix-training).
|
|
112
|
+
const envTemplatePath = path.join(pathsUtil.getBuilderPath(appKey), 'env.template');
|
|
109
113
|
|
|
110
114
|
if (!fsSync.existsSync(envTemplatePath)) {
|
|
111
115
|
logger.warn(chalk.yellow(`⚠ env.template not found for ${appKey}, skipping update`));
|
|
@@ -15,6 +15,72 @@ const path = require('path');
|
|
|
15
15
|
const handlebars = require('handlebars');
|
|
16
16
|
const { getProjectRoot } = require('./paths');
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Extracts secret keys from integration/<appName>/env.template when present.
|
|
20
|
+
* Looks for values like `kv://systemKey/apiKey` and returns that key for
|
|
21
|
+
* `aifabrix secret set <key> <value>`.
|
|
22
|
+
*
|
|
23
|
+
* @param {object} args
|
|
24
|
+
* @param {string} args.projectRoot
|
|
25
|
+
* @param {string} args.appName
|
|
26
|
+
* @returns {Array<{path: string, description: string}>}
|
|
27
|
+
*/
|
|
28
|
+
function extractSecretPathsFromEnvTemplate({ projectRoot, appName }) {
|
|
29
|
+
if (!projectRoot || !appName) return [];
|
|
30
|
+
|
|
31
|
+
const envTemplatePath = path.join(projectRoot, 'integration', appName, 'env.template');
|
|
32
|
+
if (!fs.existsSync(envTemplatePath)) return [];
|
|
33
|
+
|
|
34
|
+
const raw = _tryReadTextOrNull(envTemplatePath);
|
|
35
|
+
if (raw === null) return [];
|
|
36
|
+
if (typeof raw !== 'string') return [];
|
|
37
|
+
|
|
38
|
+
function parseSecretKey(value) {
|
|
39
|
+
if (!value || typeof value !== 'string') return null;
|
|
40
|
+
const kvIdx = value.indexOf('kv://');
|
|
41
|
+
if (kvIdx === -1) return null;
|
|
42
|
+
const after = value.slice(kvIdx + 'kv://'.length);
|
|
43
|
+
const key = after.split(/[ \t#]/)[0]?.trim();
|
|
44
|
+
return key || null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function parseLine(line) {
|
|
48
|
+
const trimmed = line.trim();
|
|
49
|
+
if (!trimmed || trimmed.startsWith('#')) return null;
|
|
50
|
+
const eqIdx = trimmed.indexOf('=');
|
|
51
|
+
if (eqIdx <= 0) return null;
|
|
52
|
+
const varName = trimmed.slice(0, eqIdx).trim();
|
|
53
|
+
const value = trimmed.slice(eqIdx + 1).trim();
|
|
54
|
+
const key = parseSecretKey(value);
|
|
55
|
+
if (!key) return null;
|
|
56
|
+
return { varName: varName || 'Secret', key };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const out = [];
|
|
60
|
+
const seen = new Set();
|
|
61
|
+
|
|
62
|
+
for (const line of raw.split('\n')) {
|
|
63
|
+
const parsed = parseLine(line);
|
|
64
|
+
if (!parsed) continue;
|
|
65
|
+
const dedupeKey = `${parsed.varName}::${parsed.key}`;
|
|
66
|
+
if (seen.has(dedupeKey)) continue;
|
|
67
|
+
seen.add(dedupeKey);
|
|
68
|
+
out.push({ path: parsed.key, description: parsed.varName });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function _tryReadTextOrNull(p) {
|
|
75
|
+
try {
|
|
76
|
+
return fs.readFileSync(p, 'utf8');
|
|
77
|
+
} catch (e) {
|
|
78
|
+
// Some Jest suites partially mock fs.existsSync; treat missing files as absent templates.
|
|
79
|
+
if (e && e.code === 'ENOENT') return null;
|
|
80
|
+
throw e;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
18
84
|
/**
|
|
19
85
|
* Formats a display name from a key
|
|
20
86
|
* @param {string} key - System or app key
|
|
@@ -179,7 +245,9 @@ function buildExternalReadmeContext(params = {}) {
|
|
|
179
245
|
const normalizedExt = fileExt && fileExt.startsWith('.') ? fileExt : `.${fileExt || 'json'}`;
|
|
180
246
|
const datasources = normalizeDatasources(params.datasources, systemKey, fileExt);
|
|
181
247
|
const authType = params.authType || params.authentication?.type || params.authentication?.method || params.authentication?.authType;
|
|
182
|
-
const
|
|
248
|
+
const projectRoot = getProjectRoot();
|
|
249
|
+
const secretPathsFromEnv = extractSecretPathsFromEnvTemplate({ projectRoot, appName });
|
|
250
|
+
const secretPaths = secretPathsFromEnv.length > 0 ? secretPathsFromEnv : buildSecretPaths(systemKey, authType);
|
|
183
251
|
const rbacOptionalFile = rbacOptionalFilename(normalizedExt);
|
|
184
252
|
|
|
185
253
|
return {
|
|
@@ -238,5 +306,6 @@ module.exports = {
|
|
|
238
306
|
buildExternalReadmeContext,
|
|
239
307
|
generateExternalReadmeContent,
|
|
240
308
|
wrapPlainTextForMarkdown,
|
|
241
|
-
collapseConsecutiveBlankLines
|
|
309
|
+
collapseConsecutiveBlankLines,
|
|
310
|
+
extractSecretPathsFromEnvTemplate
|
|
242
311
|
};
|
|
@@ -19,6 +19,7 @@ const {
|
|
|
19
19
|
headerKeyValue,
|
|
20
20
|
formatStatusKeyValue,
|
|
21
21
|
colorRollupPrefixedLine,
|
|
22
|
+
formatSuccessLine,
|
|
22
23
|
metadata: metaGray
|
|
23
24
|
} = require('./cli-test-layout-chalk');
|
|
24
25
|
|
|
@@ -338,7 +339,7 @@ function displayLocalExternalTestPlanLayout(results, verbose, appName) {
|
|
|
338
339
|
function logVerboseSystemRows(systemResults) {
|
|
339
340
|
for (const s of systemResults || []) {
|
|
340
341
|
const ok = s.valid;
|
|
341
|
-
logger.log(ok ? chalk.green(
|
|
342
|
+
logger.log(ok ? (chalk.green(' ') + formatSuccessLine(s.file)) : chalk.red(` ✖ ${s.file}`));
|
|
342
343
|
(s.errors || []).forEach(e => logger.log(chalk.red(` - ${e}`)));
|
|
343
344
|
}
|
|
344
345
|
}
|
|
@@ -346,7 +347,7 @@ function logVerboseSystemRows(systemResults) {
|
|
|
346
347
|
function logVerboseDatasourceRows(datasourceResults) {
|
|
347
348
|
for (const d of datasourceResults || []) {
|
|
348
349
|
const ok = d.valid;
|
|
349
|
-
logger.log(ok ? chalk.green(
|
|
350
|
+
logger.log(ok ? (chalk.green(' ') + formatSuccessLine(`${d.key} (${d.file})`)) : chalk.red(` ✖ ${d.key} (${d.file})`));
|
|
350
351
|
(d.errors || []).forEach(e => logger.log(chalk.red(` - ${e}`)));
|
|
351
352
|
(d.warnings || []).forEach(w => logger.log(chalk.yellow(` ⚠ ${w}`)));
|
|
352
353
|
if (d.fieldMappingResults && d.fieldMappingResults.mappedFields) {
|
|
@@ -43,30 +43,57 @@ function unwrapPublicationResult(res) {
|
|
|
43
43
|
* Rules: inactive/archived → Failed; MCP expected but missing → Partial; draft → Partial; published/deployed + active → Ready.
|
|
44
44
|
* @param {Object} ds - ExternalDataSourceResponse-like
|
|
45
45
|
* @param {boolean} generateMcpContract - From application config
|
|
46
|
-
* @returns {'ready'|'partial'|'failed'}
|
|
46
|
+
* @returns {{ tier: 'ready'|'partial'|'failed', partialReason: string|null }}
|
|
47
47
|
*/
|
|
48
|
-
function
|
|
48
|
+
function classifyDatasourceTierADetail(ds, generateMcpContract) {
|
|
49
49
|
const active = ds.isActive !== false;
|
|
50
50
|
const status = String(ds.status || '').toLowerCase();
|
|
51
51
|
if (!active || status === 'archived') {
|
|
52
|
-
return 'failed';
|
|
52
|
+
return { tier: 'failed', partialReason: null };
|
|
53
53
|
}
|
|
54
54
|
if (generateMcpContract === true && !ds.mcpContract) {
|
|
55
|
-
return 'partial';
|
|
55
|
+
return { tier: 'partial', partialReason: 'mcp_missing' };
|
|
56
56
|
}
|
|
57
57
|
if (status === 'draft') {
|
|
58
|
-
return 'partial';
|
|
58
|
+
return { tier: 'partial', partialReason: 'draft' };
|
|
59
59
|
}
|
|
60
60
|
if (status === 'published' || status === 'deployed') {
|
|
61
|
-
return 'ready';
|
|
61
|
+
return { tier: 'ready', partialReason: null };
|
|
62
|
+
}
|
|
63
|
+
return { tier: 'partial', partialReason: 'unknown_status' };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {Object} ds - ExternalDataSourceResponse-like
|
|
68
|
+
* @param {boolean} generateMcpContract - From application config
|
|
69
|
+
* @returns {'ready'|'partial'|'failed'}
|
|
70
|
+
*/
|
|
71
|
+
function classifyDatasourceTierA(ds, generateMcpContract) {
|
|
72
|
+
return classifyDatasourceTierADetail(ds, generateMcpContract).tier;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Short hint for CLI when Tier A is partial (machine codes from classifyDatasourceTierADetail).
|
|
77
|
+
* @param {string|null} partialReason
|
|
78
|
+
* @returns {string}
|
|
79
|
+
*/
|
|
80
|
+
function formatTierAPartialHint(partialReason) {
|
|
81
|
+
if (partialReason === 'mcp_missing') {
|
|
82
|
+
return 'no MCP contract stored (OpenAPI link or generation)';
|
|
83
|
+
}
|
|
84
|
+
if (partialReason === 'draft') {
|
|
85
|
+
return 'status draft (trigger paths / certification gate)';
|
|
62
86
|
}
|
|
63
|
-
|
|
87
|
+
if (partialReason === 'unknown_status') {
|
|
88
|
+
return 'unexpected lifecycle status';
|
|
89
|
+
}
|
|
90
|
+
return '';
|
|
64
91
|
}
|
|
65
92
|
|
|
66
93
|
/**
|
|
67
94
|
* @param {Array<Object>} datasources - Datasource list
|
|
68
95
|
* @param {boolean} generateMcpContract
|
|
69
|
-
* @returns {{ rows: Array<{ key: string, tier: string }>, ready: number, partial: number, failed: number }}
|
|
96
|
+
* @returns {{ rows: Array<{ key: string, tier: string, partialReason?: string|null }>, ready: number, partial: number, failed: number }}
|
|
70
97
|
*/
|
|
71
98
|
function summarizeDatasourceTiersA(datasources, generateMcpContract) {
|
|
72
99
|
const rows = [];
|
|
@@ -75,10 +102,14 @@ function summarizeDatasourceTiersA(datasources, generateMcpContract) {
|
|
|
75
102
|
let failed = 0;
|
|
76
103
|
for (const ds of datasources || []) {
|
|
77
104
|
const key = ds.key || ds.sourceKey || 'unknown';
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
if (tier === '
|
|
81
|
-
|
|
105
|
+
const detail = classifyDatasourceTierADetail(ds, generateMcpContract);
|
|
106
|
+
const row = { key, tier: detail.tier };
|
|
107
|
+
if (detail.tier === 'partial' && detail.partialReason) {
|
|
108
|
+
row.partialReason = detail.partialReason;
|
|
109
|
+
}
|
|
110
|
+
rows.push(row);
|
|
111
|
+
if (detail.tier === 'ready') ready += 1;
|
|
112
|
+
else if (detail.tier === 'partial') partial += 1;
|
|
82
113
|
else failed += 1;
|
|
83
114
|
}
|
|
84
115
|
return { rows, ready, partial, failed };
|
|
@@ -401,7 +432,9 @@ module.exports = {
|
|
|
401
432
|
unwrapApiData,
|
|
402
433
|
unwrapPublicationResult,
|
|
403
434
|
isPublicationResultShape,
|
|
435
|
+
classifyDatasourceTierADetail,
|
|
404
436
|
classifyDatasourceTierA,
|
|
437
|
+
formatTierAPartialHint,
|
|
405
438
|
summarizeDatasourceTiersA,
|
|
406
439
|
aggregateVerdictFromCounts,
|
|
407
440
|
classifyDatasourceTierB,
|
|
@@ -83,7 +83,7 @@ function logDeployProbeDatasourceSection(probeData) {
|
|
|
83
83
|
* @param {Object|null} systemFromDataplane
|
|
84
84
|
* @param {boolean} genMcp
|
|
85
85
|
*/
|
|
86
|
-
function logDeployContractsSection(systemFromDataplane, genMcp) {
|
|
86
|
+
function logDeployContractsSection(systemFromDataplane, genMcp, dataplaneUrl, systemKey) {
|
|
87
87
|
if (!systemFromDataplane) return;
|
|
88
88
|
logSeparator();
|
|
89
89
|
logSectionTitle('Contracts:');
|
|
@@ -94,7 +94,7 @@ function logDeployContractsSection(systemFromDataplane, genMcp) {
|
|
|
94
94
|
} else {
|
|
95
95
|
logger.log(chalk.gray('○ OpenAPI docs URL not available'));
|
|
96
96
|
}
|
|
97
|
-
logDocsBlock(systemFromDataplane);
|
|
97
|
+
logDocsBlock(systemFromDataplane, { dataplaneUrl, systemKey, genMcp: mcpOk });
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
/**
|
|
@@ -260,7 +260,7 @@ function logDeployReadinessSummary(ctx) {
|
|
|
260
260
|
|
|
261
261
|
logDeployIdentityAndCredentialBlocks(systemCfg, !!probeData);
|
|
262
262
|
|
|
263
|
-
logDeployContractsSection(systemFromDataplane, genMcp);
|
|
263
|
+
logDeployContractsSection(systemFromDataplane, genMcp, dataplaneUrl, systemKey);
|
|
264
264
|
logDeployNextActionsSection(systemKey, probeData, summary, genMcp);
|
|
265
265
|
}
|
|
266
266
|
|
|
@@ -9,7 +9,11 @@
|
|
|
9
9
|
const chalk = require('chalk');
|
|
10
10
|
const { failureGlyph, successGlyph } = require('./cli-test-layout-chalk');
|
|
11
11
|
const logger = require('./logger');
|
|
12
|
-
const {
|
|
12
|
+
const {
|
|
13
|
+
extractIdentitySummary,
|
|
14
|
+
resolveCredentialTestEndpointDisplay,
|
|
15
|
+
formatTierAPartialHint
|
|
16
|
+
} = require('./external-system-readiness-core');
|
|
13
17
|
|
|
14
18
|
const SEP = chalk.gray('────────────────────────────────');
|
|
15
19
|
|
|
@@ -56,8 +60,12 @@ function logDatasourceTable(rows, counts, title) {
|
|
|
56
60
|
logSectionTitle(title && String(title).trim() ? String(title).trim() : 'Datasources:');
|
|
57
61
|
for (const r of rows) {
|
|
58
62
|
const statusLabel = r.tier === 'ready' ? 'Ready' : r.tier === 'failed' ? 'Failed' : 'Partial';
|
|
63
|
+
const hint =
|
|
64
|
+
r.tier === 'partial' && r.partialReason
|
|
65
|
+
? chalk.gray(` — ${formatTierAPartialHint(r.partialReason)}`)
|
|
66
|
+
: '';
|
|
59
67
|
logger.log(
|
|
60
|
-
`${tierGlyph(r.tier)} ${r.key.padEnd(14, ' ')} ${chalk.gray('(' + statusLabel + ')')}`
|
|
68
|
+
`${tierGlyph(r.tier)} ${r.key.padEnd(14, ' ')} ${chalk.gray('(' + statusLabel + ')')}${hint}`
|
|
61
69
|
);
|
|
62
70
|
}
|
|
63
71
|
logger.log('');
|
|
@@ -120,14 +128,35 @@ function logNextActions(actions, extraLine) {
|
|
|
120
128
|
}
|
|
121
129
|
}
|
|
122
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Dataplane serves MCP OpenAPI docs at /api/v1/mcp/{systemKey}/docs.
|
|
133
|
+
*
|
|
134
|
+
* @param {Object} sys - ExternalSystemResponse
|
|
135
|
+
* @param {{ dataplaneUrl?: string, systemKey?: string, genMcp?: boolean }} [opts]
|
|
136
|
+
* @returns {string|null}
|
|
137
|
+
*/
|
|
138
|
+
function deriveMcpDocsPageUrl(sys, opts) {
|
|
139
|
+
if (!sys || !opts) return null;
|
|
140
|
+
if (sys.mcpDocsPageUrl) return String(sys.mcpDocsPageUrl);
|
|
141
|
+
const { dataplaneUrl, systemKey, genMcp } = opts;
|
|
142
|
+
if (!genMcp || !dataplaneUrl || !systemKey) return null;
|
|
143
|
+
if (!sys.mcpServerUrl) return null;
|
|
144
|
+
if (!sys.openApiDocsPageUrl && !sys.apiDocumentUrl) return null;
|
|
145
|
+
const base = String(dataplaneUrl).replace(/\/+$/, '');
|
|
146
|
+
return `${base}/api/v1/mcp/${encodeURIComponent(systemKey)}/docs`;
|
|
147
|
+
}
|
|
148
|
+
|
|
123
149
|
/**
|
|
124
150
|
* @param {Object} sys - ExternalSystemResponse
|
|
151
|
+
* @param {{ dataplaneUrl?: string, systemKey?: string, genMcp?: boolean }} [opts]
|
|
125
152
|
*/
|
|
126
|
-
function logDocsBlock(sys) {
|
|
153
|
+
function logDocsBlock(sys, opts) {
|
|
127
154
|
if (!sys) return;
|
|
128
155
|
const urls = [];
|
|
129
156
|
if (sys.openApiDocsPageUrl) urls.push({ label: 'OpenAPI Docs Page', url: sys.openApiDocsPageUrl });
|
|
130
157
|
if (sys.apiDocumentUrl) urls.push({ label: 'API Docs', url: sys.apiDocumentUrl });
|
|
158
|
+
const mcpDocs = deriveMcpDocsPageUrl(sys, opts);
|
|
159
|
+
if (mcpDocs) urls.push({ label: 'MCP Docs Page', url: mcpDocs });
|
|
131
160
|
if (sys.mcpServerUrl) urls.push({ label: 'MCP Server', url: sys.mcpServerUrl });
|
|
132
161
|
if (urls.length === 0) return;
|
|
133
162
|
logSeparator();
|
|
@@ -147,5 +176,6 @@ module.exports = {
|
|
|
147
176
|
logIdentityBlock,
|
|
148
177
|
logCredentialIntentBlock,
|
|
149
178
|
logNextActions,
|
|
179
|
+
deriveMcpDocsPageUrl,
|
|
150
180
|
logDocsBlock
|
|
151
181
|
};
|
|
@@ -44,7 +44,16 @@ function logPublishResultBlock(publication) {
|
|
|
44
44
|
}
|
|
45
45
|
logSeparator();
|
|
46
46
|
logSectionTitle('MCP Contract:');
|
|
47
|
-
|
|
47
|
+
if (genMcp) {
|
|
48
|
+
logger.log(formatSuccessLine('Generation requested (manifest generateMcpContract)'));
|
|
49
|
+
logger.log(
|
|
50
|
+
chalk.gray(
|
|
51
|
+
' Per-datasource MCP appears when dataplane stores mcpContract (OpenAPI resolves + generation succeeds).'
|
|
52
|
+
)
|
|
53
|
+
);
|
|
54
|
+
} else {
|
|
55
|
+
logger.log(chalk.gray('○ Not requested (generateMcpContract false)'));
|
|
56
|
+
}
|
|
48
57
|
}
|
|
49
58
|
|
|
50
59
|
/**
|
package/lib/utils/file-upload.js
CHANGED
|
@@ -33,10 +33,10 @@ async function validateFileExists(filePath) {
|
|
|
33
33
|
* @param {Object} additionalFields - Additional fields
|
|
34
34
|
* @returns {Promise<FormData>} FormData object
|
|
35
35
|
*/
|
|
36
|
-
async function buildFormData(filePath, fieldName, additionalFields) {
|
|
36
|
+
async function buildFormData(filePath, fieldName, additionalFields, opts = {}) {
|
|
37
37
|
const formData = new FormData();
|
|
38
38
|
const fileContent = await fs.readFile(filePath);
|
|
39
|
-
const fileName = path.basename(filePath);
|
|
39
|
+
const fileName = opts.filenameOverride ? String(opts.filenameOverride) : path.basename(filePath);
|
|
40
40
|
const fileBlob = new Blob([fileContent], { type: 'application/octet-stream' });
|
|
41
41
|
formData.append(fieldName, fileBlob, fileName);
|
|
42
42
|
|
|
@@ -74,6 +74,43 @@ async function uploadFile(url, filePath, fieldName = 'file', authConfig = {}, ad
|
|
|
74
74
|
return await client.postFormData(endpointPath, formData);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Upload a file using multipart/form-data but override the filename sent to the server.
|
|
79
|
+
* Useful when the server derives a key from the upload filename.
|
|
80
|
+
*
|
|
81
|
+
* @async
|
|
82
|
+
* @function uploadFileAs
|
|
83
|
+
* @param {string} url - Full API endpoint URL
|
|
84
|
+
* @param {string} filePath - Path to file to upload
|
|
85
|
+
* @param {string} filenameOverride - Filename to present to server (e.g. 'my-key.json')
|
|
86
|
+
* @param {string} fieldName - Form field name for the file (default: 'file')
|
|
87
|
+
* @param {Object} [authConfig] - Authentication configuration
|
|
88
|
+
* @param {Object} [additionalFields] - Additional form fields to include
|
|
89
|
+
* @returns {Promise<Object>} API response
|
|
90
|
+
*/
|
|
91
|
+
async function uploadFileAs(
|
|
92
|
+
url,
|
|
93
|
+
filePath,
|
|
94
|
+
filenameOverride,
|
|
95
|
+
fieldName = 'file',
|
|
96
|
+
authConfig = {},
|
|
97
|
+
additionalFields = {}
|
|
98
|
+
) {
|
|
99
|
+
await validateFileExists(filePath);
|
|
100
|
+
if (!filenameOverride || typeof filenameOverride !== 'string') {
|
|
101
|
+
throw new Error('filenameOverride is required and must be a string');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const parsed = new URL(url);
|
|
105
|
+
const baseUrl = parsed.origin;
|
|
106
|
+
const endpointPath = parsed.pathname + parsed.search;
|
|
107
|
+
|
|
108
|
+
const formData = await buildFormData(filePath, fieldName, additionalFields, { filenameOverride });
|
|
109
|
+
const client = new ApiClient(baseUrl, authConfig);
|
|
110
|
+
return await client.postFormData(endpointPath, formData);
|
|
111
|
+
}
|
|
112
|
+
|
|
77
113
|
module.exports = {
|
|
78
|
-
uploadFile
|
|
114
|
+
uploadFile,
|
|
115
|
+
uploadFileAs
|
|
79
116
|
};
|