@aifabrix/builder 2.44.5 → 2.44.6
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 +1 -1
- package/.cursor/rules/project-rules.mdc +1 -1
- package/.npmrc.token +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 +48 -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/push.js +46 -23
- package/lib/app/register.js +1 -1
- package/lib/app/restart-display.js +95 -0
- package/lib/app/rotate-secret.js +1 -1
- package/lib/app/run-container-start.js +12 -6
- package/lib/app/run-env-compose.js +30 -1
- package/lib/app/run-helpers.js +44 -12
- package/lib/app/run-reload-sync.js +148 -0
- package/lib/app/run-resolve-image.js +51 -1
- package/lib/app/run.js +99 -73
- package/lib/build/index.js +75 -45
- package/lib/cli/doctor-check.js +117 -0
- package/lib/cli/index.js +8 -2
- package/lib/cli/infra-guided.js +445 -0
- package/lib/cli/setup-app.js +20 -2
- package/lib/cli/setup-auth.js +26 -0
- package/lib/cli/setup-dev-path-commands.js +50 -3
- package/lib/cli/setup-infra.js +134 -61
- 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.js +78 -33
- 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 +455 -0
- package/lib/commands/setup-prompts.js +388 -0
- package/lib/commands/setup.js +149 -0
- package/lib/commands/teardown.js +228 -0
- package/lib/commands/up-common.js +79 -19
- package/lib/commands/up-dataplane.js +33 -11
- package/lib/commands/up-miso.js +7 -11
- package/lib/commands/upload.js +109 -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/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 +432 -0
- package/lib/core/secrets-env-write.js +27 -1
- package/lib/core/secrets-load.js +248 -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 +4 -1
- 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/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/external-datasource.schema.json +183 -53
- package/lib/schema/external-system.schema.json +23 -10
- package/lib/schema/infra.parameter.yaml +26 -11
- 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-run-containers.js +2 -2
- 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/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/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 +19 -4
- package/lib/utils/health-check.js +135 -105
- package/lib/utils/help-builder.js +5 -1
- package/lib/utils/image-name.js +34 -7
- package/lib/utils/integration-file-backup.js +74 -0
- package/lib/utils/mutagen-install.js +30 -3
- package/lib/utils/paths.js +108 -25
- 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 +42 -3
- package/lib/utils/resolve-docker-image-ref.js +9 -3
- package/lib/utils/secrets-ancestor-paths.js +47 -0
- 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 +24 -10
- package/lib/utils/secrets-utils.js +2 -2
- package/lib/utils/system-builder-root.js +34 -0
- package/lib/utils/url-declarative-resolve-build.js +6 -1
- package/lib/utils/url-declarative-runtime-base-path.js +32 -0
- package/lib/utils/url-declarative-vdir-inactive-env.js +2 -1
- package/lib/utils/urls-local-registry.js +23 -12
- 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 +1 -1
- package/templates/applications/dataplane/application.yaml +1 -1
- package/templates/applications/dataplane/rbac.yaml +10 -10
- package/templates/applications/keycloak/env.template +8 -6
- package/templates/applications/miso-controller/application.yaml +7 -0
- package/templates/applications/miso-controller/env.template +1 -1
- package/templates/applications/miso-controller/rbac.yaml +9 -9
- package/templates/external-system/README.md.hbs +83 -123
- 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,432 @@
|
|
|
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 { expandDeclarativeUrlsInEnvContent } = require('../utils/url-declarative-resolve');
|
|
41
|
+
const { rewriteInactiveDeclarativeVdirPublicContent } = require('../utils/url-declarative-vdir-inactive-env');
|
|
42
|
+
const {
|
|
43
|
+
mergeDockerManifestPublishedPort,
|
|
44
|
+
rewriteDockerManifestPublicPortEnvLine
|
|
45
|
+
} = require('../utils/docker-manifest-public-port');
|
|
46
|
+
const { loadSecrets } = require('./secrets-load');
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Resolves kv:// references in environment template
|
|
50
|
+
* Replaces kv://keyName with actual values from secrets
|
|
51
|
+
*
|
|
52
|
+
* @async
|
|
53
|
+
* @param {string} envTemplate - Environment template content
|
|
54
|
+
* @param {Object} secrets - Secrets object from loadSecrets()
|
|
55
|
+
* @param {string} [environment='local'] - Environment context (docker/local)
|
|
56
|
+
* @param {Object|string|null} [secretsFilePaths] - Paths object with userPath and buildPath, or string path (for backward compatibility)
|
|
57
|
+
* @param {string} [appName] - Application name (optional, for error messages)
|
|
58
|
+
* @returns {Promise<string>} Resolved environment content
|
|
59
|
+
* @throws {Error} If kv:// reference cannot be resolved
|
|
60
|
+
*/
|
|
61
|
+
async function resolveKvReferences(envTemplate, secrets, environment = 'local', secretsFilePaths = null, appName = null, scopedKv = null) {
|
|
62
|
+
const os = require('os');
|
|
63
|
+
|
|
64
|
+
let developerId = null;
|
|
65
|
+
try {
|
|
66
|
+
developerId = await config.getDeveloperId();
|
|
67
|
+
} catch {
|
|
68
|
+
/* ignore */
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const envKey = String(environment || 'local').toLowerCase();
|
|
72
|
+
const mapContext = envKey === 'docker' || envKey === 'local' ? envKey : 'local';
|
|
73
|
+
|
|
74
|
+
let envVars = await buildEnvVarMap(mapContext, os, developerId);
|
|
75
|
+
if (!envVars || Object.keys(envVars).length === 0) {
|
|
76
|
+
envVars = await buildEnvVarMap('local', os, developerId);
|
|
77
|
+
}
|
|
78
|
+
const resolved = interpolateEnvVars(envTemplate, envVars);
|
|
79
|
+
const missing = collectMissingSecrets(resolved, secrets, scopedKv);
|
|
80
|
+
if (missing.length > 0) {
|
|
81
|
+
const fileInfo = formatMissingSecretsFileInfo(secretsFilePaths);
|
|
82
|
+
const resolveCommand = appName ? `aifabrix resolve ${appName}` : 'aifabrix resolve <app-name>';
|
|
83
|
+
throw new Error(`Missing secrets: ${missing.join(', ')}${fileInfo}\n\nRun "${resolveCommand}" to generate missing secrets.`);
|
|
84
|
+
}
|
|
85
|
+
return replaceKvInContent(resolved, secrets, envVars, scopedKv);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Resolve run env key and effective env-scoped kv/redis behavior for generateEnvContent.
|
|
90
|
+
*
|
|
91
|
+
* @async
|
|
92
|
+
* @param {string} appPath - Builder application directory
|
|
93
|
+
* @param {Object} [options] - generateEnvContent options; may set runEnvKey
|
|
94
|
+
* @returns {Promise<{ runEnvKey: string, effective: boolean }>}
|
|
95
|
+
*/
|
|
96
|
+
async function buildScopedKvContext(appPath, options = {}) {
|
|
97
|
+
let runEnvKey;
|
|
98
|
+
if (options.runEnvKey !== undefined && options.runEnvKey !== null) {
|
|
99
|
+
runEnvKey = String(options.runEnvKey).toLowerCase();
|
|
100
|
+
} else if (typeof config.getCurrentEnvironment === 'function') {
|
|
101
|
+
runEnvKey = String((await config.getCurrentEnvironment()) || 'dev').toLowerCase();
|
|
102
|
+
} else {
|
|
103
|
+
runEnvKey = 'dev';
|
|
104
|
+
}
|
|
105
|
+
const userCfg =
|
|
106
|
+
typeof config.getConfig === 'function'
|
|
107
|
+
? await config.getConfig()
|
|
108
|
+
: { useEnvironmentScopedResources: false };
|
|
109
|
+
const useGate = Boolean(userCfg.useEnvironmentScopedResources);
|
|
110
|
+
const appFlag = readAppEnvironmentScopedFlagForAppPath(appPath);
|
|
111
|
+
const effective = computeEffectiveEnvironmentScopedResources(useGate, appFlag, runEnvKey);
|
|
112
|
+
return { runEnvKey, effective };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Redis/DB service endpoints for docker env interpolation.
|
|
117
|
+
* @returns {Promise<{ redisHost: string, redisPort: number, dbHost: string, dbPort: number }>}
|
|
118
|
+
*/
|
|
119
|
+
async function getDockerRedisDbEndpoints() {
|
|
120
|
+
const { getEnvHosts, getServiceHost, getServicePort, getLocalhostOverride } = require('../utils/env-endpoints');
|
|
121
|
+
const hosts = await getEnvHosts('docker');
|
|
122
|
+
const localhostOverride = getLocalhostOverride('docker');
|
|
123
|
+
const redisHost = getServiceHost(hosts.REDIS_HOST, 'docker', 'redis', localhostOverride);
|
|
124
|
+
const redisPort = await getServicePort('REDIS_PORT', 'redis', hosts, 'docker', null);
|
|
125
|
+
const dbHost = getServiceHost(hosts.DB_HOST, 'docker', 'postgres', localhostOverride);
|
|
126
|
+
const dbPort = await getServicePort('DB_PORT', 'postgres', hosts, 'docker', null);
|
|
127
|
+
return { redisHost, redisPort, dbHost, dbPort };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Config inputs for declarative url:// expansion (keeps expandDeclarativeUrlsIfPresent small).
|
|
132
|
+
* @param {string} appPath
|
|
133
|
+
* @returns {Promise<Object>}
|
|
134
|
+
*/
|
|
135
|
+
async function loadDeclarativeUrlExpandInputs(appPath) {
|
|
136
|
+
const userCfg = await config.getConfig();
|
|
137
|
+
let remoteServer = null;
|
|
138
|
+
try {
|
|
139
|
+
const rs = await config.getRemoteServer();
|
|
140
|
+
if (rs && String(rs).trim()) {
|
|
141
|
+
remoteServer = String(rs).trim();
|
|
142
|
+
}
|
|
143
|
+
} catch {
|
|
144
|
+
remoteServer = null;
|
|
145
|
+
}
|
|
146
|
+
let developerIdRaw = null;
|
|
147
|
+
try {
|
|
148
|
+
developerIdRaw = await config.getDeveloperId();
|
|
149
|
+
} catch {
|
|
150
|
+
developerIdRaw = null;
|
|
151
|
+
}
|
|
152
|
+
let infraTlsEnabled = false;
|
|
153
|
+
try {
|
|
154
|
+
infraTlsEnabled = await config.getTlsEnabled();
|
|
155
|
+
} catch {
|
|
156
|
+
infraTlsEnabled = false;
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
userCfg,
|
|
160
|
+
remoteServer,
|
|
161
|
+
developerIdRaw,
|
|
162
|
+
infraTlsEnabled,
|
|
163
|
+
appScopedFlag: readAppEnvironmentScopedFlagForAppPath(appPath)
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* After kv:// resolution, expand url:// references when application config exists.
|
|
169
|
+
* @param {string} resolved
|
|
170
|
+
* @param {string} appName
|
|
171
|
+
* @param {string} appPath
|
|
172
|
+
* @param {string|null} variablesPath
|
|
173
|
+
* @param {string} environment
|
|
174
|
+
* @param {boolean} envOnly
|
|
175
|
+
* @returns {Promise<string>}
|
|
176
|
+
*/
|
|
177
|
+
async function expandDeclarativeUrlsIfPresent(resolved, appName, appPath, variablesPath, environment, envOnly) {
|
|
178
|
+
if (envOnly || !variablesPath) {
|
|
179
|
+
return resolved;
|
|
180
|
+
}
|
|
181
|
+
const { userCfg, remoteServer, developerIdRaw, infraTlsEnabled, appScopedFlag } =
|
|
182
|
+
await loadDeclarativeUrlExpandInputs(appPath);
|
|
183
|
+
resolved = rewriteInactiveDeclarativeVdirPublicContent(resolved, variablesPath, userCfg);
|
|
184
|
+
if (!resolved.includes('url://')) {
|
|
185
|
+
return resolved;
|
|
186
|
+
}
|
|
187
|
+
return expandDeclarativeUrlsInEnvContent(resolved, {
|
|
188
|
+
profile: environment === 'docker' ? 'docker' : 'local',
|
|
189
|
+
currentAppKey: appName,
|
|
190
|
+
variablesPath,
|
|
191
|
+
useEnvironmentScopedResources: Boolean(userCfg.useEnvironmentScopedResources),
|
|
192
|
+
appEnvironmentScopedResources: appScopedFlag,
|
|
193
|
+
remoteServer,
|
|
194
|
+
developerIdRaw,
|
|
195
|
+
traefik: Boolean(userCfg.traefik),
|
|
196
|
+
infraTlsEnabled
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Docker env transformations: ports, infra endpoints, PORT. */
|
|
201
|
+
async function applyDockerTransformations(resolved, variablesPath) {
|
|
202
|
+
resolved = await resolveServicePortsInEnvContent(resolved, 'docker');
|
|
203
|
+
resolved = await rewriteInfraEndpoints(resolved, 'docker');
|
|
204
|
+
const { redisHost, redisPort, dbHost, dbPort } = await getDockerRedisDbEndpoints();
|
|
205
|
+
let dockerEnv = await getBaseDockerEnv();
|
|
206
|
+
dockerEnv = applyDockerEnvOverride(dockerEnv);
|
|
207
|
+
const containerPort = getContainerPortFromPath(variablesPath) ?? getContainerPortFromDockerEnv(dockerEnv) ?? 3000;
|
|
208
|
+
const envVars = await buildEnvVarMap('docker', null, null, { appPort: containerPort });
|
|
209
|
+
const appDoc = loadVariablesFromPath(variablesPath);
|
|
210
|
+
await mergeDockerManifestPublishedPort(envVars, appDoc);
|
|
211
|
+
envVars.REDIS_HOST = redisHost;
|
|
212
|
+
envVars.REDIS_PORT = String(redisPort);
|
|
213
|
+
envVars.DB_HOST = dbHost;
|
|
214
|
+
envVars.DB_PORT = String(dbPort);
|
|
215
|
+
envVars.PORT = String(containerPort);
|
|
216
|
+
resolved = interpolateEnvVars(resolved, envVars);
|
|
217
|
+
resolved = rewriteDockerManifestPublicPortEnvLine(resolved, envVars, appDoc);
|
|
218
|
+
return updatePortForDocker(resolved, variablesPath);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Environment-specific transformations to resolved content. */
|
|
222
|
+
async function applyEnvironmentTransformations(resolved, environment, variablesPath) {
|
|
223
|
+
if (environment === 'docker') return applyDockerTransformations(resolved, variablesPath);
|
|
224
|
+
if (environment === 'local') return adjustLocalEnvPortsInContent(resolved, variablesPath);
|
|
225
|
+
return resolved;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Generate .env content from template and secrets (no disk write).
|
|
230
|
+
* When options.envOnly is true, variablesPath is null (no application config).
|
|
231
|
+
*
|
|
232
|
+
* @param {string} appName - Application name
|
|
233
|
+
* @param {string} [secretsPath] - Path to secrets file (optional)
|
|
234
|
+
* @param {string} [environment='local'] - Environment context
|
|
235
|
+
* @param {boolean} [force=false] - Generate missing secret keys
|
|
236
|
+
* @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).
|
|
237
|
+
* @returns {Promise<string>} Resolved env content
|
|
238
|
+
*/
|
|
239
|
+
async function generateEnvContent(appName, secretsPath, environment = 'local', force = false, options = {}) {
|
|
240
|
+
const appPath = (options && options.appPath) || pathsUtil.getBuilderPath(appName);
|
|
241
|
+
const templatePath = path.join(appPath, 'env.template');
|
|
242
|
+
const variablesPath = (options && options.envOnly) ? null : resolveApplicationConfigPath(appPath);
|
|
243
|
+
const template = loadEnvTemplate(templatePath);
|
|
244
|
+
const secretsPaths = await getActualSecretsPath(secretsPath, appName);
|
|
245
|
+
if (force) {
|
|
246
|
+
const preferredPath = secretsPath ? resolveSecretsPath(secretsPath) : secretsPaths.userPath;
|
|
247
|
+
await secretsEnsure.ensureSecretsFromEnvTemplate(templatePath, { preferredFilePath: preferredPath });
|
|
248
|
+
}
|
|
249
|
+
const secrets = await loadSecrets(secretsPath, appName);
|
|
250
|
+
const { runEnvKey, effective } = await buildScopedKvContext(appPath, options);
|
|
251
|
+
const scopedKv = { envKey: runEnvKey, effective };
|
|
252
|
+
let resolved = await resolveKvReferences(template, secrets, environment, secretsPaths, appName, scopedKv);
|
|
253
|
+
if (!secretsPath) {
|
|
254
|
+
await materializeResolvedKvSecretsToUserLocal(template, secrets, scopedKv, options);
|
|
255
|
+
}
|
|
256
|
+
resolved = await expandDeclarativeUrlsIfPresent(
|
|
257
|
+
resolved,
|
|
258
|
+
appName,
|
|
259
|
+
appPath,
|
|
260
|
+
variablesPath,
|
|
261
|
+
environment,
|
|
262
|
+
Boolean(options.envOnly)
|
|
263
|
+
);
|
|
264
|
+
resolved = await applyEnvironmentTransformations(resolved, environment, variablesPath);
|
|
265
|
+
if (effective) {
|
|
266
|
+
const idx = redisDbIndexForScopedRunEnv(runEnvKey);
|
|
267
|
+
resolved = applyRedisDbIndexToEnvContent(resolved, idx);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return resolved;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Parses .env file content into a key-to-value map.
|
|
275
|
+
* Only includes lines that look like KEY=value (first = separates key and value).
|
|
276
|
+
*
|
|
277
|
+
* @param {string} content - Raw .env file content
|
|
278
|
+
* @returns {Object.<string, string>} Map of variable name to value
|
|
279
|
+
*/
|
|
280
|
+
function parseEnvContentToMap(content) {
|
|
281
|
+
if (!content || typeof content !== 'string') {
|
|
282
|
+
return {};
|
|
283
|
+
}
|
|
284
|
+
const map = {};
|
|
285
|
+
const lines = content.split(/\r?\n/);
|
|
286
|
+
for (const line of lines) {
|
|
287
|
+
const trimmed = line.trim();
|
|
288
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
const eq = trimmed.indexOf('=');
|
|
292
|
+
if (eq > 0) {
|
|
293
|
+
const key = trimmed.substring(0, eq).trim();
|
|
294
|
+
const value = trimmed.substring(eq + 1);
|
|
295
|
+
map[key] = value;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return map;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Merges new .env content with existing .env: newly resolved content wins for keys it
|
|
303
|
+
* defines (so project secrets take effect when re-running). Keys only in existing .env
|
|
304
|
+
* are appended so manual additions are kept.
|
|
305
|
+
*
|
|
306
|
+
* @param {string} newContent - Newly generated .env content (from template + loadSecrets)
|
|
307
|
+
* @param {Object.<string, string>} existingMap - Existing key-to-value map from current .env
|
|
308
|
+
* @returns {string} Merged content: new values for keys in newContent, plus extra existing keys
|
|
309
|
+
*/
|
|
310
|
+
function mergeEnvContentPreservingExisting(newContent, existingMap) {
|
|
311
|
+
const lines = newContent.split(/\r?\n/);
|
|
312
|
+
const newKeys = new Set();
|
|
313
|
+
const out = [];
|
|
314
|
+
for (const line of lines) {
|
|
315
|
+
const trimmed = line.trim();
|
|
316
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
317
|
+
out.push(line);
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
const eq = trimmed.indexOf('=');
|
|
321
|
+
if (eq > 0) {
|
|
322
|
+
const key = trimmed.substring(0, eq).trim();
|
|
323
|
+
newKeys.add(key);
|
|
324
|
+
}
|
|
325
|
+
out.push(line);
|
|
326
|
+
}
|
|
327
|
+
if (existingMap && Object.keys(existingMap).length > 0) {
|
|
328
|
+
for (const key of Object.keys(existingMap)) {
|
|
329
|
+
if (!newKeys.has(key)) {
|
|
330
|
+
out.push(`${key}=${existingMap[key]}`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return out.join('\n');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Merges a key-value map into existing .env file content, preserving comments and blank lines.
|
|
339
|
+
* For each KEY=value line in existing content, replaces value with newMap[KEY] when the key exists
|
|
340
|
+
* in newMap. Appends any keys from newMap that did not appear in the file.
|
|
341
|
+
*
|
|
342
|
+
* @param {string} existingContent - Full existing .env file content
|
|
343
|
+
* @param {Object.<string, string>} newMap - New key-to-value map (e.g. from resolved or run env)
|
|
344
|
+
* @returns {string} Merged content with comments preserved
|
|
345
|
+
*/
|
|
346
|
+
function mergeEnvMapIntoContent(existingContent, newMap) {
|
|
347
|
+
if (!newMap || Object.keys(newMap).length === 0) {
|
|
348
|
+
return typeof existingContent === 'string' ? existingContent : '';
|
|
349
|
+
}
|
|
350
|
+
const lines = (existingContent || '').split(/\r?\n/);
|
|
351
|
+
const seen = new Set();
|
|
352
|
+
const out = [];
|
|
353
|
+
for (const line of lines) {
|
|
354
|
+
const trimmed = line.trim();
|
|
355
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
356
|
+
out.push(line);
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
const eq = trimmed.indexOf('=');
|
|
360
|
+
if (eq > 0) {
|
|
361
|
+
const key = trimmed.substring(0, eq).trim();
|
|
362
|
+
seen.add(key);
|
|
363
|
+
out.push(Object.prototype.hasOwnProperty.call(newMap, key) ? `${key}=${newMap[key]}` : line);
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
out.push(line);
|
|
367
|
+
}
|
|
368
|
+
for (const key of Object.keys(newMap)) {
|
|
369
|
+
if (!seen.has(key)) out.push(`${key}=${newMap[key]}`);
|
|
370
|
+
}
|
|
371
|
+
return out.join('\n');
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Resolves content to write for .env: merges with existing file when present.
|
|
376
|
+
* @param {string} resolved - Newly generated content
|
|
377
|
+
* @param {string} pathToPreserve - Path to existing .env to merge from (or null)
|
|
378
|
+
* @returns {string} Content to write
|
|
379
|
+
*/
|
|
380
|
+
function resolveEnvContentToWrite(resolved, pathToPreserve) {
|
|
381
|
+
if (!pathToPreserve || !fs.existsSync(pathToPreserve)) return resolved;
|
|
382
|
+
const existingContent = fs.readFileSync(pathToPreserve, 'utf8');
|
|
383
|
+
const existingMap = parseEnvContentToMap(existingContent);
|
|
384
|
+
return mergeEnvContentPreservingExisting(resolved, existingMap);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Generates and writes .env file. Newly resolved values win over existing .env; extra vars in existing .env are kept.
|
|
389
|
+
* When options.envOnly is true, only env.template is used; .env is written to options.appPath.
|
|
390
|
+
* @async
|
|
391
|
+
* @param {string} appName - Name of the application
|
|
392
|
+
* @param {string} [secretsPath] - Path to secrets file (optional)
|
|
393
|
+
* @param {string} [environment='local'] - Environment context ('local' or 'docker')
|
|
394
|
+
* @param {boolean} [force=false] - Generate missing secret keys in secrets file
|
|
395
|
+
* @param {Object} [options] - Optional: appPath, envOnly, skipOutputPath, preserveFromPath
|
|
396
|
+
* @returns {Promise<string>} Path to generated .env file
|
|
397
|
+
*/
|
|
398
|
+
async function generateEnvFile(appName, secretsPath, environment = 'local', force = false, options = {}) {
|
|
399
|
+
const opts = options && typeof options === 'object' ? options : {};
|
|
400
|
+
const appPath = opts.appPath || pathsUtil.getBuilderPath(appName);
|
|
401
|
+
const envOnly = !!opts.envOnly;
|
|
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
|
+
const resolved = await generateEnvContent(appName, secretsPath, environment, force, { appPath, envOnly });
|
|
413
|
+
const preservePath = opts.preserveFromPath !== undefined && opts.preserveFromPath !== null ? opts.preserveFromPath : null;
|
|
414
|
+
const pathToPreserve = preservePath !== null ? preservePath : envPath;
|
|
415
|
+
const toWrite = resolveEnvContentToWrite(resolved, pathToPreserve);
|
|
416
|
+
fs.writeFileSync(envPath, toWrite, { mode: 0o600 });
|
|
417
|
+
|
|
418
|
+
if (!opts.skipOutputPath) {
|
|
419
|
+
const { processEnvVariables } = require('../utils/env-copy');
|
|
420
|
+
await processEnvVariables(envPath, variablesPath, appName, secretsPath);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return envPath;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
module.exports = {
|
|
427
|
+
resolveKvReferences,
|
|
428
|
+
generateEnvContent,
|
|
429
|
+
generateEnvFile,
|
|
430
|
+
parseEnvContentToMap,
|
|
431
|
+
mergeEnvMapIntoContent
|
|
432
|
+
};
|
|
@@ -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,7 @@ 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.
|
|
127
151
|
*
|
|
128
152
|
* @async
|
|
129
153
|
* @function resolveAndWriteEnvFile
|
|
@@ -145,6 +169,7 @@ async function resolveAndWriteEnvFile(appName, options = {}) {
|
|
|
145
169
|
|
|
146
170
|
let resolved = await secrets.generateEnvContent(appName, secretsPath, environment, force);
|
|
147
171
|
resolved = await injectRegistryTokens(resolved, secretsPath, appName);
|
|
172
|
+
resolved = await injectBashPrefixedExportLines(resolved, secretsPath, appName);
|
|
148
173
|
|
|
149
174
|
if (envOutputPath && typeof envOutputPath === 'string') {
|
|
150
175
|
const dir = path.dirname(envOutputPath);
|
|
@@ -179,6 +204,7 @@ async function resolveAndGetEnvMap(appName, options = {}) {
|
|
|
179
204
|
const force = options.force === true;
|
|
180
205
|
let content = await secrets.generateEnvContent(appName, secretsPath, environment, force);
|
|
181
206
|
content = await injectRegistryTokens(content, secretsPath, appName);
|
|
207
|
+
content = await injectBashPrefixedExportLines(content, secretsPath, appName);
|
|
182
208
|
return parseEnvContentToMap(content);
|
|
183
209
|
}
|
|
184
210
|
|