Skip to content

Commit

Permalink
verify npm package (#77)
Browse files Browse the repository at this point in the history
  • Loading branch information
le-cong authored Jun 5, 2024
1 parent ca9dc6e commit 6c6faab
Show file tree
Hide file tree
Showing 43 changed files with 1,162 additions and 1,106 deletions.
8 changes: 6 additions & 2 deletions .github/workflows/ci.yml
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:
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

0 comments on commit 6c6faab

Please sign in to comment.