@aifabrix/builder 2.44.4 → 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.
Files changed (214) hide show
  1. package/.cursor/rules/cli-layout.mdc +1 -1
  2. package/.cursor/rules/project-rules.mdc +1 -1
  3. package/.npmrc.token +1 -1
  4. package/README.md +15 -23
  5. package/integration/hubspot-test/README.md +2 -0
  6. package/integration/hubspot-test/test.js +5 -3
  7. package/jest.projects.js +68 -17
  8. package/lib/api/controller-health.api.js +49 -0
  9. package/lib/api/dimension-values.api.js +82 -0
  10. package/lib/api/dimensions.api.js +114 -0
  11. package/lib/api/external-systems.api.js +1 -0
  12. package/lib/api/integration-clients.api.js +168 -0
  13. package/lib/api/types/dimension-values.types.js +28 -0
  14. package/lib/api/types/dimensions.types.js +31 -0
  15. package/lib/api/types/integration-clients.types.js +45 -0
  16. package/lib/api/types/wizard.types.js +2 -1
  17. package/lib/api/validation-runner.js +46 -25
  18. package/lib/app/deploy-config.js +11 -1
  19. package/lib/app/deploy-status-display.js +3 -3
  20. package/lib/app/deploy.js +36 -14
  21. package/lib/app/display.js +15 -11
  22. package/lib/app/push.js +46 -23
  23. package/lib/app/register.js +1 -1
  24. package/lib/app/restart-display.js +95 -0
  25. package/lib/app/rotate-secret.js +1 -1
  26. package/lib/app/run-container-start.js +12 -6
  27. package/lib/app/run-env-compose.js +30 -1
  28. package/lib/app/run-helpers.js +44 -12
  29. package/lib/app/run-reload-sync.js +148 -0
  30. package/lib/app/run-resolve-image.js +51 -1
  31. package/lib/app/run.js +99 -73
  32. package/lib/build/index.js +75 -45
  33. package/lib/cli/doctor-check.js +117 -0
  34. package/lib/cli/index.js +8 -2
  35. package/lib/cli/infra-guided.js +445 -0
  36. package/lib/cli/setup-app.help.js +1 -1
  37. package/lib/cli/setup-app.js +20 -2
  38. package/lib/cli/setup-app.test-commands.js +9 -5
  39. package/lib/cli/setup-auth.js +26 -0
  40. package/lib/cli/setup-dev-path-commands.js +50 -3
  41. package/lib/cli/setup-infra.js +138 -61
  42. package/lib/cli/setup-integration-client.js +182 -0
  43. package/lib/cli/setup-parameters.js +21 -2
  44. package/lib/cli/setup-platform.js +102 -0
  45. package/lib/cli/setup-secrets.js +18 -6
  46. package/lib/cli/setup-utility.js +97 -33
  47. package/lib/commands/datasource-capability-dimension-cli.js +128 -0
  48. package/lib/commands/datasource-capability-output.js +29 -0
  49. package/lib/commands/datasource-capability-relate-cli.js +140 -0
  50. package/lib/commands/datasource-capability.js +411 -0
  51. package/lib/commands/datasource-unified-test-cli.options.js +1 -1
  52. package/lib/commands/datasource.js +53 -13
  53. package/lib/commands/dev-down.js +3 -3
  54. package/lib/commands/dev-infra-gate.js +32 -0
  55. package/lib/commands/dev-init.js +13 -7
  56. package/lib/commands/dimension-value.js +179 -0
  57. package/lib/commands/dimension.js +330 -0
  58. package/lib/commands/integration-client.js +430 -0
  59. package/lib/commands/login-device.js +65 -30
  60. package/lib/commands/login.js +21 -10
  61. package/lib/commands/parameters-validate.js +78 -13
  62. package/lib/commands/repair-datasource-auto-rbac.js +166 -0
  63. package/lib/commands/repair-datasource-keys.js +10 -5
  64. package/lib/commands/repair-datasource.js +19 -7
  65. package/lib/commands/repair-env-template.js +4 -1
  66. package/lib/commands/repair-openapi-sync.js +172 -0
  67. package/lib/commands/repair-persist.js +102 -0
  68. package/lib/commands/repair-rbac-extract.js +27 -0
  69. package/lib/commands/repair-rbac-migrate.js +186 -0
  70. package/lib/commands/repair-rbac.js +225 -19
  71. package/lib/commands/repair-system-alignment.js +246 -0
  72. package/lib/commands/repair-system-permissions.js +168 -0
  73. package/lib/commands/repair.js +120 -354
  74. package/lib/commands/secure.js +1 -1
  75. package/lib/commands/setup-modes.js +455 -0
  76. package/lib/commands/setup-prompts.js +388 -0
  77. package/lib/commands/setup.js +149 -0
  78. package/lib/commands/teardown.js +228 -0
  79. package/lib/commands/test-e2e-external.js +4 -3
  80. package/lib/commands/up-common.js +97 -12
  81. package/lib/commands/up-dataplane.js +33 -11
  82. package/lib/commands/up-miso.js +7 -11
  83. package/lib/commands/upload.js +109 -23
  84. package/lib/commands/wizard-core-helpers.js +14 -11
  85. package/lib/commands/wizard-core.js +58 -15
  86. package/lib/commands/wizard-dataplane.js +2 -2
  87. package/lib/commands/wizard-entity-selection.js +72 -14
  88. package/lib/commands/wizard-headless.js +7 -3
  89. package/lib/commands/wizard-helpers.js +13 -1
  90. package/lib/commands/wizard.js +210 -61
  91. package/lib/constants/infra-compose-service-names.js +40 -0
  92. package/lib/core/env-reader.js +16 -3
  93. package/lib/core/secrets-admin-env.js +101 -0
  94. package/lib/core/secrets-ensure-infra.js +34 -1
  95. package/lib/core/secrets-ensure.js +88 -66
  96. package/lib/core/secrets-env-content.js +432 -0
  97. package/lib/core/secrets-env-write.js +27 -1
  98. package/lib/core/secrets-load.js +248 -0
  99. package/lib/core/secrets-names.js +32 -0
  100. package/lib/core/secrets.js +17 -757
  101. package/lib/datasource/capability/basic-exposure.js +76 -0
  102. package/lib/datasource/capability/capability-diff-slice.js +41 -0
  103. package/lib/datasource/capability/capability-key.js +34 -0
  104. package/lib/datasource/capability/capability-resolve.js +172 -0
  105. package/lib/datasource/capability/capability-storage-keys.js +22 -0
  106. package/lib/datasource/capability/copy-operations.js +348 -0
  107. package/lib/datasource/capability/copy-test-payload.js +139 -0
  108. package/lib/datasource/capability/create-operations.js +235 -0
  109. package/lib/datasource/capability/dimension-operations.js +151 -0
  110. package/lib/datasource/capability/dimension-validate.js +219 -0
  111. package/lib/datasource/capability/json-pointer.js +31 -0
  112. package/lib/datasource/capability/reference-rewrite.js +51 -0
  113. package/lib/datasource/capability/relate-operations.js +325 -0
  114. package/lib/datasource/capability/relate-validate.js +219 -0
  115. package/lib/datasource/capability/remove-operations.js +275 -0
  116. package/lib/datasource/capability/run-capability-copy.js +152 -0
  117. package/lib/datasource/capability/run-capability-diff.js +135 -0
  118. package/lib/datasource/capability/run-capability-dimension.js +291 -0
  119. package/lib/datasource/capability/run-capability-edit.js +377 -0
  120. package/lib/datasource/capability/run-capability-relate.js +193 -0
  121. package/lib/datasource/capability/run-capability-remove.js +105 -0
  122. package/lib/datasource/capability/templates/minimal-fetch.json +18 -0
  123. package/lib/datasource/capability/validate-capability-slice.js +35 -0
  124. package/lib/datasource/list.js +136 -23
  125. package/lib/datasource/log-viewer.js +2 -4
  126. package/lib/datasource/unified-validation-run.js +51 -16
  127. package/lib/datasource/validate.js +53 -1
  128. package/lib/deployment/deploy-poll-ui.js +60 -0
  129. package/lib/deployment/deployer-status.js +29 -3
  130. package/lib/deployment/deployer.js +48 -30
  131. package/lib/deployment/environment.js +7 -2
  132. package/lib/deployment/poll-interval.js +72 -0
  133. package/lib/deployment/push.js +11 -9
  134. package/lib/external-system/deploy.js +4 -1
  135. package/lib/external-system/download.js +61 -32
  136. package/lib/external-system/sync-deploy-manifest.js +33 -0
  137. package/lib/generator/wizard-prompts.js +7 -1
  138. package/lib/generator/wizard.js +34 -0
  139. package/lib/infrastructure/index.js +49 -19
  140. package/lib/infrastructure/orphan-infra-docker-teardown.js +177 -0
  141. package/lib/parameters/infra-kv-discovery.js +29 -4
  142. package/lib/parameters/infra-parameter-catalog.js +6 -3
  143. package/lib/parameters/infra-parameter-validate.js +67 -19
  144. package/lib/resolvers/datasource-resolver.js +53 -0
  145. package/lib/resolvers/dimension-file.js +52 -0
  146. package/lib/resolvers/manifest-resolver.js +133 -0
  147. package/lib/schema/external-datasource.schema.json +183 -53
  148. package/lib/schema/external-system.schema.json +23 -10
  149. package/lib/schema/infra.parameter.yaml +26 -11
  150. package/lib/schema/wizard-config.schema.json +2 -2
  151. package/lib/utils/aifabrix-config-dir-walk.js +40 -0
  152. package/lib/utils/aifabrix-runtime-config-dir.js +26 -3
  153. package/lib/utils/app-run-containers.js +2 -2
  154. package/lib/utils/bash-secret-env.js +59 -0
  155. package/lib/utils/cli-secrets-error-format.js +78 -0
  156. package/lib/utils/cli-test-layout-chalk.js +31 -9
  157. package/lib/utils/cli-utils.js +4 -36
  158. package/lib/utils/datasource-test-run-display.js +8 -0
  159. package/lib/utils/dev-hosts-helper.js +3 -2
  160. package/lib/utils/dev-init-ssh-merge.js +2 -1
  161. package/lib/utils/docker-build.js +17 -9
  162. package/lib/utils/docker-reload-mount.js +127 -0
  163. package/lib/utils/external-readme.js +117 -4
  164. package/lib/utils/external-system-local-test-tty.js +3 -2
  165. package/lib/utils/external-system-readiness-core.js +45 -12
  166. package/lib/utils/external-system-readiness-deploy-display.js +3 -3
  167. package/lib/utils/external-system-readiness-display-internals.js +33 -3
  168. package/lib/utils/external-system-readiness-display.js +10 -1
  169. package/lib/utils/file-upload.js +40 -3
  170. package/lib/utils/health-check-db-init.js +107 -0
  171. package/lib/utils/health-check-public-warn.js +69 -0
  172. package/lib/utils/health-check-url.js +19 -4
  173. package/lib/utils/health-check.js +135 -105
  174. package/lib/utils/help-builder.js +5 -1
  175. package/lib/utils/image-name.js +34 -7
  176. package/lib/utils/integration-file-backup.js +74 -0
  177. package/lib/utils/mutagen-install.js +30 -3
  178. package/lib/utils/paths.js +108 -25
  179. package/lib/utils/postgres-wipe.js +212 -0
  180. package/lib/utils/register-aifabrix-shell-env.js +15 -0
  181. package/lib/utils/remote-dev-auth.js +21 -5
  182. package/lib/utils/remote-docker-env.js +9 -1
  183. package/lib/utils/remote-secrets-loader.js +42 -3
  184. package/lib/utils/resolve-docker-image-ref.js +9 -3
  185. package/lib/utils/secrets-ancestor-paths.js +47 -0
  186. package/lib/utils/secrets-helpers.js +17 -10
  187. package/lib/utils/secrets-kv-refs.js +42 -0
  188. package/lib/utils/secrets-kv-scope.js +19 -2
  189. package/lib/utils/secrets-materialize-local.js +134 -0
  190. package/lib/utils/secrets-path.js +24 -10
  191. package/lib/utils/secrets-utils.js +2 -2
  192. package/lib/utils/system-builder-root.js +34 -0
  193. package/lib/utils/url-declarative-resolve-build.js +6 -1
  194. package/lib/utils/url-declarative-runtime-base-path.js +32 -0
  195. package/lib/utils/url-declarative-vdir-inactive-env.js +2 -1
  196. package/lib/utils/urls-local-registry.js +73 -20
  197. package/lib/utils/validation-poll-ui.js +81 -0
  198. package/lib/utils/validation-run-poll.js +29 -5
  199. package/lib/utils/with-muted-logger.js +53 -0
  200. package/package.json +1 -1
  201. package/templates/applications/dataplane/application.yaml +1 -1
  202. package/templates/applications/dataplane/rbac.yaml +10 -10
  203. package/templates/applications/keycloak/env.template +8 -6
  204. package/templates/applications/miso-controller/application.yaml +7 -0
  205. package/templates/applications/miso-controller/env.template +7 -7
  206. package/templates/applications/miso-controller/rbac.yaml +9 -9
  207. package/templates/external-system/README.md.hbs +89 -102
  208. package/.nyc_output/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  209. package/.nyc_output/processinfo/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  210. package/.nyc_output/processinfo/index.json +0 -1
  211. package/lib/api/service-users.api.js +0 -150
  212. package/lib/api/types/service-users.types.js +0 -65
  213. package/lib/cli/setup-service-user.js +0 -187
  214. package/lib/commands/service-user.js +0 -429
@@ -4,774 +4,34 @@
4
4
  * This module handles secret resolution and environment file generation.
5
5
  * Resolves kv:// references from secrets files and generates .env files.
6
6
  *
7
+ * Implementation is split across secrets-load.js, secrets-env-content.js, secrets-admin-env.js, secrets-names.js.
8
+ *
7
9
  * @fileoverview Secret resolution and environment management for AI Fabrix Builder
8
10
  * @author AI Fabrix Team
9
11
  * @version 2.0.0
10
12
  */
11
- /* eslint-disable max-lines -- Central module; env-only resolve (plan 75) added required options; extract to env-merge would touch multiple callers. */
12
- const fs = require('fs');
13
- const path = require('path');
14
- const logger = require('../utils/logger');
15
- const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
16
- const config = require('./config');
17
- const {
18
- interpolateEnvVars,
19
- collectMissingSecrets,
20
- formatMissingSecretsFileInfo,
21
- replaceKvInContent,
22
- loadEnvTemplate,
23
- adjustLocalEnvPortsInContent,
24
- rewriteInfraEndpoints,
25
- readYamlAtPath,
26
- applyCanonicalSecretsOverride,
27
- validateSecrets
28
- } = require('../utils/secrets-helpers');
29
- const { buildEnvVarMap } = require('../utils/env-map');
30
- const {
31
- mergeInfraParameterDefaultsForCli,
32
- getInfraParameterCatalog,
33
- readRelaxedCatalogDefaults
34
- } = require('../parameters/infra-parameter-catalog');
35
- const { resolveServicePortsInEnvContent } = require('../utils/secrets-url');
36
- const {
37
- updatePortForDocker,
38
- getBaseDockerEnv,
39
- applyDockerEnvOverride,
40
- getContainerPortFromDockerEnv
41
- } = require('./secrets-docker-env');
42
- const { getContainerPortFromPath, loadVariablesFromPath } = require('../utils/port-resolver');
13
+
14
+ 'use strict';
15
+
16
+ const { validateSecrets } = require('../utils/secrets-helpers');
43
17
  const {
44
18
  generateMissingSecrets,
45
19
  createDefaultSecrets
46
20
  } = require('../utils/secrets-generator');
47
- const secretsEnsure = require('./secrets-ensure');
48
- const {
49
- resolveSecretsPath,
50
- getActualSecretsPath
51
- } = require('../utils/secrets-path');
21
+ const { loadSecrets } = require('./secrets-load');
52
22
  const {
53
- loadUserSecrets,
54
- loadPrimaryUserSecrets,
55
- loadDefaultSecrets,
56
- ensurePrimaryUserSecretsFileExists
57
- } = require('../utils/secrets-utils');
58
- const { decryptSecret, isEncrypted } = require('../utils/secrets-encryption');
59
- const pathsUtil = require('../utils/paths');
60
- const { ensureSecureFilePermissions } = require('../utils/secure-file-permissions');
61
- const { readAppEnvironmentScopedFlagForAppPath } = require('../utils/app-scoped-config');
62
- const { computeEffectiveEnvironmentScopedResources, redisDbIndexForScopedRunEnv } = require('../utils/environment-scoped-resources');
63
- const { applyRedisDbIndexToEnvContent } = require('../utils/redis-env-scope');
64
- const { expandDeclarativeUrlsInEnvContent } = require('../utils/url-declarative-resolve');
65
- const { rewriteInactiveDeclarativeVdirPublicContent } = require('../utils/url-declarative-vdir-inactive-env');
23
+ resolveKvReferences,
24
+ generateEnvContent,
25
+ generateEnvFile,
26
+ parseEnvContentToMap,
27
+ mergeEnvMapIntoContent
28
+ } = require('./secrets-env-content');
66
29
  const {
67
- mergeDockerManifestPublishedPort,
68
- rewriteDockerManifestPublicPortEnvLine
69
- } = require('../utils/docker-manifest-public-port');
70
-
71
- /**
72
- * Generates a canonical secret name from an environment variable key.
73
- * Converts to lowercase, replaces non-alphanumeric characters with hyphens,
74
- * collapses consecutive hyphens, and trims leading/trailing hyphens.
75
- *
76
- * @function getCanonicalSecretName
77
- * @param {string} key - Environment variable key (e.g., JWT_SECRET)
78
- * @returns {string} Canonical secret name (e.g., jwt-secret)
79
- */
80
- function getCanonicalSecretName(key) {
81
- if (!key || typeof key !== 'string') {
82
- return '';
83
- }
84
- // Insert hyphens before capital letters (camelCase -> kebab-case)
85
- // Then convert to lowercase and replace non-alphanumeric with hyphens
86
- const withHyphens = key.replace(/([a-z0-9])([A-Z])/g, '$1-$2');
87
- const lower = withHyphens.toLowerCase();
88
- const hyphenated = lower.replace(/[^a-z0-9]/g, '-');
89
- const collapsed = hyphenated.replace(/-+/g, '-');
90
- return collapsed.replace(/^-+|-+$/g, '');
91
- }
92
-
93
- /**
94
- * Decrypts encrypted values in secrets object
95
- * Checks for secure:// prefix and decrypts using encryption key from config
96
- *
97
- * @async
98
- * @function decryptSecretsObject
99
- * @param {Object} secrets - Secrets object with potentially encrypted values
100
- * @returns {Promise<Object>} Secrets object with decrypted values
101
- * @throws {Error} If decryption fails or encryption key is missing
102
- */
103
- async function decryptSecretsObject(secrets) {
104
- if (!secrets || typeof secrets !== 'object') {
105
- return secrets;
106
- }
107
-
108
- const encryptionKey = await config.getSecretsEncryptionKey();
109
- if (!encryptionKey) {
110
- // No encryption key set, check if any values are encrypted
111
- const hasEncrypted = Object.values(secrets).some(value => isEncrypted(value));
112
- if (hasEncrypted) {
113
- throw new Error('Encrypted secrets found but no encryption key configured. Run "aifabrix secure --secrets-encryption <key>" to set encryption key.');
114
- }
115
- // No encrypted values, return as-is
116
- return secrets;
117
- }
118
-
119
- const decryptedSecrets = {};
120
- for (const [key, value] of Object.entries(secrets)) {
121
- if (isEncrypted(value)) {
122
- try {
123
- decryptedSecrets[key] = decryptSecret(value, encryptionKey);
124
- } catch (error) {
125
- throw new Error(`Failed to decrypt secret '${key}': ${error.message}`);
126
- }
127
- } else {
128
- decryptedSecrets[key] = value;
129
- }
130
- }
131
-
132
- return decryptedSecrets;
133
- }
134
-
135
- /**
136
- * Loads secrets with cascading lookup
137
- * Supports both user secrets (~/.aifabrix/secrets.local.yaml) and project overrides
138
- * When aifabrix-secrets (or secrets-path) is set in config.yaml and that file exists, it is used as base; user's file (local) is strongest and overrides project for same key. Otherwise user's file first, then aifabrix-secrets as fallback.
139
- * Automatically decrypts values with secure:// prefix
140
- *
141
- * @async
142
- * @function loadSecrets
143
- * @param {string} [secretsPath] - Path to secrets file (optional, for explicit override)
144
- * @param {string} [appName] - Application name (optional, for backward compatibility)
145
- * @returns {Promise<Object>} Loaded secrets object with decrypted values (may be empty after bootstrap file create)
146
- * @throws {Error} If explicit secretsPath is set but file is missing or invalid
147
- *
148
- * @example
149
- * const secrets = await loadSecrets('../../secrets.local.yaml');
150
- * // Returns: { 'postgres-passwordKeyVault': 'admin123', ... }
151
- *
152
- * @example
153
- * // When config.yaml has aifabrix-secrets: ./secrets.local.yaml, project file is base;
154
- * // ~/.aifabrix/secrets.local.yaml overrides project for same key (local strongest).
155
- * const secrets = await loadSecrets(undefined, 'myapp');
156
- */
157
-
158
- /**
159
- * Merges config file secrets into user secrets (user wins). Returns null if path missing or config empty.
160
- * @param {Object} userSecrets - User secrets object
161
- * @param {string} resolvedConfigPath - Absolute path to config secrets file
162
- * @returns {Object|null} Merged secrets or null
163
- */
164
- function mergeUserWithConfigFile(userSecrets, resolvedConfigPath) {
165
- if (!fs.existsSync(resolvedConfigPath)) {
166
- return null;
167
- }
168
- ensureSecureFilePermissions(resolvedConfigPath);
169
- let configSecrets;
170
- try {
171
- configSecrets = readYamlAtPath(resolvedConfigPath);
172
- } catch (loadError) {
173
- throw new Error(`Failed to load secrets file ${resolvedConfigPath}: ${loadError.message}`);
174
- }
175
- if (!configSecrets || typeof configSecrets !== 'object') {
176
- return null;
177
- }
178
- const merged = { ...userSecrets };
179
- for (const key of Object.keys(configSecrets)) {
180
- if (!(key in merged) || merged[key] === undefined || merged[key] === null || merged[key] === '') {
181
- merged[key] = configSecrets[key];
182
- }
183
- }
184
- return merged;
185
- }
186
-
187
- /**
188
- * Loads config secrets path, merges with user secrets (user/master wins, public fills missing).
189
- * User/master = getPrimaryUserSecretsLocalPath() (config dir; same file as loadPrimaryUserSecrets).
190
- * Public = aifabrix-secrets path from config. Used by loadSecrets cascade.
191
- * When the effective shared-secrets target (after remote-dev resolution) is an http(s) URL,
192
- * fetches shared secrets from API (never persisted to disk).
193
- *
194
- * @async
195
- * @returns {Promise<Object|null>} Merged secrets object or null
196
- */
197
- async function loadMergedConfigAndUserSecrets() {
198
- const { loadRemoteSharedSecrets, mergeUserWithRemoteSecrets } = require('../utils/remote-secrets-loader');
199
- const remoteDevAuth = require('../utils/remote-dev-auth');
200
- const userSecrets = loadPrimaryUserSecrets();
201
- const hasKeys = (obj) => obj && Object.keys(obj).length > 0;
202
- const userOrNull = () => (hasKeys(userSecrets) ? userSecrets : null);
203
- try {
204
- const configSecretsPath = await config.getSecretsPath();
205
- if (!configSecretsPath) {
206
- return userOrNull();
207
- }
208
- const effectiveShared = await remoteDevAuth.resolveSharedSecretsEndpoint(configSecretsPath);
209
- if (remoteDevAuth.isRemoteSecretsUrl(effectiveShared)) {
210
- const remoteSecrets = await loadRemoteSharedSecrets();
211
- const merged = mergeUserWithRemoteSecrets(userSecrets, remoteSecrets);
212
- return hasKeys(merged) ? merged : userOrNull();
213
- }
214
- const resolvedConfigPath = path.isAbsolute(configSecretsPath)
215
- ? configSecretsPath
216
- : path.resolve(process.cwd(), configSecretsPath);
217
- const merged = mergeUserWithConfigFile(userSecrets, resolvedConfigPath);
218
- return merged !== null ? merged : userOrNull();
219
- } catch (error) {
220
- if (error.message && error.message.startsWith('Failed to load secrets file')) {
221
- throw error;
222
- }
223
- return null;
224
- }
225
- }
226
-
227
- /**
228
- * @returns {string[]}
229
- */
230
- function collectBuilderSecretsYamlPaths() {
231
- const projectRoot = pathsUtil.getProjectRoot();
232
- const candidates = [];
233
- if (projectRoot) {
234
- candidates.push(path.join(projectRoot, 'builder', 'secrets.local.yaml'));
235
- }
236
- try {
237
- const alt = path.join(pathsUtil.getBuilderRoot(), 'secrets.local.yaml');
238
- if (!candidates.length || path.resolve(candidates[0]) !== path.resolve(alt)) {
239
- candidates.push(alt);
240
- }
241
- } catch {
242
- /* ignore */
243
- }
244
- return candidates;
245
- }
246
-
247
- /**
248
- * Merge `builder/secrets.local.yaml` from project root and from {@link pathsUtil.getBuilderRoot} when distinct.
249
- * @param {Object|null|undefined} merged
250
- * @returns {Object|null|undefined}
251
- */
252
- function mergeBuilderSecretsLocalFiles(merged) {
253
- try {
254
- const seen = new Set();
255
- let out = merged;
256
- for (const builderPath of collectBuilderSecretsYamlPaths()) {
257
- if (!builderPath || seen.has(path.resolve(builderPath))) {
258
- continue;
259
- }
260
- seen.add(path.resolve(builderPath));
261
- if (fs.existsSync(builderPath)) {
262
- ensureSecureFilePermissions(builderPath);
263
- const builderSecrets = mergeUserWithConfigFile(out || {}, builderPath);
264
- if (builderSecrets) out = builderSecrets;
265
- }
266
- }
267
- return out;
268
- } catch {
269
- return merged;
270
- }
271
- }
272
-
273
- /**
274
- * Loads merged secrets using config/user cascade, builder file merge, and default fallback.
275
- * @async
276
- * @returns {Promise<Object>} Merged secrets object (not decrypted)
277
- */
278
- async function loadSecretsWithFallbacks() {
279
- let merged = await loadMergedConfigAndUserSecrets();
280
- if (!merged || Object.keys(merged).length === 0) {
281
- merged = loadPrimaryUserSecrets();
282
- if (Object.keys(merged).length === 0) {
283
- merged = loadUserSecrets();
284
- }
285
- merged = await applyCanonicalSecretsOverride(merged);
286
- }
287
- merged = mergeBuilderSecretsLocalFiles(merged);
288
- if (Object.keys(merged).length === 0) {
289
- merged = loadDefaultSecrets();
290
- }
291
- return merged;
292
- }
293
-
294
- async function loadSecrets(secretsPath, _appName) {
295
- if (secretsPath) {
296
- const resolvedPath = resolveSecretsPath(secretsPath);
297
- if (!fs.existsSync(resolvedPath)) {
298
- throw new Error(`Secrets file not found: ${resolvedPath}`);
299
- }
300
- ensureSecureFilePermissions(resolvedPath);
301
- const explicitSecrets = readYamlAtPath(resolvedPath);
302
- if (!explicitSecrets || typeof explicitSecrets !== 'object') {
303
- throw new Error(`Invalid secrets file format: ${resolvedPath}`);
304
- }
305
- return await decryptSecretsObject(explicitSecrets);
306
- }
307
- let mergedSecrets = await loadSecretsWithFallbacks();
308
- if (!mergedSecrets || Object.keys(mergedSecrets).length === 0) {
309
- ensurePrimaryUserSecretsFileExists();
310
- mergedSecrets = await loadSecretsWithFallbacks();
311
- }
312
- return await decryptSecretsObject(mergedSecrets || {});
313
- }
314
-
315
- /**
316
- * Resolves kv:// references in environment template
317
- * Replaces kv://keyName with actual values from secrets
318
- *
319
- * @async
320
- * @function resolveKvReferences
321
- * @param {string} envTemplate - Environment template content
322
- * @param {Object} secrets - Secrets object from loadSecrets()
323
- * @param {string} [environment='local'] - Environment context (docker/local)
324
- * @param {Object|string|null} [secretsFilePaths] - Paths object with userPath and buildPath, or string path (for backward compatibility)
325
- * @param {string} [secretsFilePaths.userPath] - User's secrets file path
326
- * @param {string|null} [secretsFilePaths.buildPath] - App's aifabrix-secrets file path (from config.yaml, if configured)
327
- * @param {string} [appName] - Application name (optional, for error messages)
328
- * @returns {Promise<string>} Resolved environment content
329
- * @throws {Error} If kv:// reference cannot be resolved
330
- *
331
- * @example
332
- * const resolved = await resolveKvReferences(template, secrets, 'local');
333
- * // Returns: 'DATABASE_URL=postgresql://user:pass@localhost:5432/db'
334
- */
335
- async function resolveKvReferences(envTemplate, secrets, environment = 'local', secretsFilePaths = null, appName = null, scopedKv = null) {
336
- const os = require('os');
337
-
338
- // Get developer-id for port variables (local and docker: *_PUBLIC_PORT = base + devId*100)
339
- let developerId = null;
340
- try {
341
- developerId = await config.getDeveloperId();
342
- } catch {
343
- // ignore, buildEnvVarMap will use default
344
- }
345
-
346
- const envKey = String(environment || 'local').toLowerCase();
347
- const mapContext = envKey === 'docker' || envKey === 'local' ? envKey : 'local';
348
-
349
- let envVars = await buildEnvVarMap(mapContext, os, developerId);
350
- if (!envVars || Object.keys(envVars).length === 0) {
351
- // Fallback to local environment variables if requested environment does not exist
352
- envVars = await buildEnvVarMap('local', os, developerId);
353
- }
354
- const resolved = interpolateEnvVars(envTemplate, envVars);
355
- const missing = collectMissingSecrets(resolved, secrets, scopedKv);
356
- if (missing.length > 0) {
357
- const fileInfo = formatMissingSecretsFileInfo(secretsFilePaths);
358
- const resolveCommand = appName ? `aifabrix resolve ${appName}` : 'aifabrix resolve <app-name>';
359
- throw new Error(`Missing secrets: ${missing.join(', ')}${fileInfo}\n\nRun "${resolveCommand}" to generate missing secrets.`);
360
- }
361
- return replaceKvInContent(resolved, secrets, envVars, scopedKv);
362
- }
363
-
364
- /**
365
- * Resolve run env key and effective env-scoped kv/redis behavior for generateEnvContent.
366
- *
367
- * @async
368
- * @param {string} appPath - Builder application directory
369
- * @param {Object} [options] - generateEnvContent options; may set runEnvKey
370
- * @returns {Promise<{ runEnvKey: string, effective: boolean }>}
371
- */
372
- async function buildScopedKvContext(appPath, options = {}) {
373
- let runEnvKey;
374
- if (options.runEnvKey !== undefined && options.runEnvKey !== null) {
375
- runEnvKey = String(options.runEnvKey).toLowerCase();
376
- } else if (typeof config.getCurrentEnvironment === 'function') {
377
- runEnvKey = String((await config.getCurrentEnvironment()) || 'dev').toLowerCase();
378
- } else {
379
- runEnvKey = 'dev';
380
- }
381
- const userCfg =
382
- typeof config.getConfig === 'function'
383
- ? await config.getConfig()
384
- : { useEnvironmentScopedResources: false };
385
- const useGate = Boolean(userCfg.useEnvironmentScopedResources);
386
- const appFlag = readAppEnvironmentScopedFlagForAppPath(appPath);
387
- const effective = computeEffectiveEnvironmentScopedResources(useGate, appFlag, runEnvKey);
388
- return { runEnvKey, effective };
389
- }
390
-
391
- /**
392
- * Redis/DB service endpoints for docker env interpolation.
393
- * @returns {Promise<{ redisHost: string, redisPort: number, dbHost: string, dbPort: number }>}
394
- */
395
- async function getDockerRedisDbEndpoints() {
396
- const { getEnvHosts, getServiceHost, getServicePort, getLocalhostOverride } = require('../utils/env-endpoints');
397
- const hosts = await getEnvHosts('docker');
398
- const localhostOverride = getLocalhostOverride('docker');
399
- const redisHost = getServiceHost(hosts.REDIS_HOST, 'docker', 'redis', localhostOverride);
400
- const redisPort = await getServicePort('REDIS_PORT', 'redis', hosts, 'docker', null);
401
- const dbHost = getServiceHost(hosts.DB_HOST, 'docker', 'postgres', localhostOverride);
402
- const dbPort = await getServicePort('DB_PORT', 'postgres', hosts, 'docker', null);
403
- return { redisHost, redisPort, dbHost, dbPort };
404
- }
405
-
406
- /**
407
- * Config inputs for declarative url:// expansion (keeps expandDeclarativeUrlsIfPresent small).
408
- * @param {string} appPath
409
- * @returns {Promise<Object>}
410
- */
411
- async function loadDeclarativeUrlExpandInputs(appPath) {
412
- const userCfg = await config.getConfig();
413
- let remoteServer = null;
414
- try {
415
- const rs = await config.getRemoteServer();
416
- if (rs && String(rs).trim()) {
417
- remoteServer = String(rs).trim();
418
- }
419
- } catch {
420
- remoteServer = null;
421
- }
422
- let developerIdRaw = null;
423
- try {
424
- developerIdRaw = await config.getDeveloperId();
425
- } catch {
426
- developerIdRaw = null;
427
- }
428
- let infraTlsEnabled = false;
429
- try {
430
- infraTlsEnabled = await config.getTlsEnabled();
431
- } catch {
432
- infraTlsEnabled = false;
433
- }
434
- return {
435
- userCfg,
436
- remoteServer,
437
- developerIdRaw,
438
- infraTlsEnabled,
439
- appScopedFlag: readAppEnvironmentScopedFlagForAppPath(appPath)
440
- };
441
- }
442
-
443
- /**
444
- * After kv:// resolution, expand url:// references when application config exists.
445
- * @param {string} resolved
446
- * @param {string} appName
447
- * @param {string} appPath
448
- * @param {string|null} variablesPath
449
- * @param {string} environment
450
- * @param {boolean} envOnly
451
- * @returns {Promise<string>}
452
- */
453
- async function expandDeclarativeUrlsIfPresent(resolved, appName, appPath, variablesPath, environment, envOnly) {
454
- if (envOnly || !variablesPath) {
455
- return resolved;
456
- }
457
- const { userCfg, remoteServer, developerIdRaw, infraTlsEnabled, appScopedFlag } =
458
- await loadDeclarativeUrlExpandInputs(appPath);
459
- resolved = rewriteInactiveDeclarativeVdirPublicContent(resolved, variablesPath, userCfg);
460
- if (!resolved.includes('url://')) {
461
- return resolved;
462
- }
463
- return expandDeclarativeUrlsInEnvContent(resolved, {
464
- profile: environment === 'docker' ? 'docker' : 'local',
465
- currentAppKey: appName,
466
- variablesPath,
467
- useEnvironmentScopedResources: Boolean(userCfg.useEnvironmentScopedResources),
468
- appEnvironmentScopedResources: appScopedFlag,
469
- remoteServer,
470
- developerIdRaw,
471
- traefik: Boolean(userCfg.traefik),
472
- infraTlsEnabled
473
- });
474
- }
475
-
476
- /** Docker env transformations: ports, infra endpoints, PORT. */
477
- async function applyDockerTransformations(resolved, variablesPath) {
478
- resolved = await resolveServicePortsInEnvContent(resolved, 'docker');
479
- resolved = await rewriteInfraEndpoints(resolved, 'docker');
480
- const { redisHost, redisPort, dbHost, dbPort } = await getDockerRedisDbEndpoints();
481
- let dockerEnv = await getBaseDockerEnv();
482
- dockerEnv = applyDockerEnvOverride(dockerEnv);
483
- const containerPort = getContainerPortFromPath(variablesPath) ?? getContainerPortFromDockerEnv(dockerEnv) ?? 3000;
484
- const envVars = await buildEnvVarMap('docker', null, null, { appPort: containerPort });
485
- const appDoc = loadVariablesFromPath(variablesPath);
486
- await mergeDockerManifestPublishedPort(envVars, appDoc);
487
- envVars.REDIS_HOST = redisHost;
488
- envVars.REDIS_PORT = String(redisPort);
489
- envVars.DB_HOST = dbHost;
490
- envVars.DB_PORT = String(dbPort);
491
- envVars.PORT = String(containerPort);
492
- resolved = interpolateEnvVars(resolved, envVars);
493
- resolved = rewriteDockerManifestPublicPortEnvLine(resolved, envVars, appDoc);
494
- return updatePortForDocker(resolved, variablesPath);
495
- }
496
- /** Environment-specific transformations to resolved content. */
497
- async function applyEnvironmentTransformations(resolved, environment, variablesPath) {
498
- if (environment === 'docker') return applyDockerTransformations(resolved, variablesPath);
499
- if (environment === 'local') return adjustLocalEnvPortsInContent(resolved, variablesPath);
500
- return resolved;
501
- }
502
-
503
- /**
504
- * Generate .env content from template and secrets (no disk write).
505
- * When options.envOnly is true, variablesPath is null (no application config).
506
- *
507
- * @param {string} appName - Application name
508
- * @param {string} [secretsPath] - Path to secrets file (optional)
509
- * @param {string} [environment='local'] - Environment context
510
- * @param {boolean} [force=false] - Generate missing secret keys
511
- * @param {Object} [options] - Optional: appPath, envOnly (env-only mode uses only env.template)
512
- * @returns {Promise<string>} Resolved env content
513
- */
514
- async function generateEnvContent(appName, secretsPath, environment = 'local', force = false, options = {}) {
515
- const appPath = (options && options.appPath) || pathsUtil.getBuilderPath(appName);
516
- const templatePath = path.join(appPath, 'env.template');
517
- const variablesPath = (options && options.envOnly) ? null : resolveApplicationConfigPath(appPath);
518
- const template = loadEnvTemplate(templatePath);
519
- const secretsPaths = await getActualSecretsPath(secretsPath, appName);
520
- if (force) {
521
- const preferredPath = secretsPath ? resolveSecretsPath(secretsPath) : secretsPaths.userPath;
522
- await secretsEnsure.ensureSecretsFromEnvTemplate(templatePath, { preferredFilePath: preferredPath });
523
- }
524
- const secrets = await loadSecrets(secretsPath, appName);
525
- const { runEnvKey, effective } = await buildScopedKvContext(appPath, options);
526
- const scopedKv = { envKey: runEnvKey, effective };
527
- let resolved = await resolveKvReferences(template, secrets, environment, secretsPaths, appName, scopedKv);
528
- resolved = await expandDeclarativeUrlsIfPresent(
529
- resolved,
530
- appName,
531
- appPath,
532
- variablesPath,
533
- environment,
534
- Boolean(options.envOnly)
535
- );
536
- resolved = await applyEnvironmentTransformations(resolved, environment, variablesPath);
537
- if (effective) {
538
- const idx = redisDbIndexForScopedRunEnv(runEnvKey);
539
- resolved = applyRedisDbIndexToEnvContent(resolved, idx);
540
- }
541
-
542
- return resolved;
543
- }
544
-
545
- /**
546
- * Parses .env file content into a key-to-value map.
547
- * Only includes lines that look like KEY=value (first = separates key and value).
548
- *
549
- * @function parseEnvContentToMap
550
- * @param {string} content - Raw .env file content
551
- * @returns {Object.<string, string>} Map of variable name to value
552
- */
553
- function parseEnvContentToMap(content) {
554
- if (!content || typeof content !== 'string') {
555
- return {};
556
- }
557
- const map = {};
558
- const lines = content.split(/\r?\n/);
559
- for (const line of lines) {
560
- const trimmed = line.trim();
561
- if (!trimmed || trimmed.startsWith('#')) {
562
- continue;
563
- }
564
- const eq = trimmed.indexOf('=');
565
- if (eq > 0) {
566
- const key = trimmed.substring(0, eq).trim();
567
- const value = trimmed.substring(eq + 1);
568
- map[key] = value;
569
- }
570
- }
571
- return map;
572
- }
573
-
574
- /**
575
- * Merges new .env content with existing .env: newly resolved content wins for keys it
576
- * defines (so project secrets take effect when re-running). Keys only in existing .env
577
- * are appended so manual additions are kept.
578
- *
579
- * @function mergeEnvContentPreservingExisting
580
- * @param {string} newContent - Newly generated .env content (from template + loadSecrets)
581
- * @param {Object.<string, string>} existingMap - Existing key-to-value map from current .env
582
- * @returns {string} Merged content: new values for keys in newContent, plus extra existing keys
583
- */
584
- function mergeEnvContentPreservingExisting(newContent, existingMap) {
585
- const lines = newContent.split(/\r?\n/);
586
- const newKeys = new Set();
587
- const out = [];
588
- for (const line of lines) {
589
- const trimmed = line.trim();
590
- if (!trimmed || trimmed.startsWith('#')) {
591
- out.push(line);
592
- continue;
593
- }
594
- const eq = trimmed.indexOf('=');
595
- if (eq > 0) {
596
- const key = trimmed.substring(0, eq).trim();
597
- newKeys.add(key);
598
- }
599
- out.push(line);
600
- }
601
- if (existingMap && Object.keys(existingMap).length > 0) {
602
- for (const key of Object.keys(existingMap)) {
603
- if (!newKeys.has(key)) {
604
- out.push(`${key}=${existingMap[key]}`);
605
- }
606
- }
607
- }
608
- return out.join('\n');
609
- }
610
-
611
- /**
612
- * Merges a key-value map into existing .env file content, preserving comments and blank lines.
613
- * For each KEY=value line in existing content, replaces value with newMap[KEY] when the key exists
614
- * in newMap. Appends any keys from newMap that did not appear in the file.
615
- *
616
- * @function mergeEnvMapIntoContent
617
- * @param {string} existingContent - Full existing .env file content
618
- * @param {Object.<string, string>} newMap - New key-to-value map (e.g. from resolved or run env)
619
- * @returns {string} Merged content with comments preserved
620
- */
621
- function mergeEnvMapIntoContent(existingContent, newMap) {
622
- if (!newMap || Object.keys(newMap).length === 0) {
623
- return typeof existingContent === 'string' ? existingContent : '';
624
- }
625
- const lines = (existingContent || '').split(/\r?\n/);
626
- const seen = new Set();
627
- const out = [];
628
- for (const line of lines) {
629
- const trimmed = line.trim();
630
- if (!trimmed || trimmed.startsWith('#')) {
631
- out.push(line);
632
- continue;
633
- }
634
- const eq = trimmed.indexOf('=');
635
- if (eq > 0) {
636
- const key = trimmed.substring(0, eq).trim();
637
- seen.add(key);
638
- out.push(Object.prototype.hasOwnProperty.call(newMap, key) ? `${key}=${newMap[key]}` : line);
639
- continue;
640
- }
641
- out.push(line);
642
- }
643
- for (const key of Object.keys(newMap)) {
644
- if (!seen.has(key)) out.push(`${key}=${newMap[key]}`);
645
- }
646
- return out.join('\n');
647
- }
648
-
649
- /**
650
- * Resolves content to write for .env: merges with existing file when present.
651
- * @param {string} resolved - Newly generated content
652
- * @param {string} pathToPreserve - Path to existing .env to merge from (or null)
653
- * @returns {string} Content to write
654
- */
655
- function resolveEnvContentToWrite(resolved, pathToPreserve) {
656
- if (!pathToPreserve || !fs.existsSync(pathToPreserve)) return resolved;
657
- const existingContent = fs.readFileSync(pathToPreserve, 'utf8');
658
- const existingMap = parseEnvContentToMap(existingContent);
659
- return mergeEnvContentPreservingExisting(resolved, existingMap);
660
- }
661
-
662
- /**
663
- * Generates and writes .env file. Newly resolved values win over existing .env; extra vars in existing .env are kept.
664
- * When options.envOnly is true, only env.template is used; .env is written to options.appPath.
665
- * @async
666
- * @function generateEnvFile
667
- * @param {string} appName - Name of the application
668
- * @param {string} [secretsPath] - Path to secrets file (optional)
669
- * @param {string} [environment='local'] - Environment context ('local' or 'docker')
670
- * @param {boolean} [force=false] - Generate missing secret keys in secrets file
671
- * @param {Object} [options] - Optional: appPath, envOnly, skipOutputPath, preserveFromPath
672
- * @returns {Promise<string>} Path to generated .env file
673
- */
674
- async function generateEnvFile(appName, secretsPath, environment = 'local', force = false, options = {}) {
675
- const opts = options && typeof options === 'object' ? options : {};
676
- const appPath = opts.appPath || pathsUtil.getBuilderPath(appName);
677
- const envOnly = !!opts.envOnly;
678
- const variablesPath = envOnly ? null : resolveApplicationConfigPath(appPath);
679
- const envPath = path.join(appPath, '.env');
680
-
681
- if (envOnly) {
682
- const templatePath = path.join(appPath, 'env.template');
683
- if (!fs.existsSync(templatePath)) {
684
- throw new Error(`env.template not found at ${templatePath}. Resolve requires env.template in the app directory.`);
685
- }
686
- }
687
-
688
- const resolved = await generateEnvContent(appName, secretsPath, environment, force, { appPath, envOnly });
689
- const preservePath = opts.preserveFromPath !== undefined && opts.preserveFromPath !== null ? opts.preserveFromPath : null;
690
- const pathToPreserve = preservePath !== null ? preservePath : envPath;
691
- const toWrite = resolveEnvContentToWrite(resolved, pathToPreserve);
692
- fs.writeFileSync(envPath, toWrite, { mode: 0o600 });
693
-
694
- if (!opts.skipOutputPath) {
695
- const { processEnvVariables } = require('../utils/env-copy');
696
- await processEnvVariables(envPath, variablesPath, appName, secretsPath);
697
- }
698
-
699
- return envPath;
700
- }
701
-
702
- /**
703
- * Writes admin env key-value pairs to content; encrypts values when encryption key is set.
704
- * @async
705
- * @param {Object.<string, string>} adminObj - Key-value object (e.g. POSTGRES_PASSWORD, ...)
706
- * @returns {Promise<string>} .env-style content (plaintext or secure:// for secrets)
707
- */
708
- async function formatAdminSecretsContent(adminObj) {
709
- const encryptionKey = await config.getSecretsEncryptionKey();
710
- const { encryptSecret } = require('../utils/secrets-encryption');
711
- const lines = ['# Infrastructure Admin Credentials'];
712
- for (const [k, v] of Object.entries(adminObj)) {
713
- const value = (v === null || v === undefined) ? '' : String(v).replace(/\n/g, ' ').trim();
714
- const valueToWrite = encryptionKey ? encryptSecret(value, encryptionKey) : value;
715
- lines.push(`${k}=${valueToWrite}`);
716
- }
717
- return lines.join('\n');
718
- }
719
-
720
- async function loadSecretsOrBootstrapForAdmin(secretsPath) {
721
- try {
722
- return await loadSecrets(secretsPath);
723
- } catch (error) {
724
- const defaultSecretsPath = secretsPath || path.join(pathsUtil.getAifabrixHome(), 'secrets.yaml');
725
- if (!fs.existsSync(defaultSecretsPath)) {
726
- logger.log('Creating default secrets file...');
727
- await createDefaultSecrets(defaultSecretsPath);
728
- return await loadSecrets(secretsPath);
729
- }
730
- throw error;
731
- }
732
- }
733
-
734
- function getInfraDefaultsMergedForAdmin() {
735
- try {
736
- return mergeInfraParameterDefaultsForCli(getInfraParameterCatalog().data, {});
737
- } catch {
738
- return {};
739
- }
740
- }
741
-
742
- function buildLocalAdminSecretsObject(secrets, infraDefaults) {
743
- const raw = secrets['postgres-passwordKeyVault'];
744
- const relaxed = readRelaxedCatalogDefaults();
745
- const postgresPassword =
746
- (raw && String(raw).trim()) ||
747
- infraDefaults.adminPassword ||
748
- relaxed.adminPassword ||
749
- '';
750
- const pgAdminEmail = infraDefaults.adminEmail || relaxed.adminEmail || '';
751
- return {
752
- POSTGRES_PASSWORD: postgresPassword,
753
- PGADMIN_DEFAULT_EMAIL: pgAdminEmail,
754
- PGADMIN_DEFAULT_PASSWORD: postgresPassword,
755
- REDIS_HOST: 'local:redis:6379:0:',
756
- REDIS_COMMANDER_USER: 'admin',
757
- REDIS_COMMANDER_PASSWORD: postgresPassword
758
- };
759
- }
30
+ formatAdminSecretsContent,
31
+ generateAdminSecretsEnv
32
+ } = require('./secrets-admin-env');
33
+ const { getCanonicalSecretName } = require('./secrets-names');
760
34
 
761
- /** Generates admin secrets for infrastructure (beside config.yaml, typically ~/.aifabrix/admin-secrets.env). Defaults from infra.parameter.yaml `defaults`. */
762
- async function generateAdminSecretsEnv(secretsPath) {
763
- const secrets = await loadSecretsOrBootstrapForAdmin(secretsPath);
764
- const infraDefaults = getInfraDefaultsMergedForAdmin();
765
- const adminObj = buildLocalAdminSecretsObject(secrets, infraDefaults);
766
- const aifabrixDir = pathsUtil.getAifabrixSystemDir();
767
- const adminEnvPath = path.join(aifabrixDir, 'admin-secrets.env');
768
- if (!fs.existsSync(aifabrixDir)) {
769
- fs.mkdirSync(aifabrixDir, { recursive: true, mode: 0o700 });
770
- }
771
- const adminSecrets = await formatAdminSecretsContent(adminObj);
772
- fs.writeFileSync(adminEnvPath, adminSecrets, { mode: 0o600 });
773
- return adminEnvPath;
774
- }
775
35
  module.exports = {
776
36
  loadSecrets,
777
37
  resolveKvReferences,