From 75dc001aea4f0daab36cb2ff909a4a91cd4b262d Mon Sep 17 00:00:00 2001 From: Randell Dawson <5313213+RandellDawson@users.noreply.github.com> Date: Tue, 9 Mar 2021 09:57:40 -0700 Subject: [PATCH] feat(tools): Add Crowdin PR creator action (#41383) Co-authored-by: Nicholas Carrigan (he/him) --- .../crowdin-i18n-client-ui-download.yml | 69 ++------ .../crowdin-i18n-curriculum-download.yml | 69 ++------ .../workflows/crowdin-i18n-docs-download..yml | 69 ++------ tools/crowdin/actions/pr-creator/action.yml | 31 ++++ tools/crowdin/actions/pr-creator/index.js | 90 +++++++++++ tools/crowdin/package-lock.json | 153 ++++++++++++++++++ tools/crowdin/package.json | 1 + 7 files changed, 317 insertions(+), 165 deletions(-) create mode 100644 tools/crowdin/actions/pr-creator/action.yml create mode 100644 tools/crowdin/actions/pr-creator/index.js diff --git a/.github/workflows/crowdin-i18n-client-ui-download.yml b/.github/workflows/crowdin-i18n-client-ui-download.yml index 2063451043..e86c675430 100644 --- a/.github/workflows/crowdin-i18n-client-ui-download.yml +++ b/.github/workflows/crowdin-i18n-client-ui-download.yml @@ -84,62 +84,21 @@ jobs: CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID_CLIENT }} CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_CAMPERBOT_SERVICE_TOKEN }} + - name: Install Dependencies + working-directory: ./tools + run: | + cd ./crowdin + npm ci + # Generate PR - all languages should go ABOVE this. # - name: Create PR - uses: actions/github-script@v3 + uses: ./tools/crowdin/actions/pr-creator with: github-token: ${{ secrets.CROWDIN_CAMPERBOT_PAT }} - script: | - const branchExists = await github.repos.getBranch({ - owner: 'freeCodeCamp', - repo: 'freeCodeCamp', - branch: 'i18n-sync-client' - }).catch(err => { - console.info("Branch does not exist. Likely no changes in download?"); - }); - if (!branchExists || branchExists.status !== 200) { - return; - } - const pullRequestExists = await github.pulls.list({ - owner: 'freeCodeCamp', - repo: 'freeCodeCamp', - head: 'freeCodeCamp:i18n-sync-client' - }); - if (pullRequestExists.data.length) { - console.info("It looks like a pull request already exists for this branch."); - return; - } - const PR = await github.pulls.create({ - owner: 'freeCodeCamp', - repo: 'freeCodeCamp', - head: 'i18n-sync-client', - base: 'main', - title: 'chore(i18n,client): processed translations', - body: 'This PR was opened auto-magically by Crowdin.' - }).catch(err => { - console.info("Unpredicted error occurred when trying to create the PR."); - console.error(err); - }); - if (!PR || PR.status !== 201) { - return; - } - const PRNumber = PR.data.number; - await github.issues.addLabels({ - owner: 'freeCodeCamp', - repo: 'freeCodeCamp', - issue_number: PRNumber, - labels: [ - "crowdin-sync", - "scope: i18n", - "scope: UI" - ] - }); - await github.pulls.requestReviewers({ - owner: 'freeCodeCamp', - repo: 'freeCodeCamp', - pull_number: PRNumber, - reviewers: [ - 'RandellDawson', - 'nhcarrigan' - ] - }); + branch: 'i18n-sync-client' + owner-repo: 'freeCodeCamp/freeCodeCamp' + base: 'main' + title: 'chore(i18n,client): processed translations' + body: 'This PR was opened auto-magically by Crowdin.' + labels: 'crowdin-sync, scope: i18n, scope: UI' + reviewers: 'RandellDawson, nhcarrigan' diff --git a/.github/workflows/crowdin-i18n-curriculum-download.yml b/.github/workflows/crowdin-i18n-curriculum-download.yml index 75f9792db8..554462e72f 100644 --- a/.github/workflows/crowdin-i18n-curriculum-download.yml +++ b/.github/workflows/crowdin-i18n-curriculum-download.yml @@ -85,62 +85,21 @@ jobs: CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID_CURRICULUM }} CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_CAMPERBOT_SERVICE_TOKEN }} + - name: Install Dependencies + working-directory: ./tools + run: | + cd ./crowdin + npm ci + # Generate PR - all languages should go ABOVE this. # - name: Create PR - uses: actions/github-script@v3 + uses: ./tools/crowdin/actions/pr-creator with: github-token: ${{ secrets.CROWDIN_CAMPERBOT_PAT }} - script: | - const branchExists = await github.repos.getBranch({ - owner: 'freeCodeCamp', - repo: 'freeCodeCamp', - branch: 'i18n-sync-learn' - }).catch(err => { - console.info("Branch does not exist. Likely no changes in download?"); - }); - if (!branchExists || branchExists.status !== 200) { - return; - } - const pullRequestExists = await github.pulls.list({ - owner: 'freeCodeCamp', - repo: 'freeCodeCamp', - head: 'freeCodeCamp:i18n-sync-learn' - }); - if (pullRequestExists.data.length) { - console.info("It looks like a pull request already exists for this branch."); - return; - } - const PR = await github.pulls.create({ - owner: 'freeCodeCamp', - repo: 'freeCodeCamp', - head: 'i18n-sync-learn', - base: 'main', - title: 'chore(i18n,learn): processed translations', - body: 'This PR was opened auto-magically by Crowdin.' - }).catch(err => { - console.info("Unpredicted error occurred when trying to create the PR."); - console.error(err); - }); - if (!PR || PR.status !== 201) { - return; - } - const PRNumber = PR.data.number; - await github.issues.addLabels({ - owner: 'freeCodeCamp', - repo: 'freeCodeCamp', - issue_number: PRNumber, - labels: [ - "crowdin-sync", - "scope: i18n", - "scope: learn" - ] - }); - await github.pulls.requestReviewers({ - owner: 'freeCodeCamp', - repo: 'freeCodeCamp', - pull_number: PRNumber, - reviewers: [ - 'RandellDawson', - 'nhcarrigan' - ] - }); + branch: 'i18n-sync-learn' + owner-repo: 'freeCodeCamp/freeCodeCamp' + base: 'main' + title: 'chore(i18n,learn): processed translations', + body: 'This PR was opened auto-magically by Crowdin.' + labels: 'crowdin-sync, scope: i18n, scope: learn' + reviewers: 'RandellDawson, nhcarrigan' diff --git a/.github/workflows/crowdin-i18n-docs-download..yml b/.github/workflows/crowdin-i18n-docs-download..yml index 1b58f79316..520c6655a0 100644 --- a/.github/workflows/crowdin-i18n-docs-download..yml +++ b/.github/workflows/crowdin-i18n-docs-download..yml @@ -118,62 +118,21 @@ jobs: CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID_DOCS }} CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_CAMPERBOT_SERVICE_TOKEN }} + - name: Install Dependencies + working-directory: ./tools + run: | + cd ./crowdin + npm ci + # Generate PR - all languages should go ABOVE this. # - name: Create PR - uses: actions/github-script@v3 + uses: ./tools/crowdin/actions/pr-creator with: github-token: ${{ secrets.CROWDIN_CAMPERBOT_PAT }} - script: | - const branchExists = await github.repos.getBranch({ - owner: 'freeCodeCamp', - repo: 'freeCodeCamp', - branch: 'i18n-sync-docs' - }).catch(err => { - console.info("Branch does not exist. Likely no changes in download?"); - }); - if (!branchExists || branchExists.status !== 200) { - return; - } - const pullRequestExists = await github.pulls.list({ - owner: 'freeCodeCamp', - repo: 'freeCodeCamp', - head: 'freeCodeCamp:i18n-sync-docs' - }); - if (pullRequestExists.data.length) { - console.info("It looks like a pull request already exists for this branch."); - return; - } - const PR = await github.pulls.create({ - owner: 'freeCodeCamp', - repo: 'freeCodeCamp', - head: 'i18n-sync-docs', - base: 'main', - title: 'chore(i18n,docs): processed translations', - body: 'This PR was opened auto-magically by Crowdin.' - }).catch(err => { - console.info("Unpredicted error occurred when trying to create the PR."); - console.error(err); - }); - if (!PR || PR.status !== 201) { - return; - } - const PRNumber = PR.data.number; - await github.issues.addLabels({ - owner: 'freeCodeCamp', - repo: 'freeCodeCamp', - issue_number: PRNumber, - labels: [ - "crowdin-sync", - "scope: i18n", - "scope: docs" - ] - }); - await github.pulls.requestReviewers({ - owner: 'freeCodeCamp', - repo: 'freeCodeCamp', - pull_number: PRNumber, - reviewers: [ - 'RandellDawson', - 'nhcarrigan' - ] - }); + branch: 'i18n-sync-docs' + owner-repo: 'freeCodeCamp/freeCodeCamp' + base: 'main' + title: 'chore(i18n,docs): processed translations', + body: 'This PR was opened auto-magically by Crowdin.' + labels: 'crowdin-sync, scope: i18n, scope: docs' + reviewers: 'RandellDawson, nhcarrigan' diff --git a/tools/crowdin/actions/pr-creator/action.yml b/tools/crowdin/actions/pr-creator/action.yml new file mode 100644 index 0000000000..11a35dba60 --- /dev/null +++ b/tools/crowdin/actions/pr-creator/action.yml @@ -0,0 +1,31 @@ +name: 'Create Crowdin PRs' +description: "Creates a PR by camperbot for Crowdin translation downloads" +runs: + using: 'node12' + main: './index.js' +inputs: + github-token: + description: 'PAT with write access to create PRs' + required: true + branch: + description: 'Branch name to which commits are made' + required: true + owner-repo: + description: 'owner and repo name specified as ownerName/repoName' + required: true + base: + description: 'base branch name' + default: 'main' + required: true + title: + description: 'PR title' + required: true + body: + description: 'PR body text' + required: true + labels: + description: 'PR labels' + required: false + reviewers: + description: 'Requested PR reviewers' + required: false diff --git a/tools/crowdin/actions/pr-creator/index.js b/tools/crowdin/actions/pr-creator/index.js new file mode 100644 index 0000000000..c63824c04f --- /dev/null +++ b/tools/crowdin/actions/pr-creator/index.js @@ -0,0 +1,90 @@ +/* eslint-disable import/no-unresolved */ +/* eslint-disable camelcase */ +const core = require('@actions/core'); +const githubRoot = require('@actions/github'); + +(async () => { + try { + const token = core.getInput('github-token'); + const branch = core.getInput('branch'); + const [owner, repo] = core.getInput('owner-repo').split('/'); + if (!owner || !repo) { + core.setFailed('Must specify a valid ownerName/repoName'); + } + const base = core.getInput('base'); + const title = core.getInput('title'); + const body = core.getInput('body'); + const labelsStr = core.getInput('labels'); + const labels = labelsStr.trim().split(/,\s+/); + const reviewersStr = core.getInput('reviewers'); + const reviewers = reviewersStr.trim().split(/,\s+/); + + const github = githubRoot.getOctokit(token); + + const branchExists = await github.repos + .getBranch({ + owner, + repo, + branch + }) + .catch(() => { + console.info('Branch does not exist. Likely no changes in download?'); + }); + if (!branchExists || branchExists.status !== 200) { + return; + } + const pullRequestExists = await github.pulls.list({ + owner, + repo, + head: `${owner}:${branch}` + }); + if (pullRequestExists.data.length) { + console.info( + 'It looks like a pull request already exists for this branch.' + ); + return; + } + const PR = await github.pulls + .create({ + owner, + repo, + head: branch, + base, + title, + body + }) + .catch(err => { + console.info( + 'Unpredicted error occurred when trying to create the PR.' + ); + console.error(err); + }); + if (!PR || PR.status !== 201) { + return; + } + const prNumber = PR.data.number; + console.log( + `https://github.com/freeCodeCamp/freeCodeCamp/pull/${prNumber} created` + ); + if (labels && labels.length) { + await github.issues.addLabels({ + owner, + repo, + issue_number: prNumber, + labels + }); + console.log(`Labels ${labels} added to PR`); + } + if (reviewers && reviewers.length) { + await github.pulls.requestReviewers({ + owner, + repo, + pull_number: prNumber, + reviewers + }); + console.log(`Requested Reviewers ${reviewers} added to PR`); + } + } catch (error) { + core.setFailed(error.message); + } +})(); diff --git a/tools/crowdin/package-lock.json b/tools/crowdin/package-lock.json index bc3de3d70a..99bc72ac84 100644 --- a/tools/crowdin/package-lock.json +++ b/tools/crowdin/package-lock.json @@ -9,6 +9,121 @@ "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.6.tgz", "integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA==" }, + "@actions/github": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@actions/github/-/github-4.0.0.tgz", + "integrity": "sha512-Ej/Y2E+VV6sR9X7pWL5F3VgEWrABaT292DRqRU6R4hnQjPtC/zD3nagxVdXWiRQvYDh8kHXo7IDmG42eJ/dOMA==", + "requires": { + "@actions/http-client": "^1.0.8", + "@octokit/core": "^3.0.0", + "@octokit/plugin-paginate-rest": "^2.2.3", + "@octokit/plugin-rest-endpoint-methods": "^4.0.0" + } + }, + "@actions/http-client": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.9.tgz", + "integrity": "sha512-0O4SsJ7q+MK0ycvXPl2e6bMXV7dxAXOGjrXS1eTF9s2S401Tp6c/P3c3Joz04QefC1J6Gt942Wl2jbm3f4mLcg==", + "requires": { + "tunnel": "0.0.6" + } + }, + "@octokit/auth-token": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.5.tgz", + "integrity": "sha512-BpGYsPgJt05M7/L/5FoE1PiAbdxXFZkX/3kDYcsvd1v6UhlnE5e96dTDr0ezX/EFwciQxf3cNV0loipsURU+WA==", + "requires": { + "@octokit/types": "^6.0.3" + } + }, + "@octokit/core": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.2.5.tgz", + "integrity": "sha512-+DCtPykGnvXKWWQI0E1XD+CCeWSBhB6kwItXqfFmNBlIlhczuDPbg+P6BtLnVBaRJDAjv+1mrUJuRsFSjktopg==", + "requires": { + "@octokit/auth-token": "^2.4.4", + "@octokit/graphql": "^4.5.8", + "@octokit/request": "^5.4.12", + "@octokit/types": "^6.0.3", + "before-after-hook": "^2.1.0", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/endpoint": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.11.tgz", + "integrity": "sha512-fUIPpx+pZyoLW4GCs3yMnlj2LfoXTWDUVPTC4V3MUEKZm48W+XYpeWSZCv+vYF1ZABUm2CqnDVf1sFtIYrj7KQ==", + "requires": { + "@octokit/types": "^6.0.3", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/graphql": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.6.0.tgz", + "integrity": "sha512-CJ6n7izLFXLvPZaWzCQDjU/RP+vHiZmWdOunaCS87v+2jxMsW9FB5ktfIxybRBxZjxuJGRnxk7xJecWTVxFUYQ==", + "requires": { + "@octokit/request": "^5.3.0", + "@octokit/types": "^6.0.3", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/openapi-types": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-5.3.1.tgz", + "integrity": "sha512-TvVk2QuIA0lQZcIMd6xbdGaGDVeNYIOa3l1ZVagAIk5K3t/WMYbcg4BISNDhzdVhm/TgQB26frAgd/GV81aHJA==" + }, + "@octokit/plugin-paginate-rest": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.11.0.tgz", + "integrity": "sha512-7L9xQank2G3r1dGqrVPo1z62V5utbykOUzlmNHPz87Pww/JpZQ9KyG5CHtUzgmB4n5iDRKYNK/86A8D98HP0yA==", + "requires": { + "@octokit/types": "^6.11.0" + } + }, + "@octokit/plugin-rest-endpoint-methods": { + "version": "4.13.4", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.13.4.tgz", + "integrity": "sha512-MGxptzVfiP8O+aydC/riheYzS/yJ9P16M29OuvtZep/sF5sKuOCQP8Wf83YCKXRsQF+ZpYfke2snbPPSIMZKzg==", + "requires": { + "@octokit/types": "^6.12.0", + "deprecation": "^2.3.1" + } + }, + "@octokit/request": { + "version": "5.4.14", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.4.14.tgz", + "integrity": "sha512-VkmtacOIQp9daSnBmDI92xNIeLuSRDOIuplp/CJomkvzt7M18NXgG044Cx/LFKLgjKt9T2tZR6AtJayba9GTSA==", + "requires": { + "@octokit/endpoint": "^6.0.1", + "@octokit/request-error": "^2.0.0", + "@octokit/types": "^6.7.1", + "deprecation": "^2.0.0", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.1", + "once": "^1.4.0", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/request-error": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.5.tgz", + "integrity": "sha512-T/2wcCFyM7SkXzNoyVNWjyVlUwBvW3igM3Btr/eKYiPmucXTtkxt2RBsf6gn3LTzaLSLTQtNmvg+dGsOxQrjZg==", + "requires": { + "@octokit/types": "^6.0.3", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "@octokit/types": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.12.0.tgz", + "integrity": "sha512-KwOf16soD7aDEEi/PgNeJlHzjZPfrmmNy+7WezSdrpnqZ7YImBJcNnX9+5RUHt1MnA4h8oISRHTqaZDGsX9DRQ==", + "requires": { + "@octokit/openapi-types": "^5.3.0" + } + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -17,6 +132,16 @@ "sprintf-js": "~1.0.2" } }, + "before-after-hook": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.0.tgz", + "integrity": "sha512-jH6rKQIfroBbhEXVmI7XmXe3ix5S/PgJqpzdDPnR8JGLHWNYLsYZ6tK5iWOF/Ra3oqEX0NobXGlzbiylIzVphQ==" + }, + "deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + }, "dotenv": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", @@ -51,6 +176,11 @@ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" }, + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" + }, "js-yaml": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", @@ -70,6 +200,14 @@ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, "picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", @@ -101,6 +239,21 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", "integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=" + }, + "tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==" + }, + "universal-user-agent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" } } } diff --git a/tools/crowdin/package.json b/tools/crowdin/package.json index 13b3a4c001..87d8c850a9 100644 --- a/tools/crowdin/package.json +++ b/tools/crowdin/package.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "@actions/core": "^1.2.6", + "@actions/github": "^4.0.0", "dotenv": "^8.2.0", "gray-matter": "^4.0.2", "node-fetch": "^2.6.1",