| 115 | } |
| 116 | |
| 117 | export async function publishCgcBundle( |
| 118 | blob: Blob, |
| 119 | repoName: string, |
| 120 | version: string |
| 121 | ): Promise<{ success: boolean; message: string; entry?: any }> { |
| 122 | try { |
| 123 | // 1. Base64 encode the ZIP blob on the client-side using FileReader |
| 124 | const base64 = await new Promise<string>((resolve, reject) => { |
| 125 | const reader = new FileReader(); |
| 126 | reader.onload = () => { |
| 127 | const result = reader.result as string; |
| 128 | resolve(result.split(",")[1]); |
| 129 | }; |
| 130 | reader.onerror = () => reject(reader.error); |
| 131 | reader.readAsDataURL(blob); |
| 132 | }); |
| 133 | |
| 134 | // 2. Compute SHA256 and size of the base64 payload natively |
| 135 | const base64Buffer = new TextEncoder().encode(base64); |
| 136 | const hashBuffer = await window.crypto.subtle.digest('SHA-256', base64Buffer); |
| 137 | const hashArray = Array.from(new Uint8Array(hashBuffer)); |
| 138 | const sha256 = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); |
| 139 | const size = base64Buffer.length; |
| 140 | |
| 141 | // 3. Stage 1: Handshake |
| 142 | const handshakeResponse = await fetch('/api/publish', { |
| 143 | method: "POST", |
| 144 | headers: { |
| 145 | "Content-Type": "application/json", |
| 146 | "X-Publish-Stage": "handshake" |
| 147 | }, |
| 148 | body: JSON.stringify({ |
| 149 | repo: repoName, |
| 150 | version: version, |
| 151 | sha256, |
| 152 | size |
| 153 | }) |
| 154 | }); |
| 155 | |
| 156 | if (!handshakeResponse.ok) { |
| 157 | const errData = await handshakeResponse.json().catch(() => ({})); |
| 158 | throw new Error(errData.error || `Handshake failed with status ${handshakeResponse.status}`); |
| 159 | } |
| 160 | |
| 161 | const handshakeData = await handshakeResponse.json(); |
| 162 | |
| 163 | // 4. PUT the payload directly to Hugging Face S3 LFS if required (bypasses Vercel completely!) |
| 164 | if (handshakeData.uploadRequired) { |
| 165 | const uploadRes = await fetch(handshakeData.uploadUrl, { |
| 166 | method: "PUT", |
| 167 | headers: handshakeData.uploadHeaders || {}, |
| 168 | body: base64 |
| 169 | }); |
| 170 | |
| 171 | if (!uploadRes.ok) { |
| 172 | throw new Error(`Failed to upload bundle to LFS storage: ${uploadRes.statusText}`); |
| 173 | } |
| 174 | } |