src/services/common/copier.js
const fs = require('fs-extra');
const path = require('path');
const { provider } = require('jimple');
/**
* A service to copy items.
*/
class Copier {
/**
* Copy a list of items between an `origin` directory and a `target` directory.
* @param {string} origin The path to the origin directory.
* @param {string} target The path to the target directory.
* @param {Array} items The list of items to copy. Each item can be a `string` with the path to
* the item, or an object with origin path of the file as key and the
* target path as value.
* @return {Promise<Array,Error>} If everything goes well, the promise will resolve on a list
* with the information of every item it copied.
*/
static copy(origin, target, items) {
const paths = [];
const list = items.map((item) => {
const result = {
from: '',
to: '',
};
if (typeof item === 'string') {
result.from = path.join(origin, item);
result.to = path.join(target, item);
result.isModule = item.startsWith('node_modules');
} else {
const [name] = Object.keys(item);
result.from = path.join(origin, name);
result.to = path.join(target, item[name]);
result.isModule = name.startsWith('node_modules');
}
paths.push(result.from);
return result;
});
return this.findMissingItems(paths)
.then(() => Promise.all(list.map((item) => (item.isModule ?
this.copyModule(item.from, item.to) :
this.copyFile(item.from, item.to)
))));
}
/**
* Copy a single file from one location to another.
* @param {string} from The current location of the file.
* @param {string} to The location of the copy.
* @return {Promise<Object,Object>} The promise will resolve on an object with the information of
* the process: `from`, `to` and `success`.
*/
static copyFile(from, to) {
return fs.copy(from, to)
.then(() => ({
from,
to,
success: true,
}))
.catch((error) => ({
from,
to,
error,
success: false,
}));
}
/**
* Copy a Node module. The reason this is different from `copyFile` is because instead of copying
* the entire module, we first read all the files on its directory, remove its modules and the
* lock files and then copy all the rest.
* @param {string} from The module path.
* @param {string} to The path to where it will be copied.
* @return {Promise<Object,Object>} The promise will resolve on an object with the information of
* the process: `from`, `to` and `success`.
*/
static copyModule(from, to) {
const ignore = ['yarn.lock', 'package-lock.json', 'node_modules'];
return fs.ensureDir(to)
.then(() => fs.readdir(from))
.then((files) => Promise.all(
files
.filter((file) => !ignore.includes(file))
.map((file) => fs.copy(path.join(from, file), path.join(to, file)))
))
.then(() => ({
from,
to,
success: true,
}))
.catch((error) => ({
from,
to,
error,
success: false,
}));
}
/**
* Given a list of items, find if any of them doesn't exist.
* @param {Array} items A list of paths.
* @return {Promise<Array,Error>} If everything goes well, the promise will resolve on a list of
* objects with the path for the `item` and a flag to indicate if
* they `exists`.
*/
static findMissingItems(items) {
return Promise.all(items.map((item) => this.pathExists(item)))
.then((results) => {
let result = {};
const missing = results.find((item) => !item.exists);
if (missing) {
result = Promise.reject(
new Error(`Error: ${missing.item} can't be copied because it doesn't exist`)
);
}
return result;
});
}
/**
* Check if an item exists.
* @param {string} item The path for the item.
* @return {Promise<Object,Error>} If everything goes well, the promise will resolve on an object
* with the keys `item`, for the item path, and `exists` to
* indicate whether the item exists or not.
*/
static pathExists(item) {
return fs.pathExists(item)
.then((exists) => ({
item,
exists,
}));
}
}
/**
* The service provider that once registered on the app container will set `Copier.copy` as the
* `copier` service.
* @example
* // Register it on the container
* container.register(copier);
* // Getting access to the service instance
* const copier = container.get('copier');
* @type {Provider}
*/
const copier = provider((app) => {
app.set('copier', () => Copier.copy.bind(Copier));
});
module.exports = {
Copier,
copier,
};