Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/workflows/ci.yml
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be updated to use 20.x/22.x now

Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: ['20.x', '21.x']
node-version: ['20.x', '22.x']
steps:
- name: Checkout Code
uses: actions/checkout@v4
Expand All @@ -30,13 +30,15 @@ jobs:
run: npm run ci:lint
- name: Run Tests
run: npm run ci:test
env:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needed to pass tests which access npm

NPM_TOKEN: ${{ secrets.NPM_TOKEN_PUBLISH }}

branchBuild:
name: Branch Build
runs-on: ubuntu-latest
strategy:
matrix:
node-version: ['20.x', '21.x']
node-version: ['20.x', '22.x']
steps:
- name: Checkout Code
uses: actions/checkout@v4
Expand All @@ -57,3 +59,5 @@ jobs:
run: npm run ci:lint
- name: Run Tests
run: npm run ci:test
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN_PUBLISH }}
2 changes: 2 additions & 0 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ jobs:
run: npm ci --ignore-scripts
- name: Calculate Code Coverage
run: npm run ci:coverage
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN_PUBLISH }}
- name: Create Coverage Report for base branch
run: |
mv coverage/lcov.info coverage/lcov_head.info
Expand Down
725 changes: 392 additions & 333 deletions package-lock.json

Large diffs are not rendered by default.

19 changes: 9 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@checkdigit/github-actions",
"version": "2.0.0",
"version": "2.1.0",
"description": " Provides supporting operations for github action builds.",
"author": "Check Digit, LLC",
"license": "MIT",
Expand All @@ -20,23 +20,22 @@
"dependencies": {
"@actions/core": "^1.10.1",
"@actions/github": "^6.0.0",
"@octokit/rest": "^20.1.0",
"debug": "^4.3.4",
"semver": "^7.6.0",
"undici": "^6.11.1"
"@octokit/rest": "^20.1.1",
"debug": "^4.3.5",
"semver": "^7.6.2",
"undici": "^6.18.2",
"uuid": "^9.0.1"
},
"devDependencies": {
"@checkdigit/eslint-config": "^9.2.0",
"@checkdigit/jest-config": "^6.0.0",
"@checkdigit/prettier-config": "^5.3.0",
"@checkdigit/jest-config": "^6.0.2",
"@checkdigit/prettier-config": "^5.4.0",
"@checkdigit/typescript-config": "^7.0.1",
"@types/debug": "^4.1.12",
"@types/semver": "^7.5.8",
"@types/uuid": "^9.0.8",
"esbuild": "^0.20.2",
"nock": "^14.0.0-beta.5",
"rimraf": "^5.0.5",
"uuid": "^9.0.1"
"rimraf": "^5.0.7"
},
"scripts": {
"build:dist-mjs": "rimraf dist-mjs && npx builder --type=module --sourceMap --outDir=dist-mjs",
Expand Down
38 changes: 38 additions & 0 deletions src/check-imports/check-imports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// check-imports/check-imports.ts

import { strict as assert } from 'node:assert';

import debug from 'debug';

import { extractPackageName, getPackageLock, satisfiesNameAndRange } from './package-lock-file-util';
import notAllowed from './packages-not-allowed';

const log = debug('github-actions:check-imports');

export default async function main(): Promise<void> {
log('Action starting');

const { packages } = await getPackageLock(process.cwd());

log('Reviewing package-lock');
for (const key in packages) {
if (Object.hasOwn(packages, key)) {
const descriptor = packages[key];
assert.ok(descriptor !== undefined, 'Package version is missing');
const packageVersion = descriptor.version;
const packageName = extractPackageName(key);

for (const [name, range, reason] of notAllowed) {
if (satisfiesNameAndRange(packageName, packageVersion, [name, range])) {
throw new Error(
`Package ${packageName}@${packageVersion} is not allowed to be imported because it is included in ${JSON.stringify(
[name, range],
)}. Package ${name}@${range} is not allowed for the following reason: ${reason}`,
);
}
}
}
}

log('Action end');
}
48 changes: 2 additions & 46 deletions src/check-imports/index.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,5 @@
// check-imports/index.ts

import { strict as assert } from 'node:assert';
import main from './check-imports';

import debug from 'debug';

import { extractPackageName, getPackageLock, satisfiesNameAndRange } from './package-lock-file-util';
import notAllowed from './packages-not-allowed';

const log = debug('check-imports');
export async function main(): Promise<void> {
log('Action starting');

const { packages } = await getPackageLock(process.cwd());

log('Reviewing package-lock');
for (const key in packages) {
if (Object.hasOwn(packages, key)) {
const descriptor = packages[key];
assert.ok(descriptor !== undefined, 'Package version is missing');
const packageVersion = descriptor.version;
const packageName = extractPackageName(key);

for (const [name, range, reason] of notAllowed) {
if (satisfiesNameAndRange(packageName, packageVersion, [name, range])) {
throw new Error(
`Package ${packageName}@${packageVersion} is not allowed to be imported because it is included in ${JSON.stringify(
[name, range],
)}. Package ${name}@${range} is not allowed for the following reason: ${reason}`,
);
}
}
}
}
}

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);
});
await main();
22 changes: 9 additions & 13 deletions src/check-imports/package-lock-file-util.spec.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,23 @@
// check-imports/package-lock-file-util.spec.ts

import { strict as assert } from 'node:assert';
import { mkdir, rm, writeFile } from 'node:fs/promises';
import { promises as fs } from 'node:fs';
import path from 'node:path';
import { tmpdir } from 'node:os';
import os from 'node:os';

import { afterAll, beforeAll, describe, it } from '@jest/globals';
import { describe, it } from '@jest/globals';
import { v4 as uuid } from 'uuid';

import examplePackageLock from './example-package-lock.json';
import { extractPackageName, getPackageLock, satisfiesNameAndRange } from './package-lock-file-util';

describe('package lock file utilities', () => {
beforeAll(async () => {
await mkdir(path.join(tmpdir(), 'temporaryDirectory'), { recursive: true });
});

afterAll(async () => {
await rm(path.join(tmpdir(), 'temporaryDirectory'), { recursive: true });
});

it('can get a package-lock file', async () => {
await writeFile(path.join(tmpdir(), 'temporaryDirectory/package-lock.json'), JSON.stringify(examplePackageLock));
const packageLock = await getPackageLock(path.join(tmpdir(), 'temporaryDirectory'));
const workFolder = path.join(os.tmpdir(), uuid());
await fs.mkdir(workFolder);

await fs.writeFile(path.join(workFolder, 'package-lock.json'), JSON.stringify(examplePackageLock));
const packageLock = await getPackageLock(workFolder);
assert.ok(packageLock.name === '@checkdigit/github-actions');
});

Expand Down
151 changes: 52 additions & 99 deletions src/check-label/check-label.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,118 +2,71 @@

import { strict as assert } from 'node:assert';
import path from 'node:path';
import { tmpdir } from 'node:os';
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
import os from 'node:os';
import { promises as fs } from 'node:fs';

import { afterAll, beforeAll, describe, it } from '@jest/globals';
import { describe, it } from '@jest/globals';
import { v4 as uuid } from 'uuid';

import gitHubNock from '../nocks/github.test';
import gitHubNock, {
createGithubEventFile,
PR_NUMBER_MAJOR,
PR_NUMBER_MINOR,
PR_NUMBER_PATCH,
} 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[0]) !== 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('.');
process.env['GITHUB_EVENT_PATH'] = await createGithubEventFile(prNumber);
}

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());
});

/* -------------------- enable if the current PR label is patch -------------------- */

// eslint-disable-next-line jest/no-disabled-tests
it.skip('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');
gitHubNock({ labelPackageVersionMain: targetVersion });

await createContext(10);

await assert.doesNotReject(checkLabel());
});

// eslint-disable-next-line jest/no-disabled-tests
it.skip('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');
gitHubNock({ labelPackageVersionMain: targetVersion });

await createContext(11);

await assert.rejects(checkLabel(), {
message: 'Version is incorrect based on Pull Request label',
});
});

/* -------------------- enable if the current PR label is major -------------------- */
it('label matches - major', 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, 'major');
gitHubNock({ labelPackageVersionMain: targetVersion });

await createContext(10);

await assert.rejects(checkLabel(), {
message: 'Version is incorrect based on Pull Request label',
});
});

it('label does not match - should be major but is major', 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, 'major');
gitHubNock({ labelPackageVersionMain: targetVersion });

await createContext(11);

await assert.doesNotReject(checkLabel());
});
it.each([
{ mainVersion: '1.0.0', currentVersion: '1.0.0', prNumber: PR_NUMBER_PATCH, success: false },
{ mainVersion: '1.0.0', currentVersion: '1.0.1', prNumber: PR_NUMBER_PATCH, success: true },
{ mainVersion: '1.0.0', currentVersion: '1.1.0', prNumber: PR_NUMBER_PATCH, success: false },
{ mainVersion: '1.0.0', currentVersion: '2.0.0', prNumber: PR_NUMBER_PATCH, success: false },
{ mainVersion: '1.1.0', currentVersion: '1.1.0', prNumber: PR_NUMBER_MINOR, success: false },
{ mainVersion: '1.1.0', currentVersion: '1.1.1', prNumber: PR_NUMBER_MINOR, success: false },
{ mainVersion: '1.1.1', currentVersion: '1.2.0', prNumber: PR_NUMBER_MINOR, success: true },
{ mainVersion: '1.1.1', currentVersion: '1.2.1', prNumber: PR_NUMBER_MINOR, success: false },
{ mainVersion: '1.1.1', currentVersion: '2.0.0', prNumber: PR_NUMBER_MINOR, success: false },
{ mainVersion: '2.2.2', currentVersion: '2.2.2', prNumber: PR_NUMBER_MAJOR, success: false },
{ mainVersion: '2.2.2', currentVersion: '2.2.3', prNumber: PR_NUMBER_MAJOR, success: false },
{ mainVersion: '2.2.2', currentVersion: '2.3.0', prNumber: PR_NUMBER_MAJOR, success: false },
{ mainVersion: '2.2.2', currentVersion: '3.0.0', prNumber: PR_NUMBER_MAJOR, success: true },
{ mainVersion: '2.2.2', currentVersion: '3.0.2', prNumber: PR_NUMBER_MAJOR, success: false },
{ mainVersion: '2.2.2', currentVersion: '3.2.2', prNumber: PR_NUMBER_MAJOR, success: false },
])(
'pull request: $prNumber; version in main branch: $mainVersion; version in PR branch: $currentVersion; success: $success',
async ({ mainVersion, currentVersion, prNumber, success }) => {
process.env['GITHUB_TOKEN'] = 'token 0000000000000000000000000000000000000001';

gitHubNock({ labelPackageVersionMain: mainVersion });

const workFolder = path.join(os.tmpdir(), uuid());
await fs.mkdir(workFolder);
await fs.writeFile(path.join(workFolder, 'package.json'), JSON.stringify({ version: currentVersion }));
await fs.writeFile(path.join(workFolder, 'package-lock.json'), JSON.stringify({ version: currentVersion }));

const originalCwd = process.cwd();
try {
process.chdir(workFolder);
await createContext(prNumber);
if (success) {
await assert.doesNotReject(checkLabel());
} else {
await assert.rejects(checkLabel());
}
} finally {
process.chdir(originalCwd);
}
},
);
});
Loading