@hasna/hooks 0.9.4 → 0.9.6
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/README.md +40 -2
- package/bin/hooks-mcp.js +330 -149
- package/bin/index.js +974 -350
- package/bin/native-safety-entry.js +1136 -0
- package/bin/serve.js +559 -165
- package/dist/index.d.ts +1 -1
- package/dist/index.js +902 -290
- package/dist/lib/native-safety.d.ts +15 -0
- package/dist/lib/resolver-types.d.ts +1 -1
- package/dist/lib/sync.d.ts +3 -2
- package/dist/lib/transport.d.ts +5 -1
- package/dist/openapi.d.ts +314 -45
- package/dist/sdk/authority.d.ts +31 -0
- package/dist/sdk/generated.d.ts +97 -0
- package/dist/sdk/index.d.ts +16 -17
- package/dist/sdk/index.js +447 -47
- package/dist/sdk/registry-client.d.ts +75 -0
- package/hooks/native-safety-entry.ts +28 -0
- package/package.json +8 -5
- package/scripts/generate-sdk.ts +60 -0
- package/scripts/validate-package.ts +104 -2
- package/scripts/verify-generated-artifacts.ts +237 -0
package/bin/serve.js
CHANGED
|
@@ -174,6 +174,7 @@ var HOOKS = [
|
|
|
174
174
|
},
|
|
175
175
|
{
|
|
176
176
|
name: "workspace-repos-guard",
|
|
177
|
+
timeoutSeconds: 10,
|
|
177
178
|
displayName: "Workspace Repos Guard",
|
|
178
179
|
description: "Blocks writes and deletes that break the protected repo-checkout structure (~/.hasna/repos/clones canonical, plus legacy ~/workspace/repos during the migration window): writes to a protected root, top-level entry creation, non-org folders, and any delete under a protected root",
|
|
179
180
|
version: "0.1.0",
|
|
@@ -192,7 +193,7 @@ var HOOKS = [
|
|
|
192
193
|
matcher: "^(Bash|apply_patch|ApplyPatch|functions\\.apply_patch)$",
|
|
193
194
|
tags: ["rm", "delete", "trash", "recoverable", "guard", "safety", "bash"],
|
|
194
195
|
rewritesInput: true,
|
|
195
|
-
timeoutSeconds:
|
|
196
|
+
timeoutSeconds: 10
|
|
196
197
|
},
|
|
197
198
|
{
|
|
198
199
|
name: "checktests",
|
|
@@ -5358,16 +5359,21 @@ function resolveHook(name) {
|
|
|
5358
5359
|
|
|
5359
5360
|
// src/serve.ts
|
|
5360
5361
|
import { readFileSync as readFileSync5 } from "fs";
|
|
5361
|
-
import { dirname as
|
|
5362
|
-
import { fileURLToPath as
|
|
5362
|
+
import { dirname as dirname4, join as join8 } from "path";
|
|
5363
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
5363
5364
|
|
|
5364
|
-
//
|
|
5365
|
+
// ../contracts/dist/client/transport.js
|
|
5365
5366
|
import { spawnSync } from "child_process";
|
|
5366
|
-
import { closeSync, fstatSync, openSync, readFileSync as
|
|
5367
|
-
import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
|
|
5368
|
-
import { createRequire } from "module";
|
|
5367
|
+
import { closeSync as closeSync2, fstatSync as fstatSync2, openSync as openSync2, readFileSync as readFileSync22 } from "fs";
|
|
5368
|
+
import { O_NOFOLLOW as O_NOFOLLOW2, O_NONBLOCK as O_NONBLOCK2, O_RDONLY as O_RDONLY2 } from "constants";
|
|
5369
5369
|
import { hostname as osHostname } from "os";
|
|
5370
|
-
import { isAbsolute as isAbsolute2, join as
|
|
5370
|
+
import { isAbsolute as isAbsolute2, join as join22 } from "path";
|
|
5371
|
+
import { createRequire } from "module";
|
|
5372
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5373
|
+
import { closeSync, fstatSync, lstatSync, openSync, readFileSync as readFileSync4, realpathSync as realpathSync2, statSync } from "fs";
|
|
5374
|
+
import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
|
|
5375
|
+
import { basename as basename2, dirname as dirname3, join as join7, relative, sep as sep2 } from "path";
|
|
5376
|
+
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "url";
|
|
5371
5377
|
function envToken(name) {
|
|
5372
5378
|
return name.toUpperCase().replace(/-/g, "_");
|
|
5373
5379
|
}
|
|
@@ -5385,6 +5391,114 @@ var CREDENTIAL_PROFILE_ENV_KEY = "HASNA_PROFILE";
|
|
|
5385
5391
|
function credentialPointerEnvKey(name) {
|
|
5386
5392
|
return `HASNA_${envToken(name)}_API_KEY_REF`;
|
|
5387
5393
|
}
|
|
5394
|
+
function e(e2, n, r) {
|
|
5395
|
+
throw new Error(r ? `No known conditions for "${n}" specifier in "${e2}" package` : `Missing "${n}" specifier in "${e2}" package`);
|
|
5396
|
+
}
|
|
5397
|
+
function n(n2, i, o, f) {
|
|
5398
|
+
let s, u, l = r(n2, o), c = function(e2) {
|
|
5399
|
+
let n3 = new Set(["default", ...e2.conditions || []]);
|
|
5400
|
+
return e2.unsafe || n3.add(e2.require ? "require" : "import"), e2.unsafe || n3.add(e2.browser ? "browser" : "node"), n3;
|
|
5401
|
+
}(f || {}), a = i[l];
|
|
5402
|
+
if (a === undefined) {
|
|
5403
|
+
let e2, n3, r, t;
|
|
5404
|
+
for (t in i)
|
|
5405
|
+
n3 && t.length < n3.length || (t[t.length - 1] === "/" && l.startsWith(t) ? (u = l.substring(t.length), n3 = t) : t.length > 1 && (r = t.indexOf("*", 1), ~r && (e2 = RegExp("^" + t.substring(0, r) + "(.*)" + t.substring(1 + r) + "$").exec(l), e2 && e2[1] && (u = e2[1], n3 = t))));
|
|
5406
|
+
a = i[n3];
|
|
5407
|
+
}
|
|
5408
|
+
return a || e(n2, l), s = t(a, c), s || e(n2, l, 1), u && function(e2, n3) {
|
|
5409
|
+
let r, t = 0, i2 = e2.length, o2 = /[*]/g, f2 = /[/]$/;
|
|
5410
|
+
for (;t < i2; t++)
|
|
5411
|
+
e2[t] = o2.test(r = e2[t]) ? r.replace(o2, n3) : f2.test(r) ? r + n3 : r;
|
|
5412
|
+
}(s, u), s;
|
|
5413
|
+
}
|
|
5414
|
+
function r(e2, n2, r2) {
|
|
5415
|
+
if (e2 === n2 || n2 === ".")
|
|
5416
|
+
return ".";
|
|
5417
|
+
let t = e2 + "/", i = t.length, o = n2.slice(0, i) === t, f = o ? n2.slice(i) : n2;
|
|
5418
|
+
return f[0] === "#" ? f : o || !r2 ? f.slice(0, 2) === "./" ? f : "./" + f : f;
|
|
5419
|
+
}
|
|
5420
|
+
function t(e2, n2, r2) {
|
|
5421
|
+
if (e2) {
|
|
5422
|
+
if (typeof e2 == "string")
|
|
5423
|
+
return r2 && r2.add(e2), [e2];
|
|
5424
|
+
let i, o;
|
|
5425
|
+
if (Array.isArray(e2)) {
|
|
5426
|
+
for (o = r2 || new Set, i = 0;i < e2.length; i++)
|
|
5427
|
+
t(e2[i], n2, o);
|
|
5428
|
+
if (!r2 && o.size)
|
|
5429
|
+
return [...o];
|
|
5430
|
+
} else
|
|
5431
|
+
for (i in e2)
|
|
5432
|
+
if (n2.has(i))
|
|
5433
|
+
return t(e2[i], n2, r2);
|
|
5434
|
+
}
|
|
5435
|
+
}
|
|
5436
|
+
function o(e2, r2, t2) {
|
|
5437
|
+
let i, o2 = e2.exports;
|
|
5438
|
+
if (o2) {
|
|
5439
|
+
if (typeof o2 == "string")
|
|
5440
|
+
o2 = { ".": o2 };
|
|
5441
|
+
else
|
|
5442
|
+
for (i in o2) {
|
|
5443
|
+
i[0] !== "." && (o2 = { ".": o2 });
|
|
5444
|
+
break;
|
|
5445
|
+
}
|
|
5446
|
+
return n(e2.name, o2, r2 || ".", t2);
|
|
5447
|
+
}
|
|
5448
|
+
}
|
|
5449
|
+
var PACKAGE = "@hasna/secrets";
|
|
5450
|
+
var MAX_PACKAGE_BYTES = 1024 * 1024;
|
|
5451
|
+
function entryPoint(directory) {
|
|
5452
|
+
const root = realpathSync2(directory);
|
|
5453
|
+
const fd = openSync(join7(root, "package.json"), O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
|
|
5454
|
+
let pkg;
|
|
5455
|
+
try {
|
|
5456
|
+
const stat = fstatSync(fd);
|
|
5457
|
+
if (!stat.isFile() || stat.size > MAX_PACKAGE_BYTES)
|
|
5458
|
+
throw new Error("Invalid Secrets package metadata");
|
|
5459
|
+
pkg = JSON.parse(readFileSync4(fd, "utf8"));
|
|
5460
|
+
} finally {
|
|
5461
|
+
closeSync(fd);
|
|
5462
|
+
}
|
|
5463
|
+
if (!pkg || typeof pkg !== "object" || !("name" in pkg) || pkg.name !== PACKAGE) {
|
|
5464
|
+
throw new Error("Invalid Secrets package identity");
|
|
5465
|
+
}
|
|
5466
|
+
const target = o(pkg, ".")?.[0];
|
|
5467
|
+
if (typeof target !== "string" || !target.startsWith("./"))
|
|
5468
|
+
throw new Error("Missing Secrets import export");
|
|
5469
|
+
const parts = target.slice(2).split("/");
|
|
5470
|
+
if (/[\\%?#\0]/.test(target) || parts.some((part) => !part || part === "." || part === ".." || part === "node_modules")) {
|
|
5471
|
+
throw new Error("Invalid Secrets import export");
|
|
5472
|
+
}
|
|
5473
|
+
const file = realpathSync2(join7(root, ...parts));
|
|
5474
|
+
const within = relative(root, file);
|
|
5475
|
+
if (!within || within === ".." || within.startsWith(`..${sep2}`) || !statSync(file).isFile()) {
|
|
5476
|
+
throw new Error("Secrets import export escapes its package or is not a file");
|
|
5477
|
+
}
|
|
5478
|
+
return pathToFileURL(file).href;
|
|
5479
|
+
}
|
|
5480
|
+
function resolveInstalledSecrets(parentUrl) {
|
|
5481
|
+
let directory = dirname3(fileURLToPath3(parentUrl));
|
|
5482
|
+
for (;; ) {
|
|
5483
|
+
if (basename2(directory) !== "node_modules") {
|
|
5484
|
+
const candidate = join7(directory, "node_modules", PACKAGE);
|
|
5485
|
+
let absent = false;
|
|
5486
|
+
try {
|
|
5487
|
+
lstatSync(candidate);
|
|
5488
|
+
} catch (error) {
|
|
5489
|
+
if (error.code !== "ENOENT")
|
|
5490
|
+
throw error;
|
|
5491
|
+
absent = true;
|
|
5492
|
+
}
|
|
5493
|
+
if (!absent)
|
|
5494
|
+
return entryPoint(candidate);
|
|
5495
|
+
}
|
|
5496
|
+
const parent = dirname3(directory);
|
|
5497
|
+
if (parent === directory)
|
|
5498
|
+
throw new Error("Secrets SDK is not installed for this consumer");
|
|
5499
|
+
directory = parent;
|
|
5500
|
+
}
|
|
5501
|
+
}
|
|
5388
5502
|
|
|
5389
5503
|
class CredentialResolutionError extends Error {
|
|
5390
5504
|
appName;
|
|
@@ -5433,14 +5547,14 @@ function hasnaHomeDir(env) {
|
|
|
5433
5547
|
if (override)
|
|
5434
5548
|
return override;
|
|
5435
5549
|
const home = homeDir(env);
|
|
5436
|
-
return home ?
|
|
5550
|
+
return home ? join22(home, HASNA_HOME_DIR) : null;
|
|
5437
5551
|
}
|
|
5438
5552
|
function appConfigDir(name, env) {
|
|
5439
5553
|
const configRoot = absoluteOverride(env, HASNA_CONFIG_HOME_ENV_KEY);
|
|
5440
5554
|
if (configRoot)
|
|
5441
|
-
return
|
|
5555
|
+
return join22(configRoot, name);
|
|
5442
5556
|
const root = hasnaHomeDir(env);
|
|
5443
|
-
return root ?
|
|
5557
|
+
return root ? join22(root, name, CONFIG_SUBDIR) : null;
|
|
5444
5558
|
}
|
|
5445
5559
|
function credentialDiskSourceList(name, env, profile = null) {
|
|
5446
5560
|
if (!SAFE_APP_SLUG.test(name))
|
|
@@ -5449,7 +5563,7 @@ function credentialDiskSourceList(name, env, profile = null) {
|
|
|
5449
5563
|
if (!directory)
|
|
5450
5564
|
return [];
|
|
5451
5565
|
const file = profile ? `${CREDENTIALS_FILE}-${profile}` : CREDENTIALS_FILE;
|
|
5452
|
-
return [{ path:
|
|
5566
|
+
return [{ path: join22(directory, file), tier: "disk" }];
|
|
5453
5567
|
}
|
|
5454
5568
|
function credentialDiskSources(name, env) {
|
|
5455
5569
|
return credentialDiskSourceList(name, env, null).map((s) => s.path);
|
|
@@ -5503,7 +5617,7 @@ function readAppConfigFile(path) {
|
|
|
5503
5617
|
};
|
|
5504
5618
|
let fd = -1;
|
|
5505
5619
|
try {
|
|
5506
|
-
fd =
|
|
5620
|
+
fd = openSync2(path, O_RDONLY2 | O_NOFOLLOW2 | O_NONBLOCK2);
|
|
5507
5621
|
} catch (error) {
|
|
5508
5622
|
const code = error.code;
|
|
5509
5623
|
if (code === "ENOENT" || code === "ENOTDIR")
|
|
@@ -5513,7 +5627,7 @@ function readAppConfigFile(path) {
|
|
|
5513
5627
|
unsafe(`the path could not be opened (${code ?? "unknown error"})`);
|
|
5514
5628
|
}
|
|
5515
5629
|
try {
|
|
5516
|
-
const before =
|
|
5630
|
+
const before = fstatSync2(fd);
|
|
5517
5631
|
if (!before.isFile())
|
|
5518
5632
|
unsafe("the path is not a regular file");
|
|
5519
5633
|
if (!configFileModeAllowed(before.mode)) {
|
|
@@ -5524,31 +5638,39 @@ function readAppConfigFile(path) {
|
|
|
5524
5638
|
unsafe("the file is not owned by the current user");
|
|
5525
5639
|
if (before.size > MAX_CREDENTIAL_FILE_BYTES)
|
|
5526
5640
|
unsafe("the file exceeds the size limit");
|
|
5527
|
-
const bytes =
|
|
5528
|
-
const after =
|
|
5641
|
+
const bytes = readFileSync22(fd);
|
|
5642
|
+
const after = fstatSync2(fd);
|
|
5529
5643
|
if (!configFileReadsCoherent(before, after)) {
|
|
5530
5644
|
unsafe("the file changed while being read");
|
|
5531
5645
|
}
|
|
5532
5646
|
return parseEnvFile(bytes.toString("utf8"));
|
|
5533
5647
|
} finally {
|
|
5534
5648
|
if (fd !== -1)
|
|
5535
|
-
|
|
5649
|
+
closeSync2(fd);
|
|
5536
5650
|
}
|
|
5537
5651
|
}
|
|
5538
|
-
function readCredentialFile(path, apiKeyKeys) {
|
|
5652
|
+
function readCredentialFile(path, apiKeyKeys, pointerKey) {
|
|
5539
5653
|
const parsed = readAppConfigFile(path);
|
|
5540
5654
|
if (!parsed)
|
|
5541
5655
|
return null;
|
|
5542
|
-
for (const key of apiKeyKeys) {
|
|
5656
|
+
for (const key of [...apiKeyKeys, pointerKey]) {
|
|
5543
5657
|
if (parsed.unusable.has(key)) {
|
|
5544
5658
|
throw new CredentialFileUnsafeError(path, `${key} is declared but blank or malformed`);
|
|
5545
5659
|
}
|
|
5546
5660
|
}
|
|
5547
5661
|
const values = apiKeyKeys.map((key) => parsed.values.get(key)?.trim()).filter((value) => Boolean(value));
|
|
5662
|
+
const pointer = parsed.values.get(pointerKey)?.trim();
|
|
5663
|
+
if (pointer !== undefined) {
|
|
5664
|
+
if (!VAULT_POINTER_SHAPE.test(pointer))
|
|
5665
|
+
throw new CredentialFileUnsafeError(path, `${pointerKey} must name a vault item`);
|
|
5666
|
+
if (values.length)
|
|
5667
|
+
throw new CredentialFileUnsafeError(path, "a credential file cannot select both a literal key and a vault reference");
|
|
5668
|
+
return { apiKey: "", pointerVaultKey: pointer };
|
|
5669
|
+
}
|
|
5548
5670
|
if (new Set(values).size > 1) {
|
|
5549
5671
|
throw new CredentialFileUnsafeError(path, "credential aliases disagree");
|
|
5550
5672
|
}
|
|
5551
|
-
return values[0]
|
|
5673
|
+
return values[0] === undefined ? null : { apiKey: values[0] };
|
|
5552
5674
|
}
|
|
5553
5675
|
function assertUsableCredential(appName, source, value) {
|
|
5554
5676
|
if (VAULT_POINTER_SHAPE.test(value)) {
|
|
@@ -5781,12 +5903,13 @@ function resolveCredential(name, env, options = {}) {
|
|
|
5781
5903
|
}
|
|
5782
5904
|
const paths = profileDiskSources(name, env, profile);
|
|
5783
5905
|
for (const path of paths) {
|
|
5784
|
-
const value = readCredentialFile(path, apiKeyKeys);
|
|
5906
|
+
const value = readCredentialFile(path, apiKeyKeys, pointerKeyName);
|
|
5785
5907
|
if (value) {
|
|
5786
|
-
|
|
5908
|
+
if (!value.pointerVaultKey)
|
|
5909
|
+
assertUsableCredential(name, path, value.apiKey);
|
|
5787
5910
|
return sealCredential({
|
|
5788
|
-
|
|
5789
|
-
tier: "profile",
|
|
5911
|
+
...value,
|
|
5912
|
+
tier: value.pointerVaultKey ? "pointer" : "profile",
|
|
5790
5913
|
source: path,
|
|
5791
5914
|
deliberate: true,
|
|
5792
5915
|
diskCandidates: paths,
|
|
@@ -5794,7 +5917,7 @@ function resolveCredential(name, env, options = {}) {
|
|
|
5794
5917
|
});
|
|
5795
5918
|
}
|
|
5796
5919
|
}
|
|
5797
|
-
throw new CredentialResolutionError(name, `Profile '${profile}' (from ${profileSource}) has no ${apiKeyKeys[0]} for '${name}'. ` + `Looked in: ${paths.join(", ") || "<no HOME in this environment>"}. ` + `A profile names WHICH identity to use, so it is never resolved around \u2014 ` + `create the profile's credential file or unset ${CREDENTIAL_PROFILE_ENV_KEY}.`, paths);
|
|
5920
|
+
throw new CredentialResolutionError(name, `Profile '${profile}' (from ${profileSource}) has no ${apiKeyKeys[0]} or ${pointerKeyName} for '${name}'. ` + `Looked in: ${paths.join(", ") || "<no HOME in this environment>"}. ` + `A profile names WHICH identity to use, so it is never resolved around \u2014 ` + `create the profile's credential file or unset ${CREDENTIAL_PROFILE_ENV_KEY}.`, paths);
|
|
5798
5921
|
}
|
|
5799
5922
|
const definedEnvEntries = apiKeyKeys.filter((key) => Object.prototype.hasOwnProperty.call(env, key) && env[key] !== undefined).map((key) => ({ key, value: String(env[key]).trim() }));
|
|
5800
5923
|
const blankEnv = definedEnvEntries.find((entry) => entry.value.length === 0);
|
|
@@ -5819,17 +5942,27 @@ function resolveCredential(name, env, options = {}) {
|
|
|
5819
5942
|
});
|
|
5820
5943
|
}
|
|
5821
5944
|
const diskSourceList = credentialDiskSourceList(name, env, null);
|
|
5822
|
-
const diskHits = diskSourceList.map((src) => ({ src, value: readCredentialFile(src.path, apiKeyKeys) })).filter((hit) => hit.value !== null);
|
|
5945
|
+
const diskHits = diskSourceList.map((src) => ({ src, value: readCredentialFile(src.path, apiKeyKeys, pointerKeyName) })).filter((hit) => hit.value !== null);
|
|
5823
5946
|
if (diskHits.length > 0) {
|
|
5824
5947
|
const winner = diskHits[0];
|
|
5825
|
-
|
|
5948
|
+
if (winner.value.pointerVaultKey) {
|
|
5949
|
+
return sealCredential({
|
|
5950
|
+
...winner.value,
|
|
5951
|
+
tier: "pointer",
|
|
5952
|
+
source: winner.src.path,
|
|
5953
|
+
deliberate: false,
|
|
5954
|
+
diskCandidates: diskPaths,
|
|
5955
|
+
warning: null
|
|
5956
|
+
});
|
|
5957
|
+
}
|
|
5958
|
+
assertUsableCredential(name, winner.src.path, winner.value.apiKey);
|
|
5826
5959
|
const divergentSources = [
|
|
5827
|
-
...diskHits.slice(1).filter((hit) => hit.value !== winner.value).map((hit) => hit.src.path),
|
|
5828
|
-
...envHit && envHit.value !== winner.value ? [envHit.key] : []
|
|
5960
|
+
...diskHits.slice(1).filter((hit) => hit.value.apiKey !== winner.value.apiKey || hit.value.pointerVaultKey !== winner.value.pointerVaultKey).map((hit) => hit.src.path),
|
|
5961
|
+
...envHit && envHit.value !== winner.value.apiKey ? [envHit.key] : []
|
|
5829
5962
|
];
|
|
5830
5963
|
const warning = divergentSources.length > 0 ? `Credential sources disagree for '${name}': ${winner.src.path} and ` + `${divergentSources.join(", ")} hold different keys. ${winner.src.path} wins, because a file on ` + `disk is re-read on every call while an environment variable is a snapshot. Reconcile them \u2014 ` + `a rotation that updated only one leaves the other to fail 401 wherever it is loaded first.` : null;
|
|
5831
5964
|
return sealCredential({
|
|
5832
|
-
apiKey: winner.value,
|
|
5965
|
+
apiKey: winner.value.apiKey,
|
|
5833
5966
|
tier: winner.src.tier,
|
|
5834
5967
|
source: winner.src.path,
|
|
5835
5968
|
deliberate: false,
|
|
@@ -5850,8 +5983,54 @@ function resolveCredential(name, env, options = {}) {
|
|
|
5850
5983
|
}
|
|
5851
5984
|
return null;
|
|
5852
5985
|
}
|
|
5853
|
-
var SECRETS_PACKAGE_SPECIFIER = "@hasna/" + "secrets";
|
|
5854
5986
|
var requireSecretsSdk = createRequire(import.meta.url);
|
|
5987
|
+
async function completePointerCredential(name, pointerResolution, env = process.env) {
|
|
5988
|
+
const secretsEnv = snapshotClientEnvironment("secrets", env);
|
|
5989
|
+
const vaultKey = pointerResolution.pointerVaultKey;
|
|
5990
|
+
const pointerEnvKey = pointerResolution.source;
|
|
5991
|
+
if (!vaultKey) {
|
|
5992
|
+
throw new CredentialResolutionError(name, `Pointer resolution from ${pointerEnvKey} carries no vault item key; this is a defect in the resolver.`, [pointerEnvKey]);
|
|
5993
|
+
}
|
|
5994
|
+
if (name === "secrets") {
|
|
5995
|
+
throw new CredentialResolutionError(name, "The Secrets bootstrap credential cannot reference the same hosted vault; configure an independent bootstrap provider.", [pointerEnvKey]);
|
|
5996
|
+
}
|
|
5997
|
+
let secretsSdk;
|
|
5998
|
+
try {
|
|
5999
|
+
const sdkUrl = resolveInstalledSecrets(import.meta.url);
|
|
6000
|
+
secretsSdk = requireSecretsSdk(fileURLToPath2(sdkUrl));
|
|
6001
|
+
} catch {
|
|
6002
|
+
throw new CredentialResolutionError(name, `${pointerEnvKey} names vault item '${vaultKey}', but the secrets SDK (@hasna/secrets) is not installed ` + `in this process. A vault pointer is TERMINAL: install @hasna/secrets to resolve it, or unset ${pointerEnvKey}.`, [pointerEnvKey]);
|
|
6003
|
+
}
|
|
6004
|
+
let client;
|
|
6005
|
+
try {
|
|
6006
|
+
const bootstrap = resolveCredential("secrets", secretsEnv);
|
|
6007
|
+
if (!bootstrap || bootstrap.tier === "pointer") {
|
|
6008
|
+
throw new CredentialResolutionError("secrets", "The Secrets vault requires an independent, non-reference bootstrap credential.", bootstrap ? [bootstrap.source] : []);
|
|
6009
|
+
}
|
|
6010
|
+
client = secretsSdk.createSecretsClientFromEnv(secretsEnv);
|
|
6011
|
+
} catch {
|
|
6012
|
+
throw new CredentialResolutionError(name, `${pointerEnvKey} names vault item '${vaultKey}', but the secrets client could not be configured from this ` + `environment (the secrets service URL and key env are missing or invalid). A vault pointer is TERMINAL and ` + `never falls through to a literal or disk credential.`, [pointerEnvKey]);
|
|
6013
|
+
}
|
|
6014
|
+
let secret;
|
|
6015
|
+
try {
|
|
6016
|
+
secret = await client.getSecret({ key: vaultKey });
|
|
6017
|
+
} catch {
|
|
6018
|
+
throw new CredentialResolutionError(name, `${pointerEnvKey} names vault item '${vaultKey}', but the vault could not be reached or the item is ` + `unavailable. A vault pointer is TERMINAL and never falls through to a literal or disk credential.`, [pointerEnvKey]);
|
|
6019
|
+
}
|
|
6020
|
+
const value = secret.value;
|
|
6021
|
+
if (!value) {
|
|
6022
|
+
throw new CredentialResolutionError(name, `${pointerEnvKey} resolved vault item '${vaultKey}', but it holds no value. A vault pointer is TERMINAL.`, [pointerEnvKey]);
|
|
6023
|
+
}
|
|
6024
|
+
assertUsableCredential(name, `${pointerEnvKey} -> vault:${vaultKey}`, value);
|
|
6025
|
+
return sealCredential({
|
|
6026
|
+
apiKey: value,
|
|
6027
|
+
tier: "pointer",
|
|
6028
|
+
source: `${pointerEnvKey} -> vault:${vaultKey}`,
|
|
6029
|
+
deliberate: pointerResolution.deliberate,
|
|
6030
|
+
diskCandidates: pointerResolution.diskCandidates,
|
|
6031
|
+
warning: null
|
|
6032
|
+
});
|
|
6033
|
+
}
|
|
5855
6034
|
var IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
|
|
5856
6035
|
var AUTHORITY_OVERRIDE_HEADERS = new Set([
|
|
5857
6036
|
"host",
|
|
@@ -5899,11 +6078,25 @@ function hooksResolverInputs(env, credentials = {}) {
|
|
|
5899
6078
|
}
|
|
5900
6079
|
|
|
5901
6080
|
// src/lib/transport.ts
|
|
5902
|
-
|
|
6081
|
+
var authorityBindings = new WeakMap;
|
|
6082
|
+
function sameCredential(left, right) {
|
|
6083
|
+
return left === null || right === null ? left === right : left.apiKey === right.apiKey && left.pointerVaultKey === right.pointerVaultKey && left.source === right.source && left.tier === right.tier;
|
|
6084
|
+
}
|
|
6085
|
+
function changedBinding() {
|
|
6086
|
+
throw new Error("REMOTE_API_CREDENTIAL_INVALID: The configured Hooks authority or credential changed while preparing a request; no request was sent.");
|
|
6087
|
+
}
|
|
6088
|
+
async function resolveHooksServePublishKeyAsync(env = process.env, options = {}) {
|
|
5903
6089
|
const { env: resolverEnv, credentials } = hooksResolverInputs(env, options.credentials);
|
|
5904
|
-
const
|
|
5905
|
-
const
|
|
5906
|
-
|
|
6090
|
+
const read = () => resolveCredential("hooks", resolverEnv, credentials);
|
|
6091
|
+
const selected = read();
|
|
6092
|
+
if (!selected || selected.tier !== "pointer")
|
|
6093
|
+
return selected?.apiKey;
|
|
6094
|
+
if (!sameCredential(selected, read()))
|
|
6095
|
+
changedBinding();
|
|
6096
|
+
const completed = await completePointerCredential("hooks", selected, env);
|
|
6097
|
+
if (!sameCredential(selected, read()))
|
|
6098
|
+
changedBinding();
|
|
6099
|
+
return completed.apiKey;
|
|
5907
6100
|
}
|
|
5908
6101
|
|
|
5909
6102
|
// src/lib/secure-compare.ts
|
|
@@ -5912,163 +6105,364 @@ function secureEqual(a, b) {
|
|
|
5912
6105
|
const ab = enc.encode(a);
|
|
5913
6106
|
const bb = enc.encode(b);
|
|
5914
6107
|
let diff = ab.length ^ bb.length;
|
|
5915
|
-
const
|
|
5916
|
-
for (let i = 0;i <
|
|
6108
|
+
const n2 = Math.max(ab.length, bb.length);
|
|
6109
|
+
for (let i = 0;i < n2; i++) {
|
|
5917
6110
|
diff |= (ab[i] ?? 0) ^ (bb[i] ?? 0);
|
|
5918
6111
|
}
|
|
5919
6112
|
return diff === 0;
|
|
5920
6113
|
}
|
|
5921
6114
|
|
|
5922
6115
|
// src/openapi.ts
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
6116
|
+
function buildOpenApiDocument(version) {
|
|
6117
|
+
return {
|
|
6118
|
+
openapi: "3.1.0",
|
|
6119
|
+
info: {
|
|
6120
|
+
title: "Hooks Registry API",
|
|
6121
|
+
version,
|
|
6122
|
+
description: "Local registry HTTP API for @hasna/hooks: catalog, lock, and artifact reads are open; publishing (PUT /api/v1/hooks) requires the API key (authorization: Bearer or x-api-key)."
|
|
6123
|
+
},
|
|
6124
|
+
servers: [{ url: "/" }],
|
|
6125
|
+
paths: {
|
|
6126
|
+
"/health": {
|
|
6127
|
+
get: {
|
|
6128
|
+
summary: "Liveness probe",
|
|
6129
|
+
operationId: "getHealth",
|
|
6130
|
+
responses: {
|
|
6131
|
+
"200": {
|
|
6132
|
+
description: "Service is alive",
|
|
6133
|
+
content: {
|
|
6134
|
+
"application/json": {
|
|
6135
|
+
schema: { $ref: "#/components/schemas/HooksHealth" }
|
|
6136
|
+
}
|
|
5942
6137
|
}
|
|
5943
6138
|
}
|
|
5944
6139
|
}
|
|
5945
6140
|
}
|
|
5946
|
-
}
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
6141
|
+
},
|
|
6142
|
+
"/ready": {
|
|
6143
|
+
get: {
|
|
6144
|
+
summary: "Readiness probe \u2014 the hooks.lock store is readable",
|
|
6145
|
+
operationId: "getReady",
|
|
6146
|
+
responses: {
|
|
6147
|
+
"200": {
|
|
6148
|
+
description: "Lock store is readable",
|
|
6149
|
+
content: {
|
|
6150
|
+
"application/json": {
|
|
6151
|
+
schema: { $ref: "#/components/schemas/HooksHealth" }
|
|
6152
|
+
}
|
|
6153
|
+
}
|
|
6154
|
+
},
|
|
6155
|
+
"503": {
|
|
6156
|
+
description: "Lock store is not readable",
|
|
6157
|
+
content: {
|
|
6158
|
+
"application/json": {
|
|
6159
|
+
schema: { $ref: "#/components/schemas/HooksHealth" }
|
|
6160
|
+
}
|
|
6161
|
+
}
|
|
6162
|
+
}
|
|
6163
|
+
}
|
|
5955
6164
|
}
|
|
5956
|
-
}
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
6165
|
+
},
|
|
6166
|
+
"/version": {
|
|
6167
|
+
get: {
|
|
6168
|
+
summary: "Package version",
|
|
6169
|
+
operationId: "getVersion",
|
|
6170
|
+
responses: {
|
|
6171
|
+
"200": {
|
|
6172
|
+
description: "The installed @hasna/hooks version",
|
|
6173
|
+
content: {
|
|
6174
|
+
"application/json": {
|
|
6175
|
+
schema: { $ref: "#/components/schemas/HooksVersion" }
|
|
6176
|
+
}
|
|
5968
6177
|
}
|
|
5969
6178
|
}
|
|
5970
6179
|
}
|
|
5971
6180
|
}
|
|
5972
|
-
}
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
summary: "List the full hook catalog with versions and sha256",
|
|
5984
|
-
operationId: "getCatalog",
|
|
5985
|
-
responses: {
|
|
5986
|
-
"200": {
|
|
5987
|
-
description: "Catalog entries",
|
|
5988
|
-
content: {
|
|
5989
|
-
"application/json": {
|
|
5990
|
-
schema: {
|
|
5991
|
-
type: "object",
|
|
5992
|
-
properties: {
|
|
5993
|
-
hooks: {
|
|
5994
|
-
type: "array",
|
|
5995
|
-
items: {
|
|
5996
|
-
type: "object",
|
|
5997
|
-
properties: {
|
|
5998
|
-
name: { type: "string" },
|
|
5999
|
-
version: { type: "string" },
|
|
6000
|
-
sha256: { type: "string" },
|
|
6001
|
-
events: { type: "array", items: { type: "string" } }
|
|
6002
|
-
}
|
|
6003
|
-
}
|
|
6004
|
-
}
|
|
6005
|
-
}
|
|
6181
|
+
},
|
|
6182
|
+
"/openapi.json": {
|
|
6183
|
+
get: {
|
|
6184
|
+
summary: "This OpenAPI document",
|
|
6185
|
+
operationId: "getOpenApi",
|
|
6186
|
+
responses: {
|
|
6187
|
+
"200": {
|
|
6188
|
+
description: "OpenAPI 3.1 document",
|
|
6189
|
+
content: {
|
|
6190
|
+
"application/json": {
|
|
6191
|
+
schema: { type: "object", additionalProperties: true }
|
|
6006
6192
|
}
|
|
6007
6193
|
}
|
|
6008
6194
|
}
|
|
6009
6195
|
}
|
|
6010
6196
|
}
|
|
6011
|
-
}
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6197
|
+
},
|
|
6198
|
+
"/api/v1/catalog": {
|
|
6199
|
+
get: {
|
|
6200
|
+
summary: "List the full hook catalog with versions and sha256",
|
|
6201
|
+
operationId: "getCatalog",
|
|
6202
|
+
responses: {
|
|
6203
|
+
"200": {
|
|
6204
|
+
description: "Catalog entries",
|
|
6205
|
+
content: {
|
|
6206
|
+
"application/json": {
|
|
6207
|
+
schema: { $ref: "#/components/schemas/HooksCatalog" }
|
|
6208
|
+
}
|
|
6209
|
+
}
|
|
6210
|
+
}
|
|
6211
|
+
}
|
|
6212
|
+
}
|
|
6213
|
+
},
|
|
6214
|
+
"/api/v1/lock": {
|
|
6215
|
+
get: {
|
|
6216
|
+
summary: "Read the published hooks.lock with per-hook versions",
|
|
6217
|
+
operationId: "getLock",
|
|
6218
|
+
responses: {
|
|
6219
|
+
"200": {
|
|
6220
|
+
description: "The lock file",
|
|
6221
|
+
content: {
|
|
6222
|
+
"application/json": {
|
|
6223
|
+
schema: { $ref: "#/components/schemas/HooksLock" }
|
|
6224
|
+
}
|
|
6225
|
+
}
|
|
6226
|
+
}
|
|
6227
|
+
}
|
|
6228
|
+
}
|
|
6229
|
+
},
|
|
6230
|
+
"/api/v1/hooks/{name}/{version}": {
|
|
6231
|
+
get: {
|
|
6232
|
+
summary: "Fetch one hook's manifest and script",
|
|
6233
|
+
operationId: "getHookArtifact",
|
|
6234
|
+
parameters: [
|
|
6235
|
+
{ name: "name", in: "path", required: true, schema: { type: "string" } },
|
|
6236
|
+
{ name: "version", in: "path", required: true, schema: { type: "string" } }
|
|
6237
|
+
],
|
|
6238
|
+
responses: {
|
|
6239
|
+
"200": {
|
|
6240
|
+
description: "Hook artifact; x-hook-sha256 header carries the script hash",
|
|
6241
|
+
headers: {
|
|
6242
|
+
"x-hook-sha256": {
|
|
6243
|
+
description: "sha256 of the served script, the same value the lock pins",
|
|
6244
|
+
schema: { type: "string" }
|
|
6245
|
+
}
|
|
6246
|
+
},
|
|
6247
|
+
content: {
|
|
6248
|
+
"application/json": {
|
|
6249
|
+
schema: { $ref: "#/components/schemas/HooksArtifact" }
|
|
6250
|
+
}
|
|
6251
|
+
}
|
|
6252
|
+
},
|
|
6253
|
+
"400": {
|
|
6254
|
+
description: "Invalid URL encoding or semver version",
|
|
6255
|
+
content: {
|
|
6256
|
+
"application/json": {
|
|
6257
|
+
schema: { $ref: "#/components/schemas/HooksError" }
|
|
6258
|
+
}
|
|
6259
|
+
}
|
|
6260
|
+
},
|
|
6261
|
+
"404": {
|
|
6262
|
+
description: "Hook not found locally",
|
|
6263
|
+
content: {
|
|
6264
|
+
"application/json": {
|
|
6265
|
+
schema: { $ref: "#/components/schemas/HooksError" }
|
|
6266
|
+
}
|
|
6267
|
+
}
|
|
6268
|
+
}
|
|
6269
|
+
}
|
|
6270
|
+
}
|
|
6271
|
+
},
|
|
6272
|
+
"/api/v1/hooks": {
|
|
6273
|
+
put: {
|
|
6274
|
+
summary: "Publish/retrust a hook (requires the API key)",
|
|
6275
|
+
operationId: "putHook",
|
|
6276
|
+
requestBody: {
|
|
6277
|
+
required: true,
|
|
6278
|
+
content: {
|
|
6279
|
+
"application/json": {
|
|
6280
|
+
schema: { $ref: "#/components/schemas/HooksPublishRequest" }
|
|
6281
|
+
}
|
|
6282
|
+
}
|
|
6031
6283
|
},
|
|
6032
|
-
|
|
6033
|
-
|
|
6284
|
+
responses: {
|
|
6285
|
+
"200": {
|
|
6286
|
+
description: "Hook retrusted and pin updated",
|
|
6287
|
+
content: {
|
|
6288
|
+
"application/json": {
|
|
6289
|
+
schema: { $ref: "#/components/schemas/HooksPublishResponse" }
|
|
6290
|
+
}
|
|
6291
|
+
}
|
|
6292
|
+
},
|
|
6293
|
+
"400": {
|
|
6294
|
+
description: "Invalid JSON body",
|
|
6295
|
+
content: {
|
|
6296
|
+
"application/json": {
|
|
6297
|
+
schema: { $ref: "#/components/schemas/HooksError" }
|
|
6298
|
+
}
|
|
6299
|
+
}
|
|
6300
|
+
},
|
|
6301
|
+
"401": {
|
|
6302
|
+
description: "Missing or invalid API key",
|
|
6303
|
+
content: {
|
|
6304
|
+
"application/json": {
|
|
6305
|
+
schema: { $ref: "#/components/schemas/HooksError" }
|
|
6306
|
+
}
|
|
6307
|
+
}
|
|
6308
|
+
},
|
|
6309
|
+
"404": {
|
|
6310
|
+
description: "Hook not found in local store",
|
|
6311
|
+
content: {
|
|
6312
|
+
"application/json": {
|
|
6313
|
+
schema: { $ref: "#/components/schemas/HooksError" }
|
|
6314
|
+
}
|
|
6315
|
+
}
|
|
6316
|
+
},
|
|
6317
|
+
"409": {
|
|
6318
|
+
description: "Version mismatch with the local store",
|
|
6319
|
+
content: {
|
|
6320
|
+
"application/json": {
|
|
6321
|
+
schema: { $ref: "#/components/schemas/HooksError" }
|
|
6322
|
+
}
|
|
6323
|
+
}
|
|
6324
|
+
}
|
|
6325
|
+
},
|
|
6326
|
+
security: [{ apiKey: [] }]
|
|
6034
6327
|
}
|
|
6035
6328
|
}
|
|
6036
6329
|
},
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6330
|
+
components: {
|
|
6331
|
+
securitySchemes: {
|
|
6332
|
+
apiKey: {
|
|
6333
|
+
type: "http",
|
|
6334
|
+
scheme: "bearer",
|
|
6335
|
+
description: "The registry API key \u2014 resolved by the client through the @hasna/contracts chain (HASNA_HOOKS_API_KEY, the Keychain item hasna.credentials.hooks.api-key, or ~/.hasna/hooks/config/credentials); the server compares the inbound bearer/x-api-key value."
|
|
6336
|
+
}
|
|
6337
|
+
},
|
|
6338
|
+
schemas: {
|
|
6339
|
+
HooksHealth: {
|
|
6340
|
+
type: "object",
|
|
6341
|
+
additionalProperties: false,
|
|
6342
|
+
required: ["status", "name"],
|
|
6343
|
+
properties: {
|
|
6344
|
+
status: { type: "string" },
|
|
6345
|
+
name: { type: "string" }
|
|
6346
|
+
}
|
|
6347
|
+
},
|
|
6348
|
+
HooksVersion: {
|
|
6349
|
+
type: "object",
|
|
6350
|
+
additionalProperties: false,
|
|
6351
|
+
required: ["version"],
|
|
6352
|
+
properties: { version: { type: "string" } }
|
|
6353
|
+
},
|
|
6354
|
+
HooksCatalogEntry: {
|
|
6355
|
+
type: "object",
|
|
6356
|
+
additionalProperties: false,
|
|
6357
|
+
required: ["name", "version", "sha256", "events", "description", "source", "versions"],
|
|
6358
|
+
properties: {
|
|
6359
|
+
name: { type: "string" },
|
|
6360
|
+
version: { type: "string" },
|
|
6361
|
+
sha256: { type: "string" },
|
|
6362
|
+
events: { type: "array", items: { type: "string" } },
|
|
6363
|
+
description: { type: "string" },
|
|
6364
|
+
source: {
|
|
6365
|
+
type: "string",
|
|
6366
|
+
description: "bundled | custom | custom-overrides-bundled"
|
|
6367
|
+
},
|
|
6368
|
+
versions: { type: "array", items: { type: "string" } }
|
|
6369
|
+
}
|
|
6370
|
+
},
|
|
6371
|
+
HooksCatalog: {
|
|
6372
|
+
type: "object",
|
|
6373
|
+
additionalProperties: false,
|
|
6374
|
+
required: ["hooks"],
|
|
6375
|
+
properties: {
|
|
6376
|
+
hooks: { type: "array", items: { $ref: "#/components/schemas/HooksCatalogEntry" } }
|
|
6377
|
+
}
|
|
6378
|
+
},
|
|
6379
|
+
HooksLockEntry: {
|
|
6380
|
+
type: "object",
|
|
6381
|
+
additionalProperties: false,
|
|
6382
|
+
required: ["version", "sha256", "source"],
|
|
6383
|
+
properties: {
|
|
6384
|
+
version: { type: "string" },
|
|
6385
|
+
sha256: { type: "string" },
|
|
6386
|
+
source: { type: "string" },
|
|
6387
|
+
pinned: {
|
|
6388
|
+
type: "boolean",
|
|
6389
|
+
description: "True for an explicit install/update pin, absent for sync-maintained pins."
|
|
6390
|
+
},
|
|
6391
|
+
versions: { type: "array", items: { type: "string" } }
|
|
6392
|
+
}
|
|
6393
|
+
},
|
|
6394
|
+
HooksLock: {
|
|
6395
|
+
type: "object",
|
|
6396
|
+
additionalProperties: false,
|
|
6397
|
+
required: ["hooks"],
|
|
6398
|
+
properties: {
|
|
6399
|
+
hooks: {
|
|
6400
|
+
type: "object",
|
|
6401
|
+
additionalProperties: { $ref: "#/components/schemas/HooksLockEntry" }
|
|
6049
6402
|
}
|
|
6050
6403
|
}
|
|
6051
6404
|
},
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
"
|
|
6056
|
-
|
|
6405
|
+
HooksManifest: {
|
|
6406
|
+
type: "object",
|
|
6407
|
+
additionalProperties: false,
|
|
6408
|
+
required: ["name", "version", "events", "script"],
|
|
6409
|
+
properties: {
|
|
6410
|
+
name: { type: "string" },
|
|
6411
|
+
version: { type: "string" },
|
|
6412
|
+
description: { type: "string" },
|
|
6413
|
+
events: { type: "array", items: { type: "string" } },
|
|
6414
|
+
script: { type: "string" },
|
|
6415
|
+
script_kind: { type: "string", enum: ["inline", "file"] },
|
|
6416
|
+
args: { type: "array", items: { type: "string" } },
|
|
6417
|
+
timeout_ms: { type: "number" }
|
|
6418
|
+
}
|
|
6057
6419
|
},
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
6420
|
+
HooksArtifact: {
|
|
6421
|
+
type: "object",
|
|
6422
|
+
additionalProperties: false,
|
|
6423
|
+
required: ["manifest", "script"],
|
|
6424
|
+
properties: {
|
|
6425
|
+
manifest: { $ref: "#/components/schemas/HooksManifest" },
|
|
6426
|
+
script: { type: "string" }
|
|
6427
|
+
}
|
|
6428
|
+
},
|
|
6429
|
+
HooksError: {
|
|
6430
|
+
type: "object",
|
|
6431
|
+
additionalProperties: false,
|
|
6432
|
+
required: ["error"],
|
|
6433
|
+
properties: { error: { type: "string" } }
|
|
6434
|
+
},
|
|
6435
|
+
HooksPublishRequest: {
|
|
6436
|
+
type: "object",
|
|
6437
|
+
additionalProperties: false,
|
|
6438
|
+
required: ["name"],
|
|
6439
|
+
properties: {
|
|
6440
|
+
name: { type: "string" },
|
|
6441
|
+
version: { type: "string" }
|
|
6442
|
+
}
|
|
6443
|
+
},
|
|
6444
|
+
HooksPublishResponse: {
|
|
6445
|
+
type: "object",
|
|
6446
|
+
additionalProperties: false,
|
|
6447
|
+
required: ["ok", "hook"],
|
|
6448
|
+
properties: {
|
|
6449
|
+
ok: { type: "boolean" },
|
|
6450
|
+
hook: {
|
|
6451
|
+
type: "object",
|
|
6452
|
+
additionalProperties: false,
|
|
6453
|
+
required: ["name", "version", "sha256"],
|
|
6454
|
+
properties: {
|
|
6455
|
+
name: { type: "string" },
|
|
6456
|
+
version: { type: "string" },
|
|
6457
|
+
sha256: { type: "string" }
|
|
6458
|
+
}
|
|
6459
|
+
}
|
|
6460
|
+
}
|
|
6461
|
+
}
|
|
6068
6462
|
}
|
|
6069
6463
|
}
|
|
6070
|
-
}
|
|
6071
|
-
}
|
|
6464
|
+
};
|
|
6465
|
+
}
|
|
6072
6466
|
|
|
6073
6467
|
// src/serve.ts
|
|
6074
6468
|
var DEFAULT_SERVE_PORT = 39428;
|
|
@@ -6076,7 +6470,7 @@ var SERVE_HOST = "127.0.0.1";
|
|
|
6076
6470
|
var SERVE_SERVICE_NAME = "hooks-registry";
|
|
6077
6471
|
function packageVersion() {
|
|
6078
6472
|
try {
|
|
6079
|
-
const pkgPath = join8(
|
|
6473
|
+
const pkgPath = join8(dirname4(fileURLToPath5(import.meta.url)), "..", "package.json");
|
|
6080
6474
|
return JSON.parse(readFileSync5(pkgPath, "utf-8")).version || "0.0.0";
|
|
6081
6475
|
} catch {
|
|
6082
6476
|
return "0.0.0";
|
|
@@ -6166,7 +6560,7 @@ function handleServeRequest(req, apiKey) {
|
|
|
6166
6560
|
return Promise.resolve(json({ version: packageVersion() }));
|
|
6167
6561
|
}
|
|
6168
6562
|
if (url.pathname === "/openapi.json" && req.method === "GET") {
|
|
6169
|
-
return Promise.resolve(json(
|
|
6563
|
+
return Promise.resolve(json(buildOpenApiDocument(packageVersion())));
|
|
6170
6564
|
}
|
|
6171
6565
|
if (url.pathname === "/api/v1/catalog" && req.method === "GET") {
|
|
6172
6566
|
return buildCatalog().then((catalog) => json({ hooks: catalog }));
|
|
@@ -6238,7 +6632,7 @@ function startServeServer(options) {
|
|
|
6238
6632
|
hostname: host,
|
|
6239
6633
|
port,
|
|
6240
6634
|
async fetch(req) {
|
|
6241
|
-
return handleServeRequest(req,
|
|
6635
|
+
return handleServeRequest(req, await resolveHooksServePublishKeyAsync());
|
|
6242
6636
|
}
|
|
6243
6637
|
});
|
|
6244
6638
|
console.error(`hooks registry listening on http://${host}:${server.port} (publish requires an API key)`);
|