@aifabrix/builder 2.44.5 → 2.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/rules/cli-layout.mdc +8 -4
- package/.cursor/rules/project-rules.mdc +1 -1
- package/README.md +15 -23
- package/integration/hubspot-test/README.md +2 -0
- package/integration/hubspot-test/test.js +5 -3
- package/jest.projects.js +104 -2
- package/lib/api/controller-health.api.js +49 -0
- package/lib/api/dimension-values.api.js +82 -0
- package/lib/api/dimensions.api.js +114 -0
- package/lib/api/external-systems.api.js +1 -0
- package/lib/api/integration-clients.api.js +168 -0
- package/lib/api/types/dimension-values.types.js +28 -0
- package/lib/api/types/dimensions.types.js +31 -0
- package/lib/api/types/integration-clients.types.js +45 -0
- package/lib/api/validation-runner.js +46 -25
- package/lib/app/deploy-config.js +11 -1
- package/lib/app/deploy-status-display.js +3 -3
- package/lib/app/deploy.js +36 -14
- package/lib/app/display.js +15 -11
- package/lib/app/helpers.js +3 -3
- package/lib/app/index.js +3 -3
- package/lib/app/push.js +46 -23
- package/lib/app/register.js +7 -6
- package/lib/app/restart-display.js +126 -0
- package/lib/app/rotate-secret.js +7 -6
- package/lib/app/run-container-start.js +12 -6
- package/lib/app/run-env-compose.js +30 -1
- package/lib/app/run-helpers.js +58 -19
- package/lib/app/run-reload-sync.js +148 -0
- package/lib/app/run-resolve-image.js +51 -1
- package/lib/app/run.js +148 -74
- package/lib/app/show-display.js +7 -0
- package/lib/app/show.js +87 -5
- package/lib/build/index.js +83 -49
- package/lib/cli/doctor-check.js +117 -0
- package/lib/cli/index.js +8 -2
- package/lib/cli/infra-guided.js +460 -0
- package/lib/cli/installation-log-command.js +73 -0
- package/lib/cli/setup-app.js +31 -3
- package/lib/cli/setup-auth.js +98 -27
- package/lib/cli/setup-dev-path-commands.js +50 -3
- package/lib/cli/setup-infra-up-dataplane-action.js +111 -0
- package/lib/cli/setup-infra-up-platform-action.js +131 -0
- package/lib/cli/setup-infra.js +132 -118
- package/lib/cli/setup-integration-client.js +182 -0
- package/lib/cli/setup-parameters.js +21 -2
- package/lib/cli/setup-platform.js +102 -0
- package/lib/cli/setup-secrets.js +18 -6
- package/lib/cli/setup-utility-resolve.js +132 -0
- package/lib/cli/setup-utility.js +143 -84
- package/lib/commands/app-logs.js +81 -33
- package/lib/commands/auth-config.js +116 -18
- package/lib/commands/datasource-capability-dimension-cli.js +128 -0
- package/lib/commands/datasource-capability-output.js +29 -0
- package/lib/commands/datasource-capability-relate-cli.js +140 -0
- package/lib/commands/datasource-capability.js +411 -0
- package/lib/commands/datasource-unified-test-cli.options.js +1 -1
- package/lib/commands/datasource.js +53 -13
- package/lib/commands/dev-down.js +3 -3
- package/lib/commands/dev-infra-gate.js +32 -0
- package/lib/commands/dev-init.js +13 -7
- package/lib/commands/dimension-value.js +179 -0
- package/lib/commands/dimension.js +330 -0
- package/lib/commands/integration-client.js +430 -0
- package/lib/commands/login-device.js +65 -30
- package/lib/commands/login.js +21 -10
- package/lib/commands/parameters-validate.js +78 -13
- package/lib/commands/repair-datasource-auto-rbac.js +166 -0
- package/lib/commands/repair-datasource-keys.js +10 -5
- package/lib/commands/repair-datasource.js +19 -7
- package/lib/commands/repair-env-template.js +4 -1
- package/lib/commands/repair-openapi-sync.js +172 -0
- package/lib/commands/repair-persist.js +102 -0
- package/lib/commands/repair-rbac-extract.js +27 -0
- package/lib/commands/repair-rbac-migrate.js +186 -0
- package/lib/commands/repair-rbac.js +214 -31
- package/lib/commands/repair-system-alignment.js +246 -0
- package/lib/commands/repair-system-permissions.js +168 -0
- package/lib/commands/repair.js +120 -338
- package/lib/commands/secure.js +1 -1
- package/lib/commands/setup-modes.js +468 -0
- package/lib/commands/setup-prompts.js +421 -0
- package/lib/commands/setup.js +254 -0
- package/lib/commands/teardown.js +277 -0
- package/lib/commands/up-common.js +113 -19
- package/lib/commands/up-dataplane.js +44 -19
- package/lib/commands/up-miso.js +18 -18
- package/lib/commands/upload.js +111 -23
- package/lib/commands/wizard-core-helpers.js +14 -11
- package/lib/commands/wizard-core.js +6 -5
- package/lib/commands/wizard-dataplane.js +2 -2
- package/lib/commands/wizard-entity-selection.js +4 -3
- package/lib/commands/wizard-headless.js +2 -1
- package/lib/commands/wizard.js +2 -1
- package/lib/constants/infra-compose-service-names.js +40 -0
- package/lib/core/audit-logger.js +1 -34
- package/lib/core/config-admin-email.js +56 -0
- package/lib/core/config-normalize.js +60 -0
- package/lib/core/config-registered-controller-urls.js +54 -0
- package/lib/core/config.js +33 -50
- package/lib/core/env-reader.js +16 -3
- package/lib/core/secrets-admin-env.js +101 -0
- package/lib/core/secrets-ensure-infra.js +34 -1
- package/lib/core/secrets-ensure.js +88 -66
- package/lib/core/secrets-env-content.js +428 -0
- package/lib/core/secrets-env-declarative-expand.js +170 -0
- package/lib/core/secrets-env-write.js +29 -1
- package/lib/core/secrets-load.js +252 -0
- package/lib/core/secrets-names.js +32 -0
- package/lib/core/secrets.js +17 -757
- package/lib/datasource/capability/basic-exposure.js +76 -0
- package/lib/datasource/capability/capability-diff-slice.js +41 -0
- package/lib/datasource/capability/capability-key.js +34 -0
- package/lib/datasource/capability/capability-resolve.js +172 -0
- package/lib/datasource/capability/capability-storage-keys.js +22 -0
- package/lib/datasource/capability/copy-operations.js +348 -0
- package/lib/datasource/capability/copy-test-payload.js +139 -0
- package/lib/datasource/capability/create-operations.js +235 -0
- package/lib/datasource/capability/dimension-operations.js +151 -0
- package/lib/datasource/capability/dimension-validate.js +219 -0
- package/lib/datasource/capability/json-pointer.js +31 -0
- package/lib/datasource/capability/reference-rewrite.js +51 -0
- package/lib/datasource/capability/relate-operations.js +325 -0
- package/lib/datasource/capability/relate-validate.js +219 -0
- package/lib/datasource/capability/remove-operations.js +275 -0
- package/lib/datasource/capability/run-capability-copy.js +152 -0
- package/lib/datasource/capability/run-capability-diff.js +135 -0
- package/lib/datasource/capability/run-capability-dimension.js +291 -0
- package/lib/datasource/capability/run-capability-edit.js +377 -0
- package/lib/datasource/capability/run-capability-relate.js +193 -0
- package/lib/datasource/capability/run-capability-remove.js +105 -0
- package/lib/datasource/capability/templates/minimal-fetch.json +18 -0
- package/lib/datasource/capability/validate-capability-slice.js +35 -0
- package/lib/datasource/list.js +136 -23
- package/lib/datasource/log-viewer.js +2 -4
- package/lib/datasource/unified-validation-run.js +51 -16
- package/lib/datasource/validate.js +53 -1
- package/lib/deployment/deploy-poll-ui.js +60 -0
- package/lib/deployment/deployer-status.js +29 -3
- package/lib/deployment/deployer.js +48 -30
- package/lib/deployment/environment.js +7 -2
- package/lib/deployment/poll-interval.js +72 -0
- package/lib/deployment/push.js +11 -9
- package/lib/external-system/deploy.js +9 -2
- package/lib/external-system/download.js +61 -32
- package/lib/external-system/sync-deploy-manifest.js +33 -0
- package/lib/infrastructure/index.js +49 -19
- package/lib/infrastructure/orphan-infra-docker-teardown.js +177 -0
- package/lib/internal/node-fs.js +2 -0
- package/lib/parameters/infra-kv-discovery.js +29 -4
- package/lib/parameters/infra-parameter-catalog.js +6 -3
- package/lib/parameters/infra-parameter-validate.js +67 -19
- package/lib/resolvers/datasource-resolver.js +53 -0
- package/lib/resolvers/dimension-file.js +52 -0
- package/lib/resolvers/manifest-resolver.js +133 -0
- package/lib/schema/application-schema.json +4 -0
- package/lib/schema/external-datasource.schema.json +183 -53
- package/lib/schema/external-system.schema.json +23 -10
- package/lib/schema/infra.parameter.yaml +26 -1
- package/lib/schema/wizard-config.schema.json +1 -1
- package/lib/utils/aifabrix-config-dir-walk.js +40 -0
- package/lib/utils/aifabrix-runtime-config-dir.js +26 -3
- package/lib/utils/app-config-resolver.js +24 -1
- package/lib/utils/app-run-containers.js +2 -2
- package/lib/utils/applications-config-defaults.js +206 -0
- package/lib/utils/auth-config-validator.js +2 -12
- package/lib/utils/bash-secret-env.js +59 -0
- package/lib/utils/cli-secrets-error-format.js +78 -0
- package/lib/utils/cli-test-layout-chalk.js +31 -9
- package/lib/utils/cli-utils.js +4 -36
- package/lib/utils/compose-generate-docker-compose.js +111 -6
- package/lib/utils/compose-generator.js +17 -8
- package/lib/utils/controller-url.js +50 -7
- package/lib/utils/datasource-test-run-display.js +8 -0
- package/lib/utils/dev-hosts-helper.js +3 -2
- package/lib/utils/dev-init-ssh-merge.js +2 -1
- package/lib/utils/docker-build.js +17 -9
- package/lib/utils/docker-reload-mount.js +127 -0
- package/lib/utils/env-copy.js +99 -14
- package/lib/utils/env-template.js +5 -1
- package/lib/utils/external-readme.js +71 -2
- package/lib/utils/external-system-local-test-tty.js +3 -2
- package/lib/utils/external-system-readiness-core.js +45 -12
- package/lib/utils/external-system-readiness-deploy-display.js +3 -3
- package/lib/utils/external-system-readiness-display-internals.js +33 -3
- package/lib/utils/external-system-readiness-display.js +10 -1
- package/lib/utils/file-upload.js +40 -3
- package/lib/utils/health-check-db-init.js +107 -0
- package/lib/utils/health-check-public-warn.js +69 -0
- package/lib/utils/health-check-url.js +28 -10
- package/lib/utils/health-check.js +139 -107
- package/lib/utils/help-builder.js +5 -1
- package/lib/utils/image-name.js +34 -7
- package/lib/utils/infra-optional-service-flags.js +69 -0
- package/lib/utils/installation-log-core.js +282 -0
- package/lib/utils/installation-log-record.js +237 -0
- package/lib/utils/installation-log.js +123 -0
- package/lib/utils/integration-file-backup.js +74 -0
- package/lib/utils/log-redaction.js +105 -0
- package/lib/utils/manifest-location.js +164 -0
- package/lib/utils/manifest-source-emit.js +162 -0
- package/lib/utils/mutagen-install.js +30 -3
- package/lib/utils/paths.js +308 -76
- package/lib/utils/postgres-wipe.js +212 -0
- package/lib/utils/register-aifabrix-shell-env.js +15 -0
- package/lib/utils/remote-dev-auth.js +21 -5
- package/lib/utils/remote-docker-env.js +9 -1
- package/lib/utils/remote-secrets-loader.js +49 -4
- package/lib/utils/resolve-docker-image-ref.js +9 -3
- package/lib/utils/run-cli-flags.js +29 -0
- package/lib/utils/secrets-ancestor-paths.js +47 -0
- package/lib/utils/secrets-canonical.js +10 -3
- package/lib/utils/secrets-helpers.js +17 -10
- package/lib/utils/secrets-kv-refs.js +42 -0
- package/lib/utils/secrets-kv-scope.js +19 -2
- package/lib/utils/secrets-materialize-local.js +134 -0
- package/lib/utils/secrets-path.js +26 -13
- package/lib/utils/secrets-utils.js +20 -10
- package/lib/utils/system-builder-root.js +42 -0
- package/lib/utils/url-declarative-public-base.js +80 -12
- package/lib/utils/url-declarative-resolve-build-urls.js +238 -0
- package/lib/utils/url-declarative-resolve-build.js +24 -388
- package/lib/utils/url-declarative-resolve-expand-token.js +189 -0
- package/lib/utils/url-declarative-resolve-load-doc.js +12 -3
- package/lib/utils/url-declarative-resolve-surface-state.js +102 -0
- package/lib/utils/url-declarative-resolve.js +47 -7
- package/lib/utils/url-declarative-runtime-base-path.js +52 -0
- package/lib/utils/url-declarative-vdir-inactive-env.js +2 -1
- package/lib/utils/urls-local-registry-scan.js +103 -0
- package/lib/utils/urls-local-registry.js +158 -76
- package/lib/utils/validation-poll-ui.js +81 -0
- package/lib/utils/validation-run-poll.js +29 -5
- package/lib/utils/with-muted-logger.js +53 -0
- package/package.json +3 -1
- package/templates/applications/dataplane/application.yaml +5 -1
- package/templates/applications/dataplane/rbac.yaml +10 -10
- package/templates/applications/keycloak/env.template +8 -6
- package/templates/applications/miso-controller/application.yaml +9 -0
- package/templates/applications/miso-controller/env.template +27 -29
- package/templates/applications/miso-controller/rbac.yaml +9 -9
- package/templates/external-system/README.md.hbs +83 -123
- package/.npmrc.token +0 -1
- package/.nyc_output/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
- package/.nyc_output/processinfo/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
- package/.nyc_output/processinfo/index.json +0 -1
- package/lib/api/service-users.api.js +0 -150
- package/lib/api/types/service-users.types.js +0 -65
- package/lib/cli/setup-service-user.js +0 -187
- package/lib/commands/service-user.js +0 -429
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Postgres data wipe helper for `aifabrix setup` Mode 2 (Wipe data).
|
|
3
|
+
*
|
|
4
|
+
* Drops every non-template database and every non-superuser role in the
|
|
5
|
+
* developer's running Postgres container, while preserving the volume,
|
|
6
|
+
* the `postgres` superuser, and the admin password (so the post-wipe
|
|
7
|
+
* `up-infra` and platform bootstrap recreate schemas + service users).
|
|
8
|
+
*
|
|
9
|
+
* Uses `docker exec` with the admin password passed via the container's
|
|
10
|
+
* environment (PGPASSWORD) so it is not visible in `ps`.
|
|
11
|
+
*
|
|
12
|
+
* @fileoverview Drop all DBs and roles in dev Postgres for setup Mode 2
|
|
13
|
+
* @author AI Fabrix Team
|
|
14
|
+
* @version 2.0.0
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
'use strict';
|
|
18
|
+
|
|
19
|
+
const config = require('../core/config');
|
|
20
|
+
const adminSecrets = require('../core/admin-secrets');
|
|
21
|
+
const dockerExec = require('./docker-exec');
|
|
22
|
+
const logger = require('./logger');
|
|
23
|
+
const chalk = require('chalk');
|
|
24
|
+
const { successGlyph } = require('./cli-test-layout-chalk');
|
|
25
|
+
|
|
26
|
+
/** Roles that must never be dropped (Postgres-managed predefined roles). */
|
|
27
|
+
const PROTECTED_ROLES = new Set([
|
|
28
|
+
// In this project, infra starts Postgres with POSTGRES_USER=pgadmin (see templates/infra/compose.yaml.hbs),
|
|
29
|
+
// so that role is the superuser we must preserve.
|
|
30
|
+
'pgadmin',
|
|
31
|
+
// Legacy / default superuser name for official Postgres images.
|
|
32
|
+
'postgres',
|
|
33
|
+
'pg_signal_backend',
|
|
34
|
+
'pg_read_server_files',
|
|
35
|
+
'pg_write_server_files',
|
|
36
|
+
'pg_execute_server_program',
|
|
37
|
+
'pg_monitor',
|
|
38
|
+
'pg_read_all_settings',
|
|
39
|
+
'pg_read_all_stats',
|
|
40
|
+
'pg_stat_scan_tables',
|
|
41
|
+
'pg_database_owner',
|
|
42
|
+
'pg_read_all_data',
|
|
43
|
+
'pg_write_all_data',
|
|
44
|
+
'pg_checkpoint',
|
|
45
|
+
'pg_use_reserved_connections',
|
|
46
|
+
'pg_create_subscription'
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
/** Databases that must never be dropped. */
|
|
50
|
+
const PROTECTED_DATABASES = new Set(['postgres', 'template0', 'template1']);
|
|
51
|
+
|
|
52
|
+
/** Superuser role used inside the infra Postgres container. */
|
|
53
|
+
const SUPERUSER_ROLE = 'pgadmin';
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Compute the developer-scoped Postgres container name.
|
|
57
|
+
* Mirrors `getInfraContainerNames` in `lib/utils/infra-status.js`.
|
|
58
|
+
* @param {number|string} devId - Developer ID
|
|
59
|
+
* @returns {string}
|
|
60
|
+
*/
|
|
61
|
+
function getPostgresContainerName(devId) {
|
|
62
|
+
const idNum = typeof devId === 'string' ? parseInt(devId, 10) : devId;
|
|
63
|
+
if (idNum === 0) return 'aifabrix-postgres';
|
|
64
|
+
return `aifabrix-dev${devId}-postgres`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Run `psql -t -A -c <sql>` inside the Postgres container with PGPASSWORD set
|
|
69
|
+
* via `-e PGPASSWORD=...` so the secret never appears on the command line.
|
|
70
|
+
*
|
|
71
|
+
* @async
|
|
72
|
+
* @param {string} container - Container name
|
|
73
|
+
* @param {string} sql - Single SQL statement (no shell metacharacters)
|
|
74
|
+
* @param {string} adminPassword - Postgres superuser password
|
|
75
|
+
* @returns {Promise<string>} stdout (trimmed)
|
|
76
|
+
* @throws {Error} If the docker exec invocation fails
|
|
77
|
+
*/
|
|
78
|
+
async function runPsql(container, sql, adminPassword) {
|
|
79
|
+
if (!container || typeof container !== 'string') {
|
|
80
|
+
throw new Error('Postgres container name is required');
|
|
81
|
+
}
|
|
82
|
+
if (!sql || typeof sql !== 'string') {
|
|
83
|
+
throw new Error('SQL statement is required');
|
|
84
|
+
}
|
|
85
|
+
if (!adminPassword || typeof adminPassword !== 'string') {
|
|
86
|
+
throw new Error('Admin password is required');
|
|
87
|
+
}
|
|
88
|
+
const escapedSql = sql.replace(/"/g, '\\"');
|
|
89
|
+
const cmd = `docker exec -e PGPASSWORD -i ${container} psql -U ${SUPERUSER_ROLE} -d postgres -tAc "${escapedSql}"`;
|
|
90
|
+
const env = { PGPASSWORD: adminPassword };
|
|
91
|
+
const result = (await dockerExec.execWithDockerEnv(cmd, { env })) || {};
|
|
92
|
+
return String(result.stdout || '').trim();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* List user databases (non-template, not in PROTECTED_DATABASES).
|
|
97
|
+
* @async
|
|
98
|
+
* @param {string} container - Container name
|
|
99
|
+
* @param {string} adminPassword - Postgres superuser password
|
|
100
|
+
* @returns {Promise<string[]>} database names
|
|
101
|
+
*/
|
|
102
|
+
async function listUserDatabases(container, adminPassword) {
|
|
103
|
+
const out = await runPsql(
|
|
104
|
+
container,
|
|
105
|
+
'SELECT datname FROM pg_database WHERE datistemplate = false;',
|
|
106
|
+
adminPassword
|
|
107
|
+
);
|
|
108
|
+
return out
|
|
109
|
+
.split('\n')
|
|
110
|
+
.map(s => s.trim())
|
|
111
|
+
.filter(name => name && !PROTECTED_DATABASES.has(name));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* List dropable roles (non-superuser, not in PROTECTED_ROLES).
|
|
116
|
+
* @async
|
|
117
|
+
* @param {string} container - Container name
|
|
118
|
+
* @param {string} adminPassword - Postgres superuser password
|
|
119
|
+
* @returns {Promise<string[]>} role names
|
|
120
|
+
*/
|
|
121
|
+
async function listDropableRoles(container, adminPassword) {
|
|
122
|
+
const out = await runPsql(
|
|
123
|
+
container,
|
|
124
|
+
'SELECT rolname FROM pg_roles WHERE rolsuper = false;',
|
|
125
|
+
adminPassword
|
|
126
|
+
);
|
|
127
|
+
return out
|
|
128
|
+
.split('\n')
|
|
129
|
+
.map(s => s.trim())
|
|
130
|
+
.filter(name => name && !PROTECTED_ROLES.has(name) && !name.startsWith('pg_'));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Drop a single database (forces disconnection of active sessions).
|
|
135
|
+
* @async
|
|
136
|
+
* @param {string} container
|
|
137
|
+
* @param {string} dbName
|
|
138
|
+
* @param {string} adminPassword
|
|
139
|
+
* @returns {Promise<void>}
|
|
140
|
+
*/
|
|
141
|
+
async function dropDatabase(container, dbName, adminPassword) {
|
|
142
|
+
if (!/^[A-Za-z_][A-Za-z0-9_-]*$/.test(dbName)) {
|
|
143
|
+
throw new Error(`Refusing to drop database with unsafe name: ${dbName}`);
|
|
144
|
+
}
|
|
145
|
+
await runPsql(
|
|
146
|
+
container,
|
|
147
|
+
`SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '${dbName}' AND pid <> pg_backend_pid();`,
|
|
148
|
+
adminPassword
|
|
149
|
+
);
|
|
150
|
+
await runPsql(container, `DROP DATABASE IF EXISTS "${dbName}";`, adminPassword);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Drop a single role (REASSIGN OWNED + DROP OWNED first to clear dependencies).
|
|
155
|
+
* @async
|
|
156
|
+
* @param {string} container
|
|
157
|
+
* @param {string} role
|
|
158
|
+
* @param {string} adminPassword
|
|
159
|
+
* @returns {Promise<void>}
|
|
160
|
+
*/
|
|
161
|
+
async function dropRole(container, role, adminPassword) {
|
|
162
|
+
if (!/^[A-Za-z_][A-Za-z0-9_-]*$/.test(role)) {
|
|
163
|
+
throw new Error(`Refusing to drop role with unsafe name: ${role}`);
|
|
164
|
+
}
|
|
165
|
+
await runPsql(
|
|
166
|
+
container,
|
|
167
|
+
`REASSIGN OWNED BY "${role}" TO ${SUPERUSER_ROLE};`,
|
|
168
|
+
adminPassword
|
|
169
|
+
).catch(() => undefined);
|
|
170
|
+
await runPsql(container, `DROP OWNED BY "${role}" CASCADE;`, adminPassword).catch(() => undefined);
|
|
171
|
+
await runPsql(container, `DROP ROLE IF EXISTS "${role}";`, adminPassword);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Drop every non-template database and every non-superuser role in the
|
|
176
|
+
* developer's running Postgres container. Caller must ensure infra is up.
|
|
177
|
+
*
|
|
178
|
+
* @async
|
|
179
|
+
* @function wipePostgresData
|
|
180
|
+
* @returns {Promise<{ databases: string[], roles: string[] }>} dropped names
|
|
181
|
+
* @throws {Error} If admin secrets are missing or psql calls fail
|
|
182
|
+
*/
|
|
183
|
+
async function wipePostgresData() {
|
|
184
|
+
const devId = await config.getDeveloperId();
|
|
185
|
+
const container = getPostgresContainerName(devId);
|
|
186
|
+
const admin = await adminSecrets.readAndDecryptAdminSecrets();
|
|
187
|
+
const password = admin && admin.POSTGRES_PASSWORD;
|
|
188
|
+
if (!password) {
|
|
189
|
+
throw new Error('POSTGRES_PASSWORD not found in admin-secrets.env. Run "aifabrix up-infra" first.');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const databases = await listUserDatabases(container, password);
|
|
193
|
+
for (const dbName of databases) {
|
|
194
|
+
await dropDatabase(container, dbName, password);
|
|
195
|
+
logger.log(chalk.gray(` ${successGlyph()} Dropped database ${dbName}`));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const roles = await listDropableRoles(container, password);
|
|
199
|
+
for (const role of roles) {
|
|
200
|
+
await dropRole(container, role, password);
|
|
201
|
+
logger.log(chalk.gray(` ${successGlyph()} Dropped role ${role}`));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return { databases, roles };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
module.exports = {
|
|
208
|
+
wipePostgresData,
|
|
209
|
+
getPostgresContainerName,
|
|
210
|
+
PROTECTED_DATABASES,
|
|
211
|
+
PROTECTED_ROLES
|
|
212
|
+
};
|
|
@@ -191,8 +191,23 @@ async function registerAifabrixShellEnvFromConfig(getConfigFn, overrides = {}) {
|
|
|
191
191
|
await applyPosixShellEnv(homeAbs, workAbs, overrides);
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
+
/**
|
|
195
|
+
* Build sh export lines for AIFABRIX_HOME / AIFABRIX_WORK from current config (stdout for eval).
|
|
196
|
+
*
|
|
197
|
+
* @async
|
|
198
|
+
* @param {function(): Promise<object>} getConfigFn - Same as config.getConfig
|
|
199
|
+
* @returns {Promise<string>} Lines suitable for `eval "$(aifabrix dev shell-env)"` on bash/zsh
|
|
200
|
+
*/
|
|
201
|
+
async function buildShellEnvExportsFromConfig(getConfigFn) {
|
|
202
|
+
const config = await getConfigFn();
|
|
203
|
+
const homeAbs = absFromConfigRaw(config['aifabrix-home']);
|
|
204
|
+
const workAbs = absFromConfigRaw(config['aifabrix-work']);
|
|
205
|
+
return buildPosixShellEnvBody(homeAbs, workAbs);
|
|
206
|
+
}
|
|
207
|
+
|
|
194
208
|
module.exports = {
|
|
195
209
|
registerAifabrixShellEnvFromConfig,
|
|
210
|
+
buildShellEnvExportsFromConfig,
|
|
196
211
|
buildPosixShellEnvBody,
|
|
197
212
|
buildProfileBlock,
|
|
198
213
|
shSingleQuoted,
|
|
@@ -5,10 +5,25 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const path = require('path');
|
|
8
|
+
const os = require('os');
|
|
8
9
|
const config = require('../core/config');
|
|
9
10
|
const { getCertDir, readClientCertPem, readServerCaPem } = require('./dev-cert-helper');
|
|
10
11
|
const { getConfigDirForPaths, getAifabrixHome, getAifabrixWork } = require('./paths');
|
|
11
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Same rules as {@link module:lib/core/config.expandTilde} / {@link module:lib/core/config.getSecretsPath}.
|
|
15
|
+
* Inline here so `resolveSharedSecretsEndpoint` stays aligned with `getSecretsPath()` even when `getAifabrixSecretsPath()`
|
|
16
|
+
* returns the raw config string (tilde not expanded).
|
|
17
|
+
*/
|
|
18
|
+
function expandConfiguredSecretsTilde(filePath) {
|
|
19
|
+
if (!filePath || typeof filePath !== 'string') return filePath;
|
|
20
|
+
if (filePath === '~') return os.homedir();
|
|
21
|
+
if (filePath.startsWith('~/') || filePath.startsWith('~' + path.sep)) {
|
|
22
|
+
return path.join(os.homedir(), filePath.slice(2));
|
|
23
|
+
}
|
|
24
|
+
return filePath;
|
|
25
|
+
}
|
|
26
|
+
|
|
12
27
|
/**
|
|
13
28
|
* Single API object so resolveSharedSecretsEndpoint and callers share one getRemoteDevAuth
|
|
14
29
|
* (Jest spies and partial mocks work reliably).
|
|
@@ -54,16 +69,17 @@ const remoteDevAuth = {
|
|
|
54
69
|
const trimmed = configuredPath.trim();
|
|
55
70
|
if (!trimmed) return configuredPath;
|
|
56
71
|
if (remoteDevAuth.isRemoteSecretsUrl(trimmed)) return trimmed.replace(/\/+$/, '');
|
|
72
|
+
const expanded = expandConfiguredSecretsTilde(trimmed);
|
|
57
73
|
const auth = await remoteDevAuth.getRemoteDevAuth();
|
|
58
|
-
if (!auth) return
|
|
59
|
-
const abs = normalizeSharedSecretsFilePath(
|
|
60
|
-
if (!abs) return
|
|
74
|
+
if (!auth) return expanded;
|
|
75
|
+
const abs = normalizeSharedSecretsFilePath(expanded);
|
|
76
|
+
if (!abs) return expanded;
|
|
61
77
|
const home = path.normalize(getAifabrixHome());
|
|
62
|
-
if (isPathUnderDir(abs, home)) return
|
|
78
|
+
if (isPathUnderDir(abs, home)) return expanded;
|
|
63
79
|
const work = getAifabrixWork();
|
|
64
80
|
if (work) {
|
|
65
81
|
const w = path.normalize(work);
|
|
66
|
-
if (isPathUnderDir(abs, w)) return
|
|
82
|
+
if (isPathUnderDir(abs, w)) return expanded;
|
|
67
83
|
}
|
|
68
84
|
const base = String(auth.serverUrl).replace(/\/+$/, '');
|
|
69
85
|
return `${base}/api/dev/secrets`;
|
|
@@ -89,7 +89,15 @@ async function getDockerExecEnv() {
|
|
|
89
89
|
if (overlay.DOCKER_HOST && !Object.prototype.hasOwnProperty.call(overlay, 'DOCKER_CERT_PATH')) {
|
|
90
90
|
delete merged.DOCKER_CERT_PATH;
|
|
91
91
|
}
|
|
92
|
-
|
|
92
|
+
let out = { ...merged, ...overlay };
|
|
93
|
+
try {
|
|
94
|
+
const { getBashPrefixedProcessEnvOverlay } = require('./bash-secret-env');
|
|
95
|
+
const bash = await getBashPrefixedProcessEnvOverlay();
|
|
96
|
+
out = { ...out, ...bash };
|
|
97
|
+
} catch {
|
|
98
|
+
/* ignore secrets load failures; Docker CLI still runs with process + remote env */
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
93
101
|
}
|
|
94
102
|
|
|
95
103
|
module.exports = { getRemoteDockerEnv, getDockerExecEnv };
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Load shared secrets from Builder Server when aifabrix-secrets is an http(s) URL
|
|
3
|
-
*
|
|
2
|
+
* Load shared secrets from Builder Server when aifabrix-secrets is an http(s) URL,
|
|
3
|
+
* or from the configured shared YAML path when it targets a local file.
|
|
4
4
|
*
|
|
5
5
|
* @fileoverview Remote shared secrets loader for .env generation
|
|
6
6
|
* @author AI Fabrix Team
|
|
7
7
|
* @version 2.0.0
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
10
12
|
const config = require('../core/config');
|
|
13
|
+
const { readYamlAtPath } = require('./secrets-canonical');
|
|
14
|
+
const { ensureSecureFilePermissions } = require('./secure-file-permissions');
|
|
11
15
|
|
|
12
16
|
/**
|
|
13
17
|
* Fetches shared secrets from Builder Server when aifabrix-secrets is an http(s) URL.
|
|
@@ -50,20 +54,61 @@ async function loadRemoteSharedSecrets() {
|
|
|
50
54
|
* Merges remote shared secrets with user secrets. User wins on same key.
|
|
51
55
|
* @param {Object} userSecrets - User secrets object
|
|
52
56
|
* @param {Object} remoteSecrets - Remote API secrets (key-value)
|
|
57
|
+
* @param {Record<string, string>} [keySources] - Mutated: winning file/API label per key (decrypt hints)
|
|
58
|
+
* @param {string} [remoteSourceLabel] - Human-readable source for keys taken from remote
|
|
53
59
|
* @returns {Object} Merged object
|
|
54
60
|
*/
|
|
55
|
-
function mergeUserWithRemoteSecrets(userSecrets, remoteSecrets) {
|
|
61
|
+
function mergeUserWithRemoteSecrets(userSecrets, remoteSecrets, keySources, remoteSourceLabel) {
|
|
56
62
|
const merged = { ...userSecrets };
|
|
57
63
|
if (!remoteSecrets || typeof remoteSecrets !== 'object') return merged;
|
|
64
|
+
const label = remoteSourceLabel || 'shared secrets API';
|
|
58
65
|
for (const key of Object.keys(remoteSecrets)) {
|
|
59
66
|
if (!(key in merged) || merged[key] === undefined || merged[key] === null || merged[key] === '') {
|
|
60
67
|
merged[key] = remoteSecrets[key];
|
|
68
|
+
if (keySources) {
|
|
69
|
+
keySources[key] = label;
|
|
70
|
+
}
|
|
61
71
|
}
|
|
62
72
|
}
|
|
63
73
|
return merged;
|
|
64
74
|
}
|
|
65
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Raw secrets from the configured shared store only (`aifabrix-secrets`): remote Builder API or shared YAML file.
|
|
78
|
+
* Does not include primary user secrets or builder merges. Used to avoid duplicating shared keys into ~/.aifabrix.
|
|
79
|
+
*
|
|
80
|
+
* @returns {Promise<Object|null>} Key-value map or null when unavailable / not configured
|
|
81
|
+
*/
|
|
82
|
+
async function loadConfiguredSharedSecretsStore() {
|
|
83
|
+
const remoteDevAuth = require('./remote-dev-auth');
|
|
84
|
+
const configSecretsPath = await config.getSecretsPath();
|
|
85
|
+
if (!configSecretsPath) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
const endpoint = await remoteDevAuth.resolveSharedSecretsEndpoint(configSecretsPath);
|
|
89
|
+
if (remoteDevAuth.isRemoteSecretsUrl(endpoint)) {
|
|
90
|
+
return loadRemoteSharedSecrets();
|
|
91
|
+
}
|
|
92
|
+
const resolvedFile = path.isAbsolute(endpoint)
|
|
93
|
+
? endpoint
|
|
94
|
+
: path.resolve(process.cwd(), endpoint);
|
|
95
|
+
if (!fs.existsSync(resolvedFile)) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
ensureSecureFilePermissions(resolvedFile);
|
|
100
|
+
const data = readYamlAtPath(resolvedFile);
|
|
101
|
+
if (!data || typeof data !== 'object') {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
return data;
|
|
105
|
+
} catch {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
66
110
|
module.exports = {
|
|
67
111
|
loadRemoteSharedSecrets,
|
|
68
|
-
mergeUserWithRemoteSecrets
|
|
112
|
+
mergeUserWithRemoteSecrets,
|
|
113
|
+
loadConfiguredSharedSecretsStore
|
|
69
114
|
};
|
|
@@ -73,16 +73,21 @@ function normalizeDockerRegistryPrefix(registry) {
|
|
|
73
73
|
*/
|
|
74
74
|
function resolveDockerImageRef(appName, appConfig, runOptions = {}) {
|
|
75
75
|
const opts = runOptions || {};
|
|
76
|
+
const tagFromOpts =
|
|
77
|
+
opts.tag !== undefined && opts.tag !== null && String(opts.tag).trim() !== ''
|
|
78
|
+
? String(opts.tag).trim()
|
|
79
|
+
: null;
|
|
80
|
+
|
|
76
81
|
if (opts.image) {
|
|
77
82
|
const parsed = parseImageOverride(opts.image);
|
|
78
83
|
return {
|
|
79
84
|
imageName: parsed ? parsed.name : getRepositoryPathFromConfig(appConfig, appName),
|
|
80
|
-
imageTag: parsed ? parsed.tag : imageTagFromConfig(appConfig)
|
|
85
|
+
imageTag: parsed ? parsed.tag : tagFromOpts || imageTagFromConfig(appConfig)
|
|
81
86
|
};
|
|
82
87
|
}
|
|
83
88
|
|
|
84
89
|
const baseRepo = getRepositoryPathFromConfig(appConfig, appName);
|
|
85
|
-
const imageTag = imageTagFromConfig(appConfig);
|
|
90
|
+
const imageTag = tagFromOpts || imageTagFromConfig(appConfig);
|
|
86
91
|
const prefix =
|
|
87
92
|
normalizeDockerRegistryPrefix(opts.registry) ||
|
|
88
93
|
normalizeDockerRegistryPrefix(appConfig?.image?.registry ?? '');
|
|
@@ -120,5 +125,6 @@ module.exports = {
|
|
|
120
125
|
resolveDockerImageRef,
|
|
121
126
|
resolveComposeImageOverrideString,
|
|
122
127
|
normalizeDockerRegistryPrefix,
|
|
123
|
-
getRepositoryPathFromConfig
|
|
128
|
+
getRepositoryPathFromConfig,
|
|
129
|
+
imageTagFromConfig
|
|
124
130
|
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize Commander flags for `aifabrix run`.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Commander v11 pairs `--no-proxy` with a default-true `--proxy` flag as `options.proxy === false`.
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* True when the user disabled proxy hints: explicit `--no-proxy` or `proxy === false` when supported.
|
|
13
|
+
*
|
|
14
|
+
* @param {Object} [options] - Commander action options
|
|
15
|
+
* @returns {boolean}
|
|
16
|
+
*/
|
|
17
|
+
function isRunCliNoProxy(options) {
|
|
18
|
+
if (!options || typeof options !== 'object') {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
if (options.proxy === false) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
return options.noProxy === true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = {
|
|
28
|
+
isRunCliNoProxy
|
|
29
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collect `secrets.local.yaml` paths along the cwd → root walk so parent workspace
|
|
3
|
+
* secrets (e.g. `/workspace/.aifabrix/`) merge when the active config is nested
|
|
4
|
+
* (e.g. `repo/.aifabrix/` from cwd).
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview Ancestor secrets.local.yaml discovery for loadSecrets cascade
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
const MAX_ANCESTOR_STEPS = 64;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {string} startDir - Directory to start from (typically process.cwd())
|
|
17
|
+
* @param {(p: string) => boolean} existsSyncFn - Sync existence check
|
|
18
|
+
* @returns {string[]} Absolute paths, nearest ancestor first (cwd-side), then parents
|
|
19
|
+
*/
|
|
20
|
+
function collectAncestorAifabrixSecretsLocalYamlPaths(startDir, existsSyncFn) {
|
|
21
|
+
if (!startDir || typeof startDir !== 'string' || typeof existsSyncFn !== 'function') {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
const out = [];
|
|
25
|
+
const seen = new Set();
|
|
26
|
+
let dir = path.resolve(startDir);
|
|
27
|
+
for (let i = 0; i < MAX_ANCESTOR_STEPS; i += 1) {
|
|
28
|
+
const secretsPath = path.join(dir, '.aifabrix', 'secrets.local.yaml');
|
|
29
|
+
if (existsSyncFn(secretsPath)) {
|
|
30
|
+
const abs = path.resolve(secretsPath);
|
|
31
|
+
if (!seen.has(abs)) {
|
|
32
|
+
seen.add(abs);
|
|
33
|
+
out.push(abs);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const parent = path.dirname(dir);
|
|
37
|
+
if (parent === dir) {
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
dir = parent;
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = {
|
|
46
|
+
collectAncestorAifabrixSecretsLocalYamlPaths
|
|
47
|
+
};
|
|
@@ -31,11 +31,14 @@ function readYamlAtPath(filePath) {
|
|
|
31
31
|
* @param {string} key - Secret key
|
|
32
32
|
* @param {*} canonicalValue - Value from canonical secrets
|
|
33
33
|
*/
|
|
34
|
-
function mergeSecretValue(result, key, canonicalValue) {
|
|
34
|
+
function mergeSecretValue(result, key, canonicalValue, keySources, canonicalSourcePath) {
|
|
35
35
|
const currentValue = result[key];
|
|
36
36
|
// Fill missing, empty, or undefined values
|
|
37
37
|
if (!(key in result) || currentValue === undefined || currentValue === null || currentValue === '') {
|
|
38
38
|
result[key] = canonicalValue;
|
|
39
|
+
if (keySources && canonicalSourcePath) {
|
|
40
|
+
keySources[key] = canonicalSourcePath;
|
|
41
|
+
}
|
|
39
42
|
return;
|
|
40
43
|
}
|
|
41
44
|
// Only replace values that are encrypted (have secure:// prefix)
|
|
@@ -43,6 +46,9 @@ function mergeSecretValue(result, key, canonicalValue) {
|
|
|
43
46
|
if (typeof currentValue === 'string' && typeof canonicalValue === 'string') {
|
|
44
47
|
if (currentValue.startsWith('secure://')) {
|
|
45
48
|
result[key] = canonicalValue;
|
|
49
|
+
if (keySources && canonicalSourcePath) {
|
|
50
|
+
keySources[key] = canonicalSourcePath;
|
|
51
|
+
}
|
|
46
52
|
}
|
|
47
53
|
}
|
|
48
54
|
}
|
|
@@ -52,9 +58,10 @@ function mergeSecretValue(result, key, canonicalValue) {
|
|
|
52
58
|
* @async
|
|
53
59
|
* @function applyCanonicalSecretsOverride
|
|
54
60
|
* @param {Object} currentSecrets - Current secrets map
|
|
61
|
+
* @param {Record<string, string>} [keySources] - Mutated: per-key source path when a value is taken from canonical YAML
|
|
55
62
|
* @returns {Promise<Object>} Possibly overridden secrets
|
|
56
63
|
*/
|
|
57
|
-
async function applyCanonicalSecretsOverride(currentSecrets) {
|
|
64
|
+
async function applyCanonicalSecretsOverride(currentSecrets, keySources) {
|
|
58
65
|
let mergedSecrets = currentSecrets || {};
|
|
59
66
|
try {
|
|
60
67
|
const canonicalPath = await config.getSecretsPath();
|
|
@@ -78,7 +85,7 @@ async function applyCanonicalSecretsOverride(currentSecrets) {
|
|
|
78
85
|
// - Replace encrypted values (secure://) with canonical plaintext
|
|
79
86
|
const result = { ...mergedSecrets };
|
|
80
87
|
for (const [key, canonicalValue] of Object.entries(configSecrets)) {
|
|
81
|
-
mergeSecretValue(result, key, canonicalValue);
|
|
88
|
+
mergeSecretValue(result, key, canonicalValue, keySources, resolvedCanonical);
|
|
82
89
|
}
|
|
83
90
|
mergedSecrets = result;
|
|
84
91
|
} catch {
|
|
@@ -19,6 +19,7 @@ const { updateContainerPortInEnvFile } = require('./env-ports');
|
|
|
19
19
|
const { buildEnvVarMap } = require('./env-map');
|
|
20
20
|
const { getLocalPortFromPath } = require('./port-resolver');
|
|
21
21
|
const { readYamlAtPath, applyCanonicalSecretsOverride } = require('./secrets-canonical');
|
|
22
|
+
const { collectUniqueKvPathStrings } = require('./secrets-kv-refs');
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* Interpolate ${VAR} occurrences with values from envVars map
|
|
@@ -51,9 +52,13 @@ const { resolveBashKvFromProcessEnv } = require('./secrets-bash-kv');
|
|
|
51
52
|
/**
|
|
52
53
|
* Last-resort when infra.parameter.yaml cannot be read (e.g. Jest suites that mock `fs`;
|
|
53
54
|
* catalog uses `node:fs`, which is often the same mocked instance). Must stay in sync with
|
|
54
|
-
* `generator.type: emptyAllowed` keys in lib/schema/infra.parameter.yaml.
|
|
55
|
+
* `generator.type: emptyAllowed` keys in lib/schema/infra.parameter.yaml; plus optional Azure/OpenAI kv names.
|
|
55
56
|
*/
|
|
56
|
-
const EMPTY_ALLOWED_KV_FALLBACK = new Set([
|
|
57
|
+
const EMPTY_ALLOWED_KV_FALLBACK = new Set([
|
|
58
|
+
'redis-passwordKeyVault',
|
|
59
|
+
'azure-openaiapi-urlKeyVault',
|
|
60
|
+
'secrets-azureOpenaiApiKeyVault'
|
|
61
|
+
]);
|
|
57
62
|
|
|
58
63
|
/**
|
|
59
64
|
* Infra catalog keys with generator `emptyAllowed` may be absent from the secrets file;
|
|
@@ -191,7 +196,12 @@ function formatMissingSecretsFileInfo(secretsFilePaths) {
|
|
|
191
196
|
if (secretsFilePaths.buildPath) {
|
|
192
197
|
paths.push(secretsFilePaths.buildPath);
|
|
193
198
|
}
|
|
194
|
-
|
|
199
|
+
let msg = `\n\nSecrets file location: ${paths.join(' and ')}`;
|
|
200
|
+
if (secretsFilePaths.sharedSecretsApiUrl && typeof secretsFilePaths.sharedSecretsApiUrl === 'string') {
|
|
201
|
+
msg +=
|
|
202
|
+
`\n(Shared secrets API: ${secretsFilePaths.sharedSecretsApiUrl.trim()}. Keys from that API are merged when resolving secrets; if a key is still missing, add it via "aifabrix secret set" / shared store or check "aifabrix secret list --shared".)`;
|
|
203
|
+
}
|
|
204
|
+
return msg;
|
|
195
205
|
}
|
|
196
206
|
return '';
|
|
197
207
|
}
|
|
@@ -459,13 +469,7 @@ async function adjustLocalEnvPortsInContent(envContent, variablesPath) {
|
|
|
459
469
|
return updated;
|
|
460
470
|
}
|
|
461
471
|
|
|
462
|
-
/**
|
|
463
|
-
* Validate secrets against the env template (skips commented and empty lines)
|
|
464
|
-
* @function validateSecrets
|
|
465
|
-
* @param {string} envTemplate - Environment template content
|
|
466
|
-
* @param {Object} secrets - Available secrets
|
|
467
|
-
* @returns {Object} Validation result
|
|
468
|
-
*/
|
|
472
|
+
/** Validate secrets vs env template (commented/empty lines skipped). */
|
|
469
473
|
function validateSecrets(envTemplate, secrets) {
|
|
470
474
|
const missing = collectMissingSecrets(envTemplate, secrets);
|
|
471
475
|
return { valid: missing.length === 0, missing };
|
|
@@ -475,6 +479,9 @@ module.exports = {
|
|
|
475
479
|
loadEnvConfig,
|
|
476
480
|
interpolateEnvVars,
|
|
477
481
|
collectMissingSecrets,
|
|
482
|
+
collectUniqueKvPathStrings,
|
|
483
|
+
resolveKvRefValue,
|
|
484
|
+
isKvKeyAllowedEmptyWhenAbsent,
|
|
478
485
|
resolveBashKvFromProcessEnv,
|
|
479
486
|
mergeSecretsWithPrefixedCopies,
|
|
480
487
|
formatMissingSecretsFileInfo,
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scan env-style content for unique kv:// path segments (comments skipped).
|
|
3
|
+
* @fileoverview Keeps secrets-helpers under max-lines
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const KV_REF_PATTERN = /kv:\/\/([a-zA-Z0-9_\-/]+)/g;
|
|
9
|
+
|
|
10
|
+
function isCommentOrEmptyLine(line) {
|
|
11
|
+
const t = line.trim();
|
|
12
|
+
return t === '' || t.startsWith('#');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {string} content
|
|
17
|
+
* @returns {string[]}
|
|
18
|
+
*/
|
|
19
|
+
function collectUniqueKvPathStrings(content) {
|
|
20
|
+
const seen = new Set();
|
|
21
|
+
const out = [];
|
|
22
|
+
if (!content || typeof content !== 'string') {
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
for (const line of content.split('\n')) {
|
|
26
|
+
if (isCommentOrEmptyLine(line)) continue;
|
|
27
|
+
let match;
|
|
28
|
+
KV_REF_PATTERN.lastIndex = 0;
|
|
29
|
+
while ((match = KV_REF_PATTERN.exec(line)) !== null) {
|
|
30
|
+
const pathStr = match[1];
|
|
31
|
+
if (!seen.has(pathStr)) {
|
|
32
|
+
seen.add(pathStr);
|
|
33
|
+
out.push(pathStr);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = {
|
|
41
|
+
collectUniqueKvPathStrings
|
|
42
|
+
};
|