doomain 0.1.14 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -2
- package/dist/commands/auth/clerk.d.ts +11 -0
- package/dist/commands/auth/clerk.js +55 -0
- package/dist/commands/auth/logout/clerk.d.ts +8 -0
- package/dist/commands/auth/logout/clerk.js +29 -0
- package/dist/commands/clerk/domains/add.d.ts +19 -0
- package/dist/commands/clerk/domains/add.js +92 -0
- package/dist/commands/link.js +15 -5
- package/dist/lib/clerk-domain.d.ts +39 -0
- package/dist/lib/clerk-domain.js +162 -0
- package/dist/lib/clerk.d.ts +56 -0
- package/dist/lib/clerk.js +65 -0
- package/dist/lib/command-schema.js +113 -5
- package/dist/lib/config.d.ts +5 -0
- package/dist/lib/errors.d.ts +1 -1
- package/dist/lib/link-domain.d.ts +15 -0
- package/dist/lib/link-domain.js +4 -3
- package/oclif.manifest.json +174 -13
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Doomain
|
|
2
2
|
|
|
3
|
-
Doomain links
|
|
3
|
+
Doomain links Vercel projects and first-time Clerk production instances to custom domains from your terminal.
|
|
4
4
|
|
|
5
5
|
It handles the boring parts of custom-domain setup: selecting the Vercel project, finding the right DNS zone, adding the domain to Vercel, writing the Vercel DNS records, waiting for public DNS propagation, and asking Vercel to verify the domain.
|
|
6
6
|
|
|
@@ -16,6 +16,7 @@ Use the interactive wizard when working by hand. Use explicit commands with `--j
|
|
|
16
16
|
- Safety checks before replacing DNS records that point elsewhere.
|
|
17
17
|
- DNS propagation and Vercel verification wait loop.
|
|
18
18
|
- DNS provider support for Spaceship, Namecheap, Cloudflare, and Hostinger.
|
|
19
|
+
- First-time Clerk production setup with automatic CNAME configuration.
|
|
19
20
|
|
|
20
21
|
## Install
|
|
21
22
|
|
|
@@ -86,6 +87,17 @@ Apply it:
|
|
|
86
87
|
doomain link app.example.com --project my-vercel-project --json
|
|
87
88
|
```
|
|
88
89
|
|
|
90
|
+
Set up a Clerk application's first production instance and primary domain:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
doomain auth clerk --platform-api-key "$CLERK_PLATFORM_API_KEY" --app app_123 --json
|
|
94
|
+
doomain clerk domains add example.com --app app_123 --json
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Create a Platform API key from the Clerk Dashboard API keys page. Platform keys start with `ak_`; Clerk instance secret keys (`sk_`) cannot create a production instance.
|
|
98
|
+
|
|
99
|
+
This Clerk command intentionally aborts when the application already has a production instance. Change existing production domains manually in the Clerk Dashboard or with Clerk CLI.
|
|
100
|
+
|
|
89
101
|
If JSON mode returns `DNS_TARGET_CONFLICT`, the current DNS target appears to point to another project or site. Re-run with `--force` only when you intend to replace that DNS target.
|
|
90
102
|
|
|
91
103
|
## Provider Setup
|
|
@@ -142,7 +154,7 @@ doomain providers connect cloudflare \
|
|
|
142
154
|
--credential accountId=your_cloudflare_account_id
|
|
143
155
|
```
|
|
144
156
|
|
|
145
|
-
Cloudflare records created for Vercel `A`, `AAAA`, and `CNAME` targets are set to `proxied: false` so
|
|
157
|
+
Cloudflare records created for Vercel or Clerk `A`, `AAAA`, and `CNAME` targets are set to `proxied: false` so the service can validate the domain.
|
|
146
158
|
|
|
147
159
|
### Hostinger
|
|
148
160
|
|
|
@@ -209,6 +221,27 @@ Use `-p` as shorthand for `--project`:
|
|
|
209
221
|
doomain link app.example.com -p my-app
|
|
210
222
|
```
|
|
211
223
|
|
|
224
|
+
## Clerk Production Domains
|
|
225
|
+
|
|
226
|
+
`doomain clerk domains add` mirrors Clerk CLI's initial production deployment API. It creates a production instance by cloning the application's development instance, sets the requested primary domain, writes every CNAME returned by Clerk, and optionally waits for Clerk's DNS, SSL, and email DNS status.
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
doomain clerk domains add example.com --app app_123
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Use `--dry-run` to verify that the application has no production instance and that Doomain can resolve the DNS zone. Clerk only returns the exact CNAME records after production is created, so they are not included in the dry-run result.
|
|
233
|
+
|
|
234
|
+
If a production instance already exists, the command returns `CLERK_PRODUCTION_EXISTS` without changing DNS. Doomain does not automate production-domain migrations because Clerk domain changes can cause downtime and require publishable-key, OAuth redirect, and deployment updates.
|
|
235
|
+
|
|
236
|
+
After creation, the result includes Clerk CLI follow-up commands. Run them to link the local project, pull production keys, finish production OAuth configuration, and verify provisioning:
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
clerk link --app app_123
|
|
240
|
+
clerk env pull --app app_123 --instance prod
|
|
241
|
+
clerk deploy
|
|
242
|
+
clerk deploy status
|
|
243
|
+
```
|
|
244
|
+
|
|
212
245
|
## What Gets Created
|
|
213
246
|
|
|
214
247
|
For apex/root domains, Doomain creates Vercel's apex `A` record:
|
|
@@ -306,6 +339,7 @@ doomain providers list --json
|
|
|
306
339
|
doomain providers status --no-verify --json
|
|
307
340
|
doomain domains list --provider cloudflare --domain example.com --json
|
|
308
341
|
doomain projects list --search my-app --json
|
|
342
|
+
doomain clerk domains add example.com --app app_123 --json
|
|
309
343
|
doomain schema --json
|
|
310
344
|
doomain schema link --json
|
|
311
345
|
```
|
|
@@ -376,6 +410,28 @@ doomain auth logout vercel --json
|
|
|
376
410
|
|
|
377
411
|
If `VERCEL_TOKEN` or `VERCEL_TEAM_ID` are still set, they continue to override local config.
|
|
378
412
|
|
|
413
|
+
### `doomain auth clerk`
|
|
414
|
+
|
|
415
|
+
Saves and verifies a Clerk Platform API key and default application id.
|
|
416
|
+
|
|
417
|
+
```bash
|
|
418
|
+
doomain auth clerk --platform-api-key ak_123 --app app_123
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### `doomain auth logout clerk`
|
|
422
|
+
|
|
423
|
+
Removes saved Clerk credentials. `CLERK_PLATFORM_API_KEY` and `CLERK_APPLICATION_ID` continue to override local config when set.
|
|
424
|
+
|
|
425
|
+
### `doomain clerk domains add <domain>`
|
|
426
|
+
|
|
427
|
+
Creates the application's first Clerk production instance and primary domain, then configures Clerk's returned CNAME records.
|
|
428
|
+
|
|
429
|
+
```bash
|
|
430
|
+
doomain clerk domains add example.com --app app_123
|
|
431
|
+
doomain clerk domains add example.com --app app_123 --dry-run --json
|
|
432
|
+
doomain clerk domains add example.com --app app_123 --no-wait --json
|
|
433
|
+
```
|
|
434
|
+
|
|
379
435
|
### `doomain providers list`
|
|
380
436
|
|
|
381
437
|
Lists supported DNS providers.
|
|
@@ -491,6 +547,13 @@ VERCEL_TOKEN
|
|
|
491
547
|
VERCEL_TEAM_ID
|
|
492
548
|
```
|
|
493
549
|
|
|
550
|
+
Clerk:
|
|
551
|
+
|
|
552
|
+
```bash
|
|
553
|
+
CLERK_PLATFORM_API_KEY
|
|
554
|
+
CLERK_APPLICATION_ID
|
|
555
|
+
```
|
|
556
|
+
|
|
494
557
|
Spaceship:
|
|
495
558
|
|
|
496
559
|
```bash
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class AuthClerk extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
app: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
'platform-api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
};
|
|
10
|
+
run(): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import * as p from '@clack/prompts';
|
|
3
|
+
import { createClerkPlatformClient } from '../../lib/clerk.js';
|
|
4
|
+
import { getConfigPath, maskSecret, updateConfig } from '../../lib/config.js';
|
|
5
|
+
import { jsonFlag } from '../../lib/flags.js';
|
|
6
|
+
import { createOutput, outputError } from '../../lib/output.js';
|
|
7
|
+
function value(input, message) {
|
|
8
|
+
const resolved = input?.trim();
|
|
9
|
+
if (!resolved)
|
|
10
|
+
throw new Error(message);
|
|
11
|
+
return resolved;
|
|
12
|
+
}
|
|
13
|
+
export default class AuthClerk extends Command {
|
|
14
|
+
static description = 'Save Clerk Platform API credentials locally.';
|
|
15
|
+
static examples = ['<%= config.bin %> <%= command.id %> --platform-api-key ak_123 --app app_123 --json'];
|
|
16
|
+
static flags = {
|
|
17
|
+
app: Flags.string({ description: 'Default Clerk application id.' }),
|
|
18
|
+
json: jsonFlag,
|
|
19
|
+
'platform-api-key': Flags.string({ description: 'Clerk Platform API key (ak_...).' }),
|
|
20
|
+
};
|
|
21
|
+
async run() {
|
|
22
|
+
const { flags } = await this.parse(AuthClerk);
|
|
23
|
+
const out = createOutput({ json: flags.json });
|
|
24
|
+
try {
|
|
25
|
+
let platformApiKey = flags['platform-api-key'] ?? process.env.CLERK_PLATFORM_API_KEY;
|
|
26
|
+
let appId = flags.app ?? process.env.CLERK_APPLICATION_ID;
|
|
27
|
+
if (!out.json) {
|
|
28
|
+
if (!platformApiKey) {
|
|
29
|
+
const entered = await p.password({ message: 'Clerk Platform API key' });
|
|
30
|
+
if (p.isCancel(entered))
|
|
31
|
+
return;
|
|
32
|
+
platformApiKey = entered;
|
|
33
|
+
}
|
|
34
|
+
if (!appId) {
|
|
35
|
+
const entered = await p.text({ message: 'Default Clerk application id' });
|
|
36
|
+
if (p.isCancel(entered))
|
|
37
|
+
return;
|
|
38
|
+
appId = entered;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
platformApiKey = value(platformApiKey, 'Missing Clerk Platform API key. Pass --platform-api-key or set CLERK_PLATFORM_API_KEY.');
|
|
42
|
+
appId = value(appId, 'Missing Clerk application id. Pass --app or set CLERK_APPLICATION_ID.');
|
|
43
|
+
if (!platformApiKey.startsWith('ak_'))
|
|
44
|
+
throw new Error('Clerk Platform API keys must start with ak_.');
|
|
45
|
+
await createClerkPlatformClient({ platformApiKey }).fetchApplication(appId);
|
|
46
|
+
await updateConfig((config) => ({ ...config, clerk: { appId, platformApiKey } }));
|
|
47
|
+
out.result({ clerk: { appId, platformApiKey: maskSecret(platformApiKey) }, configPath: getConfigPath() });
|
|
48
|
+
out.success(`Clerk credentials saved to ${getConfigPath()}.`);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
outputError(out.json, error, 'CLERK_AUTH_FAILED');
|
|
52
|
+
this.exit(1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
export default class AuthLogoutClerk extends Command {
|
|
6
|
+
static description = 'Remove saved Clerk credentials locally.';
|
|
7
|
+
static flags = { json: jsonFlag };
|
|
8
|
+
async run() {
|
|
9
|
+
const { flags } = await this.parse(AuthLogoutClerk);
|
|
10
|
+
const out = createOutput({ json: flags.json });
|
|
11
|
+
try {
|
|
12
|
+
let removed = false;
|
|
13
|
+
await updateConfig((config) => {
|
|
14
|
+
removed = config.clerk !== undefined;
|
|
15
|
+
const { clerk: _clerk, ...next } = config;
|
|
16
|
+
return next;
|
|
17
|
+
});
|
|
18
|
+
const environmentOverrides = ['CLERK_PLATFORM_API_KEY', 'CLERK_APPLICATION_ID'].filter((key) => process.env[key]);
|
|
19
|
+
out.result({ configPath: getConfigPath(), environmentOverrides, removed, service: 'clerk' });
|
|
20
|
+
if (environmentOverrides.length > 0)
|
|
21
|
+
out.warn(`Clerk environment credentials are still set: ${environmentOverrides.join(', ')}.`);
|
|
22
|
+
out.success(removed ? `Clerk credentials removed from ${getConfigPath()}.` : 'Clerk credentials were not saved.');
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
outputError(out.json, error, 'MISSING_CREDENTIALS');
|
|
26
|
+
this.exit(1);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class ClerkDomainsAdd extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
domain: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
account: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
app: 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
|
+
provider: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
timeout: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
wait: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
17
|
+
};
|
|
18
|
+
run(): Promise<void>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import * as p from '@clack/prompts';
|
|
3
|
+
import { addClerkProductionDomain } from '../../../lib/clerk-domain.js';
|
|
4
|
+
import { accountFlag, jsonFlag, providerFlag } from '../../../lib/flags.js';
|
|
5
|
+
import { createOutput, outputError } from '../../../lib/output.js';
|
|
6
|
+
function recordLine(record) {
|
|
7
|
+
return `${record.type} ${record.name} -> ${record.value}${record.proxied === undefined ? '' : ` (proxied ${record.proxied})`}`;
|
|
8
|
+
}
|
|
9
|
+
function conflictNote(warning) {
|
|
10
|
+
return [
|
|
11
|
+
'Existing:',
|
|
12
|
+
...warning.conflicts.map((conflict) => `- ${recordLine(conflict.existing)}`),
|
|
13
|
+
'',
|
|
14
|
+
'Required by Clerk:',
|
|
15
|
+
...warning.desired.map((record) => `- ${recordLine(record)}`),
|
|
16
|
+
].join('\n');
|
|
17
|
+
}
|
|
18
|
+
function resultMessage(result) {
|
|
19
|
+
if (result.clerk.verified)
|
|
20
|
+
return `${result.domain} is configured and verified for Clerk production.`;
|
|
21
|
+
return `${result.domain} is configured for Clerk production. Run ${result.nextSteps.at(-1)} to check provisioning.`;
|
|
22
|
+
}
|
|
23
|
+
export default class ClerkDomainsAdd extends Command {
|
|
24
|
+
static description = 'Create a Clerk production instance with its primary domain and configure DNS.';
|
|
25
|
+
static examples = [
|
|
26
|
+
'<%= config.bin %> <%= command.id %> example.com --app app_123 --json',
|
|
27
|
+
'<%= config.bin %> <%= command.id %> example.com --app app_123 --provider cloudflare --no-wait',
|
|
28
|
+
'<%= config.bin %> <%= command.id %> example.com --app app_123 --dry-run --json',
|
|
29
|
+
];
|
|
30
|
+
static args = {
|
|
31
|
+
domain: Args.string({ description: 'Production primary domain, for example example.com.', required: true }),
|
|
32
|
+
};
|
|
33
|
+
static flags = {
|
|
34
|
+
account: accountFlag,
|
|
35
|
+
app: Flags.string({ description: 'Clerk application id. Defaults to CLERK_APPLICATION_ID or saved Clerk config.' }),
|
|
36
|
+
'dry-run': Flags.boolean({ description: 'Check the Clerk application and DNS zone without creating the production instance.' }),
|
|
37
|
+
force: Flags.boolean({ description: 'Overwrite DNS records that conflict with Clerk requirements.' }),
|
|
38
|
+
json: jsonFlag,
|
|
39
|
+
provider: providerFlag,
|
|
40
|
+
timeout: Flags.integer({ default: 300, description: 'Wait timeout in seconds.' }),
|
|
41
|
+
wait: Flags.boolean({ allowNo: true, default: true, description: 'Wait for Clerk DNS, SSL, and email DNS verification.' }),
|
|
42
|
+
};
|
|
43
|
+
async run() {
|
|
44
|
+
const { args, flags } = await this.parse(ClerkDomainsAdd);
|
|
45
|
+
const out = createOutput({ json: flags.json });
|
|
46
|
+
let spinner;
|
|
47
|
+
try {
|
|
48
|
+
if (!out.json && !flags['dry-run']) {
|
|
49
|
+
p.note(`This creates the first production instance for Clerk application ${flags.app ?? 'the configured app'} and sets ${args.domain} as its primary domain. Existing production instances are never modified.`, 'Clerk production setup');
|
|
50
|
+
const confirmed = await p.confirm({ message: 'Create the Clerk production instance?', initialValue: false });
|
|
51
|
+
if (confirmed !== true) {
|
|
52
|
+
p.cancel('Cancelled');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
spinner = out.spinner();
|
|
57
|
+
spinner.start(flags['dry-run'] ? 'Checking Clerk production setup' : 'Creating Clerk production setup');
|
|
58
|
+
const result = await addClerkProductionDomain({
|
|
59
|
+
account: flags.account,
|
|
60
|
+
app: flags.app,
|
|
61
|
+
confirmDnsOverride: out.json
|
|
62
|
+
? undefined
|
|
63
|
+
: async (warning) => {
|
|
64
|
+
spinner?.stop('DNS conflict found');
|
|
65
|
+
p.note(conflictNote(warning), 'DNS records point elsewhere');
|
|
66
|
+
const confirmed = await p.confirm({ message: 'Overwrite these DNS records?', initialValue: false });
|
|
67
|
+
if (confirmed === true)
|
|
68
|
+
spinner?.start('Continuing Clerk production setup');
|
|
69
|
+
return confirmed === true;
|
|
70
|
+
},
|
|
71
|
+
domain: args.domain,
|
|
72
|
+
dryRun: flags['dry-run'],
|
|
73
|
+
force: flags.force,
|
|
74
|
+
progress: out.json ? undefined : (message) => spinner?.message(message),
|
|
75
|
+
provider: flags.provider,
|
|
76
|
+
timeoutSeconds: flags.timeout,
|
|
77
|
+
wait: flags.wait,
|
|
78
|
+
});
|
|
79
|
+
spinner.stop(flags['dry-run'] ? 'Clerk production setup is available' : 'Clerk production domain configured');
|
|
80
|
+
out.result(result);
|
|
81
|
+
if (!out.json) {
|
|
82
|
+
p.note(result.nextSteps.map((step) => `- ${step}`).join('\n'), 'Next steps');
|
|
83
|
+
out.outro(flags['dry-run'] ? `Ready to create Clerk production for ${result.domain}.` : resultMessage(result));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
spinner?.error('Clerk production setup failed');
|
|
88
|
+
outputError(out.json, error, 'DOMAIN_LINK_FAILED');
|
|
89
|
+
this.exit(1);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
package/dist/commands/link.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Args, Command, Flags } from '@oclif/core';
|
|
2
2
|
import * as p from '@clack/prompts';
|
|
3
|
-
import { DoomainError } from '../lib/errors.js';
|
|
4
3
|
import { accountFlag, apexFlag, domainFlag, jsonFlag, projectFlag, providerFlag, subdomainFlag } from '../lib/flags.js';
|
|
5
4
|
import { linkDomain } from '../lib/link-domain.js';
|
|
6
5
|
import { createOutput, outputError } from '../lib/output.js';
|
|
@@ -27,6 +26,18 @@ function dnsOverrideNote(warning) {
|
|
|
27
26
|
...warning.desired.map((record) => `- ${recordLine(record)}`),
|
|
28
27
|
].join('\n');
|
|
29
28
|
}
|
|
29
|
+
function providerAccount(result) {
|
|
30
|
+
return result.isDefaultAccount ? result.provider : `${result.provider}/${result.account}`;
|
|
31
|
+
}
|
|
32
|
+
function dryRunPreview(result) {
|
|
33
|
+
const account = providerAccount(result);
|
|
34
|
+
return [
|
|
35
|
+
`Vercel: add ${result.domain} to ${result.project}`,
|
|
36
|
+
`DNS provider: ${account} (${result.zoneDomain})`,
|
|
37
|
+
...result.records.map((record) => `DNS: ${recordLine(record)} in ${account}`),
|
|
38
|
+
`Actions: ${result.actions.join(', ')}`,
|
|
39
|
+
].join('\n');
|
|
40
|
+
}
|
|
30
41
|
export default class Link extends Command {
|
|
31
42
|
static description = 'Link a Vercel project to a domain and create DNS records.';
|
|
32
43
|
static examples = [
|
|
@@ -59,14 +70,13 @@ export default class Link extends Command {
|
|
|
59
70
|
let spinner;
|
|
60
71
|
const domain = flags.domain ?? args.domain;
|
|
61
72
|
try {
|
|
62
|
-
if (!domain) {
|
|
63
|
-
throw new DoomainError('MISSING_ARGUMENT', 'Domain is required. Use `doomain link <domain>` or pass --domain.');
|
|
64
|
-
}
|
|
65
73
|
if (flags['dry-run']) {
|
|
66
74
|
const result = await linkDomain({ ...flags, domain, dryRun: true, timeoutSeconds: flags.timeout });
|
|
67
75
|
out.result(result);
|
|
68
|
-
if (!out.json)
|
|
76
|
+
if (!out.json) {
|
|
77
|
+
p.note(dryRunPreview(result), 'Dry run');
|
|
69
78
|
out.success(`Dry run ready for ${result.domain}.`);
|
|
79
|
+
}
|
|
70
80
|
return;
|
|
71
81
|
}
|
|
72
82
|
out.intro('Doomain');
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type ClerkDomainStatus } from './clerk.js';
|
|
2
|
+
import { type DnsOverrideWarning } from './link-domain.js';
|
|
3
|
+
import type { DnsRecordInput } from './providers/types.js';
|
|
4
|
+
export interface AddClerkDomainInput {
|
|
5
|
+
account?: string;
|
|
6
|
+
app?: string;
|
|
7
|
+
domain?: string;
|
|
8
|
+
dryRun?: boolean;
|
|
9
|
+
force?: boolean;
|
|
10
|
+
provider?: string;
|
|
11
|
+
wait?: boolean;
|
|
12
|
+
timeoutSeconds?: number;
|
|
13
|
+
confirmDnsOverride?: (warning: DnsOverrideWarning) => Promise<boolean>;
|
|
14
|
+
progress?: (message: string) => void;
|
|
15
|
+
}
|
|
16
|
+
export interface AddClerkDomainResult {
|
|
17
|
+
account: string;
|
|
18
|
+
app: string;
|
|
19
|
+
clerk: {
|
|
20
|
+
domainId?: string;
|
|
21
|
+
productionInstanceCreated: boolean;
|
|
22
|
+
productionInstanceId?: string;
|
|
23
|
+
status?: ClerkDomainStatus;
|
|
24
|
+
verified: boolean;
|
|
25
|
+
};
|
|
26
|
+
dns: {
|
|
27
|
+
propagated: boolean;
|
|
28
|
+
skipped: DnsRecordInput[];
|
|
29
|
+
updated: boolean;
|
|
30
|
+
};
|
|
31
|
+
domain: string;
|
|
32
|
+
dryRun: boolean;
|
|
33
|
+
isDefaultAccount: boolean;
|
|
34
|
+
provider: string;
|
|
35
|
+
records: DnsRecordInput[];
|
|
36
|
+
zoneDomain: string;
|
|
37
|
+
nextSteps: string[];
|
|
38
|
+
}
|
|
39
|
+
export declare function addClerkProductionDomain(input: AddClerkDomainInput): Promise<AddClerkDomainResult>;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { createClerkPlatformClient, resolveClerkPlatformConfig } from './clerk.js';
|
|
2
|
+
import { DoomainError } from './errors.js';
|
|
3
|
+
import { resolveProviderTarget, withProviderRecordOptions } from './link-domain.js';
|
|
4
|
+
import { createProvider } from './providers/registry.js';
|
|
5
|
+
import { normalizeDomain } from './validate.js';
|
|
6
|
+
function relativeRecordName(host, zoneDomain) {
|
|
7
|
+
const normalized = host.trim().toLowerCase().replace(/\.$/, '');
|
|
8
|
+
if (normalized === zoneDomain)
|
|
9
|
+
return '@';
|
|
10
|
+
if (!normalized.endsWith(`.${zoneDomain}`)) {
|
|
11
|
+
throw new DoomainError('INVALID_INPUT', `Clerk returned DNS host ${host}, which is outside the selected zone ${zoneDomain}.`);
|
|
12
|
+
}
|
|
13
|
+
return normalized.slice(0, -(zoneDomain.length + 1));
|
|
14
|
+
}
|
|
15
|
+
function recordsFromTargets(provider, zoneDomain, targets) {
|
|
16
|
+
return targets.map((target) => withProviderRecordOptions(provider, {
|
|
17
|
+
name: relativeRecordName(target.host, zoneDomain),
|
|
18
|
+
ttl: 300,
|
|
19
|
+
type: 'CNAME',
|
|
20
|
+
value: target.value.trim().replace(/\.$/, ''),
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
23
|
+
function productionExistsError(app, instanceId) {
|
|
24
|
+
return new DoomainError('CLERK_PRODUCTION_EXISTS', `Clerk application ${app} already has a production instance. Configure domain changes manually in Clerk; Doomain will not modify an existing production domain.`, {
|
|
25
|
+
app,
|
|
26
|
+
instanceId,
|
|
27
|
+
recovery: 'Use the Clerk Dashboard Domains page or `clerk deploy` to inspect and configure the existing production instance.',
|
|
28
|
+
suggestedCommands: [`clerk link --app ${app}`, 'clerk deploy status', 'clerk open domains'],
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function dnsConflictError(warning, productionInstanceId) {
|
|
32
|
+
return new DoomainError('DNS_TARGET_CONFLICT', `${warning.domain} has DNS records that conflict with Clerk's required records. Re-run with --force to overwrite them.`, {
|
|
33
|
+
...warning,
|
|
34
|
+
partialState: { productionInstanceCreated: true, productionInstanceId },
|
|
35
|
+
recovery: 'The Clerk production instance now exists. Resolve the DNS conflict, then finish setup with `clerk deploy`.',
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function statusComplete(check) {
|
|
39
|
+
return check?.required === false || check?.status === 'complete';
|
|
40
|
+
}
|
|
41
|
+
async function waitForStatus(client, app, domainId, timeoutSeconds, progress) {
|
|
42
|
+
await client.triggerDomainDnsCheck(app, domainId).catch((error) => {
|
|
43
|
+
const conflict = error instanceof DoomainError && JSON.stringify(error.details).toLowerCase().includes('conflict');
|
|
44
|
+
if (!conflict)
|
|
45
|
+
throw error;
|
|
46
|
+
});
|
|
47
|
+
const deadline = Date.now() + timeoutSeconds * 1000;
|
|
48
|
+
let status = { status: 'incomplete' };
|
|
49
|
+
let attempt = 1;
|
|
50
|
+
while (Date.now() <= deadline) {
|
|
51
|
+
progress?.(`Checking Clerk DNS, SSL, and email DNS status (attempt ${attempt})`);
|
|
52
|
+
status = await client.getDomainStatus(app, domainId);
|
|
53
|
+
if (status.status === 'complete')
|
|
54
|
+
return status;
|
|
55
|
+
attempt += 1;
|
|
56
|
+
if (Date.now() > deadline)
|
|
57
|
+
break;
|
|
58
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
59
|
+
}
|
|
60
|
+
return status;
|
|
61
|
+
}
|
|
62
|
+
export async function addClerkProductionDomain(input) {
|
|
63
|
+
const domain = normalizeDomain(input.domain ?? '');
|
|
64
|
+
const config = await resolveClerkPlatformConfig(input.app);
|
|
65
|
+
const client = createClerkPlatformClient(config);
|
|
66
|
+
input.progress?.('Checking Clerk application');
|
|
67
|
+
const application = await client.fetchApplication(config.appId);
|
|
68
|
+
const production = application.instances.find((instance) => instance.environment_type === 'production');
|
|
69
|
+
if (production)
|
|
70
|
+
throw productionExistsError(config.appId, production.instance_id);
|
|
71
|
+
const development = application.instances.find((instance) => instance.environment_type === 'development');
|
|
72
|
+
if (!development)
|
|
73
|
+
throw new DoomainError('PROJECT_NOT_FOUND', `Clerk application ${config.appId} does not have a development instance to clone.`);
|
|
74
|
+
input.progress?.('Finding the DNS provider and zone');
|
|
75
|
+
const resolved = await resolveProviderTarget({ account: input.account, domain, provider: input.provider });
|
|
76
|
+
const provider = await createProvider(resolved.provider, { account: resolved.account });
|
|
77
|
+
const zone = await provider.getZone(resolved.target.zoneDomain);
|
|
78
|
+
if (!zone)
|
|
79
|
+
throw new DoomainError('PROVIDER_ZONE_NOT_FOUND', `${provider.name} does not have a DNS zone for ${resolved.target.zoneDomain}.`);
|
|
80
|
+
const nextSteps = [
|
|
81
|
+
`clerk link --app ${config.appId}`,
|
|
82
|
+
`clerk env pull --app ${config.appId} --instance prod`,
|
|
83
|
+
'clerk deploy',
|
|
84
|
+
'clerk deploy status',
|
|
85
|
+
];
|
|
86
|
+
if (input.dryRun) {
|
|
87
|
+
return {
|
|
88
|
+
account: resolved.account,
|
|
89
|
+
app: config.appId,
|
|
90
|
+
clerk: { productionInstanceCreated: false, verified: false },
|
|
91
|
+
dns: { propagated: false, skipped: [], updated: false },
|
|
92
|
+
domain,
|
|
93
|
+
dryRun: true,
|
|
94
|
+
isDefaultAccount: resolved.isDefaultAccount,
|
|
95
|
+
nextSteps,
|
|
96
|
+
provider: resolved.provider,
|
|
97
|
+
records: [],
|
|
98
|
+
zoneDomain: resolved.target.zoneDomain,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
input.progress?.('Creating Clerk production instance and primary domain');
|
|
102
|
+
const created = await client.createProductionInstance(config.appId, domain, development.instance_id);
|
|
103
|
+
const clerkDomain = created.active_domain;
|
|
104
|
+
if (!clerkDomain) {
|
|
105
|
+
throw new DoomainError('DOMAIN_LINK_FAILED', 'Clerk created the production instance but did not return its primary domain.', {
|
|
106
|
+
productionInstanceId: created.id,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
const records = recordsFromTargets(resolved.provider, resolved.target.zoneDomain, clerkDomain.cname_targets ?? []);
|
|
110
|
+
if (records.length === 0) {
|
|
111
|
+
throw new DoomainError('DOMAIN_LINK_FAILED', 'Clerk created the production instance but did not return any DNS records.', {
|
|
112
|
+
domainId: clerkDomain.id,
|
|
113
|
+
productionInstanceId: created.id,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
input.progress?.(`Checking existing DNS records in ${provider.name}`);
|
|
117
|
+
let forceDns = Boolean(input.force);
|
|
118
|
+
let dnsPlan = await provider.planChanges(zone, records, { force: forceDns });
|
|
119
|
+
if (!forceDns && dnsPlan.conflicts.length > 0) {
|
|
120
|
+
const warning = {
|
|
121
|
+
account: resolved.account,
|
|
122
|
+
conflicts: dnsPlan.conflicts,
|
|
123
|
+
desired: records,
|
|
124
|
+
domain,
|
|
125
|
+
provider: resolved.provider,
|
|
126
|
+
providerName: provider.name,
|
|
127
|
+
recordName: records[0]?.name ?? '@',
|
|
128
|
+
zoneDomain: resolved.target.zoneDomain,
|
|
129
|
+
};
|
|
130
|
+
forceDns = (await input.confirmDnsOverride?.(warning)) === true;
|
|
131
|
+
if (!forceDns)
|
|
132
|
+
throw dnsConflictError(warning, created.id);
|
|
133
|
+
dnsPlan = await provider.planChanges(zone, records, { force: true });
|
|
134
|
+
}
|
|
135
|
+
input.progress?.(`Creating Clerk DNS records in ${provider.name}`);
|
|
136
|
+
const dnsResult = await provider.applyChanges(zone, dnsPlan, { force: forceDns });
|
|
137
|
+
const shouldWait = input.wait ?? true;
|
|
138
|
+
const status = shouldWait
|
|
139
|
+
? await waitForStatus(client, config.appId, clerkDomain.id, input.timeoutSeconds ?? 300, input.progress)
|
|
140
|
+
: undefined;
|
|
141
|
+
const verified = status?.status === 'complete';
|
|
142
|
+
const propagated = Boolean(status && statusComplete(status.dns) && statusComplete(status.mail));
|
|
143
|
+
return {
|
|
144
|
+
account: resolved.account,
|
|
145
|
+
app: config.appId,
|
|
146
|
+
clerk: {
|
|
147
|
+
domainId: clerkDomain.id,
|
|
148
|
+
productionInstanceCreated: true,
|
|
149
|
+
productionInstanceId: created.id,
|
|
150
|
+
status,
|
|
151
|
+
verified,
|
|
152
|
+
},
|
|
153
|
+
dns: { propagated, skipped: dnsResult.skipped, updated: dnsResult.applied.length > 0 },
|
|
154
|
+
domain,
|
|
155
|
+
dryRun: false,
|
|
156
|
+
isDefaultAccount: resolved.isDefaultAccount,
|
|
157
|
+
nextSteps,
|
|
158
|
+
provider: resolved.provider,
|
|
159
|
+
records,
|
|
160
|
+
zoneDomain: resolved.target.zoneDomain,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export interface ClerkPlatformConfig {
|
|
2
|
+
appId: string;
|
|
3
|
+
platformApiKey: string;
|
|
4
|
+
}
|
|
5
|
+
export interface ClerkApplication {
|
|
6
|
+
application_id: string;
|
|
7
|
+
name?: string;
|
|
8
|
+
instances: Array<{
|
|
9
|
+
environment_type: string;
|
|
10
|
+
instance_id: string;
|
|
11
|
+
publishable_key: string;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
export interface ClerkCnameTarget {
|
|
15
|
+
host: string;
|
|
16
|
+
required: boolean;
|
|
17
|
+
value: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ClerkApplicationDomain {
|
|
20
|
+
cname_targets?: ClerkCnameTarget[];
|
|
21
|
+
frontend_api_url: string;
|
|
22
|
+
id: string;
|
|
23
|
+
is_satellite: boolean;
|
|
24
|
+
name: string;
|
|
25
|
+
}
|
|
26
|
+
export interface ClerkProductionInstance {
|
|
27
|
+
active_domain: ClerkApplicationDomain | null;
|
|
28
|
+
environment_type: 'production';
|
|
29
|
+
id: string;
|
|
30
|
+
publishable_key: string;
|
|
31
|
+
}
|
|
32
|
+
export interface ClerkDomainStatus {
|
|
33
|
+
dns?: {
|
|
34
|
+
required?: boolean;
|
|
35
|
+
status: string;
|
|
36
|
+
};
|
|
37
|
+
mail?: {
|
|
38
|
+
required?: boolean;
|
|
39
|
+
status: string;
|
|
40
|
+
};
|
|
41
|
+
ssl?: {
|
|
42
|
+
required?: boolean;
|
|
43
|
+
status: string;
|
|
44
|
+
};
|
|
45
|
+
status: string;
|
|
46
|
+
}
|
|
47
|
+
export declare function resolveClerkPlatformConfig(appId?: string): Promise<ClerkPlatformConfig>;
|
|
48
|
+
export declare function createClerkPlatformClient(config: {
|
|
49
|
+
platformApiKey: string;
|
|
50
|
+
}): {
|
|
51
|
+
fetchApplication(appId: string): Promise<ClerkApplication>;
|
|
52
|
+
createProductionInstance(appId: string, domain: string, developmentInstanceId: string): Promise<ClerkProductionInstance>;
|
|
53
|
+
getDomainStatus(appId: string, domainId: string): Promise<ClerkDomainStatus>;
|
|
54
|
+
triggerDomainDnsCheck(appId: string, domainId: string): Promise<ClerkDomainStatus>;
|
|
55
|
+
};
|
|
56
|
+
export type ClerkPlatformClient = ReturnType<typeof createClerkPlatformClient>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { loadConfig } from './config.js';
|
|
2
|
+
import { DoomainError } from './errors.js';
|
|
3
|
+
const CLERK_API_URL = 'https://api.clerk.com';
|
|
4
|
+
export async function resolveClerkPlatformConfig(appId) {
|
|
5
|
+
const config = await loadConfig();
|
|
6
|
+
const platformApiKey = process.env.CLERK_PLATFORM_API_KEY || config.clerk?.platformApiKey;
|
|
7
|
+
const resolvedAppId = appId || process.env.CLERK_APPLICATION_ID || config.clerk?.appId;
|
|
8
|
+
if (!platformApiKey) {
|
|
9
|
+
throw new DoomainError('MISSING_CREDENTIALS', 'Missing Clerk Platform API key. Run `doomain auth clerk`, set CLERK_PLATFORM_API_KEY, or pass saved credentials.');
|
|
10
|
+
}
|
|
11
|
+
if (!platformApiKey.startsWith('ak_')) {
|
|
12
|
+
throw new DoomainError('INVALID_INPUT', 'Clerk Platform API keys must start with ak_.');
|
|
13
|
+
}
|
|
14
|
+
if (!resolvedAppId) {
|
|
15
|
+
throw new DoomainError('MISSING_ARGUMENT', 'Clerk application is required. Pass --app, set CLERK_APPLICATION_ID, or save it with `doomain auth clerk`.');
|
|
16
|
+
}
|
|
17
|
+
return { appId: resolvedAppId, platformApiKey };
|
|
18
|
+
}
|
|
19
|
+
function apiErrorMessage(status, body) {
|
|
20
|
+
return body?.errors?.[0]?.long_message ?? body?.errors?.[0]?.message ?? body?.error?.message ?? body?.message ?? `Clerk API error (${status}).`;
|
|
21
|
+
}
|
|
22
|
+
function apiErrorCode(body) {
|
|
23
|
+
return body?.errors?.[0]?.code ?? body?.error?.code ?? body?.code;
|
|
24
|
+
}
|
|
25
|
+
export function createClerkPlatformClient(config) {
|
|
26
|
+
async function request(path, init = {}) {
|
|
27
|
+
const response = await fetch(`${CLERK_API_URL}${path}`, {
|
|
28
|
+
...init,
|
|
29
|
+
headers: {
|
|
30
|
+
Accept: 'application/json',
|
|
31
|
+
Authorization: `Bearer ${config.platformApiKey}`,
|
|
32
|
+
...(init.body ? { 'Content-Type': 'application/json' } : {}),
|
|
33
|
+
...(init.headers ?? {}),
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
const body = (await response.json().catch(() => undefined));
|
|
38
|
+
if (response.status === 401 || response.status === 403) {
|
|
39
|
+
throw new DoomainError('CLERK_AUTH_FAILED', `Clerk Platform API authorization failed. Check CLERK_PLATFORM_API_KEY and its application access. ${apiErrorMessage(response.status, body)}`, body);
|
|
40
|
+
}
|
|
41
|
+
if (response.status === 409 && apiErrorCode(body) === 'production_instance_exists') {
|
|
42
|
+
throw new DoomainError('CLERK_PRODUCTION_EXISTS', 'This Clerk application already has a production instance. Configure domain changes manually in Clerk; Doomain will not modify it.', body);
|
|
43
|
+
}
|
|
44
|
+
throw new DoomainError('DOMAIN_LINK_FAILED', apiErrorMessage(response.status, body), body);
|
|
45
|
+
}
|
|
46
|
+
return (await response.json());
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
fetchApplication(appId) {
|
|
50
|
+
return request(`/v1/platform/applications/${encodeURIComponent(appId)}`);
|
|
51
|
+
},
|
|
52
|
+
createProductionInstance(appId, domain, developmentInstanceId) {
|
|
53
|
+
return request(`/v1/platform/applications/${encodeURIComponent(appId)}/instances`, {
|
|
54
|
+
body: JSON.stringify({ clone_instance_id: developmentInstanceId, domain, environment_type: 'production' }),
|
|
55
|
+
method: 'POST',
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
getDomainStatus(appId, domainId) {
|
|
59
|
+
return request(`/v1/platform/applications/${encodeURIComponent(appId)}/domains/${encodeURIComponent(domainId)}/status`);
|
|
60
|
+
},
|
|
61
|
+
triggerDomainDnsCheck(appId, domainId) {
|
|
62
|
+
return request(`/v1/platform/applications/${encodeURIComponent(appId)}/domains/${encodeURIComponent(domainId)}/dns_check`, { method: 'POST' });
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -8,12 +8,13 @@ export const commandSchemas = [
|
|
|
8
8
|
'doomain link app.example.com --project my-app --json',
|
|
9
9
|
'doomain link --domain app.example.com --project my-app --json',
|
|
10
10
|
'doomain link --domain example.com --subdomain app --project my-app --json',
|
|
11
|
+
'doomain link --project my-app --json',
|
|
11
12
|
'doomain link --provider spaceship --domain example.com --apex --project my-app --dry-run --json',
|
|
12
13
|
'doomain link app.example.com --provider spaceship --account work --project my-app --json',
|
|
13
14
|
],
|
|
14
15
|
agentHint: 'For agent use, try `doomain link <domain> --json` first. Do not inspect project files, run provider status, or use --dry-run unless the user explicitly asks for a preview. The command infers project/provider and returns structured recovery errors when inference fails.',
|
|
15
16
|
agentInstructions: [
|
|
16
|
-
'When the user asks to add or link a domain, run `doomain link <domain> --json` first.',
|
|
17
|
+
'When the user asks to add or link a Vercel domain, run `doomain link <domain> --json` first.',
|
|
17
18
|
'Do not run `providers status`, `projects list`, `--help`, or `--dry-run` before the first link attempt unless the user asks for a preview or diagnosis.',
|
|
18
19
|
'Only run follow-up commands when the JSON error includes a recovery action that requires them.',
|
|
19
20
|
],
|
|
@@ -27,16 +28,69 @@ export const commandSchemas = [
|
|
|
27
28
|
{ name: 'json', type: 'boolean', description: 'Output a single JSON object and never prompt.' },
|
|
28
29
|
{ name: 'provider', type: 'string', description: 'DNS provider id. Inferred from the target domain when omitted.' },
|
|
29
30
|
{ name: 'account', type: 'string', description: 'DNS provider profile/account alias. Defaults to the provider default account.' },
|
|
30
|
-
{
|
|
31
|
+
{
|
|
32
|
+
name: 'domain',
|
|
33
|
+
type: 'string',
|
|
34
|
+
description: 'Target domain or base zone, for example app.example.com or example.com. Optional when DOOMAIN_DOMAIN or a default domain is configured.',
|
|
35
|
+
},
|
|
31
36
|
{ name: 'subdomain', type: 'string', description: 'Subdomain to add.' },
|
|
32
37
|
{ name: 'apex', type: 'boolean', description: 'Use the root/apex domain.' },
|
|
33
38
|
{ name: 'project', type: 'string', description: 'Vercel project id/name. Optional when project inference succeeds.' },
|
|
34
39
|
{ name: 'dry-run', type: 'boolean', description: 'Preview changes without writing. Intended for human previews; agents should not use this unless explicitly asked.' },
|
|
35
|
-
{
|
|
36
|
-
|
|
40
|
+
{
|
|
41
|
+
name: 'force',
|
|
42
|
+
type: 'boolean',
|
|
43
|
+
description: 'Move existing Vercel project domains and overwrite conflicting DNS records. Interactive DNS override confirmation does not move Vercel aliases; pass --force for that.',
|
|
44
|
+
},
|
|
45
|
+
{ name: 'wait', type: 'boolean', description: 'Wait for DNS and Vercel verification. Use --no-wait to skip waiting.', default: true },
|
|
37
46
|
{ name: 'timeout', type: 'integer', description: 'Wait timeout in seconds.', default: 300 },
|
|
38
47
|
],
|
|
39
48
|
},
|
|
49
|
+
{
|
|
50
|
+
name: 'clerk domains add',
|
|
51
|
+
description: 'Create the first Clerk production instance with its primary domain and configure returned DNS records.',
|
|
52
|
+
examples: [
|
|
53
|
+
'doomain clerk domains add example.com --app app_123 --json',
|
|
54
|
+
'doomain clerk domains add example.com --app app_123 --provider cloudflare --no-wait --json',
|
|
55
|
+
'doomain clerk domains add example.com --app app_123 --dry-run --json',
|
|
56
|
+
],
|
|
57
|
+
agentHint: 'Use only for first-time Clerk production setup. The command aborts with CLERK_PRODUCTION_EXISTS when production already exists; domain changes must then be completed manually in Clerk.',
|
|
58
|
+
agentInstructions: [
|
|
59
|
+
'When the user asks to configure a Clerk production domain for the first time, run `doomain clerk domains add <domain> --app <app_id> --json`.',
|
|
60
|
+
'Never use this command to migrate or replace an existing Clerk production domain.',
|
|
61
|
+
'After success, follow the returned nextSteps to pull production keys, finish OAuth setup, and verify provisioning with Clerk CLI.',
|
|
62
|
+
],
|
|
63
|
+
mutates: true,
|
|
64
|
+
safeForAgents: true,
|
|
65
|
+
flags: [
|
|
66
|
+
{ name: 'json', type: 'boolean', description: 'Output a single JSON object and never prompt.' },
|
|
67
|
+
{ name: 'app', type: 'string', description: 'Clerk application id. Defaults to CLERK_APPLICATION_ID or saved Clerk config.' },
|
|
68
|
+
{ name: 'provider', type: 'string', description: 'DNS provider id. Inferred from the target domain when omitted.' },
|
|
69
|
+
{ name: 'account', type: 'string', description: 'DNS provider profile/account alias. Defaults to the provider default account.' },
|
|
70
|
+
{ name: 'dry-run', type: 'boolean', description: 'Check application eligibility and DNS zone without creating production.' },
|
|
71
|
+
{ name: 'force', type: 'boolean', description: 'Overwrite DNS records that conflict with Clerk requirements.' },
|
|
72
|
+
{ name: 'wait', type: 'boolean', description: 'Wait for Clerk DNS, SSL, and email DNS verification. Use --no-wait to skip waiting.', default: true },
|
|
73
|
+
{ name: 'timeout', type: 'integer', description: 'Wait timeout in seconds.', default: 300 },
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'schema',
|
|
78
|
+
description: 'Print machine-readable command schemas for agents.',
|
|
79
|
+
examples: ['doomain schema --json', 'doomain schema link --json'],
|
|
80
|
+
safeForAgents: true,
|
|
81
|
+
flags: [
|
|
82
|
+
{ name: 'json', type: 'boolean', description: 'Output a single JSON object and never prompt.' },
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: 'providers list',
|
|
87
|
+
description: 'List supported DNS providers.',
|
|
88
|
+
examples: ['doomain providers list --json'],
|
|
89
|
+
safeForAgents: true,
|
|
90
|
+
flags: [
|
|
91
|
+
{ name: 'json', type: 'boolean', description: 'Output a single JSON object and never prompt.' },
|
|
92
|
+
],
|
|
93
|
+
},
|
|
40
94
|
{
|
|
41
95
|
name: 'providers connect',
|
|
42
96
|
description: 'Save DNS provider credentials locally after verifying them. Prompts for a provider when omitted.',
|
|
@@ -105,6 +159,24 @@ export const commandSchemas = [
|
|
|
105
159
|
{ name: 'account', type: 'string', description: 'DNS provider profile/account alias. Defaults to the provider default account.' },
|
|
106
160
|
],
|
|
107
161
|
},
|
|
162
|
+
{
|
|
163
|
+
name: 'auth clerk',
|
|
164
|
+
description: 'Save and verify Clerk Platform API credentials locally.',
|
|
165
|
+
examples: ['doomain auth clerk --platform-api-key ak_123 --app app_123 --json', 'doomain auth clerk'],
|
|
166
|
+
flags: [
|
|
167
|
+
{ name: 'json', type: 'boolean', description: 'Output a single JSON object and never prompt.' },
|
|
168
|
+
{ name: 'platform-api-key', type: 'string', description: 'Clerk Platform API key (ak_...).' },
|
|
169
|
+
{ name: 'app', type: 'string', description: 'Default Clerk application id.' },
|
|
170
|
+
],
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: 'auth logout clerk',
|
|
174
|
+
description: 'Remove saved Clerk credentials locally.',
|
|
175
|
+
examples: ['doomain auth logout clerk --json'],
|
|
176
|
+
flags: [
|
|
177
|
+
{ name: 'json', type: 'boolean', description: 'Output a single JSON object and never prompt.' },
|
|
178
|
+
],
|
|
179
|
+
},
|
|
108
180
|
{
|
|
109
181
|
name: 'auth vercel',
|
|
110
182
|
description: 'Save Vercel credentials locally.',
|
|
@@ -123,6 +195,42 @@ export const commandSchemas = [
|
|
|
123
195
|
{ name: 'json', type: 'boolean', description: 'Output a single JSON object and never prompt.' },
|
|
124
196
|
],
|
|
125
197
|
},
|
|
198
|
+
{
|
|
199
|
+
name: 'domains list',
|
|
200
|
+
description: 'List DNS zones and records for a provider.',
|
|
201
|
+
examples: ['doomain domains list --json', 'doomain domains list --provider cloudflare --domain example.com --json'],
|
|
202
|
+
safeForAgents: true,
|
|
203
|
+
flags: [
|
|
204
|
+
{ name: 'json', type: 'boolean', description: 'Output a single JSON object and never prompt.' },
|
|
205
|
+
{ name: 'provider', type: 'string', description: 'DNS provider id. Defaults to DOOMAIN_PROVIDER, configured default provider, then spaceship.' },
|
|
206
|
+
{ name: 'account', type: 'string', description: 'DNS provider profile/account alias. Omit to list all configured accounts for the provider.' },
|
|
207
|
+
{ name: 'domain', type: 'string', description: 'Limit output to one DNS zone.' },
|
|
208
|
+
],
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
name: 'projects list',
|
|
212
|
+
description: 'List Vercel projects.',
|
|
213
|
+
examples: ['doomain projects list --json', 'doomain projects list --search my-app --json'],
|
|
214
|
+
safeForAgents: true,
|
|
215
|
+
flags: [
|
|
216
|
+
{ name: 'json', type: 'boolean', description: 'Output a single JSON object and never prompt.' },
|
|
217
|
+
{ name: 'search', type: 'string', description: 'Filter projects by search term.' },
|
|
218
|
+
],
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
name: 'verify',
|
|
222
|
+
description: 'Ask Vercel to verify a project domain.',
|
|
223
|
+
examples: ['doomain verify --domain app.example.com --project my-app --json', 'doomain verify --domain example.com --apex --project my-app --json'],
|
|
224
|
+
mutates: true,
|
|
225
|
+
safeForAgents: true,
|
|
226
|
+
flags: [
|
|
227
|
+
{ name: 'json', type: 'boolean', description: 'Output a single JSON object and never prompt.' },
|
|
228
|
+
{ name: 'domain', type: 'string', description: 'Target domain or base zone, for example app.example.com or example.com.', required: true },
|
|
229
|
+
{ name: 'subdomain', type: 'string', description: 'Subdomain to verify.' },
|
|
230
|
+
{ name: 'apex', type: 'boolean', description: 'Use the root/apex domain.' },
|
|
231
|
+
{ name: 'project', type: 'string', description: 'Vercel project id/name. Optional when local .vercel/project.json is available.' },
|
|
232
|
+
],
|
|
233
|
+
},
|
|
126
234
|
];
|
|
127
235
|
export function getCommandSchema(name) {
|
|
128
236
|
if (!name)
|
|
@@ -141,7 +249,7 @@ async function configuredProviders() {
|
|
|
141
249
|
}));
|
|
142
250
|
}
|
|
143
251
|
function withProviderConnections(schema, providers) {
|
|
144
|
-
if (schema.name !== 'link')
|
|
252
|
+
if (schema.name !== 'link' && schema.name !== 'clerk domains add')
|
|
145
253
|
return schema;
|
|
146
254
|
return { ...schema, configuredProviders: providers };
|
|
147
255
|
}
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -6,6 +6,10 @@ export interface VercelConfig {
|
|
|
6
6
|
token?: string;
|
|
7
7
|
teamId?: string;
|
|
8
8
|
}
|
|
9
|
+
export interface ClerkConfig {
|
|
10
|
+
appId?: string;
|
|
11
|
+
platformApiKey?: string;
|
|
12
|
+
}
|
|
9
13
|
export interface ProviderAccountConfig {
|
|
10
14
|
credentials?: Record<string, string>;
|
|
11
15
|
settings?: Record<string, unknown>;
|
|
@@ -19,6 +23,7 @@ export interface SpaceshipProviderConfig extends ProviderConfig {
|
|
|
19
23
|
domains?: string[];
|
|
20
24
|
}
|
|
21
25
|
export interface DoomainConfig {
|
|
26
|
+
clerk?: ClerkConfig;
|
|
22
27
|
vercel?: VercelConfig;
|
|
23
28
|
providers?: {
|
|
24
29
|
spaceship?: SpaceshipProviderConfig;
|
package/dist/lib/errors.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type DoomainErrorCode = 'CONFIG_NOT_FOUND' | 'DNS_TARGET_CONFLICT' | 'DOMAIN_LINK_FAILED' | 'DOMAIN_ALREADY_ASSIGNED' | 'DOMAIN_VERIFY_FAILED' | 'INVALID_INPUT' | 'MISSING_ARGUMENT' | 'MISSING_CREDENTIALS' | 'PROVIDER_API_ERROR' | 'PROVIDER_AUTH_FAILED' | 'PROVIDER_PERMISSION_DENIED' | 'PROVIDER_NOT_FOUND' | 'PROVIDER_RATE_LIMITED' | 'PROVIDER_RECORD_CONFLICT' | 'PROVIDER_UNSUPPORTED_RECORD' | 'PROVIDER_ZONE_AMBIGUOUS' | 'PROVIDER_ZONE_NOT_FOUND' | 'PROJECT_NOT_FOUND' | 'VERCEL_AUTH_FAILED' | 'VERCEL_PROJECT_NOT_LINKED';
|
|
1
|
+
export type DoomainErrorCode = 'CONFIG_NOT_FOUND' | 'CLERK_AUTH_FAILED' | 'CLERK_PRODUCTION_EXISTS' | 'DNS_TARGET_CONFLICT' | 'DOMAIN_LINK_FAILED' | 'DOMAIN_ALREADY_ASSIGNED' | 'DOMAIN_VERIFY_FAILED' | 'INVALID_INPUT' | 'MISSING_ARGUMENT' | 'MISSING_CREDENTIALS' | 'PROVIDER_API_ERROR' | 'PROVIDER_AUTH_FAILED' | 'PROVIDER_PERMISSION_DENIED' | 'PROVIDER_NOT_FOUND' | 'PROVIDER_RATE_LIMITED' | 'PROVIDER_RECORD_CONFLICT' | 'PROVIDER_UNSUPPORTED_RECORD' | 'PROVIDER_ZONE_AMBIGUOUS' | 'PROVIDER_ZONE_NOT_FOUND' | 'PROJECT_NOT_FOUND' | 'VERCEL_AUTH_FAILED' | 'VERCEL_PROJECT_NOT_LINKED';
|
|
2
2
|
export declare class DoomainError extends Error {
|
|
3
3
|
readonly code: DoomainErrorCode;
|
|
4
4
|
readonly details?: unknown;
|
|
@@ -59,6 +59,21 @@ export interface LinkDomainResult extends LinkDomainPlan {
|
|
|
59
59
|
verified: boolean;
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
|
+
export interface ResolvedDnsTarget {
|
|
63
|
+
provider: string;
|
|
64
|
+
providerInferred: boolean;
|
|
65
|
+
account: string;
|
|
66
|
+
accountInferred: boolean;
|
|
67
|
+
isDefaultAccount: boolean;
|
|
68
|
+
target: {
|
|
69
|
+
fullDomain: string;
|
|
70
|
+
isApex: boolean;
|
|
71
|
+
recordName: string;
|
|
72
|
+
zoneDomain: string;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export declare function resolveProviderTarget(input: Pick<LinkDomainInput, 'account' | 'apex' | 'domain' | 'provider' | 'subdomain'>): Promise<ResolvedDnsTarget>;
|
|
76
|
+
export declare function withProviderRecordOptions(provider: string, record: DnsRecordInput): DnsRecordInput;
|
|
62
77
|
export declare function verificationRecords(raw: unknown, zoneDomain: string): DnsRecordInput[];
|
|
63
78
|
export declare function createLinkPlan(input: LinkDomainInput): Promise<LinkDomainPlan>;
|
|
64
79
|
export declare function linkDomain(input: LinkDomainInput): Promise<LinkDomainResult>;
|
package/dist/lib/link-domain.js
CHANGED
|
@@ -248,7 +248,7 @@ async function loadConfiguredProviderZones(providerId, accountInput) {
|
|
|
248
248
|
searched: results.map((result) => result.search),
|
|
249
249
|
};
|
|
250
250
|
}
|
|
251
|
-
async function resolveProviderTarget(input) {
|
|
251
|
+
export async function resolveProviderTarget(input) {
|
|
252
252
|
const requested = resolveRequestedDomain({
|
|
253
253
|
apex: input.apex,
|
|
254
254
|
domain: await resolveConfiguredDomain(input.domain),
|
|
@@ -288,7 +288,7 @@ async function resolveProviderTarget(input) {
|
|
|
288
288
|
target: targetFromZone(requested.fullDomain, selected.zone.name),
|
|
289
289
|
};
|
|
290
290
|
}
|
|
291
|
-
function withProviderRecordOptions(provider, record) {
|
|
291
|
+
export function withProviderRecordOptions(provider, record) {
|
|
292
292
|
if (provider !== 'cloudflare' || !['A', 'AAAA', 'CNAME'].includes(record.type))
|
|
293
293
|
return record;
|
|
294
294
|
return { ...record, proxied: false };
|
|
@@ -495,8 +495,9 @@ async function resolveDnsForce(input, opts) {
|
|
|
495
495
|
return true;
|
|
496
496
|
}
|
|
497
497
|
export async function createLinkPlan(input) {
|
|
498
|
+
const domain = await resolveConfiguredDomain(input.domain);
|
|
498
499
|
const project = await resolveProject(input.project);
|
|
499
|
-
const resolved = await resolveProviderTarget(input);
|
|
500
|
+
const resolved = await resolveProviderTarget({ ...input, domain });
|
|
500
501
|
const { account, accountInferred, isDefaultAccount, provider, providerInferred, target } = resolved;
|
|
501
502
|
const record = planBaseRecord({ isApex: target.isApex, provider, recordName: target.recordName });
|
|
502
503
|
return {
|
package/oclif.manifest.json
CHANGED
|
@@ -225,6 +225,51 @@
|
|
|
225
225
|
"wizard.js"
|
|
226
226
|
]
|
|
227
227
|
},
|
|
228
|
+
"auth:clerk": {
|
|
229
|
+
"aliases": [],
|
|
230
|
+
"args": {},
|
|
231
|
+
"description": "Save Clerk Platform API credentials locally.",
|
|
232
|
+
"examples": [
|
|
233
|
+
"<%= config.bin %> <%= command.id %> --platform-api-key ak_123 --app app_123 --json"
|
|
234
|
+
],
|
|
235
|
+
"flags": {
|
|
236
|
+
"app": {
|
|
237
|
+
"description": "Default Clerk application id.",
|
|
238
|
+
"name": "app",
|
|
239
|
+
"hasDynamicHelp": false,
|
|
240
|
+
"multiple": false,
|
|
241
|
+
"type": "option"
|
|
242
|
+
},
|
|
243
|
+
"json": {
|
|
244
|
+
"description": "Output a single JSON object and never prompt.",
|
|
245
|
+
"name": "json",
|
|
246
|
+
"allowNo": false,
|
|
247
|
+
"type": "boolean"
|
|
248
|
+
},
|
|
249
|
+
"platform-api-key": {
|
|
250
|
+
"description": "Clerk Platform API key (ak_...).",
|
|
251
|
+
"name": "platform-api-key",
|
|
252
|
+
"hasDynamicHelp": false,
|
|
253
|
+
"multiple": false,
|
|
254
|
+
"type": "option"
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
"hasDynamicHelp": false,
|
|
258
|
+
"hiddenAliases": [],
|
|
259
|
+
"id": "auth:clerk",
|
|
260
|
+
"pluginAlias": "doomain",
|
|
261
|
+
"pluginName": "doomain",
|
|
262
|
+
"pluginType": "core",
|
|
263
|
+
"strict": true,
|
|
264
|
+
"enableJsonFlag": false,
|
|
265
|
+
"isESM": true,
|
|
266
|
+
"relativePath": [
|
|
267
|
+
"dist",
|
|
268
|
+
"commands",
|
|
269
|
+
"auth",
|
|
270
|
+
"clerk.js"
|
|
271
|
+
]
|
|
272
|
+
},
|
|
228
273
|
"auth:vercel": {
|
|
229
274
|
"aliases": [],
|
|
230
275
|
"args": {},
|
|
@@ -316,6 +361,41 @@
|
|
|
316
361
|
"list.js"
|
|
317
362
|
]
|
|
318
363
|
},
|
|
364
|
+
"projects:list": {
|
|
365
|
+
"aliases": [],
|
|
366
|
+
"args": {},
|
|
367
|
+
"description": "List Vercel projects.",
|
|
368
|
+
"flags": {
|
|
369
|
+
"json": {
|
|
370
|
+
"description": "Output a single JSON object and never prompt.",
|
|
371
|
+
"name": "json",
|
|
372
|
+
"allowNo": false,
|
|
373
|
+
"type": "boolean"
|
|
374
|
+
},
|
|
375
|
+
"search": {
|
|
376
|
+
"description": "Filter projects by search term.",
|
|
377
|
+
"name": "search",
|
|
378
|
+
"hasDynamicHelp": false,
|
|
379
|
+
"multiple": false,
|
|
380
|
+
"type": "option"
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
"hasDynamicHelp": false,
|
|
384
|
+
"hiddenAliases": [],
|
|
385
|
+
"id": "projects:list",
|
|
386
|
+
"pluginAlias": "doomain",
|
|
387
|
+
"pluginName": "doomain",
|
|
388
|
+
"pluginType": "core",
|
|
389
|
+
"strict": true,
|
|
390
|
+
"enableJsonFlag": false,
|
|
391
|
+
"isESM": true,
|
|
392
|
+
"relativePath": [
|
|
393
|
+
"dist",
|
|
394
|
+
"commands",
|
|
395
|
+
"projects",
|
|
396
|
+
"list.js"
|
|
397
|
+
]
|
|
398
|
+
},
|
|
319
399
|
"providers:add": {
|
|
320
400
|
"aliases": [],
|
|
321
401
|
"args": {
|
|
@@ -604,28 +684,21 @@
|
|
|
604
684
|
"verify.js"
|
|
605
685
|
]
|
|
606
686
|
},
|
|
607
|
-
"
|
|
687
|
+
"auth:logout:clerk": {
|
|
608
688
|
"aliases": [],
|
|
609
689
|
"args": {},
|
|
610
|
-
"description": "
|
|
690
|
+
"description": "Remove saved Clerk credentials locally.",
|
|
611
691
|
"flags": {
|
|
612
692
|
"json": {
|
|
613
693
|
"description": "Output a single JSON object and never prompt.",
|
|
614
694
|
"name": "json",
|
|
615
695
|
"allowNo": false,
|
|
616
696
|
"type": "boolean"
|
|
617
|
-
},
|
|
618
|
-
"search": {
|
|
619
|
-
"description": "Filter projects by search term.",
|
|
620
|
-
"name": "search",
|
|
621
|
-
"hasDynamicHelp": false,
|
|
622
|
-
"multiple": false,
|
|
623
|
-
"type": "option"
|
|
624
697
|
}
|
|
625
698
|
},
|
|
626
699
|
"hasDynamicHelp": false,
|
|
627
700
|
"hiddenAliases": [],
|
|
628
|
-
"id": "
|
|
701
|
+
"id": "auth:logout:clerk",
|
|
629
702
|
"pluginAlias": "doomain",
|
|
630
703
|
"pluginName": "doomain",
|
|
631
704
|
"pluginType": "core",
|
|
@@ -635,8 +708,9 @@
|
|
|
635
708
|
"relativePath": [
|
|
636
709
|
"dist",
|
|
637
710
|
"commands",
|
|
638
|
-
"
|
|
639
|
-
"
|
|
711
|
+
"auth",
|
|
712
|
+
"logout",
|
|
713
|
+
"clerk.js"
|
|
640
714
|
]
|
|
641
715
|
},
|
|
642
716
|
"auth:logout:vercel": {
|
|
@@ -671,7 +745,94 @@
|
|
|
671
745
|
"logout",
|
|
672
746
|
"vercel.js"
|
|
673
747
|
]
|
|
748
|
+
},
|
|
749
|
+
"clerk:domains:add": {
|
|
750
|
+
"aliases": [],
|
|
751
|
+
"args": {
|
|
752
|
+
"domain": {
|
|
753
|
+
"description": "Production primary domain, for example example.com.",
|
|
754
|
+
"name": "domain",
|
|
755
|
+
"required": true
|
|
756
|
+
}
|
|
757
|
+
},
|
|
758
|
+
"description": "Create a Clerk production instance with its primary domain and configure DNS.",
|
|
759
|
+
"examples": [
|
|
760
|
+
"<%= config.bin %> <%= command.id %> example.com --app app_123 --json",
|
|
761
|
+
"<%= config.bin %> <%= command.id %> example.com --app app_123 --provider cloudflare --no-wait",
|
|
762
|
+
"<%= config.bin %> <%= command.id %> example.com --app app_123 --dry-run --json"
|
|
763
|
+
],
|
|
764
|
+
"flags": {
|
|
765
|
+
"account": {
|
|
766
|
+
"description": "DNS provider profile/account alias. Defaults to the provider default account.",
|
|
767
|
+
"name": "account",
|
|
768
|
+
"hasDynamicHelp": false,
|
|
769
|
+
"multiple": false,
|
|
770
|
+
"type": "option"
|
|
771
|
+
},
|
|
772
|
+
"app": {
|
|
773
|
+
"description": "Clerk application id. Defaults to CLERK_APPLICATION_ID or saved Clerk config.",
|
|
774
|
+
"name": "app",
|
|
775
|
+
"hasDynamicHelp": false,
|
|
776
|
+
"multiple": false,
|
|
777
|
+
"type": "option"
|
|
778
|
+
},
|
|
779
|
+
"dry-run": {
|
|
780
|
+
"description": "Check the Clerk application and DNS zone without creating the production instance.",
|
|
781
|
+
"name": "dry-run",
|
|
782
|
+
"allowNo": false,
|
|
783
|
+
"type": "boolean"
|
|
784
|
+
},
|
|
785
|
+
"force": {
|
|
786
|
+
"description": "Overwrite DNS records that conflict with Clerk requirements.",
|
|
787
|
+
"name": "force",
|
|
788
|
+
"allowNo": false,
|
|
789
|
+
"type": "boolean"
|
|
790
|
+
},
|
|
791
|
+
"json": {
|
|
792
|
+
"description": "Output a single JSON object and never prompt.",
|
|
793
|
+
"name": "json",
|
|
794
|
+
"allowNo": false,
|
|
795
|
+
"type": "boolean"
|
|
796
|
+
},
|
|
797
|
+
"provider": {
|
|
798
|
+
"description": "DNS provider id. Inferred from the target domain when omitted.",
|
|
799
|
+
"name": "provider",
|
|
800
|
+
"hasDynamicHelp": false,
|
|
801
|
+
"multiple": false,
|
|
802
|
+
"type": "option"
|
|
803
|
+
},
|
|
804
|
+
"timeout": {
|
|
805
|
+
"description": "Wait timeout in seconds.",
|
|
806
|
+
"name": "timeout",
|
|
807
|
+
"default": 300,
|
|
808
|
+
"hasDynamicHelp": false,
|
|
809
|
+
"multiple": false,
|
|
810
|
+
"type": "option"
|
|
811
|
+
},
|
|
812
|
+
"wait": {
|
|
813
|
+
"description": "Wait for Clerk DNS, SSL, and email DNS verification.",
|
|
814
|
+
"name": "wait",
|
|
815
|
+
"allowNo": true,
|
|
816
|
+
"type": "boolean"
|
|
817
|
+
}
|
|
818
|
+
},
|
|
819
|
+
"hasDynamicHelp": false,
|
|
820
|
+
"hiddenAliases": [],
|
|
821
|
+
"id": "clerk:domains:add",
|
|
822
|
+
"pluginAlias": "doomain",
|
|
823
|
+
"pluginName": "doomain",
|
|
824
|
+
"pluginType": "core",
|
|
825
|
+
"strict": true,
|
|
826
|
+
"enableJsonFlag": false,
|
|
827
|
+
"isESM": true,
|
|
828
|
+
"relativePath": [
|
|
829
|
+
"dist",
|
|
830
|
+
"commands",
|
|
831
|
+
"clerk",
|
|
832
|
+
"domains",
|
|
833
|
+
"add.js"
|
|
834
|
+
]
|
|
674
835
|
}
|
|
675
836
|
},
|
|
676
|
-
"version": "0.1.
|
|
837
|
+
"version": "0.1.16"
|
|
677
838
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "doomain",
|
|
3
|
-
"description": "
|
|
4
|
-
"version": "0.1.
|
|
3
|
+
"description": "Configure Vercel and Clerk production domains in seconds",
|
|
4
|
+
"version": "0.1.16",
|
|
5
5
|
"author": "Crafter Station",
|
|
6
6
|
"packageManager": "bun@1.3.13",
|
|
7
7
|
"bin": {
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
"keywords": [
|
|
40
40
|
"dns",
|
|
41
41
|
"domains",
|
|
42
|
-
"vercel"
|
|
42
|
+
"vercel",
|
|
43
|
+
"clerk"
|
|
43
44
|
],
|
|
44
45
|
"license": "MIT",
|
|
45
46
|
"main": "dist/index.js",
|
|
@@ -56,6 +57,9 @@
|
|
|
56
57
|
"auth": {
|
|
57
58
|
"description": "Manage authentication"
|
|
58
59
|
},
|
|
60
|
+
"clerk": {
|
|
61
|
+
"description": "Configure Clerk production domains"
|
|
62
|
+
},
|
|
59
63
|
"domains": {
|
|
60
64
|
"description": "Inspect DNS domains"
|
|
61
65
|
},
|