@illuma-ai/code-sandbox 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -24,7 +24,7 @@ type ErrorCallback = (error: SandboxError) => void;
24
24
  * - Write project files into the virtual filesystem
25
25
  * - Install npm dependencies from package.json
26
26
  * - Start the entry command (e.g., "node server.js")
27
- * - Track file modifications for git diffing
27
+ * - Track file modifications for diffing
28
28
  * - Provide preview URL for the iframe
29
29
  */
30
30
  export declare class NodepodRuntime {
package/dist/types.d.ts CHANGED
@@ -123,35 +123,6 @@ export interface RuntimeState {
123
123
  */
124
124
  errors: SandboxError[];
125
125
  }
126
- export interface GitHubRepo {
127
- owner: string;
128
- repo: string;
129
- branch?: string;
130
- path?: string;
131
- }
132
- export interface GitCommitRequest {
133
- owner: string;
134
- repo: string;
135
- /** Branch to create for the commit */
136
- branch: string;
137
- /** Base branch to fork from (default: 'main') */
138
- baseBranch?: string;
139
- /** Map of file paths to new contents (only changed files) */
140
- changes: FileMap;
141
- /** Commit message */
142
- message: string;
143
- }
144
- export interface GitPRRequest extends GitCommitRequest {
145
- /** PR title */
146
- title: string;
147
- /** PR body/description */
148
- body?: string;
149
- }
150
- export interface GitPRResult {
151
- number: number;
152
- url: string;
153
- branch: string;
154
- }
155
126
  /**
156
127
  * Imperative handle exposed by CodeSandbox via React.forwardRef.
157
128
  *
@@ -163,7 +134,7 @@ export interface GitPRResult {
163
134
  * ```tsx
164
135
  * const ref = useRef<CodeSandboxHandle>(null);
165
136
  *
166
- * // Push files from any source (GitHub, DB, S3, agent output)
137
+ * // Push files from any source
167
138
  * ref.current?.updateFiles(fileMap);
168
139
  *
169
140
  * // Push a single file (e.g., agent modified one file)
@@ -230,7 +201,7 @@ export interface CodeSandboxHandle {
230
201
  *
231
202
  * The sandbox is a pure renderer — it receives files via props and
232
203
  * exposes an imperative handle for programmatic updates. It does NOT
233
- * interact with any storage backend (GitHub, S3, DB) directly.
204
+ * interact with any storage backend directly.
234
205
  *
235
206
  * File sources (for initial load, pick ONE):
236
207
  * - `files` — pass a FileMap directly
@@ -245,7 +216,7 @@ export interface CodeSandboxHandle {
245
216
  * ```
246
217
  */
247
218
  export interface CodeSandboxProps {
248
- /** Pass files directly (from any source — GitHub, DB, S3, agent, etc.) */
219
+ /** Pass files directly (from any source) */
249
220
  files?: FileMap;
250
221
  /** OR use a built-in template name */
251
222
  template?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@illuma-ai/code-sandbox",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "type": "module",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Illuma AI (https://github.com/illuma-ai)",
@@ -64,8 +64,8 @@ function computeFileChanges(
64
64
  /**
65
65
  * Hook that manages the full Nodepod runtime lifecycle.
66
66
  *
67
- * The sandbox is a pure renderer — files come in via props or the
68
- * imperative handle. No GitHub, no polling, no storage backends.
67
+ * The sandbox is a pure renderer — it receives files via props or the
68
+ * imperative handle. No polling, no storage backends.
69
69
  *
70
70
  * @param props - CodeSandbox component props
71
71
  * @returns Reactive runtime state + control functions + imperative methods
@@ -301,7 +301,7 @@ export function useRuntime(props: CodeSandboxProps) {
301
301
  *
302
302
  * Called by the imperative handle's updateFiles() method.
303
303
  * Also usable as the initial boot path when files arrive asynchronously
304
- * (e.g., host fetches from GitHub/DB/S3, then calls updateFiles).
304
+ * (e.g., host fetches from a remote source, then calls updateFiles).
305
305
  */
306
306
  const updateFiles = useCallback(
307
307
  async (newFiles: FileMap, options?: { restartServer?: boolean }) => {
package/src/index.ts CHANGED
@@ -19,7 +19,6 @@ export type { WorkbenchView } from "./components/ViewSlider";
19
19
 
20
20
  // Services
21
21
  export { NodepodRuntime } from "./services/runtime";
22
- export { GitService } from "./services/git";
23
22
 
24
23
  // Hooks
25
24
  export { useRuntime } from "./hooks/useRuntime";
@@ -38,10 +37,6 @@ export type {
38
37
  BootProgress,
39
38
  RuntimeConfig,
40
39
  RuntimeState,
41
- GitHubRepo,
42
- GitCommitRequest,
43
- GitPRRequest,
44
- GitPRResult,
45
40
  CodeSandboxProps,
46
41
  CodeSandboxHandle,
47
42
  FileTreeProps,
@@ -38,7 +38,7 @@ const DBG = "[CodeSandbox:Runtime]";
38
38
  * - Write project files into the virtual filesystem
39
39
  * - Install npm dependencies from package.json
40
40
  * - Start the entry command (e.g., "node server.js")
41
- * - Track file modifications for git diffing
41
+ * - Track file modifications for diffing
42
42
  * - Provide preview URL for the iframe
43
43
  */
44
44
  export class NodepodRuntime {
package/src/types.ts CHANGED
@@ -160,43 +160,6 @@ export interface RuntimeState {
160
160
  errors: SandboxError[];
161
161
  }
162
162
 
163
- // ---------------------------------------------------------------------------
164
- // Git (GitHub API integration — utility, NOT used internally by sandbox)
165
- // ---------------------------------------------------------------------------
166
-
167
- export interface GitHubRepo {
168
- owner: string;
169
- repo: string;
170
- branch?: string; // default: 'main'
171
- path?: string; // subdirectory to clone (default: root)
172
- }
173
-
174
- export interface GitCommitRequest {
175
- owner: string;
176
- repo: string;
177
- /** Branch to create for the commit */
178
- branch: string;
179
- /** Base branch to fork from (default: 'main') */
180
- baseBranch?: string;
181
- /** Map of file paths to new contents (only changed files) */
182
- changes: FileMap;
183
- /** Commit message */
184
- message: string;
185
- }
186
-
187
- export interface GitPRRequest extends GitCommitRequest {
188
- /** PR title */
189
- title: string;
190
- /** PR body/description */
191
- body?: string;
192
- }
193
-
194
- export interface GitPRResult {
195
- number: number;
196
- url: string;
197
- branch: string;
198
- }
199
-
200
163
  // ---------------------------------------------------------------------------
201
164
  // Imperative Handle
202
165
  // ---------------------------------------------------------------------------
@@ -212,7 +175,7 @@ export interface GitPRResult {
212
175
  * ```tsx
213
176
  * const ref = useRef<CodeSandboxHandle>(null);
214
177
  *
215
- * // Push files from any source (GitHub, DB, S3, agent output)
178
+ * // Push files from any source
216
179
  * ref.current?.updateFiles(fileMap);
217
180
  *
218
181
  * // Push a single file (e.g., agent modified one file)
@@ -292,7 +255,7 @@ export interface CodeSandboxHandle {
292
255
  *
293
256
  * The sandbox is a pure renderer — it receives files via props and
294
257
  * exposes an imperative handle for programmatic updates. It does NOT
295
- * interact with any storage backend (GitHub, S3, DB) directly.
258
+ * interact with any storage backend directly.
296
259
  *
297
260
  * File sources (for initial load, pick ONE):
298
261
  * - `files` — pass a FileMap directly
@@ -307,7 +270,7 @@ export interface CodeSandboxHandle {
307
270
  * ```
308
271
  */
309
272
  export interface CodeSandboxProps {
310
- /** Pass files directly (from any source — GitHub, DB, S3, agent, etc.) */
273
+ /** Pass files directly (from any source) */
311
274
  files?: FileMap;
312
275
  /** OR use a built-in template name */
313
276
  template?: string;
@@ -1,57 +0,0 @@
1
- /**
2
- * GitService — GitHub API integration for loading and saving project files.
3
- *
4
- * Flow:
5
- * - Clone: GET repo tree → GET file contents → return FileMap
6
- * - Commit: POST branch → PUT files → POST PR
7
- *
8
- * All operations use the GitHub REST API (no git CLI needed).
9
- * Works entirely in the browser — no server required.
10
- */
11
- import type { FileMap, GitCommitRequest, GitHubRepo, GitPRRequest, GitPRResult } from "../types";
12
- /**
13
- * GitHub API service for cloning repos and creating PRs.
14
- *
15
- * @example
16
- * ```ts
17
- * const git = new GitService('ghp_xxxxx');
18
- * const files = await git.cloneRepo({ owner: 'user', repo: 'my-app' });
19
- * // ... user edits files ...
20
- * const pr = await git.createPR({
21
- * owner: 'user', repo: 'my-app',
22
- * branch: 'sandbox/changes', changes: editedFiles,
23
- * message: 'Update from sandbox', title: 'Sandbox changes',
24
- * });
25
- * ```
26
- */
27
- export declare class GitService {
28
- private token;
29
- constructor(token: string);
30
- /**
31
- * Clone a GitHub repository into a flat file map.
32
- *
33
- * Uses the Git Trees API for a single request to get the full file listing,
34
- * then fetches text file contents in parallel batches.
35
- *
36
- * @param repo - GitHub repository coordinates
37
- * @param onProgress - Optional progress callback (0-100)
38
- * @returns FileMap of path → content
39
- */
40
- cloneRepo(repo: GitHubRepo, onProgress?: (percent: number, message: string) => void): Promise<FileMap>;
41
- /**
42
- * Create a new branch with file changes and open a pull request.
43
- *
44
- * Steps:
45
- * 1. Get the base branch's HEAD SHA
46
- * 2. Create a new branch from that SHA
47
- * 3. For each changed file, update its contents on the new branch
48
- * 4. Create a pull request
49
- */
50
- createPR(request: GitPRRequest): Promise<GitPRResult>;
51
- /**
52
- * Commit changes without creating a PR.
53
- */
54
- commit(request: GitCommitRequest): Promise<string>;
55
- /** Fetch with auth headers */
56
- private fetch;
57
- }
@@ -1,415 +0,0 @@
1
- /**
2
- * GitService — GitHub API integration for loading and saving project files.
3
- *
4
- * Flow:
5
- * - Clone: GET repo tree → GET file contents → return FileMap
6
- * - Commit: POST branch → PUT files → POST PR
7
- *
8
- * All operations use the GitHub REST API (no git CLI needed).
9
- * Works entirely in the browser — no server required.
10
- */
11
-
12
- import type {
13
- FileMap,
14
- GitCommitRequest,
15
- GitHubRepo,
16
- GitPRRequest,
17
- GitPRResult,
18
- } from "../types";
19
-
20
- const GITHUB_API = "https://api.github.com";
21
-
22
- /** File extensions to treat as text (others are skipped) */
23
- const TEXT_EXTENSIONS = new Set([
24
- ".js",
25
- ".jsx",
26
- ".ts",
27
- ".tsx",
28
- ".json",
29
- ".html",
30
- ".htm",
31
- ".css",
32
- ".scss",
33
- ".sass",
34
- ".less",
35
- ".md",
36
- ".mdx",
37
- ".txt",
38
- ".yml",
39
- ".yaml",
40
- ".toml",
41
- ".xml",
42
- ".svg",
43
- ".env",
44
- ".gitignore",
45
- ".eslintrc",
46
- ".prettierrc",
47
- ".sh",
48
- ".bash",
49
- ".zsh",
50
- ".py",
51
- ".rb",
52
- ".go",
53
- ".rs",
54
- ".java",
55
- ".kt",
56
- ".c",
57
- ".cpp",
58
- ".h",
59
- ".hpp",
60
- ".cs",
61
- ".php",
62
- ".sql",
63
- ".graphql",
64
- ".lock",
65
- ".mjs",
66
- ".cjs",
67
- ".mts",
68
- ".cts",
69
- ".vue",
70
- ".svelte",
71
- ".astro",
72
- ]);
73
-
74
- /** Directories to skip when cloning */
75
- const SKIP_DIRS = new Set([
76
- "node_modules",
77
- ".git",
78
- "dist",
79
- "build",
80
- ".next",
81
- ".cache",
82
- "__pycache__",
83
- ".venv",
84
- "venv",
85
- "coverage",
86
- ".nyc_output",
87
- ]);
88
-
89
- /** Max file size to fetch (skip large files) */
90
- const MAX_FILE_SIZE = 500_000; // 500KB
91
-
92
- /**
93
- * GitHub API service for cloning repos and creating PRs.
94
- *
95
- * @example
96
- * ```ts
97
- * const git = new GitService('ghp_xxxxx');
98
- * const files = await git.cloneRepo({ owner: 'user', repo: 'my-app' });
99
- * // ... user edits files ...
100
- * const pr = await git.createPR({
101
- * owner: 'user', repo: 'my-app',
102
- * branch: 'sandbox/changes', changes: editedFiles,
103
- * message: 'Update from sandbox', title: 'Sandbox changes',
104
- * });
105
- * ```
106
- */
107
- export class GitService {
108
- private token: string;
109
-
110
- constructor(token: string) {
111
- this.token = token;
112
- }
113
-
114
- // -------------------------------------------------------------------------
115
- // Clone: fetch repo files as a FileMap
116
- // -------------------------------------------------------------------------
117
-
118
- /**
119
- * Clone a GitHub repository into a flat file map.
120
- *
121
- * Uses the Git Trees API for a single request to get the full file listing,
122
- * then fetches text file contents in parallel batches.
123
- *
124
- * @param repo - GitHub repository coordinates
125
- * @param onProgress - Optional progress callback (0-100)
126
- * @returns FileMap of path → content
127
- */
128
- async cloneRepo(
129
- repo: GitHubRepo,
130
- onProgress?: (percent: number, message: string) => void,
131
- ): Promise<FileMap> {
132
- const { owner, repo: repoName, branch = "main", path: subPath } = repo;
133
-
134
- onProgress?.(5, "Fetching repository structure...");
135
-
136
- // 1. Get the tree (recursive) for the branch
137
- const treeUrl = `${GITHUB_API}/repos/${owner}/${repoName}/git/trees/${branch}?recursive=1`;
138
- const treeRes = await this.fetch(treeUrl);
139
- const treeData = await treeRes.json();
140
-
141
- if (!treeData.tree) {
142
- throw new Error(
143
- `Failed to fetch repo tree: ${treeData.message || "Unknown error"}`,
144
- );
145
- }
146
-
147
- // 2. Filter to text files, skip large/binary/excluded dirs
148
- const filesToFetch: Array<{ path: string; sha: string; size: number }> = [];
149
-
150
- for (const item of treeData.tree) {
151
- if (item.type !== "blob") {
152
- continue;
153
- }
154
-
155
- // Apply subPath filter if specified
156
- if (subPath && !item.path.startsWith(subPath)) {
157
- continue;
158
- }
159
-
160
- // Skip excluded directories
161
- const parts = item.path.split("/");
162
- if (parts.some((p: string) => SKIP_DIRS.has(p))) {
163
- continue;
164
- }
165
-
166
- // Skip files that are too large
167
- if (item.size > MAX_FILE_SIZE) {
168
- continue;
169
- }
170
-
171
- // Only include text files
172
- const ext = "." + item.path.split(".").pop()?.toLowerCase();
173
- const basename = parts[parts.length - 1];
174
- // Include files with known extensions, dotfiles, or extensionless config files
175
- if (
176
- TEXT_EXTENSIONS.has(ext) ||
177
- basename.startsWith(".") ||
178
- !basename.includes(".")
179
- ) {
180
- filesToFetch.push({ path: item.path, sha: item.sha, size: item.size });
181
- }
182
- }
183
-
184
- onProgress?.(15, `Found ${filesToFetch.length} files to fetch...`);
185
-
186
- // 3. Fetch file contents in parallel batches
187
- const files: FileMap = {};
188
- const batchSize = 20;
189
-
190
- for (let i = 0; i < filesToFetch.length; i += batchSize) {
191
- const batch = filesToFetch.slice(i, i + batchSize);
192
-
193
- const results = await Promise.all(
194
- batch.map(async (file) => {
195
- try {
196
- const contentUrl = `${GITHUB_API}/repos/${owner}/${repoName}/contents/${file.path}?ref=${branch}`;
197
- const res = await this.fetch(contentUrl);
198
- const data = await res.json();
199
-
200
- if (data.encoding === "base64" && data.content) {
201
- const content = atob(data.content.replace(/\n/g, ""));
202
- // Strip subPath prefix if specified
203
- const relativePath = subPath
204
- ? file.path.replace(new RegExp(`^${subPath}/?`), "")
205
- : file.path;
206
- return { path: relativePath, content };
207
- }
208
- return null;
209
- } catch {
210
- return null;
211
- }
212
- }),
213
- );
214
-
215
- for (const result of results) {
216
- if (result) {
217
- files[result.path] = result.content;
218
- }
219
- }
220
-
221
- const percent =
222
- 15 + Math.round(((i + batch.length) / filesToFetch.length) * 80);
223
- onProgress?.(
224
- percent,
225
- `Fetching files... (${Math.min(i + batchSize, filesToFetch.length)}/${filesToFetch.length})`,
226
- );
227
- }
228
-
229
- onProgress?.(100, `Loaded ${Object.keys(files).length} files`);
230
-
231
- return files;
232
- }
233
-
234
- // -------------------------------------------------------------------------
235
- // Commit + PR: write changes back to GitHub
236
- // -------------------------------------------------------------------------
237
-
238
- /**
239
- * Create a new branch with file changes and open a pull request.
240
- *
241
- * Steps:
242
- * 1. Get the base branch's HEAD SHA
243
- * 2. Create a new branch from that SHA
244
- * 3. For each changed file, update its contents on the new branch
245
- * 4. Create a pull request
246
- */
247
- async createPR(request: GitPRRequest): Promise<GitPRResult> {
248
- const {
249
- owner,
250
- repo,
251
- branch,
252
- baseBranch = "main",
253
- changes,
254
- message,
255
- title,
256
- body,
257
- } = request;
258
-
259
- // 1. Get base branch HEAD SHA
260
- const refRes = await this.fetch(
261
- `${GITHUB_API}/repos/${owner}/${repo}/git/ref/heads/${baseBranch}`,
262
- );
263
- const refData = await refRes.json();
264
- const baseSha = refData.object.sha;
265
-
266
- // 2. Create new branch
267
- await this.fetch(`${GITHUB_API}/repos/${owner}/${repo}/git/refs`, {
268
- method: "POST",
269
- body: JSON.stringify({
270
- ref: `refs/heads/${branch}`,
271
- sha: baseSha,
272
- }),
273
- });
274
-
275
- // 3. Commit each changed file
276
- for (const [path, content] of Object.entries(changes)) {
277
- // Get current file SHA (if it exists)
278
- let fileSha: string | undefined;
279
- try {
280
- const fileRes = await this.fetch(
281
- `${GITHUB_API}/repos/${owner}/${repo}/contents/${path}?ref=${branch}`,
282
- );
283
- const fileData = await fileRes.json();
284
- fileSha = fileData.sha;
285
- } catch {
286
- // File doesn't exist yet — that's fine, we'll create it
287
- }
288
-
289
- await this.fetch(
290
- `${GITHUB_API}/repos/${owner}/${repo}/contents/${path}`,
291
- {
292
- method: "PUT",
293
- body: JSON.stringify({
294
- message,
295
- content: btoa(content),
296
- branch,
297
- ...(fileSha ? { sha: fileSha } : {}),
298
- }),
299
- },
300
- );
301
- }
302
-
303
- // 4. Create pull request
304
- const prRes = await this.fetch(
305
- `${GITHUB_API}/repos/${owner}/${repo}/pulls`,
306
- {
307
- method: "POST",
308
- body: JSON.stringify({
309
- title,
310
- body:
311
- body ||
312
- `Changes from code sandbox.\n\nModified files:\n${Object.keys(
313
- changes,
314
- )
315
- .map((p) => `- ${p}`)
316
- .join("\n")}`,
317
- head: branch,
318
- base: baseBranch,
319
- }),
320
- },
321
- );
322
-
323
- const prData = await prRes.json();
324
-
325
- return {
326
- number: prData.number,
327
- url: prData.html_url,
328
- branch,
329
- };
330
- }
331
-
332
- /**
333
- * Commit changes without creating a PR.
334
- */
335
- async commit(request: GitCommitRequest): Promise<string> {
336
- const {
337
- owner,
338
- repo,
339
- branch,
340
- baseBranch = "main",
341
- changes,
342
- message,
343
- } = request;
344
-
345
- // Get base SHA
346
- const refRes = await this.fetch(
347
- `${GITHUB_API}/repos/${owner}/${repo}/git/ref/heads/${baseBranch}`,
348
- );
349
- const refData = await refRes.json();
350
- const baseSha = refData.object.sha;
351
-
352
- // Create branch (may already exist)
353
- try {
354
- await this.fetch(`${GITHUB_API}/repos/${owner}/${repo}/git/refs`, {
355
- method: "POST",
356
- body: JSON.stringify({ ref: `refs/heads/${branch}`, sha: baseSha }),
357
- });
358
- } catch {
359
- // Branch already exists — continue
360
- }
361
-
362
- // Commit files
363
- for (const [path, content] of Object.entries(changes)) {
364
- let fileSha: string | undefined;
365
- try {
366
- const fileRes = await this.fetch(
367
- `${GITHUB_API}/repos/${owner}/${repo}/contents/${path}?ref=${branch}`,
368
- );
369
- const fileData = await fileRes.json();
370
- fileSha = fileData.sha;
371
- } catch {
372
- // New file
373
- }
374
-
375
- await this.fetch(
376
- `${GITHUB_API}/repos/${owner}/${repo}/contents/${path}`,
377
- {
378
- method: "PUT",
379
- body: JSON.stringify({
380
- message,
381
- content: btoa(content),
382
- branch,
383
- ...(fileSha ? { sha: fileSha } : {}),
384
- }),
385
- },
386
- );
387
- }
388
-
389
- return branch;
390
- }
391
-
392
- // -------------------------------------------------------------------------
393
- // Private
394
- // -------------------------------------------------------------------------
395
-
396
- /** Fetch with auth headers */
397
- private async fetch(url: string, init?: RequestInit): Promise<Response> {
398
- const res = await fetch(url, {
399
- ...init,
400
- headers: {
401
- Authorization: `Bearer ${this.token}`,
402
- Accept: "application/vnd.github.v3+json",
403
- "Content-Type": "application/json",
404
- ...(init?.headers || {}),
405
- },
406
- });
407
-
408
- if (!res.ok) {
409
- const body = await res.text();
410
- throw new Error(`GitHub API error ${res.status}: ${body}`);
411
- }
412
-
413
- return res;
414
- }
415
- }