mem-fs-editor 11.1.4 → 12.0.0
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 +22 -15
- package/dist/actions/append-tpl.d.ts +4 -3
- package/dist/actions/append-tpl.js +6 -3
- package/dist/actions/append.d.ts +2 -3
- package/dist/actions/append.js +9 -11
- package/dist/actions/commit-file-async.d.ts +1 -1
- package/dist/actions/commit-file-async.js +2 -5
- package/dist/actions/copy-async.d.ts +29 -13
- package/dist/actions/copy-async.js +89 -69
- package/dist/actions/copy-tpl-async.d.ts +4 -3
- package/dist/actions/copy-tpl-async.js +20 -19
- package/dist/actions/copy-tpl.d.ts +4 -10
- package/dist/actions/copy-tpl.js +24 -18
- package/dist/actions/copy.d.ts +28 -11
- package/dist/actions/copy.js +82 -70
- package/dist/actions/delete.d.ts +2 -2
- package/dist/actions/delete.js +14 -16
- package/dist/actions/dump.d.ts +3 -1
- package/dist/actions/dump.js +1 -1
- package/dist/actions/extend-json.d.ts +1 -1
- package/dist/actions/move.d.ts +1 -2
- package/dist/actions/read-json.d.ts +2 -1
- package/dist/actions/read-json.js +3 -4
- package/dist/actions/read.d.ts +12 -7
- package/dist/actions/read.js +3 -3
- package/dist/actions/write-json.js +3 -8
- package/dist/actions/write.d.ts +5 -5
- package/dist/actions/write.js +10 -9
- package/dist/index.d.ts +5 -13
- package/dist/index.js +4 -8
- package/dist/state.d.ts +1 -1
- package/dist/state.js +24 -21
- package/dist/transform.js +5 -1
- package/dist/util.d.ts +20 -6
- package/dist/util.js +26 -24
- package/package.json +38 -25
package/README.md
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# mem-fs-editor
|
|
2
2
|
|
|
3
|
-
[](https://github.com/SBoudrias/mem-fs-editor/actions?query=workflow%3A%22Node.js+CI%22)
|
|
4
3
|
[](http://badge.fury.io/js/mem-fs-editor)
|
|
5
4
|
[](https://codecov.io/gh/SBoudrias/mem-fs-editor)
|
|
6
5
|
|
|
@@ -55,12 +54,13 @@ Append the new contents to the current file contents.
|
|
|
55
54
|
- `options.separator` (default `os.EOL`). Separator to insert between current and new contents.
|
|
56
55
|
- `options.create` (default `false`). Create the file if doesn't exists.
|
|
57
56
|
|
|
58
|
-
### `#appendTpl(filepath, contents,
|
|
57
|
+
### `#appendTpl(filepath, contents, data[, options])`
|
|
59
58
|
|
|
60
|
-
Append the new `contents` to the
|
|
59
|
+
Append the new `contents` to the existing `filepath` content and parse the new contents as an [ejs](http://ejs.co/) template where `data` is the template context (the variable names available inside the template).
|
|
61
60
|
|
|
62
61
|
- `options.trimEnd` (default `true`). Trim trailing whitespace of the current file contents.
|
|
63
62
|
- `options.separator` (default `os.EOL`). Separator to insert between current and new contents.
|
|
63
|
+
- `options.transformOptions`. Options passed to the EJS renderer when processing the template, similar to `copyTpl`.
|
|
64
64
|
|
|
65
65
|
### `#extendJSON(filepath, contents[, replacer [, space]])`
|
|
66
66
|
|
|
@@ -74,38 +74,45 @@ Delete a file or a directory.
|
|
|
74
74
|
|
|
75
75
|
`filePath` can also be a `glob`. If `filePath` is glob, you can optionally pass in an `options.globOptions` object to change its pattern matching behavior. The full list of options are being described [here](https://github.com/mrmlnc/fast-glob#options-1). The `sync` flag is forced to be `true` in `globOptions`.
|
|
76
76
|
|
|
77
|
-
### `#copy(from, to, [options]
|
|
77
|
+
### `#copy(from, to, [options])`
|
|
78
78
|
|
|
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.
|
|
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:
|
|
83
|
+
- `destinationPath`: The resolved destination file path
|
|
84
|
+
- `sourcePath`: The resolved source file path
|
|
85
|
+
- `contents`: The file contents as a `Buffer`
|
|
86
|
+
|
|
87
|
+
It should return a tuple `[newFilepath, newContents]` where:
|
|
88
|
+
- `newFilepath`: The transformed destination path (string)
|
|
89
|
+
- `newContents`: The transformed file contents (Buffer)
|
|
83
90
|
|
|
84
91
|
`options.ignoreNoMatch` can be used to silence the error thrown if no files match the `from` pattern.
|
|
85
92
|
`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).
|
|
86
93
|
|
|
87
94
|
`from` can be a glob pattern that'll be match against the file system. If that's the case, then `to` must be an output directory. For a globified `from`, you can optionally pass in an `options.globOptions` object to change its pattern matching behavior. The full list of options are being described [here](https://github.com/mrmlnc/fast-glob#options-1). The `nodir` flag is forced to be `true` in `globOptions` to ensure a vinyl object representing each matching directory is marked as `deleted` in the `mem-fs` store.
|
|
88
95
|
|
|
89
|
-
Optionally, when `from` is a glob pattern, pass an `options.processDestinationPath` function (`processDestinationPath(destinationFile)`) returning a string who'll become the new file name.
|
|
90
|
-
|
|
91
96
|
`options.noGlob` can be used to by bypass glob matching entirely. In that case, `from` will directly match file paths against the file system.
|
|
92
97
|
|
|
93
|
-
### `#copyAsync(from, to, [options]
|
|
98
|
+
### `#copyAsync(from, to, [options])`
|
|
94
99
|
|
|
95
100
|
Async version of `copy`.
|
|
96
101
|
|
|
97
102
|
`copy` loads `from` to memory and copy its contents to `to`.
|
|
98
103
|
`copyAsync` copies directly from the disk to `to`, falling back to `copy` behavior if the file doesn't exists on disk.
|
|
99
104
|
|
|
100
|
-
Same parameters of `copy`
|
|
105
|
+
Same parameters of `copy`
|
|
106
|
+
|
|
107
|
+
See [copy() documentation for more details](#copyfrom-to-options).
|
|
101
108
|
|
|
102
|
-
### `#copyTpl(from, to,
|
|
109
|
+
### `#copyTpl(from, to, data[, options])`
|
|
103
110
|
|
|
104
|
-
Copy the `from` file and, if it is not a binary file, parse its content as an [ejs](http://ejs.co/) template where `
|
|
111
|
+
Copy the `from` file and, if it is not a binary file, parse its content as an [ejs](http://ejs.co/) template where `data` is the template context (the variable names available inside the template).
|
|
105
112
|
|
|
106
|
-
|
|
113
|
+
`options.transformOptions` replaced the old `tplOptions` parameter and is passed as ejs options. `mem-fs-editor` automatically setup the filename option so you can easily use partials.
|
|
107
114
|
|
|
108
|
-
You can also optionally pass a `
|
|
115
|
+
You can also optionally pass a `options` object (see [copy() documentation for more details](#copyfrom-to-options)).
|
|
109
116
|
|
|
110
117
|
Templates syntax looks like this:
|
|
111
118
|
|
|
@@ -122,13 +129,13 @@ Dir syntax looks like this:
|
|
|
122
129
|
|
|
123
130
|
Refer to the [ejs documentation](http://ejs.co/) for more details.
|
|
124
131
|
|
|
125
|
-
### `#copyTplAsync(from, to, [
|
|
132
|
+
### `#copyTplAsync(from, to, data[, options])`
|
|
126
133
|
|
|
127
134
|
Async version of `copyTpl` that uses `copyAsync` instead of `copy`.
|
|
128
135
|
|
|
129
136
|
Can be used for best performance. Reduces overheads.
|
|
130
137
|
|
|
131
|
-
Same parameters of `copyTpl` (see [copyTpl() documentation for more details](#
|
|
138
|
+
Same parameters of `copyTpl` (see [copyTpl() documentation for more details](#copytplfrom-to-data-options)).
|
|
132
139
|
|
|
133
140
|
### `#move(from, to, [options])`
|
|
134
141
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import ejs from 'ejs';
|
|
2
2
|
import type { MemFsEditor } from '../index.js';
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
export default function appendTpl(this: MemFsEditor, to: string, contents: string | Buffer, data?: ejs.Data, options?: NonNullable<Parameters<MemFsEditor['append']>[2]> & {
|
|
4
|
+
transformOptions?: ejs.Options;
|
|
5
|
+
}): void;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
export default function appendTpl(to, contents,
|
|
3
|
-
|
|
1
|
+
import ejs from 'ejs';
|
|
2
|
+
export default function appendTpl(to, contents, data, options) {
|
|
3
|
+
if (options?.transformOptions?.async) {
|
|
4
|
+
throw new Error('Async EJS rendering is not supported');
|
|
5
|
+
}
|
|
6
|
+
this.append(to, ejs.render(contents.toString(), data, { ...options?.transformOptions, async: false }), options);
|
|
4
7
|
}
|
package/dist/actions/append.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { MemFsEditor } from '../index.js';
|
|
2
|
-
export
|
|
2
|
+
export default function append(this: MemFsEditor, to: string, contents: string | Buffer, options?: {
|
|
3
3
|
create?: boolean;
|
|
4
4
|
trimEnd?: boolean;
|
|
5
5
|
separator?: string;
|
|
6
|
-
};
|
|
7
|
-
export default function append(this: MemFsEditor, to: string, contents: string | Buffer, options?: AppendOptions): void;
|
|
6
|
+
}): void;
|
package/dist/actions/append.js
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
import { EOL } from 'os';
|
|
2
2
|
export default function append(to, contents, options) {
|
|
3
|
-
|
|
3
|
+
const opts = {
|
|
4
|
+
create: false,
|
|
4
5
|
trimEnd: true,
|
|
5
6
|
separator: EOL,
|
|
6
7
|
...options,
|
|
7
8
|
};
|
|
8
|
-
if (!this.exists(to) &&
|
|
9
|
-
|
|
10
|
-
return;
|
|
9
|
+
if (!this.exists(to) && !opts.create) {
|
|
10
|
+
throw new Error(`${to} doesn't exist`);
|
|
11
11
|
}
|
|
12
|
-
let
|
|
13
|
-
if (
|
|
14
|
-
|
|
12
|
+
let currentContent = this.read(to, { defaults: '' });
|
|
13
|
+
if (currentContent && opts.trimEnd) {
|
|
14
|
+
currentContent = currentContent.trimEnd();
|
|
15
15
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
this.write(to, currentContents + options.separator + contents.toString());
|
|
16
|
+
const newContent = currentContent ? currentContent + opts.separator + contents.toString() : contents;
|
|
17
|
+
this.write(to, newContent);
|
|
20
18
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { MemFsEditorFile } from '../index.js';
|
|
2
|
-
export default function commitFileAsync
|
|
2
|
+
export default function commitFileAsync(file: MemFsEditorFile): Promise<void>;
|
|
@@ -23,16 +23,13 @@ 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 */
|
|
26
27
|
// eslint-disable-next-line no-bitwise
|
|
27
28
|
if ((existingMode & 0o777) !== (newMode & 0o777)) {
|
|
28
29
|
await fs.chmod(file.path, newMode);
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
|
-
async function remove(file) {
|
|
33
|
-
const remove = fs.rm || fs.rmdir;
|
|
34
|
-
await remove(file.path, { recursive: true });
|
|
35
|
-
}
|
|
36
33
|
export default async function commitFileAsync(file) {
|
|
37
34
|
if (isFileStateModified(file)) {
|
|
38
35
|
setCommittedFile(file);
|
|
@@ -40,7 +37,7 @@ export default async function commitFileAsync(file) {
|
|
|
40
37
|
}
|
|
41
38
|
else if (isFileStateDeleted(file) && !isFileNew(file)) {
|
|
42
39
|
setCommittedFile(file);
|
|
43
|
-
await
|
|
40
|
+
await fs.rm(file.path, { recursive: true });
|
|
44
41
|
}
|
|
45
42
|
clearFileState(file);
|
|
46
43
|
}
|
|
@@ -1,16 +1,32 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { type Options as GlobbyOptions } from 'globby';
|
|
1
|
+
import { GlobOptions } from 'tinyglobby';
|
|
3
2
|
import type { MemFsEditor } from '../index.js';
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
export type CopyAsyncOptions = CopySingleAsyncOptions & {
|
|
7
|
-
globOptions?: GlobbyOptions;
|
|
8
|
-
processDestinationPath?: (string: any) => string;
|
|
9
|
-
ignoreNoMatch?: boolean;
|
|
10
|
-
};
|
|
11
|
-
export declare function copyAsync(this: MemFsEditor, from: string | string[], to: string, options?: CopyAsyncOptions, context?: Data, tplSettings?: Options): Promise<void>;
|
|
12
|
-
export type CopySingleAsyncOptions = AppendOptions & CopySingleOptions & {
|
|
3
|
+
import type { Options as MultimatchOptions } from 'multimatch';
|
|
4
|
+
type CopySingleAsyncOptions = Parameters<MemFsEditor['append']>[2] & {
|
|
13
5
|
append?: boolean;
|
|
14
|
-
|
|
6
|
+
/**
|
|
7
|
+
* @experimental This API is experimental and may change without a major version bump.
|
|
8
|
+
*
|
|
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]
|
|
14
|
+
*/
|
|
15
|
+
fileTransform?: (destinationPath: string, sourcePath: string, contents: Buffer) => [string, string | Buffer] | Promise<[string, string | Buffer]>;
|
|
16
|
+
};
|
|
17
|
+
type CopyAsyncOptions = CopySingleAsyncOptions & {
|
|
18
|
+
noGlob?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Options for disk globbing.
|
|
21
|
+
* Glob options that should be compatible with minimatch results.
|
|
22
|
+
*/
|
|
23
|
+
globOptions?: Pick<GlobOptions, 'caseSensitiveMatch' | 'cwd' | 'debug' | 'deep' | 'dot' | 'expandDirectories' | 'followSymbolicLinks'>;
|
|
24
|
+
/**
|
|
25
|
+
* Options for store files matching.
|
|
26
|
+
*/
|
|
27
|
+
storeMatchOptions?: MultimatchOptions;
|
|
28
|
+
ignoreNoMatch?: boolean;
|
|
29
|
+
fromBasePath?: string;
|
|
15
30
|
};
|
|
16
|
-
export declare function
|
|
31
|
+
export declare function copyAsync(this: MemFsEditor, from: string | string[], to: string, options?: CopyAsyncOptions): Promise<void>;
|
|
32
|
+
export {};
|
|
@@ -1,95 +1,115 @@
|
|
|
1
1
|
import assert from 'assert';
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import fsPromises from 'fs/promises';
|
|
4
|
-
import path
|
|
5
|
-
import
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import createDebug from 'debug';
|
|
6
|
+
import { glob, isDynamicPattern } from 'tinyglobby';
|
|
6
7
|
import multimatch from 'multimatch';
|
|
7
|
-
import { render, globify, getCommonPath } from '../util.js';
|
|
8
8
|
import normalize from 'normalize-path';
|
|
9
9
|
import File from 'vinyl';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (!context) {
|
|
16
|
-
return filepath;
|
|
17
|
-
}
|
|
18
|
-
return render(filepath, context, tplSettings);
|
|
19
|
-
}
|
|
20
|
-
async function getOneFile(from) {
|
|
21
|
-
let oneFile;
|
|
22
|
-
if (typeof from === 'string') {
|
|
23
|
-
oneFile = from;
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
return undefined;
|
|
27
|
-
}
|
|
28
|
-
const resolved = path.resolve(oneFile);
|
|
10
|
+
import { resolveFromPaths, getCommonPath, globify, resolveGlobOptions } from '../util.js';
|
|
11
|
+
import { writeInternal } from './write.js';
|
|
12
|
+
const debug = createDebug('mem-fs-editor:copy-async');
|
|
13
|
+
async function getOneFile(filepath) {
|
|
14
|
+
const resolved = path.resolve(filepath);
|
|
29
15
|
try {
|
|
30
16
|
if ((await fsPromises.stat(resolved)).isFile()) {
|
|
31
17
|
return resolved;
|
|
32
18
|
}
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
33
20
|
}
|
|
34
|
-
catch (
|
|
21
|
+
catch (error) { }
|
|
35
22
|
return undefined;
|
|
36
23
|
}
|
|
37
|
-
export async function copyAsync(from, to, options
|
|
24
|
+
export async function copyAsync(from, to, options = {}) {
|
|
38
25
|
to = path.resolve(to);
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
26
|
+
const { noGlob } = options;
|
|
27
|
+
const hasGlobOptions = Boolean(options.globOptions);
|
|
28
|
+
const hasMultimatchOptions = Boolean(options.storeMatchOptions);
|
|
29
|
+
assert(!noGlob || !hasGlobOptions, '`noGlob` and `globOptions` are mutually exclusive');
|
|
30
|
+
assert(!noGlob || !hasMultimatchOptions, '`noGlob` and `storeMatchOptions` are mutually exclusive');
|
|
31
|
+
if (typeof from === 'string') {
|
|
32
|
+
const oneFile = await getOneFile(from);
|
|
33
|
+
if (oneFile) {
|
|
34
|
+
return copySingleAsync(this, oneFile, to, options);
|
|
35
|
+
}
|
|
43
36
|
}
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
const
|
|
37
|
+
const { fromBasePath = getCommonPath(from) } = options;
|
|
38
|
+
const resolvedFromPaths = resolveFromPaths({ from, fromBasePath });
|
|
39
|
+
const hasDynamicPattern = resolvedFromPaths.some((f) => isDynamicPattern(normalize(f.from)));
|
|
40
|
+
const { preferFiles } = resolveGlobOptions({
|
|
41
|
+
noGlob,
|
|
42
|
+
hasDynamicPattern,
|
|
43
|
+
hasGlobOptions,
|
|
44
|
+
});
|
|
47
45
|
const storeFiles = [];
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
!diskFiles.includes(file.path)) {
|
|
54
|
-
storeFiles.push(file.path);
|
|
46
|
+
const globResolved = [];
|
|
47
|
+
for (const resolvedFromPath of resolvedFromPaths) {
|
|
48
|
+
const { resolvedFrom } = resolvedFromPath;
|
|
49
|
+
if (this.store.existsInMemory(resolvedFrom)) {
|
|
50
|
+
storeFiles.push(resolvedFrom);
|
|
55
51
|
}
|
|
56
|
-
|
|
52
|
+
else {
|
|
53
|
+
globResolved.push(resolvedFromPath);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
let diskFiles = [];
|
|
57
|
+
if (globResolved.length > 0) {
|
|
58
|
+
const patterns = globResolved.map((file) => globify(file.from)).flat();
|
|
59
|
+
diskFiles = (await glob(patterns, { cwd: fromBasePath, ...options.globOptions, absolute: true, onlyFiles: true })).map((file) => path.resolve(file));
|
|
60
|
+
const normalizedStoreFilePaths = this.store
|
|
61
|
+
.all()
|
|
62
|
+
.map((file) => file.path)
|
|
63
|
+
.filter((filePath) => !diskFiles.includes(filePath))
|
|
64
|
+
.map((filePath) => normalize(filePath))
|
|
65
|
+
// The store may have a glob path and when we try to copy it will fail because not real file
|
|
66
|
+
.filter((filePath) => !isDynamicPattern(filePath));
|
|
67
|
+
multimatch(normalizedStoreFilePaths, patterns, options.storeMatchOptions).forEach((filePath) => {
|
|
68
|
+
storeFiles.push(path.resolve(filePath));
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
// Sanity checks: Makes sure we copy at least one file.
|
|
72
|
+
assert(options.ignoreNoMatch || diskFiles.length > 0 || storeFiles.length > 0, 'Trying to copy from a source that does not exist: ' + String(from));
|
|
73
|
+
// If `from` is an array, or if it contains any dynamic patterns, or if it doesn't exist, `to` must be a directory.
|
|
74
|
+
const treatToAsDir = Array.isArray(from) || !preferFiles || globResolved.length > 0;
|
|
57
75
|
let generateDestination = () => to;
|
|
58
|
-
if (
|
|
76
|
+
if (treatToAsDir) {
|
|
59
77
|
assert(!this.exists(to) || fs.statSync(to).isDirectory(), 'When copying multiple files, provide a directory as destination');
|
|
60
|
-
|
|
61
|
-
const root = getCommonPath(from);
|
|
62
|
-
generateDestination = (filepath) => {
|
|
63
|
-
const toFile = path.relative(root, filepath);
|
|
64
|
-
return processDestinationPath(path.join(to, toFile));
|
|
65
|
-
};
|
|
78
|
+
generateDestination = (filepath) => path.join(to, path.relative(fromBasePath, filepath));
|
|
66
79
|
}
|
|
67
|
-
// Sanity checks: Makes sure we copy at least one file.
|
|
68
|
-
assert(options.ignoreNoMatch || diskFiles.length > 0 || storeFiles.length > 0, 'Trying to copy from a source that does not exist: ' + from);
|
|
69
80
|
await Promise.all([
|
|
70
|
-
...diskFiles.map((file) => this
|
|
71
|
-
...storeFiles.map((file) =>
|
|
81
|
+
...diskFiles.map((file) => copySingleAsync(this, file, generateDestination(file), options)),
|
|
82
|
+
...storeFiles.map((file) => copySingleAsync(this, file, generateDestination(file), options)),
|
|
72
83
|
]);
|
|
73
84
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
85
|
+
const defaultFileTransform = (destPath, _sourcePath, contents) => [
|
|
86
|
+
destPath,
|
|
87
|
+
contents,
|
|
88
|
+
];
|
|
89
|
+
async function copySingleAsync(editor, from, to, options = {}) {
|
|
90
|
+
from = path.resolve(from);
|
|
91
|
+
debug('Copying %s to %s with %o', from, to, options);
|
|
92
|
+
const file = editor.store.get(from);
|
|
93
|
+
assert(file.contents, `Cannot copy empty file ${from}`);
|
|
94
|
+
const { fileTransform = defaultFileTransform } = options;
|
|
95
|
+
const transformPromise = fileTransform(path.resolve(to), from, file.contents);
|
|
96
|
+
let contents;
|
|
97
|
+
[to, contents] = await transformPromise;
|
|
98
|
+
if (options.append && editor.store.existsInMemory(to)) {
|
|
99
|
+
editor.append(to, contents, { create: true, ...options });
|
|
77
100
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
101
|
+
else if (File.isVinyl(file)) {
|
|
102
|
+
writeInternal(editor.store, Object.assign(file.clone({ contents: false, deep: false }), {
|
|
103
|
+
contents: Buffer.from(contents),
|
|
104
|
+
path: to,
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
writeInternal(editor.store, new File({
|
|
109
|
+
contents: Buffer.from(contents),
|
|
110
|
+
stat: file.stat,
|
|
111
|
+
path: to,
|
|
112
|
+
history: [file.path],
|
|
113
|
+
}));
|
|
88
114
|
}
|
|
89
|
-
this._write(new File({
|
|
90
|
-
contents,
|
|
91
|
-
stat: await fsPromises.stat(from),
|
|
92
|
-
path: to,
|
|
93
|
-
history: [from],
|
|
94
|
-
}));
|
|
95
115
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { MemFsEditor } from '../index.js';
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
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'> & {
|
|
4
|
+
transformOptions?: ejs.Options;
|
|
5
|
+
}): Promise<void>;
|
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
export default async function (from, to,
|
|
4
|
-
context = context || {};
|
|
5
|
-
tplSettings = tplSettings || {};
|
|
1
|
+
import { isBinary } from '../util.js';
|
|
2
|
+
import ejs from 'ejs';
|
|
3
|
+
export default async function (from, to, data = {}, options) {
|
|
6
4
|
await this.copyAsync(from, to, {
|
|
7
|
-
processDestinationPath: (path) => path.replace(/.ejs$/, ''),
|
|
8
5
|
...options,
|
|
9
|
-
async
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
async fileTransform(destinationPath, sourcePath, contents) {
|
|
7
|
+
const { transformOptions } = options ?? {};
|
|
8
|
+
const processedPath = await ejs.render(destinationPath, data, {
|
|
9
|
+
...transformOptions,
|
|
10
|
+
cache: false, // Cache uses filename as key, which is not provided in this case.
|
|
11
|
+
});
|
|
12
|
+
const processedContent = isBinary(sourcePath, contents)
|
|
13
|
+
? contents
|
|
14
|
+
: await ejs.render(contents.toString(), data, {
|
|
15
|
+
// Setting filename by default allow including partials.
|
|
16
|
+
filename: sourcePath,
|
|
17
|
+
// Async option cannot be set to true because `include()` then also become async which change the behaviors of templates.
|
|
18
|
+
// Users must pass async value in transformOptions if they want to use async features of ejs.
|
|
19
|
+
...transformOptions,
|
|
20
|
+
});
|
|
21
|
+
return [processedPath.replace(/.ejs$/, ''), processedContent];
|
|
14
22
|
},
|
|
15
|
-
|
|
16
|
-
contents,
|
|
17
|
-
filename,
|
|
18
|
-
destination,
|
|
19
|
-
context,
|
|
20
|
-
tplSettings,
|
|
21
|
-
}),
|
|
22
|
-
}, context, tplSettings);
|
|
23
|
+
});
|
|
23
24
|
}
|
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import ejs from 'ejs';
|
|
2
2
|
import type { MemFsEditor } from '../index.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
filename: string;
|
|
7
|
-
destination?: string;
|
|
8
|
-
context?: Data;
|
|
9
|
-
tplSettings?: Options;
|
|
10
|
-
}): string | Buffer;
|
|
11
|
-
export declare function copyTpl(this: MemFsEditor, from: string | string[], to: string, context?: Data, tplSettings?: Options, options?: CopySingleOptions): void;
|
|
3
|
+
export declare function copyTpl(this: MemFsEditor, from: string | string[], to: string, data?: ejs.Data, options?: Omit<NonNullable<Parameters<MemFsEditor['copy']>[2]>, 'fileTransform'> & {
|
|
4
|
+
transformOptions?: ejs.Options;
|
|
5
|
+
}): void;
|
package/dist/actions/copy-tpl.js
CHANGED
|
@@ -1,21 +1,27 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
return contents;
|
|
5
|
-
}
|
|
6
|
-
return render(contents.toString(), context, {
|
|
7
|
-
// Setting filename by default allow including partials.
|
|
8
|
-
filename,
|
|
9
|
-
cache: true,
|
|
10
|
-
...tplSettings,
|
|
11
|
-
});
|
|
12
|
-
}
|
|
13
|
-
export function copyTpl(from, to, context, tplSettings, options) {
|
|
14
|
-
context = context || {};
|
|
15
|
-
tplSettings = tplSettings || {};
|
|
1
|
+
import ejs from 'ejs';
|
|
2
|
+
import { isBinary } from '../util.js';
|
|
3
|
+
export function copyTpl(from, to, data = {}, options) {
|
|
16
4
|
this.copy(from, to, {
|
|
17
|
-
processDestinationPath: (path) => path.replace(/.ejs$/, ''),
|
|
18
5
|
...options,
|
|
19
|
-
|
|
20
|
-
|
|
6
|
+
fileTransform(destinationPath, sourcePath, contents) {
|
|
7
|
+
const { transformOptions } = options ?? {};
|
|
8
|
+
if (transformOptions?.async) {
|
|
9
|
+
throw new Error('Async EJS rendering is not supported');
|
|
10
|
+
}
|
|
11
|
+
const processedPath = ejs.render(destinationPath, data, {
|
|
12
|
+
...transformOptions,
|
|
13
|
+
cache: false, // Cache uses filename as key, which is not provided in this case.
|
|
14
|
+
async: false,
|
|
15
|
+
});
|
|
16
|
+
const processedContent = isBinary(sourcePath, contents)
|
|
17
|
+
? contents
|
|
18
|
+
: ejs.render(contents.toString(), data, {
|
|
19
|
+
// Setting filename by default allow including partials.
|
|
20
|
+
filename: sourcePath,
|
|
21
|
+
...transformOptions,
|
|
22
|
+
async: false,
|
|
23
|
+
});
|
|
24
|
+
return [processedPath.replace(/.ejs$/, ''), processedContent];
|
|
25
|
+
},
|
|
26
|
+
});
|
|
21
27
|
}
|
package/dist/actions/copy.d.ts
CHANGED
|
@@ -1,16 +1,33 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import {
|
|
1
|
+
import { type GlobOptions } from 'tinyglobby';
|
|
2
|
+
import type { Options as MultimatchOptions } from 'multimatch';
|
|
3
3
|
import type { MemFsEditor } from '../index.js';
|
|
4
|
-
|
|
4
|
+
type CopySingleOptions = {
|
|
5
|
+
append?: boolean;
|
|
6
|
+
/**
|
|
7
|
+
* @experimental This API is experimental and may change without a major version bump.
|
|
8
|
+
*
|
|
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]
|
|
14
|
+
*/
|
|
15
|
+
fileTransform?: (destinationPath: string, sourcePath: string, contents: Buffer) => [string, string | Buffer];
|
|
16
|
+
};
|
|
17
|
+
type CopyOptions = CopySingleOptions & {
|
|
5
18
|
noGlob?: boolean;
|
|
6
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Options for disk globbing.
|
|
21
|
+
* Glob options that should be compatible with minimatch results.
|
|
22
|
+
*/
|
|
23
|
+
globOptions?: Pick<GlobOptions, 'caseSensitiveMatch' | 'cwd' | 'debug' | 'deep' | 'dot' | 'expandDirectories' | 'followSymbolicLinks'>;
|
|
24
|
+
/**
|
|
25
|
+
* Options for store files matching.
|
|
26
|
+
*/
|
|
27
|
+
storeMatchOptions?: MultimatchOptions;
|
|
7
28
|
ignoreNoMatch?: boolean;
|
|
8
29
|
fromBasePath?: string;
|
|
9
|
-
processDestinationPath?: (string: any) => string;
|
|
10
|
-
};
|
|
11
|
-
export declare function copy(this: MemFsEditor, from: string | string[], to: string, options?: CopyOptions, context?: Data, tplSettings?: Options): void;
|
|
12
|
-
export type CopySingleOptions = {
|
|
13
|
-
append?: boolean;
|
|
14
|
-
process?: (contents: string | Buffer, filepath: string, destination: string) => string | Buffer;
|
|
15
30
|
};
|
|
16
|
-
export declare function
|
|
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;
|
|
33
|
+
export {};
|