@aifabrix/builder 2.60.0 → 2.62.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/README.md +14 -11
- package/docs/README.md +80 -0
- package/docs/builder-help/evidence-patterns.json +155 -0
- package/docs/builder-help/golden-examples/crm-company.json +30 -0
- package/docs/builder-help/golden-examples/crm-deal.json +29 -0
- package/docs/builder-help/golden-examples/document-storage-keyed-get.json +85 -0
- package/docs/builder-help/golden-examples/document-storage.json +30 -0
- package/docs/builder-help/golden-examples/meeting-transcript.json +29 -0
- package/docs/builder-help/golden-examples/repository-template.json +29 -0
- package/docs/builder-help/golden-examples/service-ticket.json +29 -0
- package/docs/builder-help/platform-roles.json +98 -0
- package/docs/builder-help/resource-type-catalog.json +402 -0
- package/lib/api/applications.api.js +22 -2
- package/lib/api/bootstrap-snapshot.api.js +115 -0
- package/lib/api/configuration.api.js +131 -0
- package/lib/api/system-secrets.api.js +72 -0
- package/lib/app/deploy.js +14 -5
- package/lib/app/run-docker-fallback.js +6 -1
- package/lib/app/run-env-compose.js +5 -6
- package/lib/app/run-helpers.js +2 -1
- package/lib/app/run-parameter-sync.js +142 -0
- package/lib/app/run.js +6 -4
- package/lib/build/docker-build-args.js +5 -2
- package/lib/build/index.js +3 -2
- package/lib/build/standard-docker-build.js +6 -2
- package/lib/channels/channel-artifact-generator.js +13 -3
- package/lib/cli/infra-guided-footers.js +10 -8
- package/lib/cli/infra-guided.js +30 -10
- package/lib/cli/setup-app.js +5 -0
- package/lib/cli/setup-environment.js +156 -0
- package/lib/cli/setup-infra-up-platform-action.js +14 -1
- package/lib/cli/setup-platform.js +2 -0
- package/lib/commands/auth-config.js +2 -0
- package/lib/commands/datasource-capability-upsert-cli.js +112 -0
- package/lib/commands/datasource-capability.js +4 -2
- package/lib/commands/env-secret-context.js +113 -0
- package/lib/commands/env-secret-list.js +200 -0
- package/lib/commands/env-secret-push-confirm.js +54 -0
- package/lib/commands/env-secret-push-run.js +227 -0
- package/lib/commands/env-secret-push.js +168 -0
- package/lib/commands/login.js +4 -0
- package/lib/commands/repair-datasource-apply.js +5 -1
- package/lib/commands/repair-datasource-keyed-document.js +122 -0
- package/lib/commands/repair-datasource-legacy.js +94 -0
- package/lib/commands/repair-datasource-run.js +1 -0
- package/lib/commands/setup-image-refresh.js +26 -0
- package/lib/commands/setup-modes.js +8 -9
- package/lib/commands/setup-prompts-platform-mode.js +34 -0
- package/lib/commands/setup-prompts.js +2 -180
- package/lib/commands/setup.js +3 -0
- package/lib/commands/up-builder-api.js +24 -8
- package/lib/commands/up-common.js +16 -1
- package/lib/commands/up-dataplane-bootstrap.js +70 -14
- package/lib/commands/up-dataplane-credentials.js +21 -0
- package/lib/commands/up-dataplane.js +37 -18
- package/lib/commands/up-integration-server.js +112 -0
- package/lib/commands/verify-operations-skip-e2e.js +25 -1
- package/lib/commands/verify-operations-steps.js +13 -1
- package/lib/commands/wizard-config-normalizer.js +12 -5
- package/lib/commands/wizard-core.js +5 -157
- package/lib/commands/wizard-file-saving.js +163 -0
- package/lib/core/admin-secrets-env-overlay.js +8 -3
- package/lib/core/env-platform-expand.js +5 -1
- package/lib/core/environment-access-policy.js +121 -0
- package/lib/core/environment-mode-policy.js +177 -0
- package/lib/core/local-env-overrides.js +149 -0
- package/lib/core/secrets-ensure.js +4 -2
- package/lib/core/secrets-env-content.js +17 -10
- package/lib/core/secrets-env-write.js +11 -4
- package/lib/core/secrets-load.js +4 -2
- package/lib/datasource/binary-documents-validator.js +190 -0
- package/lib/datasource/capability/capability-manifest.js +1 -0
- package/lib/datasource/capability/run-capability-upsert.js +202 -0
- package/lib/datasource/capability/upsert-ingredients.js +291 -0
- package/lib/datasource/capability/upsert-operations.js +138 -0
- package/lib/datasource/capability/upsert-test-scaffold.js +189 -0
- package/lib/datasource/validate.js +12 -5
- package/lib/deployment/installation/index.js +14 -1
- package/lib/deployment/installation/infra-catalog.js +3 -0
- package/lib/deployment/installation/local-environment-stage.js +285 -0
- package/lib/deployment/installation/local-installation.js +76 -0
- package/lib/deployment/installation/stage-input.js +5 -1
- package/lib/generator/builders.js +3 -1
- package/lib/generator/index.js +3 -0
- package/lib/generator/split-variables.js +2 -2
- package/lib/lifecycle/product-model.js +4 -3
- package/lib/lifecycle/report-display.js +3 -2
- package/lib/parameters/database-adoption.js +125 -0
- package/lib/parameters/infra-parameter-catalog.js +1 -1
- package/lib/parameters/physical-database-naming.js +136 -0
- package/lib/parameters/physical-secret-name.js +194 -0
- package/lib/parameters/rsa-secret-values.js +165 -0
- package/lib/programmatic/builder-help-enterprise-sync-fabrix.js +1 -1
- package/lib/programmatic/builder-help-governance.js +1 -1
- package/lib/programmatic/builder-help.js +1 -1
- package/lib/programmatic/run-channel-install-artifacts.js +28 -2
- package/lib/programmatic/run-channel-package.js +3 -1
- package/lib/programmatic/workspace-context.js +37 -2
- package/lib/role-assistant/test-runner-workhub-answers.js +4 -1
- package/lib/role-assistant/test-runner-workhub-missing-fields.js +42 -0
- package/lib/role-assistant/test-runner-workhub-wait-stop.js +94 -0
- package/lib/role-assistant/test-runner-workhub.js +28 -19
- package/lib/schema/application-schema.json +14 -2
- package/lib/schema/external-datasource.schema.json +23 -3
- package/lib/schema/infra.parameter.yaml +440 -56
- package/lib/schema/infrastructure-schema.json +213 -120
- package/lib/utils/compose-generate-docker-compose.js +9 -14
- package/lib/utils/compose-generator.js +4 -7
- package/lib/utils/compose-miso-env.js +2 -2
- package/lib/utils/compose-traefik-ingress-base.js +3 -4
- package/lib/utils/datasource-binary-evidence.js +92 -0
- package/lib/utils/datasource-test-run-capability-scope.js +44 -1
- package/lib/utils/datasource-test-run-debug-display.js +2 -0
- package/lib/utils/datasource-test-run-display.js +8 -2
- package/lib/utils/datasource-test-run-issue-guidance.js +176 -0
- package/lib/utils/datasource-test-run-tty-log.js +2 -0
- package/lib/utils/declarative-url-ports.js +3 -0
- package/lib/utils/env-copy.js +11 -10
- package/lib/utils/env-environment-file-paths.js +105 -0
- package/lib/utils/environment-scoped-resources.js +48 -14
- package/lib/utils/external-system-system-test-tty.js +3 -2
- package/lib/utils/image-tags.js +2 -2
- package/lib/utils/paths-system-builder-keys.js +7 -1
- package/lib/utils/paths.js +7 -2
- package/lib/utils/platform-kv-ref.js +1 -1
- package/lib/utils/postgres-platform-bootstrap.js +35 -1
- package/lib/utils/prepare-local-data-mount.js +58 -0
- package/lib/utils/redis-env-scope.js +21 -1
- package/lib/utils/registry-auth-sources.js +154 -0
- package/lib/utils/registry-credentials.js +131 -14
- package/lib/utils/secrets-generator.js +30 -21
- package/lib/utils/secrets-helpers.js +5 -8
- package/lib/utils/secrets-kv-scope.js +127 -28
- package/lib/utils/secrets-materialize-local.js +9 -0
- package/lib/utils/secrets-missing-error.js +35 -1
- package/lib/utils/system-secret-mapping.js +125 -0
- package/lib/utils/test-log-writer.js +2 -1
- package/lib/utils/token-manager.js +14 -8
- package/lib/utils/url-public-path-prefix.js +6 -10
- package/lib/validation/external-manifest-validator.js +5 -0
- package/lib/validation/openapi-contract-surface-validator.js +3 -1
- package/lib/validation/validate-external-file.js +5 -1
- package/package.json +6 -4
- package/templates/agent-kit/agent-kit.yaml +1 -1
- package/templates/agent-kit/skills/aifabrix-connected-system/SKILL.md +2 -1
- package/templates/agent-kit/skills/aifabrix-connected-system/references/delivery-gates.md +15 -0
- package/templates/agent-kit/skills/aifabrix-plan/SKILL.md +3 -2
- package/templates/agent-kit/skills/aifabrix-prove/SKILL.md +6 -2
- package/templates/agent-kit/skills/aifabrix-prove/references/evidence-lifecycle.md +22 -0
- package/templates/agent-kit/skills/aifabrix-role-assistant/SKILL.md +7 -1
- package/templates/agent-kit/skills/aifabrix-role-assistant/references/testing-playbook.md +117 -0
- package/templates/agent-kit/skills/shared/interaction.md +83 -0
- package/templates/agent-kit/skills/shared/status.md +3 -12
- package/templates/applications/builder-api/application.yaml +1 -1
- package/templates/applications/builder-api/env.template +5 -1
- package/templates/applications/dataplane/application.yaml +2 -2
- package/templates/applications/dataplane/env.template +10 -12
- package/templates/applications/integration-server/application.yaml +91 -0
- package/templates/applications/integration-server/env.template +215 -0
- package/templates/applications/keycloak/application.yaml +6 -1
- package/templates/applications/mirrored-templates.json +52 -0
- package/templates/applications/miso-controller/application.yaml +116 -2
- package/templates/applications/miso-controller/env.template +62 -17
- package/templates/applications/miso-controller/rbac.yaml +17 -0
- package/templates/external-system/external-datasource.yaml.hbs +7 -1
- package/templates/marketplace/createUiDefinition.json +52 -4
- package/templates/marketplace/main.json +88 -10
- package/templates/python/docker-compose.hbs +1 -1
- package/templates/agent-kit/skills/aifabrix-plan/references/interaction.md +0 -41
- /package/{lib/programmatic/help-content → docs/builder-help/content}/channel-onboarding.md +0 -0
- /package/{lib/programmatic/help-content → docs/builder-help/content}/cip-overview.md +0 -0
- /package/{lib/programmatic/help-content → docs/builder-help/content}/connected-system-ui.md +0 -0
- /package/{lib/programmatic/help-content → docs/builder-help/content}/dimensions-guide.md +0 -0
- /package/{lib/programmatic/help-content → docs/builder-help/content}/enterprise-sync-fabrix.md +0 -0
- /package/{lib/programmatic/help-content → docs/builder-help/content}/overview.md +0 -0
- /package/{lib/programmatic/help-content → docs/builder-help/content}/subscription-guide.md +0 -0
- /package/{lib/programmatic/help-content → docs/builder-help/content}/workflow.md +0 -0
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
const fsSync = require('fs');
|
|
12
12
|
const path = require('path');
|
|
13
13
|
const buildCopy = require('./build-copy');
|
|
14
|
+
const { prepareLocalDataMount } = require('./prepare-local-data-mount');
|
|
15
|
+
const { isScopeActiveForOptions } = require('./environment-scoped-resources');
|
|
14
16
|
const paths = require('./paths');
|
|
15
17
|
const { isSystemBuilderAppName } = require('./paths-system-builder-keys');
|
|
16
18
|
const {
|
|
@@ -187,14 +189,14 @@ function resolveStorageMountSourceRoot(appName, buildContext) {
|
|
|
187
189
|
}
|
|
188
190
|
|
|
189
191
|
/**
|
|
190
|
-
* Host `mount
|
|
192
|
+
* Host `mount/data/<appName>/` directory bind-mounted to `/mnt/data` when storage is required.
|
|
191
193
|
*
|
|
192
194
|
* @param {object} ctx - Compose template context (`devMountPath`, `appName`, `appConfig`, `serviceConfig`)
|
|
193
195
|
* @returns {string|null}
|
|
194
196
|
*/
|
|
195
197
|
function resolveLocalDataMountPathForCompose(ctx) {
|
|
196
198
|
if (ctx.devMountPath && typeof ctx.devMountPath === 'string') {
|
|
197
|
-
return path.join(ctx.devMountPath, 'mount').replace(/\\/g, '/');
|
|
199
|
+
return path.join(ctx.devMountPath, 'mount', 'data', ctx.appName).replace(/\\/g, '/');
|
|
198
200
|
}
|
|
199
201
|
const requiresStorage =
|
|
200
202
|
ctx.serviceConfig?.requiresStorage ||
|
|
@@ -212,7 +214,7 @@ function resolveLocalDataMountPathForCompose(ctx) {
|
|
|
212
214
|
if (!sourceRoot) {
|
|
213
215
|
return null;
|
|
214
216
|
}
|
|
215
|
-
return path.join(sourceRoot, 'mount').replace(/\\/g, '/');
|
|
217
|
+
return path.join(sourceRoot, 'mount', 'data', ctx.appName).replace(/\\/g, '/');
|
|
216
218
|
} catch {
|
|
217
219
|
return null;
|
|
218
220
|
}
|
|
@@ -229,12 +231,8 @@ function buildComposeTemplateContext(ctx) {
|
|
|
229
231
|
ctx.appConfig?.requires?.storage ||
|
|
230
232
|
ctx.appConfig?.services?.storage ||
|
|
231
233
|
false;
|
|
232
|
-
if (localDataMountPath && requiresStorage
|
|
233
|
-
|
|
234
|
-
fsSync.mkdirSync(localDataMountPath, { recursive: true });
|
|
235
|
-
} catch {
|
|
236
|
-
// Non-fatal: tests may use non-writable devMountPath; host must create mount/ before run.
|
|
237
|
-
}
|
|
234
|
+
if (localDataMountPath && requiresStorage) {
|
|
235
|
+
prepareLocalDataMount(localDataMountPath, ctx.envFileAbsolutePath);
|
|
238
236
|
}
|
|
239
237
|
return {
|
|
240
238
|
...ctx.serviceConfig,
|
|
@@ -253,9 +251,7 @@ function buildComposeTemplateContext(ctx) {
|
|
|
253
251
|
// An app that administers its own operating system — creating accounts, managing
|
|
254
252
|
// sshd — cannot work that way and declares `runAsRoot: true` to opt out.
|
|
255
253
|
runAsRoot: ctx.appConfig?.runAsRoot === true,
|
|
256
|
-
// Extra ports
|
|
257
|
-
// such as SSH beside HTTP. Each host port uses the main port's formula so developers
|
|
258
|
-
// on one host do not collide.
|
|
254
|
+
// Extra protocol ports use the main port offset formula to avoid developer collisions.
|
|
259
255
|
additionalPorts: (ctx.appConfig?.additionalPorts ?? []).map((entry) => ({
|
|
260
256
|
name: entry.name,
|
|
261
257
|
containerPort: entry.containerPort,
|
|
@@ -284,8 +280,7 @@ function resolveScopedComposeOpts(options) {
|
|
|
284
280
|
effectiveEnvironmentScopedResources: options.effectiveEnvironmentScopedResources === true,
|
|
285
281
|
runEnvKey
|
|
286
282
|
};
|
|
287
|
-
const useScope =
|
|
288
|
-
scopeOpts.effectiveEnvironmentScopedResources && (runEnvKey === 'dev' || runEnvKey === 'tst');
|
|
283
|
+
const useScope = isScopeActiveForOptions(scopeOpts);
|
|
289
284
|
return { scoped: useScope ? scopeOpts : null };
|
|
290
285
|
}
|
|
291
286
|
|
|
@@ -22,7 +22,8 @@ const { isVectorDatabaseName } = require('./compose-vector-helper');
|
|
|
22
22
|
const { resolveMisoEnvironment } = require('./compose-miso-env');
|
|
23
23
|
const {
|
|
24
24
|
buildScopedLocalContainerName,
|
|
25
|
-
composeTraefikServiceKey
|
|
25
|
+
composeTraefikServiceKey,
|
|
26
|
+
isScopeActiveForOptions
|
|
26
27
|
} = require('./environment-scoped-resources');
|
|
27
28
|
const {
|
|
28
29
|
derivePathFromPattern,
|
|
@@ -308,9 +309,7 @@ function buildServiceConfig(appName, config, port, devId, runExtras = {}) {
|
|
|
308
309
|
const hostPort = port;
|
|
309
310
|
const useTraefikScope =
|
|
310
311
|
scopeOpts &&
|
|
311
|
-
scopeOpts
|
|
312
|
-
scopeOpts.runEnvKey &&
|
|
313
|
-
(scopeOpts.runEnvKey === 'dev' || scopeOpts.runEnvKey === 'tst');
|
|
312
|
+
isScopeActiveForOptions(scopeOpts);
|
|
314
313
|
const composeAppKey = useTraefikScope
|
|
315
314
|
? composeTraefikServiceKey(appName, scopeOpts.runEnvKey)
|
|
316
315
|
: null;
|
|
@@ -380,9 +379,7 @@ function buildNetworkAndContainerNames(appName, devId, idNum, scopeOpts = null)
|
|
|
380
379
|
let containerName;
|
|
381
380
|
if (
|
|
382
381
|
scopeOpts &&
|
|
383
|
-
scopeOpts
|
|
384
|
-
scopeOpts.runEnvKey &&
|
|
385
|
-
(scopeOpts.runEnvKey === 'dev' || scopeOpts.runEnvKey === 'tst')
|
|
382
|
+
isScopeActiveForOptions(scopeOpts)
|
|
386
383
|
) {
|
|
387
384
|
containerName = buildScopedLocalContainerName(appName, devId, idNum, scopeOpts.runEnvKey);
|
|
388
385
|
} else {
|
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* @param {Object} options - Run options
|
|
11
|
-
* @returns {'dev'|'tst'|'pro'}
|
|
11
|
+
* @returns {'miso'|'dev'|'tst'|'pro'}
|
|
12
12
|
*/
|
|
13
13
|
function resolveMisoEnvironment(options) {
|
|
14
14
|
const env = (options.env && typeof options.env === 'string') ? options.env.toLowerCase() : 'dev';
|
|
15
|
-
return (env === 'tst' || env === 'pro') ? env : 'dev';
|
|
15
|
+
return (env === 'miso' || env === 'tst' || env === 'pro') ? env : 'dev';
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
module.exports = { resolveMisoEnvironment };
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
const {
|
|
10
|
-
buildEnvScopedTraefikPath
|
|
10
|
+
buildEnvScopedTraefikPath,
|
|
11
|
+
isScopeActiveForOptions
|
|
11
12
|
} = require('./environment-scoped-resources');
|
|
12
13
|
const { parseDeveloperIdNum } = require('./declarative-url-ports');
|
|
13
14
|
const {
|
|
@@ -122,9 +123,7 @@ function buildTraefikIngressBase(config, devId, scopeOpts, remoteServer, develop
|
|
|
122
123
|
let pathOut = basePath;
|
|
123
124
|
if (
|
|
124
125
|
scopeOpts &&
|
|
125
|
-
scopeOpts
|
|
126
|
-
scopeOpts.runEnvKey &&
|
|
127
|
-
(scopeOpts.runEnvKey === 'dev' || scopeOpts.runEnvKey === 'tst')
|
|
126
|
+
isScopeActiveForOptions(scopeOpts)
|
|
128
127
|
) {
|
|
129
128
|
pathOut = buildEnvScopedTraefikPath(basePath, scopeOpts.runEnvKey);
|
|
130
129
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/** @fileoverview Safe binary metadata and keyed document persistence evidence. */
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { keyedGetEvidence, hasExecutedKeyedGet } = require('./datasource-test-run-capability-scope');
|
|
5
|
+
const BINARY_FIELDS = new Set(['contentType', 'size', 'filename', 'sha256']);
|
|
6
|
+
|
|
7
|
+
/** @param {*} value @returns {Object} */
|
|
8
|
+
function binarySummary(value) {
|
|
9
|
+
if (!value || typeof value !== 'object') return {};
|
|
10
|
+
return Object.fromEntries(Object.entries(value).filter(([key, item]) =>
|
|
11
|
+
BINARY_FIELDS.has(key) && ['string', 'number'].includes(typeof item)));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Limit binary containers to metadata before printing or saving a report.
|
|
16
|
+
* Ordinary JSON record fields remain intact.
|
|
17
|
+
* @param {*} value
|
|
18
|
+
* @param {WeakSet} seen
|
|
19
|
+
* @returns {*}
|
|
20
|
+
*/
|
|
21
|
+
function sanitizeBinaryEvidence(value, seen = new WeakSet()) {
|
|
22
|
+
if (!value || typeof value !== 'object') return value;
|
|
23
|
+
if (Buffer.isBuffer(value) || ArrayBuffer.isView(value)) return '[binary omitted]';
|
|
24
|
+
if (seen.has(value)) return '[Circular]';
|
|
25
|
+
seen.add(value);
|
|
26
|
+
if (Array.isArray(value)) {
|
|
27
|
+
const items = value.map(item => sanitizeBinaryEvidence(item, seen));
|
|
28
|
+
seen.delete(value);
|
|
29
|
+
return items;
|
|
30
|
+
}
|
|
31
|
+
const out = {};
|
|
32
|
+
for (const [key, item] of Object.entries(value)) {
|
|
33
|
+
if (['getBinary', 'listBinary', '_binary'].includes(key)) out[key] = binarySummary(item);
|
|
34
|
+
else if (value.keyedGet === true && key === 'document') {
|
|
35
|
+
out[key] = { ...binarySummary(item) };
|
|
36
|
+
if (typeof item?.key === 'string') out[key].key = item.key;
|
|
37
|
+
if (typeof item?.persisted === 'boolean') out[key].persisted = item.persisted;
|
|
38
|
+
} else if (value.keyedGet === true && ['data', 'bytes', 'body', 'content', 'base64'].includes(key)) continue;
|
|
39
|
+
else out[key] = sanitizeBinaryEvidence(item, seen);
|
|
40
|
+
}
|
|
41
|
+
seen.delete(value);
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** @param {Object} value @param {string[]} lines */
|
|
46
|
+
function appendBinaryAndPersistence(value, lines) {
|
|
47
|
+
if (!value || typeof value !== 'object') return;
|
|
48
|
+
if (value.getBinary) lines.push(` getBinary: ${JSON.stringify(binarySummary(value.getBinary))}`);
|
|
49
|
+
if (value.scope?.expectedSource === 'get') {
|
|
50
|
+
const observed = value.observed || {};
|
|
51
|
+
lines.push(` Document persistence (expectedSource: get): documents=${Number(observed.documents) || 0}, chunks=${Number(observed.chunks) || 0}`);
|
|
52
|
+
}
|
|
53
|
+
for (const [key, child] of Object.entries(value)) {
|
|
54
|
+
if (key !== 'getBinary') appendBinaryAndPersistence(child, lines);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** @param {Object} envelope @returns {string[]} */
|
|
59
|
+
function binaryEvidenceLines(envelope) {
|
|
60
|
+
const lines = [];
|
|
61
|
+
for (const job of keyedGetEvidence(envelope)) {
|
|
62
|
+
const status = job.skipped === true ? 'skipped' : job.ok === true ? 'executed' : 'failed';
|
|
63
|
+
lines.push(` Keyed get: ${status} (${job.datasourceKey || envelope.datasourceKey || 'unknown'})`);
|
|
64
|
+
if (job.document) lines.push(` Document: ${JSON.stringify(binarySummary(job.document))}`);
|
|
65
|
+
}
|
|
66
|
+
appendBinaryAndPersistence(envelope?.integration?.stepResults, lines);
|
|
67
|
+
return lines;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** @param {Object} envelope @param {string} key @returns {boolean} */
|
|
71
|
+
function hasKeyedDocumentPersistence(envelope, key) {
|
|
72
|
+
if (!hasExecutedKeyedGet(envelope, key)) return false;
|
|
73
|
+
const steps = envelope?.integration?.stepResults || [];
|
|
74
|
+
return steps.some(step => {
|
|
75
|
+
if (step.name !== 'document_persistence' || step.success === false || step.status === 'failed') return false;
|
|
76
|
+
const row = step.evidence?.datasources?.[key];
|
|
77
|
+
const model = row?.model;
|
|
78
|
+
return model?.scope?.expectedSource === 'get' && model.observed?.documents > 0 &&
|
|
79
|
+
model.observed?.chunks > 0 && (!Array.isArray(row.failures) || row.failures.length === 0);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** @param {Object} result @param {string[]} keys @returns {boolean} */
|
|
84
|
+
function hasAllKeyedDocumentPersistence(result, keys) {
|
|
85
|
+
return keys.length > 0 && keys.every(key => {
|
|
86
|
+
const row = result?.results?.find(item => item.key === key);
|
|
87
|
+
return row?.success === true && hasKeyedDocumentPersistence(row.datasourceTestRun, key);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = { binarySummary, sanitizeBinaryEvidence, binaryEvidenceLines, hasKeyedDocumentPersistence,
|
|
92
|
+
hasAllKeyedDocumentPersistence };
|
|
@@ -4,6 +4,43 @@
|
|
|
4
4
|
* @version 2.0.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Keyed document retrieval has no list operation, so the `get` runs once against a business
|
|
9
|
+
* key instead of over a sync page. The dataplane records that run as step evidence
|
|
10
|
+
* (`sync_step_keyed_get.py`), which is why execution is read from evidence rather than from
|
|
11
|
+
* a row count. `IntegrationStepResult.evidence` is an open object by contract.
|
|
12
|
+
* @param {Object|null|undefined} envelope - DatasourceTestRun-like
|
|
13
|
+
* @returns {Array<Object>} Keyed-get evidence entries, newest first as reported
|
|
14
|
+
*/
|
|
15
|
+
function keyedGetEvidence(envelope) {
|
|
16
|
+
const integration = envelope && typeof envelope === 'object' ? envelope.integration : null;
|
|
17
|
+
const steps = integration && Array.isArray(integration.stepResults) ? integration.stepResults : [];
|
|
18
|
+
return steps
|
|
19
|
+
.map(step => (step && typeof step.evidence === 'object' ? step.evidence : null))
|
|
20
|
+
.flatMap(evidence => evidence && Array.isArray(evidence.jobs) ? evidence.jobs : [evidence])
|
|
21
|
+
.filter(evidence => evidence && evidence.keyedGet === true);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A keyed `get` counts as executed only when it actually returned a document. A skipped run
|
|
26
|
+
* (no primary key value) or a failed one is not execution, and reporting it as such would
|
|
27
|
+
* turn `--strict-capability-scope` into a rubber stamp.
|
|
28
|
+
* @param {Object|null|undefined} envelope - DatasourceTestRun-like
|
|
29
|
+
* @param {string|undefined|null} datasourceKey - Restrict to one datasource when given
|
|
30
|
+
* @returns {boolean} True when a keyed get ran successfully
|
|
31
|
+
*/
|
|
32
|
+
function hasExecutedKeyedGet(envelope, datasourceKey) {
|
|
33
|
+
const wanted = datasourceKey === undefined || datasourceKey === null || String(datasourceKey).trim() === ''
|
|
34
|
+
? ''
|
|
35
|
+
: String(datasourceKey).trim();
|
|
36
|
+
return keyedGetEvidence(envelope).some(evidence => {
|
|
37
|
+
if (evidence.ok !== true || evidence.skipped === true) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
return wanted === '' || String(evidence.datasourceKey || '') === wanted;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
7
44
|
/**
|
|
8
45
|
* @param {Object|null|undefined} envelope - DatasourceTestRun-like
|
|
9
46
|
* @param {string|undefined|null} requestedCapabilityKey - From positional [capabilityKey]
|
|
@@ -23,6 +60,10 @@ function analyzeCapabilityScope(envelope, requestedCapabilityKey) {
|
|
|
23
60
|
envelope && typeof envelope === 'object' && Array.isArray(envelope.capabilities)
|
|
24
61
|
? envelope.capabilities
|
|
25
62
|
: [];
|
|
63
|
+
if (key === 'get' && keyedGetEvidence(envelope).length > 0 &&
|
|
64
|
+
!hasExecutedKeyedGet(envelope, envelope.datasourceKey)) {
|
|
65
|
+
return { violated: true, message: 'Capabilities scope: keyed get was not executed successfully.' };
|
|
66
|
+
}
|
|
26
67
|
if (caps.length <= 1) {
|
|
27
68
|
return { violated: false };
|
|
28
69
|
}
|
|
@@ -39,5 +80,7 @@ function analyzeCapabilityScope(envelope, requestedCapabilityKey) {
|
|
|
39
80
|
}
|
|
40
81
|
|
|
41
82
|
module.exports = {
|
|
42
|
-
analyzeCapabilityScope
|
|
83
|
+
analyzeCapabilityScope,
|
|
84
|
+
keyedGetEvidence,
|
|
85
|
+
hasExecutedKeyedGet
|
|
43
86
|
};
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* @version 2.0.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
const { sanitizeBinaryEvidence } = require('./datasource-binary-evidence');
|
|
7
8
|
const { SEP, appendReferenceLayoutLines } = require('./datasource-test-run-display');
|
|
8
9
|
const { buildDebugEnvelopeSlice } = require('./datasource-test-run-debug-slice');
|
|
9
10
|
const {
|
|
@@ -250,6 +251,7 @@ function applyRawLineCap(text) {
|
|
|
250
251
|
* @returns {string}
|
|
251
252
|
*/
|
|
252
253
|
function formatDatasourceTestRunDebugBlock(envelope, mode, isTTY) {
|
|
254
|
+
envelope = sanitizeBinaryEvidence(envelope);
|
|
253
255
|
if (!envelope || typeof envelope !== 'object') return '';
|
|
254
256
|
|
|
255
257
|
const lines = ['', SEP, `Debug (${mode})`];
|
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const chalk = require('chalk');
|
|
8
|
+
const { binaryEvidenceLines } = require('./datasource-binary-evidence');
|
|
8
9
|
const { sectionTitle, headerKeyValue, colorAggregateGlyph, successGlyph, failureGlyph } = require('./cli-test-layout-chalk');
|
|
9
10
|
const { appendCertificateTTY, appendCertificateIssuanceTTY } = require('./datasource-test-run-certificate-tty');
|
|
10
11
|
const { buildTtyMetaLines: buildTtyMetaLinesCore } = require('./datasource-test-run-tty-meta-lines');
|
|
12
|
+
const { appendStructuredIssueGuidance } = require('./datasource-test-run-issue-guidance');
|
|
11
13
|
|
|
12
14
|
const SEP = '────────────────────────────────';
|
|
13
15
|
|
|
@@ -297,9 +299,12 @@ function appendValidationIssueLines(lines, envelope, maxIssues = 5) {
|
|
|
297
299
|
const cap = Math.min(maxIssues, issues.length);
|
|
298
300
|
for (let i = 0; i < cap; i += 1) {
|
|
299
301
|
const iss = issues[i];
|
|
300
|
-
const
|
|
302
|
+
const issueCode = iss && (iss.stableCode || iss.code);
|
|
303
|
+
const code = issueCode ? chalk.red(`[${issueCode}] `) : '';
|
|
304
|
+
const severity = iss?.severity ? chalk.gray(`[${String(iss.severity)}] `) : '';
|
|
301
305
|
const msg = iss && iss.message ? String(iss.message) : JSON.stringify(iss);
|
|
302
|
-
lines.push(` ${code}${chalk.yellow(msg)}`);
|
|
306
|
+
lines.push(` ${code}${severity}${chalk.yellow(msg)}`);
|
|
307
|
+
appendStructuredIssueGuidance(lines, iss);
|
|
303
308
|
appendDpSec013Details(lines, iss);
|
|
304
309
|
}
|
|
305
310
|
if (issues.length > cap) {
|
|
@@ -448,6 +453,7 @@ function formatDatasourceTestRunTTY(envelope, options = {}) {
|
|
|
448
453
|
appendRefsSectionIfEnabled(lines, envelope, options);
|
|
449
454
|
appendValidationIssueLines(lines, envelope);
|
|
450
455
|
appendIntegrationStepLines(lines, envelope);
|
|
456
|
+
lines.push(...binaryEvidenceLines(envelope));
|
|
451
457
|
const focus = normalizedFocusCapabilityKey(options.focusCapabilityKey);
|
|
452
458
|
if (focus) {
|
|
453
459
|
lines.push(formatCapabilityFocusSection(envelope, focus));
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured human guidance for DatasourceTestRun validation issues.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Preserve actionable issue fields in TTY output
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
|
|
11
|
+
const STEP_VERBS = {
|
|
12
|
+
locate_file: 'open',
|
|
13
|
+
update_path: 'update',
|
|
14
|
+
add_mapping: 'add mapping',
|
|
15
|
+
set_value: 'set',
|
|
16
|
+
remove_value: 'remove',
|
|
17
|
+
add_item: 'add to',
|
|
18
|
+
remove_item: 'remove from'
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const UPSERT_GUIDANCE = {
|
|
22
|
+
'DP-CIP-030': {
|
|
23
|
+
observed: 'The declared upsert does not contain the required create, update, and records-output composition.',
|
|
24
|
+
consequence: 'The runtime cannot execute one deterministic create-or-update operation.',
|
|
25
|
+
aiFixable: true,
|
|
26
|
+
remediation: [
|
|
27
|
+
'Run aifabrix datasource capability upsert <file-or-key> --overwrite --dry-run, review the patch, then apply and validate it.'
|
|
28
|
+
],
|
|
29
|
+
prevention: 'Author upsert through the capability command and keep both source halves valid.',
|
|
30
|
+
residualRisk: 'Publication remains blocked while the composition is malformed.',
|
|
31
|
+
rerun: 'Rerun the same focused datasource validation or E2E case.'
|
|
32
|
+
},
|
|
33
|
+
'DP-CIP-031': {
|
|
34
|
+
observed: 'The upsert declares no usable business identity.',
|
|
35
|
+
consequence: 'Existence cannot be resolved, so a retry may create a duplicate or update the wrong record.',
|
|
36
|
+
aiFixable: true,
|
|
37
|
+
remediation: [
|
|
38
|
+
'Run aifabrix datasource capability upsert <file-or-key> --identity <fields> --dry-run with author-approved mapped non-primary fields.'
|
|
39
|
+
],
|
|
40
|
+
prevention: 'Model business identity before exposing a composed write.',
|
|
41
|
+
residualRisk: 'Upsert must remain unavailable until identity is explicit and validated.',
|
|
42
|
+
rerun: 'Rerun the same focused datasource validation or E2E case.'
|
|
43
|
+
},
|
|
44
|
+
'DP-CIP-040': {
|
|
45
|
+
observed: 'No declared read operation can constrain the complete business identity.',
|
|
46
|
+
consequence: 'The source record may be missed, creating a duplicate or selecting the wrong update target.',
|
|
47
|
+
aiFixable: false,
|
|
48
|
+
remediation: [
|
|
49
|
+
'Declare a real exact lookup only if the source API supports it.',
|
|
50
|
+
'Otherwise keep separate create and update capabilities and withhold upsert readiness.'
|
|
51
|
+
],
|
|
52
|
+
prevention: 'Require complete equality lookup coverage before authoring upsert.',
|
|
53
|
+
residualRisk: 'AI cannot repair a source API that lacks exact lookup or uniqueness.',
|
|
54
|
+
rerun: 'After a supported lookup is declared, rerun the same focused validation and E2E proof.'
|
|
55
|
+
},
|
|
56
|
+
'DP-CIP-041': {
|
|
57
|
+
observed: 'A covering identity read is declared, but live filtering has not been proven.',
|
|
58
|
+
consequence: 'Static coverage alone cannot show that the source applies the constraint or exposes writes in time.',
|
|
59
|
+
aiFixable: false,
|
|
60
|
+
remediation: ['Run the focused live validation and two-write no-duplicate E2E proof; do not add a duplicate read declaration.'],
|
|
61
|
+
prevention: 'Keep exact lookup and no-duplicate proof scenarios with every upsert datasource.',
|
|
62
|
+
residualRisk: 'Read-after-write delay can leave a bounded duplicate window until runtime protection is proven.',
|
|
63
|
+
rerun: 'Rerun the same focused datasource validation and upsert E2E case.'
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
function normalizeOptionalString(value) {
|
|
68
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function hasGuidanceValue(value) {
|
|
72
|
+
if (Array.isArray(value)) return value.some(hasGuidanceValue);
|
|
73
|
+
if (typeof value === 'boolean') return true;
|
|
74
|
+
if (value && typeof value === 'object') return Object.keys(value).length > 0;
|
|
75
|
+
return normalizeOptionalString(value) !== '';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function firstGuidanceValue(...values) {
|
|
79
|
+
return values.find(hasGuidanceValue);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function guidancePayload(item) {
|
|
83
|
+
return normalizeOptionalString(item.value) || normalizeOptionalString(item.template);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function guidanceVerb(step) {
|
|
87
|
+
return STEP_VERBS[step] || step.replace(/_/g, ' ') || 'update';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Render one guidance entry, which may be a string or a structured fix instruction.
|
|
92
|
+
*
|
|
93
|
+
* `fix.instructions[]` entries are objects ({step, path, value, template, note}),
|
|
94
|
+
* so stringifying them printed `[object Object]` and the remediation — the part a
|
|
95
|
+
* developer acts on — was lost.
|
|
96
|
+
*
|
|
97
|
+
* @param {unknown} item guidance entry
|
|
98
|
+
* @returns {string|null} display text, or null when there is nothing to show
|
|
99
|
+
*/
|
|
100
|
+
function formatGuidanceItem(item) {
|
|
101
|
+
if (typeof item === 'string') return item.trim() || null;
|
|
102
|
+
if (!item || typeof item !== 'object') return null;
|
|
103
|
+
|
|
104
|
+
const step = normalizeOptionalString(item.step);
|
|
105
|
+
const note = normalizeOptionalString(item.note);
|
|
106
|
+
const target = normalizeOptionalString(item.path);
|
|
107
|
+
const payload = guidancePayload(item);
|
|
108
|
+
|
|
109
|
+
// `explain` carries no path or value; its note *is* the instruction.
|
|
110
|
+
if (step === 'explain' || (!target && !payload)) {
|
|
111
|
+
return note || (step ? step.replace(/_/g, ' ') : null);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const verb = guidanceVerb(step);
|
|
115
|
+
// With no path the payload *is* the target (e.g. locate_file carries a path in
|
|
116
|
+
// `value`), so it reads as "open <file>" rather than "open: <file>".
|
|
117
|
+
let text = target ? `${verb} ${target}` : `${verb} ${payload}`;
|
|
118
|
+
if (target && payload) text += step === 'set_value' ? ` = ${payload}` : `: ${payload}`;
|
|
119
|
+
return note ? `${text} — ${note}` : text;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function appendGuidanceValue(lines, label, value) {
|
|
123
|
+
const values = Array.isArray(value) ? value : [value];
|
|
124
|
+
values
|
|
125
|
+
.map(formatGuidanceItem)
|
|
126
|
+
.filter(Boolean)
|
|
127
|
+
.forEach((text) => {
|
|
128
|
+
lines.push(` ${chalk.gray(`${label}:`)} ${chalk.white(text)}`);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function enrichUpsertIssueGuidance(issue) {
|
|
133
|
+
if (!issue || typeof issue !== 'object') return issue;
|
|
134
|
+
const code = issue.stableCode || issue.code;
|
|
135
|
+
const fallback = UPSERT_GUIDANCE[code];
|
|
136
|
+
if (!fallback) return issue;
|
|
137
|
+
const enriched = { ...issue };
|
|
138
|
+
Object.entries(fallback).forEach(([key, value]) => {
|
|
139
|
+
if (!hasGuidanceValue(enriched[key])) enriched[key] = value;
|
|
140
|
+
});
|
|
141
|
+
return enriched;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function appendStructuredIssueGuidance(lines, issue) {
|
|
145
|
+
if (!issue || typeof issue !== 'object') return;
|
|
146
|
+
const display = enrichUpsertIssueGuidance(issue);
|
|
147
|
+
appendGuidanceValue(lines, 'Observed', firstGuidanceValue(
|
|
148
|
+
issue.observed, issue.details?.diagnosis, display.observed
|
|
149
|
+
));
|
|
150
|
+
appendGuidanceValue(lines, 'Consequence', firstGuidanceValue(
|
|
151
|
+
issue.impact, issue.consequence, issue.reason, display.consequence
|
|
152
|
+
));
|
|
153
|
+
if (typeof display.aiFixable === 'boolean') {
|
|
154
|
+
appendGuidanceValue(lines, 'Can AI fix?', display.aiFixable
|
|
155
|
+
? 'Yes — only as an authorized deterministic local manifest change.'
|
|
156
|
+
: 'No automatic fix is safe for this source or live-proof limitation.');
|
|
157
|
+
}
|
|
158
|
+
appendGuidanceValue(lines, 'Safe action', firstGuidanceValue(
|
|
159
|
+
issue.fix?.instructions, issue.remediation, issue.hint, display.remediation
|
|
160
|
+
));
|
|
161
|
+
appendGuidanceValue(lines, 'Prevention', firstGuidanceValue(
|
|
162
|
+
issue.prevention, issue.fix?.prevention, display.prevention
|
|
163
|
+
));
|
|
164
|
+
appendGuidanceValue(lines, 'Residual risk', firstGuidanceValue(
|
|
165
|
+
issue.residualRisk, issue.risk, display.residualRisk
|
|
166
|
+
));
|
|
167
|
+
appendGuidanceValue(lines, 'Rerun', firstGuidanceValue(
|
|
168
|
+
issue.rerun, issue.fix?.rerun, display.rerun
|
|
169
|
+
));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
module.exports = {
|
|
173
|
+
appendStructuredIssueGuidance,
|
|
174
|
+
enrichUpsertIssueGuidance,
|
|
175
|
+
formatGuidanceItem
|
|
176
|
+
};
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
'use strict';
|
|
6
6
|
|
|
7
7
|
const chalk = require('chalk');
|
|
8
|
+
const { sanitizeBinaryEvidence } = require('./datasource-binary-evidence');
|
|
8
9
|
const logger = require('./logger');
|
|
9
10
|
const { getReportVersionStderrMessage } = require('./datasource-test-run-report-version');
|
|
10
11
|
const {
|
|
@@ -42,6 +43,7 @@ function emitCapabilityScopeDiagnostics(envelope, opts = {}) {
|
|
|
42
43
|
* @param {string} [options.requestedCapabilityKey]
|
|
43
44
|
*/
|
|
44
45
|
function printDatasourceTestRunForTTY(envelope, options = {}) {
|
|
46
|
+
envelope = sanitizeBinaryEvidence(envelope);
|
|
45
47
|
const mode = resolveDebugDisplayMode(options.debug);
|
|
46
48
|
const displayOpts = {
|
|
47
49
|
focusCapabilityKey: options.requestedCapabilityKey,
|
package/lib/utils/env-copy.js
CHANGED
|
@@ -56,16 +56,17 @@ function readDeveloperIdFromConfig(config) {
|
|
|
56
56
|
* Creates the mount folder on the local filesystem (next to the .env file) when it does not exist.
|
|
57
57
|
* @param {string} content - Env file content
|
|
58
58
|
* @param {string} outputPath - Resolved path of the .env file being written
|
|
59
|
+
* @param {string} [appName] - System key (defaults to output directory name)
|
|
59
60
|
* @returns {string} Content with /mnt/data replaced by path to mount directory
|
|
60
61
|
*/
|
|
61
|
-
function substituteMntDataForLocal(content, outputPath) {
|
|
62
|
+
function substituteMntDataForLocal(content, outputPath, appName) {
|
|
62
63
|
const outputDir = path.dirname(outputPath);
|
|
63
64
|
// Avoid introducing a drive letter for root-relative paths (e.g. "\tmp\out"),
|
|
64
65
|
// which are common in unit tests and some Windows path constructions.
|
|
65
66
|
const isWinRootRelative = process.platform === 'win32' && /^\\(?!\\)/.test(outputDir);
|
|
66
67
|
const localMountPath = isWinRootRelative
|
|
67
|
-
? path.win32.normalize(path.win32.join(outputDir, 'mount'))
|
|
68
|
-
: path.resolve(outputDir, 'mount');
|
|
68
|
+
? path.win32.normalize(path.win32.join(outputDir, 'mount', 'data', appName || path.basename(outputDir)))
|
|
69
|
+
: path.resolve(outputDir, 'mount', 'data', appName || path.basename(outputDir));
|
|
69
70
|
if (!fs.existsSync(localMountPath)) {
|
|
70
71
|
fs.mkdirSync(localMountPath, { recursive: true });
|
|
71
72
|
}
|
|
@@ -140,12 +141,12 @@ async function writeEnvOutputForReload(outputPath, runEnvPath, appName) {
|
|
|
140
141
|
baseContent = await fsp.readFile(outputPath, 'utf8');
|
|
141
142
|
} else if (appName) {
|
|
142
143
|
baseContent = await generateEnvContent(appName, null, 'local', false);
|
|
143
|
-
baseContent = substituteMntDataForLocal(baseContent, outputPath);
|
|
144
|
+
baseContent = substituteMntDataForLocal(baseContent, outputPath, appName);
|
|
144
145
|
} else {
|
|
145
146
|
baseContent = runContent;
|
|
146
147
|
}
|
|
147
148
|
let toWrite = mergeEnvMapIntoContent(baseContent, runMap, { appendMissingFromNewMap: false });
|
|
148
|
-
toWrite = substituteMntDataForLocal(toWrite, outputPath);
|
|
149
|
+
toWrite = substituteMntDataForLocal(toWrite, outputPath, appName);
|
|
149
150
|
await fsp.writeFile(outputPath, toWrite, { mode: 0o600 });
|
|
150
151
|
logger.log(
|
|
151
152
|
formatSuccessLine(
|
|
@@ -163,7 +164,7 @@ async function writeEnvOutputForReload(outputPath, runEnvPath, appName) {
|
|
|
163
164
|
async function writeEnvOutputForLocal(appName, outputPath) {
|
|
164
165
|
const { generateEnvContent, parseEnvContentToMap, mergeEnvMapIntoContent } = require('../core/secrets');
|
|
165
166
|
let localContent = await generateEnvContent(appName, null, 'local', false);
|
|
166
|
-
localContent = substituteMntDataForLocal(localContent, outputPath);
|
|
167
|
+
localContent = substituteMntDataForLocal(localContent, outputPath, appName);
|
|
167
168
|
let toWrite = localContent;
|
|
168
169
|
if (fs.existsSync(outputPath)) {
|
|
169
170
|
const existingContent = await fsp.readFile(outputPath, 'utf8');
|
|
@@ -319,7 +320,7 @@ async function patchEnvContentForLocal(envContent, variables) {
|
|
|
319
320
|
async function syncWrittenBuilderEnvToOutputPath(envPath, outputPath, envOutputPathLabel, copyOptions = {}) {
|
|
320
321
|
const { parseEnvContentToMap, mergeEnvMapIntoContent } = require('../core/secrets');
|
|
321
322
|
const raw = fs.readFileSync(envPath, 'utf8');
|
|
322
|
-
const synced = substituteMntDataForLocal(raw, outputPath);
|
|
323
|
+
const synced = substituteMntDataForLocal(raw, outputPath, copyOptions.appName || path.basename(path.dirname(envPath)));
|
|
323
324
|
let toWrite = synced;
|
|
324
325
|
if (!copyOptions.freshEnv && fs.existsSync(outputPath)) {
|
|
325
326
|
const existingContent = fs.readFileSync(outputPath, 'utf8');
|
|
@@ -344,7 +345,7 @@ async function writeLocalEnvToOutputPath(outputPath, appName, secretsPath, envOu
|
|
|
344
345
|
const { generateEnvContent, parseEnvContentToMap, mergeEnvMapIntoContent } = require('../core/secrets');
|
|
345
346
|
const genOpts = extraOpts.appPath ? { appPath: extraOpts.appPath } : {};
|
|
346
347
|
let localEnvContent = await generateEnvContent(appName, secretsPath, 'local', false, genOpts);
|
|
347
|
-
localEnvContent = substituteMntDataForLocal(localEnvContent, outputPath);
|
|
348
|
+
localEnvContent = substituteMntDataForLocal(localEnvContent, outputPath, appName);
|
|
348
349
|
let toWrite = localEnvContent;
|
|
349
350
|
if (!extraOpts.freshEnv && fs.existsSync(outputPath)) {
|
|
350
351
|
const existingContent = fs.readFileSync(outputPath, 'utf8');
|
|
@@ -366,7 +367,7 @@ async function writeLocalEnvToOutputPath(outputPath, appName, secretsPath, envOu
|
|
|
366
367
|
async function writePatchedEnvToOutputPath(envPath, outputPath, variables, envOutputPathLabel) {
|
|
367
368
|
const envContent = fs.readFileSync(envPath, 'utf8');
|
|
368
369
|
let patchedContent = await patchEnvContentForLocal(envContent, variables);
|
|
369
|
-
patchedContent = substituteMntDataForLocal(patchedContent, outputPath);
|
|
370
|
+
patchedContent = substituteMntDataForLocal(patchedContent, outputPath, variables.app?.key || path.basename(path.dirname(envPath)));
|
|
370
371
|
fs.writeFileSync(outputPath, patchedContent, { mode: 0o600 });
|
|
371
372
|
logger.log(formatSuccessLine(`Copied .env to: ${envOutputPathLabel}`));
|
|
372
373
|
}
|
|
@@ -405,7 +406,7 @@ async function copyOrRegenerateEnvForNamedApp(opts) {
|
|
|
405
406
|
return;
|
|
406
407
|
}
|
|
407
408
|
if (fs.existsSync(envPath)) {
|
|
408
|
-
await syncWrittenBuilderEnvToOutputPath(envPath, outputPath, label, { freshEnv: freshEnv === true });
|
|
409
|
+
await syncWrittenBuilderEnvToOutputPath(envPath, outputPath, label, { freshEnv: freshEnv === true, appName });
|
|
409
410
|
return;
|
|
410
411
|
}
|
|
411
412
|
await writeLocalEnvToOutputPath(outputPath, appName, secretsPath, label, localOpts);
|