almostnode 0.2.6 → 0.2.8

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