Skip to content

Commit 8b5a4f0

Browse files
recover building templates
1 parent 75d1512 commit 8b5a4f0

File tree

2 files changed

+156
-47
lines changed

2 files changed

+156
-47
lines changed

src/Sandbox.ts

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,19 @@ export class Sandbox {
144144
return session;
145145
}
146146

147+
private getCustomEnv(customSession?: SessionCreateOptions) {
148+
if (!customSession) {
149+
return undefined;
150+
}
151+
152+
return customSession.git
153+
? {
154+
...customSession.env,
155+
GIT_CONFIG: "$HOME/private/.gitconfig",
156+
}
157+
: customSession.env;
158+
}
159+
147160
/**
148161
* Connects to the Sandbox using a WebSocket connection, allowing you to interact with it. You can pass a custom session to connect to a specific user workspace, controlling permissions, git credentials and environment variables.
149162
*/
@@ -178,39 +191,48 @@ export class Sandbox {
178191

179192
const session = await Session.create(agentClient, {
180193
username: customSession ? joinResult.client.username : undefined,
181-
env: customSession?.env,
194+
env: this.getCustomEnv(customSession),
182195
hostToken: customSession?.hostToken,
183196
});
184197

185198
if (customSession?.git) {
186-
const netrc = await session.commands.runBackground([
187-
`mkdir -p ~/private`,
188-
`cat > ~/private/.netrc <<EOF
189-
machine ${customSession.git.provider}
190-
login ${customSession.git.username || "x-access-token"}
191-
password ${customSession.git.accessToken}
192-
EOF`,
193-
`chmod 600 ~/private/.netrc`,
194-
`cd ~`,
195-
`ln -sfn private/.netrc .netrc`,
196-
]);
197-
netrc.onOutput(console.log);
198-
console.log(await netrc.open());
199-
await netrc.waitUntilComplete();
199+
try {
200+
await session.fs.mkdir("/root/private");
201+
} catch {}
200202

201-
const config = await session.commands.runBackground([
202-
`cat > ~/private/.gitconfig <<EOF
203-
[user]
203+
await Promise.all([
204+
session.fs.writeTextFile(
205+
"/root/private/.gitcredentials",
206+
`https://${customSession.git.username || "x-access-token"}:${
207+
customSession.git.accessToken
208+
}@${customSession.git.provider}\n`,
209+
{
210+
create: true,
211+
overwrite: true,
212+
}
213+
),
214+
session.fs.writeTextFile(
215+
"/root/private/.gitconfig",
216+
`[user]
204217
name = ${customSession.git.name || customSession.id}
205218
email = ${customSession.git.email}
206-
EOF`,
207-
`chmod 600 ~/private/.gitconfig`,
208-
`cd "~"`,
209-
`ln -sfn private/.gitconfig .gitconfig`,
219+
[credential]
220+
helper = store --file ~/private/.gitcredentials`,
221+
{
222+
create: true,
223+
overwrite: true,
224+
}
225+
),
210226
]);
211-
config.onOutput(console.log);
212-
console.log(await config.open());
213-
await config.waitUntilComplete();
227+
}
228+
229+
return session;
230+
}
231+
232+
/**
233+
* Returns a browser session connected to this Sandbox, allowing you to interact with it. You can pass a custom session to connect to a specific user workspace, controlling permissions, git credentials and environment variables.
234+
}\n[credential]\n helper = store --file ~/private/.gitcredentials\n"`
235+
);
214236
}
215237
216238
return session;
@@ -228,7 +250,7 @@ EOF`,
228250

229251
return {
230252
id: this.id,
231-
env: customSession?.env,
253+
env: this.getCustomEnv(customSession),
232254
sessionId: customSession?.id,
233255
hostToken: customSession?.hostToken,
234256
bootupType: this.bootupType,

src/bin/commands/build.ts

Lines changed: 108 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { promises as fs } from "fs";
22
import path from "path";
33
import { isBinaryFile } from "isbinaryfile";
4+
import * as readline from "readline";
45

56
import { createClient, createConfig, type Client } from "@hey-api/client-fetch";
67
import ora from "ora";
@@ -118,7 +119,7 @@ export const buildCommand: yargs.CommandModule<
118119
return `\n${spinnerMessages.join("\n")}`;
119120
}
120121

121-
const sandboxIds = await Promise.all(
122+
const sandboxes = await Promise.all(
122123
clusters.map(async ({ host: cluster, slug }, index) => {
123124
const clusterApiClient: Client = createClient(
124125
createConfig({
@@ -129,14 +130,6 @@ export const buildCommand: yargs.CommandModule<
129130
},
130131
})
131132
);
132-
const sdk = new CodeSandbox(API_KEY, {
133-
baseUrl: BASE_URL,
134-
headers: {
135-
"x-pitcher-manager-url": `https://${cluster}/api/v1`,
136-
},
137-
});
138-
139-
let sandboxId: string | undefined;
140133

141134
try {
142135
const { hash, files: filePaths } = await hashDirectory(
@@ -146,7 +139,8 @@ export const buildCommand: yargs.CommandModule<
146139
const tag = `sha:${shortHash}-${slug}`;
147140

148141
spinner.start(updateSpinnerMessage(index, "Creating sandbox..."));
149-
sandboxId = await createSandbox({
142+
143+
const sandboxId = await createSandbox({
150144
apiClient: clusterApiClient,
151145
shaTag: tag,
152146
fromSandbox: argv.fromSandbox,
@@ -157,6 +151,40 @@ export const buildCommand: yargs.CommandModule<
157151
: VMTier.fromName("Micro"),
158152
});
159153

154+
return { sandboxId, filePaths, cluster };
155+
} catch (error) {
156+
spinner.fail(
157+
updateSpinnerMessage(
158+
index,
159+
error instanceof Error
160+
? error.message
161+
: "Unknown error occurred"
162+
)
163+
);
164+
throw error;
165+
}
166+
})
167+
);
168+
169+
const results = await Promise.allSettled(
170+
sandboxes.map(async ({ sandboxId, filePaths, cluster }, index) => {
171+
const clusterApiClient: Client = createClient(
172+
createConfig({
173+
baseUrl: BASE_URL,
174+
headers: {
175+
Authorization: `Bearer ${API_KEY}`,
176+
"x-pitcher-manager-url": `https://${cluster}/api/v1`,
177+
},
178+
})
179+
);
180+
const sdk = new CodeSandbox(API_KEY, {
181+
baseUrl: BASE_URL,
182+
headers: {
183+
"x-pitcher-manager-url": `https://${cluster}/api/v1`,
184+
},
185+
});
186+
187+
try {
160188
spinner.start(
161189
updateSpinnerMessage(index, "Starting sandbox...", sandboxId)
162190
);
@@ -209,6 +237,7 @@ export const buildCommand: yargs.CommandModule<
209237
? VMTier.fromName(argv.vmTier)
210238
: VMTier.fromName("Micro"),
211239
});
240+
212241
session = await sandbox.connect();
213242

214243
const disposableStore = new DisposableStore();
@@ -295,11 +324,7 @@ export const buildCommand: yargs.CommandModule<
295324
}
296325

297326
spinner.start(
298-
updateSpinnerMessage(
299-
index,
300-
"Creating memory snapshot...",
301-
sandboxId
302-
)
327+
updateSpinnerMessage(index, "Creating snapshot...", sandboxId)
303328
);
304329
await sdk.sandboxes.hibernate(sandbox.id);
305330
spinner.start(
@@ -308,27 +333,75 @@ export const buildCommand: yargs.CommandModule<
308333

309334
return sandbox.id;
310335
} catch (error) {
311-
spinner.fail(
336+
spinner.start(
312337
updateSpinnerMessage(
313338
index,
314-
error instanceof Error
315-
? error.message
316-
: "Unknown error occurred",
339+
"Failed, please manually verify at https://codesandbox.io/s/" +
340+
sandboxId,
317341
sandboxId
318342
)
319343
);
344+
320345
throw error;
321346
}
322347
})
323348
);
324349

325-
spinner.succeed(`\n${spinnerMessages.join("\n")}`);
350+
const failedSandboxes = sandboxes.filter(
351+
(_, index) => results[index].status === "rejected"
352+
);
353+
354+
if (failedSandboxes.length > 0) {
355+
spinner.info(`\n${spinnerMessages.join("\n")}`);
356+
357+
await waitForEnter(
358+
`\nThere was an issue preparing the sandboxes. Verify ${failedSandboxes
359+
.map((sandbox) => sandbox.sandboxId)
360+
.join(", ")} and press ENTER to create snapshot...\n`
361+
);
362+
363+
failedSandboxes.forEach(({ sandboxId }) => {
364+
updateSpinnerMessage(
365+
sandboxes.findIndex((sandbox) => sandbox.sandboxId === sandboxId),
366+
"Creating snapshot...",
367+
sandboxId
368+
);
369+
});
370+
371+
spinner.start(`\n${spinnerMessages.join("\n")}`);
372+
373+
await Promise.all(
374+
failedSandboxes.map(async ({ sandboxId, cluster }) => {
375+
const sdk = new CodeSandbox(API_KEY, {
376+
baseUrl: BASE_URL,
377+
headers: {
378+
"x-pitcher-manager-url": `https://${cluster}/api/v1`,
379+
},
380+
});
381+
382+
await sdk.sandboxes.hibernate(sandboxId);
383+
384+
spinner.start(
385+
updateSpinnerMessage(
386+
sandboxes.findIndex(
387+
(sandbox) => sandbox.sandboxId === sandboxId
388+
),
389+
"Snapshot created",
390+
sandboxId
391+
)
392+
);
393+
})
394+
);
395+
spinner.succeed(`\n${spinnerMessages.join("\n")}`);
396+
} else {
397+
spinner.succeed(`\n${spinnerMessages.join("\n")}`);
398+
}
326399

327400
const data = handleResponse(
328401
await vmCreateTag({
329402
client: apiClient,
330403
body: {
331-
vm_ids: sandboxIds,
404+
vm_ids: sandboxes.map((sandbox) => sandbox.sandboxId),
332405
},
333406
}),
334407
"Failed to create template"
@@ -371,6 +444,20 @@ type CreateSandboxParams = {
371444
ipcountry?: string;
372445
};
373446

447+
function waitForEnter(message: string) {
448+
const rl = readline.createInterface({
449+
input: process.stdin,
450+
output: process.stdout,
451+
});
452+
453+
return new Promise<void>((resolve) => {
454+
rl.question(message, () => {
455+
rl.close();
456+
resolve();
457+
});
458+
});
459+
}
460+
374461
function createAlias(directory: string, alias: string) {
375462
const aliasParts = alias.split("@");
376463

0 commit comments

Comments
 (0)