|
| 1 | +/* eslint-disable total-functions/no-unsafe-readonly-mutable-assignment */ |
| 2 | +/* eslint-disable @typescript-eslint/naming-convention */ |
| 3 | +/* eslint-disable no-await-in-loop */ |
| 4 | +import { constants as fsConstants } from "node:fs"; |
| 5 | +import fs from "node:fs/promises"; |
| 6 | +import os from "node:os"; |
| 7 | +import nodePath from "node:path"; |
| 8 | +const RETRIES = 5; |
| 9 | +const RETRY_TIMEOUT_MILLISECONDS = 200; |
| 10 | +// eslint-disable-next-line n/no-process-env |
| 11 | +const DEBUG = process.env.USING_TEMPORARY_FILES_DEBUG === "1"; |
| 12 | +async function ensureDirectoryExists(filePath) { |
| 13 | + const directory = nodePath.dirname(filePath); |
| 14 | + try { |
| 15 | + await fs.access(directory, fsConstants.W_OK); |
| 16 | + } |
| 17 | + catch { |
| 18 | + await fs.mkdir(directory, { |
| 19 | + recursive: true, |
| 20 | + }); |
| 21 | + } |
| 22 | +} |
| 23 | +function createAddFunction(basePath) { |
| 24 | + return async function add(filePath, content) { |
| 25 | + const fullPath = nodePath.join(basePath, filePath); |
| 26 | + await ensureDirectoryExists(fullPath); |
| 27 | + await fs.writeFile(fullPath, content); |
| 28 | + }; |
| 29 | +} |
| 30 | +function createAddDirectoryFunction(basePath) { |
| 31 | + return async function addDirectory(filePath) { |
| 32 | + const fullPath = nodePath.join(basePath, filePath); |
| 33 | + await fs.mkdir(fullPath, { |
| 34 | + recursive: true, |
| 35 | + }); |
| 36 | + }; |
| 37 | +} |
| 38 | +function createRemoveFunction(basePath) { |
| 39 | + return async function remove(filePath) { |
| 40 | + const fullPath = nodePath.join(basePath, filePath); |
| 41 | + await ensureDirectoryExists(fullPath); |
| 42 | + await fs.rm(fullPath); |
| 43 | + }; |
| 44 | +} |
| 45 | +function createReadFunction(basePath) { |
| 46 | + return async function read(filePath, encoding = "utf8") { |
| 47 | + const fullPath = nodePath.join(basePath, filePath); |
| 48 | + return await fs.readFile(fullPath, encoding); |
| 49 | + }; |
| 50 | +} |
| 51 | +// eslint-disable-next-line max-statements |
| 52 | +export async function usingTemporaryFiles(...callbacks) { |
| 53 | + const baseDirectory = DEBUG |
| 54 | + ? nodePath.resolve(process.cwd(), "./") |
| 55 | + : os.tmpdir(); |
| 56 | + const temporaryDirectory = String(await fs.mkdtemp(nodePath.join(baseDirectory, "utf-"))); |
| 57 | + try { |
| 58 | + for (const callback of callbacks) { |
| 59 | + // eslint-disable-next-line n/callback-return |
| 60 | + await callback({ |
| 61 | + add: createAddFunction(temporaryDirectory), |
| 62 | + addDirectory: createAddDirectoryFunction(temporaryDirectory), |
| 63 | + path(...relativePaths) { |
| 64 | + return nodePath.join(temporaryDirectory, ...relativePaths); |
| 65 | + }, |
| 66 | + read: createReadFunction(temporaryDirectory), |
| 67 | + remove: createRemoveFunction(temporaryDirectory), |
| 68 | + }); |
| 69 | + } |
| 70 | + } |
| 71 | + finally { |
| 72 | + let retries = RETRIES; |
| 73 | + while (retries > 0) { |
| 74 | + try { |
| 75 | + await fs.rm(temporaryDirectory, { |
| 76 | + recursive: true, |
| 77 | + }); |
| 78 | + break; |
| 79 | + } |
| 80 | + catch { |
| 81 | + // eslint-disable-next-line promise/avoid-new, compat/compat |
| 82 | + await new Promise((resolve) => { |
| 83 | + setTimeout(resolve, RETRY_TIMEOUT_MILLISECONDS); |
| 84 | + }); |
| 85 | + retries -= 1; |
| 86 | + } |
| 87 | + } |
| 88 | + } |
| 89 | +} |
0 commit comments