107 lines
3.0 KiB
JavaScript
107 lines
3.0 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Soul 创业派对 - MySQL 直接执行脚本
|
|
* 当 MCP MySQL 因端口非 3306 无法连接时,用此脚本执行 SQL。
|
|
*
|
|
* 用法:
|
|
* node run.js "SELECT 1"
|
|
* node run.js -f migration.sql
|
|
* node run.js --dsn "user:pass@tcp(host:port)/db" "SELECT 1"
|
|
*
|
|
* 凭证来源(优先级):
|
|
* 1. 环境变量 DB_DSN
|
|
* 2. 参数 --dsn
|
|
* 3. 读取 soul-api/.env 中的 DB_DSN
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
function parseDsn(dsn) {
|
|
// user:password@tcp(host:port)/database?params
|
|
const match = dsn.match(/^([^:]+):([^@]+)@tcp\(([^:]+):(\d+)\)\/([^?]+)/);
|
|
if (!match) throw new Error('DB_DSN 格式应为: user:password@tcp(host:port)/database');
|
|
return {
|
|
user: match[1],
|
|
password: match[2],
|
|
host: match[3],
|
|
port: parseInt(match[4], 10),
|
|
database: match[5],
|
|
};
|
|
}
|
|
|
|
function loadDsn() {
|
|
if (process.env.DB_DSN) return process.env.DB_DSN;
|
|
const envPath = path.resolve(__dirname, '../../../soul-api/.env');
|
|
if (fs.existsSync(envPath)) {
|
|
const content = fs.readFileSync(envPath, 'utf8');
|
|
const line = content.split('\n').find((l) => l.trim().startsWith('DB_DSN='));
|
|
if (line) {
|
|
const val = line.split('=')[1]?.trim();
|
|
if (val) return val.replace(/^["']|["']$/g, '');
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
async function main() {
|
|
let dsn = process.argv.find((a) => a.startsWith('--dsn='))?.split('=')[1];
|
|
if (!dsn) dsn = loadDsn();
|
|
if (!dsn) {
|
|
console.error('未找到 DB_DSN。请设置环境变量 DB_DSN 或确保 soul-api/.env 中有 DB_DSN');
|
|
process.exit(1);
|
|
}
|
|
|
|
let sql = '';
|
|
const fileArg = process.argv.indexOf('-f');
|
|
if (fileArg !== -1 && process.argv[fileArg + 1]) {
|
|
const filePath = path.resolve(process.cwd(), process.argv[fileArg + 1]);
|
|
if (!fs.existsSync(filePath)) {
|
|
console.error('文件不存在:', filePath);
|
|
process.exit(1);
|
|
}
|
|
sql = fs.readFileSync(filePath, 'utf8');
|
|
} else {
|
|
const sqlArg = process.argv.find((a, i) => i > 0 && !a.startsWith('-') && a !== '-f');
|
|
if (!sqlArg) {
|
|
console.error('用法: node run.js "SQL" 或 node run.js -f migration.sql');
|
|
process.exit(1);
|
|
}
|
|
sql = sqlArg;
|
|
}
|
|
|
|
if (!sql.trim()) {
|
|
console.error('SQL 为空');
|
|
process.exit(1);
|
|
}
|
|
|
|
const config = parseDsn(dsn);
|
|
const mysql = require('mysql2/promise');
|
|
const conn = await mysql.createConnection({
|
|
host: config.host,
|
|
port: config.port,
|
|
user: config.user,
|
|
password: config.password,
|
|
database: config.database,
|
|
multipleStatements: true,
|
|
});
|
|
|
|
try {
|
|
const [result] = await conn.query(sql);
|
|
if (Array.isArray(result)) {
|
|
console.log(JSON.stringify(result, null, 2));
|
|
} else if (result && typeof result === 'object' && 'affectedRows' in result) {
|
|
console.log(JSON.stringify({ affectedRows: result.affectedRows, insertId: result.insertId }, null, 2));
|
|
} else {
|
|
console.log(result);
|
|
}
|
|
} catch (err) {
|
|
console.error('执行失败:', err.message);
|
|
process.exit(1);
|
|
} finally {
|
|
await conn.end();
|
|
}
|
|
}
|
|
|
|
main();
|