dkimkit 0.0.0-stage → 0.6.1
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 +153 -2
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +398 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/dns-host.d.ts +10 -0
- package/dist/src/dns-host.js +31 -0
- package/dist/src/dns-host.js.map +1 -0
- package/dist/src/errors.d.ts +17 -0
- package/dist/src/errors.js +51 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/hosted/api.d.ts +95 -0
- package/dist/src/hosted/api.js +781 -0
- package/dist/src/hosted/api.js.map +1 -0
- package/dist/src/hosted/auth.d.ts +70 -0
- package/dist/src/hosted/auth.js +121 -0
- package/dist/src/hosted/auth.js.map +1 -0
- package/dist/src/hosted/billing-dodo.d.ts +58 -0
- package/dist/src/hosted/billing-dodo.js +139 -0
- package/dist/src/hosted/billing-dodo.js.map +1 -0
- package/dist/src/hosted/billing-stripe.d.ts +82 -0
- package/dist/src/hosted/billing-stripe.js +205 -0
- package/dist/src/hosted/billing-stripe.js.map +1 -0
- package/dist/src/hosted/billing.d.ts +114 -0
- package/dist/src/hosted/billing.js +173 -0
- package/dist/src/hosted/billing.js.map +1 -0
- package/dist/src/hosted/cli.d.ts +2 -0
- package/dist/src/hosted/cli.js +305 -0
- package/dist/src/hosted/cli.js.map +1 -0
- package/dist/src/hosted/crypto.d.ts +22 -0
- package/dist/src/hosted/crypto.js +55 -0
- package/dist/src/hosted/crypto.js.map +1 -0
- package/dist/src/hosted/index.d.ts +7 -0
- package/dist/src/hosted/index.js +11 -0
- package/dist/src/hosted/index.js.map +1 -0
- package/dist/src/hosted/markdown.d.ts +16 -0
- package/dist/src/hosted/markdown.js +168 -0
- package/dist/src/hosted/markdown.js.map +1 -0
- package/dist/src/hosted/net.d.ts +9 -0
- package/dist/src/hosted/net.js +60 -0
- package/dist/src/hosted/net.js.map +1 -0
- package/dist/src/hosted/postgres-store.d.ts +62 -0
- package/dist/src/hosted/postgres-store.js +387 -0
- package/dist/src/hosted/postgres-store.js.map +1 -0
- package/dist/src/hosted/serialize.d.ts +144 -0
- package/dist/src/hosted/serialize.js +126 -0
- package/dist/src/hosted/serialize.js.map +1 -0
- package/dist/src/hosted/service.d.ts +166 -0
- package/dist/src/hosted/service.js +582 -0
- package/dist/src/hosted/service.js.map +1 -0
- package/dist/src/hosted/site.d.ts +54 -0
- package/dist/src/hosted/site.js +472 -0
- package/dist/src/hosted/site.js.map +1 -0
- package/dist/src/hosted/sqlite-store.d.ts +45 -0
- package/dist/src/hosted/sqlite-store.js +351 -0
- package/dist/src/hosted/sqlite-store.js.map +1 -0
- package/dist/src/hosted/store.d.ts +257 -0
- package/dist/src/hosted/store.js +181 -0
- package/dist/src/hosted/store.js.map +1 -0
- package/dist/src/hosted/widget.d.ts +46 -0
- package/dist/src/hosted/widget.js +130 -0
- package/dist/src/hosted/widget.js.map +1 -0
- package/dist/src/hosted/worker.d.ts +45 -0
- package/dist/src/hosted/worker.js +142 -0
- package/dist/src/hosted/worker.js.map +1 -0
- package/dist/src/http.d.ts +14 -0
- package/dist/src/http.js +54 -0
- package/dist/src/http.js.map +1 -0
- package/dist/src/index.d.ts +19 -0
- package/dist/src/index.js +19 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/orchestrator.d.ts +70 -0
- package/dist/src/orchestrator.js +69 -0
- package/dist/src/orchestrator.js.map +1 -0
- package/dist/src/propagation.d.ts +30 -0
- package/dist/src/propagation.js +76 -0
- package/dist/src/propagation.js.map +1 -0
- package/dist/src/providers/dns/aws.d.ts +28 -0
- package/dist/src/providers/dns/aws.js +103 -0
- package/dist/src/providers/dns/aws.js.map +1 -0
- package/dist/src/providers/dns/cloudflare.d.ts +26 -0
- package/dist/src/providers/dns/cloudflare.js +97 -0
- package/dist/src/providers/dns/cloudflare.js.map +1 -0
- package/dist/src/providers/dns/manual.d.ts +12 -0
- package/dist/src/providers/dns/manual.js +18 -0
- package/dist/src/providers/dns/manual.js.map +1 -0
- package/dist/src/providers/dns/route53.d.ts +34 -0
- package/dist/src/providers/dns/route53.js +176 -0
- package/dist/src/providers/dns/route53.js.map +1 -0
- package/dist/src/providers/email/factory.d.ts +29 -0
- package/dist/src/providers/email/factory.js +67 -0
- package/dist/src/providers/email/factory.js.map +1 -0
- package/dist/src/providers/email/mailgun.d.ts +36 -0
- package/dist/src/providers/email/mailgun.js +141 -0
- package/dist/src/providers/email/mailgun.js.map +1 -0
- package/dist/src/providers/email/postmark.d.ts +37 -0
- package/dist/src/providers/email/postmark.js +121 -0
- package/dist/src/providers/email/postmark.js.map +1 -0
- package/dist/src/providers/email/sendgrid.d.ts +31 -0
- package/dist/src/providers/email/sendgrid.js +109 -0
- package/dist/src/providers/email/sendgrid.js.map +1 -0
- package/dist/src/providers/email/ses.d.ts +39 -0
- package/dist/src/providers/email/ses.js +123 -0
- package/dist/src/providers/email/ses.js.map +1 -0
- package/dist/src/providers/factory.d.ts +14 -0
- package/dist/src/providers/factory.js +32 -0
- package/dist/src/providers/factory.js.map +1 -0
- package/dist/src/records.d.ts +35 -0
- package/dist/src/records.js +109 -0
- package/dist/src/records.js.map +1 -0
- package/dist/src/server.d.ts +67 -0
- package/dist/src/server.js +301 -0
- package/dist/src/server.js.map +1 -0
- package/dist/src/types.d.ts +75 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/xml.d.ts +6 -0
- package/dist/src/xml.js +23 -0
- package/dist/src/xml.js.map +1 -0
- package/docs/billing.md +43 -0
- package/docs/getting-started.md +57 -0
- package/docs/hosted-api.md +192 -0
- package/docs/self-hosting.md +148 -0
- package/docs/widget.md +121 -0
- package/package.json +63 -4
- package/web/dashboard.html +390 -0
- package/web/index.html +697 -0
- package/web/widget-frame.html +480 -0
- package/web/widget.js +91 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/http.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export * from './errors.js';
|
|
3
|
+
export { decideChange, describeRecord, isValidDomain, normalizeName, normalizeValue, sameValue, type ExistingRecord } from './records.js';
|
|
4
|
+
export { authoritativeLookup, ResolverLookup, waitForPropagation, type DnsLookup, type WaitOptions } from './propagation.js';
|
|
5
|
+
export { setupDomain, checkStatus, type SetupOptions, type SetupResult, type SetupEvent, type SetupStatus, type StatusResult } from './orchestrator.js';
|
|
6
|
+
export { SendGridProvider, SENDGRID_INBOUND_MX, type SendGridOptions } from './providers/email/sendgrid.js';
|
|
7
|
+
export { CloudflareDns, type CloudflareOptions } from './providers/dns/cloudflare.js';
|
|
8
|
+
export { ManualDns } from './providers/dns/manual.js';
|
|
9
|
+
export type { FetchLike } from './http.js';
|
|
10
|
+
export { Route53Dns, type Route53Options } from './providers/dns/route53.js';
|
|
11
|
+
export { loadAwsCredentials, signRequest, type AwsCredentials } from './providers/dns/aws.js';
|
|
12
|
+
export { createDnsProvider, DNS_PROVIDERS, type DnsProviderName, type DnsProviderConfig } from './providers/factory.js';
|
|
13
|
+
export { startUiServer, parseSetupRequest, type UiServer, type UiServerOptions, type SetupRequest } from './server.js';
|
|
14
|
+
export { verifyWebhookSignature, signWebhook, WEBHOOK_SIGNATURE_HEADER } from './hosted/crypto.js';
|
|
15
|
+
export { detectDnsHost, type DnsHost } from './dns-host.js';
|
|
16
|
+
export { PostmarkProvider, type PostmarkOptions } from './providers/email/postmark.js';
|
|
17
|
+
export { MailgunProvider, type MailgunOptions } from './providers/email/mailgun.js';
|
|
18
|
+
export { SesProvider, type SesOptions } from './providers/email/ses.js';
|
|
19
|
+
export { createEmailProvider, EMAIL_PROVIDERS, type EmailCredentials, type EmailProviderName } from './providers/email/factory.js';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export * from './errors.js';
|
|
3
|
+
export { decideChange, describeRecord, isValidDomain, normalizeName, normalizeValue, sameValue } from './records.js';
|
|
4
|
+
export { authoritativeLookup, ResolverLookup, waitForPropagation } from './propagation.js';
|
|
5
|
+
export { setupDomain, checkStatus } from './orchestrator.js';
|
|
6
|
+
export { SendGridProvider, SENDGRID_INBOUND_MX } from './providers/email/sendgrid.js';
|
|
7
|
+
export { CloudflareDns } from './providers/dns/cloudflare.js';
|
|
8
|
+
export { ManualDns } from './providers/dns/manual.js';
|
|
9
|
+
export { Route53Dns } from './providers/dns/route53.js';
|
|
10
|
+
export { loadAwsCredentials, signRequest } from './providers/dns/aws.js';
|
|
11
|
+
export { createDnsProvider, DNS_PROVIDERS } from './providers/factory.js';
|
|
12
|
+
export { startUiServer, parseSetupRequest } from './server.js';
|
|
13
|
+
export { verifyWebhookSignature, signWebhook, WEBHOOK_SIGNATURE_HEADER } from './hosted/crypto.js';
|
|
14
|
+
export { detectDnsHost } from './dns-host.js';
|
|
15
|
+
export { PostmarkProvider } from './providers/email/postmark.js';
|
|
16
|
+
export { MailgunProvider } from './providers/email/mailgun.js';
|
|
17
|
+
export { SesProvider } from './providers/email/ses.js';
|
|
18
|
+
export { createEmailProvider, EMAIL_PROVIDERS } from './providers/email/factory.js';
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,SAAS,EAAuB,MAAM,cAAc,CAAC;AAC1I,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,kBAAkB,EAAoC,MAAM,kBAAkB,CAAC;AAC7H,OAAO,EAAE,WAAW,EAAE,WAAW,EAA6F,MAAM,mBAAmB,CAAC;AACxJ,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAwB,MAAM,+BAA+B,CAAC;AAC5G,OAAO,EAAE,aAAa,EAA0B,MAAM,+BAA+B,CAAC;AACtF,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAEtD,OAAO,EAAE,UAAU,EAAuB,MAAM,4BAA4B,CAAC;AAC7E,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAuB,MAAM,wBAAwB,CAAC;AAC9F,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAgD,MAAM,wBAAwB,CAAC;AACxH,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAA0D,MAAM,aAAa,CAAC;AACvH,OAAO,EAAE,sBAAsB,EAAE,WAAW,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AACnG,OAAO,EAAE,aAAa,EAAgB,MAAM,eAAe,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAwB,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,eAAe,EAAuB,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,WAAW,EAAmB,MAAM,0BAA0B,CAAC;AACxE,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAiD,MAAM,8BAA8B,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { type DnsLookup } from './propagation.js';
|
|
2
|
+
import type { DnsProvider, DnsRecord, DomainAuthentication, EmailProvider, InboundAction, InboundParseOptions, PlannedChange, ValidationResult } from './types.js';
|
|
3
|
+
export type SetupEvent = {
|
|
4
|
+
type: 'domain';
|
|
5
|
+
auth: DomainAuthentication;
|
|
6
|
+
} | {
|
|
7
|
+
type: 'plan';
|
|
8
|
+
changes: PlannedChange[];
|
|
9
|
+
} | {
|
|
10
|
+
type: 'applied';
|
|
11
|
+
changes: PlannedChange[];
|
|
12
|
+
} | {
|
|
13
|
+
type: 'propagation';
|
|
14
|
+
pending: DnsRecord[];
|
|
15
|
+
total: number;
|
|
16
|
+
elapsedMs: number;
|
|
17
|
+
} | {
|
|
18
|
+
type: 'validation';
|
|
19
|
+
attempt: number;
|
|
20
|
+
result: ValidationResult;
|
|
21
|
+
} | {
|
|
22
|
+
type: 'inbound';
|
|
23
|
+
hostname: string;
|
|
24
|
+
url: string;
|
|
25
|
+
action: InboundAction;
|
|
26
|
+
};
|
|
27
|
+
export interface SetupOptions {
|
|
28
|
+
domain: string;
|
|
29
|
+
subdomain?: string;
|
|
30
|
+
inbound?: InboundParseOptions;
|
|
31
|
+
/** Replace existing CNAME/MX records that point elsewhere. */
|
|
32
|
+
overwrite?: boolean;
|
|
33
|
+
/** Plan only: create nothing in DNS. Note the provider may still create the domain to learn its records. */
|
|
34
|
+
dryRun?: boolean;
|
|
35
|
+
/** Called before writing DNS when there are changes; return false to abort. */
|
|
36
|
+
confirm?: (changes: PlannedChange[]) => Promise<boolean>;
|
|
37
|
+
propagation?: {
|
|
38
|
+
timeoutMs?: number;
|
|
39
|
+
intervalMs?: number;
|
|
40
|
+
lookup?: DnsLookup;
|
|
41
|
+
};
|
|
42
|
+
validation?: {
|
|
43
|
+
attempts?: number;
|
|
44
|
+
intervalMs?: number;
|
|
45
|
+
};
|
|
46
|
+
onEvent?: (event: SetupEvent) => void;
|
|
47
|
+
/** Stops waiting (propagation, validation retries) and rejects with an AbortError. */
|
|
48
|
+
signal?: AbortSignal;
|
|
49
|
+
}
|
|
50
|
+
export type SetupStatus = 'complete' | 'dry-run' | 'aborted' | 'dns-pending' | 'validation-failed';
|
|
51
|
+
export interface SetupResult {
|
|
52
|
+
status: SetupStatus;
|
|
53
|
+
domain: DomainAuthentication;
|
|
54
|
+
changes: PlannedChange[];
|
|
55
|
+
pending?: DnsRecord[];
|
|
56
|
+
validation?: ValidationResult;
|
|
57
|
+
inbound?: InboundAction;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Runs the full lifecycle: provider domain → DNS plan/apply → propagation →
|
|
61
|
+
* provider validation → inbound parse. Idempotent, so it is safe to re-run
|
|
62
|
+
* after a `dns-pending` or `validation-failed` result.
|
|
63
|
+
*/
|
|
64
|
+
export declare function setupDomain(email: EmailProvider, dns: DnsProvider, opts: SetupOptions): Promise<SetupResult>;
|
|
65
|
+
export interface StatusResult {
|
|
66
|
+
domain: DomainAuthentication | null;
|
|
67
|
+
validation?: ValidationResult;
|
|
68
|
+
}
|
|
69
|
+
/** Read-only check: finds the domain and asks the provider to re-validate it. */
|
|
70
|
+
export declare function checkStatus(email: EmailProvider, domain: string, subdomain?: string): Promise<StatusResult>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { DnsConflictError } from './errors.js';
|
|
2
|
+
import { sleep } from './http.js';
|
|
3
|
+
import { authoritativeLookup, waitForPropagation } from './propagation.js';
|
|
4
|
+
/**
|
|
5
|
+
* Runs the full lifecycle: provider domain → DNS plan/apply → propagation →
|
|
6
|
+
* provider validation → inbound parse. Idempotent, so it is safe to re-run
|
|
7
|
+
* after a `dns-pending` or `validation-failed` result.
|
|
8
|
+
*/
|
|
9
|
+
export async function setupDomain(email, dns, opts) {
|
|
10
|
+
const emit = opts.onEvent ?? (() => { });
|
|
11
|
+
const { signal } = opts;
|
|
12
|
+
const auth = await email.ensureDomain(opts.domain, { subdomain: opts.subdomain });
|
|
13
|
+
emit({ type: 'domain', auth });
|
|
14
|
+
const records = [...auth.records, ...(opts.inbound ? email.inboundRecords(opts.inbound.hostname) : [])];
|
|
15
|
+
const changes = await dns.plan(records, { overwrite: opts.overwrite });
|
|
16
|
+
emit({ type: 'plan', changes });
|
|
17
|
+
const conflicts = changes.filter((c) => c.action === 'conflict');
|
|
18
|
+
if (conflicts.length > 0)
|
|
19
|
+
throw new DnsConflictError(conflicts);
|
|
20
|
+
if (opts.dryRun)
|
|
21
|
+
return { status: 'dry-run', domain: auth, changes };
|
|
22
|
+
const writes = changes.filter((c) => c.action === 'create' || c.action === 'update');
|
|
23
|
+
if (writes.length > 0) {
|
|
24
|
+
if (opts.confirm && !(await opts.confirm(changes)))
|
|
25
|
+
return { status: 'aborted', domain: auth, changes };
|
|
26
|
+
await dns.apply(changes);
|
|
27
|
+
emit({ type: 'applied', changes: writes });
|
|
28
|
+
}
|
|
29
|
+
// Wait for the planned values, which can differ from the provider's (e.g. an SPF record merged into an existing one).
|
|
30
|
+
const planned = changes.map((c) => c.record);
|
|
31
|
+
const lookup = opts.propagation?.lookup ?? (await authoritativeLookup(opts.domain));
|
|
32
|
+
const pending = await waitForPropagation(planned, lookup, {
|
|
33
|
+
timeoutMs: opts.propagation?.timeoutMs,
|
|
34
|
+
intervalMs: opts.propagation?.intervalMs,
|
|
35
|
+
onProgress: (p, elapsedMs) => emit({ type: 'propagation', pending: p, total: planned.length, elapsedMs }),
|
|
36
|
+
signal,
|
|
37
|
+
});
|
|
38
|
+
if (pending.length > 0)
|
|
39
|
+
return { status: 'dns-pending', domain: auth, changes, pending };
|
|
40
|
+
// The provider's resolvers can lag behind the authoritative servers, so retry.
|
|
41
|
+
const { attempts = 6, intervalMs = 10_000 } = opts.validation ?? {};
|
|
42
|
+
let validation;
|
|
43
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
44
|
+
signal?.throwIfAborted();
|
|
45
|
+
validation = await email.validateDomain(auth.id);
|
|
46
|
+
emit({ type: 'validation', attempt, result: validation });
|
|
47
|
+
if (validation.valid)
|
|
48
|
+
break;
|
|
49
|
+
if (attempt < attempts)
|
|
50
|
+
await sleep(intervalMs, signal);
|
|
51
|
+
}
|
|
52
|
+
if (!validation?.valid)
|
|
53
|
+
return { status: 'validation-failed', domain: auth, changes, validation };
|
|
54
|
+
let inbound;
|
|
55
|
+
if (opts.inbound) {
|
|
56
|
+
signal?.throwIfAborted();
|
|
57
|
+
inbound = await email.ensureInboundParse(opts.inbound);
|
|
58
|
+
emit({ type: 'inbound', hostname: opts.inbound.hostname, url: opts.inbound.url, action: inbound });
|
|
59
|
+
}
|
|
60
|
+
return { status: 'complete', domain: { ...auth, valid: true }, changes, validation, inbound };
|
|
61
|
+
}
|
|
62
|
+
/** Read-only check: finds the domain and asks the provider to re-validate it. */
|
|
63
|
+
export async function checkStatus(email, domain, subdomain) {
|
|
64
|
+
const auth = await email.findDomain(domain, subdomain);
|
|
65
|
+
if (!auth)
|
|
66
|
+
return { domain: null };
|
|
67
|
+
return { domain: auth, validation: await email.validateDomain(auth.id) };
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=orchestrator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orchestrator.js","sourceRoot":"","sources":["../../src/orchestrator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Resolver } from 'node:dns/promises';
|
|
2
|
+
import type { DnsRecord } from './types.js';
|
|
3
|
+
export interface DnsLookup {
|
|
4
|
+
/** True when `record` is currently visible with the expected value. */
|
|
5
|
+
check(record: DnsRecord): Promise<boolean>;
|
|
6
|
+
}
|
|
7
|
+
export declare class ResolverLookup implements DnsLookup {
|
|
8
|
+
private readonly resolver;
|
|
9
|
+
constructor(resolver: Resolver);
|
|
10
|
+
check(record: DnsRecord): Promise<boolean>;
|
|
11
|
+
}
|
|
12
|
+
/** The zone apex holding `domain` and its nameservers, found by walking up the labels. */
|
|
13
|
+
export declare function findZoneNameservers(domain: string, resolveNs?: (name: string) => Promise<string[]>): Promise<{
|
|
14
|
+
zone: string;
|
|
15
|
+
nameservers: string[];
|
|
16
|
+
} | null>;
|
|
17
|
+
/**
|
|
18
|
+
* Queries the domain's authoritative nameservers directly, so results reflect
|
|
19
|
+
* what was just written rather than a recursive resolver's cache. Falls back
|
|
20
|
+
* to public resolvers if the nameservers can't be found.
|
|
21
|
+
*/
|
|
22
|
+
export declare function authoritativeLookup(domain: string): Promise<DnsLookup>;
|
|
23
|
+
export interface WaitOptions {
|
|
24
|
+
timeoutMs?: number;
|
|
25
|
+
intervalMs?: number;
|
|
26
|
+
onProgress?: (pending: DnsRecord[], elapsedMs: number) => void;
|
|
27
|
+
signal?: AbortSignal;
|
|
28
|
+
}
|
|
29
|
+
/** Polls until every record is visible or the timeout passes. Returns the records still missing. */
|
|
30
|
+
export declare function waitForPropagation(records: DnsRecord[], lookup: DnsLookup, opts?: WaitOptions): Promise<DnsRecord[]>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Resolver } from 'node:dns/promises';
|
|
2
|
+
import { sleep } from './http.js';
|
|
3
|
+
import { normalizeValue, sameValue } from './records.js';
|
|
4
|
+
const PUBLIC_RESOLVERS = ['1.1.1.1', '8.8.8.8'];
|
|
5
|
+
export class ResolverLookup {
|
|
6
|
+
resolver;
|
|
7
|
+
constructor(resolver) {
|
|
8
|
+
this.resolver = resolver;
|
|
9
|
+
}
|
|
10
|
+
async check(record) {
|
|
11
|
+
try {
|
|
12
|
+
switch (record.type) {
|
|
13
|
+
case 'CNAME':
|
|
14
|
+
return (await this.resolver.resolveCname(record.name)).some((v) => sameValue('CNAME', v, record.value));
|
|
15
|
+
case 'MX':
|
|
16
|
+
return (await this.resolver.resolveMx(record.name)).some((mx) => sameValue('MX', mx.exchange, record.value));
|
|
17
|
+
case 'TXT': {
|
|
18
|
+
const wanted = normalizeValue('TXT', record.value);
|
|
19
|
+
return (await this.resolver.resolveTxt(record.name)).some((chunks) => chunks.join('') === wanted);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
// NXDOMAIN, NODATA, SERVFAIL, timeouts: not visible yet.
|
|
25
|
+
if (typeof err.code === 'string')
|
|
26
|
+
return false;
|
|
27
|
+
throw err;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** The zone apex holding `domain` and its nameservers, found by walking up the labels. */
|
|
32
|
+
export async function findZoneNameservers(domain, resolveNs = (name) => new Resolver().resolveNs(name)) {
|
|
33
|
+
const labels = domain.split('.');
|
|
34
|
+
for (let i = 0; i < labels.length - 1; i++) {
|
|
35
|
+
const zone = labels.slice(i).join('.');
|
|
36
|
+
try {
|
|
37
|
+
const nameservers = await resolveNs(zone);
|
|
38
|
+
if (nameservers.length > 0)
|
|
39
|
+
return { zone, nameservers: nameservers.map((n) => n.toLowerCase()) };
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Not a zone apex; try the parent.
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Queries the domain's authoritative nameservers directly, so results reflect
|
|
49
|
+
* what was just written rather than a recursive resolver's cache. Falls back
|
|
50
|
+
* to public resolvers if the nameservers can't be found.
|
|
51
|
+
*/
|
|
52
|
+
export async function authoritativeLookup(domain) {
|
|
53
|
+
const system = new Resolver();
|
|
54
|
+
const found = await findZoneNameservers(domain);
|
|
55
|
+
const ips = found ? (await Promise.all(found.nameservers.map((ns) => system.resolve4(ns).catch(() => [])))).flat() : [];
|
|
56
|
+
const resolver = new Resolver({ timeout: 3000, tries: 2 });
|
|
57
|
+
resolver.setServers(ips.length > 0 ? ips : PUBLIC_RESOLVERS);
|
|
58
|
+
return new ResolverLookup(resolver);
|
|
59
|
+
}
|
|
60
|
+
/** Polls until every record is visible or the timeout passes. Returns the records still missing. */
|
|
61
|
+
export async function waitForPropagation(records, lookup, opts = {}) {
|
|
62
|
+
const { timeoutMs = 300_000, intervalMs = 5_000, onProgress, signal } = opts;
|
|
63
|
+
const started = Date.now();
|
|
64
|
+
let pending = records;
|
|
65
|
+
for (;;) {
|
|
66
|
+
signal?.throwIfAborted();
|
|
67
|
+
const visible = await Promise.all(pending.map((r) => lookup.check(r)));
|
|
68
|
+
pending = pending.filter((_, i) => !visible[i]);
|
|
69
|
+
const elapsed = Date.now() - started;
|
|
70
|
+
onProgress?.(pending, elapsed);
|
|
71
|
+
if (pending.length === 0 || elapsed + intervalMs > timeoutMs)
|
|
72
|
+
return pending;
|
|
73
|
+
await sleep(intervalMs, signal);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=propagation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"propagation.js","sourceRoot":"","sources":["../../src/propagation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface AwsCredentials {
|
|
2
|
+
accessKeyId: string;
|
|
3
|
+
secretAccessKey: string;
|
|
4
|
+
sessionToken?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface SignableRequest {
|
|
7
|
+
method: string;
|
|
8
|
+
url: string;
|
|
9
|
+
headers?: Record<string, string>;
|
|
10
|
+
body?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* AWS Signature Version 4. Returns the headers to send (everything except
|
|
14
|
+
* `host`, which fetch sets itself to the same value that was signed).
|
|
15
|
+
*/
|
|
16
|
+
export declare function signRequest(req: SignableRequest, credentials: AwsCredentials, region: string, service: string, now?: Date): Record<string, string>;
|
|
17
|
+
export interface LoadCredentialsOptions {
|
|
18
|
+
/** Named profile in the shared credentials file. Overrides environment keys. */
|
|
19
|
+
profile?: string;
|
|
20
|
+
env?: NodeJS.ProcessEnv;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Static credentials from AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY, or a profile
|
|
24
|
+
* in ~/.aws/credentials (AWS_PROFILE, AWS_SHARED_CREDENTIALS_FILE honored).
|
|
25
|
+
* SSO and instance roles are not supported; export keys for those
|
|
26
|
+
* (e.g. `aws configure export-credentials --format env`).
|
|
27
|
+
*/
|
|
28
|
+
export declare function loadAwsCredentials(opts?: LoadCredentialsOptions): Promise<AwsCredentials>;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { createHash, createHmac } from 'node:crypto';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
const sha256Hex = (data) => createHash('sha256').update(data, 'utf8').digest('hex');
|
|
6
|
+
const hmac = (key, data) => createHmac('sha256', key).update(data, 'utf8').digest();
|
|
7
|
+
/** encodeURIComponent plus the reserved characters SigV4 requires escaped. */
|
|
8
|
+
function uriEncode(s) {
|
|
9
|
+
return encodeURIComponent(s).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* AWS Signature Version 4. Returns the headers to send (everything except
|
|
13
|
+
* `host`, which fetch sets itself to the same value that was signed).
|
|
14
|
+
*/
|
|
15
|
+
export function signRequest(req, credentials, region, service, now = new Date()) {
|
|
16
|
+
const url = new URL(req.url);
|
|
17
|
+
const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, '');
|
|
18
|
+
const date = amzDate.slice(0, 8);
|
|
19
|
+
const headers = { ...req.headers, 'x-amz-date': amzDate };
|
|
20
|
+
if (credentials.sessionToken)
|
|
21
|
+
headers['x-amz-security-token'] = credentials.sessionToken;
|
|
22
|
+
const canonical = { host: url.host };
|
|
23
|
+
for (const [k, v] of Object.entries(headers))
|
|
24
|
+
canonical[k.toLowerCase()] = v.trim().replace(/\s+/g, ' ');
|
|
25
|
+
const names = Object.keys(canonical).sort();
|
|
26
|
+
const signedHeaders = names.join(';');
|
|
27
|
+
const query = [...url.searchParams]
|
|
28
|
+
.map(([k, v]) => [uriEncode(k), uriEncode(v)])
|
|
29
|
+
.sort(([ak, av], [bk, bv]) => (ak === bk ? (av < bv ? -1 : av > bv ? 1 : 0) : ak < bk ? -1 : 1))
|
|
30
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
31
|
+
.join('&');
|
|
32
|
+
// url.pathname is already encoded once; non-S3 services expect it encoded again.
|
|
33
|
+
const path = url.pathname.split('/').map(uriEncode).join('/') || '/';
|
|
34
|
+
const canonicalRequest = [
|
|
35
|
+
req.method.toUpperCase(),
|
|
36
|
+
path,
|
|
37
|
+
query,
|
|
38
|
+
names.map((n) => `${n}:${canonical[n]}\n`).join(''),
|
|
39
|
+
signedHeaders,
|
|
40
|
+
sha256Hex(req.body ?? ''),
|
|
41
|
+
].join('\n');
|
|
42
|
+
const scope = `${date}/${region}/${service}/aws4_request`;
|
|
43
|
+
const stringToSign = ['AWS4-HMAC-SHA256', amzDate, scope, sha256Hex(canonicalRequest)].join('\n');
|
|
44
|
+
const key = hmac(hmac(hmac(hmac(`AWS4${credentials.secretAccessKey}`, date), region), service), 'aws4_request');
|
|
45
|
+
const signature = createHmac('sha256', key).update(stringToSign, 'utf8').digest('hex');
|
|
46
|
+
return {
|
|
47
|
+
...headers,
|
|
48
|
+
Authorization: `AWS4-HMAC-SHA256 Credential=${credentials.accessKeyId}/${scope}, SignedHeaders=${signedHeaders}, Signature=${signature}`,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Static credentials from AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY, or a profile
|
|
53
|
+
* in ~/.aws/credentials (AWS_PROFILE, AWS_SHARED_CREDENTIALS_FILE honored).
|
|
54
|
+
* SSO and instance roles are not supported; export keys for those
|
|
55
|
+
* (e.g. `aws configure export-credentials --format env`).
|
|
56
|
+
*/
|
|
57
|
+
export async function loadAwsCredentials(opts = {}) {
|
|
58
|
+
const env = opts.env ?? process.env;
|
|
59
|
+
if (!opts.profile && env.AWS_ACCESS_KEY_ID && env.AWS_SECRET_ACCESS_KEY) {
|
|
60
|
+
return {
|
|
61
|
+
accessKeyId: env.AWS_ACCESS_KEY_ID,
|
|
62
|
+
secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
|
|
63
|
+
...(env.AWS_SESSION_TOKEN ? { sessionToken: env.AWS_SESSION_TOKEN } : {}),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const profile = opts.profile ?? env.AWS_PROFILE ?? 'default';
|
|
67
|
+
const file = env.AWS_SHARED_CREDENTIALS_FILE ?? join(homedir(), '.aws', 'credentials');
|
|
68
|
+
let text;
|
|
69
|
+
try {
|
|
70
|
+
text = await readFile(file, 'utf8');
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
throw new Error(`No AWS credentials: set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY, or create ${file}.`);
|
|
74
|
+
}
|
|
75
|
+
const section = parseIni(text)[profile];
|
|
76
|
+
if (!section?.aws_access_key_id || !section.aws_secret_access_key) {
|
|
77
|
+
throw new Error(`AWS profile "${profile}" in ${file} has no aws_access_key_id/aws_secret_access_key.`);
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
accessKeyId: section.aws_access_key_id,
|
|
81
|
+
secretAccessKey: section.aws_secret_access_key,
|
|
82
|
+
...(section.aws_session_token ? { sessionToken: section.aws_session_token } : {}),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function parseIni(text) {
|
|
86
|
+
const sections = {};
|
|
87
|
+
let current;
|
|
88
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
89
|
+
const line = raw.trim();
|
|
90
|
+
if (!line || line.startsWith('#') || line.startsWith(';'))
|
|
91
|
+
continue;
|
|
92
|
+
const header = /^\[\s*(?:profile\s+)?([^\]]+?)\s*\]$/.exec(line);
|
|
93
|
+
if (header) {
|
|
94
|
+
current = sections[header[1]] ??= {};
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const eq = line.indexOf('=');
|
|
98
|
+
if (current && eq > 0)
|
|
99
|
+
current[line.slice(0, eq).trim().toLowerCase()] = line.slice(eq + 1).trim();
|
|
100
|
+
}
|
|
101
|
+
return sections;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=aws.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aws.js","sourceRoot":"","sources":["../../../../src/providers/dns/aws.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type FetchLike } from '../../http.js';
|
|
2
|
+
import type { DnsProvider, DnsRecord, PlannedChange } from '../../types.js';
|
|
3
|
+
export interface CloudflareOptions {
|
|
4
|
+
/** Needs Zone:Read and DNS:Edit on the target zone. */
|
|
5
|
+
apiToken: string;
|
|
6
|
+
baseUrl?: string;
|
|
7
|
+
fetch?: FetchLike;
|
|
8
|
+
}
|
|
9
|
+
export declare class CloudflareDns implements DnsProvider {
|
|
10
|
+
private readonly opts;
|
|
11
|
+
readonly name = "cloudflare";
|
|
12
|
+
private readonly baseUrl;
|
|
13
|
+
private readonly fetchImpl;
|
|
14
|
+
private readonly zoneIds;
|
|
15
|
+
/** Zones confirmed to exist, by name. */
|
|
16
|
+
private readonly knownZones;
|
|
17
|
+
constructor(opts: CloudflareOptions);
|
|
18
|
+
plan(records: DnsRecord[], opts?: {
|
|
19
|
+
overwrite?: boolean;
|
|
20
|
+
}): Promise<PlannedChange[]>;
|
|
21
|
+
apply(changes: PlannedChange[]): Promise<void>;
|
|
22
|
+
/** Finds the zone holding `name` by trying it and each parent domain. */
|
|
23
|
+
zoneFor(name: string): Promise<string>;
|
|
24
|
+
private lookupZone;
|
|
25
|
+
private request;
|
|
26
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { ConfigError, DnsConflictError } from '../../errors.js';
|
|
2
|
+
import { requestJson } from '../../http.js';
|
|
3
|
+
import { decideChange, normalizeName } from '../../records.js';
|
|
4
|
+
export class CloudflareDns {
|
|
5
|
+
opts;
|
|
6
|
+
name = 'cloudflare';
|
|
7
|
+
baseUrl;
|
|
8
|
+
fetchImpl;
|
|
9
|
+
zoneIds = new Map();
|
|
10
|
+
/** Zones confirmed to exist, by name. */
|
|
11
|
+
knownZones = new Map();
|
|
12
|
+
constructor(opts) {
|
|
13
|
+
this.opts = opts;
|
|
14
|
+
this.baseUrl = opts.baseUrl ?? 'https://api.cloudflare.com/client/v4';
|
|
15
|
+
this.fetchImpl = opts.fetch ?? fetch;
|
|
16
|
+
}
|
|
17
|
+
async plan(records, opts = {}) {
|
|
18
|
+
const changes = [];
|
|
19
|
+
for (const record of records) {
|
|
20
|
+
const zoneId = await this.zoneFor(record.name);
|
|
21
|
+
const existing = await this.request(`/zones/${zoneId}/dns_records?name=${encodeURIComponent(record.name)}&per_page=100`);
|
|
22
|
+
const neutral = existing.map((e) => ({
|
|
23
|
+
id: e.id,
|
|
24
|
+
type: e.type,
|
|
25
|
+
value: e.content,
|
|
26
|
+
priority: e.priority,
|
|
27
|
+
proxied: e.proxied,
|
|
28
|
+
}));
|
|
29
|
+
changes.push(decideChange(record, neutral, opts.overwrite, records));
|
|
30
|
+
}
|
|
31
|
+
return changes;
|
|
32
|
+
}
|
|
33
|
+
async apply(changes) {
|
|
34
|
+
const conflicts = changes.filter((c) => c.action === 'conflict');
|
|
35
|
+
if (conflicts.length > 0)
|
|
36
|
+
throw new DnsConflictError(conflicts);
|
|
37
|
+
for (const change of changes) {
|
|
38
|
+
if (change.action === 'noop')
|
|
39
|
+
continue;
|
|
40
|
+
const zoneId = await this.zoneFor(change.record.name);
|
|
41
|
+
const body = toCloudflareBody(change.record);
|
|
42
|
+
if (change.action === 'update') {
|
|
43
|
+
await this.request(`/zones/${zoneId}/dns_records/${change.existingId}`, { method: 'PUT', body });
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
await this.request(`/zones/${zoneId}/dns_records`, { method: 'POST', body });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** Finds the zone holding `name` by trying it and each parent domain. */
|
|
51
|
+
async zoneFor(name) {
|
|
52
|
+
const labels = normalizeName(name).split('.');
|
|
53
|
+
const candidates = labels.slice(0, -1).map((_, i) => labels.slice(i).join('.'));
|
|
54
|
+
// Records usually share a zone; reuse one already found before calling the API.
|
|
55
|
+
const known = candidates.find((c) => this.knownZones.has(c));
|
|
56
|
+
if (known)
|
|
57
|
+
return this.knownZones.get(known);
|
|
58
|
+
for (const candidate of candidates) {
|
|
59
|
+
const id = await this.lookupZone(candidate);
|
|
60
|
+
if (id) {
|
|
61
|
+
this.knownZones.set(candidate, id);
|
|
62
|
+
return id;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
throw new ConfigError(`No Cloudflare zone found for ${name}. Check the domain is on this Cloudflare account and the token has Zone:Read access.`);
|
|
66
|
+
}
|
|
67
|
+
lookupZone(zoneName) {
|
|
68
|
+
let id = this.zoneIds.get(zoneName);
|
|
69
|
+
if (!id) {
|
|
70
|
+
id = this.request(`/zones?name=${encodeURIComponent(zoneName)}`).then((z) => z[0]?.id ?? null);
|
|
71
|
+
this.zoneIds.set(zoneName, id);
|
|
72
|
+
}
|
|
73
|
+
return id;
|
|
74
|
+
}
|
|
75
|
+
async request(path, opts = {}) {
|
|
76
|
+
const envelope = await requestJson(this.fetchImpl, 'Cloudflare', this.baseUrl + path, { ...opts, headers: { Authorization: `Bearer ${this.opts.apiToken}` } }, describeCloudflareError);
|
|
77
|
+
return envelope.result;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function toCloudflareBody(record) {
|
|
81
|
+
return {
|
|
82
|
+
type: record.type,
|
|
83
|
+
name: record.name,
|
|
84
|
+
content: record.value,
|
|
85
|
+
ttl: 1, // automatic
|
|
86
|
+
// Proxied CNAMEs resolve to Cloudflare IPs, which fails provider verification.
|
|
87
|
+
...(record.type === 'CNAME' ? { proxied: false } : {}),
|
|
88
|
+
...(record.type === 'MX' ? { priority: record.priority ?? 10 } : {}),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function describeCloudflareError(body) {
|
|
92
|
+
const errors = body?.errors;
|
|
93
|
+
if (!Array.isArray(errors) || errors.length === 0)
|
|
94
|
+
return undefined;
|
|
95
|
+
return errors.map((e) => `${e.message} (code ${e.code})`).join('; ');
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=cloudflare.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudflare.js","sourceRoot":"","sources":["../../../../src/providers/dns/cloudflare.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { DnsProvider, DnsRecord, PlannedChange } from '../../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* For DNS hosts without an API integration: hands the records to a callback
|
|
4
|
+
* (e.g. print them and wait for the user), then relies on propagation polling.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ManualDns implements DnsProvider {
|
|
7
|
+
private readonly onRecords;
|
|
8
|
+
readonly name = "manual";
|
|
9
|
+
constructor(onRecords: (records: DnsRecord[]) => Promise<void>);
|
|
10
|
+
plan(records: DnsRecord[]): Promise<PlannedChange[]>;
|
|
11
|
+
apply(changes: PlannedChange[]): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* For DNS hosts without an API integration: hands the records to a callback
|
|
3
|
+
* (e.g. print them and wait for the user), then relies on propagation polling.
|
|
4
|
+
*/
|
|
5
|
+
export class ManualDns {
|
|
6
|
+
onRecords;
|
|
7
|
+
name = 'manual';
|
|
8
|
+
constructor(onRecords) {
|
|
9
|
+
this.onRecords = onRecords;
|
|
10
|
+
}
|
|
11
|
+
async plan(records) {
|
|
12
|
+
return records.map((record) => ({ record, action: 'create' }));
|
|
13
|
+
}
|
|
14
|
+
async apply(changes) {
|
|
15
|
+
await this.onRecords(changes.filter((c) => c.action !== 'noop').map((c) => c.record));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=manual.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manual.js","sourceRoot":"","sources":["../../../../src/providers/dns/manual.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,OAAO,SAAS;IAGS;IAFpB,IAAI,GAAG,QAAQ,CAAC;IAEzB,YAA6B,SAAkD;QAAlD,cAAS,GAAT,SAAS,CAAyC;IAAG,CAAC;IAEnF,KAAK,CAAC,IAAI,CAAC,OAAoB;QAC7B,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;IACjE,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,OAAwB;QAClC,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;IACxF,CAAC;CACF"}
|