Files
Genarrative/scripts/lint-staged-rustfmt.test.ts
T
kdletters 33f5ad68bf
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 7m19s
Project CI / AI game creator shell Rust shard 3/4 (push) Successful in 7m26s
Project CI / AI game creator shell Rust shard 4/4 (push) Successful in 7m32s
Project CI / AI game creator shell Rust shard 2/4 (push) Successful in 7m34s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m57s
Project CI / AI game creator shell Rust crates (push) Successful in 3m18s
Project CI / Native shell tests (push) Successful in 10m54s
Project CI / Backend tests (push) Successful in 12m42s
Project CI / Frontend tests (push) Successful in 13m4s
Project CI / AI game creator shell web tests (push) Successful in 5m1s
Project CI / Repository checks (push) Successful in 12m48s
接入 DotCraft Unity 编辑器插件与受控执行链路 (#423)
AGC 原有插件系统无法直接操作已打开的 Unity Editor。本变更增加内置 `agc-unity-editor`,在 Windows x64 / Unity Mono 上支持当前项目探测、连接与 C# 执行,不向 Unity 工程安装 UPM 桥接包。

## 主要变更

- 固定复用 DotCraft.Unity 0.4.3 的 Attach 核心,提供自包含 .NET helper,保留上游许可证、来源及修改记录。
- GUI、Runtime、DirectProject 共用 Runner 执行服务;补齐项目身份、并发、总期限、回执确认与持久不确定状态阻断。
- 现有打开项目入口支持 Unity,按项目类型及开关暴露插件和 Agent 工具。
- Windows 构建准备 helper 并随包分发;插件 JS/Rust 测试接入现有 CI 组,Jenkins 增加 .NET 10 工具链预检。

## 验证

- .NET helper 27 项测试、自包含发布及最小环境协议 smoke 通过。
- Unity 6000.3.7f1 实机验证通过:连接、C# 执行、编译错误修复、断连重连、Domain Reload 后重新握手;真实 Runner 的 ACK、并发拒绝和跨重启阻断通过。
- 宿主 Unity、PluginHost、Cocos、MCP、工具目录与引擎识别定向回归通过;前端类型检查、插件 JS/Rust、CI 配置、格式、编码和文档门禁通过。

Linux CI 不代替 Windows helper/实机验证;发行安装包 UI smoke、其它 Unity 版本和 Unity CoreCLR 未验证。Unity 演示工程中的场景和组件已撤销,不在此 PR 范围内。

---------

Co-authored-by: kdletters <61648117+kdletters@users.noreply.github.com>
Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/423
2026-09-19 12:29:27 +08:00

116 lines
4.3 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',
'plugins/agc-unity-editor/native/unity-editor-bridge/src/lib.rs',
]),
).toEqual([
'server-rs/Cargo.toml',
'apps/ai-game-creator-shell/src-tauri/Cargo.toml',
'plugins/agc-unity-editor/native/unity-editor-bridge/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);
}
});
});