Skip to content

Commit

Permalink
feat(semver): ✅ add option to skip committing changes on version bump
Browse files Browse the repository at this point in the history
  • Loading branch information
cakeinpanic authored and edbzn committed Aug 3, 2022
1 parent 659ce37 commit 0533977
Show file tree
Hide file tree
Showing 12 changed files with 183 additions and 22 deletions.
16 changes: 5 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ nx run workspace:version [...options]
| **`--trackDeps`** | `boolean` | `false` | bump dependent packages (bump A if A depends on B) ([details](https://github.com/jscutlery/semver#tracking-dependencies)) |
| **`--allowEmptyRelease`** | `boolean` | `false` | force a patch increment even if library source didn't change |
| **`--skipCommitTypes`** | `string[]` | `[]` | treat commits with specified types as non invoking version bump ([details](https://github.com/jscutlery/semver#skipping-release-for-specific-types-of-commits)) |
| **`--skipCommit`** | `boolean` | `false` | skips generating a new commit, leaves all changes in index. Tag would be put on last commit ([details](https://github.com/jscutlery/semver#skipping-commit)) |
| **`--commitMessageFormat`** | `string` | `undefined` | format the auto-generated message commit ([details](https://github.com/jscutlery/semver#commit-message-customization)) |
| **`--preset`** | `string` | `'angular'` | specify the commit message guideline preset |

Expand Down Expand Up @@ -170,18 +171,11 @@ For example, if you had only one commit from the last version:
docs(project): update documentation about new feature
```

would not cause a new release (because `--skipCommitTypes=docs,ci` was specified).
#### Skipping commit

And two commits:

```
docs(project): update documentation about new feature
fix(project): get rig of annoying bug
```

would produce a patch bump.

Please keep in mind that changelog would be generated by [conventional-changelog](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular#type) which ignores some types by design (i.e. `docs`, `test` and others).
In some cases your release process relies only on tags and you don't want new commit with version bump and changelog updates to be made.
For this cases you can provide `--skipCommit` flag and changes made by library would stay in index without committing.
Tag for the new version would be put on the last existing commit

#### Triggering executors post-release

Expand Down
48 changes: 48 additions & 0 deletions packages/semver/src/executors/version/index.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ $`)
expect(result).toEqual({ success: true });
});


it('should commit all changes', () => {
expect(uncommitedChanges()).toHaveLength(0);
});
Expand Down Expand Up @@ -1219,6 +1220,49 @@ $`)
});
});

describe('--skipCommit', () => {
beforeEach(async () => {
testingWorkspace = setupTestingWorkspace(new Map(commonWorkspaceFiles));

/* Commit changes. */
commitChanges();
});

afterEach(() => testingWorkspace.tearDown());

it('should make changes but not create a commit', async () => {
result = await version(
{
...defaultBuilderOptions,
skipCommit: true
},
createFakeContext({
project: 'b',
projectRoot: resolve(testingWorkspace.root, 'packages/b'),
workspaceRoot: testingWorkspace.root,
})
);

expect(commitMessage()).toBe('perf(a): ⚡ improve quickness');
expect(uncommitedChanges()).toHaveLength(1)
expect(commitMessageOfATag('b-0.0.1')).toBe('fix(b): 🐞 fix emptiness');

expect(readFileSync('packages/b/CHANGELOG.md', 'utf-8')).toMatch(
new RegExp(`^# Changelog
This file was generated.*
## 0.0.1 \\(.*\\)
### Bug Fixes
\\* \\*\\*b:\\*\\* 🐞 fix emptiness .*
$`)
);
});
});

// The testing workspace isn't really configured for
// executors, perhaps using the `new FSTree()` from
// and `new Workspace()` @nrwl/toa would give a
Expand Down Expand Up @@ -1309,6 +1353,10 @@ function commitMessage() {
return execSync('git show -s --format=%s', { encoding: 'utf-8' }).trim();
}

function commitMessageOfATag(tagName: string) {
return execSync(`git log -1 --format=format:"%B" ${tagName}`, { encoding: 'utf-8' }).trim();
}

function projectGraph() {
// package a depends on lib d
// package c depends on lib e
Expand Down
34 changes: 33 additions & 1 deletion packages/semver/src/executors/version/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { runPostTargets } from './utils/post-target';
import * as project from './utils/project';
import { tryBump } from './utils/try-bump';
import * as workspace from './utils/workspace';

const LAST_COMMIT_HASH ='lastCommitHash'
jest.mock('./utils/changelog');
jest.mock('./utils/project');
jest.mock('./utils/commit', () => ({
Expand Down Expand Up @@ -41,6 +41,7 @@ describe('@jscutlery/semver:version', () => {
typeof changelog.calculateChangelogChanges
>;
const mockTryPush = git.tryPush as jest.MockedFunction<typeof git.tryPush>;
const mockGetLastProjectCommitHash = git.getLastProjectCommitHash as jest.MockedFunction<typeof git.getLastProjectCommitHash>;
const mockAddToStage = git.addToStage as jest.MockedFunction<
typeof git.addToStage
>;
Expand Down Expand Up @@ -105,6 +106,9 @@ describe('@jscutlery/semver:version', () => {

/* Mock Git execution */
mockTryPush.mockReturnValue(of(''));


mockGetLastProjectCommitHash.mockReturnValue(of(LAST_COMMIT_HASH))
mockAddToStage.mockReturnValue(of(undefined));
mockCommit.mockReturnValue(of(undefined));
mockCreateTag.mockReturnValue(of(''));
Expand Down Expand Up @@ -198,6 +202,34 @@ describe('@jscutlery/semver:version', () => {
);
});

describe('--skipCommit', () => {
it('should not make a commit', async () => {
const { success } = await version(
{
...options,
skipCommit: true
},
context
);

expect(success).toBe(true);
expect(mockCommit).toBeCalledWith( expect.objectContaining({skipCommit: true}))
});

it('should put tag on last commit in a library', async () => {
const { success } = await version(
{
...options,
skipCommit: true
},
context
);

expect(success).toBe(true);
expect(mockCreateTag).toBeCalledWith(expect.objectContaining({ commitHash: LAST_COMMIT_HASH }));
});
});

describe('--syncVersions=false (independent mode)', () => {
it('should run semver independently on a project', async () => {
const { success } = await version(options, context);
Expand Down
8 changes: 7 additions & 1 deletion packages/semver/src/executors/version/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export default async function version(
preset,
allowEmptyRelease,
skipCommitTypes,
skipCommit
} = _normalizeOptions(options);
const workspaceRoot = context.root;
const projectName = context.projectName as string;
Expand Down Expand Up @@ -130,12 +131,14 @@ export default async function version(
skipProjectChangelog,
commitMessage,
dependencyUpdates,
skipCommit
};

const version$ = defer(() =>
syncVersions
? versionWorkspace({
...options,
projectRoot,
skipRootChangelog,
})
: versionProject({
Expand Down Expand Up @@ -184,7 +187,9 @@ export default async function version(
...(dryRun === false ? [_runPostTargets({ notes })] : [])
)
),
reduce((result) => result, { success: true })
reduce((result) => {
return result
}, { success: true })
);
})
);
Expand Down Expand Up @@ -227,6 +232,7 @@ function _normalizeOptions(options: VersionBuilderSchema) {
changelogHeader: options.changelogHeader ?? defaultHeader,
versionTagPrefix: options.tagPrefix ?? options.versionTagPrefix,
commitMessageFormat: options.commitMessageFormat as string,
skipCommit: options.skipCommit || false,
preset:
options.preset === 'angular'
? 'angular'
Expand Down
1 change: 1 addition & 0 deletions packages/semver/src/executors/version/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface VersionBuilderSchema {
skipRootChangelog?: boolean;
skipProjectChangelog?: boolean;
trackDeps?: boolean;
skipCommit?: boolean;
/**
* @deprecated Use the alias releaseAs (--releaseAs) instead.
* @sunset 3.0.0
Expand Down
5 changes: 5 additions & 0 deletions packages/semver/src/executors/version/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@
"type": "boolean",
"default": false
},
"skipCommit": {
"description": "Allows to skip making a commit when bumping a version(but put the tag on last existent commit)",
"type": "boolean",
"default": false
},
"skipCommitTypes": {
"description": " Specify array of commit types to be ignored when calculating next version bump",
"type": "array",
Expand Down
18 changes: 18 additions & 0 deletions packages/semver/src/executors/version/utils/commit.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ describe(commit.name, () => {
commit({
dryRun: false,
noVerify: false,
skipCommit: false,
commitMessage: 'chore(release): 1.0.0',
projectName: 'p',
})
Expand All @@ -43,6 +44,7 @@ describe(commit.name, () => {
commit({
dryRun: true,
noVerify: false,
skipCommit: false,
commitMessage: 'chore(release): 1.0.0',
projectName: 'p',
}).subscribe({
Expand All @@ -53,11 +55,27 @@ describe(commit.name, () => {
});
});

it('should skip commit with --skipCommit but do not complete the stream', (done) => {
commit({
dryRun: false,
noVerify: false,
skipCommit: true,
commitMessage: 'chore(release): 1.0.0',
projectName: 'p',
}).subscribe({
next: () => {
expect(cp.exec).not.toBeCalled();
done();
},
});
});

it('should pass --noVerify', async () => {
await lastValueFrom(
commit({
dryRun: false,
noVerify: true,
skipCommit: false,
commitMessage: 'chore(release): 1.0.0',
projectName: 'p',
})
Expand Down
9 changes: 8 additions & 1 deletion packages/semver/src/executors/version/utils/commit.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { of } from 'rxjs';
import { EMPTY, map, type Observable } from 'rxjs';
import { exec } from '../../common/exec';
import { logStep } from './logger';
Expand All @@ -6,18 +7,24 @@ import { createTemplateString } from './template-string';
export function commit({
dryRun,
noVerify,
skipCommit,
commitMessage,
projectName,
}: {
dryRun: boolean;
skipCommit: boolean;
noVerify: boolean;
commitMessage: string;
projectName: string;
}): Observable<void> {
}): Observable<void | null> {
if (dryRun) {
return EMPTY;
}

if (skipCommit) {
return of(null);
}

return exec('git', [
'commit',
...(noVerify ? ['--no-verify'] : []),
Expand Down
4 changes: 4 additions & 0 deletions packages/semver/src/executors/version/utils/git.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ describe('git', () => {
const tag = await lastValueFrom(
createTag({
dryRun: false,
commitHash: '123',
tag: 'project-a-1.0.0',
commitMessage: 'chore(release): 1.0.0',
projectName: 'p',
Expand All @@ -234,6 +235,7 @@ describe('git', () => {
'tag',
'-a',
'project-a-1.0.0',
'123',
'-m',
'chore(release): 1.0.0',
])
Expand All @@ -244,6 +246,7 @@ describe('git', () => {
createTag({
dryRun: true,
tag: 'project-a-1.0.0',
commitHash: '123',
commitMessage: 'chore(release): 1.0.0',
projectName: 'p',
}).subscribe({
Expand All @@ -266,6 +269,7 @@ describe('git', () => {
createTag({
dryRun: false,
tag: 'project-a-1.0.0',
commitHash: '123',
commitMessage: 'chore(release): 1.0.0',
projectName: 'p',
}).subscribe({
Expand Down
Loading

0 comments on commit 0533977

Please sign in to comment.