doomain 0.1.12 → 0.1.13
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/link.js +40 -0
- package/dist/commands/wizard.js +38 -2
- package/dist/lib/errors.d.ts +1 -1
- package/dist/lib/link-domain.d.ts +13 -2
- package/dist/lib/link-domain.js +42 -6
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
package/dist/commands/link.js
CHANGED
|
@@ -1,8 +1,32 @@
|
|
|
1
1
|
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import * as p from '@clack/prompts';
|
|
2
3
|
import { DoomainError } from '../lib/errors.js';
|
|
3
4
|
import { accountFlag, apexFlag, domainFlag, jsonFlag, projectFlag, providerFlag, subdomainFlag } from '../lib/flags.js';
|
|
4
5
|
import { linkDomain } from '../lib/link-domain.js';
|
|
5
6
|
import { createOutput, outputError } from '../lib/output.js';
|
|
7
|
+
function recordName(name) {
|
|
8
|
+
return name === '@' ? 'root' : name;
|
|
9
|
+
}
|
|
10
|
+
function recordLine(record) {
|
|
11
|
+
const details = [
|
|
12
|
+
record.priority === undefined ? undefined : `priority ${record.priority}`,
|
|
13
|
+
record.proxied === undefined ? undefined : `proxied ${record.proxied}`,
|
|
14
|
+
record.ttl === undefined ? undefined : `ttl ${record.ttl}`,
|
|
15
|
+
].filter(Boolean);
|
|
16
|
+
return `${record.type} ${recordName(record.name)} -> ${record.value}${details.length > 0 ? ` (${details.join(', ')})` : ''}`;
|
|
17
|
+
}
|
|
18
|
+
function dnsOverrideNote(warning) {
|
|
19
|
+
const account = warning.account === 'default' ? warning.providerName : `${warning.providerName}/${warning.account}`;
|
|
20
|
+
return [
|
|
21
|
+
`${warning.domain} already has DNS records in ${account} (${warning.zoneDomain}) that do not match the Vercel target.`,
|
|
22
|
+
'',
|
|
23
|
+
'Existing:',
|
|
24
|
+
...warning.conflicts.map((conflict) => `- ${recordLine(conflict.existing)}`),
|
|
25
|
+
'',
|
|
26
|
+
'Desired:',
|
|
27
|
+
...warning.desired.map((record) => `- ${recordLine(record)}`),
|
|
28
|
+
].join('\n');
|
|
29
|
+
}
|
|
6
30
|
export default class Link extends Command {
|
|
7
31
|
static description = 'Link a Vercel project to a domain and create DNS records.';
|
|
8
32
|
static examples = [
|
|
@@ -51,6 +75,22 @@ export default class Link extends Command {
|
|
|
51
75
|
const result = await linkDomain({
|
|
52
76
|
...flags,
|
|
53
77
|
domain,
|
|
78
|
+
confirmDnsOverride: out.json
|
|
79
|
+
? undefined
|
|
80
|
+
: async (warning) => {
|
|
81
|
+
spinner?.stop('Existing DNS target found');
|
|
82
|
+
p.note(dnsOverrideNote(warning), 'DNS already points elsewhere');
|
|
83
|
+
const confirmed = await p.confirm({
|
|
84
|
+
message: `Override existing DNS records for ${warning.domain}?`,
|
|
85
|
+
initialValue: false,
|
|
86
|
+
});
|
|
87
|
+
if (confirmed === true) {
|
|
88
|
+
spinner?.start('Continuing domain link');
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
spinner = undefined;
|
|
92
|
+
return false;
|
|
93
|
+
},
|
|
54
94
|
dryRun: false,
|
|
55
95
|
progress: out.json ? undefined : ({ message }) => spinner?.message(message),
|
|
56
96
|
timeoutSeconds: flags.timeout,
|
package/dist/commands/wizard.js
CHANGED
|
@@ -157,9 +157,31 @@ async function promptVercelToken(globalTokens) {
|
|
|
157
157
|
}
|
|
158
158
|
return promptRequired('Vercel token', { password: true });
|
|
159
159
|
}
|
|
160
|
+
function recordName(name) {
|
|
161
|
+
return name === '@' ? 'root' : name;
|
|
162
|
+
}
|
|
163
|
+
function recordLine(record) {
|
|
164
|
+
const details = [
|
|
165
|
+
record.priority === undefined ? undefined : `priority ${record.priority}`,
|
|
166
|
+
record.proxied === undefined ? undefined : `proxied ${record.proxied}`,
|
|
167
|
+
record.ttl === undefined ? undefined : `ttl ${record.ttl}`,
|
|
168
|
+
].filter(Boolean);
|
|
169
|
+
return `${record.type} ${recordName(record.name)} -> ${record.value}${details.length > 0 ? ` (${details.join(', ')})` : ''}`;
|
|
170
|
+
}
|
|
160
171
|
function recordPreview(record, providerName) {
|
|
161
|
-
|
|
162
|
-
|
|
172
|
+
return `DNS: ${recordLine(record)} in ${providerName}`;
|
|
173
|
+
}
|
|
174
|
+
function dnsOverrideNote(warning) {
|
|
175
|
+
const account = warning.account === DEFAULT_PROVIDER_ACCOUNT ? warning.providerName : `${warning.providerName}/${warning.account}`;
|
|
176
|
+
return [
|
|
177
|
+
`${warning.domain} already has DNS records in ${account} (${warning.zoneDomain}) that do not match the Vercel target.`,
|
|
178
|
+
'',
|
|
179
|
+
'Existing:',
|
|
180
|
+
...warning.conflicts.map((conflict) => `- ${recordLine(conflict.existing)}`),
|
|
181
|
+
'',
|
|
182
|
+
'Desired:',
|
|
183
|
+
...warning.desired.map((record) => `- ${recordLine(record)}`),
|
|
184
|
+
].join('\n');
|
|
163
185
|
}
|
|
164
186
|
export default class Wizard extends Command {
|
|
165
187
|
static description = 'Interactive Vercel domain linker.';
|
|
@@ -389,6 +411,20 @@ export default class Wizard extends Command {
|
|
|
389
411
|
subdomain,
|
|
390
412
|
apex,
|
|
391
413
|
project,
|
|
414
|
+
confirmDnsOverride: async (warning) => {
|
|
415
|
+
spinner.stop('Existing DNS target found');
|
|
416
|
+
p.note(dnsOverrideNote(warning), 'DNS already points elsewhere');
|
|
417
|
+
const confirmed = await p.confirm({
|
|
418
|
+
message: `Override existing DNS records for ${warning.domain}?`,
|
|
419
|
+
initialValue: false,
|
|
420
|
+
});
|
|
421
|
+
if (confirmed === true) {
|
|
422
|
+
spinner.start('Continuing domain link');
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
425
|
+
activeSpinner = undefined;
|
|
426
|
+
return false;
|
|
427
|
+
},
|
|
392
428
|
progress: ({ message }) => spinner.message(message),
|
|
393
429
|
wait: true,
|
|
394
430
|
});
|
package/dist/lib/errors.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type DoomainErrorCode = 'CONFIG_NOT_FOUND' | 'DOMAIN_LINK_FAILED' | 'DOMAIN_ALREADY_ASSIGNED' | 'DOMAIN_VERIFY_FAILED' | 'INVALID_INPUT' | 'MISSING_ARGUMENT' | 'MISSING_CREDENTIALS' | 'PROVIDER_API_ERROR' | 'PROVIDER_AUTH_FAILED' | 'PROVIDER_PERMISSION_DENIED' | 'PROVIDER_NOT_FOUND' | 'PROVIDER_RATE_LIMITED' | 'PROVIDER_RECORD_CONFLICT' | 'PROVIDER_UNSUPPORTED_RECORD' | 'PROVIDER_ZONE_AMBIGUOUS' | 'PROVIDER_ZONE_NOT_FOUND' | 'PROJECT_NOT_FOUND' | 'VERCEL_AUTH_FAILED' | 'VERCEL_PROJECT_NOT_LINKED';
|
|
1
|
+
export type DoomainErrorCode = 'CONFIG_NOT_FOUND' | 'DNS_TARGET_CONFLICT' | 'DOMAIN_LINK_FAILED' | 'DOMAIN_ALREADY_ASSIGNED' | 'DOMAIN_VERIFY_FAILED' | 'INVALID_INPUT' | 'MISSING_ARGUMENT' | 'MISSING_CREDENTIALS' | 'PROVIDER_API_ERROR' | 'PROVIDER_AUTH_FAILED' | 'PROVIDER_PERMISSION_DENIED' | 'PROVIDER_NOT_FOUND' | 'PROVIDER_RATE_LIMITED' | 'PROVIDER_RECORD_CONFLICT' | 'PROVIDER_UNSUPPORTED_RECORD' | 'PROVIDER_ZONE_AMBIGUOUS' | 'PROVIDER_ZONE_NOT_FOUND' | 'PROJECT_NOT_FOUND' | 'VERCEL_AUTH_FAILED' | 'VERCEL_PROJECT_NOT_LINKED';
|
|
2
2
|
export declare class DoomainError extends Error {
|
|
3
3
|
readonly code: DoomainErrorCode;
|
|
4
4
|
readonly details?: unknown;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DnsRecordInput } from './providers/types.js';
|
|
1
|
+
import type { DnsConflict, DnsRecordInput } from './providers/types.js';
|
|
2
2
|
export interface LinkDomainInput {
|
|
3
3
|
provider?: string;
|
|
4
4
|
account?: string;
|
|
@@ -11,14 +11,25 @@ export interface LinkDomainInput {
|
|
|
11
11
|
wait?: boolean;
|
|
12
12
|
timeoutSeconds?: number;
|
|
13
13
|
progress?: LinkDomainProgressCallback;
|
|
14
|
+
confirmDnsOverride?: (warning: DnsOverrideWarning) => Promise<boolean>;
|
|
14
15
|
}
|
|
15
16
|
export type LinkDomainProjectSource = 'config' | 'env' | 'flag' | 'packageJson' | 'vercelProjectFile';
|
|
16
|
-
export type LinkDomainProgressStage = 'dns:apply' | 'dns:plan' | 'dns:resolve-zone' | 'dns:wait' | 'vercel:add-domain' | 'vercel:get-domain' | 'vercel:get-target' | 'vercel:verify';
|
|
17
|
+
export type LinkDomainProgressStage = 'dns:apply' | 'dns:inspect' | 'dns:override-confirm' | 'dns:plan' | 'dns:resolve-zone' | 'dns:wait' | 'vercel:add-domain' | 'vercel:get-domain' | 'vercel:get-target' | 'vercel:verify';
|
|
17
18
|
export interface LinkDomainProgress {
|
|
18
19
|
message: string;
|
|
19
20
|
stage: LinkDomainProgressStage;
|
|
20
21
|
}
|
|
21
22
|
export type LinkDomainProgressCallback = (progress: LinkDomainProgress) => void;
|
|
23
|
+
export interface DnsOverrideWarning {
|
|
24
|
+
account: string;
|
|
25
|
+
conflicts: DnsConflict[];
|
|
26
|
+
desired: DnsRecordInput[];
|
|
27
|
+
domain: string;
|
|
28
|
+
provider: string;
|
|
29
|
+
providerName: string;
|
|
30
|
+
recordName: string;
|
|
31
|
+
zoneDomain: string;
|
|
32
|
+
}
|
|
22
33
|
export interface LinkDomainPlan {
|
|
23
34
|
provider: string;
|
|
24
35
|
providerInferred: boolean;
|
package/dist/lib/link-domain.js
CHANGED
|
@@ -459,6 +459,41 @@ async function waitForVercelDomainReady(opts) {
|
|
|
459
459
|
function reportProgress(input, stage, message) {
|
|
460
460
|
input.progress?.({ message, stage });
|
|
461
461
|
}
|
|
462
|
+
function dnsTargetConflictError(warning) {
|
|
463
|
+
return new DoomainError('DNS_TARGET_CONFLICT', `${warning.domain} already has DNS records that point somewhere else. Re-run with --force to overwrite them.`, {
|
|
464
|
+
account: warning.account,
|
|
465
|
+
conflicts: warning.conflicts,
|
|
466
|
+
desired: warning.desired,
|
|
467
|
+
domain: warning.domain,
|
|
468
|
+
provider: warning.provider,
|
|
469
|
+
providerName: warning.providerName,
|
|
470
|
+
recovery: 'Confirm the DNS override in interactive mode, or re-run with --force to overwrite conflicting DNS records.',
|
|
471
|
+
recordName: warning.recordName,
|
|
472
|
+
suggestedCommands: [`doomain link ${warning.domain} --project <project> --force --json`],
|
|
473
|
+
zoneDomain: warning.zoneDomain,
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
async function resolveDnsForce(input, opts) {
|
|
477
|
+
reportProgress(input, 'dns:inspect', `Checking existing DNS records in ${opts.provider.name}`);
|
|
478
|
+
const dnsPlan = await opts.provider.planChanges(opts.zone, [opts.baseRecord], { force: input.force });
|
|
479
|
+
if (input.force || dnsPlan.conflicts.length === 0)
|
|
480
|
+
return Boolean(input.force);
|
|
481
|
+
const warning = {
|
|
482
|
+
account: opts.plan.account,
|
|
483
|
+
conflicts: dnsPlan.conflicts,
|
|
484
|
+
desired: [opts.baseRecord],
|
|
485
|
+
domain: opts.plan.domain,
|
|
486
|
+
provider: opts.plan.provider,
|
|
487
|
+
providerName: opts.provider.name,
|
|
488
|
+
recordName: opts.plan.recordName,
|
|
489
|
+
zoneDomain: opts.plan.zoneDomain,
|
|
490
|
+
};
|
|
491
|
+
reportProgress(input, 'dns:override-confirm', `Existing DNS records point ${opts.plan.domain} somewhere else`);
|
|
492
|
+
const confirmed = await input.confirmDnsOverride?.(warning);
|
|
493
|
+
if (!confirmed)
|
|
494
|
+
throw dnsTargetConflictError(warning);
|
|
495
|
+
return true;
|
|
496
|
+
}
|
|
462
497
|
export async function createLinkPlan(input) {
|
|
463
498
|
const project = await resolveProject(input.project);
|
|
464
499
|
const resolved = await resolveProviderTarget(input);
|
|
@@ -495,22 +530,23 @@ export async function linkDomain(input) {
|
|
|
495
530
|
const provider = await createProvider(plan.provider, { account: plan.account });
|
|
496
531
|
reportProgress(input, 'dns:resolve-zone', `Finding ${provider.name} DNS zone`);
|
|
497
532
|
const zone = await resolveZone(provider, plan.zoneDomain);
|
|
498
|
-
reportProgress(input, 'vercel:add-domain', 'Adding domain to Vercel');
|
|
499
|
-
const addResult = await vercel.addDomainToProject(plan.project, plan.domain, { force: input.force });
|
|
500
533
|
reportProgress(input, 'vercel:get-target', 'Reading Vercel DNS target');
|
|
501
534
|
const cname = plan.isApex ? undefined : await vercel.getRecommendedCname(plan.domain);
|
|
535
|
+
const baseRecord = planBaseRecord({ isApex: plan.isApex, provider: plan.provider, recordName: plan.recordName, cname });
|
|
536
|
+
const forceDns = await resolveDnsForce(input, { baseRecord, plan, provider, zone });
|
|
537
|
+
reportProgress(input, 'vercel:add-domain', 'Adding domain to Vercel');
|
|
538
|
+
const addResult = await vercel.addDomainToProject(plan.project, plan.domain, { force: input.force });
|
|
502
539
|
reportProgress(input, 'vercel:get-domain', 'Reading Vercel verification records');
|
|
503
540
|
const projectDomain = await vercel.getProjectDomain(plan.project, plan.domain);
|
|
504
|
-
const baseRecord = planBaseRecord({ isApex: plan.isApex, provider: plan.provider, recordName: plan.recordName, cname });
|
|
505
541
|
const verificationDnsRecords = uniqueRecords([
|
|
506
542
|
...planVerificationRecords(plan.provider, addResult.raw, plan.zoneDomain),
|
|
507
543
|
...planVerificationRecords(plan.provider, projectDomain, plan.zoneDomain),
|
|
508
544
|
]);
|
|
509
545
|
const records = [baseRecord, ...verificationDnsRecords];
|
|
510
546
|
reportProgress(input, 'dns:plan', `Reading ${provider.name} DNS records`);
|
|
511
|
-
const dnsPlan = await provider.planChanges(zone, records, { force:
|
|
547
|
+
const dnsPlan = await provider.planChanges(zone, records, { force: forceDns });
|
|
512
548
|
reportProgress(input, 'dns:apply', `Updating DNS records in ${provider.name}`);
|
|
513
|
-
const dnsResult = await provider.applyChanges(zone, dnsPlan, { force:
|
|
549
|
+
const dnsResult = await provider.applyChanges(zone, dnsPlan, { force: forceDns });
|
|
514
550
|
const shouldWait = input.wait ?? true;
|
|
515
551
|
if (shouldWait) {
|
|
516
552
|
reportProgress(input, 'dns:wait', verificationDnsRecords.length > 0 ? 'DNS records saved; asking Vercel to verify ownership' : 'DNS records saved; asking Vercel to verify');
|
|
@@ -518,7 +554,7 @@ export async function linkDomain(input) {
|
|
|
518
554
|
const waitResult = shouldWait
|
|
519
555
|
? await waitForVercelDomainReady({
|
|
520
556
|
domain: plan.domain,
|
|
521
|
-
force:
|
|
557
|
+
force: forceDns,
|
|
522
558
|
input,
|
|
523
559
|
project: plan.project,
|
|
524
560
|
provider,
|
package/oclif.manifest.json
CHANGED