@salesforce/plugin-release-management
Version:
A plugin for preparing and publishing npm packages
280 lines • 12.6 kB
JavaScript
"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