Skip to content

Commit 002768c

Browse files
Bump nodes-sdk-alpha to 0.1.0-alpha.31 and migrate /v1/vms/images calls
The new SDK's `client.vms.images.list({ workspace })` hits /preview/v2/images and now requires the workspace parameter; the list response also renamed `image_id` to `id`. Switches `sf nodes images upload` from the legacy /v1/vms/images endpoints (vmorch) to /preview/v2/images (market-api), matching the pattern used in src/lib/images/upload.ts. `sf nodes images list` now uses the SDK directly (which targets /preview/v2/images). `sf nodes images show` is migrated in the follow-up commit alongside the other v2/images callers. CLI surface (commands, flags, output) is unchanged. Generated with [Indent](https://indent.com) Co-Authored-By: Indent <noreply@indent.com>
1 parent 633588c commit 002768c

4 files changed

Lines changed: 59 additions & 48 deletions

File tree

bun.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"@formatjs/intl-segmenter": "^12.1.0",
2424
"@inkjs/ui": "^1.0.0",
2525
"@inquirer/prompts": "^8.2.0",
26-
"@sfcompute/nodes-sdk-alpha": "0.1.0-alpha.27",
26+
"@sfcompute/nodes-sdk-alpha": "0.1.0-alpha.31",
2727
"@types/ms": "^0.7.34",
2828
"async-retry": "^1.3.3",
2929
"axios": "^1.8.4",

src/lib/nodes/image/list.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { getAuthToken } from "../../../helpers/config.ts";
88
import { logAndQuit } from "../../../helpers/errors.ts";
99
import { formatDate } from "../../../helpers/format-time.ts";
1010
import { handleNodesError, nodesClient } from "../../../nodesClient.ts";
11+
import { getDefaultWorkspace } from "../../images/utils.ts";
1112

1213
const list = new Command("list")
1314
.alias("ls")
@@ -35,9 +36,10 @@ Next Steps:\n
3536
logAndQuit("Not logged in. Please run 'sf login' first.");
3637
}
3738
const client = await nodesClient(token);
39+
const workspace = await getDefaultWorkspace();
3840

3941
const spinner = ora("Fetching images...").start();
40-
const { data: images } = await client.vms.images.list();
42+
const { data: images } = await client.vms.images.list({ workspace });
4143

4244
spinner.stop();
4345

@@ -95,7 +97,7 @@ Next Steps:\n
9597
}
9698
})();
9799

98-
table.push([image.name, image.image_id, status, createdAt]);
100+
table.push([image.name, image.id, status, createdAt]);
99101
}
100102
if (images.length > 5) {
101103
table.push([
@@ -118,15 +120,15 @@ Next Steps:\n
118120
// Always show how to get info for a specific image
119121
const firstImage = sortedImages[0];
120122
if (firstImage) {
121-
console.log(` sf node images show ${chalk.cyan(firstImage.image_id)}`);
123+
console.log(` sf node images show ${chalk.cyan(firstImage.id)}`);
122124
}
123125
const firstCompletedImage = sortedImages.find(
124126
(image) => image.upload_status === "completed",
125127
);
126128
if (firstCompletedImage) {
127129
console.log(
128130
` sf nodes create -z hayesvalley -d 2h -p 13.50 --image ${chalk.cyan(
129-
firstCompletedImage.image_id,
131+
firstCompletedImage.id,
130132
)}`,
131133
);
132134
}

src/lib/nodes/image/upload.ts

Lines changed: 50 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ 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";
15+
import { getDefaultWorkspace } from "../../images/utils.ts";
1516

1617
async function readChunk(
1718
filePath: string,
@@ -76,23 +77,34 @@ const upload = new Command("upload")
7677
let progressBar: cliProgress.SingleBar | undefined;
7778

7879
try {
80+
const config = await loadConfig();
81+
const token = await getAuthToken();
82+
const apiHeaders = {
83+
Authorization: `Bearer ${token}`,
84+
"Content-Type": "application/json",
85+
};
86+
7987
preparingSpinner = ora(`Preparing upload for ${name}...`).start();
80-
const client = await apiClient();
88+
89+
const workspace = await getDefaultWorkspace();
8190

8291
// Start upload
83-
const startResponse = await client.POST("/v1/vms/images/start_upload", {
84-
body: {
85-
name,
86-
},
92+
const startResponse = await fetch(`${config.api_url}/preview/v2/images`, {
93+
method: "POST",
94+
headers: apiHeaders,
95+
body: JSON.stringify({ name, workspace }),
8796
});
8897

89-
if (!startResponse.data) {
98+
if (!startResponse.ok) {
9099
throw new Error(
91-
`Failed to start upload: ${startResponse.response.status} ${startResponse.response.statusText}`,
100+
`Failed to start upload: ${startResponse.status} ${startResponse.statusText}`,
92101
);
93102
}
94103

95-
const imageId = startResponse.data.image_id;
104+
const startData: { object: "image"; id: string; upload_status: string } =
105+
await startResponse.json();
106+
const imageId = startData.id;
107+
96108
preparingSpinner.succeed(
97109
`Started upload for image ${chalk.cyan(name)} (${chalk.blackBright(
98110
imageId,
@@ -242,25 +254,18 @@ const upload = new Command("upload")
242254
}
243255

244256
// Fetch fresh upload URL for this attempt
245-
const response = await client.POST(
246-
"/v1/vms/images/{image_id}/upload",
257+
const partResponse = await fetch(
258+
`${config.api_url}/preview/v2/images/${imageId}/parts`,
247259
{
248-
params: {
249-
path: {
250-
image_id: imageId,
251-
},
252-
},
253-
body: {
254-
part_id: part,
255-
},
260+
method: "POST",
261+
headers: apiHeaders,
262+
body: JSON.stringify({ part_id: part }),
256263
},
257264
);
258265

259-
if (!response.response.ok || !response.data) {
260-
const status = response.response.status;
261-
const errorText = response.response.ok
262-
? "No data in response"
263-
: await response.response.text().catch(() => "");
266+
if (!partResponse.ok) {
267+
const status = partResponse.status;
268+
const errorText = await partResponse.text().catch(() => "");
264269

265270
// Bail on non-transient 4xx errors (except 408 Request Timeout and 429 Too Many Requests)
266271
if (
@@ -271,18 +276,20 @@ const upload = new Command("upload")
271276
) {
272277
bail(
273278
new Error(
274-
`Failed to get upload URL for part ${part}: ${status} ${response.response.statusText} - ${errorText}`,
279+
`Failed to get upload URL for part ${part}: ${status} ${partResponse.statusText} - ${errorText}`,
275280
),
276281
);
277282
return;
278283
}
279284

280285
throw new Error(
281-
`Failed to get upload URL for part ${part}: ${status} ${response.response.statusText} - ${errorText}`,
286+
`Failed to get upload URL for part ${part}: ${status} ${partResponse.statusText} - ${errorText}`,
282287
);
283288
}
284289

285-
const url = response.data.upload_url;
290+
const partData: { url: string; expires_at: string } =
291+
await partResponse.json();
292+
const url = partData.url;
286293

287294
// Read chunk from disk with progress tracking
288295
const payload = await readChunk(
@@ -375,31 +382,33 @@ const upload = new Command("upload")
375382
}
376383

377384
const sha256Hash = hash.digest("hex");
378-
const completeResponse = await client.PUT(
379-
"/v1/vms/images/{image_id}/complete_upload",
385+
386+
// Complete upload via preview/v2 API
387+
const completeResponse = await fetch(
388+
`${config.api_url}/preview/v2/images/${imageId}/complete`,
380389
{
381-
params: {
382-
path: {
383-
image_id: imageId,
384-
},
385-
},
386-
body: {
387-
sha256_hash: sha256Hash,
388-
},
390+
method: "POST",
391+
headers: apiHeaders,
392+
body: JSON.stringify({ sha256: sha256Hash }),
389393
},
390394
);
391395

392-
if (!completeResponse.data) {
396+
if (!completeResponse.ok) {
393397
throw new Error(
394-
`Failed to complete upload: ${completeResponse.response.status} ${completeResponse.response.statusText}`,
398+
`Failed to complete upload: ${completeResponse.status} ${completeResponse.statusText}`,
395399
);
396400
}
397401

402+
const completeData: {
403+
object: "image";
404+
upload_status: string;
405+
id: string;
406+
} = await completeResponse.json();
407+
398408
finalizingSpinner.succeed("Image uploaded and verified");
399409

400-
const object = completeResponse.data;
401410
console.log(chalk.gray("\nNext steps:"));
402-
console.log(` sf nodes images show ${chalk.cyan(object.id)}`);
411+
console.log(` sf nodes images show ${chalk.cyan(completeData.id)}`);
403412
} catch (err) {
404413
// Clean up spinner timer
405414
if (spinnerTimer) {

0 commit comments

Comments
 (0)