diff --git a/packages/open-next/src/build/createAssets.ts b/packages/open-next/src/build/createAssets.ts index 266d3200..0fc224e6 100644 --- a/packages/open-next/src/build/createAssets.ts +++ b/packages/open-next/src/build/createAssets.ts @@ -86,27 +86,25 @@ export function createCacheAssets(options: buildHelper.BuildOptions) { const buildId = buildHelper.getBuildId(options); let useTagCache = false; - // Copy pages to cache folder const dotNextPath = path.join( appBuildOutputPath, ".next/standalone", packagePath, ); - const outputPath = path.join(outputDir, "cache", buildId); - [".next/server/pages", ".next/server/app"] + + const outputCachePath = path.join(outputDir, "cache", buildId); + fs.mkdirSync(outputCachePath, { recursive: true }); + + const sourceDirs = [".next/server/pages", ".next/server/app"] .map((dir) => path.join(dotNextPath, dir)) - .filter(fs.existsSync) - .forEach((dir) => fs.cpSync(dir, outputPath, { recursive: true })); + .filter(fs.existsSync); - // Remove non-cache files const htmlPages = buildHelper.getHtmlPages(dotNextPath); - buildHelper.removeFiles( - outputPath, - ({ relativePath }) => - relativePath.endsWith(".js") || - relativePath.endsWith(".js.nft.json") || - (relativePath.endsWith(".html") && htmlPages.has(relativePath)), - ); + + const isFileSkipped = (relativePath: string) => + relativePath.endsWith(".js") || + relativePath.endsWith(".js.nft.json") || + (relativePath.endsWith(".html") && htmlPages.has(relativePath)); // Merge cache files into a single file const cacheFilesPath: Record< @@ -120,36 +118,43 @@ export function createCacheAssets(options: buildHelper.BuildOptions) { } > = {}; - buildHelper.traverseFiles( - outputPath, - () => true, - ({ absolutePath }) => { - const ext = path.extname(absolutePath); - switch (ext) { - case ".meta": - case ".html": - case ".json": - case ".body": - case ".rsc": { - const newFilePath = absolutePath - .substring(0, absolutePath.length - ext.length) - .replace(/\.prefetch$/, "") - .concat(".cache"); + // Process each source directory + sourceDirs.forEach((sourceDir) => { + buildHelper.traverseFiles( + sourceDir, + ({ relativePath }) => !isFileSkipped(relativePath), + ({ absolutePath, relativePath }) => { + const ext = path.extname(absolutePath); + switch (ext) { + case ".meta": + case ".html": + case ".json": + case ".body": + case ".rsc": { + const newFilePath = path + .join(outputCachePath, relativePath) + .substring( + 0, + path.join(outputCachePath, relativePath).length - ext.length, + ) + .replace(/\.prefetch$/, "") + .concat(".cache"); - cacheFilesPath[newFilePath] = { - [ext.slice(1)]: absolutePath, - ...cacheFilesPath[newFilePath], - }; - break; + cacheFilesPath[newFilePath] = { + [ext.slice(1)]: absolutePath, + ...cacheFilesPath[newFilePath], + }; + break; + } + case ".map": + break; + default: + logger.warn(`Unknown file extension: ${ext}`); + break; } - case ".map": - break; - default: - logger.warn(`Unknown file extension: ${ext}`); - break; - } - }, - ); + }, + ); + }); // Generate cache file Object.entries(cacheFilesPath).forEach(([cacheFilePath, files]) => { @@ -174,6 +179,9 @@ export function createCacheAssets(options: buildHelper.BuildOptions) { ) : undefined, }; + + // Ensure directory exists before writing + fs.mkdirSync(path.dirname(cacheFilePath), { recursive: true }); fs.writeFileSync(cacheFilePath, JSON.stringify(cacheFileContent)); }); @@ -212,38 +220,41 @@ export function createCacheAssets(options: buildHelper.BuildOptions) { if (!options.config.dangerous?.disableTagCache) { // Compute dynamodb cache data // Traverse files inside cache to find all meta files and cache tags associated with them - buildHelper.traverseFiles( - outputPath, - ({ absolutePath }) => absolutePath.endsWith(".meta"), - ({ absolutePath, relativePath }) => { - const fileContent = fs.readFileSync(absolutePath, "utf8"); - const fileData = JSON.parse(fileContent); - if (fileData.headers?.["x-next-cache-tags"]) { - fileData.headers["x-next-cache-tags"] - .split(",") - .forEach((tag: string) => { - // TODO: We should split the tag using getDerivedTags from next.js or maybe use an in house implementation - metaFiles.push({ - tag: { S: path.posix.join(buildId, tag.trim()) }, - path: { - S: path.posix.join( - buildId, - relativePath.replace(".meta", ""), - ), - }, - // We don't care about the revalidation time here, we just need to make sure it's there - revalidatedAt: { N: "1" }, + sourceDirs.forEach((sourceDir) => { + buildHelper.traverseFiles( + sourceDir, + ({ absolutePath, relativePath }) => + absolutePath.endsWith(".meta") && !isFileSkipped(relativePath), + ({ absolutePath, relativePath }) => { + const fileContent = fs.readFileSync(absolutePath, "utf8"); + const fileData = JSON.parse(fileContent); + if (fileData.headers?.["x-next-cache-tags"]) { + fileData.headers["x-next-cache-tags"] + .split(",") + .forEach((tag: string) => { + // TODO: We should split the tag using getDerivedTags from next.js or maybe use an in house implementation + metaFiles.push({ + tag: { S: path.posix.join(buildId, tag.trim()) }, + path: { + S: path.posix.join( + buildId, + relativePath.replace(".meta", ""), + ), + }, + // We don't care about the revalidation time here, we just need to make sure it's there + revalidatedAt: { N: "1" }, + }); }); - }); - } - }, - ); + } + }, + ); + }); if (metaFiles.length > 0) { useTagCache = true; const providerPath = path.join(outputDir, "dynamodb-provider"); - //Copy open-next.config.mjs into the bundle + // Copy open-next.config.mjs into the bundle fs.mkdirSync(providerPath, { recursive: true }); buildHelper.copyOpenNextConfig(options.buildDir, providerPath); @@ -255,11 +266,5 @@ export function createCacheAssets(options: buildHelper.BuildOptions) { } } - // We need to remove files later because we need the metafiles for dynamodb tags cache - buildHelper.removeFiles( - outputPath, - ({ relativePath }) => !relativePath.endsWith(".cache"), - ); - return { useTagCache, metaFiles }; }