gatecheck 0.0.1-beta.5
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 +21 -0
- package/README.md +311 -0
- package/dist/bin.mjs +722 -0
- package/dist/setup-BGSEp6JC.mjs +441 -0
- package/package.json +51 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import { n as loadConfig, r as resolveConfigPath, t as log } from "./bin.mjs";
|
|
2
|
+
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import * as v from "valibot";
|
|
5
|
+
import { stringify } from "yaml";
|
|
6
|
+
import inquirer from "inquirer";
|
|
7
|
+
|
|
8
|
+
//#region src/pm.ts
|
|
9
|
+
const lockfiles = [
|
|
10
|
+
{
|
|
11
|
+
file: "pnpm-lock.yaml",
|
|
12
|
+
pm: "pnpm"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
file: "bun.lockb",
|
|
16
|
+
pm: "bun"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
file: "bun.lock",
|
|
20
|
+
pm: "bun"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
file: "yarn.lock",
|
|
24
|
+
pm: "yarn"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
file: "package-lock.json",
|
|
28
|
+
pm: "npm"
|
|
29
|
+
}
|
|
30
|
+
];
|
|
31
|
+
const detectPackageManager = async (cwd) => {
|
|
32
|
+
for (const { file, pm } of lockfiles) try {
|
|
33
|
+
await access(resolve(cwd, file));
|
|
34
|
+
return pm;
|
|
35
|
+
} catch {}
|
|
36
|
+
return "npm";
|
|
37
|
+
};
|
|
38
|
+
const executors = {
|
|
39
|
+
pnpm: "pnpm exec",
|
|
40
|
+
npm: "npx",
|
|
41
|
+
yarn: "yarn exec",
|
|
42
|
+
bun: "bunx"
|
|
43
|
+
};
|
|
44
|
+
const getExecutor = (pm) => executors[pm];
|
|
45
|
+
const runners = {
|
|
46
|
+
pnpm: "pnpm",
|
|
47
|
+
npm: "npx",
|
|
48
|
+
yarn: "yarn",
|
|
49
|
+
bun: "bunx"
|
|
50
|
+
};
|
|
51
|
+
const getRunner = (pm) => runners[pm];
|
|
52
|
+
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/presets.ts
|
|
55
|
+
const presets = [
|
|
56
|
+
{
|
|
57
|
+
name: "prettier",
|
|
58
|
+
description: "Format with Prettier",
|
|
59
|
+
checks: { prettier: {
|
|
60
|
+
match: "\\.((m|c)?(j|t)sx?|json|css|scss|less|html|md|ya?ml)$",
|
|
61
|
+
command: "prettier --write --no-error-on-unmatched-pattern {{ ctx.CHANGED_FILES }}",
|
|
62
|
+
group: "format"
|
|
63
|
+
} }
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: "oxfmt",
|
|
67
|
+
description: "Format with oxfmt",
|
|
68
|
+
checks: { oxfmt: {
|
|
69
|
+
match: "\\.(m|c)?(j|t)sx?$",
|
|
70
|
+
command: "oxfmt --write --no-error-on-unmatched-pattern {{ ctx.CHANGED_FILES }}",
|
|
71
|
+
group: "format"
|
|
72
|
+
} }
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: "eslint",
|
|
76
|
+
description: "Lint with ESLint",
|
|
77
|
+
checks: { eslint: {
|
|
78
|
+
match: "\\.(m|c)?(j|t)sx?$",
|
|
79
|
+
command: "eslint {{ ctx.CHANGED_FILES }}",
|
|
80
|
+
group: "lint"
|
|
81
|
+
} }
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: "oxlint",
|
|
85
|
+
description: "Lint with oxlint",
|
|
86
|
+
checks: { oxlint: {
|
|
87
|
+
match: "\\.(m|c)?(j|t)sx?$",
|
|
88
|
+
command: "oxlint --type-aware --fix {{ ctx.CHANGED_FILES }}",
|
|
89
|
+
group: "lint"
|
|
90
|
+
} }
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "biome",
|
|
94
|
+
description: "Lint & format with Biome",
|
|
95
|
+
checks: {
|
|
96
|
+
"biome-format": {
|
|
97
|
+
match: "\\.((m|c)?(j|t)sx?|json|jsonc|css)$",
|
|
98
|
+
command: "biome format --write {{ ctx.CHANGED_FILES }}",
|
|
99
|
+
group: "format"
|
|
100
|
+
},
|
|
101
|
+
"biome-check": {
|
|
102
|
+
match: "\\.((m|c)?(j|t)sx?|json|jsonc|css)$",
|
|
103
|
+
command: "biome check --write {{ ctx.CHANGED_FILES }}",
|
|
104
|
+
group: "lint"
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: "tsc",
|
|
110
|
+
description: "Type-check with TypeScript compiler",
|
|
111
|
+
checks: { typecheck: {
|
|
112
|
+
match: "\\.(m|c)?tsx?$",
|
|
113
|
+
command: "tsc --noEmit",
|
|
114
|
+
group: "typecheck"
|
|
115
|
+
} }
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: "tsgo",
|
|
119
|
+
description: "Type-check with tsgo (native TypeScript)",
|
|
120
|
+
checks: { "typecheck-tsgo": {
|
|
121
|
+
match: "\\.(m|c)?tsx?$",
|
|
122
|
+
command: "tsgo --noEmit",
|
|
123
|
+
group: "typecheck"
|
|
124
|
+
} }
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: "vitest",
|
|
128
|
+
description: "Run related tests with Vitest",
|
|
129
|
+
checks: { vitest: {
|
|
130
|
+
match: "\\.(m|c)?(j|t)sx?$",
|
|
131
|
+
command: "vitest related --run --passWithNoTests {{ ctx.CHANGED_FILES }}",
|
|
132
|
+
group: "test"
|
|
133
|
+
} }
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: "jest",
|
|
137
|
+
description: "Run related tests with Jest",
|
|
138
|
+
checks: { jest: {
|
|
139
|
+
match: "\\.(m|c)?(j|t)sx?$",
|
|
140
|
+
command: "jest --findRelatedTests --passWithNoTests {{ ctx.CHANGED_FILES }}",
|
|
141
|
+
group: "test"
|
|
142
|
+
} }
|
|
143
|
+
}
|
|
144
|
+
];
|
|
145
|
+
const REVIEW_PROMPT = [
|
|
146
|
+
"Changed files: {{ ctx.CHANGED_FILES }}",
|
|
147
|
+
"",
|
|
148
|
+
"You are a professional software architect.",
|
|
149
|
+
"Please review the changes above.",
|
|
150
|
+
"Point out any design issues, bug risks, or improvements."
|
|
151
|
+
].join("\n");
|
|
152
|
+
const reviewPresets = [{
|
|
153
|
+
name: "codex",
|
|
154
|
+
description: "Architecture review with OpenAI Codex",
|
|
155
|
+
reviews: { "codex-review": {
|
|
156
|
+
match: ".*",
|
|
157
|
+
exclude: "**/*.md",
|
|
158
|
+
vars: { prompt: REVIEW_PROMPT },
|
|
159
|
+
command: "codex exec --sandbox 'workspace-write' {{ vars.prompt }}"
|
|
160
|
+
} }
|
|
161
|
+
}, {
|
|
162
|
+
name: "claude",
|
|
163
|
+
description: "Architecture review with Claude Code",
|
|
164
|
+
reviews: { "claude-review": {
|
|
165
|
+
match: ".*",
|
|
166
|
+
exclude: "**/*.md",
|
|
167
|
+
vars: { prompt: REVIEW_PROMPT },
|
|
168
|
+
command: "claude --permission-mode 'auto' -p {{ vars.prompt }}"
|
|
169
|
+
} }
|
|
170
|
+
}];
|
|
171
|
+
|
|
172
|
+
//#endregion
|
|
173
|
+
//#region src/setup.ts
|
|
174
|
+
const DEFAULT_CHANGED = "untracked,unstaged,staged,branch:main";
|
|
175
|
+
const DEFAULT_TARGET = "all";
|
|
176
|
+
const promptDefaults = async (existing) => {
|
|
177
|
+
const answers = await inquirer.prompt([{
|
|
178
|
+
type: "input",
|
|
179
|
+
name: "changed",
|
|
180
|
+
message: "Default changed sources (comma-separated):",
|
|
181
|
+
default: existing?.changed ?? DEFAULT_CHANGED
|
|
182
|
+
}, {
|
|
183
|
+
type: "input",
|
|
184
|
+
name: "target",
|
|
185
|
+
message: "Default target groups (comma-separated or 'all'):",
|
|
186
|
+
default: existing?.target ?? DEFAULT_TARGET
|
|
187
|
+
}]);
|
|
188
|
+
return {
|
|
189
|
+
changed: answers.changed,
|
|
190
|
+
target: answers.target
|
|
191
|
+
};
|
|
192
|
+
};
|
|
193
|
+
const resolveDefaults = (existing) => ({
|
|
194
|
+
changed: existing?.changed ?? DEFAULT_CHANGED,
|
|
195
|
+
target: existing?.target ?? DEFAULT_TARGET
|
|
196
|
+
});
|
|
197
|
+
const presetDependencies = {
|
|
198
|
+
prettier: ["prettier"],
|
|
199
|
+
oxfmt: ["oxfmt"],
|
|
200
|
+
eslint: ["eslint"],
|
|
201
|
+
oxlint: ["oxlint"],
|
|
202
|
+
biome: ["@biomejs/biome"],
|
|
203
|
+
tsc: ["typescript"],
|
|
204
|
+
tsgo: ["@typescript/native-preview"],
|
|
205
|
+
vitest: ["vitest"],
|
|
206
|
+
jest: ["jest"]
|
|
207
|
+
};
|
|
208
|
+
const PackageJsonSchema = v.object({
|
|
209
|
+
dependencies: v.optional(v.record(v.string(), v.string())),
|
|
210
|
+
devDependencies: v.optional(v.record(v.string(), v.string()))
|
|
211
|
+
});
|
|
212
|
+
const readInstalledDeps = async (cwd) => {
|
|
213
|
+
try {
|
|
214
|
+
const raw = await readFile(join(cwd, "package.json"), "utf-8");
|
|
215
|
+
const json = JSON.parse(raw);
|
|
216
|
+
const pkg = v.parse(PackageJsonSchema, json);
|
|
217
|
+
const deps = /* @__PURE__ */ new Set();
|
|
218
|
+
if (pkg.dependencies !== void 0) for (const name of Object.keys(pkg.dependencies)) deps.add(name);
|
|
219
|
+
if (pkg.devDependencies !== void 0) for (const name of Object.keys(pkg.devDependencies)) deps.add(name);
|
|
220
|
+
return deps;
|
|
221
|
+
} catch {
|
|
222
|
+
return /* @__PURE__ */ new Set();
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
const prefixCommand = (command, executor) => `${executor} ${command}`;
|
|
226
|
+
const isPresetDetected = (presetName, existingNames, installedDeps) => {
|
|
227
|
+
const preset = presets.find((p) => p.name === presetName);
|
|
228
|
+
if (preset !== void 0 && Object.keys(preset.checks).some((k) => existingNames.has(k))) return true;
|
|
229
|
+
const deps = presetDependencies[presetName];
|
|
230
|
+
if (deps !== void 0 && deps.some((d) => installedDeps.has(d))) return true;
|
|
231
|
+
return false;
|
|
232
|
+
};
|
|
233
|
+
const toCheckEntries = (selected, existingChecks, executor) => {
|
|
234
|
+
const existingByName = new Map(existingChecks.map((c) => [c.name, c]));
|
|
235
|
+
return selected.flatMap((name) => {
|
|
236
|
+
const preset = presets.find((p) => p.name === name);
|
|
237
|
+
if (preset === void 0) return [];
|
|
238
|
+
const checks = preset.checks;
|
|
239
|
+
return Object.entries(checks).map(([checkName, checkConfig]) => {
|
|
240
|
+
const existing = existingByName.get(checkName);
|
|
241
|
+
if (existing !== void 0) return existing;
|
|
242
|
+
return {
|
|
243
|
+
name: checkName,
|
|
244
|
+
match: checkConfig.match,
|
|
245
|
+
group: checkConfig.group,
|
|
246
|
+
command: prefixCommand(checkConfig.command, executor)
|
|
247
|
+
};
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
};
|
|
251
|
+
const promptPresets = async (existingChecks, executor, installedDeps) => {
|
|
252
|
+
const existingNames = new Set(existingChecks.map((c) => c.name));
|
|
253
|
+
const { selected } = await inquirer.prompt([{
|
|
254
|
+
type: "checkbox",
|
|
255
|
+
name: "selected",
|
|
256
|
+
message: "Select checks to add:",
|
|
257
|
+
choices: presets.map((p) => ({
|
|
258
|
+
name: `${p.name} - ${p.description}`,
|
|
259
|
+
value: p.name,
|
|
260
|
+
checked: isPresetDetected(p.name, existingNames, installedDeps)
|
|
261
|
+
}))
|
|
262
|
+
}]);
|
|
263
|
+
return toCheckEntries(selected, existingChecks, executor);
|
|
264
|
+
};
|
|
265
|
+
const autoSelectPresets = (existingChecks, executor, installedDeps) => {
|
|
266
|
+
const existingNames = new Set(existingChecks.map((c) => c.name));
|
|
267
|
+
return toCheckEntries(presets.filter((p) => isPresetDetected(p.name, existingNames, installedDeps)).map((p) => p.name), existingChecks, executor);
|
|
268
|
+
};
|
|
269
|
+
const toReviewEntries = (selected, existingReviews) => {
|
|
270
|
+
const existingByName = new Map(existingReviews.map((r) => [r.name, r]));
|
|
271
|
+
return selected.flatMap((name) => {
|
|
272
|
+
const preset = reviewPresets.find((p) => p.name === name);
|
|
273
|
+
if (preset === void 0) return [];
|
|
274
|
+
const reviews = preset.reviews;
|
|
275
|
+
return Object.entries(reviews).map(([reviewName, reviewConfig]) => {
|
|
276
|
+
const existing = existingByName.get(reviewName);
|
|
277
|
+
if (existing !== void 0) return existing;
|
|
278
|
+
return {
|
|
279
|
+
name: reviewName,
|
|
280
|
+
...reviewConfig
|
|
281
|
+
};
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
};
|
|
285
|
+
const promptReviewPresets = async (existingReviews) => {
|
|
286
|
+
const { selected } = await inquirer.prompt([{
|
|
287
|
+
type: "select",
|
|
288
|
+
name: "selected",
|
|
289
|
+
message: "Select a review preset:",
|
|
290
|
+
choices: [...reviewPresets.map((p) => ({
|
|
291
|
+
name: `${p.name} - ${p.description}`,
|
|
292
|
+
value: p.name
|
|
293
|
+
})), {
|
|
294
|
+
name: "none - Skip review setup",
|
|
295
|
+
value: "none"
|
|
296
|
+
}]
|
|
297
|
+
}]);
|
|
298
|
+
if (selected === "none") return existingReviews;
|
|
299
|
+
return toReviewEntries([selected], existingReviews);
|
|
300
|
+
};
|
|
301
|
+
const StopHookEntrySchema = v.object({
|
|
302
|
+
matcher: v.string(),
|
|
303
|
+
hooks: v.array(v.object({
|
|
304
|
+
type: v.string(),
|
|
305
|
+
command: v.string()
|
|
306
|
+
}))
|
|
307
|
+
});
|
|
308
|
+
const ClaudeSettingsSchema = v.looseObject({ hooks: v.optional(v.looseObject({ Stop: v.optional(v.array(StopHookEntrySchema)) })) });
|
|
309
|
+
const resolveClaudeSettingsPath = (cwd) => join(cwd, ".claude", "settings.json");
|
|
310
|
+
const readClaudeSettings = async (path) => {
|
|
311
|
+
try {
|
|
312
|
+
const raw = await readFile(path, "utf-8");
|
|
313
|
+
const json = JSON.parse(raw);
|
|
314
|
+
return v.parse(ClaudeSettingsSchema, json);
|
|
315
|
+
} catch {
|
|
316
|
+
return {};
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
const hasStopHook = (settings, command) => {
|
|
320
|
+
return (settings.hooks?.Stop ?? []).some((entry) => entry.hooks.some((h) => h.command === command));
|
|
321
|
+
};
|
|
322
|
+
const promptClaudeCodeHooks = async (cwd, runner) => {
|
|
323
|
+
const settingsPath = resolveClaudeSettingsPath(cwd);
|
|
324
|
+
const command = `${runner} gatecheck check --format claude-code-hooks`;
|
|
325
|
+
const settings = await readClaudeSettings(settingsPath);
|
|
326
|
+
if (hasStopHook(settings, command)) {
|
|
327
|
+
log("Claude Code Stop hook is already configured.");
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
const { enable } = await inquirer.prompt([{
|
|
331
|
+
type: "confirm",
|
|
332
|
+
name: "enable",
|
|
333
|
+
message: "Set up Claude Code Stop hook? This runs checks before Claude finishes and blocks it from stopping if any check fails.",
|
|
334
|
+
default: true
|
|
335
|
+
}]);
|
|
336
|
+
if (!enable) return;
|
|
337
|
+
const hookEntry = {
|
|
338
|
+
matcher: "",
|
|
339
|
+
hooks: [{
|
|
340
|
+
type: "command",
|
|
341
|
+
command
|
|
342
|
+
}]
|
|
343
|
+
};
|
|
344
|
+
const existingStop = settings.hooks?.Stop ?? [];
|
|
345
|
+
const updated = {
|
|
346
|
+
...settings,
|
|
347
|
+
hooks: {
|
|
348
|
+
...settings.hooks,
|
|
349
|
+
Stop: [...existingStop, hookEntry]
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
await mkdir(dirname(settingsPath), { recursive: true });
|
|
353
|
+
await writeFile(settingsPath, `${JSON.stringify(updated, null, 2)}\n`);
|
|
354
|
+
log(`Claude Code hook written to ${settingsPath}`);
|
|
355
|
+
};
|
|
356
|
+
const CopilotHookEntrySchema = v.object({
|
|
357
|
+
type: v.string(),
|
|
358
|
+
bash: v.string()
|
|
359
|
+
});
|
|
360
|
+
const CopilotHooksFileSchema = v.looseObject({
|
|
361
|
+
version: v.number(),
|
|
362
|
+
hooks: v.optional(v.looseObject({ agentStop: v.optional(v.array(CopilotHookEntrySchema)) }))
|
|
363
|
+
});
|
|
364
|
+
const resolveCopilotHooksPath = (cwd) => join(cwd, ".github", "hooks", "gatecheck.json");
|
|
365
|
+
const readCopilotHooksFile = async (path) => {
|
|
366
|
+
try {
|
|
367
|
+
const raw = await readFile(path, "utf-8");
|
|
368
|
+
const json = JSON.parse(raw);
|
|
369
|
+
return v.parse(CopilotHooksFileSchema, json);
|
|
370
|
+
} catch {
|
|
371
|
+
return { version: 1 };
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
const hasCopilotAgentStopHook = (config, bash) => {
|
|
375
|
+
return (config.hooks?.agentStop ?? []).some((entry) => entry.bash === bash);
|
|
376
|
+
};
|
|
377
|
+
const promptCopilotCliHooks = async (cwd, runner) => {
|
|
378
|
+
const hooksPath = resolveCopilotHooksPath(cwd);
|
|
379
|
+
const bash = `${runner} gatecheck check --format copilot-cli-hooks`;
|
|
380
|
+
const config = await readCopilotHooksFile(hooksPath);
|
|
381
|
+
if (hasCopilotAgentStopHook(config, bash)) {
|
|
382
|
+
log("Copilot CLI agentStop hook is already configured.");
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
const { enable } = await inquirer.prompt([{
|
|
386
|
+
type: "confirm",
|
|
387
|
+
name: "enable",
|
|
388
|
+
message: "Set up Copilot CLI agentStop hook? This runs checks before Copilot finishes and blocks it from stopping if any check fails.",
|
|
389
|
+
default: true
|
|
390
|
+
}]);
|
|
391
|
+
if (!enable) return;
|
|
392
|
+
const hookEntry = {
|
|
393
|
+
type: "command",
|
|
394
|
+
bash
|
|
395
|
+
};
|
|
396
|
+
const existingAgentStop = config.hooks?.agentStop ?? [];
|
|
397
|
+
const updated = {
|
|
398
|
+
...config,
|
|
399
|
+
version: config.version,
|
|
400
|
+
hooks: {
|
|
401
|
+
...config.hooks,
|
|
402
|
+
agentStop: [...existingAgentStop, hookEntry]
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
await mkdir(dirname(hooksPath), { recursive: true });
|
|
406
|
+
await writeFile(hooksPath, `${JSON.stringify(updated, null, 2)}\n`);
|
|
407
|
+
log(`Copilot CLI hook written to ${hooksPath}`);
|
|
408
|
+
};
|
|
409
|
+
const runSetup = async (cwd, options) => {
|
|
410
|
+
const nonInteractive = options?.nonInteractive === true;
|
|
411
|
+
let existing;
|
|
412
|
+
try {
|
|
413
|
+
existing = await loadConfig(cwd);
|
|
414
|
+
} catch {}
|
|
415
|
+
log("gatecheck setup\n");
|
|
416
|
+
const pm = await detectPackageManager(cwd);
|
|
417
|
+
const executor = getExecutor(pm);
|
|
418
|
+
const runner = getRunner(pm);
|
|
419
|
+
log(`Detected package manager: ${pm}\n`);
|
|
420
|
+
const installedDeps = existing !== void 0 ? /* @__PURE__ */ new Set() : await readInstalledDeps(cwd);
|
|
421
|
+
const defaults = nonInteractive ? resolveDefaults(existing?.defaults) : await promptDefaults(existing?.defaults);
|
|
422
|
+
const checks = nonInteractive ? autoSelectPresets(existing?.checks ?? [], executor, installedDeps) : await promptPresets(existing?.checks ?? [], executor, installedDeps);
|
|
423
|
+
const reviews = nonInteractive ? existing?.reviews ?? [] : await promptReviewPresets(existing?.reviews ?? []);
|
|
424
|
+
const config = {
|
|
425
|
+
defaults,
|
|
426
|
+
checks,
|
|
427
|
+
...reviews.length > 0 ? { reviews } : {}
|
|
428
|
+
};
|
|
429
|
+
const configPath = resolveConfigPath(cwd);
|
|
430
|
+
await writeFile(configPath, stringify(config, { lineWidth: 120 }));
|
|
431
|
+
log(`\nConfig written to ${configPath}`);
|
|
432
|
+
if (!nonInteractive) {
|
|
433
|
+
log("");
|
|
434
|
+
await promptClaudeCodeHooks(cwd, runner);
|
|
435
|
+
log("");
|
|
436
|
+
await promptCopilotCliHooks(cwd, runner);
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
//#endregion
|
|
441
|
+
export { runSetup };
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gatecheck",
|
|
3
|
+
"version": "0.0.1-beta.5",
|
|
4
|
+
"description": "Quality gate for git changes — run checks and AI reviews against changed files",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"bin": {
|
|
7
|
+
"gatecheck": "./dist/bin.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"type": "module",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"commander": "14.0.3",
|
|
15
|
+
"inquirer": "13.3.0",
|
|
16
|
+
"valibot": "1.2.0",
|
|
17
|
+
"yaml": "2.8.2"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@tsconfig/strictest": "2.0.8",
|
|
21
|
+
"@types/inquirer": "9.0.9",
|
|
22
|
+
"@types/node": "25.3.3",
|
|
23
|
+
"@valibot/to-json-schema": "1.5.0",
|
|
24
|
+
"lefthook": "2.1.2",
|
|
25
|
+
"npm-run-all2": "8.0.4",
|
|
26
|
+
"oxfmt": "0.36.0",
|
|
27
|
+
"oxlint": "1.51.0",
|
|
28
|
+
"oxlint-tsgolint": "0.16.0",
|
|
29
|
+
"release-it": "19.2.4",
|
|
30
|
+
"release-it-pnpm": "4.6.6",
|
|
31
|
+
"tsdown": "0.20.3",
|
|
32
|
+
"tsx": "4.21.0",
|
|
33
|
+
"typescript": "5.9.3",
|
|
34
|
+
"vitest": "4.0.18"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=22"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"cli": "tsx ./src/bin.ts",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"build": "tsdown",
|
|
43
|
+
"typecheck": "tsc --noEmit",
|
|
44
|
+
"lint": "run-s lint:*",
|
|
45
|
+
"lint:oxlint": "oxlint --type-aware --type-check --ignore-path .gitignore --report-unused-disable-directives --tsconfig ./tsconfig.json",
|
|
46
|
+
"lint:oxfmt": "pnpm fix:oxfmt --check",
|
|
47
|
+
"fix": "run-s fix:*",
|
|
48
|
+
"fix:oxlint": "pnpm lint:oxlint --fix --fix-suggestions --fix-dangerously",
|
|
49
|
+
"fix:oxfmt": "oxfmt --ignore-path .gitignore --no-error-on-unmatched-pattern"
|
|
50
|
+
}
|
|
51
|
+
}
|