From 047310f784cbd722900a0fdc8998c9fa33ac4be4 Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Wed, 19 Aug 2026 16:05:51 +0200 Subject: [PATCH 1/2] Handle `package.json` in sub folder as well Before, only the root level was checked but some projects are a monorepo. --- functions/classes/ArtifactsHandler.js | 80 ++++++++++++++------------- tests/artifacts.spec.js | 78 ++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 37 deletions(-) diff --git a/functions/classes/ArtifactsHandler.js b/functions/classes/ArtifactsHandler.js index ef4ba248..1eea4a1a 100644 --- a/functions/classes/ArtifactsHandler.js +++ b/functions/classes/ArtifactsHandler.js @@ -36,10 +36,12 @@ export class ArtifactsHandler extends Handler { pull_number: body.pull_request.number, }) - // loop through all files to see if package.json has been updated - const file = files.data.find(({ filename }) => filename.includes('package.json')) + // loop through all files to see if package.json has been updated (root or subfolders) + const packageJsonFiles = files.data.filter( + ({ filename }) => filename === 'package.json' || filename.endsWith('/package.json') + ) - if (!file) { + if (packageJsonFiles.length === 0) { console.info('No package.json found in PR') response = await this.updateStatus(body, payload.success) @@ -47,42 +49,46 @@ export class ArtifactsHandler extends Handler { return response } - const refMatch = file.contents_url.match(/ref=([a-z0-9]+)/) - if (!refMatch?.[1]) { - console.warn('no ref in package.json diff url?') - - response = await this.updateStatus(body, payload.success) - - return response - } - - const content = await this.githubClient.rest.repos.getContent({ - owner: body.repository.owner.login, - repo: body.repository.name, - path: file.filename, - ref: refMatch[1], - }) - - let packageJson = '' - try { - packageJson = JSON.parse( - Buffer.from(content.data.content.toString('utf8'), 'base64').toString('ascii') - ) - } catch (e) { - response = await this.updateStatus(body, payload.success) - console.error('Parsing package.json failed:', e) - - return response - } - - const deps = { - dependencies: packageJson.dependencies || {}, - devDependencies: packageJson.devDependencies || {}, + let artifactFound = false + + for (const file of packageJsonFiles) { + const refMatch = file.contents_url.match(/ref=([a-z0-9]+)/) + if (!refMatch?.[1]) { + console.warn(`no ref in ${file.filename} diff url?`) + continue + } + + const content = await this.githubClient.rest.repos.getContent({ + owner: body.repository.owner.login, + repo: body.repository.name, + path: file.filename, + ref: refMatch[1], + }) + + let packageJson = '' + try { + packageJson = JSON.parse( + Buffer.from(content.data.content.toString('utf8'), 'base64').toString('ascii') + ) + } catch (e) { + console.error(`Parsing ${file.filename} failed:`, e) + continue + } + + const deps = { + dependencies: packageJson.dependencies || {}, + devDependencies: packageJson.devDependencies || {}, + } + + const match = JSON.stringify(deps).match(this.artifactsRegex) + + if (match !== null) { + artifactFound = true + break + } } - const match = JSON.stringify(deps).match(this.artifactsRegex) - - if (match === null) { + if (!artifactFound) { console.info('No match, success.') response = await this.updateStatus(body, payload.success) diff --git a/tests/artifacts.spec.js b/tests/artifacts.spec.js index 55ec0a6f..4f4cfccc 100644 --- a/tests/artifacts.spec.js +++ b/tests/artifacts.spec.js @@ -267,6 +267,84 @@ describe('Artifacts check', () => { ) }) + test('got a package.json file in subfolder and block merge', async () => { + fetchMock + .mockGlobal() + .route('https://api.github.com/repos/foo/bar/pulls/42/files', [ + { + sha: 'c14ebcf2a1782dec1bf65b2d4fbc295964b81754', + filename: 'main/package.json', + status: 'modified', + additions: 1, + deletions: 1, + changes: 2, + blob_url: + 'https://github.com/foo/bar/blob/d3f0969ba9b5d5f5713fd804246886917adab874/main/package.json', + raw_url: + 'https://github.com/foo/bar/raw/d3f0969ba9b5d5f5713fd804246886917adab874/main/package.json', + contents_url: + 'https://api.github.com/repos/foo/bar/contents/main/package.json?ref=d3f0969ba9b5d5f5713fd804246886917adab874', + patch: + '@@ -1,5 +1,5 @@\n "dependencies": {\n- "super": "^1.0.0"\n+ "super": "http://localhost:1234/package.zip"\n }', + }, + ]) + .route( + 'https://api.github.com/repos/foo/bar/contents/main%2Fpackage.json?ref=d3f0969ba9b5d5f5713fd804246886917adab874', + { + name: 'package.json', + path: 'main/package.json', + sha: 'c366d3955407f49b5110aae7d8d04fcf0b3ef1de', + size: 86, + type: 'file', + content: + 'ewogICJkZXBlbmRlbmNpZXMiOiB7CiAgICAic3VwZXIiOiAiaHR0cDovL2xvY2FsaG9zdDoxMjM0L3BhY2thZ2UuemlwIgogIH0KfQ==', + encoding: 'base64', + } + ) + .route( + 'https://api.github.com/repos/foo/bar/statuses/ee55a1223ce20c3e7cb776349cb7f8efb7b88511', + 200 + ) + const githubEvent = { + pull_request: { + number: 42, + title: 'Update', + body: 'This is a pretty simple change that we need to pull into master.', + head: { + sha: 'ee55a1223ce20c3e7cb776349cb7f8efb7b88511', + }, + base: { + sha: '1e55a1223ce20c3e7cb776349cb7f8efb7b8851e', + }, + }, + repository: { + name: 'bar', + full_name: 'foo/bar', + owner: { + login: 'foo', + }, + }, + } + + const fixup = new ArtifactsHandler('GH_TOKEN', 'THE BRAND', '(localhost:)') + const response = await fixup.handle(githubEvent) + expect(response).toEqual({ + body: 'Process finished with state: failure', + statusCode: 204, + }) + + expect(fetch).toHaveLastFetched( + 'https://api.github.com/repos/foo/bar/statuses/ee55a1223ce20c3e7cb776349cb7f8efb7b88511', + { + body: { + state: 'failure', + description: 'Artifacts found in deps, remove them to merge', + context: 'THE BRAND - Artifacts check', + }, + } + ) + }) + test('got a package.json file and block merge', async () => { fetchMock .mockGlobal() From 26cad62c272786c8495ec611b3b4ebb408b79ad2 Mon Sep 17 00:00:00 2001 From: Jeremy Benoist Date: Wed, 19 Aug 2026 16:12:39 +0200 Subject: [PATCH 2/2] Update README --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a7168def..958c61cc 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Apply some simple checks on each PR. ## Use Cases -We have 4 functions available: +We have 5 functions available: - **specification**: It validates GitHub Pull Requests against some specifications: - `body` should be at least 8 characters long @@ -25,6 +25,10 @@ We have 4 functions available: - if no fixup commits are found, PR is validated - **autoMerge**: It merges PR from @dependabot when the update it for a patch or a minor version - the repo must have the _auto merge_ option enabled +- **artifacts**: It checks if `package.json` dependencies contain artifact URLs matching a configured regex + - it checks `package.json` files at the repository root or in subfolders + - if a matching dependency is found, PR is blocked + - if no matching dependency is found, PR is validated ## Prerequisites @@ -62,6 +66,7 @@ You can update some options from the `serverless.yml` file: - `CHECK_BODY_LENGTH`: change the minimun length of the body of the PR - `CHECK_TITLE_LENGTH`: change the minimun length of the title of the PR - `BLOCK_LABELS`: define which label will block a PR (comma separated strings) +- `ARTIFACTS_REGEX`: define which artifact URLs in `package.json` dependencies will block a PR ## Info