UNPKG

trigger.dev

Version:

A Command-Line Interface for Trigger.dev projects

183 lines • 6.47 kB
import fsSync from "fs"; import stringify from "json-stable-stringify"; import fsModule from "fs/promises"; import fs from "node:fs"; import { homedir, tmpdir } from "node:os"; import pathModule from "node:path"; import { parseJSONC, stringifyJSONC, parseTOML, stringifyTOML } from "confbox"; // Creates a file at the given path, if the directory doesn't exist it will be created export async function createFile(path, contents) { await fsModule.mkdir(pathModule.dirname(path), { recursive: true }); await fsModule.writeFile(path, contents); return path; } /** * Sanitizes a hash to be safe for use as a filename. * esbuild's hashes are base64-encoded and may contain `/` and `+` characters. */ export function sanitizeHashForFilename(hash) { return hash.replace(/\//g, "_").replace(/\+/g, "-"); } /** * Creates a file using a content-addressable store for deduplication. * Files are stored by their content hash, so identical content is only stored once. * The build directory gets a hardlink to the stored file. * * @param filePath - The destination path for the file * @param contents - The file contents to write * @param storeDir - The shared store directory for deduplication * @param contentHash - The content hash (e.g., from esbuild's outputFile.hash) * @returns The destination file path */ export async function createFileWithStore(filePath, contents, storeDir, contentHash) { // Sanitize hash to be filesystem-safe (base64 can contain / and +) const safeHash = sanitizeHashForFilename(contentHash); // Store files by their content hash for true content-addressable storage const storePath = pathModule.join(storeDir, safeHash); // Ensure build directory exists await fsModule.mkdir(pathModule.dirname(filePath), { recursive: true }); // Remove existing file at destination if it exists (hardlinks fail on existing files) if (fsSync.existsSync(filePath)) { await fsModule.unlink(filePath); } // Check if content already exists in store by hash if (fsSync.existsSync(storePath)) { // Create hardlink from build path to store path // Fall back to copy if hardlink fails (e.g., on Windows or cross-device) try { await fsModule.link(storePath, filePath); } catch (linkError) { try { await fsModule.copyFile(storePath, filePath); } catch (copyError) { throw linkError; // Rethrow original error if copy also fails } } return filePath; } // Write to store first (using hash as filename) await fsModule.writeFile(storePath, contents); // Create hardlink in build directory (with original filename) // Fall back to copy if hardlink fails (e.g., on Windows or cross-device) try { await fsModule.link(storePath, filePath); } catch (linkError) { try { await fsModule.copyFile(storePath, filePath); } catch (copyError) { throw linkError; // Rethrow original error if copy also fails } } return filePath; } export function isDirectory(configPath) { try { return fs.statSync(configPath).isDirectory(); } catch (error) { // ignore error return false; } } export async function pathExists(path) { return fsSync.existsSync(path); } export async function someFileExists(directory, filenames) { for (let index = 0; index < filenames.length; index++) { const filename = filenames[index]; if (!filename) continue; const path = pathModule.join(directory, filename); if (await pathExists(path)) { return true; } } return false; } export async function removeFile(path) { await fsModule.unlink(path); } export async function readFile(path) { return await fsModule.readFile(path, "utf8"); } export function expandTilde(filePath) { if (typeof filePath !== "string") { throw new TypeError("Path must be a string"); } if (filePath === "~") { return homedir(); } if (filePath.startsWith("~/")) { return pathModule.resolve(homedir(), filePath.slice(2)); } return pathModule.resolve(filePath); } export async function readJSONFile(path) { const fileContents = await fsModule.readFile(path, "utf8"); return JSON.parse(fileContents); } export async function safeReadJSONFile(path) { try { const fileExists = await pathExists(path); if (!fileExists) return; const fileContents = await readFile(path); return JSON.parse(fileContents); } catch { return; } } export async function writeJSONFile(path, json, pretty = false) { await safeWriteFile(path, stringify(json, pretty ? { space: 2 } : undefined) ?? ""); } // Will create the directory if it doesn't exist export async function safeWriteFile(path, contents) { await fsModule.mkdir(pathModule.dirname(path), { recursive: true }); await fsModule.writeFile(path, contents); } export function readJSONFileSync(path) { const fileContents = fsSync.readFileSync(path, "utf8"); return JSON.parse(fileContents); } export function safeDeleteFileSync(path) { try { fs.unlinkSync(path); } catch (error) { // ignore error } } // Create a temporary directory within the OS's temp directory export async function createTempDir() { // Generate a unique temp directory path const tempDirPath = pathModule.join(tmpdir(), "trigger-"); // Create the temp directory synchronously and return the path const directory = await fsModule.mkdtemp(tempDirPath); return directory; } export async function safeReadTomlFile(path) { const fileExists = await pathExists(path); if (!fileExists) return; const fileContents = await readFile(path); return parseTOML(fileContents.replace(/\r\n/g, "\n")); } export async function writeTomlFile(path, toml) { await safeWriteFile(path, stringifyTOML(toml)); } export async function safeReadJSONCFile(path) { const fileExists = await pathExists(path); if (!fileExists) return; const fileContents = await readFile(path); return parseJSONC(fileContents.replace(/\r\n/g, "\n")); } export async function writeJSONCFile(path, json) { await safeWriteFile(path, stringifyJSONC(json)); } //# sourceMappingURL=fileSystem.js.map