From e194f3c4540fa1acb6caf4ea8aaffc236dd4b76d Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Thu, 28 Nov 2024 22:09:42 -0600 Subject: [PATCH] chore: add lefthook --- lefthook.yml | 45 ++ package.json | 5 +- packages/server/src/utils/providers/github.ts | 478 +++++++++--------- pnpm-lock.yaml | 110 +++- 4 files changed, 386 insertions(+), 252 deletions(-) create mode 100644 lefthook.yml diff --git a/lefthook.yml b/lefthook.yml new file mode 100644 index 00000000..26e26a98 --- /dev/null +++ b/lefthook.yml @@ -0,0 +1,45 @@ +# EXAMPLE USAGE: +# +# Refer for explanation to following link: +# https://github.com/evilmartians/lefthook/blob/master/docs/configuration.md +# +# pre-push: +# commands: +# packages-audit: +# tags: frontend security +# run: yarn audit +# gems-audit: +# tags: backend security +# run: bundle audit +# +# pre-commit: +# parallel: true +# commands: +# eslint: +# glob: "*.{js,ts,jsx,tsx}" +# run: yarn eslint {staged_files} +# rubocop: +# tags: backend style +# glob: "*.rb" +# exclude: '(^|/)(application|routes)\.rb$' +# run: bundle exec rubocop --force-exclusion {all_files} +# govet: +# tags: backend style +# files: git ls-files -m +# glob: "*.go" +# run: go vet {files} +# scripts: +# "hello.js": +# runner: node +# "any.go": +# runner: go run + +commit-msg: + commands: + commitlint: + run: "npx commitlint --edit $1" + +pre-commit: + commands: + lint-staged: + run: "npx lint-staged" diff --git a/package.json b/package.json index 2a4554e7..53ef55d0 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,7 @@ "build": "pnpm -r run build", "format-and-lint": "biome check .", "check": "biome check --write --no-errors-on-unmatched --files-ignore-unknown=true", - "format-and-lint:fix": "biome check . --write", - "prepare": "node .husky/install.mjs" + "format-and-lint:fix": "biome check . --write" }, "devDependencies": { "dotenv": "16.4.5", @@ -26,7 +25,7 @@ "tsx": "4.16.2", "lint-staged": "^15.2.7", "@biomejs/biome": "1.8.3", - "husky": "^9.1.6", + "lefthook": "1.8.4", "@commitlint/cli": "^19.3.0", "@commitlint/config-conventional": "^19.2.2", "@types/node": "^18.17.0" diff --git a/packages/server/src/utils/providers/github.ts b/packages/server/src/utils/providers/github.ts index 0a814623..a63a822d 100644 --- a/packages/server/src/utils/providers/github.ts +++ b/packages/server/src/utils/providers/github.ts @@ -14,195 +14,195 @@ import { type Github, findGithubById } from "@dokploy/server/services/github"; import { execAsyncRemote } from "../process/execAsync"; export const authGithub = (githubProvider: Github): Octokit => { - if (!haveGithubRequirements(githubProvider)) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Github Account not configured correctly", - }); - } + if (!haveGithubRequirements(githubProvider)) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Github Account not configured correctly", + }); + } - const octokit: Octokit = new Octokit({ - authStrategy: createAppAuth, - auth: { - appId: githubProvider?.githubAppId || 0, - privateKey: githubProvider?.githubPrivateKey || "", - installationId: githubProvider?.githubInstallationId, - }, - }); + const octokit: Octokit = new Octokit({ + authStrategy: createAppAuth, + auth: { + appId: githubProvider?.githubAppId || 0, + privateKey: githubProvider?.githubPrivateKey || "", + installationId: githubProvider?.githubInstallationId, + }, + }); - return octokit; + return octokit; }; export const getGithubToken = async ( - octokit: ReturnType + octokit: ReturnType, ) => { - const installation = (await octokit.auth({ - type: "installation", - })) as { - token: string; - }; + const installation = (await octokit.auth({ + type: "installation", + })) as { + token: string; + }; - return installation.token; + return installation.token; }; export const haveGithubRequirements = (githubProvider: Github) => { - return !!( - githubProvider?.githubAppId && - githubProvider?.githubPrivateKey && - githubProvider?.githubInstallationId - ); + return !!( + githubProvider?.githubAppId && + githubProvider?.githubPrivateKey && + githubProvider?.githubInstallationId + ); }; const getErrorCloneRequirements = (entity: { - repository?: string | null; - owner?: string | null; - branch?: string | null; + repository?: string | null; + owner?: string | null; + branch?: string | null; }) => { - const reasons: string[] = []; - const { repository, owner, branch } = entity; + const reasons: string[] = []; + const { repository, owner, branch } = entity; - if (!repository) reasons.push("1. Repository not assigned."); - if (!owner) reasons.push("2. Owner not specified."); - if (!branch) reasons.push("3. Branch not defined."); + if (!repository) reasons.push("1. Repository not assigned."); + if (!owner) reasons.push("2. Owner not specified."); + if (!branch) reasons.push("3. Branch not defined."); - return reasons; + return reasons; }; export type ApplicationWithGithub = InferResultType< - "applications", - { github: true } + "applications", + { github: true } >; export type ComposeWithGithub = InferResultType<"compose", { github: true }>; export const cloneGithubRepository = async ( - entity: ApplicationWithGithub | ComposeWithGithub, - logPath: string, - isCompose = false + entity: ApplicationWithGithub | ComposeWithGithub, + logPath: string, + isCompose = false, ) => { - const { APPLICATIONS_PATH, COMPOSE_PATH } = paths(); - const writeStream = createWriteStream(logPath, { flags: "a" }); - const { appName, repository, owner, branch, githubId } = entity; + const { APPLICATIONS_PATH, COMPOSE_PATH } = paths(); + const writeStream = createWriteStream(logPath, { flags: "a" }); + const { appName, repository, owner, branch, githubId } = entity; - if (!githubId) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "GitHub Provider not found", - }); - } + if (!githubId) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "GitHub Provider not found", + }); + } - const requirements = getErrorCloneRequirements(entity); + const requirements = getErrorCloneRequirements(entity); - // Check if requirements are met - if (requirements.length > 0) { - writeStream.write( - `\nGitHub Repository configuration failed for application: ${appName}\n` - ); - writeStream.write("Reasons:\n"); - writeStream.write(requirements.join("\n")); - writeStream.end(); - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Error: GitHub repository information is incomplete.", - }); - } + // Check if requirements are met + if (requirements.length > 0) { + writeStream.write( + `\nGitHub Repository configuration failed for application: ${appName}\n`, + ); + writeStream.write("Reasons:\n"); + writeStream.write(requirements.join("\n")); + writeStream.end(); + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Error: GitHub repository information is incomplete.", + }); + } - const githubProvider = await findGithubById(githubId); - const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH; - const outputPath = join(basePath, appName, "code"); - const octokit = authGithub(githubProvider); - const token = await getGithubToken(octokit); - const repoclone = `github.com/${owner}/${repository}.git`; - await recreateDirectory(outputPath); - const cloneUrl = `https://oauth2:${token}@${repoclone}`; + const githubProvider = await findGithubById(githubId); + const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH; + const outputPath = join(basePath, appName, "code"); + const octokit = authGithub(githubProvider); + const token = await getGithubToken(octokit); + const repoclone = `github.com/${owner}/${repository}.git`; + await recreateDirectory(outputPath); + const cloneUrl = `https://oauth2:${token}@${repoclone}`; - try { - writeStream.write(`\nClonning Repo ${repoclone} to ${outputPath}: ✅\n`); - await spawnAsync( - "git", - [ - "clone", - "--branch", - branch!, - "--depth", - "1", - "--recurse-submodules", - cloneUrl, - outputPath, - "--progress", - ], - (data) => { - if (writeStream.writable) { - writeStream.write(data); - } - } - ); - writeStream.write(`\nCloned ${repoclone}: ✅\n`); - } catch (error) { - writeStream.write(`ERROR Clonning: ${error}: ❌`); - throw error; - } finally { - writeStream.end(); - } + try { + writeStream.write(`\nClonning Repo ${repoclone} to ${outputPath}: ✅\n`); + await spawnAsync( + "git", + [ + "clone", + "--branch", + branch!, + "--depth", + "1", + "--recurse-submodules", + cloneUrl, + outputPath, + "--progress", + ], + (data) => { + if (writeStream.writable) { + writeStream.write(data); + } + }, + ); + writeStream.write(`\nCloned ${repoclone}: ✅\n`); + } catch (error) { + writeStream.write(`ERROR Clonning: ${error}: ❌`); + throw error; + } finally { + writeStream.end(); + } }; export const getGithubCloneCommand = async ( - entity: ApplicationWithGithub | ComposeWithGithub, - logPath: string, - isCompose = false + entity: ApplicationWithGithub | ComposeWithGithub, + logPath: string, + isCompose = false, ) => { - const { appName, repository, owner, branch, githubId, serverId } = entity; + const { appName, repository, owner, branch, githubId, serverId } = entity; - if (!serverId) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Server not found", - }); - } + if (!serverId) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Server not found", + }); + } - if (!githubId) { - const command = ` + if (!githubId) { + const command = ` echo "Error: ❌ Github Provider not found" >> ${logPath}; exit 1; `; - await execAsyncRemote(serverId, command); - throw new TRPCError({ - code: "NOT_FOUND", - message: "GitHub Provider not found", - }); - } + await execAsyncRemote(serverId, command); + throw new TRPCError({ + code: "NOT_FOUND", + message: "GitHub Provider not found", + }); + } - const requirements = getErrorCloneRequirements(entity); + const requirements = getErrorCloneRequirements(entity); - // Build log messages - let logMessages = ""; - if (requirements.length > 0) { - logMessages += `\nGitHub Repository configuration failed for application: ${appName}\n`; - logMessages += "Reasons:\n"; - logMessages += requirements.join("\n"); - const escapedLogMessages = logMessages - .replace(/\\/g, "\\\\") - .replace(/"/g, '\\"') - .replace(/\n/g, "\\n"); + // Build log messages + let logMessages = ""; + if (requirements.length > 0) { + logMessages += `\nGitHub Repository configuration failed for application: ${appName}\n`; + logMessages += "Reasons:\n"; + logMessages += requirements.join("\n"); + const escapedLogMessages = logMessages + .replace(/\\/g, "\\\\") + .replace(/"/g, '\\"') + .replace(/\n/g, "\\n"); - const bashCommand = ` + const bashCommand = ` echo "${escapedLogMessages}" >> ${logPath}; exit 1; # Exit with error code `; - await execAsyncRemote(serverId, bashCommand); - return; - } - const { COMPOSE_PATH, APPLICATIONS_PATH } = paths(true); - const githubProvider = await findGithubById(githubId); - const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH; - const outputPath = join(basePath, appName, "code"); - const octokit = authGithub(githubProvider); - const token = await getGithubToken(octokit); - const repoclone = `github.com/${owner}/${repository}.git`; - const cloneUrl = `https://oauth2:${token}@${repoclone}`; + await execAsyncRemote(serverId, bashCommand); + return; + } + const { COMPOSE_PATH, APPLICATIONS_PATH } = paths(true); + const githubProvider = await findGithubById(githubId); + const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH; + const outputPath = join(basePath, appName, "code"); + const octokit = authGithub(githubProvider); + const token = await getGithubToken(octokit); + const repoclone = `github.com/${owner}/${repository}.git`; + const cloneUrl = `https://oauth2:${token}@${repoclone}`; - const cloneCommand = ` + const cloneCommand = ` rm -rf ${outputPath}; mkdir -p ${outputPath}; if ! git clone --branch ${branch} --depth 1 --recurse-submodules --progress ${cloneUrl} ${outputPath} >> ${logPath} 2>&1; then @@ -212,127 +212,127 @@ fi echo "Cloned ${repoclone} to ${outputPath}: ✅" >> ${logPath}; `; - return cloneCommand; + return cloneCommand; }; export const cloneRawGithubRepository = async (entity: Compose) => { - const { appName, repository, owner, branch, githubId } = entity; + const { appName, repository, owner, branch, githubId } = entity; - if (!githubId) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "GitHub Provider not found", - }); - } - const { COMPOSE_PATH } = paths(); - const githubProvider = await findGithubById(githubId); - const basePath = COMPOSE_PATH; - const outputPath = join(basePath, appName, "code"); - const octokit = authGithub(githubProvider); - const token = await getGithubToken(octokit); - const repoclone = `github.com/${owner}/${repository}.git`; - await recreateDirectory(outputPath); - const cloneUrl = `https://oauth2:${token}@${repoclone}`; - try { - await spawnAsync("git", [ - "clone", - "--branch", - branch!, - "--depth", - "1", - "--recurse-submodules", - cloneUrl, - outputPath, - "--progress", - ]); - } catch (error) { - throw error; - } + if (!githubId) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "GitHub Provider not found", + }); + } + const { COMPOSE_PATH } = paths(); + const githubProvider = await findGithubById(githubId); + const basePath = COMPOSE_PATH; + const outputPath = join(basePath, appName, "code"); + const octokit = authGithub(githubProvider); + const token = await getGithubToken(octokit); + const repoclone = `github.com/${owner}/${repository}.git`; + await recreateDirectory(outputPath); + const cloneUrl = `https://oauth2:${token}@${repoclone}`; + try { + await spawnAsync("git", [ + "clone", + "--branch", + branch!, + "--depth", + "1", + "--recurse-submodules", + cloneUrl, + outputPath, + "--progress", + ]); + } catch (error) { + throw error; + } }; export const cloneRawGithubRepositoryRemote = async (compose: Compose) => { - const { appName, repository, owner, branch, githubId, serverId } = compose; + const { appName, repository, owner, branch, githubId, serverId } = compose; - if (!serverId) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Server not found", - }); - } - if (!githubId) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "GitHub Provider not found", - }); - } + if (!serverId) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Server not found", + }); + } + if (!githubId) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "GitHub Provider not found", + }); + } - const { COMPOSE_PATH } = paths(true); - const githubProvider = await findGithubById(githubId); - const basePath = COMPOSE_PATH; - const outputPath = join(basePath, appName, "code"); - const octokit = authGithub(githubProvider); - const token = await getGithubToken(octokit); - const repoclone = `github.com/${owner}/${repository}.git`; - const cloneUrl = `https://oauth2:${token}@${repoclone}`; - try { - const command = ` + const { COMPOSE_PATH } = paths(true); + const githubProvider = await findGithubById(githubId); + const basePath = COMPOSE_PATH; + const outputPath = join(basePath, appName, "code"); + const octokit = authGithub(githubProvider); + const token = await getGithubToken(octokit); + const repoclone = `github.com/${owner}/${repository}.git`; + const cloneUrl = `https://oauth2:${token}@${repoclone}`; + try { + const command = ` rm -rf ${outputPath}; git clone --branch ${branch} --depth 1 ${cloneUrl} ${outputPath} `; - await execAsyncRemote(serverId, command); - } catch (error) { - throw error; - } + await execAsyncRemote(serverId, command); + } catch (error) { + throw error; + } }; export const getGithubRepositories = async (githubId?: string) => { - if (!githubId) { - return []; - } + if (!githubId) { + return []; + } - const githubProvider = await findGithubById(githubId); + const githubProvider = await findGithubById(githubId); - const octokit = new Octokit({ - authStrategy: createAppAuth, - auth: { - appId: githubProvider.githubAppId, - privateKey: githubProvider.githubPrivateKey, - installationId: githubProvider.githubInstallationId, - }, - }); + const octokit = new Octokit({ + authStrategy: createAppAuth, + auth: { + appId: githubProvider.githubAppId, + privateKey: githubProvider.githubPrivateKey, + installationId: githubProvider.githubInstallationId, + }, + }); - const repositories = (await octokit.paginate( - octokit.rest.apps.listReposAccessibleToInstallation - )) as unknown as Awaited< - ReturnType - >["data"]["repositories"]; + const repositories = (await octokit.paginate( + octokit.rest.apps.listReposAccessibleToInstallation, + )) as unknown as Awaited< + ReturnType + >["data"]["repositories"]; - return repositories; + return repositories; }; export const getGithubBranches = async ( - input: typeof apiFindGithubBranches._type + input: typeof apiFindGithubBranches._type, ) => { - if (!input.githubId) { - return []; - } - const githubProvider = await findGithubById(input.githubId); + if (!input.githubId) { + return []; + } + const githubProvider = await findGithubById(input.githubId); - const octokit = new Octokit({ - authStrategy: createAppAuth, - auth: { - appId: githubProvider.githubAppId, - privateKey: githubProvider.githubPrivateKey, - installationId: githubProvider.githubInstallationId, - }, - }); + const octokit = new Octokit({ + authStrategy: createAppAuth, + auth: { + appId: githubProvider.githubAppId, + privateKey: githubProvider.githubPrivateKey, + installationId: githubProvider.githubInstallationId, + }, + }); - const branches = (await octokit.paginate(octokit.rest.repos.listBranches, { - owner: input.owner, - repo: input.repo, - })) as unknown as Awaited< - ReturnType - >["data"]; + const branches = (await octokit.paginate(octokit.rest.repos.listBranches, { + owner: input.owner, + repo: input.repo, + })) as unknown as Awaited< + ReturnType + >["data"]; - return branches; + return branches; }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8e6ae050..cea8c053 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,9 +30,9 @@ importers: esbuild: specifier: 0.20.2 version: 0.20.2 - husky: - specifier: ^9.1.6 - version: 9.1.6 + lefthook: + specifier: 1.8.4 + version: 1.8.4 lint-staged: specifier: ^15.2.7 version: 15.2.7 @@ -4722,11 +4722,6 @@ packages: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} - husky@9.1.6: - resolution: {integrity: sha512-sqbjZKK7kf44hfdE94EoX8MZNk0n7HeW37O4YrVGCF4wzgQjp+akPAkfUK5LZ6KuR/6sqeAVuXHji+RzQgOn5A==} - engines: {node: '>=18'} - hasBin: true - hyperdyperid@1.2.0: resolution: {integrity: sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==} engines: {node: '>=10.18'} @@ -4958,6 +4953,60 @@ packages: leac@0.6.0: resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==} + lefthook-darwin-arm64@1.8.4: + resolution: {integrity: sha512-OS5MsU0gvd8LYSpuQCHtmDUqwNrJ/LjCO0LGC1wNepY4OkuVl9DfX+rQ506CVUQYZiGVcwy2/qPOOBjNzA5+wQ==} + cpu: [arm64] + os: [darwin] + + lefthook-darwin-x64@1.8.4: + resolution: {integrity: sha512-QLRsqK9aTMRcVW8qz4pzI2OWnGCEcaEPJlIiFjwstYsS+wfkooxOS0UkfVMjy+QoGgEcki+cxF/FoY7lE7DDtw==} + cpu: [x64] + os: [darwin] + + lefthook-freebsd-arm64@1.8.4: + resolution: {integrity: sha512-chnQ1m/Cmn9c0sLdk5HL2SToE5LBJv5uQMdH1IGRRcw+nEqWqrMnDXvM75caiJAyjmUGvPH3czKTJDzTFV1E+A==} + cpu: [arm64] + os: [freebsd] + + lefthook-freebsd-x64@1.8.4: + resolution: {integrity: sha512-KQi+WBUdnGLnK0rHOR58kbMH5TDVN1ZjZLu66Pv9FCG7Y7shR1qtaTXu+wmxdRhMvaLeQIXRsUEPjNRC66yMmA==} + cpu: [x64] + os: [freebsd] + + lefthook-linux-arm64@1.8.4: + resolution: {integrity: sha512-CXNcqIskLwTwQARidGdFqmNxpvOU3jsWPK4KA7pq2+QmlWJ64w98ebMvNBoUmRUCXqzmUm7Udf/jpfz2fobewQ==} + cpu: [arm64] + os: [linux] + + lefthook-linux-x64@1.8.4: + resolution: {integrity: sha512-pVNITkFBxUCEtamWSM/res2Gd48+m9YKbNyIBndAuZVC5pKV5aGKZy2DNq6PWUPYiUDPx+7hoAtCJg/tlAiqhw==} + cpu: [x64] + os: [linux] + + lefthook-openbsd-arm64@1.8.4: + resolution: {integrity: sha512-l+i/Dg5X36kYzhpMGSPE3rMbWy1KSytbLB9lY1PmxYb6LRH6iQTYIoxvLabVUwSBPSq8HtIFa50+bvC5+scfVA==} + cpu: [arm64] + os: [openbsd] + + lefthook-openbsd-x64@1.8.4: + resolution: {integrity: sha512-CqhDDPPX8oHzMLgNi/Reba823DRzj+eMNWQ8axvSiIG+zmG1w20xZH5QSs/mD3tjrND90yfDd90mWMt181qPyA==} + cpu: [x64] + os: [openbsd] + + lefthook-windows-arm64@1.8.4: + resolution: {integrity: sha512-dvpvorICmVjmw29Aiczg7DcaSzkd86bEBomiGq4UsAEk3+7ExLrlWJDLFsI6xLjMKmTxy+F7eXb2uDtuFC1N4g==} + cpu: [arm64] + os: [win32] + + lefthook-windows-x64@1.8.4: + resolution: {integrity: sha512-e+y8Jt4/7PnoplhOuK48twjGVJEsU4T3J5kxD4mWfl6Cbit0YSn4bme9nW41eqCqTUqOm+ky29XlfnPHFX5ZNA==} + cpu: [x64] + os: [win32] + + lefthook@1.8.4: + resolution: {integrity: sha512-XNyMaTWNRuADOaocYiHidgNkNDz8SCekpdNJ7lqceFcBT2zjumnb28/o7IMaNROpLBZdQkLkJXSeaQWGqn3kog==} + hasBin: true + lilconfig@2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} @@ -10940,8 +10989,6 @@ snapshots: human-signals@5.0.0: {} - husky@9.1.6: {} - hyperdyperid@1.2.0: {} i18next-fs-backend@2.3.2: {} @@ -11153,6 +11200,49 @@ snapshots: leac@0.6.0: {} + lefthook-darwin-arm64@1.8.4: + optional: true + + lefthook-darwin-x64@1.8.4: + optional: true + + lefthook-freebsd-arm64@1.8.4: + optional: true + + lefthook-freebsd-x64@1.8.4: + optional: true + + lefthook-linux-arm64@1.8.4: + optional: true + + lefthook-linux-x64@1.8.4: + optional: true + + lefthook-openbsd-arm64@1.8.4: + optional: true + + lefthook-openbsd-x64@1.8.4: + optional: true + + lefthook-windows-arm64@1.8.4: + optional: true + + lefthook-windows-x64@1.8.4: + optional: true + + lefthook@1.8.4: + optionalDependencies: + lefthook-darwin-arm64: 1.8.4 + lefthook-darwin-x64: 1.8.4 + lefthook-freebsd-arm64: 1.8.4 + lefthook-freebsd-x64: 1.8.4 + lefthook-linux-arm64: 1.8.4 + lefthook-linux-x64: 1.8.4 + lefthook-openbsd-arm64: 1.8.4 + lefthook-openbsd-x64: 1.8.4 + lefthook-windows-arm64: 1.8.4 + lefthook-windows-x64: 1.8.4 + lilconfig@2.1.0: {} lilconfig@3.1.2: {}