diff --git a/.eslintrc.js b/.eslintrc.js index 8cd6fd26fa..2c10f6ab5e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -22,15 +22,16 @@ module.exports = { { files: [ "**/*.js", + "**/*.mjs", ], extends: [ "eslint:recommended", ], env: { node: true, + es2022: true, }, parserOptions: { - ecmaVersion: 2018, sourceType: "module", }, plugins: [ diff --git a/package.json b/package.json index 0e15275d6a..0e6d7168d7 100644 --- a/package.json +++ b/package.json @@ -287,6 +287,7 @@ "@types/chart.js": "^2.9.36", "@types/cli-progress": "^3.9.2", "@types/color": "^3.0.3", + "@types/command-line-args": "^5.2.0", "@types/crypto-js": "^3.1.47", "@types/dompurify": "^2.3.3", "@types/electron-devtools-installer": "^2.2.1", @@ -341,6 +342,7 @@ "circular-dependency-plugin": "^5.2.2", "cli-progress": "^3.11.0", "color": "^3.2.1", + "command-line-args": "^5.2.1", "concurrently": "^7.1.0", "css-loader": "^6.7.1", "deepdash": "^5.3.9", diff --git a/scripts/clear-release-pr.mjs b/scripts/clear-release-pr.mjs new file mode 100755 index 0000000000..4b55384e61 --- /dev/null +++ b/scripts/clear-release-pr.mjs @@ -0,0 +1,210 @@ +#!/usr/bin/env node +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +// This script creates a release PR +import { execSync, exec, spawn } from "child_process"; +import commandLineArgs from "command-line-args"; +import fse from "fs-extra"; +import { basename } from "path"; +import semver from "semver"; +import { promisify } from "util"; + +const { + SemVer, + valid: semverValid, + rcompare: semverRcompare, + lte: semverLte, +} = semver; +const { readJsonSync } = fse; +const execP = promisify(exec); + +const options = commandLineArgs([ + { + name: "type", + defaultOption: true, + }, + { + name: "preid", + }, +]); + +const validReleaseValues = [ + "major", + "minor", + "patch", +]; +const validPrereleaseValues = [ + "premajor", + "preminor", + "prepatch", + "prerelease", +]; +const validPreidValues = [ + "alpha", + "beta", +]; + +const errorMessages = { + noReleaseType: `No release type provided. Valid options are: ${[...validReleaseValues, ...validPrereleaseValues].join(", ")}`, + invalidRelease: (invalid) => `Invalid release type was provided (value was "${invalid}"). Valid options are: ${[...validReleaseValues, ...validPrereleaseValues].join(", ")}`, + noPreid: `No preid was provided. Use '--preid' to specify. Valid options are: ${validPreidValues.join(", ")}`, + invalidPreid: (invalid) => `Invalid preid was provided (value was "${invalid}"). Valid options are: ${validPreidValues.join(", ")}`, + wrongCwd: "It looks like you are running this script from the 'scripts' directory. This script assumes it is run from the root of the git repo", +}; + +if (!options.type) { + console.error(errorMessages.noReleaseType); + process.exit(1); +} + +if (validReleaseValues.includes(options.type)) { + // do nothing, is valid +} else if (validPrereleaseValues.includes(options.type)) { + if (!options.preid) { + console.error(errorMessages.noPreid); + process.exit(1); + } + + if (!validPreidValues.includes(options.preid)) { + console.error(errorMessages.invalidPreid(options.preid)); + process.exit(1); + } +} else { + console.error(errorMessages.invalidRelease(options.type)); + process.exit(1); +} + +if (basename(process.cwd()) === "scripts") { + console.error(errorMessages.wrongCwd); +} + + +const currentVersion = new SemVer(readJsonSync("./package.json").version); +const currentVersionMilestone = `${currentVersion.major}.${currentVersion.minor}.${currentVersion.patch}`; + +console.log(`current version: ${currentVersion.format()}`); +console.log("fetching tags..."); +execSync("git fetch --tags --force"); + +const actualTags = execSync("git tag --list", { encoding: "utf-8" }).split(/\r?\n/).map(line => line.trim()); +const [previousReleasedVersion] = actualTags + .map(semverValid) + .filter(Boolean) + .sort(semverRcompare) + .filter(version => semverLte(version, currentVersion)); + +const npmVersionArgs = [ + "npm", + "version", + options.type, +]; + +if (options.preid) { + npmVersionArgs.push(`--preid=${options.preid}`); +} + +npmVersionArgs.push("--git-tag-version false"); + +execSync(npmVersionArgs.join(" "), { stdio: "ignore" }); + +const newVersion = new SemVer(readJsonSync("./package.json").version); + +const getMergedPrsArgs = [ + "gh", + "pr", + "list", + "--limit=500", // Should be big enough, if not we need to release more often ;) + "--state=merged", + "--base=master", + "--json mergeCommit,title,author,labels,number,milestone", +]; + +console.log("retreiving last 500 PRs to create release PR body..."); +const mergedPrs = JSON.parse(execSync(getMergedPrsArgs.join(" "), { encoding: "utf-8" })); +const milestoneRelevantPrs = mergedPrs.filter(pr => pr.milestone && pr.milestone.title === currentVersionMilestone); +const relaventPrsQuery = await Promise.all( + milestoneRelevantPrs.map(async pr => ({ + pr, + stdout: (await execP(`git tag v${previousReleasedVersion} --no-contains ${pr.mergeCommit.oid}`)).stdout, + })), +); +const relaventPrs = relaventPrsQuery + .filter(query => query.stdout) + .map(query => query.pr); + +const enhancementPrLabelName = "enhancement"; +const bugfixPrLabelName = "bug"; + +const enhancementPrs = relaventPrs.filter(pr => pr.labels.some(label => label.name === enhancementPrLabelName)); +const bugfixPrs = relaventPrs.filter(pr => pr.labels.some(label => label.name === bugfixPrLabelName)); +const maintenencePrs = relaventPrs.filter(pr => pr.labels.every(label => label.name !== bugfixPrLabelName && label.name !== enhancementPrLabelName)); + +console.log("Found:"); +console.log(`${enhancementPrs.length} enhancement PRs`); +console.log(`${bugfixPrs.length} bug fix PRs`); +console.log(`${maintenencePrs.length} maintenence PRs`); + +const prBodyLines = [ + `## Changes since ${previousReleasedVersion}`, + "", +]; + +if (enhancementPrs.length > 0) { + prBodyLines.push( + "## 🚀 Features", + "", + ...enhancementPrs.map(pr => `- ${pr.title} (**#${pr.number}**) https://github.com/${pr.author.login}`), + "", + ); +} + +if (bugfixPrs.length > 0) { + prBodyLines.push( + "## 🐛 Bug Fixes", + "", + ...bugfixPrs.map(pr => `- ${pr.title} (**#${pr.number}**) https://github.com/${pr.author.login}`), + "", + ); +} + +if (maintenencePrs.length > 0) { + prBodyLines.push( + "## 🧰 Maintenance", + "", + ...maintenencePrs.map(pr => `- ${pr.title} (**#${pr.number}**) https://github.com/${pr.author.login}`), + "", + ); +} + +const prBody = prBodyLines.join("\n"); +const prBase = newVersion.patch === 0 + ? "master" + : `release/v${newVersion.major}.${newVersion.minor}`; +const createPrArgs = [ + "pr", + "create", + "--base", prBase, + "--title", `release ${newVersion.format()}`, + "--label", "skip-changelog", + "--body-file", "-", +]; + +const createPrProcess = spawn("gh", createPrArgs, { stdio: "pipe" }); +let result = ""; + +createPrProcess.stdout.on("data", (chunk) => result += chunk); + +createPrProcess.stdin.write(prBody); +createPrProcess.stdin.end(); + +await new Promise((resolve) => { + createPrProcess.on("close", () => { + createPrProcess.stdout.removeAllListeners(); + resolve(); + }); +}); + +console.log(result); diff --git a/yarn.lock b/yarn.lock index 45b3738ea7..564b4e973b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1426,6 +1426,11 @@ dependencies: "@types/color-convert" "*" +"@types/command-line-args@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@types/command-line-args/-/command-line-args-5.2.0.tgz#adbb77980a1cc376bb208e3f4142e907410430f6" + integrity sha512-UuKzKpJJ/Ief6ufIaIzr3A/0XnluX7RvFgwkV89Yzvm77wCh1kFaFmqN8XEnGcN62EuHdedQjEMb8mYxFLGPyA== + "@types/connect-history-api-fallback@^1.3.5": version "1.3.5" resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz#d1f7a8a09d0ed5a57aee5ae9c18ab9b803205dae" @@ -2820,6 +2825,11 @@ arr-union@^3.1.0: resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= +array-back@^3.0.1, array-back@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/array-back/-/array-back-3.1.0.tgz#b8859d7a508871c9a7b2cf42f99428f65e96bfb0" + integrity sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q== + array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" @@ -3904,6 +3914,16 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" +command-line-args@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-5.2.1.tgz#c44c32e437a57d7c51157696893c5909e9cec42e" + integrity sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg== + dependencies: + array-back "^3.1.0" + find-replace "^3.0.0" + lodash.camelcase "^4.3.0" + typical "^4.0.0" + commander@2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" @@ -5908,6 +5928,13 @@ find-npm-prefix@^1.0.2: resolved "https://registry.yarnpkg.com/find-npm-prefix/-/find-npm-prefix-1.0.2.tgz#8d8ce2c78b3b4b9e66c8acc6a37c231eb841cfdf" integrity sha512-KEftzJ+H90x6pcKtdXZEPsQse8/y/UnvzRKrOSQFprnrGaFuJ62fVkP34Iu2IYuMvyauCyoLTNkJZgrrGA2wkA== +find-replace@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-3.0.0.tgz#3e7e23d3b05167a76f770c9fbd5258b0def68c38" + integrity sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ== + dependencies: + array-back "^3.0.1" + find-root@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" @@ -13146,6 +13173,11 @@ typescript@^4.5.5: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3" integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA== +typical@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4" + integrity sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw== + uglify-js@^3.1.4: version "3.9.4" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.9.4.tgz#867402377e043c1fc7b102253a22b64e5862401b"