diff --git a/.github/workflows/update-dependents-count.yml b/.github/workflows/update-dependents-count.yml new file mode 100644 index 000000000..ba9932699 --- /dev/null +++ b/.github/workflows/update-dependents-count.yml @@ -0,0 +1,44 @@ +name: Update dependents count + +on: + workflow_dispatch: + schedule: + # β€œAt 00:00 on day-of-month 1.” https://crontab.guru/#0_0_1_*_* + - cron: "0 0 1 * *" + +permissions: + pull-requests: write + contents: write + +jobs: + run: + name: Update dependents count + if: github.event_name != 'schedule' || github.repository == 'prettier/prettier' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Setup Node.js + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + cache: "yarn" + + - name: Install Dependencies + run: yarn --immutable + + - name: Run script + run: node scripts/update-dependents-count.js + + - name: Format website + run: yarn prettier website/src/pages/index.jsx --write + + - name: Set current date env + run: echo "CURRENT_DATE=$(date +'%Y-%m-%d')" >> "$GITHUB_ENV" + + - uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 + with: + commit-message: Update dependents count (${{ env.CURRENT_DATE }}) + branch: automated-update-dependents-count-${{ env.CURRENT_DATE }} + branch-suffix: timestamp + title: Update dependents count (${{ env.CURRENT_DATE }}) diff --git a/scripts/release/run.js b/scripts/release/run.js index d79a52f4c..ba208e88c 100644 --- a/scripts/release/run.js +++ b/scripts/release/run.js @@ -55,7 +55,6 @@ for (let step of [ name: "Merge release notes PR", process: steps.mergeBlogPost, }, - steps.updateDependentsCount, { name: "Cleaning changelog", process: steps.cleanChangelog, diff --git a/scripts/release/steps/index.js b/scripts/release/steps/index.js index 888dfe24b..f26e977bb 100644 --- a/scripts/release/steps/index.js +++ b/scripts/release/steps/index.js @@ -10,7 +10,6 @@ export { default as publishToNpm } from "./publish-to-npm.js"; export { default as pushToGit } from "./push-to-git.js"; export { default as showInstructionsAfterNpmPublish } from "./show-instructions-after-npm-publish.js"; export { default as updateChangelog } from "./update-changelog.js"; -export { default as updateDependentsCount } from "./update-dependents-count.js"; export { default as updateVersion } from "./update-version.js"; export { default as validateNewVersion } from "./validate-new-version.js"; export { default as waitForBotRelease } from "./wait-for-bot-release.js"; diff --git a/scripts/release/steps/update-dependents-count.js b/scripts/release/steps/update-dependents-count.js deleted file mode 100644 index ebd17a1be..000000000 --- a/scripts/release/steps/update-dependents-count.js +++ /dev/null @@ -1,117 +0,0 @@ -import styleText from "node-style-text"; -import { fetchText, logPromise, processFile, runGit } from "../utilities.js"; - -async function getNpmDependentsCount() { - const npmPage = await logPromise( - "Fetching npm dependents count", - fetchText("https://www.npmjs.com/package/prettier"), - ); - const dependentsCountNpm = Number( - npmPage.match(/"dependentsCount":"(?\d+)",/).groups - .dependentsCount, - ); - if (Number.isNaN(dependentsCountNpm)) { - throw new TypeError( - "Invalid data from https://www.npmjs.com/package/prettier", - ); - } - - return dependentsCountNpm; -} - -async function getGithubDependentsCount() { - const githubPage = await logPromise( - "Fetching github dependents count", - fetchText("https://github.com/prettier/prettier/network/dependents"), - ); - const dependentsCountGithub = Number( - githubPage - .replaceAll("\n", "") - .match( - /.*?<\/svg>\s*(?[\d,]+)\s*Repositories\s*<\/a>/, - ) - .groups.dependentsCount.replaceAll(",", ""), - ); - if (Number.isNaN(dependentsCountGithub)) { - throw new TypeError( - "Invalid data from https://github.com/prettier/prettier/network/dependents", - ); - } - - return dependentsCountGithub; -} - -async function update({ repo }) { - const [ - { value: dependentsCountNpm, reason: dependentsNpmError }, - { value: dependentsCountGithub, reason: dependentsGithubError }, - ] = await Promise.allSettled([ - getNpmDependentsCount(), - getGithubDependentsCount(), - ]); - - if (dependentsCountNpm || dependentsCountGithub) { - processFile("website/src/pages/index.jsx", (content) => { - if (dependentsCountNpm) { - content = content.replace( - /()(.*?)(<\/strong>)/, - `$1${formatNumber(dependentsCountNpm)}$3`, - ); - } - - if (dependentsCountGithub) { - content = content.replace( - /()(.*?)(<\/strong>)/, - `$1${formatNumber(dependentsCountGithub)}$3`, - ); - } - - return content; - }); - - const isUpdated = await logPromise( - "Checking if dependents count has been updated", - async () => - (await runGit(["diff", "--name-only"])).stdout === - "website/src/pages/index.jsx", - ); - - if (isUpdated) { - await logPromise("Committing and pushing to remote", async () => { - await runGit(["add", "."]); - await runGit(["commit", "-m", "Update dependents count"]); - await runGit(["push", "--repo", repo]); - }); - } - } - - if (dependentsNpmError) { - throw dependentsNpmError; - } - - if (dependentsGithubError) { - throw dependentsGithubError; - } -} - -function formatNumber(value) { - if (value < 1e4) { - return String(value).slice(0, 1) + "0".repeat(String(value).length - 1); - } - if (value < 1e6) { - return Math.floor(value / 1e2) / 10 + "k"; - } - return Math.floor(value / 1e5) / 10 + " million"; -} - -export default async function updateDependentsCount({ dry, next, repo }) { - if (dry || next) { - return; - } - - try { - await update({ repo }); - } catch (error) { - console.log(styleText.red.bold(error.message)); - } -} diff --git a/scripts/update-dependents-count.js b/scripts/update-dependents-count.js new file mode 100644 index 000000000..923d2f99d --- /dev/null +++ b/scripts/update-dependents-count.js @@ -0,0 +1,116 @@ +import assert from "node:assert/strict"; +import fs from "node:fs/promises"; + +async function fetchText(url) { + const response = await fetch(url); + assert.ok(response.ok, `Failed to fetch ${url}: ${response.status}`); + return response.text(); +} + +async function getNpmDependentsCount() { + const npmPage = await fetchText("https://www.npmjs.com/package/prettier"); + const matches = npmPage.match(/"dependentsCount":"(?\d+)",/); + + assert.ok( + matches, + "Invalid data from https://www.npmjs.com/package/prettier", + ); + + const dependentsCountNpm = Number(matches.groups.dependentsCount); + assert.ok( + !Number.isNaN(dependentsCountNpm), + "Invalid data from https://www.npmjs.com/package/prettier", + ); + + return dependentsCountNpm; +} + +async function getGithubDependentsCount() { + const githubPage = await fetchText( + "https://github.com/prettier/prettier/network/dependents", + ); + const dependentsCountGithub = Number( + githubPage + .replaceAll("\n", "") + .match( + /.*?<\/svg>\s*(?[\d,]+)\s*Repositories\s*<\/a>/, + ) + .groups.dependentsCount.replaceAll(",", ""), + ); + assert.ok( + !Number.isNaN(dependentsCountGithub), + "Invalid data from https://github.com/prettier/prettier/network/dependents", + ); + + return dependentsCountGithub; +} + +async function update() { + const [ + { value: dependentsCountNpm, reason: dependentsNpmError }, + { value: dependentsCountGithub, reason: dependentsGithubError }, + ] = await Promise.allSettled([ + getNpmDependentsCount(), + getGithubDependentsCount(), + ]); + + const file = new URL("../website/src/pages/index.jsx", import.meta.url); + const original = await fs.readFile(file, "utf8"); + + let content = original; + for (const { name, error, value, update } of [ + { + name: "NPM dependents count", + error: dependentsNpmError, + value: dependentsCountNpm, + update: (text, value) => + text.replace( + /(?<=).*?(?=<\/strong>)/, + formatNumber(value), + ), + }, + { + name: "GitHub dependents count", + error: dependentsGithubError, + value: dependentsCountGithub, + update: (text, value) => + text.replace( + /(?<=).*?(?=<\/strong>)/, + formatNumber(value), + ), + }, + ]) { + if (error) { + console.log(`❌ Failed to update ${name}.`); + } else { + console.log(`βœ… ${name} updated.`); + content = update(content, value); + } + } + + if (original !== content) { + await fs.writeFile(file, content); + } + + if (dependentsNpmError) { + console.log(); + console.error(dependentsNpmError); + } + + if (dependentsGithubError) { + console.log(); + console.error(dependentsGithubError); + } +} + +function formatNumber(value) { + if (value < 1e4) { + return String(value).slice(0, 1) + "0".repeat(String(value).length - 1); + } + if (value < 1e6) { + return Math.floor(value / 1e2) / 10 + "k"; + } + return Math.floor(value / 1e5) / 10 + " million"; +} + +await update();