UNPKG

7.48 kBJavaScriptView Raw
1/*
2 * Copyright 2014-2016 Guy Bedford (http://guybedford.com)
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16var readJSON = require('./common').readJSON;
17var PackageName = require('./package-name');
18var asp = require('bluebird').Promise.promisify;
19var path = require('path');
20var ui = require('./ui');
21var rimraf = require('rimraf');
22var mkdirp = require('mkdirp');
23var fs = require('graceful-fs');
24var install = require('./install');
25var config = require('./config');
26var Promise = require('bluebird');
27var registry = require('./registry');
28
29exports.link = function(dir, name, options) {
30 var alias;
31 if (dir.indexOf('=') != -1) {
32 alias = dir.split('=')[0];
33 dir = dir.split('=').splice(1).join('=');
34 }
35
36 var pkg = new PackageName(name || '');
37 var pjson;
38
39 dir = path.resolve(dir);
40
41 // first check the target dir exists
42 return Promise.resolve()
43 .then(function() {
44 return new Promise(function(resolve) {
45 fs.exists(dir, resolve);
46 });
47 })
48 .then(function(exists) {
49 if (!exists)
50 throw 'Link target directory %' + dir + '% doesn\'t exist.';
51
52 return config.load();
53 })
54 .then(function() {
55 return readJSON(path.resolve(dir, 'package.json'))
56 .then(function(_pjson) {
57 pjson = _pjson;
58
59 // derive the package.json using the jspm property rules
60 if (pjson.jspm) {
61 if ('dependencies' in pjson.jspm || 'devDependencies' in pjson.jspm || 'peerDependencies' in pjson.jspm) {
62 delete pjson.dependencies;
63 delete pjson.devDependencies;
64 delete pjson.peerDependencies;
65 }
66 for (var p in pjson.jspm)
67 pjson[p] = pjson.jspm[p];
68 }
69 });
70 })
71
72 // work out the link package target
73 .then(function() {
74 if (!pkg.registry)
75 pkg.setRegistry(pjson.registry != 'jspm' && pjson.registry || 'local');
76
77 // the name is taken as the pjson name property or the directory name
78 if (!pkg.package)
79 pkg.setPackage(pjson.name || dir.split(path.sep).pop());
80
81 // ensure the name abides by the registry package name conventions but allow any by default
82 var pkgNameFormats = registry.load(pkg.registry).constructor.packageNameFormats || ['*'];
83 if (!pkgNameFormats.some(function(format) {
84 var formatRegEx = new RegExp('^' + format.replace(/\*/g, '[^\/]+') + '$');
85 return pkg.package.match(formatRegEx);
86 }))
87 throw 'Error linking `' + pkg.name + '`. The %' + pkg.registry + '% registry doesn\'t support package names of this form. Make sure to enter a valid package name as the second argument to %jspm link% or set the package.json %name% field of the package being linked.';
88
89 if (pkg.version)
90 return;
91
92 if (pjson.version) {
93 pkg.setVersion(pjson.version);
94 return;
95 }
96
97 // when there is no version given, we infer the version from any git HEAD of the linked project
98 return asp(fs.readFile)(path.resolve(dir, '.git', 'HEAD'))
99 .then(function(headSource) {
100 headSource = headSource.toString();
101 if (headSource.substr(0, 16) == 'ref: refs/heads/')
102 pkg.setVersion(headSource.substr(16).replace(/\s*$/, ''));
103 else
104 pkg.setVersion('master');
105 }, function() {
106 pkg.setVersion('master');
107 });
108 })
109 // create the symlink
110 .then(function() {
111 var libDir = pjson.directories && (pjson.directories.dist || pjson.directories.lib);
112 if (libDir)
113 libDir = path.resolve(dir, libDir);
114
115 if (!libDir || libDir == dir)
116 return;
117
118 // we need to symlink the package.json file in the dist dir
119 // to be able to link subfolders
120 // ask the user before doing this
121 return new Promise(function(resolve) {
122 fs.exists(path.resolve(libDir, 'package.json'), resolve);
123 })
124 .then(function(hasPackageJson) {
125 if (hasPackageJson)
126 return;
127
128 return ui.confirm('Create a package.json symlink in the ' + path.relative(dir, libDir) + ' folder?', true, {
129 info: 'In order to link the folder %' + path.relative(dir, libDir) + '%, ' +
130 'a symlink of the package.json needs to be created in this folder of the package so jspm can read the package configuration.'
131 })
132 .then(function(confirm) {
133 if (!confirm)
134 throw 'Linking process aborted.';
135
136 return asp(fs.symlink)(path.join(dir, 'package.json'), path.resolve(libDir, 'package.json'), 'file');
137 });
138 })
139 .then(function() {
140 dir = libDir;
141 });
142 })
143 .then(function() {
144
145 var linkPath = pkg.getPath();
146
147 return asp(fs.readlink)(pkg.getPath())
148 .then(function(linkString) {
149 if (path.resolve(path.dirname(linkPath), linkString) === dir)
150 return asp(fs.unlink)(linkPath);
151
152 return Promise.resolve(ui.confirm('Relink?', true, {
153 info: '`' + pkg.exactName + '` is already linked, are you sure you want to link over it?'
154 }))
155 .then(function(remove) {
156 if (!remove)
157 throw 'Aborted.';
158 return asp(fs.unlink)(linkPath);
159 });
160 }, function(err) {
161 if (err.code === 'ENOENT')
162 return;
163 if (err.code !== 'EINVAL' && err.code !== 'UNKNOWN')
164 throw err;
165
166 return Promise.resolve(ui.confirm('Replace installed version?', true, {
167 info: '`' + pkg.exactName + '` is already installed, are you sure you want to link over it?'
168 }))
169 .then(function(remove) {
170 if (!remove)
171 throw 'Aborted.';
172 return asp(rimraf)(linkPath);
173 });
174 })
175 .then(function() {
176 return asp(mkdirp)(path.dirname(linkPath));
177 })
178 .then(function() {
179 return asp(fs.symlink)(path.relative(path.dirname(linkPath), dir), linkPath, 'junction');
180 });
181 })
182 // now that we have linked the package, install it over itself to setup the config and install dependencies
183 // package downloads pick up linked folder package.json files and reprocess them each time
184 .then(function() {
185 // check to see if this package is already aliased in the install
186 if (!alias)
187 Object.keys(config.loader.baseMap).forEach(function(dep) {
188 if (config.loader.baseMap[dep].exactName == pkg.exactName)
189 alias = dep;
190 });
191
192 var installName = alias || pkg.package.split('/').pop();
193 config.loader.baseMap[installName] = pkg;
194 // NB options.quick for linked should still link, but just not do more than dependency checks
195 return install.install(installName, pkg.exactName, { quick: options.quick, force: options.force, dev: options.dev, peer: options.peer });
196 })
197 .then(function(aborted) {
198 if (!aborted)
199 ui.log('info',
200 '\nRun this link command again or %jspm install ' + pkg.exactName + '% to relink changes in the package.json file.\n' +
201 'Run %jspm install --unlink% to unlink and install all original packages. Linked packages can also be uninstalled normally.');
202 else
203 ui.log('info', 'Link operation aborted.');
204 }, function(err) {
205 ui.log('err', err.stack || err);
206 });
207};
208