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.
Files changed (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +293 -0
  3. package/bin/dev.cmd +3 -0
  4. package/bin/dev.js +5 -0
  5. package/bin/run.cmd +3 -0
  6. package/bin/run.js +8 -0
  7. package/dist/commands/auth/logout/vercel.d.ts +9 -0
  8. package/dist/commands/auth/logout/vercel.js +35 -0
  9. package/dist/commands/auth/vercel.d.ts +10 -0
  10. package/dist/commands/auth/vercel.js +69 -0
  11. package/dist/commands/domains/list.d.ts +10 -0
  12. package/dist/commands/domains/list.js +36 -0
  13. package/dist/commands/link.d.ts +21 -0
  14. package/dist/commands/link.js +63 -0
  15. package/dist/commands/projects/list.d.ts +9 -0
  16. package/dist/commands/projects/list.js +26 -0
  17. package/dist/commands/providers/add.d.ts +1 -0
  18. package/dist/commands/providers/add.js +1 -0
  19. package/dist/commands/providers/connect.d.ts +15 -0
  20. package/dist/commands/providers/connect.js +183 -0
  21. package/dist/commands/providers/disconnect.d.ts +13 -0
  22. package/dist/commands/providers/disconnect.js +52 -0
  23. package/dist/commands/providers/list.d.ts +8 -0
  24. package/dist/commands/providers/list.js +25 -0
  25. package/dist/commands/providers/status.d.ts +9 -0
  26. package/dist/commands/providers/status.js +31 -0
  27. package/dist/commands/providers/verify.d.ts +11 -0
  28. package/dist/commands/providers/verify.js +27 -0
  29. package/dist/commands/schema.d.ts +11 -0
  30. package/dist/commands/schema.js +29 -0
  31. package/dist/commands/verify.d.ts +12 -0
  32. package/dist/commands/verify.js +36 -0
  33. package/dist/commands/wizard.d.ts +9 -0
  34. package/dist/commands/wizard.js +322 -0
  35. package/dist/index.d.ts +1 -0
  36. package/dist/index.js +1 -0
  37. package/dist/lib/command-schema.d.ts +14 -0
  38. package/dist/lib/command-schema.js +99 -0
  39. package/dist/lib/config.d.ts +34 -0
  40. package/dist/lib/config.js +49 -0
  41. package/dist/lib/errors.d.ts +7 -0
  42. package/dist/lib/errors.js +17 -0
  43. package/dist/lib/flags.d.ts +6 -0
  44. package/dist/lib/flags.js +10 -0
  45. package/dist/lib/link-domain.d.ts +47 -0
  46. package/dist/lib/link-domain.js +336 -0
  47. package/dist/lib/local-vercel.d.ts +6 -0
  48. package/dist/lib/local-vercel.js +27 -0
  49. package/dist/lib/output.d.ts +36 -0
  50. package/dist/lib/output.js +51 -0
  51. package/dist/lib/providers/cloudflare/index.d.ts +26 -0
  52. package/dist/lib/providers/cloudflare/index.js +219 -0
  53. package/dist/lib/providers/core/config.d.ts +5 -0
  54. package/dist/lib/providers/core/config.js +33 -0
  55. package/dist/lib/providers/core/errors.d.ts +6 -0
  56. package/dist/lib/providers/core/errors.js +18 -0
  57. package/dist/lib/providers/core/http.d.ts +17 -0
  58. package/dist/lib/providers/core/http.js +43 -0
  59. package/dist/lib/providers/core/pagination.d.ts +12 -0
  60. package/dist/lib/providers/core/pagination.js +15 -0
  61. package/dist/lib/providers/core/planner.d.ts +19 -0
  62. package/dist/lib/providers/core/planner.js +79 -0
  63. package/dist/lib/providers/core/types.d.ts +123 -0
  64. package/dist/lib/providers/core/types.js +1 -0
  65. package/dist/lib/providers/namecheap/index.d.ts +28 -0
  66. package/dist/lib/providers/namecheap/index.js +289 -0
  67. package/dist/lib/providers/registry.d.ts +4 -0
  68. package/dist/lib/providers/registry.js +21 -0
  69. package/dist/lib/providers/spaceship/index.d.ts +22 -0
  70. package/dist/lib/providers/spaceship/index.js +148 -0
  71. package/dist/lib/providers/status.d.ts +16 -0
  72. package/dist/lib/providers/status.js +33 -0
  73. package/dist/lib/providers/types.d.ts +1 -0
  74. package/dist/lib/providers/types.js +1 -0
  75. package/dist/lib/validate.d.ts +15 -0
  76. package/dist/lib/validate.js +62 -0
  77. package/dist/lib/vercel.d.ts +32 -0
  78. package/dist/lib/vercel.js +131 -0
  79. package/oclif.manifest.json +633 -0
  80. 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
@@ -0,0 +1,3 @@
1
+ @echo off
2
+
3
+ node --loader ts-node/esm --no-warnings=ExperimentalWarning "%~dp0\dev" %*
package/bin/dev.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env -S node --loader ts-node/esm --disable-warning=ExperimentalWarning
2
+
3
+ import {execute} from '@oclif/core'
4
+
5
+ await execute({development: true, dir: import.meta.url})
package/bin/run.cmd ADDED
@@ -0,0 +1,3 @@
1
+ @echo off
2
+
3
+ node "%~dp0\run" %*
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
+ }