import path from "path"; import fs from "fs/promises"; import * as Log from "../../../build/output/log"; import setupDebug from "next/dist/compiled/debug"; import LRUCache from "next/dist/compiled/lru-cache"; import loadCustomRoutes from "../../../lib/load-custom-routes"; import { modifyRouteRegex } from "../../../lib/redirect-status"; import { FileType, fileExists } from "../../../lib/file-exists"; import { recursiveReadDir } from "../../../lib/recursive-readdir"; import { isDynamicRoute } from "../../../shared/lib/router/utils"; import { escapeStringRegexp } from "../../../shared/lib/escape-regexp"; import { getPathMatch } from "../../../shared/lib/router/utils/path-match"; import { getRouteRegex } from "../../../shared/lib/router/utils/route-regex"; import { getRouteMatcher } from "../../../shared/lib/router/utils/route-matcher"; import { pathHasPrefix } from "../../../shared/lib/router/utils/path-has-prefix"; import { normalizeLocalePath } from "../../../shared/lib/i18n/normalize-locale-path"; import { removePathPrefix } from "../../../shared/lib/router/utils/remove-path-prefix"; import { getMiddlewareRouteMatcher } from "../../../shared/lib/router/utils/middleware-route-matcher"; import { APP_PATH_ROUTES_MANIFEST, BUILD_ID_FILE, MIDDLEWARE_MANIFEST, PAGES_MANIFEST, PRERENDER_MANIFEST, ROUTES_MANIFEST } from "../../../shared/lib/constants"; import { normalizePathSep } from "../../../shared/lib/page-path/normalize-path-sep"; import { normalizeMetadataRoute } from "../../../lib/metadata/get-metadata-route"; const debug = setupDebug("next:router-server:filesystem"); export const buildCustomRoute = (type, item, basePath, caseSensitive)=>{ const restrictedRedirectPaths = [ "/_next" ].map((p)=>basePath ? `${basePath}${p}` : p); const match = getPathMatch(item.source, { strict: true, removeUnnamedParams: true, regexModifier: !item.internal ? (regex)=>modifyRouteRegex(regex, type === "redirect" ? restrictedRedirectPaths : undefined) : undefined, sensitive: caseSensitive }); return { ...item, ...type === "rewrite" ? { check: true } : {}, match }; }; export async function setupFsCheck(opts) { const getItemsLru = !opts.dev ? new LRUCache({ max: 1024 * 1024, length (value, key) { if (!value) return (key == null ? void 0 : key.length) || 0; return (key || "").length + (value.fsPath || "").length + value.itemPath.length + value.type.length; } }) : undefined; // routes that have _next/data endpoints (SSG/SSP) const nextDataRoutes = new Set(); const publicFolderItems = new Set(); const nextStaticFolderItems = new Set(); const legacyStaticFolderItems = new Set(); const appFiles = new Set(); const pageFiles = new Set(); let dynamicRoutes = []; let middlewareMatcher = ()=>false; const distDir = path.join(opts.dir, opts.config.distDir); const publicFolderPath = path.join(opts.dir, "public"); const nextStaticFolderPath = path.join(distDir, "static"); const legacyStaticFolderPath = path.join(opts.dir, "static"); let customRoutes = { redirects: [], rewrites: { beforeFiles: [], afterFiles: [], fallback: [] }, headers: [] }; let buildId = "development"; let prerenderManifest; if (!opts.dev) { var _middlewareManifest_middleware_, _middlewareManifest_middleware; const buildIdPath = path.join(opts.dir, opts.config.distDir, BUILD_ID_FILE); buildId = await fs.readFile(buildIdPath, "utf8"); try { for (const file of (await recursiveReadDir(publicFolderPath))){ // Ensure filename is encoded and normalized. publicFolderItems.add(encodeURI(normalizePathSep(file))); } } catch (err) { if (err.code !== "ENOENT") { throw err; } } try { for (const file of (await recursiveReadDir(legacyStaticFolderPath))){ // Ensure filename is encoded and normalized. legacyStaticFolderItems.add(encodeURI(normalizePathSep(file))); } Log.warn(`The static directory has been deprecated in favor of the public directory. https://nextjs.org/docs/messages/static-dir-deprecated`); } catch (err) { if (err.code !== "ENOENT") { throw err; } } try { for (const file of (await recursiveReadDir(nextStaticFolderPath))){ // Ensure filename is encoded and normalized. nextStaticFolderItems.add(path.posix.join("/_next/static", encodeURI(normalizePathSep(file)))); } } catch (err) { if (opts.config.output !== "standalone") throw err; } const routesManifestPath = path.join(distDir, ROUTES_MANIFEST); const prerenderManifestPath = path.join(distDir, PRERENDER_MANIFEST); const middlewareManifestPath = path.join(distDir, "server", MIDDLEWARE_MANIFEST); const pagesManifestPath = path.join(distDir, "server", PAGES_MANIFEST); const appRoutesManifestPath = path.join(distDir, APP_PATH_ROUTES_MANIFEST); const routesManifest = JSON.parse(await fs.readFile(routesManifestPath, "utf8")); prerenderManifest = JSON.parse(await fs.readFile(prerenderManifestPath, "utf8")); const middlewareManifest = JSON.parse(await fs.readFile(middlewareManifestPath, "utf8").catch(()=>"{}")); const pagesManifest = JSON.parse(await fs.readFile(pagesManifestPath, "utf8")); const appRoutesManifest = JSON.parse(await fs.readFile(appRoutesManifestPath, "utf8").catch(()=>"{}")); for (const key of Object.keys(pagesManifest)){ // ensure the non-locale version is in the set if (opts.config.i18n) { pageFiles.add(normalizeLocalePath(key, opts.config.i18n.locales).pathname); } else { pageFiles.add(key); } } for (const key of Object.keys(appRoutesManifest)){ appFiles.add(appRoutesManifest[key]); } const escapedBuildId = escapeStringRegexp(buildId); for (const route of routesManifest.dataRoutes){ if (isDynamicRoute(route.page)) { const routeRegex = getRouteRegex(route.page); dynamicRoutes.push({ ...route, regex: routeRegex.re.toString(), match: getRouteMatcher({ // TODO: fix this in the manifest itself, must also be fixed in // upstream builder that relies on this re: opts.config.i18n ? new RegExp(route.dataRouteRegex.replace(`/${escapedBuildId}/`, `/${escapedBuildId}/(?[^/]+?)/`)) : new RegExp(route.dataRouteRegex), groups: routeRegex.groups }) }); } nextDataRoutes.add(route.page); } for (const route of routesManifest.dynamicRoutes){ dynamicRoutes.push({ ...route, match: getRouteMatcher(getRouteRegex(route.page)) }); } if ((_middlewareManifest_middleware = middlewareManifest.middleware) == null ? void 0 : (_middlewareManifest_middleware_ = _middlewareManifest_middleware["/"]) == null ? void 0 : _middlewareManifest_middleware_.matchers) { var _middlewareManifest_middleware_1, _middlewareManifest_middleware1; middlewareMatcher = getMiddlewareRouteMatcher((_middlewareManifest_middleware1 = middlewareManifest.middleware) == null ? void 0 : (_middlewareManifest_middleware_1 = _middlewareManifest_middleware1["/"]) == null ? void 0 : _middlewareManifest_middleware_1.matchers); } customRoutes = { redirects: routesManifest.redirects, rewrites: routesManifest.rewrites ? Array.isArray(routesManifest.rewrites) ? { beforeFiles: [], afterFiles: routesManifest.rewrites, fallback: [] } : routesManifest.rewrites : { beforeFiles: [], afterFiles: [], fallback: [] }, headers: routesManifest.headers }; } else { // dev handling customRoutes = await loadCustomRoutes(opts.config); prerenderManifest = { version: 4, routes: {}, dynamicRoutes: {}, notFoundRoutes: [], preview: { previewModeId: require("crypto").randomBytes(16).toString("hex"), previewModeSigningKey: require("crypto").randomBytes(32).toString("hex"), previewModeEncryptionKey: require("crypto").randomBytes(32).toString("hex") } }; } const headers = customRoutes.headers.map((item)=>buildCustomRoute("header", item, opts.config.basePath, opts.config.experimental.caseSensitiveRoutes)); const redirects = customRoutes.redirects.map((item)=>buildCustomRoute("redirect", item, opts.config.basePath, opts.config.experimental.caseSensitiveRoutes)); const rewrites = { // TODO: add interception routes generateInterceptionRoutesRewrites() beforeFiles: customRoutes.rewrites.beforeFiles.map((item)=>buildCustomRoute("before_files_rewrite", item)), afterFiles: customRoutes.rewrites.afterFiles.map((item)=>buildCustomRoute("rewrite", item, opts.config.basePath, opts.config.experimental.caseSensitiveRoutes)), fallback: customRoutes.rewrites.fallback.map((item)=>buildCustomRoute("rewrite", item, opts.config.basePath, opts.config.experimental.caseSensitiveRoutes)) }; const { i18n } = opts.config; const handleLocale = (pathname, locales)=>{ let locale; if (i18n) { const i18nResult = normalizeLocalePath(pathname, locales || i18n.locales); pathname = i18nResult.pathname; locale = i18nResult.detectedLocale; } return { locale, pathname }; }; debug("nextDataRoutes", nextDataRoutes); debug("dynamicRoutes", dynamicRoutes); debug("pageFiles", pageFiles); debug("appFiles", appFiles); let ensureFn; return { headers, rewrites, redirects, buildId, handleLocale, appFiles, pageFiles, dynamicRoutes, nextDataRoutes, interceptionRoutes: undefined, devVirtualFsItems: new Set(), prerenderManifest, middlewareMatcher: middlewareMatcher, ensureCallback (fn) { ensureFn = fn; }, async getItem (itemPath) { const originalItemPath = itemPath; const itemKey = originalItemPath; const lruResult = getItemsLru == null ? void 0 : getItemsLru.get(itemKey); if (lruResult) { return lruResult; } // handle minimal mode case with .rsc output path (this is // mostly for testings) if (opts.minimalMode && itemPath.endsWith(".rsc")) { itemPath = itemPath.substring(0, itemPath.length - ".rsc".length); } const { basePath } = opts.config; if (basePath && !pathHasPrefix(itemPath, basePath)) { return null; } itemPath = removePathPrefix(itemPath, basePath) || "/"; if (itemPath !== "/" && itemPath.endsWith("/")) { itemPath = itemPath.substring(0, itemPath.length - 1); } let decodedItemPath = itemPath; try { decodedItemPath = decodeURIComponent(itemPath); } catch {} if (itemPath === "/_next/image") { return { itemPath, type: "nextImage" }; } const itemsToCheck = [ [ this.devVirtualFsItems, "devVirtualFsItem" ], [ nextStaticFolderItems, "nextStaticFolder" ], [ legacyStaticFolderItems, "legacyStaticFolder" ], [ publicFolderItems, "publicFolder" ], [ appFiles, "appFile" ], [ pageFiles, "pageFile" ] ]; for (let [items, type] of itemsToCheck){ let locale; let curItemPath = itemPath; let curDecodedItemPath = decodedItemPath; const isDynamicOutput = type === "pageFile" || type === "appFile"; if (i18n) { const localeResult = handleLocale(itemPath, // legacy behavior allows visiting static assets under // default locale but no other locale isDynamicOutput ? undefined : [ i18n == null ? void 0 : i18n.defaultLocale ]); if (localeResult.pathname !== curItemPath) { curItemPath = localeResult.pathname; locale = localeResult.locale; try { curDecodedItemPath = decodeURIComponent(curItemPath); } catch {} } } if (type === "legacyStaticFolder") { if (!pathHasPrefix(curItemPath, "/static")) { continue; } curItemPath = curItemPath.substring("/static".length); try { curDecodedItemPath = decodeURIComponent(curItemPath); } catch {} } if (type === "nextStaticFolder" && !pathHasPrefix(curItemPath, "/_next/static")) { continue; } const nextDataPrefix = `/_next/data/${buildId}/`; if (type === "pageFile" && curItemPath.startsWith(nextDataPrefix) && curItemPath.endsWith(".json")) { items = nextDataRoutes; // remove _next/data/ prefix curItemPath = curItemPath.substring(nextDataPrefix.length - 1); // remove .json postfix curItemPath = curItemPath.substring(0, curItemPath.length - ".json".length); const curLocaleResult = handleLocale(curItemPath); curItemPath = curLocaleResult.pathname === "/index" ? "/" : curLocaleResult.pathname; locale = curLocaleResult.locale; try { curDecodedItemPath = decodeURIComponent(curItemPath); } catch {} } // check decoded variant as well if (!items.has(curItemPath) && !opts.dev) { curItemPath = curDecodedItemPath; } const matchedItem = items.has(curItemPath); if (matchedItem || opts.dev) { let fsPath; let itemsRoot; switch(type){ case "nextStaticFolder": { itemsRoot = nextStaticFolderPath; curItemPath = curItemPath.substring("/_next/static".length); break; } case "legacyStaticFolder": { itemsRoot = legacyStaticFolderPath; break; } case "publicFolder": { itemsRoot = publicFolderPath; break; } default: { break; } } if (itemsRoot && curItemPath) { fsPath = path.posix.join(itemsRoot, curItemPath); } // dynamically check fs in development so we don't // have to wait on the watcher if (!matchedItem && opts.dev) { const isStaticAsset = [ "nextStaticFolder", "publicFolder", "legacyStaticFolder" ].includes(type); if (isStaticAsset && itemsRoot) { let found = fsPath && await fileExists(fsPath, FileType.File); if (!found) { try { // In dev, we ensure encoded paths match // decoded paths on the filesystem so check // that variation as well const tempItemPath = decodeURIComponent(curItemPath); fsPath = path.posix.join(itemsRoot, tempItemPath); found = await fileExists(fsPath, FileType.File); } catch {} if (!found) { continue; } } } else if (type === "pageFile" || type === "appFile") { var _ensureFn; const isAppFile = type === "appFile"; if (ensureFn && await ((_ensureFn = ensureFn({ type, itemPath: isAppFile ? normalizeMetadataRoute(curItemPath) : curItemPath })) == null ? void 0 : _ensureFn.catch(()=>"ENSURE_FAILED")) === "ENSURE_FAILED") { continue; } } else { continue; } } // i18n locales aren't matched for app dir if (type === "appFile" && locale && locale !== (i18n == null ? void 0 : i18n.defaultLocale)) { continue; } const itemResult = { type, fsPath, locale, itemsRoot, itemPath: curItemPath }; getItemsLru == null ? void 0 : getItemsLru.set(itemKey, itemResult); return itemResult; } } getItemsLru == null ? void 0 : getItemsLru.set(itemKey, null); return null; }, getDynamicRoutes () { // this should include data routes return this.dynamicRoutes; }, getMiddlewareMatchers () { return this.middlewareMatcher; } }; } //# sourceMappingURL=filesystem.js.map