@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
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* kv:// resolution, declarative URL expansion, and .env generation (content + file write).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Split from secrets.js for module size limits
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
|
|
14
|
+
const config = require('./config');
|
|
15
|
+
const {
|
|
16
|
+
interpolateEnvVars,
|
|
17
|
+
collectMissingSecrets,
|
|
18
|
+
formatMissingSecretsFileInfo,
|
|
19
|
+
replaceKvInContent,
|
|
20
|
+
loadEnvTemplate,
|
|
21
|
+
adjustLocalEnvPortsInContent,
|
|
22
|
+
rewriteInfraEndpoints
|
|
23
|
+
} = require('../utils/secrets-helpers');
|
|
24
|
+
const { buildEnvVarMap } = require('../utils/env-map');
|
|
25
|
+
const { resolveServicePortsInEnvContent } = require('../utils/secrets-url');
|
|
26
|
+
const { materializeResolvedKvSecretsToUserLocal } = require('../utils/secrets-materialize-local');
|
|
27
|
+
const {
|
|
28
|
+
updatePortForDocker,
|
|
29
|
+
getBaseDockerEnv,
|
|
30
|
+
applyDockerEnvOverride,
|
|
31
|
+
getContainerPortFromDockerEnv
|
|
32
|
+
} = require('./secrets-docker-env');
|
|
33
|
+
const { getContainerPortFromPath, loadVariablesFromPath } = require('../utils/port-resolver');
|
|
34
|
+
const secretsEnsure = require('./secrets-ensure');
|
|
35
|
+
const { resolveSecretsPath, getActualSecretsPath } = require('../utils/secrets-path');
|
|
36
|
+
const pathsUtil = require('../utils/paths');
|
|
37
|
+
const { readAppEnvironmentScopedFlagForAppPath } = require('../utils/app-scoped-config');
|
|
38
|
+
const { computeEffectiveEnvironmentScopedResources, redisDbIndexForScopedRunEnv } = require('../utils/environment-scoped-resources');
|
|
39
|
+
const { applyRedisDbIndexToEnvContent } = require('../utils/redis-env-scope');
|
|
40
|
+
const { expandDeclarativeUrlsIfPresent } = require('./secrets-env-declarative-expand');
|
|
41
|
+
const {
|
|
42
|
+
mergeDockerManifestPublishedPort,
|
|
43
|
+
rewriteDockerManifestPublicPortEnvLine
|
|
44
|
+
} = require('../utils/docker-manifest-public-port');
|
|
45
|
+
const { loadSecrets } = require('./secrets-load');
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Resolves kv:// references in environment template
|
|
49
|
+
* Replaces kv://keyName with actual values from secrets
|
|
50
|
+
*
|
|
51
|
+
* @async
|
|
52
|
+
* @param {string} envTemplate - Environment template content
|
|
53
|
+
* @param {Object} secrets - Secrets object from loadSecrets()
|
|
54
|
+
* @param {string} [environment='local'] - Environment context (docker/local)
|
|
55
|
+
* @param {Object|string|null} [secretsFilePaths] - Paths object with userPath and buildPath, or string path (for backward compatibility)
|
|
56
|
+
* @param {string} [appName] - Application name (optional, for error messages)
|
|
57
|
+
* @returns {Promise<string>} Resolved environment content
|
|
58
|
+
* @throws {Error} If kv:// reference cannot be resolved
|
|
59
|
+
*/
|
|
60
|
+
async function resolveKvReferences(envTemplate, secrets, environment = 'local', secretsFilePaths = null, appName = null, scopedKv = null) {
|
|
61
|
+
const os = require('os');
|
|
62
|
+
|
|
63
|
+
let developerId = null;
|
|
64
|
+
try {
|
|
65
|
+
developerId = await config.getDeveloperId();
|
|
66
|
+
} catch {
|
|
67
|
+
/* ignore */
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const envKey = String(environment || 'local').toLowerCase();
|
|
71
|
+
const mapContext = envKey === 'docker' || envKey === 'local' ? envKey : 'local';
|
|
72
|
+
|
|
73
|
+
let envVars = await buildEnvVarMap(mapContext, os, developerId);
|
|
74
|
+
if (!envVars || Object.keys(envVars).length === 0) {
|
|
75
|
+
envVars = await buildEnvVarMap('local', os, developerId);
|
|
76
|
+
}
|
|
77
|
+
const resolved = interpolateEnvVars(envTemplate, envVars);
|
|
78
|
+
const missing = collectMissingSecrets(resolved, secrets, scopedKv);
|
|
79
|
+
if (missing.length > 0) {
|
|
80
|
+
const fileInfo = formatMissingSecretsFileInfo(secretsFilePaths);
|
|
81
|
+
const resolveCommand = appName ? `aifabrix resolve ${appName}` : 'aifabrix resolve <app-name>';
|
|
82
|
+
throw new Error(`Missing secrets: ${missing.join(', ')}${fileInfo}\n\nRun "${resolveCommand}" to generate missing secrets.`);
|
|
83
|
+
}
|
|
84
|
+
return replaceKvInContent(resolved, secrets, envVars, scopedKv);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Resolve run env key and effective env-scoped kv/redis behavior for generateEnvContent.
|
|
89
|
+
*
|
|
90
|
+
* @async
|
|
91
|
+
* @param {string} appPath - Builder application directory
|
|
92
|
+
* @param {Object} [options] - generateEnvContent options; may set runEnvKey
|
|
93
|
+
* @returns {Promise<{ runEnvKey: string, effective: boolean }>}
|
|
94
|
+
*/
|
|
95
|
+
async function buildScopedKvContext(appPath, options = {}) {
|
|
96
|
+
let runEnvKey;
|
|
97
|
+
if (options.runEnvKey !== undefined && options.runEnvKey !== null) {
|
|
98
|
+
runEnvKey = String(options.runEnvKey).toLowerCase();
|
|
99
|
+
} else if (typeof config.getCurrentEnvironment === 'function') {
|
|
100
|
+
runEnvKey = String((await config.getCurrentEnvironment()) || 'dev').toLowerCase();
|
|
101
|
+
} else {
|
|
102
|
+
runEnvKey = 'dev';
|
|
103
|
+
}
|
|
104
|
+
const userCfg =
|
|
105
|
+
typeof config.getConfig === 'function'
|
|
106
|
+
? await config.getConfig()
|
|
107
|
+
: { useEnvironmentScopedResources: false };
|
|
108
|
+
const useGate = Boolean(userCfg.useEnvironmentScopedResources);
|
|
109
|
+
const appFlag = readAppEnvironmentScopedFlagForAppPath(appPath);
|
|
110
|
+
const effective = computeEffectiveEnvironmentScopedResources(useGate, appFlag, runEnvKey);
|
|
111
|
+
return { runEnvKey, effective };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Redis/DB service endpoints for docker env interpolation.
|
|
116
|
+
* @returns {Promise<{ redisHost: string, redisPort: number, dbHost: string, dbPort: number }>}
|
|
117
|
+
*/
|
|
118
|
+
async function getDockerRedisDbEndpoints() {
|
|
119
|
+
const { getEnvHosts, getServiceHost, getServicePort, getLocalhostOverride } = require('../utils/env-endpoints');
|
|
120
|
+
const hosts = await getEnvHosts('docker');
|
|
121
|
+
const localhostOverride = getLocalhostOverride('docker');
|
|
122
|
+
const redisHost = getServiceHost(hosts.REDIS_HOST, 'docker', 'redis', localhostOverride);
|
|
123
|
+
const redisPort = await getServicePort('REDIS_PORT', 'redis', hosts, 'docker', null);
|
|
124
|
+
const dbHost = getServiceHost(hosts.DB_HOST, 'docker', 'postgres', localhostOverride);
|
|
125
|
+
const dbPort = await getServicePort('DB_PORT', 'postgres', hosts, 'docker', null);
|
|
126
|
+
return { redisHost, redisPort, dbHost, dbPort };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Docker env transformations: ports, infra endpoints, PORT. */
|
|
130
|
+
async function applyDockerTransformations(resolved, variablesPath) {
|
|
131
|
+
resolved = await resolveServicePortsInEnvContent(resolved, 'docker');
|
|
132
|
+
resolved = await rewriteInfraEndpoints(resolved, 'docker');
|
|
133
|
+
const { redisHost, redisPort, dbHost, dbPort } = await getDockerRedisDbEndpoints();
|
|
134
|
+
let dockerEnv = await getBaseDockerEnv();
|
|
135
|
+
dockerEnv = applyDockerEnvOverride(dockerEnv);
|
|
136
|
+
const containerPort = getContainerPortFromPath(variablesPath) ?? getContainerPortFromDockerEnv(dockerEnv) ?? 3000;
|
|
137
|
+
const envVars = await buildEnvVarMap('docker', null, null, { appPort: containerPort });
|
|
138
|
+
const appDoc = loadVariablesFromPath(variablesPath);
|
|
139
|
+
await mergeDockerManifestPublishedPort(envVars, appDoc);
|
|
140
|
+
envVars.REDIS_HOST = redisHost;
|
|
141
|
+
envVars.REDIS_PORT = String(redisPort);
|
|
142
|
+
envVars.DB_HOST = dbHost;
|
|
143
|
+
envVars.DB_PORT = String(dbPort);
|
|
144
|
+
envVars.PORT = String(containerPort);
|
|
145
|
+
resolved = interpolateEnvVars(resolved, envVars);
|
|
146
|
+
resolved = rewriteDockerManifestPublicPortEnvLine(resolved, envVars, appDoc);
|
|
147
|
+
return updatePortForDocker(resolved, variablesPath);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Environment-specific transformations to resolved content. */
|
|
151
|
+
async function applyEnvironmentTransformations(resolved, environment, variablesPath) {
|
|
152
|
+
if (environment === 'docker') return applyDockerTransformations(resolved, variablesPath);
|
|
153
|
+
if (environment === 'local') return adjustLocalEnvPortsInContent(resolved, variablesPath);
|
|
154
|
+
return resolved;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Generate .env content from template and secrets (no disk write).
|
|
159
|
+
* When options.envOnly is true, variablesPath is null (no application config).
|
|
160
|
+
*
|
|
161
|
+
* @param {string} appName - Application name
|
|
162
|
+
* @param {string} [secretsPath] - Path to secrets file (optional)
|
|
163
|
+
* @param {string} [environment='local'] - Environment context
|
|
164
|
+
* @param {boolean} [force=false] - Generate missing secret keys
|
|
165
|
+
* @param {Object} [options] - Optional: appPath, envOnly (env-only mode uses only env.template); skipMaterializeKvToLocal skips persisting resolved kv to ~/.aifabrix/secrets.local.yaml. Materialization runs only when no explicit secretsPath is passed (default loadSecrets cascade uses ~/.aifabrix/secrets.local.yaml).
|
|
166
|
+
* @returns {Promise<string>} Resolved env content
|
|
167
|
+
*/
|
|
168
|
+
async function generateEnvContent(appName, secretsPath, environment = 'local', force = false, options = {}) {
|
|
169
|
+
const appPath = (options && options.appPath) || pathsUtil.getBuilderPath(appName);
|
|
170
|
+
const templatePath = path.join(appPath, 'env.template');
|
|
171
|
+
const variablesPath = (options && options.envOnly) ? null : resolveApplicationConfigPath(appPath);
|
|
172
|
+
const template = loadEnvTemplate(templatePath);
|
|
173
|
+
const secretsPaths = await getActualSecretsPath(secretsPath, appName);
|
|
174
|
+
if (force) {
|
|
175
|
+
const preferredPath = secretsPath ? resolveSecretsPath(secretsPath) : secretsPaths.userPath;
|
|
176
|
+
await secretsEnsure.ensureSecretsFromEnvTemplate(templatePath, { preferredFilePath: preferredPath });
|
|
177
|
+
}
|
|
178
|
+
const secrets = await loadSecrets(secretsPath, appName);
|
|
179
|
+
const { runEnvKey, effective } = await buildScopedKvContext(appPath, options);
|
|
180
|
+
const scopedKv = { envKey: runEnvKey, effective };
|
|
181
|
+
let resolved = await resolveKvReferences(template, secrets, environment, secretsPaths, appName, scopedKv);
|
|
182
|
+
if (!secretsPath) {
|
|
183
|
+
await materializeResolvedKvSecretsToUserLocal(template, secrets, scopedKv, options);
|
|
184
|
+
}
|
|
185
|
+
resolved = await expandDeclarativeUrlsIfPresent(
|
|
186
|
+
resolved,
|
|
187
|
+
appName,
|
|
188
|
+
appPath,
|
|
189
|
+
variablesPath,
|
|
190
|
+
environment,
|
|
191
|
+
Boolean(options.envOnly)
|
|
192
|
+
);
|
|
193
|
+
resolved = await applyEnvironmentTransformations(resolved, environment, variablesPath);
|
|
194
|
+
if (effective) {
|
|
195
|
+
const idx = redisDbIndexForScopedRunEnv(runEnvKey);
|
|
196
|
+
resolved = applyRedisDbIndexToEnvContent(resolved, idx);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return resolved;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Parses .env file content into a key-to-value map.
|
|
204
|
+
* Only includes lines that look like KEY=value (first = separates key and value).
|
|
205
|
+
*
|
|
206
|
+
* @param {string} content - Raw .env file content
|
|
207
|
+
* @returns {Object.<string, string>} Map of variable name to value
|
|
208
|
+
*/
|
|
209
|
+
function parseEnvContentToMap(content) {
|
|
210
|
+
if (!content || typeof content !== 'string') {
|
|
211
|
+
return {};
|
|
212
|
+
}
|
|
213
|
+
const map = {};
|
|
214
|
+
const lines = content.split(/\r?\n/);
|
|
215
|
+
for (const line of lines) {
|
|
216
|
+
const trimmed = line.trim();
|
|
217
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
const eq = trimmed.indexOf('=');
|
|
221
|
+
if (eq > 0) {
|
|
222
|
+
const key = trimmed.substring(0, eq).trim();
|
|
223
|
+
const value = trimmed.substring(eq + 1);
|
|
224
|
+
map[key] = value;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return map;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Merges new .env content with existing .env: newly resolved content wins for keys it
|
|
232
|
+
* defines (so project secrets take effect when re-running). Keys only in existing .env
|
|
233
|
+
* are appended so manual additions are kept.
|
|
234
|
+
*
|
|
235
|
+
* @param {string} newContent - Newly generated .env content (from template + loadSecrets)
|
|
236
|
+
* @param {Object.<string, string>} existingMap - Existing key-to-value map from current .env
|
|
237
|
+
* @returns {string} Merged content: new values for keys in newContent, plus extra existing keys
|
|
238
|
+
*/
|
|
239
|
+
function mergeEnvContentPreservingExisting(newContent, existingMap) {
|
|
240
|
+
const lines = newContent.split(/\r?\n/);
|
|
241
|
+
const newKeys = new Set();
|
|
242
|
+
const out = [];
|
|
243
|
+
for (const line of lines) {
|
|
244
|
+
const trimmed = line.trim();
|
|
245
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
246
|
+
out.push(line);
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
const eq = trimmed.indexOf('=');
|
|
250
|
+
if (eq > 0) {
|
|
251
|
+
const key = trimmed.substring(0, eq).trim();
|
|
252
|
+
newKeys.add(key);
|
|
253
|
+
}
|
|
254
|
+
out.push(line);
|
|
255
|
+
}
|
|
256
|
+
if (existingMap && Object.keys(existingMap).length > 0) {
|
|
257
|
+
for (const key of Object.keys(existingMap)) {
|
|
258
|
+
if (!newKeys.has(key)) {
|
|
259
|
+
out.push(`${key}=${existingMap[key]}`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return out.join('\n');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Merges a key-value map into existing .env file content, preserving comments and blank lines.
|
|
268
|
+
* For each KEY=value line in existing content, replaces value with newMap[KEY] when the key exists
|
|
269
|
+
* in newMap. By default appends keys from newMap that did not appear in the file; set
|
|
270
|
+
* `appendMissingFromNewMap: false` to only update keys already present (e.g. `--reload` into a
|
|
271
|
+
* resolve-generated file so run-only keys like DB_0_NAME are not tacked on the end).
|
|
272
|
+
*
|
|
273
|
+
* @param {string} existingContent - Full existing .env file content
|
|
274
|
+
* @param {Object.<string, string>} newMap - New key-to-value map (e.g. from resolved or run env)
|
|
275
|
+
* @param {Object} [options] - Merge options
|
|
276
|
+
* @param {boolean} [options.appendMissingFromNewMap=true] - When false, do not append keys only in newMap
|
|
277
|
+
* @returns {string} Merged content with comments preserved
|
|
278
|
+
*/
|
|
279
|
+
function collectSeenKeysAndMergeEnvLines(lines, newMap) {
|
|
280
|
+
const seen = new Set();
|
|
281
|
+
const out = [];
|
|
282
|
+
for (const line of lines) {
|
|
283
|
+
const trimmed = line.trim();
|
|
284
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
285
|
+
out.push(line);
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
const eq = trimmed.indexOf('=');
|
|
289
|
+
if (eq > 0) {
|
|
290
|
+
const key = trimmed.substring(0, eq).trim();
|
|
291
|
+
seen.add(key);
|
|
292
|
+
out.push(Object.prototype.hasOwnProperty.call(newMap, key) ? `${key}=${newMap[key]}` : line);
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
out.push(line);
|
|
296
|
+
}
|
|
297
|
+
return { out, seen };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function appendMissingEnvKeysFromMap(out, seen, newMap) {
|
|
301
|
+
for (const key of Object.keys(newMap)) {
|
|
302
|
+
if (!seen.has(key)) {
|
|
303
|
+
out.push(`${key}=${newMap[key]}`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function mergeEnvMapIntoContent(existingContent, newMap, options = {}) {
|
|
309
|
+
if (!newMap || Object.keys(newMap).length === 0) {
|
|
310
|
+
return typeof existingContent === 'string' ? existingContent : '';
|
|
311
|
+
}
|
|
312
|
+
const appendMissing = options.appendMissingFromNewMap !== false;
|
|
313
|
+
const lines = (existingContent || '').split(/\r?\n/);
|
|
314
|
+
const { out, seen } = collectSeenKeysAndMergeEnvLines(lines, newMap);
|
|
315
|
+
if (appendMissing) {
|
|
316
|
+
appendMissingEnvKeysFromMap(out, seen, newMap);
|
|
317
|
+
}
|
|
318
|
+
return out.join('\n');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Resolves content to write for .env: merges with existing file when present.
|
|
323
|
+
* @param {string} resolved - Newly generated content
|
|
324
|
+
* @param {string} pathToPreserve - Path to existing .env to merge from (or null)
|
|
325
|
+
* @returns {string} Content to write
|
|
326
|
+
*/
|
|
327
|
+
function resolveEnvContentToWrite(resolved, pathToPreserve) {
|
|
328
|
+
if (!pathToPreserve || !fs.existsSync(pathToPreserve)) return resolved;
|
|
329
|
+
const existingContent = fs.readFileSync(pathToPreserve, 'utf8');
|
|
330
|
+
const existingMap = parseEnvContentToMap(existingContent);
|
|
331
|
+
return mergeEnvContentPreservingExisting(resolved, existingMap);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Generates and writes .env file. Newly resolved values win over existing .env; extra vars in existing .env are kept.
|
|
336
|
+
* When `options.envOnly` is true, only env.template is used; .env is written to `options.appPath`.
|
|
337
|
+
*
|
|
338
|
+
* When `options.noWrite` is true, the function resolves the .env content in memory and skips
|
|
339
|
+
* both writes — neither `<appPath>/.env` nor `build.envOutputPath` is materialized — and returns
|
|
340
|
+
* `null`. Use this from non-resolve flows (register/rotate-secret/build/up-*) so resolved secrets
|
|
341
|
+
* never land on disk except when the user runs `aifabrix resolve <app>` explicitly.
|
|
342
|
+
*
|
|
343
|
+
* @async
|
|
344
|
+
* @param {string} appName - Name of the application
|
|
345
|
+
* @param {string} [secretsPath] - Path to secrets file (optional)
|
|
346
|
+
* @param {string} [environment='local'] - Environment context ('local' or 'docker')
|
|
347
|
+
* @param {boolean} [force=false] - Generate missing secret keys in secrets file
|
|
348
|
+
* @param {Object} [options] - Optional: appPath, envOnly, skipOutputPath, preserveFromPath, noWrite,
|
|
349
|
+
* preferLocalEnvOutputPath (when **true**, `build.envOutputPath` is regenerated with **local** `url://` profile; **false**
|
|
350
|
+
* only when **both** `remote-server` is set and `applications.<app>.reload` is true — then docker flavor matches `builder/<app>/.env`)
|
|
351
|
+
* @param {boolean} [options.noWrite=false] - When true, resolve in-memory only; do not write
|
|
352
|
+
* `<appPath>/.env` and do not call `processEnvVariables`. Returns `null` in that case.
|
|
353
|
+
* @returns {Promise<string|null>} Path to generated .env file, or `null` when `noWrite` is true
|
|
354
|
+
*
|
|
355
|
+
* @example
|
|
356
|
+
* // up-platform / up-miso / up-dataplane / register / rotate-secret / build flows:
|
|
357
|
+
* await generateEnvFile('dataplane', null, 'local', false, { noWrite: true });
|
|
358
|
+
*
|
|
359
|
+
* @example
|
|
360
|
+
* // aifabrix resolve <app> — the only legitimate writer of a persistent .env:
|
|
361
|
+
* await generateEnvFile('dataplane', undefined, 'docker', force, {
|
|
362
|
+
* appPath, envOnly, skipOutputPath: false, preserveFromPath: null
|
|
363
|
+
* });
|
|
364
|
+
*/
|
|
365
|
+
/**
|
|
366
|
+
* Materialize a resolved .env to `<appPath>/.env` and (optionally) copy through
|
|
367
|
+
* `build.envOutputPath`. Extracted so {@link generateEnvFile} can stay under the
|
|
368
|
+
* 20-statement limit while still expressing the in-memory vs on-disk branch clearly.
|
|
369
|
+
*
|
|
370
|
+
* @async
|
|
371
|
+
* @param {Object} params
|
|
372
|
+
* @param {string} params.appName
|
|
373
|
+
* @param {string} params.appPath
|
|
374
|
+
* @param {string} params.envPath - Resolved `<appPath>/.env` target
|
|
375
|
+
* @param {string} params.resolved - Fully resolved .env content
|
|
376
|
+
* @param {string|null} params.variablesPath - application.yaml path (or null when envOnly)
|
|
377
|
+
* @param {string} [params.secretsPath]
|
|
378
|
+
* @param {Object} params.opts - Caller options (preserveFromPath, skipOutputPath, preferLocalEnvOutputPath, appPath)
|
|
379
|
+
* @returns {Promise<string>} Path to the written .env file
|
|
380
|
+
*/
|
|
381
|
+
async function writeResolvedEnv({ appName, envPath, resolved, variablesPath, secretsPath, opts }) {
|
|
382
|
+
const preservePath = opts.preserveFromPath !== undefined && opts.preserveFromPath !== null ? opts.preserveFromPath : null;
|
|
383
|
+
const pathToPreserve = preservePath !== null ? preservePath : envPath;
|
|
384
|
+
const toWrite = resolveEnvContentToWrite(resolved, pathToPreserve);
|
|
385
|
+
fs.writeFileSync(envPath, toWrite, { mode: 0o600 });
|
|
386
|
+
|
|
387
|
+
if (!opts.skipOutputPath) {
|
|
388
|
+
const { processEnvVariables } = require('../utils/env-copy');
|
|
389
|
+
await processEnvVariables(envPath, variablesPath, appName, secretsPath, {
|
|
390
|
+
preferLocalEnvOutputPath: opts.preferLocalEnvOutputPath === true,
|
|
391
|
+
appPath: opts.appPath || null
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
return envPath;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
async function generateEnvFile(appName, secretsPath, environment = 'local', force = false, options = {}) {
|
|
398
|
+
const opts = options && typeof options === 'object' ? options : {};
|
|
399
|
+
const appPath = opts.appPath || pathsUtil.getBuilderPath(appName);
|
|
400
|
+
const envOnly = !!opts.envOnly;
|
|
401
|
+
const noWrite = opts.noWrite === true;
|
|
402
|
+
const variablesPath = envOnly ? null : resolveApplicationConfigPath(appPath);
|
|
403
|
+
const envPath = path.join(appPath, '.env');
|
|
404
|
+
|
|
405
|
+
if (envOnly) {
|
|
406
|
+
const templatePath = path.join(appPath, 'env.template');
|
|
407
|
+
if (!fs.existsSync(templatePath)) {
|
|
408
|
+
throw new Error(`env.template not found at ${templatePath}. Resolve requires env.template in the app directory.`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Always resolve so missing-secret / kv:// errors still surface in noWrite mode.
|
|
413
|
+
const resolved = await generateEnvContent(appName, secretsPath, environment, force, { appPath, envOnly });
|
|
414
|
+
|
|
415
|
+
if (noWrite) {
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return writeResolvedEnv({ appName, appPath, envPath, resolved, variablesPath, secretsPath, opts });
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
module.exports = {
|
|
423
|
+
resolveKvReferences,
|
|
424
|
+
generateEnvContent,
|
|
425
|
+
generateEnvFile,
|
|
426
|
+
parseEnvContentToMap,
|
|
427
|
+
mergeEnvMapIntoContent
|
|
428
|
+
};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declarative url:// expansion after kv:// (keeps secrets-env-content under max-lines).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Declarative url:// expansion after kv://; shared ctx builder; show URL helper
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const config = require('./config');
|
|
12
|
+
const pathsUtil = require('../utils/paths');
|
|
13
|
+
const { readAppEnvironmentScopedFlagForAppPath } = require('../utils/app-scoped-config');
|
|
14
|
+
const { expandDeclarativeUrlsInEnvContent } = require('../utils/url-declarative-resolve');
|
|
15
|
+
const { rewriteInactiveDeclarativeVdirPublicContent } = require('../utils/url-declarative-vdir-inactive-env');
|
|
16
|
+
const { refreshUrlsLocalRegistryFromBuilder } = require('../utils/urls-local-registry');
|
|
17
|
+
/**
|
|
18
|
+
* Config inputs for declarative url:// expansion.
|
|
19
|
+
* @param {string} appPath
|
|
20
|
+
* @returns {Promise<Object>}
|
|
21
|
+
*/
|
|
22
|
+
async function loadDeclarativeUrlExpandInputs(appPath) {
|
|
23
|
+
const userCfg = await config.getConfig();
|
|
24
|
+
let remoteServer = null;
|
|
25
|
+
try {
|
|
26
|
+
const rs = await config.getRemoteServer();
|
|
27
|
+
if (rs && String(rs).trim()) {
|
|
28
|
+
remoteServer = String(rs).trim();
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
remoteServer = null;
|
|
32
|
+
}
|
|
33
|
+
let developerIdRaw = null;
|
|
34
|
+
try {
|
|
35
|
+
developerIdRaw = await config.getDeveloperId();
|
|
36
|
+
} catch {
|
|
37
|
+
developerIdRaw = null;
|
|
38
|
+
}
|
|
39
|
+
let infraTlsEnabled = false;
|
|
40
|
+
try {
|
|
41
|
+
infraTlsEnabled = await config.getTlsEnabled();
|
|
42
|
+
} catch {
|
|
43
|
+
infraTlsEnabled = false;
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
userCfg,
|
|
47
|
+
remoteServer,
|
|
48
|
+
developerIdRaw,
|
|
49
|
+
infraTlsEnabled,
|
|
50
|
+
appScopedFlag: readAppEnvironmentScopedFlagForAppPath(appPath)
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Build ctx for {@link expandDeclarativeUrlsInEnvContent} from preloaded inputs.
|
|
56
|
+
* @param {string} appName
|
|
57
|
+
* @param {string|null|undefined} variablesPath
|
|
58
|
+
* @param {string} environment
|
|
59
|
+
* @param {Object} inputs - Result of {@link loadDeclarativeUrlExpandInputs}
|
|
60
|
+
* @returns {Object}
|
|
61
|
+
*/
|
|
62
|
+
function buildDeclarativeUrlExpandContextFromInputs(appName, variablesPath, environment, inputs) {
|
|
63
|
+
const { userCfg, remoteServer, developerIdRaw, infraTlsEnabled, appScopedFlag } = inputs;
|
|
64
|
+
let projectRoot = null;
|
|
65
|
+
try {
|
|
66
|
+
const r = pathsUtil.getProjectRoot();
|
|
67
|
+
if (r && String(r).trim()) {
|
|
68
|
+
projectRoot = String(r).trim();
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
projectRoot = null;
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
profile: environment === 'docker' ? 'docker' : 'local',
|
|
75
|
+
currentAppKey: appName,
|
|
76
|
+
variablesPath,
|
|
77
|
+
useEnvironmentScopedResources: Boolean(userCfg.useEnvironmentScopedResources),
|
|
78
|
+
appEnvironmentScopedResources: appScopedFlag,
|
|
79
|
+
remoteServer,
|
|
80
|
+
developerIdRaw,
|
|
81
|
+
infraTlsEnabled,
|
|
82
|
+
userCfg,
|
|
83
|
+
projectRoot
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Resolve `url://public` and `url://internal` for the app (same rules as run `.env`, default `docker` profile).
|
|
89
|
+
* @param {string} appKey
|
|
90
|
+
* @param {string} appPath
|
|
91
|
+
* @param {string|null|undefined} variablesPath
|
|
92
|
+
* @param {string} [environment='docker'] - Matches `secrets-env-write` default so `url://internal` uses the same service host + vdir rules as the app `.env` copied for Docker run.
|
|
93
|
+
* @returns {Promise<{ publicUrl: string, internalUrl: string }|null>}
|
|
94
|
+
*/
|
|
95
|
+
async function resolveDeclarativeShowUrlsForApp(appKey, appPath, variablesPath, environment = 'docker') {
|
|
96
|
+
if (!variablesPath || !appPath || !appKey) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
const inputs = await loadDeclarativeUrlExpandInputs(appPath);
|
|
100
|
+
const { parseSimpleEnvMap } = require('../utils/url-declarative-resolve');
|
|
101
|
+
let content = 'APP_SHOW_PUBLIC=url://public\nAPP_SHOW_INTERNAL=url://internal\n';
|
|
102
|
+
content = rewriteInactiveDeclarativeVdirPublicContent(content, variablesPath, inputs.userCfg);
|
|
103
|
+
if (!content.includes('url://')) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
const ctx = buildDeclarativeUrlExpandContextFromInputs(appKey, variablesPath, environment, inputs);
|
|
107
|
+
const out = await expandDeclarativeUrlsInEnvContent(content, ctx);
|
|
108
|
+
const m = parseSimpleEnvMap(out);
|
|
109
|
+
const publicUrl = m.APP_SHOW_PUBLIC;
|
|
110
|
+
const internalUrl = m.APP_SHOW_INTERNAL;
|
|
111
|
+
if (
|
|
112
|
+
!publicUrl ||
|
|
113
|
+
!internalUrl ||
|
|
114
|
+
String(publicUrl).includes('url://') ||
|
|
115
|
+
String(internalUrl).includes('url://')
|
|
116
|
+
) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
return { publicUrl: String(publicUrl).trim(), internalUrl: String(internalUrl).trim() };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* After kv:// resolution, expand url:// references when application config exists.
|
|
124
|
+
* Also refreshes {@link refreshUrlsLocalRegistryFromBuilder} whenever `variablesPath` is set so
|
|
125
|
+
* `urls.local.yaml` picks up `port` / `frontDoorRouting` changes even if `env.template` has no
|
|
126
|
+
* literal `url://` placeholders (they may already be expanded or absent).
|
|
127
|
+
* @param {string} resolved
|
|
128
|
+
* @param {string} appName
|
|
129
|
+
* @param {string} appPath
|
|
130
|
+
* @param {string|null} variablesPath
|
|
131
|
+
* @param {string} environment
|
|
132
|
+
* @param {boolean} envOnly
|
|
133
|
+
* @returns {Promise<string>}
|
|
134
|
+
*/
|
|
135
|
+
async function expandDeclarativeUrlsIfPresent(resolved, appName, appPath, variablesPath, environment, envOnly) {
|
|
136
|
+
if (envOnly || !variablesPath) {
|
|
137
|
+
return resolved;
|
|
138
|
+
}
|
|
139
|
+
const { userCfg, remoteServer, developerIdRaw, infraTlsEnabled, appScopedFlag } =
|
|
140
|
+
await loadDeclarativeUrlExpandInputs(appPath);
|
|
141
|
+
resolved = rewriteInactiveDeclarativeVdirPublicContent(resolved, variablesPath, userCfg);
|
|
142
|
+
const ctx = buildDeclarativeUrlExpandContextFromInputs(appName, variablesPath, environment, {
|
|
143
|
+
userCfg,
|
|
144
|
+
remoteServer,
|
|
145
|
+
developerIdRaw,
|
|
146
|
+
infraTlsEnabled,
|
|
147
|
+
appScopedFlag
|
|
148
|
+
});
|
|
149
|
+
try {
|
|
150
|
+
const pr = ctx.projectRoot && String(ctx.projectRoot).trim() ? String(ctx.projectRoot).trim() : '';
|
|
151
|
+
const registryRoot = pr || pathsUtil.getProjectRoot();
|
|
152
|
+
if (ctx.excludeCwdBuilderScan === true) {
|
|
153
|
+
refreshUrlsLocalRegistryFromBuilder(registryRoot, { excludeCwdBuilderScan: true });
|
|
154
|
+
} else {
|
|
155
|
+
refreshUrlsLocalRegistryFromBuilder(registryRoot);
|
|
156
|
+
}
|
|
157
|
+
} catch {
|
|
158
|
+
// best-effort: registry refresh must not block .env generation
|
|
159
|
+
}
|
|
160
|
+
if (!resolved.includes('url://')) {
|
|
161
|
+
return resolved;
|
|
162
|
+
}
|
|
163
|
+
return expandDeclarativeUrlsInEnvContent(resolved, ctx);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
module.exports = {
|
|
167
|
+
expandDeclarativeUrlsIfPresent,
|
|
168
|
+
loadDeclarativeUrlExpandInputs,
|
|
169
|
+
resolveDeclarativeShowUrlsForApp
|
|
170
|
+
};
|
|
@@ -100,6 +100,30 @@ async function injectRegistryTokens(content, secretsPath, appName) {
|
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Append variables from `BASH_*` secret keys (merged secrets load) when not already set in content.
|
|
105
|
+
*
|
|
106
|
+
* @param {string} content - .env-style content
|
|
107
|
+
* @param {string|null} secretsPath - Optional secrets path
|
|
108
|
+
* @param {string|null} appName - Optional app name for loadSecrets
|
|
109
|
+
* @returns {Promise<string>}
|
|
110
|
+
*/
|
|
111
|
+
async function injectBashPrefixedExportLines(content, secretsPath, appName) {
|
|
112
|
+
try {
|
|
113
|
+
const { getBashPrefixedProcessEnvOverlay } = require('../utils/bash-secret-env');
|
|
114
|
+
const bash = await getBashPrefixedProcessEnvOverlay(secretsPath, appName);
|
|
115
|
+
let out = content || '';
|
|
116
|
+
for (const [name, value] of Object.entries(bash)) {
|
|
117
|
+
if (!envContentHasKey(out, name)) {
|
|
118
|
+
out = appendEnvLine(out, name, value);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
122
|
+
} catch {
|
|
123
|
+
return content || '';
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
103
127
|
/**
|
|
104
128
|
* Parse .env-style content into a key-value map (excludes comments and empty lines).
|
|
105
129
|
* @param {string} content - .env-style content
|
|
@@ -123,7 +147,9 @@ function parseEnvContentToMap(content) {
|
|
|
123
147
|
|
|
124
148
|
/**
|
|
125
149
|
* Resolve .env in memory and write only to envOutputPath or temp (no builder/ or integration/).
|
|
126
|
-
* Injects NPM_TOKEN and PYPI_TOKEN from secrets when missing, then from process.env, so shell/install/
|
|
150
|
+
* Injects NPM_TOKEN and PYPI_TOKEN from secrets when missing, then from process.env, then names derived from `BASH_*` keys in the merged secrets store, so shell/install/build match private registry tooling.
|
|
151
|
+
*
|
|
152
|
+
* **Ephemeral / tooling path:** Used by `app-install`, `app-shell`, and `app-test` to materialize a disposable `.env` (defaults to a temp file under `os.tmpdir()`). This is **not** the same as `aifabrix resolve <app>` (which uses `generateEnvFile` and preserves `build.envOutputPath` / comments). Prefer `aifabrix resolve` when you need a durable repo `.env` for local development.
|
|
127
153
|
*
|
|
128
154
|
* @async
|
|
129
155
|
* @function resolveAndWriteEnvFile
|
|
@@ -145,6 +171,7 @@ async function resolveAndWriteEnvFile(appName, options = {}) {
|
|
|
145
171
|
|
|
146
172
|
let resolved = await secrets.generateEnvContent(appName, secretsPath, environment, force);
|
|
147
173
|
resolved = await injectRegistryTokens(resolved, secretsPath, appName);
|
|
174
|
+
resolved = await injectBashPrefixedExportLines(resolved, secretsPath, appName);
|
|
148
175
|
|
|
149
176
|
if (envOutputPath && typeof envOutputPath === 'string') {
|
|
150
177
|
const dir = path.dirname(envOutputPath);
|
|
@@ -179,6 +206,7 @@ async function resolveAndGetEnvMap(appName, options = {}) {
|
|
|
179
206
|
const force = options.force === true;
|
|
180
207
|
let content = await secrets.generateEnvContent(appName, secretsPath, environment, force);
|
|
181
208
|
content = await injectRegistryTokens(content, secretsPath, appName);
|
|
209
|
+
content = await injectBashPrefixedExportLines(content, secretsPath, appName);
|
|
182
210
|
return parseEnvContentToMap(content);
|
|
183
211
|
}
|
|
184
212
|
|