From 4a14a013ad7b9bee788c1e505667dd0e651801d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Tue, 30 Jul 2024 14:47:54 +0200 Subject: [PATCH] refactor: migrate to ESM (#542) --- app.js | 24 +++++++++---------- lib/auth-middleware.js | 10 +++----- lib/bot-username.js | 1 - lib/github-client.js | 6 ++--- lib/github-comment.js | 6 ++--- lib/github-events.js | 8 ++++--- lib/github-secret.js | 4 ++-- lib/jenkins-events.js | 8 +++---- lib/logger.js | 7 +++--- lib/node-owners.js | 10 ++------ lib/node-repo.js | 23 ++++++++---------- lib/push-jenkins-update.js | 19 +++++---------- package.json | 3 ++- scripts/event-relay.js | 6 ++--- scripts/jenkins-status.js | 6 ++--- scripts/node-ping-owners.js | 8 +++---- scripts/ping.js | 4 +--- server.js | 18 +++++++------- .../event-relay-to-github-actions.test.js | 14 +++++------ test/integration/ping.test.js | 11 +++++---- test/integration/push-jenkins-update.test.js | 16 ++++++------- test/read-fixture.js | 10 ++++---- test/unit/node-owners.test.js | 8 +++---- test/unit/node-repo-owners.test.js | 11 ++++----- test/unit/node-repo.test.js | 12 ++++------ test/unit/push-jenkins-update.test.js | 8 +++---- 26 files changed, 113 insertions(+), 148 deletions(-) delete mode 100644 lib/bot-username.js diff --git a/app.js b/app.js index c5c496f4..9ac062f1 100644 --- a/app.js +++ b/app.js @@ -1,17 +1,19 @@ 'use strict' -const express = require('express') -const bodyParser = require('body-parser') -const bunyanMiddleware = require('bunyan-middleware') -const AsyncEventEmitter = require('events-async') +import express from 'express' +import bodyParser from 'body-parser' +import bunyanMiddleware from 'bunyan-middleware' +import AsyncEventEmitter from 'events-async' -const logger = require('./lib/logger') -const authMiddleware = require('./lib/auth-middleware') +import logger from './lib/logger.js' +import authMiddleware from './lib/auth-middleware.js' +import githubEvents from './lib/github-events.js' +import jenkinsEvents from './lib/jenkins-events.js' const captureRaw = (req, res, buffer) => { req.raw = buffer } -const app = express() -const events = new AsyncEventEmitter() +export const app = express() +export const events = new AsyncEventEmitter() const logsDir = process.env.LOGS_DIR || '' @@ -29,12 +31,10 @@ app.use(bunyanMiddleware({ obscureHeaders: ['x-hub-signature'] })) -require('./lib/github-events')(app, events) -require('./lib/jenkins-events')(app, events) +githubEvents(app, events) +jenkinsEvents(app, events) app.use(function logUnhandledErrors (err, req, res, next) { logger.error(err, 'Unhandled error while responding to incoming HTTP request') res.status(500).end() }) - -module.exports = { app, events } diff --git a/lib/auth-middleware.js b/lib/auth-middleware.js index c9a1afc9..81d7b550 100644 --- a/lib/auth-middleware.js +++ b/lib/auth-middleware.js @@ -1,5 +1,3 @@ -'use strict' - /** * Routes using this middleware gets HTTP basic authentication. * There's only support for *one* username and password combination, @@ -7,18 +5,16 @@ * in the follow format: "username:password" */ -const auth = require('basic-auth') - -const pkg = require('../package') +import auth from 'basic-auth' const [username, password] = (process.env.LOGIN_CREDENTIALS || '').split(':') -module.exports = function authMiddleware (req, res, next) { +export default function authMiddleware (req, res, next) { const user = auth(req) if (user === undefined || user.name !== username || user.pass !== password) { res.statusCode = 401 - res.setHeader('WWW-Authenticate', `Basic realm="${pkg.name}"`) + res.setHeader('WWW-Authenticate', 'Basic realm="nodejs-github-bot"') res.end('Unauthorized') } else { next() diff --git a/lib/bot-username.js b/lib/bot-username.js deleted file mode 100644 index cfd9121e..00000000 --- a/lib/bot-username.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = 'nodejs-github-bot' diff --git a/lib/github-client.js b/lib/github-client.js index 060fd777..05ca43fc 100644 --- a/lib/github-client.js +++ b/lib/github-client.js @@ -1,6 +1,4 @@ -'use strict' - -const { Octokit } = require('@octokit/rest') +import { Octokit } from '@octokit/rest' const githubClient = new Octokit({ auth: process.env.GITHUB_TOKEN || 'invalid-placeholder-token', @@ -10,4 +8,4 @@ const githubClient = new Octokit({ } }) -module.exports = githubClient +export default githubClient diff --git a/lib/github-comment.js b/lib/github-comment.js index 2ad41be8..e038c543 100644 --- a/lib/github-comment.js +++ b/lib/github-comment.js @@ -1,10 +1,8 @@ -'use strict' - /* eslint-disable camelcase */ -const githubClient = require('./github-client') +import githubClient from './github-client.js' -exports.createPrComment = async function createPrComment ({ owner, repo, issue_number, logger }, body) { +export async function createPrComment ({ owner, repo, issue_number, logger }, body) { try { await githubClient.issues.createComment({ owner, diff --git a/lib/github-events.js b/lib/github-events.js index 00523af1..6b6d46fa 100644 --- a/lib/github-events.js +++ b/lib/github-events.js @@ -1,8 +1,10 @@ -const debug = require('debug')('github-events') +import debugLib from 'debug' -const githubSecret = require('./github-secret') +import * as githubSecret from './github-secret.js' -module.exports = (app, events) => { +const debug = debugLib('github-events') + +export default (app, events) => { app.post('/hooks/github', async (req, res) => { const event = req.headers['x-github-event'] if (!event) { diff --git a/lib/github-secret.js b/lib/github-secret.js index ab9f444d..1aecacc1 100644 --- a/lib/github-secret.js +++ b/lib/github-secret.js @@ -1,4 +1,4 @@ -const crypto = require('crypto') +import crypto from 'node:crypto' const secret = process.env.GITHUB_WEBHOOK_SECRET || 'hush-hush' @@ -7,7 +7,7 @@ function sign (data) { return 'sha1=' + crypto.createHmac('sha1', secret).update(buffer).digest('hex') } -exports.isValid = function isValid (req) { +export function isValid (req) { const signature = req.headers['x-hub-signature'] return signature && signature === sign(req.raw) } diff --git a/lib/jenkins-events.js b/lib/jenkins-events.js index 033f3943..7a97e551 100644 --- a/lib/jenkins-events.js +++ b/lib/jenkins-events.js @@ -1,8 +1,8 @@ -'use strict' +import debugLib from 'debug' -const pushJenkinsUpdate = require('../lib/push-jenkins-update') +import * as pushJenkinsUpdate from './push-jenkins-update.js' -const debug = require('debug')('jenkins-events') +const debug = debugLib('jenkins-events') const enabledRepos = process.env.JENKINS_ENABLED_REPOS ? process.env.JENKINS_ENABLED_REPOS.split(',') : ['citgm', 'http-parser', 'node', 'node-auto-test'] @@ -25,7 +25,7 @@ function isRelatedToPullRequest (gitRef) { return gitRef.includes('/pull/') } -module.exports = (app, events) => { +export default (app, events) => { app.post('/:repo/jenkins/:event', async (req, res) => { const isValid = pushJenkinsUpdate.validate(req.body) const repo = req.params.repo diff --git a/lib/logger.js b/lib/logger.js index 8c104c28..885ef789 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -1,7 +1,6 @@ -'use strict' +import path from 'node:path' -const path = require('path') -const bunyan = require('bunyan') +import bunyan from 'bunyan' const isRunningTests = process.env.npm_lifecycle_event === 'test' const stdoutLevel = isRunningTests ? 'FATAL' : process.env.LOG_LEVEL || 'INFO' @@ -26,7 +25,7 @@ if (logsDir) { }) } -module.exports = bunyan.createLogger({ +export default bunyan.createLogger({ name: 'bot', streams }) diff --git a/lib/node-owners.js b/lib/node-owners.js index 667a485c..ee431d07 100644 --- a/lib/node-owners.js +++ b/lib/node-owners.js @@ -1,8 +1,6 @@ -'use static' +import { matchPattern, parse } from 'codeowners-utils' -const { parse, matchPattern } = require('codeowners-utils') - -class Owners { +export class Owners { constructor (ownersDefinitions) { this._ownersDefinitions = ownersDefinitions } @@ -24,7 +22,3 @@ class Owners { return ownersForPaths.filter((v, i) => ownersForPaths.indexOf(v) === i).sort() } } - -module.exports = { - Owners -} diff --git a/lib/node-repo.js b/lib/node-repo.js index 923c6d22..c21210e6 100644 --- a/lib/node-repo.js +++ b/lib/node-repo.js @@ -1,19 +1,18 @@ -'use strict' - /* eslint-disable camelcase */ -const https = require('https') -const Aigle = require('aigle') +import https from 'node:https' + +import Aigle from 'aigle' -const githubClient = require('./github-client') -const { createPrComment } = require('./github-comment') -const { Owners } = require('./node-owners') +import githubClient from './github-client.js' +import { createPrComment } from './github-comment.js' +import { Owners } from './node-owners.js' const fiveSeconds = 5 * 1000 const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)) -async function removeLabelFromPR (options, label) { +export async function removeLabelFromPR (options, label) { // no need to request github if we didn't resolve a label if (!label) { return @@ -41,7 +40,7 @@ async function removeLabelFromPR (options, label) { return label } -function getBotPrLabels (options, cb) { +export function getBotPrLabels (options, cb) { githubClient.issues.listEvents({ owner: options.owner, repo: options.repo, @@ -184,11 +183,9 @@ async function pingOwners (options, owners) { options.logger.debug('Owners pinged successfully') } -exports.getBotPrLabels = getBotPrLabels -exports.removeLabelFromPR = removeLabelFromPR -exports.resolveOwnersThenPingPr = deferredResolveOwnersThenPingPr +export { deferredResolveOwnersThenPingPr as resolveOwnersThenPingPr } // exposed for testability -exports._testExports = { +export const _testExports = { pingOwners, getCodeOwnersFile, getCodeOwnersUrl, getDefaultBranch, listFiles, getCommentForOwners } diff --git a/lib/push-jenkins-update.js b/lib/push-jenkins-update.js index 04e94330..a2ec9cde 100644 --- a/lib/push-jenkins-update.js +++ b/lib/push-jenkins-update.js @@ -1,9 +1,7 @@ -'use strict' +import githubClient from './github-client.js' +import { createPrComment } from './github-comment.js' -const githubClient = require('./github-client') -const { createPrComment } = require('./github-comment') - -function pushStarted (options, build, cb) { +export function pushStarted (options, build, cb) { const pr = findPrInRef(build.ref) // create unique logger which is easily traceable for every subsequent log statement @@ -47,7 +45,7 @@ function pushStarted (options, build, cb) { }) } -function pushEnded (options, build, cb) { +export function pushEnded (options, build, cb) { const pr = findPrInRef(build.ref) // create unique logger which is easily traceable for every subsequent log statement @@ -77,7 +75,7 @@ function findPrInRef (gitRef) { return parseInt(gitRef.split('/')[2], 10) } -async function findLatestCommitInPr (options) { +export async function findLatestCommitInPr (options) { const paginateOptions = githubClient.pulls.listCommits.endpoint.merge({ owner: options.owner, repo: options.repo, @@ -112,12 +110,7 @@ async function createGhStatus (options, logger) { logger.info('Jenkins / Github PR status updated') } -function validate (payload) { +export function validate (payload) { const isString = (param) => typeof (payload[param]) === 'string' return ['identifier', 'status', 'message', 'commit', 'url'].every(isString) } - -exports.validate = validate -exports.pushStarted = pushStarted -exports.pushEnded = pushEnded -exports.findLatestCommitInPr = findLatestCommitInPr diff --git a/package.json b/package.json index 98e40dfd..52b2e11c 100644 --- a/package.json +++ b/package.json @@ -2,13 +2,14 @@ "name": "nodejs-github-bot", "version": "1.0.0-beta1", "description": "Node.js GitHub Bot", + "type": "module", "scripts": { "start": "node server.js | bunyan -o short", "test": "STATUS=0; tap --allow-incomplete-coverage test/**/*.test.js || STATUS=$?; standard || STATUS=$?; exit $STATUS", "test:watch": "nodemon -q -x 'npm test'" }, "engines": { - "node": ">= 20.0.0" + "node": ">= 20.11.0" }, "private": true, "license": "MIT", diff --git a/scripts/event-relay.js b/scripts/event-relay.js index d8986cc4..7fc73e40 100644 --- a/scripts/event-relay.js +++ b/scripts/event-relay.js @@ -1,6 +1,4 @@ -'use strict' - -const githubClient = require('../lib/github-client') +import githubClient from '../lib/github-client.js' async function handleJenkinsRelay (event) { const { owner, repo, identifier } = event @@ -20,6 +18,6 @@ async function handleJenkinsRelay (event) { } } -module.exports = function (_, event) { +export default function (_, event) { event.on('jenkins', handleJenkinsRelay) } diff --git a/scripts/jenkins-status.js b/scripts/jenkins-status.js index f9feaf0a..1cb6d275 100644 --- a/scripts/jenkins-status.js +++ b/scripts/jenkins-status.js @@ -1,6 +1,4 @@ -'use strict' - -const pushJenkinsUpdate = require('../lib/push-jenkins-update') +import * as pushJenkinsUpdate from '../lib/push-jenkins-update.js' function handleJenkinsStart (event) { const { repo, owner } = event @@ -30,7 +28,7 @@ function handleJenkinsStop (event) { }) } -module.exports = function (_, event) { +export default function (_, event) { event.on('jenkins.start', handleJenkinsStart) event.on('jenkins.end', handleJenkinsStop) } diff --git a/scripts/node-ping-owners.js b/scripts/node-ping-owners.js index faad94c3..ac8c7d99 100644 --- a/scripts/node-ping-owners.js +++ b/scripts/node-ping-owners.js @@ -1,10 +1,10 @@ -'use strict' +import debugLib from 'debug' -const debug = require('debug')('node_ping_owners') +import * as nodeRepo from '../lib/node-repo.js' -const nodeRepo = require('../lib/node-repo') +const debug = debugLib('node_ping_owners') -module.exports = function (app, events) { +export default function (app, events) { events.on('pull_request.opened', handlePrCreated) } diff --git a/scripts/ping.js b/scripts/ping.js index a0dbd53d..2a1d4d6b 100644 --- a/scripts/ping.js +++ b/scripts/ping.js @@ -1,6 +1,4 @@ -'use strict' - -module.exports = function (app) { +export default function (app) { app.get('/ping', (req, res) => { res.end('pong') }) diff --git a/server.js b/server.js index 97a59532..71b3961b 100644 --- a/server.js +++ b/server.js @@ -1,19 +1,19 @@ -'use strict' +import 'dotenv/config' -require('dotenv').config({ silent: true }) - -const { globSync } = require('glob') -const logger = require('./lib/logger') +import { globSync } from 'glob' +import logger from './lib/logger.js' +import { app, events } from './app.js' const port = process.env.PORT || 3000 const scriptsToLoad = process.env.SCRIPTS || './scripts/**/*.js' -const { app, events } = require('./app') // load all the files in the scripts folder -globSync(scriptsToLoad, { dotRelative: true }).forEach((file) => { +const files = globSync(scriptsToLoad, { dotRelative: true }) +for (const file of files) { logger.info('Loading:', file) - require(file)(app, events) -}) + const { default: extend } = await import(file) + extend(app, events) +} app.listen(port, () => { logger.info('Listening on port', port) diff --git a/test/integration/event-relay-to-github-actions.test.js b/test/integration/event-relay-to-github-actions.test.js index a6ed1363..021e9398 100644 --- a/test/integration/event-relay-to-github-actions.test.js +++ b/test/integration/event-relay-to-github-actions.test.js @@ -1,14 +1,14 @@ -'use strict' +import tap from 'tap' +import fetchMock from 'fetch-mock' +import supertest from 'supertest' -const tap = require('tap') -const fetchMock = require('fetch-mock') -const supertest = require('supertest') +import { app, events } from '../../app.js' -const { app, events } = require('../../app') +import readFixture from '../read-fixture.js' -const readFixture = require('../read-fixture') +import eventRelay from '../../scripts/event-relay.js' -require('../../scripts/event-relay')(app, events) +eventRelay(app, events) tap.test('Sends POST requests to https://api.github.com/repos/nodejs//dispatches', (t) => { const jenkinsPayload = readFixture('success-payload.json') diff --git a/test/integration/ping.test.js b/test/integration/ping.test.js index b4b3e7f3..74a27fcc 100644 --- a/test/integration/ping.test.js +++ b/test/integration/ping.test.js @@ -1,11 +1,12 @@ -'use strict' +import http from 'node:http' -const http = require('http') -const tap = require('tap') +import tap from 'tap' -const { app, events } = require('../../app') +import { app, events } from '../../app.js' -require('../../scripts/ping')(app, events) +import ping from '../../scripts/ping.js' + +ping(app, events) tap.test('GET /ping responds with status 200 / "pong"', (t) => { const server = app.listen() diff --git a/test/integration/push-jenkins-update.test.js b/test/integration/push-jenkins-update.test.js index 2bb61b37..715aa0e7 100644 --- a/test/integration/push-jenkins-update.test.js +++ b/test/integration/push-jenkins-update.test.js @@ -1,16 +1,16 @@ -'use strict' +import tap from 'tap' +import fetchMock from 'fetch-mock' +import supertest from 'supertest' -const tap = require('tap') -const fetchMock = require('fetch-mock') -fetchMock.config.overwriteRoutes = true +import { app, events } from '../../app.js' -const supertest = require('supertest') +import readFixture from '../read-fixture.js' -const { app, events } = require('../../app') +import jenkinsStatus from '../../scripts/jenkins-status.js' -const readFixture = require('../read-fixture') +fetchMock.config.overwriteRoutes = true -require('../../scripts/jenkins-status')(app, events) +jenkinsStatus(app, events) tap.test('Sends POST requests to https://api.github.com/repos/nodejs/node/statuses/', (t) => { const jenkinsPayload = readFixture('success-payload.json') diff --git a/test/read-fixture.js b/test/read-fixture.js index 16df834a..6c67abe7 100644 --- a/test/read-fixture.js +++ b/test/read-fixture.js @@ -1,9 +1,7 @@ -'use strict' +import fs from 'node:fs' +import path from 'node:path' -const fs = require('fs') -const path = require('path') - -module.exports = function readFixture (fixtureName) { - const content = fs.readFileSync(path.join(__dirname, '_fixtures', fixtureName)).toString() +export default function readFixture (fixtureName) { + const content = fs.readFileSync(path.join(import.meta.dirname, '_fixtures', fixtureName)).toString() return fixtureName.endsWith('.json') ? JSON.parse(content) : content } diff --git a/test/unit/node-owners.test.js b/test/unit/node-owners.test.js index fe8504d8..a3104bfb 100644 --- a/test/unit/node-owners.test.js +++ b/test/unit/node-owners.test.js @@ -1,9 +1,7 @@ -'use strict' +import tap from 'tap' -const tap = require('tap') - -const { Owners } = require('../../lib/node-owners') -const readFixture = require('../read-fixture') +import { Owners } from '../../lib/node-owners.js' +import readFixture from '../read-fixture.js' const ownersFile = readFixture('CODEOWNERS') diff --git a/test/unit/node-repo-owners.test.js b/test/unit/node-repo-owners.test.js index d8277a0f..4a45f256 100644 --- a/test/unit/node-repo-owners.test.js +++ b/test/unit/node-repo-owners.test.js @@ -1,10 +1,10 @@ -'use strict' +import tap from 'tap' +import fetchMock from 'fetch-mock' +import nock from 'nock' -const tap = require('tap') -const fetchMock = require('fetch-mock') -const nock = require('nock') +import { _testExports, resolveOwnersThenPingPr } from '../../lib/node-repo.js' +import readFixture from '../read-fixture.js' -const { resolveOwnersThenPingPr, _testExports } = require('../../lib/node-repo') const { getCodeOwnersUrl, listFiles, @@ -13,7 +13,6 @@ const { pingOwners, getCommentForOwners } = _testExports -const readFixture = require('../read-fixture') tap.test('getCodeOwnersUrl', (t) => { const owner = 'nodejs' diff --git a/test/unit/node-repo.test.js b/test/unit/node-repo.test.js index 1a0ee079..7ee931d3 100644 --- a/test/unit/node-repo.test.js +++ b/test/unit/node-repo.test.js @@ -1,12 +1,10 @@ -'use strict' +import tap from 'tap' +import fetchMock from 'fetch-mock' -const tap = require('tap') -const fetchMock = require('fetch-mock') +import * as nodeRepo from '../../lib/node-repo.js' -const nodeRepo = require('../../lib/node-repo') - -const logger = require('../../lib/logger') -const readFixture = require('../read-fixture') +import logger from '../../lib/logger.js' +import readFixture from '../read-fixture.js' tap.test('getBotPrLabels(): returns labels added by nodejs-github-bot', (t) => { const events = readFixture('pull-request-events.json') diff --git a/test/unit/push-jenkins-update.test.js b/test/unit/push-jenkins-update.test.js index f486187b..334546ac 100644 --- a/test/unit/push-jenkins-update.test.js +++ b/test/unit/push-jenkins-update.test.js @@ -1,9 +1,9 @@ -const tap = require('tap') +import tap from 'tap' -const { findLatestCommitInPr } = require('../../lib/push-jenkins-update') +import { findLatestCommitInPr } from '../../lib/push-jenkins-update.js' -const fetchMock = require('fetch-mock') -const readFixture = require('../read-fixture') +import fetchMock from 'fetch-mock' +import readFixture from '../read-fixture.js' tap.test('findLatestCommitInPr: paginates results when more than 100 commits in a PR', async (t) => { const commitsFixturePage1 = readFixture('pull-request-commits-page-1.json')