Skip to content

Commit 7de7862

Browse files
Merge pull request #96 from uniocjs/dev-groupguanfang
chore: add post commit script
2 parents 265f301 + 1f6b757 commit 7de7862

File tree

6 files changed

+194
-147
lines changed

6 files changed

+194
-147
lines changed

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"pnpm": "^9.15.9",
5252
"prompts": "^2.4.2",
5353
"reflect-metadata": "^0.2.2",
54+
"simple-git": "^3.27.0",
5455
"simple-git-hooks": "^2.12.1",
5556
"source-map-support": "^0.5.21",
5657
"supertest": "^7.1.0",
@@ -68,7 +69,8 @@
6869
"zod": "^3.24.3"
6970
},
7071
"simple-git-hooks": {
71-
"pre-commit": "pnpm lint-staged"
72+
"pre-commit": "pnpm lint-staged",
73+
"post-commit": "pnpm tsx ./scripts/post-commit.ts"
7274
},
7375
"lint-staged": {
7476
"*": "eslint . --fix"

pnpm-lock.yaml

+28
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/chore.ts

-65
This file was deleted.

scripts/i18n.ts

-65
This file was deleted.

scripts/post-commit.ts

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import fs from 'node:fs'
2+
import path from 'node:path'
3+
import process from 'node:process'
4+
import fg from 'fast-glob'
5+
import { load } from 'js-yaml'
6+
import k from 'kleur'
7+
import simpleGit from 'simple-git'
8+
9+
// eslint-disable-next-line ts/naming-convention
10+
let __dirname = globalThis.__dirname
11+
if (!globalThis.__dirname)
12+
__dirname = new URL('.', import.meta.url).pathname
13+
14+
const git = simpleGit()
15+
const workspacePath = path.resolve(__dirname, '../pnpm-workspace.yaml')
16+
const commitMessageRegex = /^([a-z]+)(\(.+\))?: (.*)$/
17+
const changesetMatchOptions: IChangesetMatchOptions = {
18+
minor: [/^feat/],
19+
patch: [/^fix/, /^perf/, /^refactor/, /^style/, /^docs/, /^chore/, /^revert/, /^ci/, /^build/, /^test/],
20+
ignore: [/^chore\(release\)/],
21+
}
22+
23+
export type ChangesetType = 'patch' | 'minor' | 'major'
24+
export interface IChangesetMatchOptions extends Partial<Record<ChangesetType, RegExp[]>> {
25+
default?: ChangesetType
26+
ignore?: RegExp[]
27+
}
28+
29+
interface ICommittedFile {
30+
filePath: string
31+
}
32+
33+
interface IWorkspacePackage {
34+
folderPath: string
35+
packageName: string
36+
}
37+
38+
export async function getLastCommitId() {
39+
const log = await git.log({ maxCount: 1 })
40+
return log.latest?.hash || ''
41+
}
42+
43+
export async function getLastCommitFiles(): Promise<ICommittedFile[]> {
44+
// 使用git命令直接获取最近一次commit的文件列表
45+
const result = await git.raw(['diff-tree', '--no-commit-id', '--name-only', '-r', 'HEAD'])
46+
// 分割成数组并过滤空行
47+
const files = result.trim().split('\n').filter(Boolean)
48+
// 转换为绝对路径
49+
return files.map(file => ({ filePath: path.resolve(file) }))
50+
}
51+
52+
export async function getLastCommitMessage(): Promise<string> {
53+
const log = await git.log({ maxCount: 1 })
54+
return log.latest?.message || ''
55+
}
56+
57+
async function getWorkspacePackages(): Promise<IWorkspacePackage[]> {
58+
if (!fs.existsSync(workspacePath))
59+
return []
60+
if (!fs.statSync(workspacePath).isFile())
61+
return []
62+
63+
const workspace = fs.readFileSync(workspacePath, 'utf-8')
64+
const parsedWorkspace = load(workspace)
65+
if (!parsedWorkspace || typeof parsedWorkspace !== 'object' || !('packages' in parsedWorkspace) || !Array.isArray(parsedWorkspace.packages))
66+
return []
67+
const { packages } = parsedWorkspace
68+
return packages
69+
.map(packageGlob => (fg.sync(packageGlob || '', {
70+
onlyDirectories: true,
71+
onlyFiles: false,
72+
absolute: true,
73+
deep: 1,
74+
})))
75+
// 拍平
76+
.flat()
77+
// 去重
78+
.filter((folderPath, index, self) => self.findIndex(fp => fp === folderPath) === index)
79+
// 没有package.json的文件夹 不视为一个workspace
80+
.filter((folderPath) => {
81+
const packageJsonPath = path.resolve(folderPath, 'package.json')
82+
return fs.existsSync(packageJsonPath) && fs.statSync(packageJsonPath).isFile()
83+
})
84+
.map(folderPath => ({
85+
folderPath,
86+
packageName: JSON.parse(fs.readFileSync(path.resolve(folderPath, 'package.json'), 'utf-8')).name,
87+
}))
88+
}
89+
90+
interface IWorkspaceCommittedFile extends ICommittedFile {
91+
workspace: IWorkspacePackage | null
92+
}
93+
94+
async function getWorkspaceCommittedFiles(committedFiles: ICommittedFile[], workspacePackages: IWorkspacePackage[]): Promise<IWorkspaceCommittedFile[]> {
95+
return committedFiles.map((committedFile): IWorkspaceCommittedFile => ({
96+
...committedFile,
97+
workspace: workspacePackages.find(workspacePackage => committedFile.filePath.startsWith(workspacePackage.folderPath)) || null,
98+
}))
99+
}
100+
101+
async function getMatchedChangeset(lastCommitMessage: string): Promise<[ChangesetType, string] | undefined> {
102+
if (!lastCommitMessage)
103+
return undefined
104+
if (!commitMessageRegex.test(lastCommitMessage))
105+
return undefined
106+
107+
const match = lastCommitMessage.match(commitMessageRegex)
108+
if (!match)
109+
return undefined
110+
const [, commitType, , commitMessage] = match
111+
if (!commitType)
112+
return undefined
113+
114+
for (const changesetType of Object.keys(changesetMatchOptions) as ChangesetType[]) {
115+
const matchOptions = changesetMatchOptions[changesetType]
116+
if (!matchOptions)
117+
continue
118+
119+
for (const matchOption of matchOptions) {
120+
if (matchOption.test(commitType))
121+
return [changesetType, commitMessage]
122+
}
123+
}
124+
125+
return undefined
126+
}
127+
128+
async function createMarkdown(changesetType: ChangesetType, workspaceCommittedFiles: IWorkspaceCommittedFile[], commitMessage: string, lastCommitId: string) {
129+
return `---
130+
${workspaceCommittedFiles.filter(v => v.workspace !== null).map(file => `"${file.workspace?.packageName}": "${changesetType}"`).join('\n')}
131+
---
132+
133+
${lastCommitId} ${commitMessage}
134+
`
135+
}
136+
137+
async function main() {
138+
const committedFiles = await getLastCommitFiles()
139+
const lastCommitId = await getLastCommitId()
140+
const workspacePackages = await getWorkspacePackages()
141+
const workspaceCommittedFiles = await getWorkspaceCommittedFiles(committedFiles, workspacePackages)
142+
const lastCommitMessage = await getLastCommitMessage()
143+
const matchedChangeset = await getMatchedChangeset(lastCommitMessage)
144+
if (!matchedChangeset)
145+
return console.warn(`The commit message "${lastCommitMessage}" does not match any angular changeset type.`)
146+
const [changesetType, commitMessage] = matchedChangeset
147+
148+
console.log(`${workspaceCommittedFiles.map(v => k.dim(`${k.green(`[${v.workspace ? '✓' : '✗'}]`)} ${path.relative(process.cwd(), v.filePath)}: detected workspace: ${v.workspace?.folderPath ? path.relative(process.cwd(), v.workspace.folderPath) : 'null'}`)).join('\n')}`)
149+
if (workspaceCommittedFiles.every(v => v.workspace === null))
150+
return console.warn(k.yellow(`The commit "${lastCommitMessage}" has no files in the workspace.`))
151+
else if (changesetMatchOptions.ignore?.some(v => v.test(lastCommitMessage)))
152+
return console.warn(k.yellow(`The commit "${lastCommitMessage}" is ignored.`))
153+
154+
console.log(k.dim(`Creating changeset file for ${lastCommitId}.md ${lastCommitMessage}`))
155+
const markdown = await createMarkdown(changesetType, workspaceCommittedFiles, commitMessage, lastCommitId)
156+
fs.writeFileSync(`.changeset/${lastCommitId}.md`, markdown)
157+
await git.add('--all')
158+
await git.commit(`docs(changeset): ${lastCommitId} ${commitMessage}`, {
159+
'--no-verify': true,
160+
} as any)
161+
}
162+
163+
main()

scripts/setup-nightly-date.ts

-16
This file was deleted.

0 commit comments

Comments
 (0)