-
Notifications
You must be signed in to change notification settings - Fork 3
/
index.js
121 lines (101 loc) · 3.68 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/* eslint-env node */
'use strict';
const path = require('path');
const fs = require('fs.extra');
const isWin = process.platform === 'win32';
class DepLinker {
constructor() {
this.projectRoot = './';
}
setRoot(root) {
if (typeof root !== 'string') {
throw new Error(`Invalid path to root. Expected String, and received ${typeof root}`);
}
this.projectRoot = root;
}
getPackageJson(root = this.projectRoot) {
const packagePath = path.join(root, 'package.json');
let packageText;
try {
packageText = fs.readFileSync(packagePath).toString();
} catch (e) {
throw new Error(`No package json found using path ${packagePath}`);
}
const packageJson = JSON.parse(packageText);
return packageJson;
}
/**
* Returns an object with dependency names and their filePath
* @method listDependencies
* @return {Array<String>} Array of dependency names
*/
listDependencies() {
const packageJson = this.getPackageJson();
const dependencies = packageJson.dependencies || {};
return Object.keys(dependencies);
}
/**
* Returns a promise to be resolved when all files have been linked.
* @method linkDependenciesTo
* @param {String} dest - Folder where dependencies will go.
* @param {Object} (Optional) Configuration options on how links will be created.
* The only options available are { type: 'junction' } or { type: 'dir'} when
* process.platform ==== 'win32'. Junctions have fewer restrictions and are more
* likely to succeed when running as a User. The default for type when running
* under Windows is Junction.
* @return {Promise} - To be resolved when all files have been copied.
*/
linkDependenciesTo(dest, opts) {
if (typeof dest !== 'string') {
throw new Error(`Not a valid destination folder: ${dest}`);
}
const dependencies = this.listDependencies();
opts = opts || {};
// Only valid on win32
const linkTypeJunction = 'junction';
const linkTypeDir = 'dir';
const linkType = (isWin && opts.type !== 'dir') ? linkTypeJunction : linkTypeDir;
// Create all folders up to the destiny folder
fs.mkdirpSync(dest);
const linkingPromises = [];
for (const depName of dependencies) {
// relative paths
const rLinkSource = path.join('./node_modules', depName);
const rLinkDestiny = path.join(dest, depName);
const linkSource = path.resolve(rLinkSource);
const linkDestiny = path.resolve(rLinkDestiny);
// Link content
const linking = new Promise((resolve) => { // Check if file exists
fs.access(linkDestiny, fs.F_OK, resolve);
})
.then((err) => new Promise((resolve) => { // Delete it if needed
if (err) {
// File doesn't exist, nothing to do.
resolve();
} else {
// File exists, let's remove it.
fs.unlink(linkDestiny, resolve);
}
}))
.then((err) => { // Report on errors
if (err) { console.log(err); }
})
.then(() => new Promise((resolve) => { // Create simbolic link
// In the future it is possible to utilize the 'runas' module
// to ShellExec this command with the Verb: RunAs under Windows. The
// ideal scenario would be for the task to prompt the user for credentials.
fs.symlink(linkSource, linkDestiny, linkType, resolve);
}))
.then((err) => { // Report on errors
if (err) { console.log(err); }
});
linkingPromises.push(linking);
}
return Promise.all(linkingPromises);
}
// Backward compatibility matters
copyDependenciesTo(...args) {
return this.linkDependenciesTo(...args);
}
}
module.exports = new DepLinker();