Skip to content

Commit c60b62e

Browse files
Merge pull request #96 from codesandbox/updates
feat: New release
2 parents 8127b49 + 49ecec8 commit c60b62e

File tree

9 files changed

+136
-27
lines changed

9 files changed

+136
-27
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@codesandbox/sdk",
3-
"version": "1.0.0",
3+
"version": "1.1.0",
44
"description": "The CodeSandbox SDK",
55
"author": "CodeSandbox",
66
"license": "MIT",

src/Sandbox.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,41 +70,42 @@ export class Sandbox {
7070
* new specs without a reboot. Be careful when scaling specs down, if the VM is using more memory
7171
* than it can scale down to, it can become very slow.
7272
*/
73-
async updateTier(sandboxId: string, tier: VMTier): Promise<void> {
73+
async updateTier(tier: VMTier): Promise<void> {
7474
const response = await vmUpdateSpecs({
7575
client: this.apiClient,
76-
path: { id: sandboxId },
76+
path: { id: this.id },
7777
body: {
7878
tier: tier.name,
7979
},
8080
});
8181

82-
handleResponse(response, `Failed to update sandbox tier ${sandboxId}`);
82+
handleResponse(response, `Failed to update sandbox tier ${this.id}`);
8383
}
8484

8585
/**
8686
* Updates the hibernation timeout for this sandbox. This is the amount of seconds the sandbox
8787
* will be kept alive without activity before it is automatically hibernated. Activity can be sessions or interactions with any endpoints exposed by the Sandbox.
8888
*/
89-
async updateHibernationTimeout(
90-
sandboxId: string,
91-
timeoutSeconds: number
92-
): Promise<void> {
89+
async updateHibernationTimeout(timeoutSeconds: number): Promise<void> {
9390
const response = await vmUpdateHibernationTimeout({
9491
client: this.apiClient,
95-
path: { id: sandboxId },
92+
path: { id: this.id },
9693
body: { hibernation_timeout_seconds: timeoutSeconds },
9794
});
9895

9996
handleResponse(
10097
response,
101-
`Failed to update hibernation timeout for sandbox ${sandboxId}`
98+
`Failed to update hibernation timeout for sandbox ${this.id}`
10299
);
103100
}
104101

105102
private async createSession(
106103
opts: SessionCreateOptions
107104
): Promise<SandboxSession> {
105+
if (opts.id.length > 20) {
106+
throw new Error("Session ID must be 32 characters or less");
107+
}
108+
108109
const response = await vmCreateSession({
109110
client: this.apiClient,
110111
body: {
@@ -236,6 +237,7 @@ export class Sandbox {
236237
return {
237238
id: this.id,
238239
env: customSession?.env,
240+
hostToken: customSession?.hostToken,
239241
bootupType: this.bootupType,
240242
cluster: this.cluster,
241243
latestPitcherVersion: this.pitcherManagerResponse.latestPitcherVersion,

src/Sandboxes.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export class Sandboxes {
7272
const sandbox = await this.createTemplateSandbox({
7373
...opts,
7474
source: "template",
75-
id: this.defaultTemplateId,
75+
id: opts.templateId || this.defaultTemplateId,
7676
});
7777

7878
const session = await sandbox.connect(
@@ -111,7 +111,7 @@ export class Sandboxes {
111111
opts: CreateSandboxTemplateSourceOpts & StartSandboxOpts
112112
) {
113113
const templateId = opts.id || this.defaultTemplateId;
114-
const privacy = opts.privacy || "public";
114+
const privacy = opts.privacy || "unlisted";
115115
const tags = opts.tags || ["sdk"];
116116
const path = opts.path || "/SDK";
117117

@@ -169,6 +169,17 @@ export class Sandboxes {
169169
handleResponse(response, `Failed to shutdown sandbox ${sandboxId}`);
170170
}
171171

172+
/**
173+
* Forks a sandbox. This will create a new sandbox from the given sandbox.
174+
*/
175+
public async fork(sandboxId: string, opts?: StartSandboxOpts) {
176+
return this.create({
177+
source: "template",
178+
id: sandboxId,
179+
...opts,
180+
});
181+
}
182+
172183
/**
173184
* Restart the sandbox. This will shutdown the sandbox, and then start it again. Files in
174185
* the project directory (`/project/sandbox`) will be preserved.
@@ -210,6 +221,9 @@ export class Sandboxes {
210221
case "template": {
211222
return this.createTemplateSandbox(opts);
212223
}
224+
default: {
225+
throw new Error("Invalid source");
226+
}
213227
}
214228
}
215229

src/sessions/WebSocketSession/commands.ts

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,7 @@ export class Commands {
3636
/**
3737
* Create and run command in a new shell. Allows you to listen to the output and kill the command.
3838
*/
39-
async runBackground(
40-
command: string | string[],
41-
opts?: Omit<ShellRunOpts, "name">
42-
) {
39+
async runBackground(command: string | string[], opts?: ShellRunOpts) {
4340
const disposableStore = new DisposableStore();
4441
const onOutput = new Emitter<string>();
4542
disposableStore.add(onOutput);
@@ -67,15 +64,24 @@ export class Commands {
6764
true
6865
);
6966

67+
const details = {
68+
type: "command",
69+
command,
70+
name: opts?.name,
71+
};
72+
7073
// Only way for us to differentiate between a command and a terminal
7174
this.pitcherClient.clients.shell.rename(
7275
shell.shellId,
73-
`COMMAND-${shell.shellId}`
76+
// We embed some details in the name to properly show the command that was run
77+
// , the name and that it is an actual command
78+
JSON.stringify(details)
7479
);
7580

7681
const cmd = new Command(
7782
this.pitcherClient,
78-
shell as protocol.shell.CommandShellDTO
83+
shell as protocol.shell.CommandShellDTO,
84+
details
7985
);
8086

8187
return cmd;
@@ -98,10 +104,24 @@ export class Commands {
98104

99105
return shells
100106
.filter(
101-
(shell) =>
102-
shell.shellType === "TERMINAL" && shell.name.startsWith("COMMAND-")
107+
(shell) => shell.shellType === "TERMINAL" && isCommandShell(shell)
103108
)
104-
.map((shell) => new Command(this.pitcherClient, shell));
109+
.map(
110+
(shell) =>
111+
new Command(this.pitcherClient, shell, JSON.parse(shell.name))
112+
);
113+
}
114+
}
115+
116+
export function isCommandShell(
117+
shell: protocol.shell.ShellDTO
118+
): shell is protocol.shell.CommandShellDTO {
119+
try {
120+
const parsed = JSON.parse(shell.name);
121+
122+
return parsed.type === "command";
123+
} catch {
124+
return false;
105125
}
106126
}
107127

@@ -133,10 +153,24 @@ export class Command {
133153
*/
134154
status: CommandStatus = "RUNNING";
135155

156+
/**
157+
* The command that was run
158+
*/
159+
command: string;
160+
161+
/**
162+
* The name of the command
163+
*/
164+
name?: string;
165+
136166
constructor(
137167
private pitcherClient: IPitcherClient,
138-
private shell: protocol.shell.ShellDTO & { buffer?: string[] }
168+
private shell: protocol.shell.ShellDTO & { buffer?: string[] },
169+
details: { command: string; name?: string }
139170
) {
171+
this.command = details.command;
172+
this.name = details.name;
173+
140174
this.disposable.addDisposable(
141175
pitcherClient.clients.shell.onShellExited(({ shellId, exitCode }) => {
142176
if (shellId === this.shell.shellId) {
@@ -171,6 +205,20 @@ export class Command {
171205
);
172206
}
173207

208+
/**
209+
* Open the command and get its current output, subscribes to future output
210+
*/
211+
async open(dimensions = DEFAULT_SHELL_SIZE): Promise<string> {
212+
const shell = await this.pitcherClient.clients.shell.open(
213+
this.shell.shellId,
214+
dimensions
215+
);
216+
217+
this.output = shell.buffer;
218+
219+
return this.output.join("\n");
220+
}
221+
174222
/**
175223
* Wait for the command to finish with its returned output
176224
*/
Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,55 @@
11
import type { IPitcherClient } from "@codesandbox/pitcher-client";
2+
import { Commands } from "./commands";
23

34
export class Git {
45
/**
56
* An event that is emitted when the git status changes.
67
*/
78
onStatusChange = this.pitcherClient.clients.git.onStatusUpdated;
89

9-
constructor(private pitcherClient: IPitcherClient) {}
10+
constructor(
11+
private pitcherClient: IPitcherClient,
12+
private commands: Commands
13+
) {}
1014

1115
/**
1216
* Get the current git status.
1317
*/
1418
status() {
1519
return this.pitcherClient.clients.git.getStatus();
1620
}
21+
22+
/**
23+
* Commit all changes to git
24+
*/
25+
async commit(message: string) {
26+
const messageWithQuotesEscaped = message.replace(/"/g, '\\"');
27+
28+
await this.commands.run([
29+
"git add .",
30+
`git commit -m "${messageWithQuotesEscaped}"`,
31+
]);
32+
}
33+
34+
/**
35+
* Checkout a branch
36+
*/
37+
async checkout(branch: string, isNewBranch = false) {
38+
if (isNewBranch) {
39+
await this.commands.run([
40+
"git checkout -b " + branch,
41+
"git push --set-upstream origin " + branch,
42+
]);
43+
return;
44+
}
45+
46+
await this.commands.run(["git checkout " + branch]);
47+
}
48+
49+
/**
50+
* Push all changes to git
51+
*/
52+
async push() {
53+
await this.commands.run(["git push"]);
54+
}
1755
}

src/sessions/WebSocketSession/hosts.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { IPitcherClient } from "@codesandbox/pitcher-client";
22
import { HostToken } from "../../Hosts";
33

4+
export { HostToken } from "../../Hosts";
5+
46
export class Hosts {
57
constructor(
68
private pitcherClient: IPitcherClient,

src/sessions/WebSocketSession/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export * from "./terminals";
2323
export * from "./commands";
2424
export * from "./git";
2525
export * from "./interpreters";
26+
export * from "./hosts";
2627

2728
export class WebSocketSession {
2829
private disposable = new Disposable();
@@ -55,7 +56,7 @@ export class WebSocketSession {
5556
/**
5657
* Namespace for Git operations in the Sandbox
5758
*/
58-
public readonly git = new Git(this.pitcherClient);
59+
public readonly git: Git;
5960

6061
/**
6162
* Namespace for managing ports on this Sandbox
@@ -88,6 +89,7 @@ export class WebSocketSession {
8889

8990
this.hosts = new Hosts(this.pitcherClient, hostToken);
9091
this.interpreters = new Interpreters(this.disposable, this.commands);
92+
this.git = new Git(this.pitcherClient, this.commands);
9193
this.disposable.addDisposable(this.pitcherClient);
9294
}
9395

src/sessions/WebSocketSession/terminals.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { protocol, IPitcherClient } from "@codesandbox/pitcher-client";
22
import type { Id } from "@codesandbox/pitcher-common";
33
import { Disposable } from "../../utils/disposable";
44
import { Emitter } from "../../utils/event";
5-
import { ShellRunOpts } from "./commands";
5+
import { isCommandShell, ShellRunOpts } from "./commands";
66

77
export type ShellSize = { cols: number; rows: number };
88

@@ -72,8 +72,7 @@ export class Terminals {
7272

7373
return shells
7474
.filter(
75-
(shell) =>
76-
shell.shellType === "TERMINAL" && !shell.name.startsWith("COMMAND-")
75+
(shell) => shell.shellType === "TERMINAL" && !isCommandShell(shell)
7776
)
7877
.map((shell) => new Terminal(shell, this.pitcherClient));
7978
}
@@ -121,6 +120,9 @@ export class Terminal {
121120
);
122121
}
123122

123+
/**
124+
* Open the terminal and get its current output, subscribes to future output
125+
*/
124126
async open(dimensions = DEFAULT_SHELL_SIZE): Promise<string> {
125127
const shell = await this.pitcherClient.clients.shell.open(
126128
this.shell.shellId,

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ export type CreateSandboxGitSourceOpts = CreateSandboxBaseOpts & {
214214
source: "git";
215215
url: string;
216216
branch: string;
217+
templateId?: string;
217218
config?: {
218219
accessToken: string;
219220
email: string;

0 commit comments

Comments
 (0)