doomain 0.1.22 → 0.1.24
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/README.md +1 -1
- package/dist/commands/auth/clerk.js +3 -2
- package/dist/commands/auth/logout/clerk.js +3 -2
- package/dist/commands/auth/logout/vercel.js +3 -2
- package/dist/commands/auth/vercel.js +5 -4
- package/dist/commands/clerk/domains/add.js +3 -2
- package/dist/commands/dns/diagnose.js +3 -2
- package/dist/commands/dns/point.js +7 -3
- package/dist/commands/dns/remove.js +3 -2
- package/dist/commands/domains/find.js +3 -2
- package/dist/commands/domains/list.js +46 -33
- package/dist/commands/link.js +4 -3
- package/dist/commands/projects/list.js +5 -2
- package/dist/commands/providers/connect.js +14 -23
- package/dist/commands/providers/disconnect.js +3 -2
- package/dist/commands/providers/status.js +2 -1
- package/dist/commands/providers/verify.js +3 -2
- package/dist/commands/schema.js +2 -1
- package/dist/commands/update.js +2 -1
- package/dist/commands/verify.js +5 -2
- package/dist/commands/version.d.ts +5 -0
- package/dist/commands/version.js +8 -0
- package/dist/commands/wizard.js +45 -46
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/clerk-domain.d.ts +2 -1
- package/dist/lib/clerk-domain.js +122 -110
- package/dist/lib/clerk.d.ts +9 -5
- package/dist/lib/clerk.js +45 -36
- package/dist/lib/command-schema.d.ts +2 -1
- package/dist/lib/command-schema.js +16 -9
- package/dist/lib/config.d.ts +6 -4
- package/dist/lib/config.js +29 -27
- package/dist/lib/diagnose-dns.d.ts +8 -5
- package/dist/lib/diagnose-dns.js +90 -92
- package/dist/lib/dns-propagation.d.ts +5 -4
- package/dist/lib/dns-propagation.js +80 -54
- package/dist/lib/dns-reconciliation.d.ts +4 -3
- package/dist/lib/dns-reconciliation.js +52 -47
- package/dist/lib/domain-provider.d.ts +5 -1
- package/dist/lib/domain-provider.js +136 -131
- package/dist/lib/effect.d.ts +6 -0
- package/dist/lib/effect.js +20 -0
- package/dist/lib/link-domain.d.ts +3 -2
- package/dist/lib/link-domain.js +239 -218
- package/dist/lib/point-domain.d.ts +7 -4
- package/dist/lib/point-domain.js +92 -90
- package/dist/lib/providers/cloudflare/index.d.ts +9 -8
- package/dist/lib/providers/cloudflare/index.js +93 -87
- package/dist/lib/providers/core/config.d.ts +4 -1
- package/dist/lib/providers/core/config.js +17 -13
- package/dist/lib/providers/core/http.d.ts +4 -1
- package/dist/lib/providers/core/http.js +28 -18
- package/dist/lib/providers/core/pagination.d.ts +3 -2
- package/dist/lib/providers/core/pagination.js +17 -14
- package/dist/lib/providers/core/planner.d.ts +5 -4
- package/dist/lib/providers/core/planner.js +18 -15
- package/dist/lib/providers/core/types.d.ts +11 -8
- package/dist/lib/providers/hostinger/index.d.ts +9 -8
- package/dist/lib/providers/hostinger/index.js +74 -72
- package/dist/lib/providers/namecheap/index.d.ts +10 -8
- package/dist/lib/providers/namecheap/index.js +134 -109
- package/dist/lib/providers/registry.d.ts +3 -3
- package/dist/lib/providers/registry.js +7 -3
- package/dist/lib/providers/spaceship/index.d.ts +9 -8
- package/dist/lib/providers/spaceship/index.js +48 -47
- package/dist/lib/providers/status.d.ts +2 -1
- package/dist/lib/providers/status.js +41 -34
- package/dist/lib/public-ip.d.ts +2 -0
- package/dist/lib/public-ip.js +14 -0
- package/dist/lib/remove-domain.d.ts +6 -3
- package/dist/lib/remove-domain.js +83 -77
- package/dist/lib/self-update.d.ts +3 -2
- package/dist/lib/self-update.js +15 -23
- package/dist/lib/vercel-auth.d.ts +2 -1
- package/dist/lib/vercel-auth.js +21 -24
- package/dist/lib/vercel.d.ts +16 -12
- package/dist/lib/vercel.js +174 -114
- package/oclif.manifest.json +21 -1
- package/package.json +8 -1
|
@@ -1,53 +1,58 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
1
2
|
import { desiredSlotPostcondition, recordMatchesSelector } from './dns-records.js';
|
|
2
3
|
import { DoomainError } from './errors.js';
|
|
3
|
-
const sleep = (milliseconds) =>
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
4
|
+
const sleep = (milliseconds) => Effect.sleep(milliseconds);
|
|
5
|
+
export function reconcileDesiredRecord(input) {
|
|
6
|
+
return Effect.gen(function* () {
|
|
7
|
+
const now = input.dependencies?.now ?? Date.now;
|
|
8
|
+
const wait = input.dependencies?.sleep ?? sleep;
|
|
9
|
+
const deadline = now() + (input.timeoutMs ?? 30_000);
|
|
10
|
+
let attempts = 0;
|
|
11
|
+
const appliedChanges = 0;
|
|
12
|
+
while (true) {
|
|
13
|
+
attempts += 1;
|
|
14
|
+
const records = yield* input.provider.listRecords(input.zone);
|
|
15
|
+
const state = desiredSlotPostcondition(records, input.desired);
|
|
16
|
+
if (state.reconciled)
|
|
17
|
+
return { appliedChanges, attempts, observed: state.observed, reconciled: true };
|
|
18
|
+
if (now() >= deadline) {
|
|
19
|
+
return yield* Effect.fail(new DoomainError('DNS_RECONCILIATION_INCOMPLETE', `The DNS provider accepted the change, but the ${input.desired.type} ${input.desired.name} slot is not reconciled yet.`, {
|
|
20
|
+
attempts,
|
|
21
|
+
expected: input.desired,
|
|
22
|
+
observed: state.observed,
|
|
23
|
+
recovery: 'Retry the command. Do not treat the DNS change as complete until reconciled is true.',
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
input.progress?.('Waiting for the DNS provider to publish the accepted change');
|
|
27
|
+
const remaining = deadline - now();
|
|
28
|
+
yield* wait(Math.min(input.intervalMs ?? 1000, Math.max(0, remaining)));
|
|
23
29
|
}
|
|
24
|
-
|
|
25
|
-
const remaining = deadline - now();
|
|
26
|
-
await wait(Math.min(input.intervalMs ?? 1000, Math.max(0, remaining)));
|
|
27
|
-
}
|
|
30
|
+
});
|
|
28
31
|
}
|
|
29
|
-
export
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
32
|
+
export function reconcileRecordRemoval(input) {
|
|
33
|
+
return Effect.gen(function* () {
|
|
34
|
+
const now = input.dependencies?.now ?? Date.now;
|
|
35
|
+
const wait = input.dependencies?.sleep ?? sleep;
|
|
36
|
+
const deadline = now() + (input.timeoutMs ?? 30_000);
|
|
37
|
+
let attempts = 0;
|
|
38
|
+
const appliedChanges = 0;
|
|
39
|
+
while (true) {
|
|
40
|
+
attempts += 1;
|
|
41
|
+
const records = yield* input.provider.listRecords(input.zone);
|
|
42
|
+
const observed = records.filter((record) => recordMatchesSelector(record, input.selector));
|
|
43
|
+
if (observed.length === 0)
|
|
44
|
+
return { appliedChanges, attempts, observed, reconciled: true };
|
|
45
|
+
if (now() >= deadline) {
|
|
46
|
+
return yield* Effect.fail(new DoomainError('DNS_RECONCILIATION_INCOMPLETE', `The DNS provider accepted the deletion, but ${observed.length} matching record${observed.length === 1 ? '' : 's'} remain.`, {
|
|
47
|
+
attempts,
|
|
48
|
+
observed,
|
|
49
|
+
selector: input.selector,
|
|
50
|
+
recovery: 'Retry the command. Do not treat the DNS deletion as complete until reconciled is true.',
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
input.progress?.('Waiting for the DNS provider to publish the accepted deletion');
|
|
54
|
+
const remaining = deadline - now();
|
|
55
|
+
yield* wait(Math.min(input.intervalMs ?? 1000, Math.max(0, remaining)));
|
|
48
56
|
}
|
|
49
|
-
|
|
50
|
-
const remaining = deadline - now();
|
|
51
|
-
await wait(Math.min(input.intervalMs ?? 1000, Math.max(0, remaining)));
|
|
52
|
-
}
|
|
57
|
+
});
|
|
53
58
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type DoomainEffect } from './effect.js';
|
|
1
2
|
import { type DoomainErrorCode } from './errors.js';
|
|
2
3
|
export interface FindDomainProviderInput {
|
|
3
4
|
account?: string;
|
|
@@ -33,6 +34,7 @@ export interface ResolveProviderTargetInput extends FindDomainProviderInput {
|
|
|
33
34
|
}
|
|
34
35
|
export interface ResolveProviderTargetOptions {
|
|
35
36
|
tolerateProviderAccountErrors?: boolean;
|
|
37
|
+
transportErrorCode?: DoomainErrorCode;
|
|
36
38
|
}
|
|
37
39
|
export interface ResolvedDnsTarget {
|
|
38
40
|
account: string;
|
|
@@ -48,6 +50,8 @@ export interface ResolvedDnsTarget {
|
|
|
48
50
|
};
|
|
49
51
|
warnings: ProviderSearchWarning[];
|
|
50
52
|
}
|
|
51
|
-
export declare function resolveProviderTarget(input: ResolveProviderTargetInput, options?: ResolveProviderTargetOptions):
|
|
53
|
+
export declare function resolveProviderTarget(input: ResolveProviderTargetInput, options?: ResolveProviderTargetOptions): DoomainEffect<ResolvedDnsTarget>;
|
|
52
54
|
/** Find the configured DNS provider account with the longest zone match for a domain. */
|
|
55
|
+
export declare function findDomainProviderEffect(input: FindDomainProviderInput): DoomainEffect<DomainProviderResult>;
|
|
56
|
+
/** Promise facade retained for the package's public programmatic API. */
|
|
53
57
|
export declare function findDomainProvider(input: FindDomainProviderInput): Promise<DomainProviderResult>;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
1
2
|
import { loadConfig } from './config.js';
|
|
3
|
+
import { runDoomainEffect, trySync } from './effect.js';
|
|
2
4
|
import { DoomainError } from './errors.js';
|
|
3
5
|
import { DEFAULT_PROVIDER_ACCOUNT, isDefaultProviderAccount, listConfiguredProviderAccounts, normalizeProviderAccount, } from './providers/core/config.js';
|
|
4
6
|
import { createProvider, getProviderDefinition, listProviderDefinitions } from './providers/registry.js';
|
|
@@ -55,46 +57,43 @@ function searchError(error) {
|
|
|
55
57
|
message: error instanceof Error ? error.message : String(error),
|
|
56
58
|
};
|
|
57
59
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
candidates: zones.map((zone) => ({
|
|
63
|
-
account: account.account,
|
|
64
|
-
isDefaultAccount: account.isDefaultAccount,
|
|
65
|
-
provider: definition.id,
|
|
66
|
-
providerName: definition.displayName,
|
|
67
|
-
zone,
|
|
68
|
-
})),
|
|
69
|
-
search: {
|
|
70
|
-
account: account.account,
|
|
71
|
-
displayName: definition.displayName,
|
|
72
|
-
id: definition.id,
|
|
73
|
-
isDefaultAccount: account.isDefaultAccount,
|
|
74
|
-
zones: zones.map((zone) => zone.name),
|
|
75
|
-
},
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
async function loadProviderZonesSafely(definition, account) {
|
|
79
|
-
try {
|
|
80
|
-
return await loadProviderZones(definition, account);
|
|
81
|
-
}
|
|
82
|
-
catch (error) {
|
|
60
|
+
function loadProviderZones(definition, account, transportErrorCode) {
|
|
61
|
+
return Effect.gen(function* () {
|
|
62
|
+
const provider = yield* createProvider(definition.id, { account: account.account, transportErrorCode });
|
|
63
|
+
const zones = yield* provider.listZones();
|
|
83
64
|
return {
|
|
84
|
-
candidates:
|
|
65
|
+
candidates: zones.map((zone) => ({
|
|
66
|
+
account: account.account,
|
|
67
|
+
isDefaultAccount: account.isDefaultAccount,
|
|
68
|
+
provider: definition.id,
|
|
69
|
+
providerName: definition.displayName,
|
|
70
|
+
zone,
|
|
71
|
+
})),
|
|
85
72
|
search: {
|
|
86
73
|
account: account.account,
|
|
87
74
|
displayName: definition.displayName,
|
|
88
|
-
error: searchError(error),
|
|
89
75
|
id: definition.id,
|
|
90
76
|
isDefaultAccount: account.isDefaultAccount,
|
|
91
|
-
zones:
|
|
77
|
+
zones: zones.map((zone) => zone.name),
|
|
92
78
|
},
|
|
93
79
|
};
|
|
94
|
-
}
|
|
80
|
+
});
|
|
95
81
|
}
|
|
96
|
-
|
|
97
|
-
return (
|
|
82
|
+
function loadProviderZonesSafely(definition, account, transportErrorCode) {
|
|
83
|
+
return loadProviderZones(definition, account, transportErrorCode).pipe(Effect.catchAll((error) => Effect.succeed({
|
|
84
|
+
candidates: [],
|
|
85
|
+
search: {
|
|
86
|
+
account: account.account,
|
|
87
|
+
displayName: definition.displayName,
|
|
88
|
+
error: searchError(error),
|
|
89
|
+
id: definition.id,
|
|
90
|
+
isDefaultAccount: account.isDefaultAccount,
|
|
91
|
+
zones: [],
|
|
92
|
+
},
|
|
93
|
+
})));
|
|
94
|
+
}
|
|
95
|
+
function providerConnectionDetails() {
|
|
96
|
+
return listProviderStatuses({ verify: false }).pipe(Effect.map((providers) => providers.map((provider) => ({
|
|
98
97
|
configured: provider.configured,
|
|
99
98
|
account: provider.account,
|
|
100
99
|
default: provider.default,
|
|
@@ -102,7 +101,7 @@ async function providerConnectionDetails() {
|
|
|
102
101
|
docsUrl: provider.docsUrl,
|
|
103
102
|
id: provider.id,
|
|
104
103
|
isDefaultAccount: provider.isDefaultAccount,
|
|
105
|
-
}));
|
|
104
|
+
}))));
|
|
106
105
|
}
|
|
107
106
|
function searchWarnings(searches) {
|
|
108
107
|
return searches.flatMap((search) => search.error
|
|
@@ -117,89 +116,99 @@ function searchWarnings(searches) {
|
|
|
117
116
|
]
|
|
118
117
|
: []);
|
|
119
118
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
119
|
+
function loadConfiguredProviderZones(providerId, accountInput, tolerateProviderAccountErrors = false, transportErrorCode = 'DOMAIN_PROVIDER_DISCOVERY_FAILED') {
|
|
120
|
+
return Effect.gen(function* () {
|
|
121
|
+
const config = yield* loadConfig();
|
|
122
|
+
const account = accountInput
|
|
123
|
+
? yield* trySync(() => normalizeProviderAccount(accountInput), 'INVALID_INPUT')
|
|
124
|
+
: undefined;
|
|
125
|
+
if (providerId) {
|
|
126
|
+
const definition = yield* trySync(() => getProviderDefinition(providerId), 'PROVIDER_NOT_FOUND');
|
|
127
|
+
const accounts = account
|
|
128
|
+
? [explicitAccountRef(definition.id, account)]
|
|
129
|
+
: yield* trySync(() => listConfiguredProviderAccounts(config, definition), 'INVALID_INPUT');
|
|
130
|
+
const selectedAccounts = accounts.length > 0 ? accounts : [defaultAccountRef(definition.id)];
|
|
131
|
+
const tolerateAccountErrors = tolerateProviderAccountErrors && !account && selectedAccounts.length > 1;
|
|
132
|
+
const results = yield* Effect.all(selectedAccounts.map((ref) => tolerateAccountErrors
|
|
133
|
+
? loadProviderZonesSafely(definition, ref, transportErrorCode)
|
|
134
|
+
: loadProviderZones(definition, ref, transportErrorCode)), { concurrency: 'unbounded' });
|
|
135
|
+
return {
|
|
136
|
+
accountInferred: account === undefined,
|
|
137
|
+
candidates: results.flatMap((result) => result.candidates),
|
|
138
|
+
providerInferred: false,
|
|
139
|
+
searched: results.map((result) => result.search),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
const providerAccounts = yield* trySync(() => listProviderDefinitions().flatMap((definition) => listConfiguredProviderAccounts(config, definition)
|
|
143
|
+
.filter((ref) => !account || ref.account === account)
|
|
144
|
+
.map((ref) => ({ definition, ref }))), 'INVALID_INPUT');
|
|
145
|
+
if (providerAccounts.length === 0) {
|
|
146
|
+
const message = account
|
|
147
|
+
? `No DNS provider account named ${account} is configured. Run \`doomain providers connect <provider> --account ${account}\` first.`
|
|
148
|
+
: 'No DNS provider is configured. Run `doomain providers connect` first.';
|
|
149
|
+
return yield* Effect.fail(new DoomainError('CONFIG_NOT_FOUND', message, {
|
|
150
|
+
account,
|
|
151
|
+
configuredProviders: yield* providerConnectionDetails(),
|
|
152
|
+
recovery: 'Connect the DNS provider that owns this domain, then retry `doomain link <domain> --json`.',
|
|
153
|
+
suggestedCommands: account
|
|
154
|
+
? [`doomain providers connect <provider> --account ${account}`, 'doomain link <domain> --json']
|
|
155
|
+
: ['doomain providers connect', 'doomain link <domain> --json'],
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
const results = yield* Effect.all(providerAccounts.map(({ definition, ref }) => tolerateProviderAccountErrors
|
|
159
|
+
? loadProviderZonesSafely(definition, ref, transportErrorCode)
|
|
160
|
+
: loadProviderZones(definition, ref, transportErrorCode)), { concurrency: 'unbounded' });
|
|
131
161
|
return {
|
|
132
162
|
accountInferred: account === undefined,
|
|
133
163
|
candidates: results.flatMap((result) => result.candidates),
|
|
134
|
-
providerInferred:
|
|
164
|
+
providerInferred: true,
|
|
135
165
|
searched: results.map((result) => result.search),
|
|
136
166
|
};
|
|
137
|
-
}
|
|
138
|
-
const providerAccounts = listProviderDefinitions().flatMap((definition) => listConfiguredProviderAccounts(config, definition)
|
|
139
|
-
.filter((ref) => !account || ref.account === account)
|
|
140
|
-
.map((ref) => ({ definition, ref })));
|
|
141
|
-
if (providerAccounts.length === 0) {
|
|
142
|
-
const message = account
|
|
143
|
-
? `No DNS provider account named ${account} is configured. Run \`doomain providers connect <provider> --account ${account}\` first.`
|
|
144
|
-
: 'No DNS provider is configured. Run `doomain providers connect` first.';
|
|
145
|
-
throw new DoomainError('CONFIG_NOT_FOUND', message, {
|
|
146
|
-
account,
|
|
147
|
-
configuredProviders: await providerConnectionDetails(),
|
|
148
|
-
recovery: 'Connect the DNS provider that owns this domain, then retry `doomain link <domain> --json`.',
|
|
149
|
-
suggestedCommands: account
|
|
150
|
-
? [`doomain providers connect <provider> --account ${account}`, 'doomain link <domain> --json']
|
|
151
|
-
: ['doomain providers connect', 'doomain link <domain> --json'],
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
const results = await Promise.all(providerAccounts.map(({ definition, ref }) => tolerateProviderAccountErrors ? loadProviderZonesSafely(definition, ref) : loadProviderZones(definition, ref)));
|
|
155
|
-
return {
|
|
156
|
-
accountInferred: account === undefined,
|
|
157
|
-
candidates: results.flatMap((result) => result.candidates),
|
|
158
|
-
providerInferred: true,
|
|
159
|
-
searched: results.map((result) => result.search),
|
|
160
|
-
};
|
|
167
|
+
});
|
|
161
168
|
}
|
|
162
|
-
export
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
item.
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
169
|
+
export function resolveProviderTarget(input, options = {}) {
|
|
170
|
+
return Effect.gen(function* () {
|
|
171
|
+
const requested = yield* trySync(() => resolveRequestedDomain(input), 'INVALID_INPUT');
|
|
172
|
+
const zones = yield* loadConfiguredProviderZones(input.provider, input.account, options.tolerateProviderAccountErrors, options.transportErrorCode);
|
|
173
|
+
const matches = zones.candidates
|
|
174
|
+
.filter((candidate) => zoneMatchesDomain(requested.fullDomain, candidate.zone.name, requested.forceExactZone))
|
|
175
|
+
.sort((a, b) => b.zone.name.length - a.zone.name.length);
|
|
176
|
+
if (matches.length === 0) {
|
|
177
|
+
const account = input.account ? normalizeProviderAccount(input.account) : undefined;
|
|
178
|
+
const providerMessage = input.provider
|
|
179
|
+
? `${getProviderDefinition(input.provider).displayName}${account ? ` account ${account}` : ''} does not have a matching DNS zone for ${requested.fullDomain}.`
|
|
180
|
+
: `No configured DNS provider has a matching DNS zone for ${requested.fullDomain}.`;
|
|
181
|
+
return yield* Effect.fail(new DoomainError('PROVIDER_ZONE_NOT_FOUND', providerMessage, {
|
|
182
|
+
account,
|
|
183
|
+
configuredProviders: yield* providerConnectionDetails(),
|
|
184
|
+
domain: requested.fullDomain,
|
|
185
|
+
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.',
|
|
186
|
+
searchedZones: zones.searched,
|
|
187
|
+
suggestedCommands: [
|
|
188
|
+
`doomain link ${requested.fullDomain} --provider <id> --account <alias> --json`,
|
|
189
|
+
'doomain providers connect',
|
|
190
|
+
],
|
|
191
|
+
}));
|
|
192
|
+
}
|
|
193
|
+
const bestLength = matches[0].zone.name.length;
|
|
194
|
+
const bestMatches = matches.filter((candidate) => candidate.zone.name.length === bestLength);
|
|
195
|
+
const uniqueBestMatches = bestMatches.filter((candidate, index, candidates) => candidates.findIndex((item) => item.provider === candidate.provider &&
|
|
196
|
+
item.account === candidate.account &&
|
|
197
|
+
item.zone.name === candidate.zone.name) === index);
|
|
198
|
+
if (uniqueBestMatches.length > 1) {
|
|
199
|
+
return yield* Effect.fail(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 }));
|
|
200
|
+
}
|
|
201
|
+
const selected = uniqueBestMatches[0];
|
|
202
|
+
return {
|
|
203
|
+
account: selected.account,
|
|
204
|
+
accountInferred: zones.accountInferred,
|
|
205
|
+
isDefaultAccount: selected.isDefaultAccount,
|
|
206
|
+
provider: selected.provider,
|
|
207
|
+
providerInferred: zones.providerInferred,
|
|
208
|
+
target: targetFromZone(requested.fullDomain, selected.zone.name),
|
|
209
|
+
warnings: searchWarnings(zones.searched),
|
|
210
|
+
};
|
|
211
|
+
});
|
|
203
212
|
}
|
|
204
213
|
function discoveryError(error, domain) {
|
|
205
214
|
if (error.code !== 'CONFIG_NOT_FOUND' && error.code !== 'PROVIDER_ZONE_NOT_FOUND')
|
|
@@ -212,26 +221,22 @@ function discoveryError(error, domain) {
|
|
|
212
221
|
});
|
|
213
222
|
}
|
|
214
223
|
/** Find the configured DNS provider account with the longest zone match for a domain. */
|
|
215
|
-
export
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
if (error instanceof DoomainError)
|
|
234
|
-
throw discoveryError(error, input.domain);
|
|
235
|
-
throw error;
|
|
236
|
-
}
|
|
224
|
+
export function findDomainProviderEffect(input) {
|
|
225
|
+
return resolveProviderTarget(input, { tolerateProviderAccountErrors: true }).pipe(Effect.map((resolved) => ({
|
|
226
|
+
account: resolved.account,
|
|
227
|
+
accountInferred: resolved.accountInferred,
|
|
228
|
+
complete: resolved.warnings.length === 0,
|
|
229
|
+
domain: resolved.target.fullDomain,
|
|
230
|
+
isApex: resolved.target.isApex,
|
|
231
|
+
isDefaultAccount: resolved.isDefaultAccount,
|
|
232
|
+
provider: resolved.provider,
|
|
233
|
+
providerInferred: resolved.providerInferred,
|
|
234
|
+
recordName: resolved.target.recordName,
|
|
235
|
+
warnings: resolved.warnings,
|
|
236
|
+
zoneDomain: resolved.target.zoneDomain,
|
|
237
|
+
})), Effect.mapError((error) => discoveryError(error, input.domain)));
|
|
238
|
+
}
|
|
239
|
+
/** Promise facade retained for the package's public programmatic API. */
|
|
240
|
+
export function findDomainProvider(input) {
|
|
241
|
+
return runDoomainEffect(findDomainProviderEffect(input));
|
|
237
242
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
import { type DoomainError, type DoomainErrorCode } from './errors.js';
|
|
3
|
+
export type DoomainEffect<A, E = DoomainError> = Effect.Effect<A, E>;
|
|
4
|
+
export declare function trySync<A>(evaluate: () => A, fallbackCode: DoomainErrorCode): DoomainEffect<A>;
|
|
5
|
+
export declare function tryPromise<A>(evaluate: () => PromiseLike<A>, fallbackCode: DoomainErrorCode): DoomainEffect<A>;
|
|
6
|
+
export declare function runDoomainEffect<A>(effect: DoomainEffect<A>): Promise<A>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
import { toDoomainError } from './errors.js';
|
|
3
|
+
export function trySync(evaluate, fallbackCode) {
|
|
4
|
+
return Effect.try({
|
|
5
|
+
try: evaluate,
|
|
6
|
+
catch: (error) => toDoomainError(error, fallbackCode),
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
export function tryPromise(evaluate, fallbackCode) {
|
|
10
|
+
return Effect.tryPromise({
|
|
11
|
+
try: evaluate,
|
|
12
|
+
catch: (error) => toDoomainError(error, fallbackCode),
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
export async function runDoomainEffect(effect) {
|
|
16
|
+
const result = await Effect.runPromise(Effect.either(effect));
|
|
17
|
+
if (result._tag === 'Left')
|
|
18
|
+
throw result.left;
|
|
19
|
+
return result.right;
|
|
20
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type DoomainEffect } from './effect.js';
|
|
1
2
|
import type { DnsConflict, DnsRecordInput } from './providers/types.js';
|
|
2
3
|
export interface LinkDomainInput {
|
|
3
4
|
provider?: string;
|
|
@@ -61,5 +62,5 @@ export interface LinkDomainResult extends LinkDomainPlan {
|
|
|
61
62
|
}
|
|
62
63
|
export declare function withProviderRecordOptions(provider: string, record: DnsRecordInput): DnsRecordInput;
|
|
63
64
|
export declare function verificationRecords(raw: unknown, zoneDomain: string): DnsRecordInput[];
|
|
64
|
-
export declare function createLinkPlan(input: LinkDomainInput):
|
|
65
|
-
export declare function linkDomain(input: LinkDomainInput):
|
|
65
|
+
export declare function createLinkPlan(input: LinkDomainInput): DoomainEffect<LinkDomainPlan>;
|
|
66
|
+
export declare function linkDomain(input: LinkDomainInput): DoomainEffect<LinkDomainResult>;
|