UNPKG

30.1 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 */
16require('core-js/es6/string');
17
18var ConfigFile = require('./config-file');
19var PackageName = require('../package-name');
20var registry = require('../registry');
21var config = require('./index');
22var path = require('path');
23var hasProperties = require('../common').hasProperties;
24var prepend = require('../common').prepend;
25var extend = require('../common').extend;
26var isPlain = require('../common').isPlain;
27var ui = require('../ui');
28var extendSystemConfig = require('../common').extendSystemConfig;
29var fs = require('fs');
30
31/*
32 * jspm loader configuration class
33 * - baseMap
34 * - depMap
35 * - upgrade16
36 * - transpiler
37 * - package for local package
38 *
39 * - browserBaseURL
40 * - nodeRegistryPaths
41 * - browserRegistryPaths
42 * - browserLibURL
43 * - nodeLibURL
44 *
45 * Public methods:
46 * - ensureRegistry(registryName)
47 *
48 * Getters / Setters:
49 * - browserPackagesURL
50 * - nodePackagesURL
51 */
52exports.JspmSystemConfig = JspmSystemConfig;
53function JspmSystemConfig(fileName) {
54 var self = this;
55
56 // primary jspm.config.js file
57 this.file = new SystemConfigFile(fileName);
58
59 // we effectively serialize into two separate configurations
60 // the user-config and the jspm-managed config
61 // this is done by "extracting" the jspm-managed config out
62 // based on tracing the package.json dependencies through the config file
63 this.file.serialize = jspmManagedConfigSerialize;
64
65 this.emptyConfig = this.file.properties.length == 0;
66
67 // having created the separate configuration files, we then manage their config centrally through
68 // the main config file by extending them into it
69 // if the config file is not provided in the package.json we still check for it at the default
70 // location of package.json configFiles.x to see if it exists in the file system
71 this.createConfigFile('dev');
72 if (this.devFile.timestamp == -1) {
73 this.devFile = null;
74 }
75 else {
76 // there is some risk with this setProperties getProperties approach due to mutability
77 // since it creates a binding between the objects as the getProperties aren't cloned
78 // we don't hit those cases due to the nature of this separation not being a clone,
79 // and by using change event forwarding, but it needs to be noted
80 if (this.file.has(['devConfig']))
81 this.file.setObject(['devConfig'], extendSystemConfig(this.file.getObject(['devConfig'], true), this.devFile.getObject([], true)));
82 else
83 this.file.setProperties(['devConfig'], this.devFile.getProperties([]));
84 }
85
86 this.createConfigFile('browser');
87 if (this.browserFile.timestamp == -1) {
88 this.browserFile = null;
89 }
90 else {
91 if (this.file.has(['browserConfig']))
92 this.file.setObject(['browserConfig'], extendSystemConfig(this.file.getObject(['browserConfig'], true), this.browserFile.getObject([], true)));
93 else
94 this.file.setProperties(['browserConfig'], this.browserFile.getProperties([]));
95 }
96
97 this.createConfigFile('node');
98 if (this.nodeFile.timestamp == -1) {
99 this.nodeFile = null;
100 }
101 else {
102 if (this.file.has(['nodeConfig']))
103 this.file.setObject(['nodeConfig'], extendSystemConfig(this.file.getObject(['nodeConfig'], true), this.nodeFile.getObject([], true)));
104 else
105 this.file.setProperties(['nodeConfig'], this.nodeFile.getProperties([]));
106 }
107
108 this.file.changed = false;
109
110 // forward change events when using separate files for these objects
111 this.file.changeEvents.push(function(memberArray) {
112 if (['devConfig', 'browserConfig', 'nodeConfig'].indexOf(memberArray[0]) != -1) {
113 var fileName = memberArray[0].substr(0, memberArray[0].length - 6) + 'File';
114 if (self[fileName]) {
115 self[fileName].changed = true;
116 return true;
117 }
118 }
119 });
120
121 // the base install map is the global map config
122 this.baseMap = {};
123 // the dep map is the package contextual map
124 this.depMap = {};
125 var map = this.file.getObject(['map'], true) || {};
126 Object.keys(map).forEach(function(key) {
127 // upgrade path
128 if (typeof map[key] == 'object') {
129 var curDepMap = self.depMap[key] = map[key];
130 Object.keys(curDepMap).forEach(function(key) {
131 curDepMap[key] = new PackageName(curDepMap[key], true);
132 });
133 return;
134 }
135 self.baseMap[key] = new PackageName(map[key], true);
136 });
137
138 (this.file.getProperties(['packages']) || []).forEach(function(prop) {
139 if (!self.file.has(['packages', prop.key, 'map']))
140 return;
141 // only parse packages with ':' in the name
142 if (prop.key.indexOf(':') == -1)
143 return;
144
145 var curMap = {};
146 var packageObj = self.file.getObject(['packages', prop.key, 'map'], true);
147 Object.keys(packageObj).forEach(function(key) {
148 if (typeof packageObj[key] != 'string')
149 return;
150 curMap[key] = new PackageName(packageObj[key], true);
151 });
152 self.depMap[prop.key] = curMap;
153 });
154
155 // copy in development tree
156
157 // if a dev config map or packages entry is already in the main config or an override,
158 // it gets added to "mapOverrides" and "packageOverrides" for dev instead of main config
159 this.devMapOverrides = {};
160 this.devPackageOverrides = {};
161
162 // copy config into main configuration
163 // it will be re-extracted on save
164 map = this.file.getObject(['devConfig', 'map']) || {};
165 Object.keys(map).forEach(function(key) {
166 var pkgName = new PackageName(map[key], true);
167 if (self.baseMap[key])
168 self.devMapOverrides[key] = map[key];
169 else
170 self.baseMap[key] = pkgName;
171 });
172 (this.file.getProperties(['devConfig', 'packages']) || []).forEach(function(prop) {
173 if (prop.key.indexOf(':') == -1 || self.depMap[prop] || !self.file.has(['devConfig', 'packages', prop.key, 'map'])) {
174 self.devPackageOverrides[prop] = self.file.getObject(['packages', prop.key]);
175 return;
176 }
177
178 var curMap = {};
179 var packageObj = self.file.getObject(['devConfig', 'packages', prop.key, 'map'], true);
180 Object.keys(packageObj).forEach(function(key) {
181 if (typeof packageObj[key] != 'string')
182 return;
183 var pkgName = new PackageName(packageObj[key], true);
184 curMap[key] = pkgName;
185 });
186 self.depMap[prop.key] = curMap;
187 });
188
189 // downgrade back to jspm 0.16 then upgrade can mess packageConfigPaths
190 this.upgrade16 = false;
191
192 // packageConfigPaths is the indicator property in the config to know if the project is jspm 0.17-ready
193 // if the config file doesn't exist, then we assume the project is jspm 0.17-ready
194 var packageConfigPaths;
195 try {
196 packageConfigPaths = this.file.getValue(['packageConfigPaths'], 'array');
197 }
198 catch(e) {}
199
200 if (!packageConfigPaths) {
201 if (this.file.getValue(['defaultJSExtensions'], 'boolean'))
202 this.upgrade16 = true;
203 this.file.setValue(['packageConfigPaths'], Object.keys(packageConfigPaths || {}).map(function(path) {
204 return packageConfigPaths[path];
205 }));
206 }
207
208 // NB deprecate with beta
209 if (this.file.has(['globalEvaluationScope']))
210 this.file.remove(['globalEvaluationScope']);
211
212 if (this.file.has(['defaultJSExtensions']))
213 this.file.remove(['defaultJSExtensions']);
214
215 this.transpiler = this.file.getValue(['transpiler']);
216 if (this.transpiler === false || this.transpiler && typeof this.transpiler != 'string')
217 this.transpiler = 'none';
218
219 this.packageName = config.pjson.name;
220 this.package = config.pjson.name && this.file.getObject(['packages', config.pjson.name], true);
221
222 this.browserBaseURL = this.file.getValue(['browserConfig', 'baseURL'], 'string') || this.file.getValue(['baseURL'], 'string');
223
224 this.browserRegistryPaths = {};
225 this.nodeRegistryPaths = {};
226
227 var paths = readAndSanitizePaths(['paths']) || {};
228 var browserPaths = readAndSanitizePaths(['browserConfig', 'paths']) || {};
229 var nodePaths = readAndSanitizePaths(['nodeConfig', 'paths']) || {};
230
231 function readAndSanitizePaths(pathsProp) {
232 var paths = self.file.getObject(pathsProp);
233 if (paths)
234 Object.keys(paths).forEach(function(path) {
235 var value = paths[path] + '';
236 if (self.browserBaseURL && !isPlain(self.browserBaseURL) && value.startsWith(self.browserBaseURL)) {
237 value = value.substr(self.browserBaseURL.length);
238 self.file.setValue(pathsProp.concat([path]), value);
239 paths[path] = value;
240 }
241 // path.length > 1 check is to retain "*": ... wildcard case
242 if (path.endsWith('*') && value.endsWith('*') && value[value.length - 2] == '/' && path.length > 1) {
243 self.file.remove(pathsProp.concat([path]));
244 delete paths[path];
245 path = path.substr(0, path.length - 1);
246 value = value.substr(0, value.length - 1);
247 self.file.setValue(pathsProp.concat([path]), value);
248 paths[path] = value;
249 }
250 });
251 return paths;
252 }
253
254 Object.keys(paths).forEach(function(path) {
255 if (!getRegistryPath(path, paths[path]))
256 return;
257 self.browserRegistryPaths[path] = paths[path];
258 self.nodeRegistryPaths[path] = paths[path];
259 });
260
261 Object.keys(browserPaths).forEach(function(path) {
262 if (!getRegistryPath(path, browserPaths[path]))
263 return;
264 self.browserRegistryPaths[path] = browserPaths[path];
265 });
266
267 Object.keys(nodePaths).forEach(function(path) {
268 if (!getRegistryPath(path, nodePaths[path]))
269 return;
270 self.nodeRegistryPaths[path] = nodePaths[path];
271 });
272
273 // if nodeConfig.paths has been explicitly opted out of via deletion, we respect that
274 if (hasProperties(this.browserRegistryPaths) && !hasProperties(this.nodeRegistryPaths))
275 this.nodeRegistryPaths = null;
276
277 this.browserLibURL = paths[this.packageName + '/'];
278 if (typeof this.browserLibURL != 'string')
279 this.browserLibURL = browserPaths[this.packageName + '/'];
280 this.nodeLibURL = paths[this.packageName + '/'];
281 if (typeof this.nodeLibURL != 'string')
282 this.nodeLibURL = nodePaths[this.packageName + '/'];
283
284 if (typeof this.nodeLibURL == 'string' && !isPlain(this.nodeLibURL)) {
285 ui.log('warn', 'Paths configuration "' + this.packageName + '/" -> "' + this.nodeLibURL + '" is not valid in Node, falling back to %directories.lib% default value.');
286 this.nodeLibURL = null;
287 }
288
289 // node paths are treated as absolute file system paths
290 if (typeof this.nodeLibURL == 'string')
291 this.nodeLibURL = path.resolve(isPlain(this.nodeLibURL) ? config.pjson.baseURL : config.pjson.dir, this.nodeLibURL) + path.sep;
292 if (this.nodeRegistryPaths)
293 Object.keys(this.nodeRegistryPaths).forEach(function(p) {
294 var value = self.nodeRegistryPaths[p];
295 self.nodeRegistryPaths[p] = path.resolve(isPlain(value) ? config.pjson.baseURL : config.pjson.dir, value) + path.sep;
296 });
297
298 // we default the nodeLibURL and browserLibURL only in the case where the package exists
299 if (this.package) {
300 if (typeof this.nodeLibURL != 'string')
301 this.nodeLibURL = config.pjson.lib;
302 if (typeof this.browserLibURL != 'string')
303 this.browserLibURL = path.relative(config.pjson.baseURL, config.pjson.lib);
304 }
305
306 // browser paths have the baseURL added back here, now that browser paths have been separated out
307 if (this.browserBaseURL) {
308 if (typeof this.browserLibURL == 'string' && isPlain(this.browserLibURL))
309 this.browserLibURL = this.browserBaseURL + this.browserLibURL;
310 if (this.browserRegistryPaths)
311 Object.keys(this.browserRegistryPaths).forEach(function(p) {
312 var value = self.browserRegistryPaths[p];
313 if (isPlain(value))
314 self.browserRegistryPaths[p] = self.browserBaseURL + value;
315 });
316 }
317}
318JspmSystemConfig.prototype.createConfigFile = function(type) {
319 if (!this[type + 'File'])
320 this[type + 'File'] = new SystemConfigFile(config.pjson['configFile' + type[0].toUpperCase() + type.substr(1)]);
321};
322JspmSystemConfig.prototype.removeConfigFile = function(type) {
323 if (this[type + 'File']) {
324 fs.unlinkSync(this[type + 'File'].fileName);
325 this[type + 'File'] = null;
326 // note as changed as we are now inlining the external file
327 this.file.changed = true;
328 }
329};
330JspmSystemConfig.prototype.ensureRegistry = function(registryName) {
331 // ensure packageNameFormats are present as packageConfigPaths in the right order
332 var packageConfigPaths = this.file.getValue(['packageConfigPaths'], 'array');
333
334 if (!packageConfigPaths)
335 this.file.setValue(['packageConfigPaths'], packageConfigPaths = []);
336
337 var lastIndex;
338 (registry.load(registryName).constructor.packageNameFormats || ['*'])
339 .forEach(function(packageNameFormat) {
340 var packageConfigPath = registryName + ':' + packageNameFormat + '.json';
341
342 var curIndex = packageConfigPaths.indexOf(packageConfigPath);
343
344 // add if not present
345 if (curIndex == -1) {
346 packageConfigPaths.push(packageConfigPath);
347 }
348 // reorder if not in order
349 else {
350 if (curIndex < lastIndex)
351 packageConfigPaths.splice(curIndex, 1);
352
353 lastIndex = curIndex;
354 }
355 });
356
357 // add to browser and Node paths
358 if (!this.browserRegistryPaths[registryName + ':'])
359 this.browserRegistryPaths[registryName + ':'] = this.browserPackagesURL + registryName + '/';
360 if (this.nodeRegistryPaths && !this.nodeRegistryPaths[registryName + ':'])
361 this.nodeRegistryPaths[registryName + ':'] = this.nodePackagesURL + registryName + path.sep;
362};
363
364Object.defineProperty(JspmSystemConfig.prototype, 'browserBaseURL', {
365 get: function() {
366 return this._browserBaseURL;
367 },
368 set: function(baseURL) {
369 if (baseURL === '')
370 baseURL = '.';
371 if (baseURL && baseURL[baseURL.length - 1] != '/')
372 baseURL += '/';
373 this._browserBaseURL = baseURL;
374 }
375});
376
377Object.defineProperty(JspmSystemConfig.prototype, 'browserLibURL', {
378 get: function() {
379 return this._browserLibURL;
380 },
381 set: function(libURL) {
382 if (libURL && libURL[libURL.length - 1] != '/')
383 libURL += '/';
384 this._browserLibURL = libURL;
385 }
386});
387
388Object.defineProperty(JspmSystemConfig.prototype, 'nodeLibURL', {
389 get: function() {
390 return this._nodeLibURL;
391 },
392 set: function(libURL) {
393 if (libURL && libURL[libURL.length - 1] != path.sep)
394 libURL += path.sep;
395 this._nodeLibURL = libURL;
396 }
397});
398
399Object.defineProperty(JspmSystemConfig.prototype, 'browserPackagesURL', {
400 get: function() {
401 this._defaultBrowserPackagesURL = this._defaultBrowserPackagesURL || (this.browserBaseURL ? '' : '/') + path.relative(config.pjson.baseURL, config.pjson.packages) + '/';
402 return getPackagesURL(this.browserRegistryPaths, '/') || this._defaultBrowserPackagesURL;
403 },
404 set: function(packagesURL) {
405 var self = this;
406 var curPackagesURL = this.browserPackagesURL;
407
408 if (packagesURL[packagesURL.length - 1] != '/')
409 packagesURL += '/';
410
411 Object.keys(this.browserRegistryPaths).forEach(function(path) {
412 var registryPath = getRegistryPath(path, self.browserRegistryPaths[path]);
413 if (registryPath && registryPath.packagesURL + '/' == curPackagesURL)
414 self.browserRegistryPaths[path] = packagesURL + registryPath.name + '/';
415 });
416
417 this._defaultBrowserPackagesURL = packagesURL;
418 }
419});
420
421Object.defineProperty(JspmSystemConfig.prototype, 'nodePackagesURL', {
422 get: function() {
423 this._defaultNodePackagesURL = this._defaultNodePackagesURL || config.pjson.packages + path.sep;
424 return this.nodeRegistryPaths && getPackagesURL(this.nodeRegistryPaths, path.sep) || this._defaultNodePackagesURL;
425 },
426 set: function(packagesURL) {
427 var self = this;
428
429 if (packagesURL[packagesURL.length - 1] != path.sep)
430 packagesURL += path.sep;
431
432 // if node registry paths was null, bring it back
433 this.nodeRegistryPaths = this.nodeRegistryPaths || {};
434 Object.keys(this.nodeRegistryPaths).forEach(function(p) {
435 var registryPath = getRegistryPath(p, self.nodeRegistryPaths[p]);
436 if (registryPath)
437 self.nodeRegistryPaths[p] = packagesURL + registryPath.name + path.sep;
438 });
439 // ensure all packageConfig registry paths
440 var packageConfigPaths = this.file.getValue(['packageConfigPaths'], 'array');
441
442 packageConfigPaths.forEach(function(configPath) {
443 var cfgPathMatch = configPath.match(/([^\:]+):/);
444 if (cfgPathMatch)
445 self.ensureRegistry(cfgPathMatch[1]);
446 });
447
448 this._defaultNodePackagesURL = packagesURL;
449 }
450});
451
452function getPackagesURL(paths, sep) {
453 var packagesURL;
454 Object.keys(paths).some(function(path) {
455 var registryPath = getRegistryPath(path, paths[path]);
456 if (registryPath) {
457 packagesURL = registryPath.packagesURL + sep;
458 return true;
459 }
460 });
461 return packagesURL;
462}
463
464function getRegistryPath(fromPath, toPath) {
465 var registryMatch = fromPath.match(/^([^:]+):$/);
466 if (!registryMatch)
467 return;
468
469 var registryName = registryMatch[1];
470
471 var packagesMatch = toPath.match(new RegExp('^(.+)\\/' + registryName + '\\/$'));
472 if (!packagesMatch)
473 return;
474
475 return {
476 name: registryName,
477 packagesURL: packagesMatch[1]
478 };
479}
480
481JspmSystemConfig.prototype.removePackage = function(exactName) {
482 delete this.depMap[exactName];
483 this.file.remove(['packages', exactName]);
484};
485
486// ensure properties are synced to file config
487JspmSystemConfig.prototype.syncFile = function() {
488 var self = this;
489
490 // extract the serialized depConfig
491 var depConfig = {
492 map: {},
493 packages: {}
494 };
495 Object.keys(this.baseMap).forEach(function(name) {
496 depConfig.map[name] = self.baseMap[name].toString();
497 });
498 Object.keys(this.depMap).forEach(function(parentName) {
499 depConfig.packages[parentName] = { map: {} };
500 Object.keys(self.depMap[parentName]).forEach(function(name) {
501 depConfig.packages[parentName].map[name] = self.depMap[parentName][name].toString();
502 });
503 });
504
505 // include peerDependencies and dependencies trees
506 var coreConfig = {};
507 moveTree(Object.keys(config.pjson.dependencies).concat(Object.keys(config.pjson.peerDependencies)), depConfig, coreConfig);
508 // filter out devDependencies
509 var devConfig = {};
510 moveTree(Object.keys(config.pjson.devDependencies), depConfig, devConfig);
511 // we then extend with our dev overrides
512 // uninstalls have the ability to bump overrides if deps become dev-only again
513 prepend(devConfig.map, this.devMapOverrides);
514 prepend(devConfig.packages, this.devPackageOverrides);
515
516 // then include what is left
517 extend(coreConfig.map, depConfig.map);
518 extend(coreConfig.packages, depConfig.packages);
519
520 this.file.setObject(['map'], coreConfig.map);
521 Object.keys(coreConfig.packages).forEach(function(pkgName) {
522 self.file.setObject(['packages', pkgName, 'map'], coreConfig.packages[pkgName].map);
523 });
524
525 this.file.setObject(['devConfig', 'map'], devConfig.map, true, true);
526 if (hasProperties(devConfig.packages))
527 this.file.setObject(['devConfig', 'packages'], devConfig.packages, true, true);
528 else
529 this.file.remove(['devConfig', 'packages']);
530
531 // any devPackages in this config, must move to the dev config
532 // any non dev packages in the dev config, must move to this config
533 Object.keys(devConfig.packages).forEach(function(devPkg) {
534 if (self.file.has(['packages', devPkg])) {
535 var pkgObj = self.file.getObject(['packages', devPkg], true);
536 self.file.setObject(['devConfig', 'packages', devPkg], pkgObj);
537 self.file.remove(['packages', devPkg]);
538 }
539 });
540 Object.keys(coreConfig.packages).forEach(function(corePkg) {
541 if (self.file.has(['devConfig', 'packages', corePkg])) {
542 var pkgObj = self.file.getObject(['devConfig', 'packages', corePkg], true);
543 self.file.setObject(['packages', corePkg], pkgObj);
544 self.file.remove(['devConfig', 'packages', corePkg]);
545 }
546 });
547
548 if (config.pjson.name && this.package) {
549 if (this.packageName != config.pjson.name) {
550 this.file.remove(['packages', this.packageName]);
551 this.file.remove(['browserConfig', 'paths', this.packageName + '/']);
552 this.file.remove(['nodeConfig', 'paths', this.packageName + '/']);
553 this.file.remove(['paths', this.packageName + '/']);
554 this.packageName = config.pjson.name;
555 }
556
557 this.file.setObject(['packages', this.packageName], this.package);
558 // ensure the local package is the first in the package list
559 this.file.orderFirst(['packages', this.packageName]);
560 }
561
562 if (this.transpiler)
563 this.file.setValue(['transpiler'], this.transpiler != 'none' ? this.transpiler : false);
564
565 if (this.browserBaseURL) {
566 if (!this.file.has(['browserConfig', 'baseURL']) && this.file.has(['baseURL']))
567 this.file.setValue(['baseURL'], this.browserBaseURL == '/' ? '/' : this.browserBaseURL.substr(0, this.browserBaseURL.length - 1));
568 else
569 this.file.setValue(['browserConfig', 'baseURL'], this.browserBaseURL == '/' ? '/' : this.browserBaseURL.substr(0, this.browserBaseURL.length - 1));
570 }
571 else {
572 this.file.remove(['browserConfig', 'baseURL']);
573 this.file.remove(['baseURL']);
574 }
575
576 var registryPaths = Object.keys(this.browserRegistryPaths);
577 if (this.nodeRegistryPaths)
578 Object.keys(this.nodeRegistryPaths).forEach(function(registryPath) {
579 if (registryPaths.indexOf(registryPath) == -1)
580 registryPaths.push(registryPath);
581 });
582
583 registryPaths.forEach(function(registryPath) {
584 setPathsConfig(registryPath, self.browserRegistryPaths[registryPath], self.nodeRegistryPaths && self.nodeRegistryPaths[registryPath], false);
585 });
586 setPathsConfig(this.packageName + '/', this.browserLibURL, this.nodeLibURL, true);
587
588 // set paths with consolidation across browser / node cases
589 function setPathsConfig(name, browserPath, nodePath, orderLast) {
590 // remove baseURL from browserPath and nodePath converting them into relative URLs
591 if (typeof browserPath == 'string' && browserPath.startsWith(self.browserBaseURL))
592 browserPath = browserPath.substr(self.browserBaseURL.length);
593 if (typeof nodePath == 'string') {
594 if (nodePath.startsWith(config.pjson.baseURL))
595 nodePath = nodePath.substr(config.pjson.baseURL.length + 1);
596 nodePath = nodePath.replace(/\\/g, '/');
597 }
598
599
600 if (!self.browserFile && typeof nodePath == 'string' && typeof browserPath == 'string' &&
601 isPlain(browserPath) && browserPath == nodePath) {
602 // path collapse when browser = node path and we're not using a separate browser config file
603 self.file.remove(['browserConfig', 'paths', name], true);
604 self.file.remove(['nodeConfig', 'paths', name], true);
605 self.file.setValue(['paths', name], browserPath);
606 if (orderLast)
607 self.file.orderLast(['paths', name]);
608 }
609 else {
610 // separation case
611 if (typeof nodePath == 'string') {
612 self.file.setValue(['nodeConfig', 'paths', name], nodePath);
613 if (orderLast)
614 self.file.orderLast(['nodeConfig', 'paths', name]);
615 }
616 if (typeof browserPath == 'string') {
617 self.file.setValue(['browserConfig', 'paths', name], browserPath);
618 if (orderLast)
619 self.file.orderLast(['browserConfig', 'paths', name]);
620 }
621 // when we have both a node and a browser entry, clear the base-level path
622 if (typeof nodePath == 'string' && typeof browserPath == 'string')
623 self.file.remove(['paths', name], true);
624 }
625 }
626
627 this.file.clearIfEmpty(['devConfig']);
628 this.file.clearIfEmpty(['browserConfig']);
629 this.file.clearIfEmpty(['nodeConfig']);
630};
631
632// extracts the config dependencies from one config object to another
633function moveTree(depsList, sourceConfig, targetConfig) {
634 targetConfig.map = targetConfig.map || {};
635 targetConfig.packages = targetConfig.packages || {};
636
637 if (sourceConfig.map) {
638 Object.keys(sourceConfig.map).forEach(function(dep) {
639 if (depsList.indexOf(dep) != -1) {
640 targetConfig.map[dep] = sourceConfig.map[dep];
641 delete sourceConfig.map[dep];
642 }
643 });
644
645 if (!hasProperties(sourceConfig.map))
646 delete sourceConfig.map;
647 }
648
649 if (sourceConfig.packages && targetConfig.map) {
650 var relatedPkgs = [];
651 function addRelatedPackage(pkg) {
652 if (relatedPkgs.indexOf(pkg) != -1 || !sourceConfig.packages[pkg])
653 return;
654
655 relatedPkgs.push(pkg);
656
657 var curPkg = sourceConfig.packages[pkg];
658 if (curPkg.map)
659 Object.keys(curPkg.map).forEach(function(dep) {
660 addRelatedPackage(curPkg.map[dep]);
661 });
662 }
663 Object.keys(targetConfig.map).forEach(function(dep) {
664 addRelatedPackage(targetConfig.map[dep]);
665 });
666 Object.keys(sourceConfig.packages).forEach(function(pkg) {
667 if (relatedPkgs.indexOf(pkg) == -1)
668 return;
669
670 var curPkg = sourceConfig.packages[pkg];
671 targetConfig.packages[pkg] = curPkg;
672 delete sourceConfig.packages[pkg];
673 });
674
675 if (!hasProperties(sourceConfig.packages))
676 delete sourceConfig.packages;
677 }
678}
679
680function jspmManagedConfigSerialize(obj) {
681 var managedCfg = {};
682
683 if (obj.packageConfigPaths) {
684 managedCfg.packageConfigPaths = obj.packageConfigPaths;
685 delete obj.packageConfigPaths;
686 }
687
688 moveTree(Object.keys(config.pjson.dependencies).concat(Object.keys(config.pjson.peerDependencies)), obj, managedCfg);
689
690 //if (!hasProperties(managedCfg.map))
691 // delete managedCfg.map;
692
693 var newline = this.style.newline;
694 var trailingNewline = this.style.trailingNewline;
695
696 return SystemConfigFile.prototype.serialize.call(this, obj) + newline + (trailingNewline ? '' : newline) +
697 SystemConfigFile.prototype.serialize.call(this, managedCfg);
698}
699
700JspmSystemConfig.prototype.write = function() {
701 this.syncFile();
702
703 // sync with pjson config file names as default write targets
704 if (config.pjson.configFile != this.file.fileName)
705 this.file.rename(config.pjson.configFile);
706
707 var devProperties, browserProperties, nodeProperties;
708 var changed = this.file.changed;
709
710 if (this.devFile) {
711 if (config.pjson.configFileDev != this.devFile.fileName)
712 this.devFile.rename(config.pjson.configFileDev);
713 devProperties = this.file.getProperties(['devConfig']) || [];
714 this.devFile.setProperties([], devProperties);
715 this.file.remove(['devConfig']);
716 this.devFile.write();
717 }
718
719 if (this.browserFile) {
720 if (config.pjson.configFileBrowser != this.browserFile.fileName)
721 this.browserFile.rename(config.pjson.configFileBrowser);
722 browserProperties = this.file.getProperties(['browserConfig']) || [];
723 this.browserFile.setProperties([], browserProperties);
724 this.file.remove(['browserConfig']);
725 this.browserFile.write();
726 }
727
728 if (this.nodeFile) {
729 if (config.pjson.configFileNode != this.nodeFile.fileName)
730 this.nodeFile.rename(config.pjson.configFileNode);
731 nodeProperties = this.file.getProperties(['nodeConfig']) || [];
732 this.nodeFile.setProperties([], nodeProperties);
733 this.file.remove(['nodeConfig']);
734 this.nodeFile.write();
735 }
736
737 this.file.changed = changed;
738
739 this.file.write();
740
741 if (devProperties)
742 this.file.setProperties(['devConfig'], devProperties);
743 if (browserProperties)
744 this.file.setProperties(['browserConfig'], browserProperties);
745 if (nodeProperties)
746 this.file.setProperties(['nodeConfig'], nodeProperties);
747 this.file.changed = false;
748};
749JspmSystemConfig.prototype.getConfig = function() {
750 this.syncFile();
751
752 return this.file.getObject([], true);
753};
754
755/*
756 * SystemConfigFile
757 * Extends ConfigFile, implementing specific jspm config serialization and deserialization
758 */
759function SystemConfigFile(fileName) {
760 ConfigFile.call(this, fileName, [
761 'baseURL',
762 'production',
763 'packageConfigPaths',
764 'paths',
765 ['browserConfig', [
766 'baseURL',
767 'packageConfigPaths',
768 'paths',
769 'warnings',
770 'transpiler',
771 'meta',
772 'map',
773 'packages',
774 'depCache',
775 'bundles'
776 ]],
777 ['nodeConfig', [
778 'baseURL',
779 'packageConfigPaths',
780 'paths',
781 'warnings',
782 'transpiler',
783 'meta',
784 'map',
785 'packages',
786 'depCache',
787 'bundles'
788 ]],
789 ['productionConfig', [
790 'baseURL',
791 'packageConfigPaths',
792 'paths',
793 'warnings',
794 'transpiler',
795 'meta',
796 'map',
797 'packages',
798 'depCache',
799 'bundles'
800 ]],
801 ['devConfig', [
802 'baseURL',
803 'packageConfigPaths',
804 'paths',
805 'warnings',
806 'transpiler',
807 'meta',
808 'map',
809 'packages',
810 'depCache',
811 'bundles'
812 ]],
813 'warnings',
814 'transpiler',
815 'meta',
816 'map',
817 'packages',
818 'depCache',
819 'bundles'
820 ]);
821}
822SystemConfigFile.prototype = Object.create(ConfigFile.prototype);
823SystemConfigFile.prototype.serialize = function(obj) {
824 // base class serialize is JSON serialization of properties
825 var serializedString = ConfigFile.prototype.serialize.call(this, obj);
826
827 var tab = this.style.tab;
828 var quote = this.style.quote;
829 var newline = this.style.newline;
830 var trailingNewline = this.style.trailingNewline;
831
832 return ('SystemJS.config(' + serializedString.trim() + ');' + (trailingNewline ? newline : ''))
833 // add a newline before "meta", "depCache", "map" blocks, removing quotes
834 // .replace(new RegExp('^' + tab + quote + '(meta|depCache|map|packages)' + quote, 'mg'), newline + tab + '$1')
835 // remove quotes on first-level letter-based properties
836 .replace(new RegExp('^' + tab + quote + '(\\w+)' + quote, 'mg'), tab + '$1');
837};
838SystemConfigFile.prototype.deserialize = function(configString) {
839 /*jshint unused:false */
840 var cfg = {};
841 var System = {
842 config: function(_cfg) {
843 extendSystemConfig(cfg, _cfg);
844 },
845 paths: {},
846 map: {}
847 };
848 var SystemJS = System;
849 eval(configString.toString());
850 return cfg;
851};