doomain 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 +293 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +5 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +8 -0
- package/dist/commands/auth/logout/vercel.d.ts +9 -0
- package/dist/commands/auth/logout/vercel.js +35 -0
- package/dist/commands/auth/vercel.d.ts +10 -0
- package/dist/commands/auth/vercel.js +69 -0
- package/dist/commands/domains/list.d.ts +10 -0
- package/dist/commands/domains/list.js +36 -0
- package/dist/commands/link.d.ts +21 -0
- package/dist/commands/link.js +63 -0
- package/dist/commands/projects/list.d.ts +9 -0
- package/dist/commands/projects/list.js +26 -0
- package/dist/commands/providers/add.d.ts +1 -0
- package/dist/commands/providers/add.js +1 -0
- package/dist/commands/providers/connect.d.ts +15 -0
- package/dist/commands/providers/connect.js +183 -0
- package/dist/commands/providers/disconnect.d.ts +13 -0
- package/dist/commands/providers/disconnect.js +52 -0
- package/dist/commands/providers/list.d.ts +8 -0
- package/dist/commands/providers/list.js +25 -0
- package/dist/commands/providers/status.d.ts +9 -0
- package/dist/commands/providers/status.js +31 -0
- package/dist/commands/providers/verify.d.ts +11 -0
- package/dist/commands/providers/verify.js +27 -0
- package/dist/commands/schema.d.ts +11 -0
- package/dist/commands/schema.js +29 -0
- package/dist/commands/verify.d.ts +12 -0
- package/dist/commands/verify.js +36 -0
- package/dist/commands/wizard.d.ts +9 -0
- package/dist/commands/wizard.js +322 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/command-schema.d.ts +14 -0
- package/dist/lib/command-schema.js +99 -0
- package/dist/lib/config.d.ts +34 -0
- package/dist/lib/config.js +49 -0
- package/dist/lib/errors.d.ts +7 -0
- package/dist/lib/errors.js +17 -0
- package/dist/lib/flags.d.ts +6 -0
- package/dist/lib/flags.js +10 -0
- package/dist/lib/link-domain.d.ts +47 -0
- package/dist/lib/link-domain.js +336 -0
- package/dist/lib/local-vercel.d.ts +6 -0
- package/dist/lib/local-vercel.js +27 -0
- package/dist/lib/output.d.ts +36 -0
- package/dist/lib/output.js +51 -0
- package/dist/lib/providers/cloudflare/index.d.ts +26 -0
- package/dist/lib/providers/cloudflare/index.js +219 -0
- package/dist/lib/providers/core/config.d.ts +5 -0
- package/dist/lib/providers/core/config.js +33 -0
- package/dist/lib/providers/core/errors.d.ts +6 -0
- package/dist/lib/providers/core/errors.js +18 -0
- package/dist/lib/providers/core/http.d.ts +17 -0
- package/dist/lib/providers/core/http.js +43 -0
- package/dist/lib/providers/core/pagination.d.ts +12 -0
- package/dist/lib/providers/core/pagination.js +15 -0
- package/dist/lib/providers/core/planner.d.ts +19 -0
- package/dist/lib/providers/core/planner.js +79 -0
- package/dist/lib/providers/core/types.d.ts +123 -0
- package/dist/lib/providers/core/types.js +1 -0
- package/dist/lib/providers/namecheap/index.d.ts +28 -0
- package/dist/lib/providers/namecheap/index.js +289 -0
- package/dist/lib/providers/registry.d.ts +4 -0
- package/dist/lib/providers/registry.js +21 -0
- package/dist/lib/providers/spaceship/index.d.ts +22 -0
- package/dist/lib/providers/spaceship/index.js +148 -0
- package/dist/lib/providers/status.d.ts +16 -0
- package/dist/lib/providers/status.js +33 -0
- package/dist/lib/providers/types.d.ts +1 -0
- package/dist/lib/providers/types.js +1 -0
- package/dist/lib/validate.d.ts +15 -0
- package/dist/lib/validate.js +62 -0
- package/dist/lib/vercel.d.ts +32 -0
- package/dist/lib/vercel.js +131 -0
- package/oclif.manifest.json +633 -0
- package/package.json +86 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Crafter Station LLC
|
|
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
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# Doomain
|
|
2
|
+
|
|
3
|
+
Doomain links a Vercel project to a domain from the terminal.
|
|
4
|
+
|
|
5
|
+
It handles the repetitive parts of shipping a custom domain: finding the right Vercel project, choosing a DNS zone, adding the domain to Vercel, creating the required DNS records, waiting for DNS propagation, and asking Vercel to verify the domain.
|
|
6
|
+
|
|
7
|
+
Use the interactive wizard when working by hand. Use `--json` when calling Doomain from scripts, CI, or agents.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- Interactive domain-linking wizard powered by Clack.
|
|
12
|
+
- Non-interactive JSON output for automation and agent workflows.
|
|
13
|
+
- Vercel project detection from `.vercel/project.json`.
|
|
14
|
+
- DNS provider inference by longest matching zone.
|
|
15
|
+
- Dry runs that preview Vercel and DNS changes before writing.
|
|
16
|
+
- DNS propagation and Vercel verification waiting.
|
|
17
|
+
- Built-in Spaceship, Namecheap, and Cloudflare providers.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install -g doomain
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Doomain requires Node.js 18 or newer.
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
Run the wizard:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
doomain
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The wizard prompts for missing credentials, lets you select a Vercel account/team from the token, detects the current Vercel project when possible, lets you connect a DNS provider, lists available domains, previews the changes, and then applies them.
|
|
36
|
+
|
|
37
|
+
For automation, pass everything explicitly and add `--json`:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
doomain link app.example.com --project my-app --json
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## How It Works
|
|
44
|
+
|
|
45
|
+
1. Resolve the Vercel account/team, then resolve the project from `--project`, `.vercel/project.json`, or the interactive project selector.
|
|
46
|
+
2. Resolve the target domain from an argument, `--domain`, `--subdomain`, `--apex`, config, or environment variables.
|
|
47
|
+
3. Choose the DNS provider from `--provider` or infer it from configured DNS zones.
|
|
48
|
+
4. Add the domain to the Vercel project.
|
|
49
|
+
5. Read Vercel's recommended target and verification records.
|
|
50
|
+
6. Plan DNS changes against the current provider records.
|
|
51
|
+
7. Apply DNS changes unless `--dry-run` is set.
|
|
52
|
+
8. Wait for public DNS propagation and Vercel verification unless `--no-wait` is set.
|
|
53
|
+
|
|
54
|
+
## Linking Domains
|
|
55
|
+
|
|
56
|
+
Link a full domain:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
doomain link app.example.com --project my-app
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Link a subdomain from a base zone:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
doomain link --domain example.com --subdomain app --project my-app
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Link the apex/root domain:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
doomain link --domain example.com --apex --project my-app
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Preview changes without writing to Vercel or DNS:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
doomain link --provider spaceship --domain example.com --apex --project my-app --dry-run --json
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Skip the verification wait:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
doomain link app.example.com --project my-app --no-wait
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Overwrite conflicting DNS records:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
doomain link app.example.com --project my-app --force
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## JSON And Agent Usage
|
|
93
|
+
|
|
94
|
+
`--json` prints a single JSON object to stdout and never prompts. Use it for scripts, CI, or agent tools.
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
doomain link app.example.com --project my-app --json
|
|
98
|
+
doomain link --domain app.example.com --project my-app --json
|
|
99
|
+
doomain link --domain example.com --subdomain app --project my-app --json
|
|
100
|
+
doomain projects list --json
|
|
101
|
+
doomain providers list --json
|
|
102
|
+
doomain providers status --no-verify --json
|
|
103
|
+
doomain schema --json
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
When `--provider` is omitted, Doomain searches configured DNS providers and selects the longest matching DNS zone for the target domain. If more than one provider has the same best match, Doomain asks you to pass `--provider` explicitly.
|
|
107
|
+
|
|
108
|
+
Use the schema command to inspect machine-readable command metadata:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
doomain schema
|
|
112
|
+
doomain schema link --json
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Credentials
|
|
116
|
+
|
|
117
|
+
Doomain stores local configuration at:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
~/.doomain/config.json
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
The config file is written with `0600` permissions. Environment variables override values in the local config.
|
|
124
|
+
|
|
125
|
+
### Vercel
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
doomain auth vercel --token vercel_token
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Interactive mode fetches Vercel teams from the token and lets you choose a team or your personal account. Use `--team-id` or `VERCEL_TEAM_ID` for non-interactive team-scoped usage.
|
|
132
|
+
|
|
133
|
+
### Spaceship
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
doomain providers connect spaceship \
|
|
137
|
+
--credential apiKey=spaceship_key \
|
|
138
|
+
--credential apiSecret=spaceship_secret
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Compatibility aliases are also available:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
doomain providers connect spaceship --api-key spaceship_key --api-secret spaceship_secret
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Spaceship API keys need domain read access and DNS record read/write access.
|
|
148
|
+
|
|
149
|
+
### Namecheap
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
doomain providers connect namecheap \
|
|
153
|
+
--credential apiUser=your_namecheap_user \
|
|
154
|
+
--credential apiKey=your_api_key \
|
|
155
|
+
--credential clientIp=your_whitelisted_ipv4
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
For Namecheap sandbox testing:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
doomain providers connect namecheap \
|
|
162
|
+
--credential apiUser=your_sandbox_user \
|
|
163
|
+
--credential apiKey=your_sandbox_key \
|
|
164
|
+
--credential clientIp=your_whitelisted_ipv4 \
|
|
165
|
+
--credential sandbox=true
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Namecheap API access must be enabled, and `clientIp` must be whitelisted in Namecheap API Access settings.
|
|
169
|
+
|
|
170
|
+
### Cloudflare
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
doomain providers connect cloudflare \
|
|
174
|
+
--credential apiToken=your_cloudflare_api_token \
|
|
175
|
+
--credential accountId=your_cloudflare_account_id
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Cloudflare API tokens need `Zone:Read` and `DNS:Edit` permissions for the account. Doomain creates Vercel records as DNS-only records, not proxied records.
|
|
179
|
+
|
|
180
|
+
## Provider Management
|
|
181
|
+
|
|
182
|
+
List supported providers:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
doomain providers list
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Interactively add a provider:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
doomain providers add
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Check configured provider health:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
doomain providers status
|
|
198
|
+
doomain providers status --no-verify --json
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Disconnect a saved DNS provider:
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
doomain providers disconnect namecheap
|
|
205
|
+
doomain providers disconnect cloudflare --json
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Remove saved Vercel credentials:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
doomain auth logout vercel
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Logout commands remove credentials from `~/.doomain/config.json`. If matching environment variables are still set, they continue to override local config.
|
|
215
|
+
|
|
216
|
+
Verify one provider's saved credentials:
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
doomain providers verify spaceship
|
|
220
|
+
doomain providers verify namecheap --json
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
List DNS zones and records:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
doomain domains list
|
|
227
|
+
doomain domains list --provider cloudflare --domain example.com --json
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Environment Variables
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
VERCEL_TOKEN
|
|
234
|
+
VERCEL_TEAM_ID
|
|
235
|
+
SPACESHIP_API_KEY
|
|
236
|
+
SPACESHIP_API_SECRET
|
|
237
|
+
NAMECHEAP_API_USER
|
|
238
|
+
NAMECHEAP_API_KEY
|
|
239
|
+
NAMECHEAP_USERNAME
|
|
240
|
+
NAMECHEAP_CLIENT_IP
|
|
241
|
+
NAMECHEAP_SANDBOX
|
|
242
|
+
CLOUDFLARE_API_TOKEN
|
|
243
|
+
CLOUDFLARE_ACCOUNT_ID
|
|
244
|
+
DOOMAIN_DOMAIN
|
|
245
|
+
DOOMAIN_PROVIDER
|
|
246
|
+
DOOMAIN_CONFIG_FILE
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Commands
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
doomain # interactive wizard
|
|
253
|
+
doomain link # link a Vercel project and domain
|
|
254
|
+
doomain auth vercel # save Vercel credentials
|
|
255
|
+
doomain auth logout vercel # remove saved Vercel credentials
|
|
256
|
+
doomain providers list # list supported DNS providers
|
|
257
|
+
doomain providers add # interactively add a DNS provider
|
|
258
|
+
doomain providers connect # save provider credentials
|
|
259
|
+
doomain providers disconnect # remove saved provider credentials
|
|
260
|
+
doomain providers status # show configured provider health
|
|
261
|
+
doomain providers verify # verify provider credentials
|
|
262
|
+
doomain domains list # list DNS zones and records
|
|
263
|
+
doomain projects list # list Vercel projects
|
|
264
|
+
doomain verify # ask Vercel to verify a domain
|
|
265
|
+
doomain schema # print command schemas for agents
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Run `doomain help <command>` for command-specific flags and examples.
|
|
269
|
+
|
|
270
|
+
## Provider Notes
|
|
271
|
+
|
|
272
|
+
Spaceship, Namecheap, and Cloudflare are implemented through a shared DNS provider contract. Each provider declares its credentials and capabilities, then implements zone listing, record listing, change planning, and change application behind the same interface.
|
|
273
|
+
|
|
274
|
+
Namecheap writes DNS through `setHosts`, which replaces the full host list. Doomain reads all existing records first, applies the planned change in memory, preserves unrelated records, then submits the complete final record set.
|
|
275
|
+
|
|
276
|
+
Cloudflare supports proxied records generally, but Doomain writes Vercel `A`, `AAAA`, and `CNAME` records with `proxied: false` so Vercel can validate them correctly.
|
|
277
|
+
|
|
278
|
+
## Development
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
bun install
|
|
282
|
+
bun run build
|
|
283
|
+
bun run test
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Useful scripts:
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
bun run lint
|
|
290
|
+
bun run prepack
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
The package is an oclif CLI. Source lives in `src/commands` and `src/lib`; compiled output is written to `dist`.
|
package/bin/dev.cmd
ADDED
package/bin/dev.js
ADDED
package/bin/run.cmd
ADDED
package/bin/run.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {execute} from '@oclif/core'
|
|
4
|
+
|
|
5
|
+
const args = process.argv.slice(2)
|
|
6
|
+
const routedArgs = args.length === 0 || (args.length === 1 && args[0] === '--json') ? ['wizard', ...args] : args
|
|
7
|
+
|
|
8
|
+
await execute({args: routedArgs, dir: import.meta.url})
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class AuthLogoutVercel extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
};
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import { getConfigPath, updateConfig } from '../../../lib/config.js';
|
|
3
|
+
import { jsonFlag } from '../../../lib/flags.js';
|
|
4
|
+
import { createOutput, outputError } from '../../../lib/output.js';
|
|
5
|
+
function envOverrides() {
|
|
6
|
+
return ['VERCEL_TOKEN', 'VERCEL_TEAM_ID'].filter((key) => process.env[key]);
|
|
7
|
+
}
|
|
8
|
+
export default class AuthLogoutVercel extends Command {
|
|
9
|
+
static description = 'Remove saved Vercel credentials locally.';
|
|
10
|
+
static examples = ['<%= config.bin %> <%= command.id %>', '<%= config.bin %> <%= command.id %> --json'];
|
|
11
|
+
static flags = {
|
|
12
|
+
json: jsonFlag,
|
|
13
|
+
};
|
|
14
|
+
async run() {
|
|
15
|
+
const { flags } = await this.parse(AuthLogoutVercel);
|
|
16
|
+
const out = createOutput({ json: flags.json });
|
|
17
|
+
try {
|
|
18
|
+
let removed = false;
|
|
19
|
+
await updateConfig((config) => {
|
|
20
|
+
removed = config.vercel !== undefined;
|
|
21
|
+
const { vercel: _vercel, ...next } = config;
|
|
22
|
+
return next;
|
|
23
|
+
});
|
|
24
|
+
const overrides = envOverrides();
|
|
25
|
+
out.result({ configPath: getConfigPath(), environmentOverrides: overrides, removed, service: 'vercel' });
|
|
26
|
+
if (overrides.length > 0)
|
|
27
|
+
out.warn(`Vercel environment credentials are still set: ${overrides.join(', ')}.`);
|
|
28
|
+
out.success(removed ? `Vercel credentials removed from ${getConfigPath()}.` : 'Vercel credentials were not saved.');
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
outputError(out.json, error, 'MISSING_CREDENTIALS');
|
|
32
|
+
this.exit(1);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class AuthVercel extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static flags: {
|
|
5
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
6
|
+
'team-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
};
|
|
9
|
+
run(): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import * as p from '@clack/prompts';
|
|
3
|
+
import { getConfigPath, maskSecret, updateConfig } from '../../lib/config.js';
|
|
4
|
+
import { jsonFlag } from '../../lib/flags.js';
|
|
5
|
+
import { createOutput, outputError } from '../../lib/output.js';
|
|
6
|
+
import { createVercelClient } from '../../lib/vercel.js';
|
|
7
|
+
const PERSONAL_ACCOUNT = '__personal__';
|
|
8
|
+
function assertValue(value, message) {
|
|
9
|
+
if (typeof value === 'string' && value.trim())
|
|
10
|
+
return value.trim();
|
|
11
|
+
throw new Error(message);
|
|
12
|
+
}
|
|
13
|
+
function teamLabel(team) {
|
|
14
|
+
const name = team.name ?? team.slug;
|
|
15
|
+
return `${name} (${team.id})`;
|
|
16
|
+
}
|
|
17
|
+
export default class AuthVercel extends Command {
|
|
18
|
+
static description = 'Save Vercel credentials locally.';
|
|
19
|
+
static flags = {
|
|
20
|
+
json: jsonFlag,
|
|
21
|
+
'team-id': Flags.string({ description: 'Vercel team id. Interactive mode can fetch and select this from your token.' }),
|
|
22
|
+
token: Flags.string({ description: 'Vercel API token.' }),
|
|
23
|
+
};
|
|
24
|
+
async run() {
|
|
25
|
+
const { flags } = await this.parse(AuthVercel);
|
|
26
|
+
const out = createOutput({ json: flags.json });
|
|
27
|
+
try {
|
|
28
|
+
let token = flags.token;
|
|
29
|
+
let teamId = flags['team-id'];
|
|
30
|
+
if (!out.json && !token) {
|
|
31
|
+
const value = await p.password({ message: 'Vercel token' });
|
|
32
|
+
if (p.isCancel(value)) {
|
|
33
|
+
p.cancel('Cancelled');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
token = value;
|
|
37
|
+
}
|
|
38
|
+
token = assertValue(token, 'Missing Vercel token. Pass --token or set VERCEL_TOKEN.');
|
|
39
|
+
if (!out.json && teamId === undefined) {
|
|
40
|
+
const spinner = p.spinner();
|
|
41
|
+
spinner.start('Loading Vercel teams');
|
|
42
|
+
const teams = await createVercelClient({ token }).listTeams();
|
|
43
|
+
spinner.stop(`Loaded ${teams.length} Vercel team${teams.length === 1 ? '' : 's'}`);
|
|
44
|
+
const selected = await p.select({
|
|
45
|
+
message: 'Select Vercel account/team',
|
|
46
|
+
options: [
|
|
47
|
+
{ label: 'Personal account', value: PERSONAL_ACCOUNT, hint: 'No team id' },
|
|
48
|
+
...teams.map((team) => ({ label: teamLabel(team), value: team.id, hint: team.role ?? team.slug })),
|
|
49
|
+
],
|
|
50
|
+
});
|
|
51
|
+
if (p.isCancel(selected)) {
|
|
52
|
+
p.cancel('Cancelled');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
teamId = selected === PERSONAL_ACCOUNT ? undefined : selected;
|
|
56
|
+
}
|
|
57
|
+
await updateConfig((config) => ({
|
|
58
|
+
...config,
|
|
59
|
+
vercel: { token, teamId },
|
|
60
|
+
}));
|
|
61
|
+
out.result({ configPath: getConfigPath(), vercel: { token: maskSecret(token), teamId } });
|
|
62
|
+
out.success(`Vercel credentials saved to ${getConfigPath()}.`);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
outputError(out.json, error, 'MISSING_CREDENTIALS');
|
|
66
|
+
this.exit(1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class DomainsList extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static flags: {
|
|
5
|
+
domain: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
6
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
provider: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
};
|
|
9
|
+
run(): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import { loadConfig } from '../../lib/config.js';
|
|
3
|
+
import { domainFlag, jsonFlag, providerFlag } from '../../lib/flags.js';
|
|
4
|
+
import { createOutput, outputError } from '../../lib/output.js';
|
|
5
|
+
import { createProvider } from '../../lib/providers/registry.js';
|
|
6
|
+
import { normalizeDomain } from '../../lib/validate.js';
|
|
7
|
+
export default class DomainsList extends Command {
|
|
8
|
+
static description = 'List DNS zones and records for a provider.';
|
|
9
|
+
static flags = {
|
|
10
|
+
domain: domainFlag,
|
|
11
|
+
json: jsonFlag,
|
|
12
|
+
provider: providerFlag,
|
|
13
|
+
};
|
|
14
|
+
async run() {
|
|
15
|
+
const { flags } = await this.parse(DomainsList);
|
|
16
|
+
const out = createOutput({ json: flags.json });
|
|
17
|
+
try {
|
|
18
|
+
const config = await loadConfig();
|
|
19
|
+
const provider = await createProvider(flags.provider ?? process.env.DOOMAIN_PROVIDER ?? config.defaults?.provider ?? 'spaceship');
|
|
20
|
+
const zones = flags.domain ? [{ id: normalizeDomain(flags.domain), name: normalizeDomain(flags.domain) }] : await provider.listZones();
|
|
21
|
+
const results = [];
|
|
22
|
+
for (const zone of zones) {
|
|
23
|
+
const records = await provider.listRecords(zone);
|
|
24
|
+
results.push({ zone, records });
|
|
25
|
+
out.info(`${zone.name} (${records.length} records)`);
|
|
26
|
+
for (const record of records)
|
|
27
|
+
out.info(` ${record.type} ${record.name} -> ${record.value}`);
|
|
28
|
+
}
|
|
29
|
+
out.result({ provider: provider.id, zones: results });
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
outputError(out.json, error, 'DOMAIN_LINK_FAILED');
|
|
33
|
+
this.exit(1);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Link extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
domain: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
apex: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
domain: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
provider: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
subdomain: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
|
+
timeout: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
18
|
+
wait: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
19
|
+
};
|
|
20
|
+
run(): Promise<void>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { apexFlag, domainFlag, jsonFlag, projectFlag, providerFlag, subdomainFlag } from '../lib/flags.js';
|
|
3
|
+
import { linkDomain } from '../lib/link-domain.js';
|
|
4
|
+
import { createOutput, outputError } from '../lib/output.js';
|
|
5
|
+
export default class Link extends Command {
|
|
6
|
+
static description = 'Link a Vercel project to a domain and create DNS records.';
|
|
7
|
+
static examples = [
|
|
8
|
+
'<%= config.bin %> <%= command.id %> app.example.com --project my-app --json',
|
|
9
|
+
'<%= config.bin %> <%= command.id %> --domain app.example.com --project my-app --json',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> --domain example.com --subdomain app --project my-app',
|
|
11
|
+
'<%= config.bin %> <%= command.id %> --provider spaceship --domain example.com --apex --project my-app --json',
|
|
12
|
+
];
|
|
13
|
+
static args = {
|
|
14
|
+
domain: Args.string({ description: 'Target domain to link, for example app.example.com.', required: false }),
|
|
15
|
+
};
|
|
16
|
+
static flags = {
|
|
17
|
+
apex: apexFlag,
|
|
18
|
+
domain: domainFlag,
|
|
19
|
+
'dry-run': Flags.boolean({ description: 'Preview changes without writing to Vercel or DNS.' }),
|
|
20
|
+
force: Flags.boolean({ description: 'Overwrite conflicting DNS records.' }),
|
|
21
|
+
json: jsonFlag,
|
|
22
|
+
project: projectFlag,
|
|
23
|
+
provider: providerFlag,
|
|
24
|
+
subdomain: subdomainFlag,
|
|
25
|
+
timeout: Flags.integer({ default: 300, description: 'Wait timeout in seconds.' }),
|
|
26
|
+
wait: Flags.boolean({ allowNo: true, default: true, description: 'Wait for DNS propagation and Vercel verification.' }),
|
|
27
|
+
};
|
|
28
|
+
async run() {
|
|
29
|
+
const { args, flags } = await this.parse(Link);
|
|
30
|
+
const out = createOutput({ json: flags.json });
|
|
31
|
+
let spinner;
|
|
32
|
+
const domain = flags.domain ?? args.domain;
|
|
33
|
+
try {
|
|
34
|
+
if (flags['dry-run']) {
|
|
35
|
+
const result = await linkDomain({ ...flags, domain, dryRun: true, timeoutSeconds: flags.timeout });
|
|
36
|
+
out.result(result);
|
|
37
|
+
if (!out.json)
|
|
38
|
+
out.success(`Dry run ready for ${result.domain}.`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
out.intro('Doomain');
|
|
42
|
+
spinner = out.spinner();
|
|
43
|
+
spinner.start('Linking Vercel project and domain');
|
|
44
|
+
const result = await linkDomain({
|
|
45
|
+
...flags,
|
|
46
|
+
domain,
|
|
47
|
+
dryRun: false,
|
|
48
|
+
progress: out.json ? undefined : ({ message }) => spinner?.message(message),
|
|
49
|
+
timeoutSeconds: flags.timeout,
|
|
50
|
+
});
|
|
51
|
+
spinner.stop(result.vercel.verified ? 'Domain linked and verified' : 'Domain linked');
|
|
52
|
+
out.result(result);
|
|
53
|
+
out.outro(result.vercel.verified
|
|
54
|
+
? `${result.domain} is linked to ${result.project}. SSL certificate provisioning may take a few minutes in Vercel.`
|
|
55
|
+
: `${result.domain} is linked to ${result.project}.`);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
spinner?.error('Domain link failed');
|
|
59
|
+
outputError(out.json, error, 'DOMAIN_LINK_FAILED');
|
|
60
|
+
this.exit(1);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class ProjectsList extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static flags: {
|
|
5
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
6
|
+
search: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
};
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { jsonFlag } from '../../lib/flags.js';
|
|
3
|
+
import { createOutput, outputError } from '../../lib/output.js';
|
|
4
|
+
import { createVercelClient, resolveVercelConfig } from '../../lib/vercel.js';
|
|
5
|
+
export default class ProjectsList extends Command {
|
|
6
|
+
static description = 'List Vercel projects.';
|
|
7
|
+
static flags = {
|
|
8
|
+
json: jsonFlag,
|
|
9
|
+
search: Flags.string({ description: 'Filter projects by search term.' }),
|
|
10
|
+
};
|
|
11
|
+
async run() {
|
|
12
|
+
const { flags } = await this.parse(ProjectsList);
|
|
13
|
+
const out = createOutput({ json: flags.json });
|
|
14
|
+
try {
|
|
15
|
+
const vercel = createVercelClient(await resolveVercelConfig());
|
|
16
|
+
const projects = await vercel.listProjects(flags.search);
|
|
17
|
+
for (const project of projects)
|
|
18
|
+
out.info(`${project.name} (${project.id})`);
|
|
19
|
+
out.result({ projects });
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
outputError(out.json, error, 'PROJECT_NOT_FOUND');
|
|
23
|
+
this.exit(1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './connect.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './connect.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class ProvidersConnect extends Command {
|
|
3
|
+
static args: {
|
|
4
|
+
provider: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static flags: {
|
|
8
|
+
'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
'api-secret': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
credential: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
'no-verify': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
};
|
|
14
|
+
run(): Promise<void>;
|
|
15
|
+
}
|