Commit 37c9d660265dcb4368a0b65f3c3aa97540231303
1 parent
62d2fb9e
apply-ddl: drop dead env aliases + collapse dbEnvFromConfig two-hop
- resolveDbConfig only ever received the 5 DB_* keys dbEnvFromConfig emits;
the MYSQL_*/DB_PASS/DB_NAME fallbacks had no producer (dead speculative
generality, a dotenv-era leftover). Remove them.
- collapse the config → DB_* env-shape → connection two-hop: resolveDbConfig
now reads config.database.{host,port,user,password,schema} directly, and
dbEnvFromConfig is removed.
- CLI contract (node apply-ddl.mjs <configPath> <ddlPath>), the only caller
via db-init, is unchanged.
- rewrite tests to the parsed-config shape; drop the alias-only tests. 53 pass.
Showing
2 changed files
with
26 additions
and
70 deletions
lib/apply-ddl.mjs
| 1 | 1 | import { parseYamlConfig } from './yaml-config.mjs' |
| 2 | 2 | |
| 3 | 3 | /** |
| 4 | - * Flatten config-vars.yaml's `database:` section into the DB_* env-shape that | |
| 5 | - * resolveDbConfig consumes. Pure; tolerates a missing section. | |
| 6 | - * | |
| 7 | - * @param {Record<string, any>} config parsed config-vars.yaml | |
| 8 | - * @returns {Record<string, string|undefined>} | |
| 9 | - */ | |
| 10 | -export function dbEnvFromConfig(config) { | |
| 11 | - const db = (config && config.database) || {} | |
| 12 | - return { | |
| 13 | - DB_HOST: db.host, | |
| 14 | - DB_PORT: db.port != null ? String(db.port) : undefined, | |
| 15 | - DB_USER: db.user, | |
| 16 | - DB_PASSWORD: db.password, | |
| 17 | - DB_SCHEMA: db.schema, | |
| 18 | - } | |
| 19 | -} | |
| 20 | - | |
| 21 | -/** | |
| 22 | 4 | * Apply a DDL file to a MySQL database using mysql2/promise. |
| 23 | 5 | * DB credentials are read from config-vars.yaml's `database:` section. |
| 24 | 6 | * |
| ... | ... | @@ -28,9 +10,9 @@ export function dbEnvFromConfig(config) { |
| 28 | 10 | export async function applyDDL({ configPath, ddlPath }) { |
| 29 | 11 | const { readFileSync } = await import('node:fs') |
| 30 | 12 | |
| 31 | - const env = dbEnvFromConfig(parseYamlConfig(readFileSync(configPath, 'utf8'))) | |
| 13 | + const config = parseYamlConfig(readFileSync(configPath, 'utf8')) | |
| 32 | 14 | const ddl = readFileSync(ddlPath, 'utf8') |
| 33 | - const { host, port, user, password, database } = resolveDbConfig(env, configPath) | |
| 15 | + const { host, port, user, password, database } = resolveDbConfig(config, configPath) | |
| 34 | 16 | |
| 35 | 17 | let mysql |
| 36 | 18 | try { |
| ... | ... | @@ -55,21 +37,23 @@ export async function applyDDL({ configPath, ddlPath }) { |
| 55 | 37 | } |
| 56 | 38 | |
| 57 | 39 | /** |
| 58 | - * Resolve mysql2 connection settings from a parsed env object. Pure (no I/O), | |
| 59 | - * so it is unit-testable without mysql2 installed. | |
| 40 | + * Resolve mysql2 connection settings from a parsed config-vars.yaml object, | |
| 41 | + * reading its `database:` section directly. Pure (no I/O), so it is | |
| 42 | + * unit-testable without mysql2 installed. | |
| 60 | 43 | * |
| 61 | 44 | * Throws if no schema resolves — V1 has no USE/CREATE DATABASE. |
| 62 | 45 | * |
| 63 | - * @param {Record<string,string>} env | |
| 46 | + * @param {Record<string, any>} config parsed config-vars.yaml | |
| 64 | 47 | * @param {string} [cfgPath] only used to make the error message actionable |
| 65 | 48 | * @returns {{host:string, port:number, user:string, password:string, database:string}} |
| 66 | 49 | */ |
| 67 | -export function resolveDbConfig(env, cfgPath = 'config-vars.yaml') { | |
| 68 | - const host = env.DB_HOST || env.MYSQL_HOST || '127.0.0.1' | |
| 69 | - const port = Number(env.DB_PORT || env.MYSQL_PORT || 3306) | |
| 70 | - const user = env.DB_USER || env.MYSQL_USER || 'root' | |
| 71 | - const password = env.DB_PASS || env.DB_PASSWORD || env.MYSQL_PASSWORD || '' | |
| 72 | - const database = env.DB_SCHEMA || env.DB_NAME || env.MYSQL_DATABASE || undefined | |
| 50 | +export function resolveDbConfig(config, cfgPath = 'config-vars.yaml') { | |
| 51 | + const db = (config && config.database) || {} | |
| 52 | + const host = db.host || '127.0.0.1' | |
| 53 | + const port = Number(db.port || 3306) | |
| 54 | + const user = db.user || 'root' | |
| 55 | + const password = db.password || '' | |
| 56 | + const database = db.schema || undefined | |
| 73 | 57 | if (!database) { |
| 74 | 58 | throw new Error(`apply-ddl: 缺数据库名 — 请在 ${cfgPath} 的 database.schema 填写`) |
| 75 | 59 | } | ... | ... |
lib/apply-ddl.test.mjs
| 1 | 1 | import { test } from 'node:test' |
| 2 | 2 | import assert from 'node:assert/strict' |
| 3 | -import { dbEnvFromConfig, resolveDbConfig } from './apply-ddl.mjs' | |
| 3 | +import { resolveDbConfig } from './apply-ddl.mjs' | |
| 4 | 4 | |
| 5 | -// ── dbEnvFromConfig(config-vars.yaml database: → DB_* env-shape adapter)── | |
| 6 | -test('dbEnvFromConfig maps the database section to the DB_* shape', () => { | |
| 7 | - const env = dbEnvFromConfig({ | |
| 8 | - database: { host: 'db.local', port: 3307, user: 'u', password: 'p@ss', schema: 'erp_test' }, | |
| 5 | +// ── resolveDbConfig(直接读 config-vars.yaml 解析后的 database: 段)───────── | |
| 6 | +test('resolveDbConfig maps the database section to mysql2 settings', () => { | |
| 7 | + const c = resolveDbConfig({ | |
| 8 | + database: { host: 'db.local', port: '3307', user: 'u', password: 'p@ss', schema: 'erp_test' }, | |
| 9 | 9 | }) |
| 10 | - assert.equal(env.DB_HOST, 'db.local') | |
| 11 | - assert.equal(env.DB_PORT, '3307') // coerced to string for resolveDbConfig | |
| 12 | - assert.equal(env.DB_USER, 'u') | |
| 13 | - assert.equal(env.DB_PASSWORD, 'p@ss') | |
| 14 | - assert.equal(env.DB_SCHEMA, 'erp_test') | |
| 15 | -}) | |
| 16 | - | |
| 17 | -test('dbEnvFromConfig tolerates a missing/empty config', () => { | |
| 18 | - assert.equal(dbEnvFromConfig({}).DB_HOST, undefined) | |
| 19 | - assert.equal(dbEnvFromConfig(null).DB_SCHEMA, undefined) | |
| 20 | -}) | |
| 21 | - | |
| 22 | -test('dbEnvFromConfig → resolveDbConfig round-trips a filled database section', () => { | |
| 23 | - const c = resolveDbConfig(dbEnvFromConfig({ database: { host: 'localhost', port: 3306, schema: 'erp_dev' } })) | |
| 24 | - assert.equal(c.host, 'localhost') | |
| 25 | - assert.equal(c.port, 3306) | |
| 26 | - assert.equal(c.database, 'erp_dev') | |
| 27 | -}) | |
| 28 | - | |
| 29 | -// ── resolveDbConfig(M1:DB_SCHEMA 是插件契约的 schema 键)───────── | |
| 30 | -test('resolveDbConfig maps DB_SCHEMA to database (plugin canonical key)', () => { | |
| 31 | - const c = resolveDbConfig({ DB_SCHEMA: 'erp_test', DB_USER: 'u', DB_PASS: 'p', DB_HOST: 'db.local', DB_PORT: '3307' }) | |
| 32 | - assert.equal(c.database, 'erp_test') | |
| 33 | 10 | assert.equal(c.host, 'db.local') |
| 34 | 11 | assert.equal(c.port, 3307) |
| 35 | 12 | assert.equal(c.user, 'u') |
| 36 | - assert.equal(c.password, 'p') | |
| 37 | -}) | |
| 38 | - | |
| 39 | -test('resolveDbConfig honors DB_NAME / MYSQL_DATABASE aliases', () => { | |
| 40 | - assert.equal(resolveDbConfig({ DB_NAME: 'a' }).database, 'a') | |
| 41 | - assert.equal(resolveDbConfig({ MYSQL_DATABASE: 'b' }).database, 'b') | |
| 42 | - // DB_SCHEMA wins over aliases | |
| 43 | - assert.equal(resolveDbConfig({ DB_SCHEMA: 's', DB_NAME: 'a' }).database, 's') | |
| 13 | + assert.equal(c.password, 'p@ss') | |
| 14 | + assert.equal(c.database, 'erp_test') | |
| 44 | 15 | }) |
| 45 | 16 | |
| 46 | -test('resolveDbConfig fails closed when no schema key is present (M1)', () => { | |
| 47 | - assert.throws(() => resolveDbConfig({ DB_USER: 'root' }, 'config-vars.yaml'), /database\.schema/) | |
| 17 | +test('resolveDbConfig fails closed when database.schema is missing (M1)', () => { | |
| 18 | + assert.throws(() => resolveDbConfig({ database: { user: 'root' } }, 'config-vars.yaml'), /database\.schema/) | |
| 19 | + assert.throws(() => resolveDbConfig({}), /database\.schema/) | |
| 48 | 20 | }) |
| 49 | 21 | |
| 50 | 22 | test('resolveDbConfig applies sane defaults for host/port/user/password', () => { |
| 51 | - const c = resolveDbConfig({ DB_SCHEMA: 's' }) | |
| 23 | + const c = resolveDbConfig({ database: { schema: 's' } }) | |
| 52 | 24 | assert.equal(c.host, '127.0.0.1') |
| 53 | 25 | assert.equal(c.port, 3306) |
| 54 | 26 | assert.equal(c.user, 'root') |
| ... | ... | @@ -56,6 +28,6 @@ test('resolveDbConfig applies sane defaults for host/port/user/password', () => |
| 56 | 28 | }) |
| 57 | 29 | |
| 58 | 30 | test('resolveDbConfig rejects invalid ports', () => { |
| 59 | - assert.throws(() => resolveDbConfig({ DB_SCHEMA: 'erp_test', DB_PORT: 'abc' }), /database\.port/) | |
| 60 | - assert.throws(() => resolveDbConfig({ DB_SCHEMA: 'erp_test', DB_PORT: '70000' }), /database\.port/) | |
| 31 | + assert.throws(() => resolveDbConfig({ database: { schema: 'erp_test', port: 'abc' } }), /database\.port/) | |
| 32 | + assert.throws(() => resolveDbConfig({ database: { schema: 'erp_test', port: '70000' } }), /database\.port/) | |
| 61 | 33 | }) | ... | ... |