Commit 37c9d660265dcb4368a0b65f3c3aa97540231303

Authored by zichun
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.
lib/apply-ddl.mjs
1 import { parseYamlConfig } from './yaml-config.mjs' 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 * Apply a DDL file to a MySQL database using mysql2/promise. 4 * Apply a DDL file to a MySQL database using mysql2/promise.
23 * DB credentials are read from config-vars.yaml's `database:` section. 5 * DB credentials are read from config-vars.yaml's `database:` section.
24 * 6 *
@@ -28,9 +10,9 @@ export function dbEnvFromConfig(config) { @@ -28,9 +10,9 @@ export function dbEnvFromConfig(config) {
28 export async function applyDDL({ configPath, ddlPath }) { 10 export async function applyDDL({ configPath, ddlPath }) {
29 const { readFileSync } = await import('node:fs') 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 const ddl = readFileSync(ddlPath, 'utf8') 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 let mysql 17 let mysql
36 try { 18 try {
@@ -55,21 +37,23 @@ export async function applyDDL({ configPath, ddlPath }) { @@ -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 * Throws if no schema resolves — V1 has no USE/CREATE DATABASE. 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 * @param {string} [cfgPath] only used to make the error message actionable 47 * @param {string} [cfgPath] only used to make the error message actionable
65 * @returns {{host:string, port:number, user:string, password:string, database:string}} 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 if (!database) { 57 if (!database) {
74 throw new Error(`apply-ddl: 缺数据库名 — 请在 ${cfgPath} 的 database.schema 填写`) 58 throw new Error(`apply-ddl: 缺数据库名 — 请在 ${cfgPath} 的 database.schema 填写`)
75 } 59 }
lib/apply-ddl.test.mjs
1 import { test } from 'node:test' 1 import { test } from 'node:test'
2 import assert from 'node:assert/strict' 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 assert.equal(c.host, 'db.local') 10 assert.equal(c.host, 'db.local')
34 assert.equal(c.port, 3307) 11 assert.equal(c.port, 3307)
35 assert.equal(c.user, 'u') 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 test('resolveDbConfig applies sane defaults for host/port/user/password', () => { 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 assert.equal(c.host, '127.0.0.1') 24 assert.equal(c.host, '127.0.0.1')
53 assert.equal(c.port, 3306) 25 assert.equal(c.port, 3306)
54 assert.equal(c.user, 'root') 26 assert.equal(c.user, 'root')
@@ -56,6 +28,6 @@ test(&#39;resolveDbConfig applies sane defaults for host/port/user/password&#39;, () =&gt; @@ -56,6 +28,6 @@ test(&#39;resolveDbConfig applies sane defaults for host/port/user/password&#39;, () =&gt;
56 }) 28 })
57 29
58 test('resolveDbConfig rejects invalid ports', () => { 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 })