@aoxborrow/registrar-client 0.2.0 → 0.4.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aoxborrow/registrar-client",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "One consistent, fully-typed TypeScript interface to many domain registrar APIs (GoDaddy, Cloudflare, Namecheap, Spaceship, Dynadot, Porkbun, and more) behind a single pluggable multi-provider client",
5
5
  "author": {
6
6
  "name": "Aaron Oxborrow",
@@ -11,6 +11,7 @@
11
11
  "access": "public"
12
12
  },
13
13
  "homepage": "https://github.com/aoxborrow/registrar-client",
14
+ "funding": "https://github.com/sponsors/aoxborrow",
14
15
  "repository": {
15
16
  "type": "git",
16
17
  "url": "git+https://github.com/aoxborrow/registrar-client.git"
package/src/client.ts CHANGED
@@ -50,8 +50,10 @@ export class RegistrarClient {
50
50
  const detail = await this.provider.getDomain(name, opts);
51
51
  let merged = { ...domains[index], ...detail };
52
52
  // Some providers expose nameservers only via a dedicated endpoint, not
53
- // on the domain detail — fetch them separately when still empty.
54
- if (merged.nameservers.length === 0) {
53
+ // on the domain detail — fetch them separately when still empty. Skip
54
+ // providers whose getNameservers just re-reads getDomain: it would
55
+ // repeat the same call and return the same empty result.
56
+ if (merged.nameservers.length === 0 && this.provider.requiresNameserversFetch) {
55
57
  try {
56
58
  const nameservers = normalizeNameservers(
57
59
  await this.provider.getNameservers(name, opts)
package/src/registrar.ts CHANGED
@@ -75,6 +75,12 @@ export abstract class BaseRegistrar implements Registrar {
75
75
  return this.features.includes(feature);
76
76
  }
77
77
 
78
+ // See Registrar.requiresNameserversFetch. Defaults true, so a provider
79
+ // whose getNameservers hits a real endpoint keeps the enrichment fallback
80
+ // without opting in; the providers whose getNameservers just re-reads getDomain
81
+ // override this to false to drop the redundant call.
82
+ readonly requiresNameserversFetch: boolean = true;
83
+
78
84
  constructor(
79
85
  credentials: RegistrarCredentials,
80
86
  httpConfig: Omit<HttpClientConfig, 'options'>,
@@ -321,6 +321,10 @@ export class DynadotRegistrar extends BaseRegistrar {
321
321
  return data.domain_info;
322
322
  }
323
323
 
324
+ // getNameservers re-reads getDomain (domain_info carries NS inline; no separate
325
+ // endpoint), so listing enrichment gains nothing from the fallback — skip it.
326
+ override readonly requiresNameserversFetch = false;
327
+
324
328
  override async getNameservers(domainName: string, opts?: RequestOptions): Promise<string[]> {
325
329
  // domain_info carries the nameservers inline (empty for Dynadot-DNS-hosted
326
330
  // domains, which have no external NS)
@@ -271,20 +271,28 @@ export class GoDaddyRegistrar extends BaseRegistrar {
271
271
  static readonly displayName = 'GoDaddy';
272
272
  static readonly website = 'godaddy.com';
273
273
  static readonly helpText =
274
- 'Authenticate one of two ways. For production, create a Personal Access ' +
275
- 'Token (PAT) at https://developer.godaddy.com and pass it as `apiToken` — ' +
276
- 'this enables the modern v3 API. For sandbox testing, create OTE ' +
277
- '(test environment) keys and pass `apiKey` + `apiSecret` with ' +
278
- '{ environment: "sandbox" }; OTE only exposes the legacy v1 API. The ' +
279
- 'sso-key (API Key/Secret) scheme is deprecated by GoDaddy in 2026. ' +
280
- 'Domain forwarding uses the v2 customer-scoped API — set `customerId` to ' +
281
- 'your GoDaddy customer UUID, or to your numeric shopper/account ID (which ' +
282
- 'the client resolves to the UUID via an sso-key-authenticated lookup).';
274
+ 'Create a Personal Access Token (PAT) at https://developer.godaddy.com and ' +
275
+ "paste it here. The PAT is GoDaddy's modern credential: it unlocks the v3 " +
276
+ 'API (discovery, registration, DNS, nameservers) and also authenticates the ' +
277
+ 'legacy v1 API for management operations (renew, transfer, auto-renew, ' +
278
+ 'lock, privacy, contacts) — one token covers everything.';
279
+ // Only the PAT is exposed for configuration — it is the sole credential
280
+ // GoDaddy recommends going forward, and it covers every production operation.
281
+ //
282
+ // The legacy `apiKey` + `apiSecret` (sso-key) pair and the `customerId` field
283
+ // are intentionally NOT advertised here. The code that consumes them is kept
284
+ // (see `authHeader`, `useV3`, `resolveCustomerId`, and the forwarding methods)
285
+ // so OTE/sandbox testing and direct programmatic use still work when those
286
+ // values are passed in credentials explicitly. But because they are no longer
287
+ // configurable, two capabilities are effectively unreachable through normal
288
+ // (config-driven) setup:
289
+ // - OTE/sandbox, which needs an sso-key (`apiKey` + `apiSecret`), and
290
+ // - domain forwarding, which needs `customerId` (a customer UUID), plus an
291
+ // sso-key to resolve a numeric shopper ID to that UUID.
292
+ // GoDaddy is deprecating the sso-key scheme in 2026, so this is the direction
293
+ // of travel regardless.
283
294
  static readonly configFields: ConfigField[] = [
284
- { name: 'apiToken', label: 'API Token (PAT)', type: 'password', required: false },
285
- { name: 'apiKey', label: 'API Key', type: 'password', required: false },
286
- { name: 'apiSecret', label: 'API Secret', type: 'password', required: false },
287
- { name: 'customerId', label: 'Customer ID or Shopper ID', type: 'text', required: false },
295
+ { name: 'apiToken', label: 'API Token (PAT)', type: 'password', required: true },
288
296
  ];
289
297
  // GoDaddy's OTE ("Operational Test Environment") is its sandbox
290
298
  static readonly supportsSandbox = true;
@@ -421,6 +429,10 @@ export class GoDaddyRegistrar extends BaseRegistrar {
421
429
  return this.toDomain(d);
422
430
  }
423
431
 
432
+ // getNameservers re-reads getDomain (GoDaddy has no dedicated NS endpoint), so
433
+ // listing enrichment gains nothing from the fallback — skip it.
434
+ override readonly requiresNameserversFetch = false;
435
+
424
436
  override async getNameservers(domainName: string, opts?: RequestOptions): Promise<string[]> {
425
437
  const domain = await this.getDomain(domainName, opts);
426
438
  return domain.nameservers;
@@ -286,6 +286,10 @@ export class NameSiloRegistrar extends BaseRegistrar {
286
286
  });
287
287
  }
288
288
 
289
+ // getNameservers re-reads getDomain (getDomainInfo carries NS; no separate
290
+ // endpoint), so listing enrichment gains nothing from the fallback — skip it.
291
+ override readonly requiresNameserversFetch = false;
292
+
289
293
  override async getNameservers(domainName: string, opts?: RequestOptions): Promise<string[]> {
290
294
  const domain = await this.getDomain(domainName, opts);
291
295
  return domain.nameservers;
@@ -204,6 +204,10 @@ export class SpaceshipRegistrar extends BaseRegistrar {
204
204
  return this.toDomain(await this.getRaw(domainName, opts));
205
205
  }
206
206
 
207
+ // getNameservers reads the same getRaw payload as getDomain (no dedicated NS
208
+ // endpoint), so listing enrichment gains nothing from the fallback — skip it.
209
+ override readonly requiresNameserversFetch = false;
210
+
207
211
  override async getNameservers(domainName: string, opts?: RequestOptions): Promise<string[]> {
208
212
  const d = await this.getRaw(domainName, opts);
209
213
  return d.nameservers?.hosts ?? [];
package/src/types.ts CHANGED
@@ -307,6 +307,13 @@ export interface Registrar {
307
307
  // the capabilities this provider supports (core contract + its extended features)
308
308
  readonly features: readonly RegistrarFeature[];
309
309
 
310
+ // Whether reading a domain's nameservers needs a getNameservers call beyond
311
+ // getDomain. True when getNameservers hits its own endpoint that can surface
312
+ // nameservers getDomain omits; false when it just re-reads getDomain, so
313
+ // listing enrichment skips the redundant fetch. See BaseRegistrar for the
314
+ // default.
315
+ readonly requiresNameserversFetch: boolean;
316
+
310
317
  // whether this provider supports a given capability
311
318
  supports(feature: RegistrarFeature): boolean;
312
319