@twyr/announce
Version:
CLI Tool and NPM Library for announcing a release on Github / Gitlab / etc. and on NPM
452 lines (404 loc) • 17.6 kB
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>commands/publish-command.js - Announce CLI - API Reference</title>
<meta name="description" content="Announce CLI - API Reference" />
<meta name="keywords" content="automation announce changelog cli console git github gitlab npm package publish release release-automation release-helper release-workflow semantic-release semantic-version semver semver-release terminal twyr version" />
<meta name="keyword" content="automation announce changelog cli console git github gitlab npm package publish release release-automation release-helper release-workflow semantic-release semantic-version semver semver-release terminal twyr version" />
<script src="scripts/prettify/prettify.js"></script>
<script src="scripts/prettify/lang-css.js"></script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
<script src="scripts/nav.js" defer></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
<label for="nav-trigger" class="navicon-button x">
<div class="navicon"></div>
</label>
<label for="nav-trigger" class="overlay"></label>
<nav class="wrap">
<input type="text" id="nav-search" placeholder="Search" />
<h2><a href="index.html">Home</a></h2>
<h2><a href="https://github.com/twyr/announce" target="_blank" class="menu-item" id="github_link">GitHub</a></h2>
<h3>Classes</h3>
<ul>
<li><a href="GitHubWrapper.html">GitHubWrapper</a>
<ul class='methods'>
<li data-type='method'><a href="GitHubWrapper.html#_fetchData">_fetchData</a></li>
<li data-type='method'><a href="GitHubWrapper.html#createRelease">createRelease</a></li>
<li data-type='method'><a href="GitHubWrapper.html#fetchCommitAuthorInformation">fetchCommitAuthorInformation</a></li>
<li data-type='method'><a href="GitHubWrapper.html#fetchCommitInformation">fetchCommitInformation</a></li>
<li data-type='method'><a href="GitHubWrapper.html#fetchReleaseInformation">fetchReleaseInformation</a></li>
<li data-type='method'><a href="GitHubWrapper.html#getCommitLink">getCommitLink</a></li>
</ul>
</li>
<li><a href="GitLabWrapper.html">GitLabWrapper</a>
<ul class='methods'>
<li data-type='method'><a href="GitLabWrapper.html#createRelease">createRelease</a></li>
<li data-type='method'><a href="GitLabWrapper.html#fetchCommitAuthorInformation">fetchCommitAuthorInformation</a></li>
<li data-type='method'><a href="GitLabWrapper.html#fetchCommitInformation">fetchCommitInformation</a></li>
<li data-type='method'><a href="GitLabWrapper.html#fetchReleaseInformation">fetchReleaseInformation</a></li>
<li data-type='method'><a href="GitLabWrapper.html#getCommitLink">getCommitLink</a></li>
</ul>
</li>
<li><a href="PrepareCommandClass.html">PrepareCommandClass</a>
<ul class='methods'>
<li data-type='method'><a href="PrepareCommandClass.html#_bumpVersion">_bumpVersion</a></li>
<li data-type='method'><a href="PrepareCommandClass.html#_computeNextVersion">_computeNextVersion</a></li>
<li data-type='method'><a href="PrepareCommandClass.html#_getCurrentVersion">_getCurrentVersion</a></li>
<li data-type='method'><a href="PrepareCommandClass.html#_getTargetFileList">_getTargetFileList</a></li>
<li data-type='method'><a href="PrepareCommandClass.html#_mergeOptions">_mergeOptions</a></li>
<li data-type='method'><a href="PrepareCommandClass.html#_setupLogger">_setupLogger</a></li>
<li data-type='method'><a href="PrepareCommandClass.html#_setupTasks">_setupTasks</a></li>
<li data-type='method'><a href="PrepareCommandClass.html#execute">execute</a></li>
</ul>
</li>
<li><a href="PublishCommandClass.html">PublishCommandClass</a>
<ul class='methods'>
<li data-type='method'><a href="PublishCommandClass.html#_getUpstreamRepositoryInfo">_getUpstreamRepositoryInfo</a></li>
<li data-type='method'><a href="PublishCommandClass.html#_initializeGit">_initializeGit</a></li>
<li data-type='method'><a href="PublishCommandClass.html#_mergeOptions">_mergeOptions</a></li>
<li data-type='method'><a href="PublishCommandClass.html#_publishToNpm">_publishToNpm</a></li>
<li data-type='method'><a href="PublishCommandClass.html#_setupLogger">_setupLogger</a></li>
<li data-type='method'><a href="PublishCommandClass.html#_setupTasks">_setupTasks</a></li>
<li data-type='method'><a href="PublishCommandClass.html#execute">execute</a></li>
</ul>
</li>
<li><a href="ReleaseCommandClass.html">ReleaseCommandClass</a>
<ul class='methods'>
<li data-type='method'><a href="ReleaseCommandClass.html#_generateChangelog">_generateChangelog</a></li>
<li data-type='method'><a href="ReleaseCommandClass.html#_generateRelease">_generateRelease</a></li>
<li data-type='method'><a href="ReleaseCommandClass.html#_initializeGit">_initializeGit</a></li>
<li data-type='method'><a href="ReleaseCommandClass.html#_mergeOptions">_mergeOptions</a></li>
<li data-type='method'><a href="ReleaseCommandClass.html#_pushUpstream">_pushUpstream</a></li>
<li data-type='method'><a href="ReleaseCommandClass.html#_restoreCode">_restoreCode</a></li>
<li data-type='method'><a href="ReleaseCommandClass.html#_setupLogger">_setupLogger</a></li>
<li data-type='method'><a href="ReleaseCommandClass.html#_setupTasks">_setupTasks</a></li>
<li data-type='method'><a href="ReleaseCommandClass.html#_stashOrCommit">_stashOrCommit</a></li>
<li data-type='method'><a href="ReleaseCommandClass.html#_summarize">_summarize</a></li>
<li data-type='method'><a href="ReleaseCommandClass.html#_tagCode">_tagCode</a></li>
<li data-type='method'><a href="ReleaseCommandClass.html#execute">execute</a></li>
</ul>
</li>
</ul>
</nav>
<div id="main">
<h1 class="page-title">commands/publish-command.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/* eslint-disable curly */
/* eslint-disable security-node/detect-non-literal-require-calls */
/* eslint-disable security/detect-non-literal-require */
/* eslint-disable security-node/non-literal-reg-expr */
/* eslint-disable security/detect-non-literal-regexp */
/* eslint-disable no-loop-func */
'use strict';
/**
* Module dependencies, required for ALL Twy'r modules
* @ignore
*/
/**
* Module dependencies, required for this module
* @ignore
*/
/**
* @class PublishCommandClass
* @classdesc The command class that handles all the publish operations.
*
* @param {object} mode - Set the current run mode - CLI or API
*
* @description
* The command class that implements the "publish" step of the workflow.
* Please see README.md for the details of what this step involves.
*
*/
class PublishCommandClass {
// #region Constructor
constructor(mode) {
this.#execMode = mode;
}
// #endregion
// #region Public Methods
/**
* @async
* @function
* @instance
* @memberof PublishCommandClass
* @name execute
*
* @param {object} options - Parsed command-line options, or options passed in via API
*
* @return {null} Nothing.
*
* @summary The main method to publish the Git Host release to NPM.
*
* This method does 2 things:
* - Gets the URL to the compressed asset for the last/specified release from the Git Host
* - Publishes the asset to NPM
*
*/
async execute(options) {
// Step 1: Setup sane defaults for the options
const mergedOptions = this._mergeOptions(options);
// Step 2: Set up the logger according to the options passed in
const logger = this._setupLogger(mergedOptions);
mergedOptions.logger = logger;
// Step 3: Setup the task list
const taskList = this?._setupTasks?.();
// Step 4: Run the tasks in sequence
// eslint-disable-next-line security-node/detect-crlf
console.log(`Publishing the release to NPM:`);
await taskList?.run?.({
'options': mergedOptions,
'execError': null
});
}
// #endregion
// #region Private Methods
/**
* @function
* @instance
* @memberof PublishCommandClass
* @name _mergeOptions
*
* @param {object} options - Parsed command-line options, or options passed in via API
*
* @return {object} Merged options - input options > configured options.
*
* @summary Merges options passed in with configured ones - and puts in sane defaults if neither is available.
*
*/
_mergeOptions(options) {
const mergedOptions = Object?.assign?.({}, options);
return mergedOptions;
}
/**
* @function
* @instance
* @memberof PublishCommandClass
* @name _setupLogger
*
* @param {object} options - merged options object returned by the _mergeOptions method
*
* @return {object} Logger object with info / error functions.
*
* @summary Creates a logger in CLI mode or uses the passed in logger object in API mode - and returns it.
*
*/
_setupLogger(options) {
if(this.#execMode === 'api')
return options?.logger;
return null;
}
/**
* @function
* @instance
* @memberof PublishCommandClass
* @name _setupTasks
*
* @return {object} Tasks as Listr.
*
* @summary Setup the list of tasks to be run
*
*/
_setupTasks() {
const Listr = require('listr');
const taskList = new Listr([{
'title': 'Initializing Git Client...',
'task': this?._initializeGit?.bind?.(this)
}, {
'title': 'Fetching upstream repository info...',
'task': this?._getUpstreamRepositoryInfo?.bind?.(this),
'skip': (ctxt) => {
if(ctxt?.options?.git)
return false;
return `No Git client found.`;
}
}, {
'title': 'Publishing to npm...',
'task': this?._publishToNpm?.bind?.(this),
'skip': (ctxt) => {
if(!ctxt?.options?.npmToken?.trim?.()?.length) return `Cannot publish without an NPM token.`;
if(!ctxt?.options?.releaseToBePublished) return `Cannot publish without a release on the Git host.`;
if(!ctxt?.options?.releaseToBePublished?.tarball_url) return `Cannot publish a release without a tarball.`;
return false;
}
}], {
'collapse': false
});
return taskList;
}
/**
* @function
* @instance
* @memberof PublishCommandClass
* @name _initializeGit
*
* @param {object} ctxt - Task context containing the options object returned by the _mergeOptions method
* @param {object} task - Reference to the task that is running
*
* @return {null} Nothing.
*
* @summary Creates a Git client instance for the current project repository and sets it on the context.
*
*/
_initializeGit(ctxt, task) {
const simpleGit = require('simple-git');
const git = simpleGit?.({
'baseDir': ctxt?.options?.currentWorkingDirectory
});
ctxt?.options?.logger?.info?.(`Initialized Git for the repository @ ${ctxt?.options?.currentWorkingDirectory}`);
task.title = `Initialize Git for the repository @ ${ctxt?.options?.currentWorkingDirectory}: Done`;
ctxt.options.git = git;
}
/**
* @async
* @function
* @instance
* @memberof PublishCommandClass
* @name _getUpstreamRepositoryInfo
*
* @param {object} ctxt - Task context containing the options object returned by the _mergeOptions method
* @param {object} task - Reference to the task that is running
*
* @return {null} Nothing.
*
* @summary Retrieves the upstream repository information, and sets a POJO with that info into the context.
*
*/
async _getUpstreamRepositoryInfo(ctxt, task) {
const gitRemote = await ctxt?.options?.git?.remote?.(['get-url', '--push', ctxt?.options?.upstream]);
const hostedGitInfo = require('hosted-git-info');
const repository = hostedGitInfo?.fromUrl?.(gitRemote);
repository.project = repository?.project?.replace?.('.git\n', '');
const GitHostWrapper = require(`./../git_host_utilities/${repository?.type}`)?.GitHostWrapper;
const gitHostWrapper = new GitHostWrapper(ctxt?.options?.[`${repository?.type}Token`]);
const releaseToBePublished = await gitHostWrapper?.fetchReleaseInformation?.(repository, ctxt?.options?.releaseName);
if(!releaseToBePublished) throw new Error(`Unknown Release: ${ctxt?.options.releaseName}`);
ctxt.options.repository = repository;
ctxt.options.releaseToBePublished = releaseToBePublished;
ctxt?.options?.logger?.info?.(`Fetch upstream repository info: Done`);
task.title = `Fetch upstream repository info: Done`;
}
/**
* @async
* @function
* @instance
* @memberof PublishCommandClass
* @name _publishToNpm
*
* @param {object} ctxt - Task context containing the options object returned by the _mergeOptions method
* @param {object} task - Reference to the task that is running
*
* @return {null} Nothing.
*
* @summary Retrieves the release assets from GitHub, and publishes them to NPM.
*
*/
async _publishToNpm(ctxt, task) {
let distTag = null;
if((ctxt?.options?.distTag ?? 'version_default') === 'version_default') {
if(ctxt?.options?.releaseToBePublished?.prerelease)
distTag = 'next';
else
distTag = 'latest';
}
const publishOptions = ['publish'];
publishOptions?.push?.(ctxt?.options?.releaseToBePublished?.tarball_url);
publishOptions?.push?.(`--tag ${distTag}`);
publishOptions?.push?.(`--access ${ctxt?.options?.access}`);
if(ctxt?.options?.dryRun) publishOptions?.push?.('--dry-run');
const execa = require('execa');
const publishProcess = execa?.('npm', publishOptions, { 'all': true });
// publishProcess?.stdout?.pipe?.(process.stdout);
// publishProcess?.stderr?.pipe?.(process.stderr);
await publishProcess;
ctxt?.options?.logger?.info?.(`Publish to NPM: Done`);
task.title = `Publish to NPM: Done`;
}
// #endregion
// #region Utility Methods
async _sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
// #endregion
// #region Private Fields
#execMode = null;
// #endregion
}
// Add the command to the cli
exports.commandCreator = function commandCreator(commanderProcess, configuration) {
const Commander = require('commander');
const publish = new Commander.Command('publish');
// Get package.json into memory... we'll use it in multiple places here
const path = require('path');
const projectPackageJson = path.join((configuration?.publish?.currentWorkingDirectory?.trim?.() ?? process.cwd()), 'package.json');
let pkg = null;
try {
pkg = require(projectPackageJson);
}
catch(err) {
// Do nothing
pkg = null;
}
if(pkg) {
// Get the dynamic template filler - use it for configuration substitution
const fillTemplate = require('es6-dynamic-template');
if(configuration?.publish?.currentWorkingDirectory) {
configuration.publish.currentWorkingDirectory = fillTemplate?.(configuration?.publish?.currentWorkingDirectory, pkg);
}
if(configuration?.publish?.releaseName) {
configuration.publish.releaseName = fillTemplate?.(configuration?.publish?.releaseName, pkg);
}
}
// Setup the command
publish?.alias?.('pub');
publish
?.option?.('--current-working-directory <folder>', 'Path to the current working directory', configuration?.release?.currentWorkingDirectory?.trim?.() ?? process?.cwd?.())
.option('--access <level>', 'Public / Restricted', configuration?.publish?.access?.trim?.() ?? 'public')
.option('--dist-tag <tag>', 'Tag to use for the published release', configuration?.publish?.distTag?.trim?.() ?? 'latest')
.option('--dry-run', 'Dry run publish', configuration?.publish?.dryRun ?? false)
.option('--github-token <token>', 'Token to use for accessing the release on GitHub', configuration?.publish?.githubToken?.trim?.() ?? process.env.GITHUB_TOKEN ?? 'PROCESS.ENV.GITHUB_TOKEN')
.option('--gitlab-token <token>', 'Token to use for accessing the release on GitLab', configuration?.publish?.gitlabToken?.trim?.() ?? process.env.GITLAB_TOKEN ?? 'PROCESS.ENV.GITLAB_TOKEN')
.option('--npm-token <token>', 'Automation Token to use for publishing the release to NPM', configuration?.publish?.npmToken?.trim?.() ?? process.env.NPM_TOKEN ?? 'PROCESS.ENV.NPM_TOKEN')
.option('--release-name <name>', 'Release name on the Git Host for fetching the compressed assets', configuration?.publish?.releaseName?.trim?.() ?? (pkg ? `V${pkg?.version} Release` : 'Release'))
.option('--upstream <remote>', 'Git remote to use for accessing the release', configuration?.publish?.upstream ?? 'upstream')
;
const commandObj = new PublishCommandClass('cli');
publish?.action?.(commandObj?.execute?.bind?.(commandObj));
// Add it to the mix
commanderProcess?.addCommand?.(publish);
return;
};
// Export the API for usage by downstream programs
exports.apiCreator = function apiCreator() {
const commandObj = new PublishCommandClass('api');
return {
'name': 'publish',
'method': commandObj.execute.bind(commandObj)
};
};
</code></pre>
</article>
</section>
</div>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.6.7</a> using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
</footer>
<script>
prettyPrint();
</script>
<script src="scripts/polyfill.js"></script>
<script src="scripts/linenumber.js"></script>
<script src="scripts/search.js" defer></script>
</body>
</html>