@hasna/domains 0.0.20 → 0.0.22
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 +140 -63
- package/dist/cli/commands/config.d.ts.map +1 -1
- package/dist/cli/commands/domain.d.ts.map +1 -1
- package/dist/cli/commands/provider.d.ts.map +1 -1
- package/dist/cli/commands/providers.d.ts.map +1 -1
- package/dist/cli/commands/storage.d.ts +3 -0
- package/dist/cli/commands/storage.d.ts.map +1 -0
- package/dist/cli/index.js +2099 -550
- package/dist/db/domain-records.d.ts +2 -2
- package/dist/db/domain-records.d.ts.map +1 -1
- package/dist/db/pg-migrations.d.ts +1 -1
- package/dist/db/storage-sync.d.ts +55 -0
- package/dist/db/storage-sync.d.ts.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +863 -157
- package/dist/lib/brandsight.d.ts +46 -2
- package/dist/lib/brandsight.d.ts.map +1 -1
- package/dist/lib/capability.d.ts +5 -3
- package/dist/lib/capability.d.ts.map +1 -1
- package/dist/lib/cloudflare-auth.d.ts.map +1 -1
- package/dist/lib/cloudflare.d.ts +2 -2
- package/dist/lib/cloudflare.d.ts.map +1 -1
- package/dist/lib/creds-check.d.ts.map +1 -1
- package/dist/lib/env-aliases.d.ts +43 -0
- package/dist/lib/env-aliases.d.ts.map +1 -0
- package/dist/lib/godaddy.d.ts +4 -4
- package/dist/lib/namecheap.d.ts +13 -0
- package/dist/lib/namecheap.d.ts.map +1 -1
- package/dist/lib/registrar.d.ts +49 -5
- package/dist/lib/registrar.d.ts.map +1 -1
- package/dist/lib/route53.d.ts +2 -0
- package/dist/lib/route53.d.ts.map +1 -1
- package/dist/lib/sedo.d.ts +7 -0
- package/dist/lib/sedo.d.ts.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +14266 -15760
- package/dist/mcp/storage-tools.d.ts +3 -0
- package/dist/mcp/storage-tools.d.ts.map +1 -0
- package/dist/storage.d.ts +5 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +5703 -0
- package/package.json +9 -3
- package/dist/cli/commands/cloud.d.ts +0 -3
- package/dist/cli/commands/cloud.d.ts.map +0 -1
- package/dist/db/cloud-sync.d.ts +0 -33
- package/dist/db/cloud-sync.d.ts.map +0 -1
- package/dist/mcp/cloud-tools.d.ts +0 -3
- package/dist/mcp/cloud-tools.d.ts.map +0 -1
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
|
+
}
|
|
997
997
|
};
|
|
998
998
|
}
|
|
999
999
|
return this;
|
|
@@ -2344,31 +2344,31 @@ var init_migrations = __esm(() => {
|
|
|
2344
2344
|
|
|
2345
2345
|
// src/db/database.ts
|
|
2346
2346
|
import { Database } from "bun:sqlite";
|
|
2347
|
-
import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
2348
|
-
import { dirname, join, resolve } from "path";
|
|
2349
|
-
import { homedir } from "os";
|
|
2347
|
+
import { copyFileSync, existsSync as existsSync2, mkdirSync, readdirSync, statSync } from "fs";
|
|
2348
|
+
import { dirname, join as join2, resolve } from "path";
|
|
2349
|
+
import { homedir as homedir2 } from "os";
|
|
2350
2350
|
function getDbPath() {
|
|
2351
2351
|
if (process.env["HASNA_DOMAINS_DB_PATH"])
|
|
2352
2352
|
return process.env["HASNA_DOMAINS_DB_PATH"];
|
|
2353
2353
|
const explicit = process.env["HASNA_DOMAINS_DIR"] ?? process.env["DOMAINS_DIR"];
|
|
2354
2354
|
if (explicit) {
|
|
2355
|
-
return
|
|
2355
|
+
return join2(explicit, "domains.db");
|
|
2356
2356
|
}
|
|
2357
|
-
const home = process.env["HOME"] || process.env["USERPROFILE"] ||
|
|
2357
|
+
const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir2();
|
|
2358
2358
|
migrateDotfile("domains");
|
|
2359
|
-
return
|
|
2359
|
+
return join2(home, ".hasna", "domains", "domains.db");
|
|
2360
2360
|
}
|
|
2361
2361
|
function migrateDotfile(name) {
|
|
2362
|
-
const home = process.env["HOME"] || process.env["USERPROFILE"] ||
|
|
2363
|
-
const oldDir =
|
|
2364
|
-
const newDir =
|
|
2365
|
-
if (!
|
|
2362
|
+
const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir2();
|
|
2363
|
+
const oldDir = join2(home, `.${name}`);
|
|
2364
|
+
const newDir = join2(home, ".hasna", name);
|
|
2365
|
+
if (!existsSync2(oldDir) || existsSync2(newDir))
|
|
2366
2366
|
return;
|
|
2367
2367
|
mkdirSync(newDir, { recursive: true });
|
|
2368
2368
|
for (const file of readdirSync(oldDir)) {
|
|
2369
|
-
const oldPath =
|
|
2369
|
+
const oldPath = join2(oldDir, file);
|
|
2370
2370
|
if (statSync(oldPath).isFile())
|
|
2371
|
-
copyFileSync(oldPath,
|
|
2371
|
+
copyFileSync(oldPath, join2(newDir, file));
|
|
2372
2372
|
}
|
|
2373
2373
|
}
|
|
2374
2374
|
function getDatabase() {
|
|
@@ -3541,6 +3541,146 @@ var init_domains = __esm(() => {
|
|
|
3541
3541
|
init_monitoring();
|
|
3542
3542
|
});
|
|
3543
3543
|
|
|
3544
|
+
// src/lib/godaddy.ts
|
|
3545
|
+
function getCredentials() {
|
|
3546
|
+
const apiKey = process.env["GODADDY_API_KEY"];
|
|
3547
|
+
const apiSecret = process.env["GODADDY_API_SECRET"];
|
|
3548
|
+
if (!apiKey || !apiSecret) {
|
|
3549
|
+
throw new Error("GoDaddy API credentials not configured. Set GODADDY_API_KEY and GODADDY_API_SECRET environment variables.");
|
|
3550
|
+
}
|
|
3551
|
+
return { apiKey, apiSecret };
|
|
3552
|
+
}
|
|
3553
|
+
function getHeaders() {
|
|
3554
|
+
const { apiKey, apiSecret } = getCredentials();
|
|
3555
|
+
return {
|
|
3556
|
+
Authorization: `sso-key ${apiKey}:${apiSecret}`,
|
|
3557
|
+
"Content-Type": "application/json",
|
|
3558
|
+
Accept: "application/json"
|
|
3559
|
+
};
|
|
3560
|
+
}
|
|
3561
|
+
async function apiRequest2(method, path, body) {
|
|
3562
|
+
const fetchFn = _overriddenFetch || globalThis.fetch;
|
|
3563
|
+
const url = `${GODADDY_API_BASE}${path}`;
|
|
3564
|
+
const headers = getHeaders();
|
|
3565
|
+
const options = { method, headers };
|
|
3566
|
+
if (body !== undefined) {
|
|
3567
|
+
options.body = JSON.stringify(body);
|
|
3568
|
+
}
|
|
3569
|
+
const response = await fetchFn(url, options);
|
|
3570
|
+
if (!response.ok) {
|
|
3571
|
+
const text = await response.text();
|
|
3572
|
+
throw new GoDaddyApiError(`GoDaddy API ${method} ${path} failed with status ${response.status}: ${text}`, response.status, { responseBody: text });
|
|
3573
|
+
}
|
|
3574
|
+
if (response.status === 204) {
|
|
3575
|
+
return;
|
|
3576
|
+
}
|
|
3577
|
+
return await response.json();
|
|
3578
|
+
}
|
|
3579
|
+
async function listGoDaddyDomains() {
|
|
3580
|
+
return apiRequest2("GET", "/v1/domains");
|
|
3581
|
+
}
|
|
3582
|
+
async function getDomainInfo2(domain) {
|
|
3583
|
+
return apiRequest2("GET", `/v1/domains/${encodeURIComponent(domain)}`);
|
|
3584
|
+
}
|
|
3585
|
+
async function renewDomain2(domain) {
|
|
3586
|
+
return apiRequest2("POST", `/v1/domains/${encodeURIComponent(domain)}/renew`, { period: 1 });
|
|
3587
|
+
}
|
|
3588
|
+
async function getDnsRecords2(domain, type) {
|
|
3589
|
+
const path = type ? `/v1/domains/${encodeURIComponent(domain)}/records/${encodeURIComponent(type)}` : `/v1/domains/${encodeURIComponent(domain)}/records`;
|
|
3590
|
+
return apiRequest2("GET", path);
|
|
3591
|
+
}
|
|
3592
|
+
async function setDnsRecords2(domain, records) {
|
|
3593
|
+
await apiRequest2("PUT", `/v1/domains/${encodeURIComponent(domain)}/records`, records);
|
|
3594
|
+
}
|
|
3595
|
+
async function checkAvailability2(domain) {
|
|
3596
|
+
return apiRequest2("GET", `/v1/domains/available?domain=${encodeURIComponent(domain)}`);
|
|
3597
|
+
}
|
|
3598
|
+
function mapGoDaddyStatus(gdStatus) {
|
|
3599
|
+
const s = gdStatus.toUpperCase();
|
|
3600
|
+
if (s === "ACTIVE")
|
|
3601
|
+
return "active";
|
|
3602
|
+
if (s === "EXPIRED")
|
|
3603
|
+
return "expired";
|
|
3604
|
+
if (s === "TRANSFERRED_OUT" || s === "TRANSFERRING" || s === "PENDING_TRANSFER")
|
|
3605
|
+
return "transferring";
|
|
3606
|
+
if (s === "REDEMPTION" || s === "PENDING_REDEMPTION")
|
|
3607
|
+
return "redemption";
|
|
3608
|
+
return "active";
|
|
3609
|
+
}
|
|
3610
|
+
async function syncToLocalDb2(dbFns) {
|
|
3611
|
+
const result = {
|
|
3612
|
+
synced: 0,
|
|
3613
|
+
created: 0,
|
|
3614
|
+
updated: 0,
|
|
3615
|
+
errors: []
|
|
3616
|
+
};
|
|
3617
|
+
let gdDomains;
|
|
3618
|
+
try {
|
|
3619
|
+
gdDomains = await listGoDaddyDomains();
|
|
3620
|
+
} catch (err) {
|
|
3621
|
+
result.errors.push(`Failed to list domains: ${err instanceof Error ? err.message : String(err)}`);
|
|
3622
|
+
return result;
|
|
3623
|
+
}
|
|
3624
|
+
for (const gd of gdDomains) {
|
|
3625
|
+
try {
|
|
3626
|
+
let detail;
|
|
3627
|
+
try {
|
|
3628
|
+
detail = await getDomainInfo2(gd.domain);
|
|
3629
|
+
} catch {
|
|
3630
|
+
detail = gd;
|
|
3631
|
+
}
|
|
3632
|
+
const existing = dbFns.getDomainByName(gd.domain);
|
|
3633
|
+
const domainData = {
|
|
3634
|
+
name: gd.domain,
|
|
3635
|
+
registrar: "GoDaddy",
|
|
3636
|
+
status: mapGoDaddyStatus(gd.status),
|
|
3637
|
+
expires_at: gd.expires ? new Date(gd.expires).toISOString() : undefined,
|
|
3638
|
+
auto_renew: gd.renewAuto,
|
|
3639
|
+
nameservers: gd.nameServers || [],
|
|
3640
|
+
registered_at: detail.createdAt ? new Date(detail.createdAt).toISOString() : undefined,
|
|
3641
|
+
metadata: {
|
|
3642
|
+
godaddy_domain_id: detail.domainId,
|
|
3643
|
+
provider: "godaddy",
|
|
3644
|
+
locked: detail.locked,
|
|
3645
|
+
privacy: detail.privacy
|
|
3646
|
+
}
|
|
3647
|
+
};
|
|
3648
|
+
if (existing) {
|
|
3649
|
+
dbFns.updateDomain(existing.id, domainData);
|
|
3650
|
+
result.updated++;
|
|
3651
|
+
} else {
|
|
3652
|
+
dbFns.createDomain(domainData);
|
|
3653
|
+
result.created++;
|
|
3654
|
+
}
|
|
3655
|
+
result.synced++;
|
|
3656
|
+
} catch (err) {
|
|
3657
|
+
result.errors.push(`Failed to sync ${gd.domain}: ${err instanceof Error ? err.message : String(err)}`);
|
|
3658
|
+
}
|
|
3659
|
+
}
|
|
3660
|
+
return result;
|
|
3661
|
+
}
|
|
3662
|
+
function godaddyCapability(env = process.env) {
|
|
3663
|
+
const configured = !!(env["GODADDY_API_KEY"] && env["GODADDY_API_SECRET"]);
|
|
3664
|
+
return {
|
|
3665
|
+
configured,
|
|
3666
|
+
gated: true,
|
|
3667
|
+
notes: configured ? "Credentials present; DNS/domain management may work with qualifying account access, but availability remains threshold-gated and direct automated purchase is not exposed here. Prefer Route53 for purchases." : "Not configured; GoDaddy production availability remains threshold-gated and DNS/domain management requires qualifying account access."
|
|
3668
|
+
};
|
|
3669
|
+
}
|
|
3670
|
+
var GoDaddyApiError, _overriddenFetch = null, GODADDY_API_BASE = "https://api.godaddy.com";
|
|
3671
|
+
var init_godaddy = __esm(() => {
|
|
3672
|
+
GoDaddyApiError = class GoDaddyApiError extends Error {
|
|
3673
|
+
statusCode;
|
|
3674
|
+
details;
|
|
3675
|
+
constructor(message, statusCode, details) {
|
|
3676
|
+
super(message);
|
|
3677
|
+
this.statusCode = statusCode;
|
|
3678
|
+
this.details = details;
|
|
3679
|
+
this.name = "GoDaddyApiError";
|
|
3680
|
+
}
|
|
3681
|
+
};
|
|
3682
|
+
});
|
|
3683
|
+
|
|
3544
3684
|
// node_modules/@smithy/types/dist-cjs/index.js
|
|
3545
3685
|
var require_dist_cjs = __commonJS((exports) => {
|
|
3546
3686
|
exports.HttpAuthLocation = undefined;
|
|
@@ -7316,11 +7456,11 @@ var require_randomUUID = __commonJS((exports) => {
|
|
|
7316
7456
|
|
|
7317
7457
|
// node_modules/@smithy/uuid/dist-cjs/index.js
|
|
7318
7458
|
var require_dist_cjs19 = __commonJS((exports) => {
|
|
7319
|
-
var
|
|
7459
|
+
var randomUUID3 = require_randomUUID();
|
|
7320
7460
|
var decimalToHex = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
|
|
7321
7461
|
var v4 = () => {
|
|
7322
|
-
if (
|
|
7323
|
-
return
|
|
7462
|
+
if (randomUUID3.randomUUID) {
|
|
7463
|
+
return randomUUID3.randomUUID();
|
|
7324
7464
|
}
|
|
7325
7465
|
const rnds = new Uint8Array(16);
|
|
7326
7466
|
crypto.getRandomValues(rnds);
|
|
@@ -7483,19 +7623,19 @@ var require_serde = __commonJS((exports) => {
|
|
|
7483
7623
|
};
|
|
7484
7624
|
var strictParseDouble = (value) => {
|
|
7485
7625
|
if (typeof value == "string") {
|
|
7486
|
-
return expectNumber(
|
|
7626
|
+
return expectNumber(parseNumber2(value));
|
|
7487
7627
|
}
|
|
7488
7628
|
return expectNumber(value);
|
|
7489
7629
|
};
|
|
7490
7630
|
var strictParseFloat = strictParseDouble;
|
|
7491
7631
|
var strictParseFloat32 = (value) => {
|
|
7492
7632
|
if (typeof value == "string") {
|
|
7493
|
-
return expectFloat32(
|
|
7633
|
+
return expectFloat32(parseNumber2(value));
|
|
7494
7634
|
}
|
|
7495
7635
|
return expectFloat32(value);
|
|
7496
7636
|
};
|
|
7497
7637
|
var NUMBER_REGEX = /(-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)|(-?Infinity)|(NaN)/g;
|
|
7498
|
-
var
|
|
7638
|
+
var parseNumber2 = (value) => {
|
|
7499
7639
|
const matches = value.match(NUMBER_REGEX);
|
|
7500
7640
|
if (matches === null || matches[0].length !== value.length) {
|
|
7501
7641
|
throw new TypeError(`Expected real number, got implicit NaN`);
|
|
@@ -7530,26 +7670,26 @@ var require_serde = __commonJS((exports) => {
|
|
|
7530
7670
|
};
|
|
7531
7671
|
var strictParseLong = (value) => {
|
|
7532
7672
|
if (typeof value === "string") {
|
|
7533
|
-
return expectLong(
|
|
7673
|
+
return expectLong(parseNumber2(value));
|
|
7534
7674
|
}
|
|
7535
7675
|
return expectLong(value);
|
|
7536
7676
|
};
|
|
7537
7677
|
var strictParseInt = strictParseLong;
|
|
7538
7678
|
var strictParseInt32 = (value) => {
|
|
7539
7679
|
if (typeof value === "string") {
|
|
7540
|
-
return expectInt32(
|
|
7680
|
+
return expectInt32(parseNumber2(value));
|
|
7541
7681
|
}
|
|
7542
7682
|
return expectInt32(value);
|
|
7543
7683
|
};
|
|
7544
7684
|
var strictParseShort = (value) => {
|
|
7545
7685
|
if (typeof value === "string") {
|
|
7546
|
-
return expectShort(
|
|
7686
|
+
return expectShort(parseNumber2(value));
|
|
7547
7687
|
}
|
|
7548
7688
|
return expectShort(value);
|
|
7549
7689
|
};
|
|
7550
7690
|
var strictParseByte = (value) => {
|
|
7551
7691
|
if (typeof value === "string") {
|
|
7552
|
-
return expectByte(
|
|
7692
|
+
return expectByte(parseNumber2(value));
|
|
7553
7693
|
}
|
|
7554
7694
|
return expectByte(value);
|
|
7555
7695
|
};
|
|
@@ -10746,8 +10886,8 @@ ${utilHexEncoding.toHex(hashedRequest)}`;
|
|
|
10746
10886
|
throw new Error("Resolved credential object is not valid");
|
|
10747
10887
|
}
|
|
10748
10888
|
}
|
|
10749
|
-
formatDate(
|
|
10750
|
-
const longDate = iso8601(
|
|
10889
|
+
formatDate(now2) {
|
|
10890
|
+
const longDate = iso8601(now2).replace(/[\-:]/g, "");
|
|
10751
10891
|
return {
|
|
10752
10892
|
longDate,
|
|
10753
10893
|
shortDate: longDate.slice(0, 8)
|
|
@@ -17519,7 +17659,7 @@ var require_readFile = __commonJS((exports) => {
|
|
|
17519
17659
|
var promises_1 = __require("fs/promises");
|
|
17520
17660
|
exports.filePromises = {};
|
|
17521
17661
|
exports.fileIntercept = {};
|
|
17522
|
-
var
|
|
17662
|
+
var readFile2 = (path, options) => {
|
|
17523
17663
|
if (exports.fileIntercept[path] !== undefined) {
|
|
17524
17664
|
return exports.fileIntercept[path];
|
|
17525
17665
|
}
|
|
@@ -17528,7 +17668,7 @@ var require_readFile = __commonJS((exports) => {
|
|
|
17528
17668
|
}
|
|
17529
17669
|
return exports.filePromises[path];
|
|
17530
17670
|
};
|
|
17531
|
-
exports.readFile =
|
|
17671
|
+
exports.readFile = readFile2;
|
|
17532
17672
|
});
|
|
17533
17673
|
|
|
17534
17674
|
// node_modules/@smithy/shared-ini-file-loader/dist-cjs/index.js
|
|
@@ -17538,7 +17678,7 @@ var require_dist_cjs36 = __commonJS((exports) => {
|
|
|
17538
17678
|
var getSSOTokenFromFile = require_getSSOTokenFromFile();
|
|
17539
17679
|
var path = __require("path");
|
|
17540
17680
|
var types = require_dist_cjs();
|
|
17541
|
-
var
|
|
17681
|
+
var readFile2 = require_readFile();
|
|
17542
17682
|
var ENV_PROFILE = "AWS_PROFILE";
|
|
17543
17683
|
var DEFAULT_PROFILE = "default";
|
|
17544
17684
|
var getProfileName = (init) => init.profile || process.env[ENV_PROFILE] || DEFAULT_PROFILE;
|
|
@@ -17622,10 +17762,10 @@ var require_dist_cjs36 = __commonJS((exports) => {
|
|
|
17622
17762
|
resolvedConfigFilepath = path.join(homeDir, configFilepath.slice(2));
|
|
17623
17763
|
}
|
|
17624
17764
|
const parsedFiles = await Promise.all([
|
|
17625
|
-
|
|
17765
|
+
readFile2.readFile(resolvedConfigFilepath, {
|
|
17626
17766
|
ignoreCache: init.ignoreCache
|
|
17627
17767
|
}).then(parseIni).then(getConfigData).catch(swallowError$1),
|
|
17628
|
-
|
|
17768
|
+
readFile2.readFile(resolvedFilepath, {
|
|
17629
17769
|
ignoreCache: init.ignoreCache
|
|
17630
17770
|
}).then(parseIni).catch(swallowError$1)
|
|
17631
17771
|
]);
|
|
@@ -17636,7 +17776,7 @@ var require_dist_cjs36 = __commonJS((exports) => {
|
|
|
17636
17776
|
};
|
|
17637
17777
|
var getSsoSessionData = (data) => Object.entries(data).filter(([key]) => key.startsWith(types.IniSectionType.SSO_SESSION + CONFIG_PREFIX_SEPARATOR)).reduce((acc, [key, value]) => ({ ...acc, [key.substring(key.indexOf(CONFIG_PREFIX_SEPARATOR) + 1)]: value }), {});
|
|
17638
17778
|
var swallowError = () => ({});
|
|
17639
|
-
var loadSsoSessionData = async (init = {}) =>
|
|
17779
|
+
var loadSsoSessionData = async (init = {}) => readFile2.readFile(init.configFilepath ?? getConfigFilepath()).then(parseIni).then(getSsoSessionData).catch(swallowError);
|
|
17640
17780
|
var mergeConfigFiles = (...files) => {
|
|
17641
17781
|
const merged = {};
|
|
17642
17782
|
for (const file of files) {
|
|
@@ -17656,10 +17796,10 @@ var require_dist_cjs36 = __commonJS((exports) => {
|
|
|
17656
17796
|
};
|
|
17657
17797
|
var externalDataInterceptor = {
|
|
17658
17798
|
getFileRecord() {
|
|
17659
|
-
return
|
|
17799
|
+
return readFile2.fileIntercept;
|
|
17660
17800
|
},
|
|
17661
17801
|
interceptFile(path2, contents) {
|
|
17662
|
-
|
|
17802
|
+
readFile2.fileIntercept[path2] = Promise.resolve(contents);
|
|
17663
17803
|
},
|
|
17664
17804
|
getTokenRecord() {
|
|
17665
17805
|
return getSSOTokenFromFile.tokenIntercept;
|
|
@@ -17669,7 +17809,7 @@ var require_dist_cjs36 = __commonJS((exports) => {
|
|
|
17669
17809
|
}
|
|
17670
17810
|
};
|
|
17671
17811
|
exports.getSSOTokenFromFile = getSSOTokenFromFile.getSSOTokenFromFile;
|
|
17672
|
-
exports.readFile =
|
|
17812
|
+
exports.readFile = readFile2.readFile;
|
|
17673
17813
|
exports.CONFIG_PREFIX_SEPARATOR = CONFIG_PREFIX_SEPARATOR;
|
|
17674
17814
|
exports.DEFAULT_PROFILE = DEFAULT_PROFILE;
|
|
17675
17815
|
exports.ENV_PROFILE = ENV_PROFILE;
|
|
@@ -22581,14 +22721,14 @@ var init_validateTokenKey = __esm(() => {
|
|
|
22581
22721
|
|
|
22582
22722
|
// node_modules/@aws-sdk/token-providers/dist-es/writeSSOTokenToFile.js
|
|
22583
22723
|
import { promises as fsPromises } from "fs";
|
|
22584
|
-
var import_shared_ini_file_loader,
|
|
22724
|
+
var import_shared_ini_file_loader, writeFile2, writeSSOTokenToFile = (id, ssoToken) => {
|
|
22585
22725
|
const tokenFilepath = import_shared_ini_file_loader.getSSOTokenFilepath(id);
|
|
22586
22726
|
const tokenString = JSON.stringify(ssoToken, null, 2);
|
|
22587
|
-
return
|
|
22727
|
+
return writeFile2(tokenFilepath, tokenString);
|
|
22588
22728
|
};
|
|
22589
22729
|
var init_writeSSOTokenToFile = __esm(() => {
|
|
22590
22730
|
import_shared_ini_file_loader = __toESM(require_dist_cjs36(), 1);
|
|
22591
|
-
({ writeFile } = fsPromises);
|
|
22731
|
+
({ writeFile: writeFile2 } = fsPromises);
|
|
22592
22732
|
});
|
|
22593
22733
|
|
|
22594
22734
|
// node_modules/@aws-sdk/token-providers/dist-es/fromSso.js
|
|
@@ -25378,8 +25518,8 @@ var require_signin = __commonJS((exports) => {
|
|
|
25378
25518
|
// node_modules/@aws-sdk/credential-provider-login/dist-es/LoginCredentialsFetcher.js
|
|
25379
25519
|
import { createHash, createPrivateKey, createPublicKey, sign } from "crypto";
|
|
25380
25520
|
import { promises as fs2 } from "fs";
|
|
25381
|
-
import { homedir as
|
|
25382
|
-
import { dirname as dirname3, join as
|
|
25521
|
+
import { homedir as homedir3 } from "os";
|
|
25522
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
25383
25523
|
var import_property_provider18, import_protocol_http2, import_shared_ini_file_loader6, LoginCredentialsFetcher;
|
|
25384
25524
|
var init_LoginCredentialsFetcher = __esm(() => {
|
|
25385
25525
|
import_property_provider18 = __toESM(require_dist_cjs23(), 1);
|
|
@@ -25401,9 +25541,9 @@ var init_LoginCredentialsFetcher = __esm(() => {
|
|
|
25401
25541
|
throw new import_property_provider18.CredentialsProviderError(`Failed to load a token for session ${this.loginSession}, please re-authenticate using aws login`, { tryNextLink: false, logger: this.logger });
|
|
25402
25542
|
}
|
|
25403
25543
|
const accessToken = token.accessToken;
|
|
25404
|
-
const
|
|
25544
|
+
const now2 = Date.now();
|
|
25405
25545
|
const expiryTime = new Date(accessToken.expiresAt).getTime();
|
|
25406
|
-
const timeUntilExpiry = expiryTime -
|
|
25546
|
+
const timeUntilExpiry = expiryTime - now2;
|
|
25407
25547
|
if (timeUntilExpiry <= LoginCredentialsFetcher.REFRESH_THRESHOLD) {
|
|
25408
25548
|
return this.refresh(token);
|
|
25409
25549
|
}
|
|
@@ -25539,10 +25679,10 @@ var init_LoginCredentialsFetcher = __esm(() => {
|
|
|
25539
25679
|
await fs2.writeFile(tokenFilePath, JSON.stringify(token, null, 2), "utf8");
|
|
25540
25680
|
}
|
|
25541
25681
|
getTokenFilePath() {
|
|
25542
|
-
const directory = process.env.AWS_LOGIN_CACHE_DIRECTORY ??
|
|
25682
|
+
const directory = process.env.AWS_LOGIN_CACHE_DIRECTORY ?? join3(homedir3(), ".aws", "login", "cache");
|
|
25543
25683
|
const loginSessionBytes = Buffer.from(this.loginSession, "utf8");
|
|
25544
25684
|
const loginSessionSha256 = createHash("sha256").update(loginSessionBytes).digest("hex");
|
|
25545
|
-
return
|
|
25685
|
+
return join3(directory, `${loginSessionSha256}.json`);
|
|
25546
25686
|
}
|
|
25547
25687
|
derToRawSignature(derSignature) {
|
|
25548
25688
|
let offset = 2;
|
|
@@ -34285,11 +34425,32 @@ var init_dist_es14 = __esm(() => {
|
|
|
34285
34425
|
});
|
|
34286
34426
|
|
|
34287
34427
|
// src/lib/route53.ts
|
|
34428
|
+
var exports_route53 = {};
|
|
34429
|
+
__export(exports_route53, {
|
|
34430
|
+
upsertRecords: () => upsertRecords,
|
|
34431
|
+
upsertRecord: () => upsertRecord,
|
|
34432
|
+
updateNameservers: () => updateNameservers2,
|
|
34433
|
+
registerDomain: () => registerDomain2,
|
|
34434
|
+
listRegisteredDomains: () => listRegisteredDomains,
|
|
34435
|
+
listRecords: () => listRecords,
|
|
34436
|
+
listHostedZones: () => listHostedZones,
|
|
34437
|
+
getRegistrationStatus: () => getRegistrationStatus,
|
|
34438
|
+
getHostedZone: () => getHostedZone,
|
|
34439
|
+
getDomainDetail: () => getDomainDetail,
|
|
34440
|
+
getConfig: () => getConfig2,
|
|
34441
|
+
findHostedZoneByDomain: () => findHostedZoneByDomain,
|
|
34442
|
+
deleteRecord: () => deleteRecord,
|
|
34443
|
+
deleteHostedZone: () => deleteHostedZone,
|
|
34444
|
+
createRoute53Provider: () => createRoute53Provider,
|
|
34445
|
+
createHostedZone: () => createHostedZone,
|
|
34446
|
+
checkAvailability: () => checkAvailability3
|
|
34447
|
+
});
|
|
34288
34448
|
function getConfig2() {
|
|
34289
34449
|
return {
|
|
34290
34450
|
region: process.env["AWS_REGION"] || "us-east-1",
|
|
34291
34451
|
accessKeyId: process.env["AWS_ACCESS_KEY_ID"],
|
|
34292
|
-
secretAccessKey: process.env["AWS_SECRET_ACCESS_KEY"]
|
|
34452
|
+
secretAccessKey: process.env["AWS_SECRET_ACCESS_KEY"],
|
|
34453
|
+
sessionToken: process.env["AWS_SESSION_TOKEN"]
|
|
34293
34454
|
};
|
|
34294
34455
|
}
|
|
34295
34456
|
function checkCredentials(cfg) {
|
|
@@ -34305,7 +34466,7 @@ function makeClients(config) {
|
|
|
34305
34466
|
const cfg = config ?? getConfig2();
|
|
34306
34467
|
checkCredentials(cfg);
|
|
34307
34468
|
const region = cfg.region || "us-east-1";
|
|
34308
|
-
const credentials = cfg.accessKeyId && cfg.secretAccessKey ? { accessKeyId: cfg.accessKeyId, secretAccessKey: cfg.secretAccessKey } : undefined;
|
|
34469
|
+
const credentials = cfg.accessKeyId && cfg.secretAccessKey ? { accessKeyId: cfg.accessKeyId, secretAccessKey: cfg.secretAccessKey, sessionToken: cfg.sessionToken } : undefined;
|
|
34309
34470
|
return {
|
|
34310
34471
|
route53: new Route53Client({ region, credentials }),
|
|
34311
34472
|
domains: new Route53DomainsClient({ region: "us-east-1", credentials })
|
|
@@ -34334,7 +34495,7 @@ async function checkAvailability3(domain, config) {
|
|
|
34334
34495
|
} catch {}
|
|
34335
34496
|
return availability;
|
|
34336
34497
|
}
|
|
34337
|
-
async function
|
|
34498
|
+
async function registerDomain2(domain, contact, durationYears = 1, autoRenew = true, config) {
|
|
34338
34499
|
const { domains } = makeClients(config);
|
|
34339
34500
|
const contactDetail = {
|
|
34340
34501
|
FirstName: contact.first_name,
|
|
@@ -34383,7 +34544,7 @@ async function getDomainDetail(domain, config) {
|
|
|
34383
34544
|
nameservers: (result.Nameservers ?? []).map((ns) => ns.Name ?? "").filter(Boolean)
|
|
34384
34545
|
};
|
|
34385
34546
|
}
|
|
34386
|
-
async function
|
|
34547
|
+
async function updateNameservers2(domain, nameservers, config, client) {
|
|
34387
34548
|
if (!nameservers.length) {
|
|
34388
34549
|
throw new Error("updateNameservers requires at least one nameserver");
|
|
34389
34550
|
}
|
|
@@ -34445,7 +34606,8 @@ async function listHostedZones(config) {
|
|
|
34445
34606
|
id: cleanZoneId(z2.Id ?? ""),
|
|
34446
34607
|
name: z2.Name ?? "",
|
|
34447
34608
|
record_count: z2.ResourceRecordSetCount ?? 0,
|
|
34448
|
-
comment: z2.Config?.Comment
|
|
34609
|
+
comment: z2.Config?.Comment,
|
|
34610
|
+
private_zone: z2.Config?.PrivateZone
|
|
34449
34611
|
});
|
|
34450
34612
|
}
|
|
34451
34613
|
marker = result.IsTruncated ? result.NextMarker : undefined;
|
|
@@ -34460,7 +34622,8 @@ async function getHostedZone(hostedZoneId, config) {
|
|
|
34460
34622
|
name: result.HostedZone?.Name ?? "",
|
|
34461
34623
|
record_count: result.HostedZone?.ResourceRecordSetCount ?? 0,
|
|
34462
34624
|
comment: result.HostedZone?.Config?.Comment,
|
|
34463
|
-
name_servers: result.DelegationSet?.NameServers ?? []
|
|
34625
|
+
name_servers: result.DelegationSet?.NameServers ?? [],
|
|
34626
|
+
private_zone: result.HostedZone?.Config?.PrivateZone
|
|
34464
34627
|
};
|
|
34465
34628
|
}
|
|
34466
34629
|
async function deleteHostedZone(hostedZoneId, config) {
|
|
@@ -34470,7 +34633,15 @@ async function deleteHostedZone(hostedZoneId, config) {
|
|
|
34470
34633
|
async function findHostedZoneByDomain(domain, config) {
|
|
34471
34634
|
const zones = await listHostedZones(config);
|
|
34472
34635
|
const normalized = domain.endsWith(".") ? domain : `${domain}.`;
|
|
34473
|
-
|
|
34636
|
+
const matches = zones.filter((z2) => z2.name === normalized);
|
|
34637
|
+
if (matches.length === 0)
|
|
34638
|
+
return null;
|
|
34639
|
+
const publicMatches = matches.filter((z2) => !z2.private_zone);
|
|
34640
|
+
const candidates = publicMatches.length > 0 ? publicMatches : matches;
|
|
34641
|
+
if (candidates.length > 1) {
|
|
34642
|
+
throw new Error(`Multiple Route 53 hosted zones found for ${domain}; specify hosted zone id`);
|
|
34643
|
+
}
|
|
34644
|
+
return candidates[0] ?? null;
|
|
34474
34645
|
}
|
|
34475
34646
|
function rrsToRecord(rrs) {
|
|
34476
34647
|
if (rrs.AliasTarget) {
|
|
@@ -34568,19 +34739,55 @@ async function upsertRecords(hostedZoneId, records, config) {
|
|
|
34568
34739
|
}
|
|
34569
34740
|
function createRoute53Provider(config) {
|
|
34570
34741
|
const cfg = config ?? getConfig2();
|
|
34742
|
+
const registerWithRoute53 = registerDomain2;
|
|
34743
|
+
const updateRoute53Nameservers = updateNameservers2;
|
|
34744
|
+
async function listDomainInventory() {
|
|
34745
|
+
const byDomain = new Map;
|
|
34746
|
+
try {
|
|
34747
|
+
const registered = await listRegisteredDomains(cfg);
|
|
34748
|
+
for (const d3 of registered) {
|
|
34749
|
+
byDomain.set(d3.domain, {
|
|
34750
|
+
domain: d3.domain,
|
|
34751
|
+
registrar: "AWS Route 53",
|
|
34752
|
+
created: "",
|
|
34753
|
+
expires: d3.expiry,
|
|
34754
|
+
nameservers: [],
|
|
34755
|
+
status: "active",
|
|
34756
|
+
auto_renew: d3.auto_renew
|
|
34757
|
+
});
|
|
34758
|
+
}
|
|
34759
|
+
} catch (error) {
|
|
34760
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
34761
|
+
if (!message.includes("route53domains:ListDomains") && !message.includes("AccessDenied")) {
|
|
34762
|
+
throw error;
|
|
34763
|
+
}
|
|
34764
|
+
}
|
|
34765
|
+
const zones = await listHostedZones(cfg);
|
|
34766
|
+
for (const z2 of zones) {
|
|
34767
|
+
const zone = z2.name_servers?.length ? z2 : await getHostedZone(z2.id, cfg).catch(() => z2);
|
|
34768
|
+
const domain = zone.name.replace(/\.$/, "");
|
|
34769
|
+
const nameservers = zone.name_servers ?? [];
|
|
34770
|
+
const existing = byDomain.get(domain);
|
|
34771
|
+
if (existing) {
|
|
34772
|
+
existing.nameservers = nameservers.length > 0 ? nameservers : existing.nameservers;
|
|
34773
|
+
continue;
|
|
34774
|
+
}
|
|
34775
|
+
byDomain.set(domain, {
|
|
34776
|
+
domain,
|
|
34777
|
+
registrar: "AWS Route 53 DNS",
|
|
34778
|
+
created: "",
|
|
34779
|
+
expires: "",
|
|
34780
|
+
nameservers,
|
|
34781
|
+
status: "active",
|
|
34782
|
+
auto_renew: false
|
|
34783
|
+
});
|
|
34784
|
+
}
|
|
34785
|
+
return Array.from(byDomain.values());
|
|
34786
|
+
}
|
|
34571
34787
|
return {
|
|
34572
34788
|
name: "route53",
|
|
34573
34789
|
async listDomains() {
|
|
34574
|
-
|
|
34575
|
-
return domains.map((d3) => ({
|
|
34576
|
-
domain: d3.domain,
|
|
34577
|
-
registrar: "AWS Route 53",
|
|
34578
|
-
created: "",
|
|
34579
|
-
expires: d3.expiry,
|
|
34580
|
-
nameservers: [],
|
|
34581
|
-
status: "active",
|
|
34582
|
-
auto_renew: d3.auto_renew
|
|
34583
|
-
}));
|
|
34790
|
+
return listDomainInventory();
|
|
34584
34791
|
},
|
|
34585
34792
|
async getDomainInfo(domain) {
|
|
34586
34793
|
const detail = await getDomainDetail(domain, cfg);
|
|
@@ -34594,6 +34801,14 @@ function createRoute53Provider(config) {
|
|
|
34594
34801
|
auto_renew: detail.auto_renew
|
|
34595
34802
|
};
|
|
34596
34803
|
},
|
|
34804
|
+
async registerDomain(domain, contact, options = {}) {
|
|
34805
|
+
const result = await registerWithRoute53(domain, contact, options.years ?? 1, options.autoRenew ?? true, cfg);
|
|
34806
|
+
return { domain, success: !!result.operationId, operationId: result.operationId };
|
|
34807
|
+
},
|
|
34808
|
+
async updateNameservers(domain, nameservers) {
|
|
34809
|
+
const result = await updateRoute53Nameservers(domain, nameservers, cfg);
|
|
34810
|
+
return { domain, success: !!result.operationId, operationId: result.operationId };
|
|
34811
|
+
},
|
|
34597
34812
|
async renewDomain(_domain) {
|
|
34598
34813
|
return { domain: _domain, success: false, orderId: undefined, chargedAmount: undefined };
|
|
34599
34814
|
},
|
|
@@ -34641,7 +34856,7 @@ function createRoute53Provider(config) {
|
|
|
34641
34856
|
};
|
|
34642
34857
|
},
|
|
34643
34858
|
async syncToLocalDb(dbFns) {
|
|
34644
|
-
const domains = await
|
|
34859
|
+
const domains = await listDomainInventory();
|
|
34645
34860
|
let synced = 0;
|
|
34646
34861
|
let created = 0;
|
|
34647
34862
|
let updated = 0;
|
|
@@ -34650,20 +34865,39 @@ function createRoute53Provider(config) {
|
|
|
34650
34865
|
try {
|
|
34651
34866
|
const existing = dbFns.getDomainByName(d3.domain);
|
|
34652
34867
|
if (existing) {
|
|
34868
|
+
const existingRoute53 = existing.metadata["route53"];
|
|
34869
|
+
const staleDnsOnlyRegistrar = d3.registrar !== "AWS Route 53" && (existing.registrar === "AWS Route 53 DNS" || existing.registrar === "AWS Route 53" && existingRoute53?.source === "route53:hosted_zones");
|
|
34653
34870
|
dbFns.updateDomain(existing.id, {
|
|
34654
|
-
registrar: "AWS Route 53",
|
|
34655
|
-
|
|
34871
|
+
...d3.registrar === "AWS Route 53" ? { registrar: "AWS Route 53" } : {},
|
|
34872
|
+
...staleDnsOnlyRegistrar ? { registrar: null } : {},
|
|
34873
|
+
expires_at: d3.expires || undefined,
|
|
34656
34874
|
auto_renew: d3.auto_renew,
|
|
34875
|
+
nameservers: d3.nameservers.length > 0 ? d3.nameservers : existing.nameservers,
|
|
34876
|
+
metadata: {
|
|
34877
|
+
...existing.metadata,
|
|
34878
|
+
route53: {
|
|
34879
|
+
source: d3.registrar === "AWS Route 53" ? "route53domains+hosted_zones" : "route53:hosted_zones",
|
|
34880
|
+
synced_at: new Date().toISOString()
|
|
34881
|
+
}
|
|
34882
|
+
},
|
|
34657
34883
|
status: "active"
|
|
34658
34884
|
});
|
|
34659
34885
|
updated++;
|
|
34660
34886
|
} else {
|
|
34661
34887
|
dbFns.createDomain({
|
|
34662
34888
|
name: d3.domain,
|
|
34663
|
-
registrar: "AWS Route 53",
|
|
34664
|
-
expires_at: d3.
|
|
34889
|
+
...d3.registrar === "AWS Route 53" ? { registrar: "AWS Route 53" } : {},
|
|
34890
|
+
expires_at: d3.expires || undefined,
|
|
34665
34891
|
auto_renew: d3.auto_renew,
|
|
34666
|
-
|
|
34892
|
+
nameservers: d3.nameservers,
|
|
34893
|
+
status: "active",
|
|
34894
|
+
notes: d3.registrar === "AWS Route 53 DNS" ? "Discovered from Route 53 hosted zones; registrar ownership was not inferred." : undefined,
|
|
34895
|
+
metadata: {
|
|
34896
|
+
route53: {
|
|
34897
|
+
source: d3.registrar === "AWS Route 53" ? "route53domains+hosted_zones" : "route53:hosted_zones",
|
|
34898
|
+
synced_at: new Date().toISOString()
|
|
34899
|
+
}
|
|
34900
|
+
}
|
|
34667
34901
|
});
|
|
34668
34902
|
created++;
|
|
34669
34903
|
}
|
|
@@ -34681,15 +34915,132 @@ var init_route53 = __esm(() => {
|
|
|
34681
34915
|
init_dist_es14();
|
|
34682
34916
|
});
|
|
34683
34917
|
|
|
34684
|
-
// src/lib/
|
|
34685
|
-
function
|
|
34686
|
-
const
|
|
34687
|
-
|
|
34688
|
-
|
|
34918
|
+
// src/lib/env-aliases.ts
|
|
34919
|
+
function firstEnv(env, names) {
|
|
34920
|
+
for (const key of names) {
|
|
34921
|
+
const value = env[key];
|
|
34922
|
+
if (value)
|
|
34923
|
+
return { key, value };
|
|
34689
34924
|
}
|
|
34690
|
-
|
|
34691
|
-
|
|
34925
|
+
return;
|
|
34926
|
+
}
|
|
34927
|
+
function hasEveryEnv(env, groups) {
|
|
34928
|
+
return groups.every((names) => !!firstEnv(env, names));
|
|
34929
|
+
}
|
|
34930
|
+
function flattenEnvNames(groups) {
|
|
34931
|
+
return Array.from(new Set(groups.flatMap((names) => [...names])));
|
|
34932
|
+
}
|
|
34933
|
+
function providerEnvNames(provider) {
|
|
34934
|
+
switch (provider.toLowerCase()) {
|
|
34935
|
+
case "namecheap":
|
|
34936
|
+
return flattenEnvNames([NAMECHEAP_ENV.apiKey, NAMECHEAP_ENV.username, NAMECHEAP_ENV.clientIp]);
|
|
34937
|
+
case "godaddy":
|
|
34938
|
+
return flattenEnvNames([GODADDY_ENV.apiKey, GODADDY_ENV.apiSecret]);
|
|
34939
|
+
case "brandsight":
|
|
34940
|
+
return flattenEnvNames([
|
|
34941
|
+
BRANDSIGHT_ENV.apiKey,
|
|
34942
|
+
BRANDSIGHT_ENV.apiSecret,
|
|
34943
|
+
BRANDSIGHT_ENV.customerId,
|
|
34944
|
+
BRANDSIGHT_ENV.shopperId,
|
|
34945
|
+
BRANDSIGHT_ENV.accountId
|
|
34946
|
+
]);
|
|
34947
|
+
case "sedo":
|
|
34948
|
+
return flattenEnvNames([SEDO_ENV.partnerId, SEDO_ENV.signKey, SEDO_ENV.username, SEDO_ENV.password]);
|
|
34949
|
+
case "cloudflare":
|
|
34950
|
+
return flattenEnvNames([CLOUDFLARE_ENV.apiToken, CLOUDFLARE_ENV.apiKey, CLOUDFLARE_ENV.email, CLOUDFLARE_ENV.accountId]);
|
|
34951
|
+
case "route53":
|
|
34952
|
+
return ["AWS_PROFILE", "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_REGION"];
|
|
34953
|
+
case "aws:beepmedia":
|
|
34954
|
+
return flattenEnvNames([BEEPMEDIA_AWS_ENV.accessKeyId, BEEPMEDIA_AWS_ENV.secretAccessKey]);
|
|
34955
|
+
default:
|
|
34956
|
+
return [];
|
|
34957
|
+
}
|
|
34958
|
+
}
|
|
34959
|
+
function hasProviderCredentials(provider, env = process.env) {
|
|
34960
|
+
switch (provider.toLowerCase()) {
|
|
34961
|
+
case "namecheap":
|
|
34962
|
+
return hasEveryEnv(env, [NAMECHEAP_ENV.apiKey, NAMECHEAP_ENV.username, NAMECHEAP_ENV.clientIp]);
|
|
34963
|
+
case "godaddy":
|
|
34964
|
+
return hasEveryEnv(env, [GODADDY_ENV.apiKey, GODADDY_ENV.apiSecret]);
|
|
34965
|
+
case "brandsight":
|
|
34966
|
+
return hasEveryEnv(env, [BRANDSIGHT_ENV.apiKey, BRANDSIGHT_ENV.apiSecret, BRANDSIGHT_ENV.customerId]);
|
|
34967
|
+
case "sedo":
|
|
34968
|
+
return hasEveryEnv(env, [SEDO_ENV.partnerId, SEDO_ENV.signKey, SEDO_ENV.username, SEDO_ENV.password]);
|
|
34969
|
+
case "cloudflare": {
|
|
34970
|
+
const tokenMode = !!firstEnv(env, CLOUDFLARE_ENV.apiToken);
|
|
34971
|
+
const keyMode = hasEveryEnv(env, [CLOUDFLARE_ENV.apiKey, CLOUDFLARE_ENV.email]);
|
|
34972
|
+
return tokenMode || keyMode;
|
|
34973
|
+
}
|
|
34974
|
+
case "route53":
|
|
34975
|
+
return !!env["AWS_PROFILE"] || hasEveryEnv(env, [["AWS_ACCESS_KEY_ID"], ["AWS_SECRET_ACCESS_KEY"]]);
|
|
34976
|
+
case "aws:beepmedia":
|
|
34977
|
+
return hasEveryEnv(env, [BEEPMEDIA_AWS_ENV.accessKeyId, BEEPMEDIA_AWS_ENV.secretAccessKey]);
|
|
34978
|
+
default:
|
|
34979
|
+
return false;
|
|
34692
34980
|
}
|
|
34981
|
+
}
|
|
34982
|
+
var NAMECHEAP_ENV, GODADDY_ENV, BRANDSIGHT_ENV, SEDO_ENV, CLOUDFLARE_ENV, BEEPMEDIA_AWS_ENV;
|
|
34983
|
+
var init_env_aliases = __esm(() => {
|
|
34984
|
+
NAMECHEAP_ENV = {
|
|
34985
|
+
apiKey: ["NAMECHEAP_API_KEY"],
|
|
34986
|
+
username: ["NAMECHEAP_USERNAME"],
|
|
34987
|
+
clientIp: ["NAMECHEAP_CLIENT_IP"]
|
|
34988
|
+
};
|
|
34989
|
+
GODADDY_ENV = {
|
|
34990
|
+
apiKey: ["GODADDY_API_KEY"],
|
|
34991
|
+
apiSecret: ["GODADDY_API_SECRET"]
|
|
34992
|
+
};
|
|
34993
|
+
BRANDSIGHT_ENV = {
|
|
34994
|
+
apiKey: [
|
|
34995
|
+
"BRANDSIGHT_API_KEY",
|
|
34996
|
+
"HASNAXYZ_BRANDSIGHT_LIVE_API_KEY",
|
|
34997
|
+
"HASNAXYZ_BRANDSIGHT_SANDBOX_API_KEY"
|
|
34998
|
+
],
|
|
34999
|
+
apiSecret: [
|
|
35000
|
+
"BRANDSIGHT_API_SECRET",
|
|
35001
|
+
"HASNAXYZ_BRANDSIGHT_LIVE_API_SECRET",
|
|
35002
|
+
"HASNAXYZ_BRANDSIGHT_SANDBOX_API_SECRET"
|
|
35003
|
+
],
|
|
35004
|
+
customerId: [
|
|
35005
|
+
"BRANDSIGHT_CUSTOMER_ID",
|
|
35006
|
+
"HASNAXYZ_BRANDSIGHT_LIVE_CUSTOMER_ID",
|
|
35007
|
+
"HASNAXYZ_BRANDSIGHT_SANDBOX_CUSTOMER_ID"
|
|
35008
|
+
],
|
|
35009
|
+
shopperId: [
|
|
35010
|
+
"BRANDSIGHT_SHOPPER_ID",
|
|
35011
|
+
"HASNAXYZ_BRANDSIGHT_LIVE_SHOPPER_ID",
|
|
35012
|
+
"HASNAXYZ_BRANDSIGHT_SANDBOX_SHOPPER_ID"
|
|
35013
|
+
],
|
|
35014
|
+
accountId: ["BRANDSIGHT_ACCOUNT_ID", "HASNAXYZ_BRANDSIGHT_LIVE_ACCOUNT_ID"]
|
|
35015
|
+
};
|
|
35016
|
+
SEDO_ENV = {
|
|
35017
|
+
partnerId: ["SEDO_PARTNER_ID", "HASNAXYZ_SEDO_LIVE_PARTNER_ID"],
|
|
35018
|
+
signKey: ["SEDO_API_KEY", "SEDO_SIGN_KEY", "HASNAXYZ_SEDO_LIVE_API_KEY"],
|
|
35019
|
+
username: ["SEDO_USERNAME", "SEDO_EMAIL", "HASNAXYZ_SEDO_LIVE_USERNAME", "HASNAXYZ_SEDO_LIVE_EMAIL"],
|
|
35020
|
+
password: ["SEDO_PASSWORD", "HASNAXYZ_SEDO_LIVE_PASSWORD"]
|
|
35021
|
+
};
|
|
35022
|
+
CLOUDFLARE_ENV = {
|
|
35023
|
+
apiToken: ["CLOUDFLARE_API_TOKEN", "HASNAXYZ_CLOUDFLARE_LIVE_API_TOKEN"],
|
|
35024
|
+
apiKey: ["CLOUDFLARE_API_KEY", "HASNAXYZ_CLOUDFLARE_LIVE_API_KEY"],
|
|
35025
|
+
email: ["CLOUDFLARE_EMAIL", "HASNAXYZ_CLOUDFLARE_LIVE_EMAIL"],
|
|
35026
|
+
accountId: ["CLOUDFLARE_ACCOUNT_ID", "HASNAXYZ_CLOUDFLARE_LIVE_ACCOUNT_ID"]
|
|
35027
|
+
};
|
|
35028
|
+
BEEPMEDIA_AWS_ENV = {
|
|
35029
|
+
accessKeyId: ["HASNASTUDIO_BEEPMEDIA_AWS_LIVE_ACCESS_KEY_ID", "BEEPMEDIA_AWS_ACCESS_KEY_ID"],
|
|
35030
|
+
secretAccessKey: ["HASNASTUDIO_BEEPMEDIA_AWS_LIVE_SECRET_ACCESS_KEY", "BEEPMEDIA_AWS_SECRET_ACCESS_KEY"]
|
|
35031
|
+
};
|
|
35032
|
+
});
|
|
35033
|
+
|
|
35034
|
+
// src/lib/cloudflare-auth.ts
|
|
35035
|
+
function resolveCloudflareConfig(env = process.env) {
|
|
35036
|
+
const accountId = firstEnv(env, CLOUDFLARE_ENV.accountId)?.value;
|
|
35037
|
+
const apiToken = firstEnv(env, CLOUDFLARE_ENV.apiToken)?.value;
|
|
35038
|
+
if (apiToken)
|
|
35039
|
+
return { apiToken, accountId };
|
|
35040
|
+
const apiKey = firstEnv(env, CLOUDFLARE_ENV.apiKey)?.value;
|
|
35041
|
+
const email = firstEnv(env, CLOUDFLARE_ENV.email)?.value;
|
|
35042
|
+
if (apiKey && email)
|
|
35043
|
+
return { apiKey, email, accountId };
|
|
34693
35044
|
return accountId ? { accountId } : {};
|
|
34694
35045
|
}
|
|
34695
35046
|
function cloudflareAuthHeaders(cfg) {
|
|
@@ -34699,6 +35050,9 @@ function cloudflareAuthHeaders(cfg) {
|
|
|
34699
35050
|
return { "X-Auth-Key": cfg.apiKey, "X-Auth-Email": cfg.email };
|
|
34700
35051
|
throw new Error("Cloudflare credentials not configured. Set CLOUDFLARE_API_TOKEN, or CLOUDFLARE_API_KEY + CLOUDFLARE_EMAIL.");
|
|
34701
35052
|
}
|
|
35053
|
+
var init_cloudflare_auth = __esm(() => {
|
|
35054
|
+
init_env_aliases();
|
|
35055
|
+
});
|
|
34702
35056
|
|
|
34703
35057
|
// src/lib/cloudflare.ts
|
|
34704
35058
|
function getConfig3() {
|
|
@@ -34785,26 +35139,111 @@ async function listRecords2(zoneId, config) {
|
|
|
34785
35139
|
}
|
|
34786
35140
|
return records;
|
|
34787
35141
|
}
|
|
34788
|
-
async function
|
|
34789
|
-
const
|
|
34790
|
-
|
|
34791
|
-
|
|
34792
|
-
|
|
34793
|
-
|
|
34794
|
-
|
|
34795
|
-
|
|
34796
|
-
|
|
34797
|
-
|
|
34798
|
-
|
|
34799
|
-
|
|
34800
|
-
|
|
34801
|
-
|
|
35142
|
+
async function listRecordsByNameType(zoneId, type, name, config) {
|
|
35143
|
+
const result = await cfFetch(`/zones/${zoneId}/dns_records?type=${encodeURIComponent(type)}&name=${encodeURIComponent(name)}`, { config });
|
|
35144
|
+
return (result ?? []).map((r3) => ({
|
|
35145
|
+
id: r3.id,
|
|
35146
|
+
type: r3.type,
|
|
35147
|
+
name: r3.name,
|
|
35148
|
+
content: r3.content,
|
|
35149
|
+
ttl: r3.ttl,
|
|
35150
|
+
priority: r3.priority,
|
|
35151
|
+
proxied: r3.proxied
|
|
35152
|
+
}));
|
|
35153
|
+
}
|
|
35154
|
+
async function replaceRecordsByNameType(zoneId, records, config) {
|
|
35155
|
+
if (records.length === 0)
|
|
35156
|
+
return;
|
|
35157
|
+
const { type, name } = records[0];
|
|
35158
|
+
const existing = await listRecordsByNameType(zoneId, type, name, config);
|
|
35159
|
+
for (const record of existing) {
|
|
35160
|
+
if (record.id)
|
|
35161
|
+
await deleteRecord2(zoneId, record.id, config);
|
|
35162
|
+
}
|
|
35163
|
+
for (const record of records) {
|
|
35164
|
+
await cfFetch(`/zones/${zoneId}/dns_records`, {
|
|
35165
|
+
method: "POST",
|
|
35166
|
+
body: {
|
|
35167
|
+
type: record.type,
|
|
35168
|
+
name: record.name,
|
|
35169
|
+
content: record.content,
|
|
35170
|
+
ttl: record.ttl ?? 1,
|
|
35171
|
+
priority: record.priority,
|
|
35172
|
+
proxied: record.proxied ?? false
|
|
35173
|
+
},
|
|
35174
|
+
config
|
|
35175
|
+
});
|
|
34802
35176
|
}
|
|
34803
35177
|
}
|
|
35178
|
+
async function deleteRecord2(zoneId, recordId, config) {
|
|
35179
|
+
await cfFetch(`/zones/${zoneId}/dns_records/${recordId}`, { method: "DELETE", config });
|
|
35180
|
+
}
|
|
35181
|
+
function zoneToDomainInfo(zone) {
|
|
35182
|
+
return {
|
|
35183
|
+
domain: zone.name,
|
|
35184
|
+
registrar: "Cloudflare DNS",
|
|
35185
|
+
created: "",
|
|
35186
|
+
expires: "",
|
|
35187
|
+
nameservers: zone.nameservers,
|
|
35188
|
+
status: zone.status === "active" ? "active" : "discovered",
|
|
35189
|
+
auto_renew: false
|
|
35190
|
+
};
|
|
35191
|
+
}
|
|
35192
|
+
function withCloudflareMetadata(existing, zone) {
|
|
35193
|
+
return {
|
|
35194
|
+
...existing,
|
|
35195
|
+
cloudflare: {
|
|
35196
|
+
zone_id: zone.id,
|
|
35197
|
+
zone_status: zone.status,
|
|
35198
|
+
source: "cloudflare:zones",
|
|
35199
|
+
synced_at: new Date().toISOString()
|
|
35200
|
+
}
|
|
35201
|
+
};
|
|
35202
|
+
}
|
|
34804
35203
|
function createCloudflareProvider(config) {
|
|
34805
35204
|
const cfg = config ?? getConfig3();
|
|
34806
35205
|
return {
|
|
34807
35206
|
name: "cloudflare",
|
|
35207
|
+
async listDomains() {
|
|
35208
|
+
const zones = await listZones(cfg);
|
|
35209
|
+
return zones.map(zoneToDomainInfo);
|
|
35210
|
+
},
|
|
35211
|
+
async syncToLocalDb(dbFns) {
|
|
35212
|
+
const zones = await listZones(cfg);
|
|
35213
|
+
let synced = 0;
|
|
35214
|
+
let created = 0;
|
|
35215
|
+
let updated = 0;
|
|
35216
|
+
const errors3 = [];
|
|
35217
|
+
for (const zone of zones) {
|
|
35218
|
+
try {
|
|
35219
|
+
const info = zoneToDomainInfo(zone);
|
|
35220
|
+
const existing = dbFns.getDomainByName(zone.name);
|
|
35221
|
+
if (existing) {
|
|
35222
|
+
dbFns.updateDomain(existing.id, {
|
|
35223
|
+
...existing.registrar === "Cloudflare DNS" ? { registrar: null } : {},
|
|
35224
|
+
status: existing.status === "discovered" && info.status === "active" ? "active" : existing.status,
|
|
35225
|
+
nameservers: zone.nameservers,
|
|
35226
|
+
metadata: withCloudflareMetadata(existing.metadata, zone)
|
|
35227
|
+
});
|
|
35228
|
+
updated++;
|
|
35229
|
+
} else {
|
|
35230
|
+
dbFns.createDomain({
|
|
35231
|
+
name: zone.name,
|
|
35232
|
+
status: info.status === "active" ? "active" : "discovered",
|
|
35233
|
+
auto_renew: false,
|
|
35234
|
+
nameservers: zone.nameservers,
|
|
35235
|
+
notes: "Discovered from Cloudflare zones; registrar ownership was not inferred.",
|
|
35236
|
+
metadata: withCloudflareMetadata({}, zone)
|
|
35237
|
+
});
|
|
35238
|
+
created++;
|
|
35239
|
+
}
|
|
35240
|
+
synced++;
|
|
35241
|
+
} catch (err) {
|
|
35242
|
+
errors3.push(`${zone.name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
35243
|
+
}
|
|
35244
|
+
}
|
|
35245
|
+
return { synced, created, updated, errors: errors3 };
|
|
35246
|
+
},
|
|
34808
35247
|
async getDnsRecords(domain) {
|
|
34809
35248
|
const zone = await getZone(domain, cfg);
|
|
34810
35249
|
if (!zone)
|
|
@@ -34822,65 +35261,59 @@ function createCloudflareProvider(config) {
|
|
|
34822
35261
|
const zone = await getZone(domain, cfg);
|
|
34823
35262
|
if (!zone)
|
|
34824
35263
|
throw new Error(`No Cloudflare zone found for ${domain}`);
|
|
35264
|
+
const grouped = new Map;
|
|
34825
35265
|
for (const r3 of records) {
|
|
34826
|
-
|
|
35266
|
+
const key = `${r3.type}|${r3.name}`;
|
|
35267
|
+
const existing = grouped.get(key) ?? [];
|
|
35268
|
+
existing.push({ type: r3.type, name: r3.name, content: r3.value, ttl: r3.ttl || 1, priority: r3.priority });
|
|
35269
|
+
grouped.set(key, existing);
|
|
35270
|
+
}
|
|
35271
|
+
for (const group of grouped.values()) {
|
|
35272
|
+
await replaceRecordsByNameType(zone.id, group, cfg);
|
|
34827
35273
|
}
|
|
34828
35274
|
return true;
|
|
34829
35275
|
}
|
|
34830
35276
|
};
|
|
34831
35277
|
}
|
|
34832
35278
|
var CF_BASE = "https://api.cloudflare.com/client/v4";
|
|
34833
|
-
var init_cloudflare = () => {
|
|
35279
|
+
var init_cloudflare = __esm(() => {
|
|
35280
|
+
init_cloudflare_auth();
|
|
35281
|
+
});
|
|
34834
35282
|
|
|
34835
35283
|
// src/lib/brandsight.ts
|
|
34836
35284
|
function resolveBrandsightConfig(env = process.env) {
|
|
35285
|
+
const apiKey = firstEnv(env, BRANDSIGHT_ENV.apiKey)?.value ?? "";
|
|
35286
|
+
const apiSecret = firstEnv(env, BRANDSIGHT_ENV.apiSecret)?.value;
|
|
35287
|
+
const customerId = firstEnv(env, BRANDSIGHT_ENV.customerId)?.value;
|
|
35288
|
+
const shopperId = firstEnv(env, BRANDSIGHT_ENV.shopperId)?.value;
|
|
35289
|
+
const accountId = firstEnv(env, BRANDSIGHT_ENV.accountId)?.value;
|
|
34837
35290
|
return {
|
|
34838
|
-
apiKey
|
|
34839
|
-
apiSecret
|
|
34840
|
-
customerId
|
|
34841
|
-
shopperId
|
|
34842
|
-
accountId
|
|
35291
|
+
apiKey,
|
|
35292
|
+
apiSecret,
|
|
35293
|
+
customerId,
|
|
35294
|
+
shopperId,
|
|
35295
|
+
accountId,
|
|
35296
|
+
baseUrl: env["BRANDSIGHT_BASE_URL"]
|
|
35297
|
+
};
|
|
35298
|
+
}
|
|
35299
|
+
function brandsightCapability(env = process.env) {
|
|
35300
|
+
const cfg = resolveBrandsightConfig(env);
|
|
35301
|
+
const configured = !!(cfg.apiKey && cfg.apiSecret && cfg.customerId);
|
|
35302
|
+
return {
|
|
35303
|
+
configured,
|
|
35304
|
+
gated: true,
|
|
35305
|
+
notes: configured ? "Credentials present, but GoDaddy Corporate Domains (Brandsight) is enterprise/contract-only; use Route53 for automated purchase." : "Not configured and enterprise/contract-only; not usable for automated purchase."
|
|
34843
35306
|
};
|
|
34844
35307
|
}
|
|
34845
35308
|
function getApiKey() {
|
|
34846
|
-
const key =
|
|
35309
|
+
const key = resolveBrandsightConfig().apiKey;
|
|
34847
35310
|
if (!key) {
|
|
34848
|
-
throw new BrandsightApiError("
|
|
35311
|
+
throw new BrandsightApiError("Brandsight API key is not set. Set BRANDSIGHT_API_KEY or HASNAXYZ_BRANDSIGHT_LIVE_API_KEY.");
|
|
34849
35312
|
}
|
|
34850
35313
|
return key;
|
|
34851
35314
|
}
|
|
34852
35315
|
function getConfig4() {
|
|
34853
|
-
return
|
|
34854
|
-
apiKey: process.env["BRANDSIGHT_API_KEY"] ?? "",
|
|
34855
|
-
accountId: process.env["BRANDSIGHT_ACCOUNT_ID"]
|
|
34856
|
-
};
|
|
34857
|
-
}
|
|
34858
|
-
async function apiRequest3(method, path, apiKey, body, baseUrl = BRANDSIGHT_BASE) {
|
|
34859
|
-
const fetchFn = _fetchFn || globalThis.fetch;
|
|
34860
|
-
const url = `${baseUrl}${path}`;
|
|
34861
|
-
const headers = {
|
|
34862
|
-
Authorization: `Bearer ${apiKey}`,
|
|
34863
|
-
"Content-Type": "application/json",
|
|
34864
|
-
Accept: "application/json",
|
|
34865
|
-
"User-Agent": USER_AGENT
|
|
34866
|
-
};
|
|
34867
|
-
try {
|
|
34868
|
-
const response = await fetchFn(url, {
|
|
34869
|
-
method,
|
|
34870
|
-
headers,
|
|
34871
|
-
body: body ? JSON.stringify(body) : undefined,
|
|
34872
|
-
signal: AbortSignal.timeout(15000)
|
|
34873
|
-
});
|
|
34874
|
-
if (!response.ok) {
|
|
34875
|
-
throw new BrandsightApiError(`Brandsight API ${method} ${path} failed with status ${response.status}`, response.status, await response.text());
|
|
34876
|
-
}
|
|
34877
|
-
const data = await response.json();
|
|
34878
|
-
return { data, stub: false };
|
|
34879
|
-
} catch (error) {
|
|
34880
|
-
if (error instanceof BrandsightApiError)
|
|
34881
|
-
throw error;
|
|
34882
|
-
return { data: null, stub: true };
|
|
34883
|
-
}
|
|
35316
|
+
return resolveBrandsightConfig();
|
|
34884
35317
|
}
|
|
34885
35318
|
async function apiGet(path, apiKey, baseUrl = BRANDSIGHT_BASE) {
|
|
34886
35319
|
const fetchFn = _fetchFn || globalThis.fetch;
|
|
@@ -34908,12 +35341,109 @@ async function apiGet(path, apiKey, baseUrl = BRANDSIGHT_BASE) {
|
|
|
34908
35341
|
return { data: null, stub: true };
|
|
34909
35342
|
}
|
|
34910
35343
|
}
|
|
35344
|
+
function requireDomainConfig(config) {
|
|
35345
|
+
const cfg = config ?? getConfig4();
|
|
35346
|
+
if (!cfg.apiKey || !cfg.apiSecret || !cfg.customerId) {
|
|
35347
|
+
throw new BrandsightApiError("Brandsight Domain API credentials are not configured. Set BRANDSIGHT_API_KEY, BRANDSIGHT_API_SECRET, and BRANDSIGHT_CUSTOMER_ID (or HASNAXYZ_BRANDSIGHT_LIVE_* aliases).");
|
|
35348
|
+
}
|
|
35349
|
+
return cfg;
|
|
35350
|
+
}
|
|
35351
|
+
function domainBaseUrl(cfg) {
|
|
35352
|
+
return cfg.baseUrl ?? BRANDSIGHT_DOMAIN_BASE;
|
|
35353
|
+
}
|
|
35354
|
+
function domainHeaders(cfg) {
|
|
35355
|
+
return {
|
|
35356
|
+
Authorization: `sso-key ${cfg.apiKey}:${cfg.apiSecret}`,
|
|
35357
|
+
"Content-Type": "application/json",
|
|
35358
|
+
Accept: "application/json",
|
|
35359
|
+
"User-Agent": USER_AGENT
|
|
35360
|
+
};
|
|
35361
|
+
}
|
|
35362
|
+
async function domainApiRequest(method, path, config, body) {
|
|
35363
|
+
const cfg = requireDomainConfig(config);
|
|
35364
|
+
const fetchFn = _fetchFn || globalThis.fetch;
|
|
35365
|
+
const response = await fetchFn(`${domainBaseUrl(cfg)}${path}`, {
|
|
35366
|
+
method,
|
|
35367
|
+
headers: domainHeaders(cfg),
|
|
35368
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
35369
|
+
signal: AbortSignal.timeout(30000)
|
|
35370
|
+
});
|
|
35371
|
+
const text = await response.text();
|
|
35372
|
+
if (!response.ok) {
|
|
35373
|
+
throw new BrandsightApiError(`Brandsight Domain API ${method} ${path} failed with status ${response.status}`, response.status, text);
|
|
35374
|
+
}
|
|
35375
|
+
if (!text.trim())
|
|
35376
|
+
return {};
|
|
35377
|
+
return JSON.parse(text);
|
|
35378
|
+
}
|
|
35379
|
+
function normalizeBrandsightDomain(raw) {
|
|
35380
|
+
const nameServers = raw.nameServers ?? raw.nameservers ?? [];
|
|
35381
|
+
const expiresAt = raw.expiresAt ?? raw.expires ?? "";
|
|
35382
|
+
const createdAt = raw.createdAt ?? raw.created ?? "";
|
|
35383
|
+
const renewAuto = raw.renewAuto ?? raw.auto_renew ?? false;
|
|
35384
|
+
return {
|
|
35385
|
+
...raw,
|
|
35386
|
+
domain: String(raw.domain ?? ""),
|
|
35387
|
+
status: String(raw.status ?? "UNKNOWN"),
|
|
35388
|
+
created: createdAt,
|
|
35389
|
+
expires: expiresAt,
|
|
35390
|
+
auto_renew: renewAuto,
|
|
35391
|
+
locked: Boolean(raw.locked),
|
|
35392
|
+
nameservers: nameServers,
|
|
35393
|
+
createdAt,
|
|
35394
|
+
expiresAt,
|
|
35395
|
+
nameServers,
|
|
35396
|
+
renewAuto
|
|
35397
|
+
};
|
|
35398
|
+
}
|
|
35399
|
+
function customerPath(cfg, path) {
|
|
35400
|
+
return `/customers/${encodeURIComponent(cfg.customerId)}${path}`;
|
|
35401
|
+
}
|
|
35402
|
+
function domainTld(domain) {
|
|
35403
|
+
const parts = domain.split(".").filter(Boolean);
|
|
35404
|
+
if (parts.length < 2)
|
|
35405
|
+
throw new BrandsightApiError(`Invalid domain name: ${domain}`);
|
|
35406
|
+
return parts.slice(1).join(".");
|
|
35407
|
+
}
|
|
35408
|
+
function brandsightContact(contact) {
|
|
35409
|
+
return {
|
|
35410
|
+
addressMailing: {
|
|
35411
|
+
address1: contact.address_line_1,
|
|
35412
|
+
city: contact.city,
|
|
35413
|
+
country: contact.country_code,
|
|
35414
|
+
postalCode: contact.zip_code,
|
|
35415
|
+
state: contact.state
|
|
35416
|
+
},
|
|
35417
|
+
email: contact.email,
|
|
35418
|
+
encoding: "ASCII",
|
|
35419
|
+
nameFirst: contact.first_name,
|
|
35420
|
+
nameLast: contact.last_name,
|
|
35421
|
+
organization: contact.organization_name,
|
|
35422
|
+
phone: contact.phone
|
|
35423
|
+
};
|
|
35424
|
+
}
|
|
35425
|
+
async function domainAvailability(domain, cfg, type, period = 1) {
|
|
35426
|
+
const params = new URLSearchParams({
|
|
35427
|
+
domain,
|
|
35428
|
+
period: String(period),
|
|
35429
|
+
type,
|
|
35430
|
+
optimizeFor: "ACCURACY"
|
|
35431
|
+
});
|
|
35432
|
+
const result = await domainApiRequest("GET", `/domains/available?${params.toString()}`, cfg);
|
|
35433
|
+
return {
|
|
35434
|
+
domain: result.domain ?? domain,
|
|
35435
|
+
available: Boolean(result.available),
|
|
35436
|
+
price: result.price,
|
|
35437
|
+
currency: result.currency,
|
|
35438
|
+
registryPremiumPricing: result.registryPremiumPricing
|
|
35439
|
+
};
|
|
35440
|
+
}
|
|
34911
35441
|
function generateStubAlerts(brandName) {
|
|
34912
|
-
const
|
|
35442
|
+
const now2 = new Date().toISOString();
|
|
34913
35443
|
return [
|
|
34914
|
-
{ domain: `${brandName}-deals.com`, type: "keyword", registered_at:
|
|
34915
|
-
{ domain: `${brandName.replace(/a/gi, "4").replace(/e/gi, "3")}.com`, type: "homoglyph", registered_at:
|
|
34916
|
-
{ domain: `${brandName}s.com`, type: "typosquat", registered_at:
|
|
35444
|
+
{ domain: `${brandName}-deals.com`, type: "keyword", registered_at: now2 },
|
|
35445
|
+
{ domain: `${brandName.replace(/a/gi, "4").replace(/e/gi, "3")}.com`, type: "homoglyph", registered_at: now2 },
|
|
35446
|
+
{ domain: `${brandName}s.com`, type: "typosquat", registered_at: now2 }
|
|
34917
35447
|
];
|
|
34918
35448
|
}
|
|
34919
35449
|
function generateStubSimilarDomains(domain) {
|
|
@@ -34960,32 +35490,160 @@ async function getThreatAssessment(domain) {
|
|
|
34960
35490
|
return { ...result.data, stub: false };
|
|
34961
35491
|
}
|
|
34962
35492
|
async function listDomains2(config) {
|
|
34963
|
-
const cfg = config
|
|
34964
|
-
const
|
|
34965
|
-
|
|
34966
|
-
|
|
34967
|
-
|
|
35493
|
+
const cfg = requireDomainConfig(config);
|
|
35494
|
+
const domains = [];
|
|
35495
|
+
const seenMarkers = new Set;
|
|
35496
|
+
let marker;
|
|
35497
|
+
while (true) {
|
|
35498
|
+
const params = new URLSearchParams({ limit: "500" });
|
|
35499
|
+
if (marker)
|
|
35500
|
+
params.set("marker", marker);
|
|
35501
|
+
const batch = await domainApiRequest("GET", customerPath(cfg, `/domains?${params.toString()}`), cfg);
|
|
35502
|
+
if (!Array.isArray(batch) || batch.length === 0)
|
|
35503
|
+
break;
|
|
35504
|
+
domains.push(...batch.map(normalizeBrandsightDomain).filter((d3) => d3.domain));
|
|
35505
|
+
if (batch.length < 500)
|
|
35506
|
+
break;
|
|
35507
|
+
const nextMarker = String(batch[batch.length - 1]?.domain ?? "");
|
|
35508
|
+
if (!nextMarker || seenMarkers.has(nextMarker))
|
|
35509
|
+
break;
|
|
35510
|
+
seenMarkers.add(nextMarker);
|
|
35511
|
+
marker = nextMarker;
|
|
35512
|
+
}
|
|
35513
|
+
return domains;
|
|
34968
35514
|
}
|
|
34969
35515
|
async function getDomainInfo3(domain, config) {
|
|
34970
|
-
const cfg = config
|
|
34971
|
-
const result = await
|
|
34972
|
-
|
|
34973
|
-
return null;
|
|
34974
|
-
return result.data;
|
|
35516
|
+
const cfg = requireDomainConfig(config);
|
|
35517
|
+
const result = await domainApiRequest("GET", customerPath(cfg, `/domains/${encodeURIComponent(domain)}`), cfg);
|
|
35518
|
+
return normalizeBrandsightDomain(result);
|
|
34975
35519
|
}
|
|
34976
35520
|
async function checkAvailability4(domain, config) {
|
|
34977
|
-
const cfg = config
|
|
34978
|
-
|
|
34979
|
-
if (result.stub)
|
|
34980
|
-
return { domain, available: false };
|
|
34981
|
-
return result.data;
|
|
35521
|
+
const cfg = requireDomainConfig(config);
|
|
35522
|
+
return domainAvailability(domain, cfg, "REGISTRATION", 1);
|
|
34982
35523
|
}
|
|
34983
35524
|
async function renewDomain3(domain, years = 1, config) {
|
|
34984
|
-
const cfg = config
|
|
34985
|
-
const
|
|
34986
|
-
if (
|
|
34987
|
-
|
|
34988
|
-
|
|
35525
|
+
const cfg = requireDomainConfig(config);
|
|
35526
|
+
const current = await getDomainInfo3(domain, cfg);
|
|
35527
|
+
if (!current?.expires)
|
|
35528
|
+
throw new BrandsightApiError(`Cannot renew ${domain}: current expiry is unavailable`);
|
|
35529
|
+
const quote = await domainAvailability(domain, cfg, "RENEWAL", years);
|
|
35530
|
+
const price = quote.price ?? current.renewal?.price;
|
|
35531
|
+
const currency = quote.currency ?? current.renewal?.currency;
|
|
35532
|
+
if (price == null || !currency) {
|
|
35533
|
+
throw new BrandsightApiError(`Cannot renew ${domain}: renewal quote did not include an exact price and currency`);
|
|
35534
|
+
}
|
|
35535
|
+
const result = await domainApiRequest("POST", customerPath(cfg, `/domains/${encodeURIComponent(domain)}/renew`), cfg, {
|
|
35536
|
+
consent: {
|
|
35537
|
+
agreedAt: new Date().toISOString(),
|
|
35538
|
+
agreedBy: cfg.shopperId ?? "domains-cli",
|
|
35539
|
+
currency,
|
|
35540
|
+
price,
|
|
35541
|
+
registryPremiumPricing: quote.registryPremiumPricing ?? false
|
|
35542
|
+
},
|
|
35543
|
+
expires: current.expires,
|
|
35544
|
+
period: years
|
|
35545
|
+
});
|
|
35546
|
+
return { success: true, orderId: result.orderId ?? result.id };
|
|
35547
|
+
}
|
|
35548
|
+
async function getLegalAgreements(tld, privacy = false, config) {
|
|
35549
|
+
const cfg = requireDomainConfig(config);
|
|
35550
|
+
const params = new URLSearchParams({
|
|
35551
|
+
privacy: String(privacy),
|
|
35552
|
+
tlds: tld
|
|
35553
|
+
});
|
|
35554
|
+
return domainApiRequest("GET", customerPath(cfg, `/domains/agreements?${params.toString()}`), cfg);
|
|
35555
|
+
}
|
|
35556
|
+
async function getRegistrationSchema(tld, config) {
|
|
35557
|
+
const cfg = requireDomainConfig(config);
|
|
35558
|
+
return domainApiRequest("GET", customerPath(cfg, `/domains/register/schema/${encodeURIComponent(tld)}`), cfg);
|
|
35559
|
+
}
|
|
35560
|
+
async function validateRegistrationRequest(payload, config) {
|
|
35561
|
+
const cfg = requireDomainConfig(config);
|
|
35562
|
+
await domainApiRequest("POST", customerPath(cfg, "/domains/register/validate"), cfg, payload);
|
|
35563
|
+
return true;
|
|
35564
|
+
}
|
|
35565
|
+
async function registerBrandsightDomain(domain, contact, options = {}, config) {
|
|
35566
|
+
const cfg = requireDomainConfig(config);
|
|
35567
|
+
const period = options.years ?? 1;
|
|
35568
|
+
const availability = await domainAvailability(domain, cfg, "REGISTRATION", period);
|
|
35569
|
+
if (!availability.available)
|
|
35570
|
+
throw new BrandsightApiError(`${domain} is not available for registration`);
|
|
35571
|
+
const price = options.premiumPrice ?? availability.price;
|
|
35572
|
+
if (price == null || !availability.currency) {
|
|
35573
|
+
throw new BrandsightApiError(`Cannot register ${domain}: availability quote did not include an exact price and currency`);
|
|
35574
|
+
}
|
|
35575
|
+
const tld = domainTld(domain);
|
|
35576
|
+
const schema = await getRegistrationSchema(tld, cfg);
|
|
35577
|
+
const required = Array.isArray(schema["required"]) ? schema["required"] : [];
|
|
35578
|
+
if (required.includes("metadata") && !options.metadata) {
|
|
35579
|
+
throw new BrandsightApiError(`Cannot register ${domain}: ${tld} requires TLD-specific metadata; pass registration metadata before validation`);
|
|
35580
|
+
}
|
|
35581
|
+
const privacy = options.privacy ?? false;
|
|
35582
|
+
const agreements = await getLegalAgreements(tld, privacy, cfg);
|
|
35583
|
+
const agreementKeys = agreements.map((a3) => a3.agreementKey).filter(Boolean);
|
|
35584
|
+
const c3 = brandsightContact(contact);
|
|
35585
|
+
const payload = {
|
|
35586
|
+
consent: {
|
|
35587
|
+
agreedAt: new Date().toISOString(),
|
|
35588
|
+
agreedBy: contact.email || cfg.shopperId || "domains-cli",
|
|
35589
|
+
agreementKeys,
|
|
35590
|
+
currency: availability.currency,
|
|
35591
|
+
price,
|
|
35592
|
+
registryPremiumPricing: availability.registryPremiumPricing ?? !!options.premiumPrice
|
|
35593
|
+
},
|
|
35594
|
+
contacts: {
|
|
35595
|
+
admin: c3,
|
|
35596
|
+
billing: c3,
|
|
35597
|
+
registrant: c3,
|
|
35598
|
+
tech: c3
|
|
35599
|
+
},
|
|
35600
|
+
domain,
|
|
35601
|
+
metadata: options.metadata ?? {},
|
|
35602
|
+
nameServers: options.nameservers ?? [],
|
|
35603
|
+
period,
|
|
35604
|
+
privacy,
|
|
35605
|
+
renewAuto: options.autoRenew ?? true
|
|
35606
|
+
};
|
|
35607
|
+
await validateRegistrationRequest(payload, cfg);
|
|
35608
|
+
const result = await domainApiRequest("POST", customerPath(cfg, "/domains/register"), cfg, payload);
|
|
35609
|
+
return {
|
|
35610
|
+
success: true,
|
|
35611
|
+
orderId: result.orderId ?? result.id,
|
|
35612
|
+
operationId: result.operationId ?? result.id,
|
|
35613
|
+
chargedAmount: String(price)
|
|
35614
|
+
};
|
|
35615
|
+
}
|
|
35616
|
+
async function updateNameservers3(domain, nameservers, config) {
|
|
35617
|
+
const cfg = requireDomainConfig(config);
|
|
35618
|
+
const result = await domainApiRequest("PUT", customerPath(cfg, `/domains/${encodeURIComponent(domain)}/nameServers`), cfg, { nameServers: nameservers });
|
|
35619
|
+
return { success: true, operationId: result.operationId ?? result.id };
|
|
35620
|
+
}
|
|
35621
|
+
async function getDnsRecords3(domain, config) {
|
|
35622
|
+
const cfg = requireDomainConfig(config);
|
|
35623
|
+
const records = [];
|
|
35624
|
+
let offset = 0;
|
|
35625
|
+
const limit = 1000;
|
|
35626
|
+
while (true) {
|
|
35627
|
+
const batch = await domainApiRequest("GET", customerPath(cfg, `/domains/${encodeURIComponent(domain)}/records?offset=${offset}&limit=${limit}`), cfg);
|
|
35628
|
+
if (!Array.isArray(batch) || batch.length === 0)
|
|
35629
|
+
break;
|
|
35630
|
+
records.push(...batch);
|
|
35631
|
+
if (batch.length < limit)
|
|
35632
|
+
break;
|
|
35633
|
+
offset++;
|
|
35634
|
+
}
|
|
35635
|
+
return records;
|
|
35636
|
+
}
|
|
35637
|
+
function normalizeBrandsightDnsRecord(record) {
|
|
35638
|
+
return {
|
|
35639
|
+
...record,
|
|
35640
|
+
ttl: Math.max(record.ttl || 600, 600)
|
|
35641
|
+
};
|
|
35642
|
+
}
|
|
35643
|
+
async function setDnsRecords3(domain, records, config) {
|
|
35644
|
+
const cfg = requireDomainConfig(config);
|
|
35645
|
+
await domainApiRequest("PUT", customerPath(cfg, `/domains/${encodeURIComponent(domain)}/records`), cfg, records.map(normalizeBrandsightDnsRecord));
|
|
35646
|
+
return true;
|
|
34989
35647
|
}
|
|
34990
35648
|
async function syncToLocalDb3(dbFns, config) {
|
|
34991
35649
|
const domains = await listDomains2(config);
|
|
@@ -35030,7 +35688,7 @@ function createBrandsightProvider(config) {
|
|
|
35030
35688
|
return domains.map((d3) => ({
|
|
35031
35689
|
domain: d3.domain,
|
|
35032
35690
|
registrar: "Brandsight",
|
|
35033
|
-
created: "",
|
|
35691
|
+
created: d3.created ?? "",
|
|
35034
35692
|
expires: d3.expires,
|
|
35035
35693
|
nameservers: d3.nameservers,
|
|
35036
35694
|
status: d3.status === "ACTIVE" ? "active" : d3.status.toLowerCase(),
|
|
@@ -35044,23 +35702,58 @@ function createBrandsightProvider(config) {
|
|
|
35044
35702
|
return {
|
|
35045
35703
|
domain: d3.domain,
|
|
35046
35704
|
registrar: "Brandsight",
|
|
35047
|
-
created: "",
|
|
35705
|
+
created: d3.created ?? "",
|
|
35048
35706
|
expires: d3.expires,
|
|
35049
35707
|
nameservers: d3.nameservers,
|
|
35050
35708
|
status: d3.status === "ACTIVE" ? "active" : d3.status.toLowerCase(),
|
|
35051
35709
|
auto_renew: d3.auto_renew
|
|
35052
35710
|
};
|
|
35053
35711
|
},
|
|
35054
|
-
async renewDomain(domain) {
|
|
35055
|
-
const result = await renewDomain3(domain,
|
|
35712
|
+
async renewDomain(domain, years = 1) {
|
|
35713
|
+
const result = await renewDomain3(domain, years, cfg);
|
|
35056
35714
|
return { domain, success: result.success, orderId: result.orderId };
|
|
35057
35715
|
},
|
|
35716
|
+
async registerDomain(domain, contact, options) {
|
|
35717
|
+
const result = await registerBrandsightDomain(domain, contact, options, cfg);
|
|
35718
|
+
return {
|
|
35719
|
+
domain,
|
|
35720
|
+
success: result.success,
|
|
35721
|
+
orderId: result.orderId,
|
|
35722
|
+
operationId: result.operationId,
|
|
35723
|
+
chargedAmount: result.chargedAmount
|
|
35724
|
+
};
|
|
35725
|
+
},
|
|
35726
|
+
async updateNameservers(domain, nameservers) {
|
|
35727
|
+
const result = await updateNameservers3(domain, nameservers, cfg);
|
|
35728
|
+
return { domain, success: result.success, operationId: result.operationId };
|
|
35729
|
+
},
|
|
35730
|
+
async getDnsRecords(domain) {
|
|
35731
|
+
const records = await getDnsRecords3(domain, cfg);
|
|
35732
|
+
return records.map((r3) => ({
|
|
35733
|
+
type: r3.type,
|
|
35734
|
+
name: r3.name,
|
|
35735
|
+
value: r3.data,
|
|
35736
|
+
ttl: r3.ttl,
|
|
35737
|
+
priority: r3.priority
|
|
35738
|
+
}));
|
|
35739
|
+
},
|
|
35740
|
+
async setDnsRecords(domain, records) {
|
|
35741
|
+
return setDnsRecords3(domain, records.map((r3) => ({
|
|
35742
|
+
type: r3.type,
|
|
35743
|
+
name: r3.name,
|
|
35744
|
+
data: r3.value,
|
|
35745
|
+
ttl: r3.ttl,
|
|
35746
|
+
priority: r3.priority
|
|
35747
|
+
})), cfg);
|
|
35748
|
+
},
|
|
35058
35749
|
async checkAvailability(domain) {
|
|
35059
35750
|
const result = await checkAvailability4(domain, cfg);
|
|
35060
35751
|
return {
|
|
35061
35752
|
domain: result.domain,
|
|
35062
35753
|
available: result.available,
|
|
35063
|
-
|
|
35754
|
+
is_premium: result.registryPremiumPricing,
|
|
35755
|
+
premium_price: result.registryPremiumPricing ? result.price : undefined,
|
|
35756
|
+
standard_price: result.registryPremiumPricing ? undefined : result.price,
|
|
35064
35757
|
currency: result.currency
|
|
35065
35758
|
};
|
|
35066
35759
|
},
|
|
@@ -35069,8 +35762,9 @@ function createBrandsightProvider(config) {
|
|
|
35069
35762
|
}
|
|
35070
35763
|
};
|
|
35071
35764
|
}
|
|
35072
|
-
var BrandsightApiError, _fetchFn = null, BRANDSIGHT_BASE = "https://api.brandsight.com/v1";
|
|
35765
|
+
var BrandsightApiError, _fetchFn = null, BRANDSIGHT_BASE = "https://api.brandsight.com/v1", BRANDSIGHT_DOMAIN_BASE = "https://api.godaddy.com/v2";
|
|
35073
35766
|
var init_brandsight = __esm(() => {
|
|
35767
|
+
init_env_aliases();
|
|
35074
35768
|
init_version();
|
|
35075
35769
|
BrandsightApiError = class BrandsightApiError extends Error {
|
|
35076
35770
|
statusCode;
|
|
@@ -35095,9 +35789,9 @@ __export(exports_config, {
|
|
|
35095
35789
|
getConfigKey: () => getConfigKey,
|
|
35096
35790
|
applyPurchaseProfile: () => applyPurchaseProfile
|
|
35097
35791
|
});
|
|
35098
|
-
import { existsSync as
|
|
35099
|
-
import { homedir as
|
|
35100
|
-
import { join as
|
|
35792
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "fs";
|
|
35793
|
+
import { homedir as homedir4 } from "os";
|
|
35794
|
+
import { join as join4 } from "path";
|
|
35101
35795
|
function getPurchaseProfile() {
|
|
35102
35796
|
return loadConfig3().purchase_aws_profile ?? process.env["DOMAINS_PURCHASE_AWS_PROFILE"] ?? undefined;
|
|
35103
35797
|
}
|
|
@@ -35110,11 +35804,11 @@ function applyPurchaseProfile() {
|
|
|
35110
35804
|
return profile;
|
|
35111
35805
|
}
|
|
35112
35806
|
function configPath() {
|
|
35113
|
-
return
|
|
35807
|
+
return join4(homedir4(), ".hasna", "domains", "config.json");
|
|
35114
35808
|
}
|
|
35115
35809
|
function loadConfig3() {
|
|
35116
35810
|
const path = configPath();
|
|
35117
|
-
if (!
|
|
35811
|
+
if (!existsSync3(path))
|
|
35118
35812
|
return {};
|
|
35119
35813
|
try {
|
|
35120
35814
|
return JSON.parse(readFileSync3(path, "utf-8"));
|
|
@@ -35124,8 +35818,8 @@ function loadConfig3() {
|
|
|
35124
35818
|
}
|
|
35125
35819
|
function saveConfig(config) {
|
|
35126
35820
|
const path = configPath();
|
|
35127
|
-
const dir =
|
|
35128
|
-
if (!
|
|
35821
|
+
const dir = join4(homedir4(), ".hasna", "domains");
|
|
35822
|
+
if (!existsSync3(dir))
|
|
35129
35823
|
mkdirSync2(dir, { recursive: true });
|
|
35130
35824
|
writeFileSync(path, JSON.stringify(config, null, 2), "utf-8");
|
|
35131
35825
|
}
|
|
@@ -35176,44 +35870,6 @@ function getConfigKey(keyPath) {
|
|
|
35176
35870
|
}
|
|
35177
35871
|
var init_config = () => {};
|
|
35178
35872
|
|
|
35179
|
-
// src/lib/creds-check.ts
|
|
35180
|
-
var exports_creds_check = {};
|
|
35181
|
-
__export(exports_creds_check, {
|
|
35182
|
-
checkProvisioningCredentials: () => checkProvisioningCredentials
|
|
35183
|
-
});
|
|
35184
|
-
function checkProvisioningCredentials(env = process.env) {
|
|
35185
|
-
const out = [];
|
|
35186
|
-
const hasAws = !!(env["AWS_ACCESS_KEY_ID"] && env["AWS_SECRET_ACCESS_KEY"]) || !!env["AWS_PROFILE"];
|
|
35187
|
-
out.push({
|
|
35188
|
-
provider: "route53",
|
|
35189
|
-
configured: hasAws,
|
|
35190
|
-
mode: env["AWS_PROFILE"] ? `profile:${env["AWS_PROFILE"]}` : hasAws ? "access-keys" : "none",
|
|
35191
|
-
detail: hasAws ? "AWS credentials present (region us-east-1 for Route53 Domains)" : "Set AWS_PROFILE or AWS_ACCESS_KEY_ID/SECRET"
|
|
35192
|
-
});
|
|
35193
|
-
const cf = resolveCloudflareConfig(env);
|
|
35194
|
-
const cfMode = cf.apiToken ? "token" : cf.apiKey && cf.email ? "global-key" : "none";
|
|
35195
|
-
out.push({
|
|
35196
|
-
provider: "cloudflare",
|
|
35197
|
-
configured: cfMode !== "none",
|
|
35198
|
-
mode: cfMode + (cf.accountId ? "+account" : ""),
|
|
35199
|
-
detail: cfMode === "none" ? "Set CLOUDFLARE_API_TOKEN or CLOUDFLARE_API_KEY+CLOUDFLARE_EMAIL" : cf.accountId ? "ok" : "missing CLOUDFLARE_ACCOUNT_ID (needed to create zones)"
|
|
35200
|
-
});
|
|
35201
|
-
const bs = resolveBrandsightConfig(env);
|
|
35202
|
-
const bsConfigured = !!(bs.apiKey && bs.apiSecret && bs.customerId);
|
|
35203
|
-
out.push({
|
|
35204
|
-
provider: "brandsight",
|
|
35205
|
-
configured: bsConfigured,
|
|
35206
|
-
mode: bsConfigured ? "full-creds" : "none",
|
|
35207
|
-
detail: "enterprise/contract-only (gated) \u2014 not used for automated purchase"
|
|
35208
|
-
});
|
|
35209
|
-
const gd = !!(env["GODADDY_API_KEY"] && env["GODADDY_API_SECRET"]);
|
|
35210
|
-
out.push({ provider: "godaddy", configured: gd, mode: gd ? "key+secret" : "none", detail: "retail API gated for purchase/DNS since 2024" });
|
|
35211
|
-
return out;
|
|
35212
|
-
}
|
|
35213
|
-
var init_creds_check = __esm(() => {
|
|
35214
|
-
init_brandsight();
|
|
35215
|
-
});
|
|
35216
|
-
|
|
35217
35873
|
// src/db/domain-history.ts
|
|
35218
35874
|
function rowToDomainHistory(row) {
|
|
35219
35875
|
return {
|
|
@@ -35296,8 +35952,10 @@ var init_domain_history = __esm(() => {
|
|
|
35296
35952
|
// src/lib/sedo.ts
|
|
35297
35953
|
var exports_sedo = {};
|
|
35298
35954
|
__export(exports_sedo, {
|
|
35955
|
+
sedoCapability: () => sedoCapability,
|
|
35299
35956
|
searchSedoDomains: () => searchSedoDomains,
|
|
35300
35957
|
searchHealthDomains: () => searchHealthDomains,
|
|
35958
|
+
resolveSedoConfig: () => resolveSedoConfig,
|
|
35301
35959
|
removeDomainFromSedo: () => removeDomainFromSedo,
|
|
35302
35960
|
recordSedoPurchase: () => recordSedoPurchase,
|
|
35303
35961
|
listSedoPortfolio: () => listSedoPortfolio,
|
|
@@ -35307,16 +35965,31 @@ __export(exports_sedo, {
|
|
|
35307
35965
|
checkSedoBlacklist: () => checkSedoBlacklist,
|
|
35308
35966
|
addDomainToSedo: () => addDomainToSedo
|
|
35309
35967
|
});
|
|
35968
|
+
function resolveSedoConfig(env = process.env) {
|
|
35969
|
+
return {
|
|
35970
|
+
partnerId: firstEnv(env, SEDO_ENV.partnerId)?.value,
|
|
35971
|
+
signKey: firstEnv(env, SEDO_ENV.signKey)?.value,
|
|
35972
|
+
username: firstEnv(env, SEDO_ENV.username)?.value,
|
|
35973
|
+
password: firstEnv(env, SEDO_ENV.password)?.value
|
|
35974
|
+
};
|
|
35975
|
+
}
|
|
35310
35976
|
function getSedoConfig() {
|
|
35311
|
-
const
|
|
35312
|
-
const signKey =
|
|
35313
|
-
const username = process.env.SEDO_USERNAME;
|
|
35314
|
-
const password = process.env.SEDO_PASSWORD;
|
|
35977
|
+
const config = resolveSedoConfig();
|
|
35978
|
+
const { partnerId, signKey, username, password } = config;
|
|
35315
35979
|
if (!partnerId || !signKey || !username || !password) {
|
|
35316
|
-
throw new Error("Sedo credentials not configured. Required: SEDO_PARTNER_ID, SEDO_API_KEY, SEDO_USERNAME, SEDO_PASSWORD");
|
|
35980
|
+
throw new Error("Sedo credentials not configured. Required: SEDO_PARTNER_ID, SEDO_API_KEY, SEDO_USERNAME, SEDO_PASSWORD (or HASNAXYZ_SEDO_LIVE_* aliases)");
|
|
35317
35981
|
}
|
|
35318
35982
|
return { partnerId, signKey, username, password };
|
|
35319
35983
|
}
|
|
35984
|
+
function sedoCapability(env = process.env) {
|
|
35985
|
+
const cfg = resolveSedoConfig(env);
|
|
35986
|
+
const configured = !!(cfg.partnerId && cfg.signKey && cfg.username && cfg.password);
|
|
35987
|
+
return {
|
|
35988
|
+
configured,
|
|
35989
|
+
gated: true,
|
|
35990
|
+
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."
|
|
35991
|
+
};
|
|
35992
|
+
}
|
|
35320
35993
|
function buildXmlParams(params) {
|
|
35321
35994
|
return {
|
|
35322
35995
|
partnerid: params.partnerId,
|
|
@@ -35515,10 +36188,85 @@ function recordSedoPurchase(domain, price, orderId) {
|
|
|
35515
36188
|
}
|
|
35516
36189
|
var BASE_URL = "https://api.sedo.com/api/v1/";
|
|
35517
36190
|
var init_sedo = __esm(() => {
|
|
36191
|
+
init_env_aliases();
|
|
35518
36192
|
init_domains();
|
|
35519
36193
|
init_domain_history();
|
|
35520
36194
|
});
|
|
35521
36195
|
|
|
36196
|
+
// src/lib/creds-check.ts
|
|
36197
|
+
var exports_creds_check = {};
|
|
36198
|
+
__export(exports_creds_check, {
|
|
36199
|
+
checkProvisioningCredentials: () => checkProvisioningCredentials
|
|
36200
|
+
});
|
|
36201
|
+
function configuredStatus(provider, mode, detail) {
|
|
36202
|
+
return { provider, configured: true, mode, detail };
|
|
36203
|
+
}
|
|
36204
|
+
function missingStatus(provider, detail) {
|
|
36205
|
+
return { provider, configured: false, mode: "none", detail };
|
|
36206
|
+
}
|
|
36207
|
+
function checkProvisioningCredentials(env = process.env) {
|
|
36208
|
+
const out = [];
|
|
36209
|
+
const hasAwsKeys = !!(env["AWS_ACCESS_KEY_ID"] && env["AWS_SECRET_ACCESS_KEY"]);
|
|
36210
|
+
const hasAwsProfile = !!env["AWS_PROFILE"];
|
|
36211
|
+
if (hasAwsProfile || hasAwsKeys) {
|
|
36212
|
+
out.push(configuredStatus("route53", hasAwsProfile ? `profile:${env["AWS_PROFILE"]}` : "access-keys", "AWS credentials present (Route 53 Domains API uses us-east-1)"));
|
|
36213
|
+
} else {
|
|
36214
|
+
out.push(missingStatus("route53", "Set AWS_PROFILE or AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY"));
|
|
36215
|
+
}
|
|
36216
|
+
const beepmediaAws = hasProviderCredentials("aws:beepmedia", env);
|
|
36217
|
+
out.push({
|
|
36218
|
+
provider: "aws:beepmedia",
|
|
36219
|
+
configured: beepmediaAws,
|
|
36220
|
+
mode: beepmediaAws ? "scoped-env" : "none",
|
|
36221
|
+
detail: beepmediaAws ? "Beepmedia AWS scoped env keys are present; export them as AWS_* or use AWS_PROFILE=beepmedia for Route53 sync." : `Set ${BEEPMEDIA_AWS_ENV.accessKeyId.join(" or ")} and ${BEEPMEDIA_AWS_ENV.secretAccessKey.join(" or ")}`
|
|
36222
|
+
});
|
|
36223
|
+
const cf = resolveCloudflareConfig(env);
|
|
36224
|
+
const cfMode = cf.apiToken ? "token" : cf.apiKey && cf.email ? "global-key" : "none";
|
|
36225
|
+
out.push({
|
|
36226
|
+
provider: "cloudflare",
|
|
36227
|
+
configured: cfMode !== "none",
|
|
36228
|
+
mode: cfMode + (cf.accountId ? "+account" : ""),
|
|
36229
|
+
detail: cfMode === "none" ? "Set CLOUDFLARE_API_TOKEN or CLOUDFLARE_API_KEY+CLOUDFLARE_EMAIL" : cf.accountId ? "ok" : "missing CLOUDFLARE_ACCOUNT_ID (needed to create zones)"
|
|
36230
|
+
});
|
|
36231
|
+
out.push({
|
|
36232
|
+
provider: "namecheap",
|
|
36233
|
+
configured: hasProviderCredentials("namecheap", env),
|
|
36234
|
+
mode: hasProviderCredentials("namecheap", env) ? "api-key+username+client-ip" : "none",
|
|
36235
|
+
detail: "Requires NAMECHEAP_API_KEY, NAMECHEAP_USERNAME, and whitelisted NAMECHEAP_CLIENT_IP"
|
|
36236
|
+
});
|
|
36237
|
+
const gd = godaddyCapability(env);
|
|
36238
|
+
out.push({
|
|
36239
|
+
provider: "godaddy",
|
|
36240
|
+
configured: gd.configured,
|
|
36241
|
+
mode: gd.configured ? "key+secret" : "none",
|
|
36242
|
+
detail: gd.notes
|
|
36243
|
+
});
|
|
36244
|
+
const bs = brandsightCapability(env);
|
|
36245
|
+
const bsMode = firstEnv(env, ["BRANDSIGHT_API_KEY"]) ? "standard-env" : bs.configured ? "scoped-env" : "none";
|
|
36246
|
+
out.push({
|
|
36247
|
+
provider: "brandsight",
|
|
36248
|
+
configured: bs.configured,
|
|
36249
|
+
mode: bsMode,
|
|
36250
|
+
detail: bs.notes
|
|
36251
|
+
});
|
|
36252
|
+
const sedo = sedoCapability(env);
|
|
36253
|
+
const sedoMode = firstEnv(env, ["SEDO_API_KEY"]) ? "standard-env" : sedo.configured ? "scoped-env" : "none";
|
|
36254
|
+
out.push({
|
|
36255
|
+
provider: "sedo",
|
|
36256
|
+
configured: sedo.configured,
|
|
36257
|
+
mode: sedoMode,
|
|
36258
|
+
detail: sedo.notes
|
|
36259
|
+
});
|
|
36260
|
+
return out;
|
|
36261
|
+
}
|
|
36262
|
+
var init_creds_check = __esm(() => {
|
|
36263
|
+
init_cloudflare_auth();
|
|
36264
|
+
init_brandsight();
|
|
36265
|
+
init_godaddy();
|
|
36266
|
+
init_sedo();
|
|
36267
|
+
init_env_aliases();
|
|
36268
|
+
});
|
|
36269
|
+
|
|
35522
36270
|
// src/lib/provision-state.ts
|
|
35523
36271
|
function isDomainTerminal(s3) {
|
|
35524
36272
|
return TERMINAL.has(s3);
|
|
@@ -35593,7 +36341,7 @@ function makeDomainDaemonDeps() {
|
|
|
35593
36341
|
return;
|
|
35594
36342
|
case "delegate_ns": {
|
|
35595
36343
|
const zone = await ensureZone(domain);
|
|
35596
|
-
await
|
|
36344
|
+
await updateNameservers2(domain, zone.nameservers);
|
|
35597
36345
|
return;
|
|
35598
36346
|
}
|
|
35599
36347
|
case "check_ns_propagation":
|
|
@@ -40553,6 +41301,684 @@ var {
|
|
|
40553
41301
|
Help
|
|
40554
41302
|
} = import__.default;
|
|
40555
41303
|
|
|
41304
|
+
// node_modules/@hasna/events/dist/commander.js
|
|
41305
|
+
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
41306
|
+
import { existsSync } from "fs";
|
|
41307
|
+
import { homedir } from "os";
|
|
41308
|
+
import { join } from "path";
|
|
41309
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
41310
|
+
import { randomUUID } from "crypto";
|
|
41311
|
+
import { spawn } from "child_process";
|
|
41312
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
41313
|
+
function getPathValue(input, path) {
|
|
41314
|
+
return path.split(".").reduce((value, part) => {
|
|
41315
|
+
if (value && typeof value === "object" && part in value) {
|
|
41316
|
+
return value[part];
|
|
41317
|
+
}
|
|
41318
|
+
return;
|
|
41319
|
+
}, input);
|
|
41320
|
+
}
|
|
41321
|
+
function wildcardToRegExp(pattern) {
|
|
41322
|
+
const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, ".*");
|
|
41323
|
+
return new RegExp(`^${escaped}$`);
|
|
41324
|
+
}
|
|
41325
|
+
function matchString(value, matcher) {
|
|
41326
|
+
if (matcher === undefined)
|
|
41327
|
+
return true;
|
|
41328
|
+
if (value === undefined)
|
|
41329
|
+
return false;
|
|
41330
|
+
const matchers = Array.isArray(matcher) ? matcher : [matcher];
|
|
41331
|
+
return matchers.some((item) => wildcardToRegExp(item).test(value));
|
|
41332
|
+
}
|
|
41333
|
+
function matchRecord(input, matcher) {
|
|
41334
|
+
if (!matcher)
|
|
41335
|
+
return true;
|
|
41336
|
+
return Object.entries(matcher).every(([path, expected]) => {
|
|
41337
|
+
const actual = getPathValue(input, path);
|
|
41338
|
+
if (typeof expected === "string" || Array.isArray(expected)) {
|
|
41339
|
+
return matchString(actual === undefined ? undefined : String(actual), expected);
|
|
41340
|
+
}
|
|
41341
|
+
return actual === expected;
|
|
41342
|
+
});
|
|
41343
|
+
}
|
|
41344
|
+
function eventMatchesFilter(event, filter) {
|
|
41345
|
+
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);
|
|
41346
|
+
}
|
|
41347
|
+
function channelMatchesEvent(channel, event) {
|
|
41348
|
+
if (!channel.enabled)
|
|
41349
|
+
return false;
|
|
41350
|
+
if (!channel.filters || channel.filters.length === 0)
|
|
41351
|
+
return true;
|
|
41352
|
+
return channel.filters.some((filter) => eventMatchesFilter(event, filter));
|
|
41353
|
+
}
|
|
41354
|
+
var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
|
|
41355
|
+
var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
|
|
41356
|
+
function getEventsDataDir(override) {
|
|
41357
|
+
return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join(homedir(), ".hasna", "events");
|
|
41358
|
+
}
|
|
41359
|
+
|
|
41360
|
+
class JsonEventsStore {
|
|
41361
|
+
dataDir;
|
|
41362
|
+
channelsPath;
|
|
41363
|
+
eventsPath;
|
|
41364
|
+
deliveriesPath;
|
|
41365
|
+
constructor(dataDir = getEventsDataDir()) {
|
|
41366
|
+
this.dataDir = dataDir;
|
|
41367
|
+
this.channelsPath = join(dataDir, "channels.json");
|
|
41368
|
+
this.eventsPath = join(dataDir, "events.json");
|
|
41369
|
+
this.deliveriesPath = join(dataDir, "deliveries.json");
|
|
41370
|
+
}
|
|
41371
|
+
async init() {
|
|
41372
|
+
await mkdir(this.dataDir, { recursive: true, mode: 448 });
|
|
41373
|
+
await chmod(this.dataDir, 448).catch(() => {
|
|
41374
|
+
return;
|
|
41375
|
+
});
|
|
41376
|
+
await this.ensureArrayFile(this.channelsPath);
|
|
41377
|
+
await this.ensureArrayFile(this.eventsPath);
|
|
41378
|
+
await this.ensureArrayFile(this.deliveriesPath);
|
|
41379
|
+
}
|
|
41380
|
+
async addChannel(channel) {
|
|
41381
|
+
await this.init();
|
|
41382
|
+
const channels = await this.readJson(this.channelsPath, []);
|
|
41383
|
+
const index = channels.findIndex((item) => item.id === channel.id);
|
|
41384
|
+
if (index >= 0) {
|
|
41385
|
+
channels[index] = { ...channel, createdAt: channels[index].createdAt, updatedAt: new Date().toISOString() };
|
|
41386
|
+
} else {
|
|
41387
|
+
channels.push(channel);
|
|
41388
|
+
}
|
|
41389
|
+
await this.writeJson(this.channelsPath, channels);
|
|
41390
|
+
return index >= 0 ? channels[index] : channel;
|
|
41391
|
+
}
|
|
41392
|
+
async listChannels() {
|
|
41393
|
+
await this.init();
|
|
41394
|
+
return this.readJson(this.channelsPath, []);
|
|
41395
|
+
}
|
|
41396
|
+
async getChannel(id) {
|
|
41397
|
+
const channels = await this.listChannels();
|
|
41398
|
+
return channels.find((channel) => channel.id === id);
|
|
41399
|
+
}
|
|
41400
|
+
async removeChannel(id) {
|
|
41401
|
+
await this.init();
|
|
41402
|
+
const channels = await this.readJson(this.channelsPath, []);
|
|
41403
|
+
const next = channels.filter((channel) => channel.id !== id);
|
|
41404
|
+
await this.writeJson(this.channelsPath, next);
|
|
41405
|
+
return next.length !== channels.length;
|
|
41406
|
+
}
|
|
41407
|
+
async appendEvent(event) {
|
|
41408
|
+
await this.init();
|
|
41409
|
+
const events = await this.readJson(this.eventsPath, []);
|
|
41410
|
+
events.push(event);
|
|
41411
|
+
await this.writeJson(this.eventsPath, events);
|
|
41412
|
+
return event;
|
|
41413
|
+
}
|
|
41414
|
+
async listEvents() {
|
|
41415
|
+
await this.init();
|
|
41416
|
+
return this.readJson(this.eventsPath, []);
|
|
41417
|
+
}
|
|
41418
|
+
async findEventByIdentity(identity) {
|
|
41419
|
+
const events = await this.listEvents();
|
|
41420
|
+
return events.find((event) => identity.id !== undefined && event.id === identity.id || identity.dedupeKey !== undefined && event.dedupeKey === identity.dedupeKey);
|
|
41421
|
+
}
|
|
41422
|
+
async appendDelivery(result) {
|
|
41423
|
+
await this.init();
|
|
41424
|
+
const deliveries = await this.readJson(this.deliveriesPath, []);
|
|
41425
|
+
deliveries.push(result);
|
|
41426
|
+
await this.writeJson(this.deliveriesPath, deliveries);
|
|
41427
|
+
return result;
|
|
41428
|
+
}
|
|
41429
|
+
async listDeliveries() {
|
|
41430
|
+
await this.init();
|
|
41431
|
+
return this.readJson(this.deliveriesPath, []);
|
|
41432
|
+
}
|
|
41433
|
+
async exportData() {
|
|
41434
|
+
return {
|
|
41435
|
+
channels: await this.listChannels(),
|
|
41436
|
+
events: await this.listEvents(),
|
|
41437
|
+
deliveries: await this.listDeliveries()
|
|
41438
|
+
};
|
|
41439
|
+
}
|
|
41440
|
+
async ensureArrayFile(path) {
|
|
41441
|
+
if (!existsSync(path)) {
|
|
41442
|
+
await writeFile(path, `[]
|
|
41443
|
+
`, { encoding: "utf-8", mode: 384 });
|
|
41444
|
+
}
|
|
41445
|
+
await chmod(path, 384).catch(() => {
|
|
41446
|
+
return;
|
|
41447
|
+
});
|
|
41448
|
+
}
|
|
41449
|
+
async readJson(path, fallback) {
|
|
41450
|
+
try {
|
|
41451
|
+
const raw = await readFile(path, "utf-8");
|
|
41452
|
+
if (!raw.trim())
|
|
41453
|
+
return fallback;
|
|
41454
|
+
return JSON.parse(raw);
|
|
41455
|
+
} catch (error) {
|
|
41456
|
+
if (error.code === "ENOENT")
|
|
41457
|
+
return fallback;
|
|
41458
|
+
throw error;
|
|
41459
|
+
}
|
|
41460
|
+
}
|
|
41461
|
+
async writeJson(path, value) {
|
|
41462
|
+
const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
41463
|
+
await writeFile(tempPath, `${JSON.stringify(value, null, 2)}
|
|
41464
|
+
`, { encoding: "utf-8", mode: 384 });
|
|
41465
|
+
await rename(tempPath, path);
|
|
41466
|
+
await chmod(path, 384).catch(() => {
|
|
41467
|
+
return;
|
|
41468
|
+
});
|
|
41469
|
+
}
|
|
41470
|
+
}
|
|
41471
|
+
var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
|
|
41472
|
+
function buildSignatureBase(timestamp, body) {
|
|
41473
|
+
return `${timestamp}.${body}`;
|
|
41474
|
+
}
|
|
41475
|
+
function signPayload(secret, timestamp, body) {
|
|
41476
|
+
const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
|
|
41477
|
+
return `sha256=${digest}`;
|
|
41478
|
+
}
|
|
41479
|
+
function now() {
|
|
41480
|
+
return new Date().toISOString();
|
|
41481
|
+
}
|
|
41482
|
+
function truncate(value, max = 4096) {
|
|
41483
|
+
return value.length > max ? `${value.slice(0, max)}...` : value;
|
|
41484
|
+
}
|
|
41485
|
+
function buildWebhookRequest(event, channel) {
|
|
41486
|
+
if (!channel.webhook)
|
|
41487
|
+
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
41488
|
+
const body = JSON.stringify(event);
|
|
41489
|
+
const timestamp = event.time;
|
|
41490
|
+
const headers = {
|
|
41491
|
+
"Content-Type": "application/json",
|
|
41492
|
+
"User-Agent": "@hasna/events",
|
|
41493
|
+
"X-Hasna-Event-Id": event.id,
|
|
41494
|
+
"X-Hasna-Event-Type": event.type,
|
|
41495
|
+
"X-Hasna-Timestamp": timestamp,
|
|
41496
|
+
...channel.webhook.headers
|
|
41497
|
+
};
|
|
41498
|
+
if (channel.webhook.secret) {
|
|
41499
|
+
headers["X-Hasna-Signature"] = signPayload(channel.webhook.secret, timestamp, body);
|
|
41500
|
+
}
|
|
41501
|
+
return { body, headers };
|
|
41502
|
+
}
|
|
41503
|
+
async function dispatchWebhook(event, channel, options = {}) {
|
|
41504
|
+
if (!channel.webhook)
|
|
41505
|
+
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
41506
|
+
const startedAt = now();
|
|
41507
|
+
const { body, headers } = buildWebhookRequest(event, channel);
|
|
41508
|
+
const controller = new AbortController;
|
|
41509
|
+
const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
|
|
41510
|
+
try {
|
|
41511
|
+
const response = await (options.fetchImpl ?? fetch)(channel.webhook.url, {
|
|
41512
|
+
method: "POST",
|
|
41513
|
+
headers,
|
|
41514
|
+
body,
|
|
41515
|
+
signal: controller.signal
|
|
41516
|
+
});
|
|
41517
|
+
const responseBody = truncate(await response.text());
|
|
41518
|
+
return {
|
|
41519
|
+
attempt: 1,
|
|
41520
|
+
status: response.ok ? "success" : "failed",
|
|
41521
|
+
startedAt,
|
|
41522
|
+
completedAt: now(),
|
|
41523
|
+
responseStatus: response.status,
|
|
41524
|
+
responseBody,
|
|
41525
|
+
error: response.ok ? undefined : `Webhook returned HTTP ${response.status}`
|
|
41526
|
+
};
|
|
41527
|
+
} catch (error) {
|
|
41528
|
+
return {
|
|
41529
|
+
attempt: 1,
|
|
41530
|
+
status: "failed",
|
|
41531
|
+
startedAt,
|
|
41532
|
+
completedAt: now(),
|
|
41533
|
+
error: error instanceof Error ? error.message : String(error)
|
|
41534
|
+
};
|
|
41535
|
+
} finally {
|
|
41536
|
+
clearTimeout(timeout);
|
|
41537
|
+
}
|
|
41538
|
+
}
|
|
41539
|
+
async function dispatchCommand(event, channel) {
|
|
41540
|
+
if (!channel.command)
|
|
41541
|
+
throw new Error(`Channel ${channel.id} has no command config`);
|
|
41542
|
+
const startedAt = now();
|
|
41543
|
+
const eventJson = JSON.stringify(event);
|
|
41544
|
+
const env = {
|
|
41545
|
+
...process.env,
|
|
41546
|
+
...channel.command.env,
|
|
41547
|
+
HASNA_CHANNEL_ID: channel.id,
|
|
41548
|
+
HASNA_EVENT_ID: event.id,
|
|
41549
|
+
HASNA_EVENT_TYPE: event.type,
|
|
41550
|
+
HASNA_EVENT_SOURCE: event.source,
|
|
41551
|
+
HASNA_EVENT_SUBJECT: event.subject ?? "",
|
|
41552
|
+
HASNA_EVENT_SEVERITY: event.severity,
|
|
41553
|
+
HASNA_EVENT_TIME: event.time,
|
|
41554
|
+
HASNA_EVENT_DEDUPE_KEY: event.dedupeKey ?? "",
|
|
41555
|
+
HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
|
|
41556
|
+
HASNA_EVENT_JSON: eventJson
|
|
41557
|
+
};
|
|
41558
|
+
return new Promise((resolve) => {
|
|
41559
|
+
const child = spawn(channel.command.command, channel.command.args ?? [], {
|
|
41560
|
+
cwd: channel.command.cwd,
|
|
41561
|
+
env,
|
|
41562
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
41563
|
+
});
|
|
41564
|
+
let stdout = "";
|
|
41565
|
+
let stderr = "";
|
|
41566
|
+
const timeout = setTimeout(() => child.kill("SIGTERM"), channel.command.timeoutMs ?? 15000);
|
|
41567
|
+
child.stdin.end(eventJson);
|
|
41568
|
+
child.stdout.on("data", (chunk) => {
|
|
41569
|
+
stdout += chunk.toString();
|
|
41570
|
+
});
|
|
41571
|
+
child.stderr.on("data", (chunk) => {
|
|
41572
|
+
stderr += chunk.toString();
|
|
41573
|
+
});
|
|
41574
|
+
child.on("error", (error) => {
|
|
41575
|
+
clearTimeout(timeout);
|
|
41576
|
+
resolve({
|
|
41577
|
+
attempt: 1,
|
|
41578
|
+
status: "failed",
|
|
41579
|
+
startedAt,
|
|
41580
|
+
completedAt: now(),
|
|
41581
|
+
stdout: truncate(stdout),
|
|
41582
|
+
stderr: truncate(stderr),
|
|
41583
|
+
error: error.message
|
|
41584
|
+
});
|
|
41585
|
+
});
|
|
41586
|
+
child.on("close", (code, signal) => {
|
|
41587
|
+
clearTimeout(timeout);
|
|
41588
|
+
const success = code === 0;
|
|
41589
|
+
resolve({
|
|
41590
|
+
attempt: 1,
|
|
41591
|
+
status: success ? "success" : "failed",
|
|
41592
|
+
startedAt,
|
|
41593
|
+
completedAt: now(),
|
|
41594
|
+
stdout: truncate(stdout),
|
|
41595
|
+
stderr: truncate(stderr),
|
|
41596
|
+
error: success ? undefined : `Command exited with ${signal ? `signal ${signal}` : `code ${code}`}`
|
|
41597
|
+
});
|
|
41598
|
+
});
|
|
41599
|
+
});
|
|
41600
|
+
}
|
|
41601
|
+
async function dispatchChannel(event, channel, options = {}) {
|
|
41602
|
+
if (channel.transport === "webhook")
|
|
41603
|
+
return dispatchWebhook(event, channel, options);
|
|
41604
|
+
if (channel.transport === "command")
|
|
41605
|
+
return dispatchCommand(event, channel);
|
|
41606
|
+
return {
|
|
41607
|
+
attempt: 1,
|
|
41608
|
+
status: "skipped",
|
|
41609
|
+
startedAt: now(),
|
|
41610
|
+
completedAt: now(),
|
|
41611
|
+
error: `Unsupported transport: ${channel.transport}`
|
|
41612
|
+
};
|
|
41613
|
+
}
|
|
41614
|
+
function createDeliveryResult(event, channel, attempts) {
|
|
41615
|
+
const status = attempts.some((attempt) => attempt.status === "success") ? "success" : attempts.every((attempt) => attempt.status === "skipped") ? "skipped" : "failed";
|
|
41616
|
+
return {
|
|
41617
|
+
id: randomUUID(),
|
|
41618
|
+
eventId: event.id,
|
|
41619
|
+
channelId: channel.id,
|
|
41620
|
+
transport: channel.transport,
|
|
41621
|
+
status,
|
|
41622
|
+
attempts,
|
|
41623
|
+
createdAt: attempts[0]?.startedAt ?? now(),
|
|
41624
|
+
completedAt: attempts.at(-1)?.completedAt ?? now()
|
|
41625
|
+
};
|
|
41626
|
+
}
|
|
41627
|
+
function createEvent(input) {
|
|
41628
|
+
return {
|
|
41629
|
+
id: input.id ?? randomUUID2(),
|
|
41630
|
+
source: input.source,
|
|
41631
|
+
type: input.type,
|
|
41632
|
+
time: normalizeTime(input.time),
|
|
41633
|
+
subject: input.subject,
|
|
41634
|
+
severity: input.severity ?? "info",
|
|
41635
|
+
data: input.data ?? {},
|
|
41636
|
+
message: input.message,
|
|
41637
|
+
dedupeKey: input.dedupeKey,
|
|
41638
|
+
schemaVersion: input.schemaVersion ?? "1.0",
|
|
41639
|
+
metadata: input.metadata ?? {}
|
|
41640
|
+
};
|
|
41641
|
+
}
|
|
41642
|
+
|
|
41643
|
+
class EventsClient {
|
|
41644
|
+
store;
|
|
41645
|
+
redactors;
|
|
41646
|
+
transportOptions;
|
|
41647
|
+
constructor(options = {}) {
|
|
41648
|
+
this.store = options.store ?? new JsonEventsStore(options.dataDir);
|
|
41649
|
+
this.redactors = options.redactors ?? [];
|
|
41650
|
+
this.transportOptions = { fetchImpl: options.fetchImpl };
|
|
41651
|
+
}
|
|
41652
|
+
async addChannel(input) {
|
|
41653
|
+
const timestamp = new Date().toISOString();
|
|
41654
|
+
return this.store.addChannel({
|
|
41655
|
+
...input,
|
|
41656
|
+
createdAt: input.createdAt ?? timestamp,
|
|
41657
|
+
updatedAt: input.updatedAt ?? timestamp
|
|
41658
|
+
});
|
|
41659
|
+
}
|
|
41660
|
+
async listChannels() {
|
|
41661
|
+
return this.store.listChannels();
|
|
41662
|
+
}
|
|
41663
|
+
async removeChannel(id) {
|
|
41664
|
+
return this.store.removeChannel(id);
|
|
41665
|
+
}
|
|
41666
|
+
async emit(input, options = {}) {
|
|
41667
|
+
const event = options.redactSensitiveData === false ? createEvent(input) : redactSensitiveKeys(createEvent(input));
|
|
41668
|
+
if (options.dedupe !== false) {
|
|
41669
|
+
const existing = await this.store.findEventByIdentity({ id: input.id, dedupeKey: event.dedupeKey });
|
|
41670
|
+
if (existing) {
|
|
41671
|
+
return { event: existing, deliveries: [], deduped: true };
|
|
41672
|
+
}
|
|
41673
|
+
}
|
|
41674
|
+
await this.store.appendEvent(event);
|
|
41675
|
+
const deliveries = options.deliver === false ? [] : await this.deliver(event);
|
|
41676
|
+
return { event, deliveries, deduped: false };
|
|
41677
|
+
}
|
|
41678
|
+
async listEvents() {
|
|
41679
|
+
return this.store.listEvents();
|
|
41680
|
+
}
|
|
41681
|
+
async listDeliveries() {
|
|
41682
|
+
return this.store.listDeliveries();
|
|
41683
|
+
}
|
|
41684
|
+
async deliver(event) {
|
|
41685
|
+
const channels = await this.store.listChannels();
|
|
41686
|
+
const selected = channels.filter((channel) => channelMatchesEvent(channel, event));
|
|
41687
|
+
const deliveries = [];
|
|
41688
|
+
for (const channel of selected) {
|
|
41689
|
+
const eventForChannel = await this.applyRedaction(event, channel);
|
|
41690
|
+
const result = await this.deliverWithRetry(eventForChannel, channel);
|
|
41691
|
+
await this.store.appendDelivery(result);
|
|
41692
|
+
deliveries.push(result);
|
|
41693
|
+
}
|
|
41694
|
+
return deliveries;
|
|
41695
|
+
}
|
|
41696
|
+
async testChannel(id, input = {}) {
|
|
41697
|
+
const channel = await this.store.getChannel(id);
|
|
41698
|
+
if (!channel)
|
|
41699
|
+
throw new Error(`Channel not found: ${id}`);
|
|
41700
|
+
const event = createEvent({
|
|
41701
|
+
source: input.source ?? "hasna.events",
|
|
41702
|
+
type: input.type ?? "events.test",
|
|
41703
|
+
subject: input.subject ?? id,
|
|
41704
|
+
severity: input.severity ?? "info",
|
|
41705
|
+
data: input.data ?? { test: true },
|
|
41706
|
+
message: input.message ?? "Hasna events test delivery",
|
|
41707
|
+
dedupeKey: input.dedupeKey,
|
|
41708
|
+
schemaVersion: input.schemaVersion,
|
|
41709
|
+
metadata: input.metadata,
|
|
41710
|
+
time: input.time,
|
|
41711
|
+
id: input.id
|
|
41712
|
+
});
|
|
41713
|
+
const eventForChannel = await this.applyRedaction(event, channel);
|
|
41714
|
+
const result = await this.deliverWithRetry(eventForChannel, channel);
|
|
41715
|
+
await this.store.appendDelivery(result);
|
|
41716
|
+
return result;
|
|
41717
|
+
}
|
|
41718
|
+
async replay(options = {}) {
|
|
41719
|
+
const events = (await this.store.listEvents()).filter((event) => {
|
|
41720
|
+
if (options.eventId && event.id !== options.eventId)
|
|
41721
|
+
return false;
|
|
41722
|
+
if (options.source && event.source !== options.source)
|
|
41723
|
+
return false;
|
|
41724
|
+
if (options.type && event.type !== options.type)
|
|
41725
|
+
return false;
|
|
41726
|
+
return true;
|
|
41727
|
+
});
|
|
41728
|
+
if (options.dryRun)
|
|
41729
|
+
return { events, deliveries: [] };
|
|
41730
|
+
const deliveries = [];
|
|
41731
|
+
for (const event of events) {
|
|
41732
|
+
deliveries.push(...await this.deliver(event));
|
|
41733
|
+
}
|
|
41734
|
+
return { events, deliveries };
|
|
41735
|
+
}
|
|
41736
|
+
async applyRedaction(event, channel) {
|
|
41737
|
+
let next = redactPaths(event, channel.redact?.paths ?? [], channel.redact?.replacement ?? "[REDACTED]");
|
|
41738
|
+
for (const redactor of this.redactors) {
|
|
41739
|
+
next = await redactor(next, channel);
|
|
41740
|
+
}
|
|
41741
|
+
return next;
|
|
41742
|
+
}
|
|
41743
|
+
async deliverWithRetry(event, channel) {
|
|
41744
|
+
const policy = normalizeRetryPolicy(channel.retry);
|
|
41745
|
+
const attempts = [];
|
|
41746
|
+
for (let index = 0;index < policy.maxAttempts; index += 1) {
|
|
41747
|
+
const attempt = await dispatchChannel(event, channel, this.transportOptions);
|
|
41748
|
+
attempt.attempt = index + 1;
|
|
41749
|
+
if (attempt.status === "failed" && index + 1 < policy.maxAttempts) {
|
|
41750
|
+
attempt.nextBackoffMs = Math.round(policy.backoffMs * policy.multiplier ** index);
|
|
41751
|
+
}
|
|
41752
|
+
attempts.push(attempt);
|
|
41753
|
+
if (attempt.status !== "failed")
|
|
41754
|
+
break;
|
|
41755
|
+
if (attempt.nextBackoffMs)
|
|
41756
|
+
await Bun.sleep(attempt.nextBackoffMs);
|
|
41757
|
+
}
|
|
41758
|
+
return createDeliveryResult(event, channel, attempts);
|
|
41759
|
+
}
|
|
41760
|
+
}
|
|
41761
|
+
function redactPaths(event, paths, replacement = "[REDACTED]") {
|
|
41762
|
+
if (paths.length === 0)
|
|
41763
|
+
return event;
|
|
41764
|
+
const copy = structuredClone(event);
|
|
41765
|
+
for (const path of paths) {
|
|
41766
|
+
setPath(copy, path, replacement);
|
|
41767
|
+
}
|
|
41768
|
+
return copy;
|
|
41769
|
+
}
|
|
41770
|
+
function sanitizeChannelForOutput(channel) {
|
|
41771
|
+
const copy = structuredClone(channel);
|
|
41772
|
+
if (copy.webhook?.secret)
|
|
41773
|
+
copy.webhook.secret = "[REDACTED]";
|
|
41774
|
+
if (copy.command?.env) {
|
|
41775
|
+
copy.command.env = Object.fromEntries(Object.entries(copy.command.env).map(([key, value]) => [key, shouldRedactKey(key) ? "[REDACTED]" : value]));
|
|
41776
|
+
}
|
|
41777
|
+
return copy;
|
|
41778
|
+
}
|
|
41779
|
+
function sanitizeChannelsForOutput(channels) {
|
|
41780
|
+
return channels.map(sanitizeChannelForOutput);
|
|
41781
|
+
}
|
|
41782
|
+
function redactSensitiveKeys(event, replacement = "[REDACTED]") {
|
|
41783
|
+
return redactValue(event, replacement);
|
|
41784
|
+
}
|
|
41785
|
+
function shouldRedactKey(key) {
|
|
41786
|
+
return /secret|token|password|api[_-]?key|authorization/i.test(key);
|
|
41787
|
+
}
|
|
41788
|
+
function redactValue(value, replacement) {
|
|
41789
|
+
if (Array.isArray(value))
|
|
41790
|
+
return value.map((item) => redactValue(item, replacement));
|
|
41791
|
+
if (!value || typeof value !== "object")
|
|
41792
|
+
return value;
|
|
41793
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
41794
|
+
key,
|
|
41795
|
+
shouldRedactKey(key) ? replacement : redactValue(item, replacement)
|
|
41796
|
+
]));
|
|
41797
|
+
}
|
|
41798
|
+
function setPath(input, path, replacement) {
|
|
41799
|
+
const parts = path.split(".");
|
|
41800
|
+
let cursor = input;
|
|
41801
|
+
for (const part of parts.slice(0, -1)) {
|
|
41802
|
+
const next = cursor[part];
|
|
41803
|
+
if (!next || typeof next !== "object")
|
|
41804
|
+
return;
|
|
41805
|
+
cursor = next;
|
|
41806
|
+
}
|
|
41807
|
+
const last = parts.at(-1);
|
|
41808
|
+
if (last && last in cursor)
|
|
41809
|
+
cursor[last] = replacement;
|
|
41810
|
+
}
|
|
41811
|
+
function normalizeTime(value) {
|
|
41812
|
+
if (!value)
|
|
41813
|
+
return new Date().toISOString();
|
|
41814
|
+
return value instanceof Date ? value.toISOString() : value;
|
|
41815
|
+
}
|
|
41816
|
+
function normalizeRetryPolicy(policy) {
|
|
41817
|
+
return {
|
|
41818
|
+
maxAttempts: Math.max(1, policy?.maxAttempts ?? 1),
|
|
41819
|
+
backoffMs: Math.max(0, policy?.backoffMs ?? 250),
|
|
41820
|
+
multiplier: Math.max(1, policy?.multiplier ?? 2)
|
|
41821
|
+
};
|
|
41822
|
+
}
|
|
41823
|
+
function parseJsonObject(value, fallback) {
|
|
41824
|
+
if (!value)
|
|
41825
|
+
return fallback;
|
|
41826
|
+
const parsed = JSON.parse(value);
|
|
41827
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
41828
|
+
throw new Error("Expected a JSON object");
|
|
41829
|
+
}
|
|
41830
|
+
return parsed;
|
|
41831
|
+
}
|
|
41832
|
+
function parseHeaders(values) {
|
|
41833
|
+
if (!values?.length)
|
|
41834
|
+
return;
|
|
41835
|
+
const headers = {};
|
|
41836
|
+
for (const value of values) {
|
|
41837
|
+
const separator = value.indexOf("=");
|
|
41838
|
+
if (separator === -1)
|
|
41839
|
+
throw new Error(`Invalid header, expected name=value: ${value}`);
|
|
41840
|
+
headers[value.slice(0, separator)] = value.slice(separator + 1);
|
|
41841
|
+
}
|
|
41842
|
+
return headers;
|
|
41843
|
+
}
|
|
41844
|
+
function parseFilter(options) {
|
|
41845
|
+
const filter2 = {};
|
|
41846
|
+
if (options.source)
|
|
41847
|
+
filter2.source = options.source;
|
|
41848
|
+
if (options.type)
|
|
41849
|
+
filter2.type = options.type;
|
|
41850
|
+
if (options.subject)
|
|
41851
|
+
filter2.subject = options.subject;
|
|
41852
|
+
if (options.severity)
|
|
41853
|
+
filter2.severity = options.severity;
|
|
41854
|
+
return Object.keys(filter2).length > 0 ? [filter2] : undefined;
|
|
41855
|
+
}
|
|
41856
|
+
function createClient(options) {
|
|
41857
|
+
if (options.createClient)
|
|
41858
|
+
return options.createClient();
|
|
41859
|
+
return new EventsClient({ store: new JsonEventsStore(options.dataDir) });
|
|
41860
|
+
}
|
|
41861
|
+
function print(value, json, text) {
|
|
41862
|
+
if (json)
|
|
41863
|
+
console.log(JSON.stringify(value, null, 2));
|
|
41864
|
+
else
|
|
41865
|
+
console.log(text);
|
|
41866
|
+
}
|
|
41867
|
+
function registerWebhookCommands(program2, options) {
|
|
41868
|
+
const webhooks = program2.command(options.webhooksCommandName ?? "webhooks").description("Manage Hasna event webhook subscriptions");
|
|
41869
|
+
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) => {
|
|
41870
|
+
const timestamp = new Date().toISOString();
|
|
41871
|
+
const channel = {
|
|
41872
|
+
id: actionOptions.id,
|
|
41873
|
+
name: actionOptions.name,
|
|
41874
|
+
enabled: !actionOptions.disabled,
|
|
41875
|
+
transport: actionOptions.transport,
|
|
41876
|
+
filters: parseFilter(actionOptions),
|
|
41877
|
+
retry: actionOptions.retryAttempts || actionOptions.retryBackoffMs ? { maxAttempts: actionOptions.retryAttempts, backoffMs: actionOptions.retryBackoffMs } : undefined,
|
|
41878
|
+
redact: actionOptions.redact?.length ? { paths: actionOptions.redact } : undefined,
|
|
41879
|
+
createdAt: timestamp,
|
|
41880
|
+
updatedAt: timestamp
|
|
41881
|
+
};
|
|
41882
|
+
if (actionOptions.transport === "webhook") {
|
|
41883
|
+
channel.webhook = { url: target, secret: actionOptions.secret, headers: parseHeaders(actionOptions.header), timeoutMs: actionOptions.timeoutMs };
|
|
41884
|
+
} else if (actionOptions.transport === "command") {
|
|
41885
|
+
channel.command = { command: target, args: actionOptions.arg ?? [], timeoutMs: actionOptions.timeoutMs };
|
|
41886
|
+
} else {
|
|
41887
|
+
throw new Error(`Transport ${actionOptions.transport} is reserved for future use and cannot be added yet`);
|
|
41888
|
+
}
|
|
41889
|
+
const saved = await createClient(options).addChannel(channel);
|
|
41890
|
+
print(sanitizeChannelForOutput(saved), Boolean(actionOptions.json), `Added ${saved.transport} channel ${saved.id}`);
|
|
41891
|
+
});
|
|
41892
|
+
webhooks.command("list").description("List configured subscriptions").option("-j, --json", "Print JSON output", false).action(async (actionOptions) => {
|
|
41893
|
+
const channels = await createClient(options).listChannels();
|
|
41894
|
+
if (actionOptions.json) {
|
|
41895
|
+
console.log(JSON.stringify(sanitizeChannelsForOutput(channels), null, 2));
|
|
41896
|
+
return;
|
|
41897
|
+
}
|
|
41898
|
+
if (!channels.length) {
|
|
41899
|
+
console.log("No channels configured.");
|
|
41900
|
+
return;
|
|
41901
|
+
}
|
|
41902
|
+
for (const channel of channels) {
|
|
41903
|
+
console.log(`${channel.id} ${channel.enabled ? "enabled" : "disabled"} ${channel.transport} ${channel.webhook?.url ?? channel.command?.command ?? channel.transport}`);
|
|
41904
|
+
}
|
|
41905
|
+
});
|
|
41906
|
+
webhooks.command("remove").description("Remove a subscription").argument("<id>", "Subscription/channel identifier").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions) => {
|
|
41907
|
+
const removed = await createClient(options).removeChannel(id);
|
|
41908
|
+
print({ removed }, Boolean(actionOptions.json), removed ? `Removed ${id}` : `Channel not found: ${id}`);
|
|
41909
|
+
});
|
|
41910
|
+
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) => {
|
|
41911
|
+
const result = await createClient(options).testChannel(id, {
|
|
41912
|
+
source: options.source,
|
|
41913
|
+
type: actionOptions.type,
|
|
41914
|
+
subject: actionOptions.subject ?? id,
|
|
41915
|
+
message: actionOptions.message,
|
|
41916
|
+
data: parseJsonObject(actionOptions.data, { test: true })
|
|
41917
|
+
});
|
|
41918
|
+
print(result, Boolean(actionOptions.json), `${result.status}: ${result.channelId}`);
|
|
41919
|
+
});
|
|
41920
|
+
return webhooks;
|
|
41921
|
+
}
|
|
41922
|
+
function registerEventCommands(program2, options) {
|
|
41923
|
+
const events = program2.command(options.eventsCommandName ?? "events").description("Emit, list, and replay Hasna events");
|
|
41924
|
+
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) => {
|
|
41925
|
+
const result = await createClient(options).emit({
|
|
41926
|
+
source: actionOptions.source ?? options.source,
|
|
41927
|
+
type,
|
|
41928
|
+
subject: actionOptions.subject,
|
|
41929
|
+
severity: actionOptions.severity,
|
|
41930
|
+
message: actionOptions.message,
|
|
41931
|
+
dedupeKey: actionOptions.dedupeKey,
|
|
41932
|
+
data: parseJsonObject(actionOptions.data, {}),
|
|
41933
|
+
metadata: parseJsonObject(actionOptions.metadata, {})
|
|
41934
|
+
}, { deliver: actionOptions.deliver, dedupe: actionOptions.dedupe });
|
|
41935
|
+
print(result, Boolean(actionOptions.json), `${result.deduped ? "Deduped" : "Emitted"} ${result.event.id} to ${result.deliveries.length} channel(s)`);
|
|
41936
|
+
});
|
|
41937
|
+
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) => {
|
|
41938
|
+
let rows = await createClient(options).listEvents();
|
|
41939
|
+
if (actionOptions.source)
|
|
41940
|
+
rows = rows.filter((event) => event.source === actionOptions.source);
|
|
41941
|
+
if (actionOptions.type)
|
|
41942
|
+
rows = rows.filter((event) => event.type === actionOptions.type);
|
|
41943
|
+
if (actionOptions.limit)
|
|
41944
|
+
rows = rows.slice(-actionOptions.limit);
|
|
41945
|
+
if (actionOptions.json) {
|
|
41946
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
41947
|
+
return;
|
|
41948
|
+
}
|
|
41949
|
+
if (!rows.length) {
|
|
41950
|
+
console.log("No events recorded.");
|
|
41951
|
+
return;
|
|
41952
|
+
}
|
|
41953
|
+
for (const event of rows)
|
|
41954
|
+
console.log(`${event.time} ${event.id} ${event.source} ${event.type} ${event.severity}`);
|
|
41955
|
+
});
|
|
41956
|
+
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) => {
|
|
41957
|
+
const result = await createClient(options).replay({
|
|
41958
|
+
eventId: actionOptions.id,
|
|
41959
|
+
source: actionOptions.source,
|
|
41960
|
+
type: actionOptions.type,
|
|
41961
|
+
dryRun: actionOptions.dryRun
|
|
41962
|
+
});
|
|
41963
|
+
print(result, Boolean(actionOptions.json), `Replayed ${result.events.length} event(s), ${result.deliveries.length} delivery result(s)`);
|
|
41964
|
+
});
|
|
41965
|
+
return events;
|
|
41966
|
+
}
|
|
41967
|
+
function registerEventsCommands(program2, options) {
|
|
41968
|
+
registerWebhookCommands(program2, options);
|
|
41969
|
+
registerEventCommands(program2, options);
|
|
41970
|
+
}
|
|
41971
|
+
function parseNumber(value) {
|
|
41972
|
+
const parsed = Number(value);
|
|
41973
|
+
if (!Number.isFinite(parsed))
|
|
41974
|
+
throw new Error(`Expected a number, got ${value}`);
|
|
41975
|
+
return parsed;
|
|
41976
|
+
}
|
|
41977
|
+
function collectValues(value, previous) {
|
|
41978
|
+
previous.push(value);
|
|
41979
|
+
return previous;
|
|
41980
|
+
}
|
|
41981
|
+
|
|
40556
41982
|
// src/cli/commands/domain.ts
|
|
40557
41983
|
init_domains();
|
|
40558
41984
|
|
|
@@ -40655,6 +42081,61 @@ async function renewDomain(domain, years = 1, config) {
|
|
|
40655
42081
|
chargedAmount: chargedAmount || undefined
|
|
40656
42082
|
};
|
|
40657
42083
|
}
|
|
42084
|
+
function namecheapContactParams(contact) {
|
|
42085
|
+
const organization = contact.organization_name || "NA";
|
|
42086
|
+
const base = {
|
|
42087
|
+
FirstName: contact.first_name,
|
|
42088
|
+
LastName: contact.last_name,
|
|
42089
|
+
OrganizationName: organization,
|
|
42090
|
+
Address1: contact.address_line_1,
|
|
42091
|
+
City: contact.city,
|
|
42092
|
+
StateProvince: contact.state,
|
|
42093
|
+
PostalCode: contact.zip_code,
|
|
42094
|
+
Country: contact.country_code,
|
|
42095
|
+
Phone: contact.phone,
|
|
42096
|
+
EmailAddress: contact.email
|
|
42097
|
+
};
|
|
42098
|
+
const params = {};
|
|
42099
|
+
for (const prefix of ["Registrant", "Tech", "Admin", "AuxBilling"]) {
|
|
42100
|
+
for (const [key, value] of Object.entries(base)) {
|
|
42101
|
+
params[prefix + key] = value;
|
|
42102
|
+
}
|
|
42103
|
+
}
|
|
42104
|
+
return params;
|
|
42105
|
+
}
|
|
42106
|
+
async function registerDomain(domain, contact, options = {}, config) {
|
|
42107
|
+
const cfg = config || getConfig();
|
|
42108
|
+
const params = {
|
|
42109
|
+
DomainName: domain,
|
|
42110
|
+
Years: String(options.years ?? 1),
|
|
42111
|
+
AddFreeWhoisguard: options.whoisGuard === false ? "no" : "yes",
|
|
42112
|
+
WGEnabled: options.whoisGuard === false ? "no" : "yes",
|
|
42113
|
+
...namecheapContactParams(contact)
|
|
42114
|
+
};
|
|
42115
|
+
if (options.premiumPrice !== undefined) {
|
|
42116
|
+
params.IsPremiumDomain = "true";
|
|
42117
|
+
params.PremiumPrice = String(options.premiumPrice);
|
|
42118
|
+
}
|
|
42119
|
+
const xml = await apiRequest(cfg, "namecheap.domains.create", params);
|
|
42120
|
+
return {
|
|
42121
|
+
domain,
|
|
42122
|
+
success: xml.includes('Status="OK"') || xml.includes('Registered="true"'),
|
|
42123
|
+
orderId: parseXmlValue(xml, "OrderId") || undefined,
|
|
42124
|
+
chargedAmount: parseXmlValue(xml, "ChargedAmount") || undefined
|
|
42125
|
+
};
|
|
42126
|
+
}
|
|
42127
|
+
async function updateNameservers(domain, nameservers, config) {
|
|
42128
|
+
if (nameservers.length === 0)
|
|
42129
|
+
throw new Error("updateNameservers requires at least one nameserver");
|
|
42130
|
+
const cfg = config || getConfig();
|
|
42131
|
+
const { sld, tld } = splitDomain(domain);
|
|
42132
|
+
const xml = await apiRequest(cfg, "namecheap.domains.dns.setCustom", {
|
|
42133
|
+
SLD: sld,
|
|
42134
|
+
TLD: tld,
|
|
42135
|
+
Nameservers: nameservers.join(",")
|
|
42136
|
+
});
|
|
42137
|
+
return xml.includes('Status="OK"') || xml.includes('Updated="true"');
|
|
42138
|
+
}
|
|
40658
42139
|
async function getDnsRecords(_domain, sld, tld, config) {
|
|
40659
42140
|
const cfg = config || getConfig();
|
|
40660
42141
|
const xml = await apiRequest(cfg, "namecheap.domains.dns.getHosts", {
|
|
@@ -40783,141 +42264,12 @@ function normalizeDate(dateStr) {
|
|
|
40783
42264
|
}
|
|
40784
42265
|
}
|
|
40785
42266
|
|
|
40786
|
-
// src/lib/godaddy.ts
|
|
40787
|
-
class GoDaddyApiError extends Error {
|
|
40788
|
-
statusCode;
|
|
40789
|
-
details;
|
|
40790
|
-
constructor(message, statusCode, details) {
|
|
40791
|
-
super(message);
|
|
40792
|
-
this.statusCode = statusCode;
|
|
40793
|
-
this.details = details;
|
|
40794
|
-
this.name = "GoDaddyApiError";
|
|
40795
|
-
}
|
|
40796
|
-
}
|
|
40797
|
-
var _overriddenFetch = null;
|
|
40798
|
-
function getCredentials() {
|
|
40799
|
-
const apiKey = process.env["GODADDY_API_KEY"];
|
|
40800
|
-
const apiSecret = process.env["GODADDY_API_SECRET"];
|
|
40801
|
-
if (!apiKey || !apiSecret) {
|
|
40802
|
-
throw new Error("GoDaddy API credentials not configured. Set GODADDY_API_KEY and GODADDY_API_SECRET environment variables.");
|
|
40803
|
-
}
|
|
40804
|
-
return { apiKey, apiSecret };
|
|
40805
|
-
}
|
|
40806
|
-
function getHeaders() {
|
|
40807
|
-
const { apiKey, apiSecret } = getCredentials();
|
|
40808
|
-
return {
|
|
40809
|
-
Authorization: `sso-key ${apiKey}:${apiSecret}`,
|
|
40810
|
-
"Content-Type": "application/json",
|
|
40811
|
-
Accept: "application/json"
|
|
40812
|
-
};
|
|
40813
|
-
}
|
|
40814
|
-
var GODADDY_API_BASE = "https://api.godaddy.com";
|
|
40815
|
-
async function apiRequest2(method, path, body) {
|
|
40816
|
-
const fetchFn = _overriddenFetch || globalThis.fetch;
|
|
40817
|
-
const url = `${GODADDY_API_BASE}${path}`;
|
|
40818
|
-
const headers = getHeaders();
|
|
40819
|
-
const options = { method, headers };
|
|
40820
|
-
if (body !== undefined) {
|
|
40821
|
-
options.body = JSON.stringify(body);
|
|
40822
|
-
}
|
|
40823
|
-
const response = await fetchFn(url, options);
|
|
40824
|
-
if (!response.ok) {
|
|
40825
|
-
const text = await response.text();
|
|
40826
|
-
throw new GoDaddyApiError(`GoDaddy API ${method} ${path} failed with status ${response.status}: ${text}`, response.status, { responseBody: text });
|
|
40827
|
-
}
|
|
40828
|
-
if (response.status === 204) {
|
|
40829
|
-
return;
|
|
40830
|
-
}
|
|
40831
|
-
return await response.json();
|
|
40832
|
-
}
|
|
40833
|
-
async function listGoDaddyDomains() {
|
|
40834
|
-
return apiRequest2("GET", "/v1/domains");
|
|
40835
|
-
}
|
|
40836
|
-
async function getDomainInfo2(domain) {
|
|
40837
|
-
return apiRequest2("GET", `/v1/domains/${encodeURIComponent(domain)}`);
|
|
40838
|
-
}
|
|
40839
|
-
async function renewDomain2(domain) {
|
|
40840
|
-
return apiRequest2("POST", `/v1/domains/${encodeURIComponent(domain)}/renew`, { period: 1 });
|
|
40841
|
-
}
|
|
40842
|
-
async function getDnsRecords2(domain, type) {
|
|
40843
|
-
const path = type ? `/v1/domains/${encodeURIComponent(domain)}/records/${encodeURIComponent(type)}` : `/v1/domains/${encodeURIComponent(domain)}/records`;
|
|
40844
|
-
return apiRequest2("GET", path);
|
|
40845
|
-
}
|
|
40846
|
-
async function setDnsRecords2(domain, records) {
|
|
40847
|
-
await apiRequest2("PUT", `/v1/domains/${encodeURIComponent(domain)}/records`, records);
|
|
40848
|
-
}
|
|
40849
|
-
async function checkAvailability2(domain) {
|
|
40850
|
-
return apiRequest2("GET", `/v1/domains/available?domain=${encodeURIComponent(domain)}`);
|
|
40851
|
-
}
|
|
40852
|
-
function mapGoDaddyStatus(gdStatus) {
|
|
40853
|
-
const s = gdStatus.toUpperCase();
|
|
40854
|
-
if (s === "ACTIVE")
|
|
40855
|
-
return "active";
|
|
40856
|
-
if (s === "EXPIRED")
|
|
40857
|
-
return "expired";
|
|
40858
|
-
if (s === "TRANSFERRED_OUT" || s === "TRANSFERRING" || s === "PENDING_TRANSFER")
|
|
40859
|
-
return "transferring";
|
|
40860
|
-
if (s === "REDEMPTION" || s === "PENDING_REDEMPTION")
|
|
40861
|
-
return "redemption";
|
|
40862
|
-
return "active";
|
|
40863
|
-
}
|
|
40864
|
-
async function syncToLocalDb2(dbFns) {
|
|
40865
|
-
const result = {
|
|
40866
|
-
synced: 0,
|
|
40867
|
-
created: 0,
|
|
40868
|
-
updated: 0,
|
|
40869
|
-
errors: []
|
|
40870
|
-
};
|
|
40871
|
-
let gdDomains;
|
|
40872
|
-
try {
|
|
40873
|
-
gdDomains = await listGoDaddyDomains();
|
|
40874
|
-
} catch (err) {
|
|
40875
|
-
result.errors.push(`Failed to list domains: ${err instanceof Error ? err.message : String(err)}`);
|
|
40876
|
-
return result;
|
|
40877
|
-
}
|
|
40878
|
-
for (const gd of gdDomains) {
|
|
40879
|
-
try {
|
|
40880
|
-
let detail;
|
|
40881
|
-
try {
|
|
40882
|
-
detail = await getDomainInfo2(gd.domain);
|
|
40883
|
-
} catch {
|
|
40884
|
-
detail = gd;
|
|
40885
|
-
}
|
|
40886
|
-
const existing = dbFns.getDomainByName(gd.domain);
|
|
40887
|
-
const domainData = {
|
|
40888
|
-
name: gd.domain,
|
|
40889
|
-
registrar: "GoDaddy",
|
|
40890
|
-
status: mapGoDaddyStatus(gd.status),
|
|
40891
|
-
expires_at: gd.expires ? new Date(gd.expires).toISOString() : undefined,
|
|
40892
|
-
auto_renew: gd.renewAuto,
|
|
40893
|
-
nameservers: gd.nameServers || [],
|
|
40894
|
-
registered_at: detail.createdAt ? new Date(detail.createdAt).toISOString() : undefined,
|
|
40895
|
-
metadata: {
|
|
40896
|
-
godaddy_domain_id: detail.domainId,
|
|
40897
|
-
provider: "godaddy",
|
|
40898
|
-
locked: detail.locked,
|
|
40899
|
-
privacy: detail.privacy
|
|
40900
|
-
}
|
|
40901
|
-
};
|
|
40902
|
-
if (existing) {
|
|
40903
|
-
dbFns.updateDomain(existing.id, domainData);
|
|
40904
|
-
result.updated++;
|
|
40905
|
-
} else {
|
|
40906
|
-
dbFns.createDomain(domainData);
|
|
40907
|
-
result.created++;
|
|
40908
|
-
}
|
|
40909
|
-
result.synced++;
|
|
40910
|
-
} catch (err) {
|
|
40911
|
-
result.errors.push(`Failed to sync ${gd.domain}: ${err instanceof Error ? err.message : String(err)}`);
|
|
40912
|
-
}
|
|
40913
|
-
}
|
|
40914
|
-
return result;
|
|
40915
|
-
}
|
|
40916
|
-
|
|
40917
42267
|
// src/lib/registrar.ts
|
|
42268
|
+
init_godaddy();
|
|
40918
42269
|
init_route53();
|
|
40919
42270
|
init_cloudflare();
|
|
40920
42271
|
init_brandsight();
|
|
42272
|
+
init_env_aliases();
|
|
40921
42273
|
function createNamecheapProvider() {
|
|
40922
42274
|
return {
|
|
40923
42275
|
name: "namecheap",
|
|
@@ -40947,9 +42299,27 @@ function createNamecheapProvider() {
|
|
|
40947
42299
|
auto_renew: true
|
|
40948
42300
|
};
|
|
40949
42301
|
},
|
|
40950
|
-
async
|
|
42302
|
+
async registerDomain(domain, contact, options = {}) {
|
|
40951
42303
|
const config = getConfig();
|
|
40952
|
-
const result = await
|
|
42304
|
+
const result = await registerDomain(domain, contact, {
|
|
42305
|
+
years: options.years,
|
|
42306
|
+
premiumPrice: options.premiumPrice
|
|
42307
|
+
}, config);
|
|
42308
|
+
return {
|
|
42309
|
+
domain: result.domain,
|
|
42310
|
+
success: result.success,
|
|
42311
|
+
orderId: result.orderId,
|
|
42312
|
+
chargedAmount: result.chargedAmount
|
|
42313
|
+
};
|
|
42314
|
+
},
|
|
42315
|
+
async updateNameservers(domain, nameservers) {
|
|
42316
|
+
const config = getConfig();
|
|
42317
|
+
const success = await updateNameservers(domain, nameservers, config);
|
|
42318
|
+
return { domain, success };
|
|
42319
|
+
},
|
|
42320
|
+
async renewDomain(domain, years = 1) {
|
|
42321
|
+
const config = getConfig();
|
|
42322
|
+
const result = await renewDomain(domain, years, config);
|
|
40953
42323
|
return {
|
|
40954
42324
|
domain: result.domain,
|
|
40955
42325
|
success: result.success,
|
|
@@ -41085,8 +42455,9 @@ var providerRegistry = new Map([
|
|
|
41085
42455
|
name: "namecheap",
|
|
41086
42456
|
type: "full",
|
|
41087
42457
|
configured: false,
|
|
41088
|
-
envVars:
|
|
42458
|
+
envVars: providerEnvNames("namecheap")
|
|
41089
42459
|
},
|
|
42460
|
+
createInventory: createNamecheapProvider,
|
|
41090
42461
|
createRegistrar: createNamecheapProvider,
|
|
41091
42462
|
createDns: createNamecheapProvider
|
|
41092
42463
|
}],
|
|
@@ -41095,8 +42466,9 @@ var providerRegistry = new Map([
|
|
|
41095
42466
|
name: "godaddy",
|
|
41096
42467
|
type: "full",
|
|
41097
42468
|
configured: false,
|
|
41098
|
-
envVars:
|
|
42469
|
+
envVars: providerEnvNames("godaddy")
|
|
41099
42470
|
},
|
|
42471
|
+
createInventory: createGoDaddyProvider,
|
|
41100
42472
|
createRegistrar: createGoDaddyProvider,
|
|
41101
42473
|
createDns: createGoDaddyProvider
|
|
41102
42474
|
}],
|
|
@@ -41105,8 +42477,9 @@ var providerRegistry = new Map([
|
|
|
41105
42477
|
name: "route53",
|
|
41106
42478
|
type: "full",
|
|
41107
42479
|
configured: false,
|
|
41108
|
-
envVars:
|
|
42480
|
+
envVars: providerEnvNames("route53")
|
|
41109
42481
|
},
|
|
42482
|
+
createInventory: () => createRoute53Provider(),
|
|
41110
42483
|
createRegistrar: () => createRoute53Provider(),
|
|
41111
42484
|
createDns: () => createRoute53Provider()
|
|
41112
42485
|
}],
|
|
@@ -41115,47 +42488,77 @@ var providerRegistry = new Map([
|
|
|
41115
42488
|
name: "cloudflare",
|
|
41116
42489
|
type: "dns",
|
|
41117
42490
|
configured: false,
|
|
41118
|
-
envVars:
|
|
42491
|
+
envVars: providerEnvNames("cloudflare")
|
|
41119
42492
|
},
|
|
42493
|
+
createInventory: () => createCloudflareProvider(),
|
|
41120
42494
|
createDns: createCloudflareProvider
|
|
41121
42495
|
}],
|
|
41122
42496
|
["brandsight", {
|
|
41123
42497
|
info: {
|
|
41124
42498
|
name: "brandsight",
|
|
41125
|
-
type: "
|
|
42499
|
+
type: "full",
|
|
41126
42500
|
configured: false,
|
|
41127
|
-
envVars:
|
|
42501
|
+
envVars: providerEnvNames("brandsight")
|
|
41128
42502
|
},
|
|
41129
|
-
|
|
42503
|
+
createInventory: createBrandsightProvider,
|
|
42504
|
+
createRegistrar: createBrandsightProvider,
|
|
42505
|
+
createDns: createBrandsightProvider
|
|
42506
|
+
}],
|
|
42507
|
+
["sedo", {
|
|
42508
|
+
info: {
|
|
42509
|
+
name: "sedo",
|
|
42510
|
+
type: "marketplace",
|
|
42511
|
+
configured: false,
|
|
42512
|
+
envVars: providerEnvNames("sedo")
|
|
42513
|
+
}
|
|
41130
42514
|
}]
|
|
41131
42515
|
]);
|
|
41132
|
-
function isConfigured(
|
|
41133
|
-
return
|
|
42516
|
+
function isConfigured(providerName) {
|
|
42517
|
+
return hasProviderCredentials(providerName);
|
|
41134
42518
|
}
|
|
41135
42519
|
function getAvailableProviders() {
|
|
41136
42520
|
return Array.from(providerRegistry.values()).map((e3) => ({
|
|
41137
42521
|
...e3.info,
|
|
41138
|
-
configured: isConfigured(e3.info.
|
|
42522
|
+
configured: isConfigured(e3.info.name),
|
|
42523
|
+
inventory: !!e3.createInventory
|
|
41139
42524
|
}));
|
|
41140
42525
|
}
|
|
42526
|
+
function getProviderInfo(name) {
|
|
42527
|
+
const entry = providerRegistry.get(name.toLowerCase());
|
|
42528
|
+
if (!entry)
|
|
42529
|
+
return null;
|
|
42530
|
+
return { ...entry.info, configured: isConfigured(entry.info.name), inventory: !!entry.createInventory };
|
|
42531
|
+
}
|
|
42532
|
+
function providerHasRegistrar(name) {
|
|
42533
|
+
return !!providerRegistry.get(name.toLowerCase())?.createRegistrar;
|
|
42534
|
+
}
|
|
42535
|
+
function providerHasInventory(name) {
|
|
42536
|
+
return !!providerRegistry.get(name.toLowerCase())?.createInventory;
|
|
42537
|
+
}
|
|
42538
|
+
function getDomainInventoryProvider(name) {
|
|
42539
|
+
const entry = providerRegistry.get(name.toLowerCase());
|
|
42540
|
+
if (!entry?.createInventory)
|
|
42541
|
+
throw new Error(`No domain inventory provider: ${name}`);
|
|
42542
|
+
return entry.createInventory();
|
|
42543
|
+
}
|
|
41141
42544
|
function getRegistrarProvider(name) {
|
|
41142
|
-
const entry = providerRegistry.get(name);
|
|
42545
|
+
const entry = providerRegistry.get(name.toLowerCase());
|
|
41143
42546
|
if (!entry?.createRegistrar)
|
|
41144
42547
|
throw new Error(`No registrar provider: ${name}`);
|
|
41145
42548
|
return entry.createRegistrar();
|
|
41146
42549
|
}
|
|
41147
42550
|
function getDnsProvider(name) {
|
|
41148
|
-
const entry = providerRegistry.get(name);
|
|
42551
|
+
const entry = providerRegistry.get(name.toLowerCase());
|
|
41149
42552
|
if (!entry?.createDns)
|
|
41150
42553
|
throw new Error(`No DNS provider: ${name}`);
|
|
41151
42554
|
return entry.createDns();
|
|
41152
42555
|
}
|
|
41153
42556
|
async function syncAll(dbFns) {
|
|
41154
|
-
const available = getAvailableProviders().filter((p3) => p3.configured && (p3.
|
|
42557
|
+
const available = getAvailableProviders().filter((p3) => p3.configured && providerHasInventory(p3.name));
|
|
41155
42558
|
const result = { providers: [], totalSynced: 0, totalErrors: [] };
|
|
41156
42559
|
for (const info of available) {
|
|
41157
42560
|
try {
|
|
41158
|
-
const provider =
|
|
42561
|
+
const provider = getDomainInventoryProvider(info.name);
|
|
41159
42562
|
const syncResult = await provider.syncToLocalDb(dbFns);
|
|
41160
42563
|
result.providers.push({ name: info.name, result: syncResult });
|
|
41161
42564
|
result.totalSynced += syncResult.synced;
|
|
@@ -41173,14 +42576,16 @@ function autoDetectRegistrar(domain, getDomainByName2) {
|
|
|
41173
42576
|
if (!dbDomain?.registrar)
|
|
41174
42577
|
return null;
|
|
41175
42578
|
const r3 = dbDomain.registrar.toLowerCase();
|
|
42579
|
+
if (r3.includes("cloudflare dns") || r3.includes("route 53 dns") || r3.includes("route53 dns"))
|
|
42580
|
+
return null;
|
|
41176
42581
|
if (r3.includes("namecheap"))
|
|
41177
42582
|
return "namecheap";
|
|
41178
42583
|
if (r3.includes("godaddy"))
|
|
41179
42584
|
return "godaddy";
|
|
41180
|
-
if (r3.includes("route 53") || r3.includes("route53")
|
|
42585
|
+
if (r3.includes("route 53") || r3.includes("route53"))
|
|
41181
42586
|
return "route53";
|
|
41182
42587
|
if (r3.includes("cloudflare"))
|
|
41183
|
-
return
|
|
42588
|
+
return null;
|
|
41184
42589
|
if (r3.includes("brandsight"))
|
|
41185
42590
|
return "brandsight";
|
|
41186
42591
|
return null;
|
|
@@ -41520,10 +42925,10 @@ WHOIS for ${result.domain} [${result.source}]:`);
|
|
|
41520
42925
|
process.exit(1);
|
|
41521
42926
|
});
|
|
41522
42927
|
domain.command("sync").description("Sync domains from a provider to the local DB").option("--provider <name>", "Provider name (default: all configured)").action(async (opts) => {
|
|
41523
|
-
const providers = opts.provider ? [opts.provider] : getAvailableProviders().filter((p3) => p3.configured && p3.
|
|
42928
|
+
const providers = opts.provider ? [opts.provider] : getAvailableProviders().filter((p3) => p3.configured && providerHasInventory(p3.name)).map((p3) => p3.name);
|
|
41524
42929
|
for (const name of providers) {
|
|
41525
42930
|
try {
|
|
41526
|
-
const provider =
|
|
42931
|
+
const provider = getDomainInventoryProvider(name);
|
|
41527
42932
|
const result = await provider.syncToLocalDb({ getDomainByName, createDomain, updateDomain });
|
|
41528
42933
|
console.log(`\u2713 [${name}] Synced ${result.synced} (${result.created} new, ${result.updated} updated)`);
|
|
41529
42934
|
if (result.errors.length > 0)
|
|
@@ -41607,14 +43012,14 @@ WHOIS for ${result.domain} [${result.source}]:`);
|
|
|
41607
43012
|
}
|
|
41608
43013
|
console.log(`Linked ${emailId} to ${details.domain.name}`);
|
|
41609
43014
|
});
|
|
41610
|
-
domain.command("renew <name>").description("Renew a domain via its registrar provider").option("--provider <name>", "Override provider").action(async (name, opts) => {
|
|
43015
|
+
domain.command("renew <name>").description("Renew a domain via its registrar provider").option("--provider <name>", "Override provider").option("--years <n>", "Number of years to renew", "1").action(async (name, opts) => {
|
|
41611
43016
|
const providerName = opts.provider ?? autoDetectRegistrar(name, getDomainByName) ?? loadConfig3().default_registrar;
|
|
41612
43017
|
if (!providerName) {
|
|
41613
43018
|
console.error("Could not detect provider. Use --provider.");
|
|
41614
43019
|
process.exit(1);
|
|
41615
43020
|
}
|
|
41616
43021
|
const provider = getRegistrarProvider(providerName);
|
|
41617
|
-
const result = await provider.renewDomain(name);
|
|
43022
|
+
const result = await provider.renewDomain(name, parseInt(opts.years, 10));
|
|
41618
43023
|
if (result.success) {
|
|
41619
43024
|
console.log(`\u2713 Renewed: ${name}`);
|
|
41620
43025
|
if (result.orderId)
|
|
@@ -41624,7 +43029,7 @@ WHOIS for ${result.domain} [${result.source}]:`);
|
|
|
41624
43029
|
process.exit(1);
|
|
41625
43030
|
}
|
|
41626
43031
|
});
|
|
41627
|
-
domain.command("buy <name>").description("Purchase a domain via
|
|
43032
|
+
domain.command("buy <name>").description("Purchase or record a domain via registrar provider (contact defaults from: domains config set contact.*)").option("--provider <name>", "Registrar provider (default: config default-registrar or route53)").option("--registrar <name>", "Registrar/seller for recorded purchases (alias of --provider)").option("--email <email>", "Registrant email").option("--first-name <n>", "First name").option("--last-name <n>", "Last name").option("--phone <p>", "Phone").option("--address <a>", "Street address").option("--city <c>", "City").option("--state <s>", "State/province").option("--country <c>", "Country code").option("--zip <z>", "ZIP code").option("--org <o>", "Organization").option("--price <amount>", "Record a completed purchase instead of registering via Route 53").option("--expires <date>", "Expiry date for recorded purchases").option("--auto-renew <bool>", "Auto-renew for recorded purchases (true/false)").option("--years <n>", "Years", "1").option("--wait", "Poll until registration completes").option("--dns <provider>", "DNS provider to delegate to after purchase (always cloudflare)", "cloudflare").option("--no-delegate", "Skip delegating DNS to Cloudflare after purchase").action(async (name, opts) => {
|
|
41628
43033
|
const recordedPrice = parseOptionalNumber(opts.price, "--price");
|
|
41629
43034
|
if (recordedPrice !== undefined) {
|
|
41630
43035
|
const registrarName = opts.registrar ?? opts.provider ?? "manual";
|
|
@@ -41648,12 +43053,75 @@ WHOIS for ${result.domain} [${result.source}]:`);
|
|
|
41648
43053
|
}
|
|
41649
43054
|
const cfg = loadConfig3();
|
|
41650
43055
|
const providerName = opts.registrar ?? opts.provider ?? cfg.default_registrar ?? "route53";
|
|
41651
|
-
const purchaseProfile = applyPurchaseProfile();
|
|
43056
|
+
const purchaseProfile = providerName === "route53" ? applyPurchaseProfile() : undefined;
|
|
41652
43057
|
if (purchaseProfile)
|
|
41653
43058
|
console.log(`Using purchase AWS profile: ${purchaseProfile}`);
|
|
41654
43059
|
if (providerName !== "route53") {
|
|
41655
|
-
|
|
41656
|
-
|
|
43060
|
+
try {
|
|
43061
|
+
const provider = getRegistrarProvider(providerName);
|
|
43062
|
+
if (!provider.registerDomain) {
|
|
43063
|
+
console.error(`Direct domain purchase is not supported for ${providerName}. Use route53 or record a marketplace/manual purchase with --price.`);
|
|
43064
|
+
process.exit(1);
|
|
43065
|
+
}
|
|
43066
|
+
const avail = await provider.checkAvailability(name);
|
|
43067
|
+
if (!avail.available) {
|
|
43068
|
+
console.error(`\u2717 ${name} is not available`);
|
|
43069
|
+
process.exit(1);
|
|
43070
|
+
}
|
|
43071
|
+
console.log(`\u2713 Available via ${providerName}`);
|
|
43072
|
+
let contact;
|
|
43073
|
+
try {
|
|
43074
|
+
contact = resolveContact(opts);
|
|
43075
|
+
} catch (e3) {
|
|
43076
|
+
console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
|
|
43077
|
+
process.exit(1);
|
|
43078
|
+
}
|
|
43079
|
+
console.log(`Registering ${name} via ${providerName}...`);
|
|
43080
|
+
const reg = await provider.registerDomain(name, contact, {
|
|
43081
|
+
years: parseInt(opts.years, 10),
|
|
43082
|
+
premiumPrice: avail.premium_price,
|
|
43083
|
+
autoRenew: opts.autoRenew ? opts.autoRenew === "true" : true
|
|
43084
|
+
});
|
|
43085
|
+
if (!reg.success) {
|
|
43086
|
+
console.error(`\u2717 Registration failed via ${providerName}`);
|
|
43087
|
+
process.exit(1);
|
|
43088
|
+
}
|
|
43089
|
+
const existing = getDomainByName(name);
|
|
43090
|
+
const dbInput = {
|
|
43091
|
+
registrar: providerName,
|
|
43092
|
+
status: "active",
|
|
43093
|
+
auto_renew: opts.autoRenew ? opts.autoRenew === "true" : true,
|
|
43094
|
+
purchase_price: reg.chargedAmount ? Number(reg.chargedAmount) : avail.standard_price,
|
|
43095
|
+
purchase_date: new Date().toISOString(),
|
|
43096
|
+
standard_price: avail.standard_price,
|
|
43097
|
+
is_premium: avail.is_premium,
|
|
43098
|
+
premium_price: avail.premium_price
|
|
43099
|
+
};
|
|
43100
|
+
if (existing)
|
|
43101
|
+
updateDomain(existing.id, dbInput);
|
|
43102
|
+
else
|
|
43103
|
+
createDomain({ name, ...dbInput });
|
|
43104
|
+
console.log(`\u2713 Registered and added to portfolio`);
|
|
43105
|
+
if (reg.orderId)
|
|
43106
|
+
console.log(` Order: ${reg.orderId}`);
|
|
43107
|
+
const dnsProvider = opts.dns ?? "cloudflare";
|
|
43108
|
+
if (opts.delegate !== false && dnsProvider === "cloudflare") {
|
|
43109
|
+
if (!provider.updateNameservers) {
|
|
43110
|
+
console.log(` DNS: ${providerName} registration succeeded, but nameserver updates are not implemented for this provider.`);
|
|
43111
|
+
} else {
|
|
43112
|
+
const zone = await ensureZone(name);
|
|
43113
|
+
const nsUpdate = await provider.updateNameservers(name, zone.nameservers);
|
|
43114
|
+
const existing2 = getDomainByName(name);
|
|
43115
|
+
if (existing2)
|
|
43116
|
+
updateDomain(existing2.id, { nameservers: zone.nameservers });
|
|
43117
|
+
console.log(`\u2713 Cloudflare zone ${zone.id}; nameservers updated${nsUpdate.operationId ? ` (op ${nsUpdate.operationId})` : ""}`);
|
|
43118
|
+
}
|
|
43119
|
+
}
|
|
43120
|
+
return;
|
|
43121
|
+
} catch (e3) {
|
|
43122
|
+
console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
|
|
43123
|
+
process.exit(1);
|
|
43124
|
+
}
|
|
41657
43125
|
}
|
|
41658
43126
|
try {
|
|
41659
43127
|
const avail = await checkAvailability3(name);
|
|
@@ -41671,7 +43139,7 @@ WHOIS for ${result.domain} [${result.source}]:`);
|
|
|
41671
43139
|
process.exit(1);
|
|
41672
43140
|
}
|
|
41673
43141
|
console.log(`Registering ${name}...`);
|
|
41674
|
-
const reg = await
|
|
43142
|
+
const reg = await registerDomain2(name, contact, parseInt(opts.years));
|
|
41675
43143
|
console.log(`\u2713 Submitted (operation: ${reg.operationId})`);
|
|
41676
43144
|
if (opts.wait) {
|
|
41677
43145
|
let status = "IN_PROGRESS";
|
|
@@ -41719,10 +43187,10 @@ WHOIS for ${result.domain} [${result.source}]:`);
|
|
|
41719
43187
|
console.log(`Delegating DNS to Cloudflare...`);
|
|
41720
43188
|
const del = await delegateDomainToCloudflare(name, {
|
|
41721
43189
|
createCloudflareZone: async (d3) => {
|
|
41722
|
-
const z2 = await
|
|
43190
|
+
const z2 = await ensureZone(d3);
|
|
41723
43191
|
return { id: z2.id, nameservers: z2.nameservers };
|
|
41724
43192
|
},
|
|
41725
|
-
updateNameservers: (d3, ns) =>
|
|
43193
|
+
updateNameservers: (d3, ns) => updateNameservers2(d3, ns)
|
|
41726
43194
|
});
|
|
41727
43195
|
const existing2 = getDomainByName(name);
|
|
41728
43196
|
if (existing2)
|
|
@@ -41741,16 +43209,16 @@ WHOIS for ${result.domain} [${result.source}]:`);
|
|
|
41741
43209
|
process.exit(1);
|
|
41742
43210
|
}
|
|
41743
43211
|
});
|
|
41744
|
-
domain.command("setup <name>").description("Full setup: buy domain + create DNS zone + point nameservers (contact defaults from config)").option("--registrar <n>", "Registrar provider (default: config default-registrar or route53)").option("--dns <n>", "DNS provider (default: config default-dns or
|
|
43212
|
+
domain.command("setup <name>").description("Full setup: buy domain + create DNS zone + point nameservers (contact defaults from config)").option("--registrar <n>", "Registrar provider (default: config default-registrar or route53)").option("--dns <n>", "DNS provider (default: config default-dns or cloudflare)").option("--email <e>", "Registrant email").option("--first-name <n>", "First name").option("--last-name <n>", "Last name").option("--phone <p>", "Phone").option("--address <a>", "Street address").option("--city <c>", "City").option("--state <s>", "State/province").option("--country <c>", "Country code").option("--zip <z>", "ZIP code").option("--org <o>", "Organization").option("--years <n>", "Years", "1").option("--wait", "Poll until registration completes before creating DNS zone").action(async (name, opts) => {
|
|
41745
43213
|
const cfg = loadConfig3();
|
|
41746
43214
|
const registrarName = opts.registrar ?? cfg.default_registrar ?? "route53";
|
|
41747
|
-
const dnsName = opts.dns ?? cfg.default_dns ??
|
|
43215
|
+
const dnsName = opts.dns ?? cfg.default_dns ?? "cloudflare";
|
|
41748
43216
|
console.log(`
|
|
41749
43217
|
Setting up ${name}`);
|
|
41750
43218
|
console.log(` Registrar: ${registrarName} | DNS: ${dnsName}
|
|
41751
43219
|
`);
|
|
41752
43220
|
try {
|
|
41753
|
-
process.stdout.write("[1/4] Checking availability... ");
|
|
43221
|
+
process.stdout.write(opts.wait ? "[1/5] Checking availability... " : "[1/4] Checking availability... ");
|
|
41754
43222
|
const avail = await checkAvailability3(name);
|
|
41755
43223
|
if (!avail.available) {
|
|
41756
43224
|
console.log("not available");
|
|
@@ -41770,8 +43238,8 @@ Setting up ${name}`);
|
|
|
41770
43238
|
console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
|
|
41771
43239
|
process.exit(1);
|
|
41772
43240
|
}
|
|
41773
|
-
process.stdout.write("[2/4] Registering domain... ");
|
|
41774
|
-
const reg = await
|
|
43241
|
+
process.stdout.write(opts.wait ? "[2/5] Registering domain... " : "[2/4] Registering domain... ");
|
|
43242
|
+
const reg = await registerDomain2(name, contact, parseInt(opts.years));
|
|
41775
43243
|
console.log(`submitted (${reg.operationId})`);
|
|
41776
43244
|
if (opts.wait) {
|
|
41777
43245
|
let status = "IN_PROGRESS";
|
|
@@ -41788,19 +43256,38 @@ Setting up ${name}`);
|
|
|
41788
43256
|
}
|
|
41789
43257
|
console.log(" Registration confirmed");
|
|
41790
43258
|
}
|
|
41791
|
-
process.stdout.write("[3/4] Creating DNS zone... ");
|
|
43259
|
+
process.stdout.write(opts.wait ? "[3/5] Creating DNS zone... " : "[3/4] Creating DNS zone... ");
|
|
41792
43260
|
let nameservers = [];
|
|
41793
43261
|
if (dnsName === "cloudflare") {
|
|
41794
|
-
const zone = await
|
|
43262
|
+
const zone = await ensureZone(name);
|
|
41795
43263
|
nameservers = zone.nameservers ?? [];
|
|
41796
|
-
console.log(
|
|
43264
|
+
console.log("ready (" + zone.id + ")");
|
|
41797
43265
|
} else {
|
|
41798
43266
|
const zone = await createHostedZone(name, `Managed by @hasna/domains`);
|
|
41799
43267
|
nameservers = zone.name_servers ?? [];
|
|
41800
43268
|
console.log(`created (${zone.id})`);
|
|
41801
43269
|
}
|
|
41802
|
-
|
|
41803
|
-
|
|
43270
|
+
if (nameservers.length > 0) {
|
|
43271
|
+
if (opts.wait) {
|
|
43272
|
+
process.stdout.write("[4/5] Updating registrar nameservers... ");
|
|
43273
|
+
if (registrarName !== "route53") {
|
|
43274
|
+
console.log("skipped");
|
|
43275
|
+
console.error("Only Route53 nameserver delegation is currently implemented for setup.");
|
|
43276
|
+
process.exit(1);
|
|
43277
|
+
}
|
|
43278
|
+
const nsUpdate = await updateNameservers2(name, nameservers);
|
|
43279
|
+
console.log("submitted (" + nsUpdate.operationId + ")");
|
|
43280
|
+
} else {
|
|
43281
|
+
console.log(" Nameserver delegation skipped until registration completes; re-run with --wait or use domains r53 domain-info/status first.");
|
|
43282
|
+
}
|
|
43283
|
+
}
|
|
43284
|
+
process.stdout.write(opts.wait ? "[5/5] Adding to portfolio... " : "[4/4] Adding to portfolio... ");
|
|
43285
|
+
const existing = getDomainByName(name);
|
|
43286
|
+
const dbInput = { registrar: `AWS Route 53`, status: "active", auto_renew: true, nameservers };
|
|
43287
|
+
if (existing)
|
|
43288
|
+
updateDomain(existing.id, dbInput);
|
|
43289
|
+
else
|
|
43290
|
+
createDomain({ name, ...dbInput });
|
|
41804
43291
|
console.log("done");
|
|
41805
43292
|
console.log(`
|
|
41806
43293
|
\u2713 Setup complete for ${name}`);
|
|
@@ -42294,6 +43781,10 @@ function registerMonitorCommand(program2) {
|
|
|
42294
43781
|
}
|
|
42295
43782
|
|
|
42296
43783
|
// src/cli/commands/provider.ts
|
|
43784
|
+
function isAwsAccessDenied(error) {
|
|
43785
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
43786
|
+
return msg.includes("AccessDenied") || msg.includes("route53domains:ListDomains");
|
|
43787
|
+
}
|
|
42297
43788
|
function registerProviderCommand(program2) {
|
|
42298
43789
|
const provider = program2.command("provider").description("Configure and test registrar/DNS providers");
|
|
42299
43790
|
provider.command("list").description("Show all providers and their configuration status").option("-j, --json", "Output JSON").action((opts) => {
|
|
@@ -42307,7 +43798,8 @@ Registrar / DNS Providers:`);
|
|
|
42307
43798
|
for (const p3 of providers) {
|
|
42308
43799
|
const status = p3.configured ? "\u2713 configured" : "\u2717 not configured";
|
|
42309
43800
|
const type = p3.type === "full" ? "registrar + dns" : p3.type;
|
|
42310
|
-
|
|
43801
|
+
const capabilities = [type, p3.inventory ? "inventory" : null].filter(Boolean).join(", ");
|
|
43802
|
+
console.log(` ${p3.name.padEnd(12)} [${capabilities}] ${status}`);
|
|
42311
43803
|
if (!p3.configured) {
|
|
42312
43804
|
console.log(` Missing: ${p3.envVars.join(", ")}`);
|
|
42313
43805
|
}
|
|
@@ -42315,22 +43807,23 @@ Registrar / DNS Providers:`);
|
|
|
42315
43807
|
console.log();
|
|
42316
43808
|
});
|
|
42317
43809
|
provider.command("test <name>").description("Live-test a provider's credentials").option("-j, --json", "Output JSON").action(async (name, opts) => {
|
|
43810
|
+
const providerName = name.toLowerCase();
|
|
42318
43811
|
const providers = getAvailableProviders();
|
|
42319
|
-
const info = providers.find((p3) => p3.name ===
|
|
43812
|
+
const info = providers.find((p3) => p3.name === providerName);
|
|
42320
43813
|
if (!info) {
|
|
42321
43814
|
const error = `Unknown provider: ${name}`;
|
|
42322
43815
|
if (opts.json) {
|
|
42323
|
-
console.log(JSON.stringify({ provider:
|
|
43816
|
+
console.log(JSON.stringify({ provider: providerName, ok: false, error }, null, 2));
|
|
42324
43817
|
} else {
|
|
42325
43818
|
console.error(error);
|
|
42326
43819
|
}
|
|
42327
43820
|
process.exit(1);
|
|
42328
43821
|
}
|
|
42329
43822
|
if (!info.configured) {
|
|
42330
|
-
const error = `${
|
|
43823
|
+
const error = `${providerName} is not configured. Set: ${info.envVars.join(", ")}`;
|
|
42331
43824
|
if (opts.json) {
|
|
42332
43825
|
console.log(JSON.stringify({
|
|
42333
|
-
provider:
|
|
43826
|
+
provider: providerName,
|
|
42334
43827
|
ok: false,
|
|
42335
43828
|
configured: false,
|
|
42336
43829
|
required_env: info.envVars,
|
|
@@ -42342,33 +43835,70 @@ Registrar / DNS Providers:`);
|
|
|
42342
43835
|
process.exit(1);
|
|
42343
43836
|
}
|
|
42344
43837
|
if (!opts.json) {
|
|
42345
|
-
console.log(`Testing ${
|
|
43838
|
+
console.log(`Testing ${providerName}...`);
|
|
42346
43839
|
}
|
|
42347
43840
|
let registrarOk = null;
|
|
42348
43841
|
let dnsOk = null;
|
|
43842
|
+
let marketplaceOk = null;
|
|
43843
|
+
const notes = [];
|
|
42349
43844
|
try {
|
|
42350
43845
|
if (info.type === "registrar" || info.type === "full") {
|
|
42351
|
-
|
|
42352
|
-
|
|
42353
|
-
|
|
42354
|
-
|
|
42355
|
-
|
|
43846
|
+
if (providerName === "route53") {
|
|
43847
|
+
try {
|
|
43848
|
+
const { listRegisteredDomains: listRegisteredDomains2 } = await Promise.resolve().then(() => (init_route53(), exports_route53));
|
|
43849
|
+
await listRegisteredDomains2();
|
|
43850
|
+
registrarOk = true;
|
|
43851
|
+
if (!opts.json)
|
|
43852
|
+
console.log("\u2713 Registrar connection OK");
|
|
43853
|
+
} catch (error) {
|
|
43854
|
+
if (!isAwsAccessDenied(error))
|
|
43855
|
+
throw error;
|
|
43856
|
+
registrarOk = false;
|
|
43857
|
+
notes.push("route53domains-listdomains-access-denied");
|
|
43858
|
+
if (!opts.json)
|
|
43859
|
+
console.log("\u2022 Route53 Domains registrar API is not available for these AWS credentials");
|
|
43860
|
+
}
|
|
43861
|
+
} else {
|
|
43862
|
+
const reg = getRegistrarProvider(providerName);
|
|
43863
|
+
await reg.listDomains();
|
|
43864
|
+
registrarOk = true;
|
|
43865
|
+
if (!opts.json)
|
|
43866
|
+
console.log("\u2713 Registrar connection OK");
|
|
43867
|
+
}
|
|
42356
43868
|
}
|
|
42357
43869
|
if (info.type === "dns" || info.type === "full") {
|
|
42358
|
-
|
|
42359
|
-
|
|
42360
|
-
|
|
43870
|
+
if (providerName === "brandsight" && registrarOk) {
|
|
43871
|
+
dnsOk = null;
|
|
43872
|
+
notes.push("dns-not-probed-with-synthetic-invalid-domain");
|
|
43873
|
+
if (!opts.json)
|
|
43874
|
+
console.log("\u2022 DNS not probed with synthetic invalid domain");
|
|
43875
|
+
} else {
|
|
43876
|
+
const dns = getDnsProvider(providerName);
|
|
43877
|
+
await dns.getDnsRecords("__test_nonexistent_domain__.invalid");
|
|
43878
|
+
dnsOk = true;
|
|
43879
|
+
if (!opts.json)
|
|
43880
|
+
console.log("\u2713 DNS connection OK");
|
|
43881
|
+
}
|
|
43882
|
+
}
|
|
43883
|
+
if (info.type === "marketplace") {
|
|
43884
|
+
if (providerName !== "sedo")
|
|
43885
|
+
throw new Error("No marketplace tester for provider: " + providerName);
|
|
43886
|
+
const { listSedoPortfolio: listSedoPortfolio2 } = await Promise.resolve().then(() => (init_sedo(), exports_sedo));
|
|
43887
|
+
await listSedoPortfolio2({ limit: 1 });
|
|
43888
|
+
marketplaceOk = true;
|
|
42361
43889
|
if (!opts.json)
|
|
42362
|
-
console.log("\u2713
|
|
43890
|
+
console.log("\u2713 Marketplace connection OK");
|
|
42363
43891
|
}
|
|
42364
43892
|
if (opts.json) {
|
|
42365
43893
|
console.log(JSON.stringify({
|
|
42366
|
-
provider:
|
|
43894
|
+
provider: providerName,
|
|
42367
43895
|
ok: true,
|
|
42368
43896
|
configured: true,
|
|
42369
43897
|
type: info.type,
|
|
42370
43898
|
registrar_ok: registrarOk,
|
|
42371
|
-
dns_ok: dnsOk
|
|
43899
|
+
dns_ok: dnsOk,
|
|
43900
|
+
marketplace_ok: marketplaceOk,
|
|
43901
|
+
...notes.length > 0 ? { notes } : {}
|
|
42372
43902
|
}, null, 2));
|
|
42373
43903
|
}
|
|
42374
43904
|
} catch (e3) {
|
|
@@ -42376,13 +43906,15 @@ Registrar / DNS Providers:`);
|
|
|
42376
43906
|
if (msg.includes("not found") || msg.includes("No hosted zone") || msg.includes("NXDOMAIN")) {
|
|
42377
43907
|
if (opts.json) {
|
|
42378
43908
|
console.log(JSON.stringify({
|
|
42379
|
-
provider:
|
|
43909
|
+
provider: providerName,
|
|
42380
43910
|
ok: true,
|
|
42381
43911
|
configured: true,
|
|
42382
43912
|
type: info.type,
|
|
42383
43913
|
registrar_ok: registrarOk,
|
|
42384
43914
|
dns_ok: true,
|
|
42385
|
-
|
|
43915
|
+
marketplace_ok: marketplaceOk,
|
|
43916
|
+
note: "connection-ok-no-domains-yet",
|
|
43917
|
+
...notes.length > 0 ? { notes } : {}
|
|
42386
43918
|
}, null, 2));
|
|
42387
43919
|
} else {
|
|
42388
43920
|
console.log("\u2713 Connection OK (no domains yet)");
|
|
@@ -42391,16 +43923,17 @@ Registrar / DNS Providers:`);
|
|
|
42391
43923
|
}
|
|
42392
43924
|
if (opts.json) {
|
|
42393
43925
|
console.log(JSON.stringify({
|
|
42394
|
-
provider:
|
|
43926
|
+
provider: providerName,
|
|
42395
43927
|
ok: false,
|
|
42396
43928
|
configured: true,
|
|
42397
43929
|
type: info.type,
|
|
42398
43930
|
registrar_ok: registrarOk,
|
|
42399
43931
|
dns_ok: dnsOk,
|
|
43932
|
+
marketplace_ok: marketplaceOk,
|
|
42400
43933
|
error: msg
|
|
42401
43934
|
}, null, 2));
|
|
42402
43935
|
} else {
|
|
42403
|
-
console.error(`\u2717 ${
|
|
43936
|
+
console.error(`\u2717 ${providerName} test failed: ${msg}`);
|
|
42404
43937
|
}
|
|
42405
43938
|
process.exit(1);
|
|
42406
43939
|
}
|
|
@@ -42410,23 +43943,48 @@ Registrar / DNS Providers:`);
|
|
|
42410
43943
|
// src/cli/commands/providers.ts
|
|
42411
43944
|
init_config();
|
|
42412
43945
|
init_domains();
|
|
43946
|
+
function registrarProviderNames() {
|
|
43947
|
+
return getAvailableProviders().filter((p3) => providerHasRegistrar(p3.name)).map((p3) => p3.name).join(", ");
|
|
43948
|
+
}
|
|
43949
|
+
function inventoryProviderNames() {
|
|
43950
|
+
return getAvailableProviders().filter((p3) => providerHasInventory(p3.name)).map((p3) => p3.name).join(", ");
|
|
43951
|
+
}
|
|
43952
|
+
function requireInventoryProvider(name) {
|
|
43953
|
+
const info = getProviderInfo(name);
|
|
43954
|
+
if (!info) {
|
|
43955
|
+
throw new Error(`Unknown provider: ${name}. Supported domain inventory providers: ${inventoryProviderNames()}`);
|
|
43956
|
+
}
|
|
43957
|
+
if (!providerHasInventory(name)) {
|
|
43958
|
+
throw new Error(`${name} is a ${info.type} provider without domain inventory sync. Supported domain inventory providers: ${inventoryProviderNames()}`);
|
|
43959
|
+
}
|
|
43960
|
+
}
|
|
43961
|
+
function requireRegistrarProvider(name) {
|
|
43962
|
+
const info = getProviderInfo(name);
|
|
43963
|
+
if (!info) {
|
|
43964
|
+
throw new Error(`Unknown provider: ${name}. Supported registrars: ${registrarProviderNames()}`);
|
|
43965
|
+
}
|
|
43966
|
+
if (!providerHasRegistrar(name)) {
|
|
43967
|
+
throw new Error(`${name} is a ${info.type} provider, not a registrar. Supported registrars: ${registrarProviderNames()}`);
|
|
43968
|
+
}
|
|
43969
|
+
}
|
|
42413
43970
|
function registerProviderCommands(program2) {
|
|
42414
|
-
program2.command("providers").description("Show which
|
|
43971
|
+
program2.command("providers").description("Show which providers are configured").option("--json", "Output as JSON", false).action((opts) => {
|
|
42415
43972
|
const providers = getAvailableProviders();
|
|
42416
43973
|
if (opts.json) {
|
|
42417
43974
|
console.log(JSON.stringify(providers, null, 2));
|
|
42418
43975
|
} else {
|
|
42419
|
-
console.log("
|
|
43976
|
+
console.log("Providers:");
|
|
42420
43977
|
for (const p3 of providers) {
|
|
42421
43978
|
const status = p3.configured ? "CONFIGURED" : "not configured";
|
|
42422
|
-
|
|
43979
|
+
const capabilities = [p3.type, p3.inventory ? "inventory" : null].filter(Boolean).join(", ");
|
|
43980
|
+
console.log(` ${p3.name} [${capabilities}]: ${status}`);
|
|
42423
43981
|
if (!p3.configured) {
|
|
42424
|
-
console.log(`
|
|
43982
|
+
console.log(` Accepted env: ${p3.envVars.join(", ")}`);
|
|
42425
43983
|
}
|
|
42426
43984
|
}
|
|
42427
43985
|
}
|
|
42428
43986
|
});
|
|
42429
|
-
program2.command("sync").description("Sync domains from a provider to local DB").option("--provider <provider>", "Provider name
|
|
43987
|
+
program2.command("sync").description("Sync domains from a domain inventory provider to local DB").option("--provider <provider>", "Provider name").option("--all", "Sync from all configured domain inventory providers").option("--json", "Output as JSON", false).action(async (opts) => {
|
|
42430
43988
|
if (opts.all) {
|
|
42431
43989
|
try {
|
|
42432
43990
|
const result = await syncAll({
|
|
@@ -42439,7 +43997,7 @@ function registerProviderCommands(program2) {
|
|
|
42439
43997
|
} else {
|
|
42440
43998
|
console.log(`Synced ${result.totalSynced} domain(s) from ${result.providers.length} provider(s)`);
|
|
42441
43999
|
for (const p3 of result.providers) {
|
|
42442
|
-
console.log(` ${p3.name}: ${p3.result.synced} synced`);
|
|
44000
|
+
console.log(` ${p3.name}: ${p3.result.synced} synced (${p3.result.created} new, ${p3.result.updated} updated)`);
|
|
42443
44001
|
}
|
|
42444
44002
|
if (result.totalErrors.length > 0) {
|
|
42445
44003
|
console.log("Errors:");
|
|
@@ -42459,59 +44017,29 @@ function registerProviderCommands(program2) {
|
|
|
42459
44017
|
console.error("Specify --provider <name> or --all");
|
|
42460
44018
|
process.exit(1);
|
|
42461
44019
|
}
|
|
42462
|
-
|
|
42463
|
-
|
|
42464
|
-
|
|
42465
|
-
|
|
42466
|
-
|
|
42467
|
-
|
|
42468
|
-
|
|
42469
|
-
|
|
42470
|
-
|
|
42471
|
-
|
|
42472
|
-
|
|
42473
|
-
|
|
42474
|
-
|
|
42475
|
-
|
|
42476
|
-
|
|
42477
|
-
console.log("Errors:");
|
|
42478
|
-
for (const e3 of result.errors) {
|
|
42479
|
-
console.log(` - ${e3}`);
|
|
42480
|
-
}
|
|
42481
|
-
}
|
|
42482
|
-
}
|
|
42483
|
-
} catch (error) {
|
|
42484
|
-
console.error(`Sync failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
42485
|
-
process.exit(1);
|
|
42486
|
-
}
|
|
42487
|
-
} else if (provider === "godaddy") {
|
|
42488
|
-
try {
|
|
42489
|
-
const result = await syncToLocalDb2({
|
|
42490
|
-
getDomainByName,
|
|
42491
|
-
createDomain,
|
|
42492
|
-
updateDomain
|
|
42493
|
-
});
|
|
42494
|
-
if (opts.json) {
|
|
42495
|
-
console.log(JSON.stringify(result, null, 2));
|
|
42496
|
-
} else {
|
|
42497
|
-
console.log(`Synced ${result.synced} domain(s) from GoDaddy (created: ${result.created}, updated: ${result.updated})`);
|
|
42498
|
-
if (result.errors.length > 0) {
|
|
42499
|
-
console.log("Errors:");
|
|
42500
|
-
for (const e3 of result.errors) {
|
|
42501
|
-
console.log(` - ${e3}`);
|
|
42502
|
-
}
|
|
42503
|
-
}
|
|
44020
|
+
try {
|
|
44021
|
+
requireInventoryProvider(provider);
|
|
44022
|
+
const result = await getDomainInventoryProvider(provider).syncToLocalDb({
|
|
44023
|
+
getDomainByName,
|
|
44024
|
+
createDomain,
|
|
44025
|
+
updateDomain
|
|
44026
|
+
});
|
|
44027
|
+
if (opts.json) {
|
|
44028
|
+
console.log(JSON.stringify({ provider, ...result }, null, 2));
|
|
44029
|
+
} else {
|
|
44030
|
+
console.log(`Synced ${result.synced} domain(s) from ${provider} (${result.created} new, ${result.updated} updated)`);
|
|
44031
|
+
if (result.errors.length > 0) {
|
|
44032
|
+
console.log("Errors:");
|
|
44033
|
+
for (const e3 of result.errors)
|
|
44034
|
+
console.log(` - ${e3}`);
|
|
42504
44035
|
}
|
|
42505
|
-
} catch (error) {
|
|
42506
|
-
console.error(`Sync failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
42507
|
-
process.exit(1);
|
|
42508
44036
|
}
|
|
42509
|
-
}
|
|
42510
|
-
console.error(`
|
|
44037
|
+
} catch (error) {
|
|
44038
|
+
console.error(`Sync failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
42511
44039
|
process.exit(1);
|
|
42512
44040
|
}
|
|
42513
44041
|
});
|
|
42514
|
-
program2.command("renew").description("Renew a domain via provider (auto-detects registrar from DB if --provider not given)").argument("<name>", "Domain name (e.g. example.com)").option("--provider <provider>", "Provider name
|
|
44042
|
+
program2.command("renew").description("Renew a domain via provider (auto-detects registrar from DB if --provider not given)").argument("<name>", "Domain name (e.g. example.com)").option("--provider <provider>", "Provider name").option("--years <n>", "Number of years to renew", "1").option("--json", "Output as JSON", false).action(async (name, opts) => {
|
|
42515
44043
|
let provider = (opts.provider || "").toLowerCase();
|
|
42516
44044
|
if (!provider) {
|
|
42517
44045
|
const detected = autoDetectRegistrar(name, getDomainByName);
|
|
@@ -42520,53 +44048,44 @@ function registerProviderCommands(program2) {
|
|
|
42520
44048
|
process.exit(1);
|
|
42521
44049
|
}
|
|
42522
44050
|
provider = detected;
|
|
42523
|
-
|
|
44051
|
+
if (!opts.json)
|
|
44052
|
+
console.log(`Auto-detected registrar: ${provider}`);
|
|
42524
44053
|
}
|
|
42525
|
-
|
|
42526
|
-
|
|
42527
|
-
|
|
42528
|
-
|
|
42529
|
-
|
|
42530
|
-
} else {
|
|
42531
|
-
console.log(`Renewed ${result.domain} successfully via Namecheap`);
|
|
42532
|
-
if (result.chargedAmount)
|
|
42533
|
-
console.log(` Charged: $${result.chargedAmount}`);
|
|
42534
|
-
if (result.orderId)
|
|
42535
|
-
console.log(` Order ID: ${result.orderId}`);
|
|
42536
|
-
}
|
|
42537
|
-
} catch (error) {
|
|
42538
|
-
console.error(`Renewal failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
42539
|
-
process.exit(1);
|
|
44054
|
+
try {
|
|
44055
|
+
requireRegistrarProvider(provider);
|
|
44056
|
+
const result = await getRegistrarProvider(provider).renewDomain(name, parseInt(opts.years, 10));
|
|
44057
|
+
if (!result.success) {
|
|
44058
|
+
throw new Error(`Renewal failed or is not supported for ${provider}`);
|
|
42540
44059
|
}
|
|
42541
|
-
|
|
42542
|
-
|
|
42543
|
-
|
|
42544
|
-
|
|
42545
|
-
|
|
42546
|
-
|
|
42547
|
-
|
|
42548
|
-
|
|
42549
|
-
console.log(` Order ID: ${result.orderId}`);
|
|
42550
|
-
if (result.total)
|
|
42551
|
-
console.log(` Total: $${(result.total / 100).toFixed(2)}`);
|
|
42552
|
-
}
|
|
42553
|
-
} catch (error) {
|
|
42554
|
-
console.error(`Renewal failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
42555
|
-
process.exit(1);
|
|
44060
|
+
if (opts.json) {
|
|
44061
|
+
console.log(JSON.stringify({ provider, ...result }, null, 2));
|
|
44062
|
+
} else {
|
|
44063
|
+
console.log(`Renewed ${result.domain} successfully via ${provider}`);
|
|
44064
|
+
if (result.chargedAmount)
|
|
44065
|
+
console.log(` Charged: $${result.chargedAmount}`);
|
|
44066
|
+
if (result.orderId)
|
|
44067
|
+
console.log(` Order ID: ${result.orderId}`);
|
|
42556
44068
|
}
|
|
42557
|
-
}
|
|
42558
|
-
console.error(`
|
|
44069
|
+
} catch (error) {
|
|
44070
|
+
console.error(`Renewal failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
42559
44071
|
process.exit(1);
|
|
42560
44072
|
}
|
|
42561
44073
|
});
|
|
42562
|
-
program2.command("check").description("Check domain availability via a registrar provider
|
|
44074
|
+
program2.command("check").description("Check domain availability via a registrar provider").argument("<name>", "Domain name (e.g. example.com)").option("--provider <name>", "Registrar provider \u2014 defaults to config default-registrar, else route53").option("--json", "Output as JSON", false).action(async (name, opts) => {
|
|
42563
44075
|
try {
|
|
42564
|
-
const providerName = opts.provider ?? loadConfig3().default_registrar ?? "
|
|
42565
|
-
|
|
44076
|
+
const providerName = (opts.provider ?? loadConfig3().default_registrar ?? "route53").toLowerCase();
|
|
44077
|
+
requireRegistrarProvider(providerName);
|
|
44078
|
+
const result = await getRegistrarProvider(providerName).checkAvailability(name);
|
|
42566
44079
|
if (opts.json) {
|
|
42567
44080
|
console.log(JSON.stringify({ provider: providerName, ...result }, null, 2));
|
|
42568
44081
|
} else {
|
|
42569
44082
|
console.log(`${result.domain} is ${result.available ? "AVAILABLE" : "NOT available"} (via ${providerName})`);
|
|
44083
|
+
if (result.is_premium)
|
|
44084
|
+
console.log(` Premium ask: ${result.premium_price ?? "unknown"}`);
|
|
44085
|
+
if (result.standard_price !== undefined) {
|
|
44086
|
+
const currency = result.currency ? ` ${result.currency}` : "";
|
|
44087
|
+
console.log(` Standard price: ${result.standard_price}${currency}`);
|
|
44088
|
+
}
|
|
42570
44089
|
}
|
|
42571
44090
|
} catch (error) {
|
|
42572
44091
|
console.error(`Availability check failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -42580,6 +44099,7 @@ init_config();
|
|
|
42580
44099
|
var VALID_KEYS = [
|
|
42581
44100
|
"default-registrar",
|
|
42582
44101
|
"default-dns",
|
|
44102
|
+
"purchase-aws-profile",
|
|
42583
44103
|
"contact.first-name",
|
|
42584
44104
|
"contact.last-name",
|
|
42585
44105
|
"contact.email",
|
|
@@ -42601,8 +44121,9 @@ function registerConfigCommands(program2) {
|
|
|
42601
44121
|
}
|
|
42602
44122
|
console.log(`
|
|
42603
44123
|
Configuration (~/.hasna/domains/config.json):`);
|
|
42604
|
-
console.log(` default-registrar:
|
|
42605
|
-
console.log(` default-dns:
|
|
44124
|
+
console.log(` default-registrar: ${cfg.default_registrar ?? "(not set)"}`);
|
|
44125
|
+
console.log(` default-dns: ${cfg.default_dns ?? "(not set)"}`);
|
|
44126
|
+
console.log(` purchase-aws-profile: ${cfg.purchase_aws_profile ?? "(not set)"}`);
|
|
42606
44127
|
if (cfg.contact && Object.keys(cfg.contact).length > 0) {
|
|
42607
44128
|
console.log(`
|
|
42608
44129
|
contact:`);
|
|
@@ -42703,7 +44224,7 @@ function registerDoctorCommand(program2) {
|
|
|
42703
44224
|
fail(`${c3.provider}: not configured (${c3.detail})`);
|
|
42704
44225
|
}
|
|
42705
44226
|
section("Providers");
|
|
42706
|
-
const providers = getAvailableProviders().filter((p3) => p3.
|
|
44227
|
+
const providers = getAvailableProviders().filter((p3) => p3.type !== "marketplace");
|
|
42707
44228
|
for (const p3 of providers) {
|
|
42708
44229
|
if (!p3.configured) {
|
|
42709
44230
|
fail(`${p3.name}: not configured`, `Set: ${p3.envVars.join(", ")}`);
|
|
@@ -42753,15 +44274,15 @@ ${"\u2500".repeat(45)}`);
|
|
|
42753
44274
|
}
|
|
42754
44275
|
|
|
42755
44276
|
// src/cli/commands/mcp-install.ts
|
|
42756
|
-
import { existsSync as
|
|
42757
|
-
import { homedir as
|
|
42758
|
-
import { dirname as dirname4, join as
|
|
44277
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
44278
|
+
import { homedir as homedir5 } from "os";
|
|
44279
|
+
import { dirname as dirname4, join as join5 } from "path";
|
|
42759
44280
|
import { execSync as execSync3 } from "child_process";
|
|
42760
44281
|
var MCP_SERVER_NAME = "domains";
|
|
42761
44282
|
function getClaudeConfigPaths() {
|
|
42762
44283
|
return {
|
|
42763
|
-
global:
|
|
42764
|
-
project:
|
|
44284
|
+
global: join5(homedir5(), ".claude", "claude_desktop_config.json"),
|
|
44285
|
+
project: join5(process.cwd(), ".claude", "settings.json")
|
|
42765
44286
|
};
|
|
42766
44287
|
}
|
|
42767
44288
|
function getMcpBinaryPath() {
|
|
@@ -42773,12 +44294,12 @@ function getMcpBinaryPath() {
|
|
|
42773
44294
|
}
|
|
42774
44295
|
function ensureConfigDir(configPath2) {
|
|
42775
44296
|
const dir = dirname4(configPath2);
|
|
42776
|
-
if (!
|
|
44297
|
+
if (!existsSync4(dir)) {
|
|
42777
44298
|
mkdirSync3(dir, { recursive: true });
|
|
42778
44299
|
}
|
|
42779
44300
|
}
|
|
42780
44301
|
function readConfig(configPath2) {
|
|
42781
|
-
if (!
|
|
44302
|
+
if (!existsSync4(configPath2))
|
|
42782
44303
|
return {};
|
|
42783
44304
|
try {
|
|
42784
44305
|
return JSON.parse(readFileSync5(configPath2, "utf-8"));
|
|
@@ -42807,7 +44328,7 @@ function registerMcpCommand(program2) {
|
|
|
42807
44328
|
mcp.command("uninstall").description("Remove domains MCP server from Claude Code config").option("--project", "Remove from project config instead of global").action((opts) => {
|
|
42808
44329
|
const paths = getClaudeConfigPaths();
|
|
42809
44330
|
const configPath2 = opts.project ? paths.project : paths.global;
|
|
42810
|
-
if (!
|
|
44331
|
+
if (!existsSync4(configPath2)) {
|
|
42811
44332
|
console.log("Config file not found \u2014 nothing to remove.");
|
|
42812
44333
|
return;
|
|
42813
44334
|
}
|
|
@@ -42825,7 +44346,7 @@ function registerMcpCommand(program2) {
|
|
|
42825
44346
|
const paths = getClaudeConfigPaths();
|
|
42826
44347
|
const status = [];
|
|
42827
44348
|
for (const [scope, configPath2] of [["global", paths.global], ["project", paths.project]]) {
|
|
42828
|
-
if (!
|
|
44349
|
+
if (!existsSync4(configPath2)) {
|
|
42829
44350
|
status.push({ scope, config_path: configPath2, exists: false, registered: false });
|
|
42830
44351
|
continue;
|
|
42831
44352
|
}
|
|
@@ -43049,7 +44570,7 @@ function registerRoute53Commands(program2) {
|
|
|
43049
44570
|
process.exit(1);
|
|
43050
44571
|
}
|
|
43051
44572
|
console.log(`Registering ${domain}...`);
|
|
43052
|
-
const result = await
|
|
44573
|
+
const result = await registerDomain2(domain, contact, parseInt(opts.years), opts.autoRenew);
|
|
43053
44574
|
console.log(`\u2713 Registration submitted: ${domain}`);
|
|
43054
44575
|
console.log(` Operation ID: ${result.operationId}`);
|
|
43055
44576
|
console.log(` Check status: domains r53 status ${result.operationId}`);
|
|
@@ -43346,7 +44867,7 @@ DNS Records for ${domain}:`);
|
|
|
43346
44867
|
console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
|
|
43347
44868
|
process.exit(1);
|
|
43348
44869
|
}
|
|
43349
|
-
const reg = await
|
|
44870
|
+
const reg = await registerDomain2(domain, contact, parseInt(opts.years));
|
|
43350
44871
|
console.log(` \u2713 Submitted (operation: ${reg.operationId})`);
|
|
43351
44872
|
console.log(` Waiting for registration to complete...`);
|
|
43352
44873
|
let status = "IN_PROGRESS";
|
|
@@ -43377,7 +44898,7 @@ DNS Records for ${domain}:`);
|
|
|
43377
44898
|
},
|
|
43378
44899
|
getRegistrarNs: async (d3) => (await getDomainDetail(d3)).nameservers,
|
|
43379
44900
|
setRegistrarNs: async (d3, ns) => {
|
|
43380
|
-
await
|
|
44901
|
+
await updateNameservers2(d3, ns);
|
|
43381
44902
|
}
|
|
43382
44903
|
});
|
|
43383
44904
|
console.log(` \u2713 Zone ${setup.created ? "created" : "reused"} (${setup.zoneId})` + (setup.nsUpdated ? ` \u2014 registry NS repointed to this zone` : ``));
|
|
@@ -45554,10 +47075,10 @@ function registerInteractiveCommand(program2) {
|
|
|
45554
47075
|
});
|
|
45555
47076
|
}
|
|
45556
47077
|
|
|
45557
|
-
// src/cli/commands/
|
|
47078
|
+
// src/cli/commands/storage.ts
|
|
45558
47079
|
import chalk2 from "chalk";
|
|
45559
47080
|
|
|
45560
|
-
// src/db/
|
|
47081
|
+
// src/db/storage-sync.ts
|
|
45561
47082
|
init_database();
|
|
45562
47083
|
|
|
45563
47084
|
// src/db/pg-migrations.ts
|
|
@@ -45734,8 +47255,8 @@ class PgAdapterAsync {
|
|
|
45734
47255
|
}
|
|
45735
47256
|
}
|
|
45736
47257
|
|
|
45737
|
-
// src/db/
|
|
45738
|
-
var
|
|
47258
|
+
// src/db/storage-sync.ts
|
|
47259
|
+
var STORAGE_TABLES = [
|
|
45739
47260
|
"domains",
|
|
45740
47261
|
"dns_records",
|
|
45741
47262
|
"alerts",
|
|
@@ -45755,25 +47276,50 @@ var PRIMARY_KEYS = {
|
|
|
45755
47276
|
domain_history: ["id"],
|
|
45756
47277
|
domain_reputation: ["id"]
|
|
45757
47278
|
};
|
|
45758
|
-
|
|
45759
|
-
|
|
47279
|
+
var DOMAINS_STORAGE_ENV = "HASNA_DOMAINS_DATABASE_URL";
|
|
47280
|
+
var DOMAINS_STORAGE_FALLBACK_ENV = "DOMAINS_DATABASE_URL";
|
|
47281
|
+
var DOMAINS_STORAGE_MODE_ENV = "HASNA_DOMAINS_STORAGE_MODE";
|
|
47282
|
+
var DOMAINS_STORAGE_MODE_FALLBACK_ENV = "DOMAINS_STORAGE_MODE";
|
|
47283
|
+
var STORAGE_DATABASE_ENV = [DOMAINS_STORAGE_ENV, DOMAINS_STORAGE_FALLBACK_ENV];
|
|
47284
|
+
var STORAGE_MODE_ENV = [DOMAINS_STORAGE_MODE_ENV, DOMAINS_STORAGE_MODE_FALLBACK_ENV];
|
|
47285
|
+
function firstEnv2(names) {
|
|
47286
|
+
for (const name of names) {
|
|
47287
|
+
const value = process.env[name];
|
|
47288
|
+
if (value)
|
|
47289
|
+
return value;
|
|
47290
|
+
}
|
|
47291
|
+
return null;
|
|
47292
|
+
}
|
|
47293
|
+
function normalizeStorageMode(value) {
|
|
47294
|
+
if (!value)
|
|
47295
|
+
return null;
|
|
47296
|
+
const normalized = value.trim().toLowerCase();
|
|
47297
|
+
if (normalized === "local" || normalized === "remote" || normalized === "hybrid")
|
|
47298
|
+
return normalized;
|
|
47299
|
+
return null;
|
|
45760
47300
|
}
|
|
45761
|
-
|
|
45762
|
-
|
|
47301
|
+
function getStorageDatabaseUrl() {
|
|
47302
|
+
return firstEnv2(STORAGE_DATABASE_ENV);
|
|
47303
|
+
}
|
|
47304
|
+
function getStorageMode() {
|
|
47305
|
+
return normalizeStorageMode(firstEnv2(STORAGE_MODE_ENV)) ?? (getStorageDatabaseUrl() ? "remote" : "local");
|
|
47306
|
+
}
|
|
47307
|
+
async function getStoragePg() {
|
|
47308
|
+
const url = getStorageDatabaseUrl();
|
|
45763
47309
|
if (!url) {
|
|
45764
|
-
throw new Error("Missing
|
|
47310
|
+
throw new Error("Missing HASNA_DOMAINS_DATABASE_URL or DOMAINS_DATABASE_URL");
|
|
45765
47311
|
}
|
|
45766
47312
|
return new PgAdapterAsync(url);
|
|
45767
47313
|
}
|
|
45768
|
-
async function
|
|
47314
|
+
async function runStorageMigrations(remote) {
|
|
45769
47315
|
for (const sql of PG_MIGRATIONS)
|
|
45770
47316
|
await remote.run(sql);
|
|
45771
47317
|
}
|
|
45772
|
-
async function
|
|
45773
|
-
const remote = await
|
|
47318
|
+
async function storagePush(options) {
|
|
47319
|
+
const remote = await getStoragePg();
|
|
45774
47320
|
const db = getDatabase();
|
|
45775
47321
|
try {
|
|
45776
|
-
await
|
|
47322
|
+
await runStorageMigrations(remote);
|
|
45777
47323
|
const results = [];
|
|
45778
47324
|
for (const table of resolveTables(options?.tables))
|
|
45779
47325
|
results.push(await pushTable(db, remote, table));
|
|
@@ -45783,11 +47329,11 @@ async function cloudPush(options) {
|
|
|
45783
47329
|
await remote.close();
|
|
45784
47330
|
}
|
|
45785
47331
|
}
|
|
45786
|
-
async function
|
|
45787
|
-
const remote = await
|
|
47332
|
+
async function storagePull(options) {
|
|
47333
|
+
const remote = await getStoragePg();
|
|
45788
47334
|
const db = getDatabase();
|
|
45789
47335
|
try {
|
|
45790
|
-
await
|
|
47336
|
+
await runStorageMigrations(remote);
|
|
45791
47337
|
const results = [];
|
|
45792
47338
|
for (const table of resolveTables(options?.tables))
|
|
45793
47339
|
results.push(await pullTable(remote, db, table));
|
|
@@ -45797,20 +47343,30 @@ async function cloudPull(options) {
|
|
|
45797
47343
|
await remote.close();
|
|
45798
47344
|
}
|
|
45799
47345
|
}
|
|
45800
|
-
async function
|
|
45801
|
-
const pull = await
|
|
45802
|
-
const push = await
|
|
47346
|
+
async function storageSync(options) {
|
|
47347
|
+
const pull = await storagePull(options);
|
|
47348
|
+
const push = await storagePush(options);
|
|
45803
47349
|
return { pull, push };
|
|
45804
47350
|
}
|
|
45805
|
-
function
|
|
47351
|
+
function getStorageSyncMetaAll() {
|
|
45806
47352
|
const db = getDatabase();
|
|
45807
47353
|
ensureSyncMetaTable(db);
|
|
45808
47354
|
return db.query("SELECT table_name, last_synced_at, direction FROM _domains_sync_meta ORDER BY table_name, direction").all();
|
|
45809
47355
|
}
|
|
47356
|
+
function getStorageStatus() {
|
|
47357
|
+
return {
|
|
47358
|
+
configured: Boolean(getStorageDatabaseUrl()),
|
|
47359
|
+
env: STORAGE_DATABASE_ENV,
|
|
47360
|
+
mode: getStorageMode(),
|
|
47361
|
+
service: "domains",
|
|
47362
|
+
tables: STORAGE_TABLES,
|
|
47363
|
+
sync: getStorageSyncMetaAll()
|
|
47364
|
+
};
|
|
47365
|
+
}
|
|
45810
47366
|
function resolveTables(tables) {
|
|
45811
47367
|
if (!tables || tables.length === 0)
|
|
45812
|
-
return [...
|
|
45813
|
-
const allowed = new Set(
|
|
47368
|
+
return [...STORAGE_TABLES];
|
|
47369
|
+
const allowed = new Set(STORAGE_TABLES);
|
|
45814
47370
|
const requested = tables.map((table) => table.trim()).filter(Boolean);
|
|
45815
47371
|
const invalid = requested.filter((table) => !allowed.has(table));
|
|
45816
47372
|
if (invalid.length > 0)
|
|
@@ -45946,12 +47502,7 @@ function coerceForSqlite(value) {
|
|
|
45946
47502
|
return String(value);
|
|
45947
47503
|
}
|
|
45948
47504
|
|
|
45949
|
-
// src/cli/commands/
|
|
45950
|
-
var CLOUD_ENV = [
|
|
45951
|
-
"HASNA_DOMAINS_CLOUD_DATABASE_URL",
|
|
45952
|
-
"OPEN_DOMAINS_CLOUD_DATABASE_URL",
|
|
45953
|
-
"DOMAINS_CLOUD_DATABASE_URL"
|
|
45954
|
-
];
|
|
47505
|
+
// src/cli/commands/storage.ts
|
|
45955
47506
|
function parseTables(value) {
|
|
45956
47507
|
if (!value)
|
|
45957
47508
|
return;
|
|
@@ -45968,21 +47519,15 @@ function printResults(results, label) {
|
|
|
45968
47519
|
}
|
|
45969
47520
|
console.log(`Done. ${total} rows ${label}.`);
|
|
45970
47521
|
}
|
|
45971
|
-
function
|
|
45972
|
-
|
|
45973
|
-
|
|
45974
|
-
const info = {
|
|
45975
|
-
configured: Boolean(getCloudDatabaseUrl()),
|
|
45976
|
-
env: CLOUD_ENV,
|
|
45977
|
-
service: "domains",
|
|
45978
|
-
tables: CLOUD_TABLES,
|
|
45979
|
-
sync: getSyncMetaAll()
|
|
45980
|
-
};
|
|
47522
|
+
function installStorageSubcommands(storage) {
|
|
47523
|
+
storage.command("status").description("Show remote storage config and local sync state").option("--json", "Output as JSON").action((opts) => {
|
|
47524
|
+
const info = getStorageStatus();
|
|
45981
47525
|
if (opts.json) {
|
|
45982
47526
|
printJson(info);
|
|
45983
47527
|
return;
|
|
45984
47528
|
}
|
|
45985
|
-
console.log(`
|
|
47529
|
+
console.log(`Storage mode: ${info.mode}`);
|
|
47530
|
+
console.log(`Remote storage configured: ${info.configured ? "yes" : "no"}`);
|
|
45986
47531
|
console.log(`Tables: ${info.tables.join(", ")}`);
|
|
45987
47532
|
if (info.sync.length === 0)
|
|
45988
47533
|
console.log("Sync: no local sync history");
|
|
@@ -45990,9 +47535,9 @@ function registerCloudCommand(program2) {
|
|
|
45990
47535
|
console.log(` ${entry.table_name} ${entry.direction}: ${entry.last_synced_at ?? "never"}`);
|
|
45991
47536
|
}
|
|
45992
47537
|
});
|
|
45993
|
-
|
|
47538
|
+
storage.command("push").description("Push local domains data to remote PostgreSQL storage").option("--tables <tables>", "Comma-separated table names").option("--json", "Output as JSON").action(async (opts) => {
|
|
45994
47539
|
try {
|
|
45995
|
-
const results = await
|
|
47540
|
+
const results = await storagePush({ tables: parseTables(opts.tables) });
|
|
45996
47541
|
if (opts.json) {
|
|
45997
47542
|
printJson(results);
|
|
45998
47543
|
return;
|
|
@@ -46003,9 +47548,9 @@ function registerCloudCommand(program2) {
|
|
|
46003
47548
|
process.exit(1);
|
|
46004
47549
|
}
|
|
46005
47550
|
});
|
|
46006
|
-
|
|
47551
|
+
storage.command("pull").description("Pull domains data from remote PostgreSQL storage to local SQLite").option("--tables <tables>", "Comma-separated table names").option("--json", "Output as JSON").action(async (opts) => {
|
|
46007
47552
|
try {
|
|
46008
|
-
const results = await
|
|
47553
|
+
const results = await storagePull({ tables: parseTables(opts.tables) });
|
|
46009
47554
|
if (opts.json) {
|
|
46010
47555
|
printJson(results);
|
|
46011
47556
|
return;
|
|
@@ -46016,9 +47561,9 @@ function registerCloudCommand(program2) {
|
|
|
46016
47561
|
process.exit(1);
|
|
46017
47562
|
}
|
|
46018
47563
|
});
|
|
46019
|
-
|
|
47564
|
+
storage.command("sync").description("Bidirectional sync: pull then push").option("--tables <tables>", "Comma-separated table names").option("--json", "Output as JSON").action(async (opts) => {
|
|
46020
47565
|
try {
|
|
46021
|
-
const result = await
|
|
47566
|
+
const result = await storageSync({ tables: parseTables(opts.tables) });
|
|
46022
47567
|
if (opts.json) {
|
|
46023
47568
|
printJson(result);
|
|
46024
47569
|
return;
|
|
@@ -46031,6 +47576,9 @@ function registerCloudCommand(program2) {
|
|
|
46031
47576
|
}
|
|
46032
47577
|
});
|
|
46033
47578
|
}
|
|
47579
|
+
function registerStorageCommand(program2) {
|
|
47580
|
+
installStorageSubcommands(program2.command("storage").description("Manage domains local/remote storage sync"));
|
|
47581
|
+
}
|
|
46034
47582
|
|
|
46035
47583
|
// src/cli/index.ts
|
|
46036
47584
|
init_version();
|
|
@@ -46056,6 +47604,7 @@ registerOutreachCommand(program2);
|
|
|
46056
47604
|
registerSedoCommand(program2);
|
|
46057
47605
|
registerWalletCommand(program2);
|
|
46058
47606
|
registerProvisionCommand(program2);
|
|
46059
|
-
|
|
47607
|
+
registerStorageCommand(program2);
|
|
47608
|
+
registerEventsCommands(program2, { source: "domains" });
|
|
46060
47609
|
registerInteractiveCommand(program2);
|
|
46061
47610
|
program2.parse(process.argv);
|