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,17 +1,20 @@
1
+ import { Effect } from 'effect';
1
2
  import { loadConfig } from './config.js';
2
- import { DoomainError } from './errors.js';
3
+ import { DoomainError, toDoomainError } from './errors.js';
3
4
  import { listGlobalVercelTokens } from './vercel-auth.js';
4
5
  const VERCEL_API_URL = 'https://api.vercel.com';
5
6
  export const VERCEL_APEX_A_RECORD = '76.76.21.21';
6
7
  export const VERCEL_CNAME_RECORD = 'cname.vercel-dns.com';
7
- export async function resolveVercelConfig() {
8
- const config = await loadConfig();
9
- const token = process.env.VERCEL_TOKEN || config.vercel?.token || (await listGlobalVercelTokens())[0]?.token;
10
- const teamId = process.env.VERCEL_TEAM_ID || config.vercel?.teamId;
11
- if (!token) {
12
- throw new DoomainError('MISSING_CREDENTIALS', 'Missing Vercel token. Run `doomain auth vercel`, set VERCEL_TOKEN, or sign in with Vercel CLI.');
13
- }
14
- return { token, teamId };
8
+ export function resolveVercelConfig() {
9
+ return Effect.gen(function* () {
10
+ const config = yield* loadConfig();
11
+ const token = process.env.VERCEL_TOKEN || config.vercel?.token || (yield* listGlobalVercelTokens())[0]?.token;
12
+ const teamId = process.env.VERCEL_TEAM_ID || config.vercel?.teamId;
13
+ if (!token) {
14
+ return yield* Effect.fail(new DoomainError('MISSING_CREDENTIALS', 'Missing Vercel token. Run `doomain auth vercel`, set VERCEL_TOKEN, or sign in with Vercel CLI.'));
15
+ }
16
+ return { token, teamId };
17
+ });
15
18
  }
16
19
  function appendTeam(path, teamId) {
17
20
  if (!teamId)
@@ -50,140 +53,197 @@ function findProjectDomainTarget(raw, domain) {
50
53
  (isSameDomain(target.domain, domain) ||
51
54
  isSameDomain(target.name, domain)));
52
55
  }
53
- export function createVercelClient(config) {
54
- async function request(path, init = {}, opts = {}) {
55
- const response = await fetch(`${VERCEL_API_URL}${opts.team === false ? path : appendTeam(path, config.teamId)}`, {
56
- ...init,
57
- headers: {
58
- Authorization: `Bearer ${config.token}`,
59
- 'Content-Type': 'application/json',
60
- ...(init.headers ?? {}),
61
- },
56
+ export function createVercelClient(config, clientOpts = {}) {
57
+ const decodeErrorCode = clientOpts.transportErrorCode ?? 'DOMAIN_LINK_FAILED';
58
+ function decode(label, value, validate) {
59
+ return Effect.try({
60
+ try: () => validate(value),
61
+ catch: (cause) => new DoomainError(decodeErrorCode, `Vercel returned an invalid ${label} response.`, {
62
+ cause: cause instanceof Error ? cause.message : String(cause),
63
+ response: value,
64
+ }),
65
+ });
66
+ }
67
+ function recordResponse(label, value) {
68
+ return decode(label, value, (candidate) => {
69
+ if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate))
70
+ throw new TypeError('Expected an object.');
71
+ return candidate;
62
72
  });
63
- if (!response.ok) {
64
- const body = (await response.json().catch(() => undefined));
65
- if (response.status === 401) {
66
- throw new DoomainError('VERCEL_AUTH_FAILED', vercelAuthErrorMessage(body), body);
73
+ }
74
+ function request(path, init = {}, opts = {}) {
75
+ return Effect.gen(function* () {
76
+ const response = yield* Effect.tryPromise({
77
+ try: (signal) => fetch(`${VERCEL_API_URL}${opts.team === false ? path : appendTeam(path, config.teamId)}`, {
78
+ ...init,
79
+ headers: {
80
+ Authorization: `Bearer ${config.token}`,
81
+ 'Content-Type': 'application/json',
82
+ ...(init.headers ?? {}),
83
+ },
84
+ signal: init.signal ?? signal,
85
+ }),
86
+ catch: (cause) => toDoomainError(cause, clientOpts.transportErrorCode ?? 'DOMAIN_LINK_FAILED'),
87
+ });
88
+ if (!response.ok) {
89
+ const body = yield* Effect.tryPromise(() => response.json()).pipe(Effect.catchAll(() => Effect.succeed(undefined)));
90
+ if (response.status === 401) {
91
+ return yield* Effect.fail(new DoomainError('VERCEL_AUTH_FAILED', vercelAuthErrorMessage(body), body));
92
+ }
93
+ return yield* Effect.fail(new DoomainError('DOMAIN_LINK_FAILED', apiErrorMessage(response.status, body), body));
67
94
  }
68
- throw new DoomainError('DOMAIN_LINK_FAILED', apiErrorMessage(response.status, body), body);
69
- }
70
- if (response.status === 204)
71
- return undefined;
72
- return (await response.json().catch(() => undefined));
95
+ if (response.status === 204)
96
+ return undefined;
97
+ return (yield* Effect.tryPromise({
98
+ try: () => response.json(),
99
+ catch: (cause) => toDoomainError(cause, clientOpts.transportErrorCode ?? 'DOMAIN_LINK_FAILED'),
100
+ }));
101
+ });
73
102
  }
74
103
  return {
75
- async listTeams() {
76
- const teamsById = new Map();
77
- const seenCursors = new Set();
78
- let cursor;
79
- for (let page = 0; page < 25; page += 1) {
80
- const query = new URLSearchParams({ limit: '100' });
81
- if (cursor)
82
- query.set('until', cursor);
83
- const result = await request(`/v2/teams?${query.toString()}`, {}, { team: false });
84
- for (const team of result.teams) {
85
- teamsById.set(team.id, {
86
- id: team.id,
87
- name: team.name ?? null,
88
- role: team.membership?.role ?? null,
89
- slug: team.slug ?? team.id,
104
+ listTeams() {
105
+ return Effect.gen(function* () {
106
+ const teamsById = new Map();
107
+ const seenCursors = new Set();
108
+ let cursor;
109
+ for (let page = 0; page < 25; page += 1) {
110
+ const query = new URLSearchParams({ limit: '100' });
111
+ if (cursor)
112
+ query.set('until', cursor);
113
+ const raw = yield* request(`/v2/teams?${query.toString()}`, {}, { team: false });
114
+ const result = yield* decode('teams', raw, (candidate) => {
115
+ const response = candidate;
116
+ if (!response || !Array.isArray(response.teams))
117
+ throw new TypeError('Expected a teams array.');
118
+ if (response.teams.some((team) => !team || typeof team.id !== 'string')) {
119
+ throw new TypeError('Expected every team to have an id.');
120
+ }
121
+ return response;
90
122
  });
123
+ for (const team of result.teams) {
124
+ teamsById.set(team.id, {
125
+ id: team.id,
126
+ name: team.name ?? null,
127
+ role: team.membership?.role ?? null,
128
+ slug: team.slug ?? team.id,
129
+ });
130
+ }
131
+ const next = result.pagination?.next?.toString();
132
+ if (!next || seenCursors.has(next))
133
+ break;
134
+ seenCursors.add(next);
135
+ cursor = next;
91
136
  }
92
- const next = result.pagination?.next?.toString();
93
- if (!next || seenCursors.has(next))
94
- break;
95
- seenCursors.add(next);
96
- cursor = next;
97
- }
98
- return [...teamsById.values()].sort((a, b) => (a.name ?? a.slug).localeCompare(b.name ?? b.slug));
137
+ return [...teamsById.values()].sort((a, b) => (a.name ?? a.slug).localeCompare(b.name ?? b.slug));
138
+ });
99
139
  },
100
- async listProjects(search) {
101
- const projectsById = new Map();
102
- const seenCursors = new Set();
103
- let cursor;
104
- for (let page = 0; page < 25; page += 1) {
105
- const query = new URLSearchParams({ limit: '100' });
106
- if (search)
107
- query.set('search', search);
108
- if (cursor)
109
- query.set('from', cursor);
110
- const result = await request(`/v9/projects?${query.toString()}`);
111
- for (const project of result.projects) {
112
- projectsById.set(project.id, {
113
- id: project.id,
114
- name: project.name,
115
- framework: project.framework ?? null,
116
- updatedAt: project.updatedAt ?? null,
140
+ listProjects(search) {
141
+ return Effect.gen(function* () {
142
+ const projectsById = new Map();
143
+ const seenCursors = new Set();
144
+ let cursor;
145
+ for (let page = 0; page < 25; page += 1) {
146
+ const query = new URLSearchParams({ limit: '100' });
147
+ if (search)
148
+ query.set('search', search);
149
+ if (cursor)
150
+ query.set('from', cursor);
151
+ const raw = yield* request(`/v9/projects?${query.toString()}`);
152
+ const result = yield* decode('projects', raw, (candidate) => {
153
+ const response = candidate;
154
+ if (!response || !Array.isArray(response.projects))
155
+ throw new TypeError('Expected a projects array.');
156
+ if (response.projects.some((project) => !project || typeof project.id !== 'string' || typeof project.name !== 'string')) {
157
+ throw new TypeError('Expected every project to have an id and name.');
158
+ }
159
+ return response;
117
160
  });
161
+ for (const project of result.projects) {
162
+ projectsById.set(project.id, {
163
+ id: project.id,
164
+ name: project.name,
165
+ framework: project.framework ?? null,
166
+ updatedAt: project.updatedAt ?? null,
167
+ });
168
+ }
169
+ const next = result.pagination?.next?.toString();
170
+ if (!next || seenCursors.has(next))
171
+ break;
172
+ seenCursors.add(next);
173
+ cursor = next;
118
174
  }
119
- const next = result.pagination?.next?.toString();
120
- if (!next || seenCursors.has(next))
121
- break;
122
- seenCursors.add(next);
123
- cursor = next;
124
- }
125
- return [...projectsById.values()].sort((a, b) => a.name.localeCompare(b.name));
175
+ return [...projectsById.values()].sort((a, b) => a.name.localeCompare(b.name));
176
+ });
126
177
  },
127
- async addDomainToProject(project, domain, opts = {}) {
128
- try {
129
- const raw = await request(`/v10/projects/${encodeURIComponent(project)}/domains`, {
178
+ addDomainToProject(project, domain, opts = {}) {
179
+ return Effect.gen(this, function* () {
180
+ const attempted = yield* Effect.either(request(`/v10/projects/${encodeURIComponent(project)}/domains`, {
130
181
  method: 'POST',
131
182
  body: JSON.stringify({ name: domain }),
132
- });
133
- const projectDomain = findProjectDomainTarget(raw, domain);
134
- if (!projectDomain) {
135
- throw new DoomainError('DOMAIN_LINK_FAILED', `Vercel did not return ${domain} after adding it to project ${project}.`, raw);
183
+ }));
184
+ if (attempted._tag === 'Right') {
185
+ const raw = attempted.right;
186
+ const projectDomain = findProjectDomainTarget(raw, domain);
187
+ if (!projectDomain) {
188
+ return yield* Effect.fail(new DoomainError('DOMAIN_LINK_FAILED', `Vercel did not return ${domain} after adding it to project ${project}.`, raw));
189
+ }
190
+ return { alreadyAdded: false, raw: projectDomain };
136
191
  }
137
- return { alreadyAdded: false, raw: projectDomain };
138
- }
139
- catch (error) {
192
+ const error = attempted.left;
140
193
  if (isDomainConflictError(error)) {
141
- const projectDomain = await this.getProjectDomain(project, domain).catch(() => undefined);
194
+ const projectDomain = yield* this.getProjectDomain(project, domain).pipe(Effect.catchAll(() => Effect.succeed(undefined)));
142
195
  if (projectDomain)
143
196
  return { alreadyAdded: true, raw: projectDomain };
144
197
  if (opts.force) {
145
- const owner = await this.findProjectDomainOwner(domain);
198
+ const owner = yield* this.findProjectDomainOwner(domain);
146
199
  if (owner && owner.project.id !== project) {
147
- await this.removeDomainFromProject(owner.project.id, domain);
148
- return this.addDomainToProject(project, domain);
200
+ yield* this.removeDomainFromProject(owner.project.id, domain);
201
+ return yield* this.addDomainToProject(project, domain);
149
202
  }
150
203
  }
151
- throw new DoomainError('DOMAIN_ALREADY_ASSIGNED', `Vercel reports ${domain} is already assigned to another project. Re-run with --force if you intend to move it to ${project}.`, error instanceof DoomainError ? error.details : undefined);
204
+ return yield* Effect.fail(new DoomainError('DOMAIN_ALREADY_ASSIGNED', `Vercel reports ${domain} is already assigned to another project. Re-run with --force if you intend to move it to ${project}.`, error.details));
152
205
  }
153
- throw error;
154
- }
206
+ return yield* Effect.fail(error);
207
+ });
155
208
  },
156
- async findProjectDomainOwner(domain) {
157
- for (const project of await this.listProjects()) {
158
- const domains = await this.listProjectDomains(project.id).catch(() => []);
159
- const match = domains.find((item) => isSameDomain(item.name, domain));
160
- if (match)
161
- return { domain: match, project };
162
- }
163
- return undefined;
209
+ findProjectDomainOwner(domain) {
210
+ return Effect.gen(this, function* () {
211
+ for (const project of yield* this.listProjects()) {
212
+ const domains = yield* this.listProjectDomains(project.id).pipe(Effect.catchAll(() => Effect.succeed([])));
213
+ const match = domains.find((item) => isSameDomain(item.name, domain));
214
+ if (match)
215
+ return { domain: match, project };
216
+ }
217
+ return undefined;
218
+ });
164
219
  },
165
- async getDomainConfig(domain) {
166
- return request(`/v6/domains/${encodeURIComponent(domain)}/config`);
220
+ getDomainConfig(domain) {
221
+ return request(`/v6/domains/${encodeURIComponent(domain)}/config`).pipe(Effect.flatMap((value) => recordResponse('domain config', value)));
167
222
  },
168
- async getRecommendedCname(domain) {
169
- const config = await this.getDomainConfig(domain).catch(() => undefined);
170
- const recommended = config?.recommendedCNAME?.sort((a, b) => (a.rank ?? 999) - (b.rank ?? 999))[0];
171
- return recommended?.value?.replace(/\.$/, '') || VERCEL_CNAME_RECORD;
223
+ getRecommendedCname(domain) {
224
+ return this.getDomainConfig(domain).pipe(Effect.catchAll(() => Effect.succeed(undefined)), Effect.map((domainConfig) => {
225
+ const recommended = domainConfig?.recommendedCNAME?.sort((a, b) => (a.rank ?? 999) - (b.rank ?? 999))[0];
226
+ return recommended?.value?.replace(/\.$/, '') || VERCEL_CNAME_RECORD;
227
+ }));
172
228
  },
173
- async getProjectDomain(project, domain) {
174
- return request(`/v9/projects/${encodeURIComponent(project)}/domains/${encodeURIComponent(domain)}`);
229
+ getProjectDomain(project, domain) {
230
+ return request(`/v9/projects/${encodeURIComponent(project)}/domains/${encodeURIComponent(domain)}`).pipe(Effect.flatMap((value) => recordResponse('project domain', value)));
175
231
  },
176
- async listProjectDomains(project) {
177
- const result = await request(`/v9/projects/${encodeURIComponent(project)}/domains`);
178
- return result.domains ?? [];
232
+ listProjectDomains(project) {
233
+ return request(`/v9/projects/${encodeURIComponent(project)}/domains`).pipe(Effect.flatMap((value) => decode('project domains', value, (candidate) => {
234
+ const response = candidate;
235
+ if (!response || !Array.isArray(response.domains))
236
+ throw new TypeError('Expected a domains array.');
237
+ return response.domains;
238
+ })));
179
239
  },
180
- async removeDomainFromProject(project, domain) {
181
- await request(`/v9/projects/${encodeURIComponent(project)}/domains/${encodeURIComponent(domain)}`, {
240
+ removeDomainFromProject(project, domain) {
241
+ return request(`/v9/projects/${encodeURIComponent(project)}/domains/${encodeURIComponent(domain)}`, {
182
242
  method: 'DELETE',
183
- });
243
+ }).pipe(Effect.asVoid);
184
244
  },
185
- async verifyProjectDomain(project, domain) {
186
- return request(`/v9/projects/${encodeURIComponent(project)}/domains/${encodeURIComponent(domain)}/verify`, { method: 'POST' });
245
+ verifyProjectDomain(project, domain) {
246
+ return request(`/v9/projects/${encodeURIComponent(project)}/domains/${encodeURIComponent(domain)}/verify`, { method: 'POST' }).pipe(Effect.flatMap((value) => recordResponse('domain verification', value)));
187
247
  },
188
248
  };
189
249
  }
@@ -259,6 +259,26 @@
259
259
  "verify.js"
260
260
  ]
261
261
  },
262
+ "version": {
263
+ "aliases": [],
264
+ "args": {},
265
+ "description": "Display the installed doomain version.",
266
+ "flags": {},
267
+ "hasDynamicHelp": false,
268
+ "hiddenAliases": [],
269
+ "id": "version",
270
+ "pluginAlias": "doomain",
271
+ "pluginName": "doomain",
272
+ "pluginType": "core",
273
+ "strict": true,
274
+ "enableJsonFlag": false,
275
+ "isESM": true,
276
+ "relativePath": [
277
+ "dist",
278
+ "commands",
279
+ "version.js"
280
+ ]
281
+ },
262
282
  "wizard": {
263
283
  "aliases": [],
264
284
  "args": {},
@@ -1306,5 +1326,5 @@
1306
1326
  ]
1307
1327
  }
1308
1328
  },
1309
- "version": "0.1.22"
1329
+ "version": "0.1.24"
1310
1330
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "doomain",
3
3
  "description": "Configure production domains and DNS records in seconds",
4
- "version": "0.1.22",
4
+ "version": "0.1.24",
5
5
  "author": "Crafter Station",
6
6
  "packageManager": "bun@1.3.13",
7
7
  "bin": {
@@ -12,6 +12,7 @@
12
12
  "@clack/prompts": "^1.2.0",
13
13
  "@oclif/core": "^4",
14
14
  "@oclif/plugin-help": "^6",
15
+ "effect": "^3.22.2",
15
16
  "fast-xml-parser": "^5.7.2"
16
17
  },
17
18
  "devDependencies": {
@@ -47,6 +48,12 @@
47
48
  "main": "dist/index.js",
48
49
  "type": "module",
49
50
  "oclif": {
51
+ "additionalHelpFlags": [
52
+ "-h"
53
+ ],
54
+ "additionalVersionFlags": [
55
+ "-v"
56
+ ],
50
57
  "bin": "doomain",
51
58
  "dirname": "doomain",
52
59
  "commands": "./dist/commands",