@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.
- package/dist/cli/commands/deploy.js +72 -4
- package/dist/cli/commands/domain.js +134 -59
- package/package.json +1 -1
- package/renderer/app/api/collections/route.js +16 -7
- package/renderer/app/api/deploy/route.js +3 -3
- package/renderer/app/api/domains/add/route.js +118 -40
- package/renderer/app/api/domains/remove/route.js +16 -3
- package/renderer/app/api/domains/verify/route.js +82 -17
- package/renderer/app/api/schema/route.js +12 -11
- package/renderer/components/docs-header.js +11 -11
- package/renderer/lib/docs/config/domain-schema.js +23 -1
- package/renderer/lib/docs/config/index.js +1 -1
- package/renderer/lib/storage/blob.js +4 -2
- package/renderer/lib/vercel/domains.js +275 -0
|
@@ -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
|
+
}
|