almostnode 0.2.5 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -11977,6 +11977,123 @@ export default css;
11977
11977
  }
11978
11978
  }
11979
11979
  };
11980
+ const CONFIG_FILE_NAMES = [
11981
+ "/tailwind.config.ts",
11982
+ "/tailwind.config.js",
11983
+ "/tailwind.config.mjs"
11984
+ ];
11985
+ async function loadTailwindConfig(vfs2, root = "/") {
11986
+ let configPath = null;
11987
+ let configContent = null;
11988
+ for (const fileName of CONFIG_FILE_NAMES) {
11989
+ const fullPath = root === "/" ? fileName : `${root}${fileName}`;
11990
+ try {
11991
+ const content = vfs2.readFileSync(fullPath);
11992
+ configContent = typeof content === "string" ? content : content instanceof Uint8Array ? new TextDecoder("utf-8").decode(content) : Buffer.from(content).toString("utf-8");
11993
+ configPath = fullPath;
11994
+ break;
11995
+ } catch {
11996
+ continue;
11997
+ }
11998
+ }
11999
+ if (!configPath || configContent === null) {
12000
+ return {
12001
+ configScript: "",
12002
+ success: true
12003
+ };
12004
+ }
12005
+ try {
12006
+ const jsConfig = stripTypescriptSyntax(configContent);
12007
+ const configObject = extractConfigObject(jsConfig);
12008
+ if (!configObject) {
12009
+ return {
12010
+ configScript: "",
12011
+ success: false,
12012
+ error: "Could not extract config object from tailwind.config"
12013
+ };
12014
+ }
12015
+ const configScript = generateConfigScript(configObject);
12016
+ return {
12017
+ configScript,
12018
+ success: true
12019
+ };
12020
+ } catch (error) {
12021
+ return {
12022
+ configScript: "",
12023
+ success: false,
12024
+ error: `Failed to parse tailwind.config: ${error instanceof Error ? error.message : String(error)}`
12025
+ };
12026
+ }
12027
+ }
12028
+ function stripTypescriptSyntax(content) {
12029
+ let result = content;
12030
+ result = result.replace(/import\s+type\s+\{[^}]*\}\s+from\s+['"][^'"]*['"]\s*;?\s*/g, "");
12031
+ result = result.replace(/import\s+\{[^}]*\}\s+from\s+['"][^'"]*['"]\s*;?\s*/g, "");
12032
+ result = result.replace(/\s+satisfies\s+\w+\s*$/gm, "");
12033
+ result = result.replace(/\s+satisfies\s+\w+\s*;?\s*$/gm, "");
12034
+ result = result.replace(/:\s*Config\s*=/g, " =");
12035
+ result = result.replace(/\s+as\s+const\s*/g, " ");
12036
+ return result;
12037
+ }
12038
+ function extractConfigObject(content) {
12039
+ const exportDefaultMatch = content.match(/export\s+default\s*/);
12040
+ if (!exportDefaultMatch || exportDefaultMatch.index === void 0) {
12041
+ return null;
12042
+ }
12043
+ const startIndex = exportDefaultMatch.index + exportDefaultMatch[0].length;
12044
+ const remaining = content.substring(startIndex);
12045
+ const trimmedRemaining = remaining.trimStart();
12046
+ if (!trimmedRemaining.startsWith("{")) {
12047
+ return null;
12048
+ }
12049
+ const objectStart = startIndex + (remaining.length - trimmedRemaining.length);
12050
+ const objectContent = content.substring(objectStart);
12051
+ let braceCount = 0;
12052
+ let inString = false;
12053
+ let stringChar = "";
12054
+ let escaped = false;
12055
+ let endIndex = -1;
12056
+ for (let i = 0; i < objectContent.length; i++) {
12057
+ const char = objectContent[i];
12058
+ if (escaped) {
12059
+ escaped = false;
12060
+ continue;
12061
+ }
12062
+ if (char === "\\") {
12063
+ escaped = true;
12064
+ continue;
12065
+ }
12066
+ if (inString) {
12067
+ if (char === stringChar) {
12068
+ inString = false;
12069
+ }
12070
+ continue;
12071
+ }
12072
+ if (char === '"' || char === "'" || char === "`") {
12073
+ inString = true;
12074
+ stringChar = char;
12075
+ continue;
12076
+ }
12077
+ if (char === "{") {
12078
+ braceCount++;
12079
+ } else if (char === "}") {
12080
+ braceCount--;
12081
+ if (braceCount === 0) {
12082
+ endIndex = i + 1;
12083
+ break;
12084
+ }
12085
+ }
12086
+ }
12087
+ if (endIndex === -1) {
12088
+ return null;
12089
+ }
12090
+ return objectContent.substring(0, endIndex);
12091
+ }
12092
+ function generateConfigScript(configObject) {
12093
+ return `<script>
12094
+ tailwind.config = ${configObject};
12095
+ <\/script>`;
12096
+ }
11980
12097
  const isBrowser = typeof window !== "undefined" && typeof window.navigator !== "undefined" && "serviceWorker" in window.navigator;
11981
12098
  async function initEsbuild() {
11982
12099
  if (!isBrowser) return;
@@ -12201,25 +12318,31 @@ const applyVirtualBase = (url) => {
12201
12318
 
12202
12319
  export default function Link({ href, children, ...props }) {
12203
12320
  const handleClick = (e) => {
12321
+ console.log('[Link] Click handler called, href:', href);
12322
+
12204
12323
  if (props.onClick) {
12205
12324
  props.onClick(e);
12206
12325
  }
12207
12326
 
12208
12327
  // Allow cmd/ctrl click to open in new tab
12209
12328
  if (e.metaKey || e.ctrlKey) {
12329
+ console.log('[Link] Meta/Ctrl key pressed, allowing default behavior');
12210
12330
  return;
12211
12331
  }
12212
12332
 
12213
12333
  if (typeof href !== 'string' || !href || href.startsWith('#') || href.startsWith('?')) {
12334
+ console.log('[Link] Skipping navigation for href:', href);
12214
12335
  return;
12215
12336
  }
12216
12337
 
12217
12338
  if (/^(https?:)?\\/\\//.test(href)) {
12339
+ console.log('[Link] External URL, allowing default behavior:', href);
12218
12340
  return;
12219
12341
  }
12220
12342
 
12221
12343
  e.preventDefault();
12222
12344
  const resolvedHref = applyVirtualBase(href);
12345
+ console.log('[Link] Navigating to:', resolvedHref);
12223
12346
  window.history.pushState({}, '', resolvedHref);
12224
12347
  window.dispatchEvent(new PopStateEvent('popstate'));
12225
12348
  };
@@ -12557,6 +12680,305 @@ export default function Head({ children }) {
12557
12680
 
12558
12681
  return null;
12559
12682
  }
12683
+ `;
12684
+ const NEXT_IMAGE_SHIM = `
12685
+ import React from 'react';
12686
+
12687
+ function Image({
12688
+ src,
12689
+ alt = '',
12690
+ width,
12691
+ height,
12692
+ fill,
12693
+ loader,
12694
+ quality = 75,
12695
+ priority,
12696
+ loading,
12697
+ placeholder,
12698
+ blurDataURL,
12699
+ unoptimized,
12700
+ onLoad,
12701
+ onError,
12702
+ style,
12703
+ className,
12704
+ sizes,
12705
+ ...rest
12706
+ }) {
12707
+ // Handle src - could be string or StaticImageData object
12708
+ const imageSrc = typeof src === 'object' ? src.src : src;
12709
+
12710
+ // Build style object
12711
+ const imgStyle = { ...style };
12712
+ if (fill) {
12713
+ imgStyle.position = 'absolute';
12714
+ imgStyle.width = '100%';
12715
+ imgStyle.height = '100%';
12716
+ imgStyle.objectFit = imgStyle.objectFit || 'cover';
12717
+ imgStyle.inset = '0';
12718
+ }
12719
+
12720
+ return React.createElement('img', {
12721
+ src: imageSrc,
12722
+ alt,
12723
+ width: fill ? undefined : width,
12724
+ height: fill ? undefined : height,
12725
+ loading: priority ? 'eager' : (loading || 'lazy'),
12726
+ decoding: 'async',
12727
+ style: imgStyle,
12728
+ className,
12729
+ onLoad,
12730
+ onError,
12731
+ ...rest
12732
+ });
12733
+ }
12734
+
12735
+ export default Image;
12736
+ export { Image };
12737
+ `;
12738
+ const NEXT_DYNAMIC_SHIM = `
12739
+ import React from 'react';
12740
+
12741
+ function dynamic(importFn, options = {}) {
12742
+ const {
12743
+ loading: LoadingComponent,
12744
+ ssr = true,
12745
+ } = options;
12746
+
12747
+ // Create a lazy component
12748
+ const LazyComponent = React.lazy(importFn);
12749
+
12750
+ // Wrapper component that handles loading state
12751
+ function DynamicComponent(props) {
12752
+ const fallback = LoadingComponent
12753
+ ? React.createElement(LoadingComponent, { isLoading: true })
12754
+ : null;
12755
+
12756
+ return React.createElement(
12757
+ React.Suspense,
12758
+ { fallback },
12759
+ React.createElement(LazyComponent, props)
12760
+ );
12761
+ }
12762
+
12763
+ return DynamicComponent;
12764
+ }
12765
+
12766
+ export default dynamic;
12767
+ export { dynamic };
12768
+ `;
12769
+ const NEXT_SCRIPT_SHIM = `
12770
+ import React from 'react';
12771
+
12772
+ function Script({
12773
+ src,
12774
+ strategy = 'afterInteractive',
12775
+ onLoad,
12776
+ onReady,
12777
+ onError,
12778
+ children,
12779
+ dangerouslySetInnerHTML,
12780
+ ...rest
12781
+ }) {
12782
+ React.useEffect(function() {
12783
+ if (!src && !children && !dangerouslySetInnerHTML) return;
12784
+
12785
+ var script = document.createElement('script');
12786
+
12787
+ if (src) {
12788
+ script.src = src;
12789
+ script.async = strategy !== 'beforeInteractive';
12790
+ }
12791
+
12792
+ Object.keys(rest).forEach(function(key) {
12793
+ script.setAttribute(key, rest[key]);
12794
+ });
12795
+
12796
+ if (children) {
12797
+ script.textContent = children;
12798
+ } else if (dangerouslySetInnerHTML && dangerouslySetInnerHTML.__html) {
12799
+ script.textContent = dangerouslySetInnerHTML.__html;
12800
+ }
12801
+
12802
+ script.onload = function() {
12803
+ if (onLoad) onLoad();
12804
+ if (onReady) onReady();
12805
+ };
12806
+ script.onerror = onError;
12807
+
12808
+ document.head.appendChild(script);
12809
+
12810
+ return function() {
12811
+ if (script.parentNode) {
12812
+ script.parentNode.removeChild(script);
12813
+ }
12814
+ };
12815
+ }, [src]);
12816
+
12817
+ return null;
12818
+ }
12819
+
12820
+ export default Script;
12821
+ export { Script };
12822
+ `;
12823
+ const NEXT_FONT_GOOGLE_SHIM = `
12824
+ // Track loaded fonts to avoid duplicate style injections
12825
+ const loadedFonts = new Set();
12826
+
12827
+ /**
12828
+ * Convert font function name to Google Fonts family name
12829
+ * Examples:
12830
+ * DM_Sans -> DM Sans
12831
+ * Open_Sans -> Open Sans
12832
+ * Fraunces -> Fraunces
12833
+ */
12834
+ function toFontFamily(fontName) {
12835
+ return fontName.replace(/_/g, ' ');
12836
+ }
12837
+
12838
+ /**
12839
+ * Inject font CSS into document
12840
+ * - Adds preconnect links for faster font loading
12841
+ * - Loads the font from Google Fonts CDN
12842
+ * - Creates a CSS class that sets the CSS variable
12843
+ */
12844
+ function injectFontCSS(fontFamily, variableName, weight, style) {
12845
+ const fontKey = fontFamily + '-' + (variableName || 'default');
12846
+ if (loadedFonts.has(fontKey)) {
12847
+ return;
12848
+ }
12849
+ loadedFonts.add(fontKey);
12850
+
12851
+ if (typeof document === 'undefined') {
12852
+ return;
12853
+ }
12854
+
12855
+ // Add preconnect links for faster loading (only once)
12856
+ if (!document.querySelector('link[href="https://fonts.googleapis.com"]')) {
12857
+ const preconnect1 = document.createElement('link');
12858
+ preconnect1.rel = 'preconnect';
12859
+ preconnect1.href = 'https://fonts.googleapis.com';
12860
+ document.head.appendChild(preconnect1);
12861
+
12862
+ const preconnect2 = document.createElement('link');
12863
+ preconnect2.rel = 'preconnect';
12864
+ preconnect2.href = 'https://fonts.gstatic.com';
12865
+ preconnect2.crossOrigin = 'anonymous';
12866
+ document.head.appendChild(preconnect2);
12867
+ }
12868
+
12869
+ // Build Google Fonts URL
12870
+ const escapedFamily = fontFamily.replace(/ /g, '+');
12871
+
12872
+ // Build axis list based on options
12873
+ let axisList = '';
12874
+ const axes = [];
12875
+
12876
+ // Handle italic style
12877
+ if (style === 'italic') {
12878
+ axes.push('ital');
12879
+ }
12880
+
12881
+ // Handle weight - use specific weight or variable range
12882
+ if (weight && weight !== '400' && !Array.isArray(weight)) {
12883
+ // Specific weight requested
12884
+ axes.push('wght');
12885
+ if (style === 'italic') {
12886
+ axisList = ':ital,wght@1,' + weight;
12887
+ } else {
12888
+ axisList = ':wght@' + weight;
12889
+ }
12890
+ } else if (Array.isArray(weight)) {
12891
+ // Multiple weights
12892
+ axes.push('wght');
12893
+ axisList = ':wght@' + weight.join(';');
12894
+ } else {
12895
+ // Default: request common weights for flexibility
12896
+ axisList = ':wght@400;500;600;700';
12897
+ }
12898
+
12899
+ const fontUrl = 'https://fonts.googleapis.com/css2?family=' +
12900
+ escapedFamily + axisList + '&display=swap';
12901
+
12902
+ // Add link element for Google Fonts (if not already present)
12903
+ if (!document.querySelector('link[href*="family=' + escapedFamily + '"]')) {
12904
+ const link = document.createElement('link');
12905
+ link.rel = 'stylesheet';
12906
+ link.href = fontUrl;
12907
+ document.head.appendChild(link);
12908
+ }
12909
+
12910
+ // Create style element for CSS variable at :root level (globally available)
12911
+ // This makes the variable work without needing to apply the class to body
12912
+ if (variableName) {
12913
+ const styleEl = document.createElement('style');
12914
+ styleEl.setAttribute('data-font-var', variableName);
12915
+ styleEl.textContent = ':root { ' + variableName + ': "' + fontFamily + '", ' + (fontFamily.includes('Serif') ? 'serif' : 'sans-serif') + '; }';
12916
+ document.head.appendChild(styleEl);
12917
+ }
12918
+ }
12919
+
12920
+ /**
12921
+ * Create a font loader function for a specific font
12922
+ */
12923
+ function createFontLoader(fontName) {
12924
+ const fontFamily = toFontFamily(fontName);
12925
+
12926
+ return function(options = {}) {
12927
+ const {
12928
+ weight,
12929
+ style = 'normal',
12930
+ subsets = ['latin'],
12931
+ variable,
12932
+ display = 'swap',
12933
+ preload = true,
12934
+ fallback = ['sans-serif'],
12935
+ adjustFontFallback = true
12936
+ } = options;
12937
+
12938
+ // Inject the font CSS
12939
+ injectFontCSS(fontFamily, variable, weight, style);
12940
+
12941
+ // Generate class name from variable (--font-inter -> __font-inter)
12942
+ const className = variable
12943
+ ? variable.replace('--', '__')
12944
+ : '__font-' + fontName.toLowerCase().replace(/_/g, '-');
12945
+
12946
+ return {
12947
+ className,
12948
+ variable: className,
12949
+ style: {
12950
+ fontFamily: '"' + fontFamily + '", ' + fallback.join(', ')
12951
+ }
12952
+ };
12953
+ };
12954
+ }
12955
+
12956
+ /**
12957
+ * Use a Proxy to dynamically create font loaders for ANY font name
12958
+ * This allows: import { AnyGoogleFont } from "next/font/google"
12959
+ */
12960
+ const fontProxy = new Proxy({}, {
12961
+ get(target, prop) {
12962
+ // Handle special properties
12963
+ if (prop === '__esModule') return true;
12964
+ if (prop === 'default') return fontProxy;
12965
+ if (typeof prop !== 'string') return undefined;
12966
+
12967
+ // Create a font loader for this font name
12968
+ return createFontLoader(prop);
12969
+ }
12970
+ });
12971
+
12972
+ // Export the proxy as both default and named exports
12973
+ export default fontProxy;
12974
+
12975
+ // Re-export through proxy for named imports
12976
+ export const {
12977
+ Fraunces, Inter, DM_Sans, DM_Serif_Text, Roboto, Open_Sans, Lato,
12978
+ Montserrat, Poppins, Playfair_Display, Merriweather, Raleway, Nunito,
12979
+ Ubuntu, Oswald, Quicksand, Work_Sans, Fira_Sans, Barlow, Mulish, Rubik,
12980
+ Noto_Sans, Manrope, Space_Grotesk, Geist, Geist_Mono
12981
+ } = fontProxy;
12560
12982
  `;
12561
12983
  NextDevServer = class extends DevServer {
12562
12984
  constructor(vfs2, options2) {
@@ -12569,6 +12991,10 @@ export default function Head({ children }) {
12569
12991
  __publicField(this, "hmrTargetWindow", null);
12570
12992
  __publicField(this, "options");
12571
12993
  __publicField(this, "transformCache", /* @__PURE__ */ new Map());
12994
+ __publicField(this, "pathAliases", /* @__PURE__ */ new Map());
12995
+ __publicField(this, "tailwindConfigScript", "");
12996
+ __publicField(this, "tailwindConfigLoaded", false);
12997
+ __publicField(this, "assetPrefix", "");
12572
12998
  this.options = options2;
12573
12999
  this.pagesDir = options2.pagesDir || "/pages";
12574
13000
  this.appDir = options2.appDir || "/app";
@@ -12578,6 +13004,82 @@ export default function Head({ children }) {
12578
13004
  } else {
12579
13005
  this.useAppRouter = this.hasAppRouter();
12580
13006
  }
13007
+ this.loadPathAliases();
13008
+ this.loadAssetPrefix(options2.assetPrefix);
13009
+ }
13010
+ loadPathAliases() {
13011
+ var _a2;
13012
+ try {
13013
+ const tsconfigPath = "/tsconfig.json";
13014
+ if (!this.vfs.existsSync(tsconfigPath)) {
13015
+ return;
13016
+ }
13017
+ const content = this.vfs.readFileSync(tsconfigPath, "utf-8");
13018
+ const tsconfig = JSON.parse(content);
13019
+ const paths = (_a2 = tsconfig == null ? void 0 : tsconfig.compilerOptions) == null ? void 0 : _a2.paths;
13020
+ if (!paths) {
13021
+ return;
13022
+ }
13023
+ for (const [alias, targets] of Object.entries(paths)) {
13024
+ if (Array.isArray(targets) && targets.length > 0) {
13025
+ const aliasPrefix = alias.replace(/\*$/, "");
13026
+ const targetPrefix = targets[0].replace(/\*$/, "").replace(/^\./, "");
13027
+ this.pathAliases.set(aliasPrefix, targetPrefix);
13028
+ }
13029
+ }
13030
+ } catch (e) {
13031
+ }
13032
+ }
13033
+ loadAssetPrefix(optionValue) {
13034
+ if (optionValue !== void 0) {
13035
+ this.assetPrefix = optionValue.startsWith("/") ? optionValue : `/${optionValue}`;
13036
+ if (this.assetPrefix.endsWith("/")) {
13037
+ this.assetPrefix = this.assetPrefix.slice(0, -1);
13038
+ }
13039
+ return;
13040
+ }
13041
+ try {
13042
+ const configFiles = [
13043
+ "/next.config.ts",
13044
+ "/next.config.js",
13045
+ "/next.config.mjs"
13046
+ ];
13047
+ for (const configPath of configFiles) {
13048
+ if (!this.vfs.existsSync(configPath)) {
13049
+ continue;
13050
+ }
13051
+ const content = this.vfs.readFileSync(configPath, "utf-8");
13052
+ const match = content.match(/assetPrefix\s*:\s*["']([^"']+)["']/);
13053
+ if (match) {
13054
+ let prefix = match[1];
13055
+ if (!prefix.startsWith("/")) {
13056
+ prefix = `/${prefix}`;
13057
+ }
13058
+ if (prefix.endsWith("/")) {
13059
+ prefix = prefix.slice(0, -1);
13060
+ }
13061
+ this.assetPrefix = prefix;
13062
+ return;
13063
+ }
13064
+ }
13065
+ } catch (e) {
13066
+ }
13067
+ }
13068
+ resolvePathAliases(code2, currentFile) {
13069
+ if (this.pathAliases.size === 0) {
13070
+ return code2;
13071
+ }
13072
+ const virtualBase = `/__virtual__/${this.port}`;
13073
+ let result = code2;
13074
+ for (const [alias, target] of this.pathAliases) {
13075
+ const aliasEscaped = alias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13076
+ const pattern = new RegExp(`(from\\s*['"]|import\\s*\\(\\s*['"])${aliasEscaped}([^'"]+)(['"])`, "g");
13077
+ result = result.replace(pattern, (match, prefix, path2, quote) => {
13078
+ const resolvedPath2 = `${virtualBase}${target}${path2}`;
13079
+ return `${prefix}${resolvedPath2}${quote}`;
13080
+ });
13081
+ }
13082
+ return result;
12581
13083
  }
12582
13084
  setEnv(key, value) {
12583
13085
  this.options.env = this.options.env || {};
@@ -12593,20 +13095,38 @@ export default function Head({ children }) {
12593
13095
  }
12594
13096
  generateEnvScript() {
12595
13097
  const env = this.options.env || {};
12596
- const publicEnvVars = Object.entries(env).filter(([key]) => key.startsWith("NEXT_PUBLIC_")).reduce((acc, [k, v]) => ({
12597
- ...acc,
12598
- [k]: v
12599
- }), {});
12600
- if (Object.keys(publicEnvVars).length === 0) {
12601
- return "";
13098
+ const publicEnvVars = {};
13099
+ for (const [key, value] of Object.entries(env)) {
13100
+ if (key.startsWith("NEXT_PUBLIC_")) {
13101
+ publicEnvVars[key] = value;
13102
+ }
12602
13103
  }
12603
13104
  return `<script>
12604
- // NEXT_PUBLIC_* environment variables (injected by NextDevServer)
13105
+ // Environment variables (injected by NextDevServer)
12605
13106
  window.process = window.process || {};
12606
13107
  window.process.env = window.process.env || {};
12607
13108
  Object.assign(window.process.env, ${JSON.stringify(publicEnvVars)});
12608
13109
  <\/script>`;
12609
13110
  }
13111
+ async loadTailwindConfigIfNeeded() {
13112
+ if (this.tailwindConfigLoaded) {
13113
+ return this.tailwindConfigScript;
13114
+ }
13115
+ try {
13116
+ const result = await loadTailwindConfig(this.vfs, this.root);
13117
+ if (result.success) {
13118
+ this.tailwindConfigScript = result.configScript;
13119
+ } else if (result.error) {
13120
+ console.warn("[NextDevServer] Tailwind config warning:", result.error);
13121
+ this.tailwindConfigScript = "";
13122
+ }
13123
+ } catch (error) {
13124
+ console.warn("[NextDevServer] Failed to load tailwind.config:", error);
13125
+ this.tailwindConfigScript = "";
13126
+ }
13127
+ this.tailwindConfigLoaded = true;
13128
+ return this.tailwindConfigScript;
13129
+ }
12610
13130
  hasAppRouter() {
12611
13131
  try {
12612
13132
  if (!this.exists(this.appDir)) return false;
@@ -12626,10 +13146,26 @@ export default function Head({ children }) {
12626
13146
  }
12627
13147
  async handleRequest(method, url2, headers, body) {
12628
13148
  const urlObj = new URL(url2, "http://localhost");
12629
- const pathname = urlObj.pathname;
13149
+ let pathname = urlObj.pathname;
13150
+ const virtualPrefixMatch = pathname.match(/^\/__virtual__\/\d+/);
13151
+ if (virtualPrefixMatch) {
13152
+ pathname = pathname.slice(virtualPrefixMatch[0].length) || "/";
13153
+ }
13154
+ if (this.assetPrefix && pathname.startsWith(this.assetPrefix)) {
13155
+ const rest = pathname.slice(this.assetPrefix.length);
13156
+ if (rest === "" || rest.startsWith("/")) {
13157
+ pathname = rest || "/";
13158
+ if (pathname.startsWith("//")) {
13159
+ pathname = pathname.slice(1);
13160
+ }
13161
+ }
13162
+ }
12630
13163
  if (pathname.startsWith("/_next/shims/")) {
12631
13164
  return this.serveNextShim(pathname);
12632
13165
  }
13166
+ if (pathname === "/_next/route-info") {
13167
+ return this.serveRouteInfo(urlObj.searchParams.get("pathname") || "/");
13168
+ }
12633
13169
  if (pathname.startsWith("/_next/pages/")) {
12634
13170
  return this.servePageComponent(pathname);
12635
13171
  }
@@ -12649,6 +13185,13 @@ export default function Head({ children }) {
12649
13185
  if (this.needsTransform(pathname) && this.exists(pathname)) {
12650
13186
  return this.transformAndServe(pathname, pathname);
12651
13187
  }
13188
+ const resolvedFile = this.resolveFileWithExtension(pathname);
13189
+ if (resolvedFile) {
13190
+ if (this.needsTransform(resolvedFile)) {
13191
+ return this.transformAndServe(resolvedFile, pathname);
13192
+ }
13193
+ return this.serveFile(resolvedFile);
13194
+ }
12652
13195
  if (this.exists(pathname) && !this.isDirectory(pathname)) {
12653
13196
  return this.serveFile(pathname);
12654
13197
  }
@@ -12670,6 +13213,18 @@ export default function Head({ children }) {
12670
13213
  case "navigation":
12671
13214
  code2 = NEXT_NAVIGATION_SHIM;
12672
13215
  break;
13216
+ case "image":
13217
+ code2 = NEXT_IMAGE_SHIM;
13218
+ break;
13219
+ case "dynamic":
13220
+ code2 = NEXT_DYNAMIC_SHIM;
13221
+ break;
13222
+ case "script":
13223
+ code2 = NEXT_SCRIPT_SHIM;
13224
+ break;
13225
+ case "font/google":
13226
+ code2 = NEXT_FONT_GOOGLE_SHIM;
13227
+ break;
12673
13228
  default:
12674
13229
  return this.notFound(pathname);
12675
13230
  }
@@ -12685,6 +13240,28 @@ export default function Head({ children }) {
12685
13240
  body: buffer2
12686
13241
  };
12687
13242
  }
13243
+ serveRouteInfo(pathname) {
13244
+ const route = this.resolveAppRoute(pathname);
13245
+ const info = route ? {
13246
+ params: route.params,
13247
+ found: true
13248
+ } : {
13249
+ params: {},
13250
+ found: false
13251
+ };
13252
+ const json = JSON.stringify(info);
13253
+ const buffer2 = BufferPolyfill.from(json);
13254
+ return {
13255
+ statusCode: 200,
13256
+ statusMessage: "OK",
13257
+ headers: {
13258
+ "Content-Type": "application/json; charset=utf-8",
13259
+ "Content-Length": String(buffer2.length),
13260
+ "Cache-Control": "no-cache"
13261
+ },
13262
+ body: buffer2
13263
+ };
13264
+ }
12688
13265
  serveStaticAsset(pathname) {
12689
13266
  const filePath = pathname.replace("/_next/static/", "/");
12690
13267
  if (this.exists(filePath)) {
@@ -13193,7 +13770,8 @@ export default function Head({ children }) {
13193
13770
  if (this.exists(pagePath)) {
13194
13771
  return {
13195
13772
  page: pagePath,
13196
- layouts
13773
+ layouts,
13774
+ params: {}
13197
13775
  };
13198
13776
  }
13199
13777
  }
@@ -13206,7 +13784,7 @@ export default function Head({ children }) {
13206
13784
  ".js",
13207
13785
  ".ts"
13208
13786
  ];
13209
- const tryPath = (dirPath, remainingSegments, layouts2) => {
13787
+ const tryPath = (dirPath, remainingSegments, layouts2, params) => {
13210
13788
  for (const ext of extensions) {
13211
13789
  const layoutPath = `${dirPath}/layout${ext}`;
13212
13790
  if (this.exists(layoutPath) && !layouts2.includes(layoutPath)) {
@@ -13222,7 +13800,8 @@ export default function Head({ children }) {
13222
13800
  if (this.exists(pagePath)) {
13223
13801
  return {
13224
13802
  page: pagePath,
13225
- layouts: layouts2
13803
+ layouts: layouts2,
13804
+ params
13226
13805
  };
13227
13806
  }
13228
13807
  }
@@ -13231,16 +13810,35 @@ export default function Head({ children }) {
13231
13810
  const [current, ...rest] = remainingSegments;
13232
13811
  const exactPath = `${dirPath}/${current}`;
13233
13812
  if (this.isDirectory(exactPath)) {
13234
- const result = tryPath(exactPath, rest, layouts2);
13813
+ const result = tryPath(exactPath, rest, layouts2, params);
13235
13814
  if (result) return result;
13236
13815
  }
13237
13816
  try {
13238
13817
  const entries = this.vfs.readdirSync(dirPath);
13239
13818
  for (const entry of entries) {
13240
- if (entry.startsWith("[") && entry.endsWith("]") && !entry.includes(".")) {
13819
+ if (entry.startsWith("[...") && entry.endsWith("]")) {
13241
13820
  const dynamicPath = `${dirPath}/${entry}`;
13242
13821
  if (this.isDirectory(dynamicPath)) {
13243
- const result = tryPath(dynamicPath, rest, layouts2);
13822
+ const paramName = entry.slice(4, -1);
13823
+ const newParams = {
13824
+ ...params,
13825
+ [paramName]: [
13826
+ current,
13827
+ ...rest
13828
+ ]
13829
+ };
13830
+ const result = tryPath(dynamicPath, [], layouts2, newParams);
13831
+ if (result) return result;
13832
+ }
13833
+ } else if (entry.startsWith("[") && entry.endsWith("]") && !entry.includes(".")) {
13834
+ const dynamicPath = `${dirPath}/${entry}`;
13835
+ if (this.isDirectory(dynamicPath)) {
13836
+ const paramName = entry.slice(1, -1);
13837
+ const newParams = {
13838
+ ...params,
13839
+ [paramName]: current
13840
+ };
13841
+ const result = tryPath(dynamicPath, rest, layouts2, newParams);
13244
13842
  if (result) return result;
13245
13843
  }
13246
13844
  }
@@ -13257,7 +13855,7 @@ export default function Head({ children }) {
13257
13855
  break;
13258
13856
  }
13259
13857
  }
13260
- return tryPath(this.appDir, segments, layouts);
13858
+ return tryPath(this.appDir, segments, layouts, {});
13261
13859
  }
13262
13860
  async generateAppRouterHtml(route, pathname) {
13263
13861
  const virtualPrefix = `/__virtual__/${this.port}`;
@@ -13277,6 +13875,7 @@ export default function Head({ children }) {
13277
13875
  for (let i = route.layouts.length - 1; i >= 0; i--) {
13278
13876
  }
13279
13877
  const envScript = this.generateEnvScript();
13878
+ const tailwindConfigScript = await this.loadTailwindConfigIfNeeded();
13280
13879
  return `<!DOCTYPE html>
13281
13880
  <html lang="en">
13282
13881
  <head>
@@ -13286,6 +13885,7 @@ export default function Head({ children }) {
13286
13885
  <title>Next.js App</title>
13287
13886
  ${envScript}
13288
13887
  ${TAILWIND_CDN_SCRIPT}
13888
+ ${tailwindConfigScript}
13289
13889
  ${CORS_PROXY_SCRIPT}
13290
13890
  ${globalCssLinks.join("\n ")}
13291
13891
  ${REACT_REFRESH_PREAMBLE}
@@ -13307,7 +13907,11 @@ export default function Head({ children }) {
13307
13907
  "next/link": "${virtualPrefix}/_next/shims/link.js",
13308
13908
  "next/router": "${virtualPrefix}/_next/shims/router.js",
13309
13909
  "next/head": "${virtualPrefix}/_next/shims/head.js",
13310
- "next/navigation": "${virtualPrefix}/_next/shims/navigation.js"
13910
+ "next/navigation": "${virtualPrefix}/_next/shims/navigation.js",
13911
+ "next/image": "${virtualPrefix}/_next/shims/image.js",
13912
+ "next/dynamic": "${virtualPrefix}/_next/shims/dynamic.js",
13913
+ "next/script": "${virtualPrefix}/_next/shims/script.js",
13914
+ "next/font/google": "${virtualPrefix}/_next/shims/font/google.js"
13311
13915
  }
13312
13916
  }
13313
13917
  <\/script>
@@ -13321,6 +13925,39 @@ export default function Head({ children }) {
13321
13925
 
13322
13926
  const virtualBase = '${virtualPrefix}';
13323
13927
 
13928
+ // Initial route params (embedded by server for initial page load)
13929
+ const initialRouteParams = ${JSON.stringify(route.params)};
13930
+ const initialPathname = '${pathname}';
13931
+
13932
+ // Route params cache for client-side navigation
13933
+ const routeParamsCache = new Map();
13934
+ routeParamsCache.set(initialPathname, initialRouteParams);
13935
+
13936
+ // Extract route params from server for client-side navigation
13937
+ async function extractRouteParams(pathname) {
13938
+ // Strip virtual base if present
13939
+ let route = pathname;
13940
+ if (route.startsWith(virtualBase)) {
13941
+ route = route.slice(virtualBase.length);
13942
+ }
13943
+ route = route.replace(/^\\/+/, '/') || '/';
13944
+
13945
+ // Check cache first
13946
+ if (routeParamsCache.has(route)) {
13947
+ return routeParamsCache.get(route);
13948
+ }
13949
+
13950
+ try {
13951
+ const response = await fetch(virtualBase + '/_next/route-info?pathname=' + encodeURIComponent(route));
13952
+ const info = await response.json();
13953
+ routeParamsCache.set(route, info.params || {});
13954
+ return info.params || {};
13955
+ } catch (e) {
13956
+ console.error('[Router] Failed to extract route params:', e);
13957
+ return {};
13958
+ }
13959
+ }
13960
+
13324
13961
  // Convert URL path to app router page module path
13325
13962
  function getAppPageModulePath(pathname) {
13326
13963
  let route = pathname;
@@ -13387,11 +14024,60 @@ export default function Head({ children }) {
13387
14024
  return layouts;
13388
14025
  }
13389
14026
 
14027
+ // Wrapper for async Server Components
14028
+ function AsyncComponent({ component: Component, pathname, search }) {
14029
+ const [content, setContent] = React.useState(null);
14030
+ const [error, setError] = React.useState(null);
14031
+
14032
+ React.useEffect(() => {
14033
+ let cancelled = false;
14034
+ async function render() {
14035
+ try {
14036
+ // Create searchParams as a Promise (Next.js 15 pattern)
14037
+ const url = new URL(window.location.href);
14038
+ const searchParamsObj = Object.fromEntries(url.searchParams);
14039
+ const searchParams = Promise.resolve(searchParamsObj);
14040
+
14041
+ // Extract route params from pathname (fetches from server for dynamic routes)
14042
+ const routeParams = await extractRouteParams(pathname);
14043
+ const params = Promise.resolve(routeParams);
14044
+
14045
+ // Call component with props like Next.js does for page components
14046
+ const result = Component({ searchParams, params });
14047
+ if (result && typeof result.then === 'function') {
14048
+ // It's a Promise (async component)
14049
+ const resolved = await result;
14050
+ if (!cancelled) setContent(resolved);
14051
+ } else {
14052
+ // Synchronous component - result is already JSX
14053
+ if (!cancelled) setContent(result);
14054
+ }
14055
+ } catch (e) {
14056
+ console.error('[AsyncComponent] Error rendering:', e);
14057
+ if (!cancelled) setError(e);
14058
+ }
14059
+ }
14060
+ render();
14061
+ return () => { cancelled = true; };
14062
+ }, [Component, pathname, search]);
14063
+
14064
+ if (error) {
14065
+ return React.createElement('div', { style: { color: 'red', padding: '20px' } },
14066
+ 'Error: ' + error.message
14067
+ );
14068
+ }
14069
+ if (!content) {
14070
+ return React.createElement('div', { style: { padding: '20px' } }, 'Loading...');
14071
+ }
14072
+ return content;
14073
+ }
14074
+
13390
14075
  // Router component
13391
14076
  function Router() {
13392
14077
  const [Page, setPage] = React.useState(null);
13393
14078
  const [layouts, setLayouts] = React.useState([]);
13394
14079
  const [path, setPath] = React.useState(window.location.pathname);
14080
+ const [search, setSearch] = React.useState(window.location.search);
13395
14081
 
13396
14082
  React.useEffect(() => {
13397
14083
  Promise.all([loadPage(path), loadLayouts(path)]).then(([P, L]) => {
@@ -13403,21 +14089,35 @@ export default function Head({ children }) {
13403
14089
  React.useEffect(() => {
13404
14090
  const handleNavigation = async () => {
13405
14091
  const newPath = window.location.pathname;
14092
+ const newSearch = window.location.search;
14093
+ console.log('[Router] handleNavigation called, newPath:', newPath, 'current path:', path);
14094
+
14095
+ // Always update search params
14096
+ if (newSearch !== search) {
14097
+ setSearch(newSearch);
14098
+ }
14099
+
13406
14100
  if (newPath !== path) {
14101
+ console.log('[Router] Path changed, loading new page...');
13407
14102
  setPath(newPath);
13408
14103
  const [P, L] = await Promise.all([loadPage(newPath), loadLayouts(newPath)]);
14104
+ console.log('[Router] Page loaded:', !!P, 'Layouts:', L.length);
13409
14105
  if (P) setPage(() => P);
13410
14106
  setLayouts(L);
14107
+ } else {
14108
+ console.log('[Router] Path unchanged, skipping navigation');
13411
14109
  }
13412
14110
  };
13413
14111
  window.addEventListener('popstate', handleNavigation);
14112
+ console.log('[Router] Added popstate listener for path:', path);
13414
14113
  return () => window.removeEventListener('popstate', handleNavigation);
13415
- }, [path]);
14114
+ }, [path, search]);
13416
14115
 
13417
14116
  if (!Page) return null;
13418
14117
 
13419
- // Build nested layout structure
13420
- let content = React.createElement(Page);
14118
+ // Use AsyncComponent wrapper to handle async Server Components
14119
+ // Pass search to force re-render when query params change
14120
+ let content = React.createElement(AsyncComponent, { component: Page, pathname: path, search: search });
13421
14121
  for (let i = layouts.length - 1; i >= 0; i--) {
13422
14122
  content = React.createElement(layouts[i], null, content);
13423
14123
  }
@@ -13539,6 +14239,7 @@ export default function Head({ children }) {
13539
14239
  }
13540
14240
  }
13541
14241
  const envScript = this.generateEnvScript();
14242
+ const tailwindConfigScript = await this.loadTailwindConfigIfNeeded();
13542
14243
  return `<!DOCTYPE html>
13543
14244
  <html lang="en">
13544
14245
  <head>
@@ -13548,6 +14249,7 @@ export default function Head({ children }) {
13548
14249
  <title>Next.js App</title>
13549
14250
  ${envScript}
13550
14251
  ${TAILWIND_CDN_SCRIPT}
14252
+ ${tailwindConfigScript}
13551
14253
  ${CORS_PROXY_SCRIPT}
13552
14254
  ${globalCssLinks.join("\n ")}
13553
14255
  ${REACT_REFRESH_PREAMBLE}
@@ -13561,7 +14263,12 @@ export default function Head({ children }) {
13561
14263
  "react-dom/client": "https://esm.sh/react-dom@18.2.0/client?dev",
13562
14264
  "next/link": "${virtualPrefix}/_next/shims/link.js",
13563
14265
  "next/router": "${virtualPrefix}/_next/shims/router.js",
13564
- "next/head": "${virtualPrefix}/_next/shims/head.js"
14266
+ "next/head": "${virtualPrefix}/_next/shims/head.js",
14267
+ "next/navigation": "${virtualPrefix}/_next/shims/navigation.js",
14268
+ "next/image": "${virtualPrefix}/_next/shims/image.js",
14269
+ "next/dynamic": "${virtualPrefix}/_next/shims/dynamic.js",
14270
+ "next/script": "${virtualPrefix}/_next/shims/script.js",
14271
+ "next/font/google": "${virtualPrefix}/_next/shims/font/google.js"
13565
14272
  }
13566
14273
  }
13567
14274
  <\/script>
@@ -13677,6 +14384,30 @@ export default function Head({ children }) {
13677
14384
  body: buffer2
13678
14385
  };
13679
14386
  }
14387
+ resolveFileWithExtension(pathname) {
14388
+ if (/\.\w+$/.test(pathname) && this.exists(pathname)) {
14389
+ return pathname;
14390
+ }
14391
+ const extensions = [
14392
+ ".tsx",
14393
+ ".ts",
14394
+ ".jsx",
14395
+ ".js"
14396
+ ];
14397
+ for (const ext of extensions) {
14398
+ const withExt = pathname + ext;
14399
+ if (this.exists(withExt)) {
14400
+ return withExt;
14401
+ }
14402
+ }
14403
+ for (const ext of extensions) {
14404
+ const indexPath = pathname + "/index" + ext;
14405
+ if (this.exists(indexPath)) {
14406
+ return indexPath;
14407
+ }
14408
+ }
14409
+ return null;
14410
+ }
13680
14411
  needsTransform(path2) {
13681
14412
  return /\.(jsx|tsx|ts)$/.test(path2);
13682
14413
  }
@@ -13700,7 +14431,7 @@ export default function Head({ children }) {
13700
14431
  body: buffer22
13701
14432
  };
13702
14433
  }
13703
- const transformed = await this.transformCode(content, urlPath);
14434
+ const transformed = await this.transformCode(content, filePath);
13704
14435
  this.transformCache.set(filePath, {
13705
14436
  code: transformed,
13706
14437
  hash
@@ -13743,11 +14474,12 @@ console.error(${JSON.stringify(message)});`;
13743
14474
  throw new Error("esbuild not available");
13744
14475
  }
13745
14476
  const codeWithoutCssImports = this.stripCssImports(code2);
14477
+ const codeWithResolvedAliases = this.resolvePathAliases(codeWithoutCssImports, filename2);
13746
14478
  let loader = "js";
13747
14479
  if (filename2.endsWith(".jsx")) loader = "jsx";
13748
14480
  else if (filename2.endsWith(".tsx")) loader = "tsx";
13749
14481
  else if (filename2.endsWith(".ts")) loader = "ts";
13750
- const result = await esbuild2.transform(codeWithoutCssImports, {
14482
+ const result = await esbuild2.transform(codeWithResolvedAliases, {
13751
14483
  loader,
13752
14484
  format: "esm",
13753
14485
  target: "esnext",
@@ -13756,15 +14488,57 @@ console.error(${JSON.stringify(message)});`;
13756
14488
  sourcemap: "inline",
13757
14489
  sourcefile: filename2
13758
14490
  });
14491
+ const codeWithCdnImports = this.redirectNpmImports(result.code);
13759
14492
  if (/\.(jsx|tsx)$/.test(filename2)) {
13760
- return this.addReactRefresh(result.code, filename2);
14493
+ return this.addReactRefresh(codeWithCdnImports, filename2);
13761
14494
  }
13762
- return result.code;
14495
+ return codeWithCdnImports;
14496
+ }
14497
+ redirectNpmImports(code2) {
14498
+ const explicitMappings = {
14499
+ "react": "https://esm.sh/react@18.2.0?dev",
14500
+ "react/jsx-runtime": "https://esm.sh/react@18.2.0&dev/jsx-runtime",
14501
+ "react/jsx-dev-runtime": "https://esm.sh/react@18.2.0&dev/jsx-dev-runtime",
14502
+ "react-dom": "https://esm.sh/react-dom@18.2.0?dev",
14503
+ "react-dom/client": "https://esm.sh/react-dom@18.2.0/client?dev"
14504
+ };
14505
+ const localPackages = /* @__PURE__ */ new Set([
14506
+ "next/link",
14507
+ "next/router",
14508
+ "next/head",
14509
+ "next/navigation",
14510
+ "next/dynamic",
14511
+ "next/image",
14512
+ "next/script",
14513
+ "next/font/google",
14514
+ "convex/_generated/api"
14515
+ ]);
14516
+ const importPattern = /(from\s*['"])([^'"./][^'"]*?)(['"])/g;
14517
+ return code2.replace(importPattern, (match, prefix, packageName, suffix) => {
14518
+ if (packageName.startsWith("http://") || packageName.startsWith("https://") || packageName.startsWith("/__virtual__")) {
14519
+ return match;
14520
+ }
14521
+ if (explicitMappings[packageName]) {
14522
+ return `${prefix}${explicitMappings[packageName]}${suffix}`;
14523
+ }
14524
+ if (localPackages.has(packageName)) {
14525
+ return match;
14526
+ }
14527
+ const basePkg = packageName.includes("/") ? packageName.split("/")[0] : packageName;
14528
+ const isScoped = basePkg.startsWith("@");
14529
+ const scopedBasePkg = isScoped && packageName.includes("/") ? packageName.split("/").slice(0, 2).join("/") : basePkg;
14530
+ if (localPackages.has(scopedBasePkg)) {
14531
+ return match;
14532
+ }
14533
+ const esmUrl = `https://esm.sh/${packageName}?external=react`;
14534
+ return `${prefix}${esmUrl}${suffix}`;
14535
+ });
13763
14536
  }
13764
14537
  stripCssImports(code2) {
13765
- return code2.replace(/import\s+['"][^'"]+\.css['"]\s*;?/g, "// CSS import removed (loaded via <link>)");
14538
+ return code2.replace(/import\s+['"][^'"]+\.css['"]\s*;?/g, "");
13766
14539
  }
13767
14540
  async transformApiHandler(code2, filename2) {
14541
+ const codeWithResolvedAliases = this.resolvePathAliases(code2, filename2);
13768
14542
  if (isBrowser) {
13769
14543
  await initEsbuild();
13770
14544
  const esbuild2 = getEsbuild();
@@ -13775,7 +14549,7 @@ console.error(${JSON.stringify(message)});`;
13775
14549
  if (filename2.endsWith(".jsx")) loader = "jsx";
13776
14550
  else if (filename2.endsWith(".tsx")) loader = "tsx";
13777
14551
  else if (filename2.endsWith(".ts")) loader = "ts";
13778
- const result = await esbuild2.transform(code2, {
14552
+ const result = await esbuild2.transform(codeWithResolvedAliases, {
13779
14553
  loader,
13780
14554
  format: "cjs",
13781
14555
  target: "esnext",
@@ -13784,7 +14558,7 @@ console.error(${JSON.stringify(message)});`;
13784
14558
  });
13785
14559
  return result.code;
13786
14560
  }
13787
- let transformed = code2;
14561
+ let transformed = codeWithResolvedAliases;
13788
14562
  transformed = transformed.replace(/import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g, 'const $1 = require("$2")');
13789
14563
  transformed = transformed.replace(/import\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/g, 'const {$1} = require("$2")');
13790
14564
  transformed = transformed.replace(/export\s+default\s+function\s+(\w+)/g, "module.exports = function $1");
@@ -13900,6 +14674,50 @@ ${registrations}
13900
14674
  }
13901
14675
  }
13902
14676
  }
14677
+ serveFile(filePath) {
14678
+ if (filePath.endsWith(".json")) {
14679
+ try {
14680
+ const normalizedPath = this.resolvePath(filePath);
14681
+ const content = this.vfs.readFileSync(normalizedPath);
14682
+ let jsonContent;
14683
+ if (typeof content === "string") {
14684
+ jsonContent = content;
14685
+ } else if (content instanceof Uint8Array) {
14686
+ jsonContent = new TextDecoder("utf-8").decode(content);
14687
+ } else {
14688
+ jsonContent = BufferPolyfill.from(content).toString("utf-8");
14689
+ }
14690
+ const esModuleContent = `export default ${jsonContent};`;
14691
+ const buffer2 = BufferPolyfill.from(esModuleContent);
14692
+ return {
14693
+ statusCode: 200,
14694
+ statusMessage: "OK",
14695
+ headers: {
14696
+ "Content-Type": "application/javascript; charset=utf-8",
14697
+ "Content-Length": String(buffer2.length),
14698
+ "Cache-Control": "no-cache"
14699
+ },
14700
+ body: buffer2
14701
+ };
14702
+ } catch (error) {
14703
+ if (error.code === "ENOENT") {
14704
+ return this.notFound(filePath);
14705
+ }
14706
+ return this.serverError(error);
14707
+ }
14708
+ }
14709
+ return super.serveFile(filePath);
14710
+ }
14711
+ resolvePath(urlPath) {
14712
+ let path2 = urlPath.split("?")[0].split("#")[0];
14713
+ if (!path2.startsWith("/")) {
14714
+ path2 = "/" + path2;
14715
+ }
14716
+ if (this.root !== "/") {
14717
+ path2 = this.root + path2;
14718
+ }
14719
+ return path2;
14720
+ }
13903
14721
  stop() {
13904
14722
  if (this.watcherCleanup) {
13905
14723
  this.watcherCleanup();