diff --git a/.github/scripts/auto-respond-pr.js b/.github/scripts/auto-respond-pr.js new file mode 100644 index 000000000..8cb989294 --- /dev/null +++ b/.github/scripts/auto-respond-pr.js @@ -0,0 +1,40 @@ +const { Octokit } = require('@octokit/rest') +const fs = require('fs') + +const githubToken = process.env.GITHUB_TOKEN +const repoFullName = process.env.GITHUB_REPOSITORY +const [owner, repo] = repoFullName.split('/') +const ev = JSON.parse( + fs.readFileSync(process.env.GITHUB_EVENT_PATH, 'utf8') +) +const prNumber = ev.pull_request.number + +const octokit = new Octokit({ + auth: githubToken +}) + +async function main () { + const diffOutput = fs.readFileSync('diff_output.txt', 'utf-8') + + let commentBody = ` +Generated file outputs: + +${diffOutput} +` + + if (commentBody.length > 65536) { + commentBody = '❌ Generated diff output is too large to post as a comment, run locally to see the diff and validate' + } + + await octokit.rest.issues.createComment({ + owner, + repo, + issue_number: prNumber, + body: commentBody + }) +} + +main().catch((err) => { + console.error(err) + process.exit(1) +}) diff --git a/.github/scripts/diff-directories.js b/.github/scripts/diff-directories.js new file mode 100644 index 000000000..15ddefce0 --- /dev/null +++ b/.github/scripts/diff-directories.js @@ -0,0 +1,123 @@ +const fs = require('fs') +const path = require('path') +const diff = require('diff') + +function readFilesRecursively (directory) { + const filenames = fs.readdirSync(directory) + const files = {} + + filenames.forEach((filename) => { + const filePath = path.join(directory, filename) + const fileStats = fs.statSync(filePath) + + if (fileStats.isDirectory()) { + const nestedFiles = readFilesRecursively(filePath) + for (const [nestedFilePath, nestedFileContent] of Object.entries(nestedFiles)) { + files[path.join(filename, nestedFilePath)] = nestedFileContent + } + } else { + files[filename] = fs.readFileSync(filePath, 'utf-8') + } + }) + + return files +} + +/** + * Removes the version from the file contents to imrpove diff readability + * @param {string} fileContent + * @param {string} filePath + * @returns {string} + */ +function mungeFileContents (fileContent, filePath) { + if (filePath.endsWith('.json')) { + const fileJSON = JSON.parse(fileContent) + delete fileJSON.version + if ('features' in fileJSON) { + for (const key of Object.keys(fileJSON.features)) { + if ('hash' in fileJSON.features[key]) { + delete fileJSON.features[key].hash + } + } + } + return JSON.stringify(fileJSON, null, 4) + } + return fileContent +} + +function displayDiffs (dir1Files, dir2Files, isOpen) { + const out = [] + for (const [filePath, fileContent] of Object.entries(dir1Files)) { + let diffOut = '' + if (filePath in dir2Files) { + const fileOut = mungeFileContents(fileContent, filePath) + const file2Out = mungeFileContents(dir2Files[filePath], filePath) + if (fileOut === file2Out) { + diffOut = `⚠️ File ${filePath} is identical` + } else { + // Slice of file header from diff output + const fileDiff = diff.createPatch(filePath, fileOut, file2Out).split('\n').slice(2).join('\n') + if (fileDiff) { + fileDiffOut = + diffOut = ` + +\`\`\`diff\n +${fileDiff} +\`\`\` + +` + } + } + + delete dir2Files[filePath] + } else { + diffOut = `❌ File ${filePath} only exists in old changeset` + } + out.push(renderDetails(filePath, diffOut, isOpen)) + } + + for (const filePath of Object.keys(dir2Files)) { + out.push(`❌ File ${filePath} only exists in new changeset`) + } + return out +} + +function renderDetails (section, text, isOpen) { + const open = isOpen ? 'open' : '' + return `
+${section} +${text} +
` +} + +if (process.argv.length !== 4) { + console.error('Usage: node diff_directories.js ') + process.exit(1) +} + +const dir1 = process.argv[2] +const dir2 = process.argv[3] + +const sections = { + legacy: {}, + latest: {} +} +function sortFiles (dirFiles, dirName) { + for (const [filePath, fileContent] of Object.entries(dirFiles)) { + if (filePath.startsWith('v2')) { + sections.latest[dirName] = sections.latest[dirName] || {} + sections.latest[dirName][filePath] = fileContent + } else { + sections.legacy[dirName] = sections.legacy[dirName] || {} + sections.legacy[dirName][filePath] = fileContent + } + } +} +sortFiles(readFilesRecursively(dir1), 'dir1') +sortFiles(readFilesRecursively(dir2), 'dir2') + +for (const [section, files] of Object.entries(sections)) { + const isOpen = section === 'latest' + const fileOut = displayDiffs(files.dir1, files.dir2, isOpen).join('\n') + console.log(renderDetails(section, fileOut, isOpen)) +} diff --git a/.github/workflows/auto-respond-pr.yml b/.github/workflows/auto-respond-pr.yml new file mode 100644 index 000000000..79c6f3c31 --- /dev/null +++ b/.github/workflows/auto-respond-pr.yml @@ -0,0 +1,53 @@ +name: Auto Respond to PR + +on: + pull_request: + types: + - opened + - synchronize + +jobs: + auto_respond: + runs-on: ubuntu-latest + + steps: + - name: Checkout main branch + uses: actions/checkout@v2 + with: + ref: main + repository: ${{ github.event.pull_request.head.repo.full_name }} + path: main + + - name: Checkout PR branch + uses: actions/checkout@v2 + with: + ref: ${{ github.event.pull_request.head.ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + path: pr + + - name: Install dependencies + run: | + npm install @octokit/rest + npm install diff + + - name: Run build script on main branch + run: | + cd main + npm install + node index.js + cd .. + + - name: Run build script on PR branch + run: | + cd pr + npm install + node index.js + cd .. + + - name: Create diff of file outputs + run: node pr/.github/scripts/diff-directories.js main/generated pr/generated > diff_output.txt + + - name: Run auto response script + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: node pr/.github/scripts/auto-respond-pr.js diff --git a/features/runtime-checks.json b/features/runtime-checks.json index 099d17597..c2a8e6a13 100644 --- a/features/runtime-checks.json +++ b/features/runtime-checks.json @@ -64,7 +64,7 @@ "domain": "facebook.com" }, { - "domain": "youtube.com" + "disabled_domain": "youtube.com" }, { "disabled_domain": "stackoverflow.com" diff --git a/package-lock.json b/package-lock.json index 5847fa34d..7ad6cdf8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "devDependencies": { "ajv": "^8.6.3", "chai": "^4.3.4", + "diff": "^5.1.0", "eslint": "^7.32.0", "eslint-config-standard": "^16.0.3", "eslint-plugin-import": "^2.24.2", @@ -609,9 +610,9 @@ } }, "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", "dev": true, "engines": { "node": ">=0.3.1" @@ -2026,6 +2027,15 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/mocha/node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/mocha/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -3742,9 +3752,9 @@ } }, "diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", "dev": true }, "doctrine": { @@ -4781,6 +4791,12 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", diff --git a/package.json b/package.json index 4b311e070..14c281dc9 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "devDependencies": { "ajv": "^8.6.3", "chai": "^4.3.4", + "diff": "^5.1.0", "eslint": "^7.32.0", "eslint-config-standard": "^16.0.3", "eslint-plugin-import": "^2.24.2",