mem-fs-editor 12.0.2 → 12.0.3

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/README.md CHANGED
@@ -79,16 +79,30 @@ Delete a file or a directory.
79
79
  Copy file(s) from the `from` path to the `to` path.
80
80
  When passing array, you should pass `options.fromBasePath` to be used to calculate the `to` relative path. The common directory will be detected and used as `fromBasePath` otherwise.
81
81
 
82
- Optionally, pass an `options.fileTransform` function (`fileTransform(destinationPath, sourcePath, contents)`) that transforms both the destination path and file contents. The function takes two arguments:
82
+ Optionally, pass an `options.fileTransform` function that transforms both the destination path and file contents. The function receives a single object parameter with the following properties:
83
83
 
84
- - `destinationPath`: The resolved destination file path
85
- - `sourcePath`: The resolved source file path
84
+ - `destinationPath`: The resolved destination file path (string)
85
+ - `sourcePath`: The source file path (string)
86
86
  - `contents`: The file contents as a `Buffer`
87
+ - `data`: Optional data passed via `options.transformData`
88
+ - `options`: Optional options passed via `options.transformOptions`
87
89
 
88
- It should return a tuple `[newFilepath, newContents]` where:
90
+ The function should return an object `{ path, contents }` where:
89
91
 
90
- - `newFilepath`: The transformed destination path (string)
91
- - `newContents`: The transformed file contents (Buffer)
92
+ - `path`: The transformed destination path (string)
93
+ - `contents`: The transformed file contents (string | Buffer)
94
+
95
+ Example:
96
+
97
+ ```js
98
+ fs.copy('source.txt', 'dest.txt', {
99
+ fileTransform({ destinationPath, sourcePath, contents, data, options }) {
100
+ const newPath = destinationPath.replace('.txt', '.md');
101
+ const newContents = contents.toString().toUpperCase();
102
+ return { path: newPath, contents: newContents };
103
+ },
104
+ });
105
+ ```
92
106
 
93
107
  `options.ignoreNoMatch` can be used to silence the error thrown if no files match the `from` pattern.
94
108
  `options.append` can be used to append `from` contents to `to` instead of copying, when the file is already loaded in mem-fs (safe for regeneration).
@@ -104,7 +118,7 @@ Async version of `copy`.
104
118
  `copy` loads `from` to memory and copy its contents to `to`.
105
119
  `copyAsync` copies directly from the disk to `to`, falling back to `copy` behavior if the file doesn't exists on disk.
106
120
 
107
- Same parameters of `copy`
121
+ Same parameters of `copy`. The `fileTransform` function can also return a `Promise<{ path: string; contents: string | Buffer }>` for async transformations.
108
122
 
109
123
  See [copy() documentation for more details](#copyfrom-to-options).
110
124
 
@@ -23,7 +23,6 @@ async function write(file) {
23
23
  await fs.writeFile(file.path, file.contents, { mode: newMode });
24
24
  if (newMode !== undefined) {
25
25
  const { mode: existingMode } = await fs.stat(file.path);
26
- /* c8 ignore next */
27
26
  // eslint-disable-next-line no-bitwise
28
27
  if ((existingMode & 0o777) !== (newMode & 0o777)) {
29
28
  await fs.chmod(file.path, newMode);
@@ -1,20 +1,37 @@
1
1
  import { GlobOptions } from 'tinyglobby';
2
2
  import type { MemFsEditor } from '../index.js';
3
3
  import type { Options as MultimatchOptions } from 'multimatch';
4
- type CopySingleAsyncOptions = Parameters<MemFsEditor['append']>[2] & {
4
+ type CopySingleAsyncOptions<TransformData = any, TransformOptions = any> = Parameters<MemFsEditor['append']>[2] & {
5
5
  append?: boolean;
6
6
  /**
7
7
  * @experimental This API is experimental and may change without a major version bump.
8
8
  *
9
9
  * Transform both the file path and content during copy.
10
- * @param destinationPath The destination file path
11
- * @param sourcePath The source file path
12
- * @param contents The file content as Buffer
13
- * @returns Tuple of [new filepath, new content]
10
+ * @param options The transform options
11
+ * @param options.destinationPath The destination file path
12
+ * @param options.sourcePath The source file path
13
+ * @param options.contents The file content as Buffer
14
+ * @param options.options The options passed to fileTransform
15
+ * @param options.data The data passed to fileTransform
16
+ * @returns An object containing the new file path and contents.
14
17
  */
15
- fileTransform?: (destinationPath: string, sourcePath: string, contents: Buffer) => [string, string | Buffer] | Promise<[string, string | Buffer]>;
18
+ fileTransform?: (options: {
19
+ destinationPath: string;
20
+ sourcePath: string;
21
+ contents: Buffer;
22
+ data?: TransformData;
23
+ options?: TransformOptions;
24
+ }) => {
25
+ path: string;
26
+ contents: string | Buffer;
27
+ } | Promise<{
28
+ path: string;
29
+ contents: string | Buffer;
30
+ }>;
31
+ transformData?: TransformData;
32
+ transformOptions?: TransformOptions;
16
33
  };
17
- type CopyAsyncOptions = CopySingleAsyncOptions & {
34
+ type CopyAsyncOptions<TransformData = any, TransformOptions = any> = CopySingleAsyncOptions<TransformData, TransformOptions> & {
18
35
  noGlob?: boolean;
19
36
  /**
20
37
  * Options for disk globbing.
@@ -28,5 +45,5 @@ type CopyAsyncOptions = CopySingleAsyncOptions & {
28
45
  ignoreNoMatch?: boolean;
29
46
  fromBasePath?: string;
30
47
  };
31
- export declare function copyAsync(this: MemFsEditor, from: string | string[], to: string, options?: CopyAsyncOptions): Promise<void>;
48
+ export declare function copyAsync<const TransformData = any, const TransformOptions = any>(this: MemFsEditor, from: string | string[], to: string, options?: CopyAsyncOptions<TransformData, TransformOptions>): Promise<void>;
32
49
  export {};
@@ -82,19 +82,25 @@ export async function copyAsync(from, to, options = {}) {
82
82
  ...storeFiles.map((file) => copySingleAsync(this, file, generateDestination(file), options)),
83
83
  ]);
84
84
  }
85
- const defaultFileTransform = (destPath, _sourcePath, contents) => [
86
- destPath,
85
+ const defaultFileTransform = ({ destinationPath, contents }) => ({
86
+ path: destinationPath,
87
87
  contents,
88
- ];
88
+ });
89
89
  async function copySingleAsync(editor, from, to, options = {}) {
90
90
  from = path.resolve(from);
91
91
  debug('Copying %s to %s with %o', from, to, options);
92
92
  const file = editor.store.get(from);
93
93
  assert(file.contents, `Cannot copy empty file ${from}`);
94
- const { fileTransform = defaultFileTransform } = options;
95
- const transformPromise = fileTransform(path.resolve(to), from, file.contents);
94
+ const { fileTransform = defaultFileTransform, transformOptions, transformData } = options;
95
+ const transformPromise = fileTransform({
96
+ destinationPath: path.resolve(to),
97
+ sourcePath: from,
98
+ contents: file.contents,
99
+ options: transformOptions,
100
+ data: transformData,
101
+ });
96
102
  let contents;
97
- [to, contents] = await transformPromise;
103
+ ({ path: to, contents } = await transformPromise);
98
104
  if (options.append && editor.store.existsInMemory(to)) {
99
105
  editor.append(to, contents, { create: true, ...options });
100
106
  }
@@ -1,5 +1,5 @@
1
1
  import type { MemFsEditor } from '../index.js';
2
2
  import ejs from 'ejs';
3
- export default function (this: MemFsEditor, from: string | string[], to: string, data?: ejs.Data, options?: Omit<NonNullable<Parameters<MemFsEditor['copyAsync']>[2]>, 'fileTransform'> & {
3
+ export default function (this: MemFsEditor, from: string | string[], to: string, data?: ejs.Data, options?: Omit<NonNullable<Parameters<MemFsEditor['copyAsync']>[2]>, 'fileTransform' | 'transformData'> & {
4
4
  transformOptions?: ejs.Options;
5
5
  }): Promise<void>;
@@ -1,12 +1,20 @@
1
1
  import { isBinary } from '../util.js';
2
2
  import ejs from 'ejs';
3
- export default async function (from, to, data = {}, options) {
3
+ export default async function (from, to, data = {}, options, compatOptions) {
4
+ /* v8 ignore next -- @preserve */
5
+ if (compatOptions) {
6
+ // Backward compatibility.
7
+ options = {
8
+ ...compatOptions,
9
+ transformOptions: options,
10
+ };
11
+ }
4
12
  await this.copyAsync(from, to, {
5
13
  ...options,
6
- async fileTransform(destinationPath, sourcePath, contents) {
7
- const { transformOptions } = options ?? {};
8
- const processedPath = await ejs.render(destinationPath, data, {
9
- ...transformOptions,
14
+ transformData: data,
15
+ async fileTransform({ destinationPath, sourcePath, contents, data, options }) {
16
+ let processedPath = await ejs.render(destinationPath, data, {
17
+ ...options,
10
18
  cache: false, // Cache uses filename as key, which is not provided in this case.
11
19
  });
12
20
  const processedContent = isBinary(sourcePath, contents)
@@ -16,9 +24,13 @@ export default async function (from, to, data = {}, options) {
16
24
  filename: sourcePath,
17
25
  // Async option cannot be set to true because `include()` then also become async which change the behaviors of templates.
18
26
  // Users must pass async value in transformOptions if they want to use async features of ejs.
19
- ...transformOptions,
27
+ ...options,
20
28
  });
21
- return [processedPath.replace(/.ejs$/, ''), processedContent];
29
+ if (!to.endsWith('.ejs')) {
30
+ // If the initial destination path ends with .ejs, the output is expected to be .ejs file, so we keep the extension. Remove .ejs extension for other cases.
31
+ processedPath = processedPath.replace(/.ejs$/, '');
32
+ }
33
+ return { path: processedPath, contents: processedContent };
22
34
  },
23
35
  });
24
36
  }
@@ -1,5 +1,5 @@
1
1
  import ejs from 'ejs';
2
2
  import type { MemFsEditor } from '../index.js';
3
- export declare function copyTpl(this: MemFsEditor, from: string | string[], to: string, data?: ejs.Data, options?: Omit<NonNullable<Parameters<MemFsEditor['copy']>[2]>, 'fileTransform'> & {
3
+ export declare function copyTpl(this: MemFsEditor, from: string | string[], to: string, data?: ejs.Data, options?: Omit<NonNullable<Parameters<MemFsEditor['copy']>[2]>, 'fileTransform' | 'transformData'> & {
4
4
  transformOptions?: ejs.Options;
5
5
  }): void;
@@ -1,15 +1,23 @@
1
1
  import ejs from 'ejs';
2
2
  import { isBinary } from '../util.js';
3
- export function copyTpl(from, to, data = {}, options) {
3
+ export function copyTpl(from, to, data = {}, options, compatOptions) {
4
+ /* v8 ignore next -- @preserve */
5
+ if (compatOptions) {
6
+ // Backward compatibility.
7
+ options = {
8
+ ...compatOptions,
9
+ transformOptions: options,
10
+ };
11
+ }
4
12
  this.copy(from, to, {
5
13
  ...options,
6
- fileTransform(destinationPath, sourcePath, contents) {
7
- const { transformOptions } = options ?? {};
8
- if (transformOptions?.async) {
14
+ transformData: data,
15
+ fileTransform({ destinationPath, sourcePath, contents, data, options }) {
16
+ if (options?.async) {
9
17
  throw new Error('Async EJS rendering is not supported');
10
18
  }
11
- const processedPath = ejs.render(destinationPath, data, {
12
- ...transformOptions,
19
+ let processedPath = ejs.render(destinationPath, data, {
20
+ ...options,
13
21
  cache: false, // Cache uses filename as key, which is not provided in this case.
14
22
  async: false,
15
23
  });
@@ -18,10 +26,14 @@ export function copyTpl(from, to, data = {}, options) {
18
26
  : ejs.render(contents.toString(), data, {
19
27
  // Setting filename by default allow including partials.
20
28
  filename: sourcePath,
21
- ...transformOptions,
29
+ ...options,
22
30
  async: false,
23
31
  });
24
- return [processedPath.replace(/.ejs$/, ''), processedContent];
32
+ if (!to.endsWith('.ejs')) {
33
+ // If the initial destination path ends with .ejs, the output is expected to be .ejs file, so we keep the extension. Remove .ejs extension for other cases.
34
+ processedPath = processedPath.replace(/.ejs$/, '');
35
+ }
36
+ return { path: processedPath, contents: processedContent };
25
37
  },
26
38
  });
27
39
  }
@@ -1,20 +1,34 @@
1
1
  import { type GlobOptions } from 'tinyglobby';
2
2
  import type { Options as MultimatchOptions } from 'multimatch';
3
3
  import type { MemFsEditor } from '../index.js';
4
- type CopySingleOptions = {
4
+ type CopySingleOptions<TransformData = any, TransformOptions = any> = {
5
5
  append?: boolean;
6
6
  /**
7
7
  * @experimental This API is experimental and may change without a major version bump.
8
8
  *
9
9
  * Transform both the file path and content during copy.
10
- * @param destinationPath The destination file path
11
- * @param sourcePath The source file path
12
- * @param contents The file content as Buffer
13
- * @returns Tuple of [new filepath, new content]
10
+ * @param options The transform options
11
+ * @param options.destinationPath The destination file path
12
+ * @param options.sourcePath The source file path
13
+ * @param options.contents The file content as Buffer
14
+ * @param options.options The options passed to fileTransform
15
+ * @param options.data The data passed to fileTransform
16
+ * @returns An object containing the new file path and contents.
14
17
  */
15
- fileTransform?: (destinationPath: string, sourcePath: string, contents: Buffer) => [string, string | Buffer];
18
+ fileTransform?: (options: {
19
+ destinationPath: string;
20
+ sourcePath: string;
21
+ contents: Buffer;
22
+ data?: TransformData;
23
+ options?: TransformOptions;
24
+ }) => {
25
+ path: string;
26
+ contents: string | Buffer;
27
+ };
28
+ transformData?: TransformData;
29
+ transformOptions?: TransformOptions;
16
30
  };
17
- type CopyOptions = CopySingleOptions & {
31
+ type CopyOptions<TransformData = any, TransformOptions = any> = CopySingleOptions<TransformData, TransformOptions> & {
18
32
  noGlob?: boolean;
19
33
  /**
20
34
  * Options for disk globbing.
@@ -28,6 +42,6 @@ type CopyOptions = CopySingleOptions & {
28
42
  ignoreNoMatch?: boolean;
29
43
  fromBasePath?: string;
30
44
  };
31
- export declare function copy(this: MemFsEditor, from: string | string[], to: string, options?: CopyOptions): void;
32
- export declare function copySingle(editor: MemFsEditor, from: string, to: string, options?: CopySingleOptions): void;
45
+ export declare function copy<const TransformData = any, const TransformOptions = any>(this: MemFsEditor, from: string | string[], to: string, options?: CopyOptions<TransformData, TransformOptions>): void;
46
+ export declare function copySingle<const TransformData = any, const TransformOptions = any>(editor: MemFsEditor, from: string, to: string, options?: CopySingleOptions<TransformData, TransformOptions>): void;
33
47
  export {};
@@ -74,20 +74,27 @@ export function copy(from, to, options = {}) {
74
74
  copySingle(this, file.resolvedFrom, toFile, options);
75
75
  });
76
76
  }
77
- const defaultFileTransform = (destPath, _srcPath, contents) => [
78
- destPath,
77
+ const defaultFileTransform = ({ destinationPath, contents }) => ({
78
+ path: destinationPath,
79
79
  contents,
80
- ];
80
+ });
81
81
  export function copySingle(editor, from, to, options = {}) {
82
82
  assert(editor.exists(from), 'Trying to copy from a source that does not exist: ' + from);
83
83
  debug('Copying %s to %s with %o', from, to, options);
84
84
  const file = editor.store.get(from);
85
85
  let contents;
86
+ /* v8 ignore next -- @preserve should not happen */
86
87
  if (!file.contents) {
87
88
  throw new Error(`Cannot copy empty file ${from}`);
88
89
  }
89
- const { fileTransform = defaultFileTransform } = options;
90
- [to, contents] = fileTransform(path.resolve(to), from, file.contents);
90
+ const { fileTransform = defaultFileTransform, transformOptions, transformData } = options;
91
+ ({ path: to, contents } = fileTransform({
92
+ destinationPath: path.resolve(to),
93
+ sourcePath: from,
94
+ contents: file.contents,
95
+ options: transformOptions,
96
+ data: transformData,
97
+ }));
91
98
  if (options.append && editor.store.existsInMemory(to)) {
92
99
  editor.append(to, contents, { create: true, ...options });
93
100
  }
@@ -3,7 +3,6 @@ import { resolve } from 'path';
3
3
  import { isFileStateModified, setModifiedFileState } from '../state.js';
4
4
  import File from 'vinyl';
5
5
  export const isMemFsEditorFileEqual = (a, b) => {
6
- /* c8 ignore next */
7
6
  if (a.stat?.mode !== b.stat?.mode) {
8
7
  return false;
9
8
  }
package/dist/util.js CHANGED
@@ -33,7 +33,7 @@ export function globify(inputFilePath) {
33
33
  }
34
34
  if (!fs.existsSync(filePath)) {
35
35
  // The target of a pattern who's not a glob and doesn't match an existing
36
- // entity on the disk is ambiguous. As such, match both files and directories.
36
+ // Entity on the disk is ambiguous. As such, match both files and directories.
37
37
  return [filePath, normalize(path.join(filePath, '**'))];
38
38
  }
39
39
  const fsStats = fs.statSync(filePath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mem-fs-editor",
3
- "version": "12.0.2",
3
+ "version": "12.0.3",
4
4
  "description": "File edition helpers working on top of mem-fs",
5
5
  "repository": "SBoudrias/mem-fs-editor",
6
6
  "license": "MIT",