Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement universal Git hooks integration #184

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions lib/universal/git-hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@

import { parse } from "https://deno.land/std/flags/mod.ts";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Be sure to turn on Deno linting in VS Code, it will remind you that this is not a proper way to reference imports. You should use pinned versions.

import { Command } from "https://deno.land/x/cliffy/command/mod.ts";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Be sure to turn on Deno linting in VS Code, it will remind you that this is not a proper way to reference imports. You should use pinned versions.


async function initHooks() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add default args like:

initHooks(gitHooksPath = path.join(Deno.cwd(), '.githooks') )

Include anything else that might be optional.

const gitHooksDir = '.githooks';
await Deno.mkdir(gitHooksDir, { recursive: true });

const hooks = {
'pre-commit': preCommit,
'pre-push': prePush,
'prepare-commit-message': prepareCommitMessage,
};

for (const [name, handler] of Object.entries(hooks)) {
const script = `#!/usr/bin/env -S deno run --allow-read --allow-write --allow-env
import { ${name} } from "https://raw.githubusercontent.com/netspective-labs/sql-aide/vX.Y.Z/lib/universal/git-hooks.ts";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vX.Y.Z will fail every time.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means you gave me a PR without local testing. Please never do that again.

await ${name}();`;

await Deno.writeTextFile(`${gitHooksDir}/${name}`, script, { mode: 0o755 });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use Deno stdlib path module and path.join() not string concatenation for filename.

}
}

async function preCommit() {
if (Deno.env.get("GITHOOK_BYPASS")) return;

console.log("Running pre-commit checks...");

await runDenoCommand(["deno", "lint"], "Linting code...");

await runDenoCommand(["deno", "test"], "Running unit tests...");

console.log("Pre-commit checks passed.");
}

async function prePush() {
if (Deno.env.get("GITHOOK_BYPASS")) return;

console.log("Running pre-push checks...");

await runDenoCommand(["deno", "check"], "Type checking...");

console.log("Pre-push checks passed.");
}

async function prepareCommitMessage(args: string[]) {
if (Deno.env.get("GITHOOK_BYPASS")) return;

console.log("Preparing commit message...");

const commitMessageFilePath = args[0];
let commitMessage = await Deno.readTextFile(commitMessageFilePath);
commitMessage = "Prefix: " + commitMessage;
await Deno.writeTextFile(commitMessageFilePath, commitMessage);

console.log("Commit message prepared.");
}

async function runDenoCommand(cmd: string[], taskDescription: string) {
console.log(taskDescription);
const process = Deno.run({
cmd,
stdout: "piped",
stderr: "piped",
});

const { code } = await process.status();

if (code !== 0) {
const rawError = await process.stderrOutput();
const errorString = new TextDecoder().decode(rawError);
console.error(`${taskDescription} failed:\n`, errorString);
Deno.exit(code);
}
}

async function main() {
const { _: [hookName], ...args } = parse(Deno.args);

switch (hookName) {
case "pre-commit":
await preCommit();
break;
case "pre-push":
await prePush();
break;
case "prepare-commit-msg":
await prepareCommitMessage(args._ as string[]);
break;
default:
console.error(`Unsupported hook: ${hookName}`);
Deno.exit(1);
}
}

if (import.meta.main) {
await main();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why two if(import.meta.main) blocks?

}

if (import.meta.main) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why two if(import.meta.main) blocks?

new Command()
.name("git-hooks")
.description("Manage Git hooks.")
.action(initHooks)
.parse(Deno.args);
}

export { preCommit, prePush, prepareCommitMessage };