Skip to content

Commit

Permalink
feat: allow 'git commit' and 'git push' during the prepare and/or pub…
Browse files Browse the repository at this point in the history
…lish step
  • Loading branch information
jfairley committed Jan 25, 2019
1 parent f720fb9 commit 6d10d6a
Show file tree
Hide file tree
Showing 6 changed files with 301 additions and 52 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Expand Up @@ -22,7 +22,7 @@
.LSOverride

# Icon must end with two \r
Icon
Icon

# Thumbnails
._*
Expand Down Expand Up @@ -129,3 +129,4 @@ $RECYCLE.BIN/

package-lock.json
yarn.lock
.idea/
78 changes: 62 additions & 16 deletions index.js
@@ -1,31 +1,77 @@
const {defaultTo, castArray} = require('lodash');
const {defaultTo, castArray, isNil} = require('lodash');
const verifyGit = require('./lib/verify');
const prepareGit = require('./lib/prepare');
const commitAndPushGit = require('./lib/commit-and-push');

let verified;
let prepareVerified;
let publishVerified;

function verifyConditions(pluginConfig, context) {
function resolvePluginConfig(step, pluginConfig, context) {
let pluginConfiguredByStepName = false;

// If the plugin config is defined under its intended step, prefer that
if (pluginConfig[step]) {
pluginConfig = pluginConfig[step];
pluginConfiguredByStepName = true;
}

// If the Git prepare plugin or publish plugin is used and has `assets` or `message` configured, validate them now in order to prevent any release if the configuration is wrong
const {options} = context;
// If the Git prepare plugin is used and has `assets` or `message` configured, validate them now in order to prevent any release if the configuration is wrong
if (options.prepare) {
const preparePlugin =
castArray(options.prepare).find(config => config.path && config.path === '@semantic-release/git') || {};
if (options[step]) {
const stepPlugin =
castArray(options[step]).find(config => config.path && config.path === '@semantic-release/git') || {};

pluginConfig.assets = defaultTo(pluginConfig.assets, stepPlugin.assets);
pluginConfig.message = defaultTo(pluginConfig.message, stepPlugin.message);
pluginConfiguredByStepName = true;
}

// If the plugin configuration was called out by name OR if we're executing the 'prepare' step in which case, use of the base configuration is allowed
if (pluginConfiguredByStepName || step === 'prepare') {
return pluginConfig;
}

// The base configuration can be applied only to the 'prepare' step. Otherwise return nothing.
return undefined;
}

pluginConfig.assets = defaultTo(pluginConfig.assets, preparePlugin.assets);
pluginConfig.message = defaultTo(pluginConfig.message, preparePlugin.message);
function verifyConditions(pluginConfig, context) {
const prepareConfig = resolvePluginConfig('prepare', pluginConfig, context);
if (prepareConfig) {
verifyGit(prepareConfig);
prepareVerified = true;
}

verifyGit(pluginConfig);
verified = true;
const publishConfig = resolvePluginConfig('publish', pluginConfig, context);
if (publishConfig) {
verifyGit(publishConfig);
publishVerified = true;
}
}

async function prepare(pluginConfig, context) {
if (!verified) {
pluginConfig = resolvePluginConfig('prepare', pluginConfig, context);

if (!prepareVerified) {
verifyGit(pluginConfig);
prepareVerified = true;
}

await commitAndPushGit(pluginConfig, context);
}

async function publish(pluginConfig, context) {
pluginConfig = resolvePluginConfig('publish', pluginConfig, context);
if (isNil(pluginConfig)) {
// If a 'publish' config isn't specifically called out, ignore this step
return;
}

if (!publishVerified) {
verifyGit(pluginConfig);
verified = true;
publishVerified = true;
}

await prepareGit(pluginConfig, context);
await commitAndPushGit(pluginConfig, context);
}

module.exports = {verifyConditions, prepare};
module.exports = {verifyConditions, prepare, publish};
File renamed without changes.
22 changes: 11 additions & 11 deletions test/prepare.test.js → test/commit-and-push.test.js
Expand Up @@ -2,7 +2,7 @@ import path from 'path';
import test from 'ava';
import {outputFile} from 'fs-extra';
import {stub} from 'sinon';
import prepare from '../lib/prepare';
import commitAndPush from '../lib/commit-and-push';
import {gitRepo, gitGetCommits, gitCommitedFiles} from './helpers/git-utils';

test.beforeEach(t => {
Expand All @@ -28,7 +28,7 @@ test('Commit CHANGELOG.md, package.json, package-lock.json, and npm-shrinkwrap.j
await outputFile(pkgLockPath, "{name: 'test-package'}");
await outputFile(shrinkwrapPath, "{name: 'test-package'}");

await prepare(pluginConfig, {cwd, env, options, lastRelease, nextRelease, logger: t.context.logger});
await commitAndPush(pluginConfig, {cwd, env, options, lastRelease, nextRelease, logger: t.context.logger});

// Verify the remote repo has a the version referencing the same commit sha at the local head
const [commit] = await gitGetCommits(undefined, {cwd, env});
Expand Down Expand Up @@ -56,7 +56,7 @@ test('Exclude CHANGELOG.md, package.json, package-lock.json, and npm-shrinkwrap.
await outputFile(path.resolve(cwd, 'package-lock.json'), "{name: 'test-package'}");
await outputFile(path.resolve(cwd, 'npm-shrinkwrap.json'), "{name: 'test-package'}");

await prepare(pluginConfig, {cwd, env, options, lastRelease, nextRelease, logger: t.context.logger});
await commitAndPush(pluginConfig, {cwd, env, options, lastRelease, nextRelease, logger: t.context.logger});

// Verify no files have been commited
t.deepEqual(await gitCommitedFiles('HEAD', {cwd, env}), []);
Expand All @@ -76,7 +76,7 @@ Last release: \${lastRelease.version}
const nextRelease = {version: '2.0.0', gitTag: 'v2.0.0', notes: 'Test release note'};
await outputFile(path.resolve(cwd, 'CHANGELOG.md'), 'Initial CHANGELOG');

await prepare(pluginConfig, {cwd, env, options, lastRelease, nextRelease, logger: t.context.logger});
await commitAndPush(pluginConfig, {cwd, env, options, lastRelease, nextRelease, logger: t.context.logger});

// Verify the files that have been commited
t.deepEqual(await gitCommitedFiles('HEAD', {cwd, env}), ['CHANGELOG.md']);
Expand Down Expand Up @@ -105,7 +105,7 @@ test('Commit files matching the patterns in "assets"', async t => {
await outputFile(path.resolve(cwd, 'dir2/file6.js'), 'Test content');
await outputFile(path.resolve(cwd, 'dir2/file7.css'), 'Test content');

await prepare(pluginConfig, {cwd, env, options, lastRelease, nextRelease, logger: t.context.logger});
await commitAndPush(pluginConfig, {cwd, env, options, lastRelease, nextRelease, logger: t.context.logger});

// Verify file2 and file1 have been commited
// file4.js is excluded as no glob matching
Expand Down Expand Up @@ -138,7 +138,7 @@ test('Commit files matching the patterns in "assets" as Objects', async t => {
await outputFile(path.resolve(cwd, 'dir2/file6.js'), 'Test content');
await outputFile(path.resolve(cwd, 'dir2/file7.css'), 'Test content');

await prepare(pluginConfig, {cwd, env, options, lastRelease, nextRelease, logger: t.context.logger});
await commitAndPush(pluginConfig, {cwd, env, options, lastRelease, nextRelease, logger: t.context.logger});

// Verify file2 and file1 have been commited
// file4.js is excluded as no glob matching
Expand All @@ -162,7 +162,7 @@ test('Commit files matching the patterns in "assets" as single glob', async t =>
await outputFile(path.resolve(cwd, 'dist/file1.js'), 'Test content');
await outputFile(path.resolve(cwd, 'dist/file2.css'), 'Test content');

await prepare(pluginConfig, {cwd, env, options, lastRelease, nextRelease, logger: t.context.logger});
await commitAndPush(pluginConfig, {cwd, env, options, lastRelease, nextRelease, logger: t.context.logger});

t.deepEqual(await gitCommitedFiles('HEAD', {cwd, env}), ['dist/file1.js']);
t.deepEqual(t.context.log.args[0], ['Found %d file(s) to commit', 1]);
Expand All @@ -177,7 +177,7 @@ test('Commit files matching the patterns in "assets", including dot files', asyn
const nextRelease = {version: '2.0.0', gitTag: 'v2.0.0'};
await outputFile(path.resolve(cwd, 'dist/.dotfile'), 'Test content');

await prepare(pluginConfig, {cwd, env, options, lastRelease, nextRelease, logger: t.context.logger});
await commitAndPush(pluginConfig, {cwd, env, options, lastRelease, nextRelease, logger: t.context.logger});

t.deepEqual(await gitCommitedFiles('HEAD', {cwd, env}), ['dist/.dotfile']);
t.deepEqual(t.context.log.args[0], ['Found %d file(s) to commit', 1]);
Expand All @@ -196,7 +196,7 @@ test('Set the commit author and committer name/email based on environment variab
const nextRelease = {version: '2.0.0', gitTag: 'v2.0.0', notes: 'Test release note'};
await outputFile(path.resolve(cwd, 'CHANGELOG.md'), 'Initial CHANGELOG');

await prepare({}, {cwd, env, options, lastRelease, nextRelease, logger: t.context.logger});
await commitAndPush({}, {cwd, env, options, lastRelease, nextRelease, logger: t.context.logger});

// Verify the files that have been commited
t.deepEqual(await gitCommitedFiles('HEAD', {cwd, env}), ['CHANGELOG.md']);
Expand All @@ -217,7 +217,7 @@ test('Skip negated pattern if its alone in its group', async t => {
const nextRelease = {version: '2.0.0', gitTag: 'v2.0.0'};
await outputFile(path.resolve(cwd, 'file.js'), 'Test content');

await prepare(pluginConfig, {cwd, env, options, lastRelease, nextRelease, logger: t.context.logger});
await commitAndPush(pluginConfig, {cwd, env, options, lastRelease, nextRelease, logger: t.context.logger});

t.deepEqual(await gitCommitedFiles('HEAD', {cwd, env}), ['file.js']);
t.deepEqual(t.context.log.args[0], ['Found %d file(s) to commit', 1]);
Expand All @@ -231,7 +231,7 @@ test('Skip commit if there is no files to commit', async t => {
const lastRelease = {};
const nextRelease = {version: '2.0.0', gitTag: 'v2.0.0', notes: 'Test release note'};

await prepare(pluginConfig, {cwd, env, options, lastRelease, nextRelease, logger: t.context.logger});
await commitAndPush(pluginConfig, {cwd, env, options, lastRelease, nextRelease, logger: t.context.logger});

// Verify the files that have been commited
t.deepEqual(await gitCommitedFiles('HEAD', {cwd, env}), []);
Expand Down
11 changes: 11 additions & 0 deletions test/helpers/git-utils.js
Expand Up @@ -4,6 +4,7 @@ import fileUrl from 'file-url';
import pReduce from 'p-reduce';
import gitLogParser from 'git-log-parser';
import getStream from 'get-stream';
import {emptyDir} from 'fs-extra';

/**
* Create a temporary git repository.
Expand All @@ -16,6 +17,7 @@ import getStream from 'get-stream';
*/
export async function gitRepo(withRemote, branch = 'master') {
let cwd = tempy.directory();
await emptyDir(cwd);

await execa('git', ['init'].concat(withRemote ? ['--bare'] : []), {cwd});

Expand Down Expand Up @@ -184,3 +186,12 @@ export async function gitCommitedFiles(ref = 'HEAD', execaOpts) {
.split('\n')
.filter(file => Boolean(file));
}

// /**
// * Undo the latest commit to reset the test bed
// *
// * @param {Object} [execaOpts] Options to pass to `execa`.
// */
// export async function undoCommit(execaOpts) {
// await execa.stdout('git', ['reset', '--hard', 'HEAD^'], execaOpts);
// }

0 comments on commit 6d10d6a

Please sign in to comment.