@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.
Files changed (152) hide show
  1. package/dist/browser-bundle.d.ts +40 -0
  2. package/dist/builtins.d.ts +22 -0
  3. package/dist/code-transform.d.ts +7 -0
  4. package/dist/config/cdn.d.ts +13 -0
  5. package/dist/container.d.ts +101 -0
  6. package/dist/dev-server.d.ts +69 -0
  7. package/dist/errors.d.ts +19 -0
  8. package/dist/frameworks/code-transforms.d.ts +32 -0
  9. package/dist/frameworks/next-api-handler.d.ts +72 -0
  10. package/dist/frameworks/next-dev-server.d.ts +141 -0
  11. package/dist/frameworks/next-html-generator.d.ts +36 -0
  12. package/dist/frameworks/next-route-resolver.d.ts +19 -0
  13. package/dist/frameworks/next-shims.d.ts +78 -0
  14. package/dist/frameworks/remix-dev-server.d.ts +47 -0
  15. package/dist/frameworks/sveltekit-dev-server.d.ts +43 -0
  16. package/dist/frameworks/vite-dev-server.d.ts +50 -0
  17. package/dist/fs-errors.d.ts +36 -0
  18. package/dist/index.cjs +14916 -0
  19. package/dist/index.cjs.map +1 -0
  20. package/dist/index.d.ts +61 -0
  21. package/dist/index.mjs +14898 -0
  22. package/dist/index.mjs.map +1 -0
  23. package/dist/kernel.d.ts +48 -0
  24. package/dist/memfs.d.ts +144 -0
  25. package/dist/metrics.d.ts +78 -0
  26. package/dist/module-resolver.d.ts +60 -0
  27. package/dist/network-interceptor.d.ts +71 -0
  28. package/dist/npm/cache.d.ts +76 -0
  29. package/dist/npm/index.d.ts +60 -0
  30. package/dist/npm/lockfile-reader.d.ts +32 -0
  31. package/dist/npm/pnpm.d.ts +18 -0
  32. package/dist/npm/registry.d.ts +45 -0
  33. package/dist/npm/resolver.d.ts +39 -0
  34. package/dist/npm/sync-installer.d.ts +18 -0
  35. package/dist/npm/tarball.d.ts +4 -0
  36. package/dist/npm/workspaces.d.ts +46 -0
  37. package/dist/persistence.d.ts +94 -0
  38. package/dist/plugin.d.ts +156 -0
  39. package/dist/polyfills/assert.d.ts +30 -0
  40. package/dist/polyfills/child_process.d.ts +116 -0
  41. package/dist/polyfills/chokidar.d.ts +18 -0
  42. package/dist/polyfills/crypto.d.ts +49 -0
  43. package/dist/polyfills/events.d.ts +28 -0
  44. package/dist/polyfills/fs.d.ts +82 -0
  45. package/dist/polyfills/http.d.ts +147 -0
  46. package/dist/polyfills/module.d.ts +29 -0
  47. package/dist/polyfills/net.d.ts +53 -0
  48. package/dist/polyfills/os.d.ts +91 -0
  49. package/dist/polyfills/path.d.ts +96 -0
  50. package/dist/polyfills/perf_hooks.d.ts +21 -0
  51. package/dist/polyfills/process.d.ts +99 -0
  52. package/dist/polyfills/querystring.d.ts +15 -0
  53. package/dist/polyfills/readdirp.d.ts +18 -0
  54. package/dist/polyfills/readline.d.ts +32 -0
  55. package/dist/polyfills/stream.d.ts +106 -0
  56. package/dist/polyfills/stubs.d.ts +737 -0
  57. package/dist/polyfills/tty.d.ts +25 -0
  58. package/dist/polyfills/url.d.ts +41 -0
  59. package/dist/polyfills/util.d.ts +61 -0
  60. package/dist/polyfills/v8.d.ts +43 -0
  61. package/dist/polyfills/vm.d.ts +76 -0
  62. package/dist/polyfills/worker-threads.d.ts +77 -0
  63. package/dist/polyfills/ws.d.ts +32 -0
  64. package/dist/polyfills/zlib.d.ts +87 -0
  65. package/dist/runtime-helpers.d.ts +4 -0
  66. package/dist/runtime-interface.d.ts +39 -0
  67. package/dist/sandbox.d.ts +69 -0
  68. package/dist/server-bridge.d.ts +55 -0
  69. package/dist/shell-commands.d.ts +2 -0
  70. package/dist/shell.d.ts +101 -0
  71. package/dist/transpiler.d.ts +47 -0
  72. package/dist/type-checker.d.ts +57 -0
  73. package/dist/types/package-json.d.ts +17 -0
  74. package/dist/utils/binary-encoding.d.ts +4 -0
  75. package/dist/utils/hash.d.ts +6 -0
  76. package/dist/utils/safe-path.d.ts +6 -0
  77. package/dist/worker-runtime.d.ts +34 -0
  78. package/package.json +59 -0
  79. package/src/browser-bundle.ts +498 -0
  80. package/src/builtins.ts +222 -0
  81. package/src/code-transform.ts +183 -0
  82. package/src/config/cdn.ts +17 -0
  83. package/src/container.ts +343 -0
  84. package/src/dev-server.ts +322 -0
  85. package/src/errors.ts +604 -0
  86. package/src/frameworks/code-transforms.ts +667 -0
  87. package/src/frameworks/next-api-handler.ts +366 -0
  88. package/src/frameworks/next-dev-server.ts +1252 -0
  89. package/src/frameworks/next-html-generator.ts +585 -0
  90. package/src/frameworks/next-route-resolver.ts +521 -0
  91. package/src/frameworks/next-shims.ts +1084 -0
  92. package/src/frameworks/remix-dev-server.ts +163 -0
  93. package/src/frameworks/sveltekit-dev-server.ts +197 -0
  94. package/src/frameworks/vite-dev-server.ts +370 -0
  95. package/src/fs-errors.ts +118 -0
  96. package/src/index.ts +188 -0
  97. package/src/kernel.ts +381 -0
  98. package/src/memfs.ts +1006 -0
  99. package/src/metrics.ts +140 -0
  100. package/src/module-resolver.ts +511 -0
  101. package/src/network-interceptor.ts +143 -0
  102. package/src/npm/cache.ts +172 -0
  103. package/src/npm/index.ts +377 -0
  104. package/src/npm/lockfile-reader.ts +105 -0
  105. package/src/npm/pnpm.ts +108 -0
  106. package/src/npm/registry.ts +120 -0
  107. package/src/npm/resolver.ts +339 -0
  108. package/src/npm/sync-installer.ts +217 -0
  109. package/src/npm/tarball.ts +136 -0
  110. package/src/npm/workspaces.ts +255 -0
  111. package/src/persistence.ts +235 -0
  112. package/src/plugin.ts +293 -0
  113. package/src/polyfills/assert.ts +164 -0
  114. package/src/polyfills/child_process.ts +535 -0
  115. package/src/polyfills/chokidar.ts +52 -0
  116. package/src/polyfills/crypto.ts +433 -0
  117. package/src/polyfills/events.ts +178 -0
  118. package/src/polyfills/fs.ts +297 -0
  119. package/src/polyfills/http.ts +478 -0
  120. package/src/polyfills/module.ts +97 -0
  121. package/src/polyfills/net.ts +123 -0
  122. package/src/polyfills/os.ts +108 -0
  123. package/src/polyfills/path.ts +169 -0
  124. package/src/polyfills/perf_hooks.ts +30 -0
  125. package/src/polyfills/process.ts +349 -0
  126. package/src/polyfills/querystring.ts +66 -0
  127. package/src/polyfills/readdirp.ts +72 -0
  128. package/src/polyfills/readline.ts +80 -0
  129. package/src/polyfills/stream.ts +610 -0
  130. package/src/polyfills/stubs.ts +600 -0
  131. package/src/polyfills/tty.ts +43 -0
  132. package/src/polyfills/url.ts +97 -0
  133. package/src/polyfills/util.ts +173 -0
  134. package/src/polyfills/v8.ts +62 -0
  135. package/src/polyfills/vm.ts +111 -0
  136. package/src/polyfills/worker-threads.ts +189 -0
  137. package/src/polyfills/ws.ts +73 -0
  138. package/src/polyfills/zlib.ts +244 -0
  139. package/src/runtime-helpers.ts +83 -0
  140. package/src/runtime-interface.ts +46 -0
  141. package/src/sandbox.ts +178 -0
  142. package/src/server-bridge.ts +473 -0
  143. package/src/service-worker.ts +153 -0
  144. package/src/shell-commands.ts +708 -0
  145. package/src/shell.ts +795 -0
  146. package/src/transpiler.ts +282 -0
  147. package/src/type-checker.ts +241 -0
  148. package/src/types/package-json.ts +17 -0
  149. package/src/utils/binary-encoding.ts +38 -0
  150. package/src/utils/hash.ts +24 -0
  151. package/src/utils/safe-path.ts +38 -0
  152. package/src/worker-runtime.ts +42 -0
@@ -0,0 +1,667 @@
1
+ /**
2
+ * Code transformation utilities for ESM/CJS conversion, CSS imports,
3
+ * npm import redirection, and React Refresh registration.
4
+ */
5
+
6
+ import * as acorn from "acorn";
7
+ import acornJsx from "acorn-jsx";
8
+ import { simpleHash } from "../utils/hash";
9
+ import { REACT_CDN, REACT_DOM_CDN } from "../config/cdn";
10
+
11
+ const jsxParser = acorn.Parser.extend(acornJsx());
12
+
13
+ let _varCounter = 0;
14
+
15
+ // JS identifiers can contain $, so we use [\w$]+ instead of \w+
16
+ const ID = "[\\w$]+";
17
+
18
+ export interface CssModuleContext {
19
+ readFile: (path: string) => string;
20
+ exists: (path: string) => boolean;
21
+ }
22
+
23
+ export function transformEsmToCjsSimple(code: string): string {
24
+ let result = code;
25
+
26
+ // Transform: import defaultExport, { named } from 'module' (combined)
27
+ result = result.replace(
28
+ new RegExp(
29
+ `^([ \\t]*)import\\s+(${ID})\\s*,\\s*\\{([\\s\\S]+?)\\}\\s+from\\s+['\"]([^'\"]+)['\"]\\s*;?`,
30
+ "gm",
31
+ ),
32
+ (_, indent, defaultName, imports, mod) => {
33
+ const parts = imports
34
+ .split(",")
35
+ .map((s: string) => s.trim())
36
+ .filter(Boolean);
37
+ const bindings = parts.map((p: string) => {
38
+ const [name, alias] = p.split(/\s+as\s+/).map((s: string) => s.trim());
39
+ return alias ? `${name}: ${alias}` : name;
40
+ });
41
+ const tmpVar = `_mod_${(++_varCounter).toString(36)}`;
42
+ return `${indent}const ${tmpVar} = require("${mod}");\n${indent}const ${defaultName} = ${tmpVar}.default || ${tmpVar};\n${indent}const { ${bindings.join(
43
+ ", ",
44
+ )} } = ${tmpVar};`;
45
+ },
46
+ );
47
+
48
+ // Transform: import defaultExport, * as name from 'module' (combined)
49
+ result = result.replace(
50
+ new RegExp(
51
+ `^([ \\t]*)import\\s+(${ID})\\s*,\\s*\\*\\s+as\\s+(${ID})\\s+from\\s+['\"]([^'\"]+)['\"]\\s*;?`,
52
+ "gm",
53
+ ),
54
+ (_, indent, defaultName, nsName, mod) => {
55
+ return `${indent}const ${nsName} = require("${mod}");\n${indent}const ${defaultName} = ${nsName}.default || ${nsName};`;
56
+ },
57
+ );
58
+
59
+ // Transform: import defaultExport from 'module'
60
+ result = result.replace(
61
+ new RegExp(
62
+ `^([ \\t]*)import\\s+(${ID})\\s+from\\s+['\"]([^'\"]+)['\"]\\s*;?`,
63
+ "gm",
64
+ ),
65
+ (_, indent, name, mod) => `${indent}const ${name} = require("${mod}");`,
66
+ );
67
+
68
+ // Transform: import { a, b as c } from 'module'
69
+ result = result.replace(
70
+ /^([ \t]*)import\s+\{([\s\S]+?)\}\s+from\s+['"]([^'"]+)['"]\s*;?/gm,
71
+ (_, indent, imports, mod) => {
72
+ const parts = imports
73
+ .split(",")
74
+ .map((s: string) => s.trim())
75
+ .filter(Boolean);
76
+ const bindings = parts.map((p: string) => {
77
+ const [name, alias] = p.split(/\s+as\s+/).map((s: string) => s.trim());
78
+ return alias ? `${name}: ${alias}` : name;
79
+ });
80
+ return `${indent}const { ${bindings.join(", ")} } = require("${mod}");`;
81
+ },
82
+ );
83
+
84
+ // Transform: import * as name from 'module'
85
+ result = result.replace(
86
+ new RegExp(
87
+ `^([ \\t]*)import\\s+\\*\\s+as\\s+(${ID})\\s+from\\s+['\"]([^'\"]+)['\"]\\s*;?`,
88
+ "gm",
89
+ ),
90
+ (_, indent, name, mod) => `${indent}const ${name} = require("${mod}");`,
91
+ );
92
+
93
+ // Transform: import 'module' (side-effect only)
94
+ result = result.replace(
95
+ /^([ \t]*)import\s+['"]([^'"]+)['"]\s*;?/gm,
96
+ (_, indent, mod) => `${indent}require("${mod}");`,
97
+ );
98
+
99
+ // Transform: export default expr
100
+ result = result.replace(
101
+ /^([ \t]*)export\s+default\s+(?=class\s|function\s)/gm,
102
+ "$1module.exports = exports.default = ",
103
+ );
104
+ result = result.replace(
105
+ /^([ \t]*)export\s+default\s+/gm,
106
+ "$1module.exports = exports.default = ",
107
+ );
108
+
109
+ // Transform: export { a, b as c }
110
+ result = result.replace(
111
+ /^([ \t]*)export\s+\{([\s\S]+?)\}\s*(?:from\s+['"]([^'"]+)['"])?\s*;?/gm,
112
+ (_, indent, exportsList, mod) => {
113
+ const parts = exportsList
114
+ .split(",")
115
+ .map((s: string) => s.trim())
116
+ .filter(Boolean);
117
+ if (mod) {
118
+ const tmpVar = `_re_export_${(++_varCounter).toString(36)}`;
119
+ const lines = [`${indent}const ${tmpVar} = require("${mod}");`];
120
+ for (const part of parts) {
121
+ const [name, alias] = part
122
+ .split(/\s+as\s+/)
123
+ .map((s: string) => s.trim());
124
+ lines.push(
125
+ `${indent}Object.defineProperty(exports, "${
126
+ alias || name
127
+ }", { enumerable: true, get: function() { return ${tmpVar}.${name}; } });`,
128
+ );
129
+ }
130
+ return lines.join("\n");
131
+ }
132
+ return parts
133
+ .map((part: string) => {
134
+ const [name, alias] = part
135
+ .split(/\s+as\s+/)
136
+ .map((s: string) => s.trim());
137
+ return `${indent}Object.defineProperty(exports, "${
138
+ alias || name
139
+ }", { enumerable: true, get: function() { return ${name}; } });`;
140
+ })
141
+ .join("\n");
142
+ },
143
+ );
144
+
145
+ // Transform: export const/let/var name = ...
146
+ result = result.replace(/^([ \t]*)export\s+(const|let|var)\s+/gm, "$1$2 ");
147
+ const declMatch = code.matchAll(
148
+ new RegExp(`^[ \\t]*export\\s+(?:const|let|var)\\s+(${ID})`, "gm"),
149
+ );
150
+ for (const m of declMatch) {
151
+ const name = m[1];
152
+ result += `\nObject.defineProperty(exports, "${name}", { enumerable: true, get: function() { return ${name}; } });`;
153
+ }
154
+
155
+ // Transform: export function name() / export class name / export async function
156
+ result = result.replace(
157
+ new RegExp(
158
+ `^([ \\t]*)export\\s+(async\\s+)?(function|class)\\s+(${ID})`,
159
+ "gm",
160
+ ),
161
+ (_, indent, async_, type, name) =>
162
+ `${indent}${async_ || ""}${type} ${name}`,
163
+ );
164
+ const funcClassMatch = code.matchAll(
165
+ new RegExp(
166
+ `^[ \\t]*export\\s+(?:async\\s+)?(?:function|class)\\s+(${ID})`,
167
+ "gm",
168
+ ),
169
+ );
170
+ for (const m of funcClassMatch) {
171
+ const name = m[1];
172
+ result += `\nObject.defineProperty(exports, "${name}", { enumerable: true, get: function() { return ${name}; } });`;
173
+ }
174
+
175
+ // Transform: export * as name from 'module' (must come before export * from)
176
+ result = result.replace(
177
+ new RegExp(
178
+ `^([ \\t]*)export\\s+\\*\\s+as\\s+(${ID})\\s+from\\s+['\"]([^'\"]+)['\"]\\s*;?`,
179
+ "gm",
180
+ ),
181
+ (_, indent, name, mod) => `${indent}exports.${name} = require("${mod}");`,
182
+ );
183
+
184
+ // Transform: export * from 'module' — filter dangerous keys and default
185
+ result = result.replace(
186
+ /^([ \t]*)export\s+\*\s+from\s+['"]([^'"]+)['"]\s*;?/gm,
187
+ (_, indent, mod) =>
188
+ `${indent}(function(m) { for (var k in m) if (k !== "default" && k !== "__proto__" && k !== "constructor" && k !== "prototype" && Object.prototype.hasOwnProperty.call(m, k)) exports[k] = m[k]; })(require("${mod}"));`,
189
+ );
190
+
191
+ return result;
192
+ }
193
+
194
+ // ── CSS Import Handling ──────────────────────────────────────────────
195
+
196
+ export function resolveRelativePath(dir: string, relativePath: string): string {
197
+ const parts = dir.split("/").filter(Boolean);
198
+ const relParts = relativePath.split("/");
199
+
200
+ for (const part of relParts) {
201
+ if (part === "..") {
202
+ parts.pop();
203
+ } else if (part !== "." && part !== "") {
204
+ parts.push(part);
205
+ }
206
+ }
207
+
208
+ return "/" + parts.join("/");
209
+ }
210
+
211
+ export function resolveCssModulePath(
212
+ cssPath: string,
213
+ currentFile: string | undefined,
214
+ ctx: CssModuleContext,
215
+ ): string | null {
216
+ if (currentFile && (cssPath.startsWith("./") || cssPath.startsWith("../"))) {
217
+ const dir = currentFile.replace(/\/[^/]+$/, "");
218
+ const resolved = resolveRelativePath(dir, cssPath);
219
+ if (ctx.exists(resolved)) return resolved;
220
+ }
221
+
222
+ if (ctx.exists(cssPath)) return cssPath;
223
+
224
+ const withSlash = "/" + cssPath.replace(/^\.\//, "");
225
+ if (ctx.exists(withSlash)) return withSlash;
226
+
227
+ return null;
228
+ }
229
+
230
+ /**
231
+ * Generate replacement code for a CSS Module import.
232
+ * Parses the CSS file, extracts class names via regex (no css-tree dependency),
233
+ * generates scoped names, and injects the scoped CSS via a style tag.
234
+ */
235
+ export function generateCssModuleReplacement(
236
+ varName: string,
237
+ cssPath: string,
238
+ currentFile: string | undefined,
239
+ ctx: CssModuleContext,
240
+ ): string {
241
+ try {
242
+ const resolvedPath = resolveCssModulePath(cssPath, currentFile, ctx);
243
+ if (!resolvedPath) return `const ${varName} = {};`;
244
+
245
+ const cssContent = ctx.readFile(resolvedPath);
246
+ const fileHash = simpleHash(resolvedPath + cssContent).slice(0, 6);
247
+
248
+ const classMap: Record<string, string> = {};
249
+
250
+ // Extract class names via regex and build scoped names
251
+ const classRegex = /\.([a-zA-Z_][\w-]*)/g;
252
+ let m;
253
+ while ((m = classRegex.exec(cssContent)) !== null) {
254
+ if (!classMap[m[1]]) {
255
+ classMap[m[1]] = `${m[1]}_${fileHash}`;
256
+ }
257
+ }
258
+
259
+ // Replace class names in CSS to create scoped version
260
+ let scopedCss = cssContent;
261
+ for (const [original, scoped] of Object.entries(classMap)) {
262
+ scopedCss = scopedCss.replace(
263
+ new RegExp(
264
+ `\\.${original.replace(
265
+ /[-/\\^$*+?.()|[\]{}]/g,
266
+ "\\$&",
267
+ )}(?=[\\s{:,>+~])`,
268
+ "g",
269
+ ),
270
+ `.${scoped}`,
271
+ );
272
+ }
273
+
274
+ const escapedCss = scopedCss
275
+ .replace(/\\/g, "\\\\")
276
+ .replace(/`/g, "\\`")
277
+ .replace(/\$/g, "\\$");
278
+
279
+ const mapEntries = Object.entries(classMap)
280
+ .map(([k, v]) => `${JSON.stringify(k)}: ${JSON.stringify(v)}`)
281
+ .join(", ");
282
+
283
+ return `const ${varName} = {${mapEntries}};
284
+ (function() {
285
+ if (typeof document !== 'undefined') {
286
+ var id = ${JSON.stringify("cssmod-" + fileHash)};
287
+ if (!document.getElementById(id)) {
288
+ var s = document.createElement('style');
289
+ s.id = id;
290
+ s.textContent = \`${escapedCss}\`;
291
+ document.head.appendChild(s);
292
+ }
293
+ }
294
+ })();`;
295
+ } catch {
296
+ return `const ${varName} = {};`;
297
+ }
298
+ }
299
+
300
+ /**
301
+ * Strip CSS imports from code. CSS Module imports (*.module.css) are converted
302
+ * to inline objects with class name mappings. Regular CSS imports are removed.
303
+ */
304
+ export function stripCssImports(
305
+ code: string,
306
+ currentFile: string | undefined,
307
+ ctx: CssModuleContext,
308
+ ): string {
309
+ // CSS Module default imports
310
+ code = code.replace(
311
+ /import\s+(\w+)\s+from\s+['"]([^'"]+\.module\.css)['"]\s*;?/g,
312
+ (_match, varName, cssPath) => {
313
+ return generateCssModuleReplacement(varName, cssPath, currentFile, ctx);
314
+ },
315
+ );
316
+
317
+ // Destructured CSS Module imports
318
+ code = code.replace(
319
+ /import\s+\{([\s\S]+?)\}\s+from\s+['"]([^'"]+\.module\.css)['"]\s*;?/g,
320
+ (_match, names, cssPath) => {
321
+ const varName = "__cssModule_" + simpleHash(cssPath);
322
+ const replacement = generateCssModuleReplacement(
323
+ varName,
324
+ cssPath,
325
+ currentFile,
326
+ ctx,
327
+ );
328
+ const namedExports = (names as string)
329
+ .split(",")
330
+ .map((n: string) => {
331
+ const trimmed = n.trim();
332
+ const parts = trimmed.split(/\s+as\s+/);
333
+ const key = parts[0].trim();
334
+ const alias = parts[1]?.trim() || key;
335
+ return `const ${alias} = ${varName}[${JSON.stringify(key)}];`;
336
+ })
337
+ .join("\n");
338
+ return `${replacement}\n${namedExports}`;
339
+ },
340
+ );
341
+
342
+ // Strip remaining plain CSS imports
343
+ return code.replace(/import\s+['"][^'"]+\.css['"]\s*;?/g, "");
344
+ }
345
+
346
+ // ── NPM Import Redirection ──────────────────────────────────────────
347
+
348
+ const EXPLICIT_MAPPINGS: Record<string, string> = {
349
+ react: `${REACT_CDN}?dev`,
350
+ "react/jsx-runtime": `${REACT_CDN}&dev/jsx-runtime`,
351
+ "react/jsx-dev-runtime": `${REACT_CDN}&dev/jsx-dev-runtime`,
352
+ "react-dom": `${REACT_DOM_CDN}?dev`,
353
+ "react-dom/client": `${REACT_DOM_CDN}/client?dev`,
354
+ };
355
+
356
+ const LOCAL_PACKAGES = new Set([
357
+ "next/link",
358
+ "next/router",
359
+ "next/head",
360
+ "next/navigation",
361
+ "next/dynamic",
362
+ "next/image",
363
+ "next/script",
364
+ "next/font/google",
365
+ "next/font/local",
366
+ ]);
367
+
368
+ function extractMajorVersion(range: string): string | null {
369
+ const match = range.match(/(\d+)\.\d+/);
370
+ return match ? match[1] : null;
371
+ }
372
+
373
+ function resolveNpmPackage(
374
+ packageName: string,
375
+ extraLocalPackages?: Set<string>,
376
+ dependencies?: Record<string, string>,
377
+ esmShDeps?: string,
378
+ installedPackages?: Set<string>,
379
+ ): string | null {
380
+ if (
381
+ packageName.startsWith(".") ||
382
+ packageName.startsWith("/") ||
383
+ packageName.startsWith("http://") ||
384
+ packageName.startsWith("https://") ||
385
+ packageName.startsWith("/__virtual__")
386
+ ) {
387
+ return null;
388
+ }
389
+
390
+ if (EXPLICIT_MAPPINGS[packageName]) return EXPLICIT_MAPPINGS[packageName];
391
+ if (LOCAL_PACKAGES.has(packageName)) return null;
392
+ if (extraLocalPackages?.has(packageName)) return null;
393
+
394
+ const basePkg = packageName.includes("/")
395
+ ? packageName.split("/")[0]
396
+ : packageName;
397
+ const isScoped = basePkg.startsWith("@");
398
+ const scopedBasePkg =
399
+ isScoped && packageName.includes("/")
400
+ ? packageName.split("/").slice(0, 2).join("/")
401
+ : basePkg;
402
+
403
+ if (LOCAL_PACKAGES.has(scopedBasePkg)) return null;
404
+ if (extraLocalPackages?.has(scopedBasePkg)) return null;
405
+
406
+ if (installedPackages?.has(scopedBasePkg)) return `/_npm/${packageName}`;
407
+
408
+ let esmPkg = packageName;
409
+ if (dependencies) {
410
+ const depVersion = dependencies[scopedBasePkg];
411
+ if (depVersion) {
412
+ const major = extractMajorVersion(depVersion);
413
+ if (major) {
414
+ const subpath = packageName.slice(scopedBasePkg.length);
415
+ esmPkg = `${scopedBasePkg}@${major}${subpath}`;
416
+ }
417
+ }
418
+ }
419
+
420
+ const depsParam = esmShDeps ? `&deps=${esmShDeps}` : "";
421
+ return `https://esm.sh/${esmPkg}?external=react${depsParam}`;
422
+ }
423
+
424
+ /**
425
+ * Redirect bare npm package imports to esm.sh CDN URLs.
426
+ * Uses acorn AST for precision, falls back to regex.
427
+ */
428
+ export function redirectNpmImports(
429
+ code: string,
430
+ additionalLocalPackages?: string[],
431
+ dependencies?: Record<string, string>,
432
+ esmShDeps?: string,
433
+ installedPackages?: Set<string>,
434
+ ): string {
435
+ const extraSet = additionalLocalPackages?.length
436
+ ? new Set(additionalLocalPackages)
437
+ : undefined;
438
+ try {
439
+ return redirectNpmImportsAst(
440
+ code,
441
+ extraSet,
442
+ dependencies,
443
+ esmShDeps,
444
+ installedPackages,
445
+ );
446
+ } catch {
447
+ return redirectNpmImportsRegex(
448
+ code,
449
+ extraSet,
450
+ dependencies,
451
+ esmShDeps,
452
+ installedPackages,
453
+ );
454
+ }
455
+ }
456
+
457
+ function redirectNpmImportsAst(
458
+ code: string,
459
+ extraLocalPackages?: Set<string>,
460
+ dependencies?: Record<string, string>,
461
+ esmShDeps?: string,
462
+ installedPackages?: Set<string>,
463
+ ): string {
464
+ const ast = jsxParser.parse(code, {
465
+ ecmaVersion: "latest",
466
+ sourceType: "module",
467
+ });
468
+ const replacements: Array<[number, number, string]> = [];
469
+
470
+ function processSource(sourceNode: any) {
471
+ if (!sourceNode || sourceNode.type !== "Literal") return;
472
+ const resolved = resolveNpmPackage(
473
+ sourceNode.value,
474
+ extraLocalPackages,
475
+ dependencies,
476
+ esmShDeps,
477
+ installedPackages,
478
+ );
479
+ if (resolved) {
480
+ replacements.push([
481
+ sourceNode.start,
482
+ sourceNode.end,
483
+ JSON.stringify(resolved),
484
+ ]);
485
+ }
486
+ }
487
+
488
+ for (const node of (ast as any).body) {
489
+ if (node.type === "ImportDeclaration") processSource(node.source);
490
+ else if (node.type === "ExportNamedDeclaration" && node.source)
491
+ processSource(node.source);
492
+ else if (node.type === "ExportAllDeclaration") processSource(node.source);
493
+ }
494
+
495
+ if (replacements.length === 0) return code;
496
+
497
+ let result = code;
498
+ replacements.sort((a, b) => b[0] - a[0]);
499
+ for (const [start, end, replacement] of replacements) {
500
+ result = result.slice(0, start) + replacement + result.slice(end);
501
+ }
502
+
503
+ return result;
504
+ }
505
+
506
+ function redirectNpmImportsRegex(
507
+ code: string,
508
+ extraLocalPackages?: Set<string>,
509
+ dependencies?: Record<string, string>,
510
+ esmShDeps?: string,
511
+ installedPackages?: Set<string>,
512
+ ): string {
513
+ const importPattern = /(from\s*['"])([^'"./][^'"]*?)(['"])/g;
514
+ return code.replace(importPattern, (match, prefix, packageName, suffix) => {
515
+ const resolved = resolveNpmPackage(
516
+ packageName,
517
+ extraLocalPackages,
518
+ dependencies,
519
+ esmShDeps,
520
+ installedPackages,
521
+ );
522
+ if (!resolved) return match;
523
+ return `${prefix}${resolved}${suffix}`;
524
+ });
525
+ }
526
+
527
+ // ── React Refresh Registration ───────────────────────────────────────
528
+
529
+ /**
530
+ * Add React Refresh registration to transformed code.
531
+ * Enables true HMR (state-preserving) for React components.
532
+ */
533
+ export function addReactRefresh(code: string, filename: string): string {
534
+ const components = detectReactComponents(code);
535
+
536
+ if (components.length === 0) {
537
+ return `// HMR Setup
538
+ import.meta.hot = window.__vite_hot_context__("${filename}");
539
+
540
+ ${code}
541
+
542
+ // HMR Accept
543
+ if (import.meta.hot) {
544
+ import.meta.hot.accept();
545
+ }
546
+ `;
547
+ }
548
+
549
+ const registrations = components
550
+ .map(name => ` $RefreshReg$(${name}, "${filename} ${name}");`)
551
+ .join("\n");
552
+
553
+ return `// HMR Setup
554
+ import.meta.hot = window.__vite_hot_context__("${filename}");
555
+
556
+ ${code}
557
+
558
+ // React Refresh Registration
559
+ if (import.meta.hot) {
560
+ ${registrations}
561
+ import.meta.hot.accept(() => {
562
+ if (window.$RefreshRuntime$) {
563
+ window.$RefreshRuntime$.performReactRefresh();
564
+ }
565
+ });
566
+ }
567
+ `;
568
+ }
569
+
570
+ function isUppercaseStart(name: string): boolean {
571
+ return name.length > 0 && name[0] >= "A" && name[0] <= "Z";
572
+ }
573
+
574
+ function detectReactComponents(code: string): string[] {
575
+ try {
576
+ return detectReactComponentsAst(code);
577
+ } catch {
578
+ return detectReactComponentsRegex(code);
579
+ }
580
+ }
581
+
582
+ function detectReactComponentsAst(code: string): string[] {
583
+ const ast = jsxParser.parse(code, {
584
+ ecmaVersion: "latest",
585
+ sourceType: "module",
586
+ });
587
+ const components: string[] = [];
588
+
589
+ for (const node of (ast as any).body) {
590
+ if (
591
+ node.type === "FunctionDeclaration" &&
592
+ node.id &&
593
+ isUppercaseStart(node.id.name)
594
+ ) {
595
+ if (!components.includes(node.id.name)) components.push(node.id.name);
596
+ }
597
+
598
+ if (
599
+ node.type === "ExportDefaultDeclaration" &&
600
+ node.declaration?.type === "FunctionDeclaration" &&
601
+ node.declaration.id &&
602
+ isUppercaseStart(node.declaration.id.name)
603
+ ) {
604
+ if (!components.includes(node.declaration.id.name))
605
+ components.push(node.declaration.id.name);
606
+ }
607
+
608
+ if (
609
+ node.type === "ExportNamedDeclaration" &&
610
+ node.declaration?.type === "FunctionDeclaration" &&
611
+ node.declaration.id &&
612
+ isUppercaseStart(node.declaration.id.name)
613
+ ) {
614
+ if (!components.includes(node.declaration.id.name))
615
+ components.push(node.declaration.id.name);
616
+ }
617
+
618
+ const varDecl =
619
+ node.type === "VariableDeclaration"
620
+ ? node
621
+ : node.type === "ExportNamedDeclaration" &&
622
+ node.declaration?.type === "VariableDeclaration"
623
+ ? node.declaration
624
+ : null;
625
+
626
+ if (varDecl) {
627
+ for (const declarator of varDecl.declarations) {
628
+ if (
629
+ declarator.id?.name &&
630
+ isUppercaseStart(declarator.id.name) &&
631
+ declarator.init
632
+ ) {
633
+ const initType = declarator.init.type;
634
+ if (
635
+ initType === "ArrowFunctionExpression" ||
636
+ initType === "FunctionExpression" ||
637
+ initType === "CallExpression"
638
+ ) {
639
+ if (!components.includes(declarator.id.name))
640
+ components.push(declarator.id.name);
641
+ }
642
+ }
643
+ }
644
+ }
645
+ }
646
+
647
+ return components;
648
+ }
649
+
650
+ function detectReactComponentsRegex(code: string): string[] {
651
+ const components: string[] = [];
652
+
653
+ const funcDeclRegex =
654
+ /(?:^|\n)(?:export\s+)?(?:async\s+)?function\s+([A-Z][a-zA-Z0-9]*)\s*\(/g;
655
+ let match;
656
+ while ((match = funcDeclRegex.exec(code)) !== null) {
657
+ if (!components.includes(match[1])) components.push(match[1]);
658
+ }
659
+
660
+ const arrowRegex =
661
+ /(?:^|\n)(?:export\s+)?(?:const|let|var)\s+([A-Z][a-zA-Z0-9]*)\s*=/g;
662
+ while ((match = arrowRegex.exec(code)) !== null) {
663
+ if (!components.includes(match[1])) components.push(match[1]);
664
+ }
665
+
666
+ return components;
667
+ }