doomain 0.1.23 → 0.1.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/dist/commands/auth/clerk.js +3 -2
  2. package/dist/commands/auth/logout/clerk.js +3 -2
  3. package/dist/commands/auth/logout/vercel.js +3 -2
  4. package/dist/commands/auth/vercel.js +5 -4
  5. package/dist/commands/clerk/domains/add.js +3 -2
  6. package/dist/commands/dns/diagnose.js +3 -2
  7. package/dist/commands/dns/point.js +7 -3
  8. package/dist/commands/dns/remove.js +3 -2
  9. package/dist/commands/domains/find.js +3 -2
  10. package/dist/commands/domains/list.js +46 -33
  11. package/dist/commands/link.js +4 -3
  12. package/dist/commands/projects/list.js +5 -2
  13. package/dist/commands/providers/connect.js +14 -23
  14. package/dist/commands/providers/disconnect.js +3 -2
  15. package/dist/commands/providers/status.js +2 -1
  16. package/dist/commands/providers/verify.js +3 -2
  17. package/dist/commands/schema.js +2 -1
  18. package/dist/commands/update.js +2 -1
  19. package/dist/commands/verify.js +5 -2
  20. package/dist/commands/wizard.js +45 -46
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.js +1 -1
  23. package/dist/lib/clerk-domain.d.ts +2 -1
  24. package/dist/lib/clerk-domain.js +122 -110
  25. package/dist/lib/clerk.d.ts +9 -5
  26. package/dist/lib/clerk.js +45 -36
  27. package/dist/lib/command-schema.d.ts +2 -1
  28. package/dist/lib/command-schema.js +9 -9
  29. package/dist/lib/config.d.ts +6 -4
  30. package/dist/lib/config.js +29 -27
  31. package/dist/lib/diagnose-dns.d.ts +8 -5
  32. package/dist/lib/diagnose-dns.js +90 -92
  33. package/dist/lib/dns-propagation.d.ts +5 -4
  34. package/dist/lib/dns-propagation.js +80 -54
  35. package/dist/lib/dns-reconciliation.d.ts +4 -3
  36. package/dist/lib/dns-reconciliation.js +52 -47
  37. package/dist/lib/domain-provider.d.ts +5 -1
  38. package/dist/lib/domain-provider.js +136 -131
  39. package/dist/lib/effect.d.ts +6 -0
  40. package/dist/lib/effect.js +20 -0
  41. package/dist/lib/link-domain.d.ts +3 -2
  42. package/dist/lib/link-domain.js +239 -218
  43. package/dist/lib/point-domain.d.ts +7 -4
  44. package/dist/lib/point-domain.js +92 -90
  45. package/dist/lib/providers/cloudflare/index.d.ts +9 -8
  46. package/dist/lib/providers/cloudflare/index.js +93 -87
  47. package/dist/lib/providers/core/config.d.ts +4 -1
  48. package/dist/lib/providers/core/config.js +17 -13
  49. package/dist/lib/providers/core/http.d.ts +4 -1
  50. package/dist/lib/providers/core/http.js +28 -18
  51. package/dist/lib/providers/core/pagination.d.ts +3 -2
  52. package/dist/lib/providers/core/pagination.js +17 -14
  53. package/dist/lib/providers/core/planner.d.ts +5 -4
  54. package/dist/lib/providers/core/planner.js +18 -15
  55. package/dist/lib/providers/core/types.d.ts +11 -8
  56. package/dist/lib/providers/hostinger/index.d.ts +9 -8
  57. package/dist/lib/providers/hostinger/index.js +74 -72
  58. package/dist/lib/providers/namecheap/index.d.ts +10 -8
  59. package/dist/lib/providers/namecheap/index.js +134 -109
  60. package/dist/lib/providers/registry.d.ts +3 -3
  61. package/dist/lib/providers/registry.js +7 -3
  62. package/dist/lib/providers/spaceship/index.d.ts +9 -8
  63. package/dist/lib/providers/spaceship/index.js +48 -47
  64. package/dist/lib/providers/status.d.ts +2 -1
  65. package/dist/lib/providers/status.js +41 -34
  66. package/dist/lib/public-ip.d.ts +2 -0
  67. package/dist/lib/public-ip.js +14 -0
  68. package/dist/lib/remove-domain.d.ts +6 -3
  69. package/dist/lib/remove-domain.js +83 -77
  70. package/dist/lib/self-update.d.ts +3 -2
  71. package/dist/lib/self-update.js +15 -23
  72. package/dist/lib/vercel-auth.d.ts +2 -1
  73. package/dist/lib/vercel-auth.js +21 -24
  74. package/dist/lib/vercel.d.ts +16 -12
  75. package/dist/lib/vercel.js +174 -114
  76. package/oclif.manifest.json +1 -1
  77. package/package.json +2 -1
@@ -2,6 +2,7 @@ import * as p from '@clack/prompts';
2
2
  import { Command, Flags } from '@oclif/core';
3
3
  import { createClerkPlatformClient } from '../../lib/clerk.js';
4
4
  import { getConfigPath, maskSecret, updateConfig } from '../../lib/config.js';
5
+ import { runDoomainEffect } from '../../lib/effect.js';
5
6
  import { jsonFlag } from '../../lib/flags.js';
6
7
  import { createOutput, outputError } from '../../lib/output.js';
7
8
  function value(input, message) {
@@ -42,8 +43,8 @@ export default class AuthClerk extends Command {
42
43
  appId = value(appId, 'Missing Clerk application id. Pass --app or set CLERK_APPLICATION_ID.');
43
44
  if (!platformApiKey.startsWith('ak_'))
44
45
  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 } }));
46
+ await runDoomainEffect(createClerkPlatformClient({ platformApiKey }, { transportErrorCode: 'CLERK_AUTH_FAILED' }).fetchApplication(appId));
47
+ await runDoomainEffect(updateConfig((config) => ({ ...config, clerk: { appId, platformApiKey } }), 'CLERK_AUTH_FAILED'));
47
48
  out.result({ clerk: { appId, platformApiKey: maskSecret(platformApiKey) }, configPath: getConfigPath() });
48
49
  out.success(`Clerk credentials saved to ${getConfigPath()}.`);
49
50
  }
@@ -1,5 +1,6 @@
1
1
  import { Command } from '@oclif/core';
2
2
  import { getConfigPath, updateConfig } from '../../../lib/config.js';
3
+ import { runDoomainEffect } from '../../../lib/effect.js';
3
4
  import { jsonFlag } from '../../../lib/flags.js';
4
5
  import { createOutput, outputError } from '../../../lib/output.js';
5
6
  export default class AuthLogoutClerk extends Command {
@@ -10,11 +11,11 @@ export default class AuthLogoutClerk extends Command {
10
11
  const out = createOutput({ json: flags.json });
11
12
  try {
12
13
  let removed = false;
13
- await updateConfig((config) => {
14
+ await runDoomainEffect(updateConfig((config) => {
14
15
  removed = config.clerk !== undefined;
15
16
  const { clerk: _clerk, ...next } = config;
16
17
  return next;
17
- });
18
+ }, 'MISSING_CREDENTIALS'));
18
19
  const environmentOverrides = ['CLERK_PLATFORM_API_KEY', 'CLERK_APPLICATION_ID'].filter((key) => process.env[key]);
19
20
  out.result({ configPath: getConfigPath(), environmentOverrides, removed, service: 'clerk' });
20
21
  if (environmentOverrides.length > 0)
@@ -1,5 +1,6 @@
1
1
  import { Command } from '@oclif/core';
2
2
  import { getConfigPath, updateConfig } from '../../../lib/config.js';
3
+ import { runDoomainEffect } from '../../../lib/effect.js';
3
4
  import { jsonFlag } from '../../../lib/flags.js';
4
5
  import { createOutput, outputError } from '../../../lib/output.js';
5
6
  function envOverrides() {
@@ -16,11 +17,11 @@ export default class AuthLogoutVercel extends Command {
16
17
  const out = createOutput({ json: flags.json });
17
18
  try {
18
19
  let removed = false;
19
- await updateConfig((config) => {
20
+ await runDoomainEffect(updateConfig((config) => {
20
21
  removed = config.vercel !== undefined;
21
22
  const { vercel: _vercel, ...next } = config;
22
23
  return next;
23
- });
24
+ }, 'MISSING_CREDENTIALS'));
24
25
  const overrides = envOverrides();
25
26
  out.result({ configPath: getConfigPath(), environmentOverrides: overrides, removed, service: 'vercel' });
26
27
  if (overrides.length > 0)
@@ -1,6 +1,7 @@
1
1
  import * as p from '@clack/prompts';
2
2
  import { Command, Flags } from '@oclif/core';
3
3
  import { getConfigPath, maskSecret, updateConfig } from '../../lib/config.js';
4
+ import { runDoomainEffect } from '../../lib/effect.js';
4
5
  import { jsonFlag } from '../../lib/flags.js';
5
6
  import { createOutput, outputError } from '../../lib/output.js';
6
7
  import { createVercelClient } from '../../lib/vercel.js';
@@ -61,7 +62,7 @@ export default class AuthVercel extends Command {
61
62
  try {
62
63
  let token = flags.token;
63
64
  let teamId = flags['team-id'] || process.env.VERCEL_TEAM_ID?.trim() || undefined;
64
- const globalTokens = token ? [] : await listGlobalVercelTokens();
65
+ const globalTokens = token ? [] : await runDoomainEffect(listGlobalVercelTokens());
65
66
  if (!token) {
66
67
  if (out.json) {
67
68
  token = globalTokens[0]?.token;
@@ -76,7 +77,7 @@ export default class AuthVercel extends Command {
76
77
  if (!out.json && teamId === undefined) {
77
78
  const spinner = p.spinner();
78
79
  spinner.start('Loading Vercel teams');
79
- const teams = await createVercelClient({ token }).listTeams();
80
+ const teams = await runDoomainEffect(createVercelClient({ token }, { transportErrorCode: 'MISSING_CREDENTIALS' }).listTeams());
80
81
  spinner.stop(`Loaded ${teams.length} Vercel team${teams.length === 1 ? '' : 's'}`);
81
82
  const selected = await p.select({
82
83
  message: 'Select Vercel account/team',
@@ -91,10 +92,10 @@ export default class AuthVercel extends Command {
91
92
  }
92
93
  teamId = selected === PERSONAL_ACCOUNT ? undefined : selected;
93
94
  }
94
- await updateConfig((config) => ({
95
+ await runDoomainEffect(updateConfig((config) => ({
95
96
  ...config,
96
97
  vercel: { token, teamId },
97
- }));
98
+ }), 'MISSING_CREDENTIALS'));
98
99
  out.result({ configPath: getConfigPath(), vercel: { token: maskSecret(token), teamId } });
99
100
  out.success(`Vercel credentials saved to ${getConfigPath()}.`);
100
101
  }
@@ -1,6 +1,7 @@
1
1
  import * as p from '@clack/prompts';
2
2
  import { Args, Command, Flags } from '@oclif/core';
3
3
  import { addClerkProductionDomain } from '../../../lib/clerk-domain.js';
4
+ import { runDoomainEffect } from '../../../lib/effect.js';
4
5
  import { accountFlag, jsonFlag, providerFlag } from '../../../lib/flags.js';
5
6
  import { createOutput, outputError } from '../../../lib/output.js';
6
7
  function recordLine(record) {
@@ -61,7 +62,7 @@ export default class ClerkDomainsAdd extends Command {
61
62
  }
62
63
  spinner = out.spinner();
63
64
  spinner.start(flags['dry-run'] ? 'Checking Clerk production setup' : 'Creating Clerk production setup');
64
- const result = await addClerkProductionDomain({
65
+ const result = await runDoomainEffect(addClerkProductionDomain({
65
66
  account: flags.account,
66
67
  app: flags.app,
67
68
  confirmDnsOverride: out.json
@@ -81,7 +82,7 @@ export default class ClerkDomainsAdd extends Command {
81
82
  provider: flags.provider,
82
83
  timeoutSeconds: flags.timeout,
83
84
  wait: flags.wait,
84
- });
85
+ }));
85
86
  spinner.stop(flags['dry-run'] ? 'Clerk production setup is available' : 'Clerk production domain configured');
86
87
  out.result(result);
87
88
  if (!out.json) {
@@ -1,5 +1,6 @@
1
1
  import { Args, Command, Flags } from '@oclif/core';
2
2
  import { diagnoseDns } from '../../lib/diagnose-dns.js';
3
+ import { runDoomainEffect } from '../../lib/effect.js';
3
4
  import { accountFlag, jsonFlag, providerFlag } from '../../lib/flags.js';
4
5
  import { createOutput, outputError } from '../../lib/output.js';
5
6
  export default class DnsDiagnose extends Command {
@@ -24,13 +25,13 @@ export default class DnsDiagnose extends Command {
24
25
  const spinner = out.json ? undefined : out.spinner();
25
26
  try {
26
27
  spinner?.start(`Diagnosing ${args.domain}`);
27
- const result = await diagnoseDns({
28
+ const result = await runDoomainEffect(diagnoseDns({
28
29
  account: flags.account,
29
30
  domain: args.domain,
30
31
  provider: flags.provider,
31
32
  recordType: flags.type,
32
33
  target: flags.target,
33
- });
34
+ }));
34
35
  spinner?.stop('DNS diagnosis complete');
35
36
  out.result(result);
36
37
  out.info(`Provider: ${result.provider}/${result.account} (${result.zoneDomain})`);
@@ -1,6 +1,7 @@
1
1
  import * as p from '@clack/prompts';
2
2
  import { Args, Command, Flags } from '@oclif/core';
3
3
  import { normalizeDnsValue } from '../../lib/dns-records.js';
4
+ import { runDoomainEffect } from '../../lib/effect.js';
4
5
  import { accountFlag, jsonFlag, providerFlag } from '../../lib/flags.js';
5
6
  import { createOutput, outputError } from '../../lib/output.js';
6
7
  import { pointDomain } from '../../lib/point-domain.js';
@@ -97,14 +98,17 @@ export default class DnsPoint extends Command {
97
98
  spinner = out.spinner();
98
99
  spinner.start(`Pointing ${args.domain}`);
99
100
  }
100
- const result = await pointDomain({
101
+ const result = await runDoomainEffect(pointDomain({
101
102
  account: flags.account,
102
103
  confirmDnsOverride: out.json
103
104
  ? undefined
104
105
  : async (warning) => {
105
106
  spinner?.stop('Existing DNS target found');
106
107
  p.note(conflictNote(warning), 'DNS already points elsewhere');
107
- const confirmed = await p.confirm({ message: `Override DNS for ${warning.domain}?`, initialValue: false });
108
+ const confirmed = await p.confirm({
109
+ message: `Override DNS for ${warning.domain}?`,
110
+ initialValue: false,
111
+ });
108
112
  if (confirmed === true)
109
113
  spinner?.start(`Pointing ${warning.domain}`);
110
114
  return confirmed === true;
@@ -119,7 +123,7 @@ export default class DnsPoint extends Command {
119
123
  timeoutSeconds: flags.timeout,
120
124
  ttl: flags.ttl,
121
125
  wait: flags.wait,
122
- });
126
+ }));
123
127
  if (flags['dry-run']) {
124
128
  out.result(result);
125
129
  if (!out.json)
@@ -1,5 +1,6 @@
1
1
  import * as p from '@clack/prompts';
2
2
  import { Args, Command, Flags } from '@oclif/core';
3
+ import { runDoomainEffect } from '../../lib/effect.js';
3
4
  import { accountFlag, jsonFlag, providerFlag } from '../../lib/flags.js';
4
5
  import { createOutput, outputError } from '../../lib/output.js';
5
6
  import { removeDomain } from '../../lib/remove-domain.js';
@@ -36,7 +37,7 @@ export default class DnsRemove extends Command {
36
37
  const spinner = flags['dry-run'] || out.json ? undefined : out.spinner();
37
38
  try {
38
39
  spinner?.start(`Inspecting ${args.domain}`);
39
- const result = await removeDomain({
40
+ const result = await runDoomainEffect(removeDomain({
40
41
  account: flags.account,
41
42
  allMatching: flags['all-matching'],
42
43
  confirmMultiple: out.json
@@ -58,7 +59,7 @@ export default class DnsRemove extends Command {
58
59
  provider: flags.provider,
59
60
  recordType: flags.type,
60
61
  value: flags.value,
61
- });
62
+ }));
62
63
  spinner?.stop(result.removed === 0 ? 'No matching records found' : 'DNS records removed');
63
64
  out.result(result);
64
65
  if (flags['dry-run'] && !out.json)
@@ -1,5 +1,6 @@
1
1
  import { Args, Command } from '@oclif/core';
2
- import { findDomainProvider } from '../../lib/domain-provider.js';
2
+ import { findDomainProviderEffect } from '../../lib/domain-provider.js';
3
+ import { runDoomainEffect } from '../../lib/effect.js';
3
4
  import { DoomainError } from '../../lib/errors.js';
4
5
  import { accountFlag, domainFlag, jsonFlag, providerFlag } from '../../lib/flags.js';
5
6
  import { createOutput, outputError } from '../../lib/output.js';
@@ -26,7 +27,7 @@ export default class DomainsFind extends Command {
26
27
  const domain = flags.domain ?? args.domain;
27
28
  if (!domain)
28
29
  throw new DoomainError('MISSING_ARGUMENT', 'Domain is required. Pass it as an argument or use --domain.');
29
- const result = await findDomainProvider({ account: flags.account, domain, provider: flags.provider });
30
+ const result = await runDoomainEffect(findDomainProviderEffect({ account: flags.account, domain, provider: flags.provider }));
30
31
  const account = result.isDefaultAccount ? result.provider : `${result.provider}/${result.account}`;
31
32
  out.info(`${result.domain} is managed by ${account} in DNS zone ${result.zoneDomain}.`);
32
33
  for (const warning of result.warnings) {
@@ -1,18 +1,24 @@
1
1
  import { Command } from '@oclif/core';
2
+ import { Effect } from 'effect';
2
3
  import { loadConfig } from '../../lib/config.js';
4
+ import { runDoomainEffect, trySync } from '../../lib/effect.js';
5
+ import { DoomainError } from '../../lib/errors.js';
3
6
  import { accountFlag, domainFlag, jsonFlag, providerFlag } from '../../lib/flags.js';
4
7
  import { createOutput, outputError } from '../../lib/output.js';
5
8
  import { DEFAULT_PROVIDER_ACCOUNT, isDefaultProviderAccount, listConfiguredProviderAccounts, normalizeProviderAccount, } from '../../lib/providers/core/config.js';
6
9
  import { createProvider, getProviderDefinition } from '../../lib/providers/registry.js';
7
10
  import { normalizeDomain } from '../../lib/validate.js';
8
- async function resolveZones(provider, domain) {
11
+ function resolveZones(provider, domain) {
9
12
  if (!domain)
10
13
  return provider.listZones();
11
- const normalized = normalizeDomain(domain);
12
- const zone = await provider.getZone(normalized);
13
- if (!zone)
14
- throw new Error(`${provider.name} does not have a DNS zone for ${normalized}.`);
15
- return [zone];
14
+ return Effect.gen(function* () {
15
+ const normalized = yield* trySync(() => normalizeDomain(domain), 'DOMAIN_LINK_FAILED');
16
+ const zone = yield* provider.getZone(normalized);
17
+ if (!zone) {
18
+ return yield* Effect.fail(new DoomainError('DOMAIN_LINK_FAILED', `${provider.name} does not have a DNS zone for ${normalized}.`));
19
+ }
20
+ return [zone];
21
+ });
16
22
  }
17
23
  export default class DomainsList extends Command {
18
24
  static description = 'List DNS zones and records for a provider.';
@@ -26,36 +32,43 @@ export default class DomainsList extends Command {
26
32
  const { flags } = await this.parse(DomainsList);
27
33
  const out = createOutput({ json: flags.json });
28
34
  try {
29
- const config = await loadConfig();
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
37
- ? accounts
38
- : [{ account: DEFAULT_PROVIDER_ACCOUNT, isDefaultAccount: true, providerId: definition.id }];
39
- const results = [];
40
- for (const selectedAccount of selectedAccounts) {
41
- const provider = await createProvider(definition.id, { account: selectedAccount.account });
42
- const zones = await resolveZones(provider, flags.domain);
43
- for (const zone of zones) {
44
- const records = await provider.listRecords(zone);
45
- results.push({
35
+ const { definition, results, selectedAccounts } = await runDoomainEffect(Effect.gen(function* () {
36
+ const config = yield* loadConfig();
37
+ const providerId = flags.provider ?? process.env.DOOMAIN_PROVIDER ?? config.defaults?.provider ?? 'spaceship';
38
+ const definition = yield* trySync(() => getProviderDefinition(providerId), 'DOMAIN_LINK_FAILED');
39
+ const account = flags.account
40
+ ? yield* trySync(() => normalizeProviderAccount(flags.account), 'DOMAIN_LINK_FAILED')
41
+ : undefined;
42
+ const accounts = account
43
+ ? [{ account, isDefaultAccount: isDefaultProviderAccount(account), providerId: definition.id }]
44
+ : yield* trySync(() => listConfiguredProviderAccounts(config, definition), 'DOMAIN_LINK_FAILED');
45
+ const selectedAccounts = accounts.length > 0
46
+ ? accounts
47
+ : [{ account: DEFAULT_PROVIDER_ACCOUNT, isDefaultAccount: true, providerId: definition.id }];
48
+ const results = [];
49
+ for (const selectedAccount of selectedAccounts) {
50
+ const provider = yield* createProvider(definition.id, {
46
51
  account: selectedAccount.account,
47
- isDefaultAccount: selectedAccount.isDefaultAccount,
48
- provider: provider.id,
49
- records,
50
- zone,
52
+ transportErrorCode: 'DOMAIN_LINK_FAILED',
51
53
  });
52
- const accountLabel = selectedAccount.isDefaultAccount
53
- ? provider.id
54
- : `${provider.id}/${selectedAccount.account}`;
55
- out.info(`${zone.name} (${records.length} records) via ${accountLabel}`);
56
- for (const record of records)
57
- out.info(` ${record.type} ${record.name} -> ${record.value}`);
54
+ const zones = yield* resolveZones(provider, flags.domain);
55
+ for (const zone of zones) {
56
+ results.push({
57
+ account: selectedAccount.account,
58
+ isDefaultAccount: selectedAccount.isDefaultAccount,
59
+ provider: provider.id,
60
+ records: yield* provider.listRecords(zone),
61
+ zone,
62
+ });
63
+ }
58
64
  }
65
+ return { definition, results, selectedAccounts };
66
+ }));
67
+ for (const result of results) {
68
+ const accountLabel = result.isDefaultAccount ? result.provider : `${result.provider}/${result.account}`;
69
+ out.info(`${result.zone.name} (${result.records.length} records) via ${accountLabel}`);
70
+ for (const record of result.records)
71
+ out.info(` ${record.type} ${record.name} -> ${record.value}`);
59
72
  }
60
73
  out.result({
61
74
  ...(selectedAccounts.length === 1
@@ -1,5 +1,6 @@
1
1
  import * as p from '@clack/prompts';
2
2
  import { Args, Command, Flags } from '@oclif/core';
3
+ import { runDoomainEffect } from '../lib/effect.js';
3
4
  import { accountFlag, apexFlag, domainFlag, jsonFlag, projectFlag, providerFlag, subdomainFlag } from '../lib/flags.js';
4
5
  import { linkDomain } from '../lib/link-domain.js';
5
6
  import { createOutput, outputError } from '../lib/output.js';
@@ -77,7 +78,7 @@ export default class Link extends Command {
77
78
  const domain = flags.domain ?? args.domain;
78
79
  try {
79
80
  if (flags['dry-run']) {
80
- const result = await linkDomain({ ...flags, domain, dryRun: true, timeoutSeconds: flags.timeout });
81
+ const result = await runDoomainEffect(linkDomain({ ...flags, domain, dryRun: true, timeoutSeconds: flags.timeout }));
81
82
  out.result(result);
82
83
  if (!out.json) {
83
84
  p.note(dryRunPreview(result), 'Dry run');
@@ -88,7 +89,7 @@ export default class Link extends Command {
88
89
  out.intro('Doomain');
89
90
  spinner = out.spinner();
90
91
  spinner.start('Linking Vercel project and domain');
91
- const result = await linkDomain({
92
+ const result = await runDoomainEffect(linkDomain({
92
93
  ...flags,
93
94
  domain,
94
95
  confirmDnsOverride: out.json
@@ -110,7 +111,7 @@ export default class Link extends Command {
110
111
  dryRun: false,
111
112
  progress: out.json ? undefined : ({ message }) => spinner?.message(message),
112
113
  timeoutSeconds: flags.timeout,
113
- });
114
+ }));
114
115
  spinner.stop(result.vercel.verified ? 'Domain linked and verified' : 'Domain linked');
115
116
  out.result(result);
116
117
  out.outro(result.vercel.verified
@@ -1,4 +1,5 @@
1
1
  import { Command, Flags } from '@oclif/core';
2
+ import { runDoomainEffect } from '../../lib/effect.js';
2
3
  import { jsonFlag } from '../../lib/flags.js';
3
4
  import { createOutput, outputError } from '../../lib/output.js';
4
5
  import { createVercelClient, resolveVercelConfig } from '../../lib/vercel.js';
@@ -12,8 +13,10 @@ export default class ProjectsList extends Command {
12
13
  const { flags } = await this.parse(ProjectsList);
13
14
  const out = createOutput({ json: flags.json });
14
15
  try {
15
- const vercel = createVercelClient(await resolveVercelConfig());
16
- const projects = await vercel.listProjects(flags.search);
16
+ const vercel = createVercelClient(await runDoomainEffect(resolveVercelConfig()), {
17
+ transportErrorCode: 'PROJECT_NOT_FOUND',
18
+ });
19
+ const projects = await runDoomainEffect(vercel.listProjects(flags.search));
17
20
  for (const project of projects)
18
21
  out.info(`${project.name} (${project.id})`);
19
22
  out.result({ projects });
@@ -1,10 +1,12 @@
1
1
  import * as p from '@clack/prompts';
2
2
  import { Args, Command, Flags } from '@oclif/core';
3
3
  import { getConfigPath, loadConfig, maskSecret, updateConfig } from '../../lib/config.js';
4
+ import { runDoomainEffect } from '../../lib/effect.js';
4
5
  import { accountFlag, jsonFlag } from '../../lib/flags.js';
5
6
  import { createOutput, outputError } from '../../lib/output.js';
6
7
  import { DEFAULT_PROVIDER_ACCOUNT, isDefaultProviderAccount, listConfiguredProviderAccounts, normalizeProviderAccount, providerAccountHasCredentials, withProviderAccountCredentials, } from '../../lib/providers/core/config.js';
7
8
  import { getProviderDefinition, listProviderDefinitions } from '../../lib/providers/registry.js';
9
+ import { fetchPublicIp } from '../../lib/public-ip.js';
8
10
  function requireString(value, message) {
9
11
  if (typeof value === 'string' && value.trim())
10
12
  return value.trim();
@@ -25,23 +27,6 @@ function legacyFlagValue(flags, credential) {
25
27
  const value = flags[key];
26
28
  return typeof value === 'string' && value.trim() ? value.trim() : undefined;
27
29
  }
28
- async function fetchPublicIp() {
29
- const controller = new AbortController();
30
- const timeout = setTimeout(() => controller.abort(), 2000);
31
- try {
32
- const response = await fetch('https://api.ipify.org', { signal: controller.signal });
33
- if (!response.ok)
34
- return undefined;
35
- const ip = (await response.text()).trim();
36
- return ip || undefined;
37
- }
38
- catch {
39
- return undefined;
40
- }
41
- finally {
42
- clearTimeout(timeout);
43
- }
44
- }
45
30
  function credentialInitialValue(credential, detectedPublicIp) {
46
31
  if (credential.key !== 'clientIp')
47
32
  return undefined;
@@ -91,7 +76,7 @@ async function confirmProviderAccountOverwrite(definition, account) {
91
76
  return value;
92
77
  }
93
78
  async function promptProvider() {
94
- const config = await loadConfig();
79
+ const config = await runDoomainEffect(loadConfig());
95
80
  const selected = await p.select({
96
81
  message: 'Choose DNS provider',
97
82
  options: listProviderDefinitions().map((definition) => ({
@@ -148,7 +133,7 @@ export default class ProvidersConnect extends Command {
148
133
  if (!account)
149
134
  return;
150
135
  const isDefaultAccount = isDefaultProviderAccount(account);
151
- const currentConfig = await loadConfig();
136
+ const currentConfig = await runDoomainEffect(loadConfig());
152
137
  if (!out.json && providerAccountHasCredentials(currentConfig, definition.id, account)) {
153
138
  const overwrite = await confirmProviderAccountOverwrite(definition, account);
154
139
  if (!overwrite)
@@ -156,7 +141,7 @@ export default class ProvidersConnect extends Command {
156
141
  }
157
142
  const passedCredentials = parseCredentialFlags(flags.credential);
158
143
  const credentials = {};
159
- const detectedPublicIp = !out.json && usesClientIp(definition) ? await fetchPublicIp() : undefined;
144
+ const detectedPublicIp = !out.json && usesClientIp(definition) ? await runDoomainEffect(fetchPublicIp()) : undefined;
160
145
  if (!out.json)
161
146
  showSetupGuide(definition, detectedPublicIp);
162
147
  for (const credential of definition.credentials) {
@@ -189,7 +174,13 @@ export default class ProvidersConnect extends Command {
189
174
  if (!flags['no-verify']) {
190
175
  spinner = out.json ? undefined : out.spinner();
191
176
  spinner?.start(`Verifying ${definition.displayName} credentials`);
192
- const zones = await definition.create({ credentials, debug: process.env.DOOMAIN_DEBUG === '1' }).listZones();
177
+ const zones = await runDoomainEffect(definition
178
+ .create({
179
+ credentials,
180
+ debug: process.env.DOOMAIN_DEBUG === '1',
181
+ transportErrorCode: 'MISSING_CREDENTIALS',
182
+ })
183
+ .listZones());
193
184
  domainCount = zones.length;
194
185
  spinner?.stop(`Verified ${definition.displayName} credentials and found ${domainCount} domain${domainCount === 1 ? '' : 's'}`);
195
186
  spinner = undefined;
@@ -206,14 +197,14 @@ export default class ProvidersConnect extends Command {
206
197
  }
207
198
  setDefault = value;
208
199
  }
209
- await updateConfig((config) => ({
200
+ await runDoomainEffect(updateConfig((config) => ({
210
201
  ...config,
211
202
  defaults: setDefault ? { ...config.defaults, provider: definition.id } : config.defaults,
212
203
  providers: {
213
204
  ...config.providers,
214
205
  [definition.id]: withProviderAccountCredentials(config.providers?.[definition.id], account, credentials),
215
206
  },
216
- }));
207
+ }), 'MISSING_CREDENTIALS'));
217
208
  out.result({
218
209
  account,
219
210
  configPath: getConfigPath(),
@@ -1,5 +1,6 @@
1
1
  import { Args, Command } from '@oclif/core';
2
2
  import { getConfigPath, updateConfig } from '../../lib/config.js';
3
+ import { runDoomainEffect } from '../../lib/effect.js';
3
4
  import { accountFlag, jsonFlag } from '../../lib/flags.js';
4
5
  import { createOutput, outputError } from '../../lib/output.js';
5
6
  import { isDefaultProviderAccount, normalizeProviderAccount } from '../../lib/providers/core/config.js';
@@ -28,7 +29,7 @@ export default class ProvidersDisconnect extends Command {
28
29
  const definition = getProviderDefinition(args.provider);
29
30
  const account = flags.account ? normalizeProviderAccount(flags.account) : undefined;
30
31
  let removed = false;
31
- await updateConfig((config) => {
32
+ await runDoomainEffect(updateConfig((config) => {
32
33
  const providers = { ...config.providers };
33
34
  const provider = providers[definition.id];
34
35
  if (!account) {
@@ -71,7 +72,7 @@ export default class ProvidersDisconnect extends Command {
71
72
  defaults: Object.keys(defaults).length > 0 ? defaults : undefined,
72
73
  providers: Object.keys(providers).length > 0 ? providers : undefined,
73
74
  };
74
- });
75
+ }, 'PROVIDER_NOT_FOUND'));
75
76
  const overrides = envOverrides(definition);
76
77
  out.result({
77
78
  account,
@@ -1,4 +1,5 @@
1
1
  import { Command, Flags } from '@oclif/core';
2
+ import { runDoomainEffect } from '../../lib/effect.js';
2
3
  import { jsonFlag } from '../../lib/flags.js';
3
4
  import { createOutput } from '../../lib/output.js';
4
5
  import { listProviderStatuses } from '../../lib/providers/status.js';
@@ -22,7 +23,7 @@ export default class ProvidersStatus extends Command {
22
23
  const out = createOutput({ json: flags.json });
23
24
  const spinner = out.json || flags['no-verify'] ? undefined : out.spinner();
24
25
  spinner?.start('Checking DNS providers');
25
- const providers = await listProviderStatuses({ verify: !flags['no-verify'] });
26
+ const providers = await runDoomainEffect(listProviderStatuses({ verify: !flags['no-verify'] }));
26
27
  spinner?.stop('Checked DNS providers');
27
28
  for (const provider of providers) {
28
29
  out.info(`${provider.displayName} (${provider.accountLabel}) - ${formatStatus(provider)}${provider.isPreferredProvider ? ' [preferred provider]' : ''}`);
@@ -1,4 +1,5 @@
1
1
  import { Args, Command } from '@oclif/core';
2
+ import { runDoomainEffect } from '../../lib/effect.js';
2
3
  import { accountFlag, jsonFlag } from '../../lib/flags.js';
3
4
  import { createOutput, outputError } from '../../lib/output.js';
4
5
  import { isDefaultProviderAccount, normalizeProviderAccount } from '../../lib/providers/core/config.js';
@@ -17,8 +18,8 @@ export default class ProvidersVerify extends Command {
17
18
  const out = createOutput({ json: flags.json });
18
19
  try {
19
20
  const account = normalizeProviderAccount(flags.account);
20
- const provider = await createProvider(args.provider, { account });
21
- const health = await provider.verifyCredentials();
21
+ const provider = await runDoomainEffect(createProvider(args.provider, { account, transportErrorCode: 'PROVIDER_AUTH_FAILED' }));
22
+ const health = await runDoomainEffect(provider.verifyCredentials());
22
23
  out.result({ account, health, isDefaultAccount: isDefaultProviderAccount(account), provider: provider.id });
23
24
  out.success(`${provider.name} credentials verified.`);
24
25
  }
@@ -1,5 +1,6 @@
1
1
  import { Args, Command } from '@oclif/core';
2
2
  import { getCommandSchemaForAgents } from '../lib/command-schema.js';
3
+ import { runDoomainEffect } from '../lib/effect.js';
3
4
  import { jsonFlag } from '../lib/flags.js';
4
5
  import { createOutput, outputError } from '../lib/output.js';
5
6
  export default class Schema extends Command {
@@ -14,7 +15,7 @@ export default class Schema extends Command {
14
15
  const { args, flags } = await this.parse(Schema);
15
16
  const out = createOutput({ json: flags.json });
16
17
  try {
17
- const schema = await getCommandSchemaForAgents(args.command);
18
+ const schema = await runDoomainEffect(getCommandSchemaForAgents(args.command));
18
19
  if (!schema)
19
20
  throw new Error(`Unknown command schema: ${args.command}`);
20
21
  if (!out.json)
@@ -1,4 +1,5 @@
1
1
  import { Command } from '@oclif/core';
2
+ import { runDoomainEffect } from '../lib/effect.js';
2
3
  import { jsonFlag } from '../lib/flags.js';
3
4
  import { createOutput, outputError } from '../lib/output.js';
4
5
  import { installLatestVersion } from '../lib/self-update.js';
@@ -14,7 +15,7 @@ export default class Update extends Command {
14
15
  const spinner = out.spinner();
15
16
  try {
16
17
  spinner.start('Downloading the latest doomain version from npm');
17
- const result = await installLatestVersion();
18
+ const result = await runDoomainEffect(installLatestVersion());
18
19
  spinner.stop('Installed the latest doomain version');
19
20
  out.result(result);
20
21
  out.success('Installed the latest doomain version from npm.');
@@ -1,4 +1,5 @@
1
1
  import { Command } from '@oclif/core';
2
+ import { runDoomainEffect } from '../lib/effect.js';
2
3
  import { apexFlag, domainFlag, jsonFlag, projectFlag, subdomainFlag } from '../lib/flags.js';
3
4
  import { detectLocalVercelProject } from '../lib/local-vercel.js';
4
5
  import { createOutput, outputError } from '../lib/output.js';
@@ -23,8 +24,10 @@ export default class Verify extends Command {
23
24
  const project = flags.project ?? detectLocalVercelProject()?.projectId;
24
25
  if (!project)
25
26
  throw new Error('Missing --project and no local .vercel/project.json was found.');
26
- const vercel = createVercelClient(await resolveVercelConfig());
27
- const result = await vercel.verifyProjectDomain(project, target.fullDomain);
27
+ const vercel = createVercelClient(await runDoomainEffect(resolveVercelConfig()), {
28
+ transportErrorCode: 'DOMAIN_VERIFY_FAILED',
29
+ });
30
+ const result = await runDoomainEffect(vercel.verifyProjectDomain(project, target.fullDomain));
28
31
  out.result({ project, domain: target.fullDomain, vercel: result });
29
32
  out.success(`Verification requested for ${target.fullDomain}.`);
30
33
  }