@salesforce/plugin-release-management
Version:
A plugin for preparing and publishing npm packages
236 lines • 9.93 kB
JavaScript
;
/*
* Copyright (c) 2018, 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
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.api = void 0;
/* eslint-disable no-underscore-dangle */
const fs = require("fs/promises");
const child_process_1 = require("child_process");
const os_1 = require("os");
const path_1 = require("path");
const core_1 = require("@salesforce/core");
const kit_1 = require("@salesforce/kit");
const ProxyAgent = require("proxy-agent");
const proxy_from_env_1 = require("proxy-from-env");
const SimplifiedSigning_1 = require("./SimplifiedSigning");
const error_1 = require("./error");
const NpmName_1 = require("./NpmName");
class PathGetter {
constructor(target) {
this._cwd = process.cwd();
if (!target) {
this._target = this._cwd;
}
else if (target && target.includes(this._cwd)) {
this._target = target;
}
else {
this._target = (0, path_1.join)(this._cwd, target);
}
this._packageJson = (0, path_1.join)(this._target, PathGetter.packageJson);
this._packageJsonBak = (0, path_1.join)(this._target, `${PathGetter.packageJson}.bak`);
}
get packageJson() {
return this._packageJson;
}
get packageJsonBak() {
return this._packageJsonBak;
}
get target() {
return this._target;
}
getFile(filename) {
return (0, path_1.join)(this._target, filename);
}
getIgnoreFile(filename) {
return (0, path_1.join)(this._cwd, filename);
}
}
PathGetter.packageJson = 'package.json';
let cliUx;
let pathGetter;
exports.api = {
setUx(ux) {
cliUx = ux;
},
/**
* call out to npm pack;
*/
pack() {
if (!pathGetter)
pathGetter = new PathGetter();
return new Promise((resolve, reject) => {
const command = 'npm pack -p';
(0, child_process_1.exec)(command, { cwd: pathGetter.target, maxBuffer: 1024 * 4096 }, (error, stdout, stderr) => {
if (error && error['code']) {
return reject(new error_1.ExecProcessFailed(command, error['code'], stderr));
}
else {
const output = stdout.split(os_1.EOL);
if (output.length > 1) {
// note the output end with a newline;
const path = output[output.length - 2];
if (path && path.endsWith('tgz')) {
return resolve(pathGetter.getFile(path));
}
else {
return reject(new kit_1.NamedError('UnexpectedNpmFormat', `Npm pack did not return an expected tgz filename result: [${path}]`));
}
}
else {
return reject(new kit_1.NamedError('UnexpectedNpmFormat', `The output from the npm utility is unexpected [${stdout}]`));
}
}
});
});
},
/**
* read the package.json file for the target npm to be signed.
*/
retrievePackageJson() {
return fs.readFile(pathGetter.packageJson, { encoding: 'utf8' });
},
/**
* read the npm ignore file for the target npm
*
* @param filename - local path to the npmignore file
*/
retrieveIgnoreFile(filename) {
return fs.readFile(pathGetter.getIgnoreFile(filename), { encoding: 'utf8' });
},
/**
* checks the ignore content for the code signing patterns. *.tgz, *.sig package.json.bak
*
* @param content
*/
validateNpmIgnorePatterns(content) {
const validate = (pattern) => {
if (!content) {
throw new kit_1.NamedError('MissingNpmIgnoreFile', 'Missing .npmignore file. The following patterns are required in for code signing: *.tgz, *.sig, package.json.bak.');
}
if (!content.includes(pattern)) {
throw new kit_1.NamedError('MissingNpmIgnorePattern', `.npmignore is missing ${pattern}. The following patterns are required for code signing: *.tgz, *.sig, package.json.bak`);
}
};
validate('*.tgz');
validate('*.sig');
validate('package.json.bak');
},
/**
* checks the ignore content for the code signing patterns. *.tgz, *.sig package.json.bak
*
* @param content
*/
validateNpmFilePatterns(patterns) {
const validate = (pattern) => {
if (patterns.includes(pattern)) {
throw new kit_1.NamedError('ForbiddenFilePattern', 'the files property in package.json should not include the following: *.tgz, *.sig, package.json.bak');
}
};
validate('*.tgz');
validate('*.sig');
validate('package.json.bak');
},
/**
* makes a backup copy pf package.json
*
* @param src - the package.json to backup
* @param dest - package.json.bak
*/
async copyPackageDotJson(src, dest) {
await fs.copyFile(src, dest);
},
/**
* used to update the contents of package.json
*
* @param pJson - the updated json content to write to disk
*/
writePackageJson(pJson) {
return fs.writeFile(pathGetter.packageJson, JSON.stringify(pJson, null, 4));
},
async revertPackageJsonIfExists() {
// Restore the package.json file so it doesn't show a git diff.
await fs.access(pathGetter.packageJsonBak);
cliUx.log(`Restoring package.json from ${pathGetter.packageJsonBak}`);
await exports.api.copyPackageDotJson(pathGetter.packageJsonBak, pathGetter.packageJson);
await fs.unlink(pathGetter.packageJsonBak);
},
/**
* main method to pack and sign an npm.
*
* @param args - reference to process.argv
* @param ux - The cli ux interface usually provided by oclif.
* @return {Promise<SigningResponse>} The SigningResponse
*/
async packSignVerifyModifyPackageJSON(targetPackagePath) {
const logger = await core_1.Logger.child('packAndSign');
pathGetter = new PathGetter(targetPackagePath);
try {
// read package.json info
const packageJsonContent = await exports.api.retrievePackageJson();
const packageJson = JSON.parse(packageJsonContent);
logger.debug('parsed the package.json content');
if (packageJson.files) {
// validate that files property does not include forbidden patterns
exports.api.validateNpmFilePatterns(packageJson.files);
}
else {
// validate npm ignore has what we name.
const npmIgnoreContent = await exports.api.retrieveIgnoreFile('.npmignore');
exports.api.validateNpmIgnorePatterns(npmIgnoreContent);
logger.debug('validated the expected npm ignore patterns');
}
// Recommend updating git ignore to match npmignore.
const filename = '.gitignore';
const gitIgnoreContent = await exports.api.retrieveIgnoreFile(filename);
try {
exports.api.validateNpmIgnorePatterns(gitIgnoreContent);
logger.debug('validated the expected git ignore patterns');
}
catch (e) {
cliUx.warn(`WARNING: The following patterns are recommended in ${filename} for code signing: *.tgz, *.sig, package.json.bak.`);
}
// get the packageJson name/version
const npmName = NpmName_1.NpmName.parse(packageJson.name);
logger.debug(`parsed the following npmName components: ${JSON.stringify(npmName, null, 4)}`);
npmName.tag = packageJson.version;
// make a backup of the packageJson
await exports.api.copyPackageDotJson(pathGetter.packageJson, pathGetter.packageJsonBak);
logger.debug('made a backup of the package.json file.');
cliUx.log(`Backed up ${pathGetter.packageJson} to ${pathGetter.packageJsonBak}`);
const packageNameWithOrWithoutScope = npmName.scope ? `@${npmName.scope}/${npmName.name}` : npmName.name;
// we have to modify package.json with security URLs BEFORE packing
// update the package.json object with the signature urls and write it to disk.
packageJson.sfdx = (0, SimplifiedSigning_1.getSfdxProperty)(packageNameWithOrWithoutScope, npmName.tag);
await exports.api.writePackageJson(packageJson);
cliUx.log('Successfully updated package.json with public key and signature file locations.');
cliUx.logJson(packageJson.sfdx);
const filepath = await exports.api.pack();
cliUx.log(`Packed tgz to ${filepath}`);
const signResponse = await (0, SimplifiedSigning_1.signVerifyUpload)({
upload: true,
targetFileToSign: filepath,
packageName: packageNameWithOrWithoutScope,
packageVersion: npmName.tag,
});
return signResponse;
}
finally {
// prevent any publish-time changes from persisting to git
await exports.api.revertPackageJsonIfExists();
}
},
getAgentForUri(url) {
/* eslint-disable @typescript-eslint/no-unsafe-call */
const proxyUrl = (0, proxy_from_env_1.getProxyForUrl)(url);
const agent = ProxyAgent(proxyUrl);
/* eslint-disable @typescript-eslint/no-unsafe-call */
return { https: agent, http: agent };
},
};
//# sourceMappingURL=packAndSign.js.map