Skip to content

Commit

Permalink
Support flat copy (#111)
Browse files Browse the repository at this point in the history
  • Loading branch information
sibiraj-s committed Dec 4, 2021
1 parent 2c8e590 commit 83bf2dd
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 14 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [12.x, 14.x, 16.x]
node-version: [12.x, 14.x, 16.x, 17.x]

name: Test - ${{ matrix.os }} - Node v${{ matrix.node-version }}

Expand Down
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,18 @@ Copy individual files or entire directories from a source folder to a destinatio
```js
[
{ source: '/path/from', destination: '/path/to' },
{ source: '/path/**/*.js', destination: '/path' },
{
source: '/path/**/*.js',
destination: '/path',
options: {
flat: false,
preserveTimestamps: true,
overwite: true,
},
globOptions: {
dot: true,
},
},
{ source: '/path/fromfile.txt', destination: '/path/tofile.txt' },
{ source: '/path/**/*.{html,js}', destination: '/path/to' },
{ source: '/path/{file1,file2}.js', destination: '/path/to' },
Expand All @@ -129,6 +140,8 @@ Copy individual files or entire directories from a source folder to a destinatio

- source[`string`] - a file or a directory or a glob
- destination[`string`] - a file or a directory.
- options ['object`] - copy options
- globOptions [`object`] - options to forward to glob options

**Caveats**

Expand Down
14 changes: 12 additions & 2 deletions src/actions/copy.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,26 @@ const fsExtraDefaultOptions = {
};

const copy = async (task, { logger }) => {
const { source, absoluteSource, destination, absoluteDestination, context, toType } = task;
const {
source,
absoluteSource,
destination,
absoluteDestination,
context,
toType,
options = {},
globOptions = {},
} = task;

logger.log(`copying from ${source} to ${destination}`);

if (isGlob(source)) {
const cpOptions = {
...options,
cwd: context,
};

await globCopy(source, absoluteDestination, cpOptions);
await globCopy(source, absoluteDestination, cpOptions, globOptions);
} else {
const isSourceFile = fs.lstatSync(absoluteSource).isFile();

Expand Down
31 changes: 29 additions & 2 deletions src/options-schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,33 @@ export default {
type: 'string',
minLength: 1,
},
options: {
additionalProperties: false,
type: 'object',
description: 'Options to forward to archiver',
properties: {
flat: {
description: 'Flatten the directory structure. All copied files will be put in the same directory',
type: 'boolean',
default: false,
},
overwrite: {
description: 'overwrite existing file or directory',
type: 'boolean',
default: true,
},
preserveTimestamps: {
description: 'Set last modification and access times to the ones of the original source files',
type: 'boolean',
default: false,
},
},
},
globOptions: {
additionalProperties: true,
type: 'object',
description: 'Options to forward to fast-glob',
},
},
},
],
Expand Down Expand Up @@ -187,7 +214,7 @@ export default {
runOnceInWatchMode: {
type: 'boolean',
default: false,
description: 'Run tasks only at first compilation in watch mode'
}
description: 'Run tasks only at first compilation in watch mode',
},
},
};
16 changes: 10 additions & 6 deletions src/utils/glob-copy.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,35 @@ import fse from 'fs-extra';
import fg from 'fast-glob';

const defaultOptions = {
flat: false,
cwd: process.cwd(),
};

const destPath = (pattern, file, cwd) => {
const destPath = (pattern, file, options = defaultOptions) => {
if (options.flat) {
return path.posix.basename(file);
}

const pathArr = pattern.split('/');
const globIndex = pathArr.findIndex((item) => (item ? fg.isDynamicPattern(item) : false));
const normalized = pathArr.slice(0, globIndex).join('/');

const absolutePath = path.isAbsolute(normalized) ? normalized : path.posix.join(cwd, normalized);
const absolutePath = path.isAbsolute(normalized) ? normalized : path.posix.join(options.cwd, normalized);

return path.relative(absolutePath, file);
};

const globCopy = async (pattern, destination, options = defaultOptions) => {
const globCopy = async (pattern, destination, options = defaultOptions, globOptions = {}) => {
await fse.ensureDir(destination);

const matches = await fg(pattern, { dot: true, absolute: true, cwd: options.cwd });
const matches = await fg(pattern, { dot: true, ...globOptions, absolute: true, cwd: options.cwd });

const entries = matches.map((file) => {
const destDir = path.isAbsolute(destination) ? destination : path.posix.join(options.cwd, destination);
const destFileName = destPath(pattern, file, options.cwd);
const destFileName = destPath(pattern, file, options);

return {
from: file,

destDir,
destFileName,
to: path.posix.join(destDir, destFileName),
Expand Down
40 changes: 38 additions & 2 deletions tests/copy.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { existsSync } from 'node:fs';
import { basename, join } from 'node:path';
import { basename, join, sep } from 'node:path';

import test from 'ava';
import del from 'del';
Expand Down Expand Up @@ -94,7 +94,43 @@ test('should deep copy files to directory given a glob source', async (t) => {

t.true(existsSync(join(tmpdir, dirName)));
t.true(existsSync(join(tmpdir, dirName, basename(file1))));
t.true(existsSync(join(nestedDir, basename(file2))));
t.true(existsSync(join(tmpdir, dirName, nestedDir.split(sep).pop(), basename(file2))));
});

test('should flat copy the files to directory given a glob source', async (t) => {
const { tmpdir } = t.context;

const file1 = await tempy.file(tmpdir);
const nestedDir = await tempy.dir({ root: tmpdir });
const file2 = await tempy.file(nestedDir);

const dirName = tempy.getDirName();

const config = {
context: tmpdir,
events: {
onEnd: {
copy: [
{
source: '**/*',
destination: dirName,
options: {
flat: true,
},
globOptions: {},
},
],
},
},
};

const compiler = getCompiler();
new FileManagerPlugin(config).apply(compiler);
await compile(compiler);

t.true(existsSync(join(tmpdir, dirName)));
t.true(existsSync(join(tmpdir, dirName, basename(file1))));
t.true(existsSync(join(tmpdir, dirName, basename(file2))));
});

test(`should create destination directory if it doesn't exist and copy files`, async (t) => {
Expand Down
17 changes: 17 additions & 0 deletions types.d.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,35 @@
import type { ArchiverOptions } from 'archiver';
import type { Options as DelOptions } from 'del';
import type { Compiler, WebpackPluginInstance } from 'webpack';
import type { CopyOptions } from 'fs-extra';
import type { Options as FgOptions } from 'fast-glob';

type FsCopyOptions = Pick<CopyOptions, 'overwrite' | 'preserveTimestamps'>;

interface CopyActionOptions extends FsCopyOptions {
/**
* Flatten directory structure. All copied files will be put in the same directory.
* disabled by default
*/
flat: boolean;
}

/** Copy individual files or entire directories from a source folder to a destination folder */
type Copy = {
/** Copy source. A file or directory or a glob */
source: string;
/** Copy destination */
destination: string;
/** Copy Options */
options: CopyActionOptions;
/** Glob options */
globOptions: Omit<FgOptions, 'absolute' | 'cwd'>;
}[];

/** Delete individual files or entire directories */
type Delete = (
| {
/** A folder or file or a glob to delete */
source: string;
/** Options to forward to del */
options: DelOptions;
Expand Down

0 comments on commit 83bf2dd

Please sign in to comment.