@pyyupsk/nit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 pyyupsk
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # @pyyupsk/nit
2
+
3
+ Lightweight, zero-dependency Git hooks manager for JavaScript/Node.js projects.
4
+
5
+ Configure hooks in `package.json` — no extra config files, no dependencies.
6
+
7
+ ## Features
8
+
9
+ - Zero runtime dependencies
10
+ - Config lives in `package.json` under a `"nit"` key
11
+ - Auto-installs hooks via the `prepare` lifecycle script
12
+ - CI-safe `check` command — exits 1 when hooks are out of sync
13
+ - Works with any package manager (bun, npm, pnpm, yarn)
14
+
15
+ ## Installation
16
+
17
+ ```sh
18
+ bun add -D @pyyupsk/nit
19
+ ```
20
+
21
+ Then add a `prepare` script so hooks are installed automatically on `bun install`:
22
+
23
+ ```json
24
+ {
25
+ "scripts": {
26
+ "prepare": "nit install"
27
+ }
28
+ }
29
+ ```
30
+
31
+ ## Configuration
32
+
33
+ Add a `"nit"` key to your `package.json`:
34
+
35
+ ```json
36
+ {
37
+ "nit": {
38
+ "hooks": {
39
+ "pre-commit": "bun run lint && bun run typecheck",
40
+ "commit-msg": "bun run test"
41
+ }
42
+ }
43
+ }
44
+ ```
45
+
46
+ Any valid [Git hook name](https://git-scm.com/docs/githooks) is supported as a key. The value is the shell command to run.
47
+
48
+ ## CLI
49
+
50
+ ```sh
51
+ nit install # write hooks to .git/hooks/ (default when run bare)
52
+ nit sync # alias for install
53
+ nit check # exit 1 if installed hooks differ from config (CI-safe)
54
+ ```
55
+
56
+ ### `nit install` / `nit sync`
57
+
58
+ Reads `nit.hooks` from `package.json` and writes a shell script for each entry into `.git/hooks/`. Each hook file is made executable automatically.
59
+
60
+ ```sh
61
+ $ nit install
62
+ nit: installed pre-commit, commit-msg
63
+ ```
64
+
65
+ ### `nit check`
66
+
67
+ Compares the installed hook files against the current config without writing anything. Exits with code `1` if any hook is missing or has a different command — useful in CI to enforce that hooks are committed.
68
+
69
+ ```sh
70
+ $ nit check
71
+ nit: hooks are up to date
72
+ ```
73
+
74
+ ## Self-hosting Example
75
+
76
+ `nit` manages its own hooks:
77
+
78
+ ```json
79
+ {
80
+ "nit": {
81
+ "hooks": {
82
+ "pre-commit": "bun format && bun check && bun typecheck && bun test && bun run build"
83
+ }
84
+ }
85
+ }
86
+ ```
87
+
88
+ ## License
89
+
90
+ MIT
package/dist/cli.cjs ADDED
@@ -0,0 +1,257 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/cli.ts
22
+ var cli_exports = {};
23
+ __export(cli_exports, {
24
+ run: () => run
25
+ });
26
+ module.exports = __toCommonJS(cli_exports);
27
+ var import_node_path6 = require("path");
28
+
29
+ // src/core/check.ts
30
+ var import_node_fs = require("fs");
31
+ var import_promises = require("fs/promises");
32
+ var import_node_path = require("path");
33
+ async function checkHooks(hooks, hooksDir) {
34
+ try {
35
+ await (0, import_promises.stat)(hooksDir);
36
+ } catch {
37
+ return [new Error(`Hooks directory does not exist: ${hooksDir}`), null];
38
+ }
39
+ for (const [name, cmd] of Object.entries(hooks)) {
40
+ const hookPath = (0, import_node_path.join)(hooksDir, name);
41
+ if (!(0, import_node_fs.existsSync)(hookPath)) {
42
+ return [null, false];
43
+ }
44
+ let content;
45
+ try {
46
+ content = await (0, import_promises.readFile)(hookPath, "utf8");
47
+ } catch (e) {
48
+ return [
49
+ e instanceof Error ? e : new Error(`Failed to read hook "${name}"`),
50
+ null
51
+ ];
52
+ }
53
+ const lines = content.split("\n");
54
+ const hasCmd = lines.some((line) => line.trim() === cmd.trim());
55
+ if (!hasCmd) {
56
+ return [null, false];
57
+ }
58
+ }
59
+ return [null, true];
60
+ }
61
+
62
+ // src/core/install.ts
63
+ var import_promises2 = require("fs/promises");
64
+ var import_node_path2 = require("path");
65
+ function hookScript(cmd) {
66
+ return `#!/bin/sh
67
+ ${cmd}
68
+ `;
69
+ }
70
+ async function installHooks(hooks, hooksDir) {
71
+ try {
72
+ await (0, import_promises2.stat)(hooksDir);
73
+ } catch {
74
+ return [new Error(`Hooks directory does not exist: ${hooksDir}`), null];
75
+ }
76
+ const written = [];
77
+ for (const [name, cmd] of Object.entries(hooks)) {
78
+ const hookPath = (0, import_node_path2.join)(hooksDir, name);
79
+ try {
80
+ await (0, import_promises2.writeFile)(hookPath, hookScript(cmd), "utf8");
81
+ written.push(hookPath);
82
+ await (0, import_promises2.chmod)(hookPath, 493);
83
+ } catch (e) {
84
+ await Promise.all(written.map((p) => (0, import_promises2.unlink)(p).catch(() => {
85
+ })));
86
+ return [
87
+ e instanceof Error ? e : new Error(`Failed to write hook "${name}"`),
88
+ null
89
+ ];
90
+ }
91
+ }
92
+ return [null, true];
93
+ }
94
+
95
+ // src/core/validate.ts
96
+ var import_node_fs2 = require("fs");
97
+ var import_node_path3 = require("path");
98
+ function commandExists(bin) {
99
+ const sep = process.platform === "win32" ? ";" : ":";
100
+ const exts = process.platform === "win32" ? [".exe", ".cmd", ".bat", ""] : [""];
101
+ const dirs = (process.env.PATH ?? "").split(sep);
102
+ return dirs.some(
103
+ (dir) => exts.some((ext) => (0, import_node_fs2.existsSync)((0, import_node_path3.join)(dir, bin + ext)))
104
+ );
105
+ }
106
+ function validateHooks(hooks) {
107
+ for (const [hookName, cmd] of Object.entries(hooks)) {
108
+ const bin = cmd.trim().split(/\s+/).at(0);
109
+ if (bin === void 0 || bin === "") {
110
+ return new Error(`Hook "${hookName}" has an empty command`);
111
+ }
112
+ if (!commandExists(bin)) {
113
+ return new Error(`Hook "${hookName}": command "${bin}" not found in PATH`);
114
+ }
115
+ }
116
+ return null;
117
+ }
118
+
119
+ // src/utils/config.ts
120
+ var import_promises3 = require("fs/promises");
121
+ var import_node_path4 = require("path");
122
+ async function readConfig(cwd) {
123
+ const pkgPath = (0, import_node_path4.join)(cwd, "package.json");
124
+ let raw;
125
+ try {
126
+ raw = await (0, import_promises3.readFile)(pkgPath, "utf8");
127
+ } catch {
128
+ return [new Error(`Cannot find package.json at ${pkgPath}`), null];
129
+ }
130
+ let pkg;
131
+ try {
132
+ pkg = JSON.parse(raw);
133
+ } catch {
134
+ return [new Error(`package.json contains invalid JSON at ${pkgPath}`), null];
135
+ }
136
+ if (typeof pkg !== "object" || pkg === null) {
137
+ return [new Error("package.json must be a JSON object"), null];
138
+ }
139
+ const nit = pkg.nit;
140
+ if (nit === void 0) {
141
+ return [null, { hooks: {} }];
142
+ }
143
+ if (typeof nit !== "object" || nit === null) {
144
+ return [new Error('"nit" in package.json must be an object'), null];
145
+ }
146
+ const nitObj = nit;
147
+ const hooks = nitObj.hooks;
148
+ if (hooks === void 0) {
149
+ return [null, { hooks: {} }];
150
+ }
151
+ if (typeof hooks !== "object" || hooks === null || Array.isArray(hooks)) {
152
+ return [new Error("hooks must be an object in nit config"), null];
153
+ }
154
+ const hooksObj = hooks;
155
+ for (const [name, cmd] of Object.entries(hooksObj)) {
156
+ if (typeof cmd !== "string") {
157
+ return [
158
+ new Error(
159
+ `Hook "${name}" must have a string command, got ${typeof cmd}`
160
+ ),
161
+ null
162
+ ];
163
+ }
164
+ }
165
+ return [null, { hooks: hooksObj }];
166
+ }
167
+
168
+ // src/utils/git.ts
169
+ var import_node_fs3 = require("fs");
170
+ var import_promises4 = require("fs/promises");
171
+ var import_node_path5 = require("path");
172
+ async function findGitDir(startPath, stopAt) {
173
+ try {
174
+ await (0, import_promises4.stat)(startPath);
175
+ } catch {
176
+ return [new Error(`Path does not exist: ${startPath}`), null];
177
+ }
178
+ let current = startPath;
179
+ const { root } = (0, import_node_path5.parse)(current);
180
+ while (current !== root) {
181
+ const candidate = (0, import_node_path5.join)(current, ".git");
182
+ if ((0, import_node_fs3.existsSync)(candidate)) {
183
+ return [null, candidate];
184
+ }
185
+ if (stopAt !== void 0 && current === stopAt) break;
186
+ const parent = (0, import_node_path5.dirname)(current);
187
+ if (parent === current) break;
188
+ current = parent;
189
+ }
190
+ return [new Error(`Not a git repository (searched from ${startPath})`), null];
191
+ }
192
+
193
+ // src/cli.ts
194
+ var silent = { log: () => {
195
+ }, error: () => {
196
+ } };
197
+ async function run(args, cwd, logger = silent) {
198
+ const command = args.at(0) ?? "install";
199
+ if (!["install", "sync", "check"].includes(command)) {
200
+ logger.error(`nit: unknown command "${command}"`);
201
+ logger.error("Usage: nit [install|sync|check]");
202
+ return 1;
203
+ }
204
+ const [cfgErr, config] = await readConfig(cwd);
205
+ if (cfgErr !== null) {
206
+ logger.error(`nit: ${cfgErr.message}`);
207
+ return 1;
208
+ }
209
+ const [gitErr, gitDir] = await findGitDir(cwd);
210
+ if (gitErr !== null) {
211
+ if (command !== "check") return 0;
212
+ logger.error(`nit: ${gitErr.message}`);
213
+ return 1;
214
+ }
215
+ const hooksDir = (0, import_node_path6.join)(gitDir, "hooks");
216
+ if (command === "check") {
217
+ const [err, inSync] = await checkHooks(config.hooks, hooksDir);
218
+ if (err !== null) {
219
+ logger.error(`nit: ${err.message}`);
220
+ return 1;
221
+ }
222
+ if (!inSync) {
223
+ logger.error("nit: hooks are out of sync \u2014 run `nit install` to fix");
224
+ return 1;
225
+ }
226
+ logger.log("nit: hooks are up to date");
227
+ return 0;
228
+ }
229
+ const validErr = validateHooks(config.hooks);
230
+ if (validErr !== null) {
231
+ logger.error(`nit: ${validErr.message}`);
232
+ return 1;
233
+ }
234
+ const [installErr] = await installHooks(config.hooks, hooksDir);
235
+ if (installErr !== null) {
236
+ logger.error(`nit: ${installErr.message}`);
237
+ return 1;
238
+ }
239
+ const hookNames = Object.keys(config.hooks);
240
+ if (hookNames.length === 0) {
241
+ logger.log("nit: no hooks configured");
242
+ } else {
243
+ logger.log(`nit: installed ${hookNames.join(", ")}`);
244
+ }
245
+ return 0;
246
+ }
247
+ var isMain = typeof process !== "undefined" && /[/\\](?:cli\.[jt]s|nit)$/.test(process.argv[1] ?? "");
248
+ if (isMain) {
249
+ const args = process.argv.slice(2);
250
+ run(args, process.cwd(), console).then((code) => {
251
+ process.exit(code);
252
+ });
253
+ }
254
+ // Annotate the CommonJS export names for ESM import in node:
255
+ 0 && (module.exports = {
256
+ run
257
+ });
package/dist/cli.js ADDED
@@ -0,0 +1,233 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { join as join6 } from "path";
5
+
6
+ // src/core/check.ts
7
+ import { existsSync } from "fs";
8
+ import { readFile, stat } from "fs/promises";
9
+ import { join } from "path";
10
+ async function checkHooks(hooks, hooksDir) {
11
+ try {
12
+ await stat(hooksDir);
13
+ } catch {
14
+ return [new Error(`Hooks directory does not exist: ${hooksDir}`), null];
15
+ }
16
+ for (const [name, cmd] of Object.entries(hooks)) {
17
+ const hookPath = join(hooksDir, name);
18
+ if (!existsSync(hookPath)) {
19
+ return [null, false];
20
+ }
21
+ let content;
22
+ try {
23
+ content = await readFile(hookPath, "utf8");
24
+ } catch (e) {
25
+ return [
26
+ e instanceof Error ? e : new Error(`Failed to read hook "${name}"`),
27
+ null
28
+ ];
29
+ }
30
+ const lines = content.split("\n");
31
+ const hasCmd = lines.some((line) => line.trim() === cmd.trim());
32
+ if (!hasCmd) {
33
+ return [null, false];
34
+ }
35
+ }
36
+ return [null, true];
37
+ }
38
+
39
+ // src/core/install.ts
40
+ import { chmod, stat as stat2, unlink, writeFile } from "fs/promises";
41
+ import { join as join2 } from "path";
42
+ function hookScript(cmd) {
43
+ return `#!/bin/sh
44
+ ${cmd}
45
+ `;
46
+ }
47
+ async function installHooks(hooks, hooksDir) {
48
+ try {
49
+ await stat2(hooksDir);
50
+ } catch {
51
+ return [new Error(`Hooks directory does not exist: ${hooksDir}`), null];
52
+ }
53
+ const written = [];
54
+ for (const [name, cmd] of Object.entries(hooks)) {
55
+ const hookPath = join2(hooksDir, name);
56
+ try {
57
+ await writeFile(hookPath, hookScript(cmd), "utf8");
58
+ written.push(hookPath);
59
+ await chmod(hookPath, 493);
60
+ } catch (e) {
61
+ await Promise.all(written.map((p) => unlink(p).catch(() => {
62
+ })));
63
+ return [
64
+ e instanceof Error ? e : new Error(`Failed to write hook "${name}"`),
65
+ null
66
+ ];
67
+ }
68
+ }
69
+ return [null, true];
70
+ }
71
+
72
+ // src/core/validate.ts
73
+ import { existsSync as existsSync2 } from "fs";
74
+ import { join as join3 } from "path";
75
+ function commandExists(bin) {
76
+ const sep = process.platform === "win32" ? ";" : ":";
77
+ const exts = process.platform === "win32" ? [".exe", ".cmd", ".bat", ""] : [""];
78
+ const dirs = (process.env.PATH ?? "").split(sep);
79
+ return dirs.some(
80
+ (dir) => exts.some((ext) => existsSync2(join3(dir, bin + ext)))
81
+ );
82
+ }
83
+ function validateHooks(hooks) {
84
+ for (const [hookName, cmd] of Object.entries(hooks)) {
85
+ const bin = cmd.trim().split(/\s+/).at(0);
86
+ if (bin === void 0 || bin === "") {
87
+ return new Error(`Hook "${hookName}" has an empty command`);
88
+ }
89
+ if (!commandExists(bin)) {
90
+ return new Error(`Hook "${hookName}": command "${bin}" not found in PATH`);
91
+ }
92
+ }
93
+ return null;
94
+ }
95
+
96
+ // src/utils/config.ts
97
+ import { readFile as readFile2 } from "fs/promises";
98
+ import { join as join4 } from "path";
99
+ async function readConfig(cwd) {
100
+ const pkgPath = join4(cwd, "package.json");
101
+ let raw;
102
+ try {
103
+ raw = await readFile2(pkgPath, "utf8");
104
+ } catch {
105
+ return [new Error(`Cannot find package.json at ${pkgPath}`), null];
106
+ }
107
+ let pkg;
108
+ try {
109
+ pkg = JSON.parse(raw);
110
+ } catch {
111
+ return [new Error(`package.json contains invalid JSON at ${pkgPath}`), null];
112
+ }
113
+ if (typeof pkg !== "object" || pkg === null) {
114
+ return [new Error("package.json must be a JSON object"), null];
115
+ }
116
+ const nit = pkg.nit;
117
+ if (nit === void 0) {
118
+ return [null, { hooks: {} }];
119
+ }
120
+ if (typeof nit !== "object" || nit === null) {
121
+ return [new Error('"nit" in package.json must be an object'), null];
122
+ }
123
+ const nitObj = nit;
124
+ const hooks = nitObj.hooks;
125
+ if (hooks === void 0) {
126
+ return [null, { hooks: {} }];
127
+ }
128
+ if (typeof hooks !== "object" || hooks === null || Array.isArray(hooks)) {
129
+ return [new Error("hooks must be an object in nit config"), null];
130
+ }
131
+ const hooksObj = hooks;
132
+ for (const [name, cmd] of Object.entries(hooksObj)) {
133
+ if (typeof cmd !== "string") {
134
+ return [
135
+ new Error(
136
+ `Hook "${name}" must have a string command, got ${typeof cmd}`
137
+ ),
138
+ null
139
+ ];
140
+ }
141
+ }
142
+ return [null, { hooks: hooksObj }];
143
+ }
144
+
145
+ // src/utils/git.ts
146
+ import { existsSync as existsSync3 } from "fs";
147
+ import { stat as stat3 } from "fs/promises";
148
+ import { dirname, join as join5, parse } from "path";
149
+ async function findGitDir(startPath, stopAt) {
150
+ try {
151
+ await stat3(startPath);
152
+ } catch {
153
+ return [new Error(`Path does not exist: ${startPath}`), null];
154
+ }
155
+ let current = startPath;
156
+ const { root } = parse(current);
157
+ while (current !== root) {
158
+ const candidate = join5(current, ".git");
159
+ if (existsSync3(candidate)) {
160
+ return [null, candidate];
161
+ }
162
+ if (stopAt !== void 0 && current === stopAt) break;
163
+ const parent = dirname(current);
164
+ if (parent === current) break;
165
+ current = parent;
166
+ }
167
+ return [new Error(`Not a git repository (searched from ${startPath})`), null];
168
+ }
169
+
170
+ // src/cli.ts
171
+ var silent = { log: () => {
172
+ }, error: () => {
173
+ } };
174
+ async function run(args, cwd, logger = silent) {
175
+ const command = args.at(0) ?? "install";
176
+ if (!["install", "sync", "check"].includes(command)) {
177
+ logger.error(`nit: unknown command "${command}"`);
178
+ logger.error("Usage: nit [install|sync|check]");
179
+ return 1;
180
+ }
181
+ const [cfgErr, config] = await readConfig(cwd);
182
+ if (cfgErr !== null) {
183
+ logger.error(`nit: ${cfgErr.message}`);
184
+ return 1;
185
+ }
186
+ const [gitErr, gitDir] = await findGitDir(cwd);
187
+ if (gitErr !== null) {
188
+ if (command !== "check") return 0;
189
+ logger.error(`nit: ${gitErr.message}`);
190
+ return 1;
191
+ }
192
+ const hooksDir = join6(gitDir, "hooks");
193
+ if (command === "check") {
194
+ const [err, inSync] = await checkHooks(config.hooks, hooksDir);
195
+ if (err !== null) {
196
+ logger.error(`nit: ${err.message}`);
197
+ return 1;
198
+ }
199
+ if (!inSync) {
200
+ logger.error("nit: hooks are out of sync \u2014 run `nit install` to fix");
201
+ return 1;
202
+ }
203
+ logger.log("nit: hooks are up to date");
204
+ return 0;
205
+ }
206
+ const validErr = validateHooks(config.hooks);
207
+ if (validErr !== null) {
208
+ logger.error(`nit: ${validErr.message}`);
209
+ return 1;
210
+ }
211
+ const [installErr] = await installHooks(config.hooks, hooksDir);
212
+ if (installErr !== null) {
213
+ logger.error(`nit: ${installErr.message}`);
214
+ return 1;
215
+ }
216
+ const hookNames = Object.keys(config.hooks);
217
+ if (hookNames.length === 0) {
218
+ logger.log("nit: no hooks configured");
219
+ } else {
220
+ logger.log(`nit: installed ${hookNames.join(", ")}`);
221
+ }
222
+ return 0;
223
+ }
224
+ var isMain = typeof process !== "undefined" && /[/\\](?:cli\.[jt]s|nit)$/.test(process.argv[1] ?? "");
225
+ if (isMain) {
226
+ const args = process.argv.slice(2);
227
+ run(args, process.cwd(), console).then((code) => {
228
+ process.exit(code);
229
+ });
230
+ }
231
+ export {
232
+ run
233
+ };
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@pyyupsk/nit",
3
+ "version": "0.1.0",
4
+ "description": "Lightweight zero-dependency Git hooks manager for JavaScript/Node.js projects",
5
+ "license": "MIT",
6
+ "packageManager": "bun@1.3.11",
7
+ "type": "module",
8
+ "main": "dist/cli.js",
9
+ "module": "dist/cli.js",
10
+ "bin": {
11
+ "nit": "dist/cli.js"
12
+ },
13
+ "engines": {
14
+ "node": ">=20"
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "check": "biome check .",
22
+ "dev": "tsup --watch",
23
+ "format": "biome format --write .",
24
+ "prepare": "bun run ./src/cli.ts install",
25
+ "test": "vitest run",
26
+ "test:watch": "vitest",
27
+ "typecheck": "tsc --noEmit"
28
+ },
29
+ "devDependencies": {
30
+ "@biomejs/biome": "^2.4.11",
31
+ "@types/bun": "^1.3.12",
32
+ "@vitest/coverage-v8": "^4.1.4",
33
+ "tsup": "^8.5.1",
34
+ "typescript": "^6.0.2",
35
+ "vitest": "^4.1.4"
36
+ },
37
+ "nit": {
38
+ "hooks": {
39
+ "pre-commit": "bun format && bun check && bun typecheck && bun test && bun run build"
40
+ }
41
+ }
42
+ }