3b385ef575
- scripts/lint-staged-rustfmt.mjs:12 [maintainability medium] 改为读取 lint-staged 追加的暂存 .rs 路径,只对真正命中暂存文件的 workspace 跑 cargo fmt --all -- --check:此前永远全量跑两个 workspace,只暂存 server-rs 干净文件的提交会被别人未暂存的 src-tauri 改动挡下来;没收到暂存列表(手工执行)时回退全量检查并在 stderr 说明,避免假装检查过。 - scripts/lint-staged-rustfmt.mjs:7 [maintainability low] apps/desktop-shell/src-tauri 显式记为「有意排除」而不是静默通过:实测该 workspace 当前整体不过 cargo fmt --check(host_bridge/mod.rs 等),直接纳入会让任何 Rust 提交都失败;命中该前缀的暂存文件会打印跳过提示,纳入步骤写在模块注释里(同时要补 package.json 的 check:rustfmt / format:rust,package.json 不在本批次改动范围)。 - 新增 scripts/lint-staged-rustfmt-workspaces.mjs:把「暂存路径 → 待检查 workspace」的纯映射逻辑独立出来,附 Windows 反斜杠/绝对路径归一化。 - 新增 scripts/lint-staged-rustfmt.test.ts(vitest,随 npm test 运行):覆盖只暂存单侧 workspace 的回归判据、手工执行回退全量、排除项与未登记路径必须被点名、路径归一化,以及与 package.json check:rustfmt 的 workspace 表同集交叉校验。
114 lines
4.1 KiB
TypeScript
114 lines
4.1 KiB
TypeScript
import { readFileSync } from 'node:fs';
|
|
import { dirname, resolve } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
import { describe, expect, test } from 'vitest';
|
|
|
|
import {
|
|
RUSTFMT_EXCLUDED_WORKSPACES,
|
|
RUSTFMT_WORKSPACES,
|
|
selectRustfmtWorkspaces,
|
|
} from './lint-staged-rustfmt-workspaces.mjs';
|
|
|
|
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
|
|
function selectedManifests(stagedFiles: string[], root = repoRoot) {
|
|
return selectRustfmtWorkspaces({
|
|
stagedFiles,
|
|
repoRoot: root,
|
|
}).workspaces.map((workspace) => workspace.manifestPath);
|
|
}
|
|
|
|
describe('lint-staged Rust 格式检查的 workspace 选择', () => {
|
|
test('只暂存 server-rs 时不再连带检查 src-tauri', () => {
|
|
// 回归判据:`cargo fmt --all` 会连未暂存的在改文件一起查,全量跑两个 workspace
|
|
// 会让「只暂存了 server-rs 干净文件」的提交被别人的 src-tauri 改动挡下来。
|
|
expect(
|
|
selectedManifests([
|
|
'server-rs/crates/api-server/src/editor_project.rs',
|
|
'server-rs/crates/shared-contracts/src/game_creation_app.rs',
|
|
]),
|
|
).toEqual(['server-rs/Cargo.toml']);
|
|
});
|
|
|
|
test('只暂存 src-tauri 时不再连带检查 server-rs', () => {
|
|
expect(
|
|
selectedManifests(['apps/ai-game-creator-shell/src-tauri/src/assets.rs']),
|
|
).toEqual(['apps/ai-game-creator-shell/src-tauri/Cargo.toml']);
|
|
});
|
|
|
|
test('两个 workspace 都有暂存文件时两个都查', () => {
|
|
expect(
|
|
selectedManifests([
|
|
'server-rs/crates/api-server/src/editor_project.rs',
|
|
'apps/ai-game-creator-shell/src-tauri/src/assets.rs',
|
|
]),
|
|
).toEqual([
|
|
'server-rs/Cargo.toml',
|
|
'apps/ai-game-creator-shell/src-tauri/Cargo.toml',
|
|
]);
|
|
});
|
|
|
|
test('手工执行(没有暂存列表)时保持全量检查语义', () => {
|
|
const selection = selectRustfmtWorkspaces({ stagedFiles: [], repoRoot });
|
|
expect(selection.usedStagedList).toBe(false);
|
|
expect(selection.workspaces).toEqual(RUSTFMT_WORKSPACES);
|
|
});
|
|
|
|
test('排除的 workspace 与未登记路径都会被显式报出,不静默通过', () => {
|
|
const selection = selectRustfmtWorkspaces({
|
|
stagedFiles: [
|
|
'apps/desktop-shell/src-tauri/src/host_bridge/mod.rs',
|
|
'somewhere-new/src/lib.rs',
|
|
],
|
|
repoRoot,
|
|
});
|
|
// desktop-shell 当前整体未过 `cargo fmt --check`,所以它不参与检查……
|
|
expect(selection.workspaces).toEqual([]);
|
|
// ……但必须被点名,否则「glob 命中了、谁都没查」就是静默通过。
|
|
expect(selection.excluded).toEqual([
|
|
'apps/desktop-shell/src-tauri/src/host_bridge/mod.rs',
|
|
]);
|
|
expect(selection.unmanaged).toEqual(['somewhere-new/src/lib.rs']);
|
|
});
|
|
|
|
test('Windows 反斜杠与绝对路径都归一化成仓库相对路径', () => {
|
|
const root = 'C:\\repo\\genarrative';
|
|
expect(
|
|
selectedManifests(
|
|
[
|
|
'server-rs\\crates\\api-server\\src\\editor_project.rs',
|
|
`${root}\\apps\\ai-game-creator-shell\\src-tauri\\src\\assets.rs`,
|
|
],
|
|
root,
|
|
),
|
|
).toEqual([
|
|
'server-rs/Cargo.toml',
|
|
'apps/ai-game-creator-shell/src-tauri/Cargo.toml',
|
|
]);
|
|
});
|
|
|
|
test('检查清单与 package.json 的 check:rustfmt 保持同一份 workspace 表', () => {
|
|
// 两处各写一份 workspace 列表就会漂移:hook 查的与 `npm run check:rustfmt` 查的必须同集。
|
|
const packageJson = JSON.parse(
|
|
readFileSync(resolve(repoRoot, 'package.json'), 'utf8'),
|
|
) as { scripts: Record<string, string> };
|
|
const manifests = Array.from(
|
|
packageJson.scripts['check:rustfmt']!.matchAll(
|
|
/--manifest-path\s+(\S+)\s+--\s+--check/gu,
|
|
),
|
|
).map((match) => match[1]!);
|
|
expect(manifests).toEqual(
|
|
RUSTFMT_WORKSPACES.map((workspace) => workspace.manifestPath),
|
|
);
|
|
// 排除项必须是「检查清单之外」的,否则这段注释与实际行为不符。
|
|
for (const excluded of RUSTFMT_EXCLUDED_WORKSPACES) {
|
|
expect(
|
|
RUSTFMT_WORKSPACES.some((workspace) =>
|
|
workspace.prefix.startsWith(excluded.prefix),
|
|
),
|
|
).toBe(false);
|
|
}
|
|
});
|
|
});
|