diff --git a/.husky/pre-commit b/.husky/pre-commit index 8ff2704..23b649a 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1 +1 @@ -pnpm run verify:commit +node scripts/husky-fast-check.mjs diff --git a/package.json b/package.json index 260d37e..ad376bb 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "lint": "pnpm run typecheck", "prepare": "husky", "typecheck": "pnpm -r --sort run typecheck", + "verify:staged": "node scripts/husky-fast-check.mjs", "verify:commit": "pnpm run lint && pnpm run build" }, "devDependencies": { diff --git a/scripts/husky-fast-check.mjs b/scripts/husky-fast-check.mjs new file mode 100644 index 0000000..daa0ea4 --- /dev/null +++ b/scripts/husky-fast-check.mjs @@ -0,0 +1,111 @@ +import { spawnSync } from 'node:child_process'; +import { existsSync } from 'node:fs'; + +const FULL_CHECK_FILES = new Set([ + 'package.json', + 'pnpm-lock.yaml', + 'pnpm-workspace.yaml', + 'tsconfig.base.json', + 'tsconfig.json', +]); +const WORKSPACES = [ + { + filter: '@kt/openapi-parser', + prefix: 'packages/openapi-parser/', + }, + { + filter: '@kt/knife4j-vue3-ui', + prefix: 'packages/knife4j-vue3-ui/', + }, + { + filter: '@kwitsukasa/knife4j-swagger-vue3', + prefix: 'packages/nestjs-knife4j/', + }, + { + filter: '@kt/knife4j-swagger-vue3-playground', + prefix: 'apps/playground/', + }, +]; + +function run(command, args) { + const result = spawnSync(command, args, { + shell: process.platform === 'win32' && command === 'pnpm', + stdio: 'inherit', + }); + + if (result.error) { + console.error(result.error.message); + process.exit(1); + } + + if (result.status !== 0) { + process.exit(result.status ?? 1); + } +} + +function output(command, args) { + const result = spawnSync(command, args, { + encoding: 'utf8', + shell: false, + }); + + if (result.error) { + console.error(result.error.message); + process.exit(1); + } + + if (result.status !== 0) { + process.exit(result.status ?? 1); + } + + return result.stdout; +} + +function getPnpmCommand() { + return 'pnpm'; +} + +function getStagedFiles() { + return output('git', [ + 'diff', + '--cached', + '--name-only', + '--diff-filter=ACMR', + '-z', + ]) + .split('\0') + .map((file) => file.replaceAll('\\', '/')) + .filter(Boolean) + .filter((file) => existsSync(file)); +} + +const stagedFiles = getStagedFiles(); + +if (stagedFiles.length === 0) { + console.info('[husky] no staged files.'); + process.exit(0); +} + +if (stagedFiles.some((file) => FULL_CHECK_FILES.has(file))) { + run(getPnpmCommand(), ['-r', '--sort', 'run', 'typecheck']); + process.exit(0); +} + +const affectedFilters = new Set(); + +for (const file of stagedFiles) { + for (const workspace of WORKSPACES) { + if (file.startsWith(workspace.prefix)) { + affectedFilters.add(workspace.filter); + } + } +} + +if (affectedFilters.size === 0) { + console.info('[husky] no staged workspace files need typecheck.'); + process.exit(0); +} + +for (const filter of affectedFilters) { + run(getPnpmCommand(), ['--filter', filter, 'run', 'typecheck']); +}