123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298 |
- "use strict";
- const npa = require("npm-package-arg");
- const path = require("path");
- const loadJsonFile = require("load-json-file");
- const writePkg = require("write-pkg");
- // symbol used to "hide" internal state
- const PKG = Symbol("pkg");
- /* eslint-disable no-underscore-dangle */
- // private fields
- const _location = Symbol("location");
- const _resolved = Symbol("resolved");
- const _rootPath = Symbol("rootPath");
- const _scripts = Symbol("scripts");
- const _contents = Symbol("contents");
- /**
- * @param {import("npm-package-arg").Result} result
- */
- function binSafeName({ name, scope }) {
- return scope ? name.substring(scope.length + 1) : name;
- }
- // package.json files are not that complicated, so this is intentionally naïve
- function shallowCopy(json) {
- return Object.keys(json).reduce((obj, key) => {
- const val = json[key];
- /* istanbul ignore if */
- if (Array.isArray(val)) {
- obj[key] = val.slice();
- } else if (val && typeof val === "object") {
- obj[key] = Object.assign({}, val);
- } else {
- obj[key] = val;
- }
- return obj;
- }, {});
- }
- /**
- * @typedef {object} RawManifest The subset of package.json properties that Lerna uses
- * @property {string} name
- * @property {string} version
- * @property {boolean} [private]
- * @property {Record<string, string>|string} [bin]
- * @property {Record<string, string>} [scripts]
- * @property {Record<string, string>} [dependencies]
- * @property {Record<string, string>} [devDependencies]
- * @property {Record<string, string>} [optionalDependencies]
- * @property {Record<string, string>} [peerDependencies]
- * @property {Record<'directory' | 'registry' | 'tag', string>} [publishConfig]
- * @property {string[] | { packages: string[] }} [workspaces]
- */
- /**
- * Lerna's internal representation of a local package, with
- * many values resolved directly from the original JSON.
- */
- class Package {
- /**
- * Create a Package instance from parameters, possibly reusing existing instance.
- * @param {string|Package|RawManifest} ref A path to a package.json file, Package instance, or JSON object
- * @param {string} [dir] If `ref` is a JSON object, this is the location of the manifest
- * @returns {Package}
- */
- static lazy(ref, dir = ".") {
- if (typeof ref === "string") {
- const location = path.resolve(path.basename(ref) === "package.json" ? path.dirname(ref) : ref);
- const manifest = loadJsonFile.sync(path.join(location, "package.json"));
- return new Package(manifest, location);
- }
- // don't use instanceof because it fails across nested module boundaries
- if ("__isLernaPackage" in ref) {
- return ref;
- }
- // assume ref is a json object
- return new Package(ref, dir);
- }
- /**
- * @param {RawManifest} pkg
- * @param {string} location
- * @param {string} [rootPath]
- */
- constructor(pkg, location, rootPath = location) {
- // npa will throw an error if the name is invalid
- const resolved = npa.resolve(pkg.name, `file:${path.relative(rootPath, location)}`, rootPath);
- this.name = pkg.name;
- this[PKG] = pkg;
- // omit raw pkg from default util.inspect() output, but preserve internal mutability
- Object.defineProperty(this, PKG, { enumerable: false, writable: true });
- this[_location] = location;
- this[_resolved] = resolved;
- this[_rootPath] = rootPath;
- this[_scripts] = { ...pkg.scripts };
- }
- // readonly getters
- get location() {
- return this[_location];
- }
- get private() {
- return Boolean(this[PKG].private);
- }
- get resolved() {
- return this[_resolved];
- }
- get rootPath() {
- return this[_rootPath];
- }
- get scripts() {
- return this[_scripts];
- }
- get bin() {
- const pkg = this[PKG];
- return typeof pkg.bin === "string"
- ? {
- [binSafeName(this.resolved)]: pkg.bin,
- }
- : Object.assign({}, pkg.bin);
- }
- get binLocation() {
- return path.join(this.location, "node_modules", ".bin");
- }
- get manifestLocation() {
- return path.join(this.location, "package.json");
- }
- get nodeModulesLocation() {
- return path.join(this.location, "node_modules");
- }
- // eslint-disable-next-line class-methods-use-this
- get __isLernaPackage() {
- // safer than instanceof across module boundaries
- return true;
- }
- // accessors
- get version() {
- return this[PKG].version;
- }
- set version(version) {
- this[PKG].version = version;
- }
- get contents() {
- // if modified with setter, use that value
- if (this[_contents]) {
- return this[_contents];
- }
- // if provided by pkg.publishConfig.directory value
- if (this[PKG].publishConfig && this[PKG].publishConfig.directory) {
- return path.join(this.location, this[PKG].publishConfig.directory);
- }
- // default to package root
- return this.location;
- }
- set contents(subDirectory) {
- this[_contents] = path.join(this.location, subDirectory);
- }
- // "live" collections
- get dependencies() {
- return this[PKG].dependencies;
- }
- get devDependencies() {
- return this[PKG].devDependencies;
- }
- get optionalDependencies() {
- return this[PKG].optionalDependencies;
- }
- get peerDependencies() {
- return this[PKG].peerDependencies;
- }
- /**
- * Map-like retrieval of arbitrary values
- * @template {keyof RawManifest} K
- * @param {K} key field name to retrieve value
- * @returns {RawManifest[K]} value stored under key, if present
- */
- get(key) {
- return this[PKG][key];
- }
- /**
- * Map-like storage of arbitrary values
- * @template {keyof RawManifest} K
- * @param {T} key field name to store value
- * @param {RawManifest[K]} val value to store
- * @returns {Package} instance for chaining
- */
- set(key, val) {
- this[PKG][key] = val;
- return this;
- }
- /**
- * Provide shallow copy for munging elsewhere
- * @returns {Object}
- */
- toJSON() {
- return shallowCopy(this[PKG]);
- }
- /**
- * Refresh internal state from disk (e.g., changed by external lifecycles)
- */
- refresh() {
- return loadJsonFile(this.manifestLocation).then((pkg) => {
- this[PKG] = pkg;
- return this;
- });
- }
- /**
- * Write manifest changes to disk
- * @returns {Promise} resolves when write finished
- */
- serialize() {
- return writePkg(this.manifestLocation, this[PKG]).then(() => this);
- }
- /**
- * Mutate local dependency spec according to type
- * @param {Object} resolved npa metadata
- * @param {String} depVersion semver
- * @param {String} savePrefix npm_config_save_prefix
- */
- updateLocalDependency(resolved, depVersion, savePrefix) {
- const depName = resolved.name;
- // first, try runtime dependencies
- let depCollection = this.dependencies;
- // try optionalDependencies if that didn't work
- if (!depCollection || !depCollection[depName]) {
- depCollection = this.optionalDependencies;
- }
- // fall back to devDependencies
- if (!depCollection || !depCollection[depName]) {
- depCollection = this.devDependencies;
- }
- if (resolved.registry || resolved.type === "directory") {
- // a version (1.2.3) OR range (^1.2.3) OR directory (file:../foo-pkg)
- depCollection[depName] = `${savePrefix}${depVersion}`;
- } else if (resolved.gitCommittish) {
- // a git url with matching committish (#v1.2.3 or #1.2.3)
- const [tagPrefix] = /^\D*/.exec(resolved.gitCommittish);
- // update committish
- const { hosted } = resolved; // take that, lint!
- hosted.committish = `${tagPrefix}${depVersion}`;
- // always serialize the full url (identical to previous resolved.saveSpec)
- depCollection[depName] = hosted.toString({ noGitPlus: false, noCommittish: false });
- } else if (resolved.gitRange) {
- // a git url with matching gitRange (#semver:^1.2.3)
- const { hosted } = resolved; // take that, lint!
- hosted.committish = `semver:${savePrefix}${depVersion}`;
- // always serialize the full url (identical to previous resolved.saveSpec)
- depCollection[depName] = hosted.toString({ noGitPlus: false, noCommittish: false });
- }
- }
- }
- module.exports.Package = Package;
|