♻️ refactor(vscode): 抽出 worktreePath 模块(slug / 目录模板解析)
Claude-Session: https://claude.ai/code/session_011cEyL6k351U2BzX1Qmygph
This commit is contained in:
1 parent
4b66d567b0
commit
3bc73d9dd6
3 files changed
+78
-45
No files matched your search
@@ -0,0 +1,32 @@
|
||||
import * as os from 'node:os'
|
||||
import * as path from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { DEFAULT_WORKTREE_TEMPLATE, deriveSlug, resolveWorktreeDir, slugFromBranch } from './worktreePath'
|
||||
|
||||
describe('deriveSlug', () => {
|
||||
it('取 plans/ 后一段', () => {
|
||||
expect(deriveSlug('docs/superpowers/plans/foo-bar/plan.md')).toBe('foo-bar')
|
||||
})
|
||||
it('没有 plans/specs 锚点时退回父目录名', () => {
|
||||
expect(deriveSlug('docs/x/baz/plan.md')).toBe('baz')
|
||||
})
|
||||
})
|
||||
|
||||
describe('slugFromBranch', () => {
|
||||
it('去掉 feature/ 前缀', () => {
|
||||
expect(slugFromBranch('feature/foo-bar')).toBe('foo-bar')
|
||||
})
|
||||
it('无前缀原样返回', () => {
|
||||
expect(slugFromBranch('hotfix-1')).toBe('hotfix-1')
|
||||
})
|
||||
})
|
||||
|
||||
describe('resolveWorktreeDir', () => {
|
||||
it('展开三个占位符与 ~', () => {
|
||||
const out = resolveWorktreeDir(DEFAULT_WORKTREE_TEMPLATE, '/w/proj', 'slug')
|
||||
expect(out).toBe(path.join(os.homedir(), 'Sources', 'worktree', 'proj', 'slug'))
|
||||
})
|
||||
it('$project_root 展开为工作区绝对路径', () => {
|
||||
expect(resolveWorktreeDir('$project_root/.wt/$feature_name', '/w/proj', 's')).toBe('/w/proj/.wt/s')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,42 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import * as os from 'node:os'
|
||||
import * as path from 'node:path'
|
||||
|
||||
export const DEFAULT_WORKTREE_TEMPLATE = '~/Sources/worktree/$project_name/$feature_name'
|
||||
|
||||
/**
|
||||
* spec/plan 以目录形式存放(docs/superpowers/{specs,plans}/<slug>/…),slug 取
|
||||
* `plans`/`specs` 后一段。兜底父目录名,再兜底路径 hash,保证目录名不为空。
|
||||
*/
|
||||
export function deriveSlug(planFile: string): string {
|
||||
const segments = planFile.split('/').filter(Boolean)
|
||||
const anchor = segments.findIndex(s => s === 'plans' || s === 'specs')
|
||||
if (anchor >= 0 && segments[anchor + 1])
|
||||
return segments[anchor + 1]
|
||||
const parent = path.basename(path.dirname(planFile))
|
||||
if (parent && parent !== '.' && parent !== '/')
|
||||
return parent
|
||||
return createHash('sha256').update(planFile).digest('hex').slice(0, 8)
|
||||
}
|
||||
|
||||
/** 实施分支固定形如 `feature/<slug>`;接管方只有分支名,从这里反推 worktree 目录名。 */
|
||||
export function slugFromBranch(branch: string): string {
|
||||
return branch.startsWith('feature/') ? branch.slice('feature/'.length) : branch
|
||||
}
|
||||
|
||||
/**
|
||||
* 把 worktreeDirectory 模板展开成绝对路径。`~` 在这里展开:git/execFile 不经 shell,
|
||||
* 不会自己做 tilde 展开。
|
||||
*/
|
||||
export function resolveWorktreeDir(template: string, workspaceRoot: string, slug: string): string {
|
||||
let p = template
|
||||
.split('$project_name')
|
||||
.join(path.basename(workspaceRoot))
|
||||
.split('$project_root')
|
||||
.join(workspaceRoot)
|
||||
.split('$feature_name')
|
||||
.join(slug)
|
||||
if (p.startsWith('~'))
|
||||
p = path.join(os.homedir(), p.slice(1))
|
||||
return path.resolve(p)
|
||||
}
|
||||
Reference in new issue
Block a user