feat: 全栈 Logger 依赖注入 — DB/Route/AI 层传参 + 前端 Logger + 测试更新 + 归档 add-frontend-logger

This commit is contained in:
2026-06-01 20:32:19 +08:00
parent 4c72754739
commit 844562303c
60 changed files with 1648 additions and 778 deletions

View File

@@ -7,87 +7,9 @@
import { execFileSync } from "node:child_process";
import { existsSync, mkdirSync } from "node:fs";
import { resolve, relative } from "node:path";
import { relative, resolve } from "node:path";
import { createInterface } from "node:readline";
function git(args, opts) {
return execFileSync("git", args, { encoding: "utf-8", stdio: "pipe", ...opts });
}
function getRootDir() {
try {
return resolve(git(["rev-parse", "--show-toplevel"]).trim());
} catch {
console.error("错误: 不在 git 仓库中");
process.exit(1);
}
}
function fetchRemote() {
try {
git(["fetch", "--quiet"]);
} catch {
console.warn("警告: 无法获取远端信息,继续使用本地数据");
}
}
function listRemoteBranches() {
try {
return git(["branch", "-r"])
.trim()
.split(/\r?\n/)
.map((l) => l.trim())
.filter((l) => l && !l.includes(" -> "));
} catch {
return [];
}
}
function matchingRemoteBranches(name) {
return listRemoteBranches().filter((l) => l.endsWith(`/${name}`));
}
function localBranchExists(name) {
try {
git(["show-ref", "--verify", "--quiet", `refs/heads/${name}`]);
return true;
} catch {
return false;
}
}
function worktreeExists(worktreeDir) {
try {
const out = git(["worktree", "list"]);
const target = resolve(worktreeDir);
return out
.split(/\r?\n/)
.some((line) => {
const fields = line.trim().split(/\s+/);
return fields.length > 0 && resolve(fields[0]) === target;
});
} catch {
return false;
}
}
function shortBranchName(remoteRef) {
const idx = remoteRef.indexOf("/");
return idx === -1 ? remoteRef : remoteRef.slice(idx + 1);
}
function assertCanCreate(name, dir) {
if (existsSync(dir)) {
throw new Error(`工作区已存在于 ${dir}`);
}
if (worktreeExists(dir)) {
throw new Error(`工作区 '${name}' 已存在`);
}
if (localBranchExists(name)) {
throw new Error(`本地分支 '${name}' 已存在`);
}
}
function addWorktree(name, dir, base) {
const args = ["worktree", "add", "-b", name, dir];
if (base) args.push(base);
@@ -104,62 +26,39 @@ function ask(rl, prompt) {
return new Promise((resolve) => rl.question(prompt, resolve));
}
async function selectFromList(items, prompt, allowCreate) {
if (items.length === 0) return null;
console.log(prompt);
items.forEach((item, i) => console.log(` ${i + 1}\t${item}`));
if (allowCreate) console.log(` ${items.length + 1}\t创建新分支`);
console.log();
const max = allowCreate ? items.length + 1 : items.length;
const rl = createInterface({ input: process.stdin, output: process.stdout });
let cancelled = false;
rl.on("close", () => { cancelled = true; });
while (true) {
const raw = await ask(rl, `请选择 (1-${max}): `);
if (cancelled) {
rl.close();
process.exit(1);
}
const n = Number.parseInt(raw, 10);
if (Number.isNaN(n) || n < 1 || n > max) {
console.log(`错误: 请输入 1-${max} 之间的数字`);
continue;
}
if (n <= items.length) {
const sel = items[n - 1];
console.log(`已选择: ${sel}`);
rl.close();
return sel;
}
rl.close();
return null;
function assertCanCreate(name, dir) {
if (existsSync(dir)) {
throw new Error(`工作区已存在于 ${dir}`);
}
if (worktreeExists(dir)) {
throw new Error(`工作区 '${name}' 已存在`);
}
if (localBranchExists(name)) {
throw new Error(`本地分支 '${name}' 已存在`);
}
}
async function inputBranchName() {
const rl = createInterface({ input: process.stdin, output: process.stdout });
let cancelled = false;
rl.on("close", () => { cancelled = true; });
while (true) {
const name = (await ask(rl, "请输入新分支名称: ")).trim();
if (cancelled) {
rl.close();
process.exit(1);
}
if (name) {
rl.close();
return name;
}
console.log("错误: 分支名称不能为空");
function fetchRemote() {
try {
git(["fetch", "--quiet"]);
} catch {
console.warn("警告: 无法获取远端信息,继续使用本地数据");
}
}
function getRootDir() {
try {
return resolve(git(["rev-parse", "--show-toplevel"]).trim());
} catch {
console.error("错误: 不在 git 仓库中");
process.exit(1);
}
}
function git(args, opts) {
return execFileSync("git", args, { encoding: "utf-8", stdio: "pipe", ...opts });
}
async function handleWithName(name, worktreesDir) {
const dir = resolve(worktreesDir, name);
@@ -218,6 +117,109 @@ async function handleWithoutName(worktreesDir) {
}
}
async function inputBranchName() {
const rl = createInterface({ input: process.stdin, output: process.stdout });
let cancelled = false;
rl.on("close", () => {
cancelled = true;
});
while (true) {
const name = (await ask(rl, "请输入新分支名称: ")).trim();
if (cancelled) {
rl.close();
process.exit(1);
}
if (name) {
rl.close();
return name;
}
console.log("错误: 分支名称不能为空");
}
}
function listRemoteBranches() {
try {
return git(["branch", "-r"])
.trim()
.split(/\r?\n/)
.map((l) => l.trim())
.filter((l) => l && !l.includes(" -> "));
} catch {
return [];
}
}
function localBranchExists(name) {
try {
git(["show-ref", "--verify", "--quiet", `refs/heads/${name}`]);
return true;
} catch {
return false;
}
}
function matchingRemoteBranches(name) {
return listRemoteBranches().filter((l) => l.endsWith(`/${name}`));
}
async function selectFromList(items, prompt, allowCreate) {
if (items.length === 0) return null;
console.log(prompt);
items.forEach((item, i) => console.log(` ${i + 1}\t${item}`));
if (allowCreate) console.log(` ${items.length + 1}\t创建新分支`);
console.log();
const max = allowCreate ? items.length + 1 : items.length;
const rl = createInterface({ input: process.stdin, output: process.stdout });
let cancelled = false;
rl.on("close", () => {
cancelled = true;
});
while (true) {
const raw = await ask(rl, `请选择 (1-${max}): `);
if (cancelled) {
rl.close();
process.exit(1);
}
const n = Number.parseInt(raw, 10);
if (Number.isNaN(n) || n < 1 || n > max) {
console.log(`错误: 请输入 1-${max} 之间的数字`);
continue;
}
if (n <= items.length) {
const sel = items[n - 1];
console.log(`已选择: ${sel}`);
rl.close();
return sel;
}
rl.close();
return null;
}
}
function shortBranchName(remoteRef) {
const idx = remoteRef.indexOf("/");
return idx === -1 ? remoteRef : remoteRef.slice(idx + 1);
}
function worktreeExists(worktreeDir) {
try {
const out = git(["worktree", "list"]);
const target = resolve(worktreeDir);
return out.split(/\r?\n/).some((line) => {
const fields = line.trim().split(/\s+/);
return fields.length > 0 && resolve(fields[0]) === target;
});
} catch {
return false;
}
}
process.on("SIGINT", () => process.exit(1));
async function main() {