Skip to content

Commit 0cd36f1

Browse files
authored
chore(build): allow support releases MONGOSH-535 (#675)
1 parent 87efec0 commit 0cd36f1

File tree

7 files changed

+223
-87
lines changed

7 files changed

+223
-87
lines changed

packages/build/src/local/get-latest-tag.spec.ts

Lines changed: 45 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -10,61 +10,57 @@ describe('local get-latest-tag', () => {
1010
});
1111

1212
describe('getLatestDraftOrReleaseTagFromLog', () => {
13-
it('extracts the latest draft tag', () => {
14-
spawnSync.onFirstCall().returns({
15-
stdout: [
16-
'v0.7.9',
17-
'v0.8.0-draft.0',
18-
'v0.8.0-draft.1',
19-
'v0.8.0-draft.10',
20-
'v0.8.0-draft.2'
21-
].join('\n')
22-
});
23-
spawnSync.onSecondCall().returns({
24-
stdout: 'tagHash'
25-
});
26-
27-
const result = getLatestDraftOrReleaseTagFromLog(
28-
'somePath',
29-
spawnSync
30-
);
31-
expect(result).to.deep.equal({
32-
commit: 'tagHash',
33-
tag: {
34-
semverName: '0.8.0-draft.10',
13+
[
14+
{
15+
restriction: undefined,
16+
expected: {
17+
semverName: '0.8.1',
18+
releaseVersion: '0.8.1',
19+
draftVersion: undefined
20+
}
21+
},
22+
{
23+
restriction: { major: 0, minor: 8, patch: 0 },
24+
expected: {
25+
semverName: '0.8.0',
3526
releaseVersion: '0.8.0',
36-
draftVersion: 10
27+
draftVersion: undefined
3728
}
38-
});
39-
});
40-
41-
it('extracts the latest release tag', () => {
42-
spawnSync.onFirstCall().returns({
43-
stdout: [
44-
'v0.8.0',
45-
'v0.8.0-draft.0',
46-
'v0.8.0-draft.1',
47-
'v0.8.0-draft.10',
48-
'v0.8.1',
49-
'v0.8.0-draft.2',
50-
'v0.8.1-draft.0',
51-
].join('\n')
52-
});
53-
spawnSync.onSecondCall().returns({
54-
stdout: 'tagHash'
55-
});
56-
57-
const result = getLatestDraftOrReleaseTagFromLog(
58-
'somePath',
59-
spawnSync
60-
);
61-
expect(result).to.deep.equal({
62-
commit: 'tagHash',
63-
tag: {
29+
},
30+
{
31+
restriction: { major: 0, minor: 8, patch: undefined },
32+
expected: {
6433
semverName: '0.8.1',
6534
releaseVersion: '0.8.1',
6635
draftVersion: undefined
6736
}
37+
}
38+
].forEach(({ restriction, expected }) => {
39+
it(`extracts the latest tag when restricted to ${JSON.stringify(restriction)}`, () => {
40+
spawnSync.onFirstCall().returns({
41+
stdout: [
42+
'v0.8.0',
43+
'v0.8.0-draft.0',
44+
'v0.8.0-draft.1',
45+
'v0.8.0-draft.10',
46+
'v0.8.1',
47+
'v0.8.0-draft.2',
48+
'v0.8.1-draft.0',
49+
].join('\n')
50+
});
51+
spawnSync.onSecondCall().returns({
52+
stdout: 'tagHash'
53+
});
54+
55+
const result = getLatestDraftOrReleaseTagFromLog(
56+
'somePath',
57+
restriction,
58+
spawnSync
59+
);
60+
expect(result).to.deep.equal({
61+
commit: 'tagHash',
62+
tag: expected
63+
});
6864
});
6965
});
7066
});

packages/build/src/local/get-latest-tag.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,23 @@ export interface TagDetails {
1212
draftVersion: number | undefined;
1313
}
1414

15+
export interface ReleaseVersion {
16+
major: number | undefined;
17+
minor: number | undefined;
18+
patch: number | undefined;
19+
}
20+
1521
export function getLatestDraftOrReleaseTagFromLog(
1622
repositoryRoot: string,
23+
versionRestriction: ReleaseVersion | undefined,
1724
spawnSync: typeof spawnSyncFn = spawnSyncFn
1825
): TaggedCommit | undefined {
1926
const gitTags = spawnSync('git', ['tag'], {
2027
cwd: repositoryRoot,
2128
encoding: 'utf-8'
2229
});
2330

24-
const tagDetails = extractTags(gitTags.stdout.split('\n'));
31+
const tagDetails = extractTags(gitTags.stdout.split('\n'), versionRestriction);
2532
const sortedTagsWithCommit = tagDetails.sort((t1, t2) => {
2633
return -1 * semver.compare(t1.semverName, t2.semverName);
2734
});
@@ -42,7 +49,7 @@ export function getLatestDraftOrReleaseTagFromLog(
4249
};
4350
}
4451

45-
function extractTags(gitTags: string[]): TagDetails[] {
52+
function extractTags(gitTags: string[], versionRestriction: ReleaseVersion | undefined): TagDetails[] {
4653
const validTags = gitTags
4754
.map(tag => semver.valid(tag))
4855
.filter(v => !!v) as string[];
@@ -53,9 +60,27 @@ function extractTags(gitTags: string[]): TagDetails[] {
5360
return undefined;
5461
}
5562

63+
const major = semver.major(semverTag);
64+
const minor = semver.minor(semverTag);
65+
const patch = semver.patch(semverTag);
66+
67+
if (versionRestriction?.major !== undefined) {
68+
if (major !== versionRestriction.major) {
69+
return undefined;
70+
}
71+
if (versionRestriction.minor !== undefined) {
72+
if (minor !== versionRestriction.minor) {
73+
return undefined;
74+
}
75+
if (versionRestriction.patch !== undefined && versionRestriction.patch !== patch) {
76+
return undefined;
77+
}
78+
}
79+
}
80+
5681
return {
5782
semverName: semverTag,
58-
releaseVersion: `${semver.major(semverTag)}.${semver.minor(semverTag)}.${semver.patch(semverTag)}`,
83+
releaseVersion: `${major}.${minor}.${patch}`,
5984
draftVersion: prerelease ? parseInt(prerelease[1], 10) : undefined
6085
};
6186
}).filter(t => !!t) as TagDetails[];

packages/build/src/local/repository-status.spec.ts

Lines changed: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { expect } from 'chai';
22
import sinon from 'sinon';
3-
import { getRepositoryStatus, RepositoryStatus, verifyGitStatus } from './repository-status';
3+
import { getReleaseVersionFromBranch, getRepositoryStatus, RepositoryStatus, verifyGitStatus } from './repository-status';
44

55
describe('local repository-status', () => {
66
let spawnSync: sinon.SinonStub;
@@ -16,7 +16,7 @@ describe('local repository-status', () => {
1616
getRepositoryStatus = sinon.stub();
1717
});
1818

19-
[ 'master', 'main', 'v0.8.0', 'v0.8.x' ].forEach(branchName => {
19+
[ 'master', 'main', 'release/v0.8.0', 'release/v0.8.x' ].forEach(branchName => {
2020
it(`accepts a clean repository on ${branchName}`, () => {
2121
const status: RepositoryStatus = {
2222
branch: {
@@ -28,8 +28,8 @@ describe('local repository-status', () => {
2828
hasUnpushedTags: false
2929
};
3030
getRepositoryStatus.returns(status);
31-
verifyGitStatus('root', getRepositoryStatus);
32-
expect(getRepositoryStatus).to.have.been.calledOnce;
31+
const returnedStatus = verifyGitStatus('root', getRepositoryStatus);
32+
expect(returnedStatus).to.equal(status);
3333
});
3434
});
3535

@@ -150,23 +150,31 @@ describe('local repository-status', () => {
150150
});
151151

152152
describe('getRepositoryStatus', () => {
153-
it('parses a clean repository correctly', () => {
154-
spawnSync.returns({
155-
stdout: '## master...origin/master\n'
156-
});
157-
spawnSync.onSecondCall().returns({
158-
stdout: 'Everything up-to-date'
159-
});
153+
[
154+
'master',
155+
'main',
156+
'release/v0.7.x',
157+
'release/another-branch',
158+
'release/v0.7.9'
159+
].forEach(branch => {
160+
it('parses a clean repository correctly', () => {
161+
spawnSync.returns({
162+
stdout: `## ${branch}...origin/${branch}\n`
163+
});
164+
spawnSync.onSecondCall().returns({
165+
stdout: 'Everything up-to-date'
166+
});
160167

161-
const status = getRepositoryStatus('somePath', spawnSync);
162-
expect(status).to.deep.equal({
163-
branch: {
164-
local: 'master',
165-
tracking: 'origin/master',
166-
diverged: false
167-
},
168-
clean: true,
169-
hasUnpushedTags: false
168+
const status = getRepositoryStatus('somePath', spawnSync);
169+
expect(status).to.deep.equal({
170+
branch: {
171+
local: branch,
172+
tracking: `origin/${branch}`,
173+
diverged: false
174+
},
175+
clean: true,
176+
hasUnpushedTags: false
177+
});
170178
});
171179
});
172180

@@ -267,4 +275,29 @@ describe('local repository-status', () => {
267275
});
268276
});
269277
});
278+
279+
describe('getReleaseVersionFromDraft', () => {
280+
it('parses the release branch properly', () => {
281+
const version = getReleaseVersionFromBranch('release/v0.8.3');
282+
expect(version).to.deep.equal({
283+
major: 0,
284+
minor: 8,
285+
patch: 3
286+
});
287+
});
288+
289+
it('handles a release branch that is not fully numbered', () => {
290+
const version = getReleaseVersionFromBranch('release/v0.8.x');
291+
expect(version).to.deep.equal({
292+
major: 0,
293+
minor: 8,
294+
patch: undefined
295+
});
296+
});
297+
298+
it('returns undefined for non-release branches', () => {
299+
const version = getReleaseVersionFromBranch('master');
300+
expect(version).to.be.undefined;
301+
});
302+
});
270303
});

packages/build/src/local/repository-status.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,19 @@ export interface RepositoryStatus {
1111
hasUnpushedTags: boolean
1212
}
1313

14+
const MAIN_OR_MASTER_BRANCH = /^(main|master)$/;
15+
const RELEASE_BRANCH = /^release\/v([a-z0-9]+\.[a-z0-9]+\.[a-z0-9]+)$/;
16+
1417
export function verifyGitStatus(
1518
repositoryRoot: string,
1619
getRepositoryStatusFn: typeof getRepositoryStatus = getRepositoryStatus
17-
): void {
20+
): RepositoryStatus {
1821
const repositoryStatus = getRepositoryStatusFn(repositoryRoot);
1922
if (!repositoryStatus.branch?.local) {
2023
throw new Error('Could not determine local repository information - please verify your repository is intact.');
2124
}
22-
if (!/^(master|main|v[a-z0-9]+\.[a-z0-9]+\.[a-z0-9]+)$/.test(repositoryStatus.branch.local)) {
23-
throw new Error('The current branch does not match: master|main|vX.X.X');
25+
if (!MAIN_OR_MASTER_BRANCH.test(repositoryStatus.branch.local) && !RELEASE_BRANCH.test(repositoryStatus.branch.local)) {
26+
throw new Error('The current branch does not match: master|main|release/vX.X.X');
2427
}
2528
if (!repositoryStatus.branch.tracking) {
2629
throw new Error('The branch you are on is not tracking any remote branch.');
@@ -31,6 +34,7 @@ export function verifyGitStatus(
3134
if (repositoryStatus.hasUnpushedTags) {
3235
throw new Error('You have local tags that are not pushed to the remote. Remove or push those tags to continue.');
3336
}
37+
return repositoryStatus;
3438
}
3539

3640
export function getRepositoryStatus(
@@ -48,7 +52,7 @@ export function getRepositoryStatus(
4852

4953
const result: RepositoryStatus = {
5054
clean: true,
51-
hasUnpushedTags: tagStatus.stdout.trim() !== 'Everything up-to-date'
55+
hasUnpushedTags: (tagStatus.stdout?.trim() ?? '' + tagStatus.stderr?.trim() ?? '') !== 'Everything up-to-date'
5256
};
5357

5458
const output = gitStatus.stdout
@@ -57,13 +61,13 @@ export function getRepositoryStatus(
5761
.filter(l => !!l);
5862

5963
const branchOutput = output.find(l => l.match(/^## /));
60-
const branchInfo = branchOutput?.match(/^## ([^\s.]+)(\.\.\.([^\s]+)( \[[^\]]+])?)?/);
64+
const branchInfo = branchOutput?.match(/^## ([^\s]+?((?=\.\.\.)|$))(\.\.\.([^\s]+)( \[[^\]]+])?)?/);
6165

6266
if (branchInfo) {
6367
result.branch = {
6468
local: branchInfo[1],
65-
tracking: branchInfo[3],
66-
diverged: !!branchInfo[4]
69+
tracking: branchInfo[4],
70+
diverged: !!branchInfo[5]
6771
};
6872
}
6973

@@ -73,3 +77,22 @@ export function getRepositoryStatus(
7377
return result;
7478
}
7579

80+
export function getReleaseVersionFromBranch(branchName: string | undefined): { major: number | undefined, minor: number | undefined, patch: number | undefined} | undefined {
81+
const match = branchName?.match(RELEASE_BRANCH);
82+
if (!match) {
83+
return undefined;
84+
}
85+
86+
const versionParts = match[1].split('.');
87+
const numOrUndefiend = (num: string): number | undefined => {
88+
const value = parseInt(num, 10);
89+
return isNaN(value) ? undefined : value;
90+
};
91+
92+
return {
93+
major: numOrUndefiend(versionParts[0]),
94+
minor: numOrUndefiend(versionParts[1]),
95+
patch: numOrUndefiend(versionParts[2]),
96+
};
97+
}
98+

0 commit comments

Comments
 (0)