From 4d71154683a003982d3556b52b0d0b270fe2815a Mon Sep 17 00:00:00 2001 From: David Date: Wed, 28 Jun 2023 14:43:56 +1000 Subject: [PATCH 01/12] Add check label action --- check-label/action.yml | 14 ++++++++ src/check-label/index.ts | 78 ++++++++++++++++++++++++++++++++++++++++ src/github-api/index.ts | 54 ++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 check-label/action.yml create mode 100644 src/check-label/index.ts diff --git a/check-label/action.yml b/check-label/action.yml new file mode 100644 index 0000000..513eb9c --- /dev/null +++ b/check-label/action.yml @@ -0,0 +1,14 @@ +name: Check Label +description: Check package.json version bump matches label +inputs: + github-token: + description: Github token + required: true + default: ${{ github.token }} + command: + description: command to run + required: true +runs: + using: node16 + pre: '../setup.mjs' + main: ../build/check-label/index.js diff --git a/src/check-label/index.ts b/src/check-label/index.ts new file mode 100644 index 0000000..99fe398 --- /dev/null +++ b/src/check-label/index.ts @@ -0,0 +1,78 @@ +// check-label/index.ts + +import process from 'node:process'; +import path from 'node:path'; +import assert from 'node:assert'; +import { readFile } from 'node:fs/promises'; +import { debug } from 'debug'; +import { setFailed } from '@actions/core'; +import semver from 'semver'; + +import { getFileFromMain, getLabelsOnPR } from '../github-api'; + +const log = debug('check-label'); + +interface PackageJSON { + name: string; + version: string; + files: string[]; +} + +async function getLocalPackageJsonVersion(): Promise { + const packageJSONPath = path.join(process.cwd(), 'package.json'); + const readPackageJson = await readFile(packageJSONPath, 'utf8'); + const packageJson = JSON.parse(readPackageJson) as PackageJSON; + + return packageJson.version; +} + +export async function main(): Promise { + log('Action start'); + + const labelsPullRequest = await getLabelsOnPR(); + if (labelsPullRequest.length > 1) { + throw new Error('PR has more than one label'); + } + const label = labelsPullRequest[0]?.toLowerCase(); + assert(label, 'Unable to get label from PR'); + + const branchPackageJsonVersion = await getLocalPackageJsonVersion(); + const mainPackageJsonVersionRaw = await getFileFromMain(); + + if (!mainPackageJsonVersionRaw) { + throw new Error('Unable to get package.json from main branch'); + } + const mainPackageJsonVersion = JSON.parse(mainPackageJsonVersionRaw) as PackageJSON; + + const packageVersionDiff = semver.diff(branchPackageJsonVersion, mainPackageJsonVersion.version); + if (packageVersionDiff !== label) { + log( + `Main branch version: ${ + mainPackageJsonVersion.version + } vs branch version: ${branchPackageJsonVersion} - diff: ${String(packageVersionDiff)} - git label ${label}` + ); + setFailed(`PR has not had the package.json updated correctly`); + throw new Error('PR has not had the package.json updated correctly'); + } + + if (semver.gt(mainPackageJsonVersion.version, branchPackageJsonVersion)) { + log('Main branch version is greater than branch version'); + log(`Main branch version: ${mainPackageJsonVersion.version} vs branch version: ${branchPackageJsonVersion}`); + setFailed(`PR has not had the package.json updated correctly`); + throw new Error('PR has not had the package.json updated correctly'); + } +} + +main() + .then(() => { + process.stdin.destroy(); + // eslint-disable-next-line unicorn/no-process-exit + process.exit(0); + }) + // eslint-disable-next-line unicorn/prefer-top-level-await + .catch((error) => { + // eslint-disable-next-line no-console + console.log('Action Error - exit 1 - error:', error); + // eslint-disable-next-line unicorn/no-process-exit + process.exit(1); + }); diff --git a/src/github-api/index.ts b/src/github-api/index.ts index 1d750e1..509234e 100644 --- a/src/github-api/index.ts +++ b/src/github-api/index.ts @@ -52,6 +52,60 @@ export async function getPullRequestContext(): Promise { + if (!process.env['GITHUB_TOKEN']) { + log('getFileFromMain - GITHUB_TOKEN is not set - check action configuration'); + throw new Error(THROW_ACTION_ERROR_MESSAGE); + } + const octokat = new Octokit({ auth: process.env['GITHUB_TOKEN'] }); + + const githubContext = await getPullRequestContext(); + if (!githubContext) { + log('getFileFromMain Error - unable to get github context'); + throw new Error(THROW_UNABLE_TO_GET_CONTEXT); + } + + // get the labels attached to the PR + const { data } = (await octokat.rest.repos.getContent({ + owner: githubContext.owner, + repo: githubContext.repo, + path: 'package.json', + ref: 'main', + mediaType: { + format: 'raw', + }, + })) as { data: { content?: string } }; + + if (!data.content) { + return; + } + return Buffer.from(data.content, 'base64').toString('utf8'); +} + +export async function getLabelsOnPR(): Promise { + if (!process.env['GITHUB_TOKEN']) { + log('getLabelsOnPR - GITHUB_TOKEN is not set - check action configuration'); + throw new Error(THROW_ACTION_ERROR_MESSAGE); + } + const octokat = new Octokit({ auth: process.env['GITHUB_TOKEN'] }); + + const githubContext = await getPullRequestContext(); + if (!githubContext) { + log('getLabelsOnPR Error - unable to get github context'); + throw new Error(THROW_UNABLE_TO_GET_CONTEXT); + } + + // get the labels attached to the PR + const pullReqeust = await octokat.rest.pulls.get({ + owner: githubContext.owner, + repo: githubContext.repo, + // eslint-disable-next-line camelcase + pull_number: githubContext.number, + }); + + return pullReqeust.data.labels.map((label) => label.name); +} + export async function publishCommentAndRemovePrevious( message: string, prefixOfPreviousMessageToRemove?: string From c6115c2e24428f0a330b47a5ce5ed1d01c162b88 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 28 Jun 2023 15:08:33 +1000 Subject: [PATCH 02/12] Add debug --- src/github-api/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/github-api/index.ts b/src/github-api/index.ts index 509234e..631654a 100644 --- a/src/github-api/index.ts +++ b/src/github-api/index.ts @@ -76,6 +76,8 @@ export async function getFileFromMain(): Promise { }, })) as { data: { content?: string } }; + log('getFileFromMain - data', data); + if (!data.content) { return; } From 10fb9205d1a9d83d4f08cabb88023d513b736bd9 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 28 Jun 2023 15:18:22 +1000 Subject: [PATCH 03/12] Github types are completely wrong --- src/github-api/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/github-api/index.ts b/src/github-api/index.ts index 631654a..31fdbbb 100644 --- a/src/github-api/index.ts +++ b/src/github-api/index.ts @@ -74,14 +74,14 @@ export async function getFileFromMain(): Promise { mediaType: { format: 'raw', }, - })) as { data: { content?: string } }; + })) as unknown as { data: string }; log('getFileFromMain - data', data); - if (!data.content) { + if (!data) { return; } - return Buffer.from(data.content, 'base64').toString('utf8'); + return Buffer.from(data, 'base64').toString('utf8'); } export async function getLabelsOnPR(): Promise { From 65284f3919d03141b676299ad1c770b4adc04f00 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 28 Jun 2023 15:22:41 +1000 Subject: [PATCH 04/12] Github types are completely wrong --- src/github-api/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/github-api/index.ts b/src/github-api/index.ts index 31fdbbb..91b1de2 100644 --- a/src/github-api/index.ts +++ b/src/github-api/index.ts @@ -81,7 +81,7 @@ export async function getFileFromMain(): Promise { if (!data) { return; } - return Buffer.from(data, 'base64').toString('utf8'); + return data; } export async function getLabelsOnPR(): Promise { From 2697ddcecfbcdf32bf6a60a273055422e05c138b Mon Sep 17 00:00:00 2001 From: David Date: Wed, 28 Jun 2023 15:48:17 +1000 Subject: [PATCH 05/12] Check for package-lock not matching package.sjon --- src/check-label/index.ts | 11 +++++++---- src/github-api/index.ts | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/check-label/index.ts b/src/check-label/index.ts index 99fe398..f4139f8 100644 --- a/src/check-label/index.ts +++ b/src/check-label/index.ts @@ -18,8 +18,8 @@ interface PackageJSON { files: string[]; } -async function getLocalPackageJsonVersion(): Promise { - const packageJSONPath = path.join(process.cwd(), 'package.json'); +async function getLocalPackageJsonVersion(fileName: string): Promise { + const packageJSONPath = path.join(process.cwd(), fileName); const readPackageJson = await readFile(packageJSONPath, 'utf8'); const packageJson = JSON.parse(readPackageJson) as PackageJSON; @@ -36,8 +36,8 @@ export async function main(): Promise { const label = labelsPullRequest[0]?.toLowerCase(); assert(label, 'Unable to get label from PR'); - const branchPackageJsonVersion = await getLocalPackageJsonVersion(); - const mainPackageJsonVersionRaw = await getFileFromMain(); + const branchPackageJsonVersion = await getLocalPackageJsonVersion('package.json'); + const mainPackageJsonVersionRaw = await getFileFromMain('package.json'); if (!mainPackageJsonVersionRaw) { throw new Error('Unable to get package.json from main branch'); @@ -61,6 +61,9 @@ export async function main(): Promise { setFailed(`PR has not had the package.json updated correctly`); throw new Error('PR has not had the package.json updated correctly'); } + + const branchLockFile = await getLocalPackageJsonVersion('package-lock.json'); + assert.equal(branchLockFile, branchPackageJsonVersion, 'package.json and package-lock.json versions do not match'); } main() diff --git a/src/github-api/index.ts b/src/github-api/index.ts index 91b1de2..8662540 100644 --- a/src/github-api/index.ts +++ b/src/github-api/index.ts @@ -52,7 +52,7 @@ export async function getPullRequestContext(): Promise { +export async function getFileFromMain(filename: string): Promise { if (!process.env['GITHUB_TOKEN']) { log('getFileFromMain - GITHUB_TOKEN is not set - check action configuration'); throw new Error(THROW_ACTION_ERROR_MESSAGE); @@ -69,7 +69,7 @@ export async function getFileFromMain(): Promise { const { data } = (await octokat.rest.repos.getContent({ owner: githubContext.owner, repo: githubContext.repo, - path: 'package.json', + path: filename, ref: 'main', mediaType: { format: 'raw', From 0813f43b453e8f6e5dcc90c3444e9ad68a332bd1 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 29 Jun 2023 17:15:40 +1000 Subject: [PATCH 06/12] Add tests and refactor --- src/check-label/check-label.spec.ts | 96 +++++++++++++++++++++++++++++ src/check-label/check-label.ts | 66 ++++++++++++++++++++ src/check-label/index.ts | 66 +------------------- src/github-api/index.ts | 1 - src/nocks/github.test.ts | 36 ++++++++++- 5 files changed, 199 insertions(+), 66 deletions(-) create mode 100644 src/check-label/check-label.spec.ts create mode 100644 src/check-label/check-label.ts diff --git a/src/check-label/check-label.spec.ts b/src/check-label/check-label.spec.ts new file mode 100644 index 0000000..eec9c4a --- /dev/null +++ b/src/check-label/check-label.spec.ts @@ -0,0 +1,96 @@ +// check-label/check-label.spec.ts + +import { strict as assert } from 'node:assert'; +import process from 'node:process'; +import path from 'node:path'; +import { tmpdir } from 'node:os'; +import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'; +import { v4 as uuid } from 'uuid'; + +import gitHubNock from '../nocks/github.test'; +import checkLabel from './check-label'; + +async function createContext(prNumber: number) { + process.env['GITHUB_REPOSITORY'] = 'checkdigit/testlabel'; + const filePath = path.join(tmpdir(), 'actioncontexttestlabel', uuid()); + await writeFile( + filePath, + JSON.stringify({ + // eslint-disable-next-line camelcase + pull_request: { + number: prNumber, + }, + }) + ); + process.env['GITHUB_EVENT_PATH'] = filePath; +} + +function semverSubtract(version: string, versionLabel: 'patch' | 'major' | 'minor'): string { + const versionParts = version.split('.'); + if (versionLabel === 'major' && Number(versionParts[1]) !== 0) { + versionParts[0] = (Number(versionParts[0]) - 1).toString(); + } + + if (versionLabel === 'minor' && Number(versionParts[1]) !== 0) { + versionParts[1] = (Number(versionParts[1]) - 1).toString(); + } + + if (versionLabel === 'patch' && Number(versionParts[2]) !== 0) { + versionParts[2] = (Number(versionParts[2]) - 1).toString(); + } + + return versionParts.join('.'); +} + +describe('check label', () => { + beforeAll(async () => mkdir(path.join(tmpdir(), 'actioncontexttestlabel'))); + afterAll(async () => rm(path.join(tmpdir(), 'actioncontexttestlabel'), { recursive: true })); + + it('Test with no labels throws correctly', async () => { + // assert that the call to checkLabel rejects a promise + await assert.rejects(checkLabel(), { + message: 'incorrect action configuration', + }); + }); + + it('Test with no labels throws correctly on missing context', async () => { + process.env['GITHUB_TOKEN'] = 'token 0000000000000000000000000000000000000001'; + // assert that the call to checkLabel rejects a promise + await assert.rejects(checkLabel(), { + message: 'unable to get context', + }); + }); + + it('label matches - patch', async () => { + process.env['GITHUB_TOKEN'] = 'token 0000000000000000000000000000000000000001'; + + const packageJsonRaw = await readFile(path.join(process.cwd(), 'package.json'), 'utf8'); + const packageJson = JSON.parse(packageJsonRaw); + + const targetVersion = semverSubtract(packageJson.version, 'patch'); + assert(targetVersion !== null); + gitHubNock({ labelPackageVersionMain: targetVersion }); + + await createContext(10); + // assert that the call to checkLabel rejects a promise + await assert.doesNotReject(checkLabel()); + }); + + it('label does not match - should be major but is patch', async () => { + process.env['GITHUB_TOKEN'] = 'token 0000000000000000000000000000000000000001'; + + const packageJsonRaw = await readFile(path.join(process.cwd(), 'package.json'), 'utf8'); + const packageJson = JSON.parse(packageJsonRaw); + + const targetVersion = semverSubtract(packageJson.version, 'patch'); + assert(targetVersion !== null); + gitHubNock({ labelPackageVersionMain: targetVersion }); + + await createContext(11); + // assert that the call to checkLabel rejects a promise + + await assert.rejects(checkLabel(), { + message: 'PR has not had the package.json updated correctly', + }); + }); +}); diff --git a/src/check-label/check-label.ts b/src/check-label/check-label.ts new file mode 100644 index 0000000..a61ba4c --- /dev/null +++ b/src/check-label/check-label.ts @@ -0,0 +1,66 @@ +// check-label/check-label.ts + +import process from 'node:process'; +import path from 'node:path'; +import assert from 'node:assert'; +import { readFile } from 'node:fs/promises'; +import { debug } from 'debug'; +import { setFailed } from '@actions/core'; +import semver from 'semver'; + +import { getFileFromMain, getLabelsOnPR } from '../github-api'; + +const log = debug('check-label'); + +interface PackageJSON { + name: string; + version: string; + files: string[]; +} + +async function getLocalPackageJsonVersion(fileName: string): Promise { + const packageJSONPath = path.join(process.cwd(), fileName); + const readPackageJson = await readFile(packageJSONPath, 'utf8'); + const packageJson = JSON.parse(readPackageJson) as PackageJSON; + + return packageJson.version; +} +export default async function (): Promise { + log('Action start'); + + const labelsPullRequest = await getLabelsOnPR(); + if (labelsPullRequest.length > 1) { + throw new Error('PR has more than one label'); + } + const label = labelsPullRequest[0]?.toLowerCase(); + assert(label, 'Unable to get label from PR'); + + const branchPackageJsonVersion = await getLocalPackageJsonVersion('package.json'); + const mainPackageJsonVersionRaw = await getFileFromMain('package.json'); + + if (!mainPackageJsonVersionRaw) { + throw new Error('Unable to get package.json from main branch'); + } + const mainPackageJsonVersion = JSON.parse(mainPackageJsonVersionRaw) as PackageJSON; + + const packageVersionDiff = semver.diff(branchPackageJsonVersion, mainPackageJsonVersion.version); + if (packageVersionDiff !== label) { + log( + `Main branch version: ${ + mainPackageJsonVersion.version + } vs branch version: ${branchPackageJsonVersion} - diff: ${String(packageVersionDiff)} - git label ${label}` + ); + setFailed(`PR has not had the package.json updated correctly`); + throw new Error('PR has not had the package.json updated correctly'); + } + + if (semver.gt(mainPackageJsonVersion.version, branchPackageJsonVersion)) { + log('Main branch version is greater than branch version'); + log(`Main branch version: ${mainPackageJsonVersion.version} vs branch version: ${branchPackageJsonVersion}`); + setFailed(`PR has not had the package.json updated correctly`); + throw new Error('PR has not had the package.json updated correctly'); + } + + const branchLockFile = await getLocalPackageJsonVersion('package-lock.json'); + assert.equal(branchLockFile, branchPackageJsonVersion, 'package.json and package-lock.json versions do not match'); +} diff --git a/src/check-label/index.ts b/src/check-label/index.ts index f4139f8..f8caa9e 100644 --- a/src/check-label/index.ts +++ b/src/check-label/index.ts @@ -1,72 +1,10 @@ // check-label/index.ts import process from 'node:process'; -import path from 'node:path'; -import assert from 'node:assert'; -import { readFile } from 'node:fs/promises'; -import { debug } from 'debug'; -import { setFailed } from '@actions/core'; -import semver from 'semver'; -import { getFileFromMain, getLabelsOnPR } from '../github-api'; +import checkLabel from './check-label'; -const log = debug('check-label'); - -interface PackageJSON { - name: string; - version: string; - files: string[]; -} - -async function getLocalPackageJsonVersion(fileName: string): Promise { - const packageJSONPath = path.join(process.cwd(), fileName); - const readPackageJson = await readFile(packageJSONPath, 'utf8'); - const packageJson = JSON.parse(readPackageJson) as PackageJSON; - - return packageJson.version; -} - -export async function main(): Promise { - log('Action start'); - - const labelsPullRequest = await getLabelsOnPR(); - if (labelsPullRequest.length > 1) { - throw new Error('PR has more than one label'); - } - const label = labelsPullRequest[0]?.toLowerCase(); - assert(label, 'Unable to get label from PR'); - - const branchPackageJsonVersion = await getLocalPackageJsonVersion('package.json'); - const mainPackageJsonVersionRaw = await getFileFromMain('package.json'); - - if (!mainPackageJsonVersionRaw) { - throw new Error('Unable to get package.json from main branch'); - } - const mainPackageJsonVersion = JSON.parse(mainPackageJsonVersionRaw) as PackageJSON; - - const packageVersionDiff = semver.diff(branchPackageJsonVersion, mainPackageJsonVersion.version); - if (packageVersionDiff !== label) { - log( - `Main branch version: ${ - mainPackageJsonVersion.version - } vs branch version: ${branchPackageJsonVersion} - diff: ${String(packageVersionDiff)} - git label ${label}` - ); - setFailed(`PR has not had the package.json updated correctly`); - throw new Error('PR has not had the package.json updated correctly'); - } - - if (semver.gt(mainPackageJsonVersion.version, branchPackageJsonVersion)) { - log('Main branch version is greater than branch version'); - log(`Main branch version: ${mainPackageJsonVersion.version} vs branch version: ${branchPackageJsonVersion}`); - setFailed(`PR has not had the package.json updated correctly`); - throw new Error('PR has not had the package.json updated correctly'); - } - - const branchLockFile = await getLocalPackageJsonVersion('package-lock.json'); - assert.equal(branchLockFile, branchPackageJsonVersion, 'package.json and package-lock.json versions do not match'); -} - -main() +checkLabel() .then(() => { process.stdin.destroy(); // eslint-disable-next-line unicorn/no-process-exit diff --git a/src/github-api/index.ts b/src/github-api/index.ts index 8662540..59c33d3 100644 --- a/src/github-api/index.ts +++ b/src/github-api/index.ts @@ -97,7 +97,6 @@ export async function getLabelsOnPR(): Promise { throw new Error(THROW_UNABLE_TO_GET_CONTEXT); } - // get the labels attached to the PR const pullReqeust = await octokat.rest.pulls.get({ owner: githubContext.owner, repo: githubContext.repo, diff --git a/src/nocks/github.test.ts b/src/nocks/github.test.ts index c865743..62dbffd 100644 --- a/src/nocks/github.test.ts +++ b/src/nocks/github.test.ts @@ -1,7 +1,10 @@ // nocks/github.test.ts import nock from 'nock'; -export default function (): void { +export interface GithubNock { + labelPackageVersionMain?: string; +} +export default function (options?: GithubNock): void { nock('https://api.github.com/').persist().get('/repos/checkdigit/nocomments/issues/10/comments').reply(200); nock('https://api.github.com/').persist().post('/repos/checkdigit/nocomments/issues/10/comments').reply(200); @@ -102,6 +105,37 @@ export default function (): void { }, })); + // return label + nock('https://api.github.com/') + .persist() + .get('/repos/checkdigit/testlabel/pulls/10') + .reply(200, () => ({ + labels: [{ name: 'patch' }], + })); + + nock('https://api.github.com/') + .persist() + .get('/repos/checkdigit/testlabel/pulls/11') + .reply(200, () => ({ + labels: [{ name: 'major' }], + })); + + // return a raw package json file + nock('https://api.github.com/') + .persist() + .get('/repos/checkdigit/testlabel/contents/package.json?ref=main') + .reply(200, () => { + if (options?.labelPackageVersionMain) { + return `{"version": "${options.labelPackageVersionMain}"}`; + } + return '{"version": "1.0.0"}'; + }); + + nock('https://api.github.com/') + .persist() + .get('/repos/checkdigit/testlabel/contents/package-lock.json?ref=main') + .reply(200, () => '{"version": "1.0.0"}'); + // allow delete operations to the two comments that should be deleted nock('https://api.github.com/').persist().delete('/repos/checkdigit/comments/issues/comments/1').reply(200); nock('https://api.github.com/').persist().delete('/repos/checkdigit/comments/issues/comments/3').reply(200); From 23bb3b20dbde4ed68cc27cc0a2e4970a39cad43b Mon Sep 17 00:00:00 2001 From: David Date: Fri, 30 Jun 2023 13:21:40 +1000 Subject: [PATCH 07/12] Fix tests --- src/check-label/check-label.spec.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/check-label/check-label.spec.ts b/src/check-label/check-label.spec.ts index eec9c4a..331205b 100644 --- a/src/check-label/check-label.spec.ts +++ b/src/check-label/check-label.spec.ts @@ -43,7 +43,10 @@ function semverSubtract(version: string, versionLabel: 'patch' | 'major' | 'mino } describe('check label', () => { - beforeAll(async () => mkdir(path.join(tmpdir(), 'actioncontexttestlabel'))); + beforeAll(async () => { + await mkdir(path.join(tmpdir(), 'actioncontexttestlabel')); + delete process.env['GITHUB_TOKEN']; + }); afterAll(async () => rm(path.join(tmpdir(), 'actioncontexttestlabel'), { recursive: true })); it('Test with no labels throws correctly', async () => { From cf0bf34c6514aea360b9fea318898c52fd3c2e1f Mon Sep 17 00:00:00 2001 From: David Date: Fri, 30 Jun 2023 13:33:31 +1000 Subject: [PATCH 08/12] Fix tests - handle github action config --- src/check-label/check-label.spec.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/check-label/check-label.spec.ts b/src/check-label/check-label.spec.ts index 331205b..1789ab0 100644 --- a/src/check-label/check-label.spec.ts +++ b/src/check-label/check-label.spec.ts @@ -43,17 +43,12 @@ function semverSubtract(version: string, versionLabel: 'patch' | 'major' | 'mino } describe('check label', () => { - beforeAll(async () => { - await mkdir(path.join(tmpdir(), 'actioncontexttestlabel')); - delete process.env['GITHUB_TOKEN']; - }); + beforeAll(async () => mkdir(path.join(tmpdir(), 'actioncontexttestlabel'))); afterAll(async () => rm(path.join(tmpdir(), 'actioncontexttestlabel'), { recursive: true })); it('Test with no labels throws correctly', async () => { // assert that the call to checkLabel rejects a promise - await assert.rejects(checkLabel(), { - message: 'incorrect action configuration', - }); + await assert.rejects(checkLabel()); }); it('Test with no labels throws correctly on missing context', async () => { From 75e60f7ff417903a0b3a1de62024f24bb2970eec Mon Sep 17 00:00:00 2001 From: David Date: Fri, 30 Jun 2023 13:36:30 +1000 Subject: [PATCH 09/12] be less specific about exception thrown --- src/check-label/check-label.spec.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/check-label/check-label.spec.ts b/src/check-label/check-label.spec.ts index 1789ab0..e4df796 100644 --- a/src/check-label/check-label.spec.ts +++ b/src/check-label/check-label.spec.ts @@ -51,14 +51,6 @@ describe('check label', () => { await assert.rejects(checkLabel()); }); - it('Test with no labels throws correctly on missing context', async () => { - process.env['GITHUB_TOKEN'] = 'token 0000000000000000000000000000000000000001'; - // assert that the call to checkLabel rejects a promise - await assert.rejects(checkLabel(), { - message: 'unable to get context', - }); - }); - it('label matches - patch', async () => { process.env['GITHUB_TOKEN'] = 'token 0000000000000000000000000000000000000001'; From c9d361b75f9d39bbd17db99cbd496177202fb7af Mon Sep 17 00:00:00 2001 From: David Date: Fri, 30 Jun 2023 14:54:04 +1000 Subject: [PATCH 10/12] Expand semver check for minor and major versions --- .../check-label-compare-match-semver.spec.ts | 35 +++++++++++++++++++ src/check-label/check-label.ts | 24 ++++++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 src/check-label/check-label-compare-match-semver.spec.ts diff --git a/src/check-label/check-label-compare-match-semver.spec.ts b/src/check-label/check-label-compare-match-semver.spec.ts new file mode 100644 index 0000000..47731f2 --- /dev/null +++ b/src/check-label/check-label-compare-match-semver.spec.ts @@ -0,0 +1,35 @@ +// check-label/check-label-compare-match-semver.spec.ts + +import { strict as assert } from 'node:assert'; + +import { validateVersionCompareMatchesSemver } from './check-label'; + +describe('compare and match semver', () => { + it('Test basic patch', async () => { + assert.equal(validateVersionCompareMatchesSemver('1.0.1', '1.0.0'), 'patch'); + assert.equal(validateVersionCompareMatchesSemver('1.0.0', '1.0.0'), null); + }); + + it('Test minor', async () => { + assert.equal(validateVersionCompareMatchesSemver('1.1.0', '1.0.2'), 'minor'); + + assert.throws(() => validateVersionCompareMatchesSemver('1.1.1', '1.0.1'), { + message: 'Minor version bump but patch version is not 0', + }); + assert.throws(() => validateVersionCompareMatchesSemver('1.1.2', '1.0.1'), { + message: 'Minor version bump but patch version is not 0', + }); + }); + + it('Test major', async () => { + assert.equal(validateVersionCompareMatchesSemver('2.0.0', '1.0.2'), 'major'); + + assert.throws(() => validateVersionCompareMatchesSemver('2.20.0', '1.20.1'), { + message: 'Major version bump but minor and patch version is not 0', + }); + + assert.throws(() => validateVersionCompareMatchesSemver('2.0.1', '1.20.1'), { + message: 'Major version bump but minor and patch version is not 0', + }); + }); +}); diff --git a/src/check-label/check-label.ts b/src/check-label/check-label.ts index a61ba4c..2afa069 100644 --- a/src/check-label/check-label.ts +++ b/src/check-label/check-label.ts @@ -25,6 +25,25 @@ async function getLocalPackageJsonVersion(fileName: string): Promise { return packageJson.version; } + +export function validateVersionCompareMatchesSemver( + branchPackageJsonVersion: string, + mainPackageJsonVersion: string +): semver.ReleaseType | null { + const semVersionDiff = semver.diff(branchPackageJsonVersion, mainPackageJsonVersion); + const newVersionList = branchPackageJsonVersion.split('.'); + + if (semVersionDiff === 'minor' && Number(newVersionList[2]) !== 0) { + throw new Error('Minor version bump but patch version is not 0'); + } + + if (semVersionDiff === 'major' && (Number(newVersionList[1]) !== 0 || Number(newVersionList[2]) !== 0)) { + throw new Error('Major version bump but minor and patch version is not 0'); + } + + return semVersionDiff; +} + export default async function (): Promise { log('Action start'); @@ -43,7 +62,10 @@ export default async function (): Promise { } const mainPackageJsonVersion = JSON.parse(mainPackageJsonVersionRaw) as PackageJSON; - const packageVersionDiff = semver.diff(branchPackageJsonVersion, mainPackageJsonVersion.version); + const packageVersionDiff = validateVersionCompareMatchesSemver( + branchPackageJsonVersion, + mainPackageJsonVersion.version + ); if (packageVersionDiff !== label) { log( `Main branch version: ${ From 0ee5816afca28f8078bb25963b00338a3d6485e9 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 6 Jul 2023 14:10:27 +1000 Subject: [PATCH 11/12] Validation version improvements --- .../check-label-compare-match-semver.spec.ts | 49 +++++++++++----- src/check-label/check-label.ts | 58 ++++++++----------- 2 files changed, 61 insertions(+), 46 deletions(-) diff --git a/src/check-label/check-label-compare-match-semver.spec.ts b/src/check-label/check-label-compare-match-semver.spec.ts index 47731f2..b22c7c9 100644 --- a/src/check-label/check-label-compare-match-semver.spec.ts +++ b/src/check-label/check-label-compare-match-semver.spec.ts @@ -2,34 +2,57 @@ import { strict as assert } from 'node:assert'; -import { validateVersionCompareMatchesSemver } from './check-label'; +import { validateVersion } from './check-label'; + +const assertError = 'Version is incorrect based on Pull Request label'; describe('compare and match semver', () => { it('Test basic patch', async () => { - assert.equal(validateVersionCompareMatchesSemver('1.0.1', '1.0.0'), 'patch'); - assert.equal(validateVersionCompareMatchesSemver('1.0.0', '1.0.0'), null); + assert.equal(validateVersion('1.0.1', '1.0.0', 'patch'), true); + + assert.throws(() => validateVersion('1.0.0', '1.0.0', 'patch'), { + message: assertError, + }); }); it('Test minor', async () => { - assert.equal(validateVersionCompareMatchesSemver('1.1.0', '1.0.2'), 'minor'); + assert.equal(validateVersion('1.1.0', '1.0.2', 'minor'), true); - assert.throws(() => validateVersionCompareMatchesSemver('1.1.1', '1.0.1'), { - message: 'Minor version bump but patch version is not 0', + assert.throws(() => validateVersion('1.1.1', '1.0.1', 'minor'), { + message: assertError, }); - assert.throws(() => validateVersionCompareMatchesSemver('1.1.2', '1.0.1'), { - message: 'Minor version bump but patch version is not 0', + assert.throws(() => validateVersion('1.1.2', '1.0.1', 'minor'), { + message: assertError, }); }); it('Test major', async () => { - assert.equal(validateVersionCompareMatchesSemver('2.0.0', '1.0.2'), 'major'); + assert.equal(validateVersion('2.0.0', '1.0.2', 'major'), true); + + assert.throws(() => validateVersion('2.20.0', '1.20.1', 'major'), { + message: assertError, + }); + + assert.throws(() => validateVersion('2.0.1', '1.20.1', 'major'), { + message: assertError, + }); + }); + + it('Test major fails with large version gap', async () => { + assert.throws(() => validateVersion('8.0.0', '1.0.0', 'major'), { + message: assertError, + }); + }); - assert.throws(() => validateVersionCompareMatchesSemver('2.20.0', '1.20.1'), { - message: 'Major version bump but minor and patch version is not 0', + it('Test throws when main is ahead', async () => { + assert.throws(() => validateVersion('8.0.0', '9.0.0', 'major'), { + message: 'main version is ahead of branch version', }); + }); - assert.throws(() => validateVersionCompareMatchesSemver('2.0.1', '1.20.1'), { - message: 'Major version bump but minor and patch version is not 0', + it('Test invalid label', async () => { + assert.throws(() => validateVersion('1.0.0', '1.0.0', 'invalid'), { + message: 'Invalid label', }); }); }); diff --git a/src/check-label/check-label.ts b/src/check-label/check-label.ts index 2afa069..13f4fc2 100644 --- a/src/check-label/check-label.ts +++ b/src/check-label/check-label.ts @@ -2,10 +2,9 @@ import process from 'node:process'; import path from 'node:path'; -import assert from 'node:assert'; +import { strict as assert } from 'node:assert'; import { readFile } from 'node:fs/promises'; import { debug } from 'debug'; -import { setFailed } from '@actions/core'; import semver from 'semver'; import { getFileFromMain, getLabelsOnPR } from '../github-api'; @@ -26,22 +25,34 @@ async function getLocalPackageJsonVersion(fileName: string): Promise { return packageJson.version; } -export function validateVersionCompareMatchesSemver( +export function validateVersion( branchPackageJsonVersion: string, - mainPackageJsonVersion: string -): semver.ReleaseType | null { - const semVersionDiff = semver.diff(branchPackageJsonVersion, mainPackageJsonVersion); - const newVersionList = branchPackageJsonVersion.split('.'); - - if (semVersionDiff === 'minor' && Number(newVersionList[2]) !== 0) { - throw new Error('Minor version bump but patch version is not 0'); + mainPackageJsonVersion: string, + prLabel: string +): true { + if (semver.gt(mainPackageJsonVersion, branchPackageJsonVersion)) { + log(`Main branch version: ${mainPackageJsonVersion} vs branch version: ${branchPackageJsonVersion}`); + throw new Error('main version is ahead of branch version'); } - if (semVersionDiff === 'major' && (Number(newVersionList[1]) !== 0 || Number(newVersionList[2]) !== 0)) { - throw new Error('Major version bump but minor and patch version is not 0'); + const mainVersionSplit = mainPackageJsonVersion.split('.'); + + if (prLabel === 'patch') { + mainVersionSplit[2] = (Number(mainVersionSplit[2]) + 1).toString(); + } else if (prLabel === 'minor') { + mainVersionSplit[1] = (Number(mainVersionSplit[1]) + 1).toString(); + mainVersionSplit[2] = '0'; + } else if (prLabel === 'major') { + mainVersionSplit[0] = (Number(mainVersionSplit[0]) + 1).toString(); + mainVersionSplit[1] = '0'; + mainVersionSplit[2] = '0'; + } else { + throw new Error('Invalid label'); } - return semVersionDiff; + const expectedVersion = mainVersionSplit.join('.'); + assert.equal(expectedVersion, branchPackageJsonVersion, 'Version is incorrect based on Pull Request label'); + return true; } export default async function (): Promise { @@ -62,26 +73,7 @@ export default async function (): Promise { } const mainPackageJsonVersion = JSON.parse(mainPackageJsonVersionRaw) as PackageJSON; - const packageVersionDiff = validateVersionCompareMatchesSemver( - branchPackageJsonVersion, - mainPackageJsonVersion.version - ); - if (packageVersionDiff !== label) { - log( - `Main branch version: ${ - mainPackageJsonVersion.version - } vs branch version: ${branchPackageJsonVersion} - diff: ${String(packageVersionDiff)} - git label ${label}` - ); - setFailed(`PR has not had the package.json updated correctly`); - throw new Error('PR has not had the package.json updated correctly'); - } - - if (semver.gt(mainPackageJsonVersion.version, branchPackageJsonVersion)) { - log('Main branch version is greater than branch version'); - log(`Main branch version: ${mainPackageJsonVersion.version} vs branch version: ${branchPackageJsonVersion}`); - setFailed(`PR has not had the package.json updated correctly`); - throw new Error('PR has not had the package.json updated correctly'); - } + validateVersion(branchPackageJsonVersion, mainPackageJsonVersion.version, label); const branchLockFile = await getLocalPackageJsonVersion('package-lock.json'); assert.equal(branchLockFile, branchPackageJsonVersion, 'package.json and package-lock.json versions do not match'); From cc4eff99001b945b45cd803836d885543047ee74 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 6 Jul 2023 14:18:07 +1000 Subject: [PATCH 12/12] Update test to support new response --- src/check-label/check-label.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/check-label/check-label.spec.ts b/src/check-label/check-label.spec.ts index e4df796..0b0bfd0 100644 --- a/src/check-label/check-label.spec.ts +++ b/src/check-label/check-label.spec.ts @@ -80,7 +80,7 @@ describe('check label', () => { // assert that the call to checkLabel rejects a promise await assert.rejects(checkLabel(), { - message: 'PR has not had the package.json updated correctly', + message: 'Version is incorrect based on Pull Request label', }); }); });