UNPKG

26.3 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.Manifest = void 0;
4const tslib_1 = require("tslib");
5const fslib_1 = require("@yarnpkg/fslib");
6const parsers_1 = require("@yarnpkg/parsers");
7const semver_1 = tslib_1.__importDefault(require("semver"));
8const miscUtils = tslib_1.__importStar(require("./miscUtils"));
9const semverUtils = tslib_1.__importStar(require("./semverUtils"));
10const structUtils = tslib_1.__importStar(require("./structUtils"));
11class Manifest {
12 constructor() {
13 this.indent = ` `;
14 this.name = null;
15 this.version = null;
16 this.os = null;
17 this.cpu = null;
18 this.type = null;
19 this["private"] = false;
20 this.license = null;
21 this.main = null;
22 this.module = null;
23 this.browser = null;
24 this.languageName = null;
25 this.bin = new Map();
26 this.scripts = new Map();
27 this.dependencies = new Map();
28 this.devDependencies = new Map();
29 this.peerDependencies = new Map();
30 this.workspaceDefinitions = [];
31 this.dependenciesMeta = new Map();
32 this.peerDependenciesMeta = new Map();
33 this.resolutions = [];
34 this.files = null;
35 this.publishConfig = null;
36 this.preferUnplugged = null;
37 this.raw = {};
38 /**
39 * errors found in the raw manifest while loading
40 */
41 this.errors = [];
42 }
43 static async tryFind(path, { baseFs = new fslib_1.NodeFS() } = {}) {
44 const manifestPath = fslib_1.ppath.join(path, `package.json`);
45 if (!await baseFs.existsPromise(manifestPath))
46 return null;
47 return await Manifest.fromFile(manifestPath, { baseFs });
48 }
49 static async find(path, { baseFs } = {}) {
50 const manifest = await Manifest.tryFind(path, { baseFs });
51 if (manifest === null)
52 throw new Error(`Manifest not found`);
53 return manifest;
54 }
55 static async fromFile(path, { baseFs = new fslib_1.NodeFS() } = {}) {
56 const manifest = new Manifest();
57 await manifest.loadFile(path, { baseFs });
58 return manifest;
59 }
60 static fromText(text) {
61 const manifest = new Manifest();
62 manifest.loadFromText(text);
63 return manifest;
64 }
65 loadFromText(text) {
66 let data;
67 try {
68 data = JSON.parse(stripBOM(text) || `{}`);
69 }
70 catch (error) {
71 error.message += ` (when parsing ${text})`;
72 throw error;
73 }
74 this.load(data);
75 this.indent = getIndent(text);
76 }
77 async loadFile(path, { baseFs = new fslib_1.NodeFS() }) {
78 const content = await baseFs.readFilePromise(path, `utf8`);
79 let data;
80 try {
81 data = JSON.parse(stripBOM(content) || `{}`);
82 }
83 catch (error) {
84 error.message += ` (when parsing ${path})`;
85 throw error;
86 }
87 this.load(data);
88 this.indent = getIndent(content);
89 }
90 load(data) {
91 if (typeof data !== `object` || data === null)
92 throw new Error(`Utterly invalid manifest data (${data})`);
93 this.raw = data;
94 const errors = [];
95 if (typeof data.name === `string`) {
96 try {
97 this.name = structUtils.parseIdent(data.name);
98 }
99 catch (error) {
100 errors.push(new Error(`Parsing failed for the 'name' field`));
101 }
102 }
103 if (typeof data.version === `string`)
104 this.version = data.version;
105 if (Array.isArray(data.os)) {
106 const os = [];
107 this.os = os;
108 for (const item of data.os) {
109 if (typeof item !== `string`) {
110 errors.push(new Error(`Parsing failed for the 'os' field`));
111 }
112 else {
113 os.push(item);
114 }
115 }
116 }
117 if (Array.isArray(data.cpu)) {
118 const cpu = [];
119 this.cpu = cpu;
120 for (const item of data.cpu) {
121 if (typeof item !== `string`) {
122 errors.push(new Error(`Parsing failed for the 'cpu' field`));
123 }
124 else {
125 cpu.push(item);
126 }
127 }
128 }
129 if (typeof data.type === `string`)
130 this.type = data.type;
131 if (typeof data.private === `boolean`)
132 this.private = data.private;
133 if (typeof data.license === `string`)
134 this.license = data.license;
135 if (typeof data.languageName === `string`)
136 this.languageName = data.languageName;
137 if (typeof data.main === `string`)
138 this.main = data.main;
139 if (typeof data.module === `string`)
140 this.module = data.module;
141 if (data.browser != null)
142 this.browser = data.browser;
143 if (typeof data.bin === `string`) {
144 if (this.name !== null) {
145 this.bin = new Map([[this.name.name, data.bin]]);
146 }
147 else {
148 errors.push(new Error(`String bin field, but no attached package name`));
149 }
150 }
151 else if (typeof data.bin === `object` && data.bin !== null) {
152 for (const [key, value] of Object.entries(data.bin)) {
153 if (typeof value !== `string`) {
154 errors.push(new Error(`Invalid bin definition for '${key}'`));
155 continue;
156 }
157 this.bin.set(key, value);
158 }
159 }
160 if (typeof data.scripts === `object` && data.scripts !== null) {
161 for (const [key, value] of Object.entries(data.scripts)) {
162 if (typeof value !== `string`) {
163 errors.push(new Error(`Invalid script definition for '${key}'`));
164 continue;
165 }
166 this.scripts.set(key, value);
167 }
168 }
169 if (typeof data.dependencies === `object` && data.dependencies !== null) {
170 for (const [name, range] of Object.entries(data.dependencies)) {
171 if (typeof range !== `string`) {
172 errors.push(new Error(`Invalid dependency range for '${name}'`));
173 continue;
174 }
175 let ident;
176 try {
177 ident = structUtils.parseIdent(name);
178 }
179 catch (error) {
180 errors.push(new Error(`Parsing failed for the dependency name '${name}'`));
181 continue;
182 }
183 const descriptor = structUtils.makeDescriptor(ident, range);
184 this.dependencies.set(descriptor.identHash, descriptor);
185 }
186 }
187 if (typeof data.devDependencies === `object` && data.devDependencies !== null) {
188 for (const [name, range] of Object.entries(data.devDependencies)) {
189 if (typeof range !== `string`) {
190 errors.push(new Error(`Invalid dependency range for '${name}'`));
191 continue;
192 }
193 let ident;
194 try {
195 ident = structUtils.parseIdent(name);
196 }
197 catch (error) {
198 errors.push(new Error(`Parsing failed for the dependency name '${name}'`));
199 continue;
200 }
201 const descriptor = structUtils.makeDescriptor(ident, range);
202 this.devDependencies.set(descriptor.identHash, descriptor);
203 }
204 }
205 if (typeof data.peerDependencies === `object` && data.peerDependencies !== null) {
206 for (let [name, range] of Object.entries(data.peerDependencies)) {
207 let ident;
208 try {
209 ident = structUtils.parseIdent(name);
210 }
211 catch (error) {
212 errors.push(new Error(`Parsing failed for the dependency name '${name}'`));
213 continue;
214 }
215 if (typeof range !== `string` || !semverUtils.validRange(range)) {
216 errors.push(new Error(`Invalid dependency range for '${name}'`));
217 range = `*`;
218 }
219 const descriptor = structUtils.makeDescriptor(ident, range);
220 this.peerDependencies.set(descriptor.identHash, descriptor);
221 }
222 }
223 const workspaces = Array.isArray(data.workspaces)
224 ? data.workspaces
225 : typeof data.workspaces === `object` && data.workspaces !== null && Array.isArray(data.workspaces.packages)
226 ? data.workspaces.packages
227 : [];
228 for (const entry of workspaces) {
229 if (typeof entry !== `string`) {
230 errors.push(new Error(`Invalid workspace definition for '${entry}'`));
231 continue;
232 }
233 this.workspaceDefinitions.push({
234 pattern: entry,
235 });
236 }
237 if (typeof data.dependenciesMeta === `object` && data.dependenciesMeta !== null) {
238 for (const [pattern, meta] of Object.entries(data.dependenciesMeta)) {
239 if (typeof meta !== `object` || meta === null) {
240 errors.push(new Error(`Invalid meta field for '${pattern}`));
241 continue;
242 }
243 const descriptor = structUtils.parseDescriptor(pattern);
244 const dependencyMeta = this.ensureDependencyMeta(descriptor);
245 Object.assign(dependencyMeta, meta);
246 }
247 }
248 if (typeof data.peerDependenciesMeta === `object` && data.peerDependenciesMeta !== null) {
249 for (const [pattern, meta] of Object.entries(data.peerDependenciesMeta)) {
250 if (typeof meta !== `object` || meta === null) {
251 errors.push(new Error(`Invalid meta field for '${pattern}`));
252 continue;
253 }
254 const descriptor = structUtils.parseDescriptor(pattern);
255 const peerDependencyMeta = this.ensurePeerDependencyMeta(descriptor);
256 Object.assign(peerDependencyMeta, meta);
257 }
258 }
259 if (typeof data.resolutions === `object` && data.resolutions !== null) {
260 for (const [pattern, reference] of Object.entries(data.resolutions)) {
261 if (typeof reference !== `string`) {
262 errors.push(new Error(`Invalid resolution entry for '${pattern}'`));
263 continue;
264 }
265 try {
266 this.resolutions.push({ pattern: parsers_1.parseResolution(pattern), reference });
267 }
268 catch (error) {
269 errors.push(error);
270 continue;
271 }
272 }
273 }
274 if (Array.isArray(data.files) && data.files.length !== 0) {
275 this.files = new Set();
276 for (const filename of data.files) {
277 if (typeof filename !== `string`) {
278 errors.push(new Error(`Invalid files entry for '${filename}'`));
279 continue;
280 }
281 this.files.add(filename);
282 }
283 }
284 if (typeof data.publishConfig === `object` && data.publishConfig !== null) {
285 this.publishConfig = {};
286 if (typeof data.publishConfig.access === `string`)
287 this.publishConfig.access = data.publishConfig.access;
288 if (typeof data.publishConfig.main === `string`)
289 this.publishConfig.main = data.publishConfig.main;
290 if (typeof data.publishConfig.module === `string`)
291 this.publishConfig.module = data.publishConfig.module;
292 if (typeof data.publishConfig.browser === `string`)
293 this.publishConfig.browser = data.publishConfig.browser;
294 if (typeof data.publishConfig.registry === `string`)
295 this.publishConfig.registry = data.publishConfig.registry;
296 if (typeof data.publishConfig.bin === `string`) {
297 if (this.name !== null) {
298 this.publishConfig.bin = new Map([[this.name.name, data.publishConfig.bin]]);
299 }
300 else {
301 errors.push(new Error(`String bin field, but no attached package name`));
302 }
303 }
304 else if (typeof data.publishConfig.bin === `object` && data.publishConfig.bin !== null) {
305 this.publishConfig.bin = new Map();
306 for (const [key, value] of Object.entries(data.publishConfig.bin)) {
307 if (typeof value !== `string`) {
308 errors.push(new Error(`Invalid bin definition for '${key}'`));
309 continue;
310 }
311 this.publishConfig.bin.set(key, value);
312 }
313 }
314 if (Array.isArray(data.publishConfig.executableFiles)) {
315 this.publishConfig.executableFiles = new Set();
316 for (const value of data.publishConfig.executableFiles) {
317 if (typeof value !== `string`) {
318 errors.push(new Error(`Invalid executable file definition`));
319 continue;
320 }
321 this.publishConfig.executableFiles.add(fslib_1.npath.toPortablePath(value));
322 }
323 }
324 }
325 // We treat optional dependencies after both the regular dependency field
326 // and the dependenciesMeta field have been generated (because we will
327 // override them)
328 if (typeof data.optionalDependencies === `object` && data.optionalDependencies !== null) {
329 for (const [name, range] of Object.entries(data.optionalDependencies)) {
330 if (typeof range !== `string`) {
331 errors.push(new Error(`Invalid dependency range for '${name}'`));
332 continue;
333 }
334 let ident;
335 try {
336 ident = structUtils.parseIdent(name);
337 }
338 catch (error) {
339 errors.push(new Error(`Parsing failed for the dependency name '${name}'`));
340 continue;
341 }
342 // Note that we store the optional dependencies in the same store as
343 // the one that keep the regular dependencies, because they're
344 // effectively the same (the only difference is that optional
345 // dependencies have an extra field set in dependenciesMeta).
346 const realDescriptor = structUtils.makeDescriptor(ident, range);
347 this.dependencies.set(realDescriptor.identHash, realDescriptor);
348 const identDescriptor = structUtils.makeDescriptor(ident, `unknown`);
349 const dependencyMeta = this.ensureDependencyMeta(identDescriptor);
350 Object.assign(dependencyMeta, { optional: true });
351 }
352 }
353 if (typeof data.preferUnplugged === `boolean`)
354 this.preferUnplugged = data.preferUnplugged;
355 this.errors = errors;
356 }
357 getForScope(type) {
358 switch (type) {
359 case `dependencies`:
360 return this.dependencies;
361 case `devDependencies`:
362 return this.devDependencies;
363 case `peerDependencies`:
364 return this.peerDependencies;
365 default: {
366 throw new Error(`Unsupported value ("${type}")`);
367 }
368 }
369 }
370 hasConsumerDependency(ident) {
371 if (this.dependencies.has(ident.identHash))
372 return true;
373 if (this.peerDependencies.has(ident.identHash))
374 return true;
375 return false;
376 }
377 hasHardDependency(ident) {
378 if (this.dependencies.has(ident.identHash))
379 return true;
380 if (this.devDependencies.has(ident.identHash))
381 return true;
382 return false;
383 }
384 hasSoftDependency(ident) {
385 if (this.peerDependencies.has(ident.identHash))
386 return true;
387 return false;
388 }
389 hasDependency(ident) {
390 if (this.hasHardDependency(ident))
391 return true;
392 if (this.hasSoftDependency(ident))
393 return true;
394 return false;
395 }
396 isCompatibleWithOS(os) {
397 return this.os === null || isManifestFieldCompatible(this.os, os);
398 }
399 isCompatibleWithCPU(cpu) {
400 return this.cpu === null || isManifestFieldCompatible(this.cpu, cpu);
401 }
402 ensureDependencyMeta(descriptor) {
403 if (descriptor.range !== `unknown` && !semver_1.default.valid(descriptor.range))
404 throw new Error(`Invalid meta field range for '${structUtils.stringifyDescriptor(descriptor)}'`);
405 const identString = structUtils.stringifyIdent(descriptor);
406 const range = descriptor.range !== `unknown` ? descriptor.range : null;
407 let dependencyMetaSet = this.dependenciesMeta.get(identString);
408 if (!dependencyMetaSet)
409 this.dependenciesMeta.set(identString, dependencyMetaSet = new Map());
410 let dependencyMeta = dependencyMetaSet.get(range);
411 if (!dependencyMeta)
412 dependencyMetaSet.set(range, dependencyMeta = {});
413 return dependencyMeta;
414 }
415 ensurePeerDependencyMeta(descriptor) {
416 if (descriptor.range !== `unknown`)
417 throw new Error(`Invalid meta field range for '${structUtils.stringifyDescriptor(descriptor)}'`);
418 const identString = structUtils.stringifyIdent(descriptor);
419 let peerDependencyMeta = this.peerDependenciesMeta.get(identString);
420 if (!peerDependencyMeta)
421 this.peerDependenciesMeta.set(identString, peerDependencyMeta = {});
422 return peerDependencyMeta;
423 }
424 setRawField(name, value, { after = [] } = {}) {
425 const afterSet = new Set(after.filter(key => {
426 return Object.prototype.hasOwnProperty.call(this.raw, key);
427 }));
428 if (afterSet.size === 0 || Object.prototype.hasOwnProperty.call(this.raw, name)) {
429 this.raw[name] = value;
430 }
431 else {
432 const oldRaw = this.raw;
433 const newRaw = this.raw = {};
434 let inserted = false;
435 for (const key of Object.keys(oldRaw)) {
436 newRaw[key] = oldRaw[key];
437 if (!inserted) {
438 afterSet.delete(key);
439 if (afterSet.size === 0) {
440 newRaw[name] = value;
441 inserted = true;
442 }
443 }
444 }
445 }
446 }
447 exportTo(data, { compatibilityMode = true } = {}) {
448 // Note that we even set the fields that we re-set later; it
449 // allows us to preserve the key ordering
450 Object.assign(data, this.raw);
451 if (this.name !== null)
452 data.name = structUtils.stringifyIdent(this.name);
453 else
454 delete data.name;
455 if (this.version !== null)
456 data.version = this.version;
457 else
458 delete data.version;
459 if (this.os !== null)
460 data.os = this.os;
461 else
462 delete data.os;
463 if (this.cpu !== null)
464 data.cpu = this.cpu;
465 else
466 delete data.cpu;
467 if (this.type !== null)
468 data.type = this.type;
469 else
470 delete data.type;
471 if (this.private)
472 data.private = true;
473 else
474 delete data.private;
475 if (this.license !== null)
476 data.license = this.license;
477 else
478 delete data.license;
479 if (this.languageName !== null)
480 data.languageName = this.languageName;
481 else
482 delete data.languageName;
483 if (this.main !== null)
484 data.main = this.main;
485 else
486 delete data.main;
487 if (this.module !== null)
488 data.module = this.module;
489 else
490 delete data.module;
491 if (this.browser !== null)
492 data.browser = this.browser;
493 else
494 delete data.browser;
495 if (this.bin.size === 1 && this.name !== null && this.bin.has(this.name.name)) {
496 data.bin = this.bin.get(this.name.name);
497 }
498 else if (this.bin.size > 0) {
499 data.bin = Object.assign({}, ...Array.from(this.bin.keys()).sort().map(name => {
500 return { [name]: this.bin.get(name) };
501 }));
502 }
503 else {
504 delete data.bin;
505 }
506 if (this.workspaceDefinitions.length > 0)
507 data.workspaces = this.workspaceDefinitions.map(({ pattern }) => pattern);
508 else
509 delete data.workspaces;
510 const regularDependencies = [];
511 const optionalDependencies = [];
512 for (const dependency of this.dependencies.values()) {
513 const dependencyMetaSet = this.dependenciesMeta.get(structUtils.stringifyIdent(dependency));
514 let isOptionallyBuilt = false;
515 if (compatibilityMode) {
516 if (dependencyMetaSet) {
517 const meta = dependencyMetaSet.get(null);
518 if (meta && meta.optional) {
519 isOptionallyBuilt = true;
520 }
521 }
522 }
523 if (isOptionallyBuilt) {
524 optionalDependencies.push(dependency);
525 }
526 else {
527 regularDependencies.push(dependency);
528 }
529 }
530 if (regularDependencies.length > 0) {
531 data.dependencies = Object.assign({}, ...structUtils.sortDescriptors(regularDependencies).map(dependency => {
532 return { [structUtils.stringifyIdent(dependency)]: dependency.range };
533 }));
534 }
535 else {
536 delete data.dependencies;
537 }
538 if (optionalDependencies.length > 0) {
539 data.optionalDependencies = Object.assign({}, ...structUtils.sortDescriptors(optionalDependencies).map(dependency => {
540 return { [structUtils.stringifyIdent(dependency)]: dependency.range };
541 }));
542 }
543 else {
544 delete data.optionalDependencies;
545 }
546 if (this.devDependencies.size > 0) {
547 data.devDependencies = Object.assign({}, ...structUtils.sortDescriptors(this.devDependencies.values()).map(dependency => {
548 return { [structUtils.stringifyIdent(dependency)]: dependency.range };
549 }));
550 }
551 else {
552 delete data.devDependencies;
553 }
554 if (this.peerDependencies.size > 0) {
555 data.peerDependencies = Object.assign({}, ...structUtils.sortDescriptors(this.peerDependencies.values()).map(dependency => {
556 return { [structUtils.stringifyIdent(dependency)]: dependency.range };
557 }));
558 }
559 else {
560 delete data.peerDependencies;
561 }
562 data.dependenciesMeta = {};
563 for (const [identString, dependencyMetaSet] of miscUtils.sortMap(this.dependenciesMeta.entries(), ([identString, dependencyMetaSet]) => identString)) {
564 for (const [range, meta] of miscUtils.sortMap(dependencyMetaSet.entries(), ([range, meta]) => range !== null ? `0${range}` : `1`)) {
565 const key = range !== null
566 ? structUtils.stringifyDescriptor(structUtils.makeDescriptor(structUtils.parseIdent(identString), range))
567 : identString;
568 const metaCopy = { ...meta };
569 if (compatibilityMode && range === null)
570 delete metaCopy.optional;
571 if (Object.keys(metaCopy).length === 0)
572 continue;
573 data.dependenciesMeta[key] = metaCopy;
574 }
575 }
576 if (Object.keys(data.dependenciesMeta).length === 0)
577 delete data.dependenciesMeta;
578 if (this.peerDependenciesMeta.size > 0) {
579 data.peerDependenciesMeta = Object.assign({}, ...miscUtils.sortMap(this.peerDependenciesMeta.entries(), ([identString, meta]) => identString).map(([identString, meta]) => {
580 return { [identString]: meta };
581 }));
582 }
583 else {
584 delete data.peerDependenciesMeta;
585 }
586 if (this.resolutions.length > 0) {
587 data.resolutions = Object.assign({}, ...this.resolutions.map(({ pattern, reference }) => {
588 return { [parsers_1.stringifyResolution(pattern)]: reference };
589 }));
590 }
591 else {
592 delete data.resolutions;
593 }
594 if (this.files !== null)
595 data.files = Array.from(this.files);
596 else
597 delete data.files;
598 if (this.preferUnplugged !== null)
599 data.preferUnplugged = this.preferUnplugged;
600 else
601 delete data.preferUnplugged;
602 return data;
603 }
604}
605exports.Manifest = Manifest;
606Manifest.fileName = `package.json`;
607Manifest.allDependencies = [`dependencies`, `devDependencies`, `peerDependencies`];
608Manifest.hardDependencies = [`dependencies`, `devDependencies`];
609function getIndent(content) {
610 const indentMatch = content.match(/^[ \t]+/m);
611 if (indentMatch) {
612 return indentMatch[0];
613 }
614 else {
615 return ` `;
616 }
617}
618function stripBOM(content) {
619 if (content.charCodeAt(0) === 0xFEFF) {
620 return content.slice(1);
621 }
622 else {
623 return content;
624 }
625}
626function isManifestFieldCompatible(rules, actual) {
627 let isNotWhitelist = true;
628 let isBlacklist = false;
629 for (const rule of rules) {
630 if (rule[0] === `!`) {
631 isBlacklist = true;
632 if (actual === rule.slice(1)) {
633 return false;
634 }
635 }
636 else {
637 isNotWhitelist = false;
638 if (rule === actual) {
639 return true;
640 }
641 }
642 }
643 // Blacklists with whitelisted items should be treated as whitelists for `os` and `cpu` in `package.json`
644 return isBlacklist && isNotWhitelist;
645}