trigger.dev
Version:
A Command-Line Interface for Trigger.dev projects
183 lines • 6.47 kB
JavaScript
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