UNPKG

firebase-tools

Version:
340 lines (339 loc) 14.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.I18N_SOURCE = void 0; exports.cleanEscapedChars = cleanEscapedChars; exports.cleanCustomRouteI18n = cleanCustomRouteI18n; exports.cleanI18n = cleanI18n; exports.isRewriteSupportedByHosting = isRewriteSupportedByHosting; exports.isRedirectSupportedByHosting = isRedirectSupportedByHosting; exports.isHeaderSupportedByHosting = isHeaderSupportedByHosting; exports.getNextjsRewritesToUse = getNextjsRewritesToUse; exports.usesAppDirRouter = usesAppDirRouter; exports.usesNextImage = usesNextImage; exports.hasUnoptimizedImage = hasUnoptimizedImage; exports.isUsingMiddleware = isUsingMiddleware; exports.isUsingImageOptimization = isUsingImageOptimization; exports.isUsingNextImageInAppDirectory = isUsingNextImageInAppDirectory; exports.isUsingAppDirectory = isUsingAppDirectory; exports.allDependencyNames = allDependencyNames; exports.getMiddlewareMatcherRegexes = getMiddlewareMatcherRegexes; exports.getNonStaticRoutes = getNonStaticRoutes; exports.getNonStaticServerComponents = getNonStaticServerComponents; exports.getAppMetadataFromMetaFiles = getAppMetadataFromMetaFiles; exports.getBuildId = getBuildId; exports.getNextVersion = getNextVersion; exports.getNextVersionRaw = getNextVersionRaw; exports.hasStaticAppNotFoundComponent = hasStaticAppNotFoundComponent; exports.getRoutesWithServerAction = getRoutesWithServerAction; exports.getProductionDistDirFiles = getProductionDistDirFiles; exports.whichNextConfigFile = whichNextConfigFile; exports.findEsbuildPath = findEsbuildPath; exports.getGlobalEsbuildVersion = getGlobalEsbuildVersion; exports.installEsbuild = installEsbuild; exports.isNextJsVersionVulnerable = isNextJsVersionVulnerable; const fs_1 = require("fs"); const fs_extra_1 = require("fs-extra"); const path_1 = require("path"); const promises_1 = require("fs/promises"); const glob_1 = require("glob"); const semver_1 = require("semver"); const utils_1 = require("../utils"); const constants_1 = require("./constants"); const fsutils_1 = require("../../fsutils"); const utils_2 = require("../../utils"); const child_process_1 = require("child_process"); const error_1 = require("../../error"); exports.I18N_SOURCE = /\/:nextInternalLocale(\([^\)]+\))?/; function cleanEscapedChars(path) { return path.replace(/\\([(){}:+?*])/g, (a, b) => b); } function cleanCustomRouteI18n(path) { return path.replace(exports.I18N_SOURCE, ""); } function cleanI18n(it) { const [, localesRegex] = it.source.match(exports.I18N_SOURCE) || [undefined, undefined]; const source = localesRegex ? cleanCustomRouteI18n(it.source) : it.source; const destination = "destination" in it && localesRegex ? cleanCustomRouteI18n(it.destination) : it.destination; const regex = "regex" in it && localesRegex ? it.regex.replace(`(?:/${localesRegex})`, "") : it.regex; return { ...it, source, destination, regex, }; } function isRewriteSupportedByHosting(rewrite) { return !("has" in rewrite || "missing" in rewrite || (0, utils_1.isUrl)(rewrite.destination) || rewrite.destination.includes("?")); } function isRedirectSupportedByHosting(redirect) { return !("has" in redirect || "missing" in redirect || "internal" in redirect || redirect.destination.includes("?")); } function isHeaderSupportedByHosting(header) { return !("has" in header || "missing" in header); } function getNextjsRewritesToUse(nextJsRewrites) { if (Array.isArray(nextJsRewrites)) { return nextJsRewrites.map(cleanI18n); } if (nextJsRewrites?.beforeFiles) { return nextJsRewrites.beforeFiles.map(cleanI18n); } return []; } function usesAppDirRouter(sourceDir) { const appPathRoutesManifestPath = (0, path_1.join)(sourceDir, constants_1.APP_PATH_ROUTES_MANIFEST); return (0, fs_1.existsSync)(appPathRoutesManifestPath); } async function usesNextImage(sourceDir, distDir) { const exportMarker = await (0, utils_1.readJSON)((0, path_1.join)(sourceDir, distDir, constants_1.EXPORT_MARKER)); return exportMarker.isNextImageImported; } async function hasUnoptimizedImage(sourceDir, distDir) { const imagesManifest = await (0, utils_1.readJSON)((0, path_1.join)(sourceDir, distDir, constants_1.IMAGES_MANIFEST)); return imagesManifest.images.unoptimized; } async function isUsingMiddleware(dir, isDevMode) { if (isDevMode) { const [middlewareJs, middlewareTs] = await Promise.all([ (0, fs_extra_1.pathExists)((0, path_1.join)(dir, "middleware.js")), (0, fs_extra_1.pathExists)((0, path_1.join)(dir, "middleware.ts")), ]); return middlewareJs || middlewareTs; } else { const middlewareManifest = await (0, utils_1.readJSON)((0, path_1.join)(dir, "server", constants_1.MIDDLEWARE_MANIFEST)); return Object.keys(middlewareManifest.middleware).length > 0; } } async function isUsingImageOptimization(projectDir, distDir) { let isNextImageImported = await usesNextImage(projectDir, distDir); if (!isNextImageImported && isUsingAppDirectory((0, path_1.join)(projectDir, distDir))) { if (await isUsingNextImageInAppDirectory(projectDir, distDir)) { isNextImageImported = true; } } if (isNextImageImported) { const imagesManifest = await (0, utils_1.readJSON)((0, path_1.join)(projectDir, distDir, constants_1.IMAGES_MANIFEST)); return !imagesManifest.images.unoptimized; } return false; } async function isUsingNextImageInAppDirectory(projectDir, nextDir) { const nextImagePath = ["node_modules", "next", "dist", "client", "image"]; const nextImageString = utils_2.IS_WINDOWS ? nextImagePath.join(path_1.sep + path_1.sep) : (0, path_1.join)(...nextImagePath); const files = (0, glob_1.sync)((0, path_1.join)(projectDir, nextDir, "server", "**", "*client-reference-manifest.js")); for (const filepath of files) { const fileContents = await (0, promises_1.readFile)(filepath, "utf-8"); if (fileContents.includes(nextImageString)) { return true; } } return false; } function isUsingAppDirectory(dir) { const appPathRoutesManifestPath = (0, path_1.join)(dir, constants_1.APP_PATH_ROUTES_MANIFEST); return (0, fsutils_1.fileExistsSync)(appPathRoutesManifestPath); } function allDependencyNames(mod) { if (!mod.dependencies) return []; const dependencyNames = Object.keys(mod.dependencies).reduce((acc, it) => [...acc, it, ...allDependencyNames(mod.dependencies[it])], []); return dependencyNames; } function getMiddlewareMatcherRegexes(middlewareManifest) { const middlewareObjectValues = Object.values(middlewareManifest.middleware); let middlewareMatchers; if (middlewareManifest.version === 1) { middlewareMatchers = middlewareObjectValues.map((page) => ({ regexp: page.regexp })); } else { middlewareMatchers = middlewareObjectValues .map((page) => page.matchers) .flat(); } return middlewareMatchers.map((matcher) => new RegExp(matcher.regexp)); } function getNonStaticRoutes(pagesManifestJSON, prerenderedRoutes, dynamicRoutes) { const nonStaticRoutes = Object.entries(pagesManifestJSON) .filter(([it, src]) => !((0, path_1.extname)(src) !== ".js" || ["/_app", "/_error", "/_document"].includes(it) || prerenderedRoutes.includes(it) || dynamicRoutes.includes(it))) .map(([it]) => it); return nonStaticRoutes; } function getNonStaticServerComponents(appPathsManifest, appPathRoutesManifest, prerenderedRoutes, dynamicRoutes) { const nonStaticServerComponents = Object.entries(appPathsManifest) .filter(([it, src]) => { if ((0, path_1.extname)(src) !== ".js") return; const path = appPathRoutesManifest[it]; return !(prerenderedRoutes.includes(path) || dynamicRoutes.includes(path)); }) .map(([it]) => it); return new Set(nonStaticServerComponents); } async function getAppMetadataFromMetaFiles(sourceDir, distDir, basePath, appPathRoutesManifest) { const headers = []; const pprRoutes = []; await Promise.all(Object.entries(appPathRoutesManifest).map(async ([key, source]) => { if (!["route", "page"].includes((0, path_1.basename)(key))) return; const parts = source.split("/").filter((it) => !!it); const partsOrIndex = parts.length > 0 ? parts : ["index"]; const routePath = (0, path_1.join)(sourceDir, distDir, "server", "app", ...partsOrIndex); const metadataPath = `${routePath}.meta`; if ((0, fsutils_1.dirExistsSync)(routePath) && (0, fsutils_1.fileExistsSync)(metadataPath)) { const meta = await (0, utils_1.readJSON)(metadataPath); if (meta.headers) headers.push({ source: path_1.posix.join(basePath, source), headers: Object.entries(meta.headers).map(([key, value]) => ({ key, value })), }); if (meta.postponed) pprRoutes.push(source); } })); return { headers, pprRoutes }; } async function getBuildId(distDir) { const buildId = await (0, promises_1.readFile)((0, path_1.join)(distDir, "BUILD_ID")); return buildId.toString(); } function getNextVersion(cwd) { const dependency = (0, utils_1.findDependency)("next", { cwd, depth: 0, omitDev: false }); if (!dependency) return undefined; const nextVersionSemver = (0, semver_1.coerce)(dependency.version); if (!nextVersionSemver) return dependency.version; return nextVersionSemver.toString(); } function getNextVersionRaw(cwd) { const dependency = (0, utils_1.findDependency)("next", { cwd, depth: 0, omitDev: false }); return dependency?.version; } async function hasStaticAppNotFoundComponent(sourceDir, distDir) { return (0, fs_extra_1.pathExists)((0, path_1.join)(sourceDir, distDir, "server", "app", "_not-found.html")); } function getRoutesWithServerAction(serverReferenceManifest, appPathRoutesManifest) { const routesWithServerAction = new Set(); for (const key of Object.keys(serverReferenceManifest)) { if (key !== "edge" && key !== "node") continue; const edgeOrNode = serverReferenceManifest[key]; for (const actionId of Object.keys(edgeOrNode)) { if (!edgeOrNode[actionId].layer) continue; for (const [route, type] of Object.entries(edgeOrNode[actionId].layer)) { if (type === constants_1.WEBPACK_LAYERS.actionBrowser) { routesWithServerAction.add(appPathRoutesManifest[route.replace("app", "")]); } } } } return Array.from(routesWithServerAction); } async function getProductionDistDirFiles(sourceDir, distDir) { return (0, glob_1.glob)("**", { ignore: [(0, path_1.join)("cache", "webpack", "*-development", "**"), (0, path_1.join)("cache", "eslint", "**")], cwd: (0, path_1.join)(sourceDir, distDir), nodir: true, absolute: false, }); } async function whichNextConfigFile(dir) { for (const file of constants_1.CONFIG_FILES) { if (await (0, fs_extra_1.pathExists)((0, path_1.join)(dir, file))) return file; } return null; } function findEsbuildPath() { try { const esbuildBinPath = (0, child_process_1.execSync)("npx which esbuild", { encoding: "utf8" })?.trim(); if (!esbuildBinPath) { return null; } const globalVersion = getGlobalEsbuildVersion(esbuildBinPath); if (globalVersion && !(0, semver_1.satisfies)(globalVersion, constants_1.ESBUILD_VERSION)) { console.warn(`Warning: Global esbuild version (${globalVersion}) does not match the required version (${constants_1.ESBUILD_VERSION}).`); } return (0, path_1.resolve)((0, path_1.dirname)(esbuildBinPath), "../esbuild"); } catch (error) { console.error(`Failed to find esbuild with npx which: ${error}`); return null; } } function getGlobalEsbuildVersion(binPath) { try { const versionOutput = (0, child_process_1.execSync)(`"${binPath}" --version`, { encoding: "utf8" })?.trim(); if (!versionOutput) { return null; } const versionMatch = versionOutput.match(/(\d+\.\d+\.\d+)/); return versionMatch ? versionMatch[0] : null; } catch (error) { console.error(`Failed to get global esbuild version: ${error}`); return null; } } function installEsbuild(version) { const installCommand = `npm install esbuild@${version} --no-save`; try { (0, child_process_1.execSync)(installCommand, { stdio: "inherit" }); } catch (error) { if (error instanceof error_1.FirebaseError) { throw error; } else { throw new error_1.FirebaseError(`Failed to install esbuild: ${error}`, { original: error }); } } } function isNextJsVersionVulnerable(versionStr) { const v = (0, semver_1.parse)(versionStr); if (!v) return false; if (v.major === 15) { if (v.minor === 0) return (0, semver_1.lt)(versionStr, "15.0.5"); if (v.minor === 1) return (0, semver_1.lt)(versionStr, "15.1.9"); if (v.minor === 2) return (0, semver_1.lt)(versionStr, "15.2.6"); if (v.minor === 3) return (0, semver_1.lt)(versionStr, "15.3.6"); if (v.minor === 4) return (0, semver_1.lt)(versionStr, "15.4.8"); if (v.minor === 5) return (0, semver_1.lt)(versionStr, "15.5.7"); return false; } if (v.major === 16) { if (v.minor === 0) return (0, semver_1.lt)(versionStr, "16.0.7"); return false; } if (v.major === 14) { const pre = (0, semver_1.prerelease)(versionStr); if (pre && pre.includes("canary")) { if ((0, semver_1.gte)(versionStr, "14.3.0-canary.77")) { return true; } } } return false; }