doomain 0.1.10 → 0.1.12

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.
@@ -6,10 +6,13 @@ export interface VercelConfig {
6
6
  token?: string;
7
7
  teamId?: string;
8
8
  }
9
- export interface ProviderConfig {
9
+ export interface ProviderAccountConfig {
10
10
  credentials?: Record<string, string>;
11
11
  settings?: Record<string, unknown>;
12
12
  }
13
+ export interface ProviderConfig extends ProviderAccountConfig {
14
+ accounts?: Record<string, ProviderAccountConfig | undefined>;
15
+ }
13
16
  export interface SpaceshipProviderConfig extends ProviderConfig {
14
17
  apiKey?: string;
15
18
  apiSecret?: string;
@@ -1,5 +1,6 @@
1
1
  export declare const jsonFlag: import("@oclif/core/interfaces").BooleanFlag<boolean>;
2
2
  export declare const providerFlag: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
3
+ export declare const accountFlag: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
3
4
  export declare const domainFlag: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
4
5
  export declare const subdomainFlag: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
5
6
  export declare const apexFlag: import("@oclif/core/interfaces").BooleanFlag<boolean>;
package/dist/lib/flags.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Flags } from '@oclif/core';
2
2
  export const jsonFlag = Flags.boolean({ description: 'Output a single JSON object and never prompt.' });
3
3
  export const providerFlag = Flags.string({ description: 'DNS provider id. Inferred from the target domain when omitted.' });
4
+ export const accountFlag = Flags.string({ description: 'DNS provider profile/account alias. Defaults to the provider default account.' });
4
5
  export const domainFlag = Flags.string({ description: 'Target domain or base zone, for example app.example.com or example.com.' });
5
6
  export const subdomainFlag = Flags.string({ description: 'Subdomain to add, for example app for app.example.com.' });
6
7
  export const apexFlag = Flags.boolean({ description: 'Use the root/apex domain instead of a subdomain.' });
@@ -1,6 +1,7 @@
1
1
  import type { DnsRecordInput } from './providers/types.js';
2
2
  export interface LinkDomainInput {
3
3
  provider?: string;
4
+ account?: string;
4
5
  domain?: string;
5
6
  subdomain?: string;
6
7
  apex?: boolean;
@@ -21,6 +22,9 @@ export type LinkDomainProgressCallback = (progress: LinkDomainProgress) => void;
21
22
  export interface LinkDomainPlan {
22
23
  provider: string;
23
24
  providerInferred: boolean;
25
+ account: string;
26
+ accountInferred: boolean;
27
+ isDefaultAccount: boolean;
24
28
  project: string;
25
29
  projectSource: LinkDomainProjectSource;
26
30
  recordName: string;
@@ -4,8 +4,9 @@ import { dirname, join, parse } from 'node:path';
4
4
  import { loadConfig } from './config.js';
5
5
  import { DoomainError } from './errors.js';
6
6
  import { detectLocalVercelProject } from './local-vercel.js';
7
+ import { DEFAULT_PROVIDER_ACCOUNT, isDefaultProviderAccount, listConfiguredProviderAccounts, normalizeProviderAccount, } from './providers/core/config.js';
7
8
  import { createProvider, getProviderDefinition, listProviderDefinitions } from './providers/registry.js';
8
- import { isProviderConfigured, listProviderStatuses } from './providers/status.js';
9
+ import { listProviderStatuses } from './providers/status.js';
9
10
  import { normalizeDomain, normalizeSubdomain } from './validate.js';
10
11
  import { createVercelClient, resolveVercelConfig, VERCEL_APEX_A_RECORD, VERCEL_CNAME_RECORD } from './vercel.js';
11
12
  function cleanDnsValue(value) {
@@ -118,10 +119,12 @@ async function resolveZone(provider, zoneDomain) {
118
119
  async function providerConnectionDetails() {
119
120
  return (await listProviderStatuses({ verify: false })).map((provider) => ({
120
121
  configured: provider.configured,
122
+ account: provider.account,
121
123
  default: provider.default,
122
124
  displayName: provider.displayName,
123
125
  docsUrl: provider.docsUrl,
124
126
  id: provider.id,
127
+ isDefaultAccount: provider.isDefaultAccount,
125
128
  }));
126
129
  }
127
130
  function resolveRequestedDomain(opts) {
@@ -155,51 +158,91 @@ function targetFromZone(fullDomain, zoneDomain) {
155
158
  }
156
159
  function candidateDetails(candidates) {
157
160
  return candidates.map((candidate) => ({
161
+ account: candidate.account,
162
+ isDefaultAccount: candidate.isDefaultAccount,
158
163
  provider: candidate.provider,
159
164
  providerName: candidate.providerName,
160
165
  zoneDomain: candidate.zone.name,
161
166
  }));
162
167
  }
163
- async function loadProviderZones(definition) {
164
- const provider = await createProvider(definition.id);
168
+ function defaultAccountRef(providerId) {
169
+ return { account: DEFAULT_PROVIDER_ACCOUNT, isDefaultAccount: true, providerId };
170
+ }
171
+ function explicitAccountRef(providerId, account) {
172
+ const normalized = normalizeProviderAccount(account);
173
+ return { account: normalized, isDefaultAccount: isDefaultProviderAccount(normalized), providerId };
174
+ }
175
+ async function loadProviderZones(definition, account) {
176
+ const provider = await createProvider(definition.id, { account: account.account });
165
177
  const zones = await provider.listZones();
166
178
  return {
167
- candidates: zones.map((zone) => ({ provider: definition.id, providerName: definition.displayName, zone })),
168
- search: { displayName: definition.displayName, id: definition.id, zones: zones.map((zone) => zone.name) },
179
+ candidates: zones.map((zone) => ({
180
+ account: account.account,
181
+ isDefaultAccount: account.isDefaultAccount,
182
+ provider: definition.id,
183
+ providerName: definition.displayName,
184
+ zone,
185
+ })),
186
+ search: {
187
+ account: account.account,
188
+ displayName: definition.displayName,
189
+ id: definition.id,
190
+ isDefaultAccount: account.isDefaultAccount,
191
+ zones: zones.map((zone) => zone.name),
192
+ },
169
193
  };
170
194
  }
171
- async function loadConfiguredProviderZones(providerId) {
195
+ async function loadConfiguredProviderZones(providerId, accountInput) {
196
+ const config = await loadConfig();
197
+ const account = accountInput ? normalizeProviderAccount(accountInput) : undefined;
172
198
  if (providerId) {
173
199
  const definition = getProviderDefinition(providerId);
174
- const result = await loadProviderZones(definition);
175
- return { candidates: result.candidates, providerInferred: false, searched: [result.search] };
200
+ const accounts = account ? [explicitAccountRef(definition.id, account)] : listConfiguredProviderAccounts(config, definition);
201
+ const selectedAccounts = accounts.length > 0 ? accounts : [defaultAccountRef(definition.id)];
202
+ const results = await Promise.all(selectedAccounts.map((ref) => loadProviderZones(definition, ref)));
203
+ return {
204
+ accountInferred: account === undefined,
205
+ candidates: results.flatMap((result) => result.candidates),
206
+ providerInferred: false,
207
+ searched: results.map((result) => result.search),
208
+ };
176
209
  }
177
- const config = await loadConfig();
178
- const definitions = listProviderDefinitions().filter((definition) => isProviderConfigured(definition, config));
179
- if (definitions.length === 0) {
180
- throw new DoomainError('CONFIG_NOT_FOUND', 'No DNS provider is configured. Run `doomain providers connect` first.', {
210
+ const providerAccounts = listProviderDefinitions().flatMap((definition) => listConfiguredProviderAccounts(config, definition)
211
+ .filter((ref) => !account || ref.account === account)
212
+ .map((ref) => ({ definition, ref })));
213
+ if (providerAccounts.length === 0) {
214
+ const message = account
215
+ ? `No DNS provider account named ${account} is configured. Run \`doomain providers connect <provider> --account ${account}\` first.`
216
+ : 'No DNS provider is configured. Run `doomain providers connect` first.';
217
+ throw new DoomainError('CONFIG_NOT_FOUND', message, {
218
+ account,
181
219
  configuredProviders: await providerConnectionDetails(),
182
220
  recovery: 'Connect the DNS provider that owns this domain, then retry `doomain link <domain> --json`.',
183
- suggestedCommands: ['doomain providers connect', 'doomain link <domain> --json'],
221
+ suggestedCommands: account
222
+ ? [`doomain providers connect <provider> --account ${account}`, 'doomain link <domain> --json']
223
+ : ['doomain providers connect', 'doomain link <domain> --json'],
184
224
  });
185
225
  }
186
- const results = await Promise.all(definitions.map(async (definition) => {
226
+ const results = await Promise.all(providerAccounts.map(async ({ definition, ref }) => {
187
227
  try {
188
- return await loadProviderZones(definition);
228
+ return await loadProviderZones(definition, ref);
189
229
  }
190
230
  catch (error) {
191
231
  return {
192
232
  candidates: [],
193
233
  search: {
234
+ account: ref.account,
194
235
  displayName: definition.displayName,
195
236
  error: error instanceof Error ? error.message : String(error),
196
237
  id: definition.id,
238
+ isDefaultAccount: ref.isDefaultAccount,
197
239
  zones: [],
198
240
  },
199
241
  };
200
242
  }
201
243
  }));
202
244
  return {
245
+ accountInferred: account === undefined,
203
246
  candidates: results.flatMap((result) => result.candidates),
204
247
  providerInferred: true,
205
248
  searched: results.map((result) => result.search),
@@ -211,30 +254,35 @@ async function resolveProviderTarget(input) {
211
254
  domain: await resolveConfiguredDomain(input.domain),
212
255
  subdomain: input.subdomain,
213
256
  });
214
- const zones = await loadConfiguredProviderZones(input.provider);
257
+ const zones = await loadConfiguredProviderZones(input.provider, input.account);
215
258
  const matches = zones.candidates
216
259
  .filter((candidate) => zoneMatchesDomain(requested.fullDomain, candidate.zone.name, requested.forceExactZone))
217
260
  .sort((a, b) => b.zone.name.length - a.zone.name.length);
218
261
  if (matches.length === 0) {
262
+ const account = input.account ? normalizeProviderAccount(input.account) : undefined;
219
263
  const providerMessage = input.provider
220
- ? `${getProviderDefinition(input.provider).displayName} does not have a matching DNS zone for ${requested.fullDomain}.`
264
+ ? `${getProviderDefinition(input.provider).displayName}${account ? ` account ${account}` : ''} does not have a matching DNS zone for ${requested.fullDomain}.`
221
265
  : `No configured DNS provider has a matching DNS zone for ${requested.fullDomain}.`;
222
266
  throw new DoomainError('PROVIDER_ZONE_NOT_FOUND', providerMessage, {
267
+ account,
223
268
  configuredProviders: await providerConnectionDetails(),
224
269
  domain: requested.fullDomain,
225
- recovery: 'Retry with --provider <id> only if another configured provider owns this zone. Otherwise connect the DNS provider that owns this domain.',
270
+ recovery: 'Retry with --provider <id> --account <alias> only if another configured provider account owns this zone. Otherwise connect the DNS provider account that owns this domain.',
226
271
  searchedZones: zones.searched,
227
- suggestedCommands: [`doomain link ${requested.fullDomain} --provider <id> --json`, 'doomain providers connect'],
272
+ suggestedCommands: [`doomain link ${requested.fullDomain} --provider <id> --account <alias> --json`, 'doomain providers connect'],
228
273
  });
229
274
  }
230
275
  const bestLength = matches[0].zone.name.length;
231
276
  const bestMatches = matches.filter((candidate) => candidate.zone.name.length === bestLength);
232
- const uniqueBestMatches = bestMatches.filter((candidate, index, candidates) => candidates.findIndex((item) => item.provider === candidate.provider && item.zone.name === candidate.zone.name) === index);
277
+ const uniqueBestMatches = bestMatches.filter((candidate, index, candidates) => candidates.findIndex((item) => item.provider === candidate.provider && item.account === candidate.account && item.zone.name === candidate.zone.name) === index);
233
278
  if (uniqueBestMatches.length > 1) {
234
- throw new DoomainError('PROVIDER_ZONE_AMBIGUOUS', `Multiple DNS providers have a matching DNS zone for ${requested.fullDomain}. Pass --provider to choose one.`, { candidates: candidateDetails(uniqueBestMatches), domain: requested.fullDomain });
279
+ throw new DoomainError('PROVIDER_ZONE_AMBIGUOUS', `Multiple DNS provider accounts have a matching DNS zone for ${requested.fullDomain}. Pass --provider and --account to choose one.`, { candidates: candidateDetails(uniqueBestMatches), domain: requested.fullDomain });
235
280
  }
236
281
  const selected = uniqueBestMatches[0];
237
282
  return {
283
+ account: selected.account,
284
+ accountInferred: zones.accountInferred,
285
+ isDefaultAccount: selected.isDefaultAccount,
238
286
  provider: selected.provider,
239
287
  providerInferred: zones.providerInferred,
240
288
  target: targetFromZone(requested.fullDomain, selected.zone.name),
@@ -414,9 +462,12 @@ function reportProgress(input, stage, message) {
414
462
  export async function createLinkPlan(input) {
415
463
  const project = await resolveProject(input.project);
416
464
  const resolved = await resolveProviderTarget(input);
417
- const { provider, providerInferred, target } = resolved;
465
+ const { account, accountInferred, isDefaultAccount, provider, providerInferred, target } = resolved;
418
466
  const record = planBaseRecord({ isApex: target.isApex, provider, recordName: target.recordName });
419
467
  return {
468
+ account,
469
+ accountInferred,
470
+ isDefaultAccount,
420
471
  provider,
421
472
  providerInferred,
422
473
  project: project.project,
@@ -441,7 +492,7 @@ export async function linkDomain(input) {
441
492
  };
442
493
  }
443
494
  const vercel = createVercelClient(await resolveVercelConfig());
444
- const provider = await createProvider(plan.provider);
495
+ const provider = await createProvider(plan.provider, { account: plan.account });
445
496
  reportProgress(input, 'dns:resolve-zone', `Finding ${provider.name} DNS zone`);
446
497
  const zone = await resolveZone(provider, plan.zoneDomain);
447
498
  reportProgress(input, 'vercel:add-domain', 'Adding domain to Vercel');
@@ -1,5 +1,20 @@
1
- import { type DoomainConfig } from '../../config.js';
1
+ import { type DoomainConfig, type ProviderConfig } from '../../config.js';
2
2
  import type { CredentialDefinition, DnsProviderDefinition, ProviderContext } from './types.js';
3
- export declare function getProviderCredentials(config: DoomainConfig, providerId: string): Record<string, string>;
4
- export declare function getProviderCredential(config: DoomainConfig, providerId: string, credential: CredentialDefinition): string | undefined;
5
- export declare function createProviderContext(definition: DnsProviderDefinition): Promise<ProviderContext>;
3
+ export declare const DEFAULT_PROVIDER_ACCOUNT = "default";
4
+ export interface ProviderAccountRef {
5
+ account: string;
6
+ isDefaultAccount: boolean;
7
+ providerId: string;
8
+ }
9
+ export interface ProviderAccountOptions {
10
+ account?: string;
11
+ }
12
+ export declare function normalizeProviderAccount(account?: string): string;
13
+ export declare function isDefaultProviderAccount(account?: string): boolean;
14
+ export declare function getProviderCredentials(config: DoomainConfig, providerId: string, opts?: ProviderAccountOptions): Record<string, string>;
15
+ export declare function getProviderCredential(config: DoomainConfig, providerId: string, credential: CredentialDefinition, opts?: ProviderAccountOptions): string | undefined;
16
+ export declare function providerAccountHasCredentials(config: DoomainConfig, providerId: string, accountInput?: string): boolean;
17
+ export declare function withProviderAccountCredentials(current: ProviderConfig | undefined, accountInput: string, credentials: Record<string, string>): ProviderConfig;
18
+ export declare function isProviderAccountConfigured(definition: DnsProviderDefinition, config: DoomainConfig, opts?: ProviderAccountOptions): boolean;
19
+ export declare function listConfiguredProviderAccounts(config: DoomainConfig, definition: DnsProviderDefinition): ProviderAccountRef[];
20
+ export declare function createProviderContext(definition: DnsProviderDefinition, opts?: ProviderAccountOptions): Promise<ProviderContext>;
@@ -1,5 +1,19 @@
1
1
  import { loadConfig } from '../../config.js';
2
2
  import { DoomainError } from '../../errors.js';
3
+ import { ensureProviderAccount } from '../../validate.js';
4
+ export const DEFAULT_PROVIDER_ACCOUNT = 'default';
5
+ export function normalizeProviderAccount(account) {
6
+ if (!account?.trim())
7
+ return DEFAULT_PROVIDER_ACCOUNT;
8
+ return ensureProviderAccount(account);
9
+ }
10
+ export function isDefaultProviderAccount(account) {
11
+ return normalizeProviderAccount(account) === DEFAULT_PROVIDER_ACCOUNT;
12
+ }
13
+ function providerConfig(config, providerId) {
14
+ const value = config.providers?.[providerId];
15
+ return value && typeof value === 'object' ? value : undefined;
16
+ }
3
17
  function legacyCredential(config, providerId, key) {
4
18
  if (providerId !== 'spaceship')
5
19
  return undefined;
@@ -8,25 +22,77 @@ function legacyCredential(config, providerId, key) {
8
22
  return undefined;
9
23
  return legacy[key];
10
24
  }
11
- export function getProviderCredentials(config, providerId) {
12
- const providerConfig = config.providers?.[providerId];
13
- const credentials = providerConfig && typeof providerConfig === 'object' && 'credentials' in providerConfig
14
- ? (providerConfig.credentials ?? {})
15
- : {};
16
- return { ...credentials };
25
+ function credentialFromSavedConfig(config, providerId, account, key) {
26
+ const credentials = getProviderCredentials(config, providerId, { account });
27
+ if (credentials[key])
28
+ return credentials[key];
29
+ if (account === DEFAULT_PROVIDER_ACCOUNT)
30
+ return legacyCredential(config, providerId, key);
31
+ return undefined;
32
+ }
33
+ function hasCredentials(credentials) {
34
+ return credentials !== undefined && Object.keys(credentials).length > 0;
35
+ }
36
+ export function getProviderCredentials(config, providerId, opts = {}) {
37
+ const account = normalizeProviderAccount(opts.account);
38
+ const current = providerConfig(config, providerId);
39
+ const credentials = account === DEFAULT_PROVIDER_ACCOUNT ? current?.credentials : current?.accounts?.[account]?.credentials;
40
+ return { ...(credentials ?? {}) };
41
+ }
42
+ export function getProviderCredential(config, providerId, credential, opts = {}) {
43
+ const account = normalizeProviderAccount(opts.account);
44
+ return process.env[credential.env] || credentialFromSavedConfig(config, providerId, account, credential.key);
17
45
  }
18
- export function getProviderCredential(config, providerId, credential) {
19
- return process.env[credential.env] || getProviderCredentials(config, providerId)[credential.key] || legacyCredential(config, providerId, credential.key);
46
+ export function providerAccountHasCredentials(config, providerId, accountInput) {
47
+ const account = normalizeProviderAccount(accountInput);
48
+ const current = providerConfig(config, providerId);
49
+ if (!current)
50
+ return false;
51
+ if (account !== DEFAULT_PROVIDER_ACCOUNT)
52
+ return hasCredentials(current.accounts?.[account]?.credentials);
53
+ return hasCredentials(current.credentials) || Boolean(legacyCredential(config, providerId, 'apiKey') || legacyCredential(config, providerId, 'apiSecret'));
54
+ }
55
+ export function withProviderAccountCredentials(current, accountInput, credentials) {
56
+ const account = normalizeProviderAccount(accountInput);
57
+ if (account === DEFAULT_PROVIDER_ACCOUNT)
58
+ return { ...current, credentials };
59
+ return {
60
+ ...current,
61
+ accounts: {
62
+ ...current?.accounts,
63
+ [account]: { credentials },
64
+ },
65
+ };
66
+ }
67
+ export function isProviderAccountConfigured(definition, config, opts = {}) {
68
+ return definition.credentials.every((credential) => credential.required === false || Boolean(getProviderCredential(config, definition.id, credential, opts)));
69
+ }
70
+ export function listConfiguredProviderAccounts(config, definition) {
71
+ const accounts = [];
72
+ if (isProviderAccountConfigured(definition, config, { account: DEFAULT_PROVIDER_ACCOUNT })) {
73
+ accounts.push({ account: DEFAULT_PROVIDER_ACCOUNT, isDefaultAccount: true, providerId: definition.id });
74
+ }
75
+ for (const account of Object.keys(providerConfig(config, definition.id)?.accounts ?? {}).sort()) {
76
+ const normalized = normalizeProviderAccount(account);
77
+ if (normalized === DEFAULT_PROVIDER_ACCOUNT)
78
+ continue;
79
+ if (!isProviderAccountConfigured(definition, config, { account: normalized }))
80
+ continue;
81
+ accounts.push({ account: normalized, isDefaultAccount: false, providerId: definition.id });
82
+ }
83
+ return accounts;
20
84
  }
21
- export async function createProviderContext(definition) {
85
+ export async function createProviderContext(definition, opts = {}) {
22
86
  const config = await loadConfig();
23
87
  const credentials = {};
88
+ const account = normalizeProviderAccount(opts.account);
24
89
  for (const credential of definition.credentials) {
25
- const value = getProviderCredential(config, definition.id, credential);
90
+ const value = getProviderCredential(config, definition.id, credential, { account });
26
91
  if (value)
27
92
  credentials[credential.key] = value;
28
93
  else if (credential.required !== false) {
29
- throw new DoomainError('MISSING_CREDENTIALS', `Missing ${definition.displayName} ${credential.label}. Run \`doomain providers connect ${definition.id}\` or set ${credential.env}.`);
94
+ const accountHint = account === DEFAULT_PROVIDER_ACCOUNT ? '' : ` for account ${account}`;
95
+ throw new DoomainError('MISSING_CREDENTIALS', `Missing ${definition.displayName} ${credential.label}${accountHint}. Run \`doomain providers connect ${definition.id}${account === DEFAULT_PROVIDER_ACCOUNT ? '' : ` --account ${account}`}\` or set ${credential.env}.`, { account, provider: definition.id });
30
96
  }
31
97
  }
32
98
  return { credentials, debug: process.env.DOOMAIN_DEBUG === '1' };
@@ -1,4 +1,6 @@
1
1
  import type { DnsProvider, DnsProviderDefinition } from './types.js';
2
2
  export declare function listProviderDefinitions(): DnsProviderDefinition[];
3
3
  export declare function getProviderDefinition(id: string): DnsProviderDefinition;
4
- export declare function createProvider(id: string): Promise<DnsProvider>;
4
+ export declare function createProvider(id: string, opts?: {
5
+ account?: string;
6
+ }): Promise<DnsProvider>;
@@ -16,7 +16,7 @@ export function getProviderDefinition(id) {
16
16
  throw new DoomainError('PROVIDER_NOT_FOUND', `Unsupported DNS provider: ${id}`);
17
17
  return definition;
18
18
  }
19
- export async function createProvider(id) {
19
+ export async function createProvider(id, opts = {}) {
20
20
  const definition = getProviderDefinition(id);
21
- return definition.create(await createProviderContext(definition));
21
+ return definition.create(await createProviderContext(definition, opts));
22
22
  }
@@ -1,6 +1,7 @@
1
1
  import { type DoomainConfig } from '../config.js';
2
2
  import type { DnsProviderDefinition } from './types.js';
3
3
  export interface ProviderStatus {
4
+ account: string;
4
5
  configured: boolean;
5
6
  default: boolean;
6
7
  displayName: string;
@@ -8,9 +9,12 @@ export interface ProviderStatus {
8
9
  domainCount?: number;
9
10
  error?: string;
10
11
  id: string;
12
+ isDefaultAccount: boolean;
11
13
  verified?: boolean;
12
14
  }
13
- export declare function isProviderConfigured(definition: DnsProviderDefinition, config: DoomainConfig): boolean;
15
+ export declare function isProviderConfigured(definition: DnsProviderDefinition, config: DoomainConfig, opts?: {
16
+ account?: string;
17
+ }): boolean;
14
18
  export declare function listProviderStatuses(opts?: {
15
19
  verify?: boolean;
16
20
  }): Promise<ProviderStatus[]>;
@@ -1,33 +1,40 @@
1
1
  import { loadConfig } from '../config.js';
2
- import { getProviderCredential } from './core/config.js';
2
+ import { DEFAULT_PROVIDER_ACCOUNT, isProviderAccountConfigured, listConfiguredProviderAccounts, normalizeProviderAccount, } from './core/config.js';
3
3
  import { createProvider, listProviderDefinitions } from './registry.js';
4
- export function isProviderConfigured(definition, config) {
5
- return definition.credentials.every((credential) => credential.required === false || Boolean(getProviderCredential(config, definition.id, credential)));
4
+ export function isProviderConfigured(definition, config, opts = {}) {
5
+ return isProviderAccountConfigured(definition, config, opts);
6
6
  }
7
7
  export async function listProviderStatuses(opts = {}) {
8
8
  const config = await loadConfig();
9
9
  const statuses = [];
10
10
  for (const definition of listProviderDefinitions()) {
11
- const configured = isProviderConfigured(definition, config);
12
- const status = {
13
- configured,
14
- default: config.defaults?.provider === definition.id,
15
- displayName: definition.displayName,
16
- docsUrl: definition.docsUrl,
17
- id: definition.id,
18
- };
19
- if (configured && opts.verify) {
20
- try {
21
- const zones = await (await createProvider(definition.id)).listZones();
22
- status.domainCount = zones.length;
23
- status.verified = true;
24
- }
25
- catch (error) {
26
- status.error = error instanceof Error ? error.message : String(error);
27
- status.verified = false;
11
+ const accounts = listConfiguredProviderAccounts(config, definition);
12
+ const refs = accounts.length > 0 ? accounts : [{ account: DEFAULT_PROVIDER_ACCOUNT, isDefaultAccount: true, providerId: definition.id }];
13
+ for (const ref of refs) {
14
+ const account = normalizeProviderAccount(ref.account);
15
+ const configured = accounts.some((item) => item.account === account);
16
+ const status = {
17
+ account,
18
+ configured,
19
+ default: config.defaults?.provider === definition.id,
20
+ displayName: definition.displayName,
21
+ docsUrl: definition.docsUrl,
22
+ id: definition.id,
23
+ isDefaultAccount: ref.isDefaultAccount,
24
+ };
25
+ if (configured && opts.verify) {
26
+ try {
27
+ const zones = await (await createProvider(definition.id, { account })).listZones();
28
+ status.domainCount = zones.length;
29
+ status.verified = true;
30
+ }
31
+ catch (error) {
32
+ status.error = error instanceof Error ? error.message : String(error);
33
+ status.verified = false;
34
+ }
28
35
  }
36
+ statuses.push(status);
29
37
  }
30
- statuses.push(status);
31
38
  }
32
39
  return statuses;
33
40
  }
@@ -12,4 +12,5 @@ export declare function resolveDomainTarget(opts: {
12
12
  apex?: boolean;
13
13
  }): DomainTarget;
14
14
  export declare function ensureProviderId(value: string): string;
15
+ export declare function ensureProviderAccount(value: string): string;
15
16
  export declare function ensureProject(value?: string): string;
@@ -54,6 +54,13 @@ export function ensureProviderId(value) {
54
54
  }
55
55
  return provider;
56
56
  }
57
+ export function ensureProviderAccount(value) {
58
+ const account = value.trim().toLowerCase();
59
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(account)) {
60
+ throw new DoomainError('INVALID_INPUT', `Invalid provider account alias: ${value}`);
61
+ }
62
+ return account;
63
+ }
57
64
  export function ensureProject(value) {
58
65
  const project = value?.trim();
59
66
  if (!project)