@run0/jiki 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.
- package/dist/browser-bundle.d.ts +40 -0
- package/dist/builtins.d.ts +22 -0
- package/dist/code-transform.d.ts +7 -0
- package/dist/config/cdn.d.ts +13 -0
- package/dist/container.d.ts +101 -0
- package/dist/dev-server.d.ts +69 -0
- package/dist/errors.d.ts +19 -0
- package/dist/frameworks/code-transforms.d.ts +32 -0
- package/dist/frameworks/next-api-handler.d.ts +72 -0
- package/dist/frameworks/next-dev-server.d.ts +141 -0
- package/dist/frameworks/next-html-generator.d.ts +36 -0
- package/dist/frameworks/next-route-resolver.d.ts +19 -0
- package/dist/frameworks/next-shims.d.ts +78 -0
- package/dist/frameworks/remix-dev-server.d.ts +47 -0
- package/dist/frameworks/sveltekit-dev-server.d.ts +43 -0
- package/dist/frameworks/vite-dev-server.d.ts +50 -0
- package/dist/fs-errors.d.ts +36 -0
- package/dist/index.cjs +14916 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.mjs +14898 -0
- package/dist/index.mjs.map +1 -0
- package/dist/kernel.d.ts +48 -0
- package/dist/memfs.d.ts +144 -0
- package/dist/metrics.d.ts +78 -0
- package/dist/module-resolver.d.ts +60 -0
- package/dist/network-interceptor.d.ts +71 -0
- package/dist/npm/cache.d.ts +76 -0
- package/dist/npm/index.d.ts +60 -0
- package/dist/npm/lockfile-reader.d.ts +32 -0
- package/dist/npm/pnpm.d.ts +18 -0
- package/dist/npm/registry.d.ts +45 -0
- package/dist/npm/resolver.d.ts +39 -0
- package/dist/npm/sync-installer.d.ts +18 -0
- package/dist/npm/tarball.d.ts +4 -0
- package/dist/npm/workspaces.d.ts +46 -0
- package/dist/persistence.d.ts +94 -0
- package/dist/plugin.d.ts +156 -0
- package/dist/polyfills/assert.d.ts +30 -0
- package/dist/polyfills/child_process.d.ts +116 -0
- package/dist/polyfills/chokidar.d.ts +18 -0
- package/dist/polyfills/crypto.d.ts +49 -0
- package/dist/polyfills/events.d.ts +28 -0
- package/dist/polyfills/fs.d.ts +82 -0
- package/dist/polyfills/http.d.ts +147 -0
- package/dist/polyfills/module.d.ts +29 -0
- package/dist/polyfills/net.d.ts +53 -0
- package/dist/polyfills/os.d.ts +91 -0
- package/dist/polyfills/path.d.ts +96 -0
- package/dist/polyfills/perf_hooks.d.ts +21 -0
- package/dist/polyfills/process.d.ts +99 -0
- package/dist/polyfills/querystring.d.ts +15 -0
- package/dist/polyfills/readdirp.d.ts +18 -0
- package/dist/polyfills/readline.d.ts +32 -0
- package/dist/polyfills/stream.d.ts +106 -0
- package/dist/polyfills/stubs.d.ts +737 -0
- package/dist/polyfills/tty.d.ts +25 -0
- package/dist/polyfills/url.d.ts +41 -0
- package/dist/polyfills/util.d.ts +61 -0
- package/dist/polyfills/v8.d.ts +43 -0
- package/dist/polyfills/vm.d.ts +76 -0
- package/dist/polyfills/worker-threads.d.ts +77 -0
- package/dist/polyfills/ws.d.ts +32 -0
- package/dist/polyfills/zlib.d.ts +87 -0
- package/dist/runtime-helpers.d.ts +4 -0
- package/dist/runtime-interface.d.ts +39 -0
- package/dist/sandbox.d.ts +69 -0
- package/dist/server-bridge.d.ts +55 -0
- package/dist/shell-commands.d.ts +2 -0
- package/dist/shell.d.ts +101 -0
- package/dist/transpiler.d.ts +47 -0
- package/dist/type-checker.d.ts +57 -0
- package/dist/types/package-json.d.ts +17 -0
- package/dist/utils/binary-encoding.d.ts +4 -0
- package/dist/utils/hash.d.ts +6 -0
- package/dist/utils/safe-path.d.ts +6 -0
- package/dist/worker-runtime.d.ts +34 -0
- package/package.json +59 -0
- package/src/browser-bundle.ts +498 -0
- package/src/builtins.ts +222 -0
- package/src/code-transform.ts +183 -0
- package/src/config/cdn.ts +17 -0
- package/src/container.ts +343 -0
- package/src/dev-server.ts +322 -0
- package/src/errors.ts +604 -0
- package/src/frameworks/code-transforms.ts +667 -0
- package/src/frameworks/next-api-handler.ts +366 -0
- package/src/frameworks/next-dev-server.ts +1252 -0
- package/src/frameworks/next-html-generator.ts +585 -0
- package/src/frameworks/next-route-resolver.ts +521 -0
- package/src/frameworks/next-shims.ts +1084 -0
- package/src/frameworks/remix-dev-server.ts +163 -0
- package/src/frameworks/sveltekit-dev-server.ts +197 -0
- package/src/frameworks/vite-dev-server.ts +370 -0
- package/src/fs-errors.ts +118 -0
- package/src/index.ts +188 -0
- package/src/kernel.ts +381 -0
- package/src/memfs.ts +1006 -0
- package/src/metrics.ts +140 -0
- package/src/module-resolver.ts +511 -0
- package/src/network-interceptor.ts +143 -0
- package/src/npm/cache.ts +172 -0
- package/src/npm/index.ts +377 -0
- package/src/npm/lockfile-reader.ts +105 -0
- package/src/npm/pnpm.ts +108 -0
- package/src/npm/registry.ts +120 -0
- package/src/npm/resolver.ts +339 -0
- package/src/npm/sync-installer.ts +217 -0
- package/src/npm/tarball.ts +136 -0
- package/src/npm/workspaces.ts +255 -0
- package/src/persistence.ts +235 -0
- package/src/plugin.ts +293 -0
- package/src/polyfills/assert.ts +164 -0
- package/src/polyfills/child_process.ts +535 -0
- package/src/polyfills/chokidar.ts +52 -0
- package/src/polyfills/crypto.ts +433 -0
- package/src/polyfills/events.ts +178 -0
- package/src/polyfills/fs.ts +297 -0
- package/src/polyfills/http.ts +478 -0
- package/src/polyfills/module.ts +97 -0
- package/src/polyfills/net.ts +123 -0
- package/src/polyfills/os.ts +108 -0
- package/src/polyfills/path.ts +169 -0
- package/src/polyfills/perf_hooks.ts +30 -0
- package/src/polyfills/process.ts +349 -0
- package/src/polyfills/querystring.ts +66 -0
- package/src/polyfills/readdirp.ts +72 -0
- package/src/polyfills/readline.ts +80 -0
- package/src/polyfills/stream.ts +610 -0
- package/src/polyfills/stubs.ts +600 -0
- package/src/polyfills/tty.ts +43 -0
- package/src/polyfills/url.ts +97 -0
- package/src/polyfills/util.ts +173 -0
- package/src/polyfills/v8.ts +62 -0
- package/src/polyfills/vm.ts +111 -0
- package/src/polyfills/worker-threads.ts +189 -0
- package/src/polyfills/ws.ts +73 -0
- package/src/polyfills/zlib.ts +244 -0
- package/src/runtime-helpers.ts +83 -0
- package/src/runtime-interface.ts +46 -0
- package/src/sandbox.ts +178 -0
- package/src/server-bridge.ts +473 -0
- package/src/service-worker.ts +153 -0
- package/src/shell-commands.ts +708 -0
- package/src/shell.ts +795 -0
- package/src/transpiler.ts +282 -0
- package/src/type-checker.ts +241 -0
- package/src/types/package-json.ts +17 -0
- package/src/utils/binary-encoding.ts +38 -0
- package/src/utils/hash.ts +24 -0
- package/src/utils/safe-path.ts +38 -0
- package/src/worker-runtime.ts +42 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript/TSX/JSX transpiler and bundler powered by esbuild-wasm.
|
|
3
|
+
* Provides lazy initialization with singleton pattern for the WASM runtime.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as esbuild from "esbuild-wasm";
|
|
7
|
+
import type { MemFS } from "./memfs";
|
|
8
|
+
import * as pathShim from "./polyfills/path";
|
|
9
|
+
|
|
10
|
+
let initialized = false;
|
|
11
|
+
let initializing: Promise<void> | null = null;
|
|
12
|
+
|
|
13
|
+
export interface TranspileOptions {
|
|
14
|
+
jsx?: "transform" | "preserve" | "automatic";
|
|
15
|
+
jsxFactory?: string;
|
|
16
|
+
jsxFragment?: string;
|
|
17
|
+
jsxImportSource?: string;
|
|
18
|
+
target?: string;
|
|
19
|
+
sourcemap?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface BundleOptions {
|
|
23
|
+
entryPoint: string;
|
|
24
|
+
format?: "esm" | "cjs" | "iife";
|
|
25
|
+
platform?: "browser" | "node" | "neutral";
|
|
26
|
+
minify?: boolean;
|
|
27
|
+
sourcemap?: boolean;
|
|
28
|
+
target?: string;
|
|
29
|
+
external?: string[];
|
|
30
|
+
outfile?: string;
|
|
31
|
+
loader?: Record<string, string>;
|
|
32
|
+
define?: Record<string, string>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface BundleResult {
|
|
36
|
+
code: string;
|
|
37
|
+
map?: string;
|
|
38
|
+
errors: esbuild.Message[];
|
|
39
|
+
warnings: esbuild.Message[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface InitOptions {
|
|
43
|
+
wasmURL?: string | URL;
|
|
44
|
+
/** When true, esbuild runs transpilation in a Web Worker (browser only). */
|
|
45
|
+
useWorker?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let customWasmURL: string | URL | undefined;
|
|
49
|
+
|
|
50
|
+
export function setWasmURL(url: string | URL): void {
|
|
51
|
+
customWasmURL = url;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function resolveWasmURL(options?: InitOptions): string | URL | undefined {
|
|
55
|
+
if (options?.wasmURL) return options.wasmURL;
|
|
56
|
+
if (customWasmURL) return customWasmURL;
|
|
57
|
+
|
|
58
|
+
const isBrowser =
|
|
59
|
+
typeof window !== "undefined" || typeof importScripts === "function";
|
|
60
|
+
if (!isBrowser) return undefined;
|
|
61
|
+
|
|
62
|
+
return "https://unpkg.com/esbuild-wasm@" + esbuild.version + "/esbuild.wasm";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function initTranspiler(options?: InitOptions): Promise<void> {
|
|
66
|
+
if (initialized) return;
|
|
67
|
+
if (initializing) return initializing;
|
|
68
|
+
|
|
69
|
+
const initOpts: esbuild.InitializeOptions = {
|
|
70
|
+
worker: options?.useWorker ?? false,
|
|
71
|
+
};
|
|
72
|
+
const wasmURL = resolveWasmURL(options);
|
|
73
|
+
if (wasmURL) initOpts.wasmURL = wasmURL;
|
|
74
|
+
|
|
75
|
+
initializing = esbuild.initialize(initOpts);
|
|
76
|
+
await initializing;
|
|
77
|
+
initialized = true;
|
|
78
|
+
initializing = null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function isInitialized(): boolean {
|
|
82
|
+
return initialized;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function loaderFromFilename(filename: string): esbuild.Loader {
|
|
86
|
+
if (filename.endsWith(".tsx")) return "tsx";
|
|
87
|
+
if (filename.endsWith(".ts")) return "ts";
|
|
88
|
+
if (filename.endsWith(".jsx")) return "jsx";
|
|
89
|
+
return "js";
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function needsTranspilation(filename: string): boolean {
|
|
93
|
+
return (
|
|
94
|
+
filename.endsWith(".ts") ||
|
|
95
|
+
filename.endsWith(".tsx") ||
|
|
96
|
+
filename.endsWith(".jsx")
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function transpile(
|
|
101
|
+
code: string,
|
|
102
|
+
filename: string,
|
|
103
|
+
options: TranspileOptions = {},
|
|
104
|
+
): Promise<string> {
|
|
105
|
+
await initTranspiler();
|
|
106
|
+
|
|
107
|
+
const loader = loaderFromFilename(filename);
|
|
108
|
+
const result = await esbuild.transform(code, {
|
|
109
|
+
loader,
|
|
110
|
+
format: "esm",
|
|
111
|
+
target: options.target || "esnext",
|
|
112
|
+
sourcemap: options.sourcemap ? "inline" : false,
|
|
113
|
+
jsx: options.jsx || "automatic",
|
|
114
|
+
jsxFactory: options.jsxFactory,
|
|
115
|
+
jsxFragment: options.jsxFragment,
|
|
116
|
+
jsxImportSource: options.jsxImportSource,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
return result.code;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const isBrowserEnv =
|
|
123
|
+
typeof window !== "undefined" || typeof importScripts === "function";
|
|
124
|
+
|
|
125
|
+
export function hasSyncSupport(): boolean {
|
|
126
|
+
return !isBrowserEnv;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function transpileSync(
|
|
130
|
+
code: string,
|
|
131
|
+
filename: string,
|
|
132
|
+
options: TranspileOptions = {},
|
|
133
|
+
): string {
|
|
134
|
+
if (!initialized) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
"Transpiler not initialized. Call initTranspiler() first or use transpile() which auto-initializes.",
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (isBrowserEnv) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
"transpileSync is not available in the browser. Use transpile() (async) or prepareFile() to pre-transpile.",
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const loader = loaderFromFilename(filename);
|
|
147
|
+
const result = esbuild.transformSync(code, {
|
|
148
|
+
loader,
|
|
149
|
+
format: "esm",
|
|
150
|
+
target: options.target || "esnext",
|
|
151
|
+
sourcemap: options.sourcemap ? "inline" : false,
|
|
152
|
+
jsx: options.jsx || "automatic",
|
|
153
|
+
jsxFactory: options.jsxFactory,
|
|
154
|
+
jsxFragment: options.jsxFragment,
|
|
155
|
+
jsxImportSource: options.jsxImportSource,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
return result.code;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function createMemFSPlugin(vfs: MemFS, cwd: string): esbuild.Plugin {
|
|
162
|
+
return {
|
|
163
|
+
name: "memfs",
|
|
164
|
+
setup(build) {
|
|
165
|
+
build.onResolve({ filter: /.*/ }, args => {
|
|
166
|
+
if (args.kind === "entry-point") {
|
|
167
|
+
const resolved = pathShim.isAbsolute(args.path)
|
|
168
|
+
? args.path
|
|
169
|
+
: pathShim.resolve(cwd, args.path);
|
|
170
|
+
return { path: resolved, namespace: "memfs" };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const resolveDir = args.resolveDir || cwd;
|
|
174
|
+
|
|
175
|
+
if (args.path.startsWith(".") || args.path.startsWith("/")) {
|
|
176
|
+
const base = args.path.startsWith("/")
|
|
177
|
+
? args.path
|
|
178
|
+
: pathShim.resolve(resolveDir, args.path);
|
|
179
|
+
|
|
180
|
+
for (const candidate of [
|
|
181
|
+
base,
|
|
182
|
+
`${base}.ts`,
|
|
183
|
+
`${base}.tsx`,
|
|
184
|
+
`${base}.js`,
|
|
185
|
+
`${base}.jsx`,
|
|
186
|
+
`${base}.json`,
|
|
187
|
+
]) {
|
|
188
|
+
if (vfs.existsSync(candidate)) {
|
|
189
|
+
try {
|
|
190
|
+
if (vfs.statSync(candidate).isFile()) {
|
|
191
|
+
return { path: candidate, namespace: "memfs" };
|
|
192
|
+
}
|
|
193
|
+
} catch {}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
for (const indexFile of [
|
|
198
|
+
"index.ts",
|
|
199
|
+
"index.tsx",
|
|
200
|
+
"index.js",
|
|
201
|
+
"index.jsx",
|
|
202
|
+
"index.json",
|
|
203
|
+
]) {
|
|
204
|
+
const indexPath = pathShim.join(base, indexFile);
|
|
205
|
+
if (vfs.existsSync(indexPath)) {
|
|
206
|
+
return { path: indexPath, namespace: "memfs" };
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return { path: args.path, external: true };
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
build.onLoad({ filter: /.*/, namespace: "memfs" }, args => {
|
|
215
|
+
try {
|
|
216
|
+
const contents = vfs.readFileSync(args.path, "utf8");
|
|
217
|
+
return { contents, loader: loaderFromFilename(args.path) };
|
|
218
|
+
} catch (e) {
|
|
219
|
+
return {
|
|
220
|
+
errors: [
|
|
221
|
+
{
|
|
222
|
+
text: `File not found: ${args.path}`,
|
|
223
|
+
} as esbuild.PartialMessage,
|
|
224
|
+
],
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export async function bundle(
|
|
233
|
+
vfs: MemFS,
|
|
234
|
+
options: BundleOptions,
|
|
235
|
+
): Promise<BundleResult> {
|
|
236
|
+
await initTranspiler();
|
|
237
|
+
|
|
238
|
+
const cwd = pathShim.dirname(
|
|
239
|
+
pathShim.isAbsolute(options.entryPoint)
|
|
240
|
+
? options.entryPoint
|
|
241
|
+
: pathShim.resolve("/", options.entryPoint),
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
const result = await esbuild.build({
|
|
245
|
+
entryPoints: [options.entryPoint],
|
|
246
|
+
bundle: true,
|
|
247
|
+
write: false,
|
|
248
|
+
format: options.format || "esm",
|
|
249
|
+
platform: options.platform || "browser",
|
|
250
|
+
minify: options.minify || false,
|
|
251
|
+
sourcemap: options.sourcemap ? "inline" : false,
|
|
252
|
+
target: options.target || "esnext",
|
|
253
|
+
external: options.external,
|
|
254
|
+
define: options.define,
|
|
255
|
+
plugins: [createMemFSPlugin(vfs, cwd)],
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const code = result.outputFiles?.[0]?.text || "";
|
|
259
|
+
|
|
260
|
+
if (options.outfile) {
|
|
261
|
+
const outDir = pathShim.dirname(options.outfile);
|
|
262
|
+
if (!vfs.existsSync(outDir)) {
|
|
263
|
+
vfs.mkdirSync(outDir, { recursive: true });
|
|
264
|
+
}
|
|
265
|
+
vfs.writeFileSync(options.outfile, code);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
code,
|
|
270
|
+
errors: result.errors,
|
|
271
|
+
warnings: result.warnings,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export async function stopTranspiler(): Promise<void> {
|
|
276
|
+
if (initialized) {
|
|
277
|
+
await esbuild.stop();
|
|
278
|
+
initialized = false;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export { esbuild };
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight TypeScript type-checker for jiki.
|
|
3
|
+
*
|
|
4
|
+
* Provides on-demand batch type checking by analyzing TypeScript files
|
|
5
|
+
* in the VFS. Uses a simple heuristic-based approach that catches common
|
|
6
|
+
* type errors without requiring the full TypeScript compiler.
|
|
7
|
+
*
|
|
8
|
+
* For full type checking, the TypeScript compiler can be installed as an
|
|
9
|
+
* npm package and run via the shell: `npx tsc --noEmit`.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const checker = new TypeChecker(container.vfs);
|
|
14
|
+
* const diagnostics = checker.check(['/src/app.ts', '/src/utils.ts']);
|
|
15
|
+
* for (const d of diagnostics) {
|
|
16
|
+
* console.log(`${d.file}:${d.line} - ${d.message}`);
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { MemFS } from "./memfs";
|
|
22
|
+
import * as pathShim from "./polyfills/path";
|
|
23
|
+
|
|
24
|
+
export interface Diagnostic {
|
|
25
|
+
file: string;
|
|
26
|
+
line: number;
|
|
27
|
+
column: number;
|
|
28
|
+
message: string;
|
|
29
|
+
severity: "error" | "warning" | "info";
|
|
30
|
+
code?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface TypeCheckerOptions {
|
|
34
|
+
/** Strict mode (default: true). */
|
|
35
|
+
strict?: boolean;
|
|
36
|
+
/** Check for unused variables (default: false). */
|
|
37
|
+
noUnusedLocals?: boolean;
|
|
38
|
+
/** Check for implicit any (default: true in strict mode). */
|
|
39
|
+
noImplicitAny?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Lightweight TypeScript type-checker that catches common errors
|
|
44
|
+
* using pattern matching and heuristics. Not a full type system,
|
|
45
|
+
* but catches the most common mistakes quickly.
|
|
46
|
+
*/
|
|
47
|
+
export class TypeChecker {
|
|
48
|
+
private vfs: MemFS;
|
|
49
|
+
private opts: Required<TypeCheckerOptions>;
|
|
50
|
+
|
|
51
|
+
constructor(vfs: MemFS, options: TypeCheckerOptions = {}) {
|
|
52
|
+
this.vfs = vfs;
|
|
53
|
+
this.opts = {
|
|
54
|
+
strict: options.strict ?? true,
|
|
55
|
+
noUnusedLocals: options.noUnusedLocals ?? false,
|
|
56
|
+
noImplicitAny: options.noImplicitAny ?? options.strict !== false,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Check a list of TypeScript files for common errors.
|
|
62
|
+
* Returns diagnostics sorted by file and line number.
|
|
63
|
+
*/
|
|
64
|
+
check(files: string[]): Diagnostic[] {
|
|
65
|
+
const diagnostics: Diagnostic[] = [];
|
|
66
|
+
|
|
67
|
+
for (const file of files) {
|
|
68
|
+
if (!file.match(/\.(ts|tsx)$/)) continue;
|
|
69
|
+
try {
|
|
70
|
+
const source = this.vfs.readFileSync(file, "utf8");
|
|
71
|
+
diagnostics.push(...this.checkFile(file, source));
|
|
72
|
+
} catch {
|
|
73
|
+
diagnostics.push({
|
|
74
|
+
file,
|
|
75
|
+
line: 0,
|
|
76
|
+
column: 0,
|
|
77
|
+
message: `Cannot read file: ${file}`,
|
|
78
|
+
severity: "error",
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return diagnostics.sort(
|
|
84
|
+
(a, b) => a.file.localeCompare(b.file) || a.line - b.line,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Discover all .ts/.tsx files in a directory and check them.
|
|
90
|
+
*/
|
|
91
|
+
checkAll(dir: string = "/src"): Diagnostic[] {
|
|
92
|
+
const files = this.discoverFiles(dir);
|
|
93
|
+
return this.check(files);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private discoverFiles(dir: string): string[] {
|
|
97
|
+
const files: string[] = [];
|
|
98
|
+
try {
|
|
99
|
+
for (const entry of this.vfs.readdirSync(dir)) {
|
|
100
|
+
const full = pathShim.join(dir, entry);
|
|
101
|
+
try {
|
|
102
|
+
if (this.vfs.statSync(full).isDirectory()) {
|
|
103
|
+
if (entry !== "node_modules" && !entry.startsWith(".")) {
|
|
104
|
+
files.push(...this.discoverFiles(full));
|
|
105
|
+
}
|
|
106
|
+
} else if (entry.match(/\.(ts|tsx)$/) && !entry.endsWith(".d.ts")) {
|
|
107
|
+
files.push(full);
|
|
108
|
+
}
|
|
109
|
+
} catch {
|
|
110
|
+
/* skip */
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
} catch {
|
|
114
|
+
/* dir may not exist */
|
|
115
|
+
}
|
|
116
|
+
return files;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private checkFile(file: string, source: string): Diagnostic[] {
|
|
120
|
+
const diags: Diagnostic[] = [];
|
|
121
|
+
const lines = source.split("\n");
|
|
122
|
+
|
|
123
|
+
for (let i = 0; i < lines.length; i++) {
|
|
124
|
+
const line = lines[i];
|
|
125
|
+
const lineNum = i + 1;
|
|
126
|
+
|
|
127
|
+
// Check for common TypeScript errors
|
|
128
|
+
|
|
129
|
+
// 1. Missing return type on exported functions (noImplicitAny)
|
|
130
|
+
if (this.opts.noImplicitAny) {
|
|
131
|
+
const exportFnMatch = line.match(
|
|
132
|
+
/^export\s+(?:default\s+)?function\s+(\w+)\s*\([^)]*\)\s*\{/,
|
|
133
|
+
);
|
|
134
|
+
if (exportFnMatch && !line.includes("):")) {
|
|
135
|
+
diags.push({
|
|
136
|
+
file,
|
|
137
|
+
line: lineNum,
|
|
138
|
+
column: 0,
|
|
139
|
+
message: `Function '${exportFnMatch[1]}' has no return type annotation`,
|
|
140
|
+
severity: "warning",
|
|
141
|
+
code: "TS7030",
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 2. Duplicate variable declarations in same scope
|
|
147
|
+
const constMatch = line.match(/^\s*(const|let|var)\s+(\w+)/);
|
|
148
|
+
if (constMatch) {
|
|
149
|
+
const varName = constMatch[2];
|
|
150
|
+
// Check for redeclaration in subsequent lines (same indentation level)
|
|
151
|
+
for (let j = i + 1; j < lines.length && j < i + 50; j++) {
|
|
152
|
+
const reDecl = lines[j].match(
|
|
153
|
+
new RegExp(`^\\s*(const|let)\\s+${varName}\\b`),
|
|
154
|
+
);
|
|
155
|
+
if (
|
|
156
|
+
reDecl &&
|
|
157
|
+
lines[j].match(/^\s*/)?.[0] === line.match(/^\s*/)?.[0]
|
|
158
|
+
) {
|
|
159
|
+
diags.push({
|
|
160
|
+
file,
|
|
161
|
+
line: j + 1,
|
|
162
|
+
column: 0,
|
|
163
|
+
message: `Cannot redeclare block-scoped variable '${varName}'`,
|
|
164
|
+
severity: "error",
|
|
165
|
+
code: "TS2451",
|
|
166
|
+
});
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// 3. Using `any` when noImplicitAny is enabled
|
|
173
|
+
if (this.opts.noImplicitAny && this.opts.strict) {
|
|
174
|
+
const anyTypeMatch = line.match(/:\s*any\b/);
|
|
175
|
+
if (
|
|
176
|
+
anyTypeMatch &&
|
|
177
|
+
!line.includes("// @ts-ignore") &&
|
|
178
|
+
!line.includes("eslint-disable")
|
|
179
|
+
) {
|
|
180
|
+
diags.push({
|
|
181
|
+
file,
|
|
182
|
+
line: lineNum,
|
|
183
|
+
column: anyTypeMatch.index ?? 0,
|
|
184
|
+
message: "Unexpected use of 'any' type",
|
|
185
|
+
severity: "warning",
|
|
186
|
+
code: "TS7006",
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// 4. Unreachable code after return/throw
|
|
192
|
+
if (line.match(/^\s*(return|throw)\b/) && i + 1 < lines.length) {
|
|
193
|
+
const nextLine = lines[i + 1]?.trim();
|
|
194
|
+
if (
|
|
195
|
+
nextLine &&
|
|
196
|
+
!nextLine.startsWith("}") &&
|
|
197
|
+
!nextLine.startsWith("//") &&
|
|
198
|
+
!nextLine.startsWith("/*") &&
|
|
199
|
+
nextLine !== ""
|
|
200
|
+
) {
|
|
201
|
+
diags.push({
|
|
202
|
+
file,
|
|
203
|
+
line: lineNum + 1,
|
|
204
|
+
column: 0,
|
|
205
|
+
message: "Unreachable code detected",
|
|
206
|
+
severity: "warning",
|
|
207
|
+
code: "TS7027",
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// 5. Syntax: missing semicolons at statement boundaries (if strict)
|
|
213
|
+
// Only flag obvious cases: variable declarations without semicolons
|
|
214
|
+
if (this.opts.strict) {
|
|
215
|
+
const stmtMatch = line.match(
|
|
216
|
+
/^\s*(const|let|var)\s+\w+\s*=\s*.+[^;{,\s]\s*$/,
|
|
217
|
+
);
|
|
218
|
+
if (
|
|
219
|
+
stmtMatch &&
|
|
220
|
+
!line.includes("//") &&
|
|
221
|
+
!line.endsWith("=>") &&
|
|
222
|
+
!line.endsWith("(")
|
|
223
|
+
) {
|
|
224
|
+
// Skip multi-line expressions
|
|
225
|
+
const nextLine = lines[i + 1]?.trim() || "";
|
|
226
|
+
if (
|
|
227
|
+
nextLine &&
|
|
228
|
+
!nextLine.startsWith(".") &&
|
|
229
|
+
!nextLine.startsWith("+") &&
|
|
230
|
+
!nextLine.startsWith("?") &&
|
|
231
|
+
!nextLine.startsWith(":")
|
|
232
|
+
) {
|
|
233
|
+
// This is a potential missing semicolon — but too noisy, skip for now
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return diags;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface PackageJson {
|
|
2
|
+
name?: string;
|
|
3
|
+
version?: string;
|
|
4
|
+
main?: string;
|
|
5
|
+
module?: string;
|
|
6
|
+
browser?: string | Record<string, string | false>;
|
|
7
|
+
exports?: unknown;
|
|
8
|
+
imports?: Record<string, unknown>;
|
|
9
|
+
bin?: string | Record<string, string>;
|
|
10
|
+
scripts?: Record<string, string>;
|
|
11
|
+
dependencies?: Record<string, string>;
|
|
12
|
+
devDependencies?: Record<string, string>;
|
|
13
|
+
peerDependencies?: Record<string, string>;
|
|
14
|
+
optionalDependencies?: Record<string, string>;
|
|
15
|
+
type?: "module" | "commonjs";
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const CHUNK = 8192;
|
|
2
|
+
|
|
3
|
+
export function uint8ToBase64(bytes: Uint8Array): string {
|
|
4
|
+
const parts: string[] = [];
|
|
5
|
+
for (let i = 0; i < bytes.length; i += CHUNK) {
|
|
6
|
+
parts.push(
|
|
7
|
+
String.fromCharCode.apply(null, Array.from(bytes.subarray(i, i + CHUNK))),
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
return btoa(parts.join(""));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function base64ToUint8(base64: string): Uint8Array {
|
|
14
|
+
const binary = atob(base64);
|
|
15
|
+
const bytes = new Uint8Array(binary.length);
|
|
16
|
+
for (let i = 0; i < binary.length; i++) {
|
|
17
|
+
bytes[i] = binary.charCodeAt(i);
|
|
18
|
+
}
|
|
19
|
+
return bytes;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function uint8ToHex(bytes: Uint8Array): string {
|
|
23
|
+
const hex = new Array(bytes.length);
|
|
24
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
25
|
+
hex[i] = bytes[i].toString(16).padStart(2, "0");
|
|
26
|
+
}
|
|
27
|
+
return hex.join("");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function uint8ToBinaryString(bytes: Uint8Array): string {
|
|
31
|
+
const parts: string[] = [];
|
|
32
|
+
for (let i = 0; i < bytes.length; i += CHUNK) {
|
|
33
|
+
parts.push(
|
|
34
|
+
String.fromCharCode.apply(null, Array.from(bytes.subarray(i, i + CHUNK))),
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return parts.join("");
|
|
38
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FNV-1a hash producing a 64-bit result (two 32-bit halves).
|
|
3
|
+
* Returns base36 string (~12 chars). Much lower collision probability
|
|
4
|
+
* than the previous 32-bit djb2 (~6 chars).
|
|
5
|
+
*/
|
|
6
|
+
export function simpleHash(str: string): string {
|
|
7
|
+
// FNV-1a 64-bit offset basis and prime (split into high/low 32-bit)
|
|
8
|
+
let h0 = 0x811c9dc5; // low 32 bits of offset basis
|
|
9
|
+
let h1 = 0xcbf29ce4; // high 32 bits of offset basis
|
|
10
|
+
|
|
11
|
+
for (let i = 0; i < str.length; i++) {
|
|
12
|
+
const c = str.charCodeAt(i);
|
|
13
|
+
// XOR with byte
|
|
14
|
+
h0 ^= c;
|
|
15
|
+
// Multiply by FNV prime 0x00000100000001B3
|
|
16
|
+
// Using schoolbook multiplication for two 32-bit halves
|
|
17
|
+
const lo = Math.imul(h0, 0x01b3) >>> 0;
|
|
18
|
+
const hi = (Math.imul(h1, 0x01b3) + Math.imul(h0, 0x0100)) >>> 0;
|
|
19
|
+
h0 = lo;
|
|
20
|
+
h1 = hi;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return (h1 >>> 0).toString(36) + (h0 >>> 0).toString(36);
|
|
24
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { posix } from "../polyfills/path";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resolve a URL path safely within a root directory.
|
|
5
|
+
* Strips query/hash, decodes, normalizes, and ensures the result
|
|
6
|
+
* never escapes outside root via .. traversal.
|
|
7
|
+
*/
|
|
8
|
+
export function safePath(root: string, urlPath: string): string {
|
|
9
|
+
// Strip query string and hash
|
|
10
|
+
let p = urlPath.split("?")[0].split("#")[0];
|
|
11
|
+
|
|
12
|
+
// Decode percent-encoded characters
|
|
13
|
+
try {
|
|
14
|
+
p = decodeURIComponent(p);
|
|
15
|
+
} catch {
|
|
16
|
+
/* keep as-is */
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Ensure leading slash
|
|
20
|
+
if (!p.startsWith("/")) p = "/" + p;
|
|
21
|
+
|
|
22
|
+
// Normalize (resolves .., ., double slashes)
|
|
23
|
+
p = posix.normalize(p);
|
|
24
|
+
|
|
25
|
+
// Join with root
|
|
26
|
+
const full = root === "/" ? p : posix.normalize(root + "/" + p);
|
|
27
|
+
|
|
28
|
+
// Verify the result starts with root
|
|
29
|
+
const normalRoot = root === "/" ? "/" : posix.normalize(root);
|
|
30
|
+
if (
|
|
31
|
+
!full.startsWith(normalRoot === "/" ? "/" : normalRoot + "/") &&
|
|
32
|
+
full !== normalRoot
|
|
33
|
+
) {
|
|
34
|
+
return normalRoot;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return full;
|
|
38
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker runtime configuration.
|
|
3
|
+
*
|
|
4
|
+
* When `worker` mode is enabled, CPU-intensive operations (transpilation via
|
|
5
|
+
* esbuild-wasm) are offloaded to a Web Worker. This prevents UI freezes
|
|
6
|
+
* during TypeScript/JSX compilation.
|
|
7
|
+
*
|
|
8
|
+
* The `worker` option on Container controls this:
|
|
9
|
+
* - `false` (default) — everything runs on the main thread
|
|
10
|
+
* - `true` — enable worker-based transpilation
|
|
11
|
+
* - `'auto'` — use workers when `Worker` is available (browser), skip in Node.js
|
|
12
|
+
*
|
|
13
|
+
* Full kernel isolation (executing `require()` and `new Function()` in a
|
|
14
|
+
* worker) is planned for a future release and requires SharedArrayBuffer
|
|
15
|
+
* for synchronous VFS access from the worker.
|
|
16
|
+
*
|
|
17
|
+
* @module
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export type WorkerMode = boolean | "auto";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Determine whether to use worker-based transpilation.
|
|
24
|
+
* Returns `true` if workers should be enabled based on the mode
|
|
25
|
+
* and the current runtime environment.
|
|
26
|
+
*/
|
|
27
|
+
export function shouldUseWorker(mode: WorkerMode): boolean {
|
|
28
|
+
if (mode === false) return false;
|
|
29
|
+
if (mode === true) return true;
|
|
30
|
+
// 'auto' — use workers in browser environments where Worker is available
|
|
31
|
+
return typeof Worker !== "undefined" && typeof window !== "undefined";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Configuration for the worker runtime.
|
|
36
|
+
* Currently only affects transpilation; future versions will support
|
|
37
|
+
* full kernel isolation.
|
|
38
|
+
*/
|
|
39
|
+
export interface WorkerRuntimeConfig {
|
|
40
|
+
/** Whether to use Web Workers for transpilation. */
|
|
41
|
+
useWorker: boolean;
|
|
42
|
+
}
|