feat(blog): add file-based blog with dynamic slugs, MDX content and layout shell

- Introduced blog routing using Next.js App Router
- Implemented dynamic [slug] pages for blog posts
- Added MDX-based content loading via lib/posts
- Integrated shared TopBar layout with navigation
- Established clear content, lib and component separation
This commit is contained in:
PascalSchattenburg
2026-01-22 14:14:15 +01:00
parent b717952234
commit d147843c76
10412 changed files with 2475583 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
import { middlewareResponse } from './middleware-response';
import * as inspector from 'inspector';
export function getAttachNodejsDebuggerMiddleware() {
return async function(req, res, next) {
const { pathname } = new URL(`http://n${req.url}`);
if (pathname !== '/__nextjs_attach-nodejs-inspector') {
return next();
}
try {
const isInspecting = inspector.url() !== undefined;
const debugPort = process.debugPort;
if (!isInspecting) {
// Node.js will already log that the inspector is listening.
inspector.open(debugPort);
}
const inspectorURLRaw = inspector.url();
if (inspectorURLRaw === undefined) {
// could not open, possibly because already in use.
return middlewareResponse.badRequest(res, `Failed to open port "${debugPort}". Address may be already in use.`);
}
const inspectorURL = new URL(inspectorURLRaw);
const debugInfoListResponse = await fetch(`http://${inspectorURL.host}/json/list`);
const debugInfoList = await debugInfoListResponse.json();
if (!Array.isArray(debugInfoList) || debugInfoList.length === 0) {
throw Object.defineProperty(new Error('No debug targets found'), "__NEXT_ERROR_CODE", {
value: "E927",
enumerable: false,
configurable: true
});
}
return middlewareResponse.json(res, debugInfoList[0].devtoolsFrontendUrl);
} catch (error) {
return middlewareResponse.internalServerError(res);
}
};
}
//# sourceMappingURL=attach-nodejs-debugger-middleware.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/next-devtools/server/attach-nodejs-debugger-middleware.ts"],"sourcesContent":["import { middlewareResponse } from './middleware-response'\nimport type { ServerResponse, IncomingMessage } from 'http'\nimport * as inspector from 'inspector'\n\nexport function getAttachNodejsDebuggerMiddleware() {\n return async function (\n req: IncomingMessage,\n res: ServerResponse,\n next: () => void\n ): Promise<void> {\n const { pathname } = new URL(`http://n${req.url}`)\n\n if (pathname !== '/__nextjs_attach-nodejs-inspector') {\n return next()\n }\n\n try {\n const isInspecting = inspector.url() !== undefined\n const debugPort = process.debugPort\n if (!isInspecting) {\n // Node.js will already log that the inspector is listening.\n inspector.open(debugPort)\n }\n\n const inspectorURLRaw = inspector.url()\n if (inspectorURLRaw === undefined) {\n // could not open, possibly because already in use.\n return middlewareResponse.badRequest(\n res,\n `Failed to open port \"${debugPort}\". Address may be already in use.`\n )\n }\n const inspectorURL = new URL(inspectorURLRaw)\n\n const debugInfoListResponse = await fetch(\n `http://${inspectorURL.host}/json/list`\n )\n const debugInfoList = await debugInfoListResponse.json()\n if (!Array.isArray(debugInfoList) || debugInfoList.length === 0) {\n throw new Error('No debug targets found')\n }\n\n return middlewareResponse.json(res, debugInfoList[0].devtoolsFrontendUrl)\n } catch (error) {\n return middlewareResponse.internalServerError(res)\n }\n }\n}\n"],"names":["middlewareResponse","inspector","getAttachNodejsDebuggerMiddleware","req","res","next","pathname","URL","url","isInspecting","undefined","debugPort","process","open","inspectorURLRaw","badRequest","inspectorURL","debugInfoListResponse","fetch","host","debugInfoList","json","Array","isArray","length","Error","devtoolsFrontendUrl","error","internalServerError"],"mappings":"AAAA,SAASA,kBAAkB,QAAQ,wBAAuB;AAE1D,YAAYC,eAAe,YAAW;AAEtC,OAAO,SAASC;IACd,OAAO,eACLC,GAAoB,EACpBC,GAAmB,EACnBC,IAAgB;QAEhB,MAAM,EAAEC,QAAQ,EAAE,GAAG,IAAIC,IAAI,CAAC,QAAQ,EAAEJ,IAAIK,GAAG,EAAE;QAEjD,IAAIF,aAAa,qCAAqC;YACpD,OAAOD;QACT;QAEA,IAAI;YACF,MAAMI,eAAeR,UAAUO,GAAG,OAAOE;YACzC,MAAMC,YAAYC,QAAQD,SAAS;YACnC,IAAI,CAACF,cAAc;gBACjB,4DAA4D;gBAC5DR,UAAUY,IAAI,CAACF;YACjB;YAEA,MAAMG,kBAAkBb,UAAUO,GAAG;YACrC,IAAIM,oBAAoBJ,WAAW;gBACjC,mDAAmD;gBACnD,OAAOV,mBAAmBe,UAAU,CAClCX,KACA,CAAC,qBAAqB,EAAEO,UAAU,iCAAiC,CAAC;YAExE;YACA,MAAMK,eAAe,IAAIT,IAAIO;YAE7B,MAAMG,wBAAwB,MAAMC,MAClC,CAAC,OAAO,EAAEF,aAAaG,IAAI,CAAC,UAAU,CAAC;YAEzC,MAAMC,gBAAgB,MAAMH,sBAAsBI,IAAI;YACtD,IAAI,CAACC,MAAMC,OAAO,CAACH,kBAAkBA,cAAcI,MAAM,KAAK,GAAG;gBAC/D,MAAM,qBAAmC,CAAnC,IAAIC,MAAM,2BAAV,qBAAA;2BAAA;gCAAA;kCAAA;gBAAkC;YAC1C;YAEA,OAAOzB,mBAAmBqB,IAAI,CAACjB,KAAKgB,aAAa,CAAC,EAAE,CAACM,mBAAmB;QAC1E,EAAE,OAAOC,OAAO;YACd,OAAO3B,mBAAmB4B,mBAAmB,CAACxB;QAChD;IACF;AACF","ignoreList":[0]}

View File

@@ -0,0 +1,25 @@
import { middlewareResponse } from './middleware-response';
import * as Log from '../../build/output/log';
import { devIndicatorServerState } from '../../server/dev/dev-indicator-server-state';
const DISABLE_DEV_INDICATOR_PREFIX = '/__nextjs_disable_dev_indicator';
const COOLDOWN_TIME_MS = process.env.__NEXT_DEV_INDICATOR_COOLDOWN_MS ? parseInt(process.env.__NEXT_DEV_INDICATOR_COOLDOWN_MS) : 1000 * 60 * 60 * 24;
export function getDisableDevIndicatorMiddleware() {
return async function disableDevIndicatorMiddleware(req, res, next) {
try {
const { pathname } = new URL(`http://n${req.url}`);
if (!pathname.startsWith(DISABLE_DEV_INDICATOR_PREFIX)) {
return next();
}
if (req.method !== 'POST') {
return middlewareResponse.methodNotAllowed(res);
}
devIndicatorServerState.disabledUntil = Date.now() + COOLDOWN_TIME_MS;
return middlewareResponse.noContent(res);
} catch (err) {
Log.error('Failed to disable the dev indicator:', err instanceof Error ? err.message : err);
return middlewareResponse.internalServerError(res);
}
};
}
//# sourceMappingURL=dev-indicator-middleware.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/next-devtools/server/dev-indicator-middleware.ts"],"sourcesContent":["import type { ServerResponse, IncomingMessage } from 'http'\nimport { middlewareResponse } from './middleware-response'\nimport * as Log from '../../build/output/log'\nimport { devIndicatorServerState } from '../../server/dev/dev-indicator-server-state'\n\nconst DISABLE_DEV_INDICATOR_PREFIX = '/__nextjs_disable_dev_indicator'\n\nconst COOLDOWN_TIME_MS = process.env.__NEXT_DEV_INDICATOR_COOLDOWN_MS\n ? parseInt(process.env.__NEXT_DEV_INDICATOR_COOLDOWN_MS)\n : // 1 day from now\n 1000 * 60 * 60 * 24\n\nexport function getDisableDevIndicatorMiddleware() {\n return async function disableDevIndicatorMiddleware(\n req: IncomingMessage,\n res: ServerResponse,\n next: () => void\n ): Promise<void> {\n try {\n const { pathname } = new URL(`http://n${req.url}`)\n\n if (!pathname.startsWith(DISABLE_DEV_INDICATOR_PREFIX)) {\n return next()\n }\n\n if (req.method !== 'POST') {\n return middlewareResponse.methodNotAllowed(res)\n }\n\n devIndicatorServerState.disabledUntil = Date.now() + COOLDOWN_TIME_MS\n\n return middlewareResponse.noContent(res)\n } catch (err) {\n Log.error(\n 'Failed to disable the dev indicator:',\n err instanceof Error ? err.message : err\n )\n return middlewareResponse.internalServerError(res)\n }\n }\n}\n"],"names":["middlewareResponse","Log","devIndicatorServerState","DISABLE_DEV_INDICATOR_PREFIX","COOLDOWN_TIME_MS","process","env","__NEXT_DEV_INDICATOR_COOLDOWN_MS","parseInt","getDisableDevIndicatorMiddleware","disableDevIndicatorMiddleware","req","res","next","pathname","URL","url","startsWith","method","methodNotAllowed","disabledUntil","Date","now","noContent","err","error","Error","message","internalServerError"],"mappings":"AACA,SAASA,kBAAkB,QAAQ,wBAAuB;AAC1D,YAAYC,SAAS,yBAAwB;AAC7C,SAASC,uBAAuB,QAAQ,8CAA6C;AAErF,MAAMC,+BAA+B;AAErC,MAAMC,mBAAmBC,QAAQC,GAAG,CAACC,gCAAgC,GACjEC,SAASH,QAAQC,GAAG,CAACC,gCAAgC,IAErD,OAAO,KAAK,KAAK;AAErB,OAAO,SAASE;IACd,OAAO,eAAeC,8BACpBC,GAAoB,EACpBC,GAAmB,EACnBC,IAAgB;QAEhB,IAAI;YACF,MAAM,EAAEC,QAAQ,EAAE,GAAG,IAAIC,IAAI,CAAC,QAAQ,EAAEJ,IAAIK,GAAG,EAAE;YAEjD,IAAI,CAACF,SAASG,UAAU,CAACd,+BAA+B;gBACtD,OAAOU;YACT;YAEA,IAAIF,IAAIO,MAAM,KAAK,QAAQ;gBACzB,OAAOlB,mBAAmBmB,gBAAgB,CAACP;YAC7C;YAEAV,wBAAwBkB,aAAa,GAAGC,KAAKC,GAAG,KAAKlB;YAErD,OAAOJ,mBAAmBuB,SAAS,CAACX;QACtC,EAAE,OAAOY,KAAK;YACZvB,IAAIwB,KAAK,CACP,wCACAD,eAAeE,QAAQF,IAAIG,OAAO,GAAGH;YAEvC,OAAOxB,mBAAmB4B,mBAAmB,CAAChB;QAChD;IACF;AACF","ignoreList":[0]}

View File

@@ -0,0 +1,54 @@
import { existsSync } from 'fs';
import { readFile, writeFile, mkdir } from 'fs/promises';
import { dirname, join } from 'path';
import { middlewareResponse } from './middleware-response';
import { devToolsConfigSchema } from '../shared/devtools-config-schema';
import { deepMerge } from '../shared/deepmerge';
const DEVTOOLS_CONFIG_FILENAME = 'next-devtools-config.json';
const DEVTOOLS_CONFIG_MIDDLEWARE_ENDPOINT = '/__nextjs_devtools_config';
export function devToolsConfigMiddleware({ distDir, sendUpdateSignal }) {
const configPath = join(distDir, 'cache', DEVTOOLS_CONFIG_FILENAME);
return async function devToolsConfigMiddlewareHandler(req, res, next) {
const { pathname } = new URL(`http://n${req.url}`);
if (pathname !== DEVTOOLS_CONFIG_MIDDLEWARE_ENDPOINT) {
return next();
}
if (req.method !== 'POST') {
return middlewareResponse.methodNotAllowed(res);
}
const currentConfig = await getDevToolsConfig(distDir);
const chunks = [];
for await (const chunk of req){
chunks.push(Buffer.from(chunk));
}
let body = Buffer.concat(chunks).toString('utf8');
try {
body = JSON.parse(body);
} catch (error) {
console.error('[Next.js DevTools] Invalid config body passed:', error);
return middlewareResponse.badRequest(res);
}
const validation = devToolsConfigSchema.safeParse(body);
if (!validation.success) {
console.error('[Next.js DevTools] Invalid config passed:', validation.error.message);
return middlewareResponse.badRequest(res);
}
const newConfig = deepMerge(currentConfig, validation.data);
await writeFile(configPath, JSON.stringify(newConfig, null, 2));
sendUpdateSignal(newConfig);
return middlewareResponse.noContent(res);
};
}
export async function getDevToolsConfig(distDir) {
const configPath = join(distDir, 'cache', DEVTOOLS_CONFIG_FILENAME);
if (!existsSync(configPath)) {
await mkdir(dirname(configPath), {
recursive: true
});
await writeFile(configPath, JSON.stringify({}));
return {};
}
return JSON.parse(await readFile(configPath, 'utf8'));
}
//# sourceMappingURL=devtools-config-middleware.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/next-devtools/server/devtools-config-middleware.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from 'http'\nimport type { DevToolsConfig } from '../dev-overlay/shared'\n\nimport { existsSync } from 'fs'\nimport { readFile, writeFile, mkdir } from 'fs/promises'\nimport { dirname, join } from 'path'\n\nimport { middlewareResponse } from './middleware-response'\nimport { devToolsConfigSchema } from '../shared/devtools-config-schema'\nimport { deepMerge } from '../shared/deepmerge'\n\nconst DEVTOOLS_CONFIG_FILENAME = 'next-devtools-config.json'\nconst DEVTOOLS_CONFIG_MIDDLEWARE_ENDPOINT = '/__nextjs_devtools_config'\n\nexport function devToolsConfigMiddleware({\n distDir,\n sendUpdateSignal,\n}: {\n distDir: string\n sendUpdateSignal: (data: DevToolsConfig) => void\n}) {\n const configPath = join(distDir, 'cache', DEVTOOLS_CONFIG_FILENAME)\n\n return async function devToolsConfigMiddlewareHandler(\n req: IncomingMessage,\n res: ServerResponse,\n next: () => void\n ): Promise<void> {\n const { pathname } = new URL(`http://n${req.url}`)\n\n if (pathname !== DEVTOOLS_CONFIG_MIDDLEWARE_ENDPOINT) {\n return next()\n }\n\n if (req.method !== 'POST') {\n return middlewareResponse.methodNotAllowed(res)\n }\n\n const currentConfig = await getDevToolsConfig(distDir)\n\n const chunks: Buffer[] = []\n for await (const chunk of req) {\n chunks.push(Buffer.from(chunk))\n }\n\n let body = Buffer.concat(chunks).toString('utf8')\n try {\n body = JSON.parse(body)\n } catch (error) {\n console.error('[Next.js DevTools] Invalid config body passed:', error)\n return middlewareResponse.badRequest(res)\n }\n\n const validation = devToolsConfigSchema.safeParse(body)\n if (!validation.success) {\n console.error(\n '[Next.js DevTools] Invalid config passed:',\n validation.error.message\n )\n return middlewareResponse.badRequest(res)\n }\n\n const newConfig = deepMerge(currentConfig, validation.data)\n await writeFile(configPath, JSON.stringify(newConfig, null, 2))\n\n sendUpdateSignal(newConfig)\n\n return middlewareResponse.noContent(res)\n }\n}\n\nexport async function getDevToolsConfig(\n distDir: string\n): Promise<DevToolsConfig> {\n const configPath = join(distDir, 'cache', DEVTOOLS_CONFIG_FILENAME)\n\n if (!existsSync(configPath)) {\n await mkdir(dirname(configPath), { recursive: true })\n await writeFile(configPath, JSON.stringify({}))\n return {}\n }\n\n return JSON.parse(await readFile(configPath, 'utf8'))\n}\n"],"names":["existsSync","readFile","writeFile","mkdir","dirname","join","middlewareResponse","devToolsConfigSchema","deepMerge","DEVTOOLS_CONFIG_FILENAME","DEVTOOLS_CONFIG_MIDDLEWARE_ENDPOINT","devToolsConfigMiddleware","distDir","sendUpdateSignal","configPath","devToolsConfigMiddlewareHandler","req","res","next","pathname","URL","url","method","methodNotAllowed","currentConfig","getDevToolsConfig","chunks","chunk","push","Buffer","from","body","concat","toString","JSON","parse","error","console","badRequest","validation","safeParse","success","message","newConfig","data","stringify","noContent","recursive"],"mappings":"AAGA,SAASA,UAAU,QAAQ,KAAI;AAC/B,SAASC,QAAQ,EAAEC,SAAS,EAAEC,KAAK,QAAQ,cAAa;AACxD,SAASC,OAAO,EAAEC,IAAI,QAAQ,OAAM;AAEpC,SAASC,kBAAkB,QAAQ,wBAAuB;AAC1D,SAASC,oBAAoB,QAAQ,mCAAkC;AACvE,SAASC,SAAS,QAAQ,sBAAqB;AAE/C,MAAMC,2BAA2B;AACjC,MAAMC,sCAAsC;AAE5C,OAAO,SAASC,yBAAyB,EACvCC,OAAO,EACPC,gBAAgB,EAIjB;IACC,MAAMC,aAAaT,KAAKO,SAAS,SAASH;IAE1C,OAAO,eAAeM,gCACpBC,GAAoB,EACpBC,GAAmB,EACnBC,IAAgB;QAEhB,MAAM,EAAEC,QAAQ,EAAE,GAAG,IAAIC,IAAI,CAAC,QAAQ,EAAEJ,IAAIK,GAAG,EAAE;QAEjD,IAAIF,aAAaT,qCAAqC;YACpD,OAAOQ;QACT;QAEA,IAAIF,IAAIM,MAAM,KAAK,QAAQ;YACzB,OAAOhB,mBAAmBiB,gBAAgB,CAACN;QAC7C;QAEA,MAAMO,gBAAgB,MAAMC,kBAAkBb;QAE9C,MAAMc,SAAmB,EAAE;QAC3B,WAAW,MAAMC,SAASX,IAAK;YAC7BU,OAAOE,IAAI,CAACC,OAAOC,IAAI,CAACH;QAC1B;QAEA,IAAII,OAAOF,OAAOG,MAAM,CAACN,QAAQO,QAAQ,CAAC;QAC1C,IAAI;YACFF,OAAOG,KAAKC,KAAK,CAACJ;QACpB,EAAE,OAAOK,OAAO;YACdC,QAAQD,KAAK,CAAC,kDAAkDA;YAChE,OAAO9B,mBAAmBgC,UAAU,CAACrB;QACvC;QAEA,MAAMsB,aAAahC,qBAAqBiC,SAAS,CAACT;QAClD,IAAI,CAACQ,WAAWE,OAAO,EAAE;YACvBJ,QAAQD,KAAK,CACX,6CACAG,WAAWH,KAAK,CAACM,OAAO;YAE1B,OAAOpC,mBAAmBgC,UAAU,CAACrB;QACvC;QAEA,MAAM0B,YAAYnC,UAAUgB,eAAee,WAAWK,IAAI;QAC1D,MAAM1C,UAAUY,YAAYoB,KAAKW,SAAS,CAACF,WAAW,MAAM;QAE5D9B,iBAAiB8B;QAEjB,OAAOrC,mBAAmBwC,SAAS,CAAC7B;IACtC;AACF;AAEA,OAAO,eAAeQ,kBACpBb,OAAe;IAEf,MAAME,aAAaT,KAAKO,SAAS,SAASH;IAE1C,IAAI,CAACT,WAAWc,aAAa;QAC3B,MAAMX,MAAMC,QAAQU,aAAa;YAAEiC,WAAW;QAAK;QACnD,MAAM7C,UAAUY,YAAYoB,KAAKW,SAAS,CAAC,CAAC;QAC5C,OAAO,CAAC;IACV;IAEA,OAAOX,KAAKC,KAAK,CAAC,MAAMlC,SAASa,YAAY;AAC/C","ignoreList":[0]}

View File

@@ -0,0 +1,53 @@
import path from 'path';
import * as fs from 'fs/promises';
import { constants } from 'fs';
import * as Log from '../../../build/output/log';
import { middlewareResponse } from '../middleware-response';
const FONT_PREFIX = '/__nextjs_font/';
const VALID_FONTS = [
'geist-latin-ext.woff2',
'geist-mono-latin-ext.woff2',
'geist-latin.woff2',
'geist-mono-latin.woff2'
];
const FONT_HEADERS = {
'Content-Type': 'font/woff2',
'Cache-Control': 'public, max-age=31536000, immutable'
};
export function getDevOverlayFontMiddleware() {
return async function devOverlayFontMiddleware(req, res, next) {
try {
const { pathname } = new URL(`http://n${req.url}`);
if (!pathname.startsWith(FONT_PREFIX)) {
return next();
}
const fontFile = pathname.replace(FONT_PREFIX, '');
if (!VALID_FONTS.includes(fontFile)) {
return middlewareResponse.notFound(res);
}
const fontPath = path.resolve(__dirname, fontFile);
const fileExists = await checkFileExists(fontPath);
if (!fileExists) {
return middlewareResponse.notFound(res);
}
const fontData = await fs.readFile(fontPath);
Object.entries(FONT_HEADERS).forEach(([key, value])=>{
res.setHeader(key, value);
});
res.end(fontData);
} catch (err) {
Log.error('Failed to serve font:', err instanceof Error ? err.message : err);
return middlewareResponse.internalServerError(res);
}
};
}
async function checkFileExists(filePath) {
try {
await fs.access(filePath, constants.F_OK);
return true;
} catch {
return false;
}
}
//# sourceMappingURL=get-dev-overlay-font-middleware.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/next-devtools/server/font/get-dev-overlay-font-middleware.ts"],"sourcesContent":["import type { ServerResponse, IncomingMessage } from 'http'\nimport path from 'path'\nimport * as fs from 'fs/promises'\nimport { constants } from 'fs'\nimport * as Log from '../../../build/output/log'\nimport { middlewareResponse } from '../middleware-response'\n\nconst FONT_PREFIX = '/__nextjs_font/'\n\nconst VALID_FONTS = [\n 'geist-latin-ext.woff2',\n 'geist-mono-latin-ext.woff2',\n 'geist-latin.woff2',\n 'geist-mono-latin.woff2',\n]\n\nconst FONT_HEADERS = {\n 'Content-Type': 'font/woff2',\n 'Cache-Control': 'public, max-age=31536000, immutable',\n} as const\n\nexport function getDevOverlayFontMiddleware() {\n return async function devOverlayFontMiddleware(\n req: IncomingMessage,\n res: ServerResponse,\n next: () => void\n ): Promise<void> {\n try {\n const { pathname } = new URL(`http://n${req.url}`)\n\n if (!pathname.startsWith(FONT_PREFIX)) {\n return next()\n }\n\n const fontFile = pathname.replace(FONT_PREFIX, '')\n if (!VALID_FONTS.includes(fontFile)) {\n return middlewareResponse.notFound(res)\n }\n\n const fontPath = path.resolve(__dirname, fontFile)\n const fileExists = await checkFileExists(fontPath)\n\n if (!fileExists) {\n return middlewareResponse.notFound(res)\n }\n\n const fontData = await fs.readFile(fontPath)\n Object.entries(FONT_HEADERS).forEach(([key, value]) => {\n res.setHeader(key, value)\n })\n res.end(fontData)\n } catch (err) {\n Log.error(\n 'Failed to serve font:',\n err instanceof Error ? err.message : err\n )\n return middlewareResponse.internalServerError(res)\n }\n }\n}\n\nasync function checkFileExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath, constants.F_OK)\n return true\n } catch {\n return false\n }\n}\n"],"names":["path","fs","constants","Log","middlewareResponse","FONT_PREFIX","VALID_FONTS","FONT_HEADERS","getDevOverlayFontMiddleware","devOverlayFontMiddleware","req","res","next","pathname","URL","url","startsWith","fontFile","replace","includes","notFound","fontPath","resolve","__dirname","fileExists","checkFileExists","fontData","readFile","Object","entries","forEach","key","value","setHeader","end","err","error","Error","message","internalServerError","filePath","access","F_OK"],"mappings":"AACA,OAAOA,UAAU,OAAM;AACvB,YAAYC,QAAQ,cAAa;AACjC,SAASC,SAAS,QAAQ,KAAI;AAC9B,YAAYC,SAAS,4BAA2B;AAChD,SAASC,kBAAkB,QAAQ,yBAAwB;AAE3D,MAAMC,cAAc;AAEpB,MAAMC,cAAc;IAClB;IACA;IACA;IACA;CACD;AAED,MAAMC,eAAe;IACnB,gBAAgB;IAChB,iBAAiB;AACnB;AAEA,OAAO,SAASC;IACd,OAAO,eAAeC,yBACpBC,GAAoB,EACpBC,GAAmB,EACnBC,IAAgB;QAEhB,IAAI;YACF,MAAM,EAAEC,QAAQ,EAAE,GAAG,IAAIC,IAAI,CAAC,QAAQ,EAAEJ,IAAIK,GAAG,EAAE;YAEjD,IAAI,CAACF,SAASG,UAAU,CAACX,cAAc;gBACrC,OAAOO;YACT;YAEA,MAAMK,WAAWJ,SAASK,OAAO,CAACb,aAAa;YAC/C,IAAI,CAACC,YAAYa,QAAQ,CAACF,WAAW;gBACnC,OAAOb,mBAAmBgB,QAAQ,CAACT;YACrC;YAEA,MAAMU,WAAWrB,KAAKsB,OAAO,CAACC,WAAWN;YACzC,MAAMO,aAAa,MAAMC,gBAAgBJ;YAEzC,IAAI,CAACG,YAAY;gBACf,OAAOpB,mBAAmBgB,QAAQ,CAACT;YACrC;YAEA,MAAMe,WAAW,MAAMzB,GAAG0B,QAAQ,CAACN;YACnCO,OAAOC,OAAO,CAACtB,cAAcuB,OAAO,CAAC,CAAC,CAACC,KAAKC,MAAM;gBAChDrB,IAAIsB,SAAS,CAACF,KAAKC;YACrB;YACArB,IAAIuB,GAAG,CAACR;QACV,EAAE,OAAOS,KAAK;YACZhC,IAAIiC,KAAK,CACP,yBACAD,eAAeE,QAAQF,IAAIG,OAAO,GAAGH;YAEvC,OAAO/B,mBAAmBmC,mBAAmB,CAAC5B;QAChD;IACF;AACF;AAEA,eAAec,gBAAgBe,QAAgB;IAC7C,IAAI;QACF,MAAMvC,GAAGwC,MAAM,CAACD,UAAUtC,UAAUwC,IAAI;QACxC,OAAO;IACT,EAAE,OAAM;QACN,OAAO;IACT;AACF","ignoreList":[0]}

View File

@@ -0,0 +1,27 @@
import { eventErrorFeedback } from '../../telemetry/events/error-feedback';
import { middlewareResponse } from './middleware-response';
// Handles HTTP requests to /__nextjs_error_feedback endpoint for collecting user feedback on error messages
export function getNextErrorFeedbackMiddleware(telemetry) {
return async function(req, res, next) {
const { pathname, searchParams } = new URL(`http://n${req.url}`);
if (pathname !== '/__nextjs_error_feedback') {
return next();
}
try {
const errorCode = searchParams.get('errorCode');
const wasHelpful = searchParams.get('wasHelpful');
if (!errorCode || !wasHelpful) {
return middlewareResponse.badRequest(res);
}
await telemetry.record(eventErrorFeedback({
errorCode,
wasHelpful: wasHelpful === 'true'
}));
return middlewareResponse.noContent(res);
} catch (error) {
return middlewareResponse.internalServerError(res);
}
};
}
//# sourceMappingURL=get-next-error-feedback-middleware.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/next-devtools/server/get-next-error-feedback-middleware.ts"],"sourcesContent":["import { eventErrorFeedback } from '../../telemetry/events/error-feedback'\nimport { middlewareResponse } from './middleware-response'\nimport type { ServerResponse, IncomingMessage } from 'http'\nimport type { Telemetry } from '../../telemetry/storage'\n\n// Handles HTTP requests to /__nextjs_error_feedback endpoint for collecting user feedback on error messages\nexport function getNextErrorFeedbackMiddleware(telemetry: Telemetry) {\n return async function (\n req: IncomingMessage,\n res: ServerResponse,\n next: () => void\n ): Promise<void> {\n const { pathname, searchParams } = new URL(`http://n${req.url}`)\n\n if (pathname !== '/__nextjs_error_feedback') {\n return next()\n }\n\n try {\n const errorCode = searchParams.get('errorCode')\n const wasHelpful = searchParams.get('wasHelpful')\n\n if (!errorCode || !wasHelpful) {\n return middlewareResponse.badRequest(res)\n }\n\n await telemetry.record(\n eventErrorFeedback({\n errorCode,\n wasHelpful: wasHelpful === 'true',\n })\n )\n\n return middlewareResponse.noContent(res)\n } catch (error) {\n return middlewareResponse.internalServerError(res)\n }\n }\n}\n"],"names":["eventErrorFeedback","middlewareResponse","getNextErrorFeedbackMiddleware","telemetry","req","res","next","pathname","searchParams","URL","url","errorCode","get","wasHelpful","badRequest","record","noContent","error","internalServerError"],"mappings":"AAAA,SAASA,kBAAkB,QAAQ,wCAAuC;AAC1E,SAASC,kBAAkB,QAAQ,wBAAuB;AAI1D,4GAA4G;AAC5G,OAAO,SAASC,+BAA+BC,SAAoB;IACjE,OAAO,eACLC,GAAoB,EACpBC,GAAmB,EACnBC,IAAgB;QAEhB,MAAM,EAAEC,QAAQ,EAAEC,YAAY,EAAE,GAAG,IAAIC,IAAI,CAAC,QAAQ,EAAEL,IAAIM,GAAG,EAAE;QAE/D,IAAIH,aAAa,4BAA4B;YAC3C,OAAOD;QACT;QAEA,IAAI;YACF,MAAMK,YAAYH,aAAaI,GAAG,CAAC;YACnC,MAAMC,aAAaL,aAAaI,GAAG,CAAC;YAEpC,IAAI,CAACD,aAAa,CAACE,YAAY;gBAC7B,OAAOZ,mBAAmBa,UAAU,CAACT;YACvC;YAEA,MAAMF,UAAUY,MAAM,CACpBf,mBAAmB;gBACjBW;gBACAE,YAAYA,eAAe;YAC7B;YAGF,OAAOZ,mBAAmBe,SAAS,CAACX;QACtC,EAAE,OAAOY,OAAO;YACd,OAAOhB,mBAAmBiB,mBAAmB,CAACb;QAChD;IACF;AACF","ignoreList":[0]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,39 @@
import { inspect } from 'util';
export const middlewareResponse = {
noContent (res) {
res.statusCode = 204;
res.end('No Content');
},
badRequest (res, reason) {
res.statusCode = 400;
if (reason !== undefined) {
res.setHeader('Content-Type', 'text/plain');
res.end(reason);
} else {
res.end();
}
},
notFound (res) {
res.statusCode = 404;
res.end('Not Found');
},
methodNotAllowed (res) {
res.statusCode = 405;
res.end('Method Not Allowed');
},
internalServerError (res, error) {
res.statusCode = 500;
res.setHeader('Content-Type', 'text/plain');
res.end(error !== undefined ? inspect(error, {
colors: false
}) : 'Internal Server Error');
},
json (res, data) {
res.setHeader('Content-Type', 'application/json').end(Buffer.from(JSON.stringify(data)));
},
jsonString (res, data) {
res.setHeader('Content-Type', 'application/json').end(Buffer.from(data));
}
};
//# sourceMappingURL=middleware-response.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/next-devtools/server/middleware-response.ts"],"sourcesContent":["import type { ServerResponse } from 'http'\nimport { inspect } from 'util'\n\nexport const middlewareResponse = {\n noContent(res: ServerResponse) {\n res.statusCode = 204\n res.end('No Content')\n },\n badRequest(res: ServerResponse, reason?: string) {\n res.statusCode = 400\n if (reason !== undefined) {\n res.setHeader('Content-Type', 'text/plain')\n res.end(reason)\n } else {\n res.end()\n }\n },\n notFound(res: ServerResponse) {\n res.statusCode = 404\n res.end('Not Found')\n },\n methodNotAllowed(res: ServerResponse) {\n res.statusCode = 405\n res.end('Method Not Allowed')\n },\n internalServerError(res: ServerResponse, error?: unknown) {\n res.statusCode = 500\n res.setHeader('Content-Type', 'text/plain')\n res.end(\n error !== undefined\n ? inspect(error, { colors: false })\n : 'Internal Server Error'\n )\n },\n json(res: ServerResponse, data: any) {\n res\n .setHeader('Content-Type', 'application/json')\n .end(Buffer.from(JSON.stringify(data)))\n },\n jsonString(res: ServerResponse, data: string) {\n res.setHeader('Content-Type', 'application/json').end(Buffer.from(data))\n },\n}\n"],"names":["inspect","middlewareResponse","noContent","res","statusCode","end","badRequest","reason","undefined","setHeader","notFound","methodNotAllowed","internalServerError","error","colors","json","data","Buffer","from","JSON","stringify","jsonString"],"mappings":"AACA,SAASA,OAAO,QAAQ,OAAM;AAE9B,OAAO,MAAMC,qBAAqB;IAChCC,WAAUC,GAAmB;QAC3BA,IAAIC,UAAU,GAAG;QACjBD,IAAIE,GAAG,CAAC;IACV;IACAC,YAAWH,GAAmB,EAAEI,MAAe;QAC7CJ,IAAIC,UAAU,GAAG;QACjB,IAAIG,WAAWC,WAAW;YACxBL,IAAIM,SAAS,CAAC,gBAAgB;YAC9BN,IAAIE,GAAG,CAACE;QACV,OAAO;YACLJ,IAAIE,GAAG;QACT;IACF;IACAK,UAASP,GAAmB;QAC1BA,IAAIC,UAAU,GAAG;QACjBD,IAAIE,GAAG,CAAC;IACV;IACAM,kBAAiBR,GAAmB;QAClCA,IAAIC,UAAU,GAAG;QACjBD,IAAIE,GAAG,CAAC;IACV;IACAO,qBAAoBT,GAAmB,EAAEU,KAAe;QACtDV,IAAIC,UAAU,GAAG;QACjBD,IAAIM,SAAS,CAAC,gBAAgB;QAC9BN,IAAIE,GAAG,CACLQ,UAAUL,YACNR,QAAQa,OAAO;YAAEC,QAAQ;QAAM,KAC/B;IAER;IACAC,MAAKZ,GAAmB,EAAEa,IAAS;QACjCb,IACGM,SAAS,CAAC,gBAAgB,oBAC1BJ,GAAG,CAACY,OAAOC,IAAI,CAACC,KAAKC,SAAS,CAACJ;IACpC;IACAK,YAAWlB,GAAmB,EAAEa,IAAY;QAC1Cb,IAAIM,SAAS,CAAC,gBAAgB,oBAAoBJ,GAAG,CAACY,OAAOC,IAAI,CAACF;IACpE;AACF,EAAC","ignoreList":[0]}

View File

@@ -0,0 +1,62 @@
import { RESTART_EXIT_CODE } from '../../server/lib/utils';
import { middlewareResponse } from './middleware-response';
import { invalidateFileSystemCache as invalidateWebpackFileSystemCache } from '../../build/webpack/cache-invalidation';
const EVENT_DEV_OVERLAY_RESTART_SERVER = 'DEV_OVERLAY_RESTART_SERVER';
export function getRestartDevServerMiddleware({ telemetry, turbopackProject, webpackCacheDirectories }) {
/**
* Some random value between 1 and Number.MAX_SAFE_INTEGER (inclusive). The same value is returned
* on every call to `__nextjs_server_status` until the server is restarted.
*
* Can be used to determine if two server status responses are from the same process or a
* different (restarted) process.
*/ const executionId = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER) + 1;
async function handleRestartRequest(req, res, searchParams) {
if (req.method !== 'POST') {
return middlewareResponse.methodNotAllowed(res);
}
const shouldInvalidateFileSystemCache = searchParams.has('invalidateFileSystemCache');
if (shouldInvalidateFileSystemCache) {
if (webpackCacheDirectories != null) {
await Promise.all(Array.from(webpackCacheDirectories).map(invalidateWebpackFileSystemCache));
}
if (turbopackProject != null) {
await turbopackProject.invalidateFileSystemCache();
}
}
telemetry.record({
eventName: EVENT_DEV_OVERLAY_RESTART_SERVER,
payload: {
invalidateFileSystemCache: shouldInvalidateFileSystemCache
}
});
// TODO: Use flushDetached
await telemetry.flush();
// do this async to try to give the response a chance to send
// it's not really important if it doesn't though
setTimeout(()=>{
process.exit(RESTART_EXIT_CODE);
}, 0);
return middlewareResponse.noContent(res);
}
async function handleServerStatus(req, res) {
if (req.method !== 'GET') {
return middlewareResponse.methodNotAllowed(res);
}
return middlewareResponse.json(res, {
executionId
});
}
return async function(req, res, next) {
const { pathname, searchParams } = new URL(`http://n${req.url}`);
switch(pathname){
case '/__nextjs_restart_dev':
return await handleRestartRequest(req, res, searchParams);
case '/__nextjs_server_status':
return await handleServerStatus(req, res);
default:
return next();
}
};
}
//# sourceMappingURL=restart-dev-server-middleware.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,35 @@
import { codeFrameColumns } from 'next/dist/compiled/babel/code-frame';
import isInternal from '../../shared/lib/is-internal';
import { ignoreListAnonymousStackFramesIfSandwiched as ignoreListAnonymousStackFramesIfSandwichedGeneric } from '../../server/lib/source-maps';
export function ignoreListAnonymousStackFramesIfSandwiched(responses) {
ignoreListAnonymousStackFramesIfSandwichedGeneric(responses, (response)=>{
return response.status === 'fulfilled' && response.value.originalStackFrame !== null && response.value.originalStackFrame.file === '<anonymous>';
}, (response)=>{
return response.status === 'fulfilled' && response.value.originalStackFrame !== null && response.value.originalStackFrame.ignored === true;
}, (response)=>{
return response.status === 'fulfilled' && response.value.originalStackFrame !== null ? response.value.originalStackFrame.methodName : '';
}, (response)=>{
;
response.value.originalStackFrame.ignored = true;
});
}
/**
* It looks up the code frame of the traced source.
* @note It ignores Next.js/React internals, as these can often be huge bundled files.
*/ export function getOriginalCodeFrame(frame, source, colors = process.stdout.isTTY) {
if (!source || isInternal(frame.file)) {
return null;
}
return codeFrameColumns(source, {
start: {
// 1-based, but -1 means start line without highlighting
line: frame.line1 ?? -1,
// 1-based, but 0 means whole line without column highlighting
column: frame.column1 ?? 0
}
}, {
forceColor: colors
});
}
//# sourceMappingURL=shared.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/next-devtools/server/shared.ts"],"sourcesContent":["import { codeFrameColumns } from 'next/dist/compiled/babel/code-frame'\nimport isInternal from '../../shared/lib/is-internal'\nimport type { StackFrame } from '../../server/lib/parse-stack'\nimport { ignoreListAnonymousStackFramesIfSandwiched as ignoreListAnonymousStackFramesIfSandwichedGeneric } from '../../server/lib/source-maps'\n\nexport type { StackFrame }\n\nexport interface IgnorableStackFrame extends StackFrame {\n ignored: boolean\n}\n\nexport interface OriginalStackFramesRequest {\n frames: readonly StackFrame[]\n isServer: boolean\n isEdgeServer: boolean\n isAppDirectory: boolean\n}\n\nexport type OriginalStackFramesResponse = OriginalStackFrameResponseResult[]\n\nexport type OriginalStackFrameResponseResult =\n PromiseSettledResult<OriginalStackFrameResponse>\n\nexport interface OriginalStackFrameResponse {\n originalStackFrame: (StackFrame & { ignored: boolean }) | null\n originalCodeFrame: string | null\n}\n\nexport function ignoreListAnonymousStackFramesIfSandwiched(\n responses: OriginalStackFramesResponse\n): void {\n ignoreListAnonymousStackFramesIfSandwichedGeneric(\n responses,\n (response) => {\n return (\n response.status === 'fulfilled' &&\n response.value.originalStackFrame !== null &&\n response.value.originalStackFrame.file === '<anonymous>'\n )\n },\n (response) => {\n return (\n response.status === 'fulfilled' &&\n response.value.originalStackFrame !== null &&\n response.value.originalStackFrame.ignored === true\n )\n },\n (response) => {\n return response.status === 'fulfilled' &&\n response.value.originalStackFrame !== null\n ? response.value.originalStackFrame.methodName\n : ''\n },\n (response) => {\n ;(\n response as PromiseFulfilledResult<OriginalStackFrameResponse>\n ).value.originalStackFrame!.ignored = true\n }\n )\n}\n\n/**\n * It looks up the code frame of the traced source.\n * @note It ignores Next.js/React internals, as these can often be huge bundled files.\n */\nexport function getOriginalCodeFrame(\n frame: IgnorableStackFrame,\n source: string | null,\n colors: boolean = process.stdout.isTTY\n): string | null {\n if (!source || isInternal(frame.file)) {\n return null\n }\n\n return codeFrameColumns(\n source,\n {\n start: {\n // 1-based, but -1 means start line without highlighting\n line: frame.line1 ?? -1,\n // 1-based, but 0 means whole line without column highlighting\n column: frame.column1 ?? 0,\n },\n },\n { forceColor: colors }\n )\n}\n"],"names":["codeFrameColumns","isInternal","ignoreListAnonymousStackFramesIfSandwiched","ignoreListAnonymousStackFramesIfSandwichedGeneric","responses","response","status","value","originalStackFrame","file","ignored","methodName","getOriginalCodeFrame","frame","source","colors","process","stdout","isTTY","start","line","line1","column","column1","forceColor"],"mappings":"AAAA,SAASA,gBAAgB,QAAQ,sCAAqC;AACtE,OAAOC,gBAAgB,+BAA8B;AAErD,SAASC,8CAA8CC,iDAAiD,QAAQ,+BAA8B;AAyB9I,OAAO,SAASD,2CACdE,SAAsC;IAEtCD,kDACEC,WACA,CAACC;QACC,OACEA,SAASC,MAAM,KAAK,eACpBD,SAASE,KAAK,CAACC,kBAAkB,KAAK,QACtCH,SAASE,KAAK,CAACC,kBAAkB,CAACC,IAAI,KAAK;IAE/C,GACA,CAACJ;QACC,OACEA,SAASC,MAAM,KAAK,eACpBD,SAASE,KAAK,CAACC,kBAAkB,KAAK,QACtCH,SAASE,KAAK,CAACC,kBAAkB,CAACE,OAAO,KAAK;IAElD,GACA,CAACL;QACC,OAAOA,SAASC,MAAM,KAAK,eACzBD,SAASE,KAAK,CAACC,kBAAkB,KAAK,OACpCH,SAASE,KAAK,CAACC,kBAAkB,CAACG,UAAU,GAC5C;IACN,GACA,CAACN;;QAEGA,SACAE,KAAK,CAACC,kBAAkB,CAAEE,OAAO,GAAG;IACxC;AAEJ;AAEA;;;CAGC,GACD,OAAO,SAASE,qBACdC,KAA0B,EAC1BC,MAAqB,EACrBC,SAAkBC,QAAQC,MAAM,CAACC,KAAK;IAEtC,IAAI,CAACJ,UAAUb,WAAWY,MAAMJ,IAAI,GAAG;QACrC,OAAO;IACT;IAEA,OAAOT,iBACLc,QACA;QACEK,OAAO;YACL,wDAAwD;YACxDC,MAAMP,MAAMQ,KAAK,IAAI,CAAC;YACtB,8DAA8D;YAC9DC,QAAQT,MAAMU,OAAO,IAAI;QAC3B;IACF,GACA;QAAEC,YAAYT;IAAO;AAEzB","ignoreList":[0]}

View File

@@ -0,0 +1,19 @@
// To distinguish from React error.digest, we use a different symbol here to determine if the error is from console.error or unhandled promise rejection.
const digestSym = Symbol.for('next.console.error.digest');
export function createConsoleError(message, environmentName) {
const error = typeof message === 'string' ? Object.defineProperty(new Error(message), "__NEXT_ERROR_CODE", {
value: "E394",
enumerable: false,
configurable: true
}) : message;
error[digestSym] = 'NEXT_CONSOLE_ERROR';
if (environmentName && !error.environmentName) {
error.environmentName = environmentName;
}
return error;
}
export const isConsoleError = (error)=>{
return error && error[digestSym] === 'NEXT_CONSOLE_ERROR';
};
//# sourceMappingURL=console-error.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/next-devtools/shared/console-error.ts"],"sourcesContent":["// To distinguish from React error.digest, we use a different symbol here to determine if the error is from console.error or unhandled promise rejection.\nconst digestSym = Symbol.for('next.console.error.digest')\n\n// Represent non Error shape unhandled promise rejections or console.error errors.\n// Those errors will be captured and displayed in Error Overlay.\ntype ConsoleError = Error & {\n [digestSym]: 'NEXT_CONSOLE_ERROR'\n environmentName: string\n}\n\nexport function createConsoleError(\n message: string | Error,\n environmentName?: string | null\n): ConsoleError {\n const error = (\n typeof message === 'string' ? new Error(message) : message\n ) as ConsoleError\n error[digestSym] = 'NEXT_CONSOLE_ERROR'\n\n if (environmentName && !error.environmentName) {\n error.environmentName = environmentName\n }\n\n return error\n}\n\nexport const isConsoleError = (error: any): error is ConsoleError => {\n return error && error[digestSym] === 'NEXT_CONSOLE_ERROR'\n}\n"],"names":["digestSym","Symbol","for","createConsoleError","message","environmentName","error","Error","isConsoleError"],"mappings":"AAAA,yJAAyJ;AACzJ,MAAMA,YAAYC,OAAOC,GAAG,CAAC;AAS7B,OAAO,SAASC,mBACdC,OAAuB,EACvBC,eAA+B;IAE/B,MAAMC,QACJ,OAAOF,YAAY,WAAW,qBAAkB,CAAlB,IAAIG,MAAMH,UAAV,qBAAA;eAAA;oBAAA;sBAAA;IAAiB,KAAIA;IAErDE,KAAK,CAACN,UAAU,GAAG;IAEnB,IAAIK,mBAAmB,CAACC,MAAMD,eAAe,EAAE;QAC7CC,MAAMD,eAAe,GAAGA;IAC1B;IAEA,OAAOC;AACT;AAEA,OAAO,MAAME,iBAAiB,CAACF;IAC7B,OAAOA,SAASA,KAAK,CAACN,UAAU,KAAK;AACvC,EAAC","ignoreList":[0]}

View File

@@ -0,0 +1,25 @@
export function deepMerge(target, source) {
if (!source || typeof source !== 'object' || Array.isArray(source)) {
return source;
}
if (!target || typeof target !== 'object' || Array.isArray(target)) {
return source;
}
const result = {
...target
};
for(const key in source){
const sourceValue = source[key];
const targetValue = target[key];
if (sourceValue !== undefined) {
if (sourceValue && typeof sourceValue === 'object' && !Array.isArray(sourceValue) && targetValue && typeof targetValue === 'object' && !Array.isArray(targetValue)) {
result[key] = deepMerge(targetValue, sourceValue);
} else {
result[key] = sourceValue;
}
}
}
return result;
}
//# sourceMappingURL=deepmerge.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/next-devtools/shared/deepmerge.ts"],"sourcesContent":["export function deepMerge(target: any, source: any): any {\n if (!source || typeof source !== 'object' || Array.isArray(source)) {\n return source\n }\n\n if (!target || typeof target !== 'object' || Array.isArray(target)) {\n return source\n }\n\n const result = { ...target }\n\n for (const key in source) {\n const sourceValue = source[key]\n const targetValue = target[key]\n\n if (sourceValue !== undefined) {\n if (\n sourceValue &&\n typeof sourceValue === 'object' &&\n !Array.isArray(sourceValue) &&\n targetValue &&\n typeof targetValue === 'object' &&\n !Array.isArray(targetValue)\n ) {\n result[key] = deepMerge(targetValue, sourceValue)\n } else {\n result[key] = sourceValue\n }\n }\n }\n\n return result\n}\n"],"names":["deepMerge","target","source","Array","isArray","result","key","sourceValue","targetValue","undefined"],"mappings":"AAAA,OAAO,SAASA,UAAUC,MAAW,EAAEC,MAAW;IAChD,IAAI,CAACA,UAAU,OAAOA,WAAW,YAAYC,MAAMC,OAAO,CAACF,SAAS;QAClE,OAAOA;IACT;IAEA,IAAI,CAACD,UAAU,OAAOA,WAAW,YAAYE,MAAMC,OAAO,CAACH,SAAS;QAClE,OAAOC;IACT;IAEA,MAAMG,SAAS;QAAE,GAAGJ,MAAM;IAAC;IAE3B,IAAK,MAAMK,OAAOJ,OAAQ;QACxB,MAAMK,cAAcL,MAAM,CAACI,IAAI;QAC/B,MAAME,cAAcP,MAAM,CAACK,IAAI;QAE/B,IAAIC,gBAAgBE,WAAW;YAC7B,IACEF,eACA,OAAOA,gBAAgB,YACvB,CAACJ,MAAMC,OAAO,CAACG,gBACfC,eACA,OAAOA,gBAAgB,YACvB,CAACL,MAAMC,OAAO,CAACI,cACf;gBACAH,MAAM,CAACC,IAAI,GAAGN,UAAUQ,aAAaD;YACvC,OAAO;gBACLF,MAAM,CAACC,IAAI,GAAGC;YAChB;QACF;IACF;IAEA,OAAOF;AACT","ignoreList":[0]}

View File

@@ -0,0 +1,29 @@
import { z } from 'next/dist/compiled/zod';
export const devToolsConfigSchema = z.object({
theme: z.enum([
'light',
'dark',
'system'
]).optional(),
disableDevIndicator: z.boolean().optional(),
devToolsPosition: z.enum([
'top-left',
'top-right',
'bottom-left',
'bottom-right'
]).optional(),
devToolsPanelPosition: z.record(z.string(), z.enum([
'top-left',
'top-right',
'bottom-left',
'bottom-right'
])).optional(),
devToolsPanelSize: z.record(z.string(), z.object({
width: z.number(),
height: z.number()
})).optional(),
scale: z.number().optional(),
hideShortcut: z.string().nullable().optional()
});
//# sourceMappingURL=devtools-config-schema.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/next-devtools/shared/devtools-config-schema.ts"],"sourcesContent":["import type { DevToolsConfig } from '../dev-overlay/shared'\nimport { z } from 'next/dist/compiled/zod'\n\nexport const devToolsConfigSchema: z.ZodType<DevToolsConfig> = z.object({\n theme: z.enum(['light', 'dark', 'system']).optional(),\n disableDevIndicator: z.boolean().optional(),\n devToolsPosition: z\n .enum(['top-left', 'top-right', 'bottom-left', 'bottom-right'])\n .optional(),\n devToolsPanelPosition: z\n .record(\n z.string(),\n z.enum(['top-left', 'top-right', 'bottom-left', 'bottom-right'])\n )\n .optional(),\n devToolsPanelSize: z\n .record(z.string(), z.object({ width: z.number(), height: z.number() }))\n .optional(),\n scale: z.number().optional(),\n hideShortcut: z.string().nullable().optional(),\n})\n"],"names":["z","devToolsConfigSchema","object","theme","enum","optional","disableDevIndicator","boolean","devToolsPosition","devToolsPanelPosition","record","string","devToolsPanelSize","width","number","height","scale","hideShortcut","nullable"],"mappings":"AACA,SAASA,CAAC,QAAQ,yBAAwB;AAE1C,OAAO,MAAMC,uBAAkDD,EAAEE,MAAM,CAAC;IACtEC,OAAOH,EAAEI,IAAI,CAAC;QAAC;QAAS;QAAQ;KAAS,EAAEC,QAAQ;IACnDC,qBAAqBN,EAAEO,OAAO,GAAGF,QAAQ;IACzCG,kBAAkBR,EACfI,IAAI,CAAC;QAAC;QAAY;QAAa;QAAe;KAAe,EAC7DC,QAAQ;IACXI,uBAAuBT,EACpBU,MAAM,CACLV,EAAEW,MAAM,IACRX,EAAEI,IAAI,CAAC;QAAC;QAAY;QAAa;QAAe;KAAe,GAEhEC,QAAQ;IACXO,mBAAmBZ,EAChBU,MAAM,CAACV,EAAEW,MAAM,IAAIX,EAAEE,MAAM,CAAC;QAAEW,OAAOb,EAAEc,MAAM;QAAIC,QAAQf,EAAEc,MAAM;IAAG,IACpET,QAAQ;IACXW,OAAOhB,EAAEc,MAAM,GAAGT,QAAQ;IAC1BY,cAAcjB,EAAEW,MAAM,GAAGO,QAAQ,GAAGb,QAAQ;AAC9C,GAAE","ignoreList":[0]}

View File

@@ -0,0 +1,29 @@
export const UNDEFINED_MARKER = '__next_tagged_undefined';
// Based on https://github.com/facebook/react/blob/28dc0776be2e1370fe217549d32aee2519f0cf05/packages/react-server/src/ReactFlightServer.js#L248
export function patchConsoleMethod(methodName, wrapper) {
const descriptor = Object.getOwnPropertyDescriptor(console, methodName);
if (descriptor && (descriptor.configurable || descriptor.writable) && typeof descriptor.value === 'function') {
const originalMethod = descriptor.value;
const originalName = Object.getOwnPropertyDescriptor(originalMethod, 'name');
const wrapperMethod = function(...args) {
wrapper(methodName, ...args);
originalMethod.apply(this, args);
};
if (originalName) {
Object.defineProperty(wrapperMethod, 'name', originalName);
}
Object.defineProperty(console, methodName, {
value: wrapperMethod
});
return ()=>{
Object.defineProperty(console, methodName, {
value: originalMethod,
writable: descriptor.writable,
configurable: descriptor.configurable
});
};
}
return ()=>{};
}
//# sourceMappingURL=forward-logs-shared.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/next-devtools/shared/forward-logs-shared.ts"],"sourcesContent":["export type LogMethod =\n | 'log'\n | 'info'\n | 'debug'\n | 'table'\n | 'error'\n | 'assert'\n | 'dir'\n | 'dirxml'\n | 'group'\n | 'groupCollapsed'\n | 'groupEnd'\n | 'trace'\n | 'warn'\n\nexport type ConsoleEntry<T> = {\n kind: 'console'\n method: LogMethod\n consoleMethodStack: string | null\n args: Array<\n | {\n kind: 'arg'\n data: T\n }\n | {\n kind: 'formatted-error-arg'\n prefix: string\n stack: string\n }\n >\n}\n\nexport type ConsoleErrorEntry<T> = {\n kind: 'any-logged-error'\n method: 'error'\n consoleErrorStack: string\n args: Array<\n | {\n kind: 'arg'\n data: T\n isRejectionMessage?: boolean\n }\n | {\n kind: 'formatted-error-arg'\n prefix: string\n stack: string | null\n }\n >\n}\n\nexport type FormattedErrorEntry = {\n kind: 'formatted-error'\n prefix: string\n stack: string\n method: 'error'\n}\n\nexport type ClientLogEntry =\n | ConsoleEntry<unknown>\n | ConsoleErrorEntry<unknown>\n | FormattedErrorEntry\nexport type ServerLogEntry =\n | ConsoleEntry<string>\n | ConsoleErrorEntry<string>\n | FormattedErrorEntry\n\nexport const UNDEFINED_MARKER = '__next_tagged_undefined'\n\n// Based on https://github.com/facebook/react/blob/28dc0776be2e1370fe217549d32aee2519f0cf05/packages/react-server/src/ReactFlightServer.js#L248\nexport function patchConsoleMethod<T extends keyof Console>(\n methodName: T,\n wrapper: (\n methodName: T,\n ...args: Console[T] extends (...args: infer P) => any ? P : never[]\n ) => void\n): () => void {\n const descriptor = Object.getOwnPropertyDescriptor(console, methodName)\n if (\n descriptor &&\n (descriptor.configurable || descriptor.writable) &&\n typeof descriptor.value === 'function'\n ) {\n const originalMethod = descriptor.value as Console[T] extends (\n ...args: any[]\n ) => any\n ? Console[T]\n : never\n const originalName = Object.getOwnPropertyDescriptor(originalMethod, 'name')\n const wrapperMethod = function (\n this: typeof console,\n ...args: Console[T] extends (...args: infer P) => any ? P : never[]\n ) {\n wrapper(methodName, ...args)\n\n originalMethod.apply(this, args)\n }\n if (originalName) {\n Object.defineProperty(wrapperMethod, 'name', originalName)\n }\n Object.defineProperty(console, methodName, {\n value: wrapperMethod,\n })\n\n return () => {\n Object.defineProperty(console, methodName, {\n value: originalMethod,\n writable: descriptor.writable,\n configurable: descriptor.configurable,\n })\n }\n }\n\n return () => {}\n}\n"],"names":["UNDEFINED_MARKER","patchConsoleMethod","methodName","wrapper","descriptor","Object","getOwnPropertyDescriptor","console","configurable","writable","value","originalMethod","originalName","wrapperMethod","args","apply","defineProperty"],"mappings":"AAkEA,OAAO,MAAMA,mBAAmB,0BAAyB;AAEzD,+IAA+I;AAC/I,OAAO,SAASC,mBACdC,UAAa,EACbC,OAGS;IAET,MAAMC,aAAaC,OAAOC,wBAAwB,CAACC,SAASL;IAC5D,IACEE,cACCA,CAAAA,WAAWI,YAAY,IAAIJ,WAAWK,QAAQ,AAAD,KAC9C,OAAOL,WAAWM,KAAK,KAAK,YAC5B;QACA,MAAMC,iBAAiBP,WAAWM,KAAK;QAKvC,MAAME,eAAeP,OAAOC,wBAAwB,CAACK,gBAAgB;QACrE,MAAME,gBAAgB,SAEpB,GAAGC,IAAgE;YAEnEX,QAAQD,eAAeY;YAEvBH,eAAeI,KAAK,CAAC,IAAI,EAAED;QAC7B;QACA,IAAIF,cAAc;YAChBP,OAAOW,cAAc,CAACH,eAAe,QAAQD;QAC/C;QACAP,OAAOW,cAAc,CAACT,SAASL,YAAY;YACzCQ,OAAOG;QACT;QAEA,OAAO;YACLR,OAAOW,cAAc,CAACT,SAASL,YAAY;gBACzCQ,OAAOC;gBACPF,UAAUL,WAAWK,QAAQ;gBAC7BD,cAAcJ,WAAWI,YAAY;YACvC;QACF;IACF;IAEA,OAAO,KAAO;AAChB","ignoreList":[0]}

View File

@@ -0,0 +1,3 @@
export { };
//# sourceMappingURL=hydration-error.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/next-devtools/shared/hydration-error.ts"],"sourcesContent":["export type HydrationErrorState = {\n // Hydration warning template format: <message> <serverContent> <clientContent>\n warning?: string\n reactOutputComponentDiff?: string\n}\n"],"names":[],"mappings":"AAAA,WAIC","ignoreList":[0]}

View File

@@ -0,0 +1,35 @@
import isError from '../../lib/is-error';
export function isHydrationError(error) {
return isError(error) && (error.message === 'Hydration failed because the initial UI does not match what was rendered on the server.' || error.message === 'Text content does not match server-rendered HTML.');
}
export function isHydrationWarning(message) {
return isHtmlTagsWarning(message) || isTextInTagsMismatchWarning(message) || isTextWarning(message);
}
// https://github.com/facebook/react/blob/main/packages/react-dom/src/__tests__/ReactDOMHydrationDiff-test.js used as a reference
const htmlTagsWarnings = new Set([
'Warning: Expected server HTML to contain a matching <%s> in <%s>.%s',
'Warning: Did not expect server HTML to contain a <%s> in <%s>.%s'
]);
const textAndTagsMismatchWarnings = new Set([
'Warning: Expected server HTML to contain a matching text node for "%s" in <%s>.%s',
'Warning: Did not expect server HTML to contain the text node "%s" in <%s>.%s'
]);
const textWarnings = new Set([
'Warning: Text content did not match. Server: "%s" Client: "%s"%s'
]);
export const getHydrationWarningType = (message)=>{
if (typeof message !== 'string') {
// TODO: Doesn't make sense to treat no message as a hydration error message.
// We should bail out somewhere earlier.
return 'text';
}
const normalizedMessage = message.startsWith('Warning: ') ? message : `Warning: ${message}`;
if (isHtmlTagsWarning(normalizedMessage)) return 'tag';
if (isTextInTagsMismatchWarning(normalizedMessage)) return 'text-in-tag';
return 'text';
};
const isHtmlTagsWarning = (message)=>typeof message === 'string' && htmlTagsWarnings.has(message);
const isTextInTagsMismatchWarning = (msg)=>typeof msg === 'string' && textAndTagsMismatchWarnings.has(msg);
const isTextWarning = (msg)=>typeof msg === 'string' && textWarnings.has(msg);
//# sourceMappingURL=react-18-hydration-error.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/next-devtools/shared/react-18-hydration-error.ts"],"sourcesContent":["import isError from '../../lib/is-error'\n\nexport function isHydrationError(error: unknown): boolean {\n return (\n isError(error) &&\n (error.message ===\n 'Hydration failed because the initial UI does not match what was rendered on the server.' ||\n error.message === 'Text content does not match server-rendered HTML.')\n )\n}\n\nexport function isHydrationWarning(message: unknown): message is string {\n return (\n isHtmlTagsWarning(message) ||\n isTextInTagsMismatchWarning(message) ||\n isTextWarning(message)\n )\n}\n\ntype NullableText = string | null | undefined\n\n// https://github.com/facebook/react/blob/main/packages/react-dom/src/__tests__/ReactDOMHydrationDiff-test.js used as a reference\nconst htmlTagsWarnings = new Set([\n 'Warning: Expected server HTML to contain a matching <%s> in <%s>.%s',\n 'Warning: Did not expect server HTML to contain a <%s> in <%s>.%s',\n])\nconst textAndTagsMismatchWarnings = new Set([\n 'Warning: Expected server HTML to contain a matching text node for \"%s\" in <%s>.%s',\n 'Warning: Did not expect server HTML to contain the text node \"%s\" in <%s>.%s',\n])\nconst textWarnings = new Set([\n 'Warning: Text content did not match. Server: \"%s\" Client: \"%s\"%s',\n])\n\nexport const getHydrationWarningType = (\n message: NullableText\n): 'tag' | 'text' | 'text-in-tag' => {\n if (typeof message !== 'string') {\n // TODO: Doesn't make sense to treat no message as a hydration error message.\n // We should bail out somewhere earlier.\n return 'text'\n }\n\n const normalizedMessage = message.startsWith('Warning: ')\n ? message\n : `Warning: ${message}`\n\n if (isHtmlTagsWarning(normalizedMessage)) return 'tag'\n if (isTextInTagsMismatchWarning(normalizedMessage)) return 'text-in-tag'\n\n return 'text'\n}\n\nconst isHtmlTagsWarning = (message: unknown) =>\n typeof message === 'string' && htmlTagsWarnings.has(message)\n\nconst isTextInTagsMismatchWarning = (msg: unknown) =>\n typeof msg === 'string' && textAndTagsMismatchWarnings.has(msg)\n\nconst isTextWarning = (msg: unknown) =>\n typeof msg === 'string' && textWarnings.has(msg)\n"],"names":["isError","isHydrationError","error","message","isHydrationWarning","isHtmlTagsWarning","isTextInTagsMismatchWarning","isTextWarning","htmlTagsWarnings","Set","textAndTagsMismatchWarnings","textWarnings","getHydrationWarningType","normalizedMessage","startsWith","has","msg"],"mappings":"AAAA,OAAOA,aAAa,qBAAoB;AAExC,OAAO,SAASC,iBAAiBC,KAAc;IAC7C,OACEF,QAAQE,UACPA,CAAAA,MAAMC,OAAO,KACZ,6FACAD,MAAMC,OAAO,KAAK,mDAAkD;AAE1E;AAEA,OAAO,SAASC,mBAAmBD,OAAgB;IACjD,OACEE,kBAAkBF,YAClBG,4BAA4BH,YAC5BI,cAAcJ;AAElB;AAIA,iIAAiI;AACjI,MAAMK,mBAAmB,IAAIC,IAAI;IAC/B;IACA;CACD;AACD,MAAMC,8BAA8B,IAAID,IAAI;IAC1C;IACA;CACD;AACD,MAAME,eAAe,IAAIF,IAAI;IAC3B;CACD;AAED,OAAO,MAAMG,0BAA0B,CACrCT;IAEA,IAAI,OAAOA,YAAY,UAAU;QAC/B,6EAA6E;QAC7E,wCAAwC;QACxC,OAAO;IACT;IAEA,MAAMU,oBAAoBV,QAAQW,UAAU,CAAC,eACzCX,UACA,CAAC,SAAS,EAAEA,SAAS;IAEzB,IAAIE,kBAAkBQ,oBAAoB,OAAO;IACjD,IAAIP,4BAA4BO,oBAAoB,OAAO;IAE3D,OAAO;AACT,EAAC;AAED,MAAMR,oBAAoB,CAACF,UACzB,OAAOA,YAAY,YAAYK,iBAAiBO,GAAG,CAACZ;AAEtD,MAAMG,8BAA8B,CAACU,MACnC,OAAOA,QAAQ,YAAYN,4BAA4BK,GAAG,CAACC;AAE7D,MAAMT,gBAAgB,CAACS,MACrB,OAAOA,QAAQ,YAAYL,aAAaI,GAAG,CAACC","ignoreList":[0]}

View File

@@ -0,0 +1,55 @@
export const REACT_HYDRATION_ERROR_LINK = 'https://react.dev/link/hydration-mismatch';
export const NEXTJS_HYDRATION_ERROR_LINK = 'https://nextjs.org/docs/messages/react-hydration-error';
/**
* Only React 19+ contains component stack diff in the error message
*/ const errorMessagesWithComponentStackDiff = [
/^In HTML, (.+?) cannot be a child of <(.+?)>\.(.*)\nThis will cause a hydration error\.(.*)/,
/^In HTML, (.+?) cannot be a descendant of <(.+?)>\.\nThis will cause a hydration error\.(.*)/,
/^In HTML, text nodes cannot be a child of <(.+?)>\.\nThis will cause a hydration error\./,
/^In HTML, whitespace text nodes cannot be a child of <(.+?)>\. Make sure you don't have any extra whitespace between tags on each line of your source code\.\nThis will cause a hydration error\./
];
export function isHydrationError(error) {
return isErrorMessageWithComponentStackDiff(error.message) || /Hydration failed because the server rendered (text|HTML) didn't match the client\./.test(error.message) || /A tree hydrated but some attributes of the server rendered HTML didn't match the client properties./.test(error.message);
}
export function isErrorMessageWithComponentStackDiff(msg) {
return errorMessagesWithComponentStackDiff.some((regex)=>regex.test(msg));
}
export function getHydrationErrorStackInfo(error) {
const errorMessage = error.message;
if (isErrorMessageWithComponentStackDiff(errorMessage)) {
const [message, diffLog = ''] = errorMessage.split('\n\n');
const diff = diffLog.trim();
return {
message: diff === '' ? errorMessage.trim() : message.trim(),
diff,
notes: null
};
}
const [message, maybeComponentStackDiff] = errorMessage.split(`${REACT_HYDRATION_ERROR_LINK}`);
const trimmedMessage = message.trim();
// React built-in hydration diff starts with a newline
if (maybeComponentStackDiff !== undefined && maybeComponentStackDiff.length > 1) {
const diffs = [];
maybeComponentStackDiff.split('\n').forEach((line)=>{
if (line.trim() === '') return;
if (!line.trim().startsWith('at ')) {
diffs.push(line);
}
});
const [displayedMessage, ...notes] = trimmedMessage.split('\n\n');
return {
message: displayedMessage,
diff: diffs.join('\n'),
notes: notes.join('\n\n') || null
};
} else {
const [displayedMessage, ...notes] = trimmedMessage.split('\n\n');
return {
message: displayedMessage,
diff: null,
notes: notes.join('\n\n')
};
}
}
//# sourceMappingURL=react-19-hydration-error.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/next-devtools/shared/react-19-hydration-error.ts"],"sourcesContent":["export const REACT_HYDRATION_ERROR_LINK =\n 'https://react.dev/link/hydration-mismatch'\nexport const NEXTJS_HYDRATION_ERROR_LINK =\n 'https://nextjs.org/docs/messages/react-hydration-error'\n\n/**\n * Only React 19+ contains component stack diff in the error message\n */\nconst errorMessagesWithComponentStackDiff = [\n /^In HTML, (.+?) cannot be a child of <(.+?)>\\.(.*)\\nThis will cause a hydration error\\.(.*)/,\n /^In HTML, (.+?) cannot be a descendant of <(.+?)>\\.\\nThis will cause a hydration error\\.(.*)/,\n /^In HTML, text nodes cannot be a child of <(.+?)>\\.\\nThis will cause a hydration error\\./,\n /^In HTML, whitespace text nodes cannot be a child of <(.+?)>\\. Make sure you don't have any extra whitespace between tags on each line of your source code\\.\\nThis will cause a hydration error\\./,\n]\n\nexport function isHydrationError(error: Error): boolean {\n return (\n isErrorMessageWithComponentStackDiff(error.message) ||\n /Hydration failed because the server rendered (text|HTML) didn't match the client\\./.test(\n error.message\n ) ||\n /A tree hydrated but some attributes of the server rendered HTML didn't match the client properties./.test(\n error.message\n )\n )\n}\n\nexport function isErrorMessageWithComponentStackDiff(msg: string): boolean {\n return errorMessagesWithComponentStackDiff.some((regex) => regex.test(msg))\n}\n\nexport function getHydrationErrorStackInfo(error: Error): {\n message: string | null\n notes: string | null\n diff: string | null\n} {\n const errorMessage = error.message\n if (isErrorMessageWithComponentStackDiff(errorMessage)) {\n const [message, diffLog = ''] = errorMessage.split('\\n\\n')\n const diff = diffLog.trim()\n return {\n message: diff === '' ? errorMessage.trim() : message.trim(),\n diff,\n notes: null,\n }\n }\n\n const [message, maybeComponentStackDiff] = errorMessage.split(\n `${REACT_HYDRATION_ERROR_LINK}`\n )\n const trimmedMessage = message.trim()\n // React built-in hydration diff starts with a newline\n if (\n maybeComponentStackDiff !== undefined &&\n maybeComponentStackDiff.length > 1\n ) {\n const diffs: string[] = []\n maybeComponentStackDiff.split('\\n').forEach((line) => {\n if (line.trim() === '') return\n if (!line.trim().startsWith('at ')) {\n diffs.push(line)\n }\n })\n\n const [displayedMessage, ...notes] = trimmedMessage.split('\\n\\n')\n return {\n message: displayedMessage,\n diff: diffs.join('\\n'),\n notes: notes.join('\\n\\n') || null,\n }\n } else {\n const [displayedMessage, ...notes] = trimmedMessage.split('\\n\\n')\n return {\n message: displayedMessage,\n diff: null,\n notes: notes.join('\\n\\n'),\n }\n }\n}\n"],"names":["REACT_HYDRATION_ERROR_LINK","NEXTJS_HYDRATION_ERROR_LINK","errorMessagesWithComponentStackDiff","isHydrationError","error","isErrorMessageWithComponentStackDiff","message","test","msg","some","regex","getHydrationErrorStackInfo","errorMessage","diffLog","split","diff","trim","notes","maybeComponentStackDiff","trimmedMessage","undefined","length","diffs","forEach","line","startsWith","push","displayedMessage","join"],"mappings":"AAAA,OAAO,MAAMA,6BACX,4CAA2C;AAC7C,OAAO,MAAMC,8BACX,yDAAwD;AAE1D;;CAEC,GACD,MAAMC,sCAAsC;IAC1C;IACA;IACA;IACA;CACD;AAED,OAAO,SAASC,iBAAiBC,KAAY;IAC3C,OACEC,qCAAqCD,MAAME,OAAO,KAClD,qFAAqFC,IAAI,CACvFH,MAAME,OAAO,KAEf,sGAAsGC,IAAI,CACxGH,MAAME,OAAO;AAGnB;AAEA,OAAO,SAASD,qCAAqCG,GAAW;IAC9D,OAAON,oCAAoCO,IAAI,CAAC,CAACC,QAAUA,MAAMH,IAAI,CAACC;AACxE;AAEA,OAAO,SAASG,2BAA2BP,KAAY;IAKrD,MAAMQ,eAAeR,MAAME,OAAO;IAClC,IAAID,qCAAqCO,eAAe;QACtD,MAAM,CAACN,SAASO,UAAU,EAAE,CAAC,GAAGD,aAAaE,KAAK,CAAC;QACnD,MAAMC,OAAOF,QAAQG,IAAI;QACzB,OAAO;YACLV,SAASS,SAAS,KAAKH,aAAaI,IAAI,KAAKV,QAAQU,IAAI;YACzDD;YACAE,OAAO;QACT;IACF;IAEA,MAAM,CAACX,SAASY,wBAAwB,GAAGN,aAAaE,KAAK,CAC3D,GAAGd,4BAA4B;IAEjC,MAAMmB,iBAAiBb,QAAQU,IAAI;IACnC,sDAAsD;IACtD,IACEE,4BAA4BE,aAC5BF,wBAAwBG,MAAM,GAAG,GACjC;QACA,MAAMC,QAAkB,EAAE;QAC1BJ,wBAAwBJ,KAAK,CAAC,MAAMS,OAAO,CAAC,CAACC;YAC3C,IAAIA,KAAKR,IAAI,OAAO,IAAI;YACxB,IAAI,CAACQ,KAAKR,IAAI,GAAGS,UAAU,CAAC,QAAQ;gBAClCH,MAAMI,IAAI,CAACF;YACb;QACF;QAEA,MAAM,CAACG,kBAAkB,GAAGV,MAAM,GAAGE,eAAeL,KAAK,CAAC;QAC1D,OAAO;YACLR,SAASqB;YACTZ,MAAMO,MAAMM,IAAI,CAAC;YACjBX,OAAOA,MAAMW,IAAI,CAAC,WAAW;QAC/B;IACF,OAAO;QACL,MAAM,CAACD,kBAAkB,GAAGV,MAAM,GAAGE,eAAeL,KAAK,CAAC;QAC1D,OAAO;YACLR,SAASqB;YACTZ,MAAM;YACNE,OAAOA,MAAMW,IAAI,CAAC;QACpB;IACF;AACF","ignoreList":[0]}

View File

@@ -0,0 +1,120 @@
import { isWebpackInternalResource, formatFrameSourceFile } from './webpack-module-path';
function getOriginalStackFrame(source, response) {
async function _getOriginalStackFrame() {
if (response.status === 'rejected') {
throw Object.defineProperty(new Error(response.reason), "__NEXT_ERROR_CODE", {
value: "E394",
enumerable: false,
configurable: true
});
}
const body = response.value;
return {
error: false,
reason: null,
external: false,
sourceStackFrame: source,
originalStackFrame: body.originalStackFrame,
originalCodeFrame: body.originalCodeFrame || null,
ignored: body.originalStackFrame?.ignored || false
};
}
// TODO: merge this section into ignoredList handling
if (source.file === 'file://' || source.file?.match(/https?:\/\//)) {
return Promise.resolve({
error: false,
reason: null,
external: true,
sourceStackFrame: source,
originalStackFrame: null,
originalCodeFrame: null,
ignored: true
});
}
return _getOriginalStackFrame().catch((err)=>({
error: true,
reason: err?.message ?? err?.toString() ?? 'Unknown Error',
external: false,
sourceStackFrame: source,
originalStackFrame: null,
originalCodeFrame: null,
ignored: false
}));
}
export async function getOriginalStackFrames(frames, type, isAppDir) {
const req = {
frames,
isServer: type === 'server',
isEdgeServer: type === 'edge-server',
isAppDirectory: isAppDir
};
let res = undefined;
let reason = undefined;
try {
res = await fetch('/__nextjs_original-stack-frames', {
method: 'POST',
body: JSON.stringify(req)
});
} catch (e) {
reason = e + '';
}
// When fails to fetch the original stack frames, we reject here to be
// caught at `_getOriginalStackFrame()` and return the stack frames so
// that the error overlay can render.
if (res && res.ok && res.status !== 204) {
const data = await res.json();
return Promise.all(frames.map((frame, index)=>getOriginalStackFrame(frame, data[index])));
} else {
if (res) {
reason = await res.text();
}
}
return Promise.all(frames.map((frame)=>getOriginalStackFrame(frame, {
status: 'rejected',
reason: `Failed to fetch the original stack frames ${reason ? `: ${reason}` : ''}`
})));
}
export function getFrameSource(frame) {
if (!frame.file) return '';
const isWebpackFrame = isWebpackInternalResource(frame.file);
let str = '';
// Skip URL parsing for webpack internal file paths.
if (isWebpackFrame) {
str = formatFrameSourceFile(frame.file);
} else {
try {
const u = new URL(frame.file);
let parsedPath = '';
// Strip the origin for same-origin scripts.
if (globalThis.location?.origin !== u.origin) {
// URLs can be valid without an `origin`, so long as they have a
// `protocol`. However, `origin` is preferred.
if (u.origin === 'null') {
parsedPath += u.protocol;
} else {
parsedPath += u.origin;
}
}
// Strip query string information as it's typically too verbose to be
// meaningful.
parsedPath += u.pathname;
str = formatFrameSourceFile(parsedPath);
} catch {
str = formatFrameSourceFile(frame.file);
}
}
if (!isWebpackInternalResource(frame.file) && frame.line1 != null) {
// We don't need line and column numbers for anonymous sources because
// there's no entrypoint for the location anyway.
if (str && frame.file !== '<anonymous>') {
if (frame.column1 != null) {
str += ` (${frame.line1}:${frame.column1})`;
} else {
str += ` (${frame.line1})`;
}
}
}
return str;
}
//# sourceMappingURL=stack-frame.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,3 @@
export { };
//# sourceMappingURL=types.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/next-devtools/shared/types.ts"],"sourcesContent":["export type DebugInfo = {\n devtoolsFrontendUrl: string | undefined\n}\n"],"names":[],"mappings":"AAAA,WAEC","ignoreList":[0]}

View File

@@ -0,0 +1,48 @@
export function getStaleness({ installed, staleness, expected }) {
let text = '';
let title = '';
let indicatorClass = '';
const versionLabel = `Next.js ${installed}`;
switch(staleness){
case 'newer-than-npm':
case 'fresh':
text = versionLabel;
title = `Latest available version is detected (${installed}).`;
indicatorClass = 'fresh';
break;
case 'stale-patch':
case 'stale-minor':
text = `${versionLabel} (stale)`;
title = `There is a newer version (${expected}) available, upgrade recommended! `;
indicatorClass = 'stale';
break;
case 'stale-major':
{
text = `${versionLabel} (outdated)`;
title = `An outdated version detected (latest is ${expected}), upgrade is highly recommended!`;
indicatorClass = 'outdated';
break;
}
case 'stale-prerelease':
{
text = `${versionLabel} (stale)`;
title = `There is a newer canary version (${expected}) available, please upgrade! `;
indicatorClass = 'stale';
break;
}
case 'unknown':
text = `${versionLabel} (unknown)`;
title = 'No Next.js version data was found.';
indicatorClass = 'unknown';
break;
default:
break;
}
return {
text,
indicatorClass,
title
};
}
//# sourceMappingURL=version-staleness.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/next-devtools/shared/version-staleness.ts"],"sourcesContent":["import type { VersionInfo } from '../../server/dev/parse-version-info'\n\nexport function getStaleness({ installed, staleness, expected }: VersionInfo) {\n let text = ''\n let title = ''\n let indicatorClass = ''\n const versionLabel = `Next.js ${installed}`\n switch (staleness) {\n case 'newer-than-npm':\n case 'fresh':\n text = versionLabel\n title = `Latest available version is detected (${installed}).`\n indicatorClass = 'fresh'\n break\n case 'stale-patch':\n case 'stale-minor':\n text = `${versionLabel} (stale)`\n title = `There is a newer version (${expected}) available, upgrade recommended! `\n indicatorClass = 'stale'\n break\n case 'stale-major': {\n text = `${versionLabel} (outdated)`\n title = `An outdated version detected (latest is ${expected}), upgrade is highly recommended!`\n indicatorClass = 'outdated'\n break\n }\n case 'stale-prerelease': {\n text = `${versionLabel} (stale)`\n title = `There is a newer canary version (${expected}) available, please upgrade! `\n indicatorClass = 'stale'\n break\n }\n case 'unknown':\n text = `${versionLabel} (unknown)`\n title = 'No Next.js version data was found.'\n indicatorClass = 'unknown'\n break\n default:\n break\n }\n return { text, indicatorClass, title }\n}\n"],"names":["getStaleness","installed","staleness","expected","text","title","indicatorClass","versionLabel"],"mappings":"AAEA,OAAO,SAASA,aAAa,EAAEC,SAAS,EAAEC,SAAS,EAAEC,QAAQ,EAAe;IAC1E,IAAIC,OAAO;IACX,IAAIC,QAAQ;IACZ,IAAIC,iBAAiB;IACrB,MAAMC,eAAe,CAAC,QAAQ,EAAEN,WAAW;IAC3C,OAAQC;QACN,KAAK;QACL,KAAK;YACHE,OAAOG;YACPF,QAAQ,CAAC,sCAAsC,EAAEJ,UAAU,EAAE,CAAC;YAC9DK,iBAAiB;YACjB;QACF,KAAK;QACL,KAAK;YACHF,OAAO,GAAGG,aAAa,QAAQ,CAAC;YAChCF,QAAQ,CAAC,0BAA0B,EAAEF,SAAS,kCAAkC,CAAC;YACjFG,iBAAiB;YACjB;QACF,KAAK;YAAe;gBAClBF,OAAO,GAAGG,aAAa,WAAW,CAAC;gBACnCF,QAAQ,CAAC,wCAAwC,EAAEF,SAAS,iCAAiC,CAAC;gBAC9FG,iBAAiB;gBACjB;YACF;QACA,KAAK;YAAoB;gBACvBF,OAAO,GAAGG,aAAa,QAAQ,CAAC;gBAChCF,QAAQ,CAAC,iCAAiC,EAAEF,SAAS,6BAA6B,CAAC;gBACnFG,iBAAiB;gBACjB;YACF;QACA,KAAK;YACHF,OAAO,GAAGG,aAAa,UAAU,CAAC;YAClCF,QAAQ;YACRC,iBAAiB;YACjB;QACF;YACE;IACJ;IACA,OAAO;QAAEF;QAAME;QAAgBD;IAAM;AACvC","ignoreList":[0]}

View File

@@ -0,0 +1,26 @@
const replacementRegExes = [
/^webpack-internal:\/\/\/(\([\w-]+\)\/)?/,
/^(webpack:\/\/\/|webpack:\/\/(_N_E\/)?)(\([\w-]+\)\/)?/
];
export function isWebpackInternalResource(file) {
for (const regex of replacementRegExes){
if (regex.test(file)) return true;
file = file.replace(regex, '');
}
return false;
}
/**
* Format the webpack internal id to original file path
*
* webpack-internal:///./src/hello.tsx => ./src/hello.tsx
* webpack://_N_E/./src/hello.tsx => ./src/hello.tsx
* webpack://./src/hello.tsx => ./src/hello.tsx
* webpack:///./src/hello.tsx => ./src/hello.tsx
*/ export function formatFrameSourceFile(file) {
for (const regex of replacementRegExes){
file = file.replace(regex, '');
}
return file;
}
//# sourceMappingURL=webpack-module-path.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/next-devtools/shared/webpack-module-path.ts"],"sourcesContent":["const replacementRegExes = [\n /^webpack-internal:\\/\\/\\/(\\([\\w-]+\\)\\/)?/,\n /^(webpack:\\/\\/\\/|webpack:\\/\\/(_N_E\\/)?)(\\([\\w-]+\\)\\/)?/,\n]\n\nexport function isWebpackInternalResource(file: string) {\n for (const regex of replacementRegExes) {\n if (regex.test(file)) return true\n\n file = file.replace(regex, '')\n }\n\n return false\n}\n\n/**\n * Format the webpack internal id to original file path\n *\n * webpack-internal:///./src/hello.tsx => ./src/hello.tsx\n * webpack://_N_E/./src/hello.tsx => ./src/hello.tsx\n * webpack://./src/hello.tsx => ./src/hello.tsx\n * webpack:///./src/hello.tsx => ./src/hello.tsx\n */\nexport function formatFrameSourceFile(file: string) {\n for (const regex of replacementRegExes) {\n file = file.replace(regex, '')\n }\n\n return file\n}\n"],"names":["replacementRegExes","isWebpackInternalResource","file","regex","test","replace","formatFrameSourceFile"],"mappings":"AAAA,MAAMA,qBAAqB;IACzB;IACA;CACD;AAED,OAAO,SAASC,0BAA0BC,IAAY;IACpD,KAAK,MAAMC,SAASH,mBAAoB;QACtC,IAAIG,MAAMC,IAAI,CAACF,OAAO,OAAO;QAE7BA,OAAOA,KAAKG,OAAO,CAACF,OAAO;IAC7B;IAEA,OAAO;AACT;AAEA;;;;;;;CAOC,GACD,OAAO,SAASG,sBAAsBJ,IAAY;IAChD,KAAK,MAAMC,SAASH,mBAAoB;QACtCE,OAAOA,KAAKG,OAAO,CAACF,OAAO;IAC7B;IAEA,OAAOD;AACT","ignoreList":[0]}

View File

@@ -0,0 +1,56 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { PureComponent } from 'react';
import { dispatcher } from 'next/dist/compiled/next-devtools';
import { RuntimeErrorHandler } from '../../../client/dev/runtime-error-handler';
import { ErrorBoundary } from '../../../client/components/error-boundary';
import DefaultGlobalError from '../../../client/components/builtin/global-error';
import { SEGMENT_EXPLORER_SIMULATED_ERROR_MESSAGE } from './segment-explorer-node';
function ErroredHtml({ globalError: [GlobalError, globalErrorStyles], error }) {
if (!error) {
return /*#__PURE__*/ _jsxs("html", {
children: [
/*#__PURE__*/ _jsx("head", {}),
/*#__PURE__*/ _jsx("body", {})
]
});
}
return /*#__PURE__*/ _jsxs(ErrorBoundary, {
errorComponent: DefaultGlobalError,
children: [
globalErrorStyles,
/*#__PURE__*/ _jsx(GlobalError, {
error: error
})
]
});
}
export class AppDevOverlayErrorBoundary extends PureComponent {
static getDerivedStateFromError(error) {
RuntimeErrorHandler.hadRuntimeError = true;
return {
reactError: error
};
}
componentDidCatch(err) {
if (process.env.NODE_ENV === 'development' && err.message === SEGMENT_EXPLORER_SIMULATED_ERROR_MESSAGE) {
return;
}
dispatcher.openErrorOverlay();
}
render() {
const { children, globalError } = this.props;
const { reactError } = this.state;
const fallback = /*#__PURE__*/ _jsx(ErroredHtml, {
globalError: globalError,
error: reactError
});
return reactError !== null ? fallback : children;
}
constructor(...args){
super(...args), this.state = {
reactError: null
};
}
}
//# sourceMappingURL=app-dev-overlay-error-boundary.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/next-devtools/userspace/app/app-dev-overlay-error-boundary.tsx"],"sourcesContent":["import { PureComponent } from 'react'\nimport { dispatcher } from 'next/dist/compiled/next-devtools'\nimport { RuntimeErrorHandler } from '../../../client/dev/runtime-error-handler'\nimport { ErrorBoundary } from '../../../client/components/error-boundary'\nimport DefaultGlobalError from '../../../client/components/builtin/global-error'\nimport type { GlobalErrorState } from '../../../client/components/app-router-instance'\nimport { SEGMENT_EXPLORER_SIMULATED_ERROR_MESSAGE } from './segment-explorer-node'\n\ntype AppDevOverlayErrorBoundaryProps = {\n children: React.ReactNode\n globalError: GlobalErrorState\n}\n\ntype AppDevOverlayErrorBoundaryState = {\n reactError: unknown\n}\n\nfunction ErroredHtml({\n globalError: [GlobalError, globalErrorStyles],\n error,\n}: {\n globalError: GlobalErrorState\n error: unknown\n}) {\n if (!error) {\n return (\n <html>\n <head />\n <body />\n </html>\n )\n }\n return (\n <ErrorBoundary errorComponent={DefaultGlobalError}>\n {globalErrorStyles}\n <GlobalError error={error} />\n </ErrorBoundary>\n )\n}\n\nexport class AppDevOverlayErrorBoundary extends PureComponent<\n AppDevOverlayErrorBoundaryProps,\n AppDevOverlayErrorBoundaryState\n> {\n state = { reactError: null }\n\n static getDerivedStateFromError(error: Error) {\n RuntimeErrorHandler.hadRuntimeError = true\n\n return {\n reactError: error,\n }\n }\n\n componentDidCatch(err: Error) {\n if (\n process.env.NODE_ENV === 'development' &&\n err.message === SEGMENT_EXPLORER_SIMULATED_ERROR_MESSAGE\n ) {\n return\n }\n dispatcher.openErrorOverlay()\n }\n\n render() {\n const { children, globalError } = this.props\n const { reactError } = this.state\n\n const fallback = (\n <ErroredHtml globalError={globalError} error={reactError} />\n )\n\n return reactError !== null ? fallback : children\n }\n}\n"],"names":["PureComponent","dispatcher","RuntimeErrorHandler","ErrorBoundary","DefaultGlobalError","SEGMENT_EXPLORER_SIMULATED_ERROR_MESSAGE","ErroredHtml","globalError","GlobalError","globalErrorStyles","error","html","head","body","errorComponent","AppDevOverlayErrorBoundary","getDerivedStateFromError","hadRuntimeError","reactError","componentDidCatch","err","process","env","NODE_ENV","message","openErrorOverlay","render","children","props","state","fallback"],"mappings":";AAAA,SAASA,aAAa,QAAQ,QAAO;AACrC,SAASC,UAAU,QAAQ,mCAAkC;AAC7D,SAASC,mBAAmB,QAAQ,4CAA2C;AAC/E,SAASC,aAAa,QAAQ,4CAA2C;AACzE,OAAOC,wBAAwB,kDAAiD;AAEhF,SAASC,wCAAwC,QAAQ,0BAAyB;AAWlF,SAASC,YAAY,EACnBC,aAAa,CAACC,aAAaC,kBAAkB,EAC7CC,KAAK,EAIN;IACC,IAAI,CAACA,OAAO;QACV,qBACE,MAACC;;8BACC,KAACC;8BACD,KAACC;;;IAGP;IACA,qBACE,MAACV;QAAcW,gBAAgBV;;YAC5BK;0BACD,KAACD;gBAAYE,OAAOA;;;;AAG1B;AAEA,OAAO,MAAMK,mCAAmCf;IAM9C,OAAOgB,yBAAyBN,KAAY,EAAE;QAC5CR,oBAAoBe,eAAe,GAAG;QAEtC,OAAO;YACLC,YAAYR;QACd;IACF;IAEAS,kBAAkBC,GAAU,EAAE;QAC5B,IACEC,QAAQC,GAAG,CAACC,QAAQ,KAAK,iBACzBH,IAAII,OAAO,KAAKnB,0CAChB;YACA;QACF;QACAJ,WAAWwB,gBAAgB;IAC7B;IAEAC,SAAS;QACP,MAAM,EAAEC,QAAQ,EAAEpB,WAAW,EAAE,GAAG,IAAI,CAACqB,KAAK;QAC5C,MAAM,EAAEV,UAAU,EAAE,GAAG,IAAI,CAACW,KAAK;QAEjC,MAAMC,yBACJ,KAACxB;YAAYC,aAAaA;YAAaG,OAAOQ;;QAGhD,OAAOA,eAAe,OAAOY,WAAWH;IAC1C;;QAjCK,qBAILE,QAAQ;YAAEX,YAAY;QAAK;;AA8B7B","ignoreList":[0]}

View File

@@ -0,0 +1,8 @@
import { patchConsoleError } from './errors/intercept-console-error';
import { handleGlobalErrors } from './errors/use-error-handler';
import { initializeDebugLogForwarding } from './forward-logs';
handleGlobalErrors();
patchConsoleError();
initializeDebugLogForwarding('app');
//# sourceMappingURL=app-dev-overlay-setup.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/next-devtools/userspace/app/app-dev-overlay-setup.ts"],"sourcesContent":["import { patchConsoleError } from './errors/intercept-console-error'\nimport { handleGlobalErrors } from './errors/use-error-handler'\nimport { initializeDebugLogForwarding } from './forward-logs'\n\nhandleGlobalErrors()\npatchConsoleError()\n\ninitializeDebugLogForwarding('app')\n"],"names":["patchConsoleError","handleGlobalErrors","initializeDebugLogForwarding"],"mappings":"AAAA,SAASA,iBAAiB,QAAQ,mCAAkC;AACpE,SAASC,kBAAkB,QAAQ,6BAA4B;AAC/D,SAASC,4BAA4B,QAAQ,iBAAgB;AAE7DD;AACAD;AAEAE,6BAA6B","ignoreList":[0]}

View File

@@ -0,0 +1,17 @@
import { jsx as _jsx } from "react/jsx-runtime";
import React from 'react';
import DefaultGlobalError from '../../../client/components/builtin/global-error';
import { AppDevOverlayErrorBoundary } from './app-dev-overlay-error-boundary';
// If an error is thrown while rendering an RSC stream, this will catch it in
// dev and show the error overlay.
export function RootLevelDevOverlayElement({ children }) {
return /*#__PURE__*/ _jsx(AppDevOverlayErrorBoundary, {
globalError: [
DefaultGlobalError,
null
],
children: children
});
}
//# sourceMappingURL=client-entry.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/next-devtools/userspace/app/client-entry.tsx"],"sourcesContent":["import React from 'react'\nimport DefaultGlobalError from '../../../client/components/builtin/global-error'\nimport { AppDevOverlayErrorBoundary } from './app-dev-overlay-error-boundary'\n\n// If an error is thrown while rendering an RSC stream, this will catch it in\n// dev and show the error overlay.\nexport function RootLevelDevOverlayElement({\n children,\n}: {\n children: React.ReactNode\n}) {\n return (\n <AppDevOverlayErrorBoundary globalError={[DefaultGlobalError, null]}>\n {children}\n </AppDevOverlayErrorBoundary>\n )\n}\n"],"names":["React","DefaultGlobalError","AppDevOverlayErrorBoundary","RootLevelDevOverlayElement","children","globalError"],"mappings":";AAAA,OAAOA,WAAW,QAAO;AACzB,OAAOC,wBAAwB,kDAAiD;AAChF,SAASC,0BAA0B,QAAQ,mCAAkC;AAE7E,6EAA6E;AAC7E,kCAAkC;AAClC,OAAO,SAASC,2BAA2B,EACzCC,QAAQ,EAGT;IACC,qBACE,KAACF;QAA2BG,aAAa;YAACJ;YAAoB;SAAK;kBAChEG;;AAGP","ignoreList":[0]}

View File

@@ -0,0 +1,5 @@
export { originConsoleError } from './intercept-console-error';
export { handleClientError } from './use-error-handler';
export { decorateDevError } from './stitched-error';
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../../src/next-devtools/userspace/app/errors/index.ts"],"sourcesContent":["export { originConsoleError } from './intercept-console-error'\nexport { handleClientError } from './use-error-handler'\nexport { decorateDevError } from './stitched-error'\n"],"names":["originConsoleError","handleClientError","decorateDevError"],"mappings":"AAAA,SAASA,kBAAkB,QAAQ,4BAA2B;AAC9D,SAASC,iBAAiB,QAAQ,sBAAqB;AACvD,SAASC,gBAAgB,QAAQ,mBAAkB","ignoreList":[0]}

View File

@@ -0,0 +1,40 @@
import isError from '../../../../lib/is-error';
import { isNextRouterError } from '../../../../client/components/is-next-router-error';
import { handleConsoleError } from './use-error-handler';
import { parseConsoleArgs } from '../../../../client/lib/console';
import { forwardErrorLog } from '../forward-logs';
export const originConsoleError = globalThis.console.error;
// Patch console.error to collect information about hydration errors
export function patchConsoleError() {
// Ensure it's only patched once
if (typeof window === 'undefined') {
return;
}
window.console.error = function error(...args) {
let maybeError;
if (process.env.NODE_ENV !== 'production') {
const { error: replayedError } = parseConsoleArgs(args);
if (replayedError) {
maybeError = replayedError;
} else if (isError(args[0])) {
maybeError = args[0];
} else {
// See https://github.com/facebook/react/blob/d50323eb845c5fde0d720cae888bf35dedd05506/packages/react-reconciler/src/ReactFiberErrorLogger.js#L78
maybeError = args[1];
}
} else {
maybeError = args[0];
}
if (!isNextRouterError(maybeError)) {
if (process.env.NODE_ENV !== 'production') {
handleConsoleError(// replayed errors have their own complex format string that should be used,
// but if we pass the error directly, `handleClientError` will ignore it
maybeError, args);
}
forwardErrorLog(args);
originConsoleError.apply(window.console, args);
}
};
}
//# sourceMappingURL=intercept-console-error.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../../src/next-devtools/userspace/app/errors/intercept-console-error.ts"],"sourcesContent":["import isError from '../../../../lib/is-error'\nimport { isNextRouterError } from '../../../../client/components/is-next-router-error'\nimport { handleConsoleError } from './use-error-handler'\nimport { parseConsoleArgs } from '../../../../client/lib/console'\nimport { forwardErrorLog } from '../forward-logs'\n\nexport const originConsoleError = globalThis.console.error\n\n// Patch console.error to collect information about hydration errors\nexport function patchConsoleError() {\n // Ensure it's only patched once\n if (typeof window === 'undefined') {\n return\n }\n window.console.error = function error(...args: any[]) {\n let maybeError: unknown\n if (process.env.NODE_ENV !== 'production') {\n const { error: replayedError } = parseConsoleArgs(args)\n if (replayedError) {\n maybeError = replayedError\n } else if (isError(args[0])) {\n maybeError = args[0]\n } else {\n // See https://github.com/facebook/react/blob/d50323eb845c5fde0d720cae888bf35dedd05506/packages/react-reconciler/src/ReactFiberErrorLogger.js#L78\n maybeError = args[1]\n }\n } else {\n maybeError = args[0]\n }\n\n if (!isNextRouterError(maybeError)) {\n if (process.env.NODE_ENV !== 'production') {\n handleConsoleError(\n // replayed errors have their own complex format string that should be used,\n // but if we pass the error directly, `handleClientError` will ignore it\n maybeError,\n args\n )\n }\n forwardErrorLog(args)\n\n originConsoleError.apply(window.console, args)\n }\n }\n}\n"],"names":["isError","isNextRouterError","handleConsoleError","parseConsoleArgs","forwardErrorLog","originConsoleError","globalThis","console","error","patchConsoleError","window","args","maybeError","process","env","NODE_ENV","replayedError","apply"],"mappings":"AAAA,OAAOA,aAAa,2BAA0B;AAC9C,SAASC,iBAAiB,QAAQ,qDAAoD;AACtF,SAASC,kBAAkB,QAAQ,sBAAqB;AACxD,SAASC,gBAAgB,QAAQ,iCAAgC;AACjE,SAASC,eAAe,QAAQ,kBAAiB;AAEjD,OAAO,MAAMC,qBAAqBC,WAAWC,OAAO,CAACC,KAAK,CAAA;AAE1D,oEAAoE;AACpE,OAAO,SAASC;IACd,gCAAgC;IAChC,IAAI,OAAOC,WAAW,aAAa;QACjC;IACF;IACAA,OAAOH,OAAO,CAACC,KAAK,GAAG,SAASA,MAAM,GAAGG,IAAW;QAClD,IAAIC;QACJ,IAAIC,QAAQC,GAAG,CAACC,QAAQ,KAAK,cAAc;YACzC,MAAM,EAAEP,OAAOQ,aAAa,EAAE,GAAGb,iBAAiBQ;YAClD,IAAIK,eAAe;gBACjBJ,aAAaI;YACf,OAAO,IAAIhB,QAAQW,IAAI,CAAC,EAAE,GAAG;gBAC3BC,aAAaD,IAAI,CAAC,EAAE;YACtB,OAAO;gBACL,iJAAiJ;gBACjJC,aAAaD,IAAI,CAAC,EAAE;YACtB;QACF,OAAO;YACLC,aAAaD,IAAI,CAAC,EAAE;QACtB;QAEA,IAAI,CAACV,kBAAkBW,aAAa;YAClC,IAAIC,QAAQC,GAAG,CAACC,QAAQ,KAAK,cAAc;gBACzCb,mBACE,4EAA4E;gBAC5E,wEAAwE;gBACxEU,YACAD;YAEJ;YACAP,gBAAgBO;YAEhBN,mBAAmBY,KAAK,CAACP,OAAOH,OAAO,EAAEI;QAC3C;IACF;AACF","ignoreList":[0]}

View File

@@ -0,0 +1,60 @@
import { useEffect } from 'react';
import { handleClientError } from './use-error-handler';
import { isNextRouterError } from '../../../../client/components/is-next-router-error';
import { MISSING_ROOT_TAGS_ERROR } from '../../../../shared/lib/errors/constants';
function readSsrError() {
if (typeof document === 'undefined') {
return null;
}
const ssrErrorTemplateTag = document.querySelector('template[data-next-error-message]');
if (ssrErrorTemplateTag) {
const message = ssrErrorTemplateTag.getAttribute('data-next-error-message');
const stack = ssrErrorTemplateTag.getAttribute('data-next-error-stack');
const digest = ssrErrorTemplateTag.getAttribute('data-next-error-digest');
const error = Object.defineProperty(new Error(message), "__NEXT_ERROR_CODE", {
value: "E394",
enumerable: false,
configurable: true
});
if (digest) {
;
error.digest = digest;
}
// Skip Next.js SSR'd internal errors that which will be handled by the error boundaries.
if (isNextRouterError(error)) {
return null;
}
error.stack = stack || '';
return error;
}
return null;
}
/**
* Needs to be in the same error boundary as the shell.
* If it commits, we know we recovered from an SSR error.
* If it doesn't commit, we errored again and React will take care of error reporting.
*/ export function ReplaySsrOnlyErrors({ onBlockingError }) {
if (process.env.NODE_ENV !== 'production') {
// Need to read during render. The attributes will be gone after commit.
const ssrError = readSsrError();
// eslint-disable-next-line react-hooks/rules-of-hooks
useEffect(()=>{
if (ssrError !== null) {
// TODO(veil): Include original Owner Stack (NDX-905)
// TODO(veil): Mark as recoverable error
// TODO(veil): console.error
handleClientError(ssrError);
// If it's missing root tags, we can't recover, make it blocking.
if (ssrError.digest === MISSING_ROOT_TAGS_ERROR) {
onBlockingError();
}
}
}, [
ssrError,
onBlockingError
]);
}
return null;
}
//# sourceMappingURL=replay-ssr-only-errors.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../../src/next-devtools/userspace/app/errors/replay-ssr-only-errors.tsx"],"sourcesContent":["import { useEffect } from 'react'\nimport { handleClientError } from './use-error-handler'\nimport { isNextRouterError } from '../../../../client/components/is-next-router-error'\nimport { MISSING_ROOT_TAGS_ERROR } from '../../../../shared/lib/errors/constants'\n\nfunction readSsrError(): (Error & { digest?: string }) | null {\n if (typeof document === 'undefined') {\n return null\n }\n\n const ssrErrorTemplateTag = document.querySelector(\n 'template[data-next-error-message]'\n )\n if (ssrErrorTemplateTag) {\n const message: string = ssrErrorTemplateTag.getAttribute(\n 'data-next-error-message'\n )!\n const stack = ssrErrorTemplateTag.getAttribute('data-next-error-stack')\n const digest = ssrErrorTemplateTag.getAttribute('data-next-error-digest')\n const error = new Error(message)\n if (digest) {\n ;(error as any).digest = digest\n }\n // Skip Next.js SSR'd internal errors that which will be handled by the error boundaries.\n if (isNextRouterError(error)) {\n return null\n }\n error.stack = stack || ''\n return error\n }\n\n return null\n}\n\n/**\n * Needs to be in the same error boundary as the shell.\n * If it commits, we know we recovered from an SSR error.\n * If it doesn't commit, we errored again and React will take care of error reporting.\n */\nexport function ReplaySsrOnlyErrors({\n onBlockingError,\n}: {\n onBlockingError: () => void\n}) {\n if (process.env.NODE_ENV !== 'production') {\n // Need to read during render. The attributes will be gone after commit.\n const ssrError = readSsrError()\n // eslint-disable-next-line react-hooks/rules-of-hooks\n useEffect(() => {\n if (ssrError !== null) {\n // TODO(veil): Include original Owner Stack (NDX-905)\n // TODO(veil): Mark as recoverable error\n // TODO(veil): console.error\n handleClientError(ssrError)\n\n // If it's missing root tags, we can't recover, make it blocking.\n if (ssrError.digest === MISSING_ROOT_TAGS_ERROR) {\n onBlockingError()\n }\n }\n }, [ssrError, onBlockingError])\n }\n\n return null\n}\n"],"names":["useEffect","handleClientError","isNextRouterError","MISSING_ROOT_TAGS_ERROR","readSsrError","document","ssrErrorTemplateTag","querySelector","message","getAttribute","stack","digest","error","Error","ReplaySsrOnlyErrors","onBlockingError","process","env","NODE_ENV","ssrError"],"mappings":"AAAA,SAASA,SAAS,QAAQ,QAAO;AACjC,SAASC,iBAAiB,QAAQ,sBAAqB;AACvD,SAASC,iBAAiB,QAAQ,qDAAoD;AACtF,SAASC,uBAAuB,QAAQ,0CAAyC;AAEjF,SAASC;IACP,IAAI,OAAOC,aAAa,aAAa;QACnC,OAAO;IACT;IAEA,MAAMC,sBAAsBD,SAASE,aAAa,CAChD;IAEF,IAAID,qBAAqB;QACvB,MAAME,UAAkBF,oBAAoBG,YAAY,CACtD;QAEF,MAAMC,QAAQJ,oBAAoBG,YAAY,CAAC;QAC/C,MAAME,SAASL,oBAAoBG,YAAY,CAAC;QAChD,MAAMG,QAAQ,qBAAkB,CAAlB,IAAIC,MAAML,UAAV,qBAAA;mBAAA;wBAAA;0BAAA;QAAiB;QAC/B,IAAIG,QAAQ;;YACRC,MAAcD,MAAM,GAAGA;QAC3B;QACA,yFAAyF;QACzF,IAAIT,kBAAkBU,QAAQ;YAC5B,OAAO;QACT;QACAA,MAAMF,KAAK,GAAGA,SAAS;QACvB,OAAOE;IACT;IAEA,OAAO;AACT;AAEA;;;;CAIC,GACD,OAAO,SAASE,oBAAoB,EAClCC,eAAe,EAGhB;IACC,IAAIC,QAAQC,GAAG,CAACC,QAAQ,KAAK,cAAc;QACzC,wEAAwE;QACxE,MAAMC,WAAWf;QACjB,sDAAsD;QACtDJ,UAAU;YACR,IAAImB,aAAa,MAAM;gBACrB,qDAAqD;gBACrD,wCAAwC;gBACxC,4BAA4B;gBAC5BlB,kBAAkBkB;gBAElB,iEAAiE;gBACjE,IAAIA,SAASR,MAAM,KAAKR,yBAAyB;oBAC/CY;gBACF;YACF;QACF,GAAG;YAACI;YAAUJ;SAAgB;IAChC;IAEA,OAAO;AACT","ignoreList":[0]}

View File

@@ -0,0 +1,29 @@
import React from 'react';
import isError from '../../../../lib/is-error';
const ownerStacks = new WeakMap();
export function getOwnerStack(error) {
return ownerStacks.get(error);
}
export function setOwnerStack(error, stack) {
ownerStacks.set(error, stack);
}
export function coerceError(value) {
return isError(value) ? value : Object.defineProperty(new Error('' + value), "__NEXT_ERROR_CODE", {
value: "E394",
enumerable: false,
configurable: true
});
}
export function setOwnerStackIfAvailable(error) {
// React 18 and prod does not have `captureOwnerStack`
if ('captureOwnerStack' in React) {
setOwnerStack(error, React.captureOwnerStack());
}
}
export function decorateDevError(thrownValue) {
const error = coerceError(thrownValue);
setOwnerStackIfAvailable(error);
return error;
}
//# sourceMappingURL=stitched-error.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../../src/next-devtools/userspace/app/errors/stitched-error.ts"],"sourcesContent":["import React from 'react'\nimport isError from '../../../../lib/is-error'\n\nconst ownerStacks = new WeakMap<Error, string | null>()\n\nexport function getOwnerStack(error: Error): string | null | undefined {\n return ownerStacks.get(error)\n}\nexport function setOwnerStack(error: Error, stack: string | null) {\n ownerStacks.set(error, stack)\n}\n\nexport function coerceError(value: unknown): Error {\n return isError(value) ? value : new Error('' + value)\n}\n\nexport function setOwnerStackIfAvailable(error: Error): void {\n // React 18 and prod does not have `captureOwnerStack`\n if ('captureOwnerStack' in React) {\n setOwnerStack(error, React.captureOwnerStack())\n }\n}\n\nexport function decorateDevError(thrownValue: unknown) {\n const error = coerceError(thrownValue)\n setOwnerStackIfAvailable(error)\n return error\n}\n"],"names":["React","isError","ownerStacks","WeakMap","getOwnerStack","error","get","setOwnerStack","stack","set","coerceError","value","Error","setOwnerStackIfAvailable","captureOwnerStack","decorateDevError","thrownValue"],"mappings":"AAAA,OAAOA,WAAW,QAAO;AACzB,OAAOC,aAAa,2BAA0B;AAE9C,MAAMC,cAAc,IAAIC;AAExB,OAAO,SAASC,cAAcC,KAAY;IACxC,OAAOH,YAAYI,GAAG,CAACD;AACzB;AACA,OAAO,SAASE,cAAcF,KAAY,EAAEG,KAAoB;IAC9DN,YAAYO,GAAG,CAACJ,OAAOG;AACzB;AAEA,OAAO,SAASE,YAAYC,KAAc;IACxC,OAAOV,QAAQU,SAASA,QAAQ,qBAAqB,CAArB,IAAIC,MAAM,KAAKD,QAAf,qBAAA;eAAA;oBAAA;sBAAA;IAAoB;AACtD;AAEA,OAAO,SAASE,yBAAyBR,KAAY;IACnD,sDAAsD;IACtD,IAAI,uBAAuBL,OAAO;QAChCO,cAAcF,OAAOL,MAAMc,iBAAiB;IAC9C;AACF;AAEA,OAAO,SAASC,iBAAiBC,WAAoB;IACnD,MAAMX,QAAQK,YAAYM;IAC1BH,yBAAyBR;IACzB,OAAOA;AACT","ignoreList":[0]}

View File

@@ -0,0 +1,102 @@
import { useEffect } from 'react';
import { isNextRouterError } from '../../../../client/components/is-next-router-error';
import { formatConsoleArgs, parseConsoleArgs } from '../../../../client/lib/console';
import isError from '../../../../lib/is-error';
import { createConsoleError } from '../../../shared/console-error';
import { coerceError, setOwnerStackIfAvailable } from './stitched-error';
import { forwardUnhandledError, logUnhandledRejection } from '../forward-logs';
const queueMicroTask = globalThis.queueMicrotask || ((cb)=>Promise.resolve().then(cb));
const errorQueue = [];
const errorHandlers = [];
const rejectionQueue = [];
const rejectionHandlers = [];
export function handleConsoleError(originError, consoleErrorArgs) {
let error;
const { environmentName } = parseConsoleArgs(consoleErrorArgs);
if (isError(originError)) {
error = createConsoleError(originError, environmentName);
} else {
error = createConsoleError(formatConsoleArgs(consoleErrorArgs), environmentName);
}
setOwnerStackIfAvailable(error);
errorQueue.push(error);
for (const handler of errorHandlers){
// Delayed the error being passed to React Dev Overlay,
// avoid the state being synchronously updated in the component.
queueMicroTask(()=>{
handler(error);
});
}
}
export function handleClientError(error) {
errorQueue.push(error);
for (const handler of errorHandlers){
// Delayed the error being passed to React Dev Overlay,
// avoid the state being synchronously updated in the component.
queueMicroTask(()=>{
handler(error);
});
}
}
export function useErrorHandler(handleOnUnhandledError, handleOnUnhandledRejection) {
useEffect(()=>{
// Handle queued errors.
errorQueue.forEach(handleOnUnhandledError);
rejectionQueue.forEach(handleOnUnhandledRejection);
// Listen to new errors.
errorHandlers.push(handleOnUnhandledError);
rejectionHandlers.push(handleOnUnhandledRejection);
return ()=>{
// Remove listeners.
errorHandlers.splice(errorHandlers.indexOf(handleOnUnhandledError), 1);
rejectionHandlers.splice(rejectionHandlers.indexOf(handleOnUnhandledRejection), 1);
// Reset error queues.
errorQueue.splice(0, errorQueue.length);
rejectionQueue.splice(0, rejectionQueue.length);
};
}, [
handleOnUnhandledError,
handleOnUnhandledRejection
]);
}
function onUnhandledError(event) {
const thrownValue = event.error;
if (isNextRouterError(thrownValue)) {
event.preventDefault();
return false;
}
// When there's an error property present, we log the error to error overlay.
// Otherwise we don't do anything as it's not logging in the console either.
if (thrownValue) {
const error = coerceError(thrownValue);
setOwnerStackIfAvailable(error);
handleClientError(error);
forwardUnhandledError(error);
}
}
function onUnhandledRejection(ev) {
const reason = ev?.reason;
if (isNextRouterError(reason)) {
ev.preventDefault();
return;
}
const error = coerceError(reason);
setOwnerStackIfAvailable(error);
rejectionQueue.push(error);
for (const handler of rejectionHandlers){
handler(error);
}
logUnhandledRejection(reason);
}
export function handleGlobalErrors() {
if (typeof window !== 'undefined') {
try {
// Increase the number of stack frames on the client
Error.stackTraceLimit = 50;
} catch {}
window.addEventListener('error', onUnhandledError);
window.addEventListener('unhandledrejection', onUnhandledRejection);
}
}
//# sourceMappingURL=use-error-handler.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,69 @@
import { configure } from 'next/dist/compiled/safe-stable-stringify';
import { getTerminalLoggingConfig } from './terminal-logging-config';
import { UNDEFINED_MARKER } from '../../shared/forward-logs-shared';
const terminalLoggingConfig = getTerminalLoggingConfig();
const PROMISE_MARKER = 'Promise {}';
const UNAVAILABLE_MARKER = '[Unable to view]';
const maximumDepth = typeof terminalLoggingConfig === 'object' && terminalLoggingConfig.depthLimit ? terminalLoggingConfig.depthLimit : 5;
const maximumBreadth = typeof terminalLoggingConfig === 'object' && terminalLoggingConfig.edgeLimit ? terminalLoggingConfig.edgeLimit : 100;
export const safeStringifyWithDepth = configure({
maximumDepth,
maximumBreadth
});
/**
* allows us to:
* - revive the undefined log in the server as it would look in the browser
* - not read/attempt to serialize promises (next will console error if you do that, and will cause this program to infinitely recurse)
* - if we read a proxy that throws (no way to detect if something is a proxy), explain to the user we can't read this data
*/ export function preLogSerializationClone(value, seen = new WeakMap()) {
if (value === undefined) return UNDEFINED_MARKER;
if (value === null || typeof value !== 'object') return value;
if (seen.has(value)) return seen.get(value);
try {
Object.keys(value);
} catch {
return UNAVAILABLE_MARKER;
}
try {
if (typeof value.then === 'function') return PROMISE_MARKER;
} catch {
return UNAVAILABLE_MARKER;
}
if (Array.isArray(value)) {
const out = [];
seen.set(value, out);
for (const item of value){
try {
out.push(preLogSerializationClone(item, seen));
} catch {
out.push(UNAVAILABLE_MARKER);
}
}
return out;
}
const proto = Object.getPrototypeOf(value);
if (proto === Object.prototype || proto === null) {
const out = {};
seen.set(value, out);
for (const key of Object.keys(value)){
try {
out[key] = preLogSerializationClone(value[key], seen);
} catch {
out[key] = UNAVAILABLE_MARKER;
}
}
return out;
}
return Object.prototype.toString.call(value);
}
// only safe if passed safeClone data
export const logStringify = (data)=>{
try {
const result = safeStringifyWithDepth(data);
return result ?? `"${UNAVAILABLE_MARKER}"`;
} catch {
return `"${UNAVAILABLE_MARKER}"`;
}
};
//# sourceMappingURL=forward-logs-utils.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/next-devtools/userspace/app/forward-logs-utils.ts"],"sourcesContent":["import { configure } from 'next/dist/compiled/safe-stable-stringify'\nimport { getTerminalLoggingConfig } from './terminal-logging-config'\nimport { UNDEFINED_MARKER } from '../../shared/forward-logs-shared'\n\nconst terminalLoggingConfig = getTerminalLoggingConfig()\n\nconst PROMISE_MARKER = 'Promise {}'\nconst UNAVAILABLE_MARKER = '[Unable to view]'\n\nconst maximumDepth =\n typeof terminalLoggingConfig === 'object' && terminalLoggingConfig.depthLimit\n ? terminalLoggingConfig.depthLimit\n : 5\nconst maximumBreadth =\n typeof terminalLoggingConfig === 'object' && terminalLoggingConfig.edgeLimit\n ? terminalLoggingConfig.edgeLimit\n : 100\n\nexport const safeStringifyWithDepth = configure({\n maximumDepth,\n maximumBreadth,\n})\n\n/**\n * allows us to:\n * - revive the undefined log in the server as it would look in the browser\n * - not read/attempt to serialize promises (next will console error if you do that, and will cause this program to infinitely recurse)\n * - if we read a proxy that throws (no way to detect if something is a proxy), explain to the user we can't read this data\n */\nexport function preLogSerializationClone<T>(\n value: T,\n seen = new WeakMap()\n): any {\n if (value === undefined) return UNDEFINED_MARKER\n if (value === null || typeof value !== 'object') return value\n if (seen.has(value as object)) return seen.get(value as object)\n\n try {\n Object.keys(value as object)\n } catch {\n return UNAVAILABLE_MARKER\n }\n\n try {\n if (typeof (value as any).then === 'function') return PROMISE_MARKER\n } catch {\n return UNAVAILABLE_MARKER\n }\n\n if (Array.isArray(value)) {\n const out: any[] = []\n seen.set(value, out)\n for (const item of value) {\n try {\n out.push(preLogSerializationClone(item, seen))\n } catch {\n out.push(UNAVAILABLE_MARKER)\n }\n }\n return out\n }\n\n const proto = Object.getPrototypeOf(value)\n if (proto === Object.prototype || proto === null) {\n const out: Record<string, unknown> = {}\n seen.set(value as object, out)\n for (const key of Object.keys(value as object)) {\n try {\n out[key] = preLogSerializationClone((value as any)[key], seen)\n } catch {\n out[key] = UNAVAILABLE_MARKER\n }\n }\n return out\n }\n\n return Object.prototype.toString.call(value)\n}\n\n// only safe if passed safeClone data\nexport const logStringify = (data: unknown): string => {\n try {\n const result = safeStringifyWithDepth(data)\n return result ?? `\"${UNAVAILABLE_MARKER}\"`\n } catch {\n return `\"${UNAVAILABLE_MARKER}\"`\n }\n}\n"],"names":["configure","getTerminalLoggingConfig","UNDEFINED_MARKER","terminalLoggingConfig","PROMISE_MARKER","UNAVAILABLE_MARKER","maximumDepth","depthLimit","maximumBreadth","edgeLimit","safeStringifyWithDepth","preLogSerializationClone","value","seen","WeakMap","undefined","has","get","Object","keys","then","Array","isArray","out","set","item","push","proto","getPrototypeOf","prototype","key","toString","call","logStringify","data","result"],"mappings":"AAAA,SAASA,SAAS,QAAQ,2CAA0C;AACpE,SAASC,wBAAwB,QAAQ,4BAA2B;AACpE,SAASC,gBAAgB,QAAQ,mCAAkC;AAEnE,MAAMC,wBAAwBF;AAE9B,MAAMG,iBAAiB;AACvB,MAAMC,qBAAqB;AAE3B,MAAMC,eACJ,OAAOH,0BAA0B,YAAYA,sBAAsBI,UAAU,GACzEJ,sBAAsBI,UAAU,GAChC;AACN,MAAMC,iBACJ,OAAOL,0BAA0B,YAAYA,sBAAsBM,SAAS,GACxEN,sBAAsBM,SAAS,GAC/B;AAEN,OAAO,MAAMC,yBAAyBV,UAAU;IAC9CM;IACAE;AACF,GAAE;AAEF;;;;;CAKC,GACD,OAAO,SAASG,yBACdC,KAAQ,EACRC,OAAO,IAAIC,SAAS;IAEpB,IAAIF,UAAUG,WAAW,OAAOb;IAChC,IAAIU,UAAU,QAAQ,OAAOA,UAAU,UAAU,OAAOA;IACxD,IAAIC,KAAKG,GAAG,CAACJ,QAAkB,OAAOC,KAAKI,GAAG,CAACL;IAE/C,IAAI;QACFM,OAAOC,IAAI,CAACP;IACd,EAAE,OAAM;QACN,OAAOP;IACT;IAEA,IAAI;QACF,IAAI,OAAO,AAACO,MAAcQ,IAAI,KAAK,YAAY,OAAOhB;IACxD,EAAE,OAAM;QACN,OAAOC;IACT;IAEA,IAAIgB,MAAMC,OAAO,CAACV,QAAQ;QACxB,MAAMW,MAAa,EAAE;QACrBV,KAAKW,GAAG,CAACZ,OAAOW;QAChB,KAAK,MAAME,QAAQb,MAAO;YACxB,IAAI;gBACFW,IAAIG,IAAI,CAACf,yBAAyBc,MAAMZ;YAC1C,EAAE,OAAM;gBACNU,IAAIG,IAAI,CAACrB;YACX;QACF;QACA,OAAOkB;IACT;IAEA,MAAMI,QAAQT,OAAOU,cAAc,CAAChB;IACpC,IAAIe,UAAUT,OAAOW,SAAS,IAAIF,UAAU,MAAM;QAChD,MAAMJ,MAA+B,CAAC;QACtCV,KAAKW,GAAG,CAACZ,OAAiBW;QAC1B,KAAK,MAAMO,OAAOZ,OAAOC,IAAI,CAACP,OAAkB;YAC9C,IAAI;gBACFW,GAAG,CAACO,IAAI,GAAGnB,yBAAyB,AAACC,KAAa,CAACkB,IAAI,EAAEjB;YAC3D,EAAE,OAAM;gBACNU,GAAG,CAACO,IAAI,GAAGzB;YACb;QACF;QACA,OAAOkB;IACT;IAEA,OAAOL,OAAOW,SAAS,CAACE,QAAQ,CAACC,IAAI,CAACpB;AACxC;AAEA,qCAAqC;AACrC,OAAO,MAAMqB,eAAe,CAACC;IAC3B,IAAI;QACF,MAAMC,SAASzB,uBAAuBwB;QACtC,OAAOC,UAAU,CAAC,CAAC,EAAE9B,mBAAmB,CAAC,CAAC;IAC5C,EAAE,OAAM;QACN,OAAO,CAAC,CAAC,EAAEA,mBAAmB,CAAC,CAAC;IAClC;AACF,EAAC","ignoreList":[0]}

View File

@@ -0,0 +1,467 @@
import { getOwnerStack, setOwnerStackIfAvailable } from './errors/stitched-error';
import { getErrorSource } from '../../../shared/lib/error-source';
import { getIsTerminalLoggingEnabled } from './terminal-logging-config';
import { patchConsoleMethod } from '../../shared/forward-logs-shared';
import { preLogSerializationClone, logStringify, safeStringifyWithDepth } from './forward-logs-utils';
// Client-side file logger for browser logs
class ClientFileLogger {
formatTimestamp() {
const now = new Date();
const hours = now.getHours().toString().padStart(2, '0');
const minutes = now.getMinutes().toString().padStart(2, '0');
const seconds = now.getSeconds().toString().padStart(2, '0');
const milliseconds = now.getMilliseconds().toString().padStart(3, '0');
return `${hours}:${minutes}:${seconds}.${milliseconds}`;
}
log(level, args) {
if (isReactServerReplayedLog(args)) {
return;
}
// Format the args into a message string
const message = args.map((arg)=>{
if (typeof arg === 'string') return arg;
if (typeof arg === 'number' || typeof arg === 'boolean') return String(arg);
if (arg === null) return 'null';
if (arg === undefined) return 'undefined';
// Handle DOM nodes - only log the tag name to avoid React proxied elements
if (arg instanceof Element) {
return `<${arg.tagName.toLowerCase()}>`;
}
return safeStringifyWithDepth(arg);
}).join(' ');
const logEntry = {
timestamp: this.formatTimestamp(),
level: level.toUpperCase(),
message
};
this.logEntries.push(logEntry);
// Schedule flush when new log is added
scheduleLogFlush();
}
getLogs() {
return [
...this.logEntries
];
}
clear() {
this.logEntries = [];
}
constructor(){
this.logEntries = [];
}
}
const clientFileLogger = new ClientFileLogger();
// Set up flush-based sending of client file logs
let logFlushTimeout = null;
let heartbeatInterval = null;
const scheduleLogFlush = ()=>{
if (logFlushTimeout) {
clearTimeout(logFlushTimeout);
}
logFlushTimeout = setTimeout(()=>{
sendClientFileLogs();
logFlushTimeout = null;
}, 100) // Send after 100ms (much faster with debouncing)
;
};
const cancelLogFlush = ()=>{
if (logFlushTimeout) {
clearTimeout(logFlushTimeout);
logFlushTimeout = null;
}
};
const startHeartbeat = ()=>{
if (heartbeatInterval) return;
heartbeatInterval = setInterval(()=>{
if (logQueue.socket && logQueue.socket.readyState === WebSocket.OPEN) {
try {
// Send a ping to keep the connection alive
logQueue.socket.send(JSON.stringify({
event: 'ping'
}));
} catch (error) {
// Connection might be closed, stop heartbeat
stopHeartbeat();
}
} else {
stopHeartbeat();
}
}, 5000) // Send ping every 5 seconds
;
};
const stopHeartbeat = ()=>{
if (heartbeatInterval) {
clearInterval(heartbeatInterval);
heartbeatInterval = null;
}
};
const isTerminalLoggingEnabled = getIsTerminalLoggingEnabled();
const methods = [
'log',
'info',
'warn',
'debug',
'table',
'assert',
'dir',
'dirxml',
'group',
'groupCollapsed',
'groupEnd',
'trace'
];
const afterThisFrame = (cb)=>{
let timeout;
const rafId = requestAnimationFrame(()=>{
timeout = setTimeout(()=>{
cb();
});
});
return ()=>{
cancelAnimationFrame(rafId);
clearTimeout(timeout);
};
};
let isPatched = false;
const serializeEntries = (entries)=>entries.map((clientEntry)=>{
switch(clientEntry.kind){
case 'any-logged-error':
case 'console':
{
return {
...clientEntry,
args: clientEntry.args.map(stringifyUserArg)
};
}
case 'formatted-error':
{
return clientEntry;
}
default:
{
return null;
}
}
});
// Function to send client file logs to server
const sendClientFileLogs = ()=>{
if (!logQueue.socket || logQueue.socket.readyState !== WebSocket.OPEN) {
return;
}
const logs = clientFileLogger.getLogs();
if (logs.length === 0) {
return;
}
try {
const payload = JSON.stringify({
event: 'client-file-logs',
logs: logs
});
logQueue.socket.send(payload);
} catch (error) {
console.error(error);
} finally{
// Clear logs regardless of send success to prevent memory leaks
clientFileLogger.clear();
}
};
// Combined state and public API
export const logQueue = {
entries: [],
flushScheduled: false,
cancelFlush: null,
socket: null,
sourceType: undefined,
router: null,
scheduleLogSend: (entry)=>{
logQueue.entries.push(entry);
if (logQueue.flushScheduled) {
return;
}
// safe to deref and use in setTimeout closure since we cancel on new socket
const socket = logQueue.socket;
if (!socket) {
return;
}
// we probably dont need this
logQueue.flushScheduled = true;
// non blocking log flush, runs at most once per frame
logQueue.cancelFlush = afterThisFrame(()=>{
logQueue.flushScheduled = false;
// just incase
try {
const payload = JSON.stringify({
event: 'browser-logs',
entries: serializeEntries(logQueue.entries),
router: logQueue.router,
// needed for source mapping, we just assign the sourceType from the last error for the whole batch
sourceType: logQueue.sourceType
});
socket.send(payload);
logQueue.entries = [];
logQueue.sourceType = undefined;
// Also send client file logs
sendClientFileLogs();
} catch {
// error (make sure u don't infinite loop)
/* noop */ }
});
},
onSocketReady: (socket)=>{
// When MCP or terminal logging is enabled, we enable the socket connection,
// otherwise it will not proceed.
if (!isTerminalLoggingEnabled && !process.env.__NEXT_MCP_SERVER) {
return;
}
if (socket.readyState !== WebSocket.OPEN) {
// invariant
return;
}
// incase an existing timeout was going to run with a stale socket
logQueue.cancelFlush?.();
logQueue.socket = socket;
// Add socket event listeners to track connection state
socket.addEventListener('close', ()=>{
cancelLogFlush();
stopHeartbeat();
});
// Only send terminal logs if enabled
if (isTerminalLoggingEnabled) {
try {
const payload = JSON.stringify({
event: 'browser-logs',
entries: serializeEntries(logQueue.entries),
router: logQueue.router,
sourceType: logQueue.sourceType
});
socket.send(payload);
logQueue.entries = [];
logQueue.sourceType = undefined;
} catch {
/** noop just incase */ }
}
// Always send client file logs when socket is ready
sendClientFileLogs();
// Start heartbeat to keep connection alive
startHeartbeat();
}
};
const stringifyUserArg = (arg)=>{
if (arg.kind !== 'arg') {
return arg;
}
return {
...arg,
data: logStringify(arg.data)
};
};
const createErrorArg = (error)=>{
const stack = stackWithOwners(error);
return {
kind: 'formatted-error-arg',
prefix: error.message ? `${error.name}: ${error.message}` : `${error.name}`,
stack
};
};
const createLogEntry = (level, args)=>{
// Always log to client file logger with args (formatting done inside log method)
clientFileLogger.log(level, args);
// Only forward to terminal if enabled
if (!isTerminalLoggingEnabled) {
return;
}
// do not abstract this, it implicitly relies on which functions call it. forcing the inlined implementation makes you think about callers
// error capture stack trace maybe
const stack = stackWithOwners(new Error());
const stackLines = stack?.split('\n');
const cleanStack = stackLines?.slice(3).join('\n') // this is probably ignored anyways
;
const entry = {
kind: 'console',
consoleMethodStack: cleanStack ?? null,
method: level,
args: args.map((arg)=>{
if (arg instanceof Error) {
return createErrorArg(arg);
}
return {
kind: 'arg',
data: preLogSerializationClone(arg)
};
})
};
logQueue.scheduleLogSend(entry);
};
export const forwardErrorLog = (args)=>{
// Always log to client file logger with args (formatting done inside log method)
clientFileLogger.log('error', args);
// Only forward to terminal if enabled
if (!isTerminalLoggingEnabled) {
return;
}
const errorObjects = args.filter((arg)=>arg instanceof Error);
const first = errorObjects.at(0);
if (first) {
const source = getErrorSource(first);
if (source) {
logQueue.sourceType = source;
}
}
/**
* browser shows stack regardless of type of data passed to console.error, so we should do the same
*
* do not abstract this, it implicitly relies on which functions call it. forcing the inlined implementation makes you think about callers
*/ const stack = stackWithOwners(new Error());
const stackLines = stack?.split('\n');
const cleanStack = stackLines?.slice(3).join('\n');
const entry = {
kind: 'any-logged-error',
method: 'error',
consoleErrorStack: cleanStack ?? '',
args: args.map((arg)=>{
if (arg instanceof Error) {
return createErrorArg(arg);
}
return {
kind: 'arg',
data: preLogSerializationClone(arg)
};
})
};
logQueue.scheduleLogSend(entry);
};
const createUncaughtErrorEntry = (errorName, errorMessage, fullStack)=>{
const entry = {
kind: 'formatted-error',
prefix: `Uncaught ${errorName}: ${errorMessage}`,
stack: fullStack,
method: 'error'
};
logQueue.scheduleLogSend(entry);
};
const stackWithOwners = (error)=>{
let ownerStack = '';
setOwnerStackIfAvailable(error);
ownerStack = getOwnerStack(error) || '';
const stack = (error.stack || '') + ownerStack;
return stack;
};
export function logUnhandledRejection(reason) {
// Always log to client file logger
const message = reason instanceof Error ? `${reason.name}: ${reason.message}` : JSON.stringify(reason);
clientFileLogger.log('error', [
`unhandledRejection: ${message}`
]);
// Only forward to terminal if enabled
if (!isTerminalLoggingEnabled) {
return;
}
if (reason instanceof Error) {
createUnhandledRejectionErrorEntry(reason, stackWithOwners(reason));
return;
}
createUnhandledRejectionNonErrorEntry(reason);
}
const createUnhandledRejectionErrorEntry = (error, fullStack)=>{
const source = getErrorSource(error);
if (source) {
logQueue.sourceType = source;
}
const entry = {
kind: 'formatted-error',
prefix: ` unhandledRejection: ${error.name}: ${error.message}`,
stack: fullStack,
method: 'error'
};
logQueue.scheduleLogSend(entry);
};
const createUnhandledRejectionNonErrorEntry = (reason)=>{
const entry = {
kind: 'any-logged-error',
// we can't access the stack since the event is dispatched async and creating an inline error would be meaningless
consoleErrorStack: '',
method: 'error',
args: [
{
kind: 'arg',
data: ` unhandledRejection:`,
isRejectionMessage: true
},
{
kind: 'arg',
data: preLogSerializationClone(reason)
}
]
};
logQueue.scheduleLogSend(entry);
};
const isHMR = (args)=>{
const firstArg = args[0];
if (typeof firstArg !== 'string') {
return false;
}
if (firstArg.startsWith('[Fast Refresh]')) {
return true;
}
if (firstArg.startsWith('[HMR]')) {
return true;
}
return false;
};
/**
* Matches the format of logs arguments React replayed from the RSC.
*/ const isReactServerReplayedLog = (args)=>{
if (args.length < 3) {
return false;
}
const [format, styles, label] = args;
if (typeof format !== 'string' || typeof styles !== 'string' || typeof label !== 'string') {
return false;
}
return format.startsWith('%c%s%c') && styles.includes('background:');
};
export function forwardUnhandledError(error) {
// Always log to client file logger
clientFileLogger.log('error', [
`uncaughtError: ${error.name}: ${error.message}`
]);
// Only forward to terminal if enabled
if (!isTerminalLoggingEnabled) {
return;
}
createUncaughtErrorEntry(error.name, error.message, stackWithOwners(error));
}
// TODO: this router check is brittle, we need to update based on the current router the user is using
export const initializeDebugLogForwarding = (router)=>{
// probably don't need this
if (isPatched) {
return;
}
// TODO(rob): why does this break rendering on server, important to know incase the same bug appears in browser
if (typeof window === 'undefined') {
return;
}
// better to be safe than sorry
try {
methods.forEach((method)=>patchConsoleMethod(method, (_, ...args)=>{
if (isHMR(args)) {
return;
}
if (isReactServerReplayedLog(args)) {
return;
}
createLogEntry(method, args);
}));
} catch {}
logQueue.router = router;
isPatched = true;
// Cleanup on page unload
window.addEventListener('beforeunload', ()=>{
cancelLogFlush();
stopHeartbeat();
// Send any remaining logs before page unloads
sendClientFileLogs();
});
};
//# sourceMappingURL=forward-logs.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,113 @@
'use client';
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { useState, createContext, useContext, use, useMemo, useCallback } from 'react';
import { useLayoutEffect } from 'react';
import { dispatcher } from 'next/dist/compiled/next-devtools';
import { notFound } from '../../../client/components/not-found';
export const SEGMENT_EXPLORER_SIMULATED_ERROR_MESSAGE = 'NEXT_DEVTOOLS_SIMULATED_ERROR';
function SegmentTrieNode({ type, pagePath }) {
const { boundaryType, setBoundaryType } = useSegmentState();
const nodeState = useMemo(()=>{
return {
type,
pagePath,
boundaryType,
setBoundaryType
};
}, [
type,
pagePath,
boundaryType,
setBoundaryType
]);
// Use `useLayoutEffect` to ensure the state is updated during suspense.
// `useEffect` won't work as the state is preserved during suspense.
useLayoutEffect(()=>{
dispatcher.segmentExplorerNodeAdd(nodeState);
return ()=>{
dispatcher.segmentExplorerNodeRemove(nodeState);
};
}, [
nodeState
]);
return null;
}
function NotFoundSegmentNode() {
notFound();
}
function ErrorSegmentNode() {
throw Object.defineProperty(new Error(SEGMENT_EXPLORER_SIMULATED_ERROR_MESSAGE), "__NEXT_ERROR_CODE", {
value: "E394",
enumerable: false,
configurable: true
});
}
const forever = new Promise(()=>{});
function LoadingSegmentNode() {
use(forever);
return null;
}
export function SegmentViewStateNode({ page }) {
useLayoutEffect(()=>{
dispatcher.segmentExplorerUpdateRouteState(page);
return ()=>{
dispatcher.segmentExplorerUpdateRouteState('');
};
}, [
page
]);
return null;
}
export function SegmentBoundaryTriggerNode() {
const { boundaryType } = useSegmentState();
let segmentNode = null;
if (boundaryType === 'loading') {
segmentNode = /*#__PURE__*/ _jsx(LoadingSegmentNode, {});
} else if (boundaryType === 'not-found') {
segmentNode = /*#__PURE__*/ _jsx(NotFoundSegmentNode, {});
} else if (boundaryType === 'error') {
segmentNode = /*#__PURE__*/ _jsx(ErrorSegmentNode, {});
}
return segmentNode;
}
export function SegmentViewNode({ type, pagePath, children }) {
const segmentNode = /*#__PURE__*/ _jsx(SegmentTrieNode, {
type: type,
pagePath: pagePath
}, type);
return /*#__PURE__*/ _jsxs(_Fragment, {
children: [
segmentNode,
children
]
});
}
const SegmentStateContext = /*#__PURE__*/ createContext({
boundaryType: null,
setBoundaryType: ()=>{}
});
export function SegmentStateProvider({ children }) {
const [boundaryType, setBoundaryType] = useState(null);
const [errorBoundaryKey, setErrorBoundaryKey] = useState(0);
const reloadBoundary = useCallback(()=>setErrorBoundaryKey((prev)=>prev + 1), []);
const setBoundaryTypeAndReload = useCallback((type)=>{
if (type === null) {
reloadBoundary();
}
setBoundaryType(type);
}, [
reloadBoundary
]);
return /*#__PURE__*/ _jsx(SegmentStateContext.Provider, {
value: {
boundaryType,
setBoundaryType: setBoundaryTypeAndReload
},
children: children
}, errorBoundaryKey);
}
export function useSegmentState() {
return useContext(SegmentStateContext);
}
//# sourceMappingURL=segment-explorer-node.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,13 @@
export function getTerminalLoggingConfig() {
try {
return JSON.parse(process.env.__NEXT_BROWSER_DEBUG_INFO_IN_TERMINAL || 'false');
} catch {
return false;
}
}
export function getIsTerminalLoggingEnabled() {
const config = getTerminalLoggingConfig();
return Boolean(config);
}
//# sourceMappingURL=terminal-logging-config.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/next-devtools/userspace/app/terminal-logging-config.ts"],"sourcesContent":["export function getTerminalLoggingConfig():\n | false\n | boolean\n | {\n depthLimit?: number\n edgeLimit?: number\n showSourceLocation?: boolean\n } {\n try {\n return JSON.parse(\n process.env.__NEXT_BROWSER_DEBUG_INFO_IN_TERMINAL || 'false'\n )\n } catch {\n return false\n }\n}\n\nexport function getIsTerminalLoggingEnabled(): boolean {\n const config = getTerminalLoggingConfig()\n return Boolean(config)\n}\n"],"names":["getTerminalLoggingConfig","JSON","parse","process","env","__NEXT_BROWSER_DEBUG_INFO_IN_TERMINAL","getIsTerminalLoggingEnabled","config","Boolean"],"mappings":"AAAA,OAAO,SAASA;IAQd,IAAI;QACF,OAAOC,KAAKC,KAAK,CACfC,QAAQC,GAAG,CAACC,qCAAqC,IAAI;IAEzD,EAAE,OAAM;QACN,OAAO;IACT;AACF;AAEA,OAAO,SAASC;IACd,MAAMC,SAASP;IACf,OAAOQ,QAAQD;AACjB","ignoreList":[0]}

View File

@@ -0,0 +1,131 @@
import { getHydrationWarningType, isHydrationError as isReact18HydrationError, isHydrationWarning as isReact18HydrationWarning } from '../../shared/react-18-hydration-error';
import { isHydrationError as isReact19HydrationError, isErrorMessageWithComponentStackDiff as isReact19HydrationWarning } from '../../shared/react-19-hydration-error';
// We only need this for React 18 or hydration console errors in React 19.
// Once we surface console.error in the dev overlay in pages router, we should only
// use this for React 18.
let hydrationErrorState = {};
const squashedHydrationErrorDetails = new WeakMap();
export function getSquashedHydrationErrorDetails(error) {
return squashedHydrationErrorDetails.has(error) ? squashedHydrationErrorDetails.get(error) : null;
}
export function attachHydrationErrorState(error) {
if (!isReact18HydrationError(error) && !isReact19HydrationError(error)) {
return;
}
let parsedHydrationErrorState = {};
// If there's any extra information in the error message to display,
// append it to the error message details property
if (hydrationErrorState.warning) {
// The patched console.error found hydration errors logged by React
// Append the logged warning to the error message
parsedHydrationErrorState = {
// It contains the warning, component stack, server and client tag names
...hydrationErrorState
};
// Consume the cached hydration diff.
// This is only required for now when we still squashed the hydration diff log into hydration error.
// Once the all error is logged to dev overlay in order, this will go away.
if (hydrationErrorState.reactOutputComponentDiff) {
parsedHydrationErrorState.reactOutputComponentDiff = hydrationErrorState.reactOutputComponentDiff;
}
squashedHydrationErrorDetails.set(error, parsedHydrationErrorState);
}
}
// TODO: Only handle React 18. Once we surface console.error in the dev overlay in pages router,
// we can use the same behavior as App Router.
export function storeHydrationErrorStateFromConsoleArgs(...args) {
let [message, firstContent, secondContent, ...rest] = args;
if (isReact18HydrationWarning(message)) {
// Some hydration warnings has 4 arguments, some has 3, fallback to the last argument
// when the 3rd argument is not the component stack but an empty string
// For some warnings, there's only 1 argument for template.
// The second argument is the diff or component stack.
if (args.length === 3) {
secondContent = '';
}
const warning = message.replace(/Warning: /, '').replace('%s', firstContent).replace('%s', secondContent)// remove the last %s from the message
.replace(/%s/g, '');
const lastArg = (rest[rest.length - 1] || '').trim();
hydrationErrorState.reactOutputComponentDiff = generateHydrationDiffReact18(message, firstContent, secondContent, lastArg);
hydrationErrorState.warning = warning;
} else if (isReact19HydrationWarning(message)) {
// Some hydration warnings has 4 arguments, some has 3, fallback to the last argument
// when the 3rd argument is not the component stack but an empty string
// For some warnings, there's only 1 argument for template.
// The second argument is the diff or component stack.
if (args.length === 3) {
secondContent = '';
}
const warning = message.replace('%s', firstContent).replace('%s', secondContent)// remove the last %s from the message
.replace(/%s/g, '');
const lastArg = (args[args.length - 1] || '').trim();
hydrationErrorState.reactOutputComponentDiff = lastArg;
hydrationErrorState.warning = warning;
}
}
/*
* Some hydration errors in React 18 does not have the diff in the error message.
* Instead it has the error stack trace which is component stack that we can leverage.
* Will parse the diff from the error stack trace
* e.g.
* Warning: Expected server HTML to contain a matching <div> in <p>.
* at div
* at p
* at div
* at div
* at Page
* output:
* <Page>
* <div>
* <p>
* > <div>
*
*/ function generateHydrationDiffReact18(message, firstContent, secondContent, lastArg) {
const componentStack = lastArg;
let firstIndex = -1;
let secondIndex = -1;
const hydrationWarningType = getHydrationWarningType(message);
// at div\n at Foo\n at Bar (....)\n -> [div, Foo]
const components = componentStack.split('\n')// .reverse()
.map((line, index)=>{
// `<space>at <component> (<location>)` -> `at <component> (<location>)`
line = line.trim();
// extract `<space>at <component>` to `<<component>>`
// e.g. ` at Foo` -> `<Foo>`
const [, component, location] = /at (\w+)( \((.*)\))?/.exec(line) || [];
// If there's no location then it's user-land stack frame
if (!location) {
if (component === firstContent && firstIndex === -1) {
firstIndex = index;
} else if (component === secondContent && secondIndex === -1) {
secondIndex = index;
}
}
return location ? '' : component;
}).filter(Boolean).reverse();
let diff = '';
for(let i = 0; i < components.length; i++){
const component = components[i];
const matchFirstContent = hydrationWarningType === 'tag' && i === components.length - firstIndex - 1;
const matchSecondContent = hydrationWarningType === 'tag' && i === components.length - secondIndex - 1;
if (matchFirstContent || matchSecondContent) {
const spaces = ' '.repeat(Math.max(i * 2 - 2, 0) + 2);
diff += `> ${spaces}<${component}>\n`;
} else {
const spaces = ' '.repeat(i * 2 + 2);
diff += `${spaces}<${component}>\n`;
}
}
if (hydrationWarningType === 'text') {
const spaces = ' '.repeat(components.length * 2);
diff += `+ ${spaces}"${firstContent}"\n`;
diff += `- ${spaces}"${secondContent}"\n`;
} else if (hydrationWarningType === 'text-in-tag') {
const spaces = ' '.repeat(components.length * 2);
diff += `> ${spaces}<${secondContent}>\n`;
diff += `> ${spaces}"${firstContent}"\n`;
}
return diff;
}
//# sourceMappingURL=hydration-error-state.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,20 @@
import React from 'react';
export class PagesDevOverlayErrorBoundary extends React.PureComponent {
static getDerivedStateFromError(error) {
return {
error
};
}
// Explicit type is needed to avoid the generated `.d.ts` having a wide return type that could be specific to the `@types/react` version.
render() {
// The component has to be unmounted or else it would continue to error
return this.state.error ? null : this.props.children;
}
constructor(...args){
super(...args), this.state = {
error: null
};
}
}
//# sourceMappingURL=pages-dev-overlay-error-boundary.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../../src/next-devtools/userspace/pages/pages-dev-overlay-error-boundary.tsx"],"sourcesContent":["import React from 'react'\n\ntype PagesDevOverlayErrorBoundaryProps = {\n children?: React.ReactNode\n}\ntype PagesDevOverlayErrorBoundaryState = { error: Error | null }\n\nexport class PagesDevOverlayErrorBoundary extends React.PureComponent<\n PagesDevOverlayErrorBoundaryProps,\n PagesDevOverlayErrorBoundaryState\n> {\n state = { error: null }\n\n static getDerivedStateFromError(error: Error) {\n return { error }\n }\n\n // Explicit type is needed to avoid the generated `.d.ts` having a wide return type that could be specific to the `@types/react` version.\n render(): React.ReactNode {\n // The component has to be unmounted or else it would continue to error\n return this.state.error ? null : this.props.children\n }\n}\n"],"names":["React","PagesDevOverlayErrorBoundary","PureComponent","getDerivedStateFromError","error","render","state","props","children"],"mappings":"AAAA,OAAOA,WAAW,QAAO;AAOzB,OAAO,MAAMC,qCAAqCD,MAAME,aAAa;IAMnE,OAAOC,yBAAyBC,KAAY,EAAE;QAC5C,OAAO;YAAEA;QAAM;IACjB;IAEA,yIAAyI;IACzIC,SAA0B;QACxB,uEAAuE;QACvE,OAAO,IAAI,CAACC,KAAK,CAACF,KAAK,GAAG,OAAO,IAAI,CAACG,KAAK,CAACC,QAAQ;IACtD;;QAdK,qBAILF,QAAQ;YAAEF,OAAO;QAAK;;AAWxB","ignoreList":[0]}

View File

@@ -0,0 +1,85 @@
import { jsx as _jsx } from "react/jsx-runtime";
import React from 'react';
import { renderPagesDevOverlay } from 'next/dist/compiled/next-devtools';
import { dispatcher } from 'next/dist/compiled/next-devtools';
import { attachHydrationErrorState, storeHydrationErrorStateFromConsoleArgs } from './hydration-error-state';
import { Router } from '../../../client/router';
import { getOwnerStack } from '../app/errors/stitched-error';
import { isRecoverableError } from '../../../client/react-client-callbacks/on-recoverable-error';
import { getSquashedHydrationErrorDetails } from './hydration-error-state';
import { PagesDevOverlayErrorBoundary } from './pages-dev-overlay-error-boundary';
import { initializeDebugLogForwarding, forwardUnhandledError, logUnhandledRejection, forwardErrorLog } from '../app/forward-logs';
const usePagesDevOverlayBridge = ()=>{
React.useInsertionEffect(()=>{
// NDT uses a different React instance so it's not technically a state update
// scheduled from useInsertionEffect.
renderPagesDevOverlay(getOwnerStack, getSquashedHydrationErrorDetails, isRecoverableError);
}, []);
React.useEffect(()=>{
const { handleStaticIndicator } = require('../../../client/dev/hot-reloader/pages/hot-reloader-pages');
Router.events.on('routeChangeComplete', handleStaticIndicator);
return function() {
Router.events.off('routeChangeComplete', handleStaticIndicator);
};
}, []);
};
export function PagesDevOverlayBridge({ children }) {
usePagesDevOverlayBridge();
return /*#__PURE__*/ _jsx(PagesDevOverlayErrorBoundary, {
children: children
});
}
let isRegistered = false;
function handleError(error) {
if (!error || !(error instanceof Error) || typeof error.stack !== 'string') {
// A non-error was thrown, we don't have anything to show. :-(
return;
}
attachHydrationErrorState(error);
// Skip ModuleBuildError and ModuleNotFoundError, as it will be sent through onBuildError callback.
// This is to avoid same error as different type showing up on client to cause flashing.
if (error.name !== 'ModuleBuildError' && error.name !== 'ModuleNotFoundError') {
dispatcher.onUnhandledError(error);
}
}
let origConsoleError = console.error;
function nextJsHandleConsoleError(...args) {
// See https://github.com/facebook/react/blob/d50323eb845c5fde0d720cae888bf35dedd05506/packages/react-reconciler/src/ReactFiberErrorLogger.js#L78
const maybeError = process.env.NODE_ENV !== 'production' ? args[1] : args[0];
storeHydrationErrorStateFromConsoleArgs(...args);
// TODO: Surfaces non-errors logged via `console.error`.
handleError(maybeError);
forwardErrorLog(args);
origConsoleError.apply(window.console, args);
}
function onUnhandledError(event) {
const error = event?.error;
handleError(error);
if (error) {
forwardUnhandledError(error);
}
}
function onUnhandledRejection(ev) {
const reason = ev?.reason;
if (!reason || !(reason instanceof Error) || typeof reason.stack !== 'string') {
// A non-error was thrown, we don't have anything to show. :-(
return;
}
dispatcher.onUnhandledRejection(reason);
logUnhandledRejection(reason);
}
export function register() {
if (isRegistered) {
return;
}
isRegistered = true;
try {
Error.stackTraceLimit = 50;
} catch {}
initializeDebugLogForwarding('pages');
window.addEventListener('error', onUnhandledError);
window.addEventListener('unhandledrejection', onUnhandledRejection);
window.console.error = nextJsHandleConsoleError;
}
//# sourceMappingURL=pages-dev-overlay-setup.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,18 @@
'use client';
import { useEffect, useTransition } from 'react';
import { dispatcher } from 'next/dist/compiled/next-devtools';
export const useAppDevRenderingIndicator = ()=>{
const [isPending, startTransition] = useTransition();
useEffect(()=>{
if (isPending) {
dispatcher.renderingIndicatorShow();
} else {
dispatcher.renderingIndicatorHide();
}
}, [
isPending
]);
return startTransition;
};
//# sourceMappingURL=use-app-dev-rendering-indicator.js.map

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../../../../src/next-devtools/userspace/use-app-dev-rendering-indicator.tsx"],"sourcesContent":["'use client'\n\nimport { useEffect, useTransition } from 'react'\nimport { dispatcher } from 'next/dist/compiled/next-devtools'\n\nexport const useAppDevRenderingIndicator = () => {\n const [isPending, startTransition] = useTransition()\n\n useEffect(() => {\n if (isPending) {\n dispatcher.renderingIndicatorShow()\n } else {\n dispatcher.renderingIndicatorHide()\n }\n }, [isPending])\n\n return startTransition\n}\n"],"names":["useEffect","useTransition","dispatcher","useAppDevRenderingIndicator","isPending","startTransition","renderingIndicatorShow","renderingIndicatorHide"],"mappings":"AAAA;AAEA,SAASA,SAAS,EAAEC,aAAa,QAAQ,QAAO;AAChD,SAASC,UAAU,QAAQ,mCAAkC;AAE7D,OAAO,MAAMC,8BAA8B;IACzC,MAAM,CAACC,WAAWC,gBAAgB,GAAGJ;IAErCD,UAAU;QACR,IAAII,WAAW;YACbF,WAAWI,sBAAsB;QACnC,OAAO;YACLJ,WAAWK,sBAAsB;QACnC;IACF,GAAG;QAACH;KAAU;IAEd,OAAOC;AACT,EAAC","ignoreList":[0]}