Skip to content

Commit

Permalink
feat(public/fs): allows destination to be a from/to map for copy and …
Browse files Browse the repository at this point in the history
…move
  • Loading branch information
rafamel committed May 20, 2019
1 parent ee7a7b6 commit 8369346
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 49 deletions.
65 changes: 42 additions & 23 deletions src/public/fs/copy/copy.ts
@@ -1,39 +1,61 @@
/* eslint-disable eqeqeq */
import path from 'path';
import fs from 'fs-extra';
import { absolute, exists } from '~/utils/file';
import { IFsUpdateOptions, TCopyFilterFn } from '../types';
import { IFsUpdateOptions, TCopyFilterFn, TDestination } from '../types';
import confirm from '~/utils/confirm';
import logger from '~/utils/logger';
import { open } from '~/utils/errors';

// TODO allow to take an option to duplicate folder structure on dest from a base + don't allow it when src is upwards instead of nested in that folder

export default async function copy(
src: string | string[],
dest: string,
dest: TDestination,
options: IFsUpdateOptions = {},
filter: TCopyFilterFn = () => true
): Promise<void> {
const cwd = process.cwd();
options = Object.assign({ overwrite: true }, options);

if (Array.isArray(src)) {
// Check dest is a folder
if (await exists(dest)) {
const stat = await fs.stat(dest);
if (!stat.isDirectory()) {
throw Error('Destination must be a folder for an array of sources');
}
}
for (let source of src) {
await each(
source,
path.join(dest, path.parse(source).base),
options,
filter
let { from, to } =
typeof dest === 'string' ? { from: undefined, to: dest } : dest;

if (!Array.isArray(src) && typeof dest === 'string') {
return each(
absolute({ path: src, cwd }),
absolute({ path: to, cwd }),
options,
filter
);
}

to = absolute({ path: to, cwd });
if (from != undefined) from = absolute({ path: from, cwd });
if (!Array.isArray(src)) src = [src].filter(Boolean);
// Check dest is a folder
if (await exists(to)) {
const stat = await fs.stat(to);
if (!stat.isDirectory()) {
throw Error(
'Destination must be a folder if an array of sources or a from/to destination map are passed'
);
}
} else {
await each(src, dest, options, filter);
}
const items = src.map((source) => {
source = absolute({ path: source, cwd });
let destination = path.join(to, path.parse(source).base);
if (from != undefined) {
const relative = path.relative(from, source);
if (relative.slice(0, 2) === '..') {
throw Error(`All source files must be within 'from'`);
}
destination = path.join(to, relative);
}

return { source, destination };
});

for (let { source, destination } of items) {
await each(source, destination, options, filter);
}
}

Expand All @@ -44,9 +66,6 @@ export async function each(
filter: TCopyFilterFn
): Promise<void> {
const cwd = process.cwd();
src = absolute({ path: src, cwd });
dest = absolute({ path: dest, cwd });

const relatives = {
src: './' + path.relative(cwd, src),
dest: './' + path.relative(cwd, dest)
Expand Down
25 changes: 17 additions & 8 deletions src/public/fs/copy/index.ts
@@ -1,23 +1,28 @@
import { TSource, IFsUpdateOptions, TCopyFilterFn } from '../types';
import {
TSource,
IFsUpdateOptions,
TCopyFilterFn,
TDestination
} from '../types';
import expose, { TExposedOverload } from '~/utils/expose';
import trunk from './copy';

export default expose(copy) as TExposedOverload<
typeof copy,
| [TSource, string]
| [TSource, string, IFsUpdateOptions]
| [TSource, string, TCopyFilterFn]
| [TSource, string, IFsUpdateOptions | undefined, TCopyFilterFn]
| [TSource, TDestination]
| [TSource, TDestination, IFsUpdateOptions]
| [TSource, TDestination, TCopyFilterFn]
| [TSource, TDestination, IFsUpdateOptions | undefined, TCopyFilterFn]
>;

function copy(
src: TSource,
dest: string,
dest: TDestination,
filter?: TCopyFilterFn
): () => Promise<void>;
function copy(
src: TSource,
dest: string,
dest: TDestination,
options?: IFsUpdateOptions,
filter?: TCopyFilterFn
): () => Promise<void>;
Expand All @@ -26,7 +31,11 @@ function copy(
* It is an *exposed* function: call `copy.fn()`, which takes the same arguments, in order to execute on call.
* @returns An asynchronous function -hence, calling `copy` won't have any effect until the returned function is called.
*/
function copy(src: TSource, dest: string, ...args: any[]): () => Promise<void> {
function copy(
src: TSource,
dest: TDestination,
...args: any[]
): () => Promise<void> {
return async () => {
const hasOptions = typeof args[0] !== 'function';
return trunk(
Expand Down
4 changes: 2 additions & 2 deletions src/public/fs/move/index.ts
@@ -1,4 +1,4 @@
import { TSource, IFsUpdateOptions } from '../types';
import { TSource, IFsUpdateOptions, TDestination } from '../types';
import expose from '~/utils/expose';
import trunk from './move';

Expand All @@ -11,7 +11,7 @@ export default expose(move);
*/
function move(
src: TSource,
dest: string,
dest: TDestination,
options?: IFsUpdateOptions
): () => Promise<void> {
return async () => {
Expand Down
57 changes: 41 additions & 16 deletions src/public/fs/move/move.ts
@@ -1,30 +1,58 @@
/* eslint-disable eqeqeq */
import path from 'path';
import fs from 'fs-extra';
import { absolute, exists } from '~/utils/file';
import { IFsUpdateOptions } from '../types';
import { IFsUpdateOptions, TDestination } from '../types';
import confirm from '~/utils/confirm';
import logger from '~/utils/logger';

export default async function move(
src: string | string[],
dest: string,
dest: TDestination,
options: IFsUpdateOptions = {}
): Promise<void> {
const cwd = process.cwd();
options = Object.assign({ overwrite: true }, options);

if (Array.isArray(src)) {
// Check dest is a folder
if (await exists(dest)) {
const stat = await fs.stat(dest);
if (!stat.isDirectory()) {
throw Error('Destination must be a folder for an array of sources');
}
let { from, to } =
typeof dest === 'string' ? { from: undefined, to: dest } : dest;

if (!Array.isArray(src) && typeof dest === 'string') {
return each(
absolute({ path: src, cwd }),
absolute({ path: to, cwd }),
options
);
}

to = absolute({ path: to, cwd });
if (from != undefined) from = absolute({ path: from, cwd });
if (!Array.isArray(src)) src = [src].filter(Boolean);
// Check dest is a folder
if (await exists(to)) {
const stat = await fs.stat(to);
if (!stat.isDirectory()) {
throw Error(
'Destination must be a folder if an array of sources or a from/to destination map are passed'
);
}
for (let source of src) {
await each(source, path.join(dest, path.parse(source).base), options);
}
const items = src.map((source) => {
source = absolute({ path: source, cwd });
let destination = path.join(to, path.parse(source).base);
if (from != undefined) {
const relative = path.relative(from, source);
if (relative.slice(0, 2) === '..') {
throw Error(`All source files must be within 'from'`);
}
destination = path.join(to, relative);
}
} else {
await each(src, dest, options);

return { source, destination };
});

for (let { source, destination } of items) {
await each(source, destination, options);
}
}

Expand All @@ -34,9 +62,6 @@ export async function each(
options: IFsUpdateOptions
): Promise<void> {
const cwd = process.cwd();
src = absolute({ path: src, cwd });
dest = absolute({ path: dest, cwd });

const relatives = {
src: './' + path.relative(cwd, src),
dest: './' + path.relative(cwd, dest)
Expand Down
2 changes: 2 additions & 0 deletions src/public/fs/types.ts
Expand Up @@ -4,6 +4,8 @@ export type TSource =
| Promise<string[]>
| (() => string[] | Promise<string[]>);

export type TDestination = string | { from?: string; to: string };

export type TCopyFilterFn =
| ((src: string, dest: string) => boolean)
| ((src: string, dest: string) => Promise<boolean>);
Expand Down

0 comments on commit 8369346

Please sign in to comment.