@metamask-previews/node-utils 0.0.0-preview-a583980c5 → 0.0.0-preview-7baa62aa9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,4 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Added
11
+
12
+ - Add `FileSandbox` type and `createSandbox`, `directoryExists`, `ensureDirectoryStructureExists`, `fileExists`, `forceRemove`, `readFile`, `readJsonFile`, `writeFile`, and `writeJsonFile` functions, moved from `@metamask/utils` ([#10926](https://github.com/MetaMask/core/pull/10926))
13
+
10
14
  [Unreleased]: https://github.com/MetaMask/core/
@@ -0,0 +1,133 @@
1
+ import type { Json } from '@metamask/type-utils';
2
+ /**
3
+ * Information about the file sandbox provided to tests that need temporary
4
+ * access to the filesystem.
5
+ */
6
+ export type FileSandbox = {
7
+ directoryPath: string;
8
+ withinSandbox: (test: (args: {
9
+ directoryPath: string;
10
+ }) => Promise<void>) => Promise<void>;
11
+ };
12
+ /**
13
+ * Read the file at the given path, assuming its content is encoded as UTF-8.
14
+ *
15
+ * @param filePath - The path to the file.
16
+ * @returns The content of the file.
17
+ * @throws An error with a stack trace if reading fails in any way.
18
+ */
19
+ export declare function readFile(filePath: string): Promise<string>;
20
+ /**
21
+ * Write content to the file at the given path, creating the directory structure
22
+ * for the file automatically if necessary.
23
+ *
24
+ * @param filePath - The path to the file.
25
+ * @param content - The new content of the file.
26
+ * @throws An error with a stack trace if writing fails in any way.
27
+ */
28
+ export declare function writeFile(filePath: string, content: string): Promise<void>;
29
+ /**
30
+ * Read the assumed JSON file at the given path, attempts to parse it, and
31
+ * get the resulting object. Supports a custom parser (in case you want to
32
+ * use the [JSON5](https://www.npmjs.com/package/json5) package instead).
33
+ *
34
+ * @param filePath - The path segments pointing to the JSON file. Will be passed
35
+ * to path.join().
36
+ * @param options - Options to this function.
37
+ * @param options.parser - The parser object to use. Defaults to `JSON`.
38
+ * @param options.parser.parse - A function that parses JSON data.
39
+ * @returns The object corresponding to the parsed JSON file, typed against the
40
+ * struct.
41
+ * @throws An error with a stack trace if reading fails in any way, or if the
42
+ * parsed value is not a plain object.
43
+ */
44
+ export declare function readJsonFile<Value extends Json>(filePath: string, { parser, }?: {
45
+ parser?: {
46
+ parse: (text: Parameters<typeof JSON.parse>[0]) => ReturnType<typeof JSON.parse>;
47
+ };
48
+ }): Promise<Value>;
49
+ /**
50
+ * Attempt to write the given JSON-like value to the file at the given path,
51
+ * creating the directory structure for the file automatically if necessary.
52
+ * Adds a newline to the end of the file. Supports a custom parser (in case you
53
+ * want to use the [JSON5](https://www.npmjs.com/package/json5) package
54
+ * instead).
55
+ *
56
+ * @param filePath - The path to write the JSON file to, including the file
57
+ * itself.
58
+ * @param jsonValue - The JSON-like value to write to the file. Make sure that
59
+ * JSON.stringify can handle it.
60
+ * @param options - The options to this function.
61
+ * @param options.prettify - Whether to format the JSON as it is turned into a
62
+ * string such that it is broken up into separate lines (using 2 spaces as
63
+ * indentation).
64
+ * @param options.stringifier - The stringifier to use. Defaults to `JSON`.
65
+ * @param options.stringifier.stringify - A function that stringifies JSON.
66
+ * @returns The object corresponding to the parsed JSON file, typed against the
67
+ * struct.
68
+ * @throws An error with a stack trace if writing fails in any way.
69
+ */
70
+ export declare function writeJsonFile(filePath: string, jsonValue: Json, { stringifier, prettify, }?: {
71
+ stringifier?: {
72
+ stringify: typeof JSON.stringify;
73
+ };
74
+ prettify?: boolean;
75
+ }): Promise<void>;
76
+ /**
77
+ * Test the given path to determine whether it represents a file.
78
+ *
79
+ * @param filePath - The path to a (supposed) file on the filesystem.
80
+ * @returns A promise for true if the file exists or false otherwise.
81
+ * @throws An error with a stack trace if reading fails in any way.
82
+ */
83
+ export declare function fileExists(filePath: string): Promise<boolean>;
84
+ /**
85
+ * Test the given path to determine whether it represents a directory.
86
+ *
87
+ * @param directoryPath - The path to a (supposed) directory on the filesystem.
88
+ * @returns A promise for true if the file exists or false otherwise.
89
+ * @throws An error with a stack trace if reading fails in any way.
90
+ */
91
+ export declare function directoryExists(directoryPath: string): Promise<boolean>;
92
+ /**
93
+ * Create the given directory along with any directories leading up to the
94
+ * directory, or do nothing if the directory already exists.
95
+ *
96
+ * @param directoryPath - The path to the desired directory.
97
+ * @throws An error with a stack trace if reading fails in any way.
98
+ */
99
+ export declare function ensureDirectoryStructureExists(directoryPath: string): Promise<void>;
100
+ /**
101
+ * Remove the given file or directory if it exists, or do nothing if it does
102
+ * not.
103
+ *
104
+ * @param entryPath - The path to the file or directory.
105
+ * @throws An error with a stack trace if removal fails in any way.
106
+ */
107
+ export declare function forceRemove(entryPath: string): Promise<void>;
108
+ /**
109
+ * Construct a sandbox object which can be used in tests that need temporary
110
+ * access to the filesystem.
111
+ *
112
+ * @param projectName - The name of the project.
113
+ * @returns The sandbox object. This contains a `withinSandbox` function which
114
+ * can be used in tests (see example).
115
+ * @example
116
+ * ```typescript
117
+ * const { withinSandbox } = createSandbox('utils');
118
+ *
119
+ * // ... later ...
120
+ *
121
+ * it('does something with the filesystem', async () => {
122
+ * await withinSandbox(async ({ directoryPath }) => {
123
+ * await fs.writeFile(
124
+ * path.join(directoryPath, 'some-file'),
125
+ * 'some content',
126
+ * 'utf8'
127
+ * );
128
+ * })
129
+ * });
130
+ * ```
131
+ */
132
+ export declare function createSandbox(projectName: string): FileSandbox;
133
+ //# sourceMappingURL=file-system.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-system.d.ts","sourceRoot":"","sources":["../src/file-system.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAMjD;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,CACb,IAAI,EAAE,CAAC,IAAI,EAAE;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,KACrD,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAsB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMhE;AAED;;;;;;;GAOG;AACH,wBAAsB,SAAS,CAC7B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAOf;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,YAAY,CAAC,KAAK,SAAS,IAAI,EACnD,QAAQ,EAAE,MAAM,EAChB,EACE,MAAa,GACd,GAAE;IACD,MAAM,CAAC,EAAE;QACP,KAAK,EAAE,CACL,IAAI,EAAE,UAAU,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KACnC,UAAU,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC;KACpC,CAAC;CACE,GACL,OAAO,CAAC,KAAK,CAAC,CAWhB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,IAAI,EACf,EACE,WAAkB,EAClB,QAAgB,GACjB,GAAE;IACD,WAAW,CAAC,EAAE;QACZ,SAAS,EAAE,OAAO,IAAI,CAAC,SAAS,CAAC;KAClC,CAAC;IACF,QAAQ,CAAC,EAAE,OAAO,CAAC;CACf,GACL,OAAO,CAAC,IAAI,CAAC,CAWf;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAWnE;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAc7E;AAED;;;;;;GAMG;AACH,wBAAsB,8BAA8B,CAClD,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC,CASf;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CASlE;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,WAAW,CAqB9D"}
@@ -0,0 +1,213 @@
1
+ // This file is intended to be used only in a Node.js context.
2
+ /* eslint-disable import-x/no-nodejs-modules */
3
+ import { isErrorWithCode, wrapError } from '@metamask/error-utils';
4
+ import fs from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import { v4 as uuidV4 } from 'uuid';
8
+ /**
9
+ * Read the file at the given path, assuming its content is encoded as UTF-8.
10
+ *
11
+ * @param filePath - The path to the file.
12
+ * @returns The content of the file.
13
+ * @throws An error with a stack trace if reading fails in any way.
14
+ */
15
+ export async function readFile(filePath) {
16
+ try {
17
+ return await fs.readFile(filePath, 'utf8');
18
+ }
19
+ catch (error) {
20
+ throw wrapError(error, `Could not read file '${filePath}'`);
21
+ }
22
+ }
23
+ /**
24
+ * Write content to the file at the given path, creating the directory structure
25
+ * for the file automatically if necessary.
26
+ *
27
+ * @param filePath - The path to the file.
28
+ * @param content - The new content of the file.
29
+ * @throws An error with a stack trace if writing fails in any way.
30
+ */
31
+ export async function writeFile(filePath, content) {
32
+ try {
33
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
34
+ await fs.writeFile(filePath, content);
35
+ }
36
+ catch (error) {
37
+ throw wrapError(error, `Could not write file '${filePath}'`);
38
+ }
39
+ }
40
+ /**
41
+ * Read the assumed JSON file at the given path, attempts to parse it, and
42
+ * get the resulting object. Supports a custom parser (in case you want to
43
+ * use the [JSON5](https://www.npmjs.com/package/json5) package instead).
44
+ *
45
+ * @param filePath - The path segments pointing to the JSON file. Will be passed
46
+ * to path.join().
47
+ * @param options - Options to this function.
48
+ * @param options.parser - The parser object to use. Defaults to `JSON`.
49
+ * @param options.parser.parse - A function that parses JSON data.
50
+ * @returns The object corresponding to the parsed JSON file, typed against the
51
+ * struct.
52
+ * @throws An error with a stack trace if reading fails in any way, or if the
53
+ * parsed value is not a plain object.
54
+ */
55
+ export async function readJsonFile(filePath, { parser = JSON, } = {}) {
56
+ try {
57
+ const content = await fs.readFile(filePath, 'utf8');
58
+ // Intentionally returning `any`. We expect validation to be done by the
59
+ // caller.
60
+ // oxlint-disable-next-line typescript/no-unsafe-return
61
+ return parser.parse(content);
62
+ }
63
+ catch (error) {
64
+ throw wrapError(error, `Could not read JSON file '${filePath}'`);
65
+ }
66
+ }
67
+ /**
68
+ * Attempt to write the given JSON-like value to the file at the given path,
69
+ * creating the directory structure for the file automatically if necessary.
70
+ * Adds a newline to the end of the file. Supports a custom parser (in case you
71
+ * want to use the [JSON5](https://www.npmjs.com/package/json5) package
72
+ * instead).
73
+ *
74
+ * @param filePath - The path to write the JSON file to, including the file
75
+ * itself.
76
+ * @param jsonValue - The JSON-like value to write to the file. Make sure that
77
+ * JSON.stringify can handle it.
78
+ * @param options - The options to this function.
79
+ * @param options.prettify - Whether to format the JSON as it is turned into a
80
+ * string such that it is broken up into separate lines (using 2 spaces as
81
+ * indentation).
82
+ * @param options.stringifier - The stringifier to use. Defaults to `JSON`.
83
+ * @param options.stringifier.stringify - A function that stringifies JSON.
84
+ * @returns The object corresponding to the parsed JSON file, typed against the
85
+ * struct.
86
+ * @throws An error with a stack trace if writing fails in any way.
87
+ */
88
+ export async function writeJsonFile(filePath, jsonValue, { stringifier = JSON, prettify = false, } = {}) {
89
+ try {
90
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
91
+ const json = prettify
92
+ ? stringifier.stringify(jsonValue, null, ' ')
93
+ : stringifier.stringify(jsonValue);
94
+ await fs.writeFile(filePath, json);
95
+ }
96
+ catch (error) {
97
+ throw wrapError(error, `Could not write JSON file '${filePath}'`);
98
+ }
99
+ }
100
+ /**
101
+ * Test the given path to determine whether it represents a file.
102
+ *
103
+ * @param filePath - The path to a (supposed) file on the filesystem.
104
+ * @returns A promise for true if the file exists or false otherwise.
105
+ * @throws An error with a stack trace if reading fails in any way.
106
+ */
107
+ export async function fileExists(filePath) {
108
+ try {
109
+ const stats = await fs.stat(filePath);
110
+ return stats.isFile();
111
+ }
112
+ catch (error) {
113
+ if (isErrorWithCode(error) && error.code === 'ENOENT') {
114
+ return false;
115
+ }
116
+ throw wrapError(error, `Could not determine if file exists '${filePath}'`);
117
+ }
118
+ }
119
+ /**
120
+ * Test the given path to determine whether it represents a directory.
121
+ *
122
+ * @param directoryPath - The path to a (supposed) directory on the filesystem.
123
+ * @returns A promise for true if the file exists or false otherwise.
124
+ * @throws An error with a stack trace if reading fails in any way.
125
+ */
126
+ export async function directoryExists(directoryPath) {
127
+ try {
128
+ const stats = await fs.stat(directoryPath);
129
+ return stats.isDirectory();
130
+ }
131
+ catch (error) {
132
+ if (isErrorWithCode(error) && error.code === 'ENOENT') {
133
+ return false;
134
+ }
135
+ throw wrapError(error, `Could not determine if directory exists '${directoryPath}'`);
136
+ }
137
+ }
138
+ /**
139
+ * Create the given directory along with any directories leading up to the
140
+ * directory, or do nothing if the directory already exists.
141
+ *
142
+ * @param directoryPath - The path to the desired directory.
143
+ * @throws An error with a stack trace if reading fails in any way.
144
+ */
145
+ export async function ensureDirectoryStructureExists(directoryPath) {
146
+ try {
147
+ await fs.mkdir(directoryPath, { recursive: true });
148
+ }
149
+ catch (error) {
150
+ throw wrapError(error, `Could not create directory structure '${directoryPath}'`);
151
+ }
152
+ }
153
+ /**
154
+ * Remove the given file or directory if it exists, or do nothing if it does
155
+ * not.
156
+ *
157
+ * @param entryPath - The path to the file or directory.
158
+ * @throws An error with a stack trace if removal fails in any way.
159
+ */
160
+ export async function forceRemove(entryPath) {
161
+ try {
162
+ await fs.rm(entryPath, {
163
+ recursive: true,
164
+ force: true,
165
+ });
166
+ }
167
+ catch (error) {
168
+ throw wrapError(error, `Could not remove file or directory '${entryPath}'`);
169
+ }
170
+ }
171
+ /**
172
+ * Construct a sandbox object which can be used in tests that need temporary
173
+ * access to the filesystem.
174
+ *
175
+ * @param projectName - The name of the project.
176
+ * @returns The sandbox object. This contains a `withinSandbox` function which
177
+ * can be used in tests (see example).
178
+ * @example
179
+ * ```typescript
180
+ * const { withinSandbox } = createSandbox('utils');
181
+ *
182
+ * // ... later ...
183
+ *
184
+ * it('does something with the filesystem', async () => {
185
+ * await withinSandbox(async ({ directoryPath }) => {
186
+ * await fs.writeFile(
187
+ * path.join(directoryPath, 'some-file'),
188
+ * 'some content',
189
+ * 'utf8'
190
+ * );
191
+ * })
192
+ * });
193
+ * ```
194
+ */
195
+ export function createSandbox(projectName) {
196
+ const directoryPath = path.join(os.tmpdir(), projectName, uuidV4());
197
+ return {
198
+ directoryPath,
199
+ async withinSandbox(test) {
200
+ if (await directoryExists(directoryPath)) {
201
+ throw new Error(`${directoryPath} already exists. Cannot continue.`);
202
+ }
203
+ await ensureDirectoryStructureExists(directoryPath);
204
+ try {
205
+ await test({ directoryPath });
206
+ }
207
+ finally {
208
+ await forceRemove(directoryPath);
209
+ }
210
+ },
211
+ };
212
+ }
213
+ //# sourceMappingURL=file-system.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-system.js","sourceRoot":"","sources":["../src/file-system.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,+CAA+C;AAE/C,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAEnE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,EAAE,IAAI,MAAM,EAAE,MAAM,MAAM,CAAC;AAapC;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,QAAgB;IAC7C,IAAI,CAAC;QACH,OAAO,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC7C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,SAAS,CAAC,KAAK,EAAE,wBAAwB,QAAQ,GAAG,CAAC,CAAC;IAC9D,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,QAAgB,EAChB,OAAe;IAEf,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5D,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACxC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,SAAS,CAAC,KAAK,EAAE,yBAAyB,QAAQ,GAAG,CAAC,CAAC;IAC/D,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,QAAgB,EAChB,EACE,MAAM,GAAG,IAAI,GACd,GAMG,EAAE;IAEN,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAEpD,wEAAwE;QACxE,UAAU;QACV,uDAAuD;QACvD,OAAO,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,SAAS,CAAC,KAAK,EAAE,6BAA6B,QAAQ,GAAG,CAAC,CAAC;IACnE,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,QAAgB,EAChB,SAAe,EACf,EACE,WAAW,GAAG,IAAI,EAClB,QAAQ,GAAG,KAAK,GACjB,GAKG,EAAE;IAEN,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5D,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;QAErC,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACrC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,SAAS,CAAC,KAAK,EAAE,8BAA8B,QAAQ,GAAG,CAAC,CAAC;IACpE,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,QAAgB;IAC/C,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACtC,OAAO,KAAK,CAAC,MAAM,EAAE,CAAC;IACxB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,eAAe,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACtD,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,SAAS,CAAC,KAAK,EAAE,uCAAuC,QAAQ,GAAG,CAAC,CAAC;IAC7E,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,aAAqB;IACzD,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC3C,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC;IAC7B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,eAAe,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACtD,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,SAAS,CACb,KAAK,EACL,4CAA4C,aAAa,GAAG,CAC7D,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAClD,aAAqB;IAErB,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,KAAK,CAAC,aAAa,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACrD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,SAAS,CACb,KAAK,EACL,yCAAyC,aAAa,GAAG,CAC1D,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,SAAiB;IACjD,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE;YACrB,SAAS,EAAE,IAAI;YACf,KAAK,EAAE,IAAI;SACZ,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,SAAS,CAAC,KAAK,EAAE,uCAAuC,SAAS,GAAG,CAAC,CAAC;IAC9E,CAAC;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,MAAM,EAAE,CAAC,CAAC;IAEpE,OAAO;QACL,aAAa;QACb,KAAK,CAAC,aAAa,CACjB,IAAwD;YAExD,IAAI,MAAM,eAAe,CAAC,aAAa,CAAC,EAAE,CAAC;gBACzC,MAAM,IAAI,KAAK,CAAC,GAAG,aAAa,mCAAmC,CAAC,CAAC;YACvE,CAAC;YAED,MAAM,8BAA8B,CAAC,aAAa,CAAC,CAAC;YAEpD,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC;YAChC,CAAC;oBAAS,CAAC;gBACT,MAAM,WAAW,CAAC,aAAa,CAAC,CAAC;YACnC,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["// This file is intended to be used only in a Node.js context.\n/* eslint-disable import-x/no-nodejs-modules */\n\nimport { isErrorWithCode, wrapError } from '@metamask/error-utils';\nimport type { Json } from '@metamask/type-utils';\nimport fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { v4 as uuidV4 } from 'uuid';\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.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.mkdir(path.dirname(filePath), { recursive: true });\n await fs.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.readFile(filePath, 'utf8');\n\n // Intentionally returning `any`. We expect validation to be done by the\n // caller.\n // oxlint-disable-next-line typescript/no-unsafe-return\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.mkdir(path.dirname(filePath), { recursive: true });\n const json = prettify\n ? stringifier.stringify(jsonValue, null, ' ')\n : stringifier.stringify(jsonValue);\n\n await fs.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.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.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.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 await fs.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.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, uuidV4());\n\n return {\n directoryPath,\n async withinSandbox(\n test: (args: { directoryPath: string }) => Promise<void>,\n ): Promise<void> {\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"]}
package/dist/index.d.ts CHANGED
@@ -1,8 +1,3 @@
1
- /**
2
- * Example function that returns a greeting for the given name.
3
- *
4
- * @param name - The name to greet.
5
- * @returns The greeting.
6
- */
7
- export default function greeter(name: string): string;
1
+ export type { FileSandbox } from './file-system.js';
2
+ export { createSandbox, directoryExists, ensureDirectoryStructureExists, fileExists, forceRemove, readFile, readJsonFile, writeFile, writeJsonFile, } from './file-system.js';
8
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EACL,aAAa,EACb,eAAe,EACf,8BAA8B,EAC9B,UAAU,EACV,WAAW,EACX,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,aAAa,GACd,MAAM,kBAAkB,CAAC"}
package/dist/index.js CHANGED
@@ -1,10 +1,2 @@
1
- /**
2
- * Example function that returns a greeting for the given name.
3
- *
4
- * @param name - The name to greet.
5
- * @returns The greeting.
6
- */
7
- export default function greeter(name) {
8
- return `Hello, ${name}!`;
9
- }
1
+ export { createSandbox, directoryExists, ensureDirectoryStructureExists, fileExists, forceRemove, readFile, readJsonFile, writeFile, writeJsonFile, } from './file-system.js';
10
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,IAAY;IAC1C,OAAO,UAAU,IAAI,GAAG,CAAC;AAC3B,CAAC","sourcesContent":["/**\n * Example function that returns a greeting for the given name.\n *\n * @param name - The name to greet.\n * @returns The greeting.\n */\nexport default function greeter(name: string): string {\n return `Hello, ${name}!`;\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,aAAa,EACb,eAAe,EACf,8BAA8B,EAC9B,UAAU,EACV,WAAW,EACX,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,aAAa,GACd,MAAM,kBAAkB,CAAC","sourcesContent":["export type { FileSandbox } from './file-system.js';\nexport {\n createSandbox,\n directoryExists,\n ensureDirectoryStructureExists,\n fileExists,\n forceRemove,\n readFile,\n readJsonFile,\n writeFile,\n writeJsonFile,\n} from './file-system.js';\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask-previews/node-utils",
3
- "version": "0.0.0-preview-a583980c5",
3
+ "version": "0.0.0-preview-7baa62aa9",
4
4
  "description": "Utilities for Node.js environments, such as filesystem access and test sandboxing",
5
5
  "keywords": [
6
6
  "Ethereum",
@@ -49,6 +49,11 @@
49
49
  "test:verbose": "vitest --reporter=verbose",
50
50
  "test:watch": "vitest --watch"
51
51
  },
52
+ "dependencies": {
53
+ "@metamask/error-utils": "^0.0.0",
54
+ "@metamask/type-utils": "^0.0.0",
55
+ "uuid": "^11.1.1"
56
+ },
52
57
  "devDependencies": {
53
58
  "@metamask/auto-changelog": "^6.2.1",
54
59
  "@typescript/native": "npm:typescript@^7.0.2",