Skip to content

Commit d93d60f

Browse files
committed
netlify deploy kind of working
1 parent 69c02e2 commit d93d60f

File tree

3 files changed

+114
-224
lines changed

3 files changed

+114
-224
lines changed

convex/netlifyDeploy.ts

Lines changed: 45 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,6 @@
11
import { action } from './_generated/server'
22
import { v } from 'convex/values'
33

4-
// Simple JWT implementation that works in Convex environment
5-
function base64UrlEncode(str: string): string {
6-
return btoa(str).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')
7-
}
8-
9-
function createSimpleJWT(payload: any, secret: string): string {
10-
const header = {
11-
alg: 'HS256',
12-
typ: 'JWT',
13-
}
14-
15-
const encodedHeader = base64UrlEncode(JSON.stringify(header))
16-
const encodedPayload = base64UrlEncode(JSON.stringify(payload))
17-
18-
// Create signature using a simple HMAC-like approach
19-
// Note: This is a simplified version for the Convex environment
20-
const data = `${encodedHeader}.${encodedPayload}`
21-
const signature = base64UrlEncode(secret + data)
22-
23-
return `${encodedHeader}.${encodedPayload}.${signature}`
24-
}
25-
264
// Helper function to convert base64 to Uint8Array (works in Convex)
275
function base64ToUint8Array(base64: string): Uint8Array {
286
const binaryString = atob(base64)
@@ -90,27 +68,24 @@ export const deployToNetlify = action({
9068

9169
// Get Netlify credentials from environment
9270
const netlifyToken = process.env.NETLIFY_TOKEN
93-
const oauthClientId = process.env.NETLIFY_OAUTH_CLIENT_ID
94-
const oauthClientSecret = process.env.NETLIFY_OAUTH_CLIENT_SECRET
9571
const netlifyTeamSlug = process.env.NETLIFY_TEAM_SLUG
9672

9773
if (!netlifyToken) {
9874
throw new Error('NETLIFY_TOKEN not configured')
9975
}
100-
if (!oauthClientId || !oauthClientSecret) {
101-
throw new Error(
102-
'NETLIFY_OAUTH_CLIENT_ID and NETLIFY_OAUTH_CLIENT_SECRET must be configured for claimable sites'
103-
)
104-
}
105-
106-
// Generate a unique session ID for this deployment
107-
const sessionId = `forge-${Date.now()}-${Math.random()
108-
.toString(36)
109-
.substr(2, 9)}`
11076

11177
try {
112-
// Step 1: Create a new site with metadata for claimable sites
113-
// Using created_via and session_id allows users to claim the site later
78+
// Step 1: Create a new site
79+
const sitePayload: any = {
80+
name: `${args.siteName}-${Date.now()}`,
81+
custom_domain: null,
82+
}
83+
84+
// Optional: specify team
85+
if (netlifyTeamSlug) {
86+
sitePayload.account_slug = netlifyTeamSlug
87+
}
88+
11489
const createSiteResponse = await fetch(
11590
'https://api.netlify.com/api/v1/sites',
11691
{
@@ -119,13 +94,7 @@ export const deployToNetlify = action({
11994
Authorization: `Bearer ${netlifyToken}`,
12095
'Content-Type': 'application/json',
12196
},
122-
body: JSON.stringify({
123-
name: `${args.siteName}-${Date.now()}`,
124-
custom_domain: null,
125-
created_via: 'TanStack Forge', // Important for claimable sites
126-
session_id: sessionId, // Session ID that will be used in the JWT
127-
account_slug: netlifyTeamSlug, // Optional: specify team
128-
}),
97+
body: JSON.stringify(sitePayload),
12998
}
13099
)
131100

@@ -173,32 +142,49 @@ export const deployToNetlify = action({
173142

174143
console.log('Build triggered successfully:', build)
175144

176-
// Step 3: Create a signed JWT claim URL
177-
// The JWT contains the OAuth client ID and session ID
178-
const claimToken = createSimpleJWT(
179-
{
180-
client_id: oauthClientId,
181-
session_id: sessionId,
182-
},
183-
oauthClientSecret!
184-
)
185-
186-
// The claim URL uses a hash fragment with the JWT token
187-
const claimUrl = `https://app.netlify.com/claim#${claimToken}`
188-
189-
console.log('Claimable site created with signed claim URL')
145+
// Step 3: Generate a claim URL using the Netlify API
146+
// Note: The PAT used here must be associated with an OAuth app to generate claim URLs
147+
let claimUrl: string | undefined = undefined
148+
149+
try {
150+
const claimResponse = await fetch(
151+
`https://api.netlify.com/api/v1/sites/${siteId}/claim`,
152+
{
153+
method: 'POST',
154+
headers: {
155+
Authorization: `Bearer ${netlifyToken}`,
156+
'Content-Type': 'application/json',
157+
},
158+
body: JSON.stringify({}),
159+
}
160+
)
161+
162+
if (claimResponse.ok) {
163+
const claimData = await claimResponse.json()
164+
claimUrl = claimData.claim_url
165+
console.log('Claimable site created with claim URL')
166+
} else {
167+
const error = await claimResponse.text()
168+
console.log(
169+
'Could not generate claim URL (PAT may not be associated with an OAuth app):',
170+
error
171+
)
172+
}
173+
} catch (error) {
174+
console.log('Failed to generate claim URL:', error)
175+
}
190176

191177
// Return the deployed site URL and build information
192178
return {
193179
url: site.ssl_url || site.url || `https://${site.name}.netlify.app`,
194180
adminUrl: site.admin_url,
195-
claimUrl: claimUrl, // Signed JWT URL for users to claim the site
181+
claimUrl: claimUrl, // URL for users to claim the site (or undefined if claim endpoint failed)
196182
siteId: siteId,
197183
deployId: build.deploy_id,
198184
buildId: build.id,
199185
siteName: site.name,
200186
buildStatus: build.state, // 'building', 'ready', 'error'
201-
isClaimable: true, // Indicates this is a claimable site
187+
isClaimable: !!claimUrl, // Indicates if claim URL was successfully generated
202188
}
203189
} catch (error) {
204190
console.error('Netlify deployment error:', error)

0 commit comments

Comments
 (0)