@illuma-ai/code-sandbox 1.0.0
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.
- package/LICENSE +15 -0
- package/dist/__sw__.js +712 -0
- package/dist/code-sandbox.css +1 -0
- package/dist/components/BootOverlay.d.ts +17 -0
- package/dist/components/CodeEditor.d.ts +11 -0
- package/dist/components/FileTree.d.ts +19 -0
- package/dist/components/Preview.d.ts +15 -0
- package/dist/components/Terminal.d.ts +15 -0
- package/dist/components/ViewSlider.d.ts +25 -0
- package/dist/components/Workbench.d.ts +28 -0
- package/dist/hooks/useRuntime.d.ts +25 -0
- package/dist/index.cjs +50074 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +77335 -0
- package/dist/index.js.map +1 -0
- package/dist/services/git.d.ts +57 -0
- package/dist/services/runtime.d.ts +119 -0
- package/dist/templates/fullstack-starter.d.ts +38 -0
- package/dist/templates/index.d.ts +38 -0
- package/dist/types.d.ts +137 -0
- package/package.json +69 -0
- package/src/components/BootOverlay.tsx +145 -0
- package/src/components/CodeEditor.tsx +168 -0
- package/src/components/FileTree.tsx +286 -0
- package/src/components/Preview.tsx +50 -0
- package/src/components/Terminal.tsx +68 -0
- package/src/components/ViewSlider.tsx +87 -0
- package/src/components/Workbench.tsx +301 -0
- package/src/hooks/useRuntime.ts +236 -0
- package/src/index.ts +48 -0
- package/src/services/git.ts +415 -0
- package/src/services/runtime.ts +536 -0
- package/src/styles.css +24 -0
- package/src/templates/fullstack-starter.ts +3297 -0
- package/src/templates/index.ts +607 -0
- package/src/types.ts +179 -0
|
@@ -0,0 +1,57 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NodepodRuntime — Manages the full lifecycle of a Nodepod sandbox.
|
|
3
|
+
*
|
|
4
|
+
* Flow: boot → write files → npm install → run entry command → track changes
|
|
5
|
+
*
|
|
6
|
+
* This is the core service that bridges project files and the browser runtime.
|
|
7
|
+
* It is framework-agnostic (no React) — consumed by the useRuntime hook.
|
|
8
|
+
*/
|
|
9
|
+
import { Nodepod } from "@illuma-ai/nodepod";
|
|
10
|
+
import type { BootProgress, BootStage, FileMap, RuntimeConfig } from "../types";
|
|
11
|
+
/** Callback type for progress updates */
|
|
12
|
+
type ProgressCallback = (progress: BootProgress) => void;
|
|
13
|
+
/** Callback type for terminal output lines */
|
|
14
|
+
type OutputCallback = (line: string) => void;
|
|
15
|
+
/** Callback type for server ready */
|
|
16
|
+
type ServerReadyCallback = (port: number, url: string) => void;
|
|
17
|
+
/**
|
|
18
|
+
* Manages the Nodepod runtime lifecycle.
|
|
19
|
+
*
|
|
20
|
+
* Responsibilities:
|
|
21
|
+
* - Boot Nodepod with COOP/COEP-compatible service worker
|
|
22
|
+
* - Write project files into the virtual filesystem
|
|
23
|
+
* - Install npm dependencies from package.json
|
|
24
|
+
* - Start the entry command (e.g., "node server.js")
|
|
25
|
+
* - Track file modifications for git diffing
|
|
26
|
+
* - Provide preview URL for the iframe
|
|
27
|
+
*/
|
|
28
|
+
export declare class NodepodRuntime {
|
|
29
|
+
private nodepod;
|
|
30
|
+
private serverProcess;
|
|
31
|
+
private config;
|
|
32
|
+
private originalFiles;
|
|
33
|
+
private currentFiles;
|
|
34
|
+
private terminalOutput;
|
|
35
|
+
private status;
|
|
36
|
+
private error;
|
|
37
|
+
private previewUrl;
|
|
38
|
+
private onProgress;
|
|
39
|
+
private onOutput;
|
|
40
|
+
private onServerReady;
|
|
41
|
+
constructor(config: RuntimeConfig);
|
|
42
|
+
/** Register a progress callback */
|
|
43
|
+
setProgressCallback(cb: ProgressCallback): void;
|
|
44
|
+
/** Register a terminal output callback */
|
|
45
|
+
setOutputCallback(cb: OutputCallback): void;
|
|
46
|
+
/** Register a server ready callback */
|
|
47
|
+
setServerReadyCallback(cb: ServerReadyCallback): void;
|
|
48
|
+
/** Get the current preview URL (null if server not ready) */
|
|
49
|
+
getPreviewUrl(): string | null;
|
|
50
|
+
/** Get current status */
|
|
51
|
+
getStatus(): BootStage;
|
|
52
|
+
/** Get all terminal output */
|
|
53
|
+
getTerminalOutput(): string[];
|
|
54
|
+
/** Get current files (with edits applied) */
|
|
55
|
+
getCurrentFiles(): FileMap;
|
|
56
|
+
/** Get the original files (as loaded, before any edits) */
|
|
57
|
+
getOriginalFiles(): FileMap;
|
|
58
|
+
/** Get only the files that have been modified since loading */
|
|
59
|
+
getChangedFiles(): FileMap;
|
|
60
|
+
/** Get the error message if status is 'error' */
|
|
61
|
+
getError(): string | null;
|
|
62
|
+
/**
|
|
63
|
+
* Boot the runtime: initialize Nodepod → write files → install → start server.
|
|
64
|
+
*
|
|
65
|
+
* This is the main entry point. Call this once.
|
|
66
|
+
* The progress callback will fire at each stage.
|
|
67
|
+
*/
|
|
68
|
+
boot(): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Write a single file to the virtual filesystem.
|
|
71
|
+
* Used for editor changes after boot.
|
|
72
|
+
*/
|
|
73
|
+
writeFile(path: string, content: string): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Read a file from the virtual filesystem.
|
|
76
|
+
*/
|
|
77
|
+
readFile(path: string): Promise<string>;
|
|
78
|
+
/**
|
|
79
|
+
* Restart the server process.
|
|
80
|
+
* Kills the current process and re-runs the entry command.
|
|
81
|
+
*/
|
|
82
|
+
restart(): Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* Tear down the runtime.
|
|
85
|
+
* Kills all processes and releases resources.
|
|
86
|
+
*/
|
|
87
|
+
teardown(): void;
|
|
88
|
+
/** Get raw Nodepod instance for advanced usage */
|
|
89
|
+
getNodepod(): Nodepod | null;
|
|
90
|
+
/**
|
|
91
|
+
* Rewrite a Nodepod virtual server URL to use /__preview__/ instead of /__virtual__/.
|
|
92
|
+
*
|
|
93
|
+
* WHY: Nodepod's serverUrl() returns /__virtual__/{port} URLs, but the
|
|
94
|
+
* Service Worker only tracks iframe client IDs for /__preview__/{port}
|
|
95
|
+
* navigations. Without client tracking, subresource requests (JS, CSS,
|
|
96
|
+
* API calls) from the preview iframe are NOT intercepted by the SW —
|
|
97
|
+
* they fall through to the real dev server and 404.
|
|
98
|
+
*/
|
|
99
|
+
private rewritePreviewUrl;
|
|
100
|
+
/** Resolve a relative path against the workdir */
|
|
101
|
+
private resolvePath;
|
|
102
|
+
/** Write all project files to the virtual filesystem */
|
|
103
|
+
private writeFiles;
|
|
104
|
+
/**
|
|
105
|
+
* Install npm dependencies by spawning `npm install` in Nodepod.
|
|
106
|
+
*
|
|
107
|
+
* Nodepod's DependencyInstaller handles this internally via its
|
|
108
|
+
* package registry. We spawn `node -e` with a require to trigger it,
|
|
109
|
+
* or use the built-in npm install mechanism.
|
|
110
|
+
*/
|
|
111
|
+
private installDependencies;
|
|
112
|
+
/** Start the entry command (e.g., "node server.js") */
|
|
113
|
+
private startServer;
|
|
114
|
+
/** Emit a progress update */
|
|
115
|
+
private emitProgress;
|
|
116
|
+
/** Append a line to terminal output */
|
|
117
|
+
private appendOutput;
|
|
118
|
+
}
|
|
119
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fullstack-starter — Production-equivalent full-stack starter template.
|
|
3
|
+
*
|
|
4
|
+
* A properly structured Express + React application with:
|
|
5
|
+
* - sql.js (SQLite WASM) as a true relational DB — same schemas work on Postgres
|
|
6
|
+
* - React Router v6 for client-side routing
|
|
7
|
+
* - React Context for auth + app state management
|
|
8
|
+
* - JWT authentication with role-based access
|
|
9
|
+
* - .ranger manifest for AI agent context
|
|
10
|
+
*
|
|
11
|
+
* Architecture:
|
|
12
|
+
* ┌─────────────────────────────────────────────────────┐
|
|
13
|
+
* │ server.js — Entry, middleware chain │
|
|
14
|
+
* │ config.js — Environment / .env config │
|
|
15
|
+
* │ db/database.js — sql.js init + migrations │
|
|
16
|
+
* │ db/migrations/001.js — Schema definitions (typed) │
|
|
17
|
+
* │ db/repositories/ — CRUD repos over SQL │
|
|
18
|
+
* │ middleware/ — Auth, errors, static │
|
|
19
|
+
* │ routes/ — Express route definitions │
|
|
20
|
+
* │ public/ — React SPA (CDN-loaded) │
|
|
21
|
+
* │ public/store/ — React Context providers │
|
|
22
|
+
* │ public/pages/ — Route-level components │
|
|
23
|
+
* │ public/components/ — Reusable UI components │
|
|
24
|
+
* │ .ranger — AI agent instruction file │
|
|
25
|
+
* │ .env.example — Env var documentation │
|
|
26
|
+
* └─────────────────────────────────────────────────────┘
|
|
27
|
+
*
|
|
28
|
+
* DATABASE:
|
|
29
|
+
* Uses sql.js (SQLite compiled to WASM) — runs in the browser.
|
|
30
|
+
* Schemas use standard SQL types (INTEGER, TEXT, REAL, BOOLEAN, TIMESTAMP).
|
|
31
|
+
* To migrate to Postgres: change db/database.js to use 'pg', keep same schemas.
|
|
32
|
+
* The repository layer abstracts all SQL — swap the query runner, not the repos.
|
|
33
|
+
*/
|
|
34
|
+
export declare const fullstackStarterTemplate: {
|
|
35
|
+
entryCommand: string;
|
|
36
|
+
port: number;
|
|
37
|
+
files: Record<string, string>;
|
|
38
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template registry — built-in project templates.
|
|
3
|
+
*
|
|
4
|
+
* The "express-react" template is a proper full-stack app:
|
|
5
|
+
* - Express backend with API routes that proxy JSONPlaceholder
|
|
6
|
+
* - React frontend as separate component files under public/
|
|
7
|
+
* - Express serves static files via fs.readFileSync (Nodepod doesn't support
|
|
8
|
+
* res.sendFile or express.static)
|
|
9
|
+
* - React + Tailwind loaded from CDN — no build step needed
|
|
10
|
+
*
|
|
11
|
+
* WHY THIS ARCHITECTURE:
|
|
12
|
+
* Nodepod can't run Vite/Next.js/Webpack. But Express works perfectly.
|
|
13
|
+
* By loading React/Tailwind from CDN and splitting the React app into
|
|
14
|
+
* separate script files, we get a realistic full-stack project structure
|
|
15
|
+
* that users can explore and edit in the code sandbox.
|
|
16
|
+
*/
|
|
17
|
+
import type { FileMap } from "../types";
|
|
18
|
+
/**
|
|
19
|
+
* Template registry — maps template names to file maps.
|
|
20
|
+
*/
|
|
21
|
+
export declare const templates: Record<string, {
|
|
22
|
+
files: FileMap;
|
|
23
|
+
entryCommand: string;
|
|
24
|
+
port: number;
|
|
25
|
+
}>;
|
|
26
|
+
/**
|
|
27
|
+
* Get a project template by name.
|
|
28
|
+
*
|
|
29
|
+
* @param name - Template name (e.g., 'express-react')
|
|
30
|
+
* @returns Template config or null if not found
|
|
31
|
+
*/
|
|
32
|
+
export declare function getTemplate(name: string): {
|
|
33
|
+
files: FileMap;
|
|
34
|
+
entryCommand: string;
|
|
35
|
+
port: number;
|
|
36
|
+
} | null;
|
|
37
|
+
/** List available template names */
|
|
38
|
+
export declare function listTemplates(): string[];
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @illuma-ai/code-sandbox — Type definitions
|
|
3
|
+
*
|
|
4
|
+
* Core types for the browser-native code sandbox.
|
|
5
|
+
* Covers file maps, runtime states, git operations, and component props.
|
|
6
|
+
*/
|
|
7
|
+
/** A flat map of file paths to their string contents */
|
|
8
|
+
export type FileMap = Record<string, string>;
|
|
9
|
+
/** Represents a single file or directory node in the tree */
|
|
10
|
+
export interface FileNode {
|
|
11
|
+
name: string;
|
|
12
|
+
path: string;
|
|
13
|
+
type: "file" | "directory";
|
|
14
|
+
children?: FileNode[];
|
|
15
|
+
}
|
|
16
|
+
/** Boot stages shown during the loading animation */
|
|
17
|
+
export type BootStage = "initializing" | "writing-files" | "installing" | "starting" | "ready" | "error";
|
|
18
|
+
export interface BootProgress {
|
|
19
|
+
stage: BootStage;
|
|
20
|
+
message: string;
|
|
21
|
+
/** 0-100 percent, approximate */
|
|
22
|
+
percent: number;
|
|
23
|
+
}
|
|
24
|
+
/** Configuration for the Nodepod runtime */
|
|
25
|
+
export interface RuntimeConfig {
|
|
26
|
+
/** Project files to write into the virtual filesystem */
|
|
27
|
+
files: FileMap;
|
|
28
|
+
/** Command to start the dev server, e.g. "node server.js" */
|
|
29
|
+
entryCommand: string;
|
|
30
|
+
/** Working directory inside Nodepod (default: "/app") */
|
|
31
|
+
workdir?: string;
|
|
32
|
+
/** Environment variables passed to processes */
|
|
33
|
+
env?: Record<string, string>;
|
|
34
|
+
/** Port to watch for server readiness (default: 3000) */
|
|
35
|
+
port?: number;
|
|
36
|
+
}
|
|
37
|
+
/** State exposed by the runtime to UI components */
|
|
38
|
+
export interface RuntimeState {
|
|
39
|
+
status: BootStage;
|
|
40
|
+
progress: BootProgress;
|
|
41
|
+
/** URL for the preview iframe once server is ready, e.g. "/__virtual__/3000/" */
|
|
42
|
+
previewUrl: string | null;
|
|
43
|
+
/** All terminal output lines (stdout + stderr) */
|
|
44
|
+
terminalOutput: string[];
|
|
45
|
+
/** Current files in the virtual FS (may differ from original after edits) */
|
|
46
|
+
files: FileMap;
|
|
47
|
+
/** Error message if status is 'error' */
|
|
48
|
+
error: string | null;
|
|
49
|
+
}
|
|
50
|
+
export interface GitHubRepo {
|
|
51
|
+
owner: string;
|
|
52
|
+
repo: string;
|
|
53
|
+
branch?: string;
|
|
54
|
+
path?: string;
|
|
55
|
+
}
|
|
56
|
+
export interface GitCommitRequest {
|
|
57
|
+
owner: string;
|
|
58
|
+
repo: string;
|
|
59
|
+
/** Branch to create for the commit */
|
|
60
|
+
branch: string;
|
|
61
|
+
/** Base branch to fork from (default: 'main') */
|
|
62
|
+
baseBranch?: string;
|
|
63
|
+
/** Map of file paths to new contents (only changed files) */
|
|
64
|
+
changes: FileMap;
|
|
65
|
+
/** Commit message */
|
|
66
|
+
message: string;
|
|
67
|
+
}
|
|
68
|
+
export interface GitPRRequest extends GitCommitRequest {
|
|
69
|
+
/** PR title */
|
|
70
|
+
title: string;
|
|
71
|
+
/** PR body/description */
|
|
72
|
+
body?: string;
|
|
73
|
+
}
|
|
74
|
+
export interface GitPRResult {
|
|
75
|
+
number: number;
|
|
76
|
+
url: string;
|
|
77
|
+
branch: string;
|
|
78
|
+
}
|
|
79
|
+
export interface CodeSandboxProps {
|
|
80
|
+
/** Pass files directly */
|
|
81
|
+
files?: FileMap;
|
|
82
|
+
/** OR load from GitHub */
|
|
83
|
+
github?: GitHubRepo;
|
|
84
|
+
/** GitHub personal access token (for private repos and write-back) */
|
|
85
|
+
gitToken?: string;
|
|
86
|
+
/** OR use a built-in template name */
|
|
87
|
+
template?: string;
|
|
88
|
+
/** Command to start the dev server (default inferred from package.json or template) */
|
|
89
|
+
entryCommand?: string;
|
|
90
|
+
/** Port the server listens on (default: 3000) */
|
|
91
|
+
port?: number;
|
|
92
|
+
/** Environment variables */
|
|
93
|
+
env?: Record<string, string>;
|
|
94
|
+
/** Fires when a file is modified in the editor */
|
|
95
|
+
onFileChange?: (path: string, content: string) => void;
|
|
96
|
+
/** Fires when the dev server is ready */
|
|
97
|
+
onServerReady?: (port: number, url: string) => void;
|
|
98
|
+
/** Fires on boot progress changes */
|
|
99
|
+
onProgress?: (progress: BootProgress) => void;
|
|
100
|
+
/** Fires on errors */
|
|
101
|
+
onError?: (error: string) => void;
|
|
102
|
+
/** CSS class name for the root element */
|
|
103
|
+
className?: string;
|
|
104
|
+
/** Height of the sandbox (default: '100vh') */
|
|
105
|
+
height?: string;
|
|
106
|
+
}
|
|
107
|
+
export interface FileTreeProps {
|
|
108
|
+
files: FileNode[];
|
|
109
|
+
selectedFile: string | null;
|
|
110
|
+
onSelectFile: (path: string) => void;
|
|
111
|
+
onCreateFile?: (path: string) => void;
|
|
112
|
+
onCreateFolder?: (path: string) => void;
|
|
113
|
+
onDeleteFile?: (path: string) => void;
|
|
114
|
+
onRenameFile?: (oldPath: string, newPath: string) => void;
|
|
115
|
+
}
|
|
116
|
+
export interface CodeEditorProps {
|
|
117
|
+
files: FileMap;
|
|
118
|
+
activeFile: string | null;
|
|
119
|
+
openFiles: string[];
|
|
120
|
+
onSelectFile: (path: string) => void;
|
|
121
|
+
onCloseFile: (path: string) => void;
|
|
122
|
+
onFileChange: (path: string, content: string) => void;
|
|
123
|
+
readOnly?: boolean;
|
|
124
|
+
}
|
|
125
|
+
export interface TerminalProps {
|
|
126
|
+
output: string[];
|
|
127
|
+
className?: string;
|
|
128
|
+
}
|
|
129
|
+
export interface PreviewProps {
|
|
130
|
+
url: string | null;
|
|
131
|
+
className?: string;
|
|
132
|
+
onRefresh?: () => void;
|
|
133
|
+
}
|
|
134
|
+
export interface BootOverlayProps {
|
|
135
|
+
progress: BootProgress;
|
|
136
|
+
className?: string;
|
|
137
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@illuma-ai/code-sandbox",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
6
|
+
"author": "Illuma AI (https://github.com/illuma-ai)",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/illuma-ai/code-sandbox.git"
|
|
13
|
+
},
|
|
14
|
+
"description": "Browser-native code sandbox with file tree, editor, terminal, and live preview powered by Nodepod",
|
|
15
|
+
"main": "./dist/index.cjs",
|
|
16
|
+
"module": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js",
|
|
22
|
+
"require": "./dist/index.cjs"
|
|
23
|
+
},
|
|
24
|
+
"./styles.css": "./dist/styles.css"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"src"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"dev": "vite",
|
|
32
|
+
"build": "vite build && tsc --emitDeclarationOnly",
|
|
33
|
+
"preview": "vite preview",
|
|
34
|
+
"typecheck": "tsc --noEmit",
|
|
35
|
+
"test": "vitest"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@illuma-ai/nodepod": "^1.0.1",
|
|
39
|
+
"allotment": "^1.20.2",
|
|
40
|
+
"framer-motion": "^12.35.2"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"@monaco-editor/react": "^4.6.0",
|
|
44
|
+
"@xterm/addon-fit": "^0.11.0",
|
|
45
|
+
"@xterm/xterm": "^5.5.0 || ^6.0.0",
|
|
46
|
+
"lucide-react": ">=0.300.0",
|
|
47
|
+
"monaco-editor": ">=0.40.0",
|
|
48
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
49
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@monaco-editor/react": "^4.7.0",
|
|
53
|
+
"@types/react": "^18.2.11",
|
|
54
|
+
"@types/react-dom": "^18.2.4",
|
|
55
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
56
|
+
"@xterm/addon-fit": "^0.11.0",
|
|
57
|
+
"@xterm/xterm": "^5.5.0",
|
|
58
|
+
"autoprefixer": "^10.4.20",
|
|
59
|
+
"lucide-react": "^0.575.0",
|
|
60
|
+
"monaco-editor": "^0.55.1",
|
|
61
|
+
"postcss": "^8.4.31",
|
|
62
|
+
"react": "^18.2.0",
|
|
63
|
+
"react-dom": "^18.2.0",
|
|
64
|
+
"tailwindcss": "^3.4.1",
|
|
65
|
+
"typescript": "^5.3.3",
|
|
66
|
+
"vite": "^6.4.1",
|
|
67
|
+
"vitest": "^3.2.1"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BootOverlay — Loading animation displayed while Nodepod boots.
|
|
3
|
+
*
|
|
4
|
+
* Shows staged progress: initializing → writing files → installing → starting → ready.
|
|
5
|
+
* Includes a progress bar and stage-specific messaging with a pulsing animation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from "react";
|
|
9
|
+
import type { BootOverlayProps } from "../types";
|
|
10
|
+
|
|
11
|
+
/** Stage display configuration */
|
|
12
|
+
const STAGE_CONFIG: Record<
|
|
13
|
+
string,
|
|
14
|
+
{ icon: string; label: string; color: string }
|
|
15
|
+
> = {
|
|
16
|
+
initializing: {
|
|
17
|
+
icon: "⚡",
|
|
18
|
+
label: "Initializing Runtime",
|
|
19
|
+
color: "text-blue-400",
|
|
20
|
+
},
|
|
21
|
+
"writing-files": {
|
|
22
|
+
icon: "📁",
|
|
23
|
+
label: "Writing Project Files",
|
|
24
|
+
color: "text-yellow-400",
|
|
25
|
+
},
|
|
26
|
+
installing: {
|
|
27
|
+
icon: "📦",
|
|
28
|
+
label: "Installing Dependencies",
|
|
29
|
+
color: "text-purple-400",
|
|
30
|
+
},
|
|
31
|
+
starting: {
|
|
32
|
+
icon: "🚀",
|
|
33
|
+
label: "Starting Application",
|
|
34
|
+
color: "text-green-400",
|
|
35
|
+
},
|
|
36
|
+
ready: { icon: "✅", label: "Application Ready", color: "text-green-400" },
|
|
37
|
+
error: { icon: "❌", label: "Error", color: "text-red-400" },
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Full-screen loading overlay with staged boot progress.
|
|
42
|
+
*
|
|
43
|
+
* Renders over the workbench while Nodepod boots. Shows:
|
|
44
|
+
* - Current stage icon + label
|
|
45
|
+
* - Progress bar with percentage
|
|
46
|
+
* - Descriptive message of what's happening
|
|
47
|
+
* - Pulsing dots animation
|
|
48
|
+
*/
|
|
49
|
+
export function BootOverlay({ progress, className = "" }: BootOverlayProps) {
|
|
50
|
+
const config = STAGE_CONFIG[progress.stage] || STAGE_CONFIG.initializing;
|
|
51
|
+
|
|
52
|
+
if (progress.stage === "ready") {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div
|
|
58
|
+
className={`absolute inset-0 z-50 flex items-center justify-center bg-sb-bg/95 backdrop-blur-sm ${className}`}
|
|
59
|
+
>
|
|
60
|
+
<div className="flex flex-col items-center gap-6 max-w-md px-8">
|
|
61
|
+
{/* Animated icon */}
|
|
62
|
+
<div className="text-5xl animate-bounce">{config.icon}</div>
|
|
63
|
+
|
|
64
|
+
{/* Stage label */}
|
|
65
|
+
<h2 className={`text-xl font-semibold ${config.color}`}>
|
|
66
|
+
{config.label}
|
|
67
|
+
{progress.stage !== "error" && <PulsingDots />}
|
|
68
|
+
</h2>
|
|
69
|
+
|
|
70
|
+
{/* Progress bar */}
|
|
71
|
+
<div className="w-64 h-2 bg-sb-bg-active rounded-full overflow-hidden">
|
|
72
|
+
<div
|
|
73
|
+
className="h-full bg-sb-accent rounded-full transition-all duration-500 ease-out"
|
|
74
|
+
style={{ width: `${progress.percent}%` }}
|
|
75
|
+
/>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
{/* Message */}
|
|
79
|
+
<p className="text-sm text-sb-text-muted text-center">
|
|
80
|
+
{progress.message}
|
|
81
|
+
</p>
|
|
82
|
+
|
|
83
|
+
{/* Stage indicators */}
|
|
84
|
+
<div className="flex items-center gap-2 mt-4">
|
|
85
|
+
{["initializing", "writing-files", "installing", "starting"].map(
|
|
86
|
+
(stage, i) => {
|
|
87
|
+
const isCompleted = getStageOrder(progress.stage) > i;
|
|
88
|
+
const isCurrent = progress.stage === stage;
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<React.Fragment key={stage}>
|
|
92
|
+
<div
|
|
93
|
+
className={`w-3 h-3 rounded-full transition-colors duration-300 ${
|
|
94
|
+
isCompleted
|
|
95
|
+
? "bg-sb-success"
|
|
96
|
+
: isCurrent
|
|
97
|
+
? "bg-sb-accent animate-pulse"
|
|
98
|
+
: "bg-sb-bg-active"
|
|
99
|
+
}`}
|
|
100
|
+
/>
|
|
101
|
+
{i < 3 && (
|
|
102
|
+
<div
|
|
103
|
+
className={`w-8 h-0.5 transition-colors duration-300 ${
|
|
104
|
+
isCompleted ? "bg-sb-success" : "bg-sb-bg-active"
|
|
105
|
+
}`}
|
|
106
|
+
/>
|
|
107
|
+
)}
|
|
108
|
+
</React.Fragment>
|
|
109
|
+
);
|
|
110
|
+
},
|
|
111
|
+
)}
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Get numeric order of a boot stage for progress indicators */
|
|
119
|
+
function getStageOrder(stage: string): number {
|
|
120
|
+
const order = [
|
|
121
|
+
"initializing",
|
|
122
|
+
"writing-files",
|
|
123
|
+
"installing",
|
|
124
|
+
"starting",
|
|
125
|
+
"ready",
|
|
126
|
+
];
|
|
127
|
+
return order.indexOf(stage);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Animated "..." dots */
|
|
131
|
+
function PulsingDots() {
|
|
132
|
+
return (
|
|
133
|
+
<span className="inline-flex ml-1">
|
|
134
|
+
<span className="animate-pulse" style={{ animationDelay: "0ms" }}>
|
|
135
|
+
.
|
|
136
|
+
</span>
|
|
137
|
+
<span className="animate-pulse" style={{ animationDelay: "200ms" }}>
|
|
138
|
+
.
|
|
139
|
+
</span>
|
|
140
|
+
<span className="animate-pulse" style={{ animationDelay: "400ms" }}>
|
|
141
|
+
.
|
|
142
|
+
</span>
|
|
143
|
+
</span>
|
|
144
|
+
);
|
|
145
|
+
}
|