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.
- package/dist/commands/domains/list.d.ts +1 -0
- package/dist/commands/domains/list.js +33 -11
- package/dist/commands/link.d.ts +1 -0
- package/dist/commands/link.js +3 -1
- package/dist/commands/providers/connect.d.ts +1 -0
- package/dist/commands/providers/connect.js +46 -5
- package/dist/commands/providers/disconnect.d.ts +1 -0
- package/dist/commands/providers/disconnect.js +35 -5
- package/dist/commands/providers/status.js +4 -2
- package/dist/commands/providers/verify.d.ts +1 -0
- package/dist/commands/providers/verify.js +6 -3
- package/dist/commands/wizard.js +60 -19
- package/dist/lib/command-schema.d.ts +1 -1
- package/dist/lib/command-schema.js +22 -3
- package/dist/lib/config.d.ts +4 -1
- package/dist/lib/flags.d.ts +1 -0
- package/dist/lib/flags.js +1 -0
- package/dist/lib/link-domain.d.ts +4 -0
- package/dist/lib/link-domain.js +74 -23
- package/dist/lib/providers/core/config.d.ts +19 -4
- package/dist/lib/providers/core/config.js +77 -11
- package/dist/lib/providers/registry.d.ts +3 -1
- package/dist/lib/providers/registry.js +2 -2
- package/dist/lib/providers/status.d.ts +5 -1
- package/dist/lib/providers/status.js +28 -21
- package/dist/lib/validate.d.ts +1 -0
- package/dist/lib/validate.js +7 -0
- package/oclif.manifest.json +80 -37
- package/package.json +1 -1
package/dist/lib/config.d.ts
CHANGED
|
@@ -6,10 +6,13 @@ export interface VercelConfig {
|
|
|
6
6
|
token?: string;
|
|
7
7
|
teamId?: string;
|
|
8
8
|
}
|
|
9
|
-
export interface
|
|
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;
|
package/dist/lib/flags.d.ts
CHANGED
|
@@ -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;
|
package/dist/lib/link-domain.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
164
|
-
|
|
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) => ({
|
|
168
|
-
|
|
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
|
|
175
|
-
|
|
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
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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:
|
|
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(
|
|
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
|
|
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
|
|
4
|
-
export
|
|
5
|
-
|
|
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
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
19
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
}
|
package/dist/lib/validate.d.ts
CHANGED
|
@@ -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;
|
package/dist/lib/validate.js
CHANGED
|
@@ -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)
|