doomain 0.1.4 → 0.1.6

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.
@@ -18,7 +18,7 @@ export default class Link extends Command {
18
18
  apex: apexFlag,
19
19
  domain: domainFlag,
20
20
  'dry-run': Flags.boolean({ description: 'Preview changes without writing to Vercel or DNS.' }),
21
- force: Flags.boolean({ description: 'Move existing Vercel aliases and overwrite conflicting DNS records.' }),
21
+ force: Flags.boolean({ description: 'Move existing Vercel project domains and overwrite conflicting DNS records.' }),
22
22
  json: jsonFlag,
23
23
  project: projectFlag,
24
24
  provider: providerFlag,
@@ -110,6 +110,9 @@ function teamLabel(team) {
110
110
  function globalTokenLabel(token) {
111
111
  return token.source === 'environment' ? `Use ${token.label}` : `Use ${token.label} token`;
112
112
  }
113
+ function isVercelAuthError(error) {
114
+ return error instanceof DoomainError && error.code === 'VERCEL_AUTH_FAILED';
115
+ }
113
116
  async function promptVercelToken(globalTokens) {
114
117
  if (globalTokens.length > 0) {
115
118
  const selected = await p.select({
@@ -154,35 +157,54 @@ export default class Wizard extends Command {
154
157
  let vercelTeamId = process.env.VERCEL_TEAM_ID || localProject?.orgId || config.vercel?.teamId;
155
158
  const defaultProvider = process.env.DOOMAIN_PROVIDER || config.defaults?.provider;
156
159
  const defaultDomain = process.env.DOOMAIN_DOMAIN || config.defaults?.domain;
160
+ let globalVercelTokens = [];
157
161
  if (!vercelToken) {
158
- const globalVercelTokens = await listGlobalVercelTokens();
162
+ globalVercelTokens = await listGlobalVercelTokens();
159
163
  vercelToken = (await promptVercelToken(globalVercelTokens)) ?? undefined;
160
164
  if (!vercelToken)
161
165
  return;
162
166
  }
163
- const teamSpinner = p.spinner();
164
167
  let teams = [];
165
- if (process.env.VERCEL_TEAM_ID) {
166
- p.log.info(`Using Vercel team ${vercelTeamId} from VERCEL_TEAM_ID.`);
167
- }
168
- else {
169
- activeSpinner = teamSpinner;
170
- teamSpinner.start('Loading Vercel teams');
171
- teams = await createVercelClient({ token: vercelToken }).listTeams();
172
- teamSpinner.stop(`Loaded ${teams.length} Vercel team${teams.length === 1 ? '' : 's'}`);
173
- activeSpinner = undefined;
174
- const selected = await p.select({
175
- message: 'Select Vercel account/team',
176
- initialValue: vercelTeamId ?? localProject?.orgId ?? PERSONAL_ACCOUNT,
177
- options: [
178
- { label: 'Personal account', value: PERSONAL_ACCOUNT, hint: 'No team id' },
179
- ...teams.map((team) => ({ label: teamLabel(team), value: team.id, hint: team.role ?? team.slug })),
180
- ],
181
- });
182
- const resolved = cancelIfNeeded(selected);
183
- if (resolved === null)
184
- return;
185
- vercelTeamId = resolved === PERSONAL_ACCOUNT ? undefined : resolved;
168
+ while (true) {
169
+ const teamSpinner = p.spinner();
170
+ try {
171
+ if (process.env.VERCEL_TEAM_ID) {
172
+ p.log.info(`Using Vercel team ${vercelTeamId} from VERCEL_TEAM_ID.`);
173
+ }
174
+ else {
175
+ activeSpinner = teamSpinner;
176
+ teamSpinner.start('Loading Vercel teams');
177
+ teams = await createVercelClient({ token: vercelToken }).listTeams();
178
+ teamSpinner.stop(`Loaded ${teams.length} Vercel team${teams.length === 1 ? '' : 's'}`);
179
+ activeSpinner = undefined;
180
+ const selected = await p.select({
181
+ message: 'Select Vercel account/team',
182
+ initialValue: vercelTeamId ?? localProject?.orgId ?? PERSONAL_ACCOUNT,
183
+ options: [
184
+ { label: 'Personal account', value: PERSONAL_ACCOUNT, hint: 'No team id' },
185
+ ...teams.map((team) => ({ label: teamLabel(team), value: team.id, hint: team.role ?? team.slug })),
186
+ ],
187
+ });
188
+ const resolved = cancelIfNeeded(selected);
189
+ if (resolved === null)
190
+ return;
191
+ vercelTeamId = resolved === PERSONAL_ACCOUNT ? undefined : resolved;
192
+ }
193
+ break;
194
+ }
195
+ catch (error) {
196
+ if (!isVercelAuthError(error) || process.env.VERCEL_TOKEN)
197
+ throw error;
198
+ activeSpinner?.error('Vercel authorization failed');
199
+ activeSpinner = undefined;
200
+ p.log.warning(error instanceof Error ? error.message : String(error));
201
+ if (globalVercelTokens.length === 0)
202
+ globalVercelTokens = await listGlobalVercelTokens();
203
+ globalVercelTokens = globalVercelTokens.filter((token) => token.token !== vercelToken);
204
+ vercelToken = (await promptVercelToken(globalVercelTokens)) ?? undefined;
205
+ if (!vercelToken)
206
+ return;
207
+ }
186
208
  }
187
209
  const selectedTeam = teams.find((team) => team.id === vercelTeamId);
188
210
  const teamDisplay = vercelTeamId ? teamLabel(selectedTeam ?? { id: vercelTeamId, name: null, role: null, slug: vercelTeamId }) : 'Personal account';
@@ -1,4 +1,4 @@
1
- export type DoomainErrorCode = 'CONFIG_NOT_FOUND' | 'DOMAIN_LINK_FAILED' | 'DOMAIN_ALREADY_ASSIGNED' | 'DOMAIN_VERIFY_FAILED' | 'INVALID_INPUT' | 'MISSING_ARGUMENT' | 'MISSING_CREDENTIALS' | 'PROVIDER_API_ERROR' | 'PROVIDER_AUTH_FAILED' | 'PROVIDER_PERMISSION_DENIED' | 'PROVIDER_NOT_FOUND' | 'PROVIDER_RATE_LIMITED' | 'PROVIDER_RECORD_CONFLICT' | 'PROVIDER_UNSUPPORTED_RECORD' | 'PROVIDER_ZONE_AMBIGUOUS' | 'PROVIDER_ZONE_NOT_FOUND' | 'PROJECT_NOT_FOUND' | 'VERCEL_PROJECT_NOT_LINKED';
1
+ export type DoomainErrorCode = 'CONFIG_NOT_FOUND' | 'DOMAIN_LINK_FAILED' | 'DOMAIN_ALREADY_ASSIGNED' | 'DOMAIN_VERIFY_FAILED' | 'INVALID_INPUT' | 'MISSING_ARGUMENT' | 'MISSING_CREDENTIALS' | 'PROVIDER_API_ERROR' | 'PROVIDER_AUTH_FAILED' | 'PROVIDER_PERMISSION_DENIED' | 'PROVIDER_NOT_FOUND' | 'PROVIDER_RATE_LIMITED' | 'PROVIDER_RECORD_CONFLICT' | 'PROVIDER_UNSUPPORTED_RECORD' | 'PROVIDER_ZONE_AMBIGUOUS' | 'PROVIDER_ZONE_NOT_FOUND' | 'PROJECT_NOT_FOUND' | 'VERCEL_AUTH_FAILED' | 'VERCEL_PROJECT_NOT_LINKED';
2
2
  export declare class DoomainError extends Error {
3
3
  readonly code: DoomainErrorCode;
4
4
  readonly details?: unknown;
@@ -22,6 +22,13 @@ function appendTeam(path, teamId) {
22
22
  function apiErrorMessage(status, body) {
23
23
  return body?.error?.message ?? `Vercel API error (${status}).`;
24
24
  }
25
+ function vercelAuthErrorMessage(body) {
26
+ const message = body?.error?.message;
27
+ if (!message || message.toLowerCase() === 'not authorized') {
28
+ return 'Vercel token is not authorized. Run `vercel login` again or enter a token from https://vercel.com/account/tokens.';
29
+ }
30
+ return `Vercel authorization failed: ${message}`;
31
+ }
25
32
  function isDomainConflictError(error) {
26
33
  if (!(error instanceof DoomainError))
27
34
  return false;
@@ -36,9 +43,11 @@ function isDomainConflictError(error) {
36
43
  function isSameDomain(value, domain) {
37
44
  return typeof value === 'string' && value.toLowerCase() === domain.toLowerCase();
38
45
  }
39
- function findAliasTarget(raw, domain) {
46
+ function findProjectDomainTarget(raw, domain) {
40
47
  const targets = Array.isArray(raw) ? raw : [raw];
41
- return targets.find((target) => target && typeof target === 'object' && isSameDomain(target.domain, domain));
48
+ return targets.find((target) => target &&
49
+ typeof target === 'object' &&
50
+ (isSameDomain(target.domain, domain) || isSameDomain(target.name, domain)));
42
51
  }
43
52
  export function createVercelClient(config) {
44
53
  async function request(path, init = {}, opts = {}) {
@@ -52,6 +61,9 @@ export function createVercelClient(config) {
52
61
  });
53
62
  if (!response.ok) {
54
63
  const body = (await response.json().catch(() => undefined));
64
+ if (response.status === 401) {
65
+ throw new DoomainError('VERCEL_AUTH_FAILED', vercelAuthErrorMessage(body), body);
66
+ }
55
67
  throw new DoomainError('DOMAIN_LINK_FAILED', apiErrorMessage(response.status, body), body);
56
68
  }
57
69
  if (response.status === 204)
@@ -113,15 +125,15 @@ export function createVercelClient(config) {
113
125
  },
114
126
  async addDomainToProject(project, domain, opts = {}) {
115
127
  try {
116
- const raw = await request(`/projects/${encodeURIComponent(project)}/alias`, {
128
+ const raw = await request(`/v10/projects/${encodeURIComponent(project)}/domains`, {
117
129
  method: 'POST',
118
- body: JSON.stringify({ domain, target: 'PRODUCTION' }),
130
+ body: JSON.stringify({ name: domain }),
119
131
  });
120
- const aliasTarget = findAliasTarget(raw, domain);
121
- if (!aliasTarget) {
132
+ const projectDomain = findProjectDomainTarget(raw, domain);
133
+ if (!projectDomain) {
122
134
  throw new DoomainError('DOMAIN_LINK_FAILED', `Vercel did not return ${domain} after adding it to project ${project}.`, raw);
123
135
  }
124
- return { alreadyAdded: false, raw: aliasTarget };
136
+ return { alreadyAdded: false, raw: projectDomain };
125
137
  }
126
138
  catch (error) {
127
139
  if (isDomainConflictError(error)) {
@@ -165,7 +177,7 @@ export function createVercelClient(config) {
165
177
  return result.domains ?? [];
166
178
  },
167
179
  async removeDomainFromProject(project, domain) {
168
- await request(`/projects/${encodeURIComponent(project)}/alias?domain=${encodeURIComponent(domain)}`, { method: 'DELETE' });
180
+ await request(`/v9/projects/${encodeURIComponent(project)}/domains/${encodeURIComponent(domain)}`, { method: 'DELETE' });
169
181
  },
170
182
  async verifyProjectDomain(project, domain) {
171
183
  return request(`/v9/projects/${encodeURIComponent(project)}/domains/${encodeURIComponent(domain)}/verify`, { method: 'POST' });
@@ -37,7 +37,7 @@
37
37
  "type": "boolean"
38
38
  },
39
39
  "force": {
40
- "description": "Move existing Vercel aliases and overwrite conflicting DNS records.",
40
+ "description": "Move existing Vercel project domains and overwrite conflicting DNS records.",
41
41
  "name": "force",
42
42
  "allowNo": false,
43
43
  "type": "boolean"
@@ -629,5 +629,5 @@
629
629
  ]
630
630
  }
631
631
  },
632
- "version": "0.1.4"
632
+ "version": "0.1.6"
633
633
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "doomain",
3
3
  "description": "Link your vercel project and domain in seconds",
4
- "version": "0.1.4",
4
+ "version": "0.1.6",
5
5
  "author": "Crafter Station",
6
6
  "packageManager": "bun@1.3.13",
7
7
  "bin": {