doomain 0.1.10 → 0.1.12
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/dist/commands/domains/list.d.ts +1 -0
- package/dist/commands/domains/list.js +33 -11
- package/dist/commands/link.d.ts +1 -0
- package/dist/commands/link.js +3 -1
- package/dist/commands/providers/connect.d.ts +1 -0
- package/dist/commands/providers/connect.js +46 -5
- package/dist/commands/providers/disconnect.d.ts +1 -0
- package/dist/commands/providers/disconnect.js +35 -5
- package/dist/commands/providers/status.js +4 -2
- package/dist/commands/providers/verify.d.ts +1 -0
- package/dist/commands/providers/verify.js +6 -3
- package/dist/commands/wizard.js +60 -19
- package/dist/lib/command-schema.d.ts +1 -1
- package/dist/lib/command-schema.js +22 -3
- package/dist/lib/config.d.ts +4 -1
- package/dist/lib/flags.d.ts +1 -0
- package/dist/lib/flags.js +1 -0
- package/dist/lib/link-domain.d.ts +4 -0
- package/dist/lib/link-domain.js +74 -23
- package/dist/lib/providers/core/config.d.ts +19 -4
- package/dist/lib/providers/core/config.js +77 -11
- package/dist/lib/providers/registry.d.ts +3 -1
- package/dist/lib/providers/registry.js +2 -2
- package/dist/lib/providers/status.d.ts +5 -1
- package/dist/lib/providers/status.js +28 -21
- package/dist/lib/validate.d.ts +1 -0
- package/dist/lib/validate.js +7 -0
- package/oclif.manifest.json +80 -37
- package/package.json +1 -1
|
@@ -2,6 +2,7 @@ import { Command } from '@oclif/core';
|
|
|
2
2
|
export default class DomainsList extends Command {
|
|
3
3
|
static description: string;
|
|
4
4
|
static flags: {
|
|
5
|
+
account: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
5
6
|
domain: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
6
7
|
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
8
|
provider: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
2
|
import { loadConfig } from '../../lib/config.js';
|
|
3
|
-
import { domainFlag, jsonFlag, providerFlag } from '../../lib/flags.js';
|
|
3
|
+
import { accountFlag, domainFlag, jsonFlag, providerFlag } from '../../lib/flags.js';
|
|
4
4
|
import { createOutput, outputError } from '../../lib/output.js';
|
|
5
|
-
import {
|
|
5
|
+
import { DEFAULT_PROVIDER_ACCOUNT, isDefaultProviderAccount, listConfiguredProviderAccounts, normalizeProviderAccount, } from '../../lib/providers/core/config.js';
|
|
6
|
+
import { createProvider, getProviderDefinition } from '../../lib/providers/registry.js';
|
|
6
7
|
import { normalizeDomain } from '../../lib/validate.js';
|
|
7
8
|
async function resolveZones(provider, domain) {
|
|
8
9
|
if (!domain)
|
|
@@ -16,6 +17,7 @@ async function resolveZones(provider, domain) {
|
|
|
16
17
|
export default class DomainsList extends Command {
|
|
17
18
|
static description = 'List DNS zones and records for a provider.';
|
|
18
19
|
static flags = {
|
|
20
|
+
account: accountFlag,
|
|
19
21
|
domain: domainFlag,
|
|
20
22
|
json: jsonFlag,
|
|
21
23
|
provider: providerFlag,
|
|
@@ -25,17 +27,37 @@ export default class DomainsList extends Command {
|
|
|
25
27
|
const out = createOutput({ json: flags.json });
|
|
26
28
|
try {
|
|
27
29
|
const config = await loadConfig();
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
+
const providerId = flags.provider ?? process.env.DOOMAIN_PROVIDER ?? config.defaults?.provider ?? 'spaceship';
|
|
31
|
+
const definition = getProviderDefinition(providerId);
|
|
32
|
+
const account = flags.account ? normalizeProviderAccount(flags.account) : undefined;
|
|
33
|
+
const accounts = account
|
|
34
|
+
? [{ account, isDefaultAccount: isDefaultProviderAccount(account), providerId: definition.id }]
|
|
35
|
+
: listConfiguredProviderAccounts(config, definition);
|
|
36
|
+
const selectedAccounts = accounts.length > 0 ? accounts : [{ account: DEFAULT_PROVIDER_ACCOUNT, isDefaultAccount: true, providerId: definition.id }];
|
|
30
37
|
const results = [];
|
|
31
|
-
for (const
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
for (const selectedAccount of selectedAccounts) {
|
|
39
|
+
const provider = await createProvider(definition.id, { account: selectedAccount.account });
|
|
40
|
+
const zones = await resolveZones(provider, flags.domain);
|
|
41
|
+
for (const zone of zones) {
|
|
42
|
+
const records = await provider.listRecords(zone);
|
|
43
|
+
results.push({
|
|
44
|
+
account: selectedAccount.account,
|
|
45
|
+
isDefaultAccount: selectedAccount.isDefaultAccount,
|
|
46
|
+
provider: provider.id,
|
|
47
|
+
records,
|
|
48
|
+
zone,
|
|
49
|
+
});
|
|
50
|
+
const accountLabel = selectedAccount.isDefaultAccount ? provider.id : `${provider.id}/${selectedAccount.account}`;
|
|
51
|
+
out.info(`${zone.name} (${records.length} records) via ${accountLabel}`);
|
|
52
|
+
for (const record of records)
|
|
53
|
+
out.info(` ${record.type} ${record.name} -> ${record.value}`);
|
|
54
|
+
}
|
|
37
55
|
}
|
|
38
|
-
out.result({
|
|
56
|
+
out.result({
|
|
57
|
+
...(selectedAccounts.length === 1 ? { account: selectedAccounts[0].account, isDefaultAccount: selectedAccounts[0].isDefaultAccount } : {}),
|
|
58
|
+
provider: definition.id,
|
|
59
|
+
zones: results,
|
|
60
|
+
});
|
|
39
61
|
}
|
|
40
62
|
catch (error) {
|
|
41
63
|
outputError(out.json, error, 'DOMAIN_LINK_FAILED');
|
package/dist/commands/link.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export default class Link extends Command {
|
|
|
6
6
|
domain: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
7
7
|
};
|
|
8
8
|
static flags: {
|
|
9
|
+
account: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
10
|
apex: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
11
|
domain: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
12
|
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
package/dist/commands/link.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Args, Command, Flags } from '@oclif/core';
|
|
2
2
|
import { DoomainError } from '../lib/errors.js';
|
|
3
|
-
import { apexFlag, domainFlag, jsonFlag, projectFlag, providerFlag, subdomainFlag } from '../lib/flags.js';
|
|
3
|
+
import { accountFlag, apexFlag, domainFlag, jsonFlag, projectFlag, providerFlag, subdomainFlag } from '../lib/flags.js';
|
|
4
4
|
import { linkDomain } from '../lib/link-domain.js';
|
|
5
5
|
import { createOutput, outputError } from '../lib/output.js';
|
|
6
6
|
export default class Link extends Command {
|
|
@@ -11,11 +11,13 @@ export default class Link extends Command {
|
|
|
11
11
|
'<%= config.bin %> <%= command.id %> --domain app.example.com --project my-app --json',
|
|
12
12
|
'<%= config.bin %> <%= command.id %> --domain example.com --subdomain app --project my-app',
|
|
13
13
|
'<%= config.bin %> <%= command.id %> --provider spaceship --domain example.com --apex --project my-app --json',
|
|
14
|
+
'<%= config.bin %> <%= command.id %> app.example.com --provider spaceship --account work --project my-app --json',
|
|
14
15
|
];
|
|
15
16
|
static args = {
|
|
16
17
|
domain: Args.string({ description: 'Target domain to link, for example app.example.com.', required: false }),
|
|
17
18
|
};
|
|
18
19
|
static flags = {
|
|
20
|
+
account: accountFlag,
|
|
19
21
|
apex: apexFlag,
|
|
20
22
|
domain: domainFlag,
|
|
21
23
|
'dry-run': Flags.boolean({ description: 'Preview changes without writing to Vercel or DNS.' }),
|
|
@@ -5,6 +5,7 @@ export default class ProvidersConnect extends Command {
|
|
|
5
5
|
};
|
|
6
6
|
static description: string;
|
|
7
7
|
static flags: {
|
|
8
|
+
account: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
9
|
'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
10
|
'api-secret': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
11
|
credential: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Args, Command, Flags } from '@oclif/core';
|
|
2
2
|
import * as p from '@clack/prompts';
|
|
3
3
|
import { getConfigPath, loadConfig, maskSecret, updateConfig } from '../../lib/config.js';
|
|
4
|
-
import { jsonFlag } from '../../lib/flags.js';
|
|
4
|
+
import { accountFlag, jsonFlag } from '../../lib/flags.js';
|
|
5
5
|
import { createOutput, outputError } from '../../lib/output.js';
|
|
6
|
+
import { DEFAULT_PROVIDER_ACCOUNT, isDefaultProviderAccount, listConfiguredProviderAccounts, normalizeProviderAccount, providerAccountHasCredentials, withProviderAccountCredentials, } from '../../lib/providers/core/config.js';
|
|
6
7
|
import { getProviderDefinition, listProviderDefinitions } from '../../lib/providers/registry.js';
|
|
7
|
-
import { isProviderConfigured } from '../../lib/providers/status.js';
|
|
8
8
|
function requireString(value, message) {
|
|
9
9
|
if (typeof value === 'string' && value.trim())
|
|
10
10
|
return value.trim();
|
|
@@ -58,12 +58,40 @@ async function promptCredential(credential, detectedPublicIp) {
|
|
|
58
58
|
}
|
|
59
59
|
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
60
60
|
}
|
|
61
|
+
function validateProviderAccount(value) {
|
|
62
|
+
try {
|
|
63
|
+
normalizeProviderAccount(value);
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
return error instanceof Error ? error.message : String(error);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function promptProviderAccount() {
|
|
71
|
+
const value = await p.text({ message: 'Profile name', placeholder: DEFAULT_PROVIDER_ACCOUNT, validate: validateProviderAccount });
|
|
72
|
+
if (p.isCancel(value)) {
|
|
73
|
+
p.cancel('Cancelled');
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
return normalizeProviderAccount(value);
|
|
77
|
+
}
|
|
78
|
+
async function confirmProviderAccountOverwrite(definition, account) {
|
|
79
|
+
const value = await p.confirm({
|
|
80
|
+
initialValue: false,
|
|
81
|
+
message: `Profile "${account}" already exists for ${definition.displayName}. Overwrite it?`,
|
|
82
|
+
});
|
|
83
|
+
if (p.isCancel(value)) {
|
|
84
|
+
p.cancel('Cancelled');
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
return value;
|
|
88
|
+
}
|
|
61
89
|
async function promptProvider() {
|
|
62
90
|
const config = await loadConfig();
|
|
63
91
|
const selected = await p.select({
|
|
64
92
|
message: 'Choose DNS provider',
|
|
65
93
|
options: listProviderDefinitions().map((definition) => ({
|
|
66
|
-
hint:
|
|
94
|
+
hint: listConfiguredProviderAccounts(config, definition).length > 0 ? 'Connected' : 'Not connected',
|
|
67
95
|
label: definition.displayName,
|
|
68
96
|
value: definition.id,
|
|
69
97
|
})),
|
|
@@ -91,6 +119,7 @@ export default class ProvidersConnect extends Command {
|
|
|
91
119
|
};
|
|
92
120
|
static description = 'Save DNS provider credentials locally.';
|
|
93
121
|
static flags = {
|
|
122
|
+
account: accountFlag,
|
|
94
123
|
'api-key': Flags.string({ description: 'Compatibility alias for Spaceship apiKey.' }),
|
|
95
124
|
'api-secret': Flags.string({ description: 'Compatibility alias for Spaceship apiSecret.' }),
|
|
96
125
|
credential: Flags.string({ char: 'c', description: 'Provider credential as key=value.', multiple: true }),
|
|
@@ -107,6 +136,16 @@ export default class ProvidersConnect extends Command {
|
|
|
107
136
|
const definition = args.provider ? getProviderDefinition(args.provider) : await promptProvider();
|
|
108
137
|
if (!definition)
|
|
109
138
|
return;
|
|
139
|
+
const account = flags.account ? normalizeProviderAccount(flags.account) : out.json ? DEFAULT_PROVIDER_ACCOUNT : await promptProviderAccount();
|
|
140
|
+
if (!account)
|
|
141
|
+
return;
|
|
142
|
+
const isDefaultAccount = isDefaultProviderAccount(account);
|
|
143
|
+
const currentConfig = await loadConfig();
|
|
144
|
+
if (!out.json && providerAccountHasCredentials(currentConfig, definition.id, account)) {
|
|
145
|
+
const overwrite = await confirmProviderAccountOverwrite(definition, account);
|
|
146
|
+
if (!overwrite)
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
110
149
|
const passedCredentials = parseCredentialFlags(flags.credential);
|
|
111
150
|
const credentials = {};
|
|
112
151
|
const detectedPublicIp = !out.json && usesClientIp(definition) ? await fetchPublicIp() : undefined;
|
|
@@ -145,7 +184,6 @@ export default class ProvidersConnect extends Command {
|
|
|
145
184
|
spinner = undefined;
|
|
146
185
|
}
|
|
147
186
|
let setDefault = true;
|
|
148
|
-
const currentConfig = await loadConfig();
|
|
149
187
|
if (!out.json && currentConfig.defaults?.provider && currentConfig.defaults.provider !== definition.id) {
|
|
150
188
|
const value = await p.confirm({
|
|
151
189
|
initialValue: true,
|
|
@@ -162,13 +200,16 @@ export default class ProvidersConnect extends Command {
|
|
|
162
200
|
defaults: setDefault ? { ...config.defaults, provider: definition.id } : config.defaults,
|
|
163
201
|
providers: {
|
|
164
202
|
...config.providers,
|
|
165
|
-
[definition.id]:
|
|
203
|
+
[definition.id]: withProviderAccountCredentials(config.providers?.[definition.id], account, credentials),
|
|
166
204
|
},
|
|
167
205
|
}));
|
|
168
206
|
out.result({
|
|
207
|
+
account,
|
|
169
208
|
configPath: getConfigPath(),
|
|
170
209
|
credentials: Object.fromEntries(Object.entries(credentials).map(([key, value]) => [key, maskSecret(value)])),
|
|
210
|
+
defaultAccount: isDefaultAccount,
|
|
171
211
|
domainCount,
|
|
212
|
+
isDefaultAccount,
|
|
172
213
|
provider: definition.id,
|
|
173
214
|
verified: !flags['no-verify'],
|
|
174
215
|
});
|
|
@@ -7,6 +7,7 @@ export default class ProvidersDisconnect extends Command {
|
|
|
7
7
|
static description: string;
|
|
8
8
|
static examples: string[];
|
|
9
9
|
static flags: {
|
|
10
|
+
account: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
11
|
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
12
|
};
|
|
12
13
|
run(): Promise<void>;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Args, Command } from '@oclif/core';
|
|
2
2
|
import { getConfigPath, updateConfig } from '../../lib/config.js';
|
|
3
|
-
import { jsonFlag } from '../../lib/flags.js';
|
|
3
|
+
import { accountFlag, jsonFlag } from '../../lib/flags.js';
|
|
4
4
|
import { createOutput, outputError } from '../../lib/output.js';
|
|
5
|
+
import { isDefaultProviderAccount, normalizeProviderAccount } from '../../lib/providers/core/config.js';
|
|
5
6
|
import { getProviderDefinition } from '../../lib/providers/registry.js';
|
|
6
7
|
function envOverrides(definition) {
|
|
7
8
|
return definition.credentials.flatMap((credential) => (process.env[credential.env] ? [credential.env] : []));
|
|
@@ -17,6 +18,7 @@ export default class ProvidersDisconnect extends Command {
|
|
|
17
18
|
'<%= config.bin %> <%= command.id %> cloudflare --json',
|
|
18
19
|
];
|
|
19
20
|
static flags = {
|
|
21
|
+
account: accountFlag,
|
|
20
22
|
json: jsonFlag,
|
|
21
23
|
};
|
|
22
24
|
async run() {
|
|
@@ -24,13 +26,41 @@ export default class ProvidersDisconnect extends Command {
|
|
|
24
26
|
const out = createOutput({ json: flags.json });
|
|
25
27
|
try {
|
|
26
28
|
const definition = getProviderDefinition(args.provider);
|
|
29
|
+
const account = flags.account ? normalizeProviderAccount(flags.account) : undefined;
|
|
27
30
|
let removed = false;
|
|
28
31
|
await updateConfig((config) => {
|
|
29
32
|
const providers = { ...config.providers };
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
const provider = providers[definition.id];
|
|
34
|
+
if (!account) {
|
|
35
|
+
removed = provider !== undefined;
|
|
36
|
+
delete providers[definition.id];
|
|
37
|
+
}
|
|
38
|
+
else if (provider) {
|
|
39
|
+
const nextProvider = { ...provider };
|
|
40
|
+
if (isDefaultProviderAccount(account)) {
|
|
41
|
+
removed = nextProvider.credentials !== undefined || (definition.id === 'spaceship' && ('apiKey' in nextProvider || 'apiSecret' in nextProvider));
|
|
42
|
+
delete nextProvider.credentials;
|
|
43
|
+
if (definition.id === 'spaceship') {
|
|
44
|
+
delete nextProvider.apiKey;
|
|
45
|
+
delete nextProvider.apiSecret;
|
|
46
|
+
delete nextProvider.domains;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
const accounts = { ...nextProvider.accounts };
|
|
51
|
+
removed = accounts[account] !== undefined;
|
|
52
|
+
delete accounts[account];
|
|
53
|
+
nextProvider.accounts = Object.keys(accounts).length > 0 ? accounts : undefined;
|
|
54
|
+
}
|
|
55
|
+
if (nextProvider.credentials || nextProvider.settings || (nextProvider.accounts && Object.keys(nextProvider.accounts).length > 0)) {
|
|
56
|
+
providers[definition.id] = nextProvider;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
delete providers[definition.id];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
32
62
|
const defaults = { ...config.defaults };
|
|
33
|
-
if (defaults.provider === definition.id)
|
|
63
|
+
if (defaults.provider === definition.id && providers[definition.id] === undefined)
|
|
34
64
|
delete defaults.provider;
|
|
35
65
|
return {
|
|
36
66
|
...config,
|
|
@@ -39,7 +69,7 @@ export default class ProvidersDisconnect extends Command {
|
|
|
39
69
|
};
|
|
40
70
|
});
|
|
41
71
|
const overrides = envOverrides(definition);
|
|
42
|
-
out.result({ configPath: getConfigPath(), environmentOverrides: overrides, provider: definition.id, removed });
|
|
72
|
+
out.result({ account, configPath: getConfigPath(), environmentOverrides: overrides, provider: definition.id, removed });
|
|
43
73
|
if (overrides.length > 0)
|
|
44
74
|
out.warn(`${definition.displayName} environment credentials are still set: ${overrides.join(', ')}.`);
|
|
45
75
|
out.success(removed ? `${definition.displayName} credentials removed from ${getConfigPath()}.` : `${definition.displayName} was not connected.`);
|
|
@@ -24,8 +24,10 @@ export default class ProvidersStatus extends Command {
|
|
|
24
24
|
spinner?.start('Checking DNS providers');
|
|
25
25
|
const providers = await listProviderStatuses({ verify: !flags['no-verify'] });
|
|
26
26
|
spinner?.stop('Checked DNS providers');
|
|
27
|
-
for (const provider of providers)
|
|
28
|
-
|
|
27
|
+
for (const provider of providers) {
|
|
28
|
+
const account = provider.isDefaultAccount ? provider.id : `${provider.id}/${provider.account}`;
|
|
29
|
+
out.info(`${provider.displayName} (${account}) - ${formatStatus(provider)}${provider.default ? ' [default]' : ''}`);
|
|
30
|
+
}
|
|
29
31
|
out.result({ providers });
|
|
30
32
|
}
|
|
31
33
|
}
|
|
@@ -5,6 +5,7 @@ export default class ProvidersVerify extends Command {
|
|
|
5
5
|
};
|
|
6
6
|
static description: string;
|
|
7
7
|
static flags: {
|
|
8
|
+
account: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
9
|
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
10
|
};
|
|
10
11
|
run(): Promise<void>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Args, Command } from '@oclif/core';
|
|
2
|
-
import { jsonFlag } from '../../lib/flags.js';
|
|
2
|
+
import { accountFlag, jsonFlag } from '../../lib/flags.js';
|
|
3
3
|
import { createOutput, outputError } from '../../lib/output.js';
|
|
4
|
+
import { isDefaultProviderAccount, normalizeProviderAccount } from '../../lib/providers/core/config.js';
|
|
4
5
|
import { createProvider } from '../../lib/providers/registry.js';
|
|
5
6
|
export default class ProvidersVerify extends Command {
|
|
6
7
|
static args = {
|
|
@@ -8,15 +9,17 @@ export default class ProvidersVerify extends Command {
|
|
|
8
9
|
};
|
|
9
10
|
static description = 'Verify saved DNS provider credentials.';
|
|
10
11
|
static flags = {
|
|
12
|
+
account: accountFlag,
|
|
11
13
|
json: jsonFlag,
|
|
12
14
|
};
|
|
13
15
|
async run() {
|
|
14
16
|
const { args, flags } = await this.parse(ProvidersVerify);
|
|
15
17
|
const out = createOutput({ json: flags.json });
|
|
16
18
|
try {
|
|
17
|
-
const
|
|
19
|
+
const account = normalizeProviderAccount(flags.account);
|
|
20
|
+
const provider = await createProvider(args.provider, { account });
|
|
18
21
|
const health = await provider.verifyCredentials();
|
|
19
|
-
out.result({ health, provider: provider.id });
|
|
22
|
+
out.result({ account, health, isDefaultAccount: isDefaultProviderAccount(account), provider: provider.id });
|
|
20
23
|
out.success(`${provider.name} credentials verified.`);
|
|
21
24
|
}
|
|
22
25
|
catch (error) {
|
package/dist/commands/wizard.js
CHANGED
|
@@ -6,8 +6,8 @@ import { jsonFlag } from '../lib/flags.js';
|
|
|
6
6
|
import { createLinkPlan, linkDomain } from '../lib/link-domain.js';
|
|
7
7
|
import { detectLocalVercelProject } from '../lib/local-vercel.js';
|
|
8
8
|
import { createOutput, outputError } from '../lib/output.js';
|
|
9
|
+
import { DEFAULT_PROVIDER_ACCOUNT, isDefaultProviderAccount, listConfiguredProviderAccounts, normalizeProviderAccount, providerAccountHasCredentials, withProviderAccountCredentials, } from '../lib/providers/core/config.js';
|
|
9
10
|
import { createProvider, getProviderDefinition, listProviderDefinitions } from '../lib/providers/registry.js';
|
|
10
|
-
import { isProviderConfigured } from '../lib/providers/status.js';
|
|
11
11
|
import { listGlobalVercelTokens } from '../lib/vercel-auth.js';
|
|
12
12
|
import { createVercelClient } from '../lib/vercel.js';
|
|
13
13
|
const PERSONAL_ACCOUNT = '__personal__';
|
|
@@ -26,6 +26,28 @@ async function promptRequired(message, opts = {}) {
|
|
|
26
26
|
const resolved = cancelIfNeeded(value);
|
|
27
27
|
return typeof resolved === 'string' ? resolved.trim() : null;
|
|
28
28
|
}
|
|
29
|
+
function validateProviderAccount(value) {
|
|
30
|
+
try {
|
|
31
|
+
normalizeProviderAccount(value);
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
return error instanceof Error ? error.message : String(error);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function promptProviderAccount() {
|
|
39
|
+
const value = await p.text({ message: 'Profile name', placeholder: DEFAULT_PROVIDER_ACCOUNT, validate: validateProviderAccount });
|
|
40
|
+
const resolved = cancelIfNeeded(value);
|
|
41
|
+
return typeof resolved === 'string' ? normalizeProviderAccount(resolved) : null;
|
|
42
|
+
}
|
|
43
|
+
async function confirmProviderAccountOverwrite(definition, account) {
|
|
44
|
+
const value = await p.confirm({
|
|
45
|
+
initialValue: false,
|
|
46
|
+
message: `Profile "${account}" already exists for ${definition.displayName}. Overwrite it?`,
|
|
47
|
+
});
|
|
48
|
+
const resolved = cancelIfNeeded(value);
|
|
49
|
+
return resolved === true;
|
|
50
|
+
}
|
|
29
51
|
async function fetchPublicIp() {
|
|
30
52
|
const controller = new AbortController();
|
|
31
53
|
const timeout = setTimeout(() => controller.abort(), 2000);
|
|
@@ -72,7 +94,7 @@ async function promptProviderDefinition(definitions, config) {
|
|
|
72
94
|
const selected = await p.select({
|
|
73
95
|
message: 'Choose DNS provider',
|
|
74
96
|
options: definitions.map((definition) => ({
|
|
75
|
-
hint:
|
|
97
|
+
hint: listConfiguredProviderAccounts(config, definition).length > 0 ? 'Connected' : 'Not connected',
|
|
76
98
|
label: definition.displayName,
|
|
77
99
|
value: definition.id,
|
|
78
100
|
})),
|
|
@@ -87,19 +109,24 @@ function showProviderSetup(definition) {
|
|
|
87
109
|
return;
|
|
88
110
|
p.note(definition.setup.notes.join('\n'), `${definition.displayName} setup`);
|
|
89
111
|
}
|
|
90
|
-
async function listProviderDomainOptions(definition) {
|
|
91
|
-
const provider = await createProvider(definition.id);
|
|
112
|
+
async function listProviderDomainOptions(definition, account) {
|
|
113
|
+
const provider = await createProvider(definition.id, { account: account.account });
|
|
92
114
|
const zones = await provider.listZones();
|
|
93
|
-
return zones.map((zone) => toProviderDomainOption(definition, zone));
|
|
115
|
+
return zones.map((zone) => toProviderDomainOption(definition, account, zone));
|
|
94
116
|
}
|
|
95
|
-
function toProviderDomainOption(definition, zone) {
|
|
117
|
+
function toProviderDomainOption(definition, account, zone) {
|
|
96
118
|
return {
|
|
119
|
+
account: account.account,
|
|
97
120
|
domain: zone.name,
|
|
98
|
-
id: `${definition.id}:${zone.name}`,
|
|
121
|
+
id: `${definition.id}:${account.account}:${zone.name}`,
|
|
122
|
+
isDefaultAccount: account.isDefaultAccount,
|
|
99
123
|
providerId: definition.id,
|
|
100
124
|
providerName: definition.displayName,
|
|
101
125
|
};
|
|
102
126
|
}
|
|
127
|
+
function providerAccountLabel(option) {
|
|
128
|
+
return option.isDefaultAccount ? option.providerName : `${option.providerName}/${option.account}`;
|
|
129
|
+
}
|
|
103
130
|
function projectLabel(project) {
|
|
104
131
|
return project.name ? `${project.name} (${project.id})` : project.id;
|
|
105
132
|
}
|
|
@@ -237,15 +264,24 @@ export default class Wizard extends Command {
|
|
|
237
264
|
const project = resolved;
|
|
238
265
|
const projectDisplay = projectLabel(projects.find((item) => item.id === project) ?? { id: project });
|
|
239
266
|
p.log.success(`Vercel ready: ${projectDisplay}`);
|
|
240
|
-
const
|
|
267
|
+
const configuredProviderAccounts = providerDefinitions.flatMap((definition) => listConfiguredProviderAccounts(config, definition).map((account) => ({ account, definition })));
|
|
241
268
|
const providerFailures = [];
|
|
242
269
|
const domainOptions = [];
|
|
243
|
-
if (
|
|
270
|
+
if (configuredProviderAccounts.length === 0) {
|
|
244
271
|
p.log.info('No DNS provider is configured yet. Connect one to continue.');
|
|
245
272
|
const selectedDefinition = await promptProviderDefinition(providerDefinitions, config);
|
|
246
273
|
if (!selectedDefinition)
|
|
247
274
|
return;
|
|
248
275
|
showProviderSetup(selectedDefinition);
|
|
276
|
+
const account = await promptProviderAccount();
|
|
277
|
+
if (!account)
|
|
278
|
+
return;
|
|
279
|
+
if (providerAccountHasCredentials(config, selectedDefinition.id, account)) {
|
|
280
|
+
const overwrite = await confirmProviderAccountOverwrite(selectedDefinition, account);
|
|
281
|
+
if (!overwrite)
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const providerAccount = { account, isDefaultAccount: isDefaultProviderAccount(account), providerId: selectedDefinition.id };
|
|
249
285
|
const credentials = await promptProviderCredentials(selectedDefinition);
|
|
250
286
|
if (!credentials)
|
|
251
287
|
return;
|
|
@@ -256,24 +292,28 @@ export default class Wizard extends Command {
|
|
|
256
292
|
const zones = await provider.listZones();
|
|
257
293
|
domainSpinner.stop(`Connected ${selectedDefinition.displayName} and loaded ${zones.length} domain${zones.length === 1 ? '' : 's'}`);
|
|
258
294
|
activeSpinner = undefined;
|
|
259
|
-
domainOptions.push(...zones.map((zone) => toProviderDomainOption(selectedDefinition, zone)));
|
|
295
|
+
domainOptions.push(...zones.map((zone) => toProviderDomainOption(selectedDefinition, providerAccount, zone)));
|
|
260
296
|
await updateConfig((current) => ({
|
|
261
297
|
...current,
|
|
262
298
|
defaults: { ...current.defaults, provider: selectedDefinition.id },
|
|
263
|
-
providers: {
|
|
299
|
+
providers: {
|
|
300
|
+
...current.providers,
|
|
301
|
+
[selectedDefinition.id]: withProviderAccountCredentials(current.providers?.[selectedDefinition.id], account, credentials),
|
|
302
|
+
},
|
|
264
303
|
vercel: { token: vercelToken, teamId: vercelTeamId },
|
|
265
304
|
}));
|
|
266
305
|
}
|
|
267
306
|
else {
|
|
268
307
|
const domainSpinner = p.spinner();
|
|
269
308
|
activeSpinner = domainSpinner;
|
|
270
|
-
domainSpinner.start(`Loading domains from ${
|
|
271
|
-
for (const definition of
|
|
309
|
+
domainSpinner.start(`Loading domains from ${configuredProviderAccounts.length} provider account${configuredProviderAccounts.length === 1 ? '' : 's'}`);
|
|
310
|
+
for (const { account, definition } of configuredProviderAccounts) {
|
|
272
311
|
try {
|
|
273
|
-
domainOptions.push(...(await listProviderDomainOptions(definition)));
|
|
312
|
+
domainOptions.push(...(await listProviderDomainOptions(definition, account)));
|
|
274
313
|
}
|
|
275
314
|
catch (error) {
|
|
276
|
-
|
|
315
|
+
const label = account.isDefaultAccount ? definition.displayName : `${definition.displayName}/${account.account}`;
|
|
316
|
+
providerFailures.push(`${label}: ${error instanceof Error ? error.message : String(error)}`);
|
|
277
317
|
}
|
|
278
318
|
}
|
|
279
319
|
domainSpinner.stop(`Loaded ${domainOptions.length} domain${domainOptions.length === 1 ? '' : 's'}`);
|
|
@@ -299,14 +339,14 @@ export default class Wizard extends Command {
|
|
|
299
339
|
placeholder: 'Type to filter domains...',
|
|
300
340
|
maxItems: 10,
|
|
301
341
|
initialValue,
|
|
302
|
-
options: domainOptions.map((option) => ({ label: option.domain, value: option.id, hint: option
|
|
342
|
+
options: domainOptions.map((option) => ({ label: option.domain, value: option.id, hint: providerAccountLabel(option) })),
|
|
303
343
|
});
|
|
304
344
|
const selectedId = cancelIfNeeded(selectedDomainId);
|
|
305
345
|
if (selectedId === null)
|
|
306
346
|
return;
|
|
307
347
|
selectedDomain = domainOptions.find((option) => option.id === selectedId) ?? domainOptions[0];
|
|
308
348
|
}
|
|
309
|
-
p.log.info(`Using domain ${selectedDomain.domain} from ${selectedDomain
|
|
349
|
+
p.log.info(`Using domain ${selectedDomain.domain} from ${providerAccountLabel(selectedDomain)}.`);
|
|
310
350
|
const domain = selectedDomain.domain;
|
|
311
351
|
await updateConfig((current) => ({
|
|
312
352
|
...current,
|
|
@@ -330,8 +370,8 @@ export default class Wizard extends Command {
|
|
|
330
370
|
return;
|
|
331
371
|
}
|
|
332
372
|
const fullDomain = apex ? domain : `${subdomain}.${domain}`;
|
|
333
|
-
const preview = await createLinkPlan({ provider: selectedDomain.providerId, domain, subdomain, apex, project });
|
|
334
|
-
p.note([`Vercel: add ${preview.domain} to ${projectDisplay}`, ...preview.records.map((record) => recordPreview(record, selectedDomain
|
|
373
|
+
const preview = await createLinkPlan({ account: selectedDomain.account, provider: selectedDomain.providerId, domain, subdomain, apex, project });
|
|
374
|
+
p.note([`Vercel: add ${preview.domain} to ${projectDisplay}`, ...preview.records.map((record) => recordPreview(record, providerAccountLabel(selectedDomain)))].join('\n'), 'Preview');
|
|
335
375
|
const confirmed = await p.confirm({
|
|
336
376
|
message: `Link ${fullDomain} via ${selectedDomain.providerName} to Vercel project ${projectDisplay}?`,
|
|
337
377
|
initialValue: true,
|
|
@@ -343,6 +383,7 @@ export default class Wizard extends Command {
|
|
|
343
383
|
activeSpinner = spinner;
|
|
344
384
|
spinner.start('Adding domain to Vercel');
|
|
345
385
|
const result = await linkDomain({
|
|
386
|
+
account: selectedDomain.account,
|
|
346
387
|
provider: selectedDomain.providerId,
|
|
347
388
|
domain,
|
|
348
389
|
subdomain,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ProviderStatus } from './providers/status.js';
|
|
2
|
-
export type ProviderConnectionStatus = Pick<ProviderStatus, 'configured' | 'default' | 'displayName' | 'docsUrl' | 'id'>;
|
|
2
|
+
export type ProviderConnectionStatus = Pick<ProviderStatus, 'account' | 'configured' | 'default' | 'displayName' | 'docsUrl' | 'id' | 'isDefaultAccount'>;
|
|
3
3
|
export interface CommandSchema {
|
|
4
4
|
name: string;
|
|
5
5
|
description: string;
|
|
@@ -9,6 +9,7 @@ export const commandSchemas = [
|
|
|
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
11
|
'doomain link --provider spaceship --domain example.com --apex --project my-app --dry-run --json',
|
|
12
|
+
'doomain link app.example.com --provider spaceship --account work --project my-app --json',
|
|
12
13
|
],
|
|
13
14
|
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.',
|
|
14
15
|
agentInstructions: [
|
|
@@ -25,6 +26,7 @@ export const commandSchemas = [
|
|
|
25
26
|
flags: [
|
|
26
27
|
{ name: 'json', type: 'boolean', description: 'Output a single JSON object and never prompt.' },
|
|
27
28
|
{ name: 'provider', type: 'string', description: 'DNS provider id. Inferred from the target domain when omitted.' },
|
|
29
|
+
{ name: 'account', type: 'string', description: 'DNS provider profile/account alias. Defaults to the provider default account.' },
|
|
28
30
|
{ name: 'domain', type: 'string', description: 'Target domain or base zone, for example app.example.com or example.com.' },
|
|
29
31
|
{ name: 'subdomain', type: 'string', description: 'Subdomain to add.' },
|
|
30
32
|
{ name: 'apex', type: 'boolean', description: 'Use the root/apex domain.' },
|
|
@@ -41,12 +43,14 @@ export const commandSchemas = [
|
|
|
41
43
|
examples: [
|
|
42
44
|
'doomain providers connect',
|
|
43
45
|
'doomain providers connect spaceship --credential apiKey=key --credential apiSecret=secret --json',
|
|
46
|
+
'doomain providers connect spaceship --account work --credential apiKey=key --credential apiSecret=secret --json',
|
|
44
47
|
'doomain providers connect namecheap --credential apiUser=user --credential apiKey=key --credential clientIp=127.0.0.1 --json',
|
|
45
48
|
'doomain providers connect cloudflare --credential apiToken=token --credential accountId=account_id --json',
|
|
46
49
|
'doomain providers connect hostinger --credential apiToken=token --json',
|
|
47
50
|
],
|
|
48
51
|
flags: [
|
|
49
52
|
{ name: 'json', type: 'boolean', description: 'Output a single JSON object and never prompt.' },
|
|
53
|
+
{ name: 'account', type: 'string', description: 'DNS provider profile/account alias. Defaults to the provider default account.' },
|
|
50
54
|
{ name: 'credential', type: 'string', description: 'Provider credential as key=value. Can be repeated.' },
|
|
51
55
|
{ name: 'api-key', type: 'string', description: 'Spaceship API key.' },
|
|
52
56
|
{ name: 'api-secret', type: 'string', description: 'Spaceship API secret.' },
|
|
@@ -56,9 +60,10 @@ export const commandSchemas = [
|
|
|
56
60
|
{
|
|
57
61
|
name: 'providers add',
|
|
58
62
|
description: 'Alias for providers connect.',
|
|
59
|
-
examples: ['doomain providers add', 'doomain providers add namecheap'],
|
|
63
|
+
examples: ['doomain providers add', 'doomain providers add namecheap', 'doomain providers add spaceship --account work'],
|
|
60
64
|
flags: [
|
|
61
65
|
{ name: 'json', type: 'boolean', description: 'Output a single JSON object and never prompt.' },
|
|
66
|
+
{ name: 'account', type: 'string', description: 'DNS provider profile/account alias. Defaults to the provider default account.' },
|
|
62
67
|
{ name: 'credential', type: 'string', description: 'Provider credential as key=value. Can be repeated.' },
|
|
63
68
|
{ name: 'no-verify', type: 'boolean', description: 'Save credentials without verifying them first.' },
|
|
64
69
|
],
|
|
@@ -75,17 +80,29 @@ export const commandSchemas = [
|
|
|
75
80
|
{
|
|
76
81
|
name: 'providers disconnect',
|
|
77
82
|
description: 'Remove saved DNS provider credentials locally.',
|
|
78
|
-
examples: [
|
|
83
|
+
examples: [
|
|
84
|
+
'doomain providers disconnect namecheap --json',
|
|
85
|
+
'doomain providers disconnect cloudflare --json',
|
|
86
|
+
'doomain providers disconnect spaceship --account work --json',
|
|
87
|
+
'doomain providers disconnect hostinger --json',
|
|
88
|
+
],
|
|
79
89
|
flags: [
|
|
80
90
|
{ name: 'json', type: 'boolean', description: 'Output a single JSON object and never prompt.' },
|
|
91
|
+
{ name: 'account', type: 'string', description: 'DNS provider profile/account alias. Omit to remove all accounts for the provider.' },
|
|
81
92
|
],
|
|
82
93
|
},
|
|
83
94
|
{
|
|
84
95
|
name: 'providers verify',
|
|
85
96
|
description: 'Verify saved DNS provider credentials.',
|
|
86
|
-
examples: [
|
|
97
|
+
examples: [
|
|
98
|
+
'doomain providers verify spaceship --json',
|
|
99
|
+
'doomain providers verify spaceship --account work --json',
|
|
100
|
+
'doomain providers verify namecheap --json',
|
|
101
|
+
'doomain providers verify hostinger --json',
|
|
102
|
+
],
|
|
87
103
|
flags: [
|
|
88
104
|
{ name: 'json', type: 'boolean', description: 'Output a single JSON object and never prompt.' },
|
|
105
|
+
{ name: 'account', type: 'string', description: 'DNS provider profile/account alias. Defaults to the provider default account.' },
|
|
89
106
|
],
|
|
90
107
|
},
|
|
91
108
|
{
|
|
@@ -114,11 +131,13 @@ export function getCommandSchema(name) {
|
|
|
114
131
|
}
|
|
115
132
|
async function configuredProviders() {
|
|
116
133
|
return (await listProviderStatuses({ verify: false })).map((provider) => ({
|
|
134
|
+
account: provider.account,
|
|
117
135
|
configured: provider.configured,
|
|
118
136
|
default: provider.default,
|
|
119
137
|
displayName: provider.displayName,
|
|
120
138
|
docsUrl: provider.docsUrl,
|
|
121
139
|
id: provider.id,
|
|
140
|
+
isDefaultAccount: provider.isDefaultAccount,
|
|
122
141
|
}));
|
|
123
142
|
}
|
|
124
143
|
function withProviderConnections(schema, providers) {
|