@aifabrix/builder 2.40.2 → 2.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/integration/hubspot/test.js +1 -1
- package/lib/api/credential.api.js +40 -0
- package/lib/api/dev.api.js +423 -0
- package/lib/api/types/credential.types.js +23 -0
- package/lib/api/types/dev.types.js +140 -0
- package/lib/app/config.js +21 -0
- package/lib/app/down.js +2 -1
- package/lib/app/index.js +9 -0
- package/lib/app/push.js +36 -12
- package/lib/app/readme.js +1 -3
- package/lib/app/run-env-compose.js +201 -0
- package/lib/app/run-helpers.js +121 -118
- package/lib/app/run.js +148 -28
- package/lib/app/show.js +5 -2
- package/lib/build/index.js +11 -3
- package/lib/cli/setup-app.js +140 -14
- package/lib/cli/setup-dev.js +180 -17
- package/lib/cli/setup-environment.js +4 -2
- package/lib/cli/setup-external-system.js +71 -21
- package/lib/cli/setup-infra.js +29 -2
- package/lib/cli/setup-secrets.js +52 -5
- package/lib/cli/setup-utility.js +12 -3
- package/lib/commands/app-install.js +172 -0
- package/lib/commands/app-shell.js +75 -0
- package/lib/commands/app-test.js +282 -0
- package/lib/commands/app.js +1 -1
- package/lib/commands/dev-cli-handlers.js +141 -0
- package/lib/commands/dev-down.js +114 -0
- package/lib/commands/dev-init.js +309 -0
- package/lib/commands/secrets-list.js +118 -0
- package/lib/commands/secrets-remove.js +97 -0
- package/lib/commands/secrets-set.js +30 -17
- package/lib/commands/secrets-validate.js +50 -0
- package/lib/commands/up-dataplane.js +2 -2
- package/lib/commands/up-miso.js +0 -25
- package/lib/commands/upload.js +26 -1
- package/lib/core/admin-secrets.js +96 -0
- package/lib/core/secrets-ensure.js +378 -0
- package/lib/core/secrets-env-write.js +157 -0
- package/lib/core/secrets.js +147 -81
- package/lib/datasource/field-reference-validator.js +91 -0
- package/lib/datasource/validate.js +21 -3
- package/lib/deployment/environment-config.js +137 -0
- package/lib/deployment/environment.js +21 -98
- package/lib/deployment/push.js +32 -2
- package/lib/external-system/download.js +7 -0
- package/lib/external-system/test-auth.js +7 -3
- package/lib/external-system/test.js +5 -1
- package/lib/generator/index.js +174 -25
- package/lib/generator/wizard.js +8 -0
- package/lib/infrastructure/helpers.js +103 -20
- package/lib/infrastructure/index.js +88 -10
- package/lib/infrastructure/services.js +70 -15
- package/lib/schema/application-schema.json +24 -3
- package/lib/schema/external-system.schema.json +435 -413
- package/lib/utils/api.js +3 -3
- package/lib/utils/app-register-auth.js +25 -3
- package/lib/utils/cli-utils.js +20 -0
- package/lib/utils/compose-generator.js +76 -75
- package/lib/utils/compose-handlebars-helpers.js +43 -0
- package/lib/utils/compose-vector-helper.js +18 -0
- package/lib/utils/config-paths.js +127 -2
- package/lib/utils/credential-secrets-env.js +267 -0
- package/lib/utils/dev-cert-helper.js +122 -0
- package/lib/utils/device-code-helpers.js +224 -0
- package/lib/utils/device-code.js +37 -336
- package/lib/utils/docker-build.js +40 -8
- package/lib/utils/env-copy.js +83 -13
- package/lib/utils/env-map.js +35 -5
- package/lib/utils/env-template.js +6 -5
- package/lib/utils/error-formatters/http-status-errors.js +20 -1
- package/lib/utils/help-builder.js +15 -2
- package/lib/utils/infra-status.js +30 -1
- package/lib/utils/local-secrets.js +7 -52
- package/lib/utils/mutagen-install.js +195 -0
- package/lib/utils/mutagen.js +146 -0
- package/lib/utils/paths.js +43 -33
- package/lib/utils/port-resolver.js +28 -16
- package/lib/utils/remote-dev-auth.js +38 -0
- package/lib/utils/remote-docker-env.js +43 -0
- package/lib/utils/remote-secrets-loader.js +60 -0
- package/lib/utils/secrets-generator.js +94 -6
- package/lib/utils/secrets-helpers.js +33 -25
- package/lib/utils/secrets-path.js +2 -2
- package/lib/utils/secrets-utils.js +52 -1
- package/lib/utils/secrets-validation.js +84 -0
- package/lib/utils/ssh-key-helper.js +116 -0
- package/lib/utils/token-manager-messages.js +90 -0
- package/lib/utils/token-manager.js +5 -4
- package/lib/utils/variable-transformer.js +3 -3
- package/lib/validation/validator.js +65 -0
- package/package.json +2 -2
- package/scripts/install-local.js +34 -15
- package/templates/README.md +0 -1
- package/templates/applications/README.md.hbs +4 -4
- package/templates/applications/dataplane/application.yaml +5 -4
- package/templates/applications/dataplane/env.template +12 -7
- package/templates/applications/keycloak/env.template +2 -0
- package/templates/applications/miso-controller/application.yaml +1 -0
- package/templates/applications/miso-controller/env.template +11 -9
- package/templates/python/docker-compose.hbs +49 -23
- package/templates/typescript/docker-compose.hbs +48 -22
package/lib/app/config.js
CHANGED
|
@@ -31,6 +31,26 @@ async function fileExists(filePath) {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Renames legacy variables.yaml to application.yaml if only variables.yaml exists.
|
|
36
|
+
* Ensures create always results in application.yaml.
|
|
37
|
+
* @async
|
|
38
|
+
* @param {string} appPath - Path to application directory
|
|
39
|
+
*/
|
|
40
|
+
async function normalizeLegacyVariablesYaml(appPath) {
|
|
41
|
+
const applicationYaml = path.join(appPath, 'application.yaml');
|
|
42
|
+
const applicationYml = path.join(appPath, 'application.yml');
|
|
43
|
+
const applicationJson = path.join(appPath, 'application.json');
|
|
44
|
+
const variablesYaml = path.join(appPath, 'variables.yaml');
|
|
45
|
+
const hasAppYaml = await fileExists(applicationYaml);
|
|
46
|
+
const hasAppYml = await fileExists(applicationYml);
|
|
47
|
+
const hasAppJson = await fileExists(applicationJson);
|
|
48
|
+
const hasVariables = await fileExists(variablesYaml);
|
|
49
|
+
if (hasVariables && !hasAppYaml && !hasAppYml && !hasAppJson) {
|
|
50
|
+
await fs.rename(variablesYaml, applicationYaml);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
34
54
|
/**
|
|
35
55
|
* Generates application.yaml file if no application config exists
|
|
36
56
|
* @async
|
|
@@ -39,6 +59,7 @@ async function fileExists(filePath) {
|
|
|
39
59
|
* @param {Object} config - Application configuration
|
|
40
60
|
*/
|
|
41
61
|
async function generateVariablesYamlFile(appPath, appName, config) {
|
|
62
|
+
await normalizeLegacyVariablesYaml(appPath);
|
|
42
63
|
const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
|
|
43
64
|
try {
|
|
44
65
|
resolveApplicationConfigPath(appPath);
|
package/lib/app/down.js
CHANGED
package/lib/app/index.js
CHANGED
|
@@ -31,6 +31,8 @@ const {
|
|
|
31
31
|
processTemplateFiles,
|
|
32
32
|
setupAppFiles
|
|
33
33
|
} = require('./helpers');
|
|
34
|
+
const path = require('path');
|
|
35
|
+
const secretsEnsure = require('../core/secrets-ensure');
|
|
34
36
|
|
|
35
37
|
/**
|
|
36
38
|
* Creates new application with scaffolded configuration files
|
|
@@ -130,6 +132,13 @@ async function generateApplicationFiles(finalAppPath, appName, config, options)
|
|
|
130
132
|
|
|
131
133
|
await generateConfigFiles(finalAppPath, appName, config, existingEnv);
|
|
132
134
|
|
|
135
|
+
const envTemplatePath = path.join(finalAppPath, 'env.template');
|
|
136
|
+
try {
|
|
137
|
+
await secretsEnsure.ensureSecretsFromEnvTemplate(envTemplatePath, {});
|
|
138
|
+
} catch (err) {
|
|
139
|
+
if (err.code !== 'ENOENT') throw err;
|
|
140
|
+
}
|
|
141
|
+
|
|
133
142
|
// Generate external system files if type is external
|
|
134
143
|
if (config.type === 'external') {
|
|
135
144
|
const externalGenerator = require('../external-system/generator');
|
package/lib/app/push.js
CHANGED
|
@@ -40,21 +40,38 @@ function validateAppName(appName) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* @param {
|
|
46
|
-
* @returns {
|
|
43
|
+
* Returns effective config (unwrap variables wrapper if present so image/app are at top level).
|
|
44
|
+
* application.yaml may have top-level image/app or a variables: { image, app } wrapper.
|
|
45
|
+
* @param {Object} config - Raw config from loadConfigFile
|
|
46
|
+
* @returns {Object} Config with image and app at top level
|
|
47
|
+
*/
|
|
48
|
+
function getEffectiveConfig(config) {
|
|
49
|
+
if (!config || typeof config !== 'object') return config || {};
|
|
50
|
+
if (config.variables && typeof config.variables === 'object' && (config.variables.image !== undefined || config.variables.app !== undefined)) {
|
|
51
|
+
return config.variables;
|
|
52
|
+
}
|
|
53
|
+
return config;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Extracts image name from configuration using the same logic as build command.
|
|
58
|
+
* Uses image.name (e.g. aifabrix/dataplane) so ACR repository matches application.yaml.
|
|
59
|
+
* @param {Object} config - Configuration object from application.yaml (or effective config)
|
|
60
|
+
* @param {string} appName - Application name (fallback when image not set)
|
|
61
|
+
* @returns {string} Image name (e.g. aifabrix/dataplane, not just dataplane)
|
|
47
62
|
*/
|
|
48
63
|
function extractImageName(config, appName) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
return
|
|
64
|
+
const c = getEffectiveConfig(config);
|
|
65
|
+
if (typeof c.image === 'string') {
|
|
66
|
+
return c.image.split(':')[0];
|
|
67
|
+
}
|
|
68
|
+
if (c.image?.name) {
|
|
69
|
+
return c.image.name;
|
|
70
|
+
}
|
|
71
|
+
if (c.app?.key) {
|
|
72
|
+
return c.app.key;
|
|
55
73
|
}
|
|
56
74
|
return appName;
|
|
57
|
-
|
|
58
75
|
}
|
|
59
76
|
|
|
60
77
|
/**
|
|
@@ -72,7 +89,8 @@ async function loadPushConfig(appName, options) {
|
|
|
72
89
|
try {
|
|
73
90
|
const configPath = resolveApplicationConfigPath(appPath);
|
|
74
91
|
const config = loadConfigFile(configPath);
|
|
75
|
-
const
|
|
92
|
+
const effective = getEffectiveConfig(config);
|
|
93
|
+
const registry = options.registry || effective.image?.registry;
|
|
76
94
|
if (!registry) {
|
|
77
95
|
throw new Error('Registry URL is required. Provide via --registry flag or configure in application config under image.registry');
|
|
78
96
|
}
|
|
@@ -110,6 +128,12 @@ async function validatePushConfig(registry, imageName, appName) {
|
|
|
110
128
|
if (!await pushUtils.checkAzureCLIInstalled()) {
|
|
111
129
|
throw new Error('Azure CLI is not installed. Install from: https://docs.microsoft.com/cli/azure/install-azure-cli');
|
|
112
130
|
}
|
|
131
|
+
|
|
132
|
+
if (!await pushUtils.checkAzureLogin()) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
'Not logged in to Azure. Run "az login" first, then run push again.'
|
|
135
|
+
);
|
|
136
|
+
}
|
|
113
137
|
}
|
|
114
138
|
|
|
115
139
|
/**
|
package/lib/app/readme.js
CHANGED
|
@@ -120,9 +120,7 @@ function buildExternalDatasourcePlaceholders(systemKey, datasourceCount) {
|
|
|
120
120
|
function buildReadmeContext(appName, config) {
|
|
121
121
|
const displayName = config.displayName || formatAppDisplayName(appName);
|
|
122
122
|
const port = config.port ?? 3000;
|
|
123
|
-
const localPort =
|
|
124
|
-
? config.build.localPort
|
|
125
|
-
: port;
|
|
123
|
+
const localPort = port;
|
|
126
124
|
const imageName = config.image?.name || `aifabrix/${appName}`;
|
|
127
125
|
// Extract registry from nested structure (config.image.registry) or flattened (config.registry)
|
|
128
126
|
const registry = config.image?.registry || config.registry || 'myacr.azurecr.io';
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for application run: clean applications dir, build merged .env, compose safeguard.
|
|
3
|
+
* Keeps run-helpers.js under line limit.
|
|
4
|
+
*
|
|
5
|
+
* @fileoverview Run env and compose helpers
|
|
6
|
+
* @author AI Fabrix Team
|
|
7
|
+
* @version 2.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const fs = require('fs').promises;
|
|
14
|
+
const fsSync = require('fs');
|
|
15
|
+
const pathsUtil = require('../utils/paths');
|
|
16
|
+
const adminSecrets = require('../core/admin-secrets');
|
|
17
|
+
const secretsEnvWrite = require('../core/secrets-env-write');
|
|
18
|
+
const { getContainerPort } = require('../utils/port-resolver');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Clean applications directory: remove generated docker-compose.yaml and .env.* files.
|
|
22
|
+
* @param {string|number} developerId - Developer ID
|
|
23
|
+
*/
|
|
24
|
+
function cleanApplicationsDir(developerId) {
|
|
25
|
+
const baseDir = pathsUtil.getApplicationsBaseDir(developerId);
|
|
26
|
+
if (!fsSync.existsSync(baseDir)) return;
|
|
27
|
+
const toRemove = [path.join(baseDir, 'docker-compose.yaml')];
|
|
28
|
+
try {
|
|
29
|
+
const entries = fsSync.readdirSync(baseDir);
|
|
30
|
+
for (const name of entries) {
|
|
31
|
+
if (name.startsWith('.env.')) toRemove.push(path.join(baseDir, name));
|
|
32
|
+
}
|
|
33
|
+
} catch {
|
|
34
|
+
// Ignore readdir errors
|
|
35
|
+
}
|
|
36
|
+
for (const filePath of toRemove) {
|
|
37
|
+
try {
|
|
38
|
+
if (fsSync.existsSync(filePath)) fsSync.unlinkSync(filePath);
|
|
39
|
+
} catch {
|
|
40
|
+
// Ignore unlink errors
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Derive PostgreSQL user from database name (same as compose-handlebars-helpers pgUserName).
|
|
47
|
+
* @param {string} dbName - Database name (e.g. keycloak)
|
|
48
|
+
* @returns {string} User name (e.g. keycloak_user)
|
|
49
|
+
*/
|
|
50
|
+
function pgUserName(dbName) {
|
|
51
|
+
if (!dbName) return '';
|
|
52
|
+
return `${String(dbName).replace(/-/g, '_')}_user`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Inject DB_N_NAME and DB_N_USER from application.yaml databases into env so .env has everything.
|
|
57
|
+
* @param {Object} env - Merged env object (mutated)
|
|
58
|
+
* @param {Object} appConfig - Application config (requires.databases or databases array)
|
|
59
|
+
*/
|
|
60
|
+
function injectDatabaseNamesAndUsers(env, appConfig) {
|
|
61
|
+
const databases = appConfig?.requires?.databases || appConfig?.databases;
|
|
62
|
+
if (!Array.isArray(databases) || databases.length === 0) return;
|
|
63
|
+
for (let i = 0; i < databases.length; i++) {
|
|
64
|
+
const db = databases[i];
|
|
65
|
+
const name = db?.name || (appConfig?.app?.key || 'app');
|
|
66
|
+
env[`DB_${i}_NAME`] = name;
|
|
67
|
+
env[`DB_${i}_USER`] = pgUserName(name);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get the env var name used for PORT in env.template (e.g. PORT=${MISO_PORT} -> MISO_PORT).
|
|
73
|
+
* @param {string} appName - Application name
|
|
74
|
+
* @returns {string|null} Variable name or null if not found
|
|
75
|
+
*/
|
|
76
|
+
function getPortVarFromEnvTemplate(appName) {
|
|
77
|
+
const builderPath = pathsUtil.getBuilderPath(appName);
|
|
78
|
+
const templatePath = path.join(builderPath, 'env.template');
|
|
79
|
+
if (!fsSync.existsSync(templatePath)) return null;
|
|
80
|
+
try {
|
|
81
|
+
const content = fsSync.readFileSync(templatePath, 'utf8');
|
|
82
|
+
const m = content.match(/^PORT\s*=\s*\$\{([A-Za-z0-9_]+)\}/m);
|
|
83
|
+
return m ? m[1] : null;
|
|
84
|
+
} catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Override PORT and the template's port variable (e.g. MISO_PORT) with container port from application.yaml.
|
|
91
|
+
* Run .env only: when running in Docker, the app listens on the container port (port or build.containerPort), not localPort.
|
|
92
|
+
* For envOutputPath .env (local, not reload) we use localPort instead - see adjustLocalEnvPortsInContent in secrets-helpers.
|
|
93
|
+
* @param {Object} env - Merged env object (mutated)
|
|
94
|
+
* @param {Object} appConfig - Application configuration (port, build.containerPort)
|
|
95
|
+
* @param {string} appName - Application name (to resolve env.template port var)
|
|
96
|
+
*/
|
|
97
|
+
function injectContainerPortForRun(env, appConfig, appName) {
|
|
98
|
+
const containerPort = getContainerPort(appConfig, 3000);
|
|
99
|
+
env.PORT = String(containerPort);
|
|
100
|
+
const portVar = getPortVarFromEnvTemplate(appName);
|
|
101
|
+
if (portVar) {
|
|
102
|
+
env[portVar] = String(containerPort);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Keys that must never be passed to the app container (admin/start-only). */
|
|
107
|
+
const ADMIN_ONLY_KEYS = [
|
|
108
|
+
'POSTGRES_PASSWORD',
|
|
109
|
+
'PGADMIN_DEFAULT_EMAIL',
|
|
110
|
+
'PGADMIN_DEFAULT_PASSWORD',
|
|
111
|
+
'REDIS_HOST',
|
|
112
|
+
'REDIS_COMMANDER_USER',
|
|
113
|
+
'REDIS_COMMANDER_PASSWORD'
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Build app-only env (merged minus admin secrets). App container must not receive admin passwords.
|
|
118
|
+
* @param {Object} merged - Full merged env
|
|
119
|
+
* @returns {Object} Env object safe for app container
|
|
120
|
+
*/
|
|
121
|
+
function buildAppOnlyEnv(merged) {
|
|
122
|
+
const appOnly = {};
|
|
123
|
+
for (const [k, v] of Object.entries(merged)) {
|
|
124
|
+
if (ADMIN_ONLY_KEYS.includes(k)) continue;
|
|
125
|
+
appOnly[k] = v;
|
|
126
|
+
}
|
|
127
|
+
return appOnly;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Build env for db-init only: POSTGRES_PASSWORD + DB_N_PASSWORD, DB_N_NAME, DB_N_USER. Used only for start, not in app container.
|
|
132
|
+
* @param {Object} merged - Full merged env
|
|
133
|
+
* @returns {Object} Env object for db-init service only
|
|
134
|
+
*/
|
|
135
|
+
function buildDbInitOnlyEnv(merged) {
|
|
136
|
+
const dbInit = {};
|
|
137
|
+
if (merged.POSTGRES_PASSWORD !== undefined) {
|
|
138
|
+
dbInit.POSTGRES_PASSWORD = merged.POSTGRES_PASSWORD;
|
|
139
|
+
}
|
|
140
|
+
for (const [k, v] of Object.entries(merged)) {
|
|
141
|
+
if (k.startsWith('DB_') && (k.endsWith('_PASSWORD') || k.endsWith('_NAME') || k.endsWith('_USER'))) {
|
|
142
|
+
dbInit[k] = v;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return dbInit;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Build two run env files: .env.run (app-only, no admin secrets) and .env.run.admin (start-only, for db-init).
|
|
150
|
+
* Admin password is never set in the app container; .env.run.admin is used only for start and then deleted.
|
|
151
|
+
* @async
|
|
152
|
+
* @param {string} appName - Application name
|
|
153
|
+
* @param {Object} appConfig - Application configuration
|
|
154
|
+
* @param {string} devDir - Applications directory path
|
|
155
|
+
* @returns {Promise<{ runEnvPath: string, runEnvAdminPath: string }>} Paths to .env.run and .env.run.admin
|
|
156
|
+
*/
|
|
157
|
+
async function buildMergedRunEnvAndWrite(appName, appConfig, devDir) {
|
|
158
|
+
const infra = require('../infrastructure');
|
|
159
|
+
const ensureAdminSecretsFn = typeof infra.ensureAdminSecrets === 'function'
|
|
160
|
+
? infra.ensureAdminSecrets
|
|
161
|
+
: require('../infrastructure/helpers').ensureAdminSecrets;
|
|
162
|
+
await ensureAdminSecretsFn();
|
|
163
|
+
const adminObj = await adminSecrets.readAndDecryptAdminSecrets();
|
|
164
|
+
const appObj = await secretsEnvWrite.resolveAndGetEnvMap(appName, {
|
|
165
|
+
environment: 'docker',
|
|
166
|
+
secretsPath: null,
|
|
167
|
+
force: false
|
|
168
|
+
});
|
|
169
|
+
const merged = { ...adminObj, ...appObj };
|
|
170
|
+
injectDatabaseNamesAndUsers(merged, appConfig);
|
|
171
|
+
injectContainerPortForRun(merged, appConfig, appName);
|
|
172
|
+
|
|
173
|
+
const runEnvPath = path.join(devDir, '.env.run');
|
|
174
|
+
const runEnvAdminPath = path.join(devDir, '.env.run.admin');
|
|
175
|
+
|
|
176
|
+
const appOnly = buildAppOnlyEnv(merged);
|
|
177
|
+
const dbInitOnly = buildDbInitOnlyEnv(merged);
|
|
178
|
+
|
|
179
|
+
await fs.writeFile(runEnvPath, adminSecrets.envObjectToContent(appOnly), { mode: 0o600 });
|
|
180
|
+
await fs.writeFile(runEnvAdminPath, adminSecrets.envObjectToContent(dbInitOnly), { mode: 0o600 });
|
|
181
|
+
|
|
182
|
+
return { runEnvPath, runEnvAdminPath };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Assert generated compose does not contain password literals in environment (ISO 27K).
|
|
187
|
+
* @param {string} composeContent - Generated docker-compose content
|
|
188
|
+
* @throws {Error} If password keys appear in environment-like assignment
|
|
189
|
+
*/
|
|
190
|
+
function assertNoPasswordLiteralsInCompose(composeContent) {
|
|
191
|
+
const badPattern = /\n\s+(-?\s*)(POSTGRES_PASSWORD|DB_\d+_PASSWORD)\s*[:=]/;
|
|
192
|
+
if (badPattern.test(composeContent)) {
|
|
193
|
+
throw new Error('Generated compose must not contain password literals (POSTGRES_PASSWORD, DB_*_PASSWORD). Use env_file only.');
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = {
|
|
198
|
+
cleanApplicationsDir,
|
|
199
|
+
buildMergedRunEnvAndWrite,
|
|
200
|
+
assertNoPasswordLiteralsInCompose
|
|
201
|
+
};
|