@codemod.com/codemod-sandbox 0.1.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.
@@ -0,0 +1,4 @@
1
+ export type * from "./types.js";
2
+ export { Capabilities } from "./capabilities.js";
3
+ export { SandboxedModule } from "./module.js";
4
+ export { typeDeclarations } from "./type-declarations.js";
@@ -0,0 +1,3 @@
1
+ export { Capabilities } from "./capabilities.js";
2
+ export { SandboxedModule } from "./module.js";
3
+ export { typeDeclarations } from "./type-declarations.js";
@@ -0,0 +1,13 @@
1
+ import type { CapabilityRegistry, DescriberInputs, DescriberOutputs, InvokeInputs, InvokeOutputs, ModuleSpec, Sandbox } from "./types.js";
2
+ export { SandboxedModule };
3
+ declare class SandboxedModule {
4
+ readonly sandbox: Sandbox;
5
+ readonly capabilities: CapabilityRegistry;
6
+ readonly modules: ModuleSpec;
7
+ private readonly executionTracker;
8
+ constructor(sandbox: Sandbox, capabilities: CapabilityRegistry, modules: ModuleSpec);
9
+ private executeModule;
10
+ private logExecutionTime;
11
+ invoke(name: string, inputs: InvokeInputs): Promise<InvokeOutputs>;
12
+ describe(name: string, inputs: DescriberInputs): Promise<DescriberOutputs>;
13
+ }
@@ -0,0 +1,46 @@
1
+ import { Capabilities } from "./capabilities.js";
2
+ export { SandboxedModule };
3
+ class SandboxedModule {
4
+ sandbox;
5
+ capabilities;
6
+ modules;
7
+ executionTracker = new Map();
8
+ constructor(sandbox, capabilities, modules) {
9
+ this.sandbox = sandbox;
10
+ this.capabilities = capabilities;
11
+ this.modules = modules;
12
+ }
13
+ async executeModule(operation, moduleName, moduleInputs) {
14
+ const sessionId = crypto.randomUUID();
15
+ const operationLabel = operation === "describe" ? "Describe" : "Invoke";
16
+ const executionLabel = `${operationLabel} module "${moduleName}": uuid="${sessionId}"`;
17
+ const session = {
18
+ sessionId,
19
+ startTime: globalThis.performance.now(),
20
+ label: executionLabel,
21
+ };
22
+ this.executionTracker.set(sessionId, session);
23
+ Capabilities.instance().install(sessionId, this.capabilities);
24
+ const moduleOutputs = await this.sandbox.runModule(sessionId, operation, this.modules, moduleName, moduleInputs);
25
+ Capabilities.instance().uninstall(sessionId);
26
+ this.logExecutionTime(sessionId);
27
+ return moduleOutputs;
28
+ }
29
+ logExecutionTime(sessionId) {
30
+ const session = this.executionTracker.get(sessionId);
31
+ if (session) {
32
+ const duration = globalThis.performance.now() - session.startTime;
33
+ console.debug?.(`${session.label}: ${duration.toFixed(0)} ms`);
34
+ this.executionTracker.delete(sessionId);
35
+ }
36
+ else {
37
+ console.warn(`Unable to find timing for "${sessionId}"`);
38
+ }
39
+ }
40
+ async invoke(name, inputs) {
41
+ return this.executeModule("default", name, inputs);
42
+ }
43
+ describe(name, inputs) {
44
+ return this.executeModule("describe", name, inputs);
45
+ }
46
+ }
@@ -0,0 +1 @@
1
+ export { NodeSandbox } from "./node.js";
@@ -0,0 +1 @@
1
+ export { NodeSandbox } from "./node.js";
@@ -0,0 +1,18 @@
1
+ import type { ModuleSpec, Sandbox } from "./types.js";
2
+ import type { UUID } from "./types.js";
3
+ export { NodeSandbox };
4
+ declare class NodeSandbox implements Sandbox {
5
+ private readonly runtimeBuffer;
6
+ private sandbox;
7
+ constructor(buffer?: Buffer);
8
+ initializeTreeSitter(locateFile: (path: string) => string): Promise<void>;
9
+ setupParser(lang: string, path: string): Promise<void>;
10
+ private getWasmInstance;
11
+ getWasmExports(): Promise<{
12
+ scanFind: (src: string, configs: any[]) => any;
13
+ scanFix: (src: string, configs: any[]) => string;
14
+ dumpASTNodes: (src: string) => any;
15
+ dumpPattern: (src: string, selector?: string | null) => any;
16
+ }>;
17
+ runModule(sessionId: UUID, operation: "default" | "describe", moduleRegistry: ModuleSpec, moduleName: string, moduleInputs: Record<string, unknown>): Promise<any>;
18
+ }
@@ -0,0 +1,69 @@
1
+ import { ConsoleStdout, OpenFile, WASI, File as WasiFile, } from "@bjorn3/browser_wasi_shim";
2
+ import { readFile } from "node:fs/promises";
3
+ import { fileURLToPath } from "node:url";
4
+ import factory from "./factory.js";
5
+ export { NodeSandbox };
6
+ async function loadWasmRuntime() {
7
+ const wasmFile = import.meta.resolve("@codemod-com/codemod-sandbox/sandbox.wasm");
8
+ return readFile(fileURLToPath(wasmFile));
9
+ }
10
+ class NodeSandbox {
11
+ runtimeBuffer;
12
+ sandbox = null;
13
+ constructor(buffer) {
14
+ this.runtimeBuffer = buffer ? Promise.resolve(buffer) : loadWasmRuntime();
15
+ }
16
+ async initializeTreeSitter(locateFile) {
17
+ const instance = await this.getWasmInstance();
18
+ await instance.Parser.init({ locateFile });
19
+ }
20
+ async setupParser(lang, path) {
21
+ const instance = await this.getWasmInstance();
22
+ return instance.setupParser(lang, path);
23
+ }
24
+ async getWasmInstance() {
25
+ if (this.sandbox) {
26
+ return this.sandbox;
27
+ }
28
+ const wasmBytes = await this.runtimeBuffer;
29
+ const wasiInstance = new WASI([], [], [
30
+ new OpenFile(new WasiFile([])), // stdin
31
+ ConsoleStdout.lineBuffered((message) => console.log(`[WASI stdout] ${message}`)),
32
+ ConsoleStdout.lineBuffered((message) => console.warn(`[WASI stderr] ${message}`)),
33
+ ]);
34
+ const sandboxFactory = factory();
35
+ const { instance } = await WebAssembly.instantiate(wasmBytes, {
36
+ "./codemod-sandbox_bg.js": sandboxFactory,
37
+ wasi_snapshot_preview1: wasiInstance.wasiImport,
38
+ });
39
+ sandboxFactory.__wbg_set_wasm(instance.exports);
40
+ // @ts-expect-error 2739
41
+ wasiInstance.start({ exports: instance.exports });
42
+ if (typeof sandboxFactory.__wbindgen_init_externref_table === "function") {
43
+ sandboxFactory.__wbindgen_init_externref_table();
44
+ }
45
+ else {
46
+ console.warn("Heap system detected - ensure factory.js has proper heap setup");
47
+ }
48
+ this.sandbox = sandboxFactory;
49
+ return this.sandbox;
50
+ }
51
+ async getWasmExports() {
52
+ const instance = await this.getWasmInstance();
53
+ return {
54
+ scanFind: instance.scanFind,
55
+ scanFix: instance.scanFix,
56
+ dumpASTNodes: instance.dumpASTNodes,
57
+ dumpPattern: instance.dumpPattern,
58
+ };
59
+ }
60
+ async runModule(sessionId, operation, moduleRegistry, moduleName, moduleInputs) {
61
+ const moduleCode = moduleRegistry[moduleName];
62
+ if (!moduleCode) {
63
+ return { $error: `Unable to find module "${moduleName}"` };
64
+ }
65
+ const instance = await this.getWasmInstance();
66
+ const executionResult = await instance.run_module(sessionId, operation, moduleName, moduleRegistry, moduleCode, JSON.stringify(moduleInputs));
67
+ return JSON.parse(executionResult);
68
+ }
69
+ }
@@ -0,0 +1,16 @@
1
+ export { Telemetry };
2
+ interface TelemetryConfig {
3
+ enabled?: boolean;
4
+ path?: number[];
5
+ }
6
+ declare class Telemetry {
7
+ private readonly config;
8
+ private operationCounter;
9
+ static create(config: TelemetryConfig): undefined;
10
+ constructor(config: TelemetryConfig);
11
+ startModule(): Promise<void>;
12
+ startCapability(): Promise<number>;
13
+ endCapability(): Promise<void>;
14
+ endModule(): Promise<void>;
15
+ invocationPath(path: number): number[];
16
+ }
@@ -0,0 +1,28 @@
1
+ // Simplified telemetry implementation - functionality removed per requirements
2
+ export { Telemetry };
3
+ class Telemetry {
4
+ config;
5
+ operationCounter = 0;
6
+ static create(config) {
7
+ // Always return undefined to effectively disable telemetry
8
+ return undefined;
9
+ }
10
+ constructor(config) {
11
+ this.config = { enabled: false, ...config };
12
+ }
13
+ async startModule() {
14
+ // No-op implementation
15
+ }
16
+ async startCapability() {
17
+ return ++this.operationCounter;
18
+ }
19
+ async endCapability() {
20
+ // No-op implementation
21
+ }
22
+ async endModule() {
23
+ // No-op implementation
24
+ }
25
+ invocationPath(path) {
26
+ return [...(this.config.path || []), path];
27
+ }
28
+ }
@@ -0,0 +1 @@
1
+ export declare const typeDeclarations = "\n/**\n * Encodes a text string as a valid component of a Uniform Resource Identifier (URI).\n * @param uriComponent A value representing an unencoded URI component.\n */\ndeclare function encodeURIComponent(\n uriComponent: string | number | boolean\n): string;\n\ndeclare function btoa(s: string | Uint8Array): string;\ndeclare function atob(s: string): string;\n\ninterface Console {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n log(...data: any[]);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n error(...data: any[]);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n warn(...data: any[]);\n}\n\n// eslint-disable-next-line no-var\ndeclare var console: Console;\n";
@@ -0,0 +1,26 @@
1
+ // THIS FILE IS GENERATED BY update-type-declarations.ts
2
+ // DO NOT EDIT DIRECTLY
3
+ export const typeDeclarations = `
4
+ /**
5
+ * Encodes a text string as a valid component of a Uniform Resource Identifier (URI).
6
+ * @param uriComponent A value representing an unencoded URI component.
7
+ */
8
+ declare function encodeURIComponent(
9
+ uriComponent: string | number | boolean
10
+ ): string;
11
+
12
+ declare function btoa(s: string | Uint8Array): string;
13
+ declare function atob(s: string): string;
14
+
15
+ interface Console {
16
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
+ log(...data: any[]);
18
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
+ error(...data: any[]);
20
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
+ warn(...data: any[]);
22
+ }
23
+
24
+ // eslint-disable-next-line no-var
25
+ declare var console: Console;
26
+ `;
@@ -0,0 +1,26 @@
1
+ export type Values = Record<string, unknown>;
2
+ export type OutputValues = Values;
3
+ export type ModuleSpec = Record<string, string>;
4
+ export type ModuleMethod = "default" | "describe";
5
+ export type UUID = `${string}-${string}-${string}-${string}-${string}`;
6
+ export type DescriberInputs = {
7
+ inputs?: Values;
8
+ inputSchema?: unknown;
9
+ outputSchema?: unknown;
10
+ asType?: boolean;
11
+ };
12
+ export type DescriberOutputs = {
13
+ inputSchema: unknown;
14
+ outputSchema: unknown;
15
+ };
16
+ export type InvokeInputs = Values;
17
+ export type InvokeOutputs = Values;
18
+ export type CapabilityFunction = (inputs: Values, path: number[]) => Promise<Values | undefined>;
19
+ export type CapabilityRegistry = {
20
+ fetch?: CapabilityFunction;
21
+ };
22
+ export type Capability = CapabilityFunction;
23
+ export type CapabilitySpec = CapabilityRegistry;
24
+ export type Sandbox = {
25
+ runModule(invocationId: UUID, method: "default" | "describe", modules: ModuleSpec, name: string, inputs: Record<string, unknown>): Promise<InvokeOutputs | DescriberOutputs>;
26
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export { WebSandbox } from "./web.js";
@@ -0,0 +1 @@
1
+ export { WebSandbox } from "./web.js";
@@ -0,0 +1,18 @@
1
+ import type { InvokeInputs, ModuleSpec, Sandbox } from "./types.js";
2
+ import type { UUID } from "./types.js";
3
+ export { WebSandbox };
4
+ declare class WebSandbox implements Sandbox {
5
+ readonly runtimeUrl: URL;
6
+ private readonly sandboxInstance;
7
+ constructor(runtimeUrl: URL);
8
+ getWasmExports(): Promise<{
9
+ scanFind: (src: string, configs: any[]) => any;
10
+ scanFix: (src: string, configs: any[]) => string;
11
+ dumpASTNodes: (src: string) => any;
12
+ dumpPattern: (src: string, selector?: string | null) => any;
13
+ }>;
14
+ private initializeSandbox;
15
+ initializeTreeSitter(): Promise<void>;
16
+ setupParser(lang: string, path: string): Promise<void>;
17
+ runModule(sessionId: UUID, operation: "default" | "describe", moduleRegistry: ModuleSpec, moduleName: string, moduleInputs: InvokeInputs): Promise<any>;
18
+ }
@@ -0,0 +1,91 @@
1
+ import { ConsoleStdout, OpenFile, WASI, File as WasiFile, } from "@bjorn3/browser_wasi_shim";
2
+ import factory from "./factory.js";
3
+ export { WebSandbox };
4
+ class WebSandbox {
5
+ runtimeUrl;
6
+ sandboxInstance;
7
+ constructor(runtimeUrl) {
8
+ this.runtimeUrl = runtimeUrl;
9
+ this.sandboxInstance = this.initializeSandbox();
10
+ }
11
+ async getWasmExports() {
12
+ const result = await this.sandboxInstance;
13
+ return {
14
+ scanFind: result.scanFind,
15
+ scanFix: result.scanFix,
16
+ dumpASTNodes: result.dumpASTNodes,
17
+ dumpPattern: result.dumpPattern,
18
+ };
19
+ }
20
+ async initializeSandbox() {
21
+ const wasiRuntime = new WASI([], [], [
22
+ new OpenFile(new WasiFile([])), // stdin
23
+ ConsoleStdout.lineBuffered((message) => console.log(`[WASI stdout] ${message}`)),
24
+ ConsoleStdout.lineBuffered((message) => console.warn(`[WASI stderr] ${message}`)),
25
+ ]);
26
+ const sandboxFactory = factory();
27
+ const { instance } = await WebAssembly.instantiateStreaming(fetch(this.runtimeUrl), {
28
+ "./codemod-sandbox_bg.js": sandboxFactory,
29
+ wasi_snapshot_preview1: wasiRuntime.wasiImport,
30
+ });
31
+ sandboxFactory.__wbg_set_wasm(instance.exports);
32
+ if (typeof sandboxFactory.__wbindgen_init_externref_table === "function") {
33
+ sandboxFactory.__wbindgen_init_externref_table();
34
+ }
35
+ else {
36
+ console.warn("Heap system detected - ensure factory.js has proper heap setup");
37
+ }
38
+ // @ts-expect-error 2739
39
+ wasiRuntime.start({ exports: instance.exports });
40
+ return sandboxFactory;
41
+ }
42
+ async initializeTreeSitter() {
43
+ const sandbox = await this.sandboxInstance;
44
+ return sandbox.Parser.init();
45
+ }
46
+ async setupParser(lang, path) {
47
+ const sandbox = await this.sandboxInstance;
48
+ return sandbox.setupParser(lang, path);
49
+ }
50
+ async runModule(sessionId, operation, moduleRegistry, moduleName, moduleInputs) {
51
+ const sandboxInstance = await this.sandboxInstance;
52
+ const moduleCode = moduleRegistry[moduleName];
53
+ if (!moduleCode) {
54
+ return { $error: `Unable to find module "${moduleName}"` };
55
+ }
56
+ const inputData = JSON.stringify(moduleInputs);
57
+ console.debug(...formatDataSize(`Run module: "${moduleName}": input size`, inputData.length));
58
+ const executionResult = await sandboxInstance.run_module(sessionId, operation, moduleName, moduleRegistry, moduleCode, inputData);
59
+ return JSON.parse(executionResult);
60
+ }
61
+ }
62
+ function formatDataSize(prefix, sizeInBytes) {
63
+ if (!Number.isInteger(sizeInBytes)) {
64
+ console.warn("formatDataSize: Input number should be an integer. Proceeding with the given value.");
65
+ }
66
+ const kilobyte = 1024;
67
+ const sizeUnits = ["Bytes", "KiB", "MiB", "GiB", "TiB"];
68
+ const colorStyles = [
69
+ "color:darkgray",
70
+ "color:blue",
71
+ "color:red;font-weight:bold",
72
+ "color:red;font-weight:bold",
73
+ "color:red;font-weight:bold",
74
+ ];
75
+ if (sizeInBytes === 0) {
76
+ return ["%c0", "color: lightgray", "Bytes"];
77
+ }
78
+ let currentSize = sizeInBytes;
79
+ let unitIndex = 0;
80
+ while (currentSize >= kilobyte && unitIndex < sizeUnits.length - 1) {
81
+ currentSize /= kilobyte;
82
+ unitIndex++;
83
+ }
84
+ const formattedSize = Number.parseFloat(currentSize.toFixed(2));
85
+ let unitLabel = sizeUnits[unitIndex];
86
+ const consoleStyle = colorStyles[unitIndex];
87
+ if (currentSize === 1) {
88
+ unitLabel = "Byte";
89
+ }
90
+ return [`${prefix} %c${formattedSize}`, consoleStyle, unitLabel];
91
+ }
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@codemod.com/codemod-sandbox",
3
+ "version": "0.1.0",
4
+ "description": "Codemod Javascript Sandbox",
5
+ "main": "./dist/src/index.js",
6
+ "exports": {
7
+ ".": "./dist/src/index.js",
8
+ "./sandbox.wasm": "./sandbox.wasm",
9
+ "./web": "./dist/src/web-exports.js",
10
+ "./node": "./dist/src/node-exports.js"
11
+ },
12
+ "types": "dist/src/index.d.ts",
13
+ "type": "module",
14
+ "scripts": {
15
+ "prepack": "pnpm run build",
16
+ "build:rs": "cd ../.. && cargo run -p codemod-sandbox-build --color=always -- -r",
17
+ "copy-runtime-files": "pnpm run build:rs && tsx scripts/copy-runtime-files.ts",
18
+ "generate-type-declarations": "tsx scripts/generate-type-declarations.ts",
19
+ "build:tsc": "pnpm run generate-type-declarations && tsc --project tsconfig.build.json --pretty",
20
+ "build:runtime": "pnpm run copy-runtime-files",
21
+ "build": "pnpm run build:runtime && pnpm run build:tsc",
22
+ "lint": "pnpm run build:tsc && eslint src/ --ext .ts",
23
+ "test": "pnpm run build && node --test --enable-source-maps --test-reporter spec dist/tests/*.js"
24
+ },
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "https://github.com/codemod-com/codemod"
28
+ },
29
+ "files": [
30
+ "dist/src",
31
+ "sandbox.wasm"
32
+ ],
33
+ "keywords": [],
34
+ "devDependencies": {
35
+ "@tsconfig/node22": "^22.0.2",
36
+ "@types/node": "^22.0.0",
37
+ "eslint": "^8.57.1",
38
+ "tsx": "^4.19.3",
39
+ "typescript": "^5.8.3"
40
+ },
41
+ "dependencies": {
42
+ "@bjorn3/browser_wasi_shim": "^0.4.1",
43
+ "web-tree-sitter": "^0.25.4"
44
+ },
45
+ "publishConfig": {
46
+ "access": "public"
47
+ },
48
+ "packageManager": "pnpm@10.4.1+sha512.c753b6c3ad7afa13af388fa6d808035a008e30ea9993f58c6663e2bc5ff21679aa834db094987129aa4d488b86df57f7b634981b2f827cdcacc698cc0cfb88af"
49
+ }
package/sandbox.wasm ADDED
Binary file