@hasna/hooks 0.6.1 → 0.6.2
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/bin/index.js +84 -36
- package/bin/serve.js +24 -5
- package/dist/index.js +41 -13
- package/dist/lib/resolve.d.ts +8 -0
- package/dist/lib/secure-compare.d.ts +8 -0
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -4288,10 +4288,18 @@ __export(exports_resolve, {
|
|
|
4288
4288
|
resolveScriptPath: () => resolveScriptPath,
|
|
4289
4289
|
resolveHookMeta: () => resolveHookMeta,
|
|
4290
4290
|
resolveHookDir: () => resolveHookDir,
|
|
4291
|
-
resolveHook: () => resolveHook
|
|
4291
|
+
resolveHook: () => resolveHook,
|
|
4292
|
+
resolveBundledHooksDir: () => resolveBundledHooksDir
|
|
4292
4293
|
});
|
|
4293
|
-
import { join as join4 } from "path";
|
|
4294
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
4294
4295
|
import { existsSync as existsSync4 } from "fs";
|
|
4296
|
+
import { fileURLToPath } from "url";
|
|
4297
|
+
function resolveBundledHooksDir(moduleDir = dirname2(fileURLToPath(import.meta.url))) {
|
|
4298
|
+
const sourceLayout = join4(moduleDir, "..", "..", "hooks");
|
|
4299
|
+
if (existsSync4(join4(sourceLayout, "hook-gitguard")))
|
|
4300
|
+
return sourceLayout;
|
|
4301
|
+
return join4(moduleDir, "..", "hooks");
|
|
4302
|
+
}
|
|
4295
4303
|
function resolveHookDir(name) {
|
|
4296
4304
|
const custom = readCustomManifest(name);
|
|
4297
4305
|
if (custom)
|
|
@@ -4300,7 +4308,7 @@ function resolveHookDir(name) {
|
|
|
4300
4308
|
if (!meta)
|
|
4301
4309
|
return;
|
|
4302
4310
|
const short = shortManifestName(name);
|
|
4303
|
-
const bundledBase =
|
|
4311
|
+
const bundledBase = resolveBundledHooksDir();
|
|
4304
4312
|
const direct = join4(bundledBase, short);
|
|
4305
4313
|
if (existsSync4(direct))
|
|
4306
4314
|
return direct;
|
|
@@ -4363,7 +4371,6 @@ function resolveHook(name) {
|
|
|
4363
4371
|
meta
|
|
4364
4372
|
};
|
|
4365
4373
|
}
|
|
4366
|
-
var __dirname = "/home/hasna/.hasna/repos/worktrees/hasna-apps/workspace-repos-guard/apps/hooks/src/lib";
|
|
4367
4374
|
var init_resolve = __esm(() => {
|
|
4368
4375
|
init_registry();
|
|
4369
4376
|
init_manifest();
|
|
@@ -4371,9 +4378,9 @@ var init_resolve = __esm(() => {
|
|
|
4371
4378
|
|
|
4372
4379
|
// src/lib/installer.ts
|
|
4373
4380
|
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
4374
|
-
import { join as join5, dirname as
|
|
4381
|
+
import { join as join5, dirname as dirname3 } from "path";
|
|
4375
4382
|
import { homedir as homedir3 } from "os";
|
|
4376
|
-
import { fileURLToPath } from "url";
|
|
4383
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4377
4384
|
function normalizeHookName(name) {
|
|
4378
4385
|
return name.startsWith("hook-") ? name : `hook-${name}`;
|
|
4379
4386
|
}
|
|
@@ -4438,7 +4445,7 @@ function readSettings(scope = "global", target = "claude") {
|
|
|
4438
4445
|
}
|
|
4439
4446
|
function writeSettings(settings, scope = "global", target = "claude") {
|
|
4440
4447
|
const path = getSettingsPath(scope, target);
|
|
4441
|
-
const dir =
|
|
4448
|
+
const dir = dirname3(path);
|
|
4442
4449
|
if (!existsSync5(dir)) {
|
|
4443
4450
|
mkdirSync3(dir, { recursive: true });
|
|
4444
4451
|
}
|
|
@@ -4535,7 +4542,7 @@ function readCodewithConfig(scope, configPath) {
|
|
|
4535
4542
|
}
|
|
4536
4543
|
function appendCodewithFragment(fragment, scope, configPath) {
|
|
4537
4544
|
const path = getSettingsPath(scope, "codewith", configPath);
|
|
4538
|
-
const dir =
|
|
4545
|
+
const dir = dirname3(path);
|
|
4539
4546
|
if (!existsSync5(dir))
|
|
4540
4547
|
mkdirSync3(dir, { recursive: true });
|
|
4541
4548
|
const existing = existsSync5(path) ? readFileSync3(path, "utf-8") : "";
|
|
@@ -4786,7 +4793,7 @@ var __dirname2, HOOKS_DIR, EVENT_MAP, getInstalledHooks;
|
|
|
4786
4793
|
var init_installer = __esm(() => {
|
|
4787
4794
|
init_registry();
|
|
4788
4795
|
init_resolve();
|
|
4789
|
-
__dirname2 =
|
|
4796
|
+
__dirname2 = dirname3(fileURLToPath2(import.meta.url));
|
|
4790
4797
|
HOOKS_DIR = existsSync5(join5(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join5(__dirname2, "..", "..", "hooks") : join5(__dirname2, "..", "hooks");
|
|
4791
4798
|
EVENT_MAP = {
|
|
4792
4799
|
claude: {
|
|
@@ -4823,6 +4830,35 @@ var init_installer = __esm(() => {
|
|
|
4823
4830
|
getInstalledHooks = getRegisteredHooks;
|
|
4824
4831
|
});
|
|
4825
4832
|
|
|
4833
|
+
// src/lib/registration.ts
|
|
4834
|
+
function safeMatcherTest(pattern, value) {
|
|
4835
|
+
try {
|
|
4836
|
+
return new RegExp(pattern).test(value);
|
|
4837
|
+
} catch {
|
|
4838
|
+
return false;
|
|
4839
|
+
}
|
|
4840
|
+
}
|
|
4841
|
+
function hookRegisteredInSettings(settings, name, eventSpec, hookMatcher) {
|
|
4842
|
+
const separator = eventSpec.indexOf(":");
|
|
4843
|
+
const eventName = separator === -1 ? eventSpec : eventSpec.slice(0, separator);
|
|
4844
|
+
const specMatcher = separator === -1 ? "" : eventSpec.slice(separator + 1);
|
|
4845
|
+
const matcher = specMatcher || hookMatcher || "";
|
|
4846
|
+
const hooks = settings.hooks?.[eventName];
|
|
4847
|
+
if (!Array.isArray(hooks))
|
|
4848
|
+
return false;
|
|
4849
|
+
return hooks.some((entry) => entry?.hooks?.some((h) => {
|
|
4850
|
+
const match = h?.command?.match(/^hooks run ([\w-]+)/);
|
|
4851
|
+
if (!match || match[1] !== name)
|
|
4852
|
+
return false;
|
|
4853
|
+
if (!matcher)
|
|
4854
|
+
return true;
|
|
4855
|
+
const entryMatcher = typeof entry.matcher === "string" ? entry.matcher : "";
|
|
4856
|
+
if (!entryMatcher)
|
|
4857
|
+
return true;
|
|
4858
|
+
return entryMatcher === matcher || safeMatcherTest(entryMatcher, matcher) || safeMatcherTest(matcher, entryMatcher);
|
|
4859
|
+
}));
|
|
4860
|
+
}
|
|
4861
|
+
|
|
4826
4862
|
// src/lib/profiles.ts
|
|
4827
4863
|
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4, readdirSync as readdirSync2, rmSync, cpSync } from "fs";
|
|
4828
4864
|
import { join as join6 } from "path";
|
|
@@ -5460,14 +5496,14 @@ __export(exports_run, {
|
|
|
5460
5496
|
});
|
|
5461
5497
|
import { randomBytes } from "crypto";
|
|
5462
5498
|
import { rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
5463
|
-
import { basename as basename2, dirname as
|
|
5499
|
+
import { basename as basename2, dirname as dirname4, isAbsolute as isAbsolute2, join as join9 } from "path";
|
|
5464
5500
|
function extensionOf(scriptPath) {
|
|
5465
5501
|
const base = basename2(scriptPath);
|
|
5466
5502
|
const dot = base.lastIndexOf(".");
|
|
5467
5503
|
return dot > 0 ? base.slice(dot + 1) : "ts";
|
|
5468
5504
|
}
|
|
5469
5505
|
async function executeVerifiedScript(options) {
|
|
5470
|
-
const scriptDir =
|
|
5506
|
+
const scriptDir = dirname4(options.scriptPath);
|
|
5471
5507
|
const tempPath = join9(scriptDir, `.hooks-verified-${randomBytes(12).toString("hex")}.${extensionOf(options.scriptPath)}`);
|
|
5472
5508
|
try {
|
|
5473
5509
|
writeFileSync6(tempPath, options.content, { flag: "wx", mode: 384 });
|
|
@@ -6036,6 +6072,19 @@ var init_storage = __esm(() => {
|
|
|
6036
6072
|
init_pg_migrations();
|
|
6037
6073
|
});
|
|
6038
6074
|
|
|
6075
|
+
// src/lib/secure-compare.ts
|
|
6076
|
+
function secureEqual(a, b) {
|
|
6077
|
+
const enc = new TextEncoder;
|
|
6078
|
+
const ab = enc.encode(a);
|
|
6079
|
+
const bb = enc.encode(b);
|
|
6080
|
+
let diff = ab.length ^ bb.length;
|
|
6081
|
+
const n = Math.max(ab.length, bb.length);
|
|
6082
|
+
for (let i = 0;i < n; i++) {
|
|
6083
|
+
diff |= (ab[i] ?? 0) ^ (bb[i] ?? 0);
|
|
6084
|
+
}
|
|
6085
|
+
return diff === 0;
|
|
6086
|
+
}
|
|
6087
|
+
|
|
6039
6088
|
// src/serve.ts
|
|
6040
6089
|
var exports_serve = {};
|
|
6041
6090
|
__export(exports_serve, {
|
|
@@ -6097,8 +6146,8 @@ function authorized(req, apiKey) {
|
|
|
6097
6146
|
return false;
|
|
6098
6147
|
const header = req.headers.get("authorization") ?? "";
|
|
6099
6148
|
if (header.startsWith("Bearer "))
|
|
6100
|
-
return header.slice("Bearer ".length)
|
|
6101
|
-
return req.headers.get("x-api-key")
|
|
6149
|
+
return secureEqual(header.slice("Bearer ".length), apiKey);
|
|
6150
|
+
return secureEqual(req.headers.get("x-api-key") ?? "", apiKey);
|
|
6102
6151
|
}
|
|
6103
6152
|
function json(body, status = 200, headers = {}) {
|
|
6104
6153
|
return new Response(JSON.stringify(body), {
|
|
@@ -6200,10 +6249,17 @@ function collectBundledCatalog() {
|
|
|
6200
6249
|
return out;
|
|
6201
6250
|
}
|
|
6202
6251
|
async function fetchJson(base, path) {
|
|
6252
|
+
const headers = { accept: "application/json" };
|
|
6253
|
+
const apiKey = resolveApiKey();
|
|
6254
|
+
if (apiKey)
|
|
6255
|
+
headers["x-api-key"] = apiKey;
|
|
6203
6256
|
const res = await fetch(`${base}${path}`, {
|
|
6204
|
-
headers
|
|
6257
|
+
headers,
|
|
6205
6258
|
signal: AbortSignal.timeout(15000)
|
|
6206
6259
|
});
|
|
6260
|
+
if (res.status === 401) {
|
|
6261
|
+
throw new Error("registry requires API key \u2014 set HASNA_HOOKS_API_KEY or HOOKS_API_KEY (vault-delivered, never stored)");
|
|
6262
|
+
}
|
|
6207
6263
|
if (!res.ok) {
|
|
6208
6264
|
throw new Error(`GET ${path} failed with status ${res.status}`);
|
|
6209
6265
|
}
|
|
@@ -6418,8 +6474,8 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
6418
6474
|
import { z as z2 } from "zod";
|
|
6419
6475
|
import { createServer } from "http";
|
|
6420
6476
|
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
|
|
6421
|
-
import { join as join11, dirname as
|
|
6422
|
-
import { fileURLToPath as
|
|
6477
|
+
import { join as join11, dirname as dirname5 } from "path";
|
|
6478
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
6423
6479
|
async function runVerifiedHook(name, scriptPath, stdin, timeoutMs) {
|
|
6424
6480
|
const content = readFileSync9(scriptPath);
|
|
6425
6481
|
const check = checkScriptHash(name, sha256Of(content));
|
|
@@ -6594,13 +6650,9 @@ function createHooksServer() {
|
|
|
6594
6650
|
if (meta && settingsExist) {
|
|
6595
6651
|
try {
|
|
6596
6652
|
const settings = JSON.parse(readFileSync9(settingsPath, "utf-8"));
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
return match && match[1] === name;
|
|
6601
|
-
}));
|
|
6602
|
-
if (!found) {
|
|
6603
|
-
issues.push({ hook: name, issue: `Not registered under correct event (${meta.event})`, severity: "error" });
|
|
6653
|
+
if (!hookRegisteredInSettings(settings, name, meta.event, meta.matcher)) {
|
|
6654
|
+
const eventName = meta.event.split(":")[0];
|
|
6655
|
+
issues.push({ hook: name, issue: `Not registered under correct event (${eventName})`, severity: "error" });
|
|
6604
6656
|
hookHealthy = false;
|
|
6605
6657
|
}
|
|
6606
6658
|
} catch {}
|
|
@@ -6957,8 +7009,8 @@ function createHooksServer() {
|
|
|
6957
7009
|
disabled.push(name);
|
|
6958
7010
|
settings.hooks.__disabled = disabled;
|
|
6959
7011
|
const { writeFileSync: writeFileSync7, mkdirSync: mkdirSync8 } = await import("fs");
|
|
6960
|
-
const { dirname:
|
|
6961
|
-
mkdirSync8(
|
|
7012
|
+
const { dirname: dirname6 } = await import("path");
|
|
7013
|
+
mkdirSync8(dirname6(settingsPath), { recursive: true });
|
|
6962
7014
|
writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + `
|
|
6963
7015
|
`);
|
|
6964
7016
|
return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: true, scope }) }] };
|
|
@@ -7290,7 +7342,7 @@ var init_server = __esm(() => {
|
|
|
7290
7342
|
init_resolve();
|
|
7291
7343
|
init_store();
|
|
7292
7344
|
init_storage();
|
|
7293
|
-
__dirname3 =
|
|
7345
|
+
__dirname3 = dirname5(fileURLToPath3(import.meta.url));
|
|
7294
7346
|
pkg = { name: "@hasna/hooks", version: "0.0.0" };
|
|
7295
7347
|
try {
|
|
7296
7348
|
for (const rel of ["../../package.json", "../package.json", "../../../package.json"]) {
|
|
@@ -8567,8 +8619,8 @@ var {
|
|
|
8567
8619
|
// src/cli/index.tsx
|
|
8568
8620
|
import chalk2 from "chalk";
|
|
8569
8621
|
import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
|
|
8570
|
-
import { join as join12, dirname as
|
|
8571
|
-
import { fileURLToPath as
|
|
8622
|
+
import { join as join12, dirname as dirname6 } from "path";
|
|
8623
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
8572
8624
|
|
|
8573
8625
|
// src/cli/components/App.tsx
|
|
8574
8626
|
import { useState as useState7 } from "react";
|
|
@@ -9746,7 +9798,7 @@ init_profiles();
|
|
|
9746
9798
|
init_manifest();
|
|
9747
9799
|
init_resolve();
|
|
9748
9800
|
import { jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime";
|
|
9749
|
-
var __dirname4 =
|
|
9801
|
+
var __dirname4 = dirname6(fileURLToPath4(import.meta.url));
|
|
9750
9802
|
var pkgPath = existsSync12(join12(__dirname4, "..", "package.json")) ? join12(__dirname4, "..", "package.json") : join12(__dirname4, "..", "..", "package.json");
|
|
9751
9803
|
var pkg2 = JSON.parse(readFileSync10(pkgPath, "utf-8"));
|
|
9752
9804
|
var program2 = new Command;
|
|
@@ -10312,13 +10364,9 @@ program2.command("doctor").option("-g, --global", "Check global settings", false
|
|
|
10312
10364
|
if (meta && settingsExist) {
|
|
10313
10365
|
try {
|
|
10314
10366
|
const settings = JSON.parse(readFileSync10(settingsPath, "utf-8"));
|
|
10315
|
-
|
|
10316
|
-
|
|
10317
|
-
|
|
10318
|
-
return match && match[1] === name;
|
|
10319
|
-
}));
|
|
10320
|
-
if (!found) {
|
|
10321
|
-
issues.push({ hook: name, issue: `Not registered under correct event (${meta.event})`, severity: "error" });
|
|
10367
|
+
if (!hookRegisteredInSettings(settings, name, meta.event, meta.matcher)) {
|
|
10368
|
+
const eventName = meta.event.split(":")[0];
|
|
10369
|
+
issues.push({ hook: name, issue: `Not registered under correct event (${eventName})`, severity: "error" });
|
|
10322
10370
|
hookHealthy = false;
|
|
10323
10371
|
}
|
|
10324
10372
|
} catch {}
|
package/bin/serve.js
CHANGED
|
@@ -5051,9 +5051,15 @@ function retrustHook(name, scriptPath, version, source) {
|
|
|
5051
5051
|
}
|
|
5052
5052
|
|
|
5053
5053
|
// src/lib/resolve.ts
|
|
5054
|
-
import { join as join5 } from "path";
|
|
5054
|
+
import { dirname as dirname2, join as join5 } from "path";
|
|
5055
5055
|
import { existsSync as existsSync5 } from "fs";
|
|
5056
|
-
|
|
5056
|
+
import { fileURLToPath } from "url";
|
|
5057
|
+
function resolveBundledHooksDir(moduleDir = dirname2(fileURLToPath(import.meta.url))) {
|
|
5058
|
+
const sourceLayout = join5(moduleDir, "..", "..", "hooks");
|
|
5059
|
+
if (existsSync5(join5(sourceLayout, "hook-gitguard")))
|
|
5060
|
+
return sourceLayout;
|
|
5061
|
+
return join5(moduleDir, "..", "hooks");
|
|
5062
|
+
}
|
|
5057
5063
|
function resolveHookDir(name) {
|
|
5058
5064
|
const custom2 = readCustomManifest(name);
|
|
5059
5065
|
if (custom2)
|
|
@@ -5062,7 +5068,7 @@ function resolveHookDir(name) {
|
|
|
5062
5068
|
if (!meta)
|
|
5063
5069
|
return;
|
|
5064
5070
|
const short = shortManifestName(name);
|
|
5065
|
-
const bundledBase =
|
|
5071
|
+
const bundledBase = resolveBundledHooksDir();
|
|
5066
5072
|
const direct = join5(bundledBase, short);
|
|
5067
5073
|
if (existsSync5(direct))
|
|
5068
5074
|
return direct;
|
|
@@ -5126,6 +5132,19 @@ function resolveHook(name) {
|
|
|
5126
5132
|
};
|
|
5127
5133
|
}
|
|
5128
5134
|
|
|
5135
|
+
// src/lib/secure-compare.ts
|
|
5136
|
+
function secureEqual(a, b) {
|
|
5137
|
+
const enc = new TextEncoder;
|
|
5138
|
+
const ab = enc.encode(a);
|
|
5139
|
+
const bb = enc.encode(b);
|
|
5140
|
+
let diff = ab.length ^ bb.length;
|
|
5141
|
+
const n = Math.max(ab.length, bb.length);
|
|
5142
|
+
for (let i = 0;i < n; i++) {
|
|
5143
|
+
diff |= (ab[i] ?? 0) ^ (bb[i] ?? 0);
|
|
5144
|
+
}
|
|
5145
|
+
return diff === 0;
|
|
5146
|
+
}
|
|
5147
|
+
|
|
5129
5148
|
// src/serve.ts
|
|
5130
5149
|
var DEFAULT_SERVE_PORT = 39428;
|
|
5131
5150
|
var SERVE_HOST = "127.0.0.1";
|
|
@@ -5182,8 +5201,8 @@ function authorized(req, apiKey) {
|
|
|
5182
5201
|
return false;
|
|
5183
5202
|
const header = req.headers.get("authorization") ?? "";
|
|
5184
5203
|
if (header.startsWith("Bearer "))
|
|
5185
|
-
return header.slice("Bearer ".length)
|
|
5186
|
-
return req.headers.get("x-api-key")
|
|
5204
|
+
return secureEqual(header.slice("Bearer ".length), apiKey);
|
|
5205
|
+
return secureEqual(req.headers.get("x-api-key") ?? "", apiKey);
|
|
5187
5206
|
}
|
|
5188
5207
|
function json(body, status = 200, headers = {}) {
|
|
5189
5208
|
return new Response(JSON.stringify(body), {
|
package/dist/index.js
CHANGED
|
@@ -4849,14 +4849,14 @@ __export(exports_run, {
|
|
|
4849
4849
|
});
|
|
4850
4850
|
import { randomBytes } from "crypto";
|
|
4851
4851
|
import { rmSync as rmSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
4852
|
-
import { basename as basename2, dirname as
|
|
4852
|
+
import { basename as basename2, dirname as dirname4, isAbsolute as isAbsolute2, join as join9 } from "path";
|
|
4853
4853
|
function extensionOf(scriptPath) {
|
|
4854
4854
|
const base = basename2(scriptPath);
|
|
4855
4855
|
const dot = base.lastIndexOf(".");
|
|
4856
4856
|
return dot > 0 ? base.slice(dot + 1) : "ts";
|
|
4857
4857
|
}
|
|
4858
4858
|
async function executeVerifiedScript(options) {
|
|
4859
|
-
const scriptDir =
|
|
4859
|
+
const scriptDir = dirname4(options.scriptPath);
|
|
4860
4860
|
const tempPath = join9(scriptDir, `.hooks-verified-${randomBytes(12).toString("hex")}.${extensionOf(options.scriptPath)}`);
|
|
4861
4861
|
try {
|
|
4862
4862
|
writeFileSync6(tempPath, options.content, { flag: "wx", mode: 384 });
|
|
@@ -5813,15 +5813,21 @@ function getHook(name) {
|
|
|
5813
5813
|
}
|
|
5814
5814
|
// src/lib/installer.ts
|
|
5815
5815
|
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
5816
|
-
import { join as join6, dirname as
|
|
5816
|
+
import { join as join6, dirname as dirname3 } from "path";
|
|
5817
5817
|
import { homedir as homedir4 } from "os";
|
|
5818
|
-
import { fileURLToPath } from "url";
|
|
5818
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5819
5819
|
|
|
5820
5820
|
// src/lib/resolve.ts
|
|
5821
|
-
import { join as join5 } from "path";
|
|
5821
|
+
import { dirname as dirname2, join as join5 } from "path";
|
|
5822
5822
|
import { existsSync as existsSync5 } from "fs";
|
|
5823
|
+
import { fileURLToPath } from "url";
|
|
5823
5824
|
init_manifest();
|
|
5824
|
-
|
|
5825
|
+
function resolveBundledHooksDir(moduleDir = dirname2(fileURLToPath(import.meta.url))) {
|
|
5826
|
+
const sourceLayout = join5(moduleDir, "..", "..", "hooks");
|
|
5827
|
+
if (existsSync5(join5(sourceLayout, "hook-gitguard")))
|
|
5828
|
+
return sourceLayout;
|
|
5829
|
+
return join5(moduleDir, "..", "hooks");
|
|
5830
|
+
}
|
|
5825
5831
|
function resolveHookDir(name) {
|
|
5826
5832
|
const custom2 = readCustomManifest(name);
|
|
5827
5833
|
if (custom2)
|
|
@@ -5830,7 +5836,7 @@ function resolveHookDir(name) {
|
|
|
5830
5836
|
if (!meta)
|
|
5831
5837
|
return;
|
|
5832
5838
|
const short = shortManifestName(name);
|
|
5833
|
-
const bundledBase =
|
|
5839
|
+
const bundledBase = resolveBundledHooksDir();
|
|
5834
5840
|
const direct = join5(bundledBase, short);
|
|
5835
5841
|
if (existsSync5(direct))
|
|
5836
5842
|
return direct;
|
|
@@ -5895,7 +5901,7 @@ function resolveHook(name) {
|
|
|
5895
5901
|
}
|
|
5896
5902
|
|
|
5897
5903
|
// src/lib/installer.ts
|
|
5898
|
-
var __dirname2 =
|
|
5904
|
+
var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
|
|
5899
5905
|
var HOOKS_DIR = existsSync6(join6(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join6(__dirname2, "..", "..", "hooks") : join6(__dirname2, "..", "hooks");
|
|
5900
5906
|
function normalizeHookName(name) {
|
|
5901
5907
|
return name.startsWith("hook-") ? name : `hook-${name}`;
|
|
@@ -5993,7 +5999,7 @@ function readSettings(scope = "global", target = "claude") {
|
|
|
5993
5999
|
}
|
|
5994
6000
|
function writeSettings(settings, scope = "global", target = "claude") {
|
|
5995
6001
|
const path = getSettingsPath(scope, target);
|
|
5996
|
-
const dir =
|
|
6002
|
+
const dir = dirname3(path);
|
|
5997
6003
|
if (!existsSync6(dir)) {
|
|
5998
6004
|
mkdirSync4(dir, { recursive: true });
|
|
5999
6005
|
}
|
|
@@ -6090,7 +6096,7 @@ function readCodewithConfig(scope, configPath) {
|
|
|
6090
6096
|
}
|
|
6091
6097
|
function appendCodewithFragment(fragment, scope, configPath) {
|
|
6092
6098
|
const path = getSettingsPath(scope, "codewith", configPath);
|
|
6093
|
-
const dir =
|
|
6099
|
+
const dir = dirname3(path);
|
|
6094
6100
|
if (!existsSync6(dir))
|
|
6095
6101
|
mkdirSync4(dir, { recursive: true });
|
|
6096
6102
|
const existing = existsSync6(path) ? readFileSync4(path, "utf-8") : "";
|
|
@@ -6476,10 +6482,17 @@ function collectBundledCatalog() {
|
|
|
6476
6482
|
return out;
|
|
6477
6483
|
}
|
|
6478
6484
|
async function fetchJson(base, path) {
|
|
6485
|
+
const headers = { accept: "application/json" };
|
|
6486
|
+
const apiKey = resolveApiKey();
|
|
6487
|
+
if (apiKey)
|
|
6488
|
+
headers["x-api-key"] = apiKey;
|
|
6479
6489
|
const res = await fetch(`${base}${path}`, {
|
|
6480
|
-
headers
|
|
6490
|
+
headers,
|
|
6481
6491
|
signal: AbortSignal.timeout(15000)
|
|
6482
6492
|
});
|
|
6493
|
+
if (res.status === 401) {
|
|
6494
|
+
throw new Error("registry requires API key \u2014 set HASNA_HOOKS_API_KEY or HOOKS_API_KEY (vault-delivered, never stored)");
|
|
6495
|
+
}
|
|
6483
6496
|
if (!res.ok) {
|
|
6484
6497
|
throw new Error(`GET ${path} failed with status ${res.status}`);
|
|
6485
6498
|
}
|
|
@@ -6594,6 +6607,21 @@ async function syncHooks(options = {}) {
|
|
|
6594
6607
|
init_manifest();
|
|
6595
6608
|
init_store();
|
|
6596
6609
|
init_config();
|
|
6610
|
+
|
|
6611
|
+
// src/lib/secure-compare.ts
|
|
6612
|
+
function secureEqual(a, b) {
|
|
6613
|
+
const enc = new TextEncoder;
|
|
6614
|
+
const ab = enc.encode(a);
|
|
6615
|
+
const bb = enc.encode(b);
|
|
6616
|
+
let diff = ab.length ^ bb.length;
|
|
6617
|
+
const n = Math.max(ab.length, bb.length);
|
|
6618
|
+
for (let i = 0;i < n; i++) {
|
|
6619
|
+
diff |= (ab[i] ?? 0) ^ (bb[i] ?? 0);
|
|
6620
|
+
}
|
|
6621
|
+
return diff === 0;
|
|
6622
|
+
}
|
|
6623
|
+
|
|
6624
|
+
// src/serve.ts
|
|
6597
6625
|
var DEFAULT_SERVE_PORT = 39428;
|
|
6598
6626
|
var SERVE_HOST = "127.0.0.1";
|
|
6599
6627
|
var SERVE_SERVICE_NAME = "hooks-registry";
|
|
@@ -6649,8 +6677,8 @@ function authorized(req, apiKey) {
|
|
|
6649
6677
|
return false;
|
|
6650
6678
|
const header = req.headers.get("authorization") ?? "";
|
|
6651
6679
|
if (header.startsWith("Bearer "))
|
|
6652
|
-
return header.slice("Bearer ".length)
|
|
6653
|
-
return req.headers.get("x-api-key")
|
|
6680
|
+
return secureEqual(header.slice("Bearer ".length), apiKey);
|
|
6681
|
+
return secureEqual(req.headers.get("x-api-key") ?? "", apiKey);
|
|
6654
6682
|
}
|
|
6655
6683
|
function json(body, status = 200, headers = {}) {
|
|
6656
6684
|
return new Response(JSON.stringify(body), {
|
package/dist/lib/resolve.d.ts
CHANGED
|
@@ -13,6 +13,14 @@ export interface ResolvedHook {
|
|
|
13
13
|
scriptPath: string;
|
|
14
14
|
meta: HookMeta;
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Locate the bundled hooks directory at runtime from the executing module's
|
|
18
|
+
* own location. A bare `__dirname` is baked to the builder's path at bundle
|
|
19
|
+
* time, so bundled hooks resolve only on the machine that built the package
|
|
20
|
+
* (0.6.0/0.6.1 regression); `import.meta.url` is preserved at runtime by the
|
|
21
|
+
* bun build target, so resolution follows the installed package instead.
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveBundledHooksDir(moduleDir?: string): string;
|
|
16
24
|
export declare function resolveHookDir(name: string): string | undefined;
|
|
17
25
|
export declare function resolveScriptPath(name: string): string | undefined;
|
|
18
26
|
export declare function resolveHookMeta(name: string): HookMeta | undefined;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constant-time string comparison for API-key checks.
|
|
3
|
+
*
|
|
4
|
+
* The loop always runs over the longer input and the result accumulates
|
|
5
|
+
* XORs, so the timing does not reveal where or whether the values differ.
|
|
6
|
+
* Available on both the bun/node and workerd runtimes.
|
|
7
|
+
*/
|
|
8
|
+
export declare function secureEqual(a: string, b: string): boolean;
|
package/package.json
CHANGED