deploy: v0.12 fix 存档key名

This commit is contained in:
2026-06-24 03:09:02 +00:00
parent 7a08d49fe3
commit e0a5281119
58743 changed files with 7297269 additions and 39505 deletions
+51
View File
@@ -0,0 +1,51 @@
Sustainable Use License
Version 1.0
Acceptance
By using the software, you agree to all of the terms and conditions below.
Copyright License
The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensable, non-transferable license to use, copy, distribute, make available, and prepare derivative works of the software, in each case subject to the limitations below.
Limitations
You may use or modify the software only for your own internal business purposes or for non-commercial or personal use. You may distribute the software or provide it to others only if you do so free of charge for non-commercial purposes. You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensor's trademarks is subject to applicable law.
Patents
The licensor grants you a license, under any patent claims the licensor can license, or becomes able to license, to make, have made, use, sell, offer for sale, import and have imported the software, in each case subject to the limitations and conditions in this license. This license does not cover any patent claims that you cause to be infringed by modifications or additions to the software. If you or your company make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
Notices
You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms. If you modify the software, you must include in any modified copies of the software a prominent notice stating that you have modified the software.
No Other Rights
These terms do not imply any licenses other than those expressly granted in these terms.
Termination
If you use the software in violation of these terms, such use is not licensed, and your license will automatically terminate. If the licensor provides you with a notice of your violation, and you cease all violation of this license no later than 30 days after you receive that notice, your license will be reinstated retroactively. However, if you violate these terms after such reinstatement, any additional violation of these terms will cause your license to terminate automatically and permanently.
No Liability
As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.
Definitions
The "licensor" is CodeSandBox B.V.
The "software" is the software the licensor makes available under these terms, including any portion of it.
"You" refers to the individual or entity agreeing to these terms.
"Your company" is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. Control means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
"Your license" is the license granted to you for the software under these terms.
"Use" means anything you do with the software requiring your license.
"Trademark" means trademarks, service marks, and similar rights.
+210
View File
@@ -0,0 +1,210 @@
# Nodebox
Nodebox is a runtime for executing Node.js modules in the browser.
## Why we built Nodebox
With `sandpack-bundler`, we allowed any developer anywhere to instantly create a fast, local, shareable playground inside their browser, without having to wait forever to install dependencies and fight with devtools. This improves the learning, experimentation and sharing experience of client-side JavaScript code.
However, server-side JavaScript remained a challenge. At CodeSandbox we have solved this by using [Firecracker VMs](https://codesandbox.io/blog/how-we-clone-a-running-vm-in-2-seconds), allowing us to bring your entire development environment to the cloud regardless of the programming language or tooling you might be using. Unfortunately, as VMs live in the cloud, they require infrastructure and a network connection, resulting in a higher cost compared to our client-side sandboxes.
To solve this problem, we built Nodebox, a runtime that runs entirely in the browser, eliminating the need for a network connection and infrastructure.
Nodebox gives you the same user experience you have come to expect from Sandpack, which means a near-instant server-side JavaScript environment at the click of a button—built for experimentation, examples and documentation.
## The differences between a VM and Nodebox
As mentioned in the previous section, we solved server-side sandboxes in CodeSandbox by using Firecracker VMs. In this section, we'll explain the advantages and disadvantages of each approach.
### Advantages of VMs over Nodebox
- You get dedicated resources, with no resource limits enforced by the browser
- You have an entire Unix OS available
- You can run any language, database, command
- You can use network sockets
- You can run large and complex projects
- A perfect 1:1 environment as compared to your local setup (at least, if you're using a Unix-based system)
- No emulation, so Node.js would run exactly the same way as locally
### Advantages of Nodebox
- No need for any infrastructure
- No need for a network connection
- Instant feedback to any change
- Easy to get started
- Easy and instant reset - simply refresh the page/iframe
- Every page visitor gets their own Nodebox instance automatically
## What makes it different
Nodebox is currently the only cross-browser Node.js runtime supporting all the latest browsers:
- Chrome;
- Firefox;
- Safari \*
> - Support for iOS Safari is in beta
Nodebox does not emulate Node.js but is, instead, a Node.js-compatible runtime. This means that it implements as much of the Node.js API as possible while keeping a minimal performance imprint, using browser API where applicable and, in some cases, leaving out certain parts of Node.js due to browser [limitations](#Limitations) or complexity.
Nodebox uses an internal dependency manager that is fine-tuned to deliver optimal initial load time by utilizing dependency caching via [Sandpack CDN](https://github.com/codesandbox/sandpack-cdn). That CDN is an open-source Rust package manager that runs in the cloud and can be self-hosted pretty easily as well.
While there are alternatives to Nodebox, they are closer to mimicking a container-based environment, running commands step-by-step, or even the entire Linux distributive in your browser. That makes them slower and harder to use compared to Nodebox, which is optimized to run sandboxes fast and with as little friction as possible.
## Limitations
Unfortunately, any type of runtime that does not have access to operating system-level APIs will come with certain limitations. For Nodebox, those are the following:
- N-API modules
- net#Sockets pointing to external IPs
- Synchronous exec/spawn
- async_hooks (planned for implementation)
- Automatic process exiting - users now need to manually call `process.exit` before the process is exited (planned for implementation)
As we implement every module manually one by one, it is possible that some will not behave correctly. If this happens, feel free to open an issue here on GitHub and we'll make sure to fix it.
## Documentation
- [**Getting started**](#getting-started)
- [API documentation](https://github.com/codesandbox/nodebox-runtime/blob/main/packages/nodebox/api.md)
---
## Getting started
Nodebox is meant for usage in your client-side applications, granting them the capability of running actual Node.js code directly in the browser. Here are a couple of examples of when Nodebox can be used:
- Building interactive examples for server-side code in your documentation;
- Showcasing a UI component library in the actual framework it's built for;
- Generally any evaluation of Node.js code and previewing it in the browser.
In the context of this tutorial, we will be working on a documentation website that illustrates different examples of using a Next.js application. Bear in mind that our documentation itself can be written in any framework of our choosing.
### Install
Nodebox can be installed from NPM just like any other dependency:
```sh
npm install @codesandbox/nodebox
```
### Setup
Nodebox consists of two main parts:
- A runtime environment evaluating the code;
- A preview environment serving the result of the evaluation.
Corresponding to these two parts, let's create two iframes in our application:
```html
<!--
The "nodebox" iframe will mount the Nodebox runtime,
allowing it to communicate with the rest of the application.
-->
<iframe id="nodebox-iframe"></iframe>
<!--
The "preview" iframe will contain the result of running
the Next.js example we will configure in a moment.
-->
<iframe id="preview-iframe"></iframe>
```
Although the runtime environment can be self-hosted, we will use the default one pointing to the deployed Nodebox instance on CodeSandbox servers. We do need, however, to specify an `iframe` reference in our application where Nodebox should render its preview.
```js
import { Nodebox } from '@codesandbox/nodebox';
const runtime = new Nodebox({
// Provide a reference to the <iframe> element in the DOM
// where Nodebox should render the preview.
iframe: document.getElementById('nodebox-iframe'),
});
// Establish a connection with the runtime environment.
await runtime.connect();
```
> Learn more about the [Nodebox API](https://github.com/codesandbox/nodebox-runtime/blob/main/packages/nodebox/api.md).
You want to establish **a single Nodebox instance** across your entire application. Bear that in mind during the setup phase and consult your framework's documentation and best practices regarding the most efficient way of achieving this.
Previews correspond to _commands_ executed in Nodebox, meaning that at this stage there will be no previews mounted at the given iframe because we haven't run any commands yet. Let's change that.
### Initialize file system
Much like your own project, the project you create in Nodebox needs files to work with. It can be a single JavaScript file or the entire project, like Astro or Next.js.
Let's describe a Next.js project that we need.
```js
// Populate the in-memory file system of Nodebox
// with a Next.js project files.
await runtime.fs.init({
'package.json': JSON.stringify({
name: 'nextjs-preview',
dependencies: {
'@next/swc-wasm-nodejs': '12.1.6',
next: '12.1.6',
react: '18.2.0',
'react-dom': '18.2.0',
},
}),
// On the index page, let's illustrate how server-side props
// propagate to your page component in Next.js.
'pages/index.jsx': `
export default function Homepage({ name }) {
return (
<div>
<h1>Hello, {name}</h1>
<p>The name "{name}" has been received from server-side props.</p>
</div>
)
}
export function getServerSideProps() {
return {
props: {
name: 'John'
}
}
}
`,
});
```
> You can reference standard Node.js modules, as well as external dependencies while writing your project files. Note that you **don't have to install** those dependencies as Nodebox will manage dependency installation, caching, and resolution automatically.
What we did above was outline a file system state of an actual Next.js project for Nodebox to run. The last step remaining is to run Next.js.
### Run project
To run the project, we will run the `npm dev` command using the Shell API provided by Nodebox.
```js
// First, create a new shell instance.
// You can use the same instance to spawn commands,
// observe stdio, restart and kill the process.
const shell = runtime.shell.create();
// Then, let's run the "dev" script that we've defined
// in "package.json" during the previous step.
const nextProcess = await shell.runCommand('npm', ['dev']);
// Find the preview by the process and mount it
// on the preview iframe on the page.
const previewInfo = await runtime.preview.getByShellId(nextProcess.id);
const previewIframe = document.getElementById('preview-iframe');
previewIframe.setAttribute('src', previewInfo.url);
```
> Note that you can treat `shell.runCommand` similar to `spawn` in Node.js. Learn more about the Shell API in the [documentation](https://github.com/codesandbox/nodebox-runtime/blob/main/packages/nodebox/api.md).
Once this command runs, it will return a shell reference we can use to retrieve the preview URL. By mounting that preview URL on our preview iframe from the setup, we can see the Next.js project running:
![](https://github.com/codesandbox/nodebox-runtime/blob/main/packages/nodebox/nextjs-preview.png)
That's it! 🎉 **Not a single server was spawned while running this Next.js application**. Everything was managed by Nodebox directly in your browser.
👉 Check out the [Sandbox for this tutorial](https://codesandbox.io/p/sandbox/nodebox-next-js-example-ji27x8).
+36
View File
@@ -0,0 +1,36 @@
import { FileSystemApi, FilesMap } from './modules/fs';
import type { FileWatchEvent } from './modules/fs';
import { ShellApi, ShellInfo, ShellProcess } from './modules/shell';
import { PreviewApi, PreviewInfo } from './modules/preview';
export type { ShellInfo, PreviewInfo, FilesMap, ShellProcess, FileWatchEvent };
export interface ChannelOptions {
iframe: HTMLIFrameElement;
/**
* A custom Nodebox runtime URL
*/
runtimeUrl?: string;
/**
* A custom Sandpack CDN URL.
*/
cdnUrl?: string;
}
/**
* Create a new Node emulator instance
*/
export declare class Nodebox {
private readonly options;
private channel;
private isConnected;
private url;
private fileSystemApi;
private shellApi;
private previewApi;
constructor(options: ChannelOptions);
/**
* Connect to the deployed Node Emulator instance.
*/
connect(): Promise<void>;
get fs(): FileSystemApi;
get shell(): ShellApi;
get preview(): PreviewApi;
}
+4
View File
@@ -0,0 +1,4 @@
export * from './messages';
export * from './Nodebox';
export * from './runtime-protocol.types';
export type { IFileStats } from './modules/fs';
+1082
View File
File diff suppressed because it is too large Load Diff
+1068
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -0,0 +1,3 @@
declare type Scope = 'preview' | 'emulator' | 'runtime' | 'bridge' | 'runtime:worker';
export declare function createDebug(scope: Scope): (message: string, ...data: any[]) => void;
export {};
+98
View File
@@ -0,0 +1,98 @@
import type { FileSystemEvents } from './modules/fs';
import type { ShellEvents } from './modules/shell';
import type { PreviewInfo } from './modules/preview';
import type { PreviewEvents } from './modules/preview';
export interface ConsumerEvents extends FileSystemEvents, ShellEvents, PreviewEvents {
connect: {
cdnUrl?: string | null;
};
}
declare type WatcherEvent = {
type: 'create';
path: string;
} | {
type: 'remove';
path: string;
} | {
type: 'change';
path: string;
} | {
type: 'rename';
oldPath: string;
newPath: string;
} | {
type: 'close';
};
export declare type FSWatchEvent = WatcherEvent & {
watcherId: string;
};
export declare type WorkerStatusUpdate = {
state: 'downloading_manifest';
} | {
state: 'downloaded_module';
name: string;
version: string;
totalPending: number;
} | {
state: 'starting_command';
} | {
state: 'command_running';
};
export interface WorkerEvents {
'internal/handshake/done': void;
'internal/operation/done': {
operationId: string;
listenerPayload: unknown;
};
'internal/operation/failed': {
operationId: string;
error: Error;
};
'runtime/ready': void;
'worker/progress': {
workerId?: string;
status: WorkerStatusUpdate;
};
'worker/tty': {
workerId: string;
payload: {
data: string;
type: 'out' | 'err';
};
};
'worker/exit': {
workerId: string;
exitCode: number;
error?: {
message: string;
};
};
'preview/port/ready': PreviewInfo;
'fs/watch-event': FSWatchEvent;
}
declare type OperationEvent<Payload> = Payload & {
operationId: string;
};
declare type MaybePromise<T> = T | Promise<T>;
export declare class MessageReceiver {
private emitter;
private senderPort;
constructor();
private waitForHandshake;
private addMessageListener;
on<Event extends keyof ConsumerEvents & string>(event: Event, listener: (message: ConsumerEvents[Event] extends [infer PayloadType, infer _] ? PayloadType : ConsumerEvents[Event]) => MaybePromise<ConsumerEvents[Event] extends [infer _, infer ReturnType] ? ReturnType | undefined : void>, options?: AddEventListenerOptions): void;
send<Event extends keyof WorkerEvents & string>(event: Event, ...data: WorkerEvents[Event] extends Record<string, unknown> ? [WorkerEvents[Event]] : [undefined?]): void;
}
export declare class MessageSender {
private readonly target;
private emitter;
private channel;
private receiverPort;
private receiverReadyPromise;
constructor(target: Window);
handshake(): Promise<void>;
on<Event extends keyof WorkerEvents & string>(event: Event, listener: (message: MessageEvent<WorkerEvents[Event]>) => void, options?: AddEventListenerOptions): void;
off<Event extends keyof WorkerEvents & string>(event: Event, listener: (message: MessageEvent<WorkerEvents[Event]>) => void, options?: AddEventListenerOptions): void;
send<Event extends keyof ConsumerEvents & string>(event: Event, ...data: ConsumerEvents[Event] extends [infer PayloadType, infer _] ? [PayloadType] : ConsumerEvents[Event] extends Record<string, unknown> ? [ConsumerEvents[Event]] : [undefined?]): Promise<ConsumerEvents[Event] extends [infer _, infer ReturnType] ? OperationEvent<ReturnType> | undefined : void>;
}
export {};
+121
View File
@@ -0,0 +1,121 @@
/// <reference types="node" />
import type { MessageSender, FSWatchEvent } from '../messages';
declare type FSEncoding = BufferEncoding | 'buffer';
export declare type FileContent = Uint8Array | string;
export interface FilesMap {
[filePath: string]: FileContent;
}
export interface FileWatchOptions {
recursive?: boolean;
}
export declare type FileWatchEvent = Omit<FSWatchEvent, 'watcherId'>;
export interface IFileStats {
type: 'dir' | 'file' | 'link';
size: number;
ino: number;
atimeMs: number;
mtimeMs: number;
ctimeMs: number;
blocks: number;
mode: number;
}
export interface FileSystemEvents {
'fs/init': {
files: FilesMap;
};
'fs/readFile': [{
path: string;
encoding?: undefined;
}, {
data: Uint8Array;
}] | [{
path: string;
encoding?: 'buffer';
}, {
data: Uint8Array;
}] | [{
path: string;
encoding?: FSEncoding;
}, {
data: string | FileContent;
}];
'fs/writeFile': {
path: string;
content: FileContent;
encoding?: BufferEncoding;
recursive?: boolean;
};
'fs/readdir': [
{
path: string;
},
{
data: string[];
}
];
'fs/stat': [
{
path: string;
},
{
data: IFileStats;
}
];
'fs/mkdir': {
path: string;
recursive?: boolean;
};
'fs/rm': {
path: string;
force?: boolean;
recursive?: boolean;
};
'fs/watch': {
watcherId: string;
includes: string[];
excludes: string[];
};
'fs/unwatch': {
watcherId: string;
};
}
declare type WriteFileOptions = BufferEncoding | {
encoding?: BufferEncoding;
recursive?: boolean;
};
export declare class FileSystemApi {
private readonly channel;
constructor(channel: MessageSender);
/**
* Initialize the File System worker with the files.
*/
init(files: FilesMap): Promise<void>;
/**
* Read a file at the given path.
*/
readFile(path: string, encoding?: undefined): Promise<Uint8Array>;
readFile(path: string, encoding: 'buffer'): Promise<Uint8Array>;
readFile(path: string, encoding: BufferEncoding): Promise<string>;
/**
* Write a file at the given path.
* Replaces the file content if the file already exists.
*/
writeFile(path: string, content: FileContent): Promise<void>;
writeFile(path: string, content: FileContent, options: WriteFileOptions): Promise<void>;
readdir(path: string): Promise<string[]>;
mkdir(path: string, options?: {
recursive?: boolean;
}): Promise<void>;
stat(path: string): Promise<IFileStats>;
rm(path: string, options?: {
force?: boolean;
recursive?: boolean;
}): Promise<void>;
/**
* Subscribe to changes at the given file or directory.
*/
watch(includes: string[], excludes: string[], listener?: (evt?: FileWatchEvent) => void): Promise<{
dispose: () => Promise<void>;
}>;
}
export {};
+22
View File
@@ -0,0 +1,22 @@
import type { MessageSender } from '../messages';
export interface PreviewEvents {
'preview/get/info': [
{
port?: number;
sourceShellId?: string;
},
PreviewInfo
];
}
export declare type PreviewInfo = {
url: string;
sourceShellId: string;
port: number;
};
export declare class PreviewApi {
private readonly channel;
constructor(channel: MessageSender);
private waitFor;
getByShellId(sourceShellId: string, timeout?: number): Promise<PreviewInfo>;
waitForPort(port: number, timeout?: number): Promise<PreviewInfo>;
}
+57
View File
@@ -0,0 +1,57 @@
import { Emitter } from 'strict-event-emitter';
import type { MessageSender, WorkerStatusUpdate } from '../messages';
export interface ShellEvents {
'shell/runCommand': [
{
command: string;
args: Array<string>;
options: ShellCommandOptions;
},
ShellInfo
];
'shell/exit': ShellInfo;
'shell/stdin': {
data: string | Uint8Array;
workerId: string;
};
}
export interface ShellCommandOptions {
cwd?: string;
env?: Record<string, string>;
}
export declare type ShellInfo = {
id: string;
};
export declare class ShellApi {
private readonly channel;
constructor(channel: MessageSender);
create(): ShellProcess;
}
export declare class ShellProcess {
private readonly channel;
id?: string;
state: 'running' | 'idle';
stdout: Emitter<{
data: [string];
}>;
stderr: Emitter<{
data: [string];
}>;
stdin: {
write: (data: string | Uint8Array) => Promise<void>;
};
constructor(channel: MessageSender);
private forwardStdEvents;
/**
* Evaluates a given module in the File
*/
runCommand(command: string, args: Array<string>, options?: ShellCommandOptions): Promise<ShellInfo>;
on(message: 'exit', listener: (exitCode: number, error?: {
message: string;
}) => void): Promise<void>;
on(message: 'progress', listener: (status: WorkerStatusUpdate) => void): Promise<void>;
/**
* Terminates the shell process.
*/
kill(): Promise<void>;
}
+13
View File
@@ -0,0 +1,13 @@
export declare const INJECT_MESSAGE_TYPE = "INJECT_AND_INVOKE";
export declare const PREVIEW_LOADED_MESSAGE_TYPE = "PREVIEW_LOADED";
export interface Message {
type: string;
}
declare type BaseScope = Record<string, unknown>;
export interface InjectMessage<Scope = BaseScope> {
uid: string;
type: typeof INJECT_MESSAGE_TYPE;
code: string;
scope: Scope;
}
export {};
+32
View File
@@ -0,0 +1,32 @@
{
"name": "@codesandbox/nodebox",
"version": "0.1.8",
"description": "Public API of Nodebox",
"main": "./build/index.js",
"module": "./build/index.mjs",
"typings": "./build/index.d.ts",
"license": "SEE LICENSE IN ./LICENSE",
"repository": {
"type": "git",
"url": "https://github.com/codesandbox/nodebox-runtime"
},
"exports": {
".": {
"import": "./build/index.mjs",
"require": "./build/index.js",
"types": "./build/index.d.ts"
}
},
"files": [
"./build"
],
"scripts": {
"dev": "NODE_ENV=development pnpm build",
"prebuild": "rimraf ./build",
"build": "node -r esbuild-register ./build.ts"
},
"dependencies": {
"outvariant": "^1.4.0",
"strict-event-emitter": "^0.4.3"
}
}