@hasna/domains 0.0.25 → 0.0.26
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/dist/cli/commands/sedo.d.ts.map +1 -1
- package/dist/cli/index.js +340 -785
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +446 -127
- package/dist/lib/capability.js +46 -0
- package/dist/lib/cloudflare-auth.js +139 -0
- package/dist/lib/cloudflare.js +394 -0
- package/dist/lib/creds-check.d.ts.map +1 -1
- package/dist/lib/delegate.js +12 -0
- package/dist/lib/env-aliases.d.ts +10 -0
- package/dist/lib/env-aliases.d.ts.map +1 -1
- package/dist/lib/registrar.js +2216 -0
- package/dist/lib/route53.d.ts +54 -11
- package/dist/lib/route53.d.ts.map +1 -1
- package/dist/lib/route53.js +678 -0
- package/dist/lib/sedo.d.ts +34 -3
- package/dist/lib/sedo.d.ts.map +1 -1
- package/dist/mcp/index.js +182 -69
- package/package.json +27 -2
package/dist/cli/index.js
CHANGED
|
@@ -993,7 +993,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
993
993
|
this._exitCallback = (err) => {
|
|
994
994
|
if (err.code !== "commander.executeSubCommandAsync") {
|
|
995
995
|
throw err;
|
|
996
|
-
}
|
|
996
|
+
} else {}
|
|
997
997
|
};
|
|
998
998
|
}
|
|
999
999
|
return this;
|
|
@@ -15426,7 +15426,7 @@ var init_bowser = __esm(() => {
|
|
|
15426
15426
|
|
|
15427
15427
|
// node_modules/@aws-sdk/core/dist-cjs/submodules/client/index.js
|
|
15428
15428
|
var require_client2 = __commonJS((exports) => {
|
|
15429
|
-
var __dirname = "/home/hasna/
|
|
15429
|
+
var __dirname = "/home/hasna/workspace/hasna/opensource/open-domains/node_modules/@aws-sdk/core/dist-cjs/submodules/client";
|
|
15430
15430
|
var retry = require_retry();
|
|
15431
15431
|
var protocols = require_protocols();
|
|
15432
15432
|
var lambdaInvokeStore = require_invoke_store();
|
|
@@ -36170,14 +36170,19 @@ __export(exports_route53, {
|
|
|
36170
36170
|
upsertRecords: () => upsertRecords,
|
|
36171
36171
|
upsertRecord: () => upsertRecord,
|
|
36172
36172
|
updateNameservers: () => updateNameservers2,
|
|
36173
|
+
transferDomain: () => transferDomain,
|
|
36174
|
+
requestTransferOutAuthCode: () => requestTransferOutAuthCode,
|
|
36173
36175
|
registerDomain: () => registerDomain2,
|
|
36176
|
+
listTldPrices: () => listTldPrices,
|
|
36174
36177
|
listRegisteredDomains: () => listRegisteredDomains,
|
|
36175
36178
|
listRecords: () => listRecords,
|
|
36176
36179
|
listHostedZones: () => listHostedZones,
|
|
36180
|
+
getTldPrice: () => getTldPrice,
|
|
36177
36181
|
getRegistrationStatus: () => getRegistrationStatus,
|
|
36178
36182
|
getHostedZone: () => getHostedZone,
|
|
36179
36183
|
getDomainDetail: () => getDomainDetail,
|
|
36180
36184
|
getConfig: () => getConfig2,
|
|
36185
|
+
findHostedZoneByNameservers: () => findHostedZoneByNameservers,
|
|
36181
36186
|
findHostedZoneByDomain: () => findHostedZoneByDomain,
|
|
36182
36187
|
deleteRecord: () => deleteRecord,
|
|
36183
36188
|
deleteHostedZone: () => deleteHostedZone,
|
|
@@ -36187,26 +36192,17 @@ __export(exports_route53, {
|
|
|
36187
36192
|
});
|
|
36188
36193
|
function getConfig2() {
|
|
36189
36194
|
return {
|
|
36190
|
-
region: process.env["AWS_REGION"] || "us-east-1",
|
|
36191
|
-
accessKeyId: process.env["AWS_ACCESS_KEY_ID"],
|
|
36192
|
-
secretAccessKey: process.env["AWS_SECRET_ACCESS_KEY"],
|
|
36193
|
-
sessionToken: process.env["AWS_SESSION_TOKEN"]
|
|
36195
|
+
region: process.env["ROUTE53_REGION"] || process.env["ROUTE53_AWS_REGION"] || process.env["AWS_REGION"] || "us-east-1",
|
|
36196
|
+
accessKeyId: process.env["ROUTE53_ACCESS_KEY_ID"] || process.env["AWS_ACCESS_KEY_ID"],
|
|
36197
|
+
secretAccessKey: process.env["ROUTE53_SECRET_ACCESS_KEY"] || process.env["AWS_SECRET_ACCESS_KEY"],
|
|
36198
|
+
sessionToken: process.env["ROUTE53_SESSION_TOKEN"] || process.env["AWS_SESSION_TOKEN"],
|
|
36199
|
+
profile: process.env["ROUTE53_AWS_PROFILE"] || process.env["AWS_PROFILE"]
|
|
36194
36200
|
};
|
|
36195
36201
|
}
|
|
36196
|
-
function checkCredentials(cfg) {
|
|
36197
|
-
if (cfg.accessKeyId && cfg.secretAccessKey)
|
|
36198
|
-
return;
|
|
36199
|
-
const hasEnv = !!(process.env["AWS_ACCESS_KEY_ID"] && process.env["AWS_SECRET_ACCESS_KEY"]);
|
|
36200
|
-
const hasProfile = !!process.env["AWS_PROFILE"];
|
|
36201
|
-
if (!hasEnv && !hasProfile) {
|
|
36202
|
-
throw new Error("AWS credentials not configured. Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY (or AWS_PROFILE) environment variables.");
|
|
36203
|
-
}
|
|
36204
|
-
}
|
|
36205
36202
|
function makeClients(config2) {
|
|
36206
36203
|
const cfg = config2 ?? getConfig2();
|
|
36207
|
-
checkCredentials(cfg);
|
|
36208
36204
|
const region = cfg.region || "us-east-1";
|
|
36209
|
-
const credentials = cfg.accessKeyId && cfg.secretAccessKey ? { accessKeyId: cfg.accessKeyId, secretAccessKey: cfg.secretAccessKey, sessionToken: cfg.sessionToken } : undefined;
|
|
36205
|
+
const credentials = cfg.accessKeyId && cfg.secretAccessKey ? { accessKeyId: cfg.accessKeyId, secretAccessKey: cfg.secretAccessKey, sessionToken: cfg.sessionToken } : cfg.profile ? fromIni({ profile: cfg.profile }) : undefined;
|
|
36210
36206
|
return {
|
|
36211
36207
|
route53: new Route53Client({ region, credentials }),
|
|
36212
36208
|
domains: new Route53DomainsClient({ region: "us-east-1", credentials })
|
|
@@ -36217,49 +36213,106 @@ async function checkAvailability3(domain, config2) {
|
|
|
36217
36213
|
const result = await domains.send(new CheckDomainAvailabilityCommand({ DomainName: domain }));
|
|
36218
36214
|
const availability = {
|
|
36219
36215
|
domain,
|
|
36220
|
-
available: result.Availability === "AVAILABLE"
|
|
36216
|
+
available: result.Availability === "AVAILABLE",
|
|
36217
|
+
availability: result.Availability ?? "UNKNOWN"
|
|
36221
36218
|
};
|
|
36222
|
-
|
|
36223
|
-
|
|
36224
|
-
|
|
36225
|
-
|
|
36226
|
-
|
|
36227
|
-
|
|
36228
|
-
|
|
36229
|
-
availability.
|
|
36230
|
-
|
|
36231
|
-
|
|
36232
|
-
|
|
36233
|
-
|
|
36234
|
-
}
|
|
36235
|
-
} catch {}
|
|
36219
|
+
if (availability.available) {
|
|
36220
|
+
try {
|
|
36221
|
+
const tld = domain.split(".").slice(1).join(".");
|
|
36222
|
+
const price = await getTldPrice(tld, config2);
|
|
36223
|
+
if (price) {
|
|
36224
|
+
availability.currency = price.currency;
|
|
36225
|
+
availability.price = price.registration_price;
|
|
36226
|
+
availability.renewal_price = price.renewal_price;
|
|
36227
|
+
availability.transfer_price = price.transfer_price;
|
|
36228
|
+
}
|
|
36229
|
+
} catch {}
|
|
36230
|
+
}
|
|
36236
36231
|
return availability;
|
|
36237
36232
|
}
|
|
36238
|
-
|
|
36233
|
+
function normalizeTld(tld) {
|
|
36234
|
+
return tld.trim().replace(/^\./, "");
|
|
36235
|
+
}
|
|
36236
|
+
function priceString(value) {
|
|
36237
|
+
return value == null ? undefined : value.toString();
|
|
36238
|
+
}
|
|
36239
|
+
function tldPriceFromRoute53Price(tld, price) {
|
|
36240
|
+
return {
|
|
36241
|
+
tld: price.Name || tld,
|
|
36242
|
+
registration_price: priceString(price.RegistrationPrice?.Price),
|
|
36243
|
+
renewal_price: priceString(price.RenewalPrice?.Price),
|
|
36244
|
+
transfer_price: priceString(price.TransferPrice?.Price),
|
|
36245
|
+
currency: price.RegistrationPrice?.Currency ?? price.RenewalPrice?.Currency ?? price.TransferPrice?.Currency
|
|
36246
|
+
};
|
|
36247
|
+
}
|
|
36248
|
+
async function getTldPrice(tld, config2) {
|
|
36249
|
+
const { domains } = makeClients(config2);
|
|
36250
|
+
const normalized = normalizeTld(tld);
|
|
36251
|
+
const prices = await domains.send(new ListPricesCommand({ Tld: normalized, MaxItems: 1 }));
|
|
36252
|
+
const price = prices.Prices?.[0];
|
|
36253
|
+
return price ? tldPriceFromRoute53Price(normalized, price) : null;
|
|
36254
|
+
}
|
|
36255
|
+
async function listTldPrices(config2) {
|
|
36256
|
+
const { domains } = makeClients(config2);
|
|
36257
|
+
const prices = [];
|
|
36258
|
+
let marker;
|
|
36259
|
+
do {
|
|
36260
|
+
const result = await domains.send(new ListPricesCommand({ Marker: marker, MaxItems: 100 }));
|
|
36261
|
+
for (const price of result.Prices ?? []) {
|
|
36262
|
+
prices.push(tldPriceFromRoute53Price(price.Name || "", price));
|
|
36263
|
+
}
|
|
36264
|
+
marker = result.NextPageMarker;
|
|
36265
|
+
} while (marker);
|
|
36266
|
+
return prices;
|
|
36267
|
+
}
|
|
36268
|
+
async function registerDomain2(domain, contact, durationYears = 1, autoRenew = true, config2, options = {}) {
|
|
36239
36269
|
const { domains } = makeClients(config2);
|
|
36240
|
-
const contactDetail =
|
|
36270
|
+
const contactDetail = contactToRoute53Contact(contact);
|
|
36271
|
+
const result = await domains.send(new RegisterDomainCommand({
|
|
36272
|
+
DomainName: domain,
|
|
36273
|
+
DurationInYears: durationYears,
|
|
36274
|
+
AutoRenew: autoRenew,
|
|
36275
|
+
AdminContact: contactDetail,
|
|
36276
|
+
RegistrantContact: contactDetail,
|
|
36277
|
+
TechContact: contactDetail,
|
|
36278
|
+
PrivacyProtectAdminContact: options.privacy_protected ?? true,
|
|
36279
|
+
PrivacyProtectRegistrantContact: options.privacy_protected ?? true,
|
|
36280
|
+
PrivacyProtectTechContact: options.privacy_protected ?? true,
|
|
36281
|
+
...options.nameservers?.length ? { Nameservers: options.nameservers.map((Name) => ({ Name })) } : {}
|
|
36282
|
+
}));
|
|
36283
|
+
return { operationId: result.OperationId ?? "" };
|
|
36284
|
+
}
|
|
36285
|
+
function contactToRoute53Contact(contact) {
|
|
36286
|
+
return {
|
|
36241
36287
|
FirstName: contact.first_name,
|
|
36242
36288
|
LastName: contact.last_name,
|
|
36243
36289
|
Email: contact.email,
|
|
36244
36290
|
PhoneNumber: contact.phone,
|
|
36245
36291
|
AddressLine1: contact.address_line_1,
|
|
36292
|
+
...contact.address_line_2 ? { AddressLine2: contact.address_line_2 } : {},
|
|
36246
36293
|
City: contact.city,
|
|
36247
|
-
State: contact.state,
|
|
36248
|
-
CountryCode: contact.country_code,
|
|
36294
|
+
...contact.state ? { State: contact.state } : {},
|
|
36295
|
+
CountryCode: contact.country_code.toUpperCase(),
|
|
36249
36296
|
ZipCode: contact.zip_code,
|
|
36250
36297
|
ContactType: contact.organization_name ? "COMPANY" : "PERSON",
|
|
36251
36298
|
...contact.organization_name ? { OrganizationName: contact.organization_name } : {}
|
|
36252
36299
|
};
|
|
36253
|
-
|
|
36300
|
+
}
|
|
36301
|
+
async function transferDomain(domain, authCode, contact, durationYears = 1, autoRenew = true, config2, options = {}) {
|
|
36302
|
+
const { domains } = makeClients(config2);
|
|
36303
|
+
const contactDetail = contactToRoute53Contact(contact);
|
|
36304
|
+
const result = await domains.send(new TransferDomainCommand({
|
|
36254
36305
|
DomainName: domain,
|
|
36306
|
+
AuthCode: authCode,
|
|
36255
36307
|
DurationInYears: durationYears,
|
|
36256
36308
|
AutoRenew: autoRenew,
|
|
36257
36309
|
AdminContact: contactDetail,
|
|
36258
36310
|
RegistrantContact: contactDetail,
|
|
36259
36311
|
TechContact: contactDetail,
|
|
36260
|
-
PrivacyProtectAdminContact: true,
|
|
36261
|
-
PrivacyProtectRegistrantContact: true,
|
|
36262
|
-
PrivacyProtectTechContact: true
|
|
36312
|
+
PrivacyProtectAdminContact: options.privacy_protected ?? true,
|
|
36313
|
+
PrivacyProtectRegistrantContact: options.privacy_protected ?? true,
|
|
36314
|
+
PrivacyProtectTechContact: options.privacy_protected ?? true,
|
|
36315
|
+
...options.nameservers?.length ? { Nameservers: options.nameservers.map((Name) => ({ Name })) } : {}
|
|
36263
36316
|
}));
|
|
36264
36317
|
return { operationId: result.OperationId ?? "" };
|
|
36265
36318
|
}
|
|
@@ -36274,14 +36327,60 @@ async function getRegistrationStatus(operationId, config2) {
|
|
|
36274
36327
|
}
|
|
36275
36328
|
async function getDomainDetail(domain, config2) {
|
|
36276
36329
|
const { domains } = makeClients(config2);
|
|
36277
|
-
const result = await
|
|
36330
|
+
const [result, summary] = await Promise.all([
|
|
36331
|
+
domains.send(new GetDomainDetailCommand({ DomainName: domain })),
|
|
36332
|
+
getDomainSummary(domain, config2)
|
|
36333
|
+
]);
|
|
36334
|
+
const privacy = result;
|
|
36278
36335
|
return {
|
|
36279
36336
|
domain: result.DomainName ?? domain,
|
|
36280
|
-
expiry: result.ExpirationDate?.toISOString() ?? "",
|
|
36281
|
-
auto_renew: result.AutoRenew ??
|
|
36282
|
-
transfer_lock: result.StatusList?.includes("TRANSFER_LOCK")
|
|
36337
|
+
expiry: result.ExpirationDate?.toISOString() ?? summary?.expiry ?? "",
|
|
36338
|
+
auto_renew: result.AutoRenew ?? summary?.auto_renew ?? null,
|
|
36339
|
+
transfer_lock: summary?.transfer_lock ?? (result.StatusList?.includes("TRANSFER_LOCK") ? true : null),
|
|
36283
36340
|
created: result.CreationDate?.toISOString() ?? "",
|
|
36284
|
-
|
|
36341
|
+
updated: result.UpdatedDate?.toISOString() ?? "",
|
|
36342
|
+
nameservers: (result.Nameservers ?? []).map((ns) => ns.Name ?? "").filter(Boolean),
|
|
36343
|
+
status_list: result.StatusList ?? [],
|
|
36344
|
+
registrar_name: result.RegistrarName,
|
|
36345
|
+
privacy_protected: privacy.RegistrantPrivacy ?? privacy.AdminPrivacy ?? privacy.TechPrivacy ?? null
|
|
36346
|
+
};
|
|
36347
|
+
}
|
|
36348
|
+
async function getDomainSummary(domain, config2) {
|
|
36349
|
+
let marker;
|
|
36350
|
+
do {
|
|
36351
|
+
const { domains } = makeClients(config2);
|
|
36352
|
+
const result = await domains.send(new ListDomainsCommand({ Marker: marker, MaxItems: 100 }));
|
|
36353
|
+
const summary = (result.Domains ?? []).find((item) => item.DomainName === domain);
|
|
36354
|
+
if (summary) {
|
|
36355
|
+
return {
|
|
36356
|
+
domain: summary.DomainName ?? domain,
|
|
36357
|
+
expiry: summary.Expiry?.toISOString() ?? "",
|
|
36358
|
+
auto_renew: summary.AutoRenew ?? false,
|
|
36359
|
+
transfer_lock: summary.TransferLock ?? false
|
|
36360
|
+
};
|
|
36361
|
+
}
|
|
36362
|
+
marker = result.NextPageMarker;
|
|
36363
|
+
} while (marker);
|
|
36364
|
+
return null;
|
|
36365
|
+
}
|
|
36366
|
+
async function requestTransferOutAuthCode(domain, config2) {
|
|
36367
|
+
const { domains } = makeClients(config2);
|
|
36368
|
+
const detail = await getDomainDetail(domain, config2);
|
|
36369
|
+
let transferLockDisabled = false;
|
|
36370
|
+
let transferLockOperationId = null;
|
|
36371
|
+
if (detail.transfer_lock) {
|
|
36372
|
+
const result = await domains.send(new DisableDomainTransferLockCommand({ DomainName: domain }));
|
|
36373
|
+
transferLockDisabled = true;
|
|
36374
|
+
transferLockOperationId = result.OperationId ?? null;
|
|
36375
|
+
}
|
|
36376
|
+
const authCodeResult = await domains.send(new RetrieveDomainAuthCodeCommand({ DomainName: domain }));
|
|
36377
|
+
if (!authCodeResult.AuthCode) {
|
|
36378
|
+
throw new Error("Route53 did not return a domain transfer authorization code");
|
|
36379
|
+
}
|
|
36380
|
+
return {
|
|
36381
|
+
auth_code: authCodeResult.AuthCode,
|
|
36382
|
+
transfer_lock_disabled: transferLockDisabled,
|
|
36383
|
+
transfer_lock_operation_id: transferLockOperationId
|
|
36285
36384
|
};
|
|
36286
36385
|
}
|
|
36287
36386
|
async function updateNameservers2(domain, nameservers, config2, client2) {
|
|
@@ -36319,9 +36418,22 @@ function cleanZoneId(id) {
|
|
|
36319
36418
|
function normalizeZoneId(id) {
|
|
36320
36419
|
return cleanZoneId(id.trim());
|
|
36321
36420
|
}
|
|
36322
|
-
|
|
36421
|
+
function normalizeNameserver(value) {
|
|
36422
|
+
return value.trim().toLowerCase().replace(/\.$/, "");
|
|
36423
|
+
}
|
|
36424
|
+
function nameserversMatch(a4, b4) {
|
|
36425
|
+
if (a4.length !== b4.length)
|
|
36426
|
+
return false;
|
|
36427
|
+
const left = [...new Set(a4.map(normalizeNameserver))].sort();
|
|
36428
|
+
const right = [...new Set(b4.map(normalizeNameserver))].sort();
|
|
36429
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
36430
|
+
}
|
|
36431
|
+
function cleanChangeId(id) {
|
|
36432
|
+
return id?.replace("/change/", "") || null;
|
|
36433
|
+
}
|
|
36434
|
+
async function createHostedZone(domain, comment, config2, options) {
|
|
36323
36435
|
const { route53 } = makeClients(config2);
|
|
36324
|
-
const callerRef = `domains-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
36436
|
+
const callerRef = options?.callerReference ?? `domains-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
36325
36437
|
const result = await route53.send(new CreateHostedZoneCommand({
|
|
36326
36438
|
Name: domain,
|
|
36327
36439
|
CallerReference: callerRef,
|
|
@@ -36368,7 +36480,38 @@ async function getHostedZone(hostedZoneId, config2) {
|
|
|
36368
36480
|
}
|
|
36369
36481
|
async function deleteHostedZone(hostedZoneId, config2) {
|
|
36370
36482
|
const { route53 } = makeClients(config2);
|
|
36371
|
-
|
|
36483
|
+
const zoneId = normalizeZoneId(hostedZoneId);
|
|
36484
|
+
const managedRecords = [];
|
|
36485
|
+
let nextName;
|
|
36486
|
+
let nextType;
|
|
36487
|
+
let nextIdentifier;
|
|
36488
|
+
do {
|
|
36489
|
+
const result = await route53.send(new ListResourceRecordSetsCommand({
|
|
36490
|
+
HostedZoneId: zoneId,
|
|
36491
|
+
StartRecordName: nextName,
|
|
36492
|
+
StartRecordType: nextType,
|
|
36493
|
+
StartRecordIdentifier: nextIdentifier
|
|
36494
|
+
}));
|
|
36495
|
+
managedRecords.push(...result.ResourceRecordSets?.filter((record) => record.Type !== "NS" && record.Type !== "SOA") ?? []);
|
|
36496
|
+
nextName = result.IsTruncated ? result.NextRecordName : undefined;
|
|
36497
|
+
nextType = result.IsTruncated ? result.NextRecordType : undefined;
|
|
36498
|
+
nextIdentifier = result.IsTruncated ? result.NextRecordIdentifier : undefined;
|
|
36499
|
+
} while (nextName && nextType);
|
|
36500
|
+
for (let index = 0;index < managedRecords.length; index += 100) {
|
|
36501
|
+
const batch = managedRecords.slice(index, index + 100);
|
|
36502
|
+
if (batch.length === 0)
|
|
36503
|
+
continue;
|
|
36504
|
+
await route53.send(new ChangeResourceRecordSetsCommand({
|
|
36505
|
+
HostedZoneId: zoneId,
|
|
36506
|
+
ChangeBatch: {
|
|
36507
|
+
Changes: batch.map((record) => ({
|
|
36508
|
+
Action: "DELETE",
|
|
36509
|
+
ResourceRecordSet: record
|
|
36510
|
+
}))
|
|
36511
|
+
}
|
|
36512
|
+
}));
|
|
36513
|
+
}
|
|
36514
|
+
await route53.send(new DeleteHostedZoneCommand({ Id: zoneId }));
|
|
36372
36515
|
}
|
|
36373
36516
|
async function findHostedZoneByDomain(domain, config2) {
|
|
36374
36517
|
const zones = await listHostedZones(config2);
|
|
@@ -36383,6 +36526,24 @@ async function findHostedZoneByDomain(domain, config2) {
|
|
|
36383
36526
|
}
|
|
36384
36527
|
return candidates[0] ?? null;
|
|
36385
36528
|
}
|
|
36529
|
+
async function findHostedZoneByNameservers(domain, nameservers, config2) {
|
|
36530
|
+
if (!nameservers.length)
|
|
36531
|
+
return null;
|
|
36532
|
+
const { route53 } = makeClients(config2);
|
|
36533
|
+
const result = await route53.send(new ListHostedZonesByNameCommand({ DNSName: domain }));
|
|
36534
|
+
const zones = (result.HostedZones ?? []).filter((zone) => zone.Name?.replace(/\.$/, "") === domain && !zone.Config?.PrivateZone);
|
|
36535
|
+
for (const zone of zones) {
|
|
36536
|
+
const id = cleanZoneId(zone.Id ?? "");
|
|
36537
|
+
if (!id)
|
|
36538
|
+
continue;
|
|
36539
|
+
const detail = await getHostedZone(id, config2);
|
|
36540
|
+
const delegatedNameservers = detail.name_servers ?? [];
|
|
36541
|
+
if (nameserversMatch(delegatedNameservers, nameservers)) {
|
|
36542
|
+
return { ...detail, id, name_servers: delegatedNameservers };
|
|
36543
|
+
}
|
|
36544
|
+
}
|
|
36545
|
+
return null;
|
|
36546
|
+
}
|
|
36386
36547
|
function rrsToRecord(rrs) {
|
|
36387
36548
|
if (rrs.AliasTarget) {
|
|
36388
36549
|
return {
|
|
@@ -36446,36 +36607,44 @@ async function listRecords(hostedZoneId, config2) {
|
|
|
36446
36607
|
} while (nextName);
|
|
36447
36608
|
return records;
|
|
36448
36609
|
}
|
|
36449
|
-
async function upsertRecord(hostedZoneId, record, config2) {
|
|
36610
|
+
async function upsertRecord(hostedZoneId, record, config2, options) {
|
|
36450
36611
|
const { route53 } = makeClients(config2);
|
|
36451
|
-
await route53.send(new ChangeResourceRecordSetsCommand({
|
|
36612
|
+
const result = await route53.send(new ChangeResourceRecordSetsCommand({
|
|
36452
36613
|
HostedZoneId: normalizeZoneId(hostedZoneId),
|
|
36453
36614
|
ChangeBatch: {
|
|
36615
|
+
...options?.comment ? { Comment: options.comment } : {},
|
|
36454
36616
|
Changes: [{ Action: "UPSERT", ResourceRecordSet: recordToRrs(record) }]
|
|
36455
36617
|
}
|
|
36456
36618
|
}));
|
|
36619
|
+
return { changeId: cleanChangeId(result.ChangeInfo?.Id) };
|
|
36457
36620
|
}
|
|
36458
|
-
async function deleteRecord(hostedZoneId, record, config2) {
|
|
36621
|
+
async function deleteRecord(hostedZoneId, record, config2, options) {
|
|
36459
36622
|
const { route53 } = makeClients(config2);
|
|
36460
|
-
await route53.send(new ChangeResourceRecordSetsCommand({
|
|
36623
|
+
const result = await route53.send(new ChangeResourceRecordSetsCommand({
|
|
36461
36624
|
HostedZoneId: normalizeZoneId(hostedZoneId),
|
|
36462
36625
|
ChangeBatch: {
|
|
36626
|
+
...options?.comment ? { Comment: options.comment } : {},
|
|
36463
36627
|
Changes: [{ Action: "DELETE", ResourceRecordSet: recordToRrs(record) }]
|
|
36464
36628
|
}
|
|
36465
36629
|
}));
|
|
36630
|
+
return { changeId: cleanChangeId(result.ChangeInfo?.Id) };
|
|
36466
36631
|
}
|
|
36467
|
-
async function upsertRecords(hostedZoneId, records, config2) {
|
|
36632
|
+
async function upsertRecords(hostedZoneId, records, config2, options) {
|
|
36468
36633
|
if (records.length === 0)
|
|
36469
|
-
return;
|
|
36634
|
+
return { changeId: null };
|
|
36470
36635
|
const { route53 } = makeClients(config2);
|
|
36471
36636
|
const changes = records.map((r4) => ({
|
|
36472
36637
|
Action: "UPSERT",
|
|
36473
36638
|
ResourceRecordSet: recordToRrs(r4)
|
|
36474
36639
|
}));
|
|
36475
|
-
await route53.send(new ChangeResourceRecordSetsCommand({
|
|
36640
|
+
const result = await route53.send(new ChangeResourceRecordSetsCommand({
|
|
36476
36641
|
HostedZoneId: normalizeZoneId(hostedZoneId),
|
|
36477
|
-
ChangeBatch: {
|
|
36642
|
+
ChangeBatch: {
|
|
36643
|
+
...options?.comment ? { Comment: options.comment } : {},
|
|
36644
|
+
Changes: changes
|
|
36645
|
+
}
|
|
36478
36646
|
}));
|
|
36647
|
+
return { changeId: cleanChangeId(result.ChangeInfo?.Id) };
|
|
36479
36648
|
}
|
|
36480
36649
|
function createRoute53Provider(config2) {
|
|
36481
36650
|
const cfg = config2 ?? getConfig2();
|
|
@@ -36538,7 +36707,7 @@ function createRoute53Provider(config2) {
|
|
|
36538
36707
|
expires: detail.expiry,
|
|
36539
36708
|
nameservers: detail.nameservers,
|
|
36540
36709
|
status: "active",
|
|
36541
|
-
auto_renew: detail.auto_renew
|
|
36710
|
+
auto_renew: detail.auto_renew ?? false
|
|
36542
36711
|
};
|
|
36543
36712
|
},
|
|
36544
36713
|
async registerDomain(domain, contact, options = {}) {
|
|
@@ -36653,6 +36822,7 @@ function createRoute53Provider(config2) {
|
|
|
36653
36822
|
var init_route53 = __esm(() => {
|
|
36654
36823
|
init_dist_es12();
|
|
36655
36824
|
init_dist_es13();
|
|
36825
|
+
init_dist_es9();
|
|
36656
36826
|
});
|
|
36657
36827
|
|
|
36658
36828
|
// src/lib/env-aliases.ts
|
|
@@ -36689,7 +36859,16 @@ function providerEnvNames(provider) {
|
|
|
36689
36859
|
case "cloudflare":
|
|
36690
36860
|
return flattenEnvNames([CLOUDFLARE_ENV.apiToken, CLOUDFLARE_ENV.apiKey, CLOUDFLARE_ENV.email, CLOUDFLARE_ENV.accountId]);
|
|
36691
36861
|
case "route53":
|
|
36692
|
-
return [
|
|
36862
|
+
return flattenEnvNames([
|
|
36863
|
+
ROUTE53_ENV.profile,
|
|
36864
|
+
ROUTE53_ENV.accessKeyId,
|
|
36865
|
+
ROUTE53_ENV.secretAccessKey,
|
|
36866
|
+
ROUTE53_ENV.sessionToken,
|
|
36867
|
+
ROUTE53_ENV.region,
|
|
36868
|
+
ROUTE53_ENV.webIdentityTokenFile,
|
|
36869
|
+
ROUTE53_ENV.roleArn,
|
|
36870
|
+
ROUTE53_ENV.containerCredentials
|
|
36871
|
+
]);
|
|
36693
36872
|
default:
|
|
36694
36873
|
return [];
|
|
36695
36874
|
}
|
|
@@ -36709,13 +36888,18 @@ function hasProviderCredentials(provider, env = process.env) {
|
|
|
36709
36888
|
const keyMode = hasEveryEnv(env, [CLOUDFLARE_ENV.apiKey, CLOUDFLARE_ENV.email]);
|
|
36710
36889
|
return tokenMode || keyMode;
|
|
36711
36890
|
}
|
|
36712
|
-
case "route53":
|
|
36713
|
-
|
|
36891
|
+
case "route53": {
|
|
36892
|
+
const profileMode = !!firstEnv(env, ROUTE53_ENV.profile);
|
|
36893
|
+
const keyMode = hasEveryEnv(env, [ROUTE53_ENV.accessKeyId, ROUTE53_ENV.secretAccessKey]);
|
|
36894
|
+
const webIdentityMode = hasEveryEnv(env, [ROUTE53_ENV.webIdentityTokenFile, ROUTE53_ENV.roleArn]);
|
|
36895
|
+
const containerMode = !!firstEnv(env, ROUTE53_ENV.containerCredentials);
|
|
36896
|
+
return profileMode || keyMode || webIdentityMode || containerMode;
|
|
36897
|
+
}
|
|
36714
36898
|
default:
|
|
36715
36899
|
return false;
|
|
36716
36900
|
}
|
|
36717
36901
|
}
|
|
36718
|
-
var NAMECHEAP_ENV, GODADDY_ENV, BRANDSIGHT_ENV, SEDO_ENV, CLOUDFLARE_ENV;
|
|
36902
|
+
var NAMECHEAP_ENV, GODADDY_ENV, BRANDSIGHT_ENV, SEDO_ENV, CLOUDFLARE_ENV, ROUTE53_ENV;
|
|
36719
36903
|
var init_env_aliases = __esm(() => {
|
|
36720
36904
|
NAMECHEAP_ENV = {
|
|
36721
36905
|
apiKey: ["NAMECHEAP_API_KEY"],
|
|
@@ -36745,6 +36929,19 @@ var init_env_aliases = __esm(() => {
|
|
|
36745
36929
|
email: ["CLOUDFLARE_EMAIL"],
|
|
36746
36930
|
accountId: ["CLOUDFLARE_ACCOUNT_ID"]
|
|
36747
36931
|
};
|
|
36932
|
+
ROUTE53_ENV = {
|
|
36933
|
+
accessKeyId: ["ROUTE53_ACCESS_KEY_ID", "AWS_ACCESS_KEY_ID"],
|
|
36934
|
+
secretAccessKey: ["ROUTE53_SECRET_ACCESS_KEY", "AWS_SECRET_ACCESS_KEY"],
|
|
36935
|
+
sessionToken: ["ROUTE53_SESSION_TOKEN", "AWS_SESSION_TOKEN"],
|
|
36936
|
+
profile: ["ROUTE53_AWS_PROFILE", "AWS_PROFILE"],
|
|
36937
|
+
region: ["ROUTE53_REGION", "ROUTE53_AWS_REGION", "AWS_REGION"],
|
|
36938
|
+
webIdentityTokenFile: ["AWS_WEB_IDENTITY_TOKEN_FILE"],
|
|
36939
|
+
roleArn: ["AWS_ROLE_ARN"],
|
|
36940
|
+
containerCredentials: [
|
|
36941
|
+
"AWS_CONTAINER_CREDENTIALS_RELATIVE_URI",
|
|
36942
|
+
"AWS_CONTAINER_CREDENTIALS_FULL_URI"
|
|
36943
|
+
]
|
|
36944
|
+
};
|
|
36748
36945
|
});
|
|
36749
36946
|
|
|
36750
36947
|
// src/lib/cloudflare-auth.ts
|
|
@@ -36774,12 +36971,12 @@ var init_cloudflare_auth = __esm(() => {
|
|
|
36774
36971
|
function getConfig3() {
|
|
36775
36972
|
return resolveCloudflareConfig();
|
|
36776
36973
|
}
|
|
36777
|
-
function
|
|
36974
|
+
function checkCredentials(cfg) {
|
|
36778
36975
|
cloudflareAuthHeaders(cfg);
|
|
36779
36976
|
}
|
|
36780
36977
|
async function cfFetch(path, opts = {}) {
|
|
36781
36978
|
const cfg = opts.config ?? getConfig3();
|
|
36782
|
-
|
|
36979
|
+
checkCredentials(cfg);
|
|
36783
36980
|
const res = await fetch(`${CF_BASE}${path}`, {
|
|
36784
36981
|
method: opts.method ?? "GET",
|
|
36785
36982
|
headers: {
|
|
@@ -37689,12 +37886,16 @@ __export(exports_sedo, {
|
|
|
37689
37886
|
resolveSedoConfig: () => resolveSedoConfig,
|
|
37690
37887
|
removeDomainFromSedo: () => removeDomainFromSedo,
|
|
37691
37888
|
recordSedoPurchase: () => recordSedoPurchase,
|
|
37889
|
+
parseSedoSearchResponse: () => parseSedoSearchResponse,
|
|
37890
|
+
parseSedoFault: () => parseSedoFault,
|
|
37692
37891
|
listSedoPortfolio: () => listSedoPortfolio,
|
|
37693
37892
|
getSedoConfig: () => getSedoConfig,
|
|
37694
37893
|
editDomainOnSedo: () => editDomainOnSedo,
|
|
37695
37894
|
checkSedoStatus: () => checkSedoStatus,
|
|
37696
37895
|
checkSedoBlacklist: () => checkSedoBlacklist,
|
|
37697
|
-
|
|
37896
|
+
buildSedoSearchParams: () => buildSedoSearchParams,
|
|
37897
|
+
addDomainToSedo: () => addDomainToSedo,
|
|
37898
|
+
SEDO_CURRENCY_CODES: () => SEDO_CURRENCY_CODES
|
|
37698
37899
|
});
|
|
37699
37900
|
function resolveSedoConfig(env = process.env) {
|
|
37700
37901
|
return {
|
|
@@ -37721,17 +37922,17 @@ function sedoCapability(env = process.env) {
|
|
|
37721
37922
|
notes: configured ? "Credentials present; Sedo supports marketplace search, portfolio listing, listing edits, and recorded purchases, but not registrar DNS or direct self-serve registration through this CLI." : "Not configured; Sedo is marketplace-only here, not registrar DNS."
|
|
37722
37923
|
};
|
|
37723
37924
|
}
|
|
37724
|
-
function
|
|
37925
|
+
function buildAuthParams(config2) {
|
|
37725
37926
|
return {
|
|
37726
|
-
partnerid:
|
|
37727
|
-
signkey:
|
|
37728
|
-
username:
|
|
37729
|
-
password:
|
|
37927
|
+
partnerid: config2.partnerId,
|
|
37928
|
+
signkey: config2.signKey,
|
|
37929
|
+
username: config2.username,
|
|
37930
|
+
password: config2.password
|
|
37730
37931
|
};
|
|
37731
37932
|
}
|
|
37732
|
-
async function
|
|
37933
|
+
async function sedoRequestRaw(action, config2, extraParams = {}) {
|
|
37733
37934
|
const params = new URLSearchParams({
|
|
37734
|
-
...
|
|
37935
|
+
...buildAuthParams(config2),
|
|
37735
37936
|
output_method: "xml",
|
|
37736
37937
|
...Object.fromEntries(Object.entries(extraParams).map(([k4, v2]) => [k4, String(v2)]))
|
|
37737
37938
|
});
|
|
@@ -37740,9 +37941,23 @@ async function sedoRequest(action, config2, extraParams = {}) {
|
|
|
37740
37941
|
if (!resp.ok) {
|
|
37741
37942
|
throw new Error(`Sedo API ${action} failed: ${resp.status} ${resp.statusText}`);
|
|
37742
37943
|
}
|
|
37743
|
-
|
|
37944
|
+
return resp.text();
|
|
37945
|
+
}
|
|
37946
|
+
async function sedoRequest(action, config2, extraParams = {}) {
|
|
37947
|
+
const text = await sedoRequestRaw(action, config2, extraParams);
|
|
37948
|
+
const fault = parseSedoFault(text);
|
|
37949
|
+
if (fault) {
|
|
37950
|
+
throw new Error(`Sedo API ${action} fault ${fault.code}: ${fault.message}`);
|
|
37951
|
+
}
|
|
37744
37952
|
return parseSedoXml(text);
|
|
37745
37953
|
}
|
|
37954
|
+
function parseSedoFault(xml) {
|
|
37955
|
+
if (!/<SEDOFAULT\b/i.test(xml))
|
|
37956
|
+
return null;
|
|
37957
|
+
const code = xml.match(/<faultcode[^>]*>([^<]*)<\/faultcode>/i)?.[1]?.trim() || "UNKNOWN";
|
|
37958
|
+
const message = xml.match(/<faultstring[^>]*>([^<]*)<\/faultstring>/i)?.[1]?.trim() || code;
|
|
37959
|
+
return { code, message };
|
|
37960
|
+
}
|
|
37746
37961
|
function parseSedoXml(xml) {
|
|
37747
37962
|
const result = {};
|
|
37748
37963
|
const itemMatches = xml.match(/<item[^>]*>([\s\S]*?)<\/item>/g);
|
|
@@ -37768,11 +37983,12 @@ function parseSedoItemXml(itemXml) {
|
|
|
37768
37983
|
}
|
|
37769
37984
|
return obj;
|
|
37770
37985
|
}
|
|
37771
|
-
|
|
37772
|
-
const config2 = getSedoConfig();
|
|
37986
|
+
function buildSedoSearchParams(query, options = {}) {
|
|
37773
37987
|
const params = {
|
|
37774
|
-
|
|
37775
|
-
|
|
37988
|
+
keyword: query,
|
|
37989
|
+
kwtype: options.kwtype || "C",
|
|
37990
|
+
resultsize: options.limit || 100,
|
|
37991
|
+
language: options.language || "en"
|
|
37776
37992
|
};
|
|
37777
37993
|
if (options.tld)
|
|
37778
37994
|
params.tld = options.tld;
|
|
@@ -37780,20 +37996,35 @@ async function searchSedoDomains(query, options = {}) {
|
|
|
37780
37996
|
params.price_min = options.minPrice;
|
|
37781
37997
|
if (options.maxPrice)
|
|
37782
37998
|
params.price_max = options.maxPrice;
|
|
37783
|
-
|
|
37999
|
+
return params;
|
|
38000
|
+
}
|
|
38001
|
+
function parseSedoSearchResponse(xml) {
|
|
38002
|
+
const fault = parseSedoFault(xml);
|
|
38003
|
+
if (fault) {
|
|
38004
|
+
throw new Error(`Sedo DomainSearch fault ${fault.code}: ${fault.message}`);
|
|
38005
|
+
}
|
|
38006
|
+
const response = parseSedoXml(xml);
|
|
37784
38007
|
const items = response.items || [];
|
|
37785
|
-
const domains = items.map((item) =>
|
|
37786
|
-
|
|
37787
|
-
|
|
37788
|
-
|
|
37789
|
-
|
|
37790
|
-
|
|
37791
|
-
|
|
37792
|
-
|
|
37793
|
-
|
|
37794
|
-
|
|
37795
|
-
|
|
37796
|
-
|
|
38008
|
+
const domains = items.map((item) => {
|
|
38009
|
+
const rawPrice = item.price;
|
|
38010
|
+
const price = rawPrice !== undefined ? parseFloat(rawPrice) : undefined;
|
|
38011
|
+
const currencyCode = item.currency;
|
|
38012
|
+
const currency = currencyCode !== undefined ? SEDO_CURRENCY_CODES[currencyCode] || currencyCode : undefined;
|
|
38013
|
+
return {
|
|
38014
|
+
domain: item.domain || "",
|
|
38015
|
+
forSale: true,
|
|
38016
|
+
price: price && price > 0 ? price : undefined,
|
|
38017
|
+
currency: price && price > 0 ? currency : undefined,
|
|
38018
|
+
status: item.type || undefined
|
|
38019
|
+
};
|
|
38020
|
+
});
|
|
38021
|
+
return { domains, total: domains.length };
|
|
38022
|
+
}
|
|
38023
|
+
async function searchSedoDomains(query, options = {}) {
|
|
38024
|
+
const config2 = getSedoConfig();
|
|
38025
|
+
const params = buildSedoSearchParams(query, options);
|
|
38026
|
+
const xml = await sedoRequestRaw("DomainSearch", config2, params);
|
|
38027
|
+
return parseSedoSearchResponse(xml);
|
|
37797
38028
|
}
|
|
37798
38029
|
async function searchHealthDomains(options = {}) {
|
|
37799
38030
|
return searchSedoDomains("health", { ...options, limit: options.limit || 100 });
|
|
@@ -37917,11 +38148,16 @@ function recordSedoPurchase(domain, price, orderId) {
|
|
|
37917
38148
|
});
|
|
37918
38149
|
return created;
|
|
37919
38150
|
}
|
|
37920
|
-
var BASE_URL = "https://api.sedo.com/api/v1/";
|
|
38151
|
+
var BASE_URL = "https://api.sedo.com/api/v1/", SEDO_CURRENCY_CODES;
|
|
37921
38152
|
var init_sedo = __esm(() => {
|
|
37922
38153
|
init_env_aliases();
|
|
37923
38154
|
init_domains();
|
|
37924
38155
|
init_domain_history();
|
|
38156
|
+
SEDO_CURRENCY_CODES = {
|
|
38157
|
+
"0": "EUR",
|
|
38158
|
+
"1": "USD",
|
|
38159
|
+
"2": "GBP"
|
|
38160
|
+
};
|
|
37925
38161
|
});
|
|
37926
38162
|
|
|
37927
38163
|
// src/lib/creds-check.ts
|
|
@@ -37937,12 +38173,16 @@ function missingStatus(provider, detail) {
|
|
|
37937
38173
|
}
|
|
37938
38174
|
function checkProvisioningCredentials(env = process.env) {
|
|
37939
38175
|
const out = [];
|
|
37940
|
-
const
|
|
37941
|
-
const
|
|
37942
|
-
|
|
37943
|
-
|
|
38176
|
+
const awsProfile = firstEnv(env, ROUTE53_ENV.profile);
|
|
38177
|
+
const awsAccessKey = firstEnv(env, ROUTE53_ENV.accessKeyId);
|
|
38178
|
+
const awsSecretKey = firstEnv(env, ROUTE53_ENV.secretAccessKey);
|
|
38179
|
+
const hasAwsKeys = !!(awsAccessKey && awsSecretKey);
|
|
38180
|
+
const hasWebIdentity = !!(firstEnv(env, ROUTE53_ENV.webIdentityTokenFile) && firstEnv(env, ROUTE53_ENV.roleArn));
|
|
38181
|
+
const hasContainerCredentials = !!firstEnv(env, ROUTE53_ENV.containerCredentials);
|
|
38182
|
+
if (awsProfile || hasAwsKeys || hasWebIdentity || hasContainerCredentials) {
|
|
38183
|
+
out.push(configuredStatus("route53", awsProfile ? `profile:${awsProfile.value}` : hasAwsKeys ? "access-keys" : hasWebIdentity ? "web-identity" : "container-credentials", "AWS credentials present (Route 53 Domains API uses us-east-1)"));
|
|
37944
38184
|
} else {
|
|
37945
|
-
out.push(missingStatus("route53", "Set AWS_PROFILE or
|
|
38185
|
+
out.push(missingStatus("route53", "Set ROUTE53_AWS_PROFILE/AWS_PROFILE, ROUTE53_ACCESS_KEY_ID+ROUTE53_SECRET_ACCESS_KEY, or AWS provider-chain credentials"));
|
|
37946
38186
|
}
|
|
37947
38187
|
const cf = resolveCloudflareConfig(env);
|
|
37948
38188
|
const cfMode = cf.apiToken ? "token" : cf.apiKey && cf.email ? "global-key" : "none";
|
|
@@ -38066,7 +38306,7 @@ function registerSedoCommand(program2) {
|
|
|
38066
38306
|
}
|
|
38067
38307
|
console.log(`Sedo marketplace \u2014 "${keyword}" (${result.total} total):`);
|
|
38068
38308
|
for (const d4 of result.domains) {
|
|
38069
|
-
const price = d4.price ? ` \u2014
|
|
38309
|
+
const price = d4.price ? ` \u2014 ${d4.price.toLocaleString()} ${d4.currency || ""}`.trimEnd() : " \u2014 make offer";
|
|
38070
38310
|
const premium = d4.isPremium ? " [PREMIUM]" : "";
|
|
38071
38311
|
console.log(` ${d4.domain}${price}${premium}`);
|
|
38072
38312
|
}
|
|
@@ -45780,691 +46020,6 @@ var init_interactive = __esm(() => {
|
|
|
45780
46020
|
init_App();
|
|
45781
46021
|
});
|
|
45782
46022
|
|
|
45783
|
-
// node_modules/@hasna/events/dist/commander.js
|
|
45784
|
-
var exports_commander = {};
|
|
45785
|
-
__export(exports_commander, {
|
|
45786
|
-
registerWebhookCommands: () => registerWebhookCommands,
|
|
45787
|
-
registerEventsCommands: () => registerEventsCommands,
|
|
45788
|
-
registerEventCommands: () => registerEventCommands
|
|
45789
|
-
});
|
|
45790
|
-
import { chmod, mkdir, readFile as readFile2, rename, writeFile as writeFile2 } from "fs/promises";
|
|
45791
|
-
import { existsSync as existsSync4 } from "fs";
|
|
45792
|
-
import { homedir as homedir5 } from "os";
|
|
45793
|
-
import { join as join5 } from "path";
|
|
45794
|
-
import { createHmac, timingSafeEqual } from "crypto";
|
|
45795
|
-
import { randomUUID } from "crypto";
|
|
45796
|
-
import { spawn } from "child_process";
|
|
45797
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
45798
|
-
function getPathValue(input, path) {
|
|
45799
|
-
return path.split(".").reduce((value, part) => {
|
|
45800
|
-
if (value && typeof value === "object" && part in value) {
|
|
45801
|
-
return value[part];
|
|
45802
|
-
}
|
|
45803
|
-
return;
|
|
45804
|
-
}, input);
|
|
45805
|
-
}
|
|
45806
|
-
function wildcardToRegExp(pattern) {
|
|
45807
|
-
const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, ".*");
|
|
45808
|
-
return new RegExp(`^${escaped}$`);
|
|
45809
|
-
}
|
|
45810
|
-
function matchString(value, matcher) {
|
|
45811
|
-
if (matcher === undefined)
|
|
45812
|
-
return true;
|
|
45813
|
-
if (value === undefined)
|
|
45814
|
-
return false;
|
|
45815
|
-
const matchers = Array.isArray(matcher) ? matcher : [matcher];
|
|
45816
|
-
return matchers.some((item) => wildcardToRegExp(item).test(value));
|
|
45817
|
-
}
|
|
45818
|
-
function matchRecord(input, matcher) {
|
|
45819
|
-
if (!matcher)
|
|
45820
|
-
return true;
|
|
45821
|
-
return Object.entries(matcher).every(([path, expected]) => {
|
|
45822
|
-
const actual = getPathValue(input, path);
|
|
45823
|
-
if (typeof expected === "string" || Array.isArray(expected)) {
|
|
45824
|
-
return matchString(actual === undefined ? undefined : String(actual), expected);
|
|
45825
|
-
}
|
|
45826
|
-
return actual === expected;
|
|
45827
|
-
});
|
|
45828
|
-
}
|
|
45829
|
-
function eventMatchesFilter(event, filter) {
|
|
45830
|
-
return matchString(event.source, filter.source) && matchString(event.type, filter.type) && matchString(event.subject, filter.subject) && matchString(event.severity, filter.severity) && matchRecord(event.data, filter.data) && matchRecord(event.metadata, filter.metadata);
|
|
45831
|
-
}
|
|
45832
|
-
function channelMatchesEvent(channel, event) {
|
|
45833
|
-
if (!channel.enabled)
|
|
45834
|
-
return false;
|
|
45835
|
-
if (!channel.filters || channel.filters.length === 0)
|
|
45836
|
-
return true;
|
|
45837
|
-
return channel.filters.some((filter) => eventMatchesFilter(event, filter));
|
|
45838
|
-
}
|
|
45839
|
-
function getEventsDataDir(override) {
|
|
45840
|
-
return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join5(homedir5(), ".hasna", "events");
|
|
45841
|
-
}
|
|
45842
|
-
|
|
45843
|
-
class JsonEventsStore {
|
|
45844
|
-
dataDir;
|
|
45845
|
-
channelsPath;
|
|
45846
|
-
eventsPath;
|
|
45847
|
-
deliveriesPath;
|
|
45848
|
-
constructor(dataDir = getEventsDataDir()) {
|
|
45849
|
-
this.dataDir = dataDir;
|
|
45850
|
-
this.channelsPath = join5(dataDir, "channels.json");
|
|
45851
|
-
this.eventsPath = join5(dataDir, "events.json");
|
|
45852
|
-
this.deliveriesPath = join5(dataDir, "deliveries.json");
|
|
45853
|
-
}
|
|
45854
|
-
async init() {
|
|
45855
|
-
await mkdir(this.dataDir, { recursive: true, mode: 448 });
|
|
45856
|
-
await chmod(this.dataDir, 448).catch(() => {
|
|
45857
|
-
return;
|
|
45858
|
-
});
|
|
45859
|
-
await this.ensureArrayFile(this.channelsPath);
|
|
45860
|
-
await this.ensureArrayFile(this.eventsPath);
|
|
45861
|
-
await this.ensureArrayFile(this.deliveriesPath);
|
|
45862
|
-
}
|
|
45863
|
-
async addChannel(channel) {
|
|
45864
|
-
await this.init();
|
|
45865
|
-
const channels = await this.readJson(this.channelsPath, []);
|
|
45866
|
-
const index = channels.findIndex((item) => item.id === channel.id);
|
|
45867
|
-
if (index >= 0) {
|
|
45868
|
-
channels[index] = { ...channel, createdAt: channels[index].createdAt, updatedAt: new Date().toISOString() };
|
|
45869
|
-
} else {
|
|
45870
|
-
channels.push(channel);
|
|
45871
|
-
}
|
|
45872
|
-
await this.writeJson(this.channelsPath, channels);
|
|
45873
|
-
return index >= 0 ? channels[index] : channel;
|
|
45874
|
-
}
|
|
45875
|
-
async listChannels() {
|
|
45876
|
-
await this.init();
|
|
45877
|
-
return this.readJson(this.channelsPath, []);
|
|
45878
|
-
}
|
|
45879
|
-
async getChannel(id) {
|
|
45880
|
-
const channels = await this.listChannels();
|
|
45881
|
-
return channels.find((channel) => channel.id === id);
|
|
45882
|
-
}
|
|
45883
|
-
async removeChannel(id) {
|
|
45884
|
-
await this.init();
|
|
45885
|
-
const channels = await this.readJson(this.channelsPath, []);
|
|
45886
|
-
const next = channels.filter((channel) => channel.id !== id);
|
|
45887
|
-
await this.writeJson(this.channelsPath, next);
|
|
45888
|
-
return next.length !== channels.length;
|
|
45889
|
-
}
|
|
45890
|
-
async appendEvent(event) {
|
|
45891
|
-
await this.init();
|
|
45892
|
-
const events = await this.readJson(this.eventsPath, []);
|
|
45893
|
-
events.push(event);
|
|
45894
|
-
await this.writeJson(this.eventsPath, events);
|
|
45895
|
-
return event;
|
|
45896
|
-
}
|
|
45897
|
-
async listEvents() {
|
|
45898
|
-
await this.init();
|
|
45899
|
-
return this.readJson(this.eventsPath, []);
|
|
45900
|
-
}
|
|
45901
|
-
async findEventByIdentity(identity2) {
|
|
45902
|
-
const events = await this.listEvents();
|
|
45903
|
-
return events.find((event) => identity2.id !== undefined && event.id === identity2.id || identity2.dedupeKey !== undefined && event.dedupeKey === identity2.dedupeKey);
|
|
45904
|
-
}
|
|
45905
|
-
async appendDelivery(result) {
|
|
45906
|
-
await this.init();
|
|
45907
|
-
const deliveries = await this.readJson(this.deliveriesPath, []);
|
|
45908
|
-
deliveries.push(result);
|
|
45909
|
-
await this.writeJson(this.deliveriesPath, deliveries);
|
|
45910
|
-
return result;
|
|
45911
|
-
}
|
|
45912
|
-
async listDeliveries() {
|
|
45913
|
-
await this.init();
|
|
45914
|
-
return this.readJson(this.deliveriesPath, []);
|
|
45915
|
-
}
|
|
45916
|
-
async exportData() {
|
|
45917
|
-
return {
|
|
45918
|
-
channels: await this.listChannels(),
|
|
45919
|
-
events: await this.listEvents(),
|
|
45920
|
-
deliveries: await this.listDeliveries()
|
|
45921
|
-
};
|
|
45922
|
-
}
|
|
45923
|
-
async ensureArrayFile(path) {
|
|
45924
|
-
if (!existsSync4(path)) {
|
|
45925
|
-
await writeFile2(path, `[]
|
|
45926
|
-
`, { encoding: "utf-8", mode: 384 });
|
|
45927
|
-
}
|
|
45928
|
-
await chmod(path, 384).catch(() => {
|
|
45929
|
-
return;
|
|
45930
|
-
});
|
|
45931
|
-
}
|
|
45932
|
-
async readJson(path, fallback) {
|
|
45933
|
-
try {
|
|
45934
|
-
const raw = await readFile2(path, "utf-8");
|
|
45935
|
-
if (!raw.trim())
|
|
45936
|
-
return fallback;
|
|
45937
|
-
return JSON.parse(raw);
|
|
45938
|
-
} catch (error) {
|
|
45939
|
-
if (error.code === "ENOENT")
|
|
45940
|
-
return fallback;
|
|
45941
|
-
throw error;
|
|
45942
|
-
}
|
|
45943
|
-
}
|
|
45944
|
-
async writeJson(path, value) {
|
|
45945
|
-
const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
45946
|
-
await writeFile2(tempPath, `${JSON.stringify(value, null, 2)}
|
|
45947
|
-
`, { encoding: "utf-8", mode: 384 });
|
|
45948
|
-
await rename(tempPath, path);
|
|
45949
|
-
await chmod(path, 384).catch(() => {
|
|
45950
|
-
return;
|
|
45951
|
-
});
|
|
45952
|
-
}
|
|
45953
|
-
}
|
|
45954
|
-
function buildSignatureBase(timestamp, body) {
|
|
45955
|
-
return `${timestamp}.${body}`;
|
|
45956
|
-
}
|
|
45957
|
-
function signPayload(secret, timestamp, body) {
|
|
45958
|
-
const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
|
|
45959
|
-
return `sha256=${digest}`;
|
|
45960
|
-
}
|
|
45961
|
-
function now() {
|
|
45962
|
-
return new Date().toISOString();
|
|
45963
|
-
}
|
|
45964
|
-
function truncate(value, max = 4096) {
|
|
45965
|
-
return value.length > max ? `${value.slice(0, max)}...` : value;
|
|
45966
|
-
}
|
|
45967
|
-
function buildWebhookRequest(event, channel) {
|
|
45968
|
-
if (!channel.webhook)
|
|
45969
|
-
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
45970
|
-
const body = JSON.stringify(event);
|
|
45971
|
-
const timestamp = event.time;
|
|
45972
|
-
const headers = {
|
|
45973
|
-
"Content-Type": "application/json",
|
|
45974
|
-
"User-Agent": "@hasna/events",
|
|
45975
|
-
"X-Hasna-Event-Id": event.id,
|
|
45976
|
-
"X-Hasna-Event-Type": event.type,
|
|
45977
|
-
"X-Hasna-Timestamp": timestamp,
|
|
45978
|
-
...channel.webhook.headers
|
|
45979
|
-
};
|
|
45980
|
-
if (channel.webhook.secret) {
|
|
45981
|
-
headers["X-Hasna-Signature"] = signPayload(channel.webhook.secret, timestamp, body);
|
|
45982
|
-
}
|
|
45983
|
-
return { body, headers };
|
|
45984
|
-
}
|
|
45985
|
-
async function dispatchWebhook(event, channel, options = {}) {
|
|
45986
|
-
if (!channel.webhook)
|
|
45987
|
-
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
45988
|
-
const startedAt = now();
|
|
45989
|
-
const { body, headers } = buildWebhookRequest(event, channel);
|
|
45990
|
-
const controller = new AbortController;
|
|
45991
|
-
const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
|
|
45992
|
-
try {
|
|
45993
|
-
const response = await (options.fetchImpl ?? fetch)(channel.webhook.url, {
|
|
45994
|
-
method: "POST",
|
|
45995
|
-
headers,
|
|
45996
|
-
body,
|
|
45997
|
-
signal: controller.signal
|
|
45998
|
-
});
|
|
45999
|
-
const responseBody = truncate(await response.text());
|
|
46000
|
-
return {
|
|
46001
|
-
attempt: 1,
|
|
46002
|
-
status: response.ok ? "success" : "failed",
|
|
46003
|
-
startedAt,
|
|
46004
|
-
completedAt: now(),
|
|
46005
|
-
responseStatus: response.status,
|
|
46006
|
-
responseBody,
|
|
46007
|
-
error: response.ok ? undefined : `Webhook returned HTTP ${response.status}`
|
|
46008
|
-
};
|
|
46009
|
-
} catch (error) {
|
|
46010
|
-
return {
|
|
46011
|
-
attempt: 1,
|
|
46012
|
-
status: "failed",
|
|
46013
|
-
startedAt,
|
|
46014
|
-
completedAt: now(),
|
|
46015
|
-
error: error instanceof Error ? error.message : String(error)
|
|
46016
|
-
};
|
|
46017
|
-
} finally {
|
|
46018
|
-
clearTimeout(timeout);
|
|
46019
|
-
}
|
|
46020
|
-
}
|
|
46021
|
-
async function dispatchCommand(event, channel) {
|
|
46022
|
-
if (!channel.command)
|
|
46023
|
-
throw new Error(`Channel ${channel.id} has no command config`);
|
|
46024
|
-
const startedAt = now();
|
|
46025
|
-
const eventJson = JSON.stringify(event);
|
|
46026
|
-
const env = {
|
|
46027
|
-
...process.env,
|
|
46028
|
-
...channel.command.env,
|
|
46029
|
-
HASNA_CHANNEL_ID: channel.id,
|
|
46030
|
-
HASNA_EVENT_ID: event.id,
|
|
46031
|
-
HASNA_EVENT_TYPE: event.type,
|
|
46032
|
-
HASNA_EVENT_SOURCE: event.source,
|
|
46033
|
-
HASNA_EVENT_SUBJECT: event.subject ?? "",
|
|
46034
|
-
HASNA_EVENT_SEVERITY: event.severity,
|
|
46035
|
-
HASNA_EVENT_TIME: event.time,
|
|
46036
|
-
HASNA_EVENT_DEDUPE_KEY: event.dedupeKey ?? "",
|
|
46037
|
-
HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
|
|
46038
|
-
HASNA_EVENT_JSON: eventJson
|
|
46039
|
-
};
|
|
46040
|
-
return new Promise((resolve3) => {
|
|
46041
|
-
const child = spawn(channel.command.command, channel.command.args ?? [], {
|
|
46042
|
-
cwd: channel.command.cwd,
|
|
46043
|
-
env,
|
|
46044
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
46045
|
-
});
|
|
46046
|
-
let stdout = "";
|
|
46047
|
-
let stderr = "";
|
|
46048
|
-
const timeout = setTimeout(() => child.kill("SIGTERM"), channel.command.timeoutMs ?? 15000);
|
|
46049
|
-
child.stdin.end(eventJson);
|
|
46050
|
-
child.stdout.on("data", (chunk) => {
|
|
46051
|
-
stdout += chunk.toString();
|
|
46052
|
-
});
|
|
46053
|
-
child.stderr.on("data", (chunk) => {
|
|
46054
|
-
stderr += chunk.toString();
|
|
46055
|
-
});
|
|
46056
|
-
child.on("error", (error) => {
|
|
46057
|
-
clearTimeout(timeout);
|
|
46058
|
-
resolve3({
|
|
46059
|
-
attempt: 1,
|
|
46060
|
-
status: "failed",
|
|
46061
|
-
startedAt,
|
|
46062
|
-
completedAt: now(),
|
|
46063
|
-
stdout: truncate(stdout),
|
|
46064
|
-
stderr: truncate(stderr),
|
|
46065
|
-
error: error.message
|
|
46066
|
-
});
|
|
46067
|
-
});
|
|
46068
|
-
child.on("close", (code, signal) => {
|
|
46069
|
-
clearTimeout(timeout);
|
|
46070
|
-
const success = code === 0;
|
|
46071
|
-
resolve3({
|
|
46072
|
-
attempt: 1,
|
|
46073
|
-
status: success ? "success" : "failed",
|
|
46074
|
-
startedAt,
|
|
46075
|
-
completedAt: now(),
|
|
46076
|
-
stdout: truncate(stdout),
|
|
46077
|
-
stderr: truncate(stderr),
|
|
46078
|
-
error: success ? undefined : `Command exited with ${signal ? `signal ${signal}` : `code ${code}`}`
|
|
46079
|
-
});
|
|
46080
|
-
});
|
|
46081
|
-
});
|
|
46082
|
-
}
|
|
46083
|
-
async function dispatchChannel(event, channel, options = {}) {
|
|
46084
|
-
if (channel.transport === "webhook")
|
|
46085
|
-
return dispatchWebhook(event, channel, options);
|
|
46086
|
-
if (channel.transport === "command")
|
|
46087
|
-
return dispatchCommand(event, channel);
|
|
46088
|
-
return {
|
|
46089
|
-
attempt: 1,
|
|
46090
|
-
status: "skipped",
|
|
46091
|
-
startedAt: now(),
|
|
46092
|
-
completedAt: now(),
|
|
46093
|
-
error: `Unsupported transport: ${channel.transport}`
|
|
46094
|
-
};
|
|
46095
|
-
}
|
|
46096
|
-
function createDeliveryResult(event, channel, attempts) {
|
|
46097
|
-
const status = attempts.some((attempt) => attempt.status === "success") ? "success" : attempts.every((attempt) => attempt.status === "skipped") ? "skipped" : "failed";
|
|
46098
|
-
return {
|
|
46099
|
-
id: randomUUID(),
|
|
46100
|
-
eventId: event.id,
|
|
46101
|
-
channelId: channel.id,
|
|
46102
|
-
transport: channel.transport,
|
|
46103
|
-
status,
|
|
46104
|
-
attempts,
|
|
46105
|
-
createdAt: attempts[0]?.startedAt ?? now(),
|
|
46106
|
-
completedAt: attempts.at(-1)?.completedAt ?? now()
|
|
46107
|
-
};
|
|
46108
|
-
}
|
|
46109
|
-
function createEvent(input) {
|
|
46110
|
-
return {
|
|
46111
|
-
id: input.id ?? randomUUID2(),
|
|
46112
|
-
source: input.source,
|
|
46113
|
-
type: input.type,
|
|
46114
|
-
time: normalizeTime(input.time),
|
|
46115
|
-
subject: input.subject,
|
|
46116
|
-
severity: input.severity ?? "info",
|
|
46117
|
-
data: input.data ?? {},
|
|
46118
|
-
message: input.message,
|
|
46119
|
-
dedupeKey: input.dedupeKey,
|
|
46120
|
-
schemaVersion: input.schemaVersion ?? "1.0",
|
|
46121
|
-
metadata: input.metadata ?? {}
|
|
46122
|
-
};
|
|
46123
|
-
}
|
|
46124
|
-
|
|
46125
|
-
class EventsClient {
|
|
46126
|
-
store;
|
|
46127
|
-
redactors;
|
|
46128
|
-
transportOptions;
|
|
46129
|
-
constructor(options = {}) {
|
|
46130
|
-
this.store = options.store ?? new JsonEventsStore(options.dataDir);
|
|
46131
|
-
this.redactors = options.redactors ?? [];
|
|
46132
|
-
this.transportOptions = { fetchImpl: options.fetchImpl };
|
|
46133
|
-
}
|
|
46134
|
-
async addChannel(input) {
|
|
46135
|
-
const timestamp = new Date().toISOString();
|
|
46136
|
-
return this.store.addChannel({
|
|
46137
|
-
...input,
|
|
46138
|
-
createdAt: input.createdAt ?? timestamp,
|
|
46139
|
-
updatedAt: input.updatedAt ?? timestamp
|
|
46140
|
-
});
|
|
46141
|
-
}
|
|
46142
|
-
async listChannels() {
|
|
46143
|
-
return this.store.listChannels();
|
|
46144
|
-
}
|
|
46145
|
-
async removeChannel(id) {
|
|
46146
|
-
return this.store.removeChannel(id);
|
|
46147
|
-
}
|
|
46148
|
-
async emit(input, options = {}) {
|
|
46149
|
-
const event = options.redactSensitiveData === false ? createEvent(input) : redactSensitiveKeys(createEvent(input));
|
|
46150
|
-
if (options.dedupe !== false) {
|
|
46151
|
-
const existing = await this.store.findEventByIdentity({ id: input.id, dedupeKey: event.dedupeKey });
|
|
46152
|
-
if (existing) {
|
|
46153
|
-
return { event: existing, deliveries: [], deduped: true };
|
|
46154
|
-
}
|
|
46155
|
-
}
|
|
46156
|
-
await this.store.appendEvent(event);
|
|
46157
|
-
const deliveries = options.deliver === false ? [] : await this.deliver(event);
|
|
46158
|
-
return { event, deliveries, deduped: false };
|
|
46159
|
-
}
|
|
46160
|
-
async listEvents() {
|
|
46161
|
-
return this.store.listEvents();
|
|
46162
|
-
}
|
|
46163
|
-
async listDeliveries() {
|
|
46164
|
-
return this.store.listDeliveries();
|
|
46165
|
-
}
|
|
46166
|
-
async deliver(event) {
|
|
46167
|
-
const channels = await this.store.listChannels();
|
|
46168
|
-
const selected = channels.filter((channel) => channelMatchesEvent(channel, event));
|
|
46169
|
-
const deliveries = [];
|
|
46170
|
-
for (const channel of selected) {
|
|
46171
|
-
const eventForChannel = await this.applyRedaction(event, channel);
|
|
46172
|
-
const result = await this.deliverWithRetry(eventForChannel, channel);
|
|
46173
|
-
await this.store.appendDelivery(result);
|
|
46174
|
-
deliveries.push(result);
|
|
46175
|
-
}
|
|
46176
|
-
return deliveries;
|
|
46177
|
-
}
|
|
46178
|
-
async testChannel(id, input = {}) {
|
|
46179
|
-
const channel = await this.store.getChannel(id);
|
|
46180
|
-
if (!channel)
|
|
46181
|
-
throw new Error(`Channel not found: ${id}`);
|
|
46182
|
-
const event = createEvent({
|
|
46183
|
-
source: input.source ?? "hasna.events",
|
|
46184
|
-
type: input.type ?? "events.test",
|
|
46185
|
-
subject: input.subject ?? id,
|
|
46186
|
-
severity: input.severity ?? "info",
|
|
46187
|
-
data: input.data ?? { test: true },
|
|
46188
|
-
message: input.message ?? "Hasna events test delivery",
|
|
46189
|
-
dedupeKey: input.dedupeKey,
|
|
46190
|
-
schemaVersion: input.schemaVersion,
|
|
46191
|
-
metadata: input.metadata,
|
|
46192
|
-
time: input.time,
|
|
46193
|
-
id: input.id
|
|
46194
|
-
});
|
|
46195
|
-
const eventForChannel = await this.applyRedaction(event, channel);
|
|
46196
|
-
const result = await this.deliverWithRetry(eventForChannel, channel);
|
|
46197
|
-
await this.store.appendDelivery(result);
|
|
46198
|
-
return result;
|
|
46199
|
-
}
|
|
46200
|
-
async replay(options = {}) {
|
|
46201
|
-
const events = (await this.store.listEvents()).filter((event) => {
|
|
46202
|
-
if (options.eventId && event.id !== options.eventId)
|
|
46203
|
-
return false;
|
|
46204
|
-
if (options.source && event.source !== options.source)
|
|
46205
|
-
return false;
|
|
46206
|
-
if (options.type && event.type !== options.type)
|
|
46207
|
-
return false;
|
|
46208
|
-
return true;
|
|
46209
|
-
});
|
|
46210
|
-
if (options.dryRun)
|
|
46211
|
-
return { events, deliveries: [] };
|
|
46212
|
-
const deliveries = [];
|
|
46213
|
-
for (const event of events) {
|
|
46214
|
-
deliveries.push(...await this.deliver(event));
|
|
46215
|
-
}
|
|
46216
|
-
return { events, deliveries };
|
|
46217
|
-
}
|
|
46218
|
-
async applyRedaction(event, channel) {
|
|
46219
|
-
let next = redactPaths(event, channel.redact?.paths ?? [], channel.redact?.replacement ?? "[REDACTED]");
|
|
46220
|
-
for (const redactor of this.redactors) {
|
|
46221
|
-
next = await redactor(next, channel);
|
|
46222
|
-
}
|
|
46223
|
-
return next;
|
|
46224
|
-
}
|
|
46225
|
-
async deliverWithRetry(event, channel) {
|
|
46226
|
-
const policy = normalizeRetryPolicy(channel.retry);
|
|
46227
|
-
const attempts = [];
|
|
46228
|
-
for (let index = 0;index < policy.maxAttempts; index += 1) {
|
|
46229
|
-
const attempt = await dispatchChannel(event, channel, this.transportOptions);
|
|
46230
|
-
attempt.attempt = index + 1;
|
|
46231
|
-
if (attempt.status === "failed" && index + 1 < policy.maxAttempts) {
|
|
46232
|
-
attempt.nextBackoffMs = Math.round(policy.backoffMs * policy.multiplier ** index);
|
|
46233
|
-
}
|
|
46234
|
-
attempts.push(attempt);
|
|
46235
|
-
if (attempt.status !== "failed")
|
|
46236
|
-
break;
|
|
46237
|
-
if (attempt.nextBackoffMs)
|
|
46238
|
-
await Bun.sleep(attempt.nextBackoffMs);
|
|
46239
|
-
}
|
|
46240
|
-
return createDeliveryResult(event, channel, attempts);
|
|
46241
|
-
}
|
|
46242
|
-
}
|
|
46243
|
-
function redactPaths(event, paths, replacement = "[REDACTED]") {
|
|
46244
|
-
if (paths.length === 0)
|
|
46245
|
-
return event;
|
|
46246
|
-
const copy = structuredClone(event);
|
|
46247
|
-
for (const path of paths) {
|
|
46248
|
-
setPath(copy, path, replacement);
|
|
46249
|
-
}
|
|
46250
|
-
return copy;
|
|
46251
|
-
}
|
|
46252
|
-
function sanitizeChannelForOutput(channel) {
|
|
46253
|
-
const copy = structuredClone(channel);
|
|
46254
|
-
if (copy.webhook?.secret)
|
|
46255
|
-
copy.webhook.secret = "[REDACTED]";
|
|
46256
|
-
if (copy.command?.env) {
|
|
46257
|
-
copy.command.env = Object.fromEntries(Object.entries(copy.command.env).map(([key, value]) => [key, shouldRedactKey(key) ? "[REDACTED]" : value]));
|
|
46258
|
-
}
|
|
46259
|
-
return copy;
|
|
46260
|
-
}
|
|
46261
|
-
function sanitizeChannelsForOutput(channels) {
|
|
46262
|
-
return channels.map(sanitizeChannelForOutput);
|
|
46263
|
-
}
|
|
46264
|
-
function redactSensitiveKeys(event, replacement = "[REDACTED]") {
|
|
46265
|
-
return redactValue(event, replacement);
|
|
46266
|
-
}
|
|
46267
|
-
function shouldRedactKey(key) {
|
|
46268
|
-
return /secret|token|password|api[_-]?key|authorization/i.test(key);
|
|
46269
|
-
}
|
|
46270
|
-
function redactValue(value, replacement) {
|
|
46271
|
-
if (Array.isArray(value))
|
|
46272
|
-
return value.map((item) => redactValue(item, replacement));
|
|
46273
|
-
if (!value || typeof value !== "object")
|
|
46274
|
-
return value;
|
|
46275
|
-
return Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
46276
|
-
key,
|
|
46277
|
-
shouldRedactKey(key) ? replacement : redactValue(item, replacement)
|
|
46278
|
-
]));
|
|
46279
|
-
}
|
|
46280
|
-
function setPath(input, path, replacement) {
|
|
46281
|
-
const parts = path.split(".");
|
|
46282
|
-
let cursor = input;
|
|
46283
|
-
for (const part of parts.slice(0, -1)) {
|
|
46284
|
-
const next = cursor[part];
|
|
46285
|
-
if (!next || typeof next !== "object")
|
|
46286
|
-
return;
|
|
46287
|
-
cursor = next;
|
|
46288
|
-
}
|
|
46289
|
-
const last = parts.at(-1);
|
|
46290
|
-
if (last && last in cursor)
|
|
46291
|
-
cursor[last] = replacement;
|
|
46292
|
-
}
|
|
46293
|
-
function normalizeTime(value) {
|
|
46294
|
-
if (!value)
|
|
46295
|
-
return new Date().toISOString();
|
|
46296
|
-
return value instanceof Date ? value.toISOString() : value;
|
|
46297
|
-
}
|
|
46298
|
-
function normalizeRetryPolicy(policy) {
|
|
46299
|
-
return {
|
|
46300
|
-
maxAttempts: Math.max(1, policy?.maxAttempts ?? 1),
|
|
46301
|
-
backoffMs: Math.max(0, policy?.backoffMs ?? 250),
|
|
46302
|
-
multiplier: Math.max(1, policy?.multiplier ?? 2)
|
|
46303
|
-
};
|
|
46304
|
-
}
|
|
46305
|
-
function parseJsonObject(value, fallback) {
|
|
46306
|
-
if (!value)
|
|
46307
|
-
return fallback;
|
|
46308
|
-
const parsed = JSON.parse(value);
|
|
46309
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
46310
|
-
throw new Error("Expected a JSON object");
|
|
46311
|
-
}
|
|
46312
|
-
return parsed;
|
|
46313
|
-
}
|
|
46314
|
-
function parseHeaders(values) {
|
|
46315
|
-
if (!values?.length)
|
|
46316
|
-
return;
|
|
46317
|
-
const headers = {};
|
|
46318
|
-
for (const value of values) {
|
|
46319
|
-
const separator = value.indexOf("=");
|
|
46320
|
-
if (separator === -1)
|
|
46321
|
-
throw new Error(`Invalid header, expected name=value: ${value}`);
|
|
46322
|
-
headers[value.slice(0, separator)] = value.slice(separator + 1);
|
|
46323
|
-
}
|
|
46324
|
-
return headers;
|
|
46325
|
-
}
|
|
46326
|
-
function parseFilter(options) {
|
|
46327
|
-
const filter2 = {};
|
|
46328
|
-
if (options.source)
|
|
46329
|
-
filter2.source = options.source;
|
|
46330
|
-
if (options.type)
|
|
46331
|
-
filter2.type = options.type;
|
|
46332
|
-
if (options.subject)
|
|
46333
|
-
filter2.subject = options.subject;
|
|
46334
|
-
if (options.severity)
|
|
46335
|
-
filter2.severity = options.severity;
|
|
46336
|
-
return Object.keys(filter2).length > 0 ? [filter2] : undefined;
|
|
46337
|
-
}
|
|
46338
|
-
function createClient(options) {
|
|
46339
|
-
if (options.createClient)
|
|
46340
|
-
return options.createClient();
|
|
46341
|
-
return new EventsClient({ store: new JsonEventsStore(options.dataDir) });
|
|
46342
|
-
}
|
|
46343
|
-
function print(value, json, text) {
|
|
46344
|
-
if (json)
|
|
46345
|
-
console.log(JSON.stringify(value, null, 2));
|
|
46346
|
-
else
|
|
46347
|
-
console.log(text);
|
|
46348
|
-
}
|
|
46349
|
-
function registerWebhookCommands(program2, options) {
|
|
46350
|
-
const webhooks = program2.command(options.webhooksCommandName ?? "webhooks").description("Manage Hasna event webhook subscriptions");
|
|
46351
|
-
webhooks.command("add").description("Add or replace a webhook or command subscription").argument("<target>", "Webhook URL or command binary").requiredOption("--id <id>", "Subscription/channel identifier").option("--transport <kind>", "Transport kind: webhook or command", "webhook").option("--name <name>", "Display name").option("--type <pattern>", "Event type filter, e.g. todos.task.*").option("--source <pattern>", "Event source filter").option("--subject <pattern>", "Event subject filter").option("--severity <pattern>", "Event severity filter").option("--secret <secret>", "Webhook HMAC secret").option("--header <name=value...>", "Webhook header", collectValues, []).option("--arg <arg...>", "Command argument", collectValues, []).option("--timeout-ms <ms>", "Transport timeout in milliseconds", parseNumber).option("--retry-attempts <n>", "Maximum delivery attempts", parseNumber).option("--retry-backoff-ms <ms>", "Initial retry backoff in milliseconds", parseNumber).option("--redact <path...>", "Event field path to redact before delivery", collectValues, []).option("--disabled", "Create channel disabled", false).option("-j, --json", "Print JSON output", false).action(async (target, actionOptions) => {
|
|
46352
|
-
const timestamp = new Date().toISOString();
|
|
46353
|
-
const channel = {
|
|
46354
|
-
id: actionOptions.id,
|
|
46355
|
-
name: actionOptions.name,
|
|
46356
|
-
enabled: !actionOptions.disabled,
|
|
46357
|
-
transport: actionOptions.transport,
|
|
46358
|
-
filters: parseFilter(actionOptions),
|
|
46359
|
-
retry: actionOptions.retryAttempts || actionOptions.retryBackoffMs ? { maxAttempts: actionOptions.retryAttempts, backoffMs: actionOptions.retryBackoffMs } : undefined,
|
|
46360
|
-
redact: actionOptions.redact?.length ? { paths: actionOptions.redact } : undefined,
|
|
46361
|
-
createdAt: timestamp,
|
|
46362
|
-
updatedAt: timestamp
|
|
46363
|
-
};
|
|
46364
|
-
if (actionOptions.transport === "webhook") {
|
|
46365
|
-
channel.webhook = { url: target, secret: actionOptions.secret, headers: parseHeaders(actionOptions.header), timeoutMs: actionOptions.timeoutMs };
|
|
46366
|
-
} else if (actionOptions.transport === "command") {
|
|
46367
|
-
channel.command = { command: target, args: actionOptions.arg ?? [], timeoutMs: actionOptions.timeoutMs };
|
|
46368
|
-
} else {
|
|
46369
|
-
throw new Error(`Transport ${actionOptions.transport} is reserved for future use and cannot be added yet`);
|
|
46370
|
-
}
|
|
46371
|
-
const saved = await createClient(options).addChannel(channel);
|
|
46372
|
-
print(sanitizeChannelForOutput(saved), Boolean(actionOptions.json), `Added ${saved.transport} channel ${saved.id}`);
|
|
46373
|
-
});
|
|
46374
|
-
webhooks.command("list").description("List configured subscriptions").option("-j, --json", "Print JSON output", false).action(async (actionOptions) => {
|
|
46375
|
-
const channels = await createClient(options).listChannels();
|
|
46376
|
-
if (actionOptions.json) {
|
|
46377
|
-
console.log(JSON.stringify(sanitizeChannelsForOutput(channels), null, 2));
|
|
46378
|
-
return;
|
|
46379
|
-
}
|
|
46380
|
-
if (!channels.length) {
|
|
46381
|
-
console.log("No channels configured.");
|
|
46382
|
-
return;
|
|
46383
|
-
}
|
|
46384
|
-
for (const channel of channels) {
|
|
46385
|
-
console.log(`${channel.id} ${channel.enabled ? "enabled" : "disabled"} ${channel.transport} ${channel.webhook?.url ?? channel.command?.command ?? channel.transport}`);
|
|
46386
|
-
}
|
|
46387
|
-
});
|
|
46388
|
-
webhooks.command("remove").description("Remove a subscription").argument("<id>", "Subscription/channel identifier").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions) => {
|
|
46389
|
-
const removed = await createClient(options).removeChannel(id);
|
|
46390
|
-
print({ removed }, Boolean(actionOptions.json), removed ? `Removed ${id}` : `Channel not found: ${id}`);
|
|
46391
|
-
});
|
|
46392
|
-
webhooks.command("test").description("Send a test event to one subscription").argument("<id>", "Subscription/channel identifier").option("--type <type>", "Event type", "events.test").option("--subject <subject>", "Event subject").option("--message <message>", "Event message", "Hasna events test delivery").option("--data <json>", "Event data JSON object").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions) => {
|
|
46393
|
-
const result = await createClient(options).testChannel(id, {
|
|
46394
|
-
source: options.source,
|
|
46395
|
-
type: actionOptions.type,
|
|
46396
|
-
subject: actionOptions.subject ?? id,
|
|
46397
|
-
message: actionOptions.message,
|
|
46398
|
-
data: parseJsonObject(actionOptions.data, { test: true })
|
|
46399
|
-
});
|
|
46400
|
-
print(result, Boolean(actionOptions.json), `${result.status}: ${result.channelId}`);
|
|
46401
|
-
});
|
|
46402
|
-
return webhooks;
|
|
46403
|
-
}
|
|
46404
|
-
function registerEventCommands(program2, options) {
|
|
46405
|
-
const events = program2.command(options.eventsCommandName ?? "events").description("Emit, list, and replay Hasna events");
|
|
46406
|
-
events.command("emit").description("Emit an event from this app").argument("<type>", "Event type").option("--source <source>", "Event source override").option("--subject <subject>", "Event subject").option("--severity <severity>", "Event severity", "info").option("--message <message>", "Event message").option("--dedupe-key <key>", "Dedupe key").option("--data <json>", "Event data JSON object").option("--metadata <json>", "Event metadata JSON object").option("--no-deliver", "Record without delivering").option("--no-dedupe", "Allow duplicate id/dedupeKey events").option("-j, --json", "Print JSON output", false).action(async (type, actionOptions) => {
|
|
46407
|
-
const result = await createClient(options).emit({
|
|
46408
|
-
source: actionOptions.source ?? options.source,
|
|
46409
|
-
type,
|
|
46410
|
-
subject: actionOptions.subject,
|
|
46411
|
-
severity: actionOptions.severity,
|
|
46412
|
-
message: actionOptions.message,
|
|
46413
|
-
dedupeKey: actionOptions.dedupeKey,
|
|
46414
|
-
data: parseJsonObject(actionOptions.data, {}),
|
|
46415
|
-
metadata: parseJsonObject(actionOptions.metadata, {})
|
|
46416
|
-
}, { deliver: actionOptions.deliver, dedupe: actionOptions.dedupe });
|
|
46417
|
-
print(result, Boolean(actionOptions.json), `${result.deduped ? "Deduped" : "Emitted"} ${result.event.id} to ${result.deliveries.length} channel(s)`);
|
|
46418
|
-
});
|
|
46419
|
-
events.command("list").description("List recorded events").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--limit <n>", "Limit results", parseNumber).option("-j, --json", "Print JSON output", false).action(async (actionOptions) => {
|
|
46420
|
-
let rows = await createClient(options).listEvents();
|
|
46421
|
-
if (actionOptions.source)
|
|
46422
|
-
rows = rows.filter((event) => event.source === actionOptions.source);
|
|
46423
|
-
if (actionOptions.type)
|
|
46424
|
-
rows = rows.filter((event) => event.type === actionOptions.type);
|
|
46425
|
-
if (actionOptions.limit)
|
|
46426
|
-
rows = rows.slice(-actionOptions.limit);
|
|
46427
|
-
if (actionOptions.json) {
|
|
46428
|
-
console.log(JSON.stringify(rows, null, 2));
|
|
46429
|
-
return;
|
|
46430
|
-
}
|
|
46431
|
-
if (!rows.length) {
|
|
46432
|
-
console.log("No events recorded.");
|
|
46433
|
-
return;
|
|
46434
|
-
}
|
|
46435
|
-
for (const event of rows)
|
|
46436
|
-
console.log(`${event.time} ${event.id} ${event.source} ${event.type} ${event.severity}`);
|
|
46437
|
-
});
|
|
46438
|
-
events.command("replay").description("Replay recorded events").option("--id <id>", "Replay one event id").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--dry-run", "Preview without delivery", false).option("-j, --json", "Print JSON output", false).action(async (actionOptions) => {
|
|
46439
|
-
const result = await createClient(options).replay({
|
|
46440
|
-
eventId: actionOptions.id,
|
|
46441
|
-
source: actionOptions.source,
|
|
46442
|
-
type: actionOptions.type,
|
|
46443
|
-
dryRun: actionOptions.dryRun
|
|
46444
|
-
});
|
|
46445
|
-
print(result, Boolean(actionOptions.json), `Replayed ${result.events.length} event(s), ${result.deliveries.length} delivery result(s)`);
|
|
46446
|
-
});
|
|
46447
|
-
return events;
|
|
46448
|
-
}
|
|
46449
|
-
function registerEventsCommands(program2, options) {
|
|
46450
|
-
registerWebhookCommands(program2, options);
|
|
46451
|
-
registerEventCommands(program2, options);
|
|
46452
|
-
}
|
|
46453
|
-
function parseNumber(value) {
|
|
46454
|
-
const parsed = Number(value);
|
|
46455
|
-
if (!Number.isFinite(parsed))
|
|
46456
|
-
throw new Error(`Expected a number, got ${value}`);
|
|
46457
|
-
return parsed;
|
|
46458
|
-
}
|
|
46459
|
-
function collectValues(value, previous) {
|
|
46460
|
-
previous.push(value);
|
|
46461
|
-
return previous;
|
|
46462
|
-
}
|
|
46463
|
-
var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR", HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME", DEFAULT_SIGNATURE_TOLERANCE_MS;
|
|
46464
|
-
var init_commander = __esm(() => {
|
|
46465
|
-
DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
|
|
46466
|
-
});
|
|
46467
|
-
|
|
46468
46023
|
// node_modules/commander/esm.mjs
|
|
46469
46024
|
var import__ = __toESM(require_commander(), 1);
|
|
46470
46025
|
var {
|
|
@@ -49184,7 +48739,7 @@ init_route53();
|
|
|
49184
48739
|
import { readFileSync as readFileSync6 } from "fs";
|
|
49185
48740
|
|
|
49186
48741
|
// src/lib/zone-setup.ts
|
|
49187
|
-
function
|
|
48742
|
+
function nameserversMatch2(a4, b4) {
|
|
49188
48743
|
const norm = (ns) => new Set(ns.map((n2) => n2.trim().toLowerCase().replace(/\.$/, "")));
|
|
49189
48744
|
const sa = norm(a4);
|
|
49190
48745
|
const sb = norm(b4);
|
|
@@ -49211,7 +48766,7 @@ async function setupDomainZone(domain, deps) {
|
|
|
49211
48766
|
}
|
|
49212
48767
|
let nsUpdated = false;
|
|
49213
48768
|
const registrarNs = await deps.getRegistrarNs(domain);
|
|
49214
|
-
if (!
|
|
48769
|
+
if (!nameserversMatch2(registrarNs, zone.name_servers)) {
|
|
49215
48770
|
await deps.setRegistrarNs(domain, zone.name_servers);
|
|
49216
48771
|
nsUpdated = true;
|
|
49217
48772
|
log("ns_aligned", { domain, zoneId: zone.id, nameservers: zone.name_servers });
|
|
@@ -49693,8 +49248,8 @@ async function registerOptionalCommands(program2, groups) {
|
|
|
49693
49248
|
}
|
|
49694
49249
|
if (groups.has("events")) {
|
|
49695
49250
|
try {
|
|
49696
|
-
const { registerEventsCommands
|
|
49697
|
-
|
|
49251
|
+
const { registerEventsCommands } = await import("@hasna/events/commander");
|
|
49252
|
+
registerEventsCommands(program2, { source: "domains" });
|
|
49698
49253
|
} catch (error) {
|
|
49699
49254
|
console.error(`Events command group is enabled but @hasna/events could not be loaded: ${error instanceof Error ? error.message : String(error)}`);
|
|
49700
49255
|
}
|