@aifabrix/builder 2.45.5 → 2.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/rules/cli-layout.mdc +2 -0
- package/.cursor/rules/cli-product-neutral.mdc +43 -0
- package/.cursor/rules/project-rules.mdc +1 -0
- package/README.md +43 -17
- package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +1 -1
- package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +1 -1
- package/jest.config.default.js +2 -1
- package/jest.config.isolated.js +2 -1
- package/jest.global-live-fabrix-hooks.js +36 -0
- package/jest.isolated-projects.js +130 -0
- package/jest.projects.js +41 -136
- package/lib/api/api-response-errors.js +124 -0
- package/lib/api/governance-scenario-pack.api.js +159 -0
- package/lib/api/index.js +43 -3
- package/lib/api/lifecycle.api.js +77 -0
- package/lib/api/pipeline.api.js +3 -1
- package/lib/api/records-bulk.api.js +14 -1
- package/lib/api/validation-runner.js +3 -1
- package/lib/app/deploy-config.js +4 -1
- package/lib/app/down.js +20 -1
- package/lib/app/push.js +4 -5
- package/lib/app/register.js +7 -4
- package/lib/app/run-env-compose.js +84 -30
- package/lib/build/index.js +5 -0
- package/lib/certification/sync-system-certification.js +13 -6
- package/lib/cli/index.js +4 -0
- package/lib/cli/infra-guided-auth.js +109 -0
- package/lib/cli/infra-guided.js +84 -81
- package/lib/cli/setup-app.help.js +0 -31
- package/lib/cli/setup-app.js +28 -2
- package/lib/cli/setup-app.test-commands.js +5 -57
- package/lib/cli/setup-auth.js +25 -0
- package/lib/cli/setup-certification-commands.js +147 -0
- package/lib/cli/setup-certification.help.js +141 -0
- package/lib/cli/setup-environment.js +19 -2
- package/lib/cli/setup-external-system-help.js +62 -0
- package/lib/cli/setup-external-system.js +25 -3
- package/lib/cli/setup-infra.js +9 -1
- package/lib/cli/setup-platform.js +6 -1
- package/lib/cli/setup-secrets.js +33 -1
- package/lib/cli/setup-utility-help-after.js +22 -2
- package/lib/cli/setup-utility-repair.js +198 -0
- package/lib/cli/setup-utility-resolve.js +40 -4
- package/lib/cli/setup-utility-validate.js +197 -0
- package/lib/cli/setup-utility.js +4 -239
- package/lib/commands/auth-status-client-tokens.js +214 -0
- package/lib/commands/auth-status-dataplane-version.js +58 -20
- package/lib/commands/auth-status.js +41 -47
- package/lib/commands/datasource-test-trust-cli.js +27 -23
- package/lib/commands/datasource-unified-test-cli.js +6 -3
- package/lib/commands/datasource-unified-test-cli.options.js +5 -1
- package/lib/commands/datasource.js +7 -7
- package/lib/commands/dev-show-display.js +8 -1
- package/lib/commands/governance-cli-shared.js +94 -0
- package/lib/commands/governance-command-action.js +132 -0
- package/lib/commands/governance-download-external.js +34 -0
- package/lib/commands/governance-generate-external.js +71 -0
- package/lib/commands/governance-upload-external.js +36 -0
- package/lib/commands/governance-verify-external.js +64 -0
- package/lib/commands/governance.js +144 -0
- package/lib/commands/lifecycle-command-action.js +123 -0
- package/lib/commands/lifecycle-external.js +268 -0
- package/lib/commands/login.js +11 -2
- package/lib/commands/repair-auth-apply.js +127 -0
- package/lib/commands/repair-changed-files.js +41 -0
- package/lib/commands/repair-datasource-keys.js +4 -1
- package/lib/commands/repair-datasource-run.js +59 -0
- package/lib/commands/repair-datasource-test-payload.js +259 -0
- package/lib/commands/repair-datasource.js +1 -51
- package/lib/commands/repair-env-template.js +64 -13
- package/lib/commands/repair-external-run.js +216 -0
- package/lib/commands/repair-openapi-sync.js +23 -0
- package/lib/commands/repair-persist.js +4 -0
- package/lib/commands/repair-rbac.js +52 -1
- package/lib/commands/repair-steps.js +145 -0
- package/lib/commands/repair.js +29 -296
- package/lib/commands/secrets-get.js +184 -0
- package/lib/commands/secrets-remove-all.js +3 -1
- package/lib/commands/setup-image-refresh.js +146 -0
- package/lib/commands/setup-modes.js +158 -162
- package/lib/commands/setup-platform-auth.js +76 -0
- package/lib/commands/setup-prompts-admin.js +244 -0
- package/lib/commands/setup-prompts-platform-mode.js +67 -0
- package/lib/commands/setup-prompts-validators.js +36 -0
- package/lib/commands/setup-prompts.js +24 -76
- package/lib/commands/setup-run-context.js +45 -0
- package/lib/commands/setup-spinners.js +39 -0
- package/lib/commands/setup.js +27 -5
- package/lib/commands/teardown.js +39 -0
- package/lib/commands/test-e2e-external.js +24 -8
- package/lib/commands/test-governance-external.js +3 -24
- package/lib/commands/test-trust-external.js +20 -21
- package/lib/commands/up-common.js +25 -13
- package/lib/commands/up-dataplane.js +50 -13
- package/lib/commands/upload-publish-context.js +75 -0
- package/lib/commands/upload-sidecar-followups.js +89 -0
- package/lib/commands/upload.js +90 -108
- package/lib/commands/verify-governance-combined-external.js +61 -0
- package/lib/commands/verify-governance-command-action.js +64 -0
- package/lib/commands/verify-governance-db.js +102 -0
- package/lib/commands/verify-governance-external.js +77 -0
- package/lib/commands/verify-governance-result-builders.js +71 -0
- package/lib/commands/verify-operations-command-action.js +68 -0
- package/lib/commands/verify-operations-external.js +268 -0
- package/lib/commands/verify-operations-persist.js +75 -0
- package/lib/commands/verify-operations-step-plan.js +68 -0
- package/lib/commands/verify-operations-steps.js +111 -0
- package/lib/commands/verify-trust-command-action.js +70 -0
- package/lib/commands/verify-trust-external.js +67 -0
- package/lib/commands/wizard-config-normalizer.js +43 -23
- package/lib/commands/wizard-core.js +2 -0
- package/lib/core/admin-password-generator.js +38 -0
- package/lib/core/admin-secrets-bundle.js +111 -0
- package/lib/core/admin-secrets-env-overlay.js +62 -0
- package/lib/core/admin-secrets.js +32 -4
- package/lib/core/config-attach-extensions.js +3 -0
- package/lib/core/config-platform-controller.js +44 -0
- package/lib/core/config-setup-installation-profile.js +42 -0
- package/lib/core/config-setup-platform-mode.js +46 -0
- package/lib/core/config.js +46 -36
- package/lib/core/secrets-admin-env.js +6 -2
- package/lib/core/secrets-ensure.js +4 -0
- package/lib/core/secrets-env-content.js +54 -48
- package/lib/core/secrets-env-write.js +10 -1
- package/lib/core/secrets-infra-placeholder-sync.js +2 -0
- package/lib/core/setup-installation-profile.js +43 -0
- package/lib/core/setup-platform-mode.js +55 -0
- package/lib/datasource/agent-trust-debug-log.js +1 -1
- package/lib/datasource/agent-trust-run.js +5 -3
- package/lib/datasource/audit-evidence-extract-debug.js +30 -18
- package/lib/datasource/audit-evidence-extract.js +7 -4
- package/lib/datasource/audit-evidence-verify-matrix.js +54 -26
- package/lib/datasource/bulk-loader-run-batches.js +15 -3
- package/lib/datasource/datasource-load-export-context.js +3 -0
- package/lib/datasource/log-cleaner.js +2 -2
- package/lib/datasource/log-viewer.js +8 -8
- package/lib/datasource/test-e2e.js +4 -1
- package/lib/datasource/unified-validation-run-body.js +3 -1
- package/lib/datasource/unified-validation-run.js +5 -2
- package/lib/deployment/environment-poll-status.js +138 -0
- package/lib/deployment/environment.js +42 -34
- package/lib/external-system/download.js +21 -20
- package/lib/external-system/generator.js +5 -1
- package/lib/external-system/integration-auth-context.js +45 -0
- package/lib/external-system/test-auth.js +12 -5
- package/lib/external-system/test.js +6 -6
- package/lib/generator/external-controller-manifest.js +15 -0
- package/lib/governance/governance-cli-errors.js +96 -0
- package/lib/governance/governance-download-display.js +38 -0
- package/lib/governance/governance-run-display.js +215 -25
- package/lib/infrastructure/admin-secrets-apply.js +84 -0
- package/lib/infrastructure/admin-secrets-ensure.js +130 -0
- package/lib/infrastructure/admin-secrets-resolve.js +95 -0
- package/lib/infrastructure/admin-secrets-sync.js +91 -0
- package/lib/infrastructure/helpers.js +23 -129
- package/lib/infrastructure/index.js +5 -6
- package/lib/infrastructure/orphan-infra-docker-teardown.js +11 -5
- package/lib/lifecycle/certification-progress.js +231 -0
- package/lib/lifecycle/dataplane-ui-url.js +145 -0
- package/lib/lifecycle/exit-codes.js +24 -0
- package/lib/lifecycle/json-envelope.js +25 -0
- package/lib/lifecycle/lifecycle-governance-options.js +51 -0
- package/lib/lifecycle/logger-quiet.js +29 -0
- package/lib/lifecycle/open-api-published.js +59 -0
- package/lib/lifecycle/operations-readiness-metrics.js +101 -0
- package/lib/lifecycle/product-model.js +196 -0
- package/lib/lifecycle/report-display.js +471 -0
- package/lib/lifecycle/scenario-upload.js +191 -0
- package/lib/lifecycle/verify-step-progress.js +87 -0
- package/lib/parameters/infra-kv-discovery.js +27 -2
- package/lib/protection/auth-context.js +10 -4
- package/lib/schema/application-schema.json +1 -1
- package/lib/schema/datasource-test-run.schema.json +89 -6
- package/lib/schema/external-datasource.schema.json +2636 -2622
- package/lib/schema/external-system.schema.json +1089 -761
- package/lib/schema/infra.parameter.yaml +45 -0
- package/lib/schema/type/credential-field-registry.json +103 -97
- package/lib/utils/aifabrix-test-runtime-guard.js +125 -0
- package/lib/utils/api.js +55 -31
- package/lib/utils/app-relative-path-env.js +112 -0
- package/lib/utils/cli-secrets-error-format.js +83 -21
- package/lib/utils/cli-utils.js +16 -4
- package/lib/utils/compose-generate-docker-compose.js +4 -0
- package/lib/utils/compose-traefik-ingress-base.js +16 -1
- package/lib/utils/config-secrets-path.js +46 -0
- package/lib/utils/controller-url.js +88 -1
- package/lib/utils/credential-secrets-env.js +137 -9
- package/lib/utils/dataplane-resolver.js +129 -2
- package/lib/utils/declarative-url-ports.js +3 -3
- package/lib/utils/deployment-auth-mode.js +41 -0
- package/lib/utils/developer-volume-id.js +33 -0
- package/lib/utils/env-copy.js +14 -9
- package/lib/utils/env-kv-orphan-lines.js +43 -0
- package/lib/utils/error-formatters/network-errors.js +13 -4
- package/lib/utils/external-env-template.js +1 -0
- package/lib/utils/external-system-readiness-core.js +10 -0
- package/lib/utils/external-system-readiness-display.js +80 -23
- package/lib/utils/health-check.js +4 -1
- package/lib/utils/help-builder.js +11 -8
- package/lib/utils/keycloak-realm-ready.js +101 -0
- package/lib/utils/load-cip-capacity-display-config.js +61 -15
- package/lib/utils/manifest-source-emit.js +2 -0
- package/lib/utils/pipeline-upload-auth-retry.js +82 -0
- package/lib/utils/platform-controller-url.js +156 -0
- package/lib/utils/postgres-platform-bootstrap.js +199 -0
- package/lib/utils/postgres-wipe.js +9 -2
- package/lib/utils/resolve-env-mode.js +47 -0
- package/lib/utils/secrets-generator.js +31 -25
- package/lib/utils/secrets-missing-error.js +164 -0
- package/lib/utils/service-url-label.js +81 -0
- package/lib/utils/setup-quiet-output.js +25 -0
- package/lib/utils/template-helpers.js +15 -0
- package/lib/utils/token-manager-client-credentials.js +201 -0
- package/lib/utils/token-manager-messages.js +8 -0
- package/lib/utils/token-manager-refresh.js +12 -2
- package/lib/utils/token-manager.js +86 -105
- package/lib/utils/upload-pipeline-ui.js +12 -5
- package/lib/utils/upload-sync-options.js +42 -0
- package/lib/utils/url-declarative-public-base.js +21 -3
- package/lib/utils/url-declarative-vdir-inactive-env.js +14 -4
- package/lib/utils/validation-poll-ui.js +8 -0
- package/lib/utils/validation-run-stage.js +45 -0
- package/package.json +1 -1
- package/templates/applications/dataplane/application.yaml +2 -1
- package/templates/applications/dataplane/env.template +24 -1
- package/templates/applications/miso-controller/env.template +16 -0
- package/templates/external-system/README.md.hbs +74 -17
- package/templates/python/docker-compose.hbs +19 -18
- package/templates/typescript/docker-compose.hbs +18 -17
- package/lib/commands/test-governance-command-action.js +0 -55
- package/lib/commands/test-trust-command-action.js +0 -118
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recreate platform Postgres databases and roles after setup "wipe data".
|
|
3
|
+
*
|
|
4
|
+
* Infra init scripts run only on first volume creation; dropping DBs in a
|
|
5
|
+
* running Postgres container requires explicit bootstrap before Keycloak/Miso start.
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview Platform database bootstrap after postgres wipe
|
|
8
|
+
* @author AI Fabrix Team
|
|
9
|
+
* @version 1.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const config = require('../core/config');
|
|
15
|
+
const adminSecrets = require('../core/admin-secrets');
|
|
16
|
+
const logger = require('./logger');
|
|
17
|
+
const chalk = require('chalk');
|
|
18
|
+
const { successGlyph } = require('./cli-test-layout-chalk');
|
|
19
|
+
const { getPostgresContainerName } = require('./postgres-wipe');
|
|
20
|
+
const dockerExec = require('./docker-exec');
|
|
21
|
+
const { isSetupQuietOutput } = require('./setup-quiet-output');
|
|
22
|
+
|
|
23
|
+
const SUPERUSER_ROLE = 'pgadmin';
|
|
24
|
+
|
|
25
|
+
/** @type {{ dbName: string, secretKey: string }[]} */
|
|
26
|
+
const PLATFORM_DATABASE_SLOTS = [
|
|
27
|
+
{ dbName: 'keycloak', secretKey: 'databases-keycloak-0-passwordKeyVault' },
|
|
28
|
+
{ dbName: 'miso', secretKey: 'databases-miso-controller-0-passwordKeyVault' },
|
|
29
|
+
{ dbName: 'miso-logs', secretKey: 'databases-miso-controller-1-passwordKeyVault' },
|
|
30
|
+
{ dbName: 'dataplane', secretKey: 'databases-dataplane-0-passwordKeyVault' },
|
|
31
|
+
{ dbName: 'dataplane-vector', secretKey: 'databases-dataplane-1-passwordKeyVault' },
|
|
32
|
+
{ dbName: 'dataplane-logs', secretKey: 'databases-dataplane-2-passwordKeyVault' },
|
|
33
|
+
{ dbName: 'dataplane-records', secretKey: 'databases-dataplane-3-passwordKeyVault' }
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {string} dbName
|
|
38
|
+
* @returns {string}
|
|
39
|
+
*/
|
|
40
|
+
function pgRoleName(dbName) {
|
|
41
|
+
return `${String(dbName).replace(/-/g, '_')}_user`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {string} s
|
|
46
|
+
* @returns {string}
|
|
47
|
+
*/
|
|
48
|
+
function escapePgString(s) {
|
|
49
|
+
if (s === null || s === undefined || typeof s !== 'string') return '';
|
|
50
|
+
return s.replace(/'/g, '\'\'');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {string} urlOrPassword
|
|
55
|
+
* @returns {string|null}
|
|
56
|
+
*/
|
|
57
|
+
function extractPasswordFromUrlOrValue(urlOrPassword) {
|
|
58
|
+
if (typeof urlOrPassword !== 'string' || urlOrPassword.length === 0) return null;
|
|
59
|
+
if (!urlOrPassword.includes('://')) return urlOrPassword;
|
|
60
|
+
try {
|
|
61
|
+
const u = new URL(
|
|
62
|
+
urlOrPassword.replace(/\$\{DB_HOST\}/g, 'postgres').replace(/\$\{DB_PORT\}/g, '5432')
|
|
63
|
+
);
|
|
64
|
+
return u.password || null;
|
|
65
|
+
} catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Escape SQL for embedding in a double-quoted shell argument (-c "...").
|
|
72
|
+
* Bash expands `$` and `$$` (PID) inside double quotes; backslash-escape every `$`.
|
|
73
|
+
*
|
|
74
|
+
* @param {string} s
|
|
75
|
+
* @returns {string}
|
|
76
|
+
*/
|
|
77
|
+
function escapeShellDoubleQuoted(s) {
|
|
78
|
+
return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\$/g, '\\$');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @async
|
|
83
|
+
* @param {string} container
|
|
84
|
+
* @param {string} sql
|
|
85
|
+
* @param {string} adminPassword
|
|
86
|
+
* @returns {Promise<void>}
|
|
87
|
+
*/
|
|
88
|
+
async function runPsqlCommand(container, sql, adminPassword, database = 'postgres') {
|
|
89
|
+
const escapedSql = escapeShellDoubleQuoted(sql);
|
|
90
|
+
const cmd =
|
|
91
|
+
`docker exec -e PGPASSWORD -i ${container} psql -U ${SUPERUSER_ROLE} -d ${database} ` +
|
|
92
|
+
`-v ON_ERROR_STOP=1 -c "${escapedSql}"`;
|
|
93
|
+
await dockerExec.execWithDockerEnv(cmd, { env: { PGPASSWORD: adminPassword } });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @async
|
|
98
|
+
* @param {string} container
|
|
99
|
+
* @param {string} dbName
|
|
100
|
+
* @param {string} role
|
|
101
|
+
* @param {string} password
|
|
102
|
+
* @param {string} adminPassword
|
|
103
|
+
* @returns {Promise<void>}
|
|
104
|
+
*/
|
|
105
|
+
async function ensureRoleAndDatabase(container, dbName, role, password, adminPassword) {
|
|
106
|
+
if (!/^[A-Za-z_][A-Za-z0-9_-]*$/.test(dbName) || !/^[A-Za-z_][A-Za-z0-9_-]*$/.test(role)) {
|
|
107
|
+
throw new Error(`Unsafe database or role name: ${dbName} / ${role}`);
|
|
108
|
+
}
|
|
109
|
+
const pwd = escapePgString(password);
|
|
110
|
+
await runPsqlCommand(
|
|
111
|
+
container,
|
|
112
|
+
`DO $$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '${role}') THEN CREATE USER "${role}" WITH PASSWORD '${pwd}'; ELSE ALTER USER "${role}" WITH PASSWORD '${pwd}'; END IF; END $$;`,
|
|
113
|
+
adminPassword
|
|
114
|
+
);
|
|
115
|
+
const exists = await dockerExec.execWithDockerEnv(
|
|
116
|
+
`docker exec -e PGPASSWORD -i ${container} psql -U ${SUPERUSER_ROLE} -d postgres -tAc "SELECT 1 FROM pg_database WHERE datname = '${dbName}'"`,
|
|
117
|
+
{ env: { PGPASSWORD: adminPassword } }
|
|
118
|
+
);
|
|
119
|
+
const hasDb = String((exists && exists.stdout) || '').trim() === '1';
|
|
120
|
+
if (!hasDb) {
|
|
121
|
+
await runPsqlCommand(
|
|
122
|
+
container,
|
|
123
|
+
`CREATE DATABASE "${dbName}" OWNER "${role}";`,
|
|
124
|
+
adminPassword
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
await runPsqlCommand(
|
|
128
|
+
container,
|
|
129
|
+
`GRANT ALL ON SCHEMA public TO "${role}"; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO "${role}";`,
|
|
130
|
+
adminPassword,
|
|
131
|
+
dbName
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @async
|
|
137
|
+
* @param {string} container
|
|
138
|
+
* @param {{ dbName: string, secretKey: string }} slot
|
|
139
|
+
* @param {Object} secrets
|
|
140
|
+
* @param {string} adminPassword
|
|
141
|
+
* @returns {Promise<string>}
|
|
142
|
+
*/
|
|
143
|
+
async function ensurePlatformDatabaseSlot(container, slot, secrets, adminPassword) {
|
|
144
|
+
const raw = secrets[slot.secretKey];
|
|
145
|
+
const password = extractPasswordFromUrlOrValue(raw);
|
|
146
|
+
if (!password || !password.trim()) {
|
|
147
|
+
throw new Error(
|
|
148
|
+
`Secret ${slot.secretKey} is missing or empty. Run "aifabrix up-infra" to ensure infra secrets.`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
const role = pgRoleName(slot.dbName);
|
|
152
|
+
await ensureRoleAndDatabase(container, slot.dbName, role, password.trim(), adminPassword);
|
|
153
|
+
if (!isSetupQuietOutput()) {
|
|
154
|
+
logger.log(chalk.gray(` ${successGlyph()} Ensured database ${slot.dbName} (${role})`));
|
|
155
|
+
}
|
|
156
|
+
return slot.dbName;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @async
|
|
161
|
+
* @returns {Promise<{ container: string, adminPassword: string, secrets: Object }>}
|
|
162
|
+
*/
|
|
163
|
+
async function resolveBootstrapContext() {
|
|
164
|
+
const devId = await config.getDeveloperId();
|
|
165
|
+
const container = getPostgresContainerName(devId);
|
|
166
|
+
const admin = await adminSecrets.readAndDecryptAdminSecrets();
|
|
167
|
+
const adminPassword = admin && admin.POSTGRES_PASSWORD;
|
|
168
|
+
if (!adminPassword) {
|
|
169
|
+
throw new Error('POSTGRES_PASSWORD not found in admin-secrets.env. Run "aifabrix up-infra" first.');
|
|
170
|
+
}
|
|
171
|
+
const secretsEnsure = require('../core/secrets-ensure');
|
|
172
|
+
await secretsEnsure.ensureInfraSecrets();
|
|
173
|
+
const { loadSecrets } = require('../core/secrets');
|
|
174
|
+
const secrets = await loadSecrets(undefined);
|
|
175
|
+
return { container, adminPassword, secrets };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Recreate platform DB users and databases from resolved kv secrets.
|
|
180
|
+
*
|
|
181
|
+
* @async
|
|
182
|
+
* @returns {Promise<string[]>} database names ensured
|
|
183
|
+
*/
|
|
184
|
+
async function bootstrapPlatformPostgresDatabases() {
|
|
185
|
+
const { container, adminPassword, secrets } = await resolveBootstrapContext();
|
|
186
|
+
const ensured = [];
|
|
187
|
+
for (const slot of PLATFORM_DATABASE_SLOTS) {
|
|
188
|
+
ensured.push(await ensurePlatformDatabaseSlot(container, slot, secrets, adminPassword));
|
|
189
|
+
}
|
|
190
|
+
return ensured;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
module.exports = {
|
|
194
|
+
PLATFORM_DATABASE_SLOTS,
|
|
195
|
+
bootstrapPlatformPostgresDatabases,
|
|
196
|
+
pgRoleName,
|
|
197
|
+
extractPasswordFromUrlOrValue,
|
|
198
|
+
escapeShellDoubleQuoted
|
|
199
|
+
};
|
|
@@ -22,6 +22,7 @@ const dockerExec = require('./docker-exec');
|
|
|
22
22
|
const logger = require('./logger');
|
|
23
23
|
const chalk = require('chalk');
|
|
24
24
|
const { successGlyph } = require('./cli-test-layout-chalk');
|
|
25
|
+
const { isSetupQuietOutput } = require('./setup-quiet-output');
|
|
25
26
|
|
|
26
27
|
/** Roles that must never be dropped (Postgres-managed predefined roles). */
|
|
27
28
|
const PROTECTED_ROLES = new Set([
|
|
@@ -190,15 +191,21 @@ async function wipePostgresData() {
|
|
|
190
191
|
}
|
|
191
192
|
|
|
192
193
|
const databases = await listUserDatabases(container, password);
|
|
194
|
+
const quiet = isSetupQuietOutput();
|
|
195
|
+
|
|
193
196
|
for (const dbName of databases) {
|
|
194
197
|
await dropDatabase(container, dbName, password);
|
|
195
|
-
|
|
198
|
+
if (!quiet) {
|
|
199
|
+
logger.log(chalk.gray(` ${successGlyph()} Dropped database ${dbName}`));
|
|
200
|
+
}
|
|
196
201
|
}
|
|
197
202
|
|
|
198
203
|
const roles = await listDropableRoles(container, password);
|
|
199
204
|
for (const role of roles) {
|
|
200
205
|
await dropRole(container, role, password);
|
|
201
|
-
|
|
206
|
+
if (!quiet) {
|
|
207
|
+
logger.log(chalk.gray(` ${successGlyph()} Dropped role ${role}`));
|
|
208
|
+
}
|
|
202
209
|
}
|
|
203
210
|
|
|
204
211
|
return { databases, roles };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chooses `local` vs `docker` for `generateEnvFile` / `resolve` (integration → local, no Docker rewrite).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Resolve environment mode for env generation
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
/** Platform builder apps that run in Docker and need docker host/port transforms on resolve. */
|
|
14
|
+
const PLATFORM_DOCKER_APP_KEYS = new Set(['keycloak', 'miso-controller', 'dataplane']);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {string} appPath - Resolved app directory from getResolveAppPath
|
|
18
|
+
* @returns {boolean}
|
|
19
|
+
*/
|
|
20
|
+
function isIntegrationAppPath(appPath) {
|
|
21
|
+
if (!appPath || typeof appPath !== 'string') return false;
|
|
22
|
+
const normalized = path.resolve(appPath);
|
|
23
|
+
const marker = `${path.sep}integration${path.sep}`;
|
|
24
|
+
return normalized.includes(marker);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {string} appPath - Resolved application directory
|
|
29
|
+
* @param {string} [appName] - App or system key
|
|
30
|
+
* @returns {'local'|'docker'}
|
|
31
|
+
*/
|
|
32
|
+
function resolveGenerateEnvEnvironment(appPath, appName) {
|
|
33
|
+
if (isIntegrationAppPath(appPath)) {
|
|
34
|
+
return 'local';
|
|
35
|
+
}
|
|
36
|
+
const key = String(appName || '').trim().toLowerCase();
|
|
37
|
+
if (PLATFORM_DOCKER_APP_KEYS.has(key)) {
|
|
38
|
+
return 'docker';
|
|
39
|
+
}
|
|
40
|
+
return 'docker';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = {
|
|
44
|
+
PLATFORM_DOCKER_APP_KEYS,
|
|
45
|
+
isIntegrationAppPath,
|
|
46
|
+
resolveGenerateEnvEnvironment
|
|
47
|
+
};
|
|
@@ -77,42 +77,44 @@ function loadYamlTolerantOfDuplicateKeys(content) {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
/**
|
|
80
|
-
*
|
|
81
|
-
* @param {
|
|
80
|
+
* True when secrets map already has a non-empty value for a kv path (flat or nested).
|
|
81
|
+
* @param {Object} secrets
|
|
82
|
+
* @param {string} pathStr - Path after kv:// (e.g. hubspot-demo/token)
|
|
82
83
|
* @returns {boolean}
|
|
83
84
|
*/
|
|
84
|
-
function
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
function secretPathExistsInStore(secrets, pathStr) {
|
|
86
|
+
if (!secrets || typeof secrets !== 'object' || !pathStr) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
const direct = secrets[pathStr];
|
|
90
|
+
if (direct !== undefined && direct !== null && String(direct).trim() !== '') {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
if (!pathStr.includes('/')) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
let current = secrets;
|
|
97
|
+
for (const segment of pathStr.split('/')) {
|
|
98
|
+
if (current === null || current === undefined || typeof current !== 'object' || !(segment in current)) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
current = current[segment];
|
|
102
|
+
}
|
|
103
|
+
return current !== undefined && current !== null && String(current).trim() !== '';
|
|
87
104
|
}
|
|
88
105
|
|
|
89
106
|
/**
|
|
90
|
-
* Finds missing secret keys from template (skips commented and empty lines)
|
|
107
|
+
* Finds missing secret keys from template (skips commented and empty lines).
|
|
108
|
+
* Uses full kv:// paths (e.g. hubspot-demo/token), aligned with collectMissingSecrets.
|
|
91
109
|
* @function findMissingSecretKeys
|
|
92
110
|
* @param {string} envTemplate - Environment template content
|
|
93
111
|
* @param {Object} existingSecrets - Existing secrets object
|
|
94
112
|
* @returns {string[]} Array of missing secret keys
|
|
95
113
|
*/
|
|
96
114
|
function findMissingSecretKeys(envTemplate, existingSecrets) {
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
const lines = envTemplate.split('\n');
|
|
101
|
-
|
|
102
|
-
for (const line of lines) {
|
|
103
|
-
if (isCommentOrEmptyLine(line)) continue;
|
|
104
|
-
let match;
|
|
105
|
-
kvPattern.lastIndex = 0;
|
|
106
|
-
while ((match = kvPattern.exec(line)) !== null) {
|
|
107
|
-
const secretKey = match[1];
|
|
108
|
-
if (!seenKeys.has(secretKey) && !(secretKey in existingSecrets)) {
|
|
109
|
-
missingKeys.push(secretKey);
|
|
110
|
-
seenKeys.add(secretKey);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return missingKeys;
|
|
115
|
+
const { collectUniqueKvPathStrings } = require('./secrets-kv-refs');
|
|
116
|
+
const paths = collectUniqueKvPathStrings(envTemplate);
|
|
117
|
+
return paths.filter(pathStr => !secretPathExistsInStore(existingSecrets, pathStr));
|
|
116
118
|
}
|
|
117
119
|
|
|
118
120
|
/**
|
|
@@ -275,6 +277,8 @@ function loadExistingSecrets(resolvedPath) {
|
|
|
275
277
|
* @throws {Error} If save fails
|
|
276
278
|
*/
|
|
277
279
|
function saveSecretsFile(resolvedPath, secrets) {
|
|
280
|
+
const { assertWritableSecretsPathForTests } = require('./aifabrix-test-runtime-guard');
|
|
281
|
+
assertWritableSecretsPathForTests(resolvedPath);
|
|
278
282
|
const dir = path.dirname(resolvedPath);
|
|
279
283
|
if (!fs.existsSync(dir)) {
|
|
280
284
|
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
@@ -356,6 +360,8 @@ function appendSecretsToFile(resolvedPath, secrets) {
|
|
|
356
360
|
if (!secrets || typeof secrets !== 'object' || Object.keys(secrets).length === 0) {
|
|
357
361
|
return;
|
|
358
362
|
}
|
|
363
|
+
const { assertWritableSecretsPathForTests } = require('./aifabrix-test-runtime-guard');
|
|
364
|
+
assertWritableSecretsPathForTests(resolvedPath);
|
|
359
365
|
const dir = path.dirname(resolvedPath);
|
|
360
366
|
if (!fs.existsSync(dir)) {
|
|
361
367
|
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build missing-secrets errors with remediation hints (no secret values).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Missing kv:// error message for resolve and env generation
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const { formatMissingSecretsFileInfo } = require('./secrets-helpers');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {string} line
|
|
15
|
+
* @returns {boolean}
|
|
16
|
+
*/
|
|
17
|
+
function isCommentOrEmptyLine(line) {
|
|
18
|
+
const t = String(line).trim();
|
|
19
|
+
return t === '' || t.startsWith('#');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {string} kvRef - e.g. kv://hubspot-demo/clientSecret
|
|
24
|
+
* @returns {string}
|
|
25
|
+
*/
|
|
26
|
+
function kvRefToSecretSetKey(kvRef) {
|
|
27
|
+
const s = String(kvRef || '').trim();
|
|
28
|
+
return s.replace(/^kv:\/\//i, '');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {string} envTemplate
|
|
33
|
+
* @param {string} kvRef
|
|
34
|
+
* @returns {string|null}
|
|
35
|
+
*/
|
|
36
|
+
function findActiveEnvLineForKvRef(envTemplate, kvRef) {
|
|
37
|
+
if (!envTemplate || !kvRef) return null;
|
|
38
|
+
const needle = String(kvRef).trim();
|
|
39
|
+
for (const line of envTemplate.split('\n')) {
|
|
40
|
+
if (isCommentOrEmptyLine(line)) continue;
|
|
41
|
+
if (line.includes(needle)) return line.trim();
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const OAUTH_STALE_KV_PATH_RE = /\/(clientId|clientSecret|tokenUrl|authorizationUrl)$/i;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @param {string[]} missing
|
|
50
|
+
* @returns {boolean}
|
|
51
|
+
*/
|
|
52
|
+
function hasStaleOAuthTemplatePaths(missing) {
|
|
53
|
+
return missing.some((ref) => OAUTH_STALE_KV_PATH_RE.test(String(ref)));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {string} key
|
|
58
|
+
* @param {Object} secrets
|
|
59
|
+
* @returns {boolean}
|
|
60
|
+
*/
|
|
61
|
+
function isPopulatedSecretKey(key, secrets) {
|
|
62
|
+
const value = secrets[key];
|
|
63
|
+
return value !== undefined && value !== null && String(value).trim() !== '';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Other populated secret paths that share the same path segment after the namespace.
|
|
68
|
+
*
|
|
69
|
+
* @param {string} pathStr - Secret path without kv:// (e.g. my-app-e2e/clientId)
|
|
70
|
+
* @param {Object} secrets
|
|
71
|
+
* @returns {string[]}
|
|
72
|
+
*/
|
|
73
|
+
function findPopulatedAlternateKvPaths(pathStr, secrets) {
|
|
74
|
+
if (!secrets || typeof secrets !== 'object' || !pathStr) {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
const slash = pathStr.indexOf('/');
|
|
78
|
+
if (slash <= 0) {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
const missingNamespace = pathStr.slice(0, slash);
|
|
82
|
+
const segment = pathStr.slice(slash + 1);
|
|
83
|
+
return Object.keys(secrets).filter((key) => {
|
|
84
|
+
if (typeof key !== 'string' || !key.includes('/')) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
const keySlash = key.indexOf('/');
|
|
88
|
+
const namespace = key.slice(0, keySlash);
|
|
89
|
+
const keySegment = key.slice(keySlash + 1);
|
|
90
|
+
return (
|
|
91
|
+
keySegment === segment &&
|
|
92
|
+
namespace !== missingNamespace &&
|
|
93
|
+
isPopulatedSecretKey(key, secrets)
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @param {string[]} missing - kv:// refs
|
|
100
|
+
* @param {Object} [secrets]
|
|
101
|
+
* @param {string} [appName]
|
|
102
|
+
* @returns {string[]}
|
|
103
|
+
*/
|
|
104
|
+
function buildKvNamespaceAlternateHints(missing, secrets, appName) {
|
|
105
|
+
const lines = [];
|
|
106
|
+
const seen = new Set();
|
|
107
|
+
for (const ref of missing) {
|
|
108
|
+
const pathStr = kvRefToSecretSetKey(ref);
|
|
109
|
+
for (const alt of findPopulatedAlternateKvPaths(pathStr, secrets)) {
|
|
110
|
+
const line = `Found kv://${alt} in secrets; env.template references kv://${pathStr}.`;
|
|
111
|
+
if (seen.has(line)) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
seen.add(line);
|
|
115
|
+
lines.push(line);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (lines.length === 0) {
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
const repairTail = appName
|
|
122
|
+
? `Use aifabrix secret set <key> for each missing path, or aifabrix repair ${appName} to align env.template.`
|
|
123
|
+
: 'Use aifabrix secret set <key> for each missing path, or repair to align env.template.';
|
|
124
|
+
lines.push(repairTail);
|
|
125
|
+
return lines;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @param {Object} opts
|
|
130
|
+
* @param {string[]} opts.missing
|
|
131
|
+
* @param {Object|string|null} [opts.secretsFilePaths]
|
|
132
|
+
* @param {string} [opts.appName]
|
|
133
|
+
* @param {string} [opts.envTemplatePath]
|
|
134
|
+
* @param {string} [opts.envTemplate]
|
|
135
|
+
* @param {Object} [opts.secrets]
|
|
136
|
+
* @returns {string}
|
|
137
|
+
*/
|
|
138
|
+
function buildMissingSecretsErrorMessage(opts) {
|
|
139
|
+
const { missing, secretsFilePaths, appName, envTemplatePath, envTemplate, secrets } = opts;
|
|
140
|
+
const fileInfo = formatMissingSecretsFileInfo(secretsFilePaths);
|
|
141
|
+
const lines = [`Missing secrets: ${missing.join(', ')}`];
|
|
142
|
+
if (fileInfo) lines.push(fileInfo.trim());
|
|
143
|
+
if (envTemplatePath) lines.push(`Env-template-path: ${envTemplatePath}`);
|
|
144
|
+
for (const ref of missing) {
|
|
145
|
+
const templateLine = findActiveEnvLineForKvRef(envTemplate, ref);
|
|
146
|
+
if (templateLine) lines.push(`Env-template-line: ${ref}|${templateLine}`);
|
|
147
|
+
}
|
|
148
|
+
if (appName) lines.push(`App-name: ${appName}`);
|
|
149
|
+
if (hasStaleOAuthTemplatePaths(missing) && appName) {
|
|
150
|
+
lines.push(`Stale-template-hint: oauth-leftover | repair ${appName}`);
|
|
151
|
+
}
|
|
152
|
+
for (const hint of buildKvNamespaceAlternateHints(missing, secrets, appName)) {
|
|
153
|
+
lines.push(`Alternate-kv-hint: ${hint}`);
|
|
154
|
+
}
|
|
155
|
+
lines.push('Remediation-hint: comment-or-delete-template-line | secret-set');
|
|
156
|
+
return `${lines.join('\n')}\n`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
module.exports = {
|
|
160
|
+
kvRefToSecretSetKey,
|
|
161
|
+
findActiveEnvLineForKvRef,
|
|
162
|
+
findPopulatedAlternateKvPaths,
|
|
163
|
+
buildMissingSecretsErrorMessage
|
|
164
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Infer which platform service a request URL targets (CLI error copy).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Service label heuristics for network error messages
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
/** @type {readonly string[]} */
|
|
12
|
+
const DATAPLANE_PATH_PREFIXES = Object.freeze([
|
|
13
|
+
'/api/v1/external',
|
|
14
|
+
'/api/v1/validation',
|
|
15
|
+
'/api/v1/records',
|
|
16
|
+
'/api/v1/wizard',
|
|
17
|
+
'/api/v1/admin',
|
|
18
|
+
'/api/v1/health',
|
|
19
|
+
'/api/v1/dataplane',
|
|
20
|
+
'/api/v1/policy',
|
|
21
|
+
'/api/v1/mcp',
|
|
22
|
+
'/api/v1/ide',
|
|
23
|
+
'/api/v1/knowledgebase'
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
/** @type {readonly string[]} */
|
|
27
|
+
const CONTROLLER_PATH_PREFIXES = Object.freeze([
|
|
28
|
+
'/api/v1/auth',
|
|
29
|
+
'/api/v1/applications',
|
|
30
|
+
'/api/v1/pipeline',
|
|
31
|
+
'/api/v1/environments',
|
|
32
|
+
'/api/v1/deployments',
|
|
33
|
+
'/api/v1/datasources',
|
|
34
|
+
'/api/v1/external-systems',
|
|
35
|
+
'/api/controller'
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {string} url - Full request URL
|
|
40
|
+
* @returns {'dataplane'|'controller'|'service'}
|
|
41
|
+
*/
|
|
42
|
+
function inferServiceLabelFromUrl(url) {
|
|
43
|
+
if (!url || typeof url !== 'string') {
|
|
44
|
+
return 'service';
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const path = new URL(url.trim()).pathname || '';
|
|
48
|
+
for (const prefix of DATAPLANE_PATH_PREFIXES) {
|
|
49
|
+
if (path.startsWith(prefix)) {
|
|
50
|
+
return 'dataplane';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
for (const prefix of CONTROLLER_PATH_PREFIXES) {
|
|
54
|
+
if (path.startsWith(prefix)) {
|
|
55
|
+
return 'controller';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} catch {
|
|
59
|
+
return 'service';
|
|
60
|
+
}
|
|
61
|
+
return 'service';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @param {'dataplane'|'controller'|'service'} label
|
|
66
|
+
* @returns {string}
|
|
67
|
+
*/
|
|
68
|
+
function serviceLabelDisplayName(label) {
|
|
69
|
+
if (label === 'dataplane') {
|
|
70
|
+
return 'dataplane';
|
|
71
|
+
}
|
|
72
|
+
if (label === 'controller') {
|
|
73
|
+
return 'controller';
|
|
74
|
+
}
|
|
75
|
+
return 'service';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = {
|
|
79
|
+
inferServiceLabelFromUrl,
|
|
80
|
+
serviceLabelDisplayName
|
|
81
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Suppress verbose CLI detail during `aifabrix setup` (guided milestones only).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Setup quiet output flag
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @returns {boolean} True while `aifabrix setup` is running
|
|
13
|
+
*/
|
|
14
|
+
function isSetupQuietOutput() {
|
|
15
|
+
try {
|
|
16
|
+
const { isSetupCommandFlow } = require('../commands/setup-run-context');
|
|
17
|
+
return isSetupCommandFlow();
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = {
|
|
24
|
+
isSetupQuietOutput
|
|
25
|
+
};
|
|
@@ -110,6 +110,20 @@ function updateDatabaseConfig(variables, options, appName) {
|
|
|
110
110
|
* @param {Object} options - CLI options
|
|
111
111
|
* @param {Object} config - Final configuration
|
|
112
112
|
*/
|
|
113
|
+
async function patchEnvTemplateRelativePathKey(appPath, appName) {
|
|
114
|
+
const fsSync = require('fs');
|
|
115
|
+
const envTemplatePath = path.join(appPath, 'env.template');
|
|
116
|
+
if (!fsSync.existsSync(envTemplatePath)) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const { renameAppRelativePathEnvKeyInTemplate } = require('./app-relative-path-env');
|
|
120
|
+
const raw = await fs.readFile(envTemplatePath, 'utf8');
|
|
121
|
+
const patched = renameAppRelativePathEnvKeyInTemplate(raw, appName);
|
|
122
|
+
if (patched !== raw) {
|
|
123
|
+
await fs.writeFile(envTemplatePath, patched, 'utf8');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
113
127
|
async function updateTemplateVariables(appPath, appName, options, config) {
|
|
114
128
|
const { resolveApplicationConfigPath } = require('./app-config-resolver');
|
|
115
129
|
const { loadConfigFile, writeConfigFile } = require('./config-format');
|
|
@@ -123,6 +137,7 @@ async function updateTemplateVariables(appPath, appName, options, config) {
|
|
|
123
137
|
updateDatabaseConfig(variables, options, appName);
|
|
124
138
|
|
|
125
139
|
writeConfigFile(variablesPath, variables);
|
|
140
|
+
await patchEnvTemplateRelativePathKey(appPath, appName);
|
|
126
141
|
} catch (error) {
|
|
127
142
|
if (error.message && !error.message.includes('not found')) {
|
|
128
143
|
logger.warn(chalk.yellow(`⚠ Warning: Could not update application config: ${error.message}`));
|