doomain 0.1.22 → 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 (80) hide show
  1. package/README.md +1 -1
  2. package/dist/commands/auth/clerk.js +3 -2
  3. package/dist/commands/auth/logout/clerk.js +3 -2
  4. package/dist/commands/auth/logout/vercel.js +3 -2
  5. package/dist/commands/auth/vercel.js +5 -4
  6. package/dist/commands/clerk/domains/add.js +3 -2
  7. package/dist/commands/dns/diagnose.js +3 -2
  8. package/dist/commands/dns/point.js +7 -3
  9. package/dist/commands/dns/remove.js +3 -2
  10. package/dist/commands/domains/find.js +3 -2
  11. package/dist/commands/domains/list.js +46 -33
  12. package/dist/commands/link.js +4 -3
  13. package/dist/commands/projects/list.js +5 -2
  14. package/dist/commands/providers/connect.js +14 -23
  15. package/dist/commands/providers/disconnect.js +3 -2
  16. package/dist/commands/providers/status.js +2 -1
  17. package/dist/commands/providers/verify.js +3 -2
  18. package/dist/commands/schema.js +2 -1
  19. package/dist/commands/update.js +2 -1
  20. package/dist/commands/verify.js +5 -2
  21. package/dist/commands/version.d.ts +5 -0
  22. package/dist/commands/version.js +8 -0
  23. package/dist/commands/wizard.js +45 -46
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.js +1 -1
  26. package/dist/lib/clerk-domain.d.ts +2 -1
  27. package/dist/lib/clerk-domain.js +122 -110
  28. package/dist/lib/clerk.d.ts +9 -5
  29. package/dist/lib/clerk.js +45 -36
  30. package/dist/lib/command-schema.d.ts +2 -1
  31. package/dist/lib/command-schema.js +16 -9
  32. package/dist/lib/config.d.ts +6 -4
  33. package/dist/lib/config.js +29 -27
  34. package/dist/lib/diagnose-dns.d.ts +8 -5
  35. package/dist/lib/diagnose-dns.js +90 -92
  36. package/dist/lib/dns-propagation.d.ts +5 -4
  37. package/dist/lib/dns-propagation.js +80 -54
  38. package/dist/lib/dns-reconciliation.d.ts +4 -3
  39. package/dist/lib/dns-reconciliation.js +52 -47
  40. package/dist/lib/domain-provider.d.ts +5 -1
  41. package/dist/lib/domain-provider.js +136 -131
  42. package/dist/lib/effect.d.ts +6 -0
  43. package/dist/lib/effect.js +20 -0
  44. package/dist/lib/link-domain.d.ts +3 -2
  45. package/dist/lib/link-domain.js +239 -218
  46. package/dist/lib/point-domain.d.ts +7 -4
  47. package/dist/lib/point-domain.js +92 -90
  48. package/dist/lib/providers/cloudflare/index.d.ts +9 -8
  49. package/dist/lib/providers/cloudflare/index.js +93 -87
  50. package/dist/lib/providers/core/config.d.ts +4 -1
  51. package/dist/lib/providers/core/config.js +17 -13
  52. package/dist/lib/providers/core/http.d.ts +4 -1
  53. package/dist/lib/providers/core/http.js +28 -18
  54. package/dist/lib/providers/core/pagination.d.ts +3 -2
  55. package/dist/lib/providers/core/pagination.js +17 -14
  56. package/dist/lib/providers/core/planner.d.ts +5 -4
  57. package/dist/lib/providers/core/planner.js +18 -15
  58. package/dist/lib/providers/core/types.d.ts +11 -8
  59. package/dist/lib/providers/hostinger/index.d.ts +9 -8
  60. package/dist/lib/providers/hostinger/index.js +74 -72
  61. package/dist/lib/providers/namecheap/index.d.ts +10 -8
  62. package/dist/lib/providers/namecheap/index.js +134 -109
  63. package/dist/lib/providers/registry.d.ts +3 -3
  64. package/dist/lib/providers/registry.js +7 -3
  65. package/dist/lib/providers/spaceship/index.d.ts +9 -8
  66. package/dist/lib/providers/spaceship/index.js +48 -47
  67. package/dist/lib/providers/status.d.ts +2 -1
  68. package/dist/lib/providers/status.js +41 -34
  69. package/dist/lib/public-ip.d.ts +2 -0
  70. package/dist/lib/public-ip.js +14 -0
  71. package/dist/lib/remove-domain.d.ts +6 -3
  72. package/dist/lib/remove-domain.js +83 -77
  73. package/dist/lib/self-update.d.ts +3 -2
  74. package/dist/lib/self-update.js +15 -23
  75. package/dist/lib/vercel-auth.d.ts +2 -1
  76. package/dist/lib/vercel-auth.js +21 -24
  77. package/dist/lib/vercel.d.ts +16 -12
  78. package/dist/lib/vercel.js +174 -114
  79. package/oclif.manifest.json +21 -1
  80. package/package.json +8 -1
@@ -1,9 +1,14 @@
1
+ import { Effect } from 'effect';
1
2
  import { reconcileRecordRemoval } from './dns-reconciliation.js';
2
3
  import { recordMatchesSelector } from './dns-records.js';
3
4
  import { resolveProviderTarget } from './domain-provider.js';
5
+ import { tryPromise } from './effect.js';
4
6
  import { DoomainError } from './errors.js';
5
7
  import { createProvider } from './providers/registry.js';
6
- const defaultDependencies = { createProvider, resolveTarget: resolveProviderTarget };
8
+ const defaultDependencies = {
9
+ createProvider,
10
+ resolveTarget: (input) => resolveProviderTarget(input, { transportErrorCode: 'DNS_REMOVE_FAILED' }),
11
+ };
7
12
  function quoteCommandArgument(value) {
8
13
  return /^[a-zA-Z0-9_./:@+-]+$/.test(value) ? value : `'${value.replaceAll("'", `'"'"'`)}'`;
9
14
  }
@@ -41,85 +46,86 @@ function ambiguousDeletionError(input, records) {
41
46
  suggestedCommands: [removalCommand(input, { allMatching: true, dryRun: true })],
42
47
  });
43
48
  }
44
- export async function removeDomain(input, dependencies = defaultDependencies) {
45
- if (!input.allMatching && input.value === undefined) {
46
- throw new DoomainError('INVALID_INPUT', 'An exact --value is required unless --all-matching is passed.', {
47
- recovery: 'Pass --value <expected-value>, or preview --all-matching with --dry-run.',
49
+ export function removeDomain(input, dependencies = defaultDependencies) {
50
+ return Effect.gen(function* () {
51
+ if (!input.allMatching && input.value === undefined) {
52
+ return yield* Effect.fail(new DoomainError('INVALID_INPUT', 'An exact --value is required unless --all-matching is passed.', {
53
+ recovery: 'Pass --value <expected-value>, or preview --all-matching with --dry-run.',
54
+ }));
55
+ }
56
+ const resolved = yield* dependencies
57
+ .resolveTarget(input)
58
+ .pipe(Effect.mapError((error) => removalResolutionError(error, input)));
59
+ const provider = yield* dependencies.createProvider(resolved.provider, {
60
+ account: resolved.account,
61
+ transportErrorCode: 'DNS_REMOVE_FAILED',
62
+ });
63
+ if (!provider.capabilities.recordTypes.includes(input.recordType)) {
64
+ return yield* Effect.fail(new DoomainError('PROVIDER_UNSUPPORTED_RECORD', `${provider.name} does not support ${input.recordType} records.`));
65
+ }
66
+ const zone = yield* provider.getZone(resolved.target.zoneDomain);
67
+ if (!zone) {
68
+ return yield* Effect.fail(new DoomainError('PROVIDER_ZONE_NOT_FOUND', `${provider.name} does not have a DNS zone for ${resolved.target.zoneDomain}.`));
69
+ }
70
+ const selector = {
71
+ name: resolved.target.recordName,
72
+ type: input.recordType,
73
+ ...(input.value === undefined ? {} : { value: input.value }),
74
+ };
75
+ const existing = yield* provider.listRecords(zone);
76
+ const matched = existing.filter((record) => recordMatchesSelector(record, selector));
77
+ if (matched.length > 1 && !input.allMatching) {
78
+ const confirmed = input.confirmMultiple
79
+ ? yield* tryPromise(() => input.confirmMultiple?.(matched) ?? Promise.resolve(false), 'DNS_REMOVE_FAILED')
80
+ : false;
81
+ if (!confirmed)
82
+ return yield* Effect.fail(ambiguousDeletionError(input, matched));
83
+ }
84
+ const base = {
85
+ account: resolved.account,
86
+ accountInferred: resolved.accountInferred,
87
+ domain: resolved.target.fullDomain,
88
+ isDefaultAccount: resolved.isDefaultAccount,
89
+ matched,
90
+ provider: resolved.provider,
91
+ providerInferred: resolved.providerInferred,
92
+ selector,
93
+ zoneDomain: resolved.target.zoneDomain,
94
+ };
95
+ if (input.dryRun) {
96
+ return {
97
+ ...base,
98
+ dryRun: true,
99
+ reconciled: false,
100
+ reconciliationAttempts: 0,
101
+ removed: 0,
102
+ };
103
+ }
104
+ if (matched.length === 0) {
105
+ return { ...base, dryRun: false, reconciled: true, reconciliationAttempts: 1, removed: 0 };
106
+ }
107
+ input.progress?.(`Deleting ${matched.length} DNS record${matched.length === 1 ? '' : 's'}`);
108
+ const plan = {
109
+ changes: matched.map((record) => ({ action: 'delete', existing: record })),
110
+ conflicts: [],
111
+ desired: [],
112
+ existing,
113
+ zone,
114
+ };
115
+ yield* provider.applyChanges(zone, plan, { force: true });
116
+ const reconciliation = yield* reconcileRecordRemoval({
117
+ progress: input.progress,
118
+ provider,
119
+ selector,
120
+ timeoutMs: (input.reconcileTimeoutSeconds ?? 30) * 1000,
121
+ zone,
48
122
  });
49
- }
50
- let resolved;
51
- try {
52
- resolved = await dependencies.resolveTarget(input);
53
- }
54
- catch (error) {
55
- if (error instanceof DoomainError)
56
- throw removalResolutionError(error, input);
57
- throw error;
58
- }
59
- const provider = await dependencies.createProvider(resolved.provider, { account: resolved.account });
60
- if (!provider.capabilities.recordTypes.includes(input.recordType)) {
61
- throw new DoomainError('PROVIDER_UNSUPPORTED_RECORD', `${provider.name} does not support ${input.recordType} records.`);
62
- }
63
- const zone = await provider.getZone(resolved.target.zoneDomain);
64
- if (!zone) {
65
- throw new DoomainError('PROVIDER_ZONE_NOT_FOUND', `${provider.name} does not have a DNS zone for ${resolved.target.zoneDomain}.`);
66
- }
67
- const selector = {
68
- name: resolved.target.recordName,
69
- type: input.recordType,
70
- ...(input.value === undefined ? {} : { value: input.value }),
71
- };
72
- const existing = await provider.listRecords(zone);
73
- const matched = existing.filter((record) => recordMatchesSelector(record, selector));
74
- if (matched.length > 1 && !input.allMatching) {
75
- const confirmed = (await input.confirmMultiple?.(matched)) === true;
76
- if (!confirmed)
77
- throw ambiguousDeletionError(input, matched);
78
- }
79
- const base = {
80
- account: resolved.account,
81
- accountInferred: resolved.accountInferred,
82
- domain: resolved.target.fullDomain,
83
- isDefaultAccount: resolved.isDefaultAccount,
84
- matched,
85
- provider: resolved.provider,
86
- providerInferred: resolved.providerInferred,
87
- selector,
88
- zoneDomain: resolved.target.zoneDomain,
89
- };
90
- if (input.dryRun) {
91
123
  return {
92
124
  ...base,
93
- dryRun: true,
94
- reconciled: false,
95
- reconciliationAttempts: 0,
96
- removed: 0,
125
+ dryRun: false,
126
+ reconciled: reconciliation.reconciled,
127
+ reconciliationAttempts: reconciliation.attempts,
128
+ removed: matched.length,
97
129
  };
98
- }
99
- if (matched.length === 0) {
100
- return { ...base, dryRun: false, reconciled: true, reconciliationAttempts: 1, removed: 0 };
101
- }
102
- input.progress?.(`Deleting ${matched.length} DNS record${matched.length === 1 ? '' : 's'}`);
103
- const plan = {
104
- changes: matched.map((record) => ({ action: 'delete', existing: record })),
105
- conflicts: [],
106
- desired: [],
107
- existing,
108
- zone,
109
- };
110
- await provider.applyChanges(zone, plan, { force: true });
111
- const reconciliation = await reconcileRecordRemoval({
112
- progress: input.progress,
113
- provider,
114
- selector,
115
- timeoutMs: (input.reconcileTimeoutSeconds ?? 30) * 1000,
116
- zone,
117
130
  });
118
- return {
119
- ...base,
120
- dryRun: false,
121
- reconciled: reconciliation.reconciled,
122
- reconciliationAttempts: reconciliation.attempts,
123
- removed: matched.length,
124
- };
125
131
  }
@@ -1,3 +1,4 @@
1
+ import type { DoomainEffect } from './effect.js';
1
2
  interface ProcessResult {
2
3
  exitCode: number | null;
3
4
  stderr: string;
@@ -7,7 +8,7 @@ interface ProcessOptions {
7
8
  env: NodeJS.ProcessEnv;
8
9
  shell: boolean;
9
10
  }
10
- type ProcessRunner = (command: string, args: string[], options: ProcessOptions) => Promise<ProcessResult>;
11
+ type ProcessRunner = (command: string, args: string[], options: ProcessOptions) => DoomainEffect<ProcessResult>;
11
12
  export interface SelfUpdateResult {
12
13
  package: string;
13
14
  packageSpec: string;
@@ -23,5 +24,5 @@ export declare function npmInstallCommand(cacheDirectory: string, platform?: Nod
23
24
  command: string;
24
25
  options: ProcessOptions;
25
26
  };
26
- export declare function installLatestVersion(options?: SelfUpdateOptions): Promise<SelfUpdateResult>;
27
+ export declare function installLatestVersion(options?: SelfUpdateOptions): DoomainEffect<SelfUpdateResult>;
27
28
  export {};
@@ -2,9 +2,10 @@ import { spawn } from 'node:child_process';
2
2
  import { mkdtemp, rm } from 'node:fs/promises';
3
3
  import { tmpdir } from 'node:os';
4
4
  import { join } from 'node:path';
5
+ import { Effect } from 'effect';
5
6
  import { DoomainError } from './errors.js';
6
7
  function runProcess(command, args, options) {
7
- return new Promise((resolve, reject) => {
8
+ return Effect.async((resume) => {
8
9
  const child = spawn(command, args, {
9
10
  ...options,
10
11
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -20,8 +21,9 @@ function runProcess(command, args, options) {
20
21
  child.stdout.on('data', (chunk) => {
21
22
  stdout += chunk;
22
23
  });
23
- child.once('error', reject);
24
- child.once('close', (exitCode) => resolve({ exitCode, stderr, stdout }));
24
+ child.once('error', (cause) => resume(Effect.fail(new DoomainError('SELF_UPDATE_FAILED', `Unable to start npm: ${cause.message}`, cause))));
25
+ child.once('close', (exitCode) => resume(Effect.succeed({ exitCode, stderr, stdout })));
26
+ return Effect.sync(() => child.kill());
25
27
  });
26
28
  }
27
29
  export function npmInstallCommand(cacheDirectory, platform = process.platform, environment = process.env) {
@@ -36,28 +38,18 @@ export function npmInstallCommand(cacheDirectory, platform = process.platform, e
36
38
  },
37
39
  };
38
40
  }
39
- export async function installLatestVersion(options = {}) {
40
- const cacheDirectory = await mkdtemp(join(options.cacheRoot ?? tmpdir(), 'doomain-npm-cache-'));
41
- try {
41
+ export function installLatestVersion(options = {}) {
42
+ const acquire = Effect.tryPromise({
43
+ try: () => mkdtemp(join(options.cacheRoot ?? tmpdir(), 'doomain-npm-cache-')),
44
+ catch: (cause) => new DoomainError('SELF_UPDATE_FAILED', `Unable to create npm cache: ${String(cause)}`, cause),
45
+ });
46
+ return Effect.acquireUseRelease(acquire, (cacheDirectory) => Effect.gen(function* () {
42
47
  const invocation = npmInstallCommand(cacheDirectory, options.platform);
43
- const result = await (options.runner ?? runProcess)(invocation.command, invocation.args, invocation.options);
48
+ const result = yield* (options.runner ?? runProcess)(invocation.command, invocation.args, invocation.options);
44
49
  if (result.exitCode !== 0) {
45
50
  const reason = result.stderr.trim() || result.stdout.trim() || `npm exited with code ${result.exitCode ?? 'unknown'}`;
46
- throw new DoomainError('SELF_UPDATE_FAILED', `Unable to update doomain: ${reason}`);
51
+ return yield* Effect.fail(new DoomainError('SELF_UPDATE_FAILED', `Unable to update doomain: ${reason}`));
47
52
  }
48
- return {
49
- package: 'doomain',
50
- packageSpec: 'doomain@latest',
51
- packageManager: 'npm',
52
- };
53
- }
54
- catch (error) {
55
- if (error instanceof DoomainError)
56
- throw error;
57
- const message = error instanceof Error ? error.message : String(error);
58
- throw new DoomainError('SELF_UPDATE_FAILED', `Unable to update doomain: ${message}`);
59
- }
60
- finally {
61
- await rm(cacheDirectory, { force: true, recursive: true }).catch(() => undefined);
62
- }
53
+ return { package: 'doomain', packageSpec: 'doomain@latest', packageManager: 'npm' };
54
+ }), (cacheDirectory) => Effect.tryPromise(() => rm(cacheDirectory, { force: true, recursive: true })).pipe(Effect.ignore));
63
55
  }
@@ -1,3 +1,4 @@
1
+ import type { DoomainEffect } from './effect.js';
1
2
  export type GlobalVercelTokenSource = 'environment' | 'vercel-cli';
2
3
  export interface GlobalVercelToken {
3
4
  authFile?: string;
@@ -10,4 +11,4 @@ export declare function listGlobalVercelTokens(opts?: {
10
11
  authFile?: string;
11
12
  authFiles?: string[];
12
13
  env?: NodeJS.ProcessEnv;
13
- }): Promise<GlobalVercelToken[]>;
14
+ }): DoomainEffect<GlobalVercelToken[], never>;
@@ -2,6 +2,7 @@ import { existsSync } from 'node:fs';
2
2
  import { readFile } from 'node:fs/promises';
3
3
  import { homedir } from 'node:os';
4
4
  import { delimiter, join } from 'node:path';
5
+ import { Effect } from 'effect';
5
6
  function xdgDataHome(env) {
6
7
  if (env.XDG_DATA_HOME)
7
8
  return env.XDG_DATA_HOME;
@@ -25,30 +26,26 @@ export function getVercelCliAuthFiles(env = process.env) {
25
26
  join(homedir(), '.vercel'),
26
27
  ].map((dir) => join(dir, 'auth.json'));
27
28
  }
28
- async function readVercelCliToken(authFile) {
29
- try {
30
- const data = JSON.parse(await readFile(authFile, 'utf8'));
31
- return typeof data.token === 'string' && data.token.trim() ? data.token.trim() : undefined;
32
- }
33
- catch {
34
- return undefined;
35
- }
29
+ function readVercelCliToken(authFile) {
30
+ return Effect.tryPromise(() => readFile(authFile, 'utf8')).pipe(Effect.flatMap((contents) => Effect.try(() => JSON.parse(contents))), Effect.map((data) => (typeof data.token === 'string' && data.token.trim() ? data.token.trim() : undefined)), Effect.catchAll(() => Effect.succeed(undefined)));
36
31
  }
37
- export async function listGlobalVercelTokens(opts = {}) {
38
- const env = opts.env ?? process.env;
39
- const tokens = [];
40
- const envToken = env.VERCEL_TOKEN?.trim();
41
- if (envToken) {
42
- tokens.push({ label: 'VERCEL_TOKEN', source: 'environment', token: envToken });
43
- }
44
- const authFiles = opts.authFile ? [opts.authFile] : (opts.authFiles ?? getVercelCliAuthFiles(env));
45
- for (const authFile of authFiles) {
46
- if (!existsSync(authFile))
47
- continue;
48
- const cliToken = await readVercelCliToken(authFile);
49
- if (cliToken && !tokens.some((item) => item.token === cliToken)) {
50
- tokens.push({ authFile, label: 'Vercel CLI', source: 'vercel-cli', token: cliToken });
32
+ export function listGlobalVercelTokens(opts = {}) {
33
+ return Effect.gen(function* () {
34
+ const env = opts.env ?? process.env;
35
+ const tokens = [];
36
+ const envToken = env.VERCEL_TOKEN?.trim();
37
+ if (envToken) {
38
+ tokens.push({ label: 'VERCEL_TOKEN', source: 'environment', token: envToken });
51
39
  }
52
- }
53
- return tokens;
40
+ const authFiles = opts.authFile ? [opts.authFile] : (opts.authFiles ?? getVercelCliAuthFiles(env));
41
+ for (const authFile of authFiles) {
42
+ if (!existsSync(authFile))
43
+ continue;
44
+ const cliToken = yield* readVercelCliToken(authFile);
45
+ if (cliToken && !tokens.some((item) => item.token === cliToken)) {
46
+ tokens.push({ authFile, label: 'Vercel CLI', source: 'vercel-cli', token: cliToken });
47
+ }
48
+ }
49
+ return tokens;
50
+ });
54
51
  }
@@ -1,3 +1,5 @@
1
+ import type { DoomainEffect } from './effect.js';
2
+ import { type DoomainErrorCode } from './errors.js';
1
3
  export declare const VERCEL_APEX_A_RECORD = "76.76.21.21";
2
4
  export declare const VERCEL_CNAME_RECORD = "cname.vercel-dns.com";
3
5
  export interface VercelProject {
@@ -23,26 +25,28 @@ interface VercelProjectDomainOwner {
23
25
  };
24
26
  project: VercelProject;
25
27
  }
26
- export declare function resolveVercelConfig(): Promise<VercelConfig>;
27
- export declare function createVercelClient(config: VercelConfig): {
28
- listTeams(): Promise<VercelTeam[]>;
29
- listProjects(search?: string): Promise<VercelProject[]>;
28
+ export declare function resolveVercelConfig(): DoomainEffect<VercelConfig>;
29
+ export declare function createVercelClient(config: VercelConfig, clientOpts?: {
30
+ transportErrorCode?: DoomainErrorCode;
31
+ }): {
32
+ listTeams(): DoomainEffect<VercelTeam[]>;
33
+ listProjects(search?: string): DoomainEffect<VercelProject[]>;
30
34
  addDomainToProject(project: string, domain: string, opts?: {
31
35
  force?: boolean;
32
- }): Promise<{
36
+ }): DoomainEffect<{
33
37
  alreadyAdded: boolean;
34
38
  raw?: unknown;
35
39
  }>;
36
- findProjectDomainOwner(domain: string): Promise<VercelProjectDomainOwner | undefined>;
37
- getDomainConfig(domain: string): Promise<Record<string, unknown>>;
38
- getRecommendedCname(domain: string): Promise<string>;
39
- getProjectDomain(project: string, domain: string): Promise<Record<string, unknown>>;
40
- listProjectDomains(project: string): Promise<Array<Record<string, unknown> & {
40
+ findProjectDomainOwner(domain: string): DoomainEffect<VercelProjectDomainOwner | undefined>;
41
+ getDomainConfig(domain: string): DoomainEffect<Record<string, unknown>>;
42
+ getRecommendedCname(domain: string): DoomainEffect<string>;
43
+ getProjectDomain(project: string, domain: string): DoomainEffect<Record<string, unknown>>;
44
+ listProjectDomains(project: string): DoomainEffect<Array<Record<string, unknown> & {
41
45
  name?: string;
42
46
  projectId?: string;
43
47
  }>>;
44
- removeDomainFromProject(project: string, domain: string): Promise<void>;
45
- verifyProjectDomain(project: string, domain: string): Promise<Record<string, unknown>>;
48
+ removeDomainFromProject(project: string, domain: string): DoomainEffect<void>;
49
+ verifyProjectDomain(project: string, domain: string): DoomainEffect<Record<string, unknown>>;
46
50
  };
47
51
  export type VercelClient = ReturnType<typeof createVercelClient>;
48
52
  export {};