@hasna/domains 0.0.21 → 0.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -35717,7 +35717,8 @@ function getConfig2() {
35717
35717
  return {
35718
35718
  region: process.env["AWS_REGION"] || "us-east-1",
35719
35719
  accessKeyId: process.env["AWS_ACCESS_KEY_ID"],
35720
- secretAccessKey: process.env["AWS_SECRET_ACCESS_KEY"]
35720
+ secretAccessKey: process.env["AWS_SECRET_ACCESS_KEY"],
35721
+ sessionToken: process.env["AWS_SESSION_TOKEN"]
35721
35722
  };
35722
35723
  }
35723
35724
  function checkCredentials(cfg) {
@@ -35733,7 +35734,7 @@ function makeClients(config) {
35733
35734
  const cfg = config ?? getConfig2();
35734
35735
  checkCredentials(cfg);
35735
35736
  const region = cfg.region || "us-east-1";
35736
- const credentials = cfg.accessKeyId && cfg.secretAccessKey ? { accessKeyId: cfg.accessKeyId, secretAccessKey: cfg.secretAccessKey } : undefined;
35737
+ const credentials = cfg.accessKeyId && cfg.secretAccessKey ? { accessKeyId: cfg.accessKeyId, secretAccessKey: cfg.secretAccessKey, sessionToken: cfg.sessionToken } : undefined;
35737
35738
  return {
35738
35739
  route53: new Route53Client({ region, credentials }),
35739
35740
  domains: new Route53DomainsClient({ region: "us-east-1", credentials })
@@ -35873,7 +35874,8 @@ async function listHostedZones(config) {
35873
35874
  id: cleanZoneId(z2.Id ?? ""),
35874
35875
  name: z2.Name ?? "",
35875
35876
  record_count: z2.ResourceRecordSetCount ?? 0,
35876
- comment: z2.Config?.Comment
35877
+ comment: z2.Config?.Comment,
35878
+ private_zone: z2.Config?.PrivateZone
35877
35879
  });
35878
35880
  }
35879
35881
  marker = result.IsTruncated ? result.NextMarker : undefined;
@@ -35888,7 +35890,8 @@ async function getHostedZone(hostedZoneId, config) {
35888
35890
  name: result.HostedZone?.Name ?? "",
35889
35891
  record_count: result.HostedZone?.ResourceRecordSetCount ?? 0,
35890
35892
  comment: result.HostedZone?.Config?.Comment,
35891
- name_servers: result.DelegationSet?.NameServers ?? []
35893
+ name_servers: result.DelegationSet?.NameServers ?? [],
35894
+ private_zone: result.HostedZone?.Config?.PrivateZone
35892
35895
  };
35893
35896
  }
35894
35897
  async function deleteHostedZone(hostedZoneId, config) {
@@ -35898,7 +35901,15 @@ async function deleteHostedZone(hostedZoneId, config) {
35898
35901
  async function findHostedZoneByDomain(domain, config) {
35899
35902
  const zones = await listHostedZones(config);
35900
35903
  const normalized = domain.endsWith(".") ? domain : `${domain}.`;
35901
- return zones.find((z2) => z2.name === normalized) ?? null;
35904
+ const matches = zones.filter((z2) => z2.name === normalized);
35905
+ if (matches.length === 0)
35906
+ return null;
35907
+ const publicMatches = matches.filter((z2) => !z2.private_zone);
35908
+ const candidates = publicMatches.length > 0 ? publicMatches : matches;
35909
+ if (candidates.length > 1) {
35910
+ throw new Error(`Multiple Route 53 hosted zones found for ${domain}; specify hosted zone id`);
35911
+ }
35912
+ return candidates[0] ?? null;
35902
35913
  }
35903
35914
  function rrsToRecord(rrs) {
35904
35915
  if (rrs.AliasTarget) {
@@ -35998,19 +36009,53 @@ function createRoute53Provider(config) {
35998
36009
  const cfg = config ?? getConfig2();
35999
36010
  const registerWithRoute53 = registerDomain2;
36000
36011
  const updateRoute53Nameservers = updateNameservers2;
36012
+ async function listDomainInventory() {
36013
+ const byDomain = new Map;
36014
+ try {
36015
+ const registered = await listRegisteredDomains(cfg);
36016
+ for (const d3 of registered) {
36017
+ byDomain.set(d3.domain, {
36018
+ domain: d3.domain,
36019
+ registrar: "AWS Route 53",
36020
+ created: "",
36021
+ expires: d3.expiry,
36022
+ nameservers: [],
36023
+ status: "active",
36024
+ auto_renew: d3.auto_renew
36025
+ });
36026
+ }
36027
+ } catch (error) {
36028
+ const message = error instanceof Error ? error.message : String(error);
36029
+ if (!message.includes("route53domains:ListDomains") && !message.includes("AccessDenied")) {
36030
+ throw error;
36031
+ }
36032
+ }
36033
+ const zones = await listHostedZones(cfg);
36034
+ for (const z2 of zones) {
36035
+ const zone = z2.name_servers?.length ? z2 : await getHostedZone(z2.id, cfg).catch(() => z2);
36036
+ const domain = zone.name.replace(/\.$/, "");
36037
+ const nameservers = zone.name_servers ?? [];
36038
+ const existing = byDomain.get(domain);
36039
+ if (existing) {
36040
+ existing.nameservers = nameservers.length > 0 ? nameservers : existing.nameservers;
36041
+ continue;
36042
+ }
36043
+ byDomain.set(domain, {
36044
+ domain,
36045
+ registrar: "AWS Route 53 DNS",
36046
+ created: "",
36047
+ expires: "",
36048
+ nameservers,
36049
+ status: "active",
36050
+ auto_renew: false
36051
+ });
36052
+ }
36053
+ return Array.from(byDomain.values());
36054
+ }
36001
36055
  return {
36002
36056
  name: "route53",
36003
36057
  async listDomains() {
36004
- const domains = await listRegisteredDomains(cfg);
36005
- return domains.map((d3) => ({
36006
- domain: d3.domain,
36007
- registrar: "AWS Route 53",
36008
- created: "",
36009
- expires: d3.expiry,
36010
- nameservers: [],
36011
- status: "active",
36012
- auto_renew: d3.auto_renew
36013
- }));
36058
+ return listDomainInventory();
36014
36059
  },
36015
36060
  async getDomainInfo(domain) {
36016
36061
  const detail = await getDomainDetail(domain, cfg);
@@ -36079,7 +36124,7 @@ function createRoute53Provider(config) {
36079
36124
  };
36080
36125
  },
36081
36126
  async syncToLocalDb(dbFns) {
36082
- const domains = await listRegisteredDomains(cfg);
36127
+ const domains = await listDomainInventory();
36083
36128
  let synced = 0;
36084
36129
  let created = 0;
36085
36130
  let updated = 0;
@@ -36088,20 +36133,39 @@ function createRoute53Provider(config) {
36088
36133
  try {
36089
36134
  const existing = dbFns.getDomainByName(d3.domain);
36090
36135
  if (existing) {
36136
+ const existingRoute53 = existing.metadata["route53"];
36137
+ const staleDnsOnlyRegistrar = d3.registrar !== "AWS Route 53" && (existing.registrar === "AWS Route 53 DNS" || existing.registrar === "AWS Route 53" && existingRoute53?.source === "route53:hosted_zones");
36091
36138
  dbFns.updateDomain(existing.id, {
36092
- registrar: "AWS Route 53",
36093
- expires_at: d3.expiry || undefined,
36139
+ ...d3.registrar === "AWS Route 53" ? { registrar: "AWS Route 53" } : {},
36140
+ ...staleDnsOnlyRegistrar ? { registrar: null } : {},
36141
+ expires_at: d3.expires || undefined,
36094
36142
  auto_renew: d3.auto_renew,
36143
+ nameservers: d3.nameservers.length > 0 ? d3.nameservers : existing.nameservers,
36144
+ metadata: {
36145
+ ...existing.metadata,
36146
+ route53: {
36147
+ source: d3.registrar === "AWS Route 53" ? "route53domains+hosted_zones" : "route53:hosted_zones",
36148
+ synced_at: new Date().toISOString()
36149
+ }
36150
+ },
36095
36151
  status: "active"
36096
36152
  });
36097
36153
  updated++;
36098
36154
  } else {
36099
36155
  dbFns.createDomain({
36100
36156
  name: d3.domain,
36101
- registrar: "AWS Route 53",
36102
- expires_at: d3.expiry || undefined,
36157
+ ...d3.registrar === "AWS Route 53" ? { registrar: "AWS Route 53" } : {},
36158
+ expires_at: d3.expires || undefined,
36103
36159
  auto_renew: d3.auto_renew,
36104
- status: "active"
36160
+ nameservers: d3.nameservers,
36161
+ status: "active",
36162
+ notes: d3.registrar === "AWS Route 53 DNS" ? "Discovered from Route 53 hosted zones; registrar ownership was not inferred." : undefined,
36163
+ metadata: {
36164
+ route53: {
36165
+ source: d3.registrar === "AWS Route 53" ? "route53domains+hosted_zones" : "route53:hosted_zones",
36166
+ synced_at: new Date().toISOString()
36167
+ }
36168
+ }
36105
36169
  });
36106
36170
  created++;
36107
36171
  }
@@ -36274,6 +36338,22 @@ async function cfFetch(path, opts = {}) {
36274
36338
  }
36275
36339
  return json.result;
36276
36340
  }
36341
+ async function listZones(config) {
36342
+ const zones = [];
36343
+ let page = 1;
36344
+ while (true) {
36345
+ const result = await cfFetch(`/zones?per_page=50&page=${page}`, { config });
36346
+ if (!result || result.length === 0)
36347
+ break;
36348
+ for (const z2 of result) {
36349
+ zones.push({ id: z2.id, name: z2.name, status: z2.status, nameservers: z2.name_servers, original_nameservers: z2.original_name_servers });
36350
+ }
36351
+ if (result.length < 50)
36352
+ break;
36353
+ page++;
36354
+ }
36355
+ return zones;
36356
+ }
36277
36357
  async function getZone(domain, config) {
36278
36358
  const result = await cfFetch(`/zones?name=${encodeURIComponent(domain)}`, { config });
36279
36359
  if (!result || result.length === 0)
@@ -36315,8 +36395,20 @@ async function listRecords2(zoneId, config) {
36315
36395
  }
36316
36396
  return records;
36317
36397
  }
36398
+ async function listRecordsByNameType(zoneId, type, name, config) {
36399
+ const result = await cfFetch(`/zones/${zoneId}/dns_records?type=${encodeURIComponent(type)}&name=${encodeURIComponent(name)}`, { config });
36400
+ return (result ?? []).map((r3) => ({
36401
+ id: r3.id,
36402
+ type: r3.type,
36403
+ name: r3.name,
36404
+ content: r3.content,
36405
+ ttl: r3.ttl,
36406
+ priority: r3.priority,
36407
+ proxied: r3.proxied
36408
+ }));
36409
+ }
36318
36410
  async function upsertRecord2(zoneId, record, config) {
36319
- const existing = await cfFetch(`/zones/${zoneId}/dns_records?type=${record.type}&name=${encodeURIComponent(record.name)}`, { config });
36411
+ const existing = await listRecordsByNameType(zoneId, record.type, record.name, config);
36320
36412
  const body = {
36321
36413
  type: record.type,
36322
36414
  name: record.name,
@@ -36325,16 +36417,106 @@ async function upsertRecord2(zoneId, record, config) {
36325
36417
  priority: record.priority,
36326
36418
  proxied: record.proxied ?? false
36327
36419
  };
36328
- if (existing && existing.length > 0) {
36329
- await cfFetch(`/zones/${zoneId}/dns_records/${existing[0].id}`, { method: "PUT", body, config });
36420
+ const sameRecord = existing.find((r3) => r3.content === record.content && (r3.priority ?? undefined) === (record.priority ?? undefined) && (r3.proxied ?? false) === (record.proxied ?? false));
36421
+ if (sameRecord?.id) {
36422
+ await cfFetch(`/zones/${zoneId}/dns_records/${sameRecord.id}`, { method: "PUT", body, config });
36330
36423
  } else {
36331
36424
  await cfFetch(`/zones/${zoneId}/dns_records`, { method: "POST", body, config });
36332
36425
  }
36333
36426
  }
36427
+ async function replaceRecordsByNameType(zoneId, records, config) {
36428
+ if (records.length === 0)
36429
+ return;
36430
+ const { type, name } = records[0];
36431
+ const existing = await listRecordsByNameType(zoneId, type, name, config);
36432
+ for (const record of existing) {
36433
+ if (record.id)
36434
+ await deleteRecord2(zoneId, record.id, config);
36435
+ }
36436
+ for (const record of records) {
36437
+ await cfFetch(`/zones/${zoneId}/dns_records`, {
36438
+ method: "POST",
36439
+ body: {
36440
+ type: record.type,
36441
+ name: record.name,
36442
+ content: record.content,
36443
+ ttl: record.ttl ?? 1,
36444
+ priority: record.priority,
36445
+ proxied: record.proxied ?? false
36446
+ },
36447
+ config
36448
+ });
36449
+ }
36450
+ }
36451
+ async function deleteRecord2(zoneId, recordId, config) {
36452
+ await cfFetch(`/zones/${zoneId}/dns_records/${recordId}`, { method: "DELETE", config });
36453
+ }
36454
+ function zoneToDomainInfo(zone) {
36455
+ return {
36456
+ domain: zone.name,
36457
+ registrar: "Cloudflare DNS",
36458
+ created: "",
36459
+ expires: "",
36460
+ nameservers: zone.nameservers,
36461
+ status: zone.status === "active" ? "active" : "discovered",
36462
+ auto_renew: false
36463
+ };
36464
+ }
36465
+ function withCloudflareMetadata(existing, zone) {
36466
+ return {
36467
+ ...existing,
36468
+ cloudflare: {
36469
+ zone_id: zone.id,
36470
+ zone_status: zone.status,
36471
+ source: "cloudflare:zones",
36472
+ synced_at: new Date().toISOString()
36473
+ }
36474
+ };
36475
+ }
36334
36476
  function createCloudflareProvider(config) {
36335
36477
  const cfg = config ?? getConfig3();
36336
36478
  return {
36337
36479
  name: "cloudflare",
36480
+ async listDomains() {
36481
+ const zones = await listZones(cfg);
36482
+ return zones.map(zoneToDomainInfo);
36483
+ },
36484
+ async syncToLocalDb(dbFns) {
36485
+ const zones = await listZones(cfg);
36486
+ let synced = 0;
36487
+ let created = 0;
36488
+ let updated = 0;
36489
+ const errors = [];
36490
+ for (const zone of zones) {
36491
+ try {
36492
+ const info = zoneToDomainInfo(zone);
36493
+ const existing = dbFns.getDomainByName(zone.name);
36494
+ if (existing) {
36495
+ dbFns.updateDomain(existing.id, {
36496
+ ...existing.registrar === "Cloudflare DNS" ? { registrar: null } : {},
36497
+ status: existing.status === "discovered" && info.status === "active" ? "active" : existing.status,
36498
+ nameservers: zone.nameservers,
36499
+ metadata: withCloudflareMetadata(existing.metadata, zone)
36500
+ });
36501
+ updated++;
36502
+ } else {
36503
+ dbFns.createDomain({
36504
+ name: zone.name,
36505
+ status: info.status === "active" ? "active" : "discovered",
36506
+ auto_renew: false,
36507
+ nameservers: zone.nameservers,
36508
+ notes: "Discovered from Cloudflare zones; registrar ownership was not inferred.",
36509
+ metadata: withCloudflareMetadata({}, zone)
36510
+ });
36511
+ created++;
36512
+ }
36513
+ synced++;
36514
+ } catch (err) {
36515
+ errors.push(`${zone.name}: ${err instanceof Error ? err.message : String(err)}`);
36516
+ }
36517
+ }
36518
+ return { synced, created, updated, errors };
36519
+ },
36338
36520
  async getDnsRecords(domain) {
36339
36521
  const zone = await getZone(domain, cfg);
36340
36522
  if (!zone)
@@ -36352,8 +36534,15 @@ function createCloudflareProvider(config) {
36352
36534
  const zone = await getZone(domain, cfg);
36353
36535
  if (!zone)
36354
36536
  throw new Error(`No Cloudflare zone found for ${domain}`);
36537
+ const grouped = new Map;
36355
36538
  for (const r3 of records) {
36356
- await upsertRecord2(zone.id, { type: r3.type, name: r3.name, content: r3.value, ttl: r3.ttl || 1, priority: r3.priority }, cfg);
36539
+ const key = `${r3.type}|${r3.name}`;
36540
+ const existing = grouped.get(key) ?? [];
36541
+ existing.push({ type: r3.type, name: r3.name, content: r3.value, ttl: r3.ttl || 1, priority: r3.priority });
36542
+ grouped.set(key, existing);
36543
+ }
36544
+ for (const group of grouped.values()) {
36545
+ await replaceRecordsByNameType(zone.id, group, cfg);
36357
36546
  }
36358
36547
  return true;
36359
36548
  }
@@ -36390,87 +36579,259 @@ var _fetchFn = null;
36390
36579
  function getConfig4() {
36391
36580
  return resolveBrandsightConfig();
36392
36581
  }
36393
- var BRANDSIGHT_BASE = "https://api.brandsight.com/v1";
36394
- async function apiRequest3(method, path, apiKey, body, baseUrl = BRANDSIGHT_BASE) {
36395
- const fetchFn = _fetchFn || globalThis.fetch;
36396
- const url = `${baseUrl}${path}`;
36397
- const headers = {
36398
- Authorization: `Bearer ${apiKey}`,
36399
- "Content-Type": "application/json",
36400
- Accept: "application/json",
36401
- "User-Agent": USER_AGENT
36402
- };
36403
- try {
36404
- const response = await fetchFn(url, {
36405
- method,
36406
- headers,
36407
- body: body ? JSON.stringify(body) : undefined,
36408
- signal: AbortSignal.timeout(15000)
36409
- });
36410
- if (!response.ok) {
36411
- throw new BrandsightApiError(`Brandsight API ${method} ${path} failed with status ${response.status}`, response.status, await response.text());
36412
- }
36413
- const data = await response.json();
36414
- return { data, stub: false };
36415
- } catch (error) {
36416
- if (error instanceof BrandsightApiError)
36417
- throw error;
36418
- return { data: null, stub: true };
36582
+ var BRANDSIGHT_DOMAIN_BASE = "https://api.godaddy.com/v2";
36583
+ function requireDomainConfig(config) {
36584
+ const cfg = config ?? getConfig4();
36585
+ if (!cfg.apiKey || !cfg.apiSecret || !cfg.customerId) {
36586
+ throw new BrandsightApiError("Brandsight Domain API credentials are not configured. Set BRANDSIGHT_API_KEY, BRANDSIGHT_API_SECRET, and BRANDSIGHT_CUSTOMER_ID (or HASNAXYZ_BRANDSIGHT_LIVE_* aliases).");
36419
36587
  }
36588
+ return cfg;
36420
36589
  }
36421
- async function apiGet(path, apiKey, baseUrl = BRANDSIGHT_BASE) {
36422
- const fetchFn = _fetchFn || globalThis.fetch;
36423
- const url = `${baseUrl}${path}`;
36424
- const headers = {
36425
- Authorization: `Bearer ${apiKey}`,
36590
+ function domainBaseUrl(cfg) {
36591
+ return cfg.baseUrl ?? BRANDSIGHT_DOMAIN_BASE;
36592
+ }
36593
+ function domainHeaders(cfg) {
36594
+ return {
36595
+ Authorization: `sso-key ${cfg.apiKey}:${cfg.apiSecret}`,
36426
36596
  "Content-Type": "application/json",
36427
36597
  Accept: "application/json",
36428
36598
  "User-Agent": USER_AGENT
36429
36599
  };
36430
- try {
36431
- const response = await fetchFn(url, {
36432
- method: "GET",
36433
- headers,
36434
- signal: AbortSignal.timeout(15000)
36435
- });
36436
- if (!response.ok) {
36437
- throw new BrandsightApiError(`Brandsight API GET ${path} failed with status ${response.status}`, response.status, await response.text());
36438
- }
36439
- const data = await response.json();
36440
- return { data, stub: false };
36441
- } catch (error) {
36442
- if (error instanceof BrandsightApiError)
36443
- throw error;
36444
- return { data: null, stub: true };
36600
+ }
36601
+ async function domainApiRequest(method, path, config, body) {
36602
+ const cfg = requireDomainConfig(config);
36603
+ const fetchFn = _fetchFn || globalThis.fetch;
36604
+ const response = await fetchFn(`${domainBaseUrl(cfg)}${path}`, {
36605
+ method,
36606
+ headers: domainHeaders(cfg),
36607
+ body: body ? JSON.stringify(body) : undefined,
36608
+ signal: AbortSignal.timeout(30000)
36609
+ });
36610
+ const text = await response.text();
36611
+ if (!response.ok) {
36612
+ throw new BrandsightApiError(`Brandsight Domain API ${method} ${path} failed with status ${response.status}`, response.status, text);
36445
36613
  }
36614
+ if (!text.trim())
36615
+ return {};
36616
+ return JSON.parse(text);
36617
+ }
36618
+ function normalizeBrandsightDomain(raw) {
36619
+ const nameServers = raw.nameServers ?? raw.nameservers ?? [];
36620
+ const expiresAt = raw.expiresAt ?? raw.expires ?? "";
36621
+ const createdAt = raw.createdAt ?? raw.created ?? "";
36622
+ const renewAuto = raw.renewAuto ?? raw.auto_renew ?? false;
36623
+ return {
36624
+ ...raw,
36625
+ domain: String(raw.domain ?? ""),
36626
+ status: String(raw.status ?? "UNKNOWN"),
36627
+ created: createdAt,
36628
+ expires: expiresAt,
36629
+ auto_renew: renewAuto,
36630
+ locked: Boolean(raw.locked),
36631
+ nameservers: nameServers,
36632
+ createdAt,
36633
+ expiresAt,
36634
+ nameServers,
36635
+ renewAuto
36636
+ };
36637
+ }
36638
+ function customerPath(cfg, path) {
36639
+ return `/customers/${encodeURIComponent(cfg.customerId)}${path}`;
36640
+ }
36641
+ function domainTld(domain) {
36642
+ const parts = domain.split(".").filter(Boolean);
36643
+ if (parts.length < 2)
36644
+ throw new BrandsightApiError(`Invalid domain name: ${domain}`);
36645
+ return parts.slice(1).join(".");
36646
+ }
36647
+ function brandsightContact(contact) {
36648
+ return {
36649
+ addressMailing: {
36650
+ address1: contact.address_line_1,
36651
+ city: contact.city,
36652
+ country: contact.country_code,
36653
+ postalCode: contact.zip_code,
36654
+ state: contact.state
36655
+ },
36656
+ email: contact.email,
36657
+ encoding: "ASCII",
36658
+ nameFirst: contact.first_name,
36659
+ nameLast: contact.last_name,
36660
+ organization: contact.organization_name,
36661
+ phone: contact.phone
36662
+ };
36663
+ }
36664
+ async function domainAvailability(domain, cfg, type, period = 1) {
36665
+ const params = new URLSearchParams({
36666
+ domain,
36667
+ period: String(period),
36668
+ type,
36669
+ optimizeFor: "ACCURACY"
36670
+ });
36671
+ const result = await domainApiRequest("GET", `/domains/available?${params.toString()}`, cfg);
36672
+ return {
36673
+ domain: result.domain ?? domain,
36674
+ available: Boolean(result.available),
36675
+ price: result.price,
36676
+ currency: result.currency,
36677
+ registryPremiumPricing: result.registryPremiumPricing
36678
+ };
36446
36679
  }
36447
36680
  async function listDomains2(config) {
36448
- const cfg = config ?? getConfig4();
36449
- const result = await apiGet("/portfolio/domains", cfg.apiKey);
36450
- if (result.stub)
36451
- return [];
36452
- return result.data.domains ?? [];
36681
+ const cfg = requireDomainConfig(config);
36682
+ const domains = [];
36683
+ const seenMarkers = new Set;
36684
+ let marker;
36685
+ while (true) {
36686
+ const params = new URLSearchParams({ limit: "500" });
36687
+ if (marker)
36688
+ params.set("marker", marker);
36689
+ const batch = await domainApiRequest("GET", customerPath(cfg, `/domains?${params.toString()}`), cfg);
36690
+ if (!Array.isArray(batch) || batch.length === 0)
36691
+ break;
36692
+ domains.push(...batch.map(normalizeBrandsightDomain).filter((d3) => d3.domain));
36693
+ if (batch.length < 500)
36694
+ break;
36695
+ const nextMarker = String(batch[batch.length - 1]?.domain ?? "");
36696
+ if (!nextMarker || seenMarkers.has(nextMarker))
36697
+ break;
36698
+ seenMarkers.add(nextMarker);
36699
+ marker = nextMarker;
36700
+ }
36701
+ return domains;
36453
36702
  }
36454
36703
  async function getDomainInfo3(domain, config) {
36455
- const cfg = config ?? getConfig4();
36456
- const result = await apiGet(`/portfolio/domains/${encodeURIComponent(domain)}`, cfg.apiKey);
36457
- if (result.stub)
36458
- return null;
36459
- return result.data;
36704
+ const cfg = requireDomainConfig(config);
36705
+ const result = await domainApiRequest("GET", customerPath(cfg, `/domains/${encodeURIComponent(domain)}`), cfg);
36706
+ return normalizeBrandsightDomain(result);
36460
36707
  }
36461
36708
  async function checkAvailability4(domain, config) {
36462
- const cfg = config ?? getConfig4();
36463
- const result = await apiGet(`/domains/check?domain=${encodeURIComponent(domain)}`, cfg.apiKey);
36464
- if (result.stub)
36465
- return { domain, available: false };
36466
- return result.data;
36709
+ const cfg = requireDomainConfig(config);
36710
+ return domainAvailability(domain, cfg, "REGISTRATION", 1);
36467
36711
  }
36468
36712
  async function renewDomain3(domain, years = 1, config) {
36469
- const cfg = config ?? getConfig4();
36470
- const result = await apiRequest3("POST", `/portfolio/domains/${encodeURIComponent(domain)}/renew`, cfg.apiKey, { years });
36471
- if (result.stub)
36472
- return { success: false };
36473
- return { success: true, orderId: result.data.orderId };
36713
+ const cfg = requireDomainConfig(config);
36714
+ const current = await getDomainInfo3(domain, cfg);
36715
+ if (!current?.expires)
36716
+ throw new BrandsightApiError(`Cannot renew ${domain}: current expiry is unavailable`);
36717
+ const quote = await domainAvailability(domain, cfg, "RENEWAL", years);
36718
+ const price = quote.price ?? current.renewal?.price;
36719
+ const currency = quote.currency ?? current.renewal?.currency;
36720
+ if (price == null || !currency) {
36721
+ throw new BrandsightApiError(`Cannot renew ${domain}: renewal quote did not include an exact price and currency`);
36722
+ }
36723
+ const result = await domainApiRequest("POST", customerPath(cfg, `/domains/${encodeURIComponent(domain)}/renew`), cfg, {
36724
+ consent: {
36725
+ agreedAt: new Date().toISOString(),
36726
+ agreedBy: cfg.shopperId ?? "domains-cli",
36727
+ currency,
36728
+ price,
36729
+ registryPremiumPricing: quote.registryPremiumPricing ?? false
36730
+ },
36731
+ expires: current.expires,
36732
+ period: years
36733
+ });
36734
+ return { success: true, orderId: result.orderId ?? result.id };
36735
+ }
36736
+ async function getLegalAgreements(tld, privacy = false, config) {
36737
+ const cfg = requireDomainConfig(config);
36738
+ const params = new URLSearchParams({
36739
+ privacy: String(privacy),
36740
+ tlds: tld
36741
+ });
36742
+ return domainApiRequest("GET", customerPath(cfg, `/domains/agreements?${params.toString()}`), cfg);
36743
+ }
36744
+ async function getRegistrationSchema(tld, config) {
36745
+ const cfg = requireDomainConfig(config);
36746
+ return domainApiRequest("GET", customerPath(cfg, `/domains/register/schema/${encodeURIComponent(tld)}`), cfg);
36747
+ }
36748
+ async function validateRegistrationRequest(payload, config) {
36749
+ const cfg = requireDomainConfig(config);
36750
+ await domainApiRequest("POST", customerPath(cfg, "/domains/register/validate"), cfg, payload);
36751
+ return true;
36752
+ }
36753
+ async function registerBrandsightDomain(domain, contact, options = {}, config) {
36754
+ const cfg = requireDomainConfig(config);
36755
+ const period = options.years ?? 1;
36756
+ const availability = await domainAvailability(domain, cfg, "REGISTRATION", period);
36757
+ if (!availability.available)
36758
+ throw new BrandsightApiError(`${domain} is not available for registration`);
36759
+ const price = options.premiumPrice ?? availability.price;
36760
+ if (price == null || !availability.currency) {
36761
+ throw new BrandsightApiError(`Cannot register ${domain}: availability quote did not include an exact price and currency`);
36762
+ }
36763
+ const tld = domainTld(domain);
36764
+ const schema = await getRegistrationSchema(tld, cfg);
36765
+ const required = Array.isArray(schema["required"]) ? schema["required"] : [];
36766
+ if (required.includes("metadata") && !options.metadata) {
36767
+ throw new BrandsightApiError(`Cannot register ${domain}: ${tld} requires TLD-specific metadata; pass registration metadata before validation`);
36768
+ }
36769
+ const privacy = options.privacy ?? false;
36770
+ const agreements = await getLegalAgreements(tld, privacy, cfg);
36771
+ const agreementKeys = agreements.map((a3) => a3.agreementKey).filter(Boolean);
36772
+ const c3 = brandsightContact(contact);
36773
+ const payload = {
36774
+ consent: {
36775
+ agreedAt: new Date().toISOString(),
36776
+ agreedBy: contact.email || cfg.shopperId || "domains-cli",
36777
+ agreementKeys,
36778
+ currency: availability.currency,
36779
+ price,
36780
+ registryPremiumPricing: availability.registryPremiumPricing ?? !!options.premiumPrice
36781
+ },
36782
+ contacts: {
36783
+ admin: c3,
36784
+ billing: c3,
36785
+ registrant: c3,
36786
+ tech: c3
36787
+ },
36788
+ domain,
36789
+ metadata: options.metadata ?? {},
36790
+ nameServers: options.nameservers ?? [],
36791
+ period,
36792
+ privacy,
36793
+ renewAuto: options.autoRenew ?? true
36794
+ };
36795
+ await validateRegistrationRequest(payload, cfg);
36796
+ const result = await domainApiRequest("POST", customerPath(cfg, "/domains/register"), cfg, payload);
36797
+ return {
36798
+ success: true,
36799
+ orderId: result.orderId ?? result.id,
36800
+ operationId: result.operationId ?? result.id,
36801
+ chargedAmount: String(price)
36802
+ };
36803
+ }
36804
+ async function updateNameservers3(domain, nameservers, config) {
36805
+ const cfg = requireDomainConfig(config);
36806
+ const result = await domainApiRequest("PUT", customerPath(cfg, `/domains/${encodeURIComponent(domain)}/nameServers`), cfg, { nameServers: nameservers });
36807
+ return { success: true, operationId: result.operationId ?? result.id };
36808
+ }
36809
+ async function getDnsRecords3(domain, config) {
36810
+ const cfg = requireDomainConfig(config);
36811
+ const records = [];
36812
+ let offset = 0;
36813
+ const limit = 1000;
36814
+ while (true) {
36815
+ const batch = await domainApiRequest("GET", customerPath(cfg, `/domains/${encodeURIComponent(domain)}/records?offset=${offset}&limit=${limit}`), cfg);
36816
+ if (!Array.isArray(batch) || batch.length === 0)
36817
+ break;
36818
+ records.push(...batch);
36819
+ if (batch.length < limit)
36820
+ break;
36821
+ offset++;
36822
+ }
36823
+ return records;
36824
+ }
36825
+ function normalizeBrandsightDnsRecord(record) {
36826
+ return {
36827
+ ...record,
36828
+ ttl: Math.max(record.ttl || 600, 600)
36829
+ };
36830
+ }
36831
+ async function setDnsRecords3(domain, records, config) {
36832
+ const cfg = requireDomainConfig(config);
36833
+ await domainApiRequest("PUT", customerPath(cfg, `/domains/${encodeURIComponent(domain)}/records`), cfg, records.map(normalizeBrandsightDnsRecord));
36834
+ return true;
36474
36835
  }
36475
36836
  async function syncToLocalDb3(dbFns, config) {
36476
36837
  const domains = await listDomains2(config);
@@ -36515,7 +36876,7 @@ function createBrandsightProvider(config) {
36515
36876
  return domains.map((d3) => ({
36516
36877
  domain: d3.domain,
36517
36878
  registrar: "Brandsight",
36518
- created: "",
36879
+ created: d3.created ?? "",
36519
36880
  expires: d3.expires,
36520
36881
  nameservers: d3.nameservers,
36521
36882
  status: d3.status === "ACTIVE" ? "active" : d3.status.toLowerCase(),
@@ -36529,7 +36890,7 @@ function createBrandsightProvider(config) {
36529
36890
  return {
36530
36891
  domain: d3.domain,
36531
36892
  registrar: "Brandsight",
36532
- created: "",
36893
+ created: d3.created ?? "",
36533
36894
  expires: d3.expires,
36534
36895
  nameservers: d3.nameservers,
36535
36896
  status: d3.status === "ACTIVE" ? "active" : d3.status.toLowerCase(),
@@ -36540,12 +36901,47 @@ function createBrandsightProvider(config) {
36540
36901
  const result = await renewDomain3(domain, years, cfg);
36541
36902
  return { domain, success: result.success, orderId: result.orderId };
36542
36903
  },
36904
+ async registerDomain(domain, contact, options) {
36905
+ const result = await registerBrandsightDomain(domain, contact, options, cfg);
36906
+ return {
36907
+ domain,
36908
+ success: result.success,
36909
+ orderId: result.orderId,
36910
+ operationId: result.operationId,
36911
+ chargedAmount: result.chargedAmount
36912
+ };
36913
+ },
36914
+ async updateNameservers(domain, nameservers) {
36915
+ const result = await updateNameservers3(domain, nameservers, cfg);
36916
+ return { domain, success: result.success, operationId: result.operationId };
36917
+ },
36918
+ async getDnsRecords(domain) {
36919
+ const records = await getDnsRecords3(domain, cfg);
36920
+ return records.map((r3) => ({
36921
+ type: r3.type,
36922
+ name: r3.name,
36923
+ value: r3.data,
36924
+ ttl: r3.ttl,
36925
+ priority: r3.priority
36926
+ }));
36927
+ },
36928
+ async setDnsRecords(domain, records) {
36929
+ return setDnsRecords3(domain, records.map((r3) => ({
36930
+ type: r3.type,
36931
+ name: r3.name,
36932
+ data: r3.value,
36933
+ ttl: r3.ttl,
36934
+ priority: r3.priority
36935
+ })), cfg);
36936
+ },
36543
36937
  async checkAvailability(domain) {
36544
36938
  const result = await checkAvailability4(domain, cfg);
36545
36939
  return {
36546
36940
  domain: result.domain,
36547
36941
  available: result.available,
36548
- standard_price: result.price,
36942
+ is_premium: result.registryPremiumPricing,
36943
+ premium_price: result.registryPremiumPricing ? result.price : undefined,
36944
+ standard_price: result.registryPremiumPricing ? undefined : result.price,
36549
36945
  currency: result.currency
36550
36946
  };
36551
36947
  },
@@ -36743,6 +37139,7 @@ var providerRegistry = new Map([
36743
37139
  configured: false,
36744
37140
  envVars: providerEnvNames("namecheap")
36745
37141
  },
37142
+ createInventory: createNamecheapProvider,
36746
37143
  createRegistrar: createNamecheapProvider,
36747
37144
  createDns: createNamecheapProvider
36748
37145
  }],
@@ -36753,6 +37150,7 @@ var providerRegistry = new Map([
36753
37150
  configured: false,
36754
37151
  envVars: providerEnvNames("godaddy")
36755
37152
  },
37153
+ createInventory: createGoDaddyProvider,
36756
37154
  createRegistrar: createGoDaddyProvider,
36757
37155
  createDns: createGoDaddyProvider
36758
37156
  }],
@@ -36763,6 +37161,7 @@ var providerRegistry = new Map([
36763
37161
  configured: false,
36764
37162
  envVars: providerEnvNames("route53")
36765
37163
  },
37164
+ createInventory: () => createRoute53Provider(),
36766
37165
  createRegistrar: () => createRoute53Provider(),
36767
37166
  createDns: () => createRoute53Provider()
36768
37167
  }],
@@ -36773,16 +37172,19 @@ var providerRegistry = new Map([
36773
37172
  configured: false,
36774
37173
  envVars: providerEnvNames("cloudflare")
36775
37174
  },
37175
+ createInventory: () => createCloudflareProvider(),
36776
37176
  createDns: createCloudflareProvider
36777
37177
  }],
36778
37178
  ["brandsight", {
36779
37179
  info: {
36780
37180
  name: "brandsight",
36781
- type: "registrar",
37181
+ type: "full",
36782
37182
  configured: false,
36783
37183
  envVars: providerEnvNames("brandsight")
36784
37184
  },
36785
- createRegistrar: createBrandsightProvider
37185
+ createInventory: createBrandsightProvider,
37186
+ createRegistrar: createBrandsightProvider,
37187
+ createDns: createBrandsightProvider
36786
37188
  }],
36787
37189
  ["sedo", {
36788
37190
  info: {
@@ -36799,14 +37201,15 @@ function isConfigured(providerName) {
36799
37201
  function getAvailableProviders() {
36800
37202
  return Array.from(providerRegistry.values()).map((e3) => ({
36801
37203
  ...e3.info,
36802
- configured: isConfigured(e3.info.name)
37204
+ configured: isConfigured(e3.info.name),
37205
+ inventory: !!e3.createInventory
36803
37206
  }));
36804
37207
  }
36805
37208
  function getProviderInfo(name) {
36806
37209
  const entry = providerRegistry.get(name.toLowerCase());
36807
37210
  if (!entry)
36808
37211
  return null;
36809
- return { ...entry.info, configured: isConfigured(entry.info.name) };
37212
+ return { ...entry.info, configured: isConfigured(entry.info.name), inventory: !!entry.createInventory };
36810
37213
  }
36811
37214
  function providerHasRegistrar(name) {
36812
37215
  return !!providerRegistry.get(name.toLowerCase())?.createRegistrar;
@@ -36814,6 +37217,15 @@ function providerHasRegistrar(name) {
36814
37217
  function providerHasDns(name) {
36815
37218
  return !!providerRegistry.get(name.toLowerCase())?.createDns;
36816
37219
  }
37220
+ function providerHasInventory(name) {
37221
+ return !!providerRegistry.get(name.toLowerCase())?.createInventory;
37222
+ }
37223
+ function getDomainInventoryProvider(name) {
37224
+ const entry = providerRegistry.get(name.toLowerCase());
37225
+ if (!entry?.createInventory)
37226
+ throw new Error(`No domain inventory provider: ${name}`);
37227
+ return entry.createInventory();
37228
+ }
36817
37229
  function getRegistrarProvider(name) {
36818
37230
  const entry = providerRegistry.get(name.toLowerCase());
36819
37231
  if (!entry?.createRegistrar)
@@ -36824,11 +37236,11 @@ function getProvider(name) {
36824
37236
  return getRegistrarProvider(name);
36825
37237
  }
36826
37238
  async function syncAll(dbFns) {
36827
- const available = getAvailableProviders().filter((p3) => p3.configured && providerHasRegistrar(p3.name));
37239
+ const available = getAvailableProviders().filter((p3) => p3.configured && providerHasInventory(p3.name));
36828
37240
  const result = { providers: [], totalSynced: 0, totalErrors: [] };
36829
37241
  for (const info of available) {
36830
37242
  try {
36831
- const provider = getRegistrarProvider(info.name);
37243
+ const provider = getDomainInventoryProvider(info.name);
36832
37244
  const syncResult = await provider.syncToLocalDb(dbFns);
36833
37245
  result.providers.push({ name: info.name, result: syncResult });
36834
37246
  result.totalSynced += syncResult.synced;
@@ -36846,14 +37258,16 @@ function autoDetectRegistrar(domain, getDomainByName2) {
36846
37258
  if (!dbDomain?.registrar)
36847
37259
  return null;
36848
37260
  const r3 = dbDomain.registrar.toLowerCase();
37261
+ if (r3.includes("cloudflare dns") || r3.includes("route 53 dns") || r3.includes("route53 dns"))
37262
+ return null;
36849
37263
  if (r3.includes("namecheap"))
36850
37264
  return "namecheap";
36851
37265
  if (r3.includes("godaddy"))
36852
37266
  return "godaddy";
36853
- if (r3.includes("route 53") || r3.includes("route53") || r3.includes("aws"))
37267
+ if (r3.includes("route 53") || r3.includes("route53"))
36854
37268
  return "route53";
36855
37269
  if (r3.includes("cloudflare"))
36856
- return "cloudflare";
37270
+ return null;
36857
37271
  if (r3.includes("brandsight"))
36858
37272
  return "brandsight";
36859
37273
  return null;
@@ -36872,8 +37286,8 @@ var PROVIDER_CAPABILITIES = {
36872
37286
  route53: { canBuy: true, canDns: true, gated: false, notes: "Full self-serve buy + hosted zones via AWS API; primary buy path." },
36873
37287
  cloudflare: { canBuy: false, canDns: true, gated: false, notes: "DNS/zone management only; not a registrar. Always our DNS." },
36874
37288
  namecheap: { canBuy: true, canDns: true, gated: false, notes: "Buy + DNS via API; requires API access + whitelisted IP." },
36875
- godaddy: { canBuy: false, canDns: true, gated: true, notes: "Domains API is account-gated; this CLI supports availability/sync/renew/DNS records where allowed, but not direct automated purchase." },
36876
- brandsight: { canBuy: false, canDns: false, gated: true, notes: "GoDaddy Corporate Domains (enterprise/contract-only); portfolio/renewal surface only in this CLI, with purchase and DNS operations contract-specific." },
37289
+ godaddy: { canBuy: false, canDns: true, gated: true, notes: "Availability remains threshold-gated; this CLI supports sync/renew/DNS records where account access qualifies, but not direct automated purchase." },
37290
+ brandsight: { canBuy: true, canDns: true, gated: true, notes: "GoDaddy Corporate Domains (enterprise/contract-only); this CLI supports portfolio, registration, renewal, nameserver, and DNS operations through the official v2 API when the account contract permits it." },
36877
37291
  sedo: { canBuy: false, canDns: false, gated: true, notes: "Marketplace API only: search/portfolio/listings and recorded purchases; no registrar DNS or direct registration API in this CLI." }
36878
37292
  };
36879
37293
  var UNKNOWN = { canBuy: false, canDns: false, gated: true, notes: "Unknown provider." };
@@ -36958,6 +37372,7 @@ export {
36958
37372
  createHostedZone as r53CreateHostedZone,
36959
37373
  checkAvailability3 as r53CheckAvailability,
36960
37374
  providerHasRegistrar,
37375
+ providerHasInventory,
36961
37376
  providerHasDns,
36962
37377
  pollRegistrationUntilDone,
36963
37378
  markDomainPremium,
@@ -36981,6 +37396,7 @@ export {
36981
37396
  getProvider,
36982
37397
  getDomainStats,
36983
37398
  getDomainOffer,
37399
+ getDomainInventoryProvider,
36984
37400
  getDomainEmailLink,
36985
37401
  getDomainDetails,
36986
37402
  getDomainByName,