diff --git a/src/helpers/paths.ts b/src/helpers/paths.ts index ab204d5..ba20d4f 100644 --- a/src/helpers/paths.ts +++ b/src/helpers/paths.ts @@ -67,16 +67,27 @@ export async function getPathPairs( paths: string | string[], destination: string, context: Context, - options: { single: boolean; glob: boolean; strict: boolean } + options: { + single: boolean; + glob: boolean; + strict: boolean; + from: string | null; + } ): Promise> { + const from = options.from; const dest = getAbsolutePath(destination, context); - const sources = await getPaths(paths, context, options); + const arr = Array.isArray(paths) ? paths : [paths]; + const sources = await getPaths( + arr.map((x) => path.resolve(from || './', x)), + context, + options + ); if (options.single) { if (sources.length > 1) { throw new Error(`Multiple sources provided for single mode`); } - return [[sources[0], dest]]; + if (!from) return [[sources[0], dest]]; } const destExists = await fs.pathExists(dest); @@ -87,9 +98,25 @@ export async function getPathPairs( throw new Error(`Destination path is not a directory: ${dest}`); } + if (!from) { + return sources.map((source) => [ + source, + path.join(dest, path.basename(source)) + ]); + } + + const fromExists = await fs.pathExists(from); + const isFromDir = fromExists + ? await fs.stat(from).then((x) => x.isDirectory()) + : false; + if (!isFromDir) { + throw new Error(`Options.from path is not a directory: ${from}`); + } + + const absoluteFrom = getAbsolutePath(from, context); return sources.map((source) => [ source, - path.join(dest, path.basename(source)) + path.join(dest, source.substring(absoluteFrom.length)) ]); } diff --git a/src/tasks/filesystem/copy.ts b/src/tasks/filesystem/copy.ts index 409683c..e11341e 100644 --- a/src/tasks/filesystem/copy.ts +++ b/src/tasks/filesystem/copy.ts @@ -16,6 +16,8 @@ export interface CopyOptions { strict?: boolean; /** Whether to error, ignore, or overwrite existing files */ exists?: 'error' | 'ignore' | 'overwrite'; + /** Absolute path, or relative to the cwd, to resolve paths from; if not null, the source folder structure will be replicated on destination */ + from?: string | null; } /** @@ -32,13 +34,20 @@ export function copy( log('debug', 'Copy', paths, 'to', destination), async (ctx: Context): Promise => { const opts = shallow( - { glob: false, single: false, strict: false, exists: 'error' }, + { + glob: false, + single: false, + strict: false, + exists: 'error', + from: null + }, options || undefined ); const pairs = await getPathPairs(paths, destination, ctx, { glob: opts.glob, single: opts.single, - strict: opts.strict + strict: opts.strict, + from: opts.from }); for (const pair of pairs) { diff --git a/src/tasks/filesystem/move.ts b/src/tasks/filesystem/move.ts index 9c13ab8..e5895e0 100644 --- a/src/tasks/filesystem/move.ts +++ b/src/tasks/filesystem/move.ts @@ -16,6 +16,8 @@ export interface MoveOptions { strict?: boolean; /** Whether to error, ignore, or overwrite existing files */ exists?: 'error' | 'ignore' | 'overwrite'; + /** Absolute path, or relative to the cwd, to resolve paths from; if not null, the source folder structure will be replicated on destination */ + from?: string | null; } /** @@ -32,17 +34,25 @@ export function move( log('debug', 'Move', paths, 'to', destination), async (ctx: Context): Promise => { const opts = shallow( - { glob: false, single: false, strict: false, exists: 'error' }, + { + glob: false, + single: false, + strict: false, + exists: 'error', + from: null + }, options || undefined ); const pairs = await getPathPairs(paths, destination, ctx, { glob: opts.glob, single: opts.single, - strict: opts.strict + strict: opts.strict, + from: opts.from }); for (const pair of pairs) { if (isCancelled(ctx)) return; + await usePair( pair, ctx,