@hasna/domains 0.0.20 → 0.0.21
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 +139 -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 +1525 -448
- 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 +348 -58
- package/dist/lib/brandsight.d.ts.map +1 -1
- package/dist/lib/capability.d.ts.map +1 -1
- package/dist/lib/cloudflare-auth.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/namecheap.d.ts +13 -0
- package/dist/lib/namecheap.d.ts.map +1 -1
- package/dist/lib/registrar.d.ts +36 -2
- package/dist/lib/registrar.d.ts.map +1 -1
- 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 +16721 -18634
- 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 +8 -2
- 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; availability checks may work, but purchase + DNS writes require a qualifying account (>=10 domains / DDC). Prefer Route53." : "Not configured; and retail Domains API is gated for purchase/DNS since 2024."
|
|
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;
|
|
@@ -34334,7 +34474,7 @@ async function checkAvailability3(domain, config) {
|
|
|
34334
34474
|
} catch {}
|
|
34335
34475
|
return availability;
|
|
34336
34476
|
}
|
|
34337
|
-
async function
|
|
34477
|
+
async function registerDomain2(domain, contact, durationYears = 1, autoRenew = true, config) {
|
|
34338
34478
|
const { domains } = makeClients(config);
|
|
34339
34479
|
const contactDetail = {
|
|
34340
34480
|
FirstName: contact.first_name,
|
|
@@ -34383,7 +34523,7 @@ async function getDomainDetail(domain, config) {
|
|
|
34383
34523
|
nameservers: (result.Nameservers ?? []).map((ns) => ns.Name ?? "").filter(Boolean)
|
|
34384
34524
|
};
|
|
34385
34525
|
}
|
|
34386
|
-
async function
|
|
34526
|
+
async function updateNameservers2(domain, nameservers, config, client) {
|
|
34387
34527
|
if (!nameservers.length) {
|
|
34388
34528
|
throw new Error("updateNameservers requires at least one nameserver");
|
|
34389
34529
|
}
|
|
@@ -34568,6 +34708,8 @@ async function upsertRecords(hostedZoneId, records, config) {
|
|
|
34568
34708
|
}
|
|
34569
34709
|
function createRoute53Provider(config) {
|
|
34570
34710
|
const cfg = config ?? getConfig2();
|
|
34711
|
+
const registerWithRoute53 = registerDomain2;
|
|
34712
|
+
const updateRoute53Nameservers = updateNameservers2;
|
|
34571
34713
|
return {
|
|
34572
34714
|
name: "route53",
|
|
34573
34715
|
async listDomains() {
|
|
@@ -34594,6 +34736,14 @@ function createRoute53Provider(config) {
|
|
|
34594
34736
|
auto_renew: detail.auto_renew
|
|
34595
34737
|
};
|
|
34596
34738
|
},
|
|
34739
|
+
async registerDomain(domain, contact, options = {}) {
|
|
34740
|
+
const result = await registerWithRoute53(domain, contact, options.years ?? 1, options.autoRenew ?? true, cfg);
|
|
34741
|
+
return { domain, success: !!result.operationId, operationId: result.operationId };
|
|
34742
|
+
},
|
|
34743
|
+
async updateNameservers(domain, nameservers) {
|
|
34744
|
+
const result = await updateRoute53Nameservers(domain, nameservers, cfg);
|
|
34745
|
+
return { domain, success: !!result.operationId, operationId: result.operationId };
|
|
34746
|
+
},
|
|
34597
34747
|
async renewDomain(_domain) {
|
|
34598
34748
|
return { domain: _domain, success: false, orderId: undefined, chargedAmount: undefined };
|
|
34599
34749
|
},
|
|
@@ -34681,15 +34831,132 @@ var init_route53 = __esm(() => {
|
|
|
34681
34831
|
init_dist_es14();
|
|
34682
34832
|
});
|
|
34683
34833
|
|
|
34684
|
-
// src/lib/
|
|
34685
|
-
function
|
|
34686
|
-
const
|
|
34687
|
-
|
|
34688
|
-
|
|
34834
|
+
// src/lib/env-aliases.ts
|
|
34835
|
+
function firstEnv(env, names) {
|
|
34836
|
+
for (const key of names) {
|
|
34837
|
+
const value = env[key];
|
|
34838
|
+
if (value)
|
|
34839
|
+
return { key, value };
|
|
34689
34840
|
}
|
|
34690
|
-
|
|
34691
|
-
|
|
34841
|
+
return;
|
|
34842
|
+
}
|
|
34843
|
+
function hasEveryEnv(env, groups) {
|
|
34844
|
+
return groups.every((names) => !!firstEnv(env, names));
|
|
34845
|
+
}
|
|
34846
|
+
function flattenEnvNames(groups) {
|
|
34847
|
+
return Array.from(new Set(groups.flatMap((names) => [...names])));
|
|
34848
|
+
}
|
|
34849
|
+
function providerEnvNames(provider) {
|
|
34850
|
+
switch (provider.toLowerCase()) {
|
|
34851
|
+
case "namecheap":
|
|
34852
|
+
return flattenEnvNames([NAMECHEAP_ENV.apiKey, NAMECHEAP_ENV.username, NAMECHEAP_ENV.clientIp]);
|
|
34853
|
+
case "godaddy":
|
|
34854
|
+
return flattenEnvNames([GODADDY_ENV.apiKey, GODADDY_ENV.apiSecret]);
|
|
34855
|
+
case "brandsight":
|
|
34856
|
+
return flattenEnvNames([
|
|
34857
|
+
BRANDSIGHT_ENV.apiKey,
|
|
34858
|
+
BRANDSIGHT_ENV.apiSecret,
|
|
34859
|
+
BRANDSIGHT_ENV.customerId,
|
|
34860
|
+
BRANDSIGHT_ENV.shopperId,
|
|
34861
|
+
BRANDSIGHT_ENV.accountId
|
|
34862
|
+
]);
|
|
34863
|
+
case "sedo":
|
|
34864
|
+
return flattenEnvNames([SEDO_ENV.partnerId, SEDO_ENV.signKey, SEDO_ENV.username, SEDO_ENV.password]);
|
|
34865
|
+
case "cloudflare":
|
|
34866
|
+
return flattenEnvNames([CLOUDFLARE_ENV.apiToken, CLOUDFLARE_ENV.apiKey, CLOUDFLARE_ENV.email, CLOUDFLARE_ENV.accountId]);
|
|
34867
|
+
case "route53":
|
|
34868
|
+
return ["AWS_PROFILE", "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_REGION"];
|
|
34869
|
+
case "aws:beepmedia":
|
|
34870
|
+
return flattenEnvNames([BEEPMEDIA_AWS_ENV.accessKeyId, BEEPMEDIA_AWS_ENV.secretAccessKey]);
|
|
34871
|
+
default:
|
|
34872
|
+
return [];
|
|
34873
|
+
}
|
|
34874
|
+
}
|
|
34875
|
+
function hasProviderCredentials(provider, env = process.env) {
|
|
34876
|
+
switch (provider.toLowerCase()) {
|
|
34877
|
+
case "namecheap":
|
|
34878
|
+
return hasEveryEnv(env, [NAMECHEAP_ENV.apiKey, NAMECHEAP_ENV.username, NAMECHEAP_ENV.clientIp]);
|
|
34879
|
+
case "godaddy":
|
|
34880
|
+
return hasEveryEnv(env, [GODADDY_ENV.apiKey, GODADDY_ENV.apiSecret]);
|
|
34881
|
+
case "brandsight":
|
|
34882
|
+
return hasEveryEnv(env, [BRANDSIGHT_ENV.apiKey, BRANDSIGHT_ENV.apiSecret, BRANDSIGHT_ENV.customerId]);
|
|
34883
|
+
case "sedo":
|
|
34884
|
+
return hasEveryEnv(env, [SEDO_ENV.partnerId, SEDO_ENV.signKey, SEDO_ENV.username, SEDO_ENV.password]);
|
|
34885
|
+
case "cloudflare": {
|
|
34886
|
+
const tokenMode = !!firstEnv(env, CLOUDFLARE_ENV.apiToken);
|
|
34887
|
+
const keyMode = hasEveryEnv(env, [CLOUDFLARE_ENV.apiKey, CLOUDFLARE_ENV.email]);
|
|
34888
|
+
return tokenMode || keyMode;
|
|
34889
|
+
}
|
|
34890
|
+
case "route53":
|
|
34891
|
+
return !!env["AWS_PROFILE"] || hasEveryEnv(env, [["AWS_ACCESS_KEY_ID"], ["AWS_SECRET_ACCESS_KEY"]]);
|
|
34892
|
+
case "aws:beepmedia":
|
|
34893
|
+
return hasEveryEnv(env, [BEEPMEDIA_AWS_ENV.accessKeyId, BEEPMEDIA_AWS_ENV.secretAccessKey]);
|
|
34894
|
+
default:
|
|
34895
|
+
return false;
|
|
34692
34896
|
}
|
|
34897
|
+
}
|
|
34898
|
+
var NAMECHEAP_ENV, GODADDY_ENV, BRANDSIGHT_ENV, SEDO_ENV, CLOUDFLARE_ENV, BEEPMEDIA_AWS_ENV;
|
|
34899
|
+
var init_env_aliases = __esm(() => {
|
|
34900
|
+
NAMECHEAP_ENV = {
|
|
34901
|
+
apiKey: ["NAMECHEAP_API_KEY"],
|
|
34902
|
+
username: ["NAMECHEAP_USERNAME"],
|
|
34903
|
+
clientIp: ["NAMECHEAP_CLIENT_IP"]
|
|
34904
|
+
};
|
|
34905
|
+
GODADDY_ENV = {
|
|
34906
|
+
apiKey: ["GODADDY_API_KEY"],
|
|
34907
|
+
apiSecret: ["GODADDY_API_SECRET"]
|
|
34908
|
+
};
|
|
34909
|
+
BRANDSIGHT_ENV = {
|
|
34910
|
+
apiKey: [
|
|
34911
|
+
"BRANDSIGHT_API_KEY",
|
|
34912
|
+
"HASNAXYZ_BRANDSIGHT_LIVE_API_KEY",
|
|
34913
|
+
"HASNAXYZ_BRANDSIGHT_SANDBOX_API_KEY"
|
|
34914
|
+
],
|
|
34915
|
+
apiSecret: [
|
|
34916
|
+
"BRANDSIGHT_API_SECRET",
|
|
34917
|
+
"HASNAXYZ_BRANDSIGHT_LIVE_API_SECRET",
|
|
34918
|
+
"HASNAXYZ_BRANDSIGHT_SANDBOX_API_SECRET"
|
|
34919
|
+
],
|
|
34920
|
+
customerId: [
|
|
34921
|
+
"BRANDSIGHT_CUSTOMER_ID",
|
|
34922
|
+
"HASNAXYZ_BRANDSIGHT_LIVE_CUSTOMER_ID",
|
|
34923
|
+
"HASNAXYZ_BRANDSIGHT_SANDBOX_CUSTOMER_ID"
|
|
34924
|
+
],
|
|
34925
|
+
shopperId: [
|
|
34926
|
+
"BRANDSIGHT_SHOPPER_ID",
|
|
34927
|
+
"HASNAXYZ_BRANDSIGHT_LIVE_SHOPPER_ID",
|
|
34928
|
+
"HASNAXYZ_BRANDSIGHT_SANDBOX_SHOPPER_ID"
|
|
34929
|
+
],
|
|
34930
|
+
accountId: ["BRANDSIGHT_ACCOUNT_ID", "HASNAXYZ_BRANDSIGHT_LIVE_ACCOUNT_ID"]
|
|
34931
|
+
};
|
|
34932
|
+
SEDO_ENV = {
|
|
34933
|
+
partnerId: ["SEDO_PARTNER_ID", "HASNAXYZ_SEDO_LIVE_PARTNER_ID"],
|
|
34934
|
+
signKey: ["SEDO_API_KEY", "SEDO_SIGN_KEY", "HASNAXYZ_SEDO_LIVE_API_KEY"],
|
|
34935
|
+
username: ["SEDO_USERNAME", "SEDO_EMAIL", "HASNAXYZ_SEDO_LIVE_USERNAME", "HASNAXYZ_SEDO_LIVE_EMAIL"],
|
|
34936
|
+
password: ["SEDO_PASSWORD", "HASNAXYZ_SEDO_LIVE_PASSWORD"]
|
|
34937
|
+
};
|
|
34938
|
+
CLOUDFLARE_ENV = {
|
|
34939
|
+
apiToken: ["CLOUDFLARE_API_TOKEN", "HASNAXYZ_CLOUDFLARE_LIVE_API_TOKEN"],
|
|
34940
|
+
apiKey: ["CLOUDFLARE_API_KEY", "HASNAXYZ_CLOUDFLARE_LIVE_API_KEY"],
|
|
34941
|
+
email: ["CLOUDFLARE_EMAIL", "HASNAXYZ_CLOUDFLARE_LIVE_EMAIL"],
|
|
34942
|
+
accountId: ["CLOUDFLARE_ACCOUNT_ID", "HASNAXYZ_CLOUDFLARE_LIVE_ACCOUNT_ID"]
|
|
34943
|
+
};
|
|
34944
|
+
BEEPMEDIA_AWS_ENV = {
|
|
34945
|
+
accessKeyId: ["HASNASTUDIO_BEEPMEDIA_AWS_LIVE_ACCESS_KEY_ID", "BEEPMEDIA_AWS_ACCESS_KEY_ID"],
|
|
34946
|
+
secretAccessKey: ["HASNASTUDIO_BEEPMEDIA_AWS_LIVE_SECRET_ACCESS_KEY", "BEEPMEDIA_AWS_SECRET_ACCESS_KEY"]
|
|
34947
|
+
};
|
|
34948
|
+
});
|
|
34949
|
+
|
|
34950
|
+
// src/lib/cloudflare-auth.ts
|
|
34951
|
+
function resolveCloudflareConfig(env = process.env) {
|
|
34952
|
+
const accountId = firstEnv(env, CLOUDFLARE_ENV.accountId)?.value;
|
|
34953
|
+
const apiToken = firstEnv(env, CLOUDFLARE_ENV.apiToken)?.value;
|
|
34954
|
+
if (apiToken)
|
|
34955
|
+
return { apiToken, accountId };
|
|
34956
|
+
const apiKey = firstEnv(env, CLOUDFLARE_ENV.apiKey)?.value;
|
|
34957
|
+
const email = firstEnv(env, CLOUDFLARE_ENV.email)?.value;
|
|
34958
|
+
if (apiKey && email)
|
|
34959
|
+
return { apiKey, email, accountId };
|
|
34693
34960
|
return accountId ? { accountId } : {};
|
|
34694
34961
|
}
|
|
34695
34962
|
function cloudflareAuthHeaders(cfg) {
|
|
@@ -34699,6 +34966,9 @@ function cloudflareAuthHeaders(cfg) {
|
|
|
34699
34966
|
return { "X-Auth-Key": cfg.apiKey, "X-Auth-Email": cfg.email };
|
|
34700
34967
|
throw new Error("Cloudflare credentials not configured. Set CLOUDFLARE_API_TOKEN, or CLOUDFLARE_API_KEY + CLOUDFLARE_EMAIL.");
|
|
34701
34968
|
}
|
|
34969
|
+
var init_cloudflare_auth = __esm(() => {
|
|
34970
|
+
init_env_aliases();
|
|
34971
|
+
});
|
|
34702
34972
|
|
|
34703
34973
|
// src/lib/cloudflare.ts
|
|
34704
34974
|
function getConfig3() {
|
|
@@ -34830,30 +35100,44 @@ function createCloudflareProvider(config) {
|
|
|
34830
35100
|
};
|
|
34831
35101
|
}
|
|
34832
35102
|
var CF_BASE = "https://api.cloudflare.com/client/v4";
|
|
34833
|
-
var init_cloudflare = () => {
|
|
35103
|
+
var init_cloudflare = __esm(() => {
|
|
35104
|
+
init_cloudflare_auth();
|
|
35105
|
+
});
|
|
34834
35106
|
|
|
34835
35107
|
// src/lib/brandsight.ts
|
|
34836
35108
|
function resolveBrandsightConfig(env = process.env) {
|
|
35109
|
+
const apiKey = firstEnv(env, BRANDSIGHT_ENV.apiKey)?.value ?? "";
|
|
35110
|
+
const apiSecret = firstEnv(env, BRANDSIGHT_ENV.apiSecret)?.value;
|
|
35111
|
+
const customerId = firstEnv(env, BRANDSIGHT_ENV.customerId)?.value;
|
|
35112
|
+
const shopperId = firstEnv(env, BRANDSIGHT_ENV.shopperId)?.value;
|
|
35113
|
+
const accountId = firstEnv(env, BRANDSIGHT_ENV.accountId)?.value;
|
|
34837
35114
|
return {
|
|
34838
|
-
apiKey
|
|
34839
|
-
apiSecret
|
|
34840
|
-
customerId
|
|
34841
|
-
shopperId
|
|
34842
|
-
accountId
|
|
35115
|
+
apiKey,
|
|
35116
|
+
apiSecret,
|
|
35117
|
+
customerId,
|
|
35118
|
+
shopperId,
|
|
35119
|
+
accountId,
|
|
35120
|
+
baseUrl: env["BRANDSIGHT_BASE_URL"]
|
|
35121
|
+
};
|
|
35122
|
+
}
|
|
35123
|
+
function brandsightCapability(env = process.env) {
|
|
35124
|
+
const cfg = resolveBrandsightConfig(env);
|
|
35125
|
+
const configured = !!(cfg.apiKey && cfg.apiSecret && cfg.customerId);
|
|
35126
|
+
return {
|
|
35127
|
+
configured,
|
|
35128
|
+
gated: true,
|
|
35129
|
+
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
35130
|
};
|
|
34844
35131
|
}
|
|
34845
35132
|
function getApiKey() {
|
|
34846
|
-
const key =
|
|
35133
|
+
const key = resolveBrandsightConfig().apiKey;
|
|
34847
35134
|
if (!key) {
|
|
34848
|
-
throw new BrandsightApiError("
|
|
35135
|
+
throw new BrandsightApiError("Brandsight API key is not set. Set BRANDSIGHT_API_KEY or HASNAXYZ_BRANDSIGHT_LIVE_API_KEY.");
|
|
34849
35136
|
}
|
|
34850
35137
|
return key;
|
|
34851
35138
|
}
|
|
34852
35139
|
function getConfig4() {
|
|
34853
|
-
return
|
|
34854
|
-
apiKey: process.env["BRANDSIGHT_API_KEY"] ?? "",
|
|
34855
|
-
accountId: process.env["BRANDSIGHT_ACCOUNT_ID"]
|
|
34856
|
-
};
|
|
35140
|
+
return resolveBrandsightConfig();
|
|
34857
35141
|
}
|
|
34858
35142
|
async function apiRequest3(method, path, apiKey, body, baseUrl = BRANDSIGHT_BASE) {
|
|
34859
35143
|
const fetchFn = _fetchFn || globalThis.fetch;
|
|
@@ -34909,11 +35193,11 @@ async function apiGet(path, apiKey, baseUrl = BRANDSIGHT_BASE) {
|
|
|
34909
35193
|
}
|
|
34910
35194
|
}
|
|
34911
35195
|
function generateStubAlerts(brandName) {
|
|
34912
|
-
const
|
|
35196
|
+
const now2 = new Date().toISOString();
|
|
34913
35197
|
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:
|
|
35198
|
+
{ domain: `${brandName}-deals.com`, type: "keyword", registered_at: now2 },
|
|
35199
|
+
{ domain: `${brandName.replace(/a/gi, "4").replace(/e/gi, "3")}.com`, type: "homoglyph", registered_at: now2 },
|
|
35200
|
+
{ domain: `${brandName}s.com`, type: "typosquat", registered_at: now2 }
|
|
34917
35201
|
];
|
|
34918
35202
|
}
|
|
34919
35203
|
function generateStubSimilarDomains(domain) {
|
|
@@ -35051,8 +35335,8 @@ function createBrandsightProvider(config) {
|
|
|
35051
35335
|
auto_renew: d3.auto_renew
|
|
35052
35336
|
};
|
|
35053
35337
|
},
|
|
35054
|
-
async renewDomain(domain) {
|
|
35055
|
-
const result = await renewDomain3(domain,
|
|
35338
|
+
async renewDomain(domain, years = 1) {
|
|
35339
|
+
const result = await renewDomain3(domain, years, cfg);
|
|
35056
35340
|
return { domain, success: result.success, orderId: result.orderId };
|
|
35057
35341
|
},
|
|
35058
35342
|
async checkAvailability(domain) {
|
|
@@ -35071,6 +35355,7 @@ function createBrandsightProvider(config) {
|
|
|
35071
35355
|
}
|
|
35072
35356
|
var BrandsightApiError, _fetchFn = null, BRANDSIGHT_BASE = "https://api.brandsight.com/v1";
|
|
35073
35357
|
var init_brandsight = __esm(() => {
|
|
35358
|
+
init_env_aliases();
|
|
35074
35359
|
init_version();
|
|
35075
35360
|
BrandsightApiError = class BrandsightApiError extends Error {
|
|
35076
35361
|
statusCode;
|
|
@@ -35095,9 +35380,9 @@ __export(exports_config, {
|
|
|
35095
35380
|
getConfigKey: () => getConfigKey,
|
|
35096
35381
|
applyPurchaseProfile: () => applyPurchaseProfile
|
|
35097
35382
|
});
|
|
35098
|
-
import { existsSync as
|
|
35099
|
-
import { homedir as
|
|
35100
|
-
import { join as
|
|
35383
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "fs";
|
|
35384
|
+
import { homedir as homedir4 } from "os";
|
|
35385
|
+
import { join as join4 } from "path";
|
|
35101
35386
|
function getPurchaseProfile() {
|
|
35102
35387
|
return loadConfig3().purchase_aws_profile ?? process.env["DOMAINS_PURCHASE_AWS_PROFILE"] ?? undefined;
|
|
35103
35388
|
}
|
|
@@ -35110,11 +35395,11 @@ function applyPurchaseProfile() {
|
|
|
35110
35395
|
return profile;
|
|
35111
35396
|
}
|
|
35112
35397
|
function configPath() {
|
|
35113
|
-
return
|
|
35398
|
+
return join4(homedir4(), ".hasna", "domains", "config.json");
|
|
35114
35399
|
}
|
|
35115
35400
|
function loadConfig3() {
|
|
35116
35401
|
const path = configPath();
|
|
35117
|
-
if (!
|
|
35402
|
+
if (!existsSync3(path))
|
|
35118
35403
|
return {};
|
|
35119
35404
|
try {
|
|
35120
35405
|
return JSON.parse(readFileSync3(path, "utf-8"));
|
|
@@ -35124,8 +35409,8 @@ function loadConfig3() {
|
|
|
35124
35409
|
}
|
|
35125
35410
|
function saveConfig(config) {
|
|
35126
35411
|
const path = configPath();
|
|
35127
|
-
const dir =
|
|
35128
|
-
if (!
|
|
35412
|
+
const dir = join4(homedir4(), ".hasna", "domains");
|
|
35413
|
+
if (!existsSync3(dir))
|
|
35129
35414
|
mkdirSync2(dir, { recursive: true });
|
|
35130
35415
|
writeFileSync(path, JSON.stringify(config, null, 2), "utf-8");
|
|
35131
35416
|
}
|
|
@@ -35176,44 +35461,6 @@ function getConfigKey(keyPath) {
|
|
|
35176
35461
|
}
|
|
35177
35462
|
var init_config = () => {};
|
|
35178
35463
|
|
|
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
35464
|
// src/db/domain-history.ts
|
|
35218
35465
|
function rowToDomainHistory(row) {
|
|
35219
35466
|
return {
|
|
@@ -35296,8 +35543,10 @@ var init_domain_history = __esm(() => {
|
|
|
35296
35543
|
// src/lib/sedo.ts
|
|
35297
35544
|
var exports_sedo = {};
|
|
35298
35545
|
__export(exports_sedo, {
|
|
35546
|
+
sedoCapability: () => sedoCapability,
|
|
35299
35547
|
searchSedoDomains: () => searchSedoDomains,
|
|
35300
35548
|
searchHealthDomains: () => searchHealthDomains,
|
|
35549
|
+
resolveSedoConfig: () => resolveSedoConfig,
|
|
35301
35550
|
removeDomainFromSedo: () => removeDomainFromSedo,
|
|
35302
35551
|
recordSedoPurchase: () => recordSedoPurchase,
|
|
35303
35552
|
listSedoPortfolio: () => listSedoPortfolio,
|
|
@@ -35307,16 +35556,31 @@ __export(exports_sedo, {
|
|
|
35307
35556
|
checkSedoBlacklist: () => checkSedoBlacklist,
|
|
35308
35557
|
addDomainToSedo: () => addDomainToSedo
|
|
35309
35558
|
});
|
|
35559
|
+
function resolveSedoConfig(env = process.env) {
|
|
35560
|
+
return {
|
|
35561
|
+
partnerId: firstEnv(env, SEDO_ENV.partnerId)?.value,
|
|
35562
|
+
signKey: firstEnv(env, SEDO_ENV.signKey)?.value,
|
|
35563
|
+
username: firstEnv(env, SEDO_ENV.username)?.value,
|
|
35564
|
+
password: firstEnv(env, SEDO_ENV.password)?.value
|
|
35565
|
+
};
|
|
35566
|
+
}
|
|
35310
35567
|
function getSedoConfig() {
|
|
35311
|
-
const
|
|
35312
|
-
const signKey =
|
|
35313
|
-
const username = process.env.SEDO_USERNAME;
|
|
35314
|
-
const password = process.env.SEDO_PASSWORD;
|
|
35568
|
+
const config = resolveSedoConfig();
|
|
35569
|
+
const { partnerId, signKey, username, password } = config;
|
|
35315
35570
|
if (!partnerId || !signKey || !username || !password) {
|
|
35316
|
-
throw new Error("Sedo credentials not configured. Required: SEDO_PARTNER_ID, SEDO_API_KEY, SEDO_USERNAME, SEDO_PASSWORD");
|
|
35571
|
+
throw new Error("Sedo credentials not configured. Required: SEDO_PARTNER_ID, SEDO_API_KEY, SEDO_USERNAME, SEDO_PASSWORD (or HASNAXYZ_SEDO_LIVE_* aliases)");
|
|
35317
35572
|
}
|
|
35318
35573
|
return { partnerId, signKey, username, password };
|
|
35319
35574
|
}
|
|
35575
|
+
function sedoCapability(env = process.env) {
|
|
35576
|
+
const cfg = resolveSedoConfig(env);
|
|
35577
|
+
const configured = !!(cfg.partnerId && cfg.signKey && cfg.username && cfg.password);
|
|
35578
|
+
return {
|
|
35579
|
+
configured,
|
|
35580
|
+
gated: true,
|
|
35581
|
+
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."
|
|
35582
|
+
};
|
|
35583
|
+
}
|
|
35320
35584
|
function buildXmlParams(params) {
|
|
35321
35585
|
return {
|
|
35322
35586
|
partnerid: params.partnerId,
|
|
@@ -35515,10 +35779,85 @@ function recordSedoPurchase(domain, price, orderId) {
|
|
|
35515
35779
|
}
|
|
35516
35780
|
var BASE_URL = "https://api.sedo.com/api/v1/";
|
|
35517
35781
|
var init_sedo = __esm(() => {
|
|
35782
|
+
init_env_aliases();
|
|
35518
35783
|
init_domains();
|
|
35519
35784
|
init_domain_history();
|
|
35520
35785
|
});
|
|
35521
35786
|
|
|
35787
|
+
// src/lib/creds-check.ts
|
|
35788
|
+
var exports_creds_check = {};
|
|
35789
|
+
__export(exports_creds_check, {
|
|
35790
|
+
checkProvisioningCredentials: () => checkProvisioningCredentials
|
|
35791
|
+
});
|
|
35792
|
+
function configuredStatus(provider, mode, detail) {
|
|
35793
|
+
return { provider, configured: true, mode, detail };
|
|
35794
|
+
}
|
|
35795
|
+
function missingStatus(provider, detail) {
|
|
35796
|
+
return { provider, configured: false, mode: "none", detail };
|
|
35797
|
+
}
|
|
35798
|
+
function checkProvisioningCredentials(env = process.env) {
|
|
35799
|
+
const out = [];
|
|
35800
|
+
const hasAwsKeys = !!(env["AWS_ACCESS_KEY_ID"] && env["AWS_SECRET_ACCESS_KEY"]);
|
|
35801
|
+
const hasAwsProfile = !!env["AWS_PROFILE"];
|
|
35802
|
+
if (hasAwsProfile || hasAwsKeys) {
|
|
35803
|
+
out.push(configuredStatus("route53", hasAwsProfile ? `profile:${env["AWS_PROFILE"]}` : "access-keys", "AWS credentials present (Route 53 Domains API uses us-east-1)"));
|
|
35804
|
+
} else {
|
|
35805
|
+
out.push(missingStatus("route53", "Set AWS_PROFILE or AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY"));
|
|
35806
|
+
}
|
|
35807
|
+
const beepmediaAws = hasProviderCredentials("aws:beepmedia", env);
|
|
35808
|
+
out.push({
|
|
35809
|
+
provider: "aws:beepmedia",
|
|
35810
|
+
configured: beepmediaAws,
|
|
35811
|
+
mode: beepmediaAws ? "scoped-env" : "none",
|
|
35812
|
+
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 ")}`
|
|
35813
|
+
});
|
|
35814
|
+
const cf = resolveCloudflareConfig(env);
|
|
35815
|
+
const cfMode = cf.apiToken ? "token" : cf.apiKey && cf.email ? "global-key" : "none";
|
|
35816
|
+
out.push({
|
|
35817
|
+
provider: "cloudflare",
|
|
35818
|
+
configured: cfMode !== "none",
|
|
35819
|
+
mode: cfMode + (cf.accountId ? "+account" : ""),
|
|
35820
|
+
detail: cfMode === "none" ? "Set CLOUDFLARE_API_TOKEN or CLOUDFLARE_API_KEY+CLOUDFLARE_EMAIL" : cf.accountId ? "ok" : "missing CLOUDFLARE_ACCOUNT_ID (needed to create zones)"
|
|
35821
|
+
});
|
|
35822
|
+
out.push({
|
|
35823
|
+
provider: "namecheap",
|
|
35824
|
+
configured: hasProviderCredentials("namecheap", env),
|
|
35825
|
+
mode: hasProviderCredentials("namecheap", env) ? "api-key+username+client-ip" : "none",
|
|
35826
|
+
detail: "Requires NAMECHEAP_API_KEY, NAMECHEAP_USERNAME, and whitelisted NAMECHEAP_CLIENT_IP"
|
|
35827
|
+
});
|
|
35828
|
+
const gd = godaddyCapability(env);
|
|
35829
|
+
out.push({
|
|
35830
|
+
provider: "godaddy",
|
|
35831
|
+
configured: gd.configured,
|
|
35832
|
+
mode: gd.configured ? "key+secret" : "none",
|
|
35833
|
+
detail: gd.notes
|
|
35834
|
+
});
|
|
35835
|
+
const bs = brandsightCapability(env);
|
|
35836
|
+
const bsMode = firstEnv(env, ["BRANDSIGHT_API_KEY"]) ? "standard-env" : bs.configured ? "scoped-env" : "none";
|
|
35837
|
+
out.push({
|
|
35838
|
+
provider: "brandsight",
|
|
35839
|
+
configured: bs.configured,
|
|
35840
|
+
mode: bsMode,
|
|
35841
|
+
detail: bs.notes
|
|
35842
|
+
});
|
|
35843
|
+
const sedo = sedoCapability(env);
|
|
35844
|
+
const sedoMode = firstEnv(env, ["SEDO_API_KEY"]) ? "standard-env" : sedo.configured ? "scoped-env" : "none";
|
|
35845
|
+
out.push({
|
|
35846
|
+
provider: "sedo",
|
|
35847
|
+
configured: sedo.configured,
|
|
35848
|
+
mode: sedoMode,
|
|
35849
|
+
detail: sedo.notes
|
|
35850
|
+
});
|
|
35851
|
+
return out;
|
|
35852
|
+
}
|
|
35853
|
+
var init_creds_check = __esm(() => {
|
|
35854
|
+
init_cloudflare_auth();
|
|
35855
|
+
init_brandsight();
|
|
35856
|
+
init_godaddy();
|
|
35857
|
+
init_sedo();
|
|
35858
|
+
init_env_aliases();
|
|
35859
|
+
});
|
|
35860
|
+
|
|
35522
35861
|
// src/lib/provision-state.ts
|
|
35523
35862
|
function isDomainTerminal(s3) {
|
|
35524
35863
|
return TERMINAL.has(s3);
|
|
@@ -35593,7 +35932,7 @@ function makeDomainDaemonDeps() {
|
|
|
35593
35932
|
return;
|
|
35594
35933
|
case "delegate_ns": {
|
|
35595
35934
|
const zone = await ensureZone(domain);
|
|
35596
|
-
await
|
|
35935
|
+
await updateNameservers2(domain, zone.nameservers);
|
|
35597
35936
|
return;
|
|
35598
35937
|
}
|
|
35599
35938
|
case "check_ns_propagation":
|
|
@@ -40553,6 +40892,684 @@ var {
|
|
|
40553
40892
|
Help
|
|
40554
40893
|
} = import__.default;
|
|
40555
40894
|
|
|
40895
|
+
// node_modules/@hasna/events/dist/commander.js
|
|
40896
|
+
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
40897
|
+
import { existsSync } from "fs";
|
|
40898
|
+
import { homedir } from "os";
|
|
40899
|
+
import { join } from "path";
|
|
40900
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
40901
|
+
import { randomUUID } from "crypto";
|
|
40902
|
+
import { spawn } from "child_process";
|
|
40903
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
40904
|
+
function getPathValue(input, path) {
|
|
40905
|
+
return path.split(".").reduce((value, part) => {
|
|
40906
|
+
if (value && typeof value === "object" && part in value) {
|
|
40907
|
+
return value[part];
|
|
40908
|
+
}
|
|
40909
|
+
return;
|
|
40910
|
+
}, input);
|
|
40911
|
+
}
|
|
40912
|
+
function wildcardToRegExp(pattern) {
|
|
40913
|
+
const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, ".*");
|
|
40914
|
+
return new RegExp(`^${escaped}$`);
|
|
40915
|
+
}
|
|
40916
|
+
function matchString(value, matcher) {
|
|
40917
|
+
if (matcher === undefined)
|
|
40918
|
+
return true;
|
|
40919
|
+
if (value === undefined)
|
|
40920
|
+
return false;
|
|
40921
|
+
const matchers = Array.isArray(matcher) ? matcher : [matcher];
|
|
40922
|
+
return matchers.some((item) => wildcardToRegExp(item).test(value));
|
|
40923
|
+
}
|
|
40924
|
+
function matchRecord(input, matcher) {
|
|
40925
|
+
if (!matcher)
|
|
40926
|
+
return true;
|
|
40927
|
+
return Object.entries(matcher).every(([path, expected]) => {
|
|
40928
|
+
const actual = getPathValue(input, path);
|
|
40929
|
+
if (typeof expected === "string" || Array.isArray(expected)) {
|
|
40930
|
+
return matchString(actual === undefined ? undefined : String(actual), expected);
|
|
40931
|
+
}
|
|
40932
|
+
return actual === expected;
|
|
40933
|
+
});
|
|
40934
|
+
}
|
|
40935
|
+
function eventMatchesFilter(event, filter) {
|
|
40936
|
+
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);
|
|
40937
|
+
}
|
|
40938
|
+
function channelMatchesEvent(channel, event) {
|
|
40939
|
+
if (!channel.enabled)
|
|
40940
|
+
return false;
|
|
40941
|
+
if (!channel.filters || channel.filters.length === 0)
|
|
40942
|
+
return true;
|
|
40943
|
+
return channel.filters.some((filter) => eventMatchesFilter(event, filter));
|
|
40944
|
+
}
|
|
40945
|
+
var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
|
|
40946
|
+
var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
|
|
40947
|
+
function getEventsDataDir(override) {
|
|
40948
|
+
return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join(homedir(), ".hasna", "events");
|
|
40949
|
+
}
|
|
40950
|
+
|
|
40951
|
+
class JsonEventsStore {
|
|
40952
|
+
dataDir;
|
|
40953
|
+
channelsPath;
|
|
40954
|
+
eventsPath;
|
|
40955
|
+
deliveriesPath;
|
|
40956
|
+
constructor(dataDir = getEventsDataDir()) {
|
|
40957
|
+
this.dataDir = dataDir;
|
|
40958
|
+
this.channelsPath = join(dataDir, "channels.json");
|
|
40959
|
+
this.eventsPath = join(dataDir, "events.json");
|
|
40960
|
+
this.deliveriesPath = join(dataDir, "deliveries.json");
|
|
40961
|
+
}
|
|
40962
|
+
async init() {
|
|
40963
|
+
await mkdir(this.dataDir, { recursive: true, mode: 448 });
|
|
40964
|
+
await chmod(this.dataDir, 448).catch(() => {
|
|
40965
|
+
return;
|
|
40966
|
+
});
|
|
40967
|
+
await this.ensureArrayFile(this.channelsPath);
|
|
40968
|
+
await this.ensureArrayFile(this.eventsPath);
|
|
40969
|
+
await this.ensureArrayFile(this.deliveriesPath);
|
|
40970
|
+
}
|
|
40971
|
+
async addChannel(channel) {
|
|
40972
|
+
await this.init();
|
|
40973
|
+
const channels = await this.readJson(this.channelsPath, []);
|
|
40974
|
+
const index = channels.findIndex((item) => item.id === channel.id);
|
|
40975
|
+
if (index >= 0) {
|
|
40976
|
+
channels[index] = { ...channel, createdAt: channels[index].createdAt, updatedAt: new Date().toISOString() };
|
|
40977
|
+
} else {
|
|
40978
|
+
channels.push(channel);
|
|
40979
|
+
}
|
|
40980
|
+
await this.writeJson(this.channelsPath, channels);
|
|
40981
|
+
return index >= 0 ? channels[index] : channel;
|
|
40982
|
+
}
|
|
40983
|
+
async listChannels() {
|
|
40984
|
+
await this.init();
|
|
40985
|
+
return this.readJson(this.channelsPath, []);
|
|
40986
|
+
}
|
|
40987
|
+
async getChannel(id) {
|
|
40988
|
+
const channels = await this.listChannels();
|
|
40989
|
+
return channels.find((channel) => channel.id === id);
|
|
40990
|
+
}
|
|
40991
|
+
async removeChannel(id) {
|
|
40992
|
+
await this.init();
|
|
40993
|
+
const channels = await this.readJson(this.channelsPath, []);
|
|
40994
|
+
const next = channels.filter((channel) => channel.id !== id);
|
|
40995
|
+
await this.writeJson(this.channelsPath, next);
|
|
40996
|
+
return next.length !== channels.length;
|
|
40997
|
+
}
|
|
40998
|
+
async appendEvent(event) {
|
|
40999
|
+
await this.init();
|
|
41000
|
+
const events = await this.readJson(this.eventsPath, []);
|
|
41001
|
+
events.push(event);
|
|
41002
|
+
await this.writeJson(this.eventsPath, events);
|
|
41003
|
+
return event;
|
|
41004
|
+
}
|
|
41005
|
+
async listEvents() {
|
|
41006
|
+
await this.init();
|
|
41007
|
+
return this.readJson(this.eventsPath, []);
|
|
41008
|
+
}
|
|
41009
|
+
async findEventByIdentity(identity) {
|
|
41010
|
+
const events = await this.listEvents();
|
|
41011
|
+
return events.find((event) => identity.id !== undefined && event.id === identity.id || identity.dedupeKey !== undefined && event.dedupeKey === identity.dedupeKey);
|
|
41012
|
+
}
|
|
41013
|
+
async appendDelivery(result) {
|
|
41014
|
+
await this.init();
|
|
41015
|
+
const deliveries = await this.readJson(this.deliveriesPath, []);
|
|
41016
|
+
deliveries.push(result);
|
|
41017
|
+
await this.writeJson(this.deliveriesPath, deliveries);
|
|
41018
|
+
return result;
|
|
41019
|
+
}
|
|
41020
|
+
async listDeliveries() {
|
|
41021
|
+
await this.init();
|
|
41022
|
+
return this.readJson(this.deliveriesPath, []);
|
|
41023
|
+
}
|
|
41024
|
+
async exportData() {
|
|
41025
|
+
return {
|
|
41026
|
+
channels: await this.listChannels(),
|
|
41027
|
+
events: await this.listEvents(),
|
|
41028
|
+
deliveries: await this.listDeliveries()
|
|
41029
|
+
};
|
|
41030
|
+
}
|
|
41031
|
+
async ensureArrayFile(path) {
|
|
41032
|
+
if (!existsSync(path)) {
|
|
41033
|
+
await writeFile(path, `[]
|
|
41034
|
+
`, { encoding: "utf-8", mode: 384 });
|
|
41035
|
+
}
|
|
41036
|
+
await chmod(path, 384).catch(() => {
|
|
41037
|
+
return;
|
|
41038
|
+
});
|
|
41039
|
+
}
|
|
41040
|
+
async readJson(path, fallback) {
|
|
41041
|
+
try {
|
|
41042
|
+
const raw = await readFile(path, "utf-8");
|
|
41043
|
+
if (!raw.trim())
|
|
41044
|
+
return fallback;
|
|
41045
|
+
return JSON.parse(raw);
|
|
41046
|
+
} catch (error) {
|
|
41047
|
+
if (error.code === "ENOENT")
|
|
41048
|
+
return fallback;
|
|
41049
|
+
throw error;
|
|
41050
|
+
}
|
|
41051
|
+
}
|
|
41052
|
+
async writeJson(path, value) {
|
|
41053
|
+
const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
41054
|
+
await writeFile(tempPath, `${JSON.stringify(value, null, 2)}
|
|
41055
|
+
`, { encoding: "utf-8", mode: 384 });
|
|
41056
|
+
await rename(tempPath, path);
|
|
41057
|
+
await chmod(path, 384).catch(() => {
|
|
41058
|
+
return;
|
|
41059
|
+
});
|
|
41060
|
+
}
|
|
41061
|
+
}
|
|
41062
|
+
var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
|
|
41063
|
+
function buildSignatureBase(timestamp, body) {
|
|
41064
|
+
return `${timestamp}.${body}`;
|
|
41065
|
+
}
|
|
41066
|
+
function signPayload(secret, timestamp, body) {
|
|
41067
|
+
const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
|
|
41068
|
+
return `sha256=${digest}`;
|
|
41069
|
+
}
|
|
41070
|
+
function now() {
|
|
41071
|
+
return new Date().toISOString();
|
|
41072
|
+
}
|
|
41073
|
+
function truncate(value, max = 4096) {
|
|
41074
|
+
return value.length > max ? `${value.slice(0, max)}...` : value;
|
|
41075
|
+
}
|
|
41076
|
+
function buildWebhookRequest(event, channel) {
|
|
41077
|
+
if (!channel.webhook)
|
|
41078
|
+
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
41079
|
+
const body = JSON.stringify(event);
|
|
41080
|
+
const timestamp = event.time;
|
|
41081
|
+
const headers = {
|
|
41082
|
+
"Content-Type": "application/json",
|
|
41083
|
+
"User-Agent": "@hasna/events",
|
|
41084
|
+
"X-Hasna-Event-Id": event.id,
|
|
41085
|
+
"X-Hasna-Event-Type": event.type,
|
|
41086
|
+
"X-Hasna-Timestamp": timestamp,
|
|
41087
|
+
...channel.webhook.headers
|
|
41088
|
+
};
|
|
41089
|
+
if (channel.webhook.secret) {
|
|
41090
|
+
headers["X-Hasna-Signature"] = signPayload(channel.webhook.secret, timestamp, body);
|
|
41091
|
+
}
|
|
41092
|
+
return { body, headers };
|
|
41093
|
+
}
|
|
41094
|
+
async function dispatchWebhook(event, channel, options = {}) {
|
|
41095
|
+
if (!channel.webhook)
|
|
41096
|
+
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
41097
|
+
const startedAt = now();
|
|
41098
|
+
const { body, headers } = buildWebhookRequest(event, channel);
|
|
41099
|
+
const controller = new AbortController;
|
|
41100
|
+
const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
|
|
41101
|
+
try {
|
|
41102
|
+
const response = await (options.fetchImpl ?? fetch)(channel.webhook.url, {
|
|
41103
|
+
method: "POST",
|
|
41104
|
+
headers,
|
|
41105
|
+
body,
|
|
41106
|
+
signal: controller.signal
|
|
41107
|
+
});
|
|
41108
|
+
const responseBody = truncate(await response.text());
|
|
41109
|
+
return {
|
|
41110
|
+
attempt: 1,
|
|
41111
|
+
status: response.ok ? "success" : "failed",
|
|
41112
|
+
startedAt,
|
|
41113
|
+
completedAt: now(),
|
|
41114
|
+
responseStatus: response.status,
|
|
41115
|
+
responseBody,
|
|
41116
|
+
error: response.ok ? undefined : `Webhook returned HTTP ${response.status}`
|
|
41117
|
+
};
|
|
41118
|
+
} catch (error) {
|
|
41119
|
+
return {
|
|
41120
|
+
attempt: 1,
|
|
41121
|
+
status: "failed",
|
|
41122
|
+
startedAt,
|
|
41123
|
+
completedAt: now(),
|
|
41124
|
+
error: error instanceof Error ? error.message : String(error)
|
|
41125
|
+
};
|
|
41126
|
+
} finally {
|
|
41127
|
+
clearTimeout(timeout);
|
|
41128
|
+
}
|
|
41129
|
+
}
|
|
41130
|
+
async function dispatchCommand(event, channel) {
|
|
41131
|
+
if (!channel.command)
|
|
41132
|
+
throw new Error(`Channel ${channel.id} has no command config`);
|
|
41133
|
+
const startedAt = now();
|
|
41134
|
+
const eventJson = JSON.stringify(event);
|
|
41135
|
+
const env = {
|
|
41136
|
+
...process.env,
|
|
41137
|
+
...channel.command.env,
|
|
41138
|
+
HASNA_CHANNEL_ID: channel.id,
|
|
41139
|
+
HASNA_EVENT_ID: event.id,
|
|
41140
|
+
HASNA_EVENT_TYPE: event.type,
|
|
41141
|
+
HASNA_EVENT_SOURCE: event.source,
|
|
41142
|
+
HASNA_EVENT_SUBJECT: event.subject ?? "",
|
|
41143
|
+
HASNA_EVENT_SEVERITY: event.severity,
|
|
41144
|
+
HASNA_EVENT_TIME: event.time,
|
|
41145
|
+
HASNA_EVENT_DEDUPE_KEY: event.dedupeKey ?? "",
|
|
41146
|
+
HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
|
|
41147
|
+
HASNA_EVENT_JSON: eventJson
|
|
41148
|
+
};
|
|
41149
|
+
return new Promise((resolve) => {
|
|
41150
|
+
const child = spawn(channel.command.command, channel.command.args ?? [], {
|
|
41151
|
+
cwd: channel.command.cwd,
|
|
41152
|
+
env,
|
|
41153
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
41154
|
+
});
|
|
41155
|
+
let stdout = "";
|
|
41156
|
+
let stderr = "";
|
|
41157
|
+
const timeout = setTimeout(() => child.kill("SIGTERM"), channel.command.timeoutMs ?? 15000);
|
|
41158
|
+
child.stdin.end(eventJson);
|
|
41159
|
+
child.stdout.on("data", (chunk) => {
|
|
41160
|
+
stdout += chunk.toString();
|
|
41161
|
+
});
|
|
41162
|
+
child.stderr.on("data", (chunk) => {
|
|
41163
|
+
stderr += chunk.toString();
|
|
41164
|
+
});
|
|
41165
|
+
child.on("error", (error) => {
|
|
41166
|
+
clearTimeout(timeout);
|
|
41167
|
+
resolve({
|
|
41168
|
+
attempt: 1,
|
|
41169
|
+
status: "failed",
|
|
41170
|
+
startedAt,
|
|
41171
|
+
completedAt: now(),
|
|
41172
|
+
stdout: truncate(stdout),
|
|
41173
|
+
stderr: truncate(stderr),
|
|
41174
|
+
error: error.message
|
|
41175
|
+
});
|
|
41176
|
+
});
|
|
41177
|
+
child.on("close", (code, signal) => {
|
|
41178
|
+
clearTimeout(timeout);
|
|
41179
|
+
const success = code === 0;
|
|
41180
|
+
resolve({
|
|
41181
|
+
attempt: 1,
|
|
41182
|
+
status: success ? "success" : "failed",
|
|
41183
|
+
startedAt,
|
|
41184
|
+
completedAt: now(),
|
|
41185
|
+
stdout: truncate(stdout),
|
|
41186
|
+
stderr: truncate(stderr),
|
|
41187
|
+
error: success ? undefined : `Command exited with ${signal ? `signal ${signal}` : `code ${code}`}`
|
|
41188
|
+
});
|
|
41189
|
+
});
|
|
41190
|
+
});
|
|
41191
|
+
}
|
|
41192
|
+
async function dispatchChannel(event, channel, options = {}) {
|
|
41193
|
+
if (channel.transport === "webhook")
|
|
41194
|
+
return dispatchWebhook(event, channel, options);
|
|
41195
|
+
if (channel.transport === "command")
|
|
41196
|
+
return dispatchCommand(event, channel);
|
|
41197
|
+
return {
|
|
41198
|
+
attempt: 1,
|
|
41199
|
+
status: "skipped",
|
|
41200
|
+
startedAt: now(),
|
|
41201
|
+
completedAt: now(),
|
|
41202
|
+
error: `Unsupported transport: ${channel.transport}`
|
|
41203
|
+
};
|
|
41204
|
+
}
|
|
41205
|
+
function createDeliveryResult(event, channel, attempts) {
|
|
41206
|
+
const status = attempts.some((attempt) => attempt.status === "success") ? "success" : attempts.every((attempt) => attempt.status === "skipped") ? "skipped" : "failed";
|
|
41207
|
+
return {
|
|
41208
|
+
id: randomUUID(),
|
|
41209
|
+
eventId: event.id,
|
|
41210
|
+
channelId: channel.id,
|
|
41211
|
+
transport: channel.transport,
|
|
41212
|
+
status,
|
|
41213
|
+
attempts,
|
|
41214
|
+
createdAt: attempts[0]?.startedAt ?? now(),
|
|
41215
|
+
completedAt: attempts.at(-1)?.completedAt ?? now()
|
|
41216
|
+
};
|
|
41217
|
+
}
|
|
41218
|
+
function createEvent(input) {
|
|
41219
|
+
return {
|
|
41220
|
+
id: input.id ?? randomUUID2(),
|
|
41221
|
+
source: input.source,
|
|
41222
|
+
type: input.type,
|
|
41223
|
+
time: normalizeTime(input.time),
|
|
41224
|
+
subject: input.subject,
|
|
41225
|
+
severity: input.severity ?? "info",
|
|
41226
|
+
data: input.data ?? {},
|
|
41227
|
+
message: input.message,
|
|
41228
|
+
dedupeKey: input.dedupeKey,
|
|
41229
|
+
schemaVersion: input.schemaVersion ?? "1.0",
|
|
41230
|
+
metadata: input.metadata ?? {}
|
|
41231
|
+
};
|
|
41232
|
+
}
|
|
41233
|
+
|
|
41234
|
+
class EventsClient {
|
|
41235
|
+
store;
|
|
41236
|
+
redactors;
|
|
41237
|
+
transportOptions;
|
|
41238
|
+
constructor(options = {}) {
|
|
41239
|
+
this.store = options.store ?? new JsonEventsStore(options.dataDir);
|
|
41240
|
+
this.redactors = options.redactors ?? [];
|
|
41241
|
+
this.transportOptions = { fetchImpl: options.fetchImpl };
|
|
41242
|
+
}
|
|
41243
|
+
async addChannel(input) {
|
|
41244
|
+
const timestamp = new Date().toISOString();
|
|
41245
|
+
return this.store.addChannel({
|
|
41246
|
+
...input,
|
|
41247
|
+
createdAt: input.createdAt ?? timestamp,
|
|
41248
|
+
updatedAt: input.updatedAt ?? timestamp
|
|
41249
|
+
});
|
|
41250
|
+
}
|
|
41251
|
+
async listChannels() {
|
|
41252
|
+
return this.store.listChannels();
|
|
41253
|
+
}
|
|
41254
|
+
async removeChannel(id) {
|
|
41255
|
+
return this.store.removeChannel(id);
|
|
41256
|
+
}
|
|
41257
|
+
async emit(input, options = {}) {
|
|
41258
|
+
const event = options.redactSensitiveData === false ? createEvent(input) : redactSensitiveKeys(createEvent(input));
|
|
41259
|
+
if (options.dedupe !== false) {
|
|
41260
|
+
const existing = await this.store.findEventByIdentity({ id: input.id, dedupeKey: event.dedupeKey });
|
|
41261
|
+
if (existing) {
|
|
41262
|
+
return { event: existing, deliveries: [], deduped: true };
|
|
41263
|
+
}
|
|
41264
|
+
}
|
|
41265
|
+
await this.store.appendEvent(event);
|
|
41266
|
+
const deliveries = options.deliver === false ? [] : await this.deliver(event);
|
|
41267
|
+
return { event, deliveries, deduped: false };
|
|
41268
|
+
}
|
|
41269
|
+
async listEvents() {
|
|
41270
|
+
return this.store.listEvents();
|
|
41271
|
+
}
|
|
41272
|
+
async listDeliveries() {
|
|
41273
|
+
return this.store.listDeliveries();
|
|
41274
|
+
}
|
|
41275
|
+
async deliver(event) {
|
|
41276
|
+
const channels = await this.store.listChannels();
|
|
41277
|
+
const selected = channels.filter((channel) => channelMatchesEvent(channel, event));
|
|
41278
|
+
const deliveries = [];
|
|
41279
|
+
for (const channel of selected) {
|
|
41280
|
+
const eventForChannel = await this.applyRedaction(event, channel);
|
|
41281
|
+
const result = await this.deliverWithRetry(eventForChannel, channel);
|
|
41282
|
+
await this.store.appendDelivery(result);
|
|
41283
|
+
deliveries.push(result);
|
|
41284
|
+
}
|
|
41285
|
+
return deliveries;
|
|
41286
|
+
}
|
|
41287
|
+
async testChannel(id, input = {}) {
|
|
41288
|
+
const channel = await this.store.getChannel(id);
|
|
41289
|
+
if (!channel)
|
|
41290
|
+
throw new Error(`Channel not found: ${id}`);
|
|
41291
|
+
const event = createEvent({
|
|
41292
|
+
source: input.source ?? "hasna.events",
|
|
41293
|
+
type: input.type ?? "events.test",
|
|
41294
|
+
subject: input.subject ?? id,
|
|
41295
|
+
severity: input.severity ?? "info",
|
|
41296
|
+
data: input.data ?? { test: true },
|
|
41297
|
+
message: input.message ?? "Hasna events test delivery",
|
|
41298
|
+
dedupeKey: input.dedupeKey,
|
|
41299
|
+
schemaVersion: input.schemaVersion,
|
|
41300
|
+
metadata: input.metadata,
|
|
41301
|
+
time: input.time,
|
|
41302
|
+
id: input.id
|
|
41303
|
+
});
|
|
41304
|
+
const eventForChannel = await this.applyRedaction(event, channel);
|
|
41305
|
+
const result = await this.deliverWithRetry(eventForChannel, channel);
|
|
41306
|
+
await this.store.appendDelivery(result);
|
|
41307
|
+
return result;
|
|
41308
|
+
}
|
|
41309
|
+
async replay(options = {}) {
|
|
41310
|
+
const events = (await this.store.listEvents()).filter((event) => {
|
|
41311
|
+
if (options.eventId && event.id !== options.eventId)
|
|
41312
|
+
return false;
|
|
41313
|
+
if (options.source && event.source !== options.source)
|
|
41314
|
+
return false;
|
|
41315
|
+
if (options.type && event.type !== options.type)
|
|
41316
|
+
return false;
|
|
41317
|
+
return true;
|
|
41318
|
+
});
|
|
41319
|
+
if (options.dryRun)
|
|
41320
|
+
return { events, deliveries: [] };
|
|
41321
|
+
const deliveries = [];
|
|
41322
|
+
for (const event of events) {
|
|
41323
|
+
deliveries.push(...await this.deliver(event));
|
|
41324
|
+
}
|
|
41325
|
+
return { events, deliveries };
|
|
41326
|
+
}
|
|
41327
|
+
async applyRedaction(event, channel) {
|
|
41328
|
+
let next = redactPaths(event, channel.redact?.paths ?? [], channel.redact?.replacement ?? "[REDACTED]");
|
|
41329
|
+
for (const redactor of this.redactors) {
|
|
41330
|
+
next = await redactor(next, channel);
|
|
41331
|
+
}
|
|
41332
|
+
return next;
|
|
41333
|
+
}
|
|
41334
|
+
async deliverWithRetry(event, channel) {
|
|
41335
|
+
const policy = normalizeRetryPolicy(channel.retry);
|
|
41336
|
+
const attempts = [];
|
|
41337
|
+
for (let index = 0;index < policy.maxAttempts; index += 1) {
|
|
41338
|
+
const attempt = await dispatchChannel(event, channel, this.transportOptions);
|
|
41339
|
+
attempt.attempt = index + 1;
|
|
41340
|
+
if (attempt.status === "failed" && index + 1 < policy.maxAttempts) {
|
|
41341
|
+
attempt.nextBackoffMs = Math.round(policy.backoffMs * policy.multiplier ** index);
|
|
41342
|
+
}
|
|
41343
|
+
attempts.push(attempt);
|
|
41344
|
+
if (attempt.status !== "failed")
|
|
41345
|
+
break;
|
|
41346
|
+
if (attempt.nextBackoffMs)
|
|
41347
|
+
await Bun.sleep(attempt.nextBackoffMs);
|
|
41348
|
+
}
|
|
41349
|
+
return createDeliveryResult(event, channel, attempts);
|
|
41350
|
+
}
|
|
41351
|
+
}
|
|
41352
|
+
function redactPaths(event, paths, replacement = "[REDACTED]") {
|
|
41353
|
+
if (paths.length === 0)
|
|
41354
|
+
return event;
|
|
41355
|
+
const copy = structuredClone(event);
|
|
41356
|
+
for (const path of paths) {
|
|
41357
|
+
setPath(copy, path, replacement);
|
|
41358
|
+
}
|
|
41359
|
+
return copy;
|
|
41360
|
+
}
|
|
41361
|
+
function sanitizeChannelForOutput(channel) {
|
|
41362
|
+
const copy = structuredClone(channel);
|
|
41363
|
+
if (copy.webhook?.secret)
|
|
41364
|
+
copy.webhook.secret = "[REDACTED]";
|
|
41365
|
+
if (copy.command?.env) {
|
|
41366
|
+
copy.command.env = Object.fromEntries(Object.entries(copy.command.env).map(([key, value]) => [key, shouldRedactKey(key) ? "[REDACTED]" : value]));
|
|
41367
|
+
}
|
|
41368
|
+
return copy;
|
|
41369
|
+
}
|
|
41370
|
+
function sanitizeChannelsForOutput(channels) {
|
|
41371
|
+
return channels.map(sanitizeChannelForOutput);
|
|
41372
|
+
}
|
|
41373
|
+
function redactSensitiveKeys(event, replacement = "[REDACTED]") {
|
|
41374
|
+
return redactValue(event, replacement);
|
|
41375
|
+
}
|
|
41376
|
+
function shouldRedactKey(key) {
|
|
41377
|
+
return /secret|token|password|api[_-]?key|authorization/i.test(key);
|
|
41378
|
+
}
|
|
41379
|
+
function redactValue(value, replacement) {
|
|
41380
|
+
if (Array.isArray(value))
|
|
41381
|
+
return value.map((item) => redactValue(item, replacement));
|
|
41382
|
+
if (!value || typeof value !== "object")
|
|
41383
|
+
return value;
|
|
41384
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
41385
|
+
key,
|
|
41386
|
+
shouldRedactKey(key) ? replacement : redactValue(item, replacement)
|
|
41387
|
+
]));
|
|
41388
|
+
}
|
|
41389
|
+
function setPath(input, path, replacement) {
|
|
41390
|
+
const parts = path.split(".");
|
|
41391
|
+
let cursor = input;
|
|
41392
|
+
for (const part of parts.slice(0, -1)) {
|
|
41393
|
+
const next = cursor[part];
|
|
41394
|
+
if (!next || typeof next !== "object")
|
|
41395
|
+
return;
|
|
41396
|
+
cursor = next;
|
|
41397
|
+
}
|
|
41398
|
+
const last = parts.at(-1);
|
|
41399
|
+
if (last && last in cursor)
|
|
41400
|
+
cursor[last] = replacement;
|
|
41401
|
+
}
|
|
41402
|
+
function normalizeTime(value) {
|
|
41403
|
+
if (!value)
|
|
41404
|
+
return new Date().toISOString();
|
|
41405
|
+
return value instanceof Date ? value.toISOString() : value;
|
|
41406
|
+
}
|
|
41407
|
+
function normalizeRetryPolicy(policy) {
|
|
41408
|
+
return {
|
|
41409
|
+
maxAttempts: Math.max(1, policy?.maxAttempts ?? 1),
|
|
41410
|
+
backoffMs: Math.max(0, policy?.backoffMs ?? 250),
|
|
41411
|
+
multiplier: Math.max(1, policy?.multiplier ?? 2)
|
|
41412
|
+
};
|
|
41413
|
+
}
|
|
41414
|
+
function parseJsonObject(value, fallback) {
|
|
41415
|
+
if (!value)
|
|
41416
|
+
return fallback;
|
|
41417
|
+
const parsed = JSON.parse(value);
|
|
41418
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
41419
|
+
throw new Error("Expected a JSON object");
|
|
41420
|
+
}
|
|
41421
|
+
return parsed;
|
|
41422
|
+
}
|
|
41423
|
+
function parseHeaders(values) {
|
|
41424
|
+
if (!values?.length)
|
|
41425
|
+
return;
|
|
41426
|
+
const headers = {};
|
|
41427
|
+
for (const value of values) {
|
|
41428
|
+
const separator = value.indexOf("=");
|
|
41429
|
+
if (separator === -1)
|
|
41430
|
+
throw new Error(`Invalid header, expected name=value: ${value}`);
|
|
41431
|
+
headers[value.slice(0, separator)] = value.slice(separator + 1);
|
|
41432
|
+
}
|
|
41433
|
+
return headers;
|
|
41434
|
+
}
|
|
41435
|
+
function parseFilter(options) {
|
|
41436
|
+
const filter2 = {};
|
|
41437
|
+
if (options.source)
|
|
41438
|
+
filter2.source = options.source;
|
|
41439
|
+
if (options.type)
|
|
41440
|
+
filter2.type = options.type;
|
|
41441
|
+
if (options.subject)
|
|
41442
|
+
filter2.subject = options.subject;
|
|
41443
|
+
if (options.severity)
|
|
41444
|
+
filter2.severity = options.severity;
|
|
41445
|
+
return Object.keys(filter2).length > 0 ? [filter2] : undefined;
|
|
41446
|
+
}
|
|
41447
|
+
function createClient(options) {
|
|
41448
|
+
if (options.createClient)
|
|
41449
|
+
return options.createClient();
|
|
41450
|
+
return new EventsClient({ store: new JsonEventsStore(options.dataDir) });
|
|
41451
|
+
}
|
|
41452
|
+
function print(value, json, text) {
|
|
41453
|
+
if (json)
|
|
41454
|
+
console.log(JSON.stringify(value, null, 2));
|
|
41455
|
+
else
|
|
41456
|
+
console.log(text);
|
|
41457
|
+
}
|
|
41458
|
+
function registerWebhookCommands(program2, options) {
|
|
41459
|
+
const webhooks = program2.command(options.webhooksCommandName ?? "webhooks").description("Manage Hasna event webhook subscriptions");
|
|
41460
|
+
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) => {
|
|
41461
|
+
const timestamp = new Date().toISOString();
|
|
41462
|
+
const channel = {
|
|
41463
|
+
id: actionOptions.id,
|
|
41464
|
+
name: actionOptions.name,
|
|
41465
|
+
enabled: !actionOptions.disabled,
|
|
41466
|
+
transport: actionOptions.transport,
|
|
41467
|
+
filters: parseFilter(actionOptions),
|
|
41468
|
+
retry: actionOptions.retryAttempts || actionOptions.retryBackoffMs ? { maxAttempts: actionOptions.retryAttempts, backoffMs: actionOptions.retryBackoffMs } : undefined,
|
|
41469
|
+
redact: actionOptions.redact?.length ? { paths: actionOptions.redact } : undefined,
|
|
41470
|
+
createdAt: timestamp,
|
|
41471
|
+
updatedAt: timestamp
|
|
41472
|
+
};
|
|
41473
|
+
if (actionOptions.transport === "webhook") {
|
|
41474
|
+
channel.webhook = { url: target, secret: actionOptions.secret, headers: parseHeaders(actionOptions.header), timeoutMs: actionOptions.timeoutMs };
|
|
41475
|
+
} else if (actionOptions.transport === "command") {
|
|
41476
|
+
channel.command = { command: target, args: actionOptions.arg ?? [], timeoutMs: actionOptions.timeoutMs };
|
|
41477
|
+
} else {
|
|
41478
|
+
throw new Error(`Transport ${actionOptions.transport} is reserved for future use and cannot be added yet`);
|
|
41479
|
+
}
|
|
41480
|
+
const saved = await createClient(options).addChannel(channel);
|
|
41481
|
+
print(sanitizeChannelForOutput(saved), Boolean(actionOptions.json), `Added ${saved.transport} channel ${saved.id}`);
|
|
41482
|
+
});
|
|
41483
|
+
webhooks.command("list").description("List configured subscriptions").option("-j, --json", "Print JSON output", false).action(async (actionOptions) => {
|
|
41484
|
+
const channels = await createClient(options).listChannels();
|
|
41485
|
+
if (actionOptions.json) {
|
|
41486
|
+
console.log(JSON.stringify(sanitizeChannelsForOutput(channels), null, 2));
|
|
41487
|
+
return;
|
|
41488
|
+
}
|
|
41489
|
+
if (!channels.length) {
|
|
41490
|
+
console.log("No channels configured.");
|
|
41491
|
+
return;
|
|
41492
|
+
}
|
|
41493
|
+
for (const channel of channels) {
|
|
41494
|
+
console.log(`${channel.id} ${channel.enabled ? "enabled" : "disabled"} ${channel.transport} ${channel.webhook?.url ?? channel.command?.command ?? channel.transport}`);
|
|
41495
|
+
}
|
|
41496
|
+
});
|
|
41497
|
+
webhooks.command("remove").description("Remove a subscription").argument("<id>", "Subscription/channel identifier").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions) => {
|
|
41498
|
+
const removed = await createClient(options).removeChannel(id);
|
|
41499
|
+
print({ removed }, Boolean(actionOptions.json), removed ? `Removed ${id}` : `Channel not found: ${id}`);
|
|
41500
|
+
});
|
|
41501
|
+
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) => {
|
|
41502
|
+
const result = await createClient(options).testChannel(id, {
|
|
41503
|
+
source: options.source,
|
|
41504
|
+
type: actionOptions.type,
|
|
41505
|
+
subject: actionOptions.subject ?? id,
|
|
41506
|
+
message: actionOptions.message,
|
|
41507
|
+
data: parseJsonObject(actionOptions.data, { test: true })
|
|
41508
|
+
});
|
|
41509
|
+
print(result, Boolean(actionOptions.json), `${result.status}: ${result.channelId}`);
|
|
41510
|
+
});
|
|
41511
|
+
return webhooks;
|
|
41512
|
+
}
|
|
41513
|
+
function registerEventCommands(program2, options) {
|
|
41514
|
+
const events = program2.command(options.eventsCommandName ?? "events").description("Emit, list, and replay Hasna events");
|
|
41515
|
+
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) => {
|
|
41516
|
+
const result = await createClient(options).emit({
|
|
41517
|
+
source: actionOptions.source ?? options.source,
|
|
41518
|
+
type,
|
|
41519
|
+
subject: actionOptions.subject,
|
|
41520
|
+
severity: actionOptions.severity,
|
|
41521
|
+
message: actionOptions.message,
|
|
41522
|
+
dedupeKey: actionOptions.dedupeKey,
|
|
41523
|
+
data: parseJsonObject(actionOptions.data, {}),
|
|
41524
|
+
metadata: parseJsonObject(actionOptions.metadata, {})
|
|
41525
|
+
}, { deliver: actionOptions.deliver, dedupe: actionOptions.dedupe });
|
|
41526
|
+
print(result, Boolean(actionOptions.json), `${result.deduped ? "Deduped" : "Emitted"} ${result.event.id} to ${result.deliveries.length} channel(s)`);
|
|
41527
|
+
});
|
|
41528
|
+
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) => {
|
|
41529
|
+
let rows = await createClient(options).listEvents();
|
|
41530
|
+
if (actionOptions.source)
|
|
41531
|
+
rows = rows.filter((event) => event.source === actionOptions.source);
|
|
41532
|
+
if (actionOptions.type)
|
|
41533
|
+
rows = rows.filter((event) => event.type === actionOptions.type);
|
|
41534
|
+
if (actionOptions.limit)
|
|
41535
|
+
rows = rows.slice(-actionOptions.limit);
|
|
41536
|
+
if (actionOptions.json) {
|
|
41537
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
41538
|
+
return;
|
|
41539
|
+
}
|
|
41540
|
+
if (!rows.length) {
|
|
41541
|
+
console.log("No events recorded.");
|
|
41542
|
+
return;
|
|
41543
|
+
}
|
|
41544
|
+
for (const event of rows)
|
|
41545
|
+
console.log(`${event.time} ${event.id} ${event.source} ${event.type} ${event.severity}`);
|
|
41546
|
+
});
|
|
41547
|
+
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) => {
|
|
41548
|
+
const result = await createClient(options).replay({
|
|
41549
|
+
eventId: actionOptions.id,
|
|
41550
|
+
source: actionOptions.source,
|
|
41551
|
+
type: actionOptions.type,
|
|
41552
|
+
dryRun: actionOptions.dryRun
|
|
41553
|
+
});
|
|
41554
|
+
print(result, Boolean(actionOptions.json), `Replayed ${result.events.length} event(s), ${result.deliveries.length} delivery result(s)`);
|
|
41555
|
+
});
|
|
41556
|
+
return events;
|
|
41557
|
+
}
|
|
41558
|
+
function registerEventsCommands(program2, options) {
|
|
41559
|
+
registerWebhookCommands(program2, options);
|
|
41560
|
+
registerEventCommands(program2, options);
|
|
41561
|
+
}
|
|
41562
|
+
function parseNumber(value) {
|
|
41563
|
+
const parsed = Number(value);
|
|
41564
|
+
if (!Number.isFinite(parsed))
|
|
41565
|
+
throw new Error(`Expected a number, got ${value}`);
|
|
41566
|
+
return parsed;
|
|
41567
|
+
}
|
|
41568
|
+
function collectValues(value, previous) {
|
|
41569
|
+
previous.push(value);
|
|
41570
|
+
return previous;
|
|
41571
|
+
}
|
|
41572
|
+
|
|
40556
41573
|
// src/cli/commands/domain.ts
|
|
40557
41574
|
init_domains();
|
|
40558
41575
|
|
|
@@ -40655,6 +41672,61 @@ async function renewDomain(domain, years = 1, config) {
|
|
|
40655
41672
|
chargedAmount: chargedAmount || undefined
|
|
40656
41673
|
};
|
|
40657
41674
|
}
|
|
41675
|
+
function namecheapContactParams(contact) {
|
|
41676
|
+
const organization = contact.organization_name || "NA";
|
|
41677
|
+
const base = {
|
|
41678
|
+
FirstName: contact.first_name,
|
|
41679
|
+
LastName: contact.last_name,
|
|
41680
|
+
OrganizationName: organization,
|
|
41681
|
+
Address1: contact.address_line_1,
|
|
41682
|
+
City: contact.city,
|
|
41683
|
+
StateProvince: contact.state,
|
|
41684
|
+
PostalCode: contact.zip_code,
|
|
41685
|
+
Country: contact.country_code,
|
|
41686
|
+
Phone: contact.phone,
|
|
41687
|
+
EmailAddress: contact.email
|
|
41688
|
+
};
|
|
41689
|
+
const params = {};
|
|
41690
|
+
for (const prefix of ["Registrant", "Tech", "Admin", "AuxBilling"]) {
|
|
41691
|
+
for (const [key, value] of Object.entries(base)) {
|
|
41692
|
+
params[prefix + key] = value;
|
|
41693
|
+
}
|
|
41694
|
+
}
|
|
41695
|
+
return params;
|
|
41696
|
+
}
|
|
41697
|
+
async function registerDomain(domain, contact, options = {}, config) {
|
|
41698
|
+
const cfg = config || getConfig();
|
|
41699
|
+
const params = {
|
|
41700
|
+
DomainName: domain,
|
|
41701
|
+
Years: String(options.years ?? 1),
|
|
41702
|
+
AddFreeWhoisguard: options.whoisGuard === false ? "no" : "yes",
|
|
41703
|
+
WGEnabled: options.whoisGuard === false ? "no" : "yes",
|
|
41704
|
+
...namecheapContactParams(contact)
|
|
41705
|
+
};
|
|
41706
|
+
if (options.premiumPrice !== undefined) {
|
|
41707
|
+
params.IsPremiumDomain = "true";
|
|
41708
|
+
params.PremiumPrice = String(options.premiumPrice);
|
|
41709
|
+
}
|
|
41710
|
+
const xml = await apiRequest(cfg, "namecheap.domains.create", params);
|
|
41711
|
+
return {
|
|
41712
|
+
domain,
|
|
41713
|
+
success: xml.includes('Status="OK"') || xml.includes('Registered="true"'),
|
|
41714
|
+
orderId: parseXmlValue(xml, "OrderId") || undefined,
|
|
41715
|
+
chargedAmount: parseXmlValue(xml, "ChargedAmount") || undefined
|
|
41716
|
+
};
|
|
41717
|
+
}
|
|
41718
|
+
async function updateNameservers(domain, nameservers, config) {
|
|
41719
|
+
if (nameservers.length === 0)
|
|
41720
|
+
throw new Error("updateNameservers requires at least one nameserver");
|
|
41721
|
+
const cfg = config || getConfig();
|
|
41722
|
+
const { sld, tld } = splitDomain(domain);
|
|
41723
|
+
const xml = await apiRequest(cfg, "namecheap.domains.dns.setCustom", {
|
|
41724
|
+
SLD: sld,
|
|
41725
|
+
TLD: tld,
|
|
41726
|
+
Nameservers: nameservers.join(",")
|
|
41727
|
+
});
|
|
41728
|
+
return xml.includes('Status="OK"') || xml.includes('Updated="true"');
|
|
41729
|
+
}
|
|
40658
41730
|
async function getDnsRecords(_domain, sld, tld, config) {
|
|
40659
41731
|
const cfg = config || getConfig();
|
|
40660
41732
|
const xml = await apiRequest(cfg, "namecheap.domains.dns.getHosts", {
|
|
@@ -40783,141 +41855,12 @@ function normalizeDate(dateStr) {
|
|
|
40783
41855
|
}
|
|
40784
41856
|
}
|
|
40785
41857
|
|
|
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
41858
|
// src/lib/registrar.ts
|
|
41859
|
+
init_godaddy();
|
|
40918
41860
|
init_route53();
|
|
40919
41861
|
init_cloudflare();
|
|
40920
41862
|
init_brandsight();
|
|
41863
|
+
init_env_aliases();
|
|
40921
41864
|
function createNamecheapProvider() {
|
|
40922
41865
|
return {
|
|
40923
41866
|
name: "namecheap",
|
|
@@ -40947,9 +41890,27 @@ function createNamecheapProvider() {
|
|
|
40947
41890
|
auto_renew: true
|
|
40948
41891
|
};
|
|
40949
41892
|
},
|
|
40950
|
-
async
|
|
41893
|
+
async registerDomain(domain, contact, options = {}) {
|
|
40951
41894
|
const config = getConfig();
|
|
40952
|
-
const result = await
|
|
41895
|
+
const result = await registerDomain(domain, contact, {
|
|
41896
|
+
years: options.years,
|
|
41897
|
+
premiumPrice: options.premiumPrice
|
|
41898
|
+
}, config);
|
|
41899
|
+
return {
|
|
41900
|
+
domain: result.domain,
|
|
41901
|
+
success: result.success,
|
|
41902
|
+
orderId: result.orderId,
|
|
41903
|
+
chargedAmount: result.chargedAmount
|
|
41904
|
+
};
|
|
41905
|
+
},
|
|
41906
|
+
async updateNameservers(domain, nameservers) {
|
|
41907
|
+
const config = getConfig();
|
|
41908
|
+
const success = await updateNameservers(domain, nameservers, config);
|
|
41909
|
+
return { domain, success };
|
|
41910
|
+
},
|
|
41911
|
+
async renewDomain(domain, years = 1) {
|
|
41912
|
+
const config = getConfig();
|
|
41913
|
+
const result = await renewDomain(domain, years, config);
|
|
40953
41914
|
return {
|
|
40954
41915
|
domain: result.domain,
|
|
40955
41916
|
success: result.success,
|
|
@@ -41085,7 +42046,7 @@ var providerRegistry = new Map([
|
|
|
41085
42046
|
name: "namecheap",
|
|
41086
42047
|
type: "full",
|
|
41087
42048
|
configured: false,
|
|
41088
|
-
envVars:
|
|
42049
|
+
envVars: providerEnvNames("namecheap")
|
|
41089
42050
|
},
|
|
41090
42051
|
createRegistrar: createNamecheapProvider,
|
|
41091
42052
|
createDns: createNamecheapProvider
|
|
@@ -41095,7 +42056,7 @@ var providerRegistry = new Map([
|
|
|
41095
42056
|
name: "godaddy",
|
|
41096
42057
|
type: "full",
|
|
41097
42058
|
configured: false,
|
|
41098
|
-
envVars:
|
|
42059
|
+
envVars: providerEnvNames("godaddy")
|
|
41099
42060
|
},
|
|
41100
42061
|
createRegistrar: createGoDaddyProvider,
|
|
41101
42062
|
createDns: createGoDaddyProvider
|
|
@@ -41105,7 +42066,7 @@ var providerRegistry = new Map([
|
|
|
41105
42066
|
name: "route53",
|
|
41106
42067
|
type: "full",
|
|
41107
42068
|
configured: false,
|
|
41108
|
-
envVars:
|
|
42069
|
+
envVars: providerEnvNames("route53")
|
|
41109
42070
|
},
|
|
41110
42071
|
createRegistrar: () => createRoute53Provider(),
|
|
41111
42072
|
createDns: () => createRoute53Provider()
|
|
@@ -41115,7 +42076,7 @@ var providerRegistry = new Map([
|
|
|
41115
42076
|
name: "cloudflare",
|
|
41116
42077
|
type: "dns",
|
|
41117
42078
|
configured: false,
|
|
41118
|
-
envVars:
|
|
42079
|
+
envVars: providerEnvNames("cloudflare")
|
|
41119
42080
|
},
|
|
41120
42081
|
createDns: createCloudflareProvider
|
|
41121
42082
|
}],
|
|
@@ -41124,34 +42085,51 @@ var providerRegistry = new Map([
|
|
|
41124
42085
|
name: "brandsight",
|
|
41125
42086
|
type: "registrar",
|
|
41126
42087
|
configured: false,
|
|
41127
|
-
envVars:
|
|
42088
|
+
envVars: providerEnvNames("brandsight")
|
|
41128
42089
|
},
|
|
41129
42090
|
createRegistrar: createBrandsightProvider
|
|
42091
|
+
}],
|
|
42092
|
+
["sedo", {
|
|
42093
|
+
info: {
|
|
42094
|
+
name: "sedo",
|
|
42095
|
+
type: "marketplace",
|
|
42096
|
+
configured: false,
|
|
42097
|
+
envVars: providerEnvNames("sedo")
|
|
42098
|
+
}
|
|
41130
42099
|
}]
|
|
41131
42100
|
]);
|
|
41132
|
-
function isConfigured(
|
|
41133
|
-
return
|
|
42101
|
+
function isConfigured(providerName) {
|
|
42102
|
+
return hasProviderCredentials(providerName);
|
|
41134
42103
|
}
|
|
41135
42104
|
function getAvailableProviders() {
|
|
41136
42105
|
return Array.from(providerRegistry.values()).map((e3) => ({
|
|
41137
42106
|
...e3.info,
|
|
41138
|
-
configured: isConfigured(e3.info.
|
|
42107
|
+
configured: isConfigured(e3.info.name)
|
|
41139
42108
|
}));
|
|
41140
42109
|
}
|
|
42110
|
+
function getProviderInfo(name) {
|
|
42111
|
+
const entry = providerRegistry.get(name.toLowerCase());
|
|
42112
|
+
if (!entry)
|
|
42113
|
+
return null;
|
|
42114
|
+
return { ...entry.info, configured: isConfigured(entry.info.name) };
|
|
42115
|
+
}
|
|
42116
|
+
function providerHasRegistrar(name) {
|
|
42117
|
+
return !!providerRegistry.get(name.toLowerCase())?.createRegistrar;
|
|
42118
|
+
}
|
|
41141
42119
|
function getRegistrarProvider(name) {
|
|
41142
|
-
const entry = providerRegistry.get(name);
|
|
42120
|
+
const entry = providerRegistry.get(name.toLowerCase());
|
|
41143
42121
|
if (!entry?.createRegistrar)
|
|
41144
42122
|
throw new Error(`No registrar provider: ${name}`);
|
|
41145
42123
|
return entry.createRegistrar();
|
|
41146
42124
|
}
|
|
41147
42125
|
function getDnsProvider(name) {
|
|
41148
|
-
const entry = providerRegistry.get(name);
|
|
42126
|
+
const entry = providerRegistry.get(name.toLowerCase());
|
|
41149
42127
|
if (!entry?.createDns)
|
|
41150
42128
|
throw new Error(`No DNS provider: ${name}`);
|
|
41151
42129
|
return entry.createDns();
|
|
41152
42130
|
}
|
|
41153
42131
|
async function syncAll(dbFns) {
|
|
41154
|
-
const available = getAvailableProviders().filter((p3) => p3.configured && (p3.
|
|
42132
|
+
const available = getAvailableProviders().filter((p3) => p3.configured && providerHasRegistrar(p3.name));
|
|
41155
42133
|
const result = { providers: [], totalSynced: 0, totalErrors: [] };
|
|
41156
42134
|
for (const info of available) {
|
|
41157
42135
|
try {
|
|
@@ -41520,7 +42498,7 @@ WHOIS for ${result.domain} [${result.source}]:`);
|
|
|
41520
42498
|
process.exit(1);
|
|
41521
42499
|
});
|
|
41522
42500
|
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.type
|
|
42501
|
+
const providers = opts.provider ? [opts.provider] : getAvailableProviders().filter((p3) => p3.configured && (p3.type === "registrar" || p3.type === "full")).map((p3) => p3.name);
|
|
41524
42502
|
for (const name of providers) {
|
|
41525
42503
|
try {
|
|
41526
42504
|
const provider = getRegistrarProvider(name);
|
|
@@ -41607,14 +42585,14 @@ WHOIS for ${result.domain} [${result.source}]:`);
|
|
|
41607
42585
|
}
|
|
41608
42586
|
console.log(`Linked ${emailId} to ${details.domain.name}`);
|
|
41609
42587
|
});
|
|
41610
|
-
domain.command("renew <name>").description("Renew a domain via its registrar provider").option("--provider <name>", "Override provider").action(async (name, opts) => {
|
|
42588
|
+
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
42589
|
const providerName = opts.provider ?? autoDetectRegistrar(name, getDomainByName) ?? loadConfig3().default_registrar;
|
|
41612
42590
|
if (!providerName) {
|
|
41613
42591
|
console.error("Could not detect provider. Use --provider.");
|
|
41614
42592
|
process.exit(1);
|
|
41615
42593
|
}
|
|
41616
42594
|
const provider = getRegistrarProvider(providerName);
|
|
41617
|
-
const result = await provider.renewDomain(name);
|
|
42595
|
+
const result = await provider.renewDomain(name, parseInt(opts.years, 10));
|
|
41618
42596
|
if (result.success) {
|
|
41619
42597
|
console.log(`\u2713 Renewed: ${name}`);
|
|
41620
42598
|
if (result.orderId)
|
|
@@ -41624,7 +42602,7 @@ WHOIS for ${result.domain} [${result.source}]:`);
|
|
|
41624
42602
|
process.exit(1);
|
|
41625
42603
|
}
|
|
41626
42604
|
});
|
|
41627
|
-
domain.command("buy <name>").description("Purchase a domain via
|
|
42605
|
+
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
42606
|
const recordedPrice = parseOptionalNumber(opts.price, "--price");
|
|
41629
42607
|
if (recordedPrice !== undefined) {
|
|
41630
42608
|
const registrarName = opts.registrar ?? opts.provider ?? "manual";
|
|
@@ -41648,12 +42626,75 @@ WHOIS for ${result.domain} [${result.source}]:`);
|
|
|
41648
42626
|
}
|
|
41649
42627
|
const cfg = loadConfig3();
|
|
41650
42628
|
const providerName = opts.registrar ?? opts.provider ?? cfg.default_registrar ?? "route53";
|
|
41651
|
-
const purchaseProfile = applyPurchaseProfile();
|
|
42629
|
+
const purchaseProfile = providerName === "route53" ? applyPurchaseProfile() : undefined;
|
|
41652
42630
|
if (purchaseProfile)
|
|
41653
42631
|
console.log(`Using purchase AWS profile: ${purchaseProfile}`);
|
|
41654
42632
|
if (providerName !== "route53") {
|
|
41655
|
-
|
|
41656
|
-
|
|
42633
|
+
try {
|
|
42634
|
+
const provider = getRegistrarProvider(providerName);
|
|
42635
|
+
if (!provider.registerDomain) {
|
|
42636
|
+
console.error(`Direct domain purchase is not supported for ${providerName}. Use route53 or record a marketplace/manual purchase with --price.`);
|
|
42637
|
+
process.exit(1);
|
|
42638
|
+
}
|
|
42639
|
+
const avail = await provider.checkAvailability(name);
|
|
42640
|
+
if (!avail.available) {
|
|
42641
|
+
console.error(`\u2717 ${name} is not available`);
|
|
42642
|
+
process.exit(1);
|
|
42643
|
+
}
|
|
42644
|
+
console.log(`\u2713 Available via ${providerName}`);
|
|
42645
|
+
let contact;
|
|
42646
|
+
try {
|
|
42647
|
+
contact = resolveContact(opts);
|
|
42648
|
+
} catch (e3) {
|
|
42649
|
+
console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
|
|
42650
|
+
process.exit(1);
|
|
42651
|
+
}
|
|
42652
|
+
console.log(`Registering ${name} via ${providerName}...`);
|
|
42653
|
+
const reg = await provider.registerDomain(name, contact, {
|
|
42654
|
+
years: parseInt(opts.years, 10),
|
|
42655
|
+
premiumPrice: avail.premium_price,
|
|
42656
|
+
autoRenew: opts.autoRenew ? opts.autoRenew === "true" : true
|
|
42657
|
+
});
|
|
42658
|
+
if (!reg.success) {
|
|
42659
|
+
console.error(`\u2717 Registration failed via ${providerName}`);
|
|
42660
|
+
process.exit(1);
|
|
42661
|
+
}
|
|
42662
|
+
const existing = getDomainByName(name);
|
|
42663
|
+
const dbInput = {
|
|
42664
|
+
registrar: providerName,
|
|
42665
|
+
status: "active",
|
|
42666
|
+
auto_renew: opts.autoRenew ? opts.autoRenew === "true" : true,
|
|
42667
|
+
purchase_price: reg.chargedAmount ? Number(reg.chargedAmount) : avail.standard_price,
|
|
42668
|
+
purchase_date: new Date().toISOString(),
|
|
42669
|
+
standard_price: avail.standard_price,
|
|
42670
|
+
is_premium: avail.is_premium,
|
|
42671
|
+
premium_price: avail.premium_price
|
|
42672
|
+
};
|
|
42673
|
+
if (existing)
|
|
42674
|
+
updateDomain(existing.id, dbInput);
|
|
42675
|
+
else
|
|
42676
|
+
createDomain({ name, ...dbInput });
|
|
42677
|
+
console.log(`\u2713 Registered and added to portfolio`);
|
|
42678
|
+
if (reg.orderId)
|
|
42679
|
+
console.log(` Order: ${reg.orderId}`);
|
|
42680
|
+
const dnsProvider = opts.dns ?? "cloudflare";
|
|
42681
|
+
if (opts.delegate !== false && dnsProvider === "cloudflare") {
|
|
42682
|
+
if (!provider.updateNameservers) {
|
|
42683
|
+
console.log(` DNS: ${providerName} registration succeeded, but nameserver updates are not implemented for this provider.`);
|
|
42684
|
+
} else {
|
|
42685
|
+
const zone = await ensureZone(name);
|
|
42686
|
+
const nsUpdate = await provider.updateNameservers(name, zone.nameservers);
|
|
42687
|
+
const existing2 = getDomainByName(name);
|
|
42688
|
+
if (existing2)
|
|
42689
|
+
updateDomain(existing2.id, { nameservers: zone.nameservers });
|
|
42690
|
+
console.log(`\u2713 Cloudflare zone ${zone.id}; nameservers updated${nsUpdate.operationId ? ` (op ${nsUpdate.operationId})` : ""}`);
|
|
42691
|
+
}
|
|
42692
|
+
}
|
|
42693
|
+
return;
|
|
42694
|
+
} catch (e3) {
|
|
42695
|
+
console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
|
|
42696
|
+
process.exit(1);
|
|
42697
|
+
}
|
|
41657
42698
|
}
|
|
41658
42699
|
try {
|
|
41659
42700
|
const avail = await checkAvailability3(name);
|
|
@@ -41671,7 +42712,7 @@ WHOIS for ${result.domain} [${result.source}]:`);
|
|
|
41671
42712
|
process.exit(1);
|
|
41672
42713
|
}
|
|
41673
42714
|
console.log(`Registering ${name}...`);
|
|
41674
|
-
const reg = await
|
|
42715
|
+
const reg = await registerDomain2(name, contact, parseInt(opts.years));
|
|
41675
42716
|
console.log(`\u2713 Submitted (operation: ${reg.operationId})`);
|
|
41676
42717
|
if (opts.wait) {
|
|
41677
42718
|
let status = "IN_PROGRESS";
|
|
@@ -41719,10 +42760,10 @@ WHOIS for ${result.domain} [${result.source}]:`);
|
|
|
41719
42760
|
console.log(`Delegating DNS to Cloudflare...`);
|
|
41720
42761
|
const del = await delegateDomainToCloudflare(name, {
|
|
41721
42762
|
createCloudflareZone: async (d3) => {
|
|
41722
|
-
const z2 = await
|
|
42763
|
+
const z2 = await ensureZone(d3);
|
|
41723
42764
|
return { id: z2.id, nameservers: z2.nameservers };
|
|
41724
42765
|
},
|
|
41725
|
-
updateNameservers: (d3, ns) =>
|
|
42766
|
+
updateNameservers: (d3, ns) => updateNameservers2(d3, ns)
|
|
41726
42767
|
});
|
|
41727
42768
|
const existing2 = getDomainByName(name);
|
|
41728
42769
|
if (existing2)
|
|
@@ -41741,16 +42782,16 @@ WHOIS for ${result.domain} [${result.source}]:`);
|
|
|
41741
42782
|
process.exit(1);
|
|
41742
42783
|
}
|
|
41743
42784
|
});
|
|
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
|
|
42785
|
+
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
42786
|
const cfg = loadConfig3();
|
|
41746
42787
|
const registrarName = opts.registrar ?? cfg.default_registrar ?? "route53";
|
|
41747
|
-
const dnsName = opts.dns ?? cfg.default_dns ??
|
|
42788
|
+
const dnsName = opts.dns ?? cfg.default_dns ?? "cloudflare";
|
|
41748
42789
|
console.log(`
|
|
41749
42790
|
Setting up ${name}`);
|
|
41750
42791
|
console.log(` Registrar: ${registrarName} | DNS: ${dnsName}
|
|
41751
42792
|
`);
|
|
41752
42793
|
try {
|
|
41753
|
-
process.stdout.write("[1/4] Checking availability... ");
|
|
42794
|
+
process.stdout.write(opts.wait ? "[1/5] Checking availability... " : "[1/4] Checking availability... ");
|
|
41754
42795
|
const avail = await checkAvailability3(name);
|
|
41755
42796
|
if (!avail.available) {
|
|
41756
42797
|
console.log("not available");
|
|
@@ -41770,8 +42811,8 @@ Setting up ${name}`);
|
|
|
41770
42811
|
console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
|
|
41771
42812
|
process.exit(1);
|
|
41772
42813
|
}
|
|
41773
|
-
process.stdout.write("[2/4] Registering domain... ");
|
|
41774
|
-
const reg = await
|
|
42814
|
+
process.stdout.write(opts.wait ? "[2/5] Registering domain... " : "[2/4] Registering domain... ");
|
|
42815
|
+
const reg = await registerDomain2(name, contact, parseInt(opts.years));
|
|
41775
42816
|
console.log(`submitted (${reg.operationId})`);
|
|
41776
42817
|
if (opts.wait) {
|
|
41777
42818
|
let status = "IN_PROGRESS";
|
|
@@ -41788,19 +42829,38 @@ Setting up ${name}`);
|
|
|
41788
42829
|
}
|
|
41789
42830
|
console.log(" Registration confirmed");
|
|
41790
42831
|
}
|
|
41791
|
-
process.stdout.write("[3/4] Creating DNS zone... ");
|
|
42832
|
+
process.stdout.write(opts.wait ? "[3/5] Creating DNS zone... " : "[3/4] Creating DNS zone... ");
|
|
41792
42833
|
let nameservers = [];
|
|
41793
42834
|
if (dnsName === "cloudflare") {
|
|
41794
|
-
const zone = await
|
|
42835
|
+
const zone = await ensureZone(name);
|
|
41795
42836
|
nameservers = zone.nameservers ?? [];
|
|
41796
|
-
console.log(
|
|
42837
|
+
console.log("ready (" + zone.id + ")");
|
|
41797
42838
|
} else {
|
|
41798
42839
|
const zone = await createHostedZone(name, `Managed by @hasna/domains`);
|
|
41799
42840
|
nameservers = zone.name_servers ?? [];
|
|
41800
42841
|
console.log(`created (${zone.id})`);
|
|
41801
42842
|
}
|
|
41802
|
-
|
|
41803
|
-
|
|
42843
|
+
if (nameservers.length > 0) {
|
|
42844
|
+
if (opts.wait) {
|
|
42845
|
+
process.stdout.write("[4/5] Updating registrar nameservers... ");
|
|
42846
|
+
if (registrarName !== "route53") {
|
|
42847
|
+
console.log("skipped");
|
|
42848
|
+
console.error("Only Route53 nameserver delegation is currently implemented for setup.");
|
|
42849
|
+
process.exit(1);
|
|
42850
|
+
}
|
|
42851
|
+
const nsUpdate = await updateNameservers2(name, nameservers);
|
|
42852
|
+
console.log("submitted (" + nsUpdate.operationId + ")");
|
|
42853
|
+
} else {
|
|
42854
|
+
console.log(" Nameserver delegation skipped until registration completes; re-run with --wait or use domains r53 domain-info/status first.");
|
|
42855
|
+
}
|
|
42856
|
+
}
|
|
42857
|
+
process.stdout.write(opts.wait ? "[5/5] Adding to portfolio... " : "[4/4] Adding to portfolio... ");
|
|
42858
|
+
const existing = getDomainByName(name);
|
|
42859
|
+
const dbInput = { registrar: `AWS Route 53`, status: "active", auto_renew: true, nameservers };
|
|
42860
|
+
if (existing)
|
|
42861
|
+
updateDomain(existing.id, dbInput);
|
|
42862
|
+
else
|
|
42863
|
+
createDomain({ name, ...dbInput });
|
|
41804
42864
|
console.log("done");
|
|
41805
42865
|
console.log(`
|
|
41806
42866
|
\u2713 Setup complete for ${name}`);
|
|
@@ -42315,22 +43375,23 @@ Registrar / DNS Providers:`);
|
|
|
42315
43375
|
console.log();
|
|
42316
43376
|
});
|
|
42317
43377
|
provider.command("test <name>").description("Live-test a provider's credentials").option("-j, --json", "Output JSON").action(async (name, opts) => {
|
|
43378
|
+
const providerName = name.toLowerCase();
|
|
42318
43379
|
const providers = getAvailableProviders();
|
|
42319
|
-
const info = providers.find((p3) => p3.name ===
|
|
43380
|
+
const info = providers.find((p3) => p3.name === providerName);
|
|
42320
43381
|
if (!info) {
|
|
42321
43382
|
const error = `Unknown provider: ${name}`;
|
|
42322
43383
|
if (opts.json) {
|
|
42323
|
-
console.log(JSON.stringify({ provider:
|
|
43384
|
+
console.log(JSON.stringify({ provider: providerName, ok: false, error }, null, 2));
|
|
42324
43385
|
} else {
|
|
42325
43386
|
console.error(error);
|
|
42326
43387
|
}
|
|
42327
43388
|
process.exit(1);
|
|
42328
43389
|
}
|
|
42329
43390
|
if (!info.configured) {
|
|
42330
|
-
const error = `${
|
|
43391
|
+
const error = `${providerName} is not configured. Set: ${info.envVars.join(", ")}`;
|
|
42331
43392
|
if (opts.json) {
|
|
42332
43393
|
console.log(JSON.stringify({
|
|
42333
|
-
provider:
|
|
43394
|
+
provider: providerName,
|
|
42334
43395
|
ok: false,
|
|
42335
43396
|
configured: false,
|
|
42336
43397
|
required_env: info.envVars,
|
|
@@ -42342,33 +43403,44 @@ Registrar / DNS Providers:`);
|
|
|
42342
43403
|
process.exit(1);
|
|
42343
43404
|
}
|
|
42344
43405
|
if (!opts.json) {
|
|
42345
|
-
console.log(`Testing ${
|
|
43406
|
+
console.log(`Testing ${providerName}...`);
|
|
42346
43407
|
}
|
|
42347
43408
|
let registrarOk = null;
|
|
42348
43409
|
let dnsOk = null;
|
|
43410
|
+
let marketplaceOk = null;
|
|
42349
43411
|
try {
|
|
42350
43412
|
if (info.type === "registrar" || info.type === "full") {
|
|
42351
|
-
const reg = getRegistrarProvider(
|
|
43413
|
+
const reg = getRegistrarProvider(providerName);
|
|
42352
43414
|
await reg.listDomains();
|
|
42353
43415
|
registrarOk = true;
|
|
42354
43416
|
if (!opts.json)
|
|
42355
43417
|
console.log("\u2713 Registrar connection OK");
|
|
42356
43418
|
}
|
|
42357
43419
|
if (info.type === "dns" || info.type === "full") {
|
|
42358
|
-
const dns = getDnsProvider(
|
|
43420
|
+
const dns = getDnsProvider(providerName);
|
|
42359
43421
|
await dns.getDnsRecords("__test_nonexistent_domain__.invalid");
|
|
42360
43422
|
dnsOk = true;
|
|
42361
43423
|
if (!opts.json)
|
|
42362
43424
|
console.log("\u2713 DNS connection OK");
|
|
42363
43425
|
}
|
|
43426
|
+
if (info.type === "marketplace") {
|
|
43427
|
+
if (providerName !== "sedo")
|
|
43428
|
+
throw new Error("No marketplace tester for provider: " + providerName);
|
|
43429
|
+
const { listSedoPortfolio: listSedoPortfolio2 } = await Promise.resolve().then(() => (init_sedo(), exports_sedo));
|
|
43430
|
+
await listSedoPortfolio2({ limit: 1 });
|
|
43431
|
+
marketplaceOk = true;
|
|
43432
|
+
if (!opts.json)
|
|
43433
|
+
console.log("\u2713 Marketplace connection OK");
|
|
43434
|
+
}
|
|
42364
43435
|
if (opts.json) {
|
|
42365
43436
|
console.log(JSON.stringify({
|
|
42366
|
-
provider:
|
|
43437
|
+
provider: providerName,
|
|
42367
43438
|
ok: true,
|
|
42368
43439
|
configured: true,
|
|
42369
43440
|
type: info.type,
|
|
42370
43441
|
registrar_ok: registrarOk,
|
|
42371
|
-
dns_ok: dnsOk
|
|
43442
|
+
dns_ok: dnsOk,
|
|
43443
|
+
marketplace_ok: marketplaceOk
|
|
42372
43444
|
}, null, 2));
|
|
42373
43445
|
}
|
|
42374
43446
|
} catch (e3) {
|
|
@@ -42376,12 +43448,13 @@ Registrar / DNS Providers:`);
|
|
|
42376
43448
|
if (msg.includes("not found") || msg.includes("No hosted zone") || msg.includes("NXDOMAIN")) {
|
|
42377
43449
|
if (opts.json) {
|
|
42378
43450
|
console.log(JSON.stringify({
|
|
42379
|
-
provider:
|
|
43451
|
+
provider: providerName,
|
|
42380
43452
|
ok: true,
|
|
42381
43453
|
configured: true,
|
|
42382
43454
|
type: info.type,
|
|
42383
43455
|
registrar_ok: registrarOk,
|
|
42384
43456
|
dns_ok: true,
|
|
43457
|
+
marketplace_ok: marketplaceOk,
|
|
42385
43458
|
note: "connection-ok-no-domains-yet"
|
|
42386
43459
|
}, null, 2));
|
|
42387
43460
|
} else {
|
|
@@ -42391,16 +43464,17 @@ Registrar / DNS Providers:`);
|
|
|
42391
43464
|
}
|
|
42392
43465
|
if (opts.json) {
|
|
42393
43466
|
console.log(JSON.stringify({
|
|
42394
|
-
provider:
|
|
43467
|
+
provider: providerName,
|
|
42395
43468
|
ok: false,
|
|
42396
43469
|
configured: true,
|
|
42397
43470
|
type: info.type,
|
|
42398
43471
|
registrar_ok: registrarOk,
|
|
42399
43472
|
dns_ok: dnsOk,
|
|
43473
|
+
marketplace_ok: marketplaceOk,
|
|
42400
43474
|
error: msg
|
|
42401
43475
|
}, null, 2));
|
|
42402
43476
|
} else {
|
|
42403
|
-
console.error(`\u2717 ${
|
|
43477
|
+
console.error(`\u2717 ${providerName} test failed: ${msg}`);
|
|
42404
43478
|
}
|
|
42405
43479
|
process.exit(1);
|
|
42406
43480
|
}
|
|
@@ -42410,23 +43484,35 @@ Registrar / DNS Providers:`);
|
|
|
42410
43484
|
// src/cli/commands/providers.ts
|
|
42411
43485
|
init_config();
|
|
42412
43486
|
init_domains();
|
|
43487
|
+
function registrarProviderNames() {
|
|
43488
|
+
return getAvailableProviders().filter((p3) => providerHasRegistrar(p3.name)).map((p3) => p3.name).join(", ");
|
|
43489
|
+
}
|
|
43490
|
+
function requireRegistrarProvider(name) {
|
|
43491
|
+
const info = getProviderInfo(name);
|
|
43492
|
+
if (!info) {
|
|
43493
|
+
throw new Error(`Unknown provider: ${name}. Supported registrars: ${registrarProviderNames()}`);
|
|
43494
|
+
}
|
|
43495
|
+
if (!providerHasRegistrar(name)) {
|
|
43496
|
+
throw new Error(`${name} is a ${info.type} provider, not a registrar. Supported registrars: ${registrarProviderNames()}`);
|
|
43497
|
+
}
|
|
43498
|
+
}
|
|
42413
43499
|
function registerProviderCommands(program2) {
|
|
42414
|
-
program2.command("providers").description("Show which
|
|
43500
|
+
program2.command("providers").description("Show which providers are configured").option("--json", "Output as JSON", false).action((opts) => {
|
|
42415
43501
|
const providers = getAvailableProviders();
|
|
42416
43502
|
if (opts.json) {
|
|
42417
43503
|
console.log(JSON.stringify(providers, null, 2));
|
|
42418
43504
|
} else {
|
|
42419
|
-
console.log("
|
|
43505
|
+
console.log("Providers:");
|
|
42420
43506
|
for (const p3 of providers) {
|
|
42421
43507
|
const status = p3.configured ? "CONFIGURED" : "not configured";
|
|
42422
|
-
console.log(` ${p3.name}: ${status}`);
|
|
43508
|
+
console.log(` ${p3.name} [${p3.type}]: ${status}`);
|
|
42423
43509
|
if (!p3.configured) {
|
|
42424
|
-
console.log(`
|
|
43510
|
+
console.log(` Accepted env: ${p3.envVars.join(", ")}`);
|
|
42425
43511
|
}
|
|
42426
43512
|
}
|
|
42427
43513
|
}
|
|
42428
43514
|
});
|
|
42429
|
-
program2.command("sync").description("Sync domains from a provider to local DB").option("--provider <provider>", "Provider name
|
|
43515
|
+
program2.command("sync").description("Sync domains from a registrar provider to local DB").option("--provider <provider>", "Provider name").option("--all", "Sync from all configured registrar providers").option("--json", "Output as JSON", false).action(async (opts) => {
|
|
42430
43516
|
if (opts.all) {
|
|
42431
43517
|
try {
|
|
42432
43518
|
const result = await syncAll({
|
|
@@ -42439,7 +43525,7 @@ function registerProviderCommands(program2) {
|
|
|
42439
43525
|
} else {
|
|
42440
43526
|
console.log(`Synced ${result.totalSynced} domain(s) from ${result.providers.length} provider(s)`);
|
|
42441
43527
|
for (const p3 of result.providers) {
|
|
42442
|
-
console.log(` ${p3.name}: ${p3.result.synced} synced`);
|
|
43528
|
+
console.log(` ${p3.name}: ${p3.result.synced} synced (${p3.result.created} new, ${p3.result.updated} updated)`);
|
|
42443
43529
|
}
|
|
42444
43530
|
if (result.totalErrors.length > 0) {
|
|
42445
43531
|
console.log("Errors:");
|
|
@@ -42459,59 +43545,29 @@ function registerProviderCommands(program2) {
|
|
|
42459
43545
|
console.error("Specify --provider <name> or --all");
|
|
42460
43546
|
process.exit(1);
|
|
42461
43547
|
}
|
|
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
|
-
}
|
|
43548
|
+
try {
|
|
43549
|
+
requireRegistrarProvider(provider);
|
|
43550
|
+
const result = await getRegistrarProvider(provider).syncToLocalDb({
|
|
43551
|
+
getDomainByName,
|
|
43552
|
+
createDomain,
|
|
43553
|
+
updateDomain
|
|
43554
|
+
});
|
|
43555
|
+
if (opts.json) {
|
|
43556
|
+
console.log(JSON.stringify({ provider, ...result }, null, 2));
|
|
43557
|
+
} else {
|
|
43558
|
+
console.log(`Synced ${result.synced} domain(s) from ${provider} (${result.created} new, ${result.updated} updated)`);
|
|
43559
|
+
if (result.errors.length > 0) {
|
|
43560
|
+
console.log("Errors:");
|
|
43561
|
+
for (const e3 of result.errors)
|
|
43562
|
+
console.log(` - ${e3}`);
|
|
42504
43563
|
}
|
|
42505
|
-
} catch (error) {
|
|
42506
|
-
console.error(`Sync failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
42507
|
-
process.exit(1);
|
|
42508
43564
|
}
|
|
42509
|
-
}
|
|
42510
|
-
console.error(`
|
|
43565
|
+
} catch (error) {
|
|
43566
|
+
console.error(`Sync failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
42511
43567
|
process.exit(1);
|
|
42512
43568
|
}
|
|
42513
43569
|
});
|
|
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
|
|
43570
|
+
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
43571
|
let provider = (opts.provider || "").toLowerCase();
|
|
42516
43572
|
if (!provider) {
|
|
42517
43573
|
const detected = autoDetectRegistrar(name, getDomainByName);
|
|
@@ -42520,53 +43576,44 @@ function registerProviderCommands(program2) {
|
|
|
42520
43576
|
process.exit(1);
|
|
42521
43577
|
}
|
|
42522
43578
|
provider = detected;
|
|
42523
|
-
|
|
43579
|
+
if (!opts.json)
|
|
43580
|
+
console.log(`Auto-detected registrar: ${provider}`);
|
|
42524
43581
|
}
|
|
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);
|
|
43582
|
+
try {
|
|
43583
|
+
requireRegistrarProvider(provider);
|
|
43584
|
+
const result = await getRegistrarProvider(provider).renewDomain(name, parseInt(opts.years, 10));
|
|
43585
|
+
if (!result.success) {
|
|
43586
|
+
throw new Error(`Renewal failed or is not supported for ${provider}`);
|
|
42540
43587
|
}
|
|
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);
|
|
43588
|
+
if (opts.json) {
|
|
43589
|
+
console.log(JSON.stringify({ provider, ...result }, null, 2));
|
|
43590
|
+
} else {
|
|
43591
|
+
console.log(`Renewed ${result.domain} successfully via ${provider}`);
|
|
43592
|
+
if (result.chargedAmount)
|
|
43593
|
+
console.log(` Charged: $${result.chargedAmount}`);
|
|
43594
|
+
if (result.orderId)
|
|
43595
|
+
console.log(` Order ID: ${result.orderId}`);
|
|
42556
43596
|
}
|
|
42557
|
-
}
|
|
42558
|
-
console.error(`
|
|
43597
|
+
} catch (error) {
|
|
43598
|
+
console.error(`Renewal failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
42559
43599
|
process.exit(1);
|
|
42560
43600
|
}
|
|
42561
43601
|
});
|
|
42562
|
-
program2.command("check").description("Check domain availability via a registrar provider
|
|
43602
|
+
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
43603
|
try {
|
|
42564
|
-
const providerName = opts.provider ?? loadConfig3().default_registrar ?? "
|
|
42565
|
-
|
|
43604
|
+
const providerName = (opts.provider ?? loadConfig3().default_registrar ?? "route53").toLowerCase();
|
|
43605
|
+
requireRegistrarProvider(providerName);
|
|
43606
|
+
const result = await getRegistrarProvider(providerName).checkAvailability(name);
|
|
42566
43607
|
if (opts.json) {
|
|
42567
43608
|
console.log(JSON.stringify({ provider: providerName, ...result }, null, 2));
|
|
42568
43609
|
} else {
|
|
42569
43610
|
console.log(`${result.domain} is ${result.available ? "AVAILABLE" : "NOT available"} (via ${providerName})`);
|
|
43611
|
+
if (result.is_premium)
|
|
43612
|
+
console.log(` Premium ask: ${result.premium_price ?? "unknown"}`);
|
|
43613
|
+
if (result.standard_price !== undefined) {
|
|
43614
|
+
const currency = result.currency ? ` ${result.currency}` : "";
|
|
43615
|
+
console.log(` Standard price: ${result.standard_price}${currency}`);
|
|
43616
|
+
}
|
|
42570
43617
|
}
|
|
42571
43618
|
} catch (error) {
|
|
42572
43619
|
console.error(`Availability check failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -42580,6 +43627,7 @@ init_config();
|
|
|
42580
43627
|
var VALID_KEYS = [
|
|
42581
43628
|
"default-registrar",
|
|
42582
43629
|
"default-dns",
|
|
43630
|
+
"purchase-aws-profile",
|
|
42583
43631
|
"contact.first-name",
|
|
42584
43632
|
"contact.last-name",
|
|
42585
43633
|
"contact.email",
|
|
@@ -42601,8 +43649,9 @@ function registerConfigCommands(program2) {
|
|
|
42601
43649
|
}
|
|
42602
43650
|
console.log(`
|
|
42603
43651
|
Configuration (~/.hasna/domains/config.json):`);
|
|
42604
|
-
console.log(` default-registrar:
|
|
42605
|
-
console.log(` default-dns:
|
|
43652
|
+
console.log(` default-registrar: ${cfg.default_registrar ?? "(not set)"}`);
|
|
43653
|
+
console.log(` default-dns: ${cfg.default_dns ?? "(not set)"}`);
|
|
43654
|
+
console.log(` purchase-aws-profile: ${cfg.purchase_aws_profile ?? "(not set)"}`);
|
|
42606
43655
|
if (cfg.contact && Object.keys(cfg.contact).length > 0) {
|
|
42607
43656
|
console.log(`
|
|
42608
43657
|
contact:`);
|
|
@@ -42703,7 +43752,7 @@ function registerDoctorCommand(program2) {
|
|
|
42703
43752
|
fail(`${c3.provider}: not configured (${c3.detail})`);
|
|
42704
43753
|
}
|
|
42705
43754
|
section("Providers");
|
|
42706
|
-
const providers = getAvailableProviders().filter((p3) => p3.
|
|
43755
|
+
const providers = getAvailableProviders().filter((p3) => p3.type !== "marketplace");
|
|
42707
43756
|
for (const p3 of providers) {
|
|
42708
43757
|
if (!p3.configured) {
|
|
42709
43758
|
fail(`${p3.name}: not configured`, `Set: ${p3.envVars.join(", ")}`);
|
|
@@ -42753,15 +43802,15 @@ ${"\u2500".repeat(45)}`);
|
|
|
42753
43802
|
}
|
|
42754
43803
|
|
|
42755
43804
|
// src/cli/commands/mcp-install.ts
|
|
42756
|
-
import { existsSync as
|
|
42757
|
-
import { homedir as
|
|
42758
|
-
import { dirname as dirname4, join as
|
|
43805
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
43806
|
+
import { homedir as homedir5 } from "os";
|
|
43807
|
+
import { dirname as dirname4, join as join5 } from "path";
|
|
42759
43808
|
import { execSync as execSync3 } from "child_process";
|
|
42760
43809
|
var MCP_SERVER_NAME = "domains";
|
|
42761
43810
|
function getClaudeConfigPaths() {
|
|
42762
43811
|
return {
|
|
42763
|
-
global:
|
|
42764
|
-
project:
|
|
43812
|
+
global: join5(homedir5(), ".claude", "claude_desktop_config.json"),
|
|
43813
|
+
project: join5(process.cwd(), ".claude", "settings.json")
|
|
42765
43814
|
};
|
|
42766
43815
|
}
|
|
42767
43816
|
function getMcpBinaryPath() {
|
|
@@ -42773,12 +43822,12 @@ function getMcpBinaryPath() {
|
|
|
42773
43822
|
}
|
|
42774
43823
|
function ensureConfigDir(configPath2) {
|
|
42775
43824
|
const dir = dirname4(configPath2);
|
|
42776
|
-
if (!
|
|
43825
|
+
if (!existsSync4(dir)) {
|
|
42777
43826
|
mkdirSync3(dir, { recursive: true });
|
|
42778
43827
|
}
|
|
42779
43828
|
}
|
|
42780
43829
|
function readConfig(configPath2) {
|
|
42781
|
-
if (!
|
|
43830
|
+
if (!existsSync4(configPath2))
|
|
42782
43831
|
return {};
|
|
42783
43832
|
try {
|
|
42784
43833
|
return JSON.parse(readFileSync5(configPath2, "utf-8"));
|
|
@@ -42807,7 +43856,7 @@ function registerMcpCommand(program2) {
|
|
|
42807
43856
|
mcp.command("uninstall").description("Remove domains MCP server from Claude Code config").option("--project", "Remove from project config instead of global").action((opts) => {
|
|
42808
43857
|
const paths = getClaudeConfigPaths();
|
|
42809
43858
|
const configPath2 = opts.project ? paths.project : paths.global;
|
|
42810
|
-
if (!
|
|
43859
|
+
if (!existsSync4(configPath2)) {
|
|
42811
43860
|
console.log("Config file not found \u2014 nothing to remove.");
|
|
42812
43861
|
return;
|
|
42813
43862
|
}
|
|
@@ -42825,7 +43874,7 @@ function registerMcpCommand(program2) {
|
|
|
42825
43874
|
const paths = getClaudeConfigPaths();
|
|
42826
43875
|
const status = [];
|
|
42827
43876
|
for (const [scope, configPath2] of [["global", paths.global], ["project", paths.project]]) {
|
|
42828
|
-
if (!
|
|
43877
|
+
if (!existsSync4(configPath2)) {
|
|
42829
43878
|
status.push({ scope, config_path: configPath2, exists: false, registered: false });
|
|
42830
43879
|
continue;
|
|
42831
43880
|
}
|
|
@@ -43049,7 +44098,7 @@ function registerRoute53Commands(program2) {
|
|
|
43049
44098
|
process.exit(1);
|
|
43050
44099
|
}
|
|
43051
44100
|
console.log(`Registering ${domain}...`);
|
|
43052
|
-
const result = await
|
|
44101
|
+
const result = await registerDomain2(domain, contact, parseInt(opts.years), opts.autoRenew);
|
|
43053
44102
|
console.log(`\u2713 Registration submitted: ${domain}`);
|
|
43054
44103
|
console.log(` Operation ID: ${result.operationId}`);
|
|
43055
44104
|
console.log(` Check status: domains r53 status ${result.operationId}`);
|
|
@@ -43346,7 +44395,7 @@ DNS Records for ${domain}:`);
|
|
|
43346
44395
|
console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
|
|
43347
44396
|
process.exit(1);
|
|
43348
44397
|
}
|
|
43349
|
-
const reg = await
|
|
44398
|
+
const reg = await registerDomain2(domain, contact, parseInt(opts.years));
|
|
43350
44399
|
console.log(` \u2713 Submitted (operation: ${reg.operationId})`);
|
|
43351
44400
|
console.log(` Waiting for registration to complete...`);
|
|
43352
44401
|
let status = "IN_PROGRESS";
|
|
@@ -43377,7 +44426,7 @@ DNS Records for ${domain}:`);
|
|
|
43377
44426
|
},
|
|
43378
44427
|
getRegistrarNs: async (d3) => (await getDomainDetail(d3)).nameservers,
|
|
43379
44428
|
setRegistrarNs: async (d3, ns) => {
|
|
43380
|
-
await
|
|
44429
|
+
await updateNameservers2(d3, ns);
|
|
43381
44430
|
}
|
|
43382
44431
|
});
|
|
43383
44432
|
console.log(` \u2713 Zone ${setup.created ? "created" : "reused"} (${setup.zoneId})` + (setup.nsUpdated ? ` \u2014 registry NS repointed to this zone` : ``));
|
|
@@ -45554,10 +46603,10 @@ function registerInteractiveCommand(program2) {
|
|
|
45554
46603
|
});
|
|
45555
46604
|
}
|
|
45556
46605
|
|
|
45557
|
-
// src/cli/commands/
|
|
46606
|
+
// src/cli/commands/storage.ts
|
|
45558
46607
|
import chalk2 from "chalk";
|
|
45559
46608
|
|
|
45560
|
-
// src/db/
|
|
46609
|
+
// src/db/storage-sync.ts
|
|
45561
46610
|
init_database();
|
|
45562
46611
|
|
|
45563
46612
|
// src/db/pg-migrations.ts
|
|
@@ -45734,8 +46783,8 @@ class PgAdapterAsync {
|
|
|
45734
46783
|
}
|
|
45735
46784
|
}
|
|
45736
46785
|
|
|
45737
|
-
// src/db/
|
|
45738
|
-
var
|
|
46786
|
+
// src/db/storage-sync.ts
|
|
46787
|
+
var STORAGE_TABLES = [
|
|
45739
46788
|
"domains",
|
|
45740
46789
|
"dns_records",
|
|
45741
46790
|
"alerts",
|
|
@@ -45755,25 +46804,50 @@ var PRIMARY_KEYS = {
|
|
|
45755
46804
|
domain_history: ["id"],
|
|
45756
46805
|
domain_reputation: ["id"]
|
|
45757
46806
|
};
|
|
45758
|
-
|
|
45759
|
-
|
|
46807
|
+
var DOMAINS_STORAGE_ENV = "HASNA_DOMAINS_DATABASE_URL";
|
|
46808
|
+
var DOMAINS_STORAGE_FALLBACK_ENV = "DOMAINS_DATABASE_URL";
|
|
46809
|
+
var DOMAINS_STORAGE_MODE_ENV = "HASNA_DOMAINS_STORAGE_MODE";
|
|
46810
|
+
var DOMAINS_STORAGE_MODE_FALLBACK_ENV = "DOMAINS_STORAGE_MODE";
|
|
46811
|
+
var STORAGE_DATABASE_ENV = [DOMAINS_STORAGE_ENV, DOMAINS_STORAGE_FALLBACK_ENV];
|
|
46812
|
+
var STORAGE_MODE_ENV = [DOMAINS_STORAGE_MODE_ENV, DOMAINS_STORAGE_MODE_FALLBACK_ENV];
|
|
46813
|
+
function firstEnv2(names) {
|
|
46814
|
+
for (const name of names) {
|
|
46815
|
+
const value = process.env[name];
|
|
46816
|
+
if (value)
|
|
46817
|
+
return value;
|
|
46818
|
+
}
|
|
46819
|
+
return null;
|
|
46820
|
+
}
|
|
46821
|
+
function normalizeStorageMode(value) {
|
|
46822
|
+
if (!value)
|
|
46823
|
+
return null;
|
|
46824
|
+
const normalized = value.trim().toLowerCase();
|
|
46825
|
+
if (normalized === "local" || normalized === "remote" || normalized === "hybrid")
|
|
46826
|
+
return normalized;
|
|
46827
|
+
return null;
|
|
45760
46828
|
}
|
|
45761
|
-
|
|
45762
|
-
|
|
46829
|
+
function getStorageDatabaseUrl() {
|
|
46830
|
+
return firstEnv2(STORAGE_DATABASE_ENV);
|
|
46831
|
+
}
|
|
46832
|
+
function getStorageMode() {
|
|
46833
|
+
return normalizeStorageMode(firstEnv2(STORAGE_MODE_ENV)) ?? (getStorageDatabaseUrl() ? "remote" : "local");
|
|
46834
|
+
}
|
|
46835
|
+
async function getStoragePg() {
|
|
46836
|
+
const url = getStorageDatabaseUrl();
|
|
45763
46837
|
if (!url) {
|
|
45764
|
-
throw new Error("Missing
|
|
46838
|
+
throw new Error("Missing HASNA_DOMAINS_DATABASE_URL or DOMAINS_DATABASE_URL");
|
|
45765
46839
|
}
|
|
45766
46840
|
return new PgAdapterAsync(url);
|
|
45767
46841
|
}
|
|
45768
|
-
async function
|
|
46842
|
+
async function runStorageMigrations(remote) {
|
|
45769
46843
|
for (const sql of PG_MIGRATIONS)
|
|
45770
46844
|
await remote.run(sql);
|
|
45771
46845
|
}
|
|
45772
|
-
async function
|
|
45773
|
-
const remote = await
|
|
46846
|
+
async function storagePush(options) {
|
|
46847
|
+
const remote = await getStoragePg();
|
|
45774
46848
|
const db = getDatabase();
|
|
45775
46849
|
try {
|
|
45776
|
-
await
|
|
46850
|
+
await runStorageMigrations(remote);
|
|
45777
46851
|
const results = [];
|
|
45778
46852
|
for (const table of resolveTables(options?.tables))
|
|
45779
46853
|
results.push(await pushTable(db, remote, table));
|
|
@@ -45783,11 +46857,11 @@ async function cloudPush(options) {
|
|
|
45783
46857
|
await remote.close();
|
|
45784
46858
|
}
|
|
45785
46859
|
}
|
|
45786
|
-
async function
|
|
45787
|
-
const remote = await
|
|
46860
|
+
async function storagePull(options) {
|
|
46861
|
+
const remote = await getStoragePg();
|
|
45788
46862
|
const db = getDatabase();
|
|
45789
46863
|
try {
|
|
45790
|
-
await
|
|
46864
|
+
await runStorageMigrations(remote);
|
|
45791
46865
|
const results = [];
|
|
45792
46866
|
for (const table of resolveTables(options?.tables))
|
|
45793
46867
|
results.push(await pullTable(remote, db, table));
|
|
@@ -45797,20 +46871,30 @@ async function cloudPull(options) {
|
|
|
45797
46871
|
await remote.close();
|
|
45798
46872
|
}
|
|
45799
46873
|
}
|
|
45800
|
-
async function
|
|
45801
|
-
const pull = await
|
|
45802
|
-
const push = await
|
|
46874
|
+
async function storageSync(options) {
|
|
46875
|
+
const pull = await storagePull(options);
|
|
46876
|
+
const push = await storagePush(options);
|
|
45803
46877
|
return { pull, push };
|
|
45804
46878
|
}
|
|
45805
|
-
function
|
|
46879
|
+
function getStorageSyncMetaAll() {
|
|
45806
46880
|
const db = getDatabase();
|
|
45807
46881
|
ensureSyncMetaTable(db);
|
|
45808
46882
|
return db.query("SELECT table_name, last_synced_at, direction FROM _domains_sync_meta ORDER BY table_name, direction").all();
|
|
45809
46883
|
}
|
|
46884
|
+
function getStorageStatus() {
|
|
46885
|
+
return {
|
|
46886
|
+
configured: Boolean(getStorageDatabaseUrl()),
|
|
46887
|
+
env: STORAGE_DATABASE_ENV,
|
|
46888
|
+
mode: getStorageMode(),
|
|
46889
|
+
service: "domains",
|
|
46890
|
+
tables: STORAGE_TABLES,
|
|
46891
|
+
sync: getStorageSyncMetaAll()
|
|
46892
|
+
};
|
|
46893
|
+
}
|
|
45810
46894
|
function resolveTables(tables) {
|
|
45811
46895
|
if (!tables || tables.length === 0)
|
|
45812
|
-
return [...
|
|
45813
|
-
const allowed = new Set(
|
|
46896
|
+
return [...STORAGE_TABLES];
|
|
46897
|
+
const allowed = new Set(STORAGE_TABLES);
|
|
45814
46898
|
const requested = tables.map((table) => table.trim()).filter(Boolean);
|
|
45815
46899
|
const invalid = requested.filter((table) => !allowed.has(table));
|
|
45816
46900
|
if (invalid.length > 0)
|
|
@@ -45946,12 +47030,7 @@ function coerceForSqlite(value) {
|
|
|
45946
47030
|
return String(value);
|
|
45947
47031
|
}
|
|
45948
47032
|
|
|
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
|
-
];
|
|
47033
|
+
// src/cli/commands/storage.ts
|
|
45955
47034
|
function parseTables(value) {
|
|
45956
47035
|
if (!value)
|
|
45957
47036
|
return;
|
|
@@ -45968,21 +47047,15 @@ function printResults(results, label) {
|
|
|
45968
47047
|
}
|
|
45969
47048
|
console.log(`Done. ${total} rows ${label}.`);
|
|
45970
47049
|
}
|
|
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
|
-
};
|
|
47050
|
+
function installStorageSubcommands(storage) {
|
|
47051
|
+
storage.command("status").description("Show remote storage config and local sync state").option("--json", "Output as JSON").action((opts) => {
|
|
47052
|
+
const info = getStorageStatus();
|
|
45981
47053
|
if (opts.json) {
|
|
45982
47054
|
printJson(info);
|
|
45983
47055
|
return;
|
|
45984
47056
|
}
|
|
45985
|
-
console.log(`
|
|
47057
|
+
console.log(`Storage mode: ${info.mode}`);
|
|
47058
|
+
console.log(`Remote storage configured: ${info.configured ? "yes" : "no"}`);
|
|
45986
47059
|
console.log(`Tables: ${info.tables.join(", ")}`);
|
|
45987
47060
|
if (info.sync.length === 0)
|
|
45988
47061
|
console.log("Sync: no local sync history");
|
|
@@ -45990,9 +47063,9 @@ function registerCloudCommand(program2) {
|
|
|
45990
47063
|
console.log(` ${entry.table_name} ${entry.direction}: ${entry.last_synced_at ?? "never"}`);
|
|
45991
47064
|
}
|
|
45992
47065
|
});
|
|
45993
|
-
|
|
47066
|
+
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
47067
|
try {
|
|
45995
|
-
const results = await
|
|
47068
|
+
const results = await storagePush({ tables: parseTables(opts.tables) });
|
|
45996
47069
|
if (opts.json) {
|
|
45997
47070
|
printJson(results);
|
|
45998
47071
|
return;
|
|
@@ -46003,9 +47076,9 @@ function registerCloudCommand(program2) {
|
|
|
46003
47076
|
process.exit(1);
|
|
46004
47077
|
}
|
|
46005
47078
|
});
|
|
46006
|
-
|
|
47079
|
+
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
47080
|
try {
|
|
46008
|
-
const results = await
|
|
47081
|
+
const results = await storagePull({ tables: parseTables(opts.tables) });
|
|
46009
47082
|
if (opts.json) {
|
|
46010
47083
|
printJson(results);
|
|
46011
47084
|
return;
|
|
@@ -46016,9 +47089,9 @@ function registerCloudCommand(program2) {
|
|
|
46016
47089
|
process.exit(1);
|
|
46017
47090
|
}
|
|
46018
47091
|
});
|
|
46019
|
-
|
|
47092
|
+
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
47093
|
try {
|
|
46021
|
-
const result = await
|
|
47094
|
+
const result = await storageSync({ tables: parseTables(opts.tables) });
|
|
46022
47095
|
if (opts.json) {
|
|
46023
47096
|
printJson(result);
|
|
46024
47097
|
return;
|
|
@@ -46031,6 +47104,9 @@ function registerCloudCommand(program2) {
|
|
|
46031
47104
|
}
|
|
46032
47105
|
});
|
|
46033
47106
|
}
|
|
47107
|
+
function registerStorageCommand(program2) {
|
|
47108
|
+
installStorageSubcommands(program2.command("storage").description("Manage domains local/remote storage sync"));
|
|
47109
|
+
}
|
|
46034
47110
|
|
|
46035
47111
|
// src/cli/index.ts
|
|
46036
47112
|
init_version();
|
|
@@ -46056,6 +47132,7 @@ registerOutreachCommand(program2);
|
|
|
46056
47132
|
registerSedoCommand(program2);
|
|
46057
47133
|
registerWalletCommand(program2);
|
|
46058
47134
|
registerProvisionCommand(program2);
|
|
46059
|
-
|
|
47135
|
+
registerStorageCommand(program2);
|
|
47136
|
+
registerEventsCommands(program2, { source: "domains" });
|
|
46060
47137
|
registerInteractiveCommand(program2);
|
|
46061
47138
|
program2.parse(process.argv);
|