accounts/dashboard/api/content-meta.ts
Haitao Pan f3d0a7f931 fix(dashboard): resolve npm run build errors
- Fix module path in prebuild script (../../scripts → ../scripts)
- Update API routes to use Promise-based params for Next.js 16
- Install @types/sanitize-html and @types/js-yaml
- Fix component prop naming (loading → isLoading, saving → isSaving, etc.)
- Update VLESS config type definition with missing 'id' property
- Fix TypeScript type conflicts and export declarations

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2025-11-06 11:07:30 +08:00

86 lines
2.3 KiB
TypeScript

import { execFile } from 'node:child_process'
import fs from 'node:fs/promises'
import path from 'node:path'
import { promisify } from 'node:util'
import { assertContentFile, ContentNotFoundError, toContentRelativePath } from './content-utils'
const execFileAsync = promisify(execFile)
export interface ContentCommitMeta {
path: string
updatedAt?: string
author?: string
message?: string
commit?: string
}
export async function getContentCommitMeta(requestPath: string): Promise<ContentCommitMeta> {
const absolutePath = await assertContentFile(requestPath)
const repoRoot = await findGitRoot(absolutePath)
if (!repoRoot) {
return {
path: toContentRelativePath(absolutePath),
}
}
const relativeToRepo = path.relative(repoRoot, absolutePath)
try {
const { stdout } = await execFileAsync(
'git',
['log', '-1', '--pretty=format:%H%x00%ct%x00%an%x00%s', '--', relativeToRepo],
{ cwd: repoRoot }
)
if (!stdout.trim()) {
return {
path: toContentRelativePath(absolutePath),
}
}
const [commit, timestamp, author, message] = stdout.trim().split('\0')
const updatedAt = timestamp ? new Date(Number(timestamp) * 1000).toISOString() : undefined
return {
path: toContentRelativePath(absolutePath),
commit,
updatedAt,
author: author || undefined,
message: message || undefined,
}
} catch (error) {
const err = error as NodeJS.ErrnoException & { stderr?: string }
if (err.code === 'ENOENT') {
throw new ContentNotFoundError(`Content file not found: ${requestPath}`)
}
if (err.code === '128' || err.stderr?.includes('unknown revision') || err.stderr?.includes('fatal')) {
return {
path: toContentRelativePath(absolutePath),
}
}
throw error
}
}
async function findGitRoot(filePath: string): Promise<string | null> {
let currentDir = path.dirname(filePath)
const root = path.parse(currentDir).root
while (currentDir && currentDir !== root) {
try {
const stat = await fs.stat(path.join(currentDir, '.git'))
if (stat.isDirectory()) {
return currentDir
}
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
throw error
}
}
currentDir = path.dirname(currentDir)
}
return null
}
export { ContentNotFoundError }