make the test suite and openapi generator platform-independent
Windows checkouts turned LF into CRLF (core.autocrlf=true), and several tests plus the openapi generator hardcoded LF or POSIX path separators. The result was a suite that was green on Linux CI but red on Windows, and an openapi.json whose /api/hermes/* routes silently disappeared when generated on Windows. Pin the repository to LF via .gitattributes, strip shebangs before Vite's SSR transform, and key route files by their posix path. Add a manually triggered macOS verification workflow so the same checks run on a self-hosted macOS runner.
This commit is contained in:
@@ -35,6 +35,26 @@ async function loadController() {
|
||||
return import('../../packages/server/src/controllers/hermes/skills')
|
||||
}
|
||||
|
||||
/**
|
||||
* 把「用户主目录」指向临时目录,并返回恢复函数。
|
||||
*
|
||||
* Node 的 os.homedir() 在 Windows 上优先读 USERPROFILE,在 POSIX 上优先读 HOME。
|
||||
* 只设 HOME 会让 Windows 落到真实用户目录,夹具就找不到,测试只在 Linux 上是绿的。
|
||||
* 两个都设,跨平台行为一致。
|
||||
*/
|
||||
function useHomeDir(root: string): () => void {
|
||||
const previousHome = process.env.HOME
|
||||
const previousUserProfile = process.env.USERPROFILE
|
||||
process.env.HOME = root
|
||||
process.env.USERPROFILE = root
|
||||
return () => {
|
||||
if (previousHome == null) delete process.env.HOME
|
||||
else process.env.HOME = previousHome
|
||||
if (previousUserProfile == null) delete process.env.USERPROFILE
|
||||
else process.env.USERPROFILE = previousUserProfile
|
||||
}
|
||||
}
|
||||
|
||||
function multipartBody(boundary: string, parts: Array<{ name: string; value: string; filename?: string; filenameStar?: string; contentType?: string }>): Buffer {
|
||||
const chunks: Buffer[] = []
|
||||
for (const part of parts) {
|
||||
@@ -200,7 +220,6 @@ describe('skills controller', () => {
|
||||
|
||||
it('lists Codex user and system skills for the codex target', async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), 'hermes-ui-codex-skills-'))
|
||||
const previousHome = process.env.HOME
|
||||
const userSkillDir = join(root, '.agents', 'skills', 'user-skill')
|
||||
const systemSkillDir = join(root, '.codex', 'skills', '.system', 'system-skill')
|
||||
|
||||
@@ -208,7 +227,7 @@ describe('skills controller', () => {
|
||||
await mkdir(systemSkillDir, { recursive: true })
|
||||
await writeFile(join(userSkillDir, 'SKILL.md'), '# User Skill\nuser codex skill\n', 'utf-8')
|
||||
await writeFile(join(systemSkillDir, 'SKILL.md'), '# System Skill\nsystem codex skill\n', 'utf-8')
|
||||
process.env.HOME = root
|
||||
const restoreHome = useHomeDir(root)
|
||||
|
||||
try {
|
||||
const { list } = await loadController()
|
||||
@@ -227,21 +246,19 @@ describe('skills controller', () => {
|
||||
})
|
||||
expect(mockReadConfigYamlForProfile).not.toHaveBeenCalled()
|
||||
} finally {
|
||||
if (previousHome == null) delete process.env.HOME
|
||||
else process.env.HOME = previousHome
|
||||
restoreHome()
|
||||
await rm(root, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('reads Codex system skill details for the codex target', async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), 'hermes-ui-codex-system-skill-'))
|
||||
const previousHome = process.env.HOME
|
||||
const systemSkillDir = join(root, '.codex', 'skills', '.system', 'imagegen')
|
||||
|
||||
await mkdir(join(systemSkillDir, 'references'), { recursive: true })
|
||||
await writeFile(join(systemSkillDir, 'SKILL.md'), '# Imagegen\nsystem image skill\n', 'utf-8')
|
||||
await writeFile(join(systemSkillDir, 'references', 'usage.md'), 'usage notes\n', 'utf-8')
|
||||
process.env.HOME = root
|
||||
const restoreHome = useHomeDir(root)
|
||||
mockListFilesRecursive.mockResolvedValue([
|
||||
{ path: 'SKILL.md', isDir: false },
|
||||
{ path: 'references/usage.md', isDir: false },
|
||||
@@ -271,8 +288,7 @@ describe('skills controller', () => {
|
||||
|
||||
expect(filesCtx.body).toEqual({ files: [{ path: 'references/usage.md', isDir: false }] })
|
||||
} finally {
|
||||
if (previousHome == null) delete process.env.HOME
|
||||
else process.env.HOME = previousHome
|
||||
restoreHome()
|
||||
await rm(root, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
@@ -477,7 +493,6 @@ describe('skills controller', () => {
|
||||
|
||||
it('updates Codex user skills but not Codex system skills', async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), 'hermes-ui-update-codex-skill-'))
|
||||
const previousHome = process.env.HOME
|
||||
const userSkillDir = join(root, '.agents', 'skills', 'user-skill')
|
||||
const systemSkillDir = join(root, '.codex', 'skills', '.system', 'system-skill')
|
||||
|
||||
@@ -485,7 +500,7 @@ describe('skills controller', () => {
|
||||
await mkdir(systemSkillDir, { recursive: true })
|
||||
await writeFile(join(userSkillDir, 'SKILL.md'), '# User Skill\n', 'utf-8')
|
||||
await writeFile(join(systemSkillDir, 'SKILL.md'), '# System Skill\n', 'utf-8')
|
||||
process.env.HOME = root
|
||||
const restoreHome = useHomeDir(root)
|
||||
|
||||
try {
|
||||
const { updateSkill } = await loadController()
|
||||
@@ -516,8 +531,7 @@ describe('skills controller', () => {
|
||||
expect(systemCtx.status).toBe(404)
|
||||
expect(systemCtx.body).toEqual({ error: 'Skill not found' })
|
||||
} finally {
|
||||
if (previousHome == null) delete process.env.HOME
|
||||
else process.env.HOME = previousHome
|
||||
restoreHome()
|
||||
await rm(root, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user