diff --git a/.github/sync-repo-settings.yaml b/.github/sync-repo-settings.yaml index 4a30a08..1350fae 100644 --- a/.github/sync-repo-settings.yaml +++ b/.github/sync-repo-settings.yaml @@ -9,9 +9,9 @@ branchProtectionRules: - "ci/kokoro: System test" - docs - lint - - test (12) - test (14) - test (16) + - test (18) - cla/google - windows - OwlBot Post Processor diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f447b84..4484516 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [12, 14, 16] + node: [14, 16, 18] steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 diff --git a/.mocharc.js b/.mocharc.cjs similarity index 100% rename from .mocharc.js rename to .mocharc.cjs diff --git a/.prettierrc.js b/.prettierrc.cjs similarity index 100% rename from .prettierrc.js rename to .prettierrc.cjs diff --git a/owlbot.py b/owlbot.py index dfc35ac..46cf3ea 100644 --- a/owlbot.py +++ b/owlbot.py @@ -14,4 +14,4 @@ import synthtool.languages.node as node -node.owlbot_main(templates_excludes=["README.md"]) +node.owlbot_main(templates_excludes=["README.md", ".prettierrc.js", ".mocharc.js"]) diff --git a/package.json b/package.json index a8af60b..03b0a46 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "repository": "googleapis/github-repo-automation", "description": "A tool for automating multiple GitHub repositories.", "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "bin": { "repo": "build/src/cli.js" @@ -14,39 +14,40 @@ "!build/src/**/*.map" ], "license": "Apache-2.0", + "type": "module", "dependencies": { - "@types/command-line-usage": "^5.0.1", - "@types/tmp": "^0.2.0", - "chalk": "^4.0.0", - "command-line-usage": "^6.1.0", + "@types/command-line-usage": "^5.0.2", + "@types/tmp": "^0.2.3", + "chalk": "^5.0.1", + "command-line-usage": "^6.1.3", "extend": "^3.0.2", - "gaxios": "^4.0.0", - "js-yaml": "^4.0.0", - "meow": "^9.0.0", - "ora": "^5.0.0", - "p-queue": "^6.3.0", + "gaxios": "^5.0.1", + "js-yaml": "^4.1.0", + "meow": "^10.1.3", + "ora": "^6.1.2", + "p-queue": "^7.2.0", "text-encoding-shim": "^1.0.5", - "tmp-promise": "^3.0.0", + "tmp-promise": "^3.0.3", "tweetsodium": "0.0.5", - "update-notifier": "^5.0.0" + "update-notifier": "^6.0.2" }, "devDependencies": { - "@compodoc/compodoc": "^1.1.11", - "@types/js-yaml": "^4.0.0", + "@compodoc/compodoc": "^1.1.19", + "@types/js-yaml": "^4.0.5", "@types/meow": "^6.0.0", - "@types/mocha": "^9.0.0", - "@types/node": "^16.0.0", + "@types/mocha": "^9.1.1", + "@types/node": "^18.6.1", "@types/proxyquire": "^1.3.28", - "@types/sinon": "^10.0.0", - "@types/update-notifier": "^5.0.0", - "c8": "^7.1.0", + "@types/sinon": "^10.0.13", + "@types/update-notifier": "^6.0.1", + "c8": "^7.12.0", "gts": "^3.1.0", - "linkinator": "^2.0.4", - "mocha": "^9.2.2", - "nock": "^13.0.0", + "linkinator": "^4.0.2", + "mocha": "^10.0.0", + "nock": "^13.2.9", "proxyquire": "^2.1.3", "sinon": "^14.0.0", - "typescript": "^4.6.4" + "typescript": "^4.7.4" }, "scripts": { "lint": "gts check", diff --git a/samples/add-collaborator.js b/samples/add-collaborator.cjs similarity index 100% rename from samples/add-collaborator.js rename to samples/add-collaborator.cjs diff --git a/samples/change-circleci-config-in-branch.js b/samples/change-circleci-config-in-branch.js deleted file mode 100644 index 464c81d..0000000 --- a/samples/change-circleci-config-in-branch.js +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2018 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @fileoverview Apply a quick fix to CircleCI configuration. - */ - -'use strict'; - -const updateFileInBranch = require('../build/src/lib/update-file-in-branch.js'); - -/** Renames incorrectly named yaml reference in the CircleCI config file. - * @param {string} circleConfigText CircleCI configuration yaml file. - * @returns {string} Returns updated config file, or undefined if anything is - * wrong. - */ -function fixCircleConfig(circleConfigText) { - const newText = circleConfigText - .toString() - .replace(new RegExp('ref_0', 'g'), 'unit_tests'); - if (newText === circleConfigText) { - return; - } - return newText; -} - -/** Main function. - */ -async function main() { - await updateFileInBranch({ - path: '.circleci/config.yml', - patchFunction: fixCircleConfig, - branch: 'remove-node7-test', - message: 'chore: rename reference', - }); -} - -main().catch(err => { - console.error(err.toString()); -}); diff --git a/samples/chmod-repo-tools.js b/samples/chmod-repo-tools.js deleted file mode 100644 index bd93ca5..0000000 --- a/samples/chmod-repo-tools.js +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2018 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @fileoverview Fix CircleCI configuration file in all repositories: remove - * node7 task from workflows and dependencies. - */ - -'use strict'; - -const yaml = require('js-yaml'); -const updateFile = require('../build/src/lib/update-file'); - -/** Inserts a chmod command after npm install to workaround some bug - * that happens only in CircleCI. - * @param {string} circleConfigText CircleCI configuration yaml file. - * @returns {string} Returns updated config file, or undefined if anything is - * wrong. - */ -function process(text) { - const config = yaml.load(text); - - const jobs = config['jobs']; - for (const name of Object.keys(jobs)) { - for (const step of jobs[name]['steps']) { - const run = step['run']; - if ( - run !== undefined && - run['name'].match(/install/i) && - !run['command'].match(/repo_tools=/) - ) { - run['command'] = run['command'].replace( - /npm install/, - 'npm install\nrepo_tools="node_modules/@google-cloud/nodejs-repo-tools/bin/tools"\nif ! test -x "$repo_tools"; then\n chmod +x "$repo_tools"\nfi' - ); - } - } - } - - let newText = yaml.dump(config); - newText = newText.replace(/ref_0/g, 'workflow_jobs'); - newText = newText.replace(/ref_1/g, 'unit_tests_steps'); - newText = newText.replace(/ref_2/g, 'remove_package_lock'); - - if (newText === text) { - return undefined; - } - return newText; -} - -/** Main function. - */ -async function main() { - await updateFile({ - path: '.circleci/config.yml', - patchFunction: process, - branch: 'repo-tools-eperm-workaround-2', - message: 'chore: one more workaround for repo-tools EPERM', - comment: - "Sometimes it just happens, only in CircleCI and never reproduced. Here is a proof that this `chmod` fixes the problem: https://circleci.com/gh/googleapis/nodejs-speech/1376 - let's apply this to all our repos and see if it fails or not.\n\nThis PR fixes system tests and sample tests which were missed by previous PR.", - reviewers: ['stephenplusplus', 'callmehiphop'], - }); -} - -main().catch(err => { - console.error(err.toString()); -}); diff --git a/samples/commit-package-lock.js b/samples/commit-package-lock.js deleted file mode 100644 index 8650d7b..0000000 --- a/samples/commit-package-lock.js +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2018 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @fileoverview Adds nightly builds workflow by copying `test` workflow - * and tweaking it (removing publish_npm part, change filters). - */ - -'use strict'; - -const updateRepo = require('../build/src/lib/update-repo.js'); -const child_process = require('child_process'); -const util = require('util'); -const exec = util.promisify(child_process.exec); - -/** Runs npm install, removes line `*-lock.js*` from `.gitignore`, and prepares - * `package-lock.json` and `.gitignore` for check-in. - * @param {string} Path to a directory where cloned repository is located. - * @returns {string[]} Returns list of files to commit. - */ -async function commitPackageLockJson(dir) { - try { - const cwd = process.cwd(); - process.chdir(dir); - await exec('npm install --package-lock-only'); - await exec("perl -pi -e 's/\\*-lock.js\\*\\n//' .gitignore"); - process.chdir(cwd); - return ['package-lock.json', '.gitignore']; - } catch (err) { - console.warn('update failed!', err.toString()); - return undefined; - } -} - -/** Main function. - */ -async function main() { - await updateRepo({ - updateCallback: commitPackageLockJson, - branch: 'add-package-lock', - message: 'chore: add package-lock.json', - comment: "As discussed with the team, let's commit `package-lock.json`.", - reviewers: ['stephenplusplus', 'callmehiphop', 'googleapis/node-team'], - }); -} - -main().catch(err => { - console.error(err.toString()); -}); diff --git a/samples/create-secret.js b/samples/create-secret.cjs similarity index 100% rename from samples/create-secret.js rename to samples/create-secret.cjs diff --git a/samples/misc/add-node10.js b/samples/misc/add-node10.cjs similarity index 100% rename from samples/misc/add-node10.js rename to samples/misc/add-node10.cjs diff --git a/samples/misc/system-test-timeout.js b/samples/misc/system-test-timeout.cjs similarity index 100% rename from samples/misc/system-test-timeout.js rename to samples/misc/system-test-timeout.cjs diff --git a/samples/remove-node7.js b/samples/remove-node7.js deleted file mode 100644 index 4f035d0..0000000 --- a/samples/remove-node7.js +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright 2018 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @fileoverview Fix CircleCI configuration file in all repositories: remove - * node7 task from workflows and dependencies. - */ - -'use strict'; - -const yaml = require('js-yaml'); -const updateFile = require('../build/src/lib/update-file.js'); - -/** A helper function to remove a job from CirleCI job array. - * @param {Object[]} jobArray Jobs, as written in CircleCI config yaml. - * @param {string} jobNameToRemove Job to remove, e.g. node7. - * @returns {boolean} true if the change was successful, false otherwise. - */ -function removeJobFromArray(jobArray, jobNameToRemove) { - let idToDelete; - for (const index in jobArray) { - const job = jobArray[index]; - const keys = Object.keys(job); - const name = keys[0]; - if (name === jobNameToRemove) { - if (idToDelete === undefined) { - idToDelete = index; - } else { - console.warn( - ` two jobs ${jobNameToRemove} found in jobs array, canceling change` - ); - return false; - } - } - } - - if (idToDelete === undefined) { - console.warn( - ` job ${jobNameToRemove} was not found in jobs array, canceling change` - ); - return false; - } - jobArray.splice(idToDelete, 1); - return true; -} - -/** Remove all references to the given job from CircleCI config file. - * @param {string} circleConfigText CircleCI configuration yaml file. - * @param {string} jobNameToRemove Job name, e.g. node7. - * @returns {string} Returns updated config file, or undefined if anything is - * wrong. - */ -function removeJobFromCircleConfig(circleConfigText, jobNameToRemove) { - const circleConfigYaml = yaml.load(circleConfigText); - - if ( - !removeJobFromArray( - circleConfigYaml['workflows']['tests']['jobs'], - jobNameToRemove - ) - ) { - console.warn( - ` cannot remove job ${jobNameToRemove} from workflow 'tests'` - ); - return; - } - - delete circleConfigYaml['jobs'][jobNameToRemove]; - - for (const job of circleConfigYaml['workflows']['tests']['jobs']) { - const keys = Object.keys(job); - const name = keys[0]; - - if (job[name]['requires'] !== undefined) { - const indexToRemove = job[name]['requires'].indexOf(jobNameToRemove); - if (indexToRemove !== -1) { - job[name]['requires'].splice(indexToRemove, 1); - } - } - } - - return yaml.dump(circleConfigYaml); -} - -/** Removes node7 job from CircleCI configuration yaml file. Used as a callback - * to `updateFile`. - * @param {string} circleConfigText CircleCI configuration yaml file. - * @returns {string} Returns updated config file, or undefined if anything is - * wrong. - */ -function removeNode7FromCircleConfig(circleConfigText) { - return removeJobFromCircleConfig(circleConfigText, 'node7'); -} - -/** Main function. - */ -async function main() { - await updateFile({ - path: '.circleci/config.yml', - patchFunction: removeNode7FromCircleConfig, - branch: 'remove-node7-test', - message: 'chore: removing node7 job from CircleCI', - comment: - "We don't need to test on Node 7 anymore. Note: no action is required for this PR for now.", - reviewers: ['stephenplusplus', 'callmehiphop'], - }); -} - -main().catch(err => { - console.error(err.toString()); -}); diff --git a/samples/setup-nighty-builds.js b/samples/setup-nighty-builds.js deleted file mode 100644 index 1408f97..0000000 --- a/samples/setup-nighty-builds.js +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright 2018 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @fileoverview Adds nightly builds workflow by copying `test` workflow - * and tweaking it (removing publish_npm part, change filters). - */ - -'use strict'; - -const path = require('path'); -const yaml = require('js-yaml'); -const fs = require('fs'); -const util = require('util'); -const readFile = util.promisify(fs.readFile); -const writeFile = util.promisify(fs.writeFile); -const updateRepo = require('../build/src/lib/update-repo.js'); - -/** Copies `test` workflow to a `nightly` workflow running every night - * at 7am UTC. - * Branch filters are set to `main`. - * Adds a Python script to detect if the current CI task is a nightly build - * or not (to be able to remove package-lock.json in case of a nightly build). - * @param {string} repoPath Path to a cloned repository. - * @returns {Promise} A promise that resolves to a list of filenames - * to be added or edited, and checked in. - */ -async function process(repoPath) { - const circleConfigPath = path.join('.circleci', 'config.yml'); - const circleConfigFullPath = path.join(repoPath, circleConfigPath); - const text = (await readFile(circleConfigFullPath)).toString(); - const config = yaml.load(text); - - const removePackageLock = { - name: 'Remove package-lock.json if needed.', - command: `WORKFLOW_NAME=\`python .circleci/get_workflow_name.py\` -echo "Workflow name: $WORKFLOW_NAME" -if [ "$WORKFLOW_NAME" = "nightly" ]; then - echo "Nightly build detected, removing package-lock.json." - rm -f package-lock.json samples/package-lock.json -else - echo "Not a nightly build, skipping this step." -fi -`, - }; - - const unitTestsSteps = config['unit_tests']['steps']; - delete config['unit_tests']; - unitTestsSteps.splice(1, 0, { - run: removePackageLock, - }); - - const jobs = config['jobs']; - for (const name of Object.keys(jobs)) { - if (name.match(/^node\d+$/)) { - jobs[name]['steps'] = unitTestsSteps; - } else if (name !== 'publish_npm') { - jobs[name]['steps'].splice(1, 0, { - run: removePackageLock, - }); - } - } - - const testJobs = config['workflows']['tests']['jobs']; - config['workflows']['nightly'] = { - triggers: [ - { - schedule: { - cron: '0 7 * * *', - filters: { - branches: { - only: 'main', - }, - }, - }, - }, - ], - jobs: testJobs, - }; - - let newText = yaml.dump(config); - newText = newText.replace(/ref_0/g, 'workflow_jobs'); - newText = newText.replace(/ref_1/g, 'unit_tests_steps'); - newText = newText.replace(/ref_2/g, 'remove_package_lock'); - await writeFile(circleConfigFullPath, newText); - - const pythonScriptPath = path.join('.circleci', 'get_workflow_name.py'); - const pythonScriptSourceFolder = '/tmp/source_folder'; - const pythonScript = await readFile( - path.join(pythonScriptSourceFolder, pythonScriptPath) - ); - await writeFile(path.join(repoPath, pythonScriptPath), pythonScript); - - return Promise.resolve([pythonScriptPath, circleConfigPath]); -} - -/** Main function. - */ -async function main() { - await updateRepo({ - updateCallback: process, - branch: 'setup-nightly-build-workflow', - message: 'chore: setup nighty build in CircleCI', - comment: - 'Creating a new workflow called `nightly` that will run every night. It will remove `package-lock.json`, then run the build.', - // reviewers: ['stephenplusplus', 'callmehiphop'], - }); -} - -main().catch(err => { - console.error(err.toString()); -}); diff --git a/samples/update-admin-branch-protection.js b/samples/update-admin-branch-protection.cjs similarity index 100% rename from samples/update-admin-branch-protection.js rename to samples/update-admin-branch-protection.cjs diff --git a/samples/update-branch-protection.js b/samples/update-branch-protection.cjs similarity index 100% rename from samples/update-branch-protection.js rename to samples/update-branch-protection.cjs diff --git a/samples/update-kokoro-branch-protection.js b/samples/update-kokoro-branch-protection.cjs similarity index 100% rename from samples/update-kokoro-branch-protection.js rename to samples/update-kokoro-branch-protection.cjs diff --git a/src/apply-change.ts b/src/apply-change.ts index ab185c2..688680b 100644 --- a/src/apply-change.ts +++ b/src/apply-change.ts @@ -18,11 +18,10 @@ */ import * as childProcess from 'child_process'; -import * as commandLineUsage from 'command-line-usage'; -import {updateRepo, UpdateRepoOptions} from './lib/update-repo'; -import {question} from './lib/question'; -import * as meow from 'meow'; -import {meowFlags} from './cli'; +import commandLineUsage from 'command-line-usage'; +import {updateRepo, UpdateRepoOptions} from './lib/update-repo.js'; +import {question} from './lib/question.js'; +import meow from 'meow'; // tslint:disable-next-line:no-any const exec = (command: string, options?: object): Promise => @@ -120,7 +119,7 @@ async function getFilesToCommit() { * @param {Object} options Options object, as returned by meow. * @returns {Boolean} True if OK to continue, false otherwise. */ -function checkOptions(cli: meow.Result) { +function checkOptions(cli: ReturnType) { if (cli.flags.help) { console.log(commandLineUsage(helpSections)); return false; @@ -164,10 +163,7 @@ function checkOptions(cli: meow.Result) { * @returns {Promise} A promise resolving to a list of files to * commit. */ -async function updateCallback( - cli: meow.Result, - repoPath: string -) { +async function updateCallback(cli: ReturnType, repoPath: string) { const cwd = process.cwd(); try { process.chdir(repoPath); @@ -202,7 +198,7 @@ async function updateCallback( /** * Main function. */ -export async function main(cli: meow.Result) { +export async function main(cli: ReturnType) { if (!checkOptions(cli)) { return; } diff --git a/src/approve-prs.ts b/src/approve-prs.ts index aba5653..a4dafd5 100644 --- a/src/approve-prs.ts +++ b/src/approve-prs.ts @@ -13,10 +13,9 @@ // limitations under the License. import * as meow from 'meow'; -import {meowFlags} from './cli'; -import {GitHubRepository, PullRequest} from './lib/github'; -import {processPRs} from './lib/asyncItemIterator'; +import {GitHubRepository, PullRequest} from './lib/github.js'; +import {processPRs} from './lib/asyncItemIterator.js'; async function processMethod(repository: GitHubRepository, pr: PullRequest) { try { diff --git a/src/cli.ts b/src/cli.ts index 00c188f..5f7ae3e 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -14,67 +14,31 @@ // See the License for the specific language governing permissions and // limitations under the License. -import {main as apply} from './apply-change'; -import {list} from './list-prs'; -import {listIssues} from './list-issues'; -import {approve} from './approve-prs'; -import {rename} from './rename-prs'; -import {reject} from './reject-prs'; -import {update} from './update-prs'; -import {merge} from './merge-prs'; -import {main as check} from './repo-check'; -import {sync, exec} from './sync'; -import * as meow from 'meow'; -import * as updateNotifier from 'update-notifier'; -import {tag} from './tag-prs'; -import {untag} from './untag-prs'; -/* eslint-disable @typescript-eslint/no-var-requires */ -const pkg = require('../../package.json'); +import {main as apply} from './apply-change.js'; +import {list} from './list-prs.js'; +import {listIssues} from './list-issues.js'; +import {approve} from './approve-prs.js'; +import {rename} from './rename-prs.js'; +import {reject} from './reject-prs.js'; +import {update} from './update-prs.js'; +import {merge} from './merge-prs.js'; +import {main as check} from './repo-check.js'; +import {sync, exec} from './sync.js'; +import meow from 'meow'; +import updateNotifier from 'update-notifier'; +import {tag} from './tag-prs.js'; +import {untag} from './untag-prs.js'; +import * as fs from 'fs'; +import * as path from 'path'; +import {fileURLToPath} from 'url'; +const filename = fileURLToPath(import.meta.url); +const dirname = path.dirname(filename); +const pkg = JSON.parse( + fs.readFileSync(path.join(dirname, '..', '..', 'package.json')).toString() +); updateNotifier({pkg}).notify(); -export const meowFlags: { - [key: string]: {type: 'string' | 'boolean' | 'number'; alias?: string}; -} = { - branch: { - type: 'string', - alias: 'b', - }, - message: { - type: 'string', - alias: 'm', - }, - comment: { - type: 'string', - alias: 'c', - }, - reviewers: { - type: 'string', - alias: 'r', - }, - silent: { - type: 'boolean', - alias: 'q', - }, - title: { - type: 'string', - alias: 't', - }, - delay: { - type: 'number', - }, - retry: { - type: 'boolean', - }, - auto: {type: 'boolean'}, - concurrency: {type: 'string'}, - author: {type: 'string'}, - yespleasedoit: {type: 'boolean'}, -}; -const meowOptions: meow.Options = { - flags: meowFlags, -}; - const cli = meow( ` Usage @@ -96,7 +60,45 @@ const cli = meow( $ repo exec -- git status $ repo exec --concurrency 10 -- git status `, - meowOptions + { + importMeta: import.meta, + flags: { + branch: { + type: 'string', + alias: 'b', + }, + message: { + type: 'string', + alias: 'm', + }, + comment: { + type: 'string', + alias: 'c', + }, + reviewers: { + type: 'string', + alias: 'r', + }, + silent: { + type: 'boolean', + alias: 'q', + }, + title: { + type: 'string', + alias: 't', + }, + delay: { + type: 'number', + }, + retry: { + type: 'boolean', + }, + auto: {type: 'boolean'}, + concurrency: {type: 'string'}, + author: {type: 'string'}, + yespleasedoit: {type: 'boolean'}, + }, + } ); if (cli.input.length < 1) { diff --git a/src/lib/asyncItemIterator.ts b/src/lib/asyncItemIterator.ts index 617ab98..959a539 100644 --- a/src/lib/asyncItemIterator.ts +++ b/src/lib/asyncItemIterator.ts @@ -13,14 +13,13 @@ // limitations under the License. import * as meow from 'meow'; -import {meowFlags} from '../cli'; import Q from 'p-queue'; -import ora = require('ora'); +import ora from 'ora'; import {debuglog} from 'util'; const debug = debuglog('repo'); -import * as configLib from './config'; -import {GitHub, GitHubRepository, PullRequest, Issue} from './github'; +import * as configLib from './config.js'; +import {GitHub, GitHubRepository, PullRequest, Issue} from './github.js'; /** * Retry the promise returned by a function if the promise throws @@ -157,7 +156,7 @@ async function process( // processing many repos in a row to avoid rate limits: const delay: number = cli.flags.delay ? Number(cli.flags.delay) : 500; const retry: boolean = cli.flags.retry ? Boolean(cli.flags.retry) : false; - const config = await configLib.getConfig(); + const config = await configLib.GetConfig.getConfig(); const retryStrategy = retry ? config.retryStrategy ?? [3000, 6000, 15000, 30000, 60000] : []; diff --git a/src/lib/config.ts b/src/lib/config.ts index 3dce7b8..c00c0dc 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -25,31 +25,33 @@ import * as os from 'os'; const cache = new Map(); -export async function getConfig(configFilename?: string) { - let filename: string; - if (configFilename) { - filename = configFilename; - } else if (process.env.REPO_CONFIG_PATH) { - filename = process.env.REPO_CONFIG_PATH; - } else { - filename = './config.yaml'; - } +export class GetConfig { + static async getConfig(configFilename?: string) { + let filename: string; + if (configFilename) { + filename = configFilename; + } else if (process.env.REPO_CONFIG_PATH) { + filename = process.env.REPO_CONFIG_PATH; + } else { + filename = './config.yaml'; + } - if (cache.has(filename)) { - return cache.get(filename)!; - } + if (cache.has(filename)) { + return cache.get(filename)!; + } - try { - const yamlContent = await readFile(filename, {encoding: 'utf8'}); - const config = yaml.load(yamlContent) as Config; - cache.set(filename, config); - config.clonePath = config.clonePath || path.join(os.homedir(), '.repo'); - return config; - } catch (err) { - console.error( - `Cannot read configuration file ${filename}. Have you created it? Use config.yaml.default as a sample.` - ); - throw new Error('Configuration file is not found'); + try { + const yamlContent = await readFile(filename, {encoding: 'utf8'}); + const config = yaml.load(yamlContent) as Config; + cache.set(filename, config); + config.clonePath = config.clonePath || path.join(os.homedir(), '.repo'); + return config; + } catch (err) { + console.error( + `Cannot read configuration file ${filename}. Have you created it? Use config.yaml.default as a sample.` + ); + throw new Error('Configuration file is not found'); + } } } diff --git a/src/lib/github.ts b/src/lib/github.ts index 581da4c..10f8048 100644 --- a/src/lib/github.ts +++ b/src/lib/github.ts @@ -17,7 +17,7 @@ */ import {Gaxios, GaxiosPromise, GaxiosOptions} from 'gaxios'; -import {Config} from './config'; +import {Config} from './config.js'; import {debuglog} from 'util'; const debug = debuglog('repo'); diff --git a/src/lib/logger.ts b/src/lib/logger.ts index 72dc236..39512b2 100644 --- a/src/lib/logger.ts +++ b/src/lib/logger.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import chalk = require('chalk'); +import chalk from 'chalk'; import * as fs from 'fs'; import {Writable} from 'stream'; diff --git a/src/lib/update-file-in-branch.ts b/src/lib/update-file-in-branch.ts deleted file mode 100644 index ffbb7df..0000000 --- a/src/lib/update-file-in-branch.ts +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2018 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @fileoverview Performs a common tasks of applying the same change to one file - * in the given branch in many GitHub repositories. - */ - -import {getConfig} from './config'; -import {GitHub, GitHubRepository} from './github'; - -/** - * Updates and commits one existing file in the given branch of the given - * repository. - * @param {GitHubRepository} repository Repository to work with. - * @param {string} branch Name of an existing branch to update. - * @param {string} path Path to an existing file to update. - * @param {patchFunction} patchFunction Callback function that should modify the - * file. - * @param {string} message Commit message. - * @returns {undefined} No return value. Prints its progress to the console. - */ -async function processRepository( - repository: GitHubRepository, - branch: string, - path: string, - patchFunction: Function, - message: string -) { - let file; - try { - file = await repository.getFileFromBranch(branch, path); - } catch (err) { - console.warn( - ' cannot get file, skipping this repository:', - (err as Error).toString() - ); - return; - } - if (file['type'] !== 'file') { - console.warn(' requested path is not file, skipping this repository'); - return; - } - - const oldFileSha = file['sha']; - const decodedContent = Buffer.from(file['content'], 'base64').toString(); - const patchedContent = patchFunction(decodedContent); - if (patchedContent === undefined) { - console.warn( - ' patch function returned undefined value, skipping this repository' - ); - return; - } - const encodedPatchedContent = Buffer.from(patchedContent).toString('base64'); - - try { - await repository.updateFileInBranch( - branch, - path, - message, - encodedPatchedContent, - oldFileSha - ); - } catch (err) { - console.warn( - ` cannot commit file ${path} to branch ${branch}, skipping this repository:`, - (err as Error).toString() - ); - return; - } - - console.log(' success!'); -} - -export interface UpdateFileInBranchOptions { - config: string; - branch: string; - path: string; - patchFunction: Function; - message: string; - comment: string; - reviewers: string[]; -} - -/** - * Updates one existing file in the given branch of all the repositories. - * @param {Object} options Options object, should contain the following fields: - * @param {string} option.config Path to a configuration file. Will use default - * `./config.yaml` if omitted. - * @param {string} options.branch Name for a new branch to use. - * @param {string} options.path Path to an existing file to update. - * @param {patchFunction} options.patchFunction Callback function that should modify the - * file. - * @param {string} options.message Commit message and pull request title. - * @param {string} options.comment Pull request body. - * @param {string[]} options.reviewers Reviewers' GitHub logins for the pull request. - * @returns {undefined} No return value. Prints its progress to the console. - */ -export async function updateFileInBranch(options: UpdateFileInBranchOptions) { - if (options.path === undefined) { - throw new Error('updateFile: path is required'); - } - - if (options.patchFunction === undefined) { - throw new Error('updateFile: patchFunction is required'); - } - - if (options.branch === undefined) { - throw new Error('updateFile: branch is required'); - } - - if (options.message === undefined) { - throw new Error('updateFile: message is required'); - } - - const config = await getConfig(); - const github = new GitHub(config); - const repos = await github.getRepositories(); - for (const repository of repos) { - console.log(repository.name); - await processRepository( - repository, - options.branch, - options.path, - options.patchFunction, - options.message - ); - } -} diff --git a/src/lib/update-file.ts b/src/lib/update-file.ts deleted file mode 100644 index 23c0325..0000000 --- a/src/lib/update-file.ts +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright 2018 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @fileoverview Performs a common tasks of applying the same change to one file - * in many GitHub repositories, and sends pull requests with the change. - */ - -import {getConfig} from './config'; -import {GitHub, GitHubRepository} from './github'; - -/** - * Updates one existing file in the repository and sends a pull request with - * this change. - * @param {GitHubRepository} repository Repository to work with. - * @param {string} path Path to an existing file to update. - * @param {patchFunction} patchFunction Callback function that should modify the - * file. - * @param {string} branch Name for a new branch to use. - * @param {string} message Commit message and pull request title. - * @param {string} comment Pull request body. - * @param {string[]} reviewers Reviewers' GitHub logins for the pull request. - * @returns {undefined} No return value. Prints its progress to the console. - */ -async function processRepository( - repository: GitHubRepository, - path: string, - patchFunction: Function, - branch: string, - message: string, - comment: string, - reviewers: string[] -) { - let file; - try { - file = await repository.getFile(path); - } catch (err) { - console.warn( - ' cannot get file, skipping this repository:', - (err as Error).toString() - ); - return; - } - if (file['type'] !== 'file') { - console.warn(' requested path is not file, skipping this repository'); - return; - } - - const oldFileSha = file['sha']; - const decodedContent = Buffer.from(file['content'], 'base64').toString(); - const patchedContent = patchFunction(decodedContent); - if (patchedContent === undefined) { - console.warn( - ' patch function returned undefined value, skipping this repository' - ); - return; - } - const encodedPatchedContent = Buffer.from(patchedContent).toString('base64'); - - let latestCommit: {[index: string]: string}; - try { - latestCommit = await repository.getLatestCommitToBaseBranch(); - } catch (err) { - console.warn( - ' cannot get sha of latest commit, skipping this repository:', - (err as Error).toString() - ); - return; - } - const latestSha = latestCommit['sha']; - - try { - await repository.createBranch(branch, latestSha); - } catch (err) { - console.warn( - ` cannot create branch ${branch}, skipping this repository:`, - (err as Error).toString() - ); - return; - } - - try { - await repository.updateFileInBranch( - branch, - path, - message, - encodedPatchedContent, - oldFileSha - ); - } catch (err) { - console.warn( - ` cannot commit file ${path} to branch ${branch}, skipping this repository:`, - (err as Error).toString() - ); - return; - } - - let pullRequest; - try { - pullRequest = await repository.createPullRequest(branch, message, comment); - } catch (err) { - console.warn( - ` cannot create pull request for branch ${branch} -> base ${repository.baseBranch}! Branch is still there.`, - (err as Error).toString() - ); - return; - } - const pullRequestNumber = pullRequest.number!; - const pullRequestUrl = pullRequest.html_url; - - if (reviewers.length > 0) { - try { - await repository.requestReview(pullRequestNumber, reviewers); - } catch (err) { - console.warn( - ` cannot request review for pull request #${pullRequestNumber}! Pull request is still there.`, - (err as Error).toString() - ); - return; - } - } - - console.log(` success! ${pullRequestUrl}`); -} - -export interface UpdateFileOptions { - config: string; - path: string; - patchFunction: Function; - branch: string; - message: string; - comment: string; - reviewers: string[]; -} - -/** - * Updates one existing file in the repository and sends a pull request with - * this change. - * @param {Object} options Options object, should contain the following fields: - * @param {string} option.config Path to a configuration file. Will use default - * `./config.yaml` if omitted. - * @param {string} options.path Path to an existing file to update. - * @param {patchFunction} options.patchFunction Callback function that should modify the - * file. - * @param {string} options.branch Name for a new branch to use. - * @param {string} options.message Commit message and pull request title. - * @param {string} options.comment Pull request body. - * @param {string[]} options.reviewers Reviewers' GitHub logins for the pull request. - * @returns {undefined} No return value. Prints its progress to the console. - */ -export async function updateFile(options: UpdateFileOptions) { - if (options.path === undefined) { - throw new Error('updateFile: path is required'); - } - - if (options.patchFunction === undefined) { - throw new Error('updateFile: patchFunction is required'); - } - - if (options.branch === undefined) { - throw new Error('updateFile: branch is required'); - } - - if (options.message === undefined) { - throw new Error('updateFile: message is required'); - } - - const comment = options.comment || ''; - const reviewers = options.reviewers || []; - - const config = await getConfig(); - const github = new GitHub(config); - const repos = await github.getRepositories(); - for (const repository of repos) { - console.log(repository.name); - await processRepository( - repository, - options.path, - options.patchFunction, - options.branch, - options.message, - comment, - reviewers - ); - } -} - -/** - * Callback function that performs required change to the file. The function - * may apply a patch, or parse and change the file, or do whatever it needs. - * @callback patchFunction - * @param {string} content Contents of the file to update. - * @returns {string} Must return `undefined` if the change was not applied for - * any reason. In this case, no change will be committed. If the change was - * applied successfully, return the new contents of the file. - */ diff --git a/src/lib/update-repo.ts b/src/lib/update-repo.ts index 1116c5e..1fee8fa 100644 --- a/src/lib/update-repo.ts +++ b/src/lib/update-repo.ts @@ -23,11 +23,10 @@ import * as path from 'path'; import {promisify} from 'util'; const exec = promisify(child_process.exec); const readFile = promisify(fs.readFile); -/* eslint-disable @typescript-eslint/no-var-requires */ -const tmp = require('tmp-promise'); +import * as tmp from 'tmp-promise'; -import {GitHub, GitHubRepository} from './github'; -import {getConfig} from './config'; +import {GitHub, GitHubRepository} from './github.js'; +import {GetConfig} from './config.js'; /** * Updates files in the cloned repository and sends a pull request with @@ -221,7 +220,7 @@ export async function updateRepo(options: UpdateRepoOptions) { const comment = options.comment || ''; const reviewers = options.reviewers || []; - const config = await getConfig(); + const config = await GetConfig.getConfig(); const github = new GitHub(config); const repos = await github.getRepositories(); for (const repository of repos) { diff --git a/src/list-issues.ts b/src/list-issues.ts index 62acdb4..68779b8 100644 --- a/src/list-issues.ts +++ b/src/list-issues.ts @@ -13,10 +13,9 @@ // limitations under the License. import * as meow from 'meow'; -import {meowFlags} from './cli'; -import {GitHubRepository, Issue} from './lib/github'; -import {processIssues} from './lib/asyncItemIterator'; +import {GitHubRepository, Issue} from './lib/github.js'; +import {processIssues} from './lib/asyncItemIterator.js'; /* eslint-disable @typescript-eslint/no-unused-vars */ async function processMethod(repository: GitHubRepository, issue: Issue) { return true; diff --git a/src/list-prs.ts b/src/list-prs.ts index 0cb78f9..b9601d1 100644 --- a/src/list-prs.ts +++ b/src/list-prs.ts @@ -13,8 +13,7 @@ // limitations under the License. import * as meow from 'meow'; -import {meowFlags} from './cli'; -import {processPRs} from './lib/asyncItemIterator'; +import {processPRs} from './lib/asyncItemIterator.js'; export async function list(cli: meow.Result) { return processPRs(cli, { diff --git a/src/merge-prs.ts b/src/merge-prs.ts index bf505d1..e4f249e 100644 --- a/src/merge-prs.ts +++ b/src/merge-prs.ts @@ -20,8 +20,8 @@ import * as meow from 'meow'; -import {GitHubRepository, PullRequest} from './lib/github'; -import {processPRs} from './lib/asyncItemIterator'; +import {GitHubRepository, PullRequest} from './lib/github.js'; +import {processPRs} from './lib/asyncItemIterator.js'; async function processMethod(repository: GitHubRepository, pr: PullRequest) { const htmlUrl = pr.html_url; diff --git a/src/reject-prs.ts b/src/reject-prs.ts index 020b82f..484b2e9 100644 --- a/src/reject-prs.ts +++ b/src/reject-prs.ts @@ -19,10 +19,9 @@ */ import * as meow from 'meow'; -import {meowFlags} from './cli'; -import {GitHubRepository, PullRequest} from './lib/github'; -import {processPRs} from './lib/asyncItemIterator'; +import {GitHubRepository, PullRequest} from './lib/github.js'; +import {processPRs} from './lib/asyncItemIterator.js'; async function processMethod( repository: GitHubRepository, diff --git a/src/rename-prs.ts b/src/rename-prs.ts index 7eb649a..60f111e 100644 --- a/src/rename-prs.ts +++ b/src/rename-prs.ts @@ -13,10 +13,9 @@ // limitations under the License. import * as meow from 'meow'; -import {meowFlags} from './cli'; -import {GitHubRepository, PullRequest} from './lib/github'; -import {processPRs} from './lib/asyncItemIterator'; +import {GitHubRepository, PullRequest} from './lib/github.js'; +import {processPRs} from './lib/asyncItemIterator.js'; let title: string; diff --git a/src/repo-check.ts b/src/repo-check.ts index b0c3f11..3c437ad 100644 --- a/src/repo-check.ts +++ b/src/repo-check.ts @@ -19,8 +19,8 @@ import {request, GaxiosResponse} from 'gaxios'; -import {getConfig} from './lib/config'; -import {GitHub, GitHubRepository} from './lib/github'; +import {GetConfig} from './lib/config.js'; +import {GitHub, GitHubRepository} from './lib/github.js'; /** * Logs and counts errors and warnings to console with fancy coloring. @@ -278,7 +278,7 @@ async function checkReadmeLinks(logger: Logger, repository: GitHubRepository) { * @param {Logger} logger Logger object. */ async function checkAllRepositories(logger: Logger) { - const config = await getConfig(); + const config = await GetConfig.getConfig(); const github = new GitHub(config); const repos = await github.getRepositories(); let index = 0; diff --git a/src/sync.ts b/src/sync.ts index e4b48b2..797bc21 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -20,15 +20,14 @@ import * as cp from 'child_process'; import * as fs from 'fs'; import * as meow from 'meow'; -import {meowFlags} from './cli'; -import ora = require('ora'); +import ora from 'ora'; import Q from 'p-queue'; import * as path from 'path'; import {promisify} from 'util'; -import {getConfig} from './lib/config'; -import {GitHub} from './lib/github'; -import * as logger from './lib/logger'; +import {GetConfig} from './lib/config.js'; +import {GitHub} from './lib/github.js'; +import * as logger from './lib/logger.js'; const mkdir = promisify(fs.mkdir); const readdir = promisify(fs.readdir); @@ -128,14 +127,14 @@ export async function exec(cli: meow.Result) { } async function getRepos() { - const config = await getConfig(); + const config = await GetConfig.getConfig(); const github = new GitHub(config); const repos = await github.getRepositories(); return repos.filter(x => !x.repository.archived); } async function getRootPath() { - const config = await getConfig(); + const config = await GetConfig.getConfig(); const repoPath = config.clonePath; if (!fs.existsSync(repoPath)) { await mkdir(repoPath); diff --git a/src/tag-prs.ts b/src/tag-prs.ts index 1630d0a..b15d8ba 100644 --- a/src/tag-prs.ts +++ b/src/tag-prs.ts @@ -13,10 +13,9 @@ // limitations under the License. import * as meow from 'meow'; -import {meowFlags} from './cli'; -import {GitHubRepository, PullRequest} from './lib/github'; -import {processPRs} from './lib/asyncItemIterator'; +import {GitHubRepository, PullRequest} from './lib/github.js'; +import {processPRs} from './lib/asyncItemIterator.js'; async function processMethod( repository: GitHubRepository, diff --git a/src/untag-prs.ts b/src/untag-prs.ts index a7b0232..d40b8b7 100644 --- a/src/untag-prs.ts +++ b/src/untag-prs.ts @@ -13,10 +13,9 @@ // limitations under the License. import * as meow from 'meow'; -import {meowFlags} from './cli'; -import {GitHubRepository, PullRequest} from './lib/github'; -import {processPRs} from './lib/asyncItemIterator'; +import {GitHubRepository, PullRequest} from './lib/github.js'; +import {processPRs} from './lib/asyncItemIterator.js'; let name: string; diff --git a/src/update-prs.ts b/src/update-prs.ts index d9b8892..fad00e5 100644 --- a/src/update-prs.ts +++ b/src/update-prs.ts @@ -20,8 +20,8 @@ import * as meow from 'meow'; -import {GitHubRepository, PullRequest} from './lib/github'; -import {processPRs} from './lib/asyncItemIterator'; +import {GitHubRepository, PullRequest} from './lib/github.js'; +import {processPRs} from './lib/asyncItemIterator.js'; async function processMethod(repository: GitHubRepository, pr: PullRequest) { const htmlUrl = pr.html_url; diff --git a/test/async-iterator.ts b/test/async-iterator.ts index 645c98b..1d7e69a 100644 --- a/test/async-iterator.ts +++ b/test/async-iterator.ts @@ -16,16 +16,15 @@ * @fileoverview Unit tests for lib/asyncItemIterator.js. */ -import * as assert from 'assert'; -import meow = require('meow'); -import {meowFlags} from '../src/cli'; +import assert from 'assert'; +import meow from 'meow'; import {describe, it} from 'mocha'; -import * as nock from 'nock'; +import nock from 'nock'; import * as sinon from 'sinon'; -import {GitHubRepository, PullRequest} from '../src/lib/github'; -import * as config from '../src/lib/config'; -import {processPRs} from '../src/lib/asyncItemIterator'; +import {GitHubRepository, PullRequest} from '../src/lib/github.js'; +import * as config from '../src/lib/config.js'; +import {processPRs} from '../src/lib/asyncItemIterator.js'; nock.disableNetConnect(); @@ -34,7 +33,7 @@ describe('asyncItemIterator', () => { sinon.restore(); }); it('should retry list operation on failure', async () => { - sinon.stub(config, 'getConfig').resolves({ + sinon.stub(config.GetConfig, 'getConfig').resolves({ githubToken: 'abc123', clonePath: '/foo/bar', retryStrategy: [5, 10, 20], @@ -46,7 +45,7 @@ describe('asyncItemIterator', () => { title: '.*', retry: true, }, - } as unknown as meow.Result; + } as unknown as ReturnType; const githubRequests = nock('https://api.github.com') .get( '/search/repositories?per_page=100&page=1&q=org%3Agoogleapis%20language%3Atypescript%20language%3Ajavascript%20is%3Apublic%20archived%3Afalse' @@ -78,7 +77,7 @@ describe('asyncItemIterator', () => { githubRequests.done(); }); it('should retry process method if it returns false', async () => { - sinon.stub(config, 'getConfig').resolves({ + sinon.stub(config.GetConfig, 'getConfig').resolves({ githubToken: 'abc123', clonePath: '/foo/bar', retryStrategy: [5, 10, 20], @@ -90,7 +89,7 @@ describe('asyncItemIterator', () => { title: '.*', retry: true, }, - } as unknown as meow.Result; + } as unknown as ReturnType; const githubRequests = nock('https://api.github.com') .get( '/search/repositories?per_page=100&page=1&q=org%3Agoogleapis%20language%3Atypescript%20language%3Ajavascript%20is%3Apublic%20archived%3Afalse' diff --git a/test/config.ts b/test/config.ts index 58b1908..1d4d33e 100644 --- a/test/config.ts +++ b/test/config.ts @@ -16,14 +16,14 @@ * @fileoverview Unit tests for lib/config.js. */ -import * as assert from 'assert'; +import assert from 'assert'; import {describe, it} from 'mocha'; import * as fs from 'fs'; import * as yaml from 'js-yaml'; import * as os from 'os'; import * as path from 'path'; -import {Config, getConfig} from '../src/lib/config'; +import {Config, GetConfig} from '../src/lib/config.js'; import * as tmp from 'tmp-promise'; @@ -62,24 +62,24 @@ describe('Config', () => { }); it('should read default configuration file', async () => { - const config = await getConfig(); + const config = await GetConfig.getConfig(); assert.deepStrictEqual(config, configObject1); }); it('should return individual values', async () => { - const config = await getConfig(); + const config = await GetConfig.getConfig(); assert.strictEqual(config.githubToken, configObject1.githubToken); assert.deepStrictEqual(config.repos, configObject1.repos); }); it('should accept configuration filename', async () => { - const config = await getConfig('./config2.yaml'); + const config = await GetConfig.getConfig('./config2.yaml'); assert.deepStrictEqual(config, configObject2); }); it('should read environment variable', async () => { process.env.REPO_CONFIG_PATH = './config2.yaml'; - const config = await getConfig(); + const config = await GetConfig.getConfig(); delete process.env.REPO_CONFIG_PATH; assert.deepStrictEqual(config, configObject2); }); @@ -88,7 +88,7 @@ describe('Config', () => { // This check will be disabled in the new gts /* eslint-disable @typescript-eslint/no-empty-function */ console.error = () => {}; - getConfig('./config3.yaml').catch(err => { + GetConfig.getConfig('./config3.yaml').catch(err => { assert(err instanceof Error); done(); }); diff --git a/test/fakes/fake-github.ts b/test/fakes/fake-github.ts index 86c0ca5..41ec1dc 100644 --- a/test/fakes/fake-github.ts +++ b/test/fakes/fake-github.ts @@ -17,8 +17,8 @@ */ import * as crypto from 'crypto'; -import {Config} from '../../src/lib/config'; -import {Repository} from '../../src/lib/github'; +import {Config} from '../../src/lib/config.js'; +import {Repository} from '../../src/lib/github.js'; function hash(input: string) { return crypto.createHash('md5').update(input).digest('hex'); diff --git a/test/github.ts b/test/github.ts index 54b519a..2a28968 100644 --- a/test/github.ts +++ b/test/github.ts @@ -16,16 +16,16 @@ * @fileoverview Unit tests for lib/github.js. */ -import * as assert from 'assert'; +import assert from 'assert'; import {describe, it} from 'mocha'; -import * as nock from 'nock'; -import {Config} from '../src/lib/config'; +import nock from 'nock'; +import {Config} from '../src/lib/config.js'; import { getClient, GitHub, GitHubRepository, Repository, -} from '../src/lib/github'; +} from '../src/lib/github.js'; nock.disableNetConnect(); diff --git a/test/update-file-in-branch.ts b/test/update-file-in-branch.ts deleted file mode 100644 index a3e82dd..0000000 --- a/test/update-file-in-branch.ts +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright 2018 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @fileoverview Tests for lib/update-file-in-branch.js. - */ - -import * as assert from 'assert'; -import {describe, it} from 'mocha'; -import * as proxyquire from 'proxyquire'; -import * as sinon from 'sinon'; -import * as fakeGitHub from './fakes/fake-github'; -const {updateFileInBranch} = proxyquire('../src/lib/update-file-in-branch', { - './github': {GitHub: fakeGitHub.FakeGitHub}, - './config': {getConfig: () => Promise.resolve({})}, -}); -import {suppressConsole} from './util'; - -describe('UpdateFileInBranch', () => { - const path = '/path/to/file.txt'; - const originalContent = 'content matches'; - const badContent = 'content does not match'; - const changedContent = 'changed content'; - const branch = 'test-branch'; - const message = 'test-message'; - // eslint-disable-next-line no-undef - beforeEach(() => { - fakeGitHub.repository.reset(); - fakeGitHub.repository.testSetFile( - branch, - path, - Buffer.from(originalContent).toString('base64') - ); - }); - // This check will be disabled in the new gts - /* eslint-disable @typescript-eslint/no-empty-function */ - // eslint-disable-next-line no-undef - afterEach(() => {}); - - const attemptUpdate = async () => { - await suppressConsole(async () => { - await updateFileInBranch({ - path, - patchFunction: (str: string) => { - if (str === originalContent) { - return changedContent; - } - return; - }, - branch, - message, - }); - }); - }; - - it('should update one file if content matches', async () => { - await attemptUpdate(); - assert.strictEqual( - fakeGitHub.repository.branches[branch][path]['content'], - Buffer.from(changedContent).toString('base64') - ); - }); - - it('should not update a file if it is not a file', async () => { - fakeGitHub.repository.branches[branch][path]['type'] = 'not-a-file'; - await attemptUpdate(); - assert.strictEqual( - fakeGitHub.repository.branches[branch][path]['content'], - Buffer.from(originalContent).toString('base64') - ); - }); - - it('should not update a file if content does not match', async () => { - fakeGitHub.repository.testSetFile( - branch, - path, - Buffer.from(badContent).toString('base64') - ); - await attemptUpdate(); - assert.strictEqual( - fakeGitHub.repository.branches[branch][path]['content'], - Buffer.from(badContent).toString('base64') - ); - }); - - it('should not update a file if it does not exist', async () => { - delete fakeGitHub.repository.branches[branch][path]; - await attemptUpdate(); - assert.strictEqual(fakeGitHub.repository.branches[branch][path], undefined); - }); - - it('should handle error if cannot update file in branch', async () => { - const stub = sinon - .stub(fakeGitHub.repository, 'updateFileInBranch') - .returns(Promise.reject(new Error('Random error'))); - await attemptUpdate(); - stub.restore(); - assert.strictEqual( - fakeGitHub.repository.branches[branch][path]['content'], - Buffer.from(originalContent).toString('base64') - ); - }); - - it('should require path parameter', async () => { - await suppressConsole(async () => { - await assert.rejects( - updateFileInBranch({ - patchFunction: (str: string) => { - if (str === originalContent) { - return changedContent; - } - return; - }, - branch, - message, - }), - /path is required/ - ); - }); - }); - - it('should require patchFunction parameter', async () => { - await suppressConsole(async () => { - await assert.rejects( - updateFileInBranch({path, branch, message}), - /patchFunction is required/ - ); - }); - }); - - it('should require branch parameter', async () => { - await suppressConsole(async () => { - await assert.rejects( - updateFileInBranch({ - path, - patchFunction: (str: string) => { - if (str === originalContent) { - return changedContent; - } - return; - }, - message, - }), - /branch is required/ - ); - }); - - it('should require message parameter', async () => { - await suppressConsole(async () => { - await assert.rejects( - updateFileInBranch({ - path, - patchFunction: (str: string) => { - if (str === originalContent) { - return changedContent; - } - return; - }, - branch, - }), - /message is requiretd/ - ); - }); - }); - }); -}); diff --git a/test/update-file.ts b/test/update-file.ts deleted file mode 100644 index 9bd211c..0000000 --- a/test/update-file.ts +++ /dev/null @@ -1,339 +0,0 @@ -// Copyright 2018 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @fileoverview Tests for lib/update-file.js. - */ - -import * as assert from 'assert'; -import {describe, it} from 'mocha'; -import * as proxyquire from 'proxyquire'; -import * as sinon from 'sinon'; -import * as fakeGitHub from './fakes/fake-github'; -const {updateFile} = proxyquire('../src/lib/update-file', { - './config': {getConfig: () => Promise.resolve({})}, - './github': {GitHub: fakeGitHub.FakeGitHub}, -}); -import {suppressConsole} from './util'; - -describe('UpdateFile', () => { - const path = '/path/to/file.txt'; - const originalContent = 'content matches'; - const badContent = 'content does not match'; - const changedContent = 'changed content'; - const branch = 'test-branch'; - const message = 'test-message'; - const comment = 'test-comment'; - const reviewers = ['test-reviewer-1', 'test-reviewer-2']; - // eslint-disable-next-line no-undef - beforeEach(() => { - fakeGitHub.repository.reset(); - fakeGitHub.repository.testSetFile( - 'main', - path, - Buffer.from(originalContent).toString('base64') - ); - }); - /* eslint-disable @typescript-eslint/no-empty-function */ - // eslint-disable-next-line no-undef - afterEach(() => {}); - - const attemptUpdate = async () => { - await suppressConsole(async () => { - await updateFile({ - path, - patchFunction: (str: string) => { - if (str === originalContent) { - return changedContent; - } - return; - }, - branch, - message, - comment, - reviewers, - }); - }); - }; - - it('should update one file if content matches', async () => { - await attemptUpdate(); - assert.strictEqual( - fakeGitHub.repository.branches['main'][path]['content'], - Buffer.from(originalContent).toString('base64') - ); - assert.strictEqual( - fakeGitHub.repository.branches[branch][path]['content'], - Buffer.from(changedContent).toString('base64') - ); - assert.deepStrictEqual(fakeGitHub.repository.prs[1], { - number: 1, - branch, - message, - comment, - reviewers, - html_url: 'http://example.com/pulls/1', - base: 'main', - }); - }); - - it('should target the base branch instead of main', async () => { - fakeGitHub.repository.testChangeBaseBranch('main'); - fakeGitHub.repository.testSetFile( - 'main', - path, - Buffer.from(originalContent).toString('base64') - ); - await attemptUpdate(); - assert.strictEqual( - fakeGitHub.repository.branches['main'][path]['content'], - Buffer.from(originalContent).toString('base64') - ); - assert.strictEqual( - fakeGitHub.repository.branches[branch][path]['content'], - Buffer.from(changedContent).toString('base64') - ); - assert.deepStrictEqual(fakeGitHub.repository.prs[1], { - number: 1, - branch, - message, - comment, - reviewers, - html_url: 'http://example.com/pulls/1', - base: 'main', - }); - }); - - it('should not update a file if it is not a file', async () => { - fakeGitHub.repository.branches['main'][path]['type'] = 'not-a-file'; - await attemptUpdate(); - assert.strictEqual( - fakeGitHub.repository.branches['main'][path]['content'], - Buffer.from(originalContent).toString('base64') - ); - assert.strictEqual(fakeGitHub.repository.branches[branch], undefined); - }); - - it('should not update a file if content does not match', async () => { - fakeGitHub.repository.testSetFile( - 'main', - path, - Buffer.from(badContent).toString('base64') - ); - await attemptUpdate(); - assert.strictEqual( - fakeGitHub.repository.branches['main'][path]['content'], - Buffer.from(badContent).toString('base64') - ); - assert.strictEqual(fakeGitHub.repository.branches[branch], undefined); - }); - - it('should not update a file if it does not exist', async () => { - delete fakeGitHub.repository.branches['main'][path]; - await attemptUpdate(); - assert.strictEqual(fakeGitHub.repository.branches['main'][path], undefined); - assert.strictEqual(fakeGitHub.repository.branches[branch], undefined); - }); - - it('should not update a file if cannot get main latest sha', async () => { - const stub = sinon - .stub(fakeGitHub.repository, 'getLatestCommitToBaseBranch') - .returns(Promise.reject(new Error('Random error'))); - await attemptUpdate(); - stub.restore(); - assert.strictEqual( - fakeGitHub.repository.branches['main'][path]['content'], - Buffer.from(originalContent).toString('base64') - ); - assert.strictEqual(fakeGitHub.repository.branches[branch], undefined); - }); - - it('should not update a file if cannot create branch', async () => { - const stub = sinon - .stub(fakeGitHub.repository, 'createBranch') - .returns(Promise.reject(new Error('Random error'))); - await attemptUpdate(); - stub.restore(); - assert.strictEqual( - fakeGitHub.repository.branches['main'][path]['content'], - Buffer.from(originalContent).toString('base64') - ); - assert.strictEqual(fakeGitHub.repository.branches[branch], undefined); - }); - - it('should not send pull request if cannot update file in branch', async () => { - const stub = sinon - .stub(fakeGitHub.repository, 'updateFileInBranch') - .returns(Promise.reject(new Error('Random error'))); - await attemptUpdate(); - stub.restore(); - assert.strictEqual( - fakeGitHub.repository.branches['main'][path]['content'], - Buffer.from(originalContent).toString('base64') - ); - assert.strictEqual( - fakeGitHub.repository.branches[branch][path]['content'], - Buffer.from(originalContent).toString('base64') - ); - assert.strictEqual(fakeGitHub.repository.prs[1], undefined); - }); - - it('should still update a file in branch if cannot create pull request', async () => { - const stub = sinon - .stub(fakeGitHub.repository, 'createPullRequest') - .returns(Promise.reject(new Error('Random error'))); - await attemptUpdate(); - stub.restore(); - assert.strictEqual( - fakeGitHub.repository.branches['main'][path]['content'], - Buffer.from(originalContent).toString('base64') - ); - assert.strictEqual( - fakeGitHub.repository.branches[branch][path]['content'], - Buffer.from(changedContent).toString('base64') - ); - }); - - it('should still update a file in branch and create pull request if cannot request review', async () => { - const stub = sinon - .stub(fakeGitHub.repository, 'requestReview') - .returns(Promise.reject(new Error('Random error'))); - await attemptUpdate(); - assert.strictEqual( - fakeGitHub.repository.branches['main'][path]['content'], - Buffer.from(originalContent).toString('base64') - ); - assert.strictEqual( - fakeGitHub.repository.branches[branch][path]['content'], - Buffer.from(changedContent).toString('base64') - ); - assert.deepStrictEqual(fakeGitHub.repository.prs[1], { - number: 1, - branch, - message, - comment, - html_url: 'http://example.com/pulls/1', - base: 'main', - }); - stub.restore(); - }); - - it('should require path parameter', async () => { - await suppressConsole(async () => { - await assert.rejects( - updateFile({ - patchFunction: (str: string) => { - if (str === originalContent) { - return changedContent; - } - return; - }, - branch, - message, - comment, - reviewers, - }), - /path is required/ - ); - }); - }); - - it('should require patchFunction parameter', async () => { - await suppressConsole(async () => { - await assert.rejects( - updateFile({ - path, - branch, - message, - comment, - reviewers, - }), - /patchFunction is required/ - ); - }); - }); - - it('should require branch parameter', async () => { - await suppressConsole(async () => { - await assert.rejects( - updateFile({ - path, - patchFunction: (str: string) => { - if (str === originalContent) { - return changedContent; - } - return; - }, - message, - comment, - reviewers, - }), - /branch is required/ - ); - }); - }); - - it('should require message parameter', async () => { - await suppressConsole(async () => { - await assert.rejects( - updateFile({ - path, - patchFunction: (str: string) => { - if (str === originalContent) { - return changedContent; - } - return; - }, - branch, - comment, - reviewers, - }), - /message is required/ - ); - }); - }); - - it('should not send review if no reviewers', async () => { - await suppressConsole(async () => { - await updateFile({ - path, - patchFunction: (str: string) => { - if (str === originalContent) { - return changedContent; - } - return; - }, - branch, - message, - comment, - }); - }); - assert.strictEqual( - fakeGitHub.repository.branches['main'][path]['content'], - Buffer.from(originalContent).toString('base64') - ); - assert.strictEqual( - fakeGitHub.repository.branches[branch][path]['content'], - Buffer.from(changedContent).toString('base64') - ); - assert.deepStrictEqual(fakeGitHub.repository.prs[1], { - number: 1, - branch, - message, - comment, - html_url: 'http://example.com/pulls/1', - base: 'main', - }); - }); -}); diff --git a/test/update-repo.ts b/test/update-repo.ts deleted file mode 100644 index beafb3d..0000000 --- a/test/update-repo.ts +++ /dev/null @@ -1,373 +0,0 @@ -// Copyright 2018 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @fileoverview Tests for lib/update-repo.js. - */ - -import * as assert from 'assert'; -import {describe, it} from 'mocha'; -import * as fs from 'fs'; -import * as path from 'path'; -import * as proxyquire from 'proxyquire'; -import * as sinon from 'sinon'; - -import * as fakeGitHub from './fakes/fake-github'; -import * as fakeTmp from './fakes/fake-tmp'; -import * as tmp from 'tmp-promise'; -import {suppressConsole} from './util'; - -const execCallback = sinon.spy(); -const {updateRepo} = proxyquire('../src/lib/update-repo', { - './github': {GitHub: fakeGitHub.FakeGitHub}, - './config': {getConfig: () => Promise.resolve({})}, - 'tmp-promise': fakeTmp, - child_process: { - exec: (command: string, callback: Function) => { - execCallback(command); - callback(); - }, - }, -}); - -describe('UpdateRepo', () => { - const pathExisting = 'file1.txt'; - const pathNonExisting = 'file2.txt'; - const pathFailed = 'failed.txt'; - const originalContent = 'content'; - const changedContent = 'changed content'; - const newContent = 'new content'; - const branch = 'test-branch'; - const message = 'test-message'; - const comment = 'test-comment'; - const reviewers = ['test-reviewer-1', 'test-reviewer-2']; - let realTmpDir; - let tmpDir: string; - - // eslint-disable-next-line no-undef - before(async () => { - realTmpDir = await tmp.dir({unsafeCleanup: true}); - fakeTmp.setDirName(realTmpDir.path); - tmpDir = fakeTmp.getDirName(); - }); - // eslint-disable-next-line no-undef - beforeEach(() => { - execCallback.resetHistory(); - fakeGitHub.repository.reset(); - fakeGitHub.repository.testSetFile( - 'main', - pathExisting, - Buffer.from(originalContent).toString('base64') - ); - fs.writeFileSync(path.join(tmpDir, pathExisting), changedContent); - fs.writeFileSync(path.join(tmpDir, pathNonExisting), newContent); - }); - - const attemptUpdate = async (files?: string[]) => { - if (files === undefined) { - files = [pathExisting, pathNonExisting]; - } - await suppressConsole(async () => { - await updateRepo({ - updateCallback: (path: string) => { - assert.strictEqual(path, tmpDir); - return Promise.resolve(files); - }, - branch, - message, - comment, - reviewers, - }); - }); - }; - - it('should perform update', async () => { - await attemptUpdate(); - assert.strictEqual( - fakeGitHub.repository.branches['main'][pathExisting]['content'], - Buffer.from(originalContent).toString('base64') - ); - assert.strictEqual( - fakeGitHub.repository.branches[branch][pathExisting]['content'], - Buffer.from(changedContent).toString('base64') - ); - assert.strictEqual( - fakeGitHub.repository.branches[branch][pathNonExisting]['content'], - Buffer.from(newContent).toString('base64') - ); - assert.deepStrictEqual(fakeGitHub.repository.prs[1], { - number: 1, - branch, - message, - comment, - reviewers, - html_url: 'http://example.com/pulls/1', - base: 'main', - }); - assert( - execCallback.calledOnceWith( - `git clone ${ - fakeGitHub.repository.getRepository()['ssh_url'] - } ${tmpDir}` - ) - ); - }); - - it('should target the base branch', async () => { - fakeGitHub.repository.testChangeBaseBranch('main'); - fakeGitHub.repository.testSetFile( - 'main', - pathExisting, - Buffer.from(originalContent).toString('base64') - ); - await attemptUpdate(); - assert.strictEqual( - fakeGitHub.repository.branches['main'][pathExisting]['content'], - Buffer.from(originalContent).toString('base64') - ); - assert.strictEqual( - fakeGitHub.repository.branches[branch][pathExisting]['content'], - Buffer.from(changedContent).toString('base64') - ); - assert.strictEqual( - fakeGitHub.repository.branches[branch][pathNonExisting]['content'], - Buffer.from(newContent).toString('base64') - ); - assert.deepStrictEqual(fakeGitHub.repository.prs[1], { - number: 1, - branch, - message, - comment, - reviewers, - html_url: 'http://example.com/pulls/1', - base: 'main', - }); - assert( - execCallback.calledOnceWith( - `git clone ${ - fakeGitHub.repository.getRepository()['ssh_url'] - } ${tmpDir}` - ) - ); - }); - - it('should not update a file if it is not a file', async () => { - fakeGitHub.repository.branches['main'][pathExisting]['type'] = 'not-a-file'; - await attemptUpdate(); - assert.strictEqual( - fakeGitHub.repository.branches['main'][pathExisting]['content'], - Buffer.from(originalContent).toString('base64') - ); - }); - - it('should not update a file if it cannot read it', async () => { - await attemptUpdate([pathFailed]); - }); - - it('should not update a file if cannot get main latest sha', async () => { - const stub = sinon - .stub(fakeGitHub.repository, 'getLatestCommitToBaseBranch') - .returns(Promise.reject(new Error('Random error'))); - await attemptUpdate(); - stub.restore(); - assert.strictEqual(fakeGitHub.repository.branches[branch], undefined); - }); - - it('should not update a file if cannot create branch', async () => { - const stub = sinon - .stub(fakeGitHub.repository, 'createBranch') - .returns(Promise.reject(new Error('Random error'))); - await attemptUpdate(); - stub.restore(); - assert.strictEqual(fakeGitHub.repository.branches[branch], undefined); - }); - - it('should not send pull request if cannot update file in branch', async () => { - const stub = sinon - .stub(fakeGitHub.repository, 'updateFileInBranch') - .rejects('Random error'); - await attemptUpdate(); - stub.restore(); - assert.strictEqual(fakeGitHub.repository.prs[1], undefined); - }); - - it('should still update a file in branch if cannot create pull request', async () => { - const stub = sinon - .stub(fakeGitHub.repository, 'createPullRequest') - .rejects('Random error'); - await attemptUpdate(); - stub.restore(); - assert.strictEqual( - fakeGitHub.repository.branches['main'][pathExisting]['content'], - Buffer.from(originalContent).toString('base64') - ); - assert.strictEqual( - fakeGitHub.repository.branches[branch][pathExisting]['content'], - Buffer.from(changedContent).toString('base64') - ); - assert.strictEqual( - fakeGitHub.repository.branches[branch][pathNonExisting]['content'], - Buffer.from(newContent).toString('base64') - ); - }); - - it('should still update a file in branch and create pull request if cannot request review', async () => { - const stub = sinon - .stub(fakeGitHub.repository, 'requestReview') - .rejects('Random error'); - await attemptUpdate(); - assert.strictEqual( - fakeGitHub.repository.branches['main'][pathExisting]['content'], - Buffer.from(originalContent).toString('base64') - ); - assert.strictEqual( - fakeGitHub.repository.branches[branch][pathExisting]['content'], - Buffer.from(changedContent).toString('base64') - ); - assert.strictEqual( - fakeGitHub.repository.branches[branch][pathNonExisting]['content'], - Buffer.from(newContent).toString('base64') - ); - assert.deepStrictEqual(fakeGitHub.repository.prs[1], { - number: 1, - branch, - message, - comment, - html_url: 'http://example.com/pulls/1', - base: 'main', - }); - stub.restore(); - }); - - it('should require updateCallback parameter', async () => { - await suppressConsole(async () => { - await assert.rejects( - updateRepo({ - branch, - message, - comment, - reviewers, - }), - /updateCallback is required/ - ); - }); - }); - - it('should require branch parameter', async () => { - await suppressConsole(async () => { - await assert.rejects( - updateRepo({ - updateCallback: () => { - return Promise.resolve(); - }, - message, - comment, - reviewers, - }), - /branch is required/ - ); - }); - }); - - it('should require message parameter', async () => { - await suppressConsole(async () => { - await assert.rejects( - updateRepo({ - updateCallback: () => { - return; - }, - branch, - comment, - reviewers, - }), - /message is required/ - ); - }); - }); - - it('should not send review if no reviewers', async () => { - await suppressConsole(async () => { - await updateRepo({ - updateCallback: () => { - return Promise.resolve([pathExisting, pathNonExisting]); - }, - branch, - message, - comment, - }); - }); - assert.strictEqual( - fakeGitHub.repository.branches['main'][pathExisting]['content'], - Buffer.from(originalContent).toString('base64') - ); - assert.strictEqual( - fakeGitHub.repository.branches[branch][pathExisting]['content'], - Buffer.from(changedContent).toString('base64') - ); - assert.strictEqual( - fakeGitHub.repository.branches[branch][pathNonExisting]['content'], - Buffer.from(newContent).toString('base64') - ); - assert.deepStrictEqual(fakeGitHub.repository.prs[1], { - number: 1, - branch, - message, - comment, - html_url: 'http://example.com/pulls/1', - base: 'main', - }); - }); - - it('should not perform update if updateCallback returned undefined value', async () => { - suppressConsole(async () => { - await updateRepo({ - updateCallback: () => { - return Promise.resolve(undefined); - }, - branch, - message, - comment, - }); - }); - assert.strictEqual(fakeGitHub.repository.branches[branch], undefined); - }); - - it('should not perform update if updateCallback returned empty list', async () => { - await suppressConsole(async () => { - await updateRepo({ - updateCallback: () => { - return Promise.resolve([]); - }, - branch, - message, - comment, - }); - }); - assert.strictEqual(fakeGitHub.repository.branches[branch], undefined); - }); - - it('should not perform update if updateCallback promise was rejected', async () => { - await suppressConsole(async () => { - await updateRepo({ - updateCallback: () => { - return Promise.reject(); - }, - branch, - message, - comment, - }); - }); - assert.strictEqual(fakeGitHub.repository.branches[branch], undefined); - }); -}); diff --git a/tsconfig.json b/tsconfig.json index 6c8311d..deae280 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,11 @@ "extends": "./node_modules/gts/tsconfig-google.json", "compilerOptions": { "rootDir": ".", - "outDir": "build" + "outDir": "build", + "esModuleInterop": true, + "lib": ["es2020"], + "module": "es2020", + "moduleResolution": "node" }, "include": [ "src/*.ts",