From a70bc53b8a748df45806665dbac81d2855c11d0c Mon Sep 17 00:00:00 2001 From: Samuel Kopp <62482066+boywithkeyboard@users.noreply.github.com> Date: Mon, 19 Jun 2023 16:35:22 +0200 Subject: [PATCH] feat!: support for .jsonc and .yml files (#6) * remove old readme * add new readme * add json schema * edit githooks.ts * recommend `stackbreak.comment-divider` extension * fix typo in readme * update readme * remove extension * fix json schema * fix reader --- README.md | 49 ----------------------- githooks.ts | 113 +++++++++++++++++++++++++++++++++------------------- readme.md | 63 +++++++++++++++++++++++++++++ schema.json | 91 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 227 insertions(+), 89 deletions(-) delete mode 100644 README.md create mode 100644 readme.md create mode 100644 schema.json diff --git a/README.md b/README.md deleted file mode 100644 index 91e63c8..0000000 --- a/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# Deno support for githooks - -See https://git-scm.com/docs/githooks and -https://deno.land/manual@v1.25.4/tools/task_runner - -This is a simple tool for [deno](https://deno.land) projects that allows you to -associate specific `deno tasks` with specific `githooks` by extending the native -`deno.json` configuration file. - -It works like this: - -- In your `deno.json` file, add a `githooks` key containing a map of `{githook}` - to `{deno task}`. For example: - -```json -// deno.json -{ - "tasks": { - "start": "deno run -A dev.ts", - "check": "deno fmt --check && deno lint" - }, - "githooks": { - "pre-commit": "check" - } -} -``` - -- In your terminal, run the `githooks.ts` script. It will automatically create a - hook file for each githook in your `deno.json` file. - -```bash -$ deno run -A -r https://deno.land/x/githooks/githooks.ts -``` - -That's it. Now your git should call `deno task check` before every commit. - ---- - -**PROTIP:** [**deco** Live](https://github.com/deco-cx/live) projects come with -this extension pre-installed in the `dev` script. You don't have to do anything, -just add `githooks` to `deno.json` and run `dev` to install the hooks -transparently. - ---- - -## TODO - -- [ ] Add support for Windows implementing something like this: - https://github.com/denoland/deno/blob/429759fe8b4207240709c240a8344d12a1e39566/cli/tools/installer.rs#L46 diff --git a/githooks.ts b/githooks.ts index ee018e5..a2b694e 100644 --- a/githooks.ts +++ b/githooks.ts @@ -1,60 +1,93 @@ -export type Githooks = { - githooks: Record; -}; +import { + brightGreen, + brightRed, + gray, +} from "https://deno.land/std@0.192.0/fmt/colors.ts"; +import * as jsonc from "https://deno.land/std@0.192.0/jsonc/parse.ts"; +import * as yaml from "https://deno.land/std@0.192.0/yaml/parse.ts"; -export type GithooksOptions = { - getConfig: () => Promise; - verbose?: boolean; -}; +/* find file ---------------------------------------------------------------- */ + +type ConfigFile = + | `${string}.json` + | `${string}.jsonc` + | `${string}.yaml` + | `${string}.yml`; -export async function readJson(filePath: string): Promise { +async function findFile( + ...paths: ConfigFile[] +): Promise< + | Record + | undefined +> { try { - const jsonString = await Deno.readTextFile(filePath); + const content = await Deno.readTextFile(paths[0]); + + const parsedContent = paths[0].endsWith(".json") + ? JSON.parse(content) + : paths[0].endsWith(".jsonc") + ? jsonc.parse(content) as Record + : yaml.parse(content) as Record; - return JSON.parse(jsonString); - } catch (err) { - err.message = `${filePath}: ${err.message}`; + return paths[0].endsWith("deno.json") || paths[0].endsWith("deno.jsonc") + ? (parsedContent.githooks ? parsedContent.githooks : undefined) + : parsedContent; + } catch (_) { + paths.shift(); - throw err; + if (paths.length > 0) { + return await findFile(...paths); + } } } -export function readDenoJson(): Promise { - return readJson("./deno.json") as Promise; -} +/* setup hooks -------------------------------------------------------------- */ -const defaultOptions: GithooksOptions = { - getConfig: readDenoJson, - verbose: true, -}; +export async function setup({ + verbose = true, + file, +}: { + file?: ConfigFile; + verbose?: boolean; +} = {}) { + const githooks = file ? await findFile(file) : await findFile( + "./githooks.json", + "./githooks.jsonc", + "./githooks.yaml", + "./githooks.yml", + "./deno.json", + "./deno.jsonc", + ); -export async function setupGithooks(opts: GithooksOptions = defaultOptions) { - const config = await opts.getConfig(); + if (!githooks) { + return verbose && + console.error(brightRed("No githooks found!")); + } - if (!config.githooks) { - opts.verbose && - console.log("No githooks found in deno.json — skipping setup."); - } else { - const hooks = Object.keys(config.githooks); + const hooks = Object.keys(githooks); - for (const hook of hooks) { - const task = config.githooks[hook]; - const hookPath = `./.git/hooks/${hook}`; - const hookScript = `#!/bin/sh\nexec deno task ${task}`; + for (const h of hooks) { + const task = githooks[h]; + const hookPath = `./.git/hooks/${h}`; + const hookScript = `#!/bin/sh\nexec deno task ${task}`; - await Deno.writeTextFile(hookPath, hookScript); + await Deno.writeTextFile(hookPath, hookScript); - if (Deno.build.os !== "windows") { - await Deno.chmod(hookPath, 0o755); - } + if (Deno.build.os !== "windows") { + await Deno.chmod(hookPath, 0o755); } - - opts.verbose && - console.log("Githooks setup successfully:", hooks.join(", ")); } + + verbose && + console.info( + gray( + `${brightGreen("Added githooks:")} ${hooks.join(", ")}`, + ), + ); } -// Execute when called directly +/* execute cli -------------------------------------------------------------- */ + if (import.meta.main) { - await setupGithooks(); + await setup(); } diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..0485008 --- /dev/null +++ b/readme.md @@ -0,0 +1,63 @@ +## githooks for Deno + +This is a simple tool for [Deno](https://deno.land) projects that allows you to +associate specific [deno tasks](https://deno.land/manual/tools/task_runner) with +specific [Git Hooks](https://git-scm.com/docs/githooks) by extending the native +`deno.json` configuration file or adding a separate one. + +It works like this: + +1. In your `deno.json` (or `deno.jsonc`) file, add a `githooks` key containing a + map of `{githook}` to `{deno task}`. For example: + + ```json + { + "tasks": { + "start": "deno run -A dev.ts", + "check": "deno fmt --check && deno lint" + }, + "githooks": { + "pre-commit": "check" + } + } + ``` + + You can also create a separate `githooks.json`, `githooks.jsonc`, + `githooks.yaml` or `githooks.yml` file: + + ```json + { + "pre-commit": "check" + } + ``` + + ```yaml + pre-commit: check + ``` + + To add autocompletion, you can use our JSON schema. This schema can either be + specified in the settings of your code editor or directly in the JSON file: + + ```json + { + "$schema": "https://deno.land/x/githooks/schema.json", + "pre-commit": "check" + } + ``` + +2. In your terminal, run the `githooks.ts` script. It will automatically create + a hook file for each githook in your `deno.json` file. + + ```bash + deno run -A -r https://deno.land/x/githooks/githooks.ts + ``` + +That's it. Now your Git Hook should call `deno task check` before every commit. + +--- + +**PROTIP:** [**deco**](https://github.com/deco-cx/deco) projects come with this +extension pre-installed in the `dev` script. You don't have to do anything, just +add `githooks` to `deno.json` and run `dev` to install the hooks transparently. + +--- diff --git a/schema.json b/schema.json new file mode 100644 index 0000000..73b8358 --- /dev/null +++ b/schema.json @@ -0,0 +1,91 @@ +{ + "$id": "https://deno.land/x/githooks/schema.json", + "$schema": "https://json-schema.org/draft-07/schema", + "type": "object", + "properties": { + "applypatch-msg": { + "type": "string" + }, + "pre-applypatch": { + "type": "string" + }, + "post-applypatch": { + "type": "string" + }, + "pre-commit": { + "type": "string" + }, + "pre-merge-commit": { + "type": "string" + }, + "prepare-commit-msg": { + "type": "string" + }, + "commit-msg": { + "type": "string" + }, + "post-commit": { + "type": "string" + }, + "pre-rebase": { + "type": "string" + }, + "post-checkout": { + "type": "string" + }, + "post-merge": { + "type": "string" + }, + "pre-push": { + "type": "string" + }, + "pre-receive": { + "type": "string" + }, + "update": { + "type": "string" + }, + "proc-receive": { + "type": "string" + }, + "post-receive": { + "type": "string" + }, + "post-update": { + "type": "string" + }, + "reference-transaction": { + "type": "string" + }, + "push-to-checkout": { + "type": "string" + }, + "pre-auto-gc": { + "type": "string" + }, + "post-rewrite": { + "type": "string" + }, + "sendemail-validate": { + "type": "string" + }, + "fsmonitor-watchman": { + "type": "string" + }, + "p4-changelist": { + "type": "string" + }, + "p4-prepare-changelist": { + "type": "string" + }, + "p4-post-changelist": { + "type": "string" + }, + "p4-pre-submit": { + "type": "string" + }, + "post-index-change": { + "type": "string" + } + } +}