/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @format */ module.exports = async (github, context) => { const issue = context.payload.issue; const issueVersionUnparsed = getReactNativeVersionFromIssueBodyIfExists(issue); const issueVersion = parseVersionFromString(issueVersionUnparsed); // Nightly versions are always supported if (reportedVersionIsNightly(issueVersionUnparsed, issueVersion)) return; if (!issueVersion) { return {label: 'Needs: Version Info'}; } // Ensure the version matches one we support const recentReleases = ( await github.rest.repos.listReleases({ owner: context.repo.owner, repo: context.repo.repo, }) ).data.map(release => release.name); const latestRelease = ( await github.rest.repos.getLatestRelease({ owner: context.repo.owner, repo: context.repo.repo, }) ).data; const latestVersion = parseVersionFromString(latestRelease.name); // We want to "insta-close" an issue if RN version provided is too old. And encourage users to upgrade. if (isVersionTooOld(issueVersion, latestVersion)) { return {label: 'Type: Too Old Version'}; } if (!isVersionSupported(issueVersion, latestVersion)) { return {label: 'Type: Unsupported Version'}; } }; /** * Check if the RN version provided in an issue is supported. * * "We support `N-2` minor versions, and the `latest` major". */ function isVersionSupported(actualVersion, latestVersion) { return ( actualVersion.major >= latestVersion.major && actualVersion.minor >= latestVersion.minor - 2 ); } /** * Check if the RN version provided in an issue is too old. * "We support `N-2` minor versions, and the `latest` major". * * A RN version is *too old* if it's: * - `1` or more *major* behind the *latest major*. * - `5` or more *minor* behind the *latest minor* in the *same major*. Less aggressive. * (e.g. If `0.72.0` is the current latest then `0.67.0` and lower is too old for `0.72.0`) */ function isVersionTooOld(actualVersion, latestVersion) { return ( latestVersion.major - actualVersion.major >= 1 || latestVersion.minor - actualVersion.minor >= 5 ); } function getReactNativeVersionFromIssueBodyIfExists(issue) { if (!issue || !issue.body) return; const rnVersionRegex = /React Native Version[\r\n]+(?.+)[\r\n]*/; const rnVersionMatch = issue.body.match(rnVersionRegex); if (!rnVersionMatch || !rnVersionMatch.groups.version) return; return rnVersionMatch.groups.version; } function reportedVersionIsNightly(unparsedVersionString, version) { if (!unparsedVersionString && !version) return false; const nightlyRegex = /nightly/i; const nightlyMatch = unparsedVersionString.match(nightlyRegex); const versionIsNightly = nightlyMatch && nightlyMatch[0]; const versionIsZero = version && version.major == 0 && version.minor == 0 && version.patch == 0; return versionIsZero || versionIsNightly; } function parseVersionFromString(version) { if (!version) return; // This will match the standard x.x.x semver format, as well as the non-standard prerelease x.x.x-rc.x const semverRegex = /(?\d+)\.(?\d+)\.(?\d+)(-[rR]{1}[cC]{1}\.(?\d+))?/; const versionMatch = version.match(semverRegex); if (!versionMatch) return; const {major, minor, patch, prerelease} = versionMatch.groups; return { major: parseInt(major), minor: parseInt(minor), patch: parseInt(patch), prerelease: parseInt(prerelease), }; }