@hasna/hooks 0.9.4 → 0.9.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/README.md +13 -2
- package/bin/hooks-mcp.js +73 -51
- package/bin/index.js +377 -126
- package/bin/serve.js +230 -38
- package/dist/index.d.ts +1 -1
- package/dist/index.js +314 -63
- 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/sdk/index.d.ts +2 -2
- package/dist/sdk/index.js +275 -46
- package/package.json +3 -3
- package/scripts/validate-package.ts +70 -2
package/bin/serve.js
CHANGED
|
@@ -5358,16 +5358,21 @@ function resolveHook(name) {
|
|
|
5358
5358
|
|
|
5359
5359
|
// src/serve.ts
|
|
5360
5360
|
import { readFileSync as readFileSync5 } from "fs";
|
|
5361
|
-
import { dirname as
|
|
5362
|
-
import { fileURLToPath as
|
|
5361
|
+
import { dirname as dirname4, join as join8 } from "path";
|
|
5362
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
5363
5363
|
|
|
5364
|
-
//
|
|
5364
|
+
// ../contracts/dist/client/transport.js
|
|
5365
5365
|
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";
|
|
5366
|
+
import { closeSync as closeSync2, fstatSync as fstatSync2, openSync as openSync2, readFileSync as readFileSync22 } from "fs";
|
|
5367
|
+
import { O_NOFOLLOW as O_NOFOLLOW2, O_NONBLOCK as O_NONBLOCK2, O_RDONLY as O_RDONLY2 } from "constants";
|
|
5369
5368
|
import { hostname as osHostname } from "os";
|
|
5370
|
-
import { isAbsolute as isAbsolute2, join as
|
|
5369
|
+
import { isAbsolute as isAbsolute2, join as join22 } from "path";
|
|
5370
|
+
import { createRequire } from "module";
|
|
5371
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5372
|
+
import { closeSync, fstatSync, lstatSync, openSync, readFileSync as readFileSync4, realpathSync as realpathSync2, statSync } from "fs";
|
|
5373
|
+
import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
|
|
5374
|
+
import { basename as basename2, dirname as dirname3, join as join7, relative, sep as sep2 } from "path";
|
|
5375
|
+
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "url";
|
|
5371
5376
|
function envToken(name) {
|
|
5372
5377
|
return name.toUpperCase().replace(/-/g, "_");
|
|
5373
5378
|
}
|
|
@@ -5385,6 +5390,114 @@ var CREDENTIAL_PROFILE_ENV_KEY = "HASNA_PROFILE";
|
|
|
5385
5390
|
function credentialPointerEnvKey(name) {
|
|
5386
5391
|
return `HASNA_${envToken(name)}_API_KEY_REF`;
|
|
5387
5392
|
}
|
|
5393
|
+
function e(e2, n, r) {
|
|
5394
|
+
throw new Error(r ? `No known conditions for "${n}" specifier in "${e2}" package` : `Missing "${n}" specifier in "${e2}" package`);
|
|
5395
|
+
}
|
|
5396
|
+
function n(n2, i, o, f) {
|
|
5397
|
+
let s, u, l = r(n2, o), c = function(e2) {
|
|
5398
|
+
let n3 = new Set(["default", ...e2.conditions || []]);
|
|
5399
|
+
return e2.unsafe || n3.add(e2.require ? "require" : "import"), e2.unsafe || n3.add(e2.browser ? "browser" : "node"), n3;
|
|
5400
|
+
}(f || {}), a = i[l];
|
|
5401
|
+
if (a === undefined) {
|
|
5402
|
+
let e2, n3, r, t;
|
|
5403
|
+
for (t in i)
|
|
5404
|
+
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))));
|
|
5405
|
+
a = i[n3];
|
|
5406
|
+
}
|
|
5407
|
+
return a || e(n2, l), s = t(a, c), s || e(n2, l, 1), u && function(e2, n3) {
|
|
5408
|
+
let r, t = 0, i2 = e2.length, o2 = /[*]/g, f2 = /[/]$/;
|
|
5409
|
+
for (;t < i2; t++)
|
|
5410
|
+
e2[t] = o2.test(r = e2[t]) ? r.replace(o2, n3) : f2.test(r) ? r + n3 : r;
|
|
5411
|
+
}(s, u), s;
|
|
5412
|
+
}
|
|
5413
|
+
function r(e2, n2, r2) {
|
|
5414
|
+
if (e2 === n2 || n2 === ".")
|
|
5415
|
+
return ".";
|
|
5416
|
+
let t = e2 + "/", i = t.length, o = n2.slice(0, i) === t, f = o ? n2.slice(i) : n2;
|
|
5417
|
+
return f[0] === "#" ? f : o || !r2 ? f.slice(0, 2) === "./" ? f : "./" + f : f;
|
|
5418
|
+
}
|
|
5419
|
+
function t(e2, n2, r2) {
|
|
5420
|
+
if (e2) {
|
|
5421
|
+
if (typeof e2 == "string")
|
|
5422
|
+
return r2 && r2.add(e2), [e2];
|
|
5423
|
+
let i, o;
|
|
5424
|
+
if (Array.isArray(e2)) {
|
|
5425
|
+
for (o = r2 || new Set, i = 0;i < e2.length; i++)
|
|
5426
|
+
t(e2[i], n2, o);
|
|
5427
|
+
if (!r2 && o.size)
|
|
5428
|
+
return [...o];
|
|
5429
|
+
} else
|
|
5430
|
+
for (i in e2)
|
|
5431
|
+
if (n2.has(i))
|
|
5432
|
+
return t(e2[i], n2, r2);
|
|
5433
|
+
}
|
|
5434
|
+
}
|
|
5435
|
+
function o(e2, r2, t2) {
|
|
5436
|
+
let i, o2 = e2.exports;
|
|
5437
|
+
if (o2) {
|
|
5438
|
+
if (typeof o2 == "string")
|
|
5439
|
+
o2 = { ".": o2 };
|
|
5440
|
+
else
|
|
5441
|
+
for (i in o2) {
|
|
5442
|
+
i[0] !== "." && (o2 = { ".": o2 });
|
|
5443
|
+
break;
|
|
5444
|
+
}
|
|
5445
|
+
return n(e2.name, o2, r2 || ".", t2);
|
|
5446
|
+
}
|
|
5447
|
+
}
|
|
5448
|
+
var PACKAGE = "@hasna/secrets";
|
|
5449
|
+
var MAX_PACKAGE_BYTES = 1024 * 1024;
|
|
5450
|
+
function entryPoint(directory) {
|
|
5451
|
+
const root = realpathSync2(directory);
|
|
5452
|
+
const fd = openSync(join7(root, "package.json"), O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
|
|
5453
|
+
let pkg;
|
|
5454
|
+
try {
|
|
5455
|
+
const stat = fstatSync(fd);
|
|
5456
|
+
if (!stat.isFile() || stat.size > MAX_PACKAGE_BYTES)
|
|
5457
|
+
throw new Error("Invalid Secrets package metadata");
|
|
5458
|
+
pkg = JSON.parse(readFileSync4(fd, "utf8"));
|
|
5459
|
+
} finally {
|
|
5460
|
+
closeSync(fd);
|
|
5461
|
+
}
|
|
5462
|
+
if (!pkg || typeof pkg !== "object" || !("name" in pkg) || pkg.name !== PACKAGE) {
|
|
5463
|
+
throw new Error("Invalid Secrets package identity");
|
|
5464
|
+
}
|
|
5465
|
+
const target = o(pkg, ".")?.[0];
|
|
5466
|
+
if (typeof target !== "string" || !target.startsWith("./"))
|
|
5467
|
+
throw new Error("Missing Secrets import export");
|
|
5468
|
+
const parts = target.slice(2).split("/");
|
|
5469
|
+
if (/[\\%?#\0]/.test(target) || parts.some((part) => !part || part === "." || part === ".." || part === "node_modules")) {
|
|
5470
|
+
throw new Error("Invalid Secrets import export");
|
|
5471
|
+
}
|
|
5472
|
+
const file = realpathSync2(join7(root, ...parts));
|
|
5473
|
+
const within = relative(root, file);
|
|
5474
|
+
if (!within || within === ".." || within.startsWith(`..${sep2}`) || !statSync(file).isFile()) {
|
|
5475
|
+
throw new Error("Secrets import export escapes its package or is not a file");
|
|
5476
|
+
}
|
|
5477
|
+
return pathToFileURL(file).href;
|
|
5478
|
+
}
|
|
5479
|
+
function resolveInstalledSecrets(parentUrl) {
|
|
5480
|
+
let directory = dirname3(fileURLToPath3(parentUrl));
|
|
5481
|
+
for (;; ) {
|
|
5482
|
+
if (basename2(directory) !== "node_modules") {
|
|
5483
|
+
const candidate = join7(directory, "node_modules", PACKAGE);
|
|
5484
|
+
let absent = false;
|
|
5485
|
+
try {
|
|
5486
|
+
lstatSync(candidate);
|
|
5487
|
+
} catch (error) {
|
|
5488
|
+
if (error.code !== "ENOENT")
|
|
5489
|
+
throw error;
|
|
5490
|
+
absent = true;
|
|
5491
|
+
}
|
|
5492
|
+
if (!absent)
|
|
5493
|
+
return entryPoint(candidate);
|
|
5494
|
+
}
|
|
5495
|
+
const parent = dirname3(directory);
|
|
5496
|
+
if (parent === directory)
|
|
5497
|
+
throw new Error("Secrets SDK is not installed for this consumer");
|
|
5498
|
+
directory = parent;
|
|
5499
|
+
}
|
|
5500
|
+
}
|
|
5388
5501
|
|
|
5389
5502
|
class CredentialResolutionError extends Error {
|
|
5390
5503
|
appName;
|
|
@@ -5433,14 +5546,14 @@ function hasnaHomeDir(env) {
|
|
|
5433
5546
|
if (override)
|
|
5434
5547
|
return override;
|
|
5435
5548
|
const home = homeDir(env);
|
|
5436
|
-
return home ?
|
|
5549
|
+
return home ? join22(home, HASNA_HOME_DIR) : null;
|
|
5437
5550
|
}
|
|
5438
5551
|
function appConfigDir(name, env) {
|
|
5439
5552
|
const configRoot = absoluteOverride(env, HASNA_CONFIG_HOME_ENV_KEY);
|
|
5440
5553
|
if (configRoot)
|
|
5441
|
-
return
|
|
5554
|
+
return join22(configRoot, name);
|
|
5442
5555
|
const root = hasnaHomeDir(env);
|
|
5443
|
-
return root ?
|
|
5556
|
+
return root ? join22(root, name, CONFIG_SUBDIR) : null;
|
|
5444
5557
|
}
|
|
5445
5558
|
function credentialDiskSourceList(name, env, profile = null) {
|
|
5446
5559
|
if (!SAFE_APP_SLUG.test(name))
|
|
@@ -5449,7 +5562,7 @@ function credentialDiskSourceList(name, env, profile = null) {
|
|
|
5449
5562
|
if (!directory)
|
|
5450
5563
|
return [];
|
|
5451
5564
|
const file = profile ? `${CREDENTIALS_FILE}-${profile}` : CREDENTIALS_FILE;
|
|
5452
|
-
return [{ path:
|
|
5565
|
+
return [{ path: join22(directory, file), tier: "disk" }];
|
|
5453
5566
|
}
|
|
5454
5567
|
function credentialDiskSources(name, env) {
|
|
5455
5568
|
return credentialDiskSourceList(name, env, null).map((s) => s.path);
|
|
@@ -5503,7 +5616,7 @@ function readAppConfigFile(path) {
|
|
|
5503
5616
|
};
|
|
5504
5617
|
let fd = -1;
|
|
5505
5618
|
try {
|
|
5506
|
-
fd =
|
|
5619
|
+
fd = openSync2(path, O_RDONLY2 | O_NOFOLLOW2 | O_NONBLOCK2);
|
|
5507
5620
|
} catch (error) {
|
|
5508
5621
|
const code = error.code;
|
|
5509
5622
|
if (code === "ENOENT" || code === "ENOTDIR")
|
|
@@ -5513,7 +5626,7 @@ function readAppConfigFile(path) {
|
|
|
5513
5626
|
unsafe(`the path could not be opened (${code ?? "unknown error"})`);
|
|
5514
5627
|
}
|
|
5515
5628
|
try {
|
|
5516
|
-
const before =
|
|
5629
|
+
const before = fstatSync2(fd);
|
|
5517
5630
|
if (!before.isFile())
|
|
5518
5631
|
unsafe("the path is not a regular file");
|
|
5519
5632
|
if (!configFileModeAllowed(before.mode)) {
|
|
@@ -5524,31 +5637,39 @@ function readAppConfigFile(path) {
|
|
|
5524
5637
|
unsafe("the file is not owned by the current user");
|
|
5525
5638
|
if (before.size > MAX_CREDENTIAL_FILE_BYTES)
|
|
5526
5639
|
unsafe("the file exceeds the size limit");
|
|
5527
|
-
const bytes =
|
|
5528
|
-
const after =
|
|
5640
|
+
const bytes = readFileSync22(fd);
|
|
5641
|
+
const after = fstatSync2(fd);
|
|
5529
5642
|
if (!configFileReadsCoherent(before, after)) {
|
|
5530
5643
|
unsafe("the file changed while being read");
|
|
5531
5644
|
}
|
|
5532
5645
|
return parseEnvFile(bytes.toString("utf8"));
|
|
5533
5646
|
} finally {
|
|
5534
5647
|
if (fd !== -1)
|
|
5535
|
-
|
|
5648
|
+
closeSync2(fd);
|
|
5536
5649
|
}
|
|
5537
5650
|
}
|
|
5538
|
-
function readCredentialFile(path, apiKeyKeys) {
|
|
5651
|
+
function readCredentialFile(path, apiKeyKeys, pointerKey) {
|
|
5539
5652
|
const parsed = readAppConfigFile(path);
|
|
5540
5653
|
if (!parsed)
|
|
5541
5654
|
return null;
|
|
5542
|
-
for (const key of apiKeyKeys) {
|
|
5655
|
+
for (const key of [...apiKeyKeys, pointerKey]) {
|
|
5543
5656
|
if (parsed.unusable.has(key)) {
|
|
5544
5657
|
throw new CredentialFileUnsafeError(path, `${key} is declared but blank or malformed`);
|
|
5545
5658
|
}
|
|
5546
5659
|
}
|
|
5547
5660
|
const values = apiKeyKeys.map((key) => parsed.values.get(key)?.trim()).filter((value) => Boolean(value));
|
|
5661
|
+
const pointer = parsed.values.get(pointerKey)?.trim();
|
|
5662
|
+
if (pointer !== undefined) {
|
|
5663
|
+
if (!VAULT_POINTER_SHAPE.test(pointer))
|
|
5664
|
+
throw new CredentialFileUnsafeError(path, `${pointerKey} must name a vault item`);
|
|
5665
|
+
if (values.length)
|
|
5666
|
+
throw new CredentialFileUnsafeError(path, "a credential file cannot select both a literal key and a vault reference");
|
|
5667
|
+
return { apiKey: "", pointerVaultKey: pointer };
|
|
5668
|
+
}
|
|
5548
5669
|
if (new Set(values).size > 1) {
|
|
5549
5670
|
throw new CredentialFileUnsafeError(path, "credential aliases disagree");
|
|
5550
5671
|
}
|
|
5551
|
-
return values[0]
|
|
5672
|
+
return values[0] === undefined ? null : { apiKey: values[0] };
|
|
5552
5673
|
}
|
|
5553
5674
|
function assertUsableCredential(appName, source, value) {
|
|
5554
5675
|
if (VAULT_POINTER_SHAPE.test(value)) {
|
|
@@ -5781,12 +5902,13 @@ function resolveCredential(name, env, options = {}) {
|
|
|
5781
5902
|
}
|
|
5782
5903
|
const paths = profileDiskSources(name, env, profile);
|
|
5783
5904
|
for (const path of paths) {
|
|
5784
|
-
const value = readCredentialFile(path, apiKeyKeys);
|
|
5905
|
+
const value = readCredentialFile(path, apiKeyKeys, pointerKeyName);
|
|
5785
5906
|
if (value) {
|
|
5786
|
-
|
|
5907
|
+
if (!value.pointerVaultKey)
|
|
5908
|
+
assertUsableCredential(name, path, value.apiKey);
|
|
5787
5909
|
return sealCredential({
|
|
5788
|
-
|
|
5789
|
-
tier: "profile",
|
|
5910
|
+
...value,
|
|
5911
|
+
tier: value.pointerVaultKey ? "pointer" : "profile",
|
|
5790
5912
|
source: path,
|
|
5791
5913
|
deliberate: true,
|
|
5792
5914
|
diskCandidates: paths,
|
|
@@ -5794,7 +5916,7 @@ function resolveCredential(name, env, options = {}) {
|
|
|
5794
5916
|
});
|
|
5795
5917
|
}
|
|
5796
5918
|
}
|
|
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);
|
|
5919
|
+
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
5920
|
}
|
|
5799
5921
|
const definedEnvEntries = apiKeyKeys.filter((key) => Object.prototype.hasOwnProperty.call(env, key) && env[key] !== undefined).map((key) => ({ key, value: String(env[key]).trim() }));
|
|
5800
5922
|
const blankEnv = definedEnvEntries.find((entry) => entry.value.length === 0);
|
|
@@ -5819,17 +5941,27 @@ function resolveCredential(name, env, options = {}) {
|
|
|
5819
5941
|
});
|
|
5820
5942
|
}
|
|
5821
5943
|
const diskSourceList = credentialDiskSourceList(name, env, null);
|
|
5822
|
-
const diskHits = diskSourceList.map((src) => ({ src, value: readCredentialFile(src.path, apiKeyKeys) })).filter((hit) => hit.value !== null);
|
|
5944
|
+
const diskHits = diskSourceList.map((src) => ({ src, value: readCredentialFile(src.path, apiKeyKeys, pointerKeyName) })).filter((hit) => hit.value !== null);
|
|
5823
5945
|
if (diskHits.length > 0) {
|
|
5824
5946
|
const winner = diskHits[0];
|
|
5825
|
-
|
|
5947
|
+
if (winner.value.pointerVaultKey) {
|
|
5948
|
+
return sealCredential({
|
|
5949
|
+
...winner.value,
|
|
5950
|
+
tier: "pointer",
|
|
5951
|
+
source: winner.src.path,
|
|
5952
|
+
deliberate: false,
|
|
5953
|
+
diskCandidates: diskPaths,
|
|
5954
|
+
warning: null
|
|
5955
|
+
});
|
|
5956
|
+
}
|
|
5957
|
+
assertUsableCredential(name, winner.src.path, winner.value.apiKey);
|
|
5826
5958
|
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] : []
|
|
5959
|
+
...diskHits.slice(1).filter((hit) => hit.value.apiKey !== winner.value.apiKey || hit.value.pointerVaultKey !== winner.value.pointerVaultKey).map((hit) => hit.src.path),
|
|
5960
|
+
...envHit && envHit.value !== winner.value.apiKey ? [envHit.key] : []
|
|
5829
5961
|
];
|
|
5830
5962
|
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
5963
|
return sealCredential({
|
|
5832
|
-
apiKey: winner.value,
|
|
5964
|
+
apiKey: winner.value.apiKey,
|
|
5833
5965
|
tier: winner.src.tier,
|
|
5834
5966
|
source: winner.src.path,
|
|
5835
5967
|
deliberate: false,
|
|
@@ -5850,8 +5982,54 @@ function resolveCredential(name, env, options = {}) {
|
|
|
5850
5982
|
}
|
|
5851
5983
|
return null;
|
|
5852
5984
|
}
|
|
5853
|
-
var SECRETS_PACKAGE_SPECIFIER = "@hasna/" + "secrets";
|
|
5854
5985
|
var requireSecretsSdk = createRequire(import.meta.url);
|
|
5986
|
+
async function completePointerCredential(name, pointerResolution, env = process.env) {
|
|
5987
|
+
const secretsEnv = snapshotClientEnvironment("secrets", env);
|
|
5988
|
+
const vaultKey = pointerResolution.pointerVaultKey;
|
|
5989
|
+
const pointerEnvKey = pointerResolution.source;
|
|
5990
|
+
if (!vaultKey) {
|
|
5991
|
+
throw new CredentialResolutionError(name, `Pointer resolution from ${pointerEnvKey} carries no vault item key; this is a defect in the resolver.`, [pointerEnvKey]);
|
|
5992
|
+
}
|
|
5993
|
+
if (name === "secrets") {
|
|
5994
|
+
throw new CredentialResolutionError(name, "The Secrets bootstrap credential cannot reference the same hosted vault; configure an independent bootstrap provider.", [pointerEnvKey]);
|
|
5995
|
+
}
|
|
5996
|
+
let secretsSdk;
|
|
5997
|
+
try {
|
|
5998
|
+
const sdkUrl = resolveInstalledSecrets(import.meta.url);
|
|
5999
|
+
secretsSdk = requireSecretsSdk(fileURLToPath2(sdkUrl));
|
|
6000
|
+
} catch {
|
|
6001
|
+
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]);
|
|
6002
|
+
}
|
|
6003
|
+
let client;
|
|
6004
|
+
try {
|
|
6005
|
+
const bootstrap = resolveCredential("secrets", secretsEnv);
|
|
6006
|
+
if (!bootstrap || bootstrap.tier === "pointer") {
|
|
6007
|
+
throw new CredentialResolutionError("secrets", "The Secrets vault requires an independent, non-reference bootstrap credential.", bootstrap ? [bootstrap.source] : []);
|
|
6008
|
+
}
|
|
6009
|
+
client = secretsSdk.createSecretsClientFromEnv(secretsEnv);
|
|
6010
|
+
} catch {
|
|
6011
|
+
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]);
|
|
6012
|
+
}
|
|
6013
|
+
let secret;
|
|
6014
|
+
try {
|
|
6015
|
+
secret = await client.getSecret({ key: vaultKey });
|
|
6016
|
+
} catch {
|
|
6017
|
+
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]);
|
|
6018
|
+
}
|
|
6019
|
+
const value = secret.value;
|
|
6020
|
+
if (!value) {
|
|
6021
|
+
throw new CredentialResolutionError(name, `${pointerEnvKey} resolved vault item '${vaultKey}', but it holds no value. A vault pointer is TERMINAL.`, [pointerEnvKey]);
|
|
6022
|
+
}
|
|
6023
|
+
assertUsableCredential(name, `${pointerEnvKey} -> vault:${vaultKey}`, value);
|
|
6024
|
+
return sealCredential({
|
|
6025
|
+
apiKey: value,
|
|
6026
|
+
tier: "pointer",
|
|
6027
|
+
source: `${pointerEnvKey} -> vault:${vaultKey}`,
|
|
6028
|
+
deliberate: pointerResolution.deliberate,
|
|
6029
|
+
diskCandidates: pointerResolution.diskCandidates,
|
|
6030
|
+
warning: null
|
|
6031
|
+
});
|
|
6032
|
+
}
|
|
5855
6033
|
var IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
|
|
5856
6034
|
var AUTHORITY_OVERRIDE_HEADERS = new Set([
|
|
5857
6035
|
"host",
|
|
@@ -5899,11 +6077,25 @@ function hooksResolverInputs(env, credentials = {}) {
|
|
|
5899
6077
|
}
|
|
5900
6078
|
|
|
5901
6079
|
// src/lib/transport.ts
|
|
5902
|
-
|
|
6080
|
+
var authorityBindings = new WeakMap;
|
|
6081
|
+
function sameCredential(left, right) {
|
|
6082
|
+
return left === null || right === null ? left === right : left.apiKey === right.apiKey && left.pointerVaultKey === right.pointerVaultKey && left.source === right.source && left.tier === right.tier;
|
|
6083
|
+
}
|
|
6084
|
+
function changedBinding() {
|
|
6085
|
+
throw new Error("REMOTE_API_CREDENTIAL_INVALID: The configured Hooks authority or credential changed while preparing a request; no request was sent.");
|
|
6086
|
+
}
|
|
6087
|
+
async function resolveHooksServePublishKeyAsync(env = process.env, options = {}) {
|
|
5903
6088
|
const { env: resolverEnv, credentials } = hooksResolverInputs(env, options.credentials);
|
|
5904
|
-
const
|
|
5905
|
-
const
|
|
5906
|
-
|
|
6089
|
+
const read = () => resolveCredential("hooks", resolverEnv, credentials);
|
|
6090
|
+
const selected = read();
|
|
6091
|
+
if (!selected || selected.tier !== "pointer")
|
|
6092
|
+
return selected?.apiKey;
|
|
6093
|
+
if (!sameCredential(selected, read()))
|
|
6094
|
+
changedBinding();
|
|
6095
|
+
const completed = await completePointerCredential("hooks", selected, env);
|
|
6096
|
+
if (!sameCredential(selected, read()))
|
|
6097
|
+
changedBinding();
|
|
6098
|
+
return completed.apiKey;
|
|
5907
6099
|
}
|
|
5908
6100
|
|
|
5909
6101
|
// src/lib/secure-compare.ts
|
|
@@ -5912,8 +6104,8 @@ function secureEqual(a, b) {
|
|
|
5912
6104
|
const ab = enc.encode(a);
|
|
5913
6105
|
const bb = enc.encode(b);
|
|
5914
6106
|
let diff = ab.length ^ bb.length;
|
|
5915
|
-
const
|
|
5916
|
-
for (let i = 0;i <
|
|
6107
|
+
const n2 = Math.max(ab.length, bb.length);
|
|
6108
|
+
for (let i = 0;i < n2; i++) {
|
|
5917
6109
|
diff |= (ab[i] ?? 0) ^ (bb[i] ?? 0);
|
|
5918
6110
|
}
|
|
5919
6111
|
return diff === 0;
|
|
@@ -6076,7 +6268,7 @@ var SERVE_HOST = "127.0.0.1";
|
|
|
6076
6268
|
var SERVE_SERVICE_NAME = "hooks-registry";
|
|
6077
6269
|
function packageVersion() {
|
|
6078
6270
|
try {
|
|
6079
|
-
const pkgPath = join8(
|
|
6271
|
+
const pkgPath = join8(dirname4(fileURLToPath5(import.meta.url)), "..", "package.json");
|
|
6080
6272
|
return JSON.parse(readFileSync5(pkgPath, "utf-8")).version || "0.0.0";
|
|
6081
6273
|
} catch {
|
|
6082
6274
|
return "0.0.0";
|
|
@@ -6238,7 +6430,7 @@ function startServeServer(options) {
|
|
|
6238
6430
|
hostname: host,
|
|
6239
6431
|
port,
|
|
6240
6432
|
async fetch(req) {
|
|
6241
|
-
return handleServeRequest(req,
|
|
6433
|
+
return handleServeRequest(req, await resolveHooksServePublishKeyAsync());
|
|
6242
6434
|
}
|
|
6243
6435
|
});
|
|
6244
6436
|
console.error(`hooks registry listening on http://${host}:${server.port} (publish requires an API key)`);
|
package/dist/index.d.ts
CHANGED
|
@@ -75,7 +75,7 @@ export { createProfile, getProfile, listProfiles, updateProfile, deleteProfile,
|
|
|
75
75
|
export { HOOKS_STORAGE_ENV, HOOKS_STORAGE_FALLBACK_ENV, HOOKS_STORAGE_BACKEND_ENV, HOOKS_STORAGE_BACKEND_FALLBACK_ENV, HOOKS_STORAGE_TABLES, RETIRED_STORAGE_MODE_ENV, STORAGE_BACKENDS, STORAGE_BACKEND_ENV, STORAGE_DATABASE_ENV, STORAGE_TABLES, getStorageBackend, getStorageDatabaseEnv, getStorageDatabaseEnvName, getStorageDatabaseUrl, getStoragePg, getStorageStatus, getSyncMetaAll, parseStorageTables, resolveTables, runStorageMigrations, storagePull, storagePush, storageSync, } from "./storage.js";
|
|
76
76
|
export type { StorageBackend, StorageEnv, StorageStatus, SyncMeta, SyncResult } from "./storage.js";
|
|
77
77
|
export { getHooksDataDir, getCustomHooksDir, getLockPath, } from "./config.js";
|
|
78
|
-
export { resolveHooksTransport, resolveHooksServePublishKey, hooksRegistryOrigin, type HooksRemoteAuthority, type HooksCredentialOptions, type HooksTransportMode, type HooksTransportNotice, type HooksTransportOptions, type HooksTransportResolution, } from "./lib/transport.js";
|
|
78
|
+
export { resolveHooksTransport, resolveHooksRequestAuthority, resolveHooksServePublishKey, resolveHooksServePublishKeyAsync, hooksRegistryOrigin, type HooksRemoteAuthority, type HooksCredentialOptions, type HooksTransportMode, type HooksTransportNotice, type HooksTransportOptions, type HooksTransportResolution, } from "./lib/transport.js";
|
|
79
79
|
export type { HooksLocalOptInEnv } from "./lib/resolver-types.js";
|
|
80
80
|
export { HOOKS_LOCAL_OPT_IN_ENV_KEYS, isHooksLocalOptIn, hasHooksEnvAuthorityIntent, selectsHooksLocalStore, hooksFailClosedLine, hooksHostedRouteLocalStoreRefusal, hooksEventSinkRefusal, } from "./lib/local-opt-in.js";
|
|
81
81
|
export { refuseLocalStore, allowLocalStore, isLocalStoreRefused } from "./db/index.js";
|