@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,1084 @@
1
+ /**
2
+ * Next.js shim constants
3
+ * Static HTML/JS strings used by NextDevServer for browser-side Next.js emulation.
4
+ * These are injected into generated HTML pages as inline scripts or served as virtual modules.
5
+ */
6
+
7
+ import { REACT_REFRESH_CDN, TAILWIND_CDN_URL } from "../config/cdn";
8
+
9
+ /**
10
+ * Tailwind CSS CDN script for runtime JIT compilation
11
+ */
12
+ export const TAILWIND_CDN_SCRIPT = `<script src="${TAILWIND_CDN_URL}"></script>`;
13
+
14
+ /**
15
+ * CORS Proxy script - provides proxyFetch function in the iframe
16
+ * Reads proxy URL from localStorage (set by parent window)
17
+ */
18
+ export const CORS_PROXY_SCRIPT = `
19
+ <script>
20
+ // CORS Proxy support for external API calls
21
+ window.__getCorsProxy = function() {
22
+ return localStorage.getItem('__corsProxyUrl') || null;
23
+ };
24
+
25
+ window.__setCorsProxy = function(url) {
26
+ if (url) {
27
+ localStorage.setItem('__corsProxyUrl', url);
28
+ } else {
29
+ localStorage.removeItem('__corsProxyUrl');
30
+ }
31
+ };
32
+
33
+ window.__proxyFetch = async function(url, options) {
34
+ const proxyUrl = window.__getCorsProxy();
35
+ if (proxyUrl) {
36
+ const proxiedUrl = proxyUrl + encodeURIComponent(url);
37
+ return fetch(proxiedUrl, options);
38
+ }
39
+ return fetch(url, options);
40
+ };
41
+ </script>
42
+ `;
43
+
44
+ /**
45
+ * React Refresh preamble - MUST run before React is loaded
46
+ */
47
+ export const REACT_REFRESH_PREAMBLE = `
48
+ <script type="module">
49
+ // Block until React Refresh is loaded and initialized
50
+ const RefreshRuntime = await import('${REACT_REFRESH_CDN}').then(m => m.default || m);
51
+
52
+ RefreshRuntime.injectIntoGlobalHook(window);
53
+ window.$RefreshRuntime$ = RefreshRuntime;
54
+ window.$RefreshRegCount$ = 0;
55
+
56
+ window.$RefreshReg$ = (type, id) => {
57
+ window.$RefreshRegCount$++;
58
+ RefreshRuntime.register(type, id);
59
+ };
60
+
61
+ window.$RefreshSig$ = () => (type) => type;
62
+
63
+ console.log('[HMR] React Refresh initialized');
64
+ </script>
65
+ `;
66
+
67
+ /**
68
+ * HMR client script for Next.js
69
+ */
70
+ export const HMR_CLIENT_SCRIPT = `
71
+ <script type="module">
72
+ (function() {
73
+ const hotModules = new Map();
74
+ const pendingUpdates = new Map();
75
+
76
+ window.__vite_hot_context__ = function createHotContext(ownerPath) {
77
+ if (hotModules.has(ownerPath)) {
78
+ return hotModules.get(ownerPath);
79
+ }
80
+
81
+ const hot = {
82
+ data: {},
83
+ accept(callback) {
84
+ hot._acceptCallback = callback;
85
+ },
86
+ dispose(callback) {
87
+ hot._disposeCallback = callback;
88
+ },
89
+ invalidate() {
90
+ location.reload();
91
+ },
92
+ prune(callback) {
93
+ hot._pruneCallback = callback;
94
+ },
95
+ on(event, cb) {},
96
+ off(event, cb) {},
97
+ send(event, data) {},
98
+ _acceptCallback: null,
99
+ _disposeCallback: null,
100
+ _pruneCallback: null,
101
+ };
102
+
103
+ hotModules.set(ownerPath, hot);
104
+ return hot;
105
+ };
106
+
107
+ // Listen for HMR updates via postMessage (works with sandboxed iframes)
108
+ window.addEventListener('message', async (event) => {
109
+ // Filter for HMR messages only
110
+ if (!event.data || event.data.channel !== 'next-hmr') return;
111
+ const { type, path, timestamp } = event.data;
112
+
113
+ if (type === 'update') {
114
+ console.log('[HMR] Update:', path);
115
+
116
+ if (path.endsWith('.css')) {
117
+ const links = document.querySelectorAll('link[rel="stylesheet"]');
118
+ links.forEach(link => {
119
+ const href = link.getAttribute('href');
120
+ if (href && href.includes(path.replace(/^\\//, ''))) {
121
+ link.href = href.split('?')[0] + '?t=' + timestamp;
122
+ }
123
+ });
124
+
125
+ const styles = document.querySelectorAll('style[data-next-dev-id]');
126
+ styles.forEach(style => {
127
+ const id = style.getAttribute('data-next-dev-id');
128
+ if (id && id.includes(path.replace(/^\\//, ''))) {
129
+ import(path + '?t=' + timestamp).catch(() => {});
130
+ }
131
+ });
132
+ } else if (path.match(/\\.(jsx?|tsx?)$/)) {
133
+ await handleJSUpdate(path, timestamp);
134
+ }
135
+ } else if (type === 'full-reload') {
136
+ console.log('[HMR] Full reload');
137
+ location.reload();
138
+ }
139
+ });
140
+
141
+ async function handleJSUpdate(path, timestamp) {
142
+ const normalizedPath = path.startsWith('/') ? path : '/' + path;
143
+ const hot = hotModules.get(normalizedPath);
144
+
145
+ try {
146
+ if (hot && hot._disposeCallback) {
147
+ hot._disposeCallback(hot.data);
148
+ }
149
+
150
+ if (window.$RefreshRuntime$) {
151
+ pendingUpdates.set(normalizedPath, timestamp);
152
+
153
+ if (pendingUpdates.size === 1) {
154
+ setTimeout(async () => {
155
+ try {
156
+ for (const [modulePath, ts] of pendingUpdates) {
157
+ const moduleUrl = '.' + modulePath + '?t=' + ts;
158
+ await import(moduleUrl);
159
+ }
160
+
161
+ window.$RefreshRuntime$.performReactRefresh();
162
+ console.log('[HMR] Updated', pendingUpdates.size, 'module(s)');
163
+
164
+ pendingUpdates.clear();
165
+ } catch (error) {
166
+ console.error('[HMR] Failed to apply update:', error);
167
+ pendingUpdates.clear();
168
+ location.reload();
169
+ }
170
+ }, 30);
171
+ }
172
+ } else {
173
+ console.log('[HMR] React Refresh not available, reloading page');
174
+ location.reload();
175
+ }
176
+ } catch (error) {
177
+ console.error('[HMR] Update failed:', error);
178
+ location.reload();
179
+ }
180
+ }
181
+
182
+ console.log('[HMR] Next.js client ready');
183
+ })();
184
+ </script>
185
+ `;
186
+
187
+ /**
188
+ * Next.js Link shim code
189
+ */
190
+ export const NEXT_LINK_SHIM = `
191
+ import React from 'react';
192
+
193
+ const getVirtualBasePath = () => {
194
+ const match = window.location.pathname.match(/^\\/__virtual__\\/\\d+(?:\\/|$)/);
195
+ if (!match) return '';
196
+ return match[0].endsWith('/') ? match[0] : match[0] + '/';
197
+ };
198
+
199
+ const getBasePath = () => window.__NEXT_BASE_PATH__ || '';
200
+
201
+ const applyVirtualBase = (url) => {
202
+ if (typeof url !== 'string') return url;
203
+ if (!url || url.startsWith('#') || url.startsWith('?')) return url;
204
+ if (/^(https?:)?\\/\\//.test(url)) return url;
205
+
206
+ // Apply basePath first
207
+ const bp = getBasePath();
208
+ if (bp && url.startsWith('/') && !url.startsWith(bp + '/') && url !== bp) {
209
+ url = bp + url;
210
+ }
211
+
212
+ const base = getVirtualBasePath();
213
+ if (!base) return url;
214
+ if (url.startsWith(base)) return url;
215
+ if (url.startsWith('/')) return base + url.slice(1);
216
+ return base + url;
217
+ };
218
+
219
+ export default function Link({ href, children, ...props }) {
220
+ const handleClick = (e) => {
221
+ console.log('[Link] Click handler called, href:', href);
222
+
223
+ if (props.onClick) {
224
+ props.onClick(e);
225
+ }
226
+
227
+ // Allow cmd/ctrl click to open in new tab
228
+ if (e.metaKey || e.ctrlKey) {
229
+ console.log('[Link] Meta/Ctrl key pressed, allowing default behavior');
230
+ return;
231
+ }
232
+
233
+ if (typeof href !== 'string' || !href || href.startsWith('#') || href.startsWith('?')) {
234
+ console.log('[Link] Skipping navigation for href:', href);
235
+ return;
236
+ }
237
+
238
+ if (/^(https?:)?\\/\\//.test(href)) {
239
+ console.log('[Link] External URL, allowing default behavior:', href);
240
+ return;
241
+ }
242
+
243
+ e.preventDefault();
244
+ const resolvedHref = applyVirtualBase(href);
245
+ console.log('[Link] Navigating to:', resolvedHref);
246
+ window.history.pushState({}, '', resolvedHref);
247
+ window.dispatchEvent(new PopStateEvent('popstate'));
248
+ };
249
+
250
+ return React.createElement('a', { href, onClick: handleClick, ...props }, children);
251
+ }
252
+
253
+ export { Link };
254
+ `;
255
+
256
+ /**
257
+ * Next.js Router shim code
258
+ */
259
+ export const NEXT_ROUTER_SHIM = `
260
+ import React, { useState, useEffect, createContext, useContext } from 'react';
261
+
262
+ const RouterContext = createContext(null);
263
+
264
+ const getVirtualBasePath = () => {
265
+ const match = window.location.pathname.match(/^\\/__virtual__\\/\\d+(?:\\/|$)/);
266
+ if (!match) return '';
267
+ return match[0].endsWith('/') ? match[0] : match[0] + '/';
268
+ };
269
+
270
+ const applyVirtualBase = (url) => {
271
+ if (typeof url !== 'string') return url;
272
+ if (!url || url.startsWith('#') || url.startsWith('?')) return url;
273
+ if (/^(https?:)?\\/\\//.test(url)) return url;
274
+
275
+ const base = getVirtualBasePath();
276
+ if (!base) return url;
277
+ if (url.startsWith(base)) return url;
278
+ if (url.startsWith('/')) return base + url.slice(1);
279
+ return base + url;
280
+ };
281
+
282
+ const stripVirtualBase = (pathname) => {
283
+ const match = pathname.match(/^\\/__virtual__\\/\\d+(?:\\/|$)/);
284
+ if (!match) return pathname;
285
+ return '/' + pathname.slice(match[0].length);
286
+ };
287
+
288
+ export function useRouter() {
289
+ const [pathname, setPathname] = useState(
290
+ typeof window !== 'undefined' ? stripVirtualBase(window.location.pathname) : '/'
291
+ );
292
+ const [query, setQuery] = useState({});
293
+
294
+ useEffect(() => {
295
+ const updateRoute = () => {
296
+ setPathname(stripVirtualBase(window.location.pathname));
297
+ setQuery(Object.fromEntries(new URLSearchParams(window.location.search)));
298
+ };
299
+
300
+ window.addEventListener('popstate', updateRoute);
301
+ updateRoute();
302
+
303
+ return () => window.removeEventListener('popstate', updateRoute);
304
+ }, []);
305
+
306
+ return {
307
+ pathname,
308
+ query,
309
+ asPath: pathname + window.location.search,
310
+ push: (url, as, options) => {
311
+ if (typeof url === 'string' && /^(https?:)?\\/\\//.test(url)) {
312
+ window.location.href = url;
313
+ return Promise.resolve(true);
314
+ }
315
+ const resolvedUrl = applyVirtualBase(url);
316
+ window.history.pushState({}, '', resolvedUrl);
317
+ window.dispatchEvent(new PopStateEvent('popstate'));
318
+ return Promise.resolve(true);
319
+ },
320
+ replace: (url, as, options) => {
321
+ if (typeof url === 'string' && /^(https?:)?\\/\\//.test(url)) {
322
+ window.location.href = url;
323
+ return Promise.resolve(true);
324
+ }
325
+ const resolvedUrl = applyVirtualBase(url);
326
+ window.history.replaceState({}, '', resolvedUrl);
327
+ window.dispatchEvent(new PopStateEvent('popstate'));
328
+ return Promise.resolve(true);
329
+ },
330
+ prefetch: (url) => {
331
+ // Preload the target page module in the background
332
+ if (typeof url === 'string' && url.startsWith('/')) {
333
+ try {
334
+ const resolvedUrl = applyVirtualBase(url);
335
+ const link = document.createElement('link');
336
+ link.rel = 'prefetch';
337
+ link.href = resolvedUrl;
338
+ link.as = 'document';
339
+ document.head.appendChild(link);
340
+ } catch (e) {
341
+ // Silently fail - prefetch is best-effort
342
+ }
343
+ }
344
+ return Promise.resolve();
345
+ },
346
+ back: () => window.history.back(),
347
+ forward: () => window.history.forward(),
348
+ reload: () => window.location.reload(),
349
+ events: {
350
+ on: () => {},
351
+ off: () => {},
352
+ emit: () => {},
353
+ },
354
+ isFallback: false,
355
+ isReady: true,
356
+ isPreview: false,
357
+ };
358
+ }
359
+
360
+ export const Router = {
361
+ events: {
362
+ on: () => {},
363
+ off: () => {},
364
+ emit: () => {},
365
+ },
366
+ push: (url) => {
367
+ if (typeof url === 'string' && /^(https?:)?\\/\\//.test(url)) {
368
+ window.location.href = url;
369
+ return Promise.resolve(true);
370
+ }
371
+ const resolvedUrl = applyVirtualBase(url);
372
+ window.history.pushState({}, '', resolvedUrl);
373
+ window.dispatchEvent(new PopStateEvent('popstate'));
374
+ return Promise.resolve(true);
375
+ },
376
+ replace: (url) => {
377
+ if (typeof url === 'string' && /^(https?:)?\\/\\//.test(url)) {
378
+ window.location.href = url;
379
+ return Promise.resolve(true);
380
+ }
381
+ const resolvedUrl = applyVirtualBase(url);
382
+ window.history.replaceState({}, '', resolvedUrl);
383
+ window.dispatchEvent(new PopStateEvent('popstate'));
384
+ return Promise.resolve(true);
385
+ },
386
+ };
387
+
388
+ export default { useRouter, Router };
389
+ `;
390
+
391
+ /**
392
+ * Next.js Navigation shim code (App Router)
393
+ *
394
+ * This shim provides App Router-specific navigation hooks from 'next/navigation'.
395
+ * These are DIFFERENT from the Pages Router hooks in 'next/router':
396
+ *
397
+ * Pages Router (next/router):
398
+ * - useRouter() returns { pathname, query, push, replace, events, ... }
399
+ * - Has router.events for route change subscriptions
400
+ * - query object contains URL params
401
+ *
402
+ * App Router (next/navigation):
403
+ * - useRouter() returns { push, replace, back, forward, refresh, prefetch }
404
+ * - usePathname() for current path
405
+ * - useSearchParams() for URL search params
406
+ * - useParams() for dynamic route segments
407
+ * - No events - use useEffect with pathname/searchParams instead
408
+ *
409
+ * @see https://nextjs.org/docs/app/api-reference/functions/use-router
410
+ */
411
+ export const NEXT_NAVIGATION_SHIM = `
412
+ import React, { useState, useEffect, useCallback, useMemo } from 'react';
413
+
414
+ const getVirtualBasePath = () => {
415
+ const match = window.location.pathname.match(/^\\/__virtual__\\/\\d+(?:\\/|$)/);
416
+ if (!match) return '';
417
+ return match[0].endsWith('/') ? match[0] : match[0] + '/';
418
+ };
419
+
420
+ const applyVirtualBase = (url) => {
421
+ if (typeof url !== 'string') return url;
422
+ if (!url || url.startsWith('#') || url.startsWith('?')) return url;
423
+ if (/^(https?:)?\\/\\//.test(url)) return url;
424
+
425
+ const base = getVirtualBasePath();
426
+ if (!base) return url;
427
+ if (url.startsWith(base)) return url;
428
+ if (url.startsWith('/')) return base + url.slice(1);
429
+ return base + url;
430
+ };
431
+
432
+ const stripVirtualBase = (pathname) => {
433
+ const match = pathname.match(/^\\/__virtual__\\/\\d+(?:\\/|$)/);
434
+ if (!match) return pathname;
435
+ return '/' + pathname.slice(match[0].length);
436
+ };
437
+
438
+ /**
439
+ * App Router's useRouter hook
440
+ * Returns navigation methods only (no pathname, no query)
441
+ * Use usePathname() and useSearchParams() for URL info
442
+ */
443
+ export function useRouter() {
444
+ const push = useCallback((url, options) => {
445
+ if (typeof url === 'string' && /^(https?:)?\\/\\//.test(url)) {
446
+ window.location.href = url;
447
+ return;
448
+ }
449
+ const resolvedUrl = applyVirtualBase(url);
450
+ window.history.pushState({}, '', resolvedUrl);
451
+ window.dispatchEvent(new PopStateEvent('popstate'));
452
+ }, []);
453
+
454
+ const replace = useCallback((url, options) => {
455
+ if (typeof url === 'string' && /^(https?:)?\\/\\//.test(url)) {
456
+ window.location.href = url;
457
+ return;
458
+ }
459
+ const resolvedUrl = applyVirtualBase(url);
460
+ window.history.replaceState({}, '', resolvedUrl);
461
+ window.dispatchEvent(new PopStateEvent('popstate'));
462
+ }, []);
463
+
464
+ const back = useCallback(() => window.history.back(), []);
465
+ const forward = useCallback(() => window.history.forward(), []);
466
+ const refresh = useCallback(() => window.location.reload(), []);
467
+ const prefetch = useCallback((url) => {
468
+ // Preload the target page module in the background
469
+ if (typeof url === 'string' && url.startsWith('/')) {
470
+ try {
471
+ const resolvedUrl = applyVirtualBase(url);
472
+ const link = document.createElement('link');
473
+ link.rel = 'prefetch';
474
+ link.href = resolvedUrl;
475
+ link.as = 'document';
476
+ document.head.appendChild(link);
477
+ } catch (e) {
478
+ // Silently fail - prefetch is best-effort
479
+ }
480
+ }
481
+ return Promise.resolve();
482
+ }, []);
483
+
484
+ return useMemo(() => ({
485
+ push,
486
+ replace,
487
+ back,
488
+ forward,
489
+ refresh,
490
+ prefetch,
491
+ }), [push, replace, back, forward, refresh, prefetch]);
492
+ }
493
+
494
+ /**
495
+ * usePathname - Returns the current URL pathname
496
+ * Reactively updates when navigation occurs
497
+ * @example const pathname = usePathname(); // '/dashboard/settings'
498
+ */
499
+ export function usePathname() {
500
+ const [pathname, setPathname] = useState(
501
+ typeof window !== 'undefined' ? stripVirtualBase(window.location.pathname) : '/'
502
+ );
503
+
504
+ useEffect(() => {
505
+ const handler = () => setPathname(stripVirtualBase(window.location.pathname));
506
+ window.addEventListener('popstate', handler);
507
+ return () => window.removeEventListener('popstate', handler);
508
+ }, []);
509
+
510
+ return pathname;
511
+ }
512
+
513
+ /**
514
+ * useSearchParams - Returns the current URL search parameters
515
+ * @example const searchParams = useSearchParams();
516
+ * const query = searchParams.get('q'); // '?q=hello' -> 'hello'
517
+ */
518
+ export function useSearchParams() {
519
+ const [searchParams, setSearchParams] = useState(() => {
520
+ if (typeof window === 'undefined') return new URLSearchParams();
521
+ return new URLSearchParams(window.location.search);
522
+ });
523
+
524
+ useEffect(() => {
525
+ const handler = () => {
526
+ setSearchParams(new URLSearchParams(window.location.search));
527
+ };
528
+ window.addEventListener('popstate', handler);
529
+ return () => window.removeEventListener('popstate', handler);
530
+ }, []);
531
+
532
+ return searchParams;
533
+ }
534
+
535
+ /**
536
+ * useParams - Returns dynamic route parameters
537
+ * For route /users/[id]/page.jsx with URL /users/123:
538
+ * @example const { id } = useParams(); // { id: '123' }
539
+ *
540
+ * Fetches params from the server's route-info endpoint for dynamic routes.
541
+ */
542
+ export function useParams() {
543
+ const [params, setParams] = useState(() => {
544
+ // Check if initial params were embedded by the server
545
+ if (typeof window !== 'undefined' && window.__NEXT_ROUTE_PARAMS__) {
546
+ return window.__NEXT_ROUTE_PARAMS__;
547
+ }
548
+ return {};
549
+ });
550
+
551
+ useEffect(() => {
552
+ let cancelled = false;
553
+
554
+ const fetchParams = async () => {
555
+ const pathname = stripVirtualBase(window.location.pathname);
556
+ const base = getVirtualBasePath();
557
+ const baseUrl = base ? base.replace(/\\/$/, '') : '';
558
+
559
+ try {
560
+ const response = await fetch(baseUrl + '/_next/route-info?pathname=' + encodeURIComponent(pathname));
561
+ const info = await response.json();
562
+ if (!cancelled && info.params) {
563
+ setParams(info.params);
564
+ }
565
+ } catch (e) {
566
+ // Silently fail - static routes won't have params
567
+ }
568
+ };
569
+
570
+ fetchParams();
571
+
572
+ const handler = () => fetchParams();
573
+ window.addEventListener('popstate', handler);
574
+ return () => {
575
+ cancelled = true;
576
+ window.removeEventListener('popstate', handler);
577
+ };
578
+ }, []);
579
+
580
+ return params;
581
+ }
582
+
583
+ /**
584
+ * useSelectedLayoutSegment - Returns the active child segment one level below
585
+ * Useful for styling active nav items in layouts
586
+ * @example For /dashboard/settings, returns 'settings' in dashboard layout
587
+ */
588
+ export function useSelectedLayoutSegment() {
589
+ const pathname = usePathname();
590
+ const segments = pathname.split('/').filter(Boolean);
591
+ return segments[0] || null;
592
+ }
593
+
594
+ /**
595
+ * useSelectedLayoutSegments - Returns all active child segments
596
+ * @example For /dashboard/settings/profile, returns ['dashboard', 'settings', 'profile']
597
+ */
598
+ export function useSelectedLayoutSegments() {
599
+ const pathname = usePathname();
600
+ return pathname.split('/').filter(Boolean);
601
+ }
602
+
603
+ /**
604
+ * redirect - Programmatic redirect (typically used in Server Components)
605
+ * In this browser implementation, performs immediate navigation
606
+ */
607
+ export function redirect(url) {
608
+ if (typeof url === 'string' && /^(https?:)?\\/\\//.test(url)) {
609
+ window.location.href = url;
610
+ return;
611
+ }
612
+ window.location.href = applyVirtualBase(url);
613
+ }
614
+
615
+ /**
616
+ * notFound - Trigger the not-found UI
617
+ * In this browser implementation, throws an error
618
+ */
619
+ export function notFound() {
620
+ throw new Error('NEXT_NOT_FOUND');
621
+ }
622
+
623
+ // Re-export Link for convenience (can import from next/navigation or next/link)
624
+ export { default as Link } from 'next/link';
625
+ `;
626
+
627
+ /**
628
+ * Next.js Head shim code
629
+ */
630
+ export const NEXT_HEAD_SHIM = `
631
+ import React, { useEffect } from 'react';
632
+
633
+ export default function Head({ children }) {
634
+ useEffect(() => {
635
+ // Process children and update document.head
636
+ React.Children.forEach(children, (child) => {
637
+ if (!React.isValidElement(child)) return;
638
+
639
+ const { type, props } = child;
640
+
641
+ if (type === 'title' && props.children) {
642
+ document.title = Array.isArray(props.children)
643
+ ? props.children.join('')
644
+ : props.children;
645
+ } else if (type === 'meta') {
646
+ const existingMeta = props.name
647
+ ? document.querySelector(\`meta[name="\${props.name}"]\`)
648
+ : props.property
649
+ ? document.querySelector(\`meta[property="\${props.property}"]\`)
650
+ : null;
651
+
652
+ if (existingMeta) {
653
+ Object.keys(props).forEach(key => {
654
+ existingMeta.setAttribute(key, props[key]);
655
+ });
656
+ } else {
657
+ const meta = document.createElement('meta');
658
+ Object.keys(props).forEach(key => {
659
+ meta.setAttribute(key, props[key]);
660
+ });
661
+ document.head.appendChild(meta);
662
+ }
663
+ } else if (type === 'link') {
664
+ const link = document.createElement('link');
665
+ Object.keys(props).forEach(key => {
666
+ link.setAttribute(key, props[key]);
667
+ });
668
+ document.head.appendChild(link);
669
+ }
670
+ });
671
+ }, [children]);
672
+
673
+ return null;
674
+ }
675
+ `;
676
+
677
+ /**
678
+ * Next.js Image shim code
679
+ * Provides a simple img-based implementation of next/image
680
+ */
681
+ export const NEXT_IMAGE_SHIM = `
682
+ import React from 'react';
683
+
684
+ function Image({
685
+ src,
686
+ alt = '',
687
+ width,
688
+ height,
689
+ fill,
690
+ loader,
691
+ quality = 75,
692
+ priority,
693
+ loading,
694
+ placeholder,
695
+ blurDataURL,
696
+ unoptimized,
697
+ onLoad,
698
+ onError,
699
+ style,
700
+ className,
701
+ sizes,
702
+ ...rest
703
+ }) {
704
+ // Handle src - could be string or StaticImageData object
705
+ const imageSrc = typeof src === 'object' ? src.src : src;
706
+
707
+ // Build style object
708
+ const imgStyle = { ...style };
709
+ if (fill) {
710
+ imgStyle.position = 'absolute';
711
+ imgStyle.width = '100%';
712
+ imgStyle.height = '100%';
713
+ imgStyle.objectFit = imgStyle.objectFit || 'cover';
714
+ imgStyle.inset = '0';
715
+ }
716
+
717
+ return React.createElement('img', {
718
+ src: imageSrc,
719
+ alt,
720
+ width: fill ? undefined : width,
721
+ height: fill ? undefined : height,
722
+ loading: priority ? 'eager' : (loading || 'lazy'),
723
+ decoding: 'async',
724
+ style: imgStyle,
725
+ className,
726
+ onLoad,
727
+ onError,
728
+ ...rest
729
+ });
730
+ }
731
+
732
+ export default Image;
733
+ export { Image };
734
+ `;
735
+
736
+ /**
737
+ * next/dynamic shim - Dynamic imports with loading states
738
+ */
739
+ export const NEXT_DYNAMIC_SHIM = `
740
+ import React from 'react';
741
+
742
+ function dynamic(importFn, options = {}) {
743
+ const {
744
+ loading: LoadingComponent,
745
+ ssr = true,
746
+ } = options;
747
+
748
+ // Create a lazy component
749
+ const LazyComponent = React.lazy(importFn);
750
+
751
+ // Wrapper component that handles loading state
752
+ function DynamicComponent(props) {
753
+ const fallback = LoadingComponent
754
+ ? React.createElement(LoadingComponent, { isLoading: true })
755
+ : null;
756
+
757
+ return React.createElement(
758
+ React.Suspense,
759
+ { fallback },
760
+ React.createElement(LazyComponent, props)
761
+ );
762
+ }
763
+
764
+ return DynamicComponent;
765
+ }
766
+
767
+ export default dynamic;
768
+ export { dynamic };
769
+ `;
770
+
771
+ /**
772
+ * next/script shim - Loads external scripts
773
+ */
774
+ export const NEXT_SCRIPT_SHIM = `
775
+ import React from 'react';
776
+
777
+ function Script({
778
+ src,
779
+ strategy = 'afterInteractive',
780
+ onLoad,
781
+ onReady,
782
+ onError,
783
+ children,
784
+ dangerouslySetInnerHTML,
785
+ ...rest
786
+ }) {
787
+ React.useEffect(function() {
788
+ if (!src && !children && !dangerouslySetInnerHTML) return;
789
+
790
+ var script = document.createElement('script');
791
+
792
+ if (src) {
793
+ script.src = src;
794
+ script.async = strategy !== 'beforeInteractive';
795
+ }
796
+
797
+ Object.keys(rest).forEach(function(key) {
798
+ script.setAttribute(key, rest[key]);
799
+ });
800
+
801
+ if (children) {
802
+ script.textContent = children;
803
+ } else if (dangerouslySetInnerHTML && dangerouslySetInnerHTML.__html) {
804
+ script.textContent = dangerouslySetInnerHTML.__html;
805
+ }
806
+
807
+ script.onload = function() {
808
+ if (onLoad) onLoad();
809
+ if (onReady) onReady();
810
+ };
811
+ script.onerror = onError;
812
+
813
+ document.head.appendChild(script);
814
+
815
+ return function() {
816
+ if (script.parentNode) {
817
+ script.parentNode.removeChild(script);
818
+ }
819
+ };
820
+ }, [src]);
821
+
822
+ return null;
823
+ }
824
+
825
+ export default Script;
826
+ export { Script };
827
+ `;
828
+
829
+ /**
830
+ * next/font/google shim - Loads Google Fonts via CDN
831
+ * Uses a Proxy to dynamically handle ANY Google Font without hardcoding
832
+ */
833
+ export const NEXT_FONT_GOOGLE_SHIM = `
834
+ // Track loaded fonts to avoid duplicate style injections
835
+ const loadedFonts = new Set();
836
+ const addedPreconnects = new Set();
837
+
838
+ /**
839
+ * Convert font function name to Google Fonts family name
840
+ * Examples:
841
+ * DM_Sans -> DM Sans
842
+ * Open_Sans -> Open Sans
843
+ * Fraunces -> Fraunces
844
+ */
845
+ function toFontFamily(fontName) {
846
+ return fontName.replace(/_/g, ' ');
847
+ }
848
+
849
+ function addPreconnect(url, crossOrigin) {
850
+ if (addedPreconnects.has(url)) return;
851
+ addedPreconnects.add(url);
852
+ if (typeof document === 'undefined') return;
853
+ const link = document.createElement('link');
854
+ link.rel = 'preconnect';
855
+ link.href = url;
856
+ if (crossOrigin) link.crossOrigin = crossOrigin;
857
+ document.head.appendChild(link);
858
+ }
859
+
860
+ /**
861
+ * Inject font CSS into document
862
+ * - Adds preconnect links for faster font loading
863
+ * - Loads the font from Google Fonts CDN
864
+ * - Creates a CSS class that sets the CSS variable
865
+ */
866
+ function injectFontCSS(fontFamily, variableName, weight, style) {
867
+ const fontKey = fontFamily + '-' + (variableName || 'default');
868
+ if (loadedFonts.has(fontKey)) {
869
+ return;
870
+ }
871
+ loadedFonts.add(fontKey);
872
+
873
+ if (typeof document === 'undefined') {
874
+ return;
875
+ }
876
+
877
+ // Add preconnect links for faster loading (deduplicated via Set)
878
+ addPreconnect('https://fonts.googleapis.com');
879
+ addPreconnect('https://fonts.gstatic.com', 'anonymous');
880
+
881
+ // Build Google Fonts URL
882
+ const escapedFamily = fontFamily.replace(/ /g, '+');
883
+
884
+ // Build axis list based on options
885
+ let axisList = '';
886
+ const axes = [];
887
+
888
+ // Handle italic style
889
+ if (style === 'italic') {
890
+ axes.push('ital');
891
+ }
892
+
893
+ // Handle weight - use specific weight or variable range
894
+ if (weight && weight !== '400' && !Array.isArray(weight)) {
895
+ // Specific weight requested
896
+ axes.push('wght');
897
+ if (style === 'italic') {
898
+ axisList = ':ital,wght@1,' + weight;
899
+ } else {
900
+ axisList = ':wght@' + weight;
901
+ }
902
+ } else if (Array.isArray(weight)) {
903
+ // Multiple weights
904
+ axes.push('wght');
905
+ axisList = ':wght@' + weight.join(';');
906
+ } else {
907
+ // Default: request common weights for flexibility
908
+ axisList = ':wght@400;500;600;700';
909
+ }
910
+
911
+ const fontUrl = 'https://fonts.googleapis.com/css2?family=' +
912
+ escapedFamily + axisList + '&display=swap';
913
+
914
+ // Add link element for Google Fonts (if not already present)
915
+ if (!document.querySelector('link[href*="family=' + escapedFamily + '"]')) {
916
+ const link = document.createElement('link');
917
+ link.rel = 'stylesheet';
918
+ link.href = fontUrl;
919
+ document.head.appendChild(link);
920
+ }
921
+
922
+ // Create style element for CSS variable at :root level (globally available)
923
+ // This makes the variable work without needing to apply the class to body
924
+ if (variableName) {
925
+ const styleEl = document.createElement('style');
926
+ styleEl.setAttribute('data-font-var', variableName);
927
+ styleEl.textContent = ':root { ' + variableName + ': "' + fontFamily + '", ' + (fontFamily.includes('Serif') ? 'serif' : 'sans-serif') + '; }';
928
+ document.head.appendChild(styleEl);
929
+ }
930
+ }
931
+
932
+ /**
933
+ * Create a font loader function for a specific font
934
+ */
935
+ function createFontLoader(fontName) {
936
+ const fontFamily = toFontFamily(fontName);
937
+
938
+ return function(options = {}) {
939
+ const {
940
+ weight,
941
+ style = 'normal',
942
+ subsets = ['latin'],
943
+ variable,
944
+ display = 'swap',
945
+ preload = true,
946
+ fallback = ['sans-serif'],
947
+ adjustFontFallback = true
948
+ } = options;
949
+
950
+ // Inject the font CSS
951
+ injectFontCSS(fontFamily, variable, weight, style);
952
+
953
+ // Generate class name from variable (--font-inter -> __font-inter)
954
+ const className = variable
955
+ ? variable.replace('--', '__')
956
+ : '__font-' + fontName.toLowerCase().replace(/_/g, '-');
957
+
958
+ return {
959
+ className,
960
+ variable: className,
961
+ style: {
962
+ fontFamily: '"' + fontFamily + '", ' + fallback.join(', ')
963
+ }
964
+ };
965
+ };
966
+ }
967
+
968
+ /**
969
+ * Use a Proxy to dynamically create font loaders for ANY font name
970
+ * This allows: import { AnyGoogleFont } from "next/font/google"
971
+ */
972
+ const fontProxy = new Proxy({}, {
973
+ get(target, prop) {
974
+ // Handle special properties
975
+ if (prop === '__esModule') return true;
976
+ if (prop === 'default') return fontProxy;
977
+ if (typeof prop !== 'string') return undefined;
978
+
979
+ // Create a font loader for this font name
980
+ return createFontLoader(prop);
981
+ }
982
+ });
983
+
984
+ // Export the proxy as both default and named exports
985
+ export default fontProxy;
986
+
987
+ // Re-export through proxy for named imports
988
+ export const {
989
+ Fraunces, Inter, DM_Sans, DM_Serif_Text, Roboto, Open_Sans, Lato,
990
+ Montserrat, Poppins, Playfair_Display, Merriweather, Raleway, Nunito,
991
+ Ubuntu, Oswald, Quicksand, Work_Sans, Fira_Sans, Barlow, Mulish, Rubik,
992
+ Noto_Sans, Manrope, Space_Grotesk, Geist, Geist_Mono
993
+ } = fontProxy;
994
+ `;
995
+
996
+ /**
997
+ * next/font/local shim - Loads local font files
998
+ * Accepts font source path and creates @font-face declaration + CSS variable
999
+ */
1000
+ export const NEXT_FONT_LOCAL_SHIM = `
1001
+ const loadedLocalFonts = new Set();
1002
+
1003
+ function localFont(options = {}) {
1004
+ const {
1005
+ src,
1006
+ weight,
1007
+ style = 'normal',
1008
+ variable,
1009
+ display = 'swap',
1010
+ fallback = ['sans-serif'],
1011
+ declarations = [],
1012
+ adjustFontFallback = true
1013
+ } = options;
1014
+
1015
+ // Determine font family name from variable or src
1016
+ const familyName = variable
1017
+ ? variable.replace('--', '').replace(/-/g, ' ')
1018
+ : 'local-font-' + Math.random().toString(36).slice(2, 8);
1019
+
1020
+ const fontKey = familyName + '-' + (variable || 'default');
1021
+ if (typeof document !== 'undefined' && !loadedLocalFonts.has(fontKey)) {
1022
+ loadedLocalFonts.add(fontKey);
1023
+
1024
+ // Build @font-face declarations
1025
+ let fontFaces = '';
1026
+
1027
+ if (typeof src === 'string') {
1028
+ // Single source
1029
+ fontFaces = '@font-face {\\n' +
1030
+ ' font-family: "' + familyName + '";\\n' +
1031
+ ' src: url("' + src + '");\\n' +
1032
+ ' font-weight: ' + (weight || '400') + ';\\n' +
1033
+ ' font-style: ' + style + ';\\n' +
1034
+ ' font-display: ' + display + ';\\n' +
1035
+ '}';
1036
+ } else if (Array.isArray(src)) {
1037
+ // Multiple sources (different weights/styles)
1038
+ fontFaces = src.map(function(s) {
1039
+ const path = typeof s === 'string' ? s : s.path;
1040
+ const w = (typeof s === 'object' && s.weight) || weight || '400';
1041
+ const st = (typeof s === 'object' && s.style) || style;
1042
+ return '@font-face {\\n' +
1043
+ ' font-family: "' + familyName + '";\\n' +
1044
+ ' src: url("' + path + '");\\n' +
1045
+ ' font-weight: ' + w + ';\\n' +
1046
+ ' font-style: ' + st + ';\\n' +
1047
+ ' font-display: ' + display + ';\\n' +
1048
+ '}';
1049
+ }).join('\\n');
1050
+ }
1051
+
1052
+ // Inject font-face CSS
1053
+ if (fontFaces) {
1054
+ var styleEl = document.createElement('style');
1055
+ styleEl.setAttribute('data-local-font', fontKey);
1056
+ styleEl.textContent = fontFaces;
1057
+ document.head.appendChild(styleEl);
1058
+ }
1059
+
1060
+ // Inject CSS variable at :root level
1061
+ if (variable) {
1062
+ var varStyle = document.createElement('style');
1063
+ varStyle.setAttribute('data-font-var', variable);
1064
+ varStyle.textContent = ':root { ' + variable + ': "' + familyName + '", ' + fallback.join(', ') + '; }';
1065
+ document.head.appendChild(varStyle);
1066
+ }
1067
+ }
1068
+
1069
+ const className = variable
1070
+ ? variable.replace('--', '__')
1071
+ : '__font-' + familyName.toLowerCase().replace(/\\s+/g, '-');
1072
+
1073
+ return {
1074
+ className,
1075
+ variable: className,
1076
+ style: {
1077
+ fontFamily: '"' + familyName + '", ' + fallback.join(', ')
1078
+ }
1079
+ };
1080
+ }
1081
+
1082
+ export default localFont;
1083
+ export { localFont };
1084
+ `;