@brainfish-ai/devdoc 0.1.49 → 0.1.50

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.
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Vercel Domains API Client
3
+ *
4
+ * Functions for managing custom domains via Vercel's REST API.
5
+ * Handles domain addition, verification, and removal.
6
+ *
7
+ * @see https://vercel.com/docs/rest-api/reference/endpoints/projects/add-a-domain-to-a-project
8
+ */ // =============================================================================
9
+ // Types
10
+ // =============================================================================
11
+ // =============================================================================
12
+ // Configuration
13
+ // =============================================================================
14
+ function getVercelConfig() {
15
+ // Support both VERCEL_API_TOKEN and VERCEL_TOKEN (used by Vercel CLI)
16
+ const token = process.env.VERCEL_API_TOKEN || process.env.VERCEL_TOKEN;
17
+ const projectId = process.env.VERCEL_PROJECT_ID;
18
+ const teamId = process.env.VERCEL_TEAM_ID;
19
+ if (!token || !projectId) {
20
+ return null;
21
+ }
22
+ return {
23
+ token,
24
+ projectId,
25
+ teamId
26
+ };
27
+ }
28
+ /**
29
+ * Check if Vercel integration is available
30
+ */ export function isVercelIntegrationEnabled() {
31
+ return getVercelConfig() !== null;
32
+ }
33
+ // =============================================================================
34
+ // API Functions
35
+ // =============================================================================
36
+ /**
37
+ * Add a domain to the Vercel project
38
+ *
39
+ * @param domainName - The domain to add (e.g., "docs.example.com")
40
+ * @returns Result with domain details and verification instructions
41
+ */ export async function addDomainToProject(domainName) {
42
+ const config = getVercelConfig();
43
+ if (!config) {
44
+ return {
45
+ success: false,
46
+ error: 'Vercel integration not configured. Set VERCEL_API_TOKEN and VERCEL_PROJECT_ID.'
47
+ };
48
+ }
49
+ const { token, projectId, teamId } = config;
50
+ try {
51
+ const url = new URL(`https://api.vercel.com/v10/projects/${projectId}/domains`);
52
+ if (teamId) {
53
+ url.searchParams.set('teamId', teamId);
54
+ }
55
+ const response = await fetch(url.toString(), {
56
+ method: 'POST',
57
+ headers: {
58
+ 'Authorization': `Bearer ${token}`,
59
+ 'Content-Type': 'application/json'
60
+ },
61
+ body: JSON.stringify({
62
+ name: domainName
63
+ })
64
+ });
65
+ const data = await response.json();
66
+ if (!response.ok) {
67
+ const errorData = data;
68
+ console.error('[Vercel Domains] Error adding domain:', errorData);
69
+ return {
70
+ success: false,
71
+ error: `Failed to add domain to Vercel: ${errorData.error?.message || 'Unknown error'}`,
72
+ vercelError: errorData.error?.code
73
+ };
74
+ }
75
+ const domainData = data;
76
+ console.log('[Vercel Domains] Domain added:', domainData.name, 'verified:', domainData.verified);
77
+ return {
78
+ success: true,
79
+ domain: domainData
80
+ };
81
+ } catch (error) {
82
+ console.error('[Vercel Domains] Network error adding domain:', error);
83
+ return {
84
+ success: false,
85
+ error: `Network error: ${error instanceof Error ? error.message : String(error)}`
86
+ };
87
+ }
88
+ }
89
+ /**
90
+ * Verify a domain's DNS configuration with Vercel
91
+ *
92
+ * @param domainName - The domain to verify
93
+ * @returns Result indicating if domain is now verified
94
+ */ export async function verifyDomain(domainName) {
95
+ const config = getVercelConfig();
96
+ if (!config) {
97
+ return {
98
+ success: false,
99
+ verified: false,
100
+ error: 'Vercel integration not configured. Set VERCEL_API_TOKEN and VERCEL_PROJECT_ID.'
101
+ };
102
+ }
103
+ const { token, projectId, teamId } = config;
104
+ try {
105
+ const url = new URL(`https://api.vercel.com/v10/projects/${projectId}/domains/${domainName}/verify`);
106
+ if (teamId) {
107
+ url.searchParams.set('teamId', teamId);
108
+ }
109
+ const response = await fetch(url.toString(), {
110
+ method: 'POST',
111
+ headers: {
112
+ 'Authorization': `Bearer ${token}`
113
+ }
114
+ });
115
+ const data = await response.json();
116
+ if (!response.ok) {
117
+ const errorData = data;
118
+ console.error('[Vercel Domains] Error verifying domain:', errorData);
119
+ return {
120
+ success: false,
121
+ verified: false,
122
+ error: `Verification failed: ${errorData.error?.message || 'Unknown error'}`
123
+ };
124
+ }
125
+ const domainData = data;
126
+ console.log('[Vercel Domains] Domain verification result:', domainData.name, 'verified:', domainData.verified);
127
+ return {
128
+ success: true,
129
+ verified: domainData.verified,
130
+ domain: domainData
131
+ };
132
+ } catch (error) {
133
+ console.error('[Vercel Domains] Network error verifying domain:', error);
134
+ return {
135
+ success: false,
136
+ verified: false,
137
+ error: `Network error: ${error instanceof Error ? error.message : String(error)}`
138
+ };
139
+ }
140
+ }
141
+ /**
142
+ * Remove a domain from the Vercel project
143
+ *
144
+ * @param domainName - The domain to remove
145
+ * @returns Result indicating success or failure
146
+ */ export async function removeDomain(domainName) {
147
+ const config = getVercelConfig();
148
+ if (!config) {
149
+ return {
150
+ success: false,
151
+ error: 'Vercel integration not configured. Set VERCEL_API_TOKEN and VERCEL_PROJECT_ID.'
152
+ };
153
+ }
154
+ const { token, projectId, teamId } = config;
155
+ try {
156
+ const url = new URL(`https://api.vercel.com/v10/projects/${projectId}/domains/${domainName}`);
157
+ if (teamId) {
158
+ url.searchParams.set('teamId', teamId);
159
+ }
160
+ const response = await fetch(url.toString(), {
161
+ method: 'DELETE',
162
+ headers: {
163
+ 'Authorization': `Bearer ${token}`
164
+ }
165
+ });
166
+ if (!response.ok) {
167
+ const errorData = await response.json();
168
+ console.error('[Vercel Domains] Error removing domain:', errorData);
169
+ return {
170
+ success: false,
171
+ error: `Failed to remove domain from Vercel: ${errorData.error?.message || 'Unknown error'}`
172
+ };
173
+ }
174
+ console.log('[Vercel Domains] Domain removed:', domainName);
175
+ return {
176
+ success: true
177
+ };
178
+ } catch (error) {
179
+ console.error('[Vercel Domains] Network error removing domain:', error);
180
+ return {
181
+ success: false,
182
+ error: `Network error: ${error instanceof Error ? error.message : String(error)}`
183
+ };
184
+ }
185
+ }
186
+ /**
187
+ * Get domain configuration and status from Vercel
188
+ *
189
+ * @param domainName - The domain to check
190
+ * @returns Domain details including verification status
191
+ */ export async function getDomainConfig(domainName) {
192
+ const config = getVercelConfig();
193
+ if (!config) {
194
+ return {
195
+ success: false,
196
+ error: 'Vercel integration not configured. Set VERCEL_API_TOKEN and VERCEL_PROJECT_ID.'
197
+ };
198
+ }
199
+ const { token, projectId, teamId } = config;
200
+ try {
201
+ const url = new URL(`https://api.vercel.com/v10/projects/${projectId}/domains/${domainName}`);
202
+ if (teamId) {
203
+ url.searchParams.set('teamId', teamId);
204
+ }
205
+ const response = await fetch(url.toString(), {
206
+ method: 'GET',
207
+ headers: {
208
+ 'Authorization': `Bearer ${token}`
209
+ }
210
+ });
211
+ if (!response.ok) {
212
+ const errorData = await response.json();
213
+ console.error('[Vercel Domains] Error getting domain config:', errorData);
214
+ return {
215
+ success: false,
216
+ error: `Failed to get domain config: ${errorData.error?.message || 'Unknown error'}`
217
+ };
218
+ }
219
+ const domainData = await response.json();
220
+ return {
221
+ success: true,
222
+ domain: domainData
223
+ };
224
+ } catch (error) {
225
+ console.error('[Vercel Domains] Network error getting domain config:', error);
226
+ return {
227
+ success: false,
228
+ error: `Network error: ${error instanceof Error ? error.message : String(error)}`
229
+ };
230
+ }
231
+ }
232
+ // =============================================================================
233
+ // Helper Functions
234
+ // =============================================================================
235
+ /**
236
+ * Format Vercel's verification instructions for display
237
+ *
238
+ * @param verification - Vercel's verification array
239
+ * @returns Formatted DNS instructions
240
+ */ export function formatVerificationInstructions(verification) {
241
+ const records = verification.map((v)=>({
242
+ type: v.type,
243
+ name: v.domain,
244
+ value: v.value
245
+ }));
246
+ const instructions = [
247
+ 'Add the following DNS record(s) to your domain provider:',
248
+ ''
249
+ ];
250
+ verification.forEach((v, i)=>{
251
+ instructions.push(`${i + 1}. ${v.type} Record:`);
252
+ instructions.push(` Name: ${v.domain}`);
253
+ instructions.push(` Value: ${v.value}`);
254
+ if (v.reason) {
255
+ instructions.push(` Note: ${v.reason}`);
256
+ }
257
+ instructions.push('');
258
+ });
259
+ instructions.push('After adding DNS records, run "devdoc domain verify" to verify.');
260
+ return {
261
+ records,
262
+ instructions
263
+ };
264
+ }
265
+ /**
266
+ * Check if a domain needs CNAME or A record based on whether it's apex or subdomain
267
+ * Vercel typically requires:
268
+ * - Subdomains: CNAME to cname.vercel-dns.com
269
+ * - Apex domains: A record to Vercel's IP or ALIAS/ANAME if supported
270
+ */ export function getDomainType(domainName) {
271
+ const parts = domainName.split('.');
272
+ // If it's just domain.tld (2 parts), it's an apex domain
273
+ // If it has more parts (like docs.domain.tld), it's a subdomain
274
+ return parts.length <= 2 ? 'apex' : 'subdomain';
275
+ }