UNPKG

@metamask/utils

Version:

Various JavaScript/TypeScript utilities of wide relevance to the MetaMask codebase

1 lines 12 kB
{"version":3,"file":"fs.mjs","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,6CAA6C;AAE7C,OAAO,EAAE,WAAW;AACpB,OAAO,EAAE,WAAW;AACpB,OAAO,IAAI,aAAa;AACxB,OAAO,KAAK,IAAI,aAAa;AAE7B,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,qBAAiB;AActD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,QAAgB;IAC7C,IAAI;QACF,OAAO,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;KACrD;IAAC,OAAO,KAAK,EAAE;QACd,MAAM,SAAS,CAAC,KAAK,EAAE,wBAAwB,QAAQ,GAAG,CAAC,CAAC;KAC7D;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,QAAgB,EAChB,OAAe;IAEf,IAAI;QACF,MAAM,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACrE,MAAM,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;KAChD;IAAC,OAAO,KAAK,EAAE;QACd,MAAM,SAAS,CAAC,KAAK,EAAE,yBAAyB,QAAQ,GAAG,CAAC,CAAC;KAC9D;AACH,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,QAAgB,EAChB,EACE,MAAM,GAAG,IAAI,MAOX,EAAE;IAEN,IAAI;QACF,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC7D,OAAO,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;KAC9B;IAAC,OAAO,KAAK,EAAE;QACd,MAAM,SAAS,CAAC,KAAK,EAAE,6BAA6B,QAAQ,GAAG,CAAC,CAAC;KAClE;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,QAAgB,EAChB,SAAe,EACf,EACE,WAAW,GAAG,IAAI,EAClB,QAAQ,GAAG,KAAK,MAMd,EAAE;IAEN,IAAI;QACF,MAAM,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACrE,MAAM,IAAI,GAAG,QAAQ;YACnB,CAAC,CAAC,WAAW,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC;YAC9C,CAAC,CAAC,WAAW,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;QACrC,MAAM,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;KAC7C;IAAC,OAAO,KAAK,EAAE;QACd,MAAM,SAAS,CAAC,KAAK,EAAE,8BAA8B,QAAQ,GAAG,CAAC,CAAC;KACnE;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,QAAgB;IAC/C,IAAI;QACF,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC/C,OAAO,KAAK,CAAC,MAAM,EAAE,CAAC;KACvB;IAAC,OAAO,KAAK,EAAE;QACd,IAAI,eAAe,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE;YACrD,OAAO,KAAK,CAAC;SACd;QAED,MAAM,SAAS,CAAC,KAAK,EAAE,uCAAuC,QAAQ,GAAG,CAAC,CAAC;KAC5E;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,aAAqB;IACzD,IAAI;QACF,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACpD,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC;KAC5B;IAAC,OAAO,KAAK,EAAE;QACd,IAAI,eAAe,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE;YACrD,OAAO,KAAK,CAAC;SACd;QAED,MAAM,SAAS,CACb,KAAK,EACL,4CAA4C,aAAa,GAAG,CAC7D,CAAC;KACH;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAClD,aAAqB;IAErB,IAAI;QACF,MAAM,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,aAAa,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;KAC7D;IAAC,OAAO,KAAK,EAAE;QACd,MAAM,SAAS,CACb,KAAK,EACL,yCAAyC,aAAa,GAAG,CAC1D,CAAC;KACH;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,SAAiB;IACjD,IAAI;QACF,OAAO,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,SAAS,EAAE;YACrC,SAAS,EAAE,IAAI;YACf,KAAK,EAAE,IAAI;SACZ,CAAC,CAAC;KACJ;IAAC,OAAO,KAAK,EAAE;QACd,MAAM,SAAS,CAAC,KAAK,EAAE,uCAAuC,SAAS,GAAG,CAAC,CAAC;KAC7E;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,aAAa,CAAC,WAAmB;IAC/C,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;IAErE,OAAO;QACL,aAAa;QACb,KAAK,CAAC,aAAa,CACjB,IAAwD;YAExD,IAAI,MAAM,eAAe,CAAC,aAAa,CAAC,EAAE;gBACxC,MAAM,IAAI,KAAK,CAAC,GAAG,aAAa,mCAAmC,CAAC,CAAC;aACtE;YAED,MAAM,8BAA8B,CAAC,aAAa,CAAC,CAAC;YAEpD,IAAI;gBACF,MAAM,IAAI,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC;aAC/B;oBAAS;gBACR,MAAM,WAAW,CAAC,aAAa,CAAC,CAAC;aAClC;QACH,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["// This file is intended to be used only in a Node.js context.\n/* eslint-disable import/no-nodejs-modules */\n\nimport fs from 'fs';\nimport os from 'os';\nimport path from 'path';\nimport * as uuid from 'uuid';\n\nimport { isErrorWithCode, wrapError } from './errors';\nimport type { Json } from './json';\n\n/**\n * Information about the file sandbox provided to tests that need temporary\n * access to the filesystem.\n */\nexport type FileSandbox = {\n directoryPath: string;\n withinSandbox: (\n test: (args: { directoryPath: string }) => Promise<void>,\n ) => Promise<void>;\n};\n\n/**\n * Read the file at the given path, assuming its content is encoded as UTF-8.\n *\n * @param filePath - The path to the file.\n * @returns The content of the file.\n * @throws An error with a stack trace if reading fails in any way.\n */\nexport async function readFile(filePath: string): Promise<string> {\n try {\n return await fs.promises.readFile(filePath, 'utf8');\n } catch (error) {\n throw wrapError(error, `Could not read file '${filePath}'`);\n }\n}\n\n/**\n * Write content to the file at the given path, creating the directory structure\n * for the file automatically if necessary.\n *\n * @param filePath - The path to the file.\n * @param content - The new content of the file.\n * @throws An error with a stack trace if writing fails in any way.\n */\nexport async function writeFile(\n filePath: string,\n content: string,\n): Promise<void> {\n try {\n await fs.promises.mkdir(path.dirname(filePath), { recursive: true });\n await fs.promises.writeFile(filePath, content);\n } catch (error) {\n throw wrapError(error, `Could not write file '${filePath}'`);\n }\n}\n\n/**\n * Read the assumed JSON file at the given path, attempts to parse it, and\n * get the resulting object. Supports a custom parser (in case you want to\n * use the [JSON5](https://www.npmjs.com/package/json5) package instead).\n *\n * @param filePath - The path segments pointing to the JSON file. Will be passed\n * to path.join().\n * @param options - Options to this function.\n * @param options.parser - The parser object to use. Defaults to `JSON`.\n * @param options.parser.parse - A function that parses JSON data.\n * @returns The object corresponding to the parsed JSON file, typed against the\n * struct.\n * @throws An error with a stack trace if reading fails in any way, or if the\n * parsed value is not a plain object.\n */\nexport async function readJsonFile<Value extends Json>(\n filePath: string,\n {\n parser = JSON,\n }: {\n parser?: {\n parse: (\n text: Parameters<typeof JSON.parse>[0],\n ) => ReturnType<typeof JSON.parse>;\n };\n } = {},\n): Promise<Value> {\n try {\n const content = await fs.promises.readFile(filePath, 'utf8');\n return parser.parse(content);\n } catch (error) {\n throw wrapError(error, `Could not read JSON file '${filePath}'`);\n }\n}\n\n/**\n * Attempt to write the given JSON-like value to the file at the given path,\n * creating the directory structure for the file automatically if necessary.\n * Adds a newline to the end of the file. Supports a custom parser (in case you\n * want to use the [JSON5](https://www.npmjs.com/package/json5) package\n * instead).\n *\n * @param filePath - The path to write the JSON file to, including the file\n * itself.\n * @param jsonValue - The JSON-like value to write to the file. Make sure that\n * JSON.stringify can handle it.\n * @param options - The options to this function.\n * @param options.prettify - Whether to format the JSON as it is turned into a\n * string such that it is broken up into separate lines (using 2 spaces as\n * indentation).\n * @param options.stringifier - The stringifier to use. Defaults to `JSON`.\n * @param options.stringifier.stringify - A function that stringifies JSON.\n * @returns The object corresponding to the parsed JSON file, typed against the\n * struct.\n * @throws An error with a stack trace if writing fails in any way.\n */\nexport async function writeJsonFile(\n filePath: string,\n jsonValue: Json,\n {\n stringifier = JSON,\n prettify = false,\n }: {\n stringifier?: {\n stringify: typeof JSON.stringify;\n };\n prettify?: boolean;\n } = {},\n): Promise<void> {\n try {\n await fs.promises.mkdir(path.dirname(filePath), { recursive: true });\n const json = prettify\n ? stringifier.stringify(jsonValue, null, ' ')\n : stringifier.stringify(jsonValue);\n await fs.promises.writeFile(filePath, json);\n } catch (error) {\n throw wrapError(error, `Could not write JSON file '${filePath}'`);\n }\n}\n\n/**\n * Test the given path to determine whether it represents a file.\n *\n * @param filePath - The path to a (supposed) file on the filesystem.\n * @returns A promise for true if the file exists or false otherwise.\n * @throws An error with a stack trace if reading fails in any way.\n */\nexport async function fileExists(filePath: string): Promise<boolean> {\n try {\n const stats = await fs.promises.stat(filePath);\n return stats.isFile();\n } catch (error) {\n if (isErrorWithCode(error) && error.code === 'ENOENT') {\n return false;\n }\n\n throw wrapError(error, `Could not determine if file exists '${filePath}'`);\n }\n}\n\n/**\n * Test the given path to determine whether it represents a directory.\n *\n * @param directoryPath - The path to a (supposed) directory on the filesystem.\n * @returns A promise for true if the file exists or false otherwise.\n * @throws An error with a stack trace if reading fails in any way.\n */\nexport async function directoryExists(directoryPath: string): Promise<boolean> {\n try {\n const stats = await fs.promises.stat(directoryPath);\n return stats.isDirectory();\n } catch (error) {\n if (isErrorWithCode(error) && error.code === 'ENOENT') {\n return false;\n }\n\n throw wrapError(\n error,\n `Could not determine if directory exists '${directoryPath}'`,\n );\n }\n}\n\n/**\n * Create the given directory along with any directories leading up to the\n * directory, or do nothing if the directory already exists.\n *\n * @param directoryPath - The path to the desired directory.\n * @throws An error with a stack trace if reading fails in any way.\n */\nexport async function ensureDirectoryStructureExists(\n directoryPath: string,\n): Promise<void> {\n try {\n await fs.promises.mkdir(directoryPath, { recursive: true });\n } catch (error) {\n throw wrapError(\n error,\n `Could not create directory structure '${directoryPath}'`,\n );\n }\n}\n\n/**\n * Remove the given file or directory if it exists, or do nothing if it does\n * not.\n *\n * @param entryPath - The path to the file or directory.\n * @throws An error with a stack trace if removal fails in any way.\n */\nexport async function forceRemove(entryPath: string): Promise<void> {\n try {\n return await fs.promises.rm(entryPath, {\n recursive: true,\n force: true,\n });\n } catch (error) {\n throw wrapError(error, `Could not remove file or directory '${entryPath}'`);\n }\n}\n\n/**\n * Construct a sandbox object which can be used in tests that need temporary\n * access to the filesystem.\n *\n * @param projectName - The name of the project.\n * @returns The sandbox object. This contains a `withinSandbox` function which\n * can be used in tests (see example).\n * @example\n * ```typescript\n * const { withinSandbox } = createSandbox('utils');\n *\n * // ... later ...\n *\n * it('does something with the filesystem', async () => {\n * await withinSandbox(async ({ directoryPath }) => {\n * await fs.promises.writeFile(\n * path.join(directoryPath, 'some-file'),\n * 'some content',\n * 'utf8'\n * );\n * })\n * });\n * ```\n */\nexport function createSandbox(projectName: string): FileSandbox {\n const directoryPath = path.join(os.tmpdir(), projectName, uuid.v4());\n\n return {\n directoryPath,\n async withinSandbox(\n test: (args: { directoryPath: string }) => Promise<void>,\n ) {\n if (await directoryExists(directoryPath)) {\n throw new Error(`${directoryPath} already exists. Cannot continue.`);\n }\n\n await ensureDirectoryStructureExists(directoryPath);\n\n try {\n await test({ directoryPath });\n } finally {\n await forceRemove(directoryPath);\n }\n },\n };\n}\n"]}