chore: check against npm version in issue validator (#38915)

I did some experimenting to see if we can grab the latest version from npm instead of GitHub releases since that's the version the user can actually use.

The npm registry sends the package metadata in a gigantic (~8mb for `next`) JSON response and parsing it with `res.json()` takes several seconds, but using `ReadableStream`, we only care about the first chunk. This way we can grab the relevant version information in a few ms. ~I added `undici` which implements `res.body.getReader()`.~ Using Node 18/native `fetch`.
This commit is contained in:
Balázs Orbán 2022-08-25 17:43:20 +01:00 committed by GitHub
parent f12788dee8
commit 5c8183464d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 218 additions and 8870 deletions

View file

@ -18,6 +18,7 @@ packages/react-refresh-utils/**/*.js
packages/react-dev-overlay/lib/**
**/__tmp__/**
.github/actions/next-stats-action/.work
.github/actions/issue-validator/index.mjs
packages/next-codemod/transforms/__testfixtures__/**/*
packages/next-codemod/transforms/__tests__/**/*
packages/next-codemod/**/*.js

View file

@ -1,6 +0,0 @@
name: Issue validator
description: 'Validates bug reports on the Next.js repository'
author: 'Vercel'
runs:
using: 'node16'
main: 'index.js'

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -478,6 +478,25 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
lru-cache
ISC
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
nextjs-project
The MIT License (MIT)
@ -547,6 +566,25 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
semver
ISC
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
tr46
MIT
@ -644,3 +682,22 @@ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
yallist
ISC
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View file

@ -6,9 +6,11 @@
"": {
"dependencies": {
"@actions/core": "1.9.0",
"@actions/github": "5.0.3"
"@actions/github": "5.0.3",
"semver": "7.3.7"
},
"devDependencies": {
"@types/semver": "7.3.10",
"@vercel/ncc": "0.34.0"
}
},
@ -82,27 +84,27 @@
}
},
"node_modules/@octokit/openapi-types": {
"version": "12.4.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.4.0.tgz",
"integrity": "sha512-Npcb7Pv30b33U04jvcD7l75yLU0mxhuX2Xqrn51YyZ5WTkF04bpbxLaZ6GcaTqu03WZQHoO/Gbfp95NGRueDUA=="
"version": "12.10.1",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.10.1.tgz",
"integrity": "sha512-P+SukKanjFY0ZhsK6wSVnQmxTP2eVPPE8OPSNuxaMYtgVzwJZgfGdwlYjf4RlRU4vLEw4ts2fsE2icG4nZ5ddQ=="
},
"node_modules/@octokit/plugin-paginate-rest": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.19.0.tgz",
"integrity": "sha512-hQ4Qysg2hNmEMuZeJkvyzM4eSZiTifOKqYAMsW8FnxFKowhuwWICSgBQ9Gn9GpUmgKB7qaf1hFvMjYaTAg5jQA==",
"version": "2.21.3",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz",
"integrity": "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==",
"dependencies": {
"@octokit/types": "^6.36.0"
"@octokit/types": "^6.40.0"
},
"peerDependencies": {
"@octokit/core": ">=2"
}
},
"node_modules/@octokit/plugin-rest-endpoint-methods": {
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.15.0.tgz",
"integrity": "sha512-Gsw9+Xm56jVhfbJoy4pt6eOOyf8/3K6CAnx1Sl7U2GhZWcg8MR6YgXWnpfdF69S2ViMXLA7nfvTDAsZpFlkLRw==",
"version": "5.16.2",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz",
"integrity": "sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==",
"dependencies": {
"@octokit/types": "^6.36.0",
"@octokit/types": "^6.39.0",
"deprecation": "^2.3.1"
},
"peerDependencies": {
@ -133,13 +135,19 @@
}
},
"node_modules/@octokit/types": {
"version": "6.37.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.37.0.tgz",
"integrity": "sha512-BXWQhFKRkjX4dVW5L2oYa0hzWOAqsEsujXsQLSdepPoDZfYdubrD1KDGpyNldGXtR8QM/WezDcxcIN1UKJMGPA==",
"version": "6.40.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.40.0.tgz",
"integrity": "sha512-MFZOU5r8SwgJWDMhrLUSvyJPtVsqA6VnbVI3TNbsmw+Jnvrktzvq2fYES/6RiJA/5Ykdwq4mJmtlYUfW7CGjmw==",
"dependencies": {
"@octokit/openapi-types": "^12.4.0"
"@octokit/openapi-types": "^12.10.0"
}
},
"node_modules/@types/semver": {
"version": "7.3.10",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.10.tgz",
"integrity": "sha512-zsv3fsC7S84NN6nPK06u79oWgrPVd0NvOyqgghV1haPaFcVxIrP4DLomRwGAXk0ui4HZA7mOcSFL98sMVW9viw==",
"dev": true
},
"node_modules/@vercel/ncc": {
"version": "0.34.0",
"resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.34.0.tgz",
@ -167,6 +175,17 @@
"node": ">=0.10.0"
}
},
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/node-fetch": {
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
@ -194,6 +213,20 @@
"wrappy": "1"
}
},
"node_modules/semver": {
"version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
@ -230,6 +263,11 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
}
},
"dependencies": {
@ -303,24 +341,24 @@
}
},
"@octokit/openapi-types": {
"version": "12.4.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.4.0.tgz",
"integrity": "sha512-Npcb7Pv30b33U04jvcD7l75yLU0mxhuX2Xqrn51YyZ5WTkF04bpbxLaZ6GcaTqu03WZQHoO/Gbfp95NGRueDUA=="
"version": "12.10.1",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.10.1.tgz",
"integrity": "sha512-P+SukKanjFY0ZhsK6wSVnQmxTP2eVPPE8OPSNuxaMYtgVzwJZgfGdwlYjf4RlRU4vLEw4ts2fsE2icG4nZ5ddQ=="
},
"@octokit/plugin-paginate-rest": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.19.0.tgz",
"integrity": "sha512-hQ4Qysg2hNmEMuZeJkvyzM4eSZiTifOKqYAMsW8FnxFKowhuwWICSgBQ9Gn9GpUmgKB7qaf1hFvMjYaTAg5jQA==",
"version": "2.21.3",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz",
"integrity": "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==",
"requires": {
"@octokit/types": "^6.36.0"
"@octokit/types": "^6.40.0"
}
},
"@octokit/plugin-rest-endpoint-methods": {
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.15.0.tgz",
"integrity": "sha512-Gsw9+Xm56jVhfbJoy4pt6eOOyf8/3K6CAnx1Sl7U2GhZWcg8MR6YgXWnpfdF69S2ViMXLA7nfvTDAsZpFlkLRw==",
"version": "5.16.2",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz",
"integrity": "sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==",
"requires": {
"@octokit/types": "^6.36.0",
"@octokit/types": "^6.39.0",
"deprecation": "^2.3.1"
}
},
@ -348,13 +386,19 @@
}
},
"@octokit/types": {
"version": "6.37.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.37.0.tgz",
"integrity": "sha512-BXWQhFKRkjX4dVW5L2oYa0hzWOAqsEsujXsQLSdepPoDZfYdubrD1KDGpyNldGXtR8QM/WezDcxcIN1UKJMGPA==",
"version": "6.40.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.40.0.tgz",
"integrity": "sha512-MFZOU5r8SwgJWDMhrLUSvyJPtVsqA6VnbVI3TNbsmw+Jnvrktzvq2fYES/6RiJA/5Ykdwq4mJmtlYUfW7CGjmw==",
"requires": {
"@octokit/openapi-types": "^12.4.0"
"@octokit/openapi-types": "^12.10.0"
}
},
"@types/semver": {
"version": "7.3.10",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.10.tgz",
"integrity": "sha512-zsv3fsC7S84NN6nPK06u79oWgrPVd0NvOyqgghV1haPaFcVxIrP4DLomRwGAXk0ui4HZA7mOcSFL98sMVW9viw==",
"dev": true
},
"@vercel/ncc": {
"version": "0.34.0",
"resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.34.0.tgz",
@ -376,6 +420,14 @@
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"requires": {
"yallist": "^4.0.0"
}
},
"node-fetch": {
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
@ -392,6 +444,14 @@
"wrappy": "1"
}
},
"semver": {
"version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
"requires": {
"lru-cache": "^6.0.0"
}
},
"tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
@ -425,6 +485,11 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
}
}
}

View file

@ -1,14 +1,16 @@
{
"private": true,
"main": "dist/index.js",
"exports": "./index.mjs",
"scripts": {
"build": "ncc -o . -m build src/index.js --license licenses.txt"
"build": "ncc -m -o . build src/index.mjs --license licenses.txt"
},
"devDependencies": {
"@types/semver": "7.3.10",
"@vercel/ncc": "0.34.0"
},
"dependencies": {
"@actions/core": "1.9.0",
"@actions/github": "5.0.3"
"@actions/github": "5.0.3",
"semver": "7.3.7"
}
}

View file

@ -1,6 +1,7 @@
// @ts-check
import * as github from '@actions/github'
import * as core from '@actions/core'
import { gte as semverGte } from 'semver'
const verifyCanaryLabel = 'please verify canary'
const bugReportLabel = 'template: bug'
@ -33,6 +34,7 @@ async function run() {
/** @type {Label[]} */
const labels = issue.labels
if (debug) {
core.info(
`Validating issue ${issueNumber}:
Labels:
@ -40,6 +42,7 @@ async function run() {
All: ${json(labels)}
Body: ${body}`
)
}
const isBugReport = newLabel.name === bugReportLabel
@ -120,35 +123,49 @@ async function run() {
)
}
const reportedNextVersion = body.match(
const reported = body.match(
/Relevant packages:\n next: (?<version>\d+\.\d+\.\d+)/
)?.groups?.version
core.info(`Reported Next.js version: ${reportedNextVersion}`)
core.info(`Reported Next.js version: ${reported}`)
if (!reportedNextVersion) {
// REVIEW: Should we add a label here?
return
}
if (!reported) return
const { tag_name: lastVersion } = await (
await client.repos.listReleases(repo)
).data[0]
const last = await getLastVersion()
core.info(`Last Next.js version, based on npm releases: ${last}`)
core.info(`Last Next.js version, based on GitHub releases: ${lastVersion}`)
if (!last.includes('canary') || reported === last) return
if (lastVersion.includes('canary') && reportedNextVersion !== lastVersion) {
await notifyOnIssue(
verifyCanaryLabel,
`The reported Next.js version did not match the latest \`next@canary\` version (${lastVersion}). The canary version of Next.js ships daily and includes all features and fixes that have not been released to the stable version yet. Think of canary as a public beta. Some issues may already be fixed in the canary version, so please verify that your issue reproduces by running \`npm install next@canary\`. If the issue does not reproduce with the canary version, then it has already been fixed and this issue can be closed.`
`The reported Next.js version did not match the latest \`next@canary\` version (${last}). The canary version of Next.js ships daily and includes all features and fixes that have not been released to the stable version yet. Think of canary as a public beta. Some issues may already be fixed in the canary version, so please verify that your issue reproduces by running \`npm install next@canary\`. If the issue does not reproduce with the canary version, then it has already been fixed and this issue can be closed.`
)
return core.info(
`Commented on issue, because it was not verified against canary`
)
}
} catch (error) {
core.setFailed(error.message)
}
}
run()
async function getLastVersion() {
try {
const res = await fetch('https://registry.npmjs.org/next', {
headers: {
accept:
'application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*',
},
})
const value = (await res.body?.getReader().read())?.value
const string = new TextDecoder().decode(value?.slice(0, 100))
const re = /"latest":"(?<latest>.*)","canary":"(?<canary>.*)","/
const { latest, canary } = string.match(re)?.groups ?? {}
return semverGte(latest, canary) ? canary : latest
} catch (error) {
core.error(error)
return ''
}
}

View file

@ -8,7 +8,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/issue-validator
- uses: actions/setup-node@v3
with:
node-version: 18
- name: 'Run issue validator'
run: node ./.github/actions/issue-validator/index.mjs
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DEBUG: 1

View file

@ -11,6 +11,7 @@ packages/react-dev-overlay/lib/**
**/__tmp__/**
lerna.json
.github/actions/next-stats-action/.work
.github/actions/issue-validator/index.mjs
packages/next-swc/crates/**/*
packages/next-swc/target/**/*
packages/next-swc/native/**/*

View file

@ -7,5 +7,6 @@ packages/next/bundles/webpack/packages/*.runtime.js
lerna.json
packages/next-codemod/transforms/__testfixtures__/**/*
packages/next-codemod/transforms/__tests__/**/*
pnpm-lock.yaml
pnpm-lock.yam
.github/actions/issue-validator/index.mjs
**/convex/_generated/**