doomain 0.1.23 → 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/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/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 +9 -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 +1 -1
- package/package.json +2 -1
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
1
2
|
import { XMLParser } from 'fast-xml-parser';
|
|
3
|
+
import { trySync } from '../../effect.js';
|
|
4
|
+
import { toDoomainError } from '../../errors.js';
|
|
2
5
|
import { normalizeDomain } from '../../validate.js';
|
|
3
6
|
import { ProviderError } from '../core/errors.js';
|
|
4
7
|
import { assertNoConflicts, planDnsChanges } from '../core/planner.js';
|
|
@@ -131,6 +134,7 @@ export class NamecheapProvider {
|
|
|
131
134
|
apiUser;
|
|
132
135
|
baseUrl;
|
|
133
136
|
clientIp;
|
|
137
|
+
transportErrorCode;
|
|
134
138
|
username;
|
|
135
139
|
constructor(context) {
|
|
136
140
|
this.apiUser = context.credentials.apiUser;
|
|
@@ -138,131 +142,152 @@ export class NamecheapProvider {
|
|
|
138
142
|
this.username = context.credentials.username || context.credentials.apiUser;
|
|
139
143
|
this.clientIp = context.credentials.clientIp;
|
|
140
144
|
this.baseUrl = bool(context.credentials.sandbox) ? NAMECHEAP_SANDBOX_URL : NAMECHEAP_PRODUCTION_URL;
|
|
145
|
+
this.transportErrorCode = context.transportErrorCode ?? 'PROVIDER_API_ERROR';
|
|
141
146
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return { ok: true };
|
|
147
|
+
verifyCredentials() {
|
|
148
|
+
return this.listZones().pipe(Effect.as({ ok: true }));
|
|
145
149
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
150
|
+
listZones() {
|
|
151
|
+
return Effect.gen(this, function* () {
|
|
152
|
+
const zones = [];
|
|
153
|
+
let currentPage = 1;
|
|
154
|
+
let totalItems = Number.POSITIVE_INFINITY;
|
|
155
|
+
while (zones.length < totalItems) {
|
|
156
|
+
const response = yield* this.request('namecheap.domains.getList', {
|
|
157
|
+
Page: String(currentPage),
|
|
158
|
+
PageSize: '100',
|
|
159
|
+
});
|
|
160
|
+
const commandResponse = response.ApiResponse.CommandResponse;
|
|
161
|
+
const result = commandResponse.DomainGetListResult;
|
|
162
|
+
const domains = asArray(result.Domain);
|
|
163
|
+
for (const domain of domains) {
|
|
164
|
+
const zone = toZone(domain);
|
|
165
|
+
if (zone)
|
|
166
|
+
zones.push(zone);
|
|
167
|
+
}
|
|
168
|
+
totalItems = pagingTotal(commandResponse, result, zones.length);
|
|
169
|
+
if (domains.length === 0)
|
|
170
|
+
break;
|
|
171
|
+
currentPage += 1;
|
|
159
172
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
break;
|
|
163
|
-
currentPage += 1;
|
|
164
|
-
}
|
|
165
|
-
return zones;
|
|
166
|
-
}
|
|
167
|
-
async getZone(domain) {
|
|
168
|
-
const normalized = normalizeDomain(domain);
|
|
169
|
-
const zones = await this.listZones();
|
|
170
|
-
return zones.find((zone) => zone.name === normalized) ?? null;
|
|
173
|
+
return zones;
|
|
174
|
+
});
|
|
171
175
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const record = toDnsRecord(host);
|
|
178
|
-
return record ? [record] : [];
|
|
176
|
+
getZone(domain) {
|
|
177
|
+
return Effect.gen(this, function* () {
|
|
178
|
+
const normalized = yield* trySync(() => normalizeDomain(domain), 'INVALID_INPUT');
|
|
179
|
+
const zones = yield* this.listZones();
|
|
180
|
+
return zones.find((zone) => zone.name === normalized) ?? null;
|
|
179
181
|
});
|
|
180
182
|
}
|
|
181
|
-
|
|
182
|
-
return
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
183
|
+
listRecords(zone) {
|
|
184
|
+
return Effect.gen(this, function* () {
|
|
185
|
+
const { sld, tld } = yield* trySync(() => splitDomain(zone.name), 'PROVIDER_ZONE_NOT_FOUND');
|
|
186
|
+
const response = yield* this.request('namecheap.domains.dns.getHosts', { SLD: sld, TLD: tld });
|
|
187
|
+
const hosts = asArray(response.ApiResponse.CommandResponse.DomainDNSGetHostsResult.host);
|
|
188
|
+
return hosts.flatMap((host) => {
|
|
189
|
+
const record = toDnsRecord(host);
|
|
190
|
+
return record ? [record] : [];
|
|
191
|
+
});
|
|
188
192
|
});
|
|
189
193
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
194
|
+
planChanges(zone, desired, opts = {}) {
|
|
195
|
+
return this.listRecords(zone).pipe(Effect.map((existing) => planDnsChanges({ desired, existing, force: opts.force, providerId: this.id, zone })));
|
|
196
|
+
}
|
|
197
|
+
applyChanges(zone, plan) {
|
|
198
|
+
return Effect.gen(this, function* () {
|
|
199
|
+
yield* assertNoConflicts(this.id, plan);
|
|
200
|
+
const finalRecords = [...plan.existing];
|
|
201
|
+
const applied = [];
|
|
202
|
+
const skipped = [];
|
|
203
|
+
for (const change of plan.changes) {
|
|
204
|
+
if (change.action === 'skip') {
|
|
205
|
+
skipped.push(change.record);
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (change.action === 'delete') {
|
|
209
|
+
const index = finalRecords.findIndex((record) => sameRecord(record, change.existing));
|
|
210
|
+
if (index !== -1)
|
|
211
|
+
finalRecords.splice(index, 1);
|
|
212
|
+
}
|
|
213
|
+
else if (change.action === 'update') {
|
|
214
|
+
const index = finalRecords.findIndex((record) => sameRecord(record, change.existing));
|
|
215
|
+
if (index !== -1)
|
|
216
|
+
finalRecords[index] = { ...change.existing, ...change.record };
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
finalRecords.push(inputToRecord(change.record));
|
|
220
|
+
}
|
|
221
|
+
applied.push(change);
|
|
212
222
|
}
|
|
213
|
-
applied.
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
return { applied, skipped };
|
|
223
|
+
if (applied.length > 0)
|
|
224
|
+
yield* this.setHosts(zone, finalRecords);
|
|
225
|
+
return { applied, skipped };
|
|
226
|
+
});
|
|
218
227
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
228
|
+
upsertRecord(zone, record) {
|
|
229
|
+
return Effect.gen(this, function* () {
|
|
230
|
+
const plan = yield* this.planChanges(zone, [record], { force: true });
|
|
231
|
+
yield* this.applyChanges(zone, plan);
|
|
232
|
+
return { ...record, ttl: record.ttl ?? capabilities.defaultTtl };
|
|
233
|
+
});
|
|
223
234
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
235
|
+
deleteRecord(zone, record) {
|
|
236
|
+
return Effect.gen(this, function* () {
|
|
237
|
+
const records = (yield* this.listRecords(zone)).filter((item) => !sameRecord(item, record));
|
|
238
|
+
yield* this.setHosts(zone, records);
|
|
239
|
+
});
|
|
227
240
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
241
|
+
setHosts(zone, records) {
|
|
242
|
+
return Effect.gen(this, function* () {
|
|
243
|
+
const { sld, tld } = yield* trySync(() => splitDomain(zone.name), 'PROVIDER_ZONE_NOT_FOUND');
|
|
244
|
+
const response = yield* this.request('namecheap.domains.dns.setHosts', { SLD: sld, TLD: tld, ...hostParams(records) }, { method: 'POST' });
|
|
245
|
+
const result = response.ApiResponse.CommandResponse.DomainDNSSetHostsResult;
|
|
246
|
+
if (!bool(result?.IsSuccess)) {
|
|
247
|
+
return yield* Effect.fail(new ProviderError('namecheap', 'PROVIDER_API_ERROR', 'Namecheap did not confirm DNS host records were updated.', result));
|
|
248
|
+
}
|
|
249
|
+
});
|
|
235
250
|
}
|
|
236
|
-
|
|
251
|
+
request(command, params, opts = {}) {
|
|
237
252
|
if (!this.clientIp) {
|
|
238
|
-
|
|
253
|
+
return Effect.fail(new ProviderError('namecheap', 'MISSING_CREDENTIALS', 'Namecheap requires a whitelisted IPv4 ClientIp credential.'));
|
|
239
254
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
255
|
+
return Effect.gen(this, function* () {
|
|
256
|
+
const query = new URLSearchParams({
|
|
257
|
+
ApiKey: this.apiKey,
|
|
258
|
+
ApiUser: this.apiUser,
|
|
259
|
+
ClientIp: this.clientIp,
|
|
260
|
+
Command: command,
|
|
261
|
+
UserName: this.username,
|
|
262
|
+
...params,
|
|
263
|
+
});
|
|
264
|
+
const method = opts.method ?? 'GET';
|
|
265
|
+
const response = yield* Effect.tryPromise({
|
|
266
|
+
try: (signal) => fetch(method === 'POST' ? this.baseUrl : `${this.baseUrl}?${query.toString()}`, {
|
|
267
|
+
...(method === 'POST'
|
|
268
|
+
? { body: query.toString(), headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, method }
|
|
269
|
+
: {}),
|
|
270
|
+
signal,
|
|
271
|
+
}),
|
|
272
|
+
catch: (cause) => toDoomainError(cause, this.transportErrorCode),
|
|
273
|
+
});
|
|
274
|
+
if (!response.ok) {
|
|
275
|
+
return yield* Effect.fail(new ProviderError('namecheap', 'PROVIDER_API_ERROR', `Namecheap API error (${response.status}).`));
|
|
276
|
+
}
|
|
277
|
+
const text = yield* Effect.tryPromise({
|
|
278
|
+
try: () => response.text(),
|
|
279
|
+
catch: (cause) => new ProviderError('namecheap', 'PROVIDER_API_ERROR', 'Invalid Namecheap API response.', cause),
|
|
280
|
+
});
|
|
281
|
+
const data = yield* trySync(() => parser.parse(text), 'PROVIDER_API_ERROR');
|
|
282
|
+
const status = data.ApiResponse?.Status;
|
|
283
|
+
if (status !== 'OK') {
|
|
284
|
+
const message = getErrorMessage(asArray(data.ApiResponse?.Errors?.Error)[0] ?? 'Namecheap API error.');
|
|
285
|
+
const code = providerCodeFromNamecheapError(message);
|
|
286
|
+
const help = namecheapSetupHelp(code);
|
|
287
|
+
return yield* Effect.fail(new ProviderError('namecheap', code, help ? `${message} ${help}` : message, data.ApiResponse?.Errors));
|
|
288
|
+
}
|
|
289
|
+
return data;
|
|
253
290
|
});
|
|
254
|
-
if (!response.ok) {
|
|
255
|
-
throw new ProviderError('namecheap', 'PROVIDER_API_ERROR', `Namecheap API error (${response.status}).`);
|
|
256
|
-
}
|
|
257
|
-
const data = parser.parse(await response.text());
|
|
258
|
-
const status = data.ApiResponse?.Status;
|
|
259
|
-
if (status !== 'OK') {
|
|
260
|
-
const message = getErrorMessage(asArray(data.ApiResponse?.Errors?.Error)[0] ?? 'Namecheap API error.');
|
|
261
|
-
const code = providerCodeFromNamecheapError(message);
|
|
262
|
-
const help = namecheapSetupHelp(code);
|
|
263
|
-
throw new ProviderError('namecheap', code, help ? `${message} ${help}` : message, data.ApiResponse?.Errors);
|
|
264
|
-
}
|
|
265
|
-
return data;
|
|
266
291
|
}
|
|
267
292
|
}
|
|
268
293
|
export const namecheapProviderDefinition = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { type DoomainEffect } from '../effect.js';
|
|
2
|
+
import { type ProviderAccountOptions } from './core/config.js';
|
|
1
3
|
import type { DnsProvider, DnsProviderDefinition } from './types.js';
|
|
2
4
|
export declare function listProviderDefinitions(): DnsProviderDefinition[];
|
|
3
5
|
export declare function getProviderDefinition(id: string): DnsProviderDefinition;
|
|
4
|
-
export declare function createProvider(id: string, opts?:
|
|
5
|
-
account?: string;
|
|
6
|
-
}): Promise<DnsProvider>;
|
|
6
|
+
export declare function createProvider(id: string, opts?: ProviderAccountOptions): DoomainEffect<DnsProvider>;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
import { trySync } from '../effect.js';
|
|
1
3
|
import { DoomainError } from '../errors.js';
|
|
2
4
|
import { ensureProviderId } from '../validate.js';
|
|
3
5
|
import { cloudflareProviderDefinition } from './cloudflare/index.js';
|
|
@@ -21,7 +23,9 @@ export function getProviderDefinition(id) {
|
|
|
21
23
|
throw new DoomainError('PROVIDER_NOT_FOUND', `Unsupported DNS provider: ${id}`);
|
|
22
24
|
return definition;
|
|
23
25
|
}
|
|
24
|
-
export
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
export function createProvider(id, opts = {}) {
|
|
27
|
+
return Effect.gen(function* () {
|
|
28
|
+
const definition = yield* trySync(() => getProviderDefinition(id), 'PROVIDER_NOT_FOUND');
|
|
29
|
+
return definition.create(yield* createProviderContext(definition, opts));
|
|
30
|
+
});
|
|
27
31
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type DoomainEffect } from '../../effect.js';
|
|
1
2
|
import type { DnsChangePlan, DnsProvider, DnsProviderDefinition, DnsRecord, DnsRecordInput, DnsZone, ProviderCapabilities, ProviderContext, ProviderHealth } from '../types.js';
|
|
2
3
|
export declare class SpaceshipProvider implements DnsProvider {
|
|
3
4
|
readonly capabilities: ProviderCapabilities;
|
|
@@ -5,18 +6,18 @@ export declare class SpaceshipProvider implements DnsProvider {
|
|
|
5
6
|
readonly name = "Spaceship";
|
|
6
7
|
private readonly http;
|
|
7
8
|
constructor(context: ProviderContext);
|
|
8
|
-
verifyCredentials():
|
|
9
|
-
listZones():
|
|
10
|
-
getZone(domain: string):
|
|
11
|
-
listRecords(zone: DnsZone):
|
|
9
|
+
verifyCredentials(): DoomainEffect<ProviderHealth>;
|
|
10
|
+
listZones(): DoomainEffect<DnsZone[]>;
|
|
11
|
+
getZone(domain: string): DoomainEffect<DnsZone | null>;
|
|
12
|
+
listRecords(zone: DnsZone): DoomainEffect<DnsRecord[]>;
|
|
12
13
|
planChanges(zone: DnsZone, desired: DnsRecordInput[], opts?: {
|
|
13
14
|
force?: boolean;
|
|
14
|
-
}):
|
|
15
|
-
applyChanges(zone: DnsZone, plan: DnsChangePlan):
|
|
15
|
+
}): DoomainEffect<DnsChangePlan>;
|
|
16
|
+
applyChanges(zone: DnsZone, plan: DnsChangePlan): DoomainEffect<{
|
|
16
17
|
applied: DnsChangePlan['changes'];
|
|
17
18
|
skipped: DnsRecordInput[];
|
|
18
19
|
}>;
|
|
19
|
-
upsertRecord(zone: DnsZone, record: DnsRecordInput):
|
|
20
|
-
deleteRecord(zone: DnsZone, record: DnsRecord):
|
|
20
|
+
upsertRecord(zone: DnsZone, record: DnsRecordInput): DoomainEffect<DnsRecord>;
|
|
21
|
+
deleteRecord(zone: DnsZone, record: DnsRecord): DoomainEffect<void>;
|
|
21
22
|
}
|
|
22
23
|
export declare const spaceshipProviderDefinition: DnsProviderDefinition;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
import { trySync } from '../../effect.js';
|
|
1
3
|
import { normalizeDomain } from '../../validate.js';
|
|
2
4
|
import { createProviderHttpClient } from '../core/http.js';
|
|
3
5
|
import { paginateBySkip } from '../core/pagination.js';
|
|
@@ -66,76 +68,75 @@ export class SpaceshipProvider {
|
|
|
66
68
|
},
|
|
67
69
|
providerId: this.id,
|
|
68
70
|
signal: context.signal,
|
|
71
|
+
transportErrorCode: context.transportErrorCode,
|
|
69
72
|
});
|
|
70
73
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return { ok: true };
|
|
74
|
+
verifyCredentials() {
|
|
75
|
+
return this.listZones().pipe(Effect.as({ ok: true }));
|
|
74
76
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
listZones() {
|
|
78
|
+
return paginateBySkip({
|
|
77
79
|
take: 100,
|
|
78
80
|
fetchPage: ({ skip, take }) => this.http.request('/domains', {
|
|
79
81
|
query: { orderBy: 'name', skip, take },
|
|
80
82
|
}),
|
|
81
|
-
})
|
|
82
|
-
return domains.flatMap((domain) => {
|
|
83
|
+
}).pipe(Effect.map((domains) => domains.flatMap((domain) => {
|
|
83
84
|
const zone = toZone(domain);
|
|
84
85
|
return zone ? [zone] : [];
|
|
85
|
-
});
|
|
86
|
+
})));
|
|
86
87
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
getZone(domain) {
|
|
89
|
+
return Effect.gen(this, function* () {
|
|
90
|
+
const normalized = yield* trySync(() => normalizeDomain(domain), 'INVALID_INPUT');
|
|
91
|
+
const zones = yield* this.listZones();
|
|
92
|
+
return zones.find((zone) => zone.name === normalized) ?? null;
|
|
93
|
+
});
|
|
91
94
|
}
|
|
92
|
-
|
|
93
|
-
|
|
95
|
+
listRecords(zone) {
|
|
96
|
+
return paginateBySkip({
|
|
94
97
|
take: 500,
|
|
95
98
|
fetchPage: ({ skip, take }) => this.http.request(`/dns/records/${zone.name}`, {
|
|
96
99
|
query: { skip, take },
|
|
97
100
|
}),
|
|
98
|
-
});
|
|
99
|
-
return records.map(toDnsRecord);
|
|
101
|
+
}).pipe(Effect.map((records) => records.map(toDnsRecord)));
|
|
100
102
|
}
|
|
101
|
-
|
|
102
|
-
return planDnsChanges({
|
|
103
|
-
desired,
|
|
104
|
-
existing: await this.listRecords(zone),
|
|
105
|
-
force: opts.force,
|
|
106
|
-
providerId: this.id,
|
|
107
|
-
zone,
|
|
108
|
-
});
|
|
103
|
+
planChanges(zone, desired, opts = {}) {
|
|
104
|
+
return this.listRecords(zone).pipe(Effect.map((existing) => planDnsChanges({ desired, existing, force: opts.force, providerId: this.id, zone })));
|
|
109
105
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
106
|
+
applyChanges(zone, plan) {
|
|
107
|
+
return Effect.gen(this, function* () {
|
|
108
|
+
yield* assertNoConflicts(this.id, plan);
|
|
109
|
+
const skipped = plan.changes.flatMap((change) => (change.action === 'skip' ? [change.record] : []));
|
|
110
|
+
const applied = plan.changes.filter((change) => change.action !== 'skip');
|
|
111
|
+
// Spaceship records do not have stable ids. Delete every old value before creating
|
|
112
|
+
// replacements so an API that processes accepted writes out of order cannot leave
|
|
113
|
+
// two address records in the same slot without the reconciler noticing and retrying.
|
|
114
|
+
for (const change of applied) {
|
|
115
|
+
if (change.action === 'delete' || change.action === 'update')
|
|
116
|
+
yield* this.deleteRecord(zone, change.existing);
|
|
117
|
+
}
|
|
118
|
+
for (const change of applied) {
|
|
119
|
+
if (change.action === 'create' || change.action === 'update')
|
|
120
|
+
yield* this.upsertRecord(zone, change.record);
|
|
121
|
+
}
|
|
122
|
+
return { applied, skipped };
|
|
123
|
+
});
|
|
126
124
|
}
|
|
127
|
-
|
|
128
|
-
|
|
125
|
+
upsertRecord(zone, record) {
|
|
126
|
+
return this.http
|
|
127
|
+
.request(`/dns/records/${zone.name}`, {
|
|
129
128
|
body: { force: true, items: [toSpaceshipItem(record)] },
|
|
130
129
|
method: 'PUT',
|
|
131
|
-
})
|
|
132
|
-
|
|
130
|
+
})
|
|
131
|
+
.pipe(Effect.as({ ...record, ttl: record.ttl ?? capabilities.defaultTtl }));
|
|
133
132
|
}
|
|
134
|
-
|
|
135
|
-
|
|
133
|
+
deleteRecord(zone, record) {
|
|
134
|
+
return this.http
|
|
135
|
+
.request(`/dns/records/${zone.name}`, {
|
|
136
136
|
body: [toSpaceshipItem(record)],
|
|
137
137
|
method: 'DELETE',
|
|
138
|
-
})
|
|
138
|
+
})
|
|
139
|
+
.pipe(Effect.asVoid);
|
|
139
140
|
}
|
|
140
141
|
}
|
|
141
142
|
export const spaceshipProviderDefinition = {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type DoomainConfig } from '../config.js';
|
|
2
|
+
import { type DoomainEffect } from '../effect.js';
|
|
2
3
|
import type { DnsProviderDefinition } from './types.js';
|
|
3
4
|
export interface ProviderStatus {
|
|
4
5
|
account: string;
|
|
@@ -20,4 +21,4 @@ export declare function isProviderConfigured(definition: DnsProviderDefinition,
|
|
|
20
21
|
}): boolean;
|
|
21
22
|
export declare function listProviderStatuses(opts?: {
|
|
22
23
|
verify?: boolean;
|
|
23
|
-
}):
|
|
24
|
+
}): DoomainEffect<ProviderStatus[]>;
|
|
@@ -1,44 +1,51 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
1
2
|
import { loadConfig } from '../config.js';
|
|
3
|
+
import { trySync } from '../effect.js';
|
|
2
4
|
import { DEFAULT_PROVIDER_ACCOUNT, isProviderAccountConfigured, listConfiguredProviderAccounts, normalizeProviderAccount, } from './core/config.js';
|
|
3
5
|
import { createProvider, listProviderDefinitions } from './registry.js';
|
|
4
6
|
export function isProviderConfigured(definition, config, opts = {}) {
|
|
5
7
|
return isProviderAccountConfigured(definition, config, opts);
|
|
6
8
|
}
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
9
|
+
export function listProviderStatuses(opts = {}) {
|
|
10
|
+
return Effect.gen(function* () {
|
|
11
|
+
const config = yield* loadConfig();
|
|
12
|
+
const statuses = [];
|
|
13
|
+
for (const definition of listProviderDefinitions()) {
|
|
14
|
+
const accounts = yield* trySync(() => listConfiguredProviderAccounts(config, definition), 'INVALID_INPUT');
|
|
15
|
+
const refs = accounts.length > 0
|
|
16
|
+
? accounts
|
|
17
|
+
: [{ account: DEFAULT_PROVIDER_ACCOUNT, isDefaultAccount: true, providerId: definition.id }];
|
|
18
|
+
for (const ref of refs) {
|
|
19
|
+
const account = yield* trySync(() => normalizeProviderAccount(ref.account), 'INVALID_INPUT');
|
|
20
|
+
const configured = accounts.some((item) => item.account === account);
|
|
21
|
+
const status = {
|
|
22
|
+
account,
|
|
23
|
+
accountLabel: ref.isDefaultAccount ? `${definition.id}/default` : `${definition.id}/${account}`,
|
|
24
|
+
configured,
|
|
25
|
+
default: config.defaults?.provider === definition.id,
|
|
26
|
+
displayName: definition.displayName,
|
|
27
|
+
docsUrl: definition.docsUrl,
|
|
28
|
+
id: definition.id,
|
|
29
|
+
isDefaultAccount: ref.isDefaultAccount,
|
|
30
|
+
isPreferredProvider: config.defaults?.provider === definition.id,
|
|
31
|
+
};
|
|
32
|
+
if (configured && opts.verify) {
|
|
33
|
+
const verified = yield* Effect.gen(function* () {
|
|
34
|
+
const provider = yield* createProvider(definition.id, { account });
|
|
35
|
+
return yield* provider.listZones();
|
|
36
|
+
}).pipe(Effect.either);
|
|
37
|
+
if (verified._tag === 'Right') {
|
|
38
|
+
status.domainCount = verified.right.length;
|
|
39
|
+
status.verified = true;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
status.error = verified.left.message;
|
|
43
|
+
status.verified = false;
|
|
44
|
+
}
|
|
38
45
|
}
|
|
46
|
+
statuses.push(status);
|
|
39
47
|
}
|
|
40
|
-
statuses.push(status);
|
|
41
48
|
}
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
return statuses;
|
|
50
|
+
});
|
|
44
51
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
export function fetchPublicIp() {
|
|
3
|
+
return Effect.acquireUseRelease(Effect.sync(() => {
|
|
4
|
+
const controller = new AbortController();
|
|
5
|
+
const timeout = setTimeout(() => controller.abort(), 2000);
|
|
6
|
+
return { controller, timeout };
|
|
7
|
+
}), ({ controller }) => Effect.tryPromise(async () => {
|
|
8
|
+
const response = await fetch('https://api.ipify.org', { signal: controller.signal });
|
|
9
|
+
if (!response.ok)
|
|
10
|
+
return undefined;
|
|
11
|
+
const ip = (await response.text()).trim();
|
|
12
|
+
return ip || undefined;
|
|
13
|
+
}).pipe(Effect.catchAll(() => Effect.succeed(undefined))), ({ timeout }) => Effect.sync(() => clearTimeout(timeout)));
|
|
14
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type DnsRecordSelector } from './dns-records.js';
|
|
2
2
|
import { type ResolvedDnsTarget } from './domain-provider.js';
|
|
3
|
+
import { type DoomainEffect } from './effect.js';
|
|
4
|
+
import { type DoomainErrorCode } from './errors.js';
|
|
3
5
|
import type { DnsProvider, DnsRecord, DnsRecordType } from './providers/types.js';
|
|
4
6
|
export interface RemoveDomainInput {
|
|
5
7
|
account?: string;
|
|
@@ -31,8 +33,9 @@ export interface RemoveDomainResult {
|
|
|
31
33
|
interface RemoveDomainDependencies {
|
|
32
34
|
createProvider: (provider: string, opts: {
|
|
33
35
|
account?: string;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
transportErrorCode?: DoomainErrorCode;
|
|
37
|
+
}) => DoomainEffect<DnsProvider>;
|
|
38
|
+
resolveTarget: (input: Pick<RemoveDomainInput, 'account' | 'domain' | 'provider'>) => DoomainEffect<ResolvedDnsTarget>;
|
|
36
39
|
}
|
|
37
|
-
export declare function removeDomain(input: RemoveDomainInput, dependencies?: RemoveDomainDependencies):
|
|
40
|
+
export declare function removeDomain(input: RemoveDomainInput, dependencies?: RemoveDomainDependencies): DoomainEffect<RemoveDomainResult>;
|
|
38
41
|
export {};
|