@gusnips/one-click-dns 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +142 -2
- package/dist/chunk-2T2PEF4H.js +521 -0
- package/dist/chunk-FGM7S7GQ.js +222 -0
- package/dist/chunk-P2O7INDB.js +8 -0
- package/dist/hosts-B0_V4Ned.d.cts +106 -0
- package/dist/hosts-B0_V4Ned.d.ts +106 -0
- package/dist/index.cjs +745 -0
- package/dist/index.d.cts +119 -0
- package/dist/index.d.ts +119 -0
- package/dist/index.js +198 -0
- package/dist/server/index.cjs +1020 -0
- package/dist/server/index.d.cts +144 -0
- package/dist/server/index.d.ts +144 -0
- package/dist/server/index.js +274 -0
- package/dist/writers/index.cjs +249 -0
- package/dist/writers/index.d.cts +57 -0
- package/dist/writers/index.d.ts +57 -0
- package/dist/writers/index.js +11 -0
- package/package.json +93 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gustavo Salomé
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,143 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @gusnips/one-click-dns
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Put DNS records in a customer's zone. The package finds where their DNS lives, then gives you the best way to add the record: a signed Domain Connect link, a pasted API token, or the steps for that host.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @gusnips/one-click-dns
|
|
7
|
+
# or
|
|
8
|
+
bun add @gusnips/one-click-dns
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
It has three entry points:
|
|
12
|
+
|
|
13
|
+
- `@gusnips/one-click-dns`: runs in the browser. The host list, the record spelling and the template presets.
|
|
14
|
+
- `@gusnips/one-click-dns/server`: reads DNS, builds signed links. Node or Bun.
|
|
15
|
+
- `@gusnips/one-click-dns/writers`: writes a record with an API token. Server only.
|
|
16
|
+
|
|
17
|
+
Keep keys and tokens on the server. Nothing in the browser entry needs them.
|
|
18
|
+
|
|
19
|
+
## First call
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { planConnection, systemDnsLookups } from "@gusnips/one-click-dns/server";
|
|
23
|
+
|
|
24
|
+
const plan = await planConnection({ hostname: "help.acme.com" }, systemDnsLookups(2000));
|
|
25
|
+
|
|
26
|
+
console.log(plan.reading?.hosts); // ["cloudflare"]
|
|
27
|
+
console.log(plan.methods.map((method) => method.kind)); // ["api-token", "manual"]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`methods` lists the ways to connect, best first:
|
|
31
|
+
|
|
32
|
+
| `kind` | What it means |
|
|
33
|
+
| ---------------- | ----------------------------------------------------------------------------------------------------- |
|
|
34
|
+
| `domain-connect` | One click. You send the person to `url`, their DNS host asks them to approve, and the records appear. |
|
|
35
|
+
| `api-token` | The person pastes a token, and your server writes the record. |
|
|
36
|
+
| `manual` | The person copies the records by hand. The host's name is in `hosts`. Always the last one. |
|
|
37
|
+
|
|
38
|
+
`plan.reading` is `null` when DNS did not answer. That is not the same as "host unknown", so do not show it as one.
|
|
39
|
+
|
|
40
|
+
## Add a record with a token
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { cloudflare } from "@gusnips/one-click-dns/writers";
|
|
44
|
+
|
|
45
|
+
const outcome = await cloudflare.addRecord({
|
|
46
|
+
token, // the person's Cloudflare API token
|
|
47
|
+
record: { type: "CNAME", name: "help.acme.com", value: "abc123.domains.example.app" },
|
|
48
|
+
});
|
|
49
|
+
// "created", "unchanged" or "unproxied"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The token is used for this call only. It is never stored, logged or put in an error. A record the person already has is never overwritten: you get a `conflict` instead.
|
|
53
|
+
|
|
54
|
+
When the write fails, it throws a `DnsWriteError` with a `code`. Word each code in your own languages:
|
|
55
|
+
|
|
56
|
+
| `code` | What to tell the person |
|
|
57
|
+
| ------------------- | ------------------------------------------------------------------------------------ |
|
|
58
|
+
| `token_invalid` | The host did not accept the token. Paste it again. |
|
|
59
|
+
| `zone_read_missing` | The token cannot list domains. Give it that permission. |
|
|
60
|
+
| `dns_edit_missing` | The token cannot change DNS records. Give it that permission. |
|
|
61
|
+
| `zone_not_found` | The token's account does not hold this domain. |
|
|
62
|
+
| `conflict` | The name already has another record (`params.type`). They decide what to do with it. |
|
|
63
|
+
| `unavailable` | The host did not answer. Try again. |
|
|
64
|
+
|
|
65
|
+
Cloudflare is the only writer today.
|
|
66
|
+
|
|
67
|
+
## One-click links (Domain Connect)
|
|
68
|
+
|
|
69
|
+
A link needs a template that your DNS host has switched on, and a key pair. You publish the public key in DNS and keep the private key on your server.
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
const plan = await planConnection(
|
|
73
|
+
{
|
|
74
|
+
hostname: "help.acme.com",
|
|
75
|
+
connect: {
|
|
76
|
+
providerId: "example.com", // your id in the Templates repository
|
|
77
|
+
keyId: "_dck1", // the TXT record name that holds your public key
|
|
78
|
+
serviceId: "custom-domain", // your template
|
|
79
|
+
privateKeyPem,
|
|
80
|
+
params: (zone) => ({
|
|
81
|
+
domain: zone,
|
|
82
|
+
host: "help",
|
|
83
|
+
target: "abc123",
|
|
84
|
+
redirect_uri: "https://app.example.com/done",
|
|
85
|
+
}),
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
systemDnsLookups(2000),
|
|
89
|
+
);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
When the host has your template, `plan.methods` starts with a `domain-connect` method. Sending the person back to `redirect_uri` proves nothing. Check the DNS yourself before you mark the domain done.
|
|
93
|
+
|
|
94
|
+
Only these providers are asked, because their `_domainconnect` value and settings were read from DNS: Cloudflare, Vercel, Simply.com and GoDaddy. A name in a customer's DNS is never fetched. More providers join the same way, one value at a time.
|
|
95
|
+
|
|
96
|
+
## Templates
|
|
97
|
+
|
|
98
|
+
A template is a small JSON file in the public [Domain Connect Templates](https://github.com/Domain-Connect/Templates) repository. It lists the records you may ask a host to add. The presets build one that follows the repository's rules:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import { cnameTemplate, templateFileName } from "@gusnips/one-click-dns";
|
|
102
|
+
|
|
103
|
+
const template = cnameTemplate(
|
|
104
|
+
{
|
|
105
|
+
providerId: "example.com",
|
|
106
|
+
providerName: "Example",
|
|
107
|
+
logoUrl: "https://example.com/logo.svg",
|
|
108
|
+
syncPubKeyDomain: "example.com",
|
|
109
|
+
syncRedirectDomain: "app.example.com",
|
|
110
|
+
},
|
|
111
|
+
{ pointsTo: "%target%.domains.example.com" },
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
console.log(templateFileName(template)); // "example.com.custom-domain.json"
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
There are presets for a CNAME, DMARC, SPF, DKIM, MX and a verification TXT.
|
|
118
|
+
|
|
119
|
+
A template belongs to the company that files it. It names your key domain, and the host checks every link against the key published there. So you cannot use someone else's template, and nobody can use yours. The customer's domain is not in the template: it comes with each link, so one template serves every customer.
|
|
120
|
+
|
|
121
|
+
To file a template: test it in the [online editor](https://domainconnect.paulonet.eu/dc/free/templateedit), run `dc-template-linter -merge-or-fail` on it, open a pull request, then ask each DNS host to switch it on.
|
|
122
|
+
|
|
123
|
+
## Steps for the host
|
|
124
|
+
|
|
125
|
+
`DNS_HOSTS` holds 33 hosts: how to recognise them from their name servers, the labels on their DNS page and the traps (a proxy on by default, a trailing dot, a TXT that needs quotes). The package holds data and no sentences, so you write the steps in your own words and languages. `dnsRecordRow` spells a record the way a host wants it typed:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
import { dnsRecordRow } from "@gusnips/one-click-dns";
|
|
129
|
+
|
|
130
|
+
dnsRecordRow("route53", "acme.com", { type: "TXT", name: "acme.com", value: "v=spf1 ~all" });
|
|
131
|
+
// { type: "TXT", name: "", value: "\"v=spf1 ~all\"", atRoot: true, rootUnsure: false }
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
A name server pattern is in the list only when the host documents it or it was seen in DNS. A label is quoted only when the host's own page shows it.
|
|
135
|
+
|
|
136
|
+
## Checks
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
bun run lint
|
|
140
|
+
bun run typecheck
|
|
141
|
+
bun run build
|
|
142
|
+
bun test src/path/to/file.test.ts
|
|
143
|
+
```
|
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
import {
|
|
2
|
+
normalizeDnsName
|
|
3
|
+
} from "./chunk-P2O7INDB.js";
|
|
4
|
+
|
|
5
|
+
// src/hosts.ts
|
|
6
|
+
var DNS_HOST_IDS = [
|
|
7
|
+
"cloudflare",
|
|
8
|
+
"godaddy",
|
|
9
|
+
"namecheap",
|
|
10
|
+
"namecheap-hosting",
|
|
11
|
+
"squarespace",
|
|
12
|
+
"route53",
|
|
13
|
+
"google-cloud-dns",
|
|
14
|
+
"azure-dns",
|
|
15
|
+
"ionos",
|
|
16
|
+
"hostinger",
|
|
17
|
+
"porkbun",
|
|
18
|
+
"name-com",
|
|
19
|
+
"dynadot",
|
|
20
|
+
"namesilo",
|
|
21
|
+
"gandi",
|
|
22
|
+
"ovhcloud",
|
|
23
|
+
"digitalocean",
|
|
24
|
+
"hetzner",
|
|
25
|
+
"vercel",
|
|
26
|
+
"netlify",
|
|
27
|
+
"wix",
|
|
28
|
+
"wordpress-com",
|
|
29
|
+
"cpanel",
|
|
30
|
+
"bluehost",
|
|
31
|
+
"hostgator",
|
|
32
|
+
"hostgator-br",
|
|
33
|
+
"siteground",
|
|
34
|
+
"registro-br",
|
|
35
|
+
"locaweb",
|
|
36
|
+
"kinghost",
|
|
37
|
+
"uol-host",
|
|
38
|
+
"umbler",
|
|
39
|
+
"dnsimple"
|
|
40
|
+
];
|
|
41
|
+
var NO_FIELDS = { name: null, value: null };
|
|
42
|
+
var SHORT = { kind: "shortName" };
|
|
43
|
+
var TRAILING_DOT = { kind: "trailingDot" };
|
|
44
|
+
var QUOTED_TXT = { kind: "quotedTxt" };
|
|
45
|
+
var CPANEL_EDITOR = {
|
|
46
|
+
link: null,
|
|
47
|
+
open: "Zone Editor",
|
|
48
|
+
add: "Add Record",
|
|
49
|
+
save: "Save Record",
|
|
50
|
+
cname: { name: "Name", value: "Record" },
|
|
51
|
+
txt: { name: "Name", value: "Record" },
|
|
52
|
+
root: null,
|
|
53
|
+
traps: []
|
|
54
|
+
};
|
|
55
|
+
var DNS_HOSTS = {
|
|
56
|
+
cloudflare: {
|
|
57
|
+
name: "Cloudflare",
|
|
58
|
+
// Documented: <name>.ns.cloudflare.com, and <color>.foundationdns.{com,net,org}.
|
|
59
|
+
ns: [/^[a-z0-9-]+\.ns\.cloudflare\.com$/, /^[a-z0-9-]+\.foundationdns\.(com|net|org)$/],
|
|
60
|
+
link: "https://dash.cloudflare.com/?to=/:account/:zone/dns/records",
|
|
61
|
+
open: "DNS Records",
|
|
62
|
+
add: "Add record",
|
|
63
|
+
save: "Save",
|
|
64
|
+
cname: { name: "Name", value: "Target" },
|
|
65
|
+
// The dashboard's label for a TXT value is not on a docs page.
|
|
66
|
+
txt: { name: "Name", value: null },
|
|
67
|
+
root: "@",
|
|
68
|
+
traps: [{ kind: "proxyOff", field: "Proxy status", value: "DNS only" }, SHORT]
|
|
69
|
+
},
|
|
70
|
+
godaddy: {
|
|
71
|
+
name: "GoDaddy",
|
|
72
|
+
// Observed: ns<NN>.domaincontrol.com (GoDaddy's docs show only ns1).
|
|
73
|
+
ns: [/^ns\d{1,2}\.domaincontrol\.com$/],
|
|
74
|
+
link: null,
|
|
75
|
+
open: "DNS",
|
|
76
|
+
add: "Add New Record",
|
|
77
|
+
save: "Save",
|
|
78
|
+
cname: { name: "Name", value: "Value" },
|
|
79
|
+
txt: { name: "Name", value: "Value" },
|
|
80
|
+
root: "@",
|
|
81
|
+
traps: [SHORT]
|
|
82
|
+
},
|
|
83
|
+
namecheap: {
|
|
84
|
+
name: "Namecheap",
|
|
85
|
+
// Documented: dns1/2.registrar-servers.com (BasicDNS), pdns1/2 (PremiumDNS),
|
|
86
|
+
// freedns1-5 (FreeDNS).
|
|
87
|
+
ns: [/^(dns|pdns|freedns)\d\.registrar-servers\.com$/],
|
|
88
|
+
link: null,
|
|
89
|
+
open: "Advanced DNS",
|
|
90
|
+
add: "Add New Record",
|
|
91
|
+
save: "Save All Changes",
|
|
92
|
+
cname: { name: "Host", value: "Value" },
|
|
93
|
+
txt: { name: "Host", value: "Value" },
|
|
94
|
+
root: "@",
|
|
95
|
+
traps: [SHORT]
|
|
96
|
+
},
|
|
97
|
+
"namecheap-hosting": {
|
|
98
|
+
name: "Namecheap hosting",
|
|
99
|
+
// Documented: dns1/2.namecheaphosting.com. The records live in cPanel, not Advanced DNS.
|
|
100
|
+
ns: [/^dns\d\.namecheaphosting\.com$/],
|
|
101
|
+
...CPANEL_EDITOR
|
|
102
|
+
},
|
|
103
|
+
squarespace: {
|
|
104
|
+
name: "Squarespace",
|
|
105
|
+
ns: [
|
|
106
|
+
// Documented: Google Cloud DNS nameservers "ending in squarespacedns.com".
|
|
107
|
+
/^[a-z0-9-]+\.squarespacedns\.com$/,
|
|
108
|
+
// Observed, listed so that these names read as ambiguous (domains moved from Google
|
|
109
|
+
// Domains sit on Google's names; older Squarespace domains on NS1's).
|
|
110
|
+
/^ns-cloud-[a-z]\d\.googledomains\.com$/,
|
|
111
|
+
/^dns\d\.p\d{2}\.nsone\.net$/
|
|
112
|
+
],
|
|
113
|
+
link: null,
|
|
114
|
+
open: "DNS Settings",
|
|
115
|
+
add: "Add record",
|
|
116
|
+
save: "Save",
|
|
117
|
+
cname: { name: "Name", value: "Data" },
|
|
118
|
+
txt: { name: "Name", value: "Text" },
|
|
119
|
+
root: "@",
|
|
120
|
+
traps: [SHORT]
|
|
121
|
+
},
|
|
122
|
+
route53: {
|
|
123
|
+
name: "Amazon Route 53",
|
|
124
|
+
// Documented: ns-<n>.awsdns-<nn>.{com,net,org,co.uk}.
|
|
125
|
+
ns: [/^ns-\d+\.awsdns-\d+\.(com|net|org|co\.uk)$/],
|
|
126
|
+
link: "https://console.aws.amazon.com/route53/v2/hostedzones",
|
|
127
|
+
open: "Hosted zones",
|
|
128
|
+
add: "Create record",
|
|
129
|
+
save: "Create records",
|
|
130
|
+
cname: { name: "Record name", value: "Value" },
|
|
131
|
+
txt: { name: "Record name", value: "Value" },
|
|
132
|
+
// "don't enter a value (for example, an @ symbol) in the Name field".
|
|
133
|
+
root: "",
|
|
134
|
+
traps: [SHORT, QUOTED_TXT]
|
|
135
|
+
},
|
|
136
|
+
"google-cloud-dns": {
|
|
137
|
+
name: "Google Cloud DNS",
|
|
138
|
+
// Documented: ns-cloud-<a-d><1-4>.googledomains.com. Squarespace domains moved from
|
|
139
|
+
// Google Domains use the same names, so a set of them is ambiguous.
|
|
140
|
+
ns: [/^ns-cloud-[a-z]\d\.googledomains\.com$/],
|
|
141
|
+
link: null,
|
|
142
|
+
open: null,
|
|
143
|
+
add: "Add standard",
|
|
144
|
+
save: "Create",
|
|
145
|
+
cname: { name: "DNS name", value: null },
|
|
146
|
+
txt: { name: "DNS name", value: null },
|
|
147
|
+
// "To create a resource record at the domain apex, leave the DNS name field blank."
|
|
148
|
+
root: "",
|
|
149
|
+
traps: [SHORT]
|
|
150
|
+
},
|
|
151
|
+
"azure-dns": {
|
|
152
|
+
name: "Azure DNS",
|
|
153
|
+
// Documented: ns<1-4>-<nn>.azure-dns.{com,net,org,info}.
|
|
154
|
+
ns: [/^ns\d-\d+\.azure-dns\.(com|net|org|info)$/],
|
|
155
|
+
link: null,
|
|
156
|
+
open: "Record sets",
|
|
157
|
+
add: "Add",
|
|
158
|
+
save: "Add",
|
|
159
|
+
cname: { name: "Name", value: null },
|
|
160
|
+
txt: { name: "Name", value: null },
|
|
161
|
+
root: "@",
|
|
162
|
+
traps: [SHORT]
|
|
163
|
+
},
|
|
164
|
+
ionos: {
|
|
165
|
+
name: "IONOS",
|
|
166
|
+
// Documented: ns1<ZZZ>.ui-dns.{de,biz,org,com}, ZZZ from 016 to 126.
|
|
167
|
+
ns: [/^ns1\d{3}\.ui-dns\.(de|biz|org|com)$/],
|
|
168
|
+
link: null,
|
|
169
|
+
open: "DNS",
|
|
170
|
+
add: "Add Record",
|
|
171
|
+
save: "Save",
|
|
172
|
+
// The two articles spell the name box differently.
|
|
173
|
+
cname: { name: "Hostname", value: "Point to" },
|
|
174
|
+
txt: { name: "Host name", value: "Value" },
|
|
175
|
+
root: "@",
|
|
176
|
+
traps: [SHORT]
|
|
177
|
+
},
|
|
178
|
+
hostinger: {
|
|
179
|
+
name: "Hostinger",
|
|
180
|
+
// No pattern: ns1/2.dns-parking.com is a search snippet, never confirmed on a page of
|
|
181
|
+
// theirs. The picker reaches Hostinger.
|
|
182
|
+
ns: [],
|
|
183
|
+
link: null,
|
|
184
|
+
open: "DNS Zone Editor",
|
|
185
|
+
add: null,
|
|
186
|
+
save: "Add Record",
|
|
187
|
+
cname: { name: "Name", value: "Points to" },
|
|
188
|
+
txt: { name: "Name", value: "TXT value" },
|
|
189
|
+
root: "@",
|
|
190
|
+
traps: [SHORT, { kind: "oneSpf" }]
|
|
191
|
+
},
|
|
192
|
+
porkbun: {
|
|
193
|
+
name: "Porkbun",
|
|
194
|
+
// Documented: <city>.ns.porkbun.com.
|
|
195
|
+
ns: [/^[a-z]+\.ns\.porkbun\.com$/],
|
|
196
|
+
link: null,
|
|
197
|
+
open: "DNS",
|
|
198
|
+
add: "Add Record",
|
|
199
|
+
save: "Add",
|
|
200
|
+
cname: { name: "Host", value: "Answer" },
|
|
201
|
+
txt: { name: "Host", value: "Answer" },
|
|
202
|
+
// "If you're adding DNS records to your root domain, you will leave this field blank."
|
|
203
|
+
root: "",
|
|
204
|
+
traps: [SHORT]
|
|
205
|
+
},
|
|
206
|
+
"name-com": {
|
|
207
|
+
name: "Name.com",
|
|
208
|
+
// Observed: ns1<abc>.name.com to ns4<abc>.name.com.
|
|
209
|
+
ns: [/^ns[1-4][a-z]{3}\.name\.com$/],
|
|
210
|
+
link: null,
|
|
211
|
+
open: "Manage DNS Records",
|
|
212
|
+
add: null,
|
|
213
|
+
save: "Add Record",
|
|
214
|
+
cname: { name: "Host", value: "Answer" },
|
|
215
|
+
txt: { name: "Host", value: "Answer" },
|
|
216
|
+
root: null,
|
|
217
|
+
traps: [SHORT]
|
|
218
|
+
},
|
|
219
|
+
dynadot: {
|
|
220
|
+
name: "Dynadot",
|
|
221
|
+
// A Dynadot staff answer on its forum: ns1/2.dyna-ns.net.
|
|
222
|
+
ns: [/^ns\d\.dyna-ns\.net$/],
|
|
223
|
+
link: null,
|
|
224
|
+
open: "DNS Settings",
|
|
225
|
+
add: null,
|
|
226
|
+
save: "Save Settings",
|
|
227
|
+
cname: { name: "Subdomain Record", value: "IP Address or Target Host" },
|
|
228
|
+
txt: NO_FIELDS,
|
|
229
|
+
// The zone's own name goes in a separate "Domain Record" section, never as "@".
|
|
230
|
+
root: null,
|
|
231
|
+
traps: [SHORT]
|
|
232
|
+
},
|
|
233
|
+
namesilo: {
|
|
234
|
+
name: "NameSilo",
|
|
235
|
+
// Documented: ns1-3.dnsowl.com.
|
|
236
|
+
ns: [/^ns\d\.dnsowl\.com$/],
|
|
237
|
+
link: null,
|
|
238
|
+
open: null,
|
|
239
|
+
add: null,
|
|
240
|
+
save: "Submit",
|
|
241
|
+
cname: NO_FIELDS,
|
|
242
|
+
txt: NO_FIELDS,
|
|
243
|
+
// "You can leave the hostname blank ... If you do enter an "@", we will convert that".
|
|
244
|
+
root: "",
|
|
245
|
+
traps: []
|
|
246
|
+
},
|
|
247
|
+
gandi: {
|
|
248
|
+
name: "Gandi",
|
|
249
|
+
// Observed: ns-<n>-<a|b|c>.gandi.net.
|
|
250
|
+
ns: [/^ns-\d+-[abc]\.gandi\.net$/],
|
|
251
|
+
link: null,
|
|
252
|
+
open: "DNS Records",
|
|
253
|
+
add: "Add",
|
|
254
|
+
save: null,
|
|
255
|
+
cname: NO_FIELDS,
|
|
256
|
+
txt: NO_FIELDS,
|
|
257
|
+
root: null,
|
|
258
|
+
traps: [SHORT]
|
|
259
|
+
},
|
|
260
|
+
ovhcloud: {
|
|
261
|
+
name: "OVHcloud",
|
|
262
|
+
// Documented: (ns|dns)<nn or nnn>.ovh.net, and (ns|dns)200.anycast.me.
|
|
263
|
+
ns: [/^(ns|dns)\d{2,3}\.ovh\.net$/, /^(ns|dns)200\.anycast\.me$/],
|
|
264
|
+
link: null,
|
|
265
|
+
open: "DNS zone",
|
|
266
|
+
add: "Add an entry",
|
|
267
|
+
save: null,
|
|
268
|
+
cname: { name: "Subdomain", value: "Target" },
|
|
269
|
+
txt: { name: "Subdomain", value: "Target" },
|
|
270
|
+
root: null,
|
|
271
|
+
traps: [SHORT, TRAILING_DOT]
|
|
272
|
+
},
|
|
273
|
+
digitalocean: {
|
|
274
|
+
name: "DigitalOcean",
|
|
275
|
+
// Documented: ns1-3.digitalocean.com.
|
|
276
|
+
ns: [/^ns[1-3]\.digitalocean\.com$/],
|
|
277
|
+
link: null,
|
|
278
|
+
open: "Domains",
|
|
279
|
+
add: "Create a record",
|
|
280
|
+
save: "Create Record",
|
|
281
|
+
cname: { name: "Hostname", value: "Is an alias of" },
|
|
282
|
+
txt: { name: "Hostname", value: "TXT Value" },
|
|
283
|
+
root: "@",
|
|
284
|
+
traps: [SHORT, TRAILING_DOT]
|
|
285
|
+
},
|
|
286
|
+
hetzner: {
|
|
287
|
+
name: "Hetzner",
|
|
288
|
+
ns: [
|
|
289
|
+
// Documented: the primary, secondary (still used by older zones) and konsoleH names.
|
|
290
|
+
/^(hydrogen|oxygen|helium)\.ns\.hetzner\.(com|de)$/,
|
|
291
|
+
/^[a-z0-9]+\.first-ns\.de$/,
|
|
292
|
+
/^[a-z0-9]+\.second-ns\.(de|com)$/,
|
|
293
|
+
/^ns1\.your-server\.de$/
|
|
294
|
+
],
|
|
295
|
+
link: null,
|
|
296
|
+
open: "DNS",
|
|
297
|
+
add: null,
|
|
298
|
+
save: "Add",
|
|
299
|
+
cname: { name: "Name", value: "Alias / Value" },
|
|
300
|
+
txt: { name: "Name", value: null },
|
|
301
|
+
root: "@",
|
|
302
|
+
traps: [SHORT, TRAILING_DOT, QUOTED_TXT]
|
|
303
|
+
},
|
|
304
|
+
vercel: {
|
|
305
|
+
name: "Vercel",
|
|
306
|
+
// Documented: ns1-4.vercel-dns.com (the "alternates" are not named).
|
|
307
|
+
ns: [/^ns[1-4]\.vercel-dns\.com$/],
|
|
308
|
+
link: "https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fdomains",
|
|
309
|
+
open: "Domains",
|
|
310
|
+
add: null,
|
|
311
|
+
save: "Add",
|
|
312
|
+
cname: { name: "Name", value: "Value" },
|
|
313
|
+
txt: { name: "Name", value: "Value" },
|
|
314
|
+
// "Leave Name blank for the root domain."
|
|
315
|
+
root: "",
|
|
316
|
+
traps: [SHORT]
|
|
317
|
+
},
|
|
318
|
+
netlify: {
|
|
319
|
+
name: "Netlify",
|
|
320
|
+
// Documented: "a format similar to dns1.p01.nsone.net". NS1's names are shared with
|
|
321
|
+
// older Squarespace zones, so a set of them is ambiguous.
|
|
322
|
+
ns: [/^dns\d\.p\d{2}\.nsone\.net$/],
|
|
323
|
+
link: null,
|
|
324
|
+
open: "DNS",
|
|
325
|
+
add: "Add new record",
|
|
326
|
+
save: "Save",
|
|
327
|
+
cname: NO_FIELDS,
|
|
328
|
+
txt: NO_FIELDS,
|
|
329
|
+
root: null,
|
|
330
|
+
traps: []
|
|
331
|
+
},
|
|
332
|
+
wix: {
|
|
333
|
+
name: "Wix",
|
|
334
|
+
// Observed: ns<n>.wixdns.net. A domain "connected to Wix via pointing" keeps its old
|
|
335
|
+
// name servers, so it never matches here.
|
|
336
|
+
ns: [/^ns\d+\.wixdns\.net$/],
|
|
337
|
+
link: null,
|
|
338
|
+
open: "Manage DNS Records",
|
|
339
|
+
add: "Add Record",
|
|
340
|
+
save: "Save",
|
|
341
|
+
cname: { name: "Host Name", value: "Value" },
|
|
342
|
+
txt: { name: "Host Name", value: "Value" },
|
|
343
|
+
// "Leave this field blank if you were instructed to add an @ sign."
|
|
344
|
+
root: "",
|
|
345
|
+
traps: []
|
|
346
|
+
},
|
|
347
|
+
"wordpress-com": {
|
|
348
|
+
name: "WordPress.com",
|
|
349
|
+
// Documented: ns1-3.wordpress.com.
|
|
350
|
+
ns: [/^ns[1-3]\.wordpress\.com$/],
|
|
351
|
+
link: "https://wordpress.com/domains/",
|
|
352
|
+
open: "DNS records",
|
|
353
|
+
add: "Add record",
|
|
354
|
+
save: null,
|
|
355
|
+
cname: { name: "Name (host)", value: "Alias of (points to)" },
|
|
356
|
+
txt: { name: "Name", value: "Text" },
|
|
357
|
+
root: "@",
|
|
358
|
+
// The Name box takes the label or the whole name, and ignores quotes around a TXT value.
|
|
359
|
+
traps: []
|
|
360
|
+
},
|
|
361
|
+
cpanel: {
|
|
362
|
+
// Hosting companies run their own name servers, so there is no pattern: the picker
|
|
363
|
+
// reaches it.
|
|
364
|
+
name: "cPanel",
|
|
365
|
+
ns: [],
|
|
366
|
+
...CPANEL_EDITOR
|
|
367
|
+
},
|
|
368
|
+
bluehost: {
|
|
369
|
+
name: "Bluehost",
|
|
370
|
+
// Documented: ns1/2.bluehost.com. Its help is behind a captcha and the menu path was
|
|
371
|
+
// read from a 2024 archive, so no label is quoted.
|
|
372
|
+
ns: [/^ns[12]\.bluehost\.com$/],
|
|
373
|
+
link: null,
|
|
374
|
+
open: null,
|
|
375
|
+
add: null,
|
|
376
|
+
save: null,
|
|
377
|
+
cname: NO_FIELDS,
|
|
378
|
+
txt: NO_FIELDS,
|
|
379
|
+
root: null,
|
|
380
|
+
traps: []
|
|
381
|
+
},
|
|
382
|
+
hostgator: {
|
|
383
|
+
name: "HostGator",
|
|
384
|
+
// Documented: hgns1/2.hostgator.com for new customers, ns<NNNN>.hostgator.com before
|
|
385
|
+
// November 2023.
|
|
386
|
+
ns: [/^hgns\d\.hostgator\.com$/, /^ns\d{4}\.hostgator\.com$/],
|
|
387
|
+
link: null,
|
|
388
|
+
open: "DNS",
|
|
389
|
+
add: "+ ADD RECORD",
|
|
390
|
+
save: "ADD",
|
|
391
|
+
cname: NO_FIELDS,
|
|
392
|
+
txt: NO_FIELDS,
|
|
393
|
+
root: null,
|
|
394
|
+
traps: []
|
|
395
|
+
},
|
|
396
|
+
"hostgator-br": {
|
|
397
|
+
name: "HostGator Brasil",
|
|
398
|
+
// Documented: dns3/4.hostgator.com.br for a registered domain with no plan yet. A plan's
|
|
399
|
+
// names come in its activation email and are not on a page.
|
|
400
|
+
ns: [/^dns[34]\.hostgator\.com\.br$/],
|
|
401
|
+
link: null,
|
|
402
|
+
open: "Editar Zona Avan\xE7ada de DNS",
|
|
403
|
+
add: "Adicionar registro",
|
|
404
|
+
save: "Adicionar",
|
|
405
|
+
// The value box's name changes with the type ("O nome é alterado conforme o Tipo").
|
|
406
|
+
cname: { name: "Nome", value: null },
|
|
407
|
+
txt: { name: "Nome", value: null },
|
|
408
|
+
root: null,
|
|
409
|
+
traps: []
|
|
410
|
+
},
|
|
411
|
+
siteground: {
|
|
412
|
+
name: "SiteGround",
|
|
413
|
+
// Documented: ns1/2.siteground.net.
|
|
414
|
+
ns: [/^ns[12]\.siteground\.net$/],
|
|
415
|
+
link: null,
|
|
416
|
+
open: "DNS Zone Editor",
|
|
417
|
+
add: null,
|
|
418
|
+
save: "Create",
|
|
419
|
+
cname: { name: "Name", value: null },
|
|
420
|
+
txt: { name: "Name", value: null },
|
|
421
|
+
root: null,
|
|
422
|
+
traps: [SHORT]
|
|
423
|
+
},
|
|
424
|
+
"registro-br": {
|
|
425
|
+
name: "Registro.br",
|
|
426
|
+
// Observed: a.auto.dns.br and b.auto.dns.br.
|
|
427
|
+
ns: [/^[ab]\.auto\.dns\.br$/],
|
|
428
|
+
link: null,
|
|
429
|
+
open: "CONFIGURAR ZONA DNS",
|
|
430
|
+
add: null,
|
|
431
|
+
save: null,
|
|
432
|
+
cname: NO_FIELDS,
|
|
433
|
+
txt: NO_FIELDS,
|
|
434
|
+
root: null,
|
|
435
|
+
traps: [{ kind: "advancedMode", mode: "Modo Avan\xE7ado" }]
|
|
436
|
+
},
|
|
437
|
+
locaweb: {
|
|
438
|
+
name: "Locaweb",
|
|
439
|
+
// Documented: ns1-3.locaweb.com.br.
|
|
440
|
+
ns: [/^ns[1-3]\.locaweb\.com\.br$/],
|
|
441
|
+
link: null,
|
|
442
|
+
open: "Editar zona DNS",
|
|
443
|
+
add: "Adicionar entrada",
|
|
444
|
+
save: null,
|
|
445
|
+
cname: { name: "Entrada", value: "Conte\xFAdo" },
|
|
446
|
+
txt: { name: "Entrada", value: "Conte\xFAdo" },
|
|
447
|
+
// "o campo Entrada é preenchido por padrão com apenas um ponto (.)".
|
|
448
|
+
root: ".",
|
|
449
|
+
traps: []
|
|
450
|
+
},
|
|
451
|
+
kinghost: {
|
|
452
|
+
name: "KingHost",
|
|
453
|
+
// Documented: dns1-6.kinghost.com.br.
|
|
454
|
+
ns: [/^dns[1-6]\.kinghost\.com\.br$/],
|
|
455
|
+
link: null,
|
|
456
|
+
open: "Gerenciar DNS",
|
|
457
|
+
add: "+ ADICIONAR DNS",
|
|
458
|
+
save: "ADICIONAR DNS",
|
|
459
|
+
// The form is a screenshot, so the case of "host" and "destino" is unconfirmed.
|
|
460
|
+
cname: NO_FIELDS,
|
|
461
|
+
txt: NO_FIELDS,
|
|
462
|
+
root: "@",
|
|
463
|
+
traps: [{ kind: "secondSave", button: "SALVAR ALTERA\xC7\xD5ES" }]
|
|
464
|
+
},
|
|
465
|
+
"uol-host": {
|
|
466
|
+
name: "UOL Host",
|
|
467
|
+
// Documented: ns1-3.dominios.uol.com.br.
|
|
468
|
+
ns: [/^ns[1-3]\.dominios\.uol\.com\.br$/],
|
|
469
|
+
link: null,
|
|
470
|
+
open: "Administrar DNS",
|
|
471
|
+
add: "Criar nova entrada",
|
|
472
|
+
save: "Salvar entrada",
|
|
473
|
+
cname: { name: "Entrada", value: "Destino" },
|
|
474
|
+
txt: { name: "Entrada", value: "Valor" },
|
|
475
|
+
root: "",
|
|
476
|
+
traps: []
|
|
477
|
+
},
|
|
478
|
+
umbler: {
|
|
479
|
+
name: "Umbler",
|
|
480
|
+
// Documented: ns<NNN>.umbler.{com,in,com.br,co.uk}.
|
|
481
|
+
ns: [/^ns\d{3}\.umbler\.(com|in|com\.br|co\.uk)$/],
|
|
482
|
+
link: null,
|
|
483
|
+
open: "Editor de DNS",
|
|
484
|
+
add: "Adicionar",
|
|
485
|
+
save: "Salvar",
|
|
486
|
+
cname: { name: "Nome", value: "Endere\xE7o" },
|
|
487
|
+
txt: { name: "Nome", value: "Dados" },
|
|
488
|
+
root: "@",
|
|
489
|
+
traps: [SHORT]
|
|
490
|
+
},
|
|
491
|
+
dnsimple: {
|
|
492
|
+
name: "DNSimple",
|
|
493
|
+
// Documented: ns1-4.dnsimple-edge.{com,net,io,org}, and the legacy ns1-4.dnsimple.com.
|
|
494
|
+
ns: [/^ns[1-4]\.dnsimple-edge\.(com|net|io|org)$/, /^ns[1-4]\.dnsimple\.com$/],
|
|
495
|
+
link: null,
|
|
496
|
+
open: "Record editor",
|
|
497
|
+
add: "Add record",
|
|
498
|
+
save: "Add Record",
|
|
499
|
+
cname: { name: "Name", value: "Content" },
|
|
500
|
+
txt: { name: "Name", value: "Content" },
|
|
501
|
+
root: "",
|
|
502
|
+
traps: [SHORT]
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
function trapOf(host, kind) {
|
|
506
|
+
return host.traps.find((trap) => trap.kind === kind);
|
|
507
|
+
}
|
|
508
|
+
function detectDnsHosts(nameservers) {
|
|
509
|
+
const names = nameservers.map(normalizeDnsName).filter(Boolean);
|
|
510
|
+
if (!names.length) return [];
|
|
511
|
+
return DNS_HOST_IDS.filter(
|
|
512
|
+
(id) => names.every((name) => DNS_HOSTS[id].ns.some((pattern) => pattern.test(name)))
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
export {
|
|
517
|
+
DNS_HOST_IDS,
|
|
518
|
+
DNS_HOSTS,
|
|
519
|
+
trapOf,
|
|
520
|
+
detectDnsHosts
|
|
521
|
+
};
|