UNPKG

@salesforce/plugin-release-management

Version:
280 lines 12.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Package = exports.parsePackageVersion = exports.parseAliasedPackageName = void 0; /* * Copyright (c) 2020, salesforce.com, inc. * All rights reserved. * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ const fs = require("fs"); const path = require("path"); const semver = require("semver"); const core_1 = require("@oclif/core"); const shelljs_1 = require("shelljs"); const core_2 = require("@salesforce/core"); const kit_1 = require("@salesforce/kit"); const ts_types_1 = require("@salesforce/ts-types"); const registry_1 = require("./registry"); function parseAliasedPackageName(alias) { return alias.replace('npm:', '').replace(/@(\^|~)?[0-9]{1,3}(?:.[0-9]{1,3})?(?:.[0-9]{1,3})?(.*?)$/, ''); } exports.parseAliasedPackageName = parseAliasedPackageName; function parsePackageVersion(alias) { const regex = /[0-9]{1,3}(?:.[0-9]{1,3})?(?:.[0-9]{1,3})?(.*?)$/; return regex.exec(alias)[0]; } exports.parsePackageVersion = parsePackageVersion; class Package extends kit_1.AsyncOptionalCreatable { constructor(location) { super(); this.location = location || (0, shelljs_1.pwd)().stdout; this.registry = new registry_1.Registry(); } async readPackageJson() { const pkgJsonPath = this.location ? path.join(this.location, 'package.json') : 'package.json'; const fileData = await fs.promises.readFile(pkgJsonPath, 'utf8'); return (0, kit_1.parseJson)(fileData, pkgJsonPath, false); } /** * Retrieve the npm package info using `npm view` * * It'll first try to find the package with the version listed in the package.json * If that version doesn't exist, it'll find the version tagged as latest */ retrieveNpmPackage() { let result = (0, shelljs_1.exec)(`npm view ${this.name}@${this.packageJson.version} ${this.registry.getRegistryParameter()} --json`, { silent: true }); if (!result.stdout) { result = (0, shelljs_1.exec)(`npm view ${this.name} ${this.registry.getRegistryParameter()} --json`, { silent: true }); } return result.stdout ? JSON.parse(result.stdout) : null; } validateNextVersion() { const nextVersionExists = (this.npmPackage.versions ?? []).includes(this.nextVersion); const currentVersion = this.npmPackage.version ?? null; if (!nextVersionExists) { this.logger.debug(`${this.npmPackage.name}@${this.nextVersion} does not exist in the registry. Proceeding...`); return { nextVersion: this.nextVersion, currentVersion, valid: true, name: this.name, }; } else { this.logger.debug(`${this.npmPackage.name}@${this.nextVersion} already exists in the registry. Exiting...`); return { nextVersion: this.nextVersion, currentVersion, valid: false, name: this.name, }; } } setNextVersion(version) { this.nextVersion = version; } getNextVersion() { return this.nextVersion; } nextVersionIsAvailable() { const pkg = this.retrieveNpmPackage(); const versions = (0, ts_types_1.get)(pkg, 'versions', []); return versions.includes(this.nextVersion); } writePackageJson(rootDir) { const pkgJsonPath = rootDir ? path.join(rootDir, 'package.json') : 'package.json'; const fileData = JSON.stringify(this.packageJson, null, 2); fs.writeFileSync(pkgJsonPath, fileData, { encoding: 'utf8', mode: '600', }); } getDistTags(name) { const result = (0, shelljs_1.exec)(`npm view ${name} dist-tags ${this.registry.getRegistryParameter()} --json`, { silent: true, }); return JSON.parse(result.stdout); } bumpResolutions(tag) { if (!this.packageJson.resolutions) { throw new core_2.SfError('Bumping resolutions requires property "resolutions" to be present in package.json'); } Object.keys(this.packageJson.resolutions).map((key) => { const versions = this.getDistTags(key); this.packageJson.resolutions[key] = versions[tag]; }); } // Lookup dependency info by package name or npm alias // Examples: @salesforce/plugin-info or @sf/info // Pass in the dependencies you want to search through (dependencies, devDependencies, resolutions, etc) getDependencyInfo(name, dependencies) { for (const [key, value] of Object.entries(dependencies)) { if (key === name) { if (value.startsWith('npm:')) { // npm alias was passed in as name, so we need to parse package name and version // e.g. passed in: "@sf/login" // dependency: "@sf/login": "npm:@salesforce/plugin-login@1.1.1" return { dependencyName: key, packageName: parseAliasedPackageName(value), alias: value, currentVersion: parsePackageVersion(value), }; } else { // package name was passed, so we can use key and value directly return { dependencyName: key, packageName: key, alias: null, currentVersion: value, }; } } if (value.startsWith(`npm:${name}`)) { // package name was passed in as name, but an alias is used for the dependency // e.g. passed in: "@salesforce/plugin-login" // dependency: "@sf/login": "npm:@salesforce/plugin-login@1.1.1" return { dependencyName: key, packageName: name, alias: value, currentVersion: parsePackageVersion(value), }; } } core_1.CliUx.ux.error(`${name} was not found in the dependencies section of the package.json`); } bumpDependencyVersions(targetDependencies) { return targetDependencies .map((dep) => { // regex for npm package with optional namespace and version // https://regex101.com/r/HmIu3N/1 const npmPackageRegex = /^((?:@[^/]+\/)?[^@/]+)(?:@([^@/]+))?$/; const [, name, version] = npmPackageRegex.exec(dep); // We will look for packages in dependencies and resolutions const { dependencies, resolutions } = this.packageJson; // find dependency in package.json (could be an npm alias) const depInfo = this.getDependencyInfo(name, { ...dependencies, ...resolutions }); // if a version is not provided, we'll look up the "latest" version depInfo.finalVersion = version ?? this.getDistTags(depInfo.packageName).latest; // return if version did not change if (depInfo.currentVersion === depInfo.finalVersion) return; // override final version if npm alias is used if (depInfo.alias) { depInfo.finalVersion = `npm:${depInfo.packageName}@${depInfo.finalVersion}`; } // update dependency (or resolution) in package.json if (dependencies[depInfo.dependencyName]) { this.packageJson.dependencies[depInfo.dependencyName] = depInfo.finalVersion; } else { this.packageJson.resolutions[depInfo.dependencyName] = depInfo.finalVersion; } return depInfo; }) .filter(Boolean); // remove falsy values, in this case the `undefined` if version did not change } getNextRCVersion(tag, isPatch = false) { const versions = this.getDistTags(this.packageJson.name); const version = semver.parse(versions[tag]); return isPatch ? `${version.major}.${version.minor}.${version.patch + 1}` : `${version.major}.${version.minor + 1}.0`; } pinDependencyVersions(targetTag) { // get the list of dependencies to hardcode if (!this.packageJson.pinnedDependencies) { throw new core_2.SfError('Pinning package dependencies requires property "pinnedDependencies" to be present in package.json'); } const { pinnedDependencies, dependencies } = this.packageJson; const deps = pinnedDependencies .map((d) => { const tagRegex = /(?<=(^@.*?)@)(.*?)$/; const [tag] = tagRegex.exec(d) || []; const name = tag ? d.replace(new RegExp(`@${tag}$`), '') : d; if (!dependencies[name]) { core_1.CliUx.ux.warn(`${name} was not found in the dependencies section of your package.json. Skipping...`); return; } const version = dependencies[name]; if (version.startsWith('npm:')) { return { name: parseAliasedPackageName(version), version: version.split('@').reverse()[0].replace('^', '').replace('~', ''), alias: name, tag: tag || targetTag, }; } else { return { name, version: version.split('@').reverse()[0].replace('^', '').replace('~', ''), alias: null, tag: tag || targetTag, }; } }) .filter((d) => !!d); const pinnedPackages = []; deps.forEach((dep) => { // get the 'release' tag version or the version specified by the passed in tag const versions = this.getDistTags(dep.name); let tag = dep.tag; // if tag is 'latest-rc' and there's no latest-rc release for a package, default to latest if (!versions[tag]) { tag = 'latest'; } // If the version in package.json is greater than the version of the requested tag, then we // assume that this is on purpose - so we don't overwrite it. For example, we might want to // include a latest-rc version for a single plugin but everything else we want latest. let version; if (semver.gt(dep.version, versions[tag])) { core_1.CliUx.ux.warn(`${dep.name} is currently pinned at ${dep.version} which is higher than ${tag} (${versions[tag]}). Assuming that this is intentional...`); version = dep.version; tag = (0, kit_1.findKey)(versions, (v) => v === version); } else { version = versions[tag]; } // insert the new hardcoded versions into the dependencies in the project's package.json if (dep.alias) { this.packageJson.dependencies[dep.alias] = `npm:${dep.name}@${version}`; } else { this.packageJson.dependencies[dep.name] = version; } // accumulate information to return pinnedPackages.push({ name: dep.name, version, tag, alias: dep.alias }); }); return pinnedPackages; } /** * Returns true if the version specified in the package.json has not been * published to the registry */ nextVersionIsHardcoded() { return !(this.npmPackage.versions ?? []).includes(this.packageJson.version); } hasScript(scriptName) { return !!(0, ts_types_1.get)(this.packageJson, `scripts.${scriptName}`, null); } async init() { this.logger = await core_2.Logger.child(this.constructor.name); this.packageJson = await this.readPackageJson(); this.name = this.packageJson.name; this.npmPackage = this.retrieveNpmPackage() || this.createDefaultNpmPackage(); } createDefaultNpmPackage() { return { name: this.name, version: this.packageJson.version, versions: [], 'dist-tags': {}, }; } } exports.Package = Package; //# sourceMappingURL=package.js.map