UNPKG

@jsenv/git-hooks

Version:

Declare git hooks in your package.json.

121 lines (109 loc) 3.91 kB
import { assertAndNormalizeDirectoryUrl, resolveUrl, writeFile, readFile, readFileSystemNodeStat, urlToFileSystemPath, writeFileSystemNodePermissions, removeFileSystemNode, catchCancellation, createCancellationTokenForProcess, } from "@jsenv/util" import { createLogger } from "@jsenv/logger" import { HOOK_NAMES, generateHookFileContent, hookIsGeneratedByUs } from "./internal/hook.js" const isWindows = process.platform === "win32" // https://github.com/typicode/husky/blob/master/src/installer/getScript.ts export const installGitHooks = async ({ cancellationToken = createCancellationTokenForProcess(), logLevel, projectDirectoryUrl, ci = process.env.CI, }) => { return catchCancellation(async () => { const logger = createLogger({ logLevel }) if (ci) { logger.info(`ci -> skip installGitHooks`) return } projectDirectoryUrl = assertAndNormalizeDirectoryUrl(projectDirectoryUrl) const packageJsonFileUrl = resolveUrl("package.json", projectDirectoryUrl) const packageJsonFileString = await readFile(packageJsonFileUrl) const packageJsonData = JSON.parse(packageJsonFileString) const { scripts = {} } = packageJsonData await Promise.all( HOOK_NAMES.map(async (hookName) => { const hookScriptName = `git-hook-${hookName}` const hookFileUrl = resolveUrl(`.git/hooks/${hookName}`, projectDirectoryUrl) const hookFileStats = await readFileSystemNodeStat(hookFileUrl, { nullIfNotFound: true, }) const hookScriptPresence = hookScriptName in scripts const hookFilePresence = Boolean(hookFileStats) cancellationToken.throwIfRequested() if (hookFilePresence) { const hookFileContent = await readFile(hookFileUrl) if (!hookIsGeneratedByUs(hookFileContent)) { if (hookScriptPresence) { logger.info(` ignore ${hookScriptName} script because there is a git ${hookName} hook file not generated by us.`) } return } if (hookScriptPresence) { const hookFileContentForScript = generateHookFileContent(scripts[hookScriptName]) if (hookFileContentForScript === hookFileContent) { logger.debug(` keep existing git ${hookName} hook file. --- file --- ${urlToFileSystemPath(hookFileUrl)}`) return } logger.info(` overwrite git ${hookName} hook file. --- file --- ${urlToFileSystemPath(hookFileUrl)} --- previous file content --- ${hookFileContent} --- file content --- ${hookFileContentForScript}`) await writeHook(hookFileUrl, hookFileContentForScript) } else { logger.info(` remove git ${hookName} hook file. --- file --- ${urlToFileSystemPath(hookFileUrl)} --- file content --- ${hookFileContent}`) await removeFileSystemNode(hookFileUrl) } } else if (hookScriptPresence) { const hookFileContentForScript = generateHookFileContent(scripts[hookScriptName]) logger.info(` write git ${hookName} hook file. --- file --- ${urlToFileSystemPath(hookFileUrl)} --- file content --- ${hookFileContentForScript}`) await writeHook(hookFileUrl, hookFileContentForScript) } }), ) }).catch((e) => { // this is required to ensure unhandledRejection will still // set process.exitCode to 1 marking the process execution as errored // preventing further command to run process.exitCode = 1 throw e }) } const writeHook = async (hookFileUrl, hookFileContent) => { await writeFile(hookFileUrl, hookFileContent) if (!isWindows) { await writeFileSystemNodePermissions(hookFileUrl, { owner: { read: true, write: true, execute: true }, group: { read: true, write: false, execute: true }, others: { read: true, write: false, execute: true }, }) } }