Commit 52014d4ee2a9ef97a04e369a2bf7ffcf5679247e

Authored by zichun
1 parent 5babb654

plan: consolidate all config into config-vars.yaml (drop .env.local + DB guards)

- config-vars.yaml now holds全部 config incl. secrets (database/admin_init.password/secrets), committed
- delete env-local-template; setup-test-db reads config-vars.yaml; gitignore no longer ignores .env.local
- docs-07 single-store rule; db-init/db-design-gen read config-vars.yaml database: section
- drop test_db_allowed_hosts whitelist + schema-looks-like-test guard (trust config-vars.yaml)
skills/plan/db-design-gen/SKILL.md
... ... @@ -47,7 +47,7 @@ allowed-tools: Read Write Edit Grep Glob AskUserQuestion
47 47  
48 48 ### C. 渲染 docs/03
49 49  
50   -1. 读取 `${CLAUDE_SKILL_DIR}/templates/docs-03-header-template.md`,填充 `schema_name`(从 `.env.local` 读 `DB_SCHEMA`,无则填 `【人工填写:DB_SCHEMA】`)、`er_overview`(纯文本 ER 概览),以及步骤 A0 确认的 `{{PK_CONVENTION}}` / `{{TENANT_COLS}}` / `{{COL_PREFIX_RULE}}`。
  50 +1. 读取 `${CLAUDE_SKILL_DIR}/templates/docs-03-header-template.md`,填充 `schema_name`(从 `config-vars.yaml` 读 `database.schema`,无则填 `【人工填写:database.schema】`)、`er_overview`(纯文本 ER 概览),以及步骤 A0 确认的 `{{PK_CONVENTION}}` / `{{TENANT_COLS}}` / `{{COL_PREFIX_RULE}}`。
51 51 2. 渲染「表清单」:对每张表:读取并填充 `${CLAUDE_SKILL_DIR}/templates/docs-03-table-template.md`,其中标准列区块用步骤 A0 确认的 `{{PK_CONVENTION}}` / `{{TENANT_COLS}}` 展开(`TENANT_COLS` = 「无」时不输出租户列行)。
52 52 3. 写入 `docs/03-数据库设计文档.md`。
53 53  
... ...
skills/plan/db-init/SKILL.md
1 1 ---
2 2 name: db-init
3   -description: A4 DB 初始化——LLM 解析 docs/03-数据库设计文档.md → 生成 sql/migrations/V1__initial_schema.sql(DDL only,Flyway 初始 migration)→ 用 lib/validate-ddl.mjs 全量校验 DDL ↔ docs/03 一致性 → 验证 MySQL 连接 → 调 scripts/setup-test-db.mjs 复用三层防护并 DROP+CREATE 空库 → 用 lib/apply-ddl.mjs apply V1。
  3 +description: A4 DB 初始化——LLM 解析 docs/03-数据库设计文档.md → 生成 sql/migrations/V1__initial_schema.sql(DDL only,Flyway 初始 migration)→ 用 lib/validate-ddl.mjs 全量校验 DDL ↔ docs/03 一致性 → 验证 MySQL 连接 → 调 scripts/setup-test-db.mjs DROP+CREATE 空库 → 用 lib/apply-ddl.mjs apply V1。
4 4 user-invocable: false
5 5 allowed-tools: Read Write Edit Glob Skill Bash(node *)
6 6 ---
... ... @@ -56,30 +56,39 @@ node "${CLAUDE_PLUGIN_ROOT}/lib/validate-ddl.mjs" \
56 56  
57 57 ### B. 数据库环境检查
58 58  
59   -用 `Glob` 确认 `.env.local` 存在(不存在 → 提示重跑 A2 `skeleton-gen` 并停下)。用 `Read` 逐行解析 `KEY=VALUE`(跳过空行 / `#` 注释,**不做 shell-source / 变量展开**),校验 `DB_HOST` / `DB_PORT` / `DB_USER` / `DB_PASSWORD` / `DB_SCHEMA` 5 项均非空——任一缺失 → 打印缺失字段并停下。
  59 +用 `Glob` 确认 `config-vars.yaml` 存在(不存在 → 提示重跑 A1 `scope-lock` 并停下)。用 `Read` 读其 `database:` 段,校验 `host` / `port` / `user` / `password` / `schema` 5 项均非空且非 `【人工填写` 占位——任一缺失 → 打印缺失字段并停下。
60 60  
61   -用解析出的值跑连通性自检。必须用 Node `spawnSync('mysql', args, {shell:false})`,不要把密码拼进 shell 命令;空密码也要传 `--password=`,避免 `mysql -p` 进入交互式等待。
  61 +用解析出的值跑连通性自检。必须用 Node `spawnSync('mysql', args, {shell:false})`,不要把密码拼进 shell 命令;空密码也要传 `--password=`,避免 `mysql -p` 进入交互式等待。下面的 `node -e` 内联了与 `lib/yaml-config.mjs` 同规则的极简 YAML 读取(2 层 map + 标量),仅读 `database:` 段:
62 62  
63 63 ```bash
64 64 node -e '
65 65 const { spawnSync } = require("node:child_process");
66 66 const { readFileSync } = require("node:fs");
67   -const env = {};
68   -for (const raw of readFileSync(".env.local", "utf8").split(/\r?\n/)) {
69   - const line = raw.trim();
70   - if (!line || line.startsWith("#")) continue;
71   - const eq = line.indexOf("=");
72   - if (eq < 0) continue;
73   - const key = line.slice(0, eq).trim();
74   - let value = line.slice(eq + 1).trim();
75   - if (value.length >= 2 && ((value[0] === "\"" && value.at(-1) === "\"") || (value[0] === "'"'"'" && value.at(-1) === "'"'"'"))) value = value.slice(1, -1);
76   - env[key] = value;
  67 +const SQ = String.fromCharCode(39), DQ = String.fromCharCode(34);
  68 +function parseScalar(raw) {
  69 + let s = String(raw).trim();
  70 + if (s === "" || s[0] === "#") return "";
  71 + const q = s[0];
  72 + if (q === SQ || q === DQ) { const e = s.indexOf(q, 1); if (e !== -1) return s.slice(1, e); }
  73 + const h = s.indexOf(" #"); if (h !== -1) s = s.slice(0, h).trim();
  74 + return s;
77 75 }
  76 +const cfg = {}; let sec = null;
  77 +for (const raw of readFileSync("config-vars.yaml", "utf8").split(/\r?\n/)) {
  78 + const t = raw.trim(); if (!t || t[0] === "#") continue;
  79 + const c = raw.indexOf(":"); if (c < 0) continue;
  80 + const k = raw.slice(0, c).trim(); if (!k) continue;
  81 + const ind = raw.length - raw.replace(/^\s+/, "").length;
  82 + const v = parseScalar(raw.slice(c + 1));
  83 + if (ind === 0) { if (v === "") { sec = {}; cfg[k] = sec; } else { cfg[k] = v; sec = null; } }
  84 + else if (sec) { sec[k] = v; }
  85 +}
  86 +const db = cfg.database || {};
78 87 const args = [
79   - `--host=${env.DB_HOST}`,
80   - `--port=${env.DB_PORT || "3306"}`,
81   - `--user=${env.DB_USER}`,
82   - `--password=${env.DB_PASSWORD || ""}`,
  88 + `--host=${db.host}`,
  89 + `--port=${db.port || "3306"}`,
  90 + `--user=${db.user}`,
  91 + `--password=${db.password || ""}`,
83 92 "-e",
84 93 "SELECT 1;"
85 94 ];
... ... @@ -90,7 +99,7 @@ process.exit(r.status === null ? 1 : r.status);
90 99  
91 100 成功 → 进入步骤 C;失败 → 打印具体错误(认证 / 主机不可达 / 端口拒接)并停下。
92 101  
93   -勾选:` - [ ] .env.local 凭据已验证(mysql -e "SELECT 1" OK)`
  102 +勾选:` - [ ] config-vars.yaml DB 凭据已验证(mysql -e "SELECT 1" OK)`
94 103  
95 104 ### C. 自动导入 MySQL
96 105  
... ... @@ -102,10 +111,10 @@ node scripts/setup-test-db.mjs
102 111  
103 112 #### C.2 把 V1 灌入已清空的 schema
104 113  
105   -调 `${CLAUDE_PLUGIN_ROOT}/lib/apply-ddl.mjs`:它用纯 JS 解析 `.env.local`(**不** shell-source,消除注入),复用 host 白名单 + schema 名安全闸,再经 mysql2 把 DDL 灌入 schema。
  114 +调 `${CLAUDE_PLUGIN_ROOT}/lib/apply-ddl.mjs`:它用纯 JS 解析 `config-vars.yaml` 的 `database:` 段(**不** shell-source,消除注入),再经 mysql2 把 DDL 灌入 schema。
106 115  
107 116 ```bash
108   -node "${CLAUDE_PLUGIN_ROOT}/lib/apply-ddl.mjs" .env.local sql/migrations/V1__initial_schema.sql
  117 +node "${CLAUDE_PLUGIN_ROOT}/lib/apply-ddl.mjs" config-vars.yaml sql/migrations/V1__initial_schema.sql
109 118 ```
110 119  
111 120 退出码与处理:
... ... @@ -113,7 +122,7 @@ node &quot;${CLAUDE_PLUGIN_ROOT}/lib/apply-ddl.mjs&quot; .env.local sql/migrations/V1__ini
113 122 - `1` → 失败:打印 stderr 并停下
114 123 - `2` → 用法错(路径找不到),打印路径并停下
115 124  
116   -勾选:` - [ ] setup-test-db.mjs 防护通过 + DROP+CREATE + apply V1 已执行`
  125 +勾选:` - [ ] setup-test-db.mjs DROP+CREATE + apply V1 已执行`
117 126  
118 127 ### D. 勾选 docs/08 进度 + 进入 A5
119 128  
... ... @@ -126,7 +135,8 @@ node &quot;${CLAUDE_PLUGIN_ROOT}/lib/apply-ddl.mjs&quot; .env.local sql/migrations/V1__ini
126 135 ## 参考
127 136  
128 137 - `${CLAUDE_PLUGIN_ROOT}/lib/validate-ddl.mjs`(A.3 docs/03 ↔ V1.sql 5 维一致性校验,跨平台纯 Node)
129   -- `${CLAUDE_PLUGIN_ROOT}/lib/apply-ddl.mjs`(C.2 安全解析 .env.local + mysql2 灌入 DDL,不 shell-source)
  138 +- `${CLAUDE_PLUGIN_ROOT}/lib/apply-ddl.mjs`(C.2 安全解析 config-vars.yaml 的 database: 段 + mysql2 灌入 DDL,不 shell-source)
  139 +- `${CLAUDE_PLUGIN_ROOT}/lib/yaml-config.mjs`(apply-ddl 依赖的极简 YAML 读取)
130 140 - `docs/03-数据库设计文档.md`(DDL 翻译输入,SSoT)
131   -- `.env.local`(DB 凭据
  141 +- `config-vars.yaml`(DB 凭据,A1 产出
132 142 - 产物:`sql/migrations/V1__initial_schema.sql`(由 Flyway 在 Spring Boot 启动时验证 / apply)
... ...
skills/plan/scope-lock/templates/config-vars-template.yaml
1   -# config-vars.yaml — non-sensitive project config; rules live in docs/07.
2   -# Sensitive values go in .env.local; only list key names under secrets_ref.
  1 +# config-vars.yaml — 项目全部配置(含敏感凭据)。随项目提交,内部 git 传播。
  2 +# 工具脚本(apply-ddl / setup-test-db)运行时按 2 层 map 解析此文件。
  3 +# 值含 : / # / 空格 / $ / 引号等特殊字符时,用单引号包裹整个值:password: 'p@ss: w0rd#1'
3 4  
4 5 backend:
5   - base_package: 【人工填写:后端根包名 / 命名空间,如 com.acme.erp】
  6 + base_package: com.xly.erp
6 7 http_port: 【人工填写:后端 HTTP 端口,默认 8080】
7 8  
8 9 frontend:
9   - pkg_name: 【人工填写:前端包名,如 acme-erp-web】
  10 + pkg_name: xly-erp-web
10 11 dev_port: 【人工填写:前端开发服务器端口,默认 5173】
11 12  
  13 +database:
  14 + host: 【人工填写:MySQL host,推荐 localhost】
  15 + port: 【人工填写:MySQL port,默认 3306】
  16 + user: 【人工填写:开发账号名】
  17 + password: 【人工填写:对应密码,含特殊字符时用单引号包裹】
  18 + schema: 【人工填写:schema 名,推荐含 test/_dev/_local,例如 erp_dev】
  19 +
12 20 admin_init:
13   - username: 【人工填写:超级管理员初始账号,如 admin】
14   - # 初始密码属敏感 → 见 .env.local 的 ADMIN_INIT_PASSWORD
  21 + username: admin
  22 + password: 666666
15 23  
16   -secrets_ref:
17   - - DB_PASSWORD # 数据库密码
18   - - JWT_SECRET # JWT / 令牌签名密钥
19   - # - REDIS_PASSWORD # 缓存 / 会话(用 Redis 时)
20   - # - ADMIN_INIT_PASSWORD # 超级管理员初始密码(有初始账号时)
21   - # - OSS_ACCESS_KEY_SECRET / SMS_API_SECRET ... # 第三方凭证按需添加
  24 +secrets:
  25 + jwt_secret: 【人工填写:JWT 签名密钥,256+ bit 随机串】
  26 + # 项目专属凭据按需取消注释 / 追加,直接填真实值:
  27 + # redis_password: 【人工填写:Redis 密码(用 Redis 时)】
  28 + # oss_access_key_secret: 【人工填写:对象存储密钥】
  29 + # sms_api_secret: 【人工填写:短信网关密钥】
... ...
skills/plan/skeleton-gen/templates/docs-07-env-template.md
... ... @@ -6,14 +6,15 @@
6 6  
7 7 ## 三、配置与凭据规则
8 8  
9   -项目配置分两处存放,**本文档只记规则、不记具体值**:
  9 +项目**全部配置**(含敏感凭据)统一存放在仓库根 `config-vars.yaml`,结构化 YAML,随项目提交(内部 git 传播)。**本文档只记规则、不记具体值**:
10 10  
11   -- **非敏感、项目级配置**(根包名 / 命名空间、应用端口、前端包名、管理员初始账号等)→ 仓库根 `config-vars.yaml`,结构化 YAML,随项目提交。
12   -- **敏感凭据**(数据库密码、JWT / 签名密钥、Redis 密码、第三方 key/secret、管理员初始密码等)→ 仓库根 `.env.local`,入 `.gitignore`,**不提交**;`config-vars.yaml` 末尾 `secrets_ref` 只登记键名作引用。
  11 +- **非敏感、项目级配置**(根包名 / 命名空间、应用端口、前端包名、管理员初始账号等)→ `config-vars.yaml` 对应段。
  12 +- **敏感凭据**(数据库密码、JWT / 签名密钥、Redis 密码、第三方 key/secret、管理员初始密码等)→ `config-vars.yaml` 的 `database` / `admin_init.password` / `secrets` 段,直接填真实值。
13 13  
14 14 规则:
15 15 - 根包名 / 命名空间一经在 `config-vars.yaml` 锁定,全项目复用,不得各模块各写。
16 16 - 端口遵循 § 二 约定;调整时改 `config-vars.yaml`,本文档不写具体端口。
17   -- 任何敏感值不得出现在 `config-vars.yaml`、docs、源码或日志中——只允许出现在 `.env.local`。
  17 +- 任何配置值(含敏感值)只允许出现在 `config-vars.yaml`,不得散落在 docs、源码或日志中。
  18 +- 工具脚本(apply-ddl / setup-test-db)运行时按 2 层 map 解析 `config-vars.yaml`;值含特殊字符时用单引号包裹。
18 19  
19 20 ## 四、常用命令
... ...
skills/plan/skeleton-gen/templates/env-local-template deleted
1   -# Local dev credentials — gitignored.
2   -# Quote values containing $/space/!/backtick with single quotes: DB_PASSWORD='p@ss$w0rd!'
3   -
4   -DB_HOST=【人工填写:MySQL host,推荐 localhost】
5   -DB_PORT=【人工填写:MySQL port,默认 3306】
6   -DB_USER=【人工填写:开发账号名】
7   -DB_PASSWORD=【人工填写:对应密码,含特殊字符时用单引号包裹】
8   -DB_SCHEMA=【人工填写:schema 名,推荐含 test/_dev/_local,例如 erp_dev】
9   -JWT_SECRET=【人工填写:JWT 签名密钥,256+ bit 随机串】
10   -
11   -# 可选:额外允许 DROP CREATE 的远程 host(空格或逗号分隔)。
12   -TEST_DB_ALLOWED_HOSTS=
skills/plan/skeleton-gen/templates/gitignore-append-template
1 1 # ==== ERP 插件推荐忽略项(skeleton-gen 追加) ====
2   -# 本地运行时配置(含真实凭据,严禁入库)
3   -.env.local
4   -.env.*.local
  2 +# 注:项目配置(含凭据)统一在 config-vars.yaml,随项目提交(内部 git 传播),不在此忽略。
5 3  
6 4 # Java / Maven
7 5 target/
... ...
skills/plan/skeleton-gen/templates/scripts-setup-test-db-template.mjs
1 1 #!/usr/bin/env node
2   -// scripts/setup-test-db.mjs — DROP + CREATE 测试库。
  2 +// scripts/setup-test-db.mjs — DROP + CREATE 测试库。
3 3 // 由 coding.mjs 的 test-gate 调用;schema 由 Flyway 在 Spring Boot 启动时重放。
4   -// 只允许本地 host(或 TEST_DB_ALLOWED_HOSTS 白名单内的 host)+ 测试库名(含 test/_dev/_local/_ci)。
  4 +// DB 凭据从仓库根 config-vars.yaml 的 database: 段读取(host / schema 完全信任该文件,无额外校验)。
5 5  
6 6 import { spawnSync } from 'node:child_process'
7 7 import { existsSync, readFileSync } from 'node:fs'
... ... @@ -9,86 +9,70 @@ import { dirname, join } from &#39;node:path&#39;
9 9 import { fileURLToPath } from 'node:url'
10 10  
11 11 const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url))
12   -const ENV_FILE = join(SCRIPT_DIR, '..', '.env.local')
  12 +const CONFIG_FILE = join(SCRIPT_DIR, '..', 'config-vars.yaml')
13 13  
14   -function parseEnv(text) {
15   - const env = {}
16   - for (const rawLine of text.split(/\r?\n/)) {
17   - const line = rawLine.trim()
18   - if (line === '' || line.startsWith('#')) continue
19   - const eq = line.indexOf('=')
20   - if (eq === -1) continue
21   - const key = line.slice(0, eq).trim()
22   - if (!key) continue
23   - let value = line.slice(eq + 1).trim()
24   - if (
25   - value.length >= 2 &&
26   - ((value.startsWith("'") && value.endsWith("'")) ||
27   - (value.startsWith('"') && value.endsWith('"')))
28   - ) {
29   - value = value.slice(1, -1)
  14 +// 极简 YAML 读取(2 层 map + 标量;与插件 lib/yaml-config.mjs 同规则,内联以免运行时依赖)。
  15 +function parseScalar(raw) {
  16 + let s = String(raw).trim()
  17 + if (s === '' || s[0] === '#') return ''
  18 + const q = s[0]
  19 + if (q === '"' || q === "'") {
  20 + const end = s.indexOf(q, 1)
  21 + if (end !== -1) return s.slice(1, end)
  22 + }
  23 + const hash = s.indexOf(' #')
  24 + if (hash !== -1) s = s.slice(0, hash).trim()
  25 + return s
  26 +}
  27 +function parseYamlConfig(text) {
  28 + const root = {}
  29 + let section = null
  30 + for (const rawLine of text.split('\n')) {
  31 + const line = rawLine.replace(/\r$/, '')
  32 + const trimmed = line.trim()
  33 + if (trimmed === '' || trimmed[0] === '#') continue
  34 + const colon = line.indexOf(':')
  35 + if (colon === -1) continue
  36 + const key = line.slice(0, colon).trim()
  37 + if (key === '') continue
  38 + const indent = line.length - line.replace(/^\s+/, '').length
  39 + const value = parseScalar(line.slice(colon + 1))
  40 + if (indent === 0) {
  41 + if (value === '') {
  42 + section = {}
  43 + root[key] = section
  44 + } else {
  45 + root[key] = value
  46 + section = null
  47 + }
  48 + } else if (section) {
  49 + section[key] = value
  50 + } else {
  51 + root[key] = value
30 52 }
31   - env[key] = value
32 53 }
33   - return env
  54 + return root
34 55 }
35 56  
36   -if (!existsSync(ENV_FILE)) {
37   - console.error(`[setup-test-db] .env.local 不存在(${ENV_FILE})`)
  57 +if (!existsSync(CONFIG_FILE)) {
  58 + console.error(`[setup-test-db] config-vars.yaml 不存在(${CONFIG_FILE})`)
38 59 process.exit(1)
39 60 }
40 61  
41   -const env = parseEnv(readFileSync(ENV_FILE, 'utf8'))
  62 +const db = parseYamlConfig(readFileSync(CONFIG_FILE, 'utf8')).database || {}
42 63  
43   -const DB_HOST = env.DB_HOST ?? ''
44   -const DB_PORT = env.DB_PORT ?? '3306'
45   -const DB_USER = env.DB_USER ?? ''
46   -const DB_PASSWORD = env.DB_PASSWORD ?? ''
47   -const DB_SCHEMA = env.DB_SCHEMA ?? ''
  64 +const DB_HOST = db.host ?? ''
  65 +const DB_PORT = db.port ?? '3306'
  66 +const DB_USER = db.user ?? ''
  67 +const DB_PASSWORD = db.password ?? ''
  68 +const DB_SCHEMA = db.schema ?? ''
48 69  
49 70 if (!/^\d+$/.test(DB_PORT) || Number(DB_PORT) <= 0 || Number(DB_PORT) > 65535) {
50   - console.error(`[setup-test-db] DB_PORT 非法: ${DB_PORT}(必须是 1..65535 的整数)`)
51   - process.exit(1)
52   -}
53   -
54   -if (!/^[A-Za-z0-9_]+$/.test(DB_SCHEMA)) {
55   - console.error(`[setup-test-db] DB_SCHEMA 只能包含字母、数字、下划线,当前为: ${DB_SCHEMA}`)
56   - process.exit(1)
57   -}
58   -
59   -// 防护 1:默认只允许本地 host(localhost / 127.0.0.1 / ::1)。
60   -// 额外允许的远程 host 在 .env.local 的 TEST_DB_ALLOWED_HOSTS 中(空格或逗号分隔)。
61   -const extraHosts = (env.TEST_DB_ALLOWED_HOSTS ?? '')
62   - .split(/[\s,]+/)
63   - .filter(Boolean)
64   -const allowedHosts = ['localhost', '127.0.0.1', '::1', ...extraHosts]
65   -if (!allowedHosts.includes(DB_HOST)) {
66   - console.error(`[setup-test-db] 拒绝在非白名单 host (${DB_HOST}) 上执行 DROP DATABASE`)
67   - console.error(` 当前白名单:${allowedHosts.join(' ')}`)
68   - console.error(' 加入 host:在 .env.local 追加 TEST_DB_ALLOWED_HOSTS="<host1> <host2>"')
69   - process.exit(1)
70   -}
71   -
72   -// 防护 2:schema 名需像测试/开发库(含 test / _dev / _local / _ci),否则拒绝。
73   -const schemaLooksLikeTest =
74   - /test/.test(DB_SCHEMA) || /_dev$/.test(DB_SCHEMA) || /_local$/.test(DB_SCHEMA) || /_ci$/.test(DB_SCHEMA)
75   -if (!schemaLooksLikeTest) {
76   - console.error(
77   - `[setup-test-db] schema '${DB_SCHEMA}' 不像测试库(期望命名含 test / _dev / _local / _ci)`
78   - )
  71 + console.error(`[setup-test-db] database.port 非法: ${DB_PORT}(必须是 1..65535 的整数)`)
79 72 process.exit(1)
80 73 }
81 74  
82 75 console.log(`[setup-test-db] 即将 DROP + CREATE \`${DB_SCHEMA}\` on ${DB_HOST}:${DB_PORT}`)
83   -if (!['localhost', '127.0.0.1', '::1'].includes(DB_HOST)) {
84   - console.log(
85   - '[setup-test-db] 目标是 **远程** host(已在 TEST_DB_ALLOWED_HOSTS 白名单中,每次 test.mjs 都会 DROP)'
86   - )
87   - console.log(`[setup-test-db] 当前白名单: ${allowedHosts.join(' ')}`)
88   - console.log(
89   - '[setup-test-db] 若不希望每次自动 DROP,从 .env.local 的 TEST_DB_ALLOWED_HOSTS 删掉此 host'
90   - )
91   -}
92 76  
93 77 const sql =
94 78 `DROP DATABASE IF EXISTS \`${DB_SCHEMA}\`; ` +
... ...