Skip to content

Commit 04ef1de

Browse files
style: use raw fetch for v2/images upload to decouple from schema types
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 0e626f5 commit 04ef1de

1 file changed

Lines changed: 48 additions & 27 deletions

File tree

src/lib/images/upload.ts

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import chalk from "chalk";
1010
import cliProgress from "cli-progress";
1111
import cliSpinners from "cli-spinners";
1212
import ora, { type Ora } from "ora";
13-
import { apiClient } from "../../apiClient.ts";
13+
import { getAuthToken, loadConfig } from "../../helpers/config.ts";
1414
import { logAndQuit } from "../../helpers/errors.ts";
1515

1616
async function readChunk(
@@ -75,23 +75,31 @@ const upload = new Command("upload")
7575
let progressBar: cliProgress.SingleBar | undefined;
7676

7777
try {
78-
const client = await apiClient();
78+
const config = await loadConfig();
79+
const token = await getAuthToken();
80+
const apiHeaders = {
81+
Authorization: `Bearer ${token}`,
82+
"Content-Type": "application/json",
83+
};
7984

8085
preparingSpinner = ora(`Preparing upload for ${name}...`).start();
8186

8287
// Create image via v2 API
83-
const startResponse = await client.POST("/v2/images", {
84-
body: { name },
88+
const startResponse = await fetch(`${config.api_url}/v2/images`, {
89+
method: "POST",
90+
headers: apiHeaders,
91+
body: JSON.stringify({ name }),
8592
});
8693

87-
if (!startResponse.response.ok || !startResponse.data) {
88-
const errorText = await startResponse.response.text().catch(() => "");
94+
if (!startResponse.ok) {
8995
throw new Error(
90-
`Failed to start upload: ${startResponse.response.status} ${startResponse.response.statusText}${errorText ? ` - ${errorText}` : ""}`,
96+
`Failed to start upload: ${startResponse.status} ${startResponse.statusText}`,
9197
);
9298
}
9399

94-
const imageId = startResponse.data.id;
100+
const startData: { object: "image"; id: string; upload_status: string } =
101+
await startResponse.json();
102+
const imageId = startData.id;
95103

96104
preparingSpinner.succeed(
97105
`Started upload for image ${chalk.cyan(name)} (${chalk.blackBright(
@@ -231,16 +239,17 @@ const upload = new Command("upload")
231239
}
232240

233241
// Get presigned URL via v2 API
234-
const partResponse = await client.POST("/v2/images/{id}/parts", {
235-
params: { path: { id: imageId } },
236-
body: { part_id: part },
237-
});
242+
const partResponse = await fetch(
243+
`${config.api_url}/v2/images/${imageId}/parts`,
244+
{
245+
method: "POST",
246+
headers: apiHeaders,
247+
body: JSON.stringify({ part_id: part }),
248+
},
249+
);
238250

239-
if (!partResponse.response.ok || !partResponse.data) {
240-
const status = partResponse.response.status;
241-
const errorText = await partResponse.response
242-
.text()
243-
.catch(() => "");
251+
if (!partResponse.ok) {
252+
const status = partResponse.status;
244253

245254
if (
246255
status >= 400 &&
@@ -250,18 +259,20 @@ const upload = new Command("upload")
250259
) {
251260
bail(
252261
new Error(
253-
`Failed to get upload URL for part ${part}: ${status} ${partResponse.response.statusText} - ${errorText}`,
262+
`Failed to get upload URL for part ${part}: ${status} ${partResponse.statusText}`,
254263
),
255264
);
256265
return;
257266
}
258267

259268
throw new Error(
260-
`Failed to get upload URL for part ${part}: ${status} ${partResponse.response.statusText} - ${errorText}`,
269+
`Failed to get upload URL for part ${part}: ${status} ${partResponse.statusText}`,
261270
);
262271
}
263272

264-
const url = partResponse.data.url;
273+
const partData: { url: string; expires_at: string } =
274+
await partResponse.json();
275+
const url = partData.url;
265276

266277
// Read chunk from disk with progress tracking
267278
const payload = await readChunk(
@@ -353,21 +364,31 @@ const upload = new Command("upload")
353364
const sha256Hash = hash.digest("hex");
354365

355366
// Complete upload via v2 API
356-
const completeResponse = await client.POST("/v2/images/{id}/complete", {
357-
params: { path: { id: imageId } },
358-
body: { sha256: sha256Hash },
359-
});
367+
const completeResponse = await fetch(
368+
`${config.api_url}/v2/images/${imageId}/complete`,
369+
{
370+
method: "POST",
371+
headers: apiHeaders,
372+
body: JSON.stringify({ sha256: sha256Hash }),
373+
},
374+
);
360375

361-
if (!completeResponse.response.ok || !completeResponse.data) {
376+
if (!completeResponse.ok) {
362377
throw new Error(
363-
`Failed to complete upload: ${completeResponse.response.status} ${completeResponse.response.statusText}${completeResponse.error ? ` - ${JSON.stringify(completeResponse.error)}` : ""}`,
378+
`Failed to complete upload: ${completeResponse.status} ${completeResponse.statusText}`,
364379
);
365380
}
366381

382+
const completeData: {
383+
object: "image";
384+
upload_status: string;
385+
id: string;
386+
} = await completeResponse.json();
387+
367388
finalizingSpinner.succeed("Image uploaded and verified");
368389

369390
console.log(chalk.gray("\nNext steps:"));
370-
console.log(` sf images get ${chalk.cyan(completeResponse.data.id)}`);
391+
console.log(` sf images get ${chalk.cyan(completeData.id)}`);
371392
} catch (err) {
372393
if (spinnerTimer) {
373394
clearInterval(spinnerTimer);

0 commit comments

Comments
 (0)