UNPKG

@inlang/paraglide-js

Version:

[![NPM Downloads](https://img.shields.io/npm/dw/%40inlang%2Fparaglide-js?logo=npm&logoColor=red&label=npm%20downloads)](https://www.npmjs.com/package/@inlang/paraglide-js) [![GitHub Issues](https://img.shields.io/github/issues-closed/opral/paraglide-js?lo

168 lines 6.71 kB
import path from "node:path"; export async function writeOutput(args) { const currentOutputHashes = await hashOutput(args.output, args.directory); // if the output hasn't changed, don't write it const changedFiles = new Set(); for (const [filePath, hash] of Object.entries(currentOutputHashes)) { if (args.previousOutputHashes?.[filePath] !== hash) { changedFiles.add(filePath); } } // Find files that have been removed in the current output const filesToDelete = new Set(); if (args.previousOutputHashes) { for (const filePath of Object.keys(args.previousOutputHashes)) { if (!currentOutputHashes[filePath]) { filesToDelete.add(filePath); } } } if (changedFiles.size === 0 && filesToDelete.size === 0) { return currentOutputHashes; } // clear the output directory // // disabled because of https://github.com/opral/inlang-paraglide-js/issues/350 // and re-enabled because of https://github.com/opral/inlang-paraglide-js/issues/420 if (args.cleanDirectory) { await args.fs.rm(args.directory, { recursive: true, force: true }); } else { await args.fs.mkdir(args.directory, { recursive: true }); } // Delete files that have been removed // ignore if cleanDirectory is true because the directory will be cleaned anyway if (filesToDelete.size > 0 && !args.cleanDirectory) { await deleteRemovedFiles(args.fs, args.directory, filesToDelete); } //Create missing directories inside the output directory await Promise.allSettled(Object.keys(args.output).map(async (filePath) => { const fullPath = path.resolve(args.directory, filePath); const directory = path.dirname(fullPath); await args.fs.mkdir(directory, { recursive: true }); })); //Write files await Promise.allSettled(Object.entries(args.output).map(async ([filePath, fileContent]) => { if (!changedFiles.has(filePath)) { return; } const fullPath = path.resolve(args.directory, filePath); await args.fs.writeFile(fullPath, fileContent); })); //Only update the previousOutputHashes if the write was successful return currentOutputHashes; } /** * Delete files that have been removed and clean up empty directories */ async function deleteRemovedFiles(fs, baseDirectory, filesToDelete) { // Collect directories that might need cleanup const potentialEmptyDirs = new Set(); // First pass: delete all files and collect parent directories await Promise.allSettled(Array.from(filesToDelete).map(async (filePath) => { const fullPath = path.resolve(baseDirectory, filePath); try { await fs.unlink(fullPath); // Add parent directory for potential cleanup const dirPath = path.dirname(fullPath); if (dirPath !== baseDirectory) { potentialEmptyDirs.add(dirPath); } } catch { // Ignore errors if the file doesn't exist } })); // Second pass: clean up empty directories, starting from deepest paths const sortedDirs = Array.from(potentialEmptyDirs).sort((a, b) => b.length - a.length); for (const dirPath of sortedDirs) { // Only process directories within the base directory if (!dirPath.startsWith(baseDirectory) || dirPath === baseDirectory) { continue; } try { const dirContents = await fs.readdir(dirPath); if (dirContents.length === 0) { await fs.rmdir(dirPath); // Add parent directory for potential cleanup const parentDir = path.dirname(dirPath); if (parentDir !== baseDirectory) { sortedDirs.push(parentDir); } } } catch { // Ignore errors during directory cleanup } } } async function hashString(input) { const encoder = new TextEncoder(); const data = encoder.encode(input); const hashBuffer = await crypto.subtle.digest("SHA-256", data); const hashArray = Array.from(new Uint8Array(hashBuffer)); return hashArray.map((b) => b.toString(16).padStart(2, "0")).join(""); } async function hashOutput(output, outputDirectory) { const hashes = {}; for (const [filePath, fileContent] of Object.entries(output)) { const combinedContent = fileContent + path.resolve(outputDirectory, filePath); hashes[filePath] = await hashString(combinedContent); } return hashes; } /** * Walk an existing output directory and produce the same hash map that * {@link writeOutput} would have returned on a previous compile. * * Used by the bundler plugins to seed `previousCompilation` on a fresh * process so the first compile's diff against unchanged inputs is empty * and `writeOutput` short-circuits — letting us avoid wiping the directory * out from under concurrent SSR/prerender readers (#659). * * Returns `undefined` if the directory does not exist. Keys are * forward-slash relative paths to match {@link hashOutput}. */ export async function hashDirectory(directory, fs) { try { const stat = await fs.stat(directory); if (!stat.isDirectory()) return undefined; } catch { return undefined; } const hashes = {}; async function walk(currentDir, relativePrefix) { let entries; try { entries = await fs.readdir(currentDir, { withFileTypes: true }); } catch { return; } for (const entry of entries) { const entryAbsolutePath = path.resolve(currentDir, entry.name); const relativePath = relativePrefix ? `${relativePrefix}/${entry.name}` : entry.name; if (entry.isDirectory()) { await walk(entryAbsolutePath, relativePath); } else if (entry.isFile()) { try { const fileContent = await fs.readFile(entryAbsolutePath, "utf-8"); const combinedContent = fileContent + path.resolve(directory, relativePath); hashes[relativePath] = await hashString(combinedContent); } catch { // Another compiler process may be rewriting this file. A partial // seed is safe; the following compile will repair missing outputs. } } } } await walk(directory, ""); return hashes; } //# sourceMappingURL=write-output.js.map