1 | const {ext, parse, nodejsIds, resolveModuleId, relativeModuleId} = require('dumber-module-loader/dist/id-utils');
|
2 | const {stripSourceMappingUrl, getSourceMap} = require('./shared');
|
3 | const {info, error} = require('./log');
|
4 | const path = require('path');
|
5 |
|
6 | module.exports = class PackageReader {
|
7 | constructor(fileReader) {
|
8 | this.fileReader = fileReader;
|
9 | this._readFile = this._readFile.bind(this);
|
10 | }
|
11 |
|
12 | banner() {
|
13 | const {version, name} = this;
|
14 | const config = this.fileReader.packageConfig;
|
15 |
|
16 | let info = [];
|
17 | if (config.location) {
|
18 | const location = path.relative(process.cwd(), config.location);
|
19 | info.push('location: ' + location);
|
20 | }
|
21 | if (config.main) {
|
22 | info.push('main: ' + config.main);
|
23 | }
|
24 | let result = `${version.padEnd(10)} ${name}`;
|
25 | if (info.length) result += ` { ${info.join(', ')} }`;
|
26 | return result;
|
27 | }
|
28 |
|
29 | ensureMainPath() {
|
30 | if (this.hasOwnProperty('mainPath')) return Promise.resolve();
|
31 |
|
32 | return this.fileReader('package.json')
|
33 | .then(file => {
|
34 | let metadata = JSON.parse(file.contents);
|
35 | this.name = metadata.name;
|
36 | this.version = metadata.version || 'N/A';
|
37 | this.browserReplacement = _browserReplacement(metadata.browser);
|
38 |
|
39 | const main = _main(metadata);
|
40 |
|
41 |
|
42 |
|
43 | return this._nodejsLoadAsFile(main)
|
44 | .catch(() => this._nodejsLoadIndex(main))
|
45 |
|
46 | .catch(() => 'index.js');
|
47 | })
|
48 | .then(mainPath => {
|
49 | const replacement = this.browserReplacement['./' + mainPath];
|
50 | if (replacement) {
|
51 |
|
52 | mainPath = replacement.slice(2);
|
53 | }
|
54 |
|
55 | this.mainPath = mainPath;
|
56 | this.parsedMainId = parse(mainPath);
|
57 | return this;
|
58 | });
|
59 | }
|
60 |
|
61 | readMain() {
|
62 | return this.ensureMainPath()
|
63 | .then(() => this._readFile(this.mainPath));
|
64 | }
|
65 |
|
66 | readResource(resource) {
|
67 | return this.ensureMainPath().then(() => {
|
68 | let parts = this.parsedMainId.parts;
|
69 | let len = parts.length;
|
70 | let i = 0;
|
71 |
|
72 | const findResource = () => {
|
73 | if (i >= len) {
|
74 | return Promise.reject(new Error(`could not find "${resource}" in package ${this.name}`));
|
75 | }
|
76 |
|
77 | let resParts = parts.slice(0, i);
|
78 | resParts.push(resource);
|
79 |
|
80 | let fullResource = resParts.join('/');
|
81 |
|
82 | const replacement = this.browserReplacement['./' + fullResource] ||
|
83 | this.browserReplacement['./' + fullResource + '.js'];
|
84 | if (replacement) {
|
85 |
|
86 | fullResource = replacement.slice(2);
|
87 | }
|
88 |
|
89 | return this._nodejsLoad(fullResource).then(
|
90 | filePath => this._readFile(filePath),
|
91 | () => {
|
92 | i += 1;
|
93 | return findResource();
|
94 | }
|
95 | );
|
96 | }
|
97 |
|
98 | return findResource();
|
99 | }).then(unit => {
|
100 | const requested = this.name + '/' + resource;
|
101 |
|
102 | if (nodejsIds(requested).indexOf(unit.moduleId) === -1) {
|
103 |
|
104 |
|
105 |
|
106 | if (unit.alias) {
|
107 | unit.alias = [unit.alias, requested];
|
108 | } else {
|
109 | unit.alias = requested;
|
110 | }
|
111 | }
|
112 | return unit;
|
113 | });
|
114 | }
|
115 |
|
116 |
|
117 | _readFile(filePath) {
|
118 | if (!this._logged) {
|
119 | this._logged = true;
|
120 | info(this.banner());
|
121 | }
|
122 |
|
123 | return this.fileReader(filePath).then(file => {
|
124 |
|
125 | if (file.defined) return file;
|
126 |
|
127 | const moduleId = this.name + '/' + parse(filePath).bareId;
|
128 |
|
129 | let replacement;
|
130 | if (normalizeExt(filePath) === '.js') {
|
131 | Object.keys(this.browserReplacement).forEach(key => {
|
132 | const target = this.browserReplacement[key];
|
133 | const baseId = this.name + '/index';
|
134 | const sourceModule = key.startsWith('.') ?
|
135 | relativeModuleId(moduleId, resolveModuleId(baseId, key)) :
|
136 | key;
|
137 |
|
138 | let targetModule;
|
139 | if (target) {
|
140 | targetModule = relativeModuleId(moduleId, resolveModuleId(baseId, target));
|
141 | } else {
|
142 |
|
143 |
|
144 | targetModule = '__ignore__';
|
145 | }
|
146 |
|
147 | if (!replacement) replacement = {};
|
148 | replacement[sourceModule] = targetModule;
|
149 | });
|
150 | }
|
151 |
|
152 | const unit = {
|
153 | path: file.path.replace(/\\/g, '/'),
|
154 | contents: stripSourceMappingUrl(file.contents),
|
155 | moduleId,
|
156 | packageName: this.name,
|
157 | packageMainPath: this.mainPath,
|
158 | sourceMap: getSourceMap(file.contents, file.path)
|
159 | };
|
160 |
|
161 |
|
162 | if (replacement) unit.replacement = replacement;
|
163 |
|
164 | if (unit.moduleId === this.name + '/' + this.parsedMainId.bareId) {
|
165 |
|
166 |
|
167 | const mExt = normalizeExt(unit.path);
|
168 | const pExt = normalizeExt(this.name);
|
169 | if (mExt === pExt || (mExt === '.js' && !pExt)) {
|
170 | unit.alias = this.name;
|
171 | }
|
172 | }
|
173 |
|
174 | return unit;
|
175 | });
|
176 | }
|
177 |
|
178 | _whenFileExists(p) {
|
179 | return this.fileReader.exists(p)
|
180 | .then(exists => {
|
181 | if (!exists) throw new Error('File does not exist: ' + p);
|
182 | });
|
183 | }
|
184 |
|
185 |
|
186 |
|
187 | _nodejsLoadAsFile(filePath) {
|
188 | return this._whenFileExists(filePath).then(
|
189 | () => filePath,
|
190 | () => {
|
191 | const jsFilePath = filePath + '.js';
|
192 | return this._whenFileExists(jsFilePath).then(
|
193 | () => jsFilePath,
|
194 | () => {
|
195 | const jsonFilePath = filePath + '.json';
|
196 | return this._whenFileExists(jsonFilePath).then(
|
197 | () => jsonFilePath
|
198 | );
|
199 | }
|
200 | );
|
201 | }
|
202 | ).then(
|
203 | p => p.replace(/\\/g, '/'),
|
204 | () => {
|
205 | throw new Error(`cannot load Nodejs file for ${filePath}`)
|
206 | }
|
207 | );
|
208 | }
|
209 |
|
210 | _nodejsLoadIndex(dirPath) {
|
211 | const indexJsFilePath = path.join(dirPath, 'index.js');
|
212 | return this._whenFileExists(indexJsFilePath).then(
|
213 | () => indexJsFilePath,
|
214 | () => {
|
215 | const indexJsonFilePath = path.join(dirPath, 'index.json');
|
216 | return this._whenFileExists(indexJsonFilePath).then(
|
217 | () => indexJsonFilePath
|
218 | );
|
219 | }
|
220 | ).then(
|
221 | p => p.replace(/\\/g, '/'),
|
222 | () => {
|
223 | throw new Error(`cannot load Nodejs index file for ${dirPath}`)
|
224 | }
|
225 | );
|
226 | }
|
227 |
|
228 | _nodejsLoadAsDirectory(dirPath) {
|
229 | const packageJsonPath = path.join(dirPath, 'package.json');
|
230 | return this.fileReader(packageJsonPath).then(
|
231 | file => {
|
232 | let metadata;
|
233 | try {
|
234 | metadata = JSON.parse(file.contents);
|
235 | } catch (err) {
|
236 | error('Failed to parse ' + packageJsonPath);
|
237 | error(err);
|
238 | throw err;
|
239 | }
|
240 |
|
241 |
|
242 | const mainResourcePath = path.join(dirPath, _main(metadata));
|
243 |
|
244 | return this._nodejsLoadAsFile(mainResourcePath)
|
245 | .catch(() => this._nodejsLoadIndex(mainResourcePath));
|
246 | },
|
247 | () => this._nodejsLoadIndex(dirPath)
|
248 | ).catch(() => {
|
249 | throw new Error(`cannot load Nodejs directory for ${dirPath}`)
|
250 | });
|
251 | }
|
252 |
|
253 | _nodejsLoad(filePath) {
|
254 | return this._nodejsLoadAsFile(filePath)
|
255 | .catch(() => this._nodejsLoadAsDirectory(filePath));
|
256 | }
|
257 | };
|
258 |
|
259 | function _main(metadata) {
|
260 | let main;
|
261 |
|
262 |
|
263 |
|
264 |
|
265 |
|
266 |
|
267 |
|
268 |
|
269 |
|
270 | if (typeof metadata.dumberForcedMain === 'string') {
|
271 | main = metadata.dumberForcedMain;
|
272 | } else if (typeof metadata.browser === 'string') {
|
273 |
|
274 | main = metadata.browser;
|
275 | } else if (typeof metadata.browser === 'object' &&
|
276 | typeof metadata.browser['.'] === 'string') {
|
277 |
|
278 | main = metadata.browser['.'];
|
279 | } else if (typeof metadata.module === 'string' &&
|
280 | !(metadata.name && metadata.name.startsWith('aurelia-'))) {
|
281 |
|
282 |
|
283 |
|
284 |
|
285 | main = metadata.module;
|
286 | } else if (typeof metadata.main === 'string') {
|
287 | main = metadata.main;
|
288 | }
|
289 |
|
290 | main = main || 'index';
|
291 | if (main.indexOf('./') === 0) main = main.slice(2);
|
292 | return main;
|
293 | }
|
294 |
|
295 |
|
296 | function _browserReplacement(browser) {
|
297 |
|
298 | if (!browser || typeof browser === 'string') return {};
|
299 |
|
300 | let replacement = {};
|
301 |
|
302 | Object.keys(browser).forEach(key => {
|
303 |
|
304 | if (key === '.') return;
|
305 | const target = browser[key];
|
306 |
|
307 | let sourceModule = filePathToModuleId(key);
|
308 |
|
309 | if (typeof target === 'string') {
|
310 | let targetModule = filePathToModuleId(target);
|
311 | if (!targetModule.startsWith('.')) {
|
312 |
|
313 | targetModule = './' + targetModule;
|
314 | }
|
315 | replacement[sourceModule] = targetModule;
|
316 | } else {
|
317 | replacement[sourceModule] = false;
|
318 | }
|
319 | });
|
320 |
|
321 | return replacement;
|
322 | }
|
323 |
|
324 | function filePathToModuleId(filePath) {
|
325 | return parse(filePath.replace(/\\/g, '/')).bareId;
|
326 | }
|
327 |
|
328 | function normalizeExt(path) {
|
329 | const e = ext(path);
|
330 | if (e === '.mjs' || e === '.cjs') return '.js';
|
331 | return e;
|
332 | }
|