From e4713c2de64e42a423cb6e214760647a359e25dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Daoust?= Date: Tue, 30 Jul 2024 11:11:49 +0200 Subject: [PATCH] Switch to ESM modules and bump version of Reffy (#659) This replaces calls to `require` with `import` syntaxes. Also taking this opportunity to fix the `study-backrefs.js` CLI, which was still expecting a results format that got changed a long time ago. Note: We will probably want to merge these individual CLIs into the main strudy CLI at some point. --- index.js | 13 +- package-lock.json | 287 +++++++++----- package.json | 3 +- src/cli/check-missing-dfns.js | 66 ++-- src/cli/study-algorithms.js | 41 +- src/cli/study-backrefs.js | 182 ++++----- src/cli/study-webidl.js | 41 +- src/lib/canonicalize-url.js | 2 +- src/lib/generate-report.js | 24 +- src/lib/load-json.js | 19 + src/lib/octokit.js | 7 +- src/lib/require-cwd.js | 23 -- src/lib/study-algorithms.js | 6 +- src/lib/study-backrefs.js | 69 ++-- src/lib/study-crawl.js | 33 +- src/lib/study-refs.js | 16 +- src/lib/study-webidl.js | 6 +- src/lib/util.js | 10 +- src/reporting/clean-pending-regeneration.js | 22 +- src/reporting/clean-reports.js | 24 +- src/reporting/file-issue-for-review.js | 405 ++++++++++---------- src/reporting/submit-issue.js | 136 ++++--- strudy.js | 26 +- test/study-backrefs.js | 4 +- test/study-refs.js | 4 +- test/study-webidl.js | 4 +- test/util.js | 4 +- 27 files changed, 762 insertions(+), 715 deletions(-) create mode 100644 src/lib/load-json.js delete mode 100644 src/lib/require-cwd.js diff --git a/index.js b/index.js index b72ffaa2..42252811 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,8 @@ -module.exports = { - studyCrawl: require("./src/lib/study-crawl").studyCrawl, - studyWebIdl: require("./src/lib/study-webidl").studyWebIdl, - generateReport: require("./src/lib/generate-report").generateReport, -}; +import studyCrawl from './src/lib/study-crawl.js'; +import studyWebIdl from './src/lib/study-webidl.js'; +import generateReport from './src/lib/generate-report.js'; + +export { studyCrawl, studyWebIdl, generateReport }; + +const strudy = { studyCrawl, studyWebIdl, generateReport }; +export default strudy; diff --git a/package-lock.json b/package-lock.json index 84788d91..1fa23bf8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "jsdom": "^24.1.1", "node-fetch": "^2.6.5", "node-pandoc": "0.3.0", - "reffy": "^16.0.0", + "reffy": "^17.1.1", "semver": "^7.3.5", "webidl2": "^24.2.2" }, @@ -52,6 +52,7 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "license": "MIT", "dependencies": { "@babel/highlight": "^7.24.7", "picocolors": "^1.0.0" @@ -64,6 +65,7 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -72,6 +74,7 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", @@ -393,15 +396,16 @@ } }, "node_modules/@puppeteer/browsers": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.2.4.tgz", - "integrity": "sha512-BdG2qiI1dn89OTUUsx2GZSpUzW+DRffR1wlMJyKxVHYrhnKoELSDxDd+2XImUkuWPEKk76H5FcM/gPFrEK1Tfw==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz", + "integrity": "sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA==", + "license": "Apache-2.0", "dependencies": { "debug": "^4.3.5", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.4.0", - "semver": "^7.6.2", + "semver": "^7.6.3", "tar-fs": "^3.0.6", "unbzip2-stream": "^1.4.3", "yargs": "^17.7.2" @@ -417,6 +421,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -430,6 +435,7 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -447,6 +453,7 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", "engines": { "node": ">=12" } @@ -454,21 +461,24 @@ "node_modules/@tootallnate/quickjs-emscripten": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", - "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==" + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "license": "MIT" }, "node_modules/@types/node": { - "version": "20.14.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.11.tgz", - "integrity": "sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==", + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.0.0.tgz", + "integrity": "sha512-VT7KSYudcPOzP5Q0wfbowyNLaVR8QWUdw+088uFWwfvpY6uCWaXpqV6ieLAu9WBcnTa7H4Z5RLK8I5t2FuOcqw==", + "license": "MIT", "optional": true, "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.11.1" } }, "node_modules/@types/yauzl": { "version": "2.10.3", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "license": "MIT", "optional": true, "dependencies": { "@types/node": "*" @@ -537,6 +547,7 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "license": "MIT", "dependencies": { "color-convert": "^1.9.0" }, @@ -569,6 +580,7 @@ "version": "0.13.4", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "license": "MIT", "dependencies": { "tslib": "^2.0.1" }, @@ -585,7 +597,8 @@ "node_modules/b4a": { "version": "1.6.6", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", - "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==" + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", + "license": "Apache-2.0" }, "node_modules/balanced-match": { "version": "1.0.2", @@ -596,12 +609,14 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.4.2.tgz", "integrity": "sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q==", + "license": "Apache-2.0", "optional": true }, "node_modules/bare-fs": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.1.tgz", "integrity": "sha512-W/Hfxc/6VehXlsgFtbB5B4xFcsCl+pAh30cYhoFyXErf6oGrwjh8SwiPAdHgpmWonKuYpZgGywN0SXt7dgsADA==", + "license": "Apache-2.0", "optional": true, "dependencies": { "bare-events": "^2.0.0", @@ -613,12 +628,14 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.0.tgz", "integrity": "sha512-v8DTT08AS/G0F9xrhyLtepoo9EJBJ85FRSMbu1pQUlAf6A8T0tEEQGMVObWeqpjhSPXsE0VGlluFBJu2fdoTNg==", + "license": "Apache-2.0", "optional": true }, "node_modules/bare-path": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", + "license": "Apache-2.0", "optional": true, "dependencies": { "bare-os": "^2.1.0" @@ -628,6 +645,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.1.3.tgz", "integrity": "sha512-tiDAH9H/kP+tvNO5sczyn9ZAA7utrSMobyDchsnyyXBuUe2FSQWbxhtuHB8jwpHYYevVo2UJpcmvvjrbHboUUQ==", + "license": "Apache-2.0", "optional": true, "dependencies": { "streamx": "^2.18.0" @@ -650,12 +668,14 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/basic-ftp": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "license": "MIT", "engines": { "node": ">=10.0.0" } @@ -715,6 +735,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -724,6 +745,7 @@ "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", "engines": { "node": "*" } @@ -732,6 +754,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", "engines": { "node": ">=6" } @@ -752,6 +775,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -789,9 +813,10 @@ } }, "node_modules/chromium-bidi": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.6.1.tgz", - "integrity": "sha512-kSxJRj0VgtUKz6nmzc2JPfyfJGzwzt65u7PqhPHtgGQUZLF5oG+ST6l6e5ONfStUMAlhSutFCjaGKllXZa16jA==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.6.2.tgz", + "integrity": "sha512-4WVBa6ijmUTVr9cZD4eicQD8Mdy/HCX3bzEIYYpmk0glqYLoWH+LqQEvV9RpDRzoQSbY1KJHloYXbDMXMbDPhg==", + "license": "Apache-2.0", "dependencies": { "mitt": "3.0.1", "urlpattern-polyfill": "10.0.0", @@ -816,6 +841,7 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", "dependencies": { "color-name": "1.1.3" } @@ -823,7 +849,8 @@ "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" }, "node_modules/combined-stream": { "version": "1.0.8", @@ -849,6 +876,7 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "license": "MIT", "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -873,12 +901,14 @@ "node_modules/cosmiconfig/node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" }, "node_modules/cosmiconfig/node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -921,6 +951,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "license": "MIT", "engines": { "node": ">= 14" } @@ -1010,6 +1041,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "license": "MIT", "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", @@ -1034,9 +1066,10 @@ "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" }, "node_modules/devtools-protocol": { - "version": "0.0.1299070", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1299070.tgz", - "integrity": "sha512-+qtL3eX50qsJ7c+qVyagqi7AWMoQCBGNfoyJZMwm/NSXVqLYbuitrWEEIzxfUmTNy7//Xe8yhMmQ+elj3uAqSg==" + "version": "0.0.1312386", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", + "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", + "license": "BSD-3-Clause" }, "node_modules/diff": { "version": "5.2.0", @@ -1061,6 +1094,7 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", "dependencies": { "once": "^1.4.0" } @@ -1081,6 +1115,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", "engines": { "node": ">=6" } @@ -1089,6 +1124,7 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" } @@ -1113,6 +1149,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "license": "BSD-2-Clause", "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", @@ -1145,6 +1182,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -1153,6 +1191,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } @@ -1172,6 +1211,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "license": "BSD-2-Clause", "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", @@ -1195,7 +1235,8 @@ "node_modules/fast-fifo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" }, "node_modules/fast-uri": { "version": "3.0.1", @@ -1206,6 +1247,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "license": "MIT", "dependencies": { "pend": "~1.2.0" } @@ -1331,6 +1373,7 @@ "version": "11.2.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -1372,6 +1415,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "license": "MIT", "dependencies": { "pump": "^3.0.0" }, @@ -1386,6 +1430,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", "integrity": "sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==", + "license": "MIT", "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", @@ -1432,7 +1477,8 @@ "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" }, "node_modules/gray-matter": { "version": "4.0.3", @@ -1452,6 +1498,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", "engines": { "node": ">=4" } @@ -1541,12 +1588,14 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "BSD-3-Clause" }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -1578,6 +1627,7 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "license": "MIT", "dependencies": { "jsbn": "1.1.0", "sprintf-js": "^1.1.3" @@ -1589,12 +1639,14 @@ "node_modules/ip-address/node_modules/sprintf-js": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "license": "BSD-3-Clause" }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" }, "node_modules/is-binary-path": { "version": "2.1.0", @@ -1714,7 +1766,8 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" }, "node_modules/js-yaml": { "version": "3.14.1", @@ -1731,7 +1784,8 @@ "node_modules/jsbn": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "license": "MIT" }, "node_modules/jsdom": { "version": "24.1.1", @@ -1810,7 +1864,8 @@ "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "1.0.0", @@ -1821,6 +1876,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "license": "MIT", "dependencies": { "universalify": "^2.0.0" }, @@ -1839,7 +1895,8 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" }, "node_modules/locate-path": { "version": "6.0.0", @@ -1946,6 +2003,7 @@ "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", "engines": { "node": ">=12" } @@ -2004,7 +2062,8 @@ "node_modules/mitt": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" }, "node_modules/mocha": { "version": "10.7.0", @@ -2150,6 +2209,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "license": "MIT", "engines": { "node": ">= 0.4.0" } @@ -2243,6 +2303,7 @@ "version": "7.0.2", "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.2.tgz", "integrity": "sha512-BFi3vZnO9X5Qt6NRz7ZOaPja3ic0PhlsmCRYLOpN11+mWBCR6XJDqW5RF3j8jm4WGGQZtBA+bTfxYzeKW73eHg==", + "license": "MIT", "dependencies": { "@tootallnate/quickjs-emscripten": "^0.23.0", "agent-base": "^7.0.2", @@ -2261,6 +2322,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "license": "MIT", "dependencies": { "degenerator": "^5.0.0", "netmask": "^2.0.2" @@ -2273,6 +2335,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -2284,6 +2347,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -2352,12 +2416,14 @@ "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "license": "MIT" }, "node_modules/picocolors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -2375,6 +2441,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -2383,6 +2450,7 @@ "version": "6.4.0", "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "license": "MIT", "dependencies": { "agent-base": "^7.0.2", "debug": "^4.3.4", @@ -2400,7 +2468,8 @@ "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" }, "node_modules/psl": { "version": "1.9.0", @@ -2412,6 +2481,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -2427,15 +2497,16 @@ } }, "node_modules/puppeteer": { - "version": "22.13.1", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-22.13.1.tgz", - "integrity": "sha512-PwXLDQK5u83Fm5A7TGMq+9BR7iHDJ8a3h21PSsh/E6VfhxiKYkU7+tvGZNSCap6k3pCNDd9oNteVBEctcBalmQ==", + "version": "22.14.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-22.14.0.tgz", + "integrity": "sha512-MGTR6/pM8zmWbTdazb6FKnwIihzsSEXBPH49mFFU96DNZpQOevCAZMnjBZGlZRGRzRK6aADCavR6SQtrbv5dQw==", "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "2.2.4", + "@puppeteer/browsers": "2.3.0", "cosmiconfig": "^9.0.0", - "devtools-protocol": "0.0.1299070", - "puppeteer-core": "22.13.1" + "devtools-protocol": "0.0.1312386", + "puppeteer-core": "22.14.0" }, "bin": { "puppeteer": "lib/esm/puppeteer/node/cli.js" @@ -2445,14 +2516,15 @@ } }, "node_modules/puppeteer-core": { - "version": "22.13.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.13.1.tgz", - "integrity": "sha512-NmhnASYp51QPRCAf9n0OPxuPMmzkKd8+2sB9Q+BjwwCG25gz6iuNc3LQDWa+cH2tyivmJppLhNNFt6Q3HmoOpw==", + "version": "22.14.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.14.0.tgz", + "integrity": "sha512-rl4tOY5LcA3e374GAlsGGHc05HL3eGNf5rZ+uxkl6id9zVZKcwcp1Z+Nd6byb6WPiPeecT/dwz8f/iUm+AZQSw==", + "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "2.2.4", - "chromium-bidi": "0.6.1", + "@puppeteer/browsers": "2.3.0", + "chromium-bidi": "0.6.2", "debug": "^4.3.5", - "devtools-protocol": "0.0.1299070", + "devtools-protocol": "0.0.1312386", "ws": "^8.18.0" }, "engines": { @@ -2468,7 +2540,8 @@ "node_modules/queue-tick": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", - "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "license": "MIT" }, "node_modules/randombytes": { "version": "2.1.0", @@ -2492,17 +2565,18 @@ } }, "node_modules/reffy": { - "version": "16.1.2", - "resolved": "https://registry.npmjs.org/reffy/-/reffy-16.1.2.tgz", - "integrity": "sha512-yiLpfuI8Ft6TZoL5dXAgWk2qdSc1SyKXyod1+C7zd50EvQgALRp0y539mL++fH2RrfiyRkvjKHAwCfxuMKsp3A==", + "version": "17.1.1", + "resolved": "https://registry.npmjs.org/reffy/-/reffy-17.1.1.tgz", + "integrity": "sha512-z7eo03Nb1/VpToazfp/W2lJ+8vVR8PTao3Tpf9ykwHdVKiez/bb18AonhqcOSVF26RDMNhtu9Jc0fU/c0NT8PQ==", + "license": "MIT", "dependencies": { "ajv": "8.17.1", "ajv-formats": "3.0.1", "commander": "12.1.0", "fetch-filecache-for-crawling": "5.1.1", - "puppeteer": "22.13.1", + "puppeteer": "22.14.0", "semver": "^7.3.5", - "web-specs": "3.13.1", + "web-specs": "3.14.0", "webidl2": "24.4.1" }, "bin": { @@ -2538,6 +2612,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", "engines": { "node": ">=4" } @@ -2669,6 +2744,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", "engines": { "node": ">= 6.0.0", "npm": ">= 3.0.0" @@ -2678,6 +2754,7 @@ "version": "2.8.3", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "license": "MIT", "dependencies": { "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" @@ -2691,6 +2768,7 @@ "version": "8.0.4", "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", + "license": "MIT", "dependencies": { "agent-base": "^7.1.1", "debug": "^4.3.4", @@ -2704,6 +2782,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", "optional": true, "engines": { "node": ">=0.10.0" @@ -2718,6 +2797,7 @@ "version": "2.18.0", "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.18.0.tgz", "integrity": "sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ==", + "license": "MIT", "dependencies": { "fast-fifo": "^1.3.2", "queue-tick": "^1.0.1", @@ -2812,6 +2892,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", "dependencies": { "has-flag": "^3.0.0" }, @@ -2829,6 +2910,7 @@ "version": "3.0.6", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", + "license": "MIT", "dependencies": { "pump": "^3.0.0", "tar-stream": "^3.1.5" @@ -2842,6 +2924,7 @@ "version": "3.1.7", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "license": "MIT", "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", @@ -2852,6 +2935,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.1.tgz", "integrity": "sha512-8zll7REEv4GDD3x4/0pW+ppIxSNs7H1J10IKFZsuOMscumCdM2a+toDGLPA3T+1+fLBql4zbt5z83GEQGGV5VA==", + "license": "Apache-2.0", "dependencies": { "b4a": "^1.6.4" } @@ -2859,7 +2943,8 @@ "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "license": "MIT" }, "node_modules/to-regex-range": { "version": "5.0.1", @@ -2916,7 +3001,8 @@ "node_modules/tslib": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" }, "node_modules/tunnel": { "version": "0.0.6", @@ -2930,15 +3016,17 @@ "version": "1.4.3", "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "license": "MIT", "dependencies": { "buffer": "^5.2.1", "through": "^2.3.8" } }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.11.1.tgz", + "integrity": "sha512-mIDEX2ek50x0OlRgxryxsenE5XaQD4on5U2inY7RApK3SOJpofyw7uW2AyfMKkhAxXIceo2DeWGVGwyvng1GNQ==", + "license": "MIT", "optional": true }, "node_modules/universal-user-agent": { @@ -2950,6 +3038,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", "engines": { "node": ">= 10.0.0" } @@ -2967,7 +3056,8 @@ "node_modules/urlpattern-polyfill": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", - "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==" + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", + "license": "MIT" }, "node_modules/uuid": { "version": "8.3.2", @@ -2990,9 +3080,10 @@ } }, "node_modules/web-specs": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/web-specs/-/web-specs-3.13.1.tgz", - "integrity": "sha512-0CBUVnsYZGedsstdS2qzUVL8XRdry2y4IApvwU+ijBMHzH3TcvsXfXDlpeBFIwMhmGgh/tYz5z39/yuC6e39ZQ==" + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/web-specs/-/web-specs-3.14.0.tgz", + "integrity": "sha512-pem3aWzO5fA5qD8rFiMXPnnFPuhlOCbZwuUNsGohq1k/Qqr7mZJVrny00BXjNdx1qojbdVcyOhzK4nuuPVrWyw==", + "license": "CC0-1.0" }, "node_modules/webidl-conversions": { "version": "3.0.1", @@ -3244,6 +3335,7 @@ "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "license": "MIT", "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" @@ -3265,6 +3357,7 @@ "version": "3.23.8", "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -3554,15 +3647,15 @@ "optional": true }, "@puppeteer/browsers": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.2.4.tgz", - "integrity": "sha512-BdG2qiI1dn89OTUUsx2GZSpUzW+DRffR1wlMJyKxVHYrhnKoELSDxDd+2XImUkuWPEKk76H5FcM/gPFrEK1Tfw==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz", + "integrity": "sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA==", "requires": { "debug": "^4.3.5", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.4.0", - "semver": "^7.6.2", + "semver": "^7.6.3", "tar-fs": "^3.0.6", "unbzip2-stream": "^1.4.3", "yargs": "^17.7.2" @@ -3605,12 +3698,12 @@ "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==" }, "@types/node": { - "version": "20.14.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.11.tgz", - "integrity": "sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==", + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.0.0.tgz", + "integrity": "sha512-VT7KSYudcPOzP5Q0wfbowyNLaVR8QWUdw+088uFWwfvpY6uCWaXpqV6ieLAu9WBcnTa7H4Z5RLK8I5t2FuOcqw==", "optional": true, "requires": { - "undici-types": "~5.26.4" + "undici-types": "~6.11.1" } }, "@types/yauzl": { @@ -3843,9 +3936,9 @@ } }, "chromium-bidi": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.6.1.tgz", - "integrity": "sha512-kSxJRj0VgtUKz6nmzc2JPfyfJGzwzt65u7PqhPHtgGQUZLF5oG+ST6l6e5ONfStUMAlhSutFCjaGKllXZa16jA==", + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.6.2.tgz", + "integrity": "sha512-4WVBa6ijmUTVr9cZD4eicQD8Mdy/HCX3bzEIYYpmk0glqYLoWH+LqQEvV9RpDRzoQSbY1KJHloYXbDMXMbDPhg==", "requires": { "mitt": "3.0.1", "urlpattern-polyfill": "10.0.0", @@ -4018,9 +4111,9 @@ "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" }, "devtools-protocol": { - "version": "0.0.1299070", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1299070.tgz", - "integrity": "sha512-+qtL3eX50qsJ7c+qVyagqi7AWMoQCBGNfoyJZMwm/NSXVqLYbuitrWEEIzxfUmTNy7//Xe8yhMmQ+elj3uAqSg==" + "version": "0.0.1312386", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", + "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==" }, "diff": { "version": "5.2.0", @@ -5008,25 +5101,25 @@ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" }, "puppeteer": { - "version": "22.13.1", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-22.13.1.tgz", - "integrity": "sha512-PwXLDQK5u83Fm5A7TGMq+9BR7iHDJ8a3h21PSsh/E6VfhxiKYkU7+tvGZNSCap6k3pCNDd9oNteVBEctcBalmQ==", + "version": "22.14.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-22.14.0.tgz", + "integrity": "sha512-MGTR6/pM8zmWbTdazb6FKnwIihzsSEXBPH49mFFU96DNZpQOevCAZMnjBZGlZRGRzRK6aADCavR6SQtrbv5dQw==", "requires": { - "@puppeteer/browsers": "2.2.4", + "@puppeteer/browsers": "2.3.0", "cosmiconfig": "^9.0.0", - "devtools-protocol": "0.0.1299070", - "puppeteer-core": "22.13.1" + "devtools-protocol": "0.0.1312386", + "puppeteer-core": "22.14.0" } }, "puppeteer-core": { - "version": "22.13.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.13.1.tgz", - "integrity": "sha512-NmhnASYp51QPRCAf9n0OPxuPMmzkKd8+2sB9Q+BjwwCG25gz6iuNc3LQDWa+cH2tyivmJppLhNNFt6Q3HmoOpw==", + "version": "22.14.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.14.0.tgz", + "integrity": "sha512-rl4tOY5LcA3e374GAlsGGHc05HL3eGNf5rZ+uxkl6id9zVZKcwcp1Z+Nd6byb6WPiPeecT/dwz8f/iUm+AZQSw==", "requires": { - "@puppeteer/browsers": "2.2.4", - "chromium-bidi": "0.6.1", + "@puppeteer/browsers": "2.3.0", + "chromium-bidi": "0.6.2", "debug": "^4.3.5", - "devtools-protocol": "0.0.1299070", + "devtools-protocol": "0.0.1312386", "ws": "^8.18.0" } }, @@ -5059,17 +5152,17 @@ } }, "reffy": { - "version": "16.1.2", - "resolved": "https://registry.npmjs.org/reffy/-/reffy-16.1.2.tgz", - "integrity": "sha512-yiLpfuI8Ft6TZoL5dXAgWk2qdSc1SyKXyod1+C7zd50EvQgALRp0y539mL++fH2RrfiyRkvjKHAwCfxuMKsp3A==", + "version": "17.1.1", + "resolved": "https://registry.npmjs.org/reffy/-/reffy-17.1.1.tgz", + "integrity": "sha512-z7eo03Nb1/VpToazfp/W2lJ+8vVR8PTao3Tpf9ykwHdVKiez/bb18AonhqcOSVF26RDMNhtu9Jc0fU/c0NT8PQ==", "requires": { "ajv": "8.17.1", "ajv-formats": "3.0.1", "commander": "12.1.0", "fetch-filecache-for-crawling": "5.1.1", - "puppeteer": "22.13.1", + "puppeteer": "22.14.0", "semver": "^7.3.5", - "web-specs": "3.13.1", + "web-specs": "3.14.0", "webidl2": "24.4.1" } }, @@ -5374,9 +5467,9 @@ } }, "undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.11.1.tgz", + "integrity": "sha512-mIDEX2ek50x0OlRgxryxsenE5XaQD4on5U2inY7RApK3SOJpofyw7uW2AyfMKkhAxXIceo2DeWGVGwyvng1GNQ==", "optional": true }, "universal-user-agent": { @@ -5417,9 +5510,9 @@ } }, "web-specs": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/web-specs/-/web-specs-3.13.1.tgz", - "integrity": "sha512-0CBUVnsYZGedsstdS2qzUVL8XRdry2y4IApvwU+ijBMHzH3TcvsXfXDlpeBFIwMhmGgh/tYz5z39/yuC6e39ZQ==" + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/web-specs/-/web-specs-3.14.0.tgz", + "integrity": "sha512-pem3aWzO5fA5qD8rFiMXPnnFPuhlOCbZwuUNsGohq1k/Qqr7mZJVrny00BXjNdx1qojbdVcyOhzK4nuuPVrWyw==" }, "webidl-conversions": { "version": "3.0.1", diff --git a/package.json b/package.json index 1b387366..4362b388 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "engines": { "node": ">=18" }, + "type": "module", "main": "index.js", "bin": "./strudy.js", "devDependencies": { @@ -42,7 +43,7 @@ "jsdom": "^24.1.1", "node-fetch": "^2.6.5", "node-pandoc": "0.3.0", - "reffy": "^16.0.0", + "reffy": "^17.1.1", "semver": "^7.3.5", "webidl2": "^24.2.2" }, diff --git a/src/cli/check-missing-dfns.js b/src/cli/check-missing-dfns.js index 1294440f..be298a75 100644 --- a/src/cli/check-missing-dfns.js +++ b/src/cli/check-missing-dfns.js @@ -22,7 +22,10 @@ * @module checker */ -const path = require('path'); +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import loadJSON from '../lib/load-json.js'; + /** * List of spec shortnames that, so far, don't follow the dfns data model @@ -358,19 +361,19 @@ function matchIdlDfn(expected, actual, * an array of missing CSS or IDL definitions. The function returns null when * there are no missing definitions. */ -function checkSpecDefinitions(spec, options = {}) { +async function checkSpecDefinitions(spec, options = {}) { if (!options.includeObsolete && specsWithObsoleteDfnsModel.includes(spec.shortname)) { return { obsoleteDfnsModel: true }; } const dfns = (typeof spec.dfns === "string") ? - require(path.resolve(options.rootFolder, spec.dfns)).dfns : + (await loadJSON(path.resolve(options.rootFolder, spec.dfns))).dfns : (spec.dfns || []); const css = (typeof spec.css === "string") ? - require(path.resolve(options.rootFolder, spec.css)) : + (await loadJSON(path.resolve(options.rootFolder, spec.css))) : (spec.css || {}); const idl = (typeof spec.idlparsed === "string") ? - require(path.resolve(options.rootFolder, spec.idlparsed)).idlparsed : + (await loadJSON(path.resolve(options.rootFolder, spec.idlparsed))).idlparsed : spec.idlparsed; // Make sure that all expected CSS definitions exist in the dfns extract @@ -466,29 +469,31 @@ function checkSpecDefinitions(spec, options = {}) { * identify the specification, and a `missing` property that is an object that * may have `css` and `idl` properties which list missing CSS/IDL definitions. */ -function checkDefinitions(pathToReport, options = {}) { +async function checkDefinitions(pathToReport, options = {}) { const rootFolder = path.resolve(process.cwd(), pathToReport); - const index = require(path.resolve(rootFolder, 'index.json')).results; + const index = (await loadJSON(path.resolve(rootFolder, 'index.json'))).results; // Check all dfns against CSS and IDL extracts const checkOptions = { rootFolder, includeObsolete: !!options.shortname }; - const missing = index - .filter(spec => !options.shortname || spec.shortname === options.shortname) - .map(spec => { - const res = { - url: spec.url, - crawled: spec.crawled, - shortname: spec.shortname, - }; - if (!spec.dfns) { + const missing = await Promise.all( + index + .filter(spec => !options.shortname || spec.shortname === options.shortname) + .map(async spec => { + const res = { + url: spec.url, + crawled: spec.crawled, + shortname: spec.shortname, + }; + if (!spec.dfns) { + return res; + } + res.missing = await checkSpecDefinitions(spec, checkOptions); return res; - } - res.missing = checkSpecDefinitions(spec, checkOptions); - return res; - }); + }) + ); return missing; } @@ -516,26 +521,27 @@ function reportMissing(missing) { /************************************************** Export methods for use as module **************************************************/ -module.exports.checkSpecDefinitions = checkSpecDefinitions; -module.exports.checkDefinitions = checkDefinitions; - -// "Inner" functions that the IDL names generator uses to link IDL terms with -// their definition (see generate-idlnames.js) -module.exports.getExpectedDfnFromIdlDesc = getExpectedDfnFromIdlDesc; -module.exports.matchIdlDfn = matchIdlDfn; - +export { + checkSpecDefinitions, + checkDefinitions, + + // "Inner" functions that the IDL names generator uses to link IDL terms with + // their definition (see generate-idlnames.js) + getExpectedDfnFromIdlDesc, + matchIdlDfn +} /************************************************** Code run if the code is run as a stand-alone module **************************************************/ -if (require.main === module) { +if (process.argv[1] === fileURLToPath(import.meta.url)) { const pathToReport = process.argv[2]; const shortname = process.argv[3] || 'all'; const format = process.argv[4] || 'markdown'; const options = (shortname === 'all') ? undefined : { shortname }; - let res = checkDefinitions(pathToReport, options); + let res = await checkDefinitions(pathToReport, options); if (shortname === 'all') { res = res .filter(result => result.missing && diff --git a/src/cli/study-algorithms.js b/src/cli/study-algorithms.js index bf03eaa9..39844990 100644 --- a/src/cli/study-algorithms.js +++ b/src/cli/study-algorithms.js @@ -1,10 +1,10 @@ #!/usr/bin/env node -const { loadCrawlResults } = require('../lib/util'); -const { studyAlgorithms } = require('../lib/study-algorithms'); -const requireFromWorkingDirectory = require('../lib/require-cwd'); -const { expandCrawlResult } = require("reffy"); -const path = require("path"); +import { loadCrawlResults } from '../lib/util.js'; +import studyAlgorithms from '../lib/study-algorithms.js'; +import loadJSON from '../lib/load-json.js'; +import { expandCrawlResult } from 'reffy'; +import path from 'node:path'; function reportToConsole(results) { const toreport = []; @@ -35,12 +35,9 @@ async function main(crawlPath, anomalyType) { crawlPath = path.join(crawlPath, 'index.json'); } - let crawl; - try { - crawl = requireFromWorkingDirectory(crawlPath); - } - catch(e) { - throw "Impossible to read " + crawlPath + ": " + e; + const crawl = await loadJSON(crawlPath); + if (!crawl) { + throw new Error("Impossible to read " + crawlPath); } const expanded = await expandCrawlResult(crawl, crawlPath.replace(/index\.json$/, ''), ['algorithms']); @@ -49,18 +46,14 @@ async function main(crawlPath, anomalyType) { } /************************************************** -Code run if the code is run as a stand-alone module +Main loop **************************************************/ -if (require.main === module) { - const crawlPath = process.argv[2]; - - if (!crawlPath) { - console.error('Web IDL analyzer must be called with a paths to crawl results as first parameter'); - process.exit(2); - } - - main(crawlPath).catch(e => { - console.error(e); - process.exit(3); - }); +const crawlPath = process.argv[2]; +if (!crawlPath) { + console.error('Web IDL analyzer must be called with a paths to crawl results as first parameter'); + process.exit(2); } +main(crawlPath).catch(e => { + console.error(e); + process.exit(3); +}); diff --git a/src/cli/study-backrefs.js b/src/cli/study-backrefs.js index 6f117586..ac985ae1 100644 --- a/src/cli/study-backrefs.js +++ b/src/cli/study-backrefs.js @@ -27,117 +27,101 @@ * @module backrefs */ -const { loadCrawlResults } = require('../lib/util'); -const { studyBackrefs } = require('../lib/study-backrefs'); -const path = require("path"); -const fetch = require("node-fetch"); +import { loadCrawlResults } from '../lib/util.js'; +import studyBackrefs from '../lib/study-backrefs.js'; +import path from 'node:path'; function reportToConsole(results) { - console.error(results); - let report = ""; - Object.keys(results) - .sort((r1, r2) => results[r1].title.localeCompare(results[r2].title)) - .forEach(s => { - const result = results[s]; - report += `
${result.title}\n\n`; - if (result.brokenLinks.length) { - report += "Links to anchors that don't exist:\n" - result.brokenLinks.forEach(l => { - report += "* " + l + "\n"; - }) - report += "\n\n"; - } - if (result.evolvingLinks.length) { - report += "Links to anchors that no longer exist in the editor draft of the target spec:\n" - result.evolvingLinks.forEach(l => { - report += "* " + l + "\n"; - }) - report += "\n\n"; - } - if (result.notDfn.length) { - report += "Links to anchors that are not definitions or headings:\n" - result.notDfn.forEach(l => { - report += "* " + l + "\n"; - }) - report += "\n\n"; - } - if (result.notExported.length) { - report += "Links to definitions that are not exported:\n" - result.notExported.forEach(l => { - report += "* " + l + "\n"; - }) - report += "\n\n"; - } - if (result.datedUrls.length) { - report += "Links to dated TR URLs:\n" - result.datedUrls.forEach(l => { - report += "* " + l + "\n"; - }) - report += "\n\n"; - } - if (result.outdatedSpecs.length) { - report += "Links to specs that should no longer be referenced:\n" - result.outdatedSpecs.forEach(l => { - report += "* " + l + "\n"; - }) - report += "\n\n"; + for (const anomaly of results) { + anomaly.specs = anomaly.specs.map(spec => { + return { shortname: spec.shortname, url: spec.url, title: spec.title }; + }); + } + const perSpec = {}; + for (const anomaly of results) { + for (const spec of anomaly.specs) { + if (!perSpec[spec.url]) { + perSpec[spec.url] = { spec, anomalies: [] }; } - if (result.unknownSpecs.length) { - report += "Links to things that look like specs but that aren't recognized as such in crawl data:\n" - result.unknownSpecs.forEach(l => { - report += "* " + l + "\n"; - }) - report += "\n\n"; + perSpec[spec.url].anomalies.push(anomaly); + } + } + + const anomalyTypes = [ + { name: 'brokenLinks', title: 'Links to anchors that do not exist' }, + { name: 'evolvingLinks', title: 'Links to anchors that no longer exist in the editor draft of the target spec' }, + { name: 'notDfn', title: 'Links to anchors that are not definitions or headings' }, + { name: 'notExported', title: 'Links to definitions that are not exported' }, + { name: 'datedUrls', title: 'Links to dated TR URLs' }, + { name: 'outdatedSpecs', title: 'Links to specs that should no longer be referenced' }, + { name: 'unknownSpecs', title: 'Links to documents that are not recognized as specs' } + ]; + let report = ''; + Object.keys(perSpec) + .sort((url1, url2) => perSpec[url1].spec.title.localeCompare(perSpec[url2].spec.title)) + .forEach(url => { + const spec = perSpec[url].spec; + const anomalies = perSpec[url].anomalies; + report += `
${spec.title}\n\n`; + for (const type of anomalyTypes) { + const links = anomalies + .filter(anomaly => anomaly.name === type.name) + .map(anomaly => anomaly.message); + if (links.length > 0) { + report += `${type.title}:\n`; + for (const link of links) { + report += `* ${link}\n`; + } + report += '\n\n'; + } } - report += "
\n"; + report += '
\n'; }); console.log(report); } /************************************************** -Code run if the code is run as a stand-alone module +Main loop **************************************************/ -if (require.main === module) { - let edCrawlResultsPath = process.argv[2]; - let trCrawlResultsPath = process.argv[3]; - - if (!edCrawlResultsPath) { - console.error('Backrefs analyzer must be called with a paths to crawl results as first parameter'); - process.exit(2); - } +let edCrawlResultsPath = process.argv[2]; +let trCrawlResultsPath = process.argv[3]; - // If only one argument is provided, consider that it is the path to the - // root folder of a crawl results, with "ed" and "tr" subfolders - if (!trCrawlResultsPath) { - trCrawlResultsPath = path.join(edCrawlResultsPath, 'tr'); - edCrawlResultsPath = path.join(edCrawlResultsPath, 'ed'); - } +if (!edCrawlResultsPath) { + console.error('Backrefs analyzer must be called with a paths to crawl results as first parameter'); + process.exit(2); +} - // Target the index file if needed - if (!edCrawlResultsPath.endsWith('index.json')) { - edCrawlResultsPath = path.join(edCrawlResultsPath, 'index.json'); - } - if (!trCrawlResultsPath.endsWith('index.json')) { - trCrawlResultsPath = path.join(trCrawlResultsPath, 'index.json'); - } +// If only one argument is provided, consider that it is the path to the +// root folder of a crawl results, with "ed" and "tr" subfolders +if (!trCrawlResultsPath) { + trCrawlResultsPath = path.join(edCrawlResultsPath, 'tr'); + edCrawlResultsPath = path.join(edCrawlResultsPath, 'ed'); +} - // Analyze the crawl results - loadCrawlResults(edCrawlResultsPath, trCrawlResultsPath) - .then(async crawl => { - // Donwload automatic map of multipages anchors in HTML spec - let htmlFragments = {}; - try { - htmlFragments = await fetch("https://html.spec.whatwg.org/multipage/fragment-links.json").then(r => r.json()); - } catch (err) { - console.warn("Could not fetch HTML fragments data, may report false positive broken links on HTML spec", err); - } - return { crawl, htmlFragments }; - }) - .then(({ crawl, htmlFragments }) => studyBackrefs(crawl.ed, crawl.tr, htmlFragments)) - .then(reportToConsole) - .catch(e => { - console.error(e); - process.exit(3); - }); +// Target the index file if needed +if (!edCrawlResultsPath.endsWith('index.json')) { + edCrawlResultsPath = path.join(edCrawlResultsPath, 'index.json'); } +if (!trCrawlResultsPath.endsWith('index.json')) { + trCrawlResultsPath = path.join(trCrawlResultsPath, 'index.json'); +} + +// Analyze the crawl results +loadCrawlResults(edCrawlResultsPath, trCrawlResultsPath) + .then(async crawl => { + // Donwload automatic map of multipages anchors in HTML spec + let htmlFragments = {}; + try { + htmlFragments = await fetch("https://html.spec.whatwg.org/multipage/fragment-links.json").then(r => r.json()); + } catch (err) { + console.warn("Could not fetch HTML fragments data, may report false positive broken links on HTML spec", err); + } + return { crawl, htmlFragments }; + }) + .then(({ crawl, htmlFragments }) => studyBackrefs(crawl.ed, crawl.tr, htmlFragments)) + .then(reportToConsole) + .catch(e => { + console.error(e); + process.exit(3); + }); diff --git a/src/cli/study-webidl.js b/src/cli/study-webidl.js index a066b9df..7d396085 100644 --- a/src/cli/study-webidl.js +++ b/src/cli/study-webidl.js @@ -1,10 +1,10 @@ #!/usr/bin/env node -const { loadCrawlResults } = require('../lib/util'); -const { studyWebIdl } = require('../lib/study-webidl'); -const requireFromWorkingDirectory = require('../lib/require-cwd'); -const { expandCrawlResult } = require("reffy"); -const path = require("path"); +import { loadCrawlResults } from '../lib/util.js'; +import studyWebIdl from '../lib/study-webidl.js'; +import loadJSON from '../lib/load-json.js'; +import { expandCrawlResult } from 'reffy'; +import path from 'node:path'; function reportToConsole(results) { @@ -20,12 +20,9 @@ async function main(crawlPath) { crawlPath = path.join(crawlPath, 'index.json'); } - let crawl; - try { - crawl = requireFromWorkingDirectory(crawlPath); - } - catch(e) { - throw "Impossible to read " + crawlPath + ": " + e; + const crawl = await loadJSON(crawlPath); + if (!crawl) { + throw new Error("Impossible to read " + crawlPath); } const expanded = await expandCrawlResult(crawl, crawlPath.replace(/index\.json$/, ''), 'idl'); @@ -34,18 +31,14 @@ async function main(crawlPath) { } /************************************************** -Code run if the code is run as a stand-alone module +Main loop **************************************************/ -if (require.main === module) { - const crawlPath = process.argv[2]; - - if (!crawlPath) { - console.error('Web IDL analyzer must be called with a paths to crawl results as first parameter'); - process.exit(2); - } - - main(crawlPath).catch(e => { - console.error(e); - process.exit(3); - }); +const crawlPath = process.argv[2]; +if (!crawlPath) { + console.error('Web IDL analyzer must be called with a paths to crawl results as first parameter'); + process.exit(2); } +main(crawlPath).catch(e => { + console.error(e); + process.exit(3); +}); diff --git a/src/lib/canonicalize-url.js b/src/lib/canonicalize-url.js index 5b4f443a..4bdd8bc2 100644 --- a/src/lib/canonicalize-url.js +++ b/src/lib/canonicalize-url.js @@ -44,4 +44,4 @@ function canonicalizesTo (url, refUrl, options = { datedToLatest: false, equival : canon.includes(refUrl); } -module.exports = { canonicalizeUrl, canonicalizesTo }; +export { canonicalizeUrl, canonicalizesTo }; diff --git a/src/lib/generate-report.js b/src/lib/generate-report.js index e3742166..349a5f6c 100644 --- a/src/lib/generate-report.js +++ b/src/lib/generate-report.js @@ -7,8 +7,7 @@ * @module markdownGenerator */ -const requireFromWorkingDirectory = require('./require-cwd'); -const fetch = require('node-fetch'); +import loadJSON from './load-json.js'; /** @@ -975,13 +974,11 @@ async function generateReport(studyFile, options) { throw new Error('Required filename to reference crawl for diff missing'); } - let study; - try { - study = typeof studyFile === 'string' ? - requireFromWorkingDirectory(studyFile) : - studyFile; - } catch (e) { - throw new Error('Impossible to read ' + studyFile + ': ' + e); + const study = typeof studyFile === 'string' ? + (await loadJSON(studyFile)) : + studyFile; + if (!study) { + throw new Error('Impossible to read ' + studyFile); } let refStudy = {}; @@ -997,10 +994,9 @@ async function generateReport(studyFile, options) { return generateDiffReport(study, refStudy, { onlyNew: options.onlyNew }); } else { - try { - refStudy = requireFromWorkingDirectory(options.refStudyFile); - } catch (e) { - throw new Error('Impossible to read ' + options.refStudyFile + ': ' + e); + refStudy = await loadJSON(options.refStudyFile); + if (!refStudy) { + throw new Error('Impossible to read ' + options.refStudyFile); } return generateDiffReport(study, refStudy, { onlyNew: options.onlyNew }); } @@ -1021,4 +1017,4 @@ async function generateReport(studyFile, options) { /************************************************** Export methods for use as module **************************************************/ -module.exports.generateReport = generateReport; +export default generateReport; diff --git a/src/lib/load-json.js b/src/lib/load-json.js new file mode 100644 index 00000000..93faee1c --- /dev/null +++ b/src/lib/load-json.js @@ -0,0 +1,19 @@ +import { readFile } from 'node:fs/promises'; + +/** + * Load a JSON file as JS object. + * + * @function + * @param {String} filename The path to the file to require + * @return {Object} The result of loading and parsing the file relative to the + * current working directory. + */ +export default async function (filename) { + try { + const json = await readFile(filename, 'utf8'); + return JSON.parse(json); + } + catch (err) { + return null; + } +} diff --git a/src/lib/octokit.js b/src/lib/octokit.js index 99e9e750..3a1fc398 100644 --- a/src/lib/octokit.js +++ b/src/lib/octokit.js @@ -3,12 +3,13 @@ * TODO: DRY with Webref? */ -const { throttling } = require("@octokit/plugin-throttling"); -const Octokit = require("@octokit/rest").Octokit.plugin(throttling); +import { throttling } from "@octokit/plugin-throttling"; +import { Octokit as rawOctokit } from "@octokit/rest"; +const Octokit = rawOctokit.plugin(throttling); const MAX_RETRIES = 3; -module.exports = function (params) { +export default function (params) { params = params || {}; const octoParams = Object.assign({ diff --git a/src/lib/require-cwd.js b/src/lib/require-cwd.js deleted file mode 100644 index 922d5c50..00000000 --- a/src/lib/require-cwd.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Wrapper around the "require" function to require files relative to the - * current working directory (CWD), instead of relative to the current JS - * file. - * - * This is typically needed to be able to use "require" to load JSON config - * files provided as command-line arguments. - * - * @function - * @param {String} filename The path to the file to require - * @return {Object} The result of requiring the file relative to the current - * working directory. - */ -const path = require('path'); - -module.exports = function requireFromWorkingDirectory(filename) { - try { - return require(path.resolve(filename)); - } - catch (err) { - return null; - } -} \ No newline at end of file diff --git a/src/lib/study-algorithms.js b/src/lib/study-algorithms.js index e6ecfe95..76f76fd9 100644 --- a/src/lib/study-algorithms.js +++ b/src/lib/study-algorithms.js @@ -1,5 +1,5 @@ -const { JSDOM } = require('jsdom'); -const { recordCategorizedAnomaly } = require('./util'); +import { JSDOM } from 'jsdom'; +import { recordCategorizedAnomaly } from './util.js'; const possibleAnomalies = [ 'missingTaskForPromise', @@ -153,4 +153,4 @@ function studyAlgorithms(edResults) { /************************************************** Export methods for use as module **************************************************/ -module.exports = { studyAlgorithms }; \ No newline at end of file +export default studyAlgorithms; \ No newline at end of file diff --git a/src/lib/study-backrefs.js b/src/lib/study-backrefs.js index 698b919d..2be544fa 100644 --- a/src/lib/study-backrefs.js +++ b/src/lib/study-backrefs.js @@ -1,4 +1,5 @@ -const { recordCategorizedAnomaly } = require('./util'); +import { loadCrawlResults, recordCategorizedAnomaly } from './util.js'; +import { fileURLToPath } from 'node:url'; const possibleAnomalies = [ 'brokenLinks', @@ -261,13 +262,14 @@ function studyLinks(spec, links, report, edResults, trResults, htmlFragments) { let shortname; if (links[link].specShortname) { shortname = links[link].specShortname; - } else { + } + else { let nakedLink = link; - // Ignoring links to PDF specs - if (nakedLink.endsWith('.pdf')) { - return; - } + // Ignoring links to PDF specs + if (nakedLink.endsWith('.pdf')) { + return; + } if (nakedLink.endsWith('.html')) { nakedLink = nakedLink.replace(/\/(Overview|overview|index)\.html$/, '/'); @@ -380,7 +382,8 @@ function studyLinks(spec, links, report, edResults, trResults, htmlFragments) { if (!isKnownId) { if ((trSourceSpec.ids || []).find(matchFullReleaseLink) && link.match(/w3\.org\/TR\//)) { recordAnomaly(spec, 'evolvingLinks', link + '#' + anchor); - } else { + } + else { if (link.startsWith('https://html.spec.whatwg.org/C') || link.startsWith('http://html.spec.whatwg.org/C')) { recordAnomaly(spec, 'nonCanonicalRefs', link); link = link.replace('http:', 'https:').replace('https://html.spec.whatwg.org/C', 'https://html.spec.whatwg.org/multipage'); @@ -389,24 +392,29 @@ function studyLinks(spec, links, report, edResults, trResults, htmlFragments) { if (link === 'https://html.spec.whatwg.org/' && // is there an equivalent id in the multipage spec? ids.find(i => i.startsWith('https://html.spec.whatwg.org/multipage/') && - (i.endsWith('#' + anchor) || i.endsWith('#' + decodeURIComponent(anchor)) || i.endsWith('#' + encodeURIComponent(anchor))))) { + (i.endsWith('#' + anchor) || i.endsWith('#' + decodeURIComponent(anchor)) || i.endsWith('#' + encodeURIComponent(anchor))))) { // Should we keep track of those? ignoring for now - } else if (link.startsWith('https://html.spec.whatwg.org/multipage') && htmlFragments && - htmlFragments[anchor] && - ids.find(matchAnchor(`https://html.spec.whatwg.org/multipage/${htmlFragments[anchor]}.html`, anchor))) { + } + else if (link.startsWith('https://html.spec.whatwg.org/multipage') && htmlFragments && + htmlFragments[anchor] && + ids.find(matchAnchor(`https://html.spec.whatwg.org/multipage/${htmlFragments[anchor]}.html`, anchor))) { // Deal with anchors that are JS-redirected from // the multipage version of HTML recordAnomaly(spec, 'frailLinks', link + '#' + anchor); - } else if (anchor.startsWith(':~:text=')) { + } + else if (anchor.startsWith(':~:text=')) { // links using text fragments are inherently fragile recordAnomaly(spec, 'frailLinks', link + '#' + anchor); - } else { + } + else { recordAnomaly(spec, 'brokenLinks', link + '#' + anchor); } } - } else if (!heading && !dfn) { + } + else if (!heading && !dfn) { recordAnomaly(spec, 'notDfn', link + '#' + anchor); - } else if (dfn && dfn.access !== 'public') { + } + else if (dfn && dfn.access !== 'public') { recordAnomaly(spec, 'notExported', link + '#' + anchor); } } @@ -416,22 +424,19 @@ function studyLinks(spec, links, report, edResults, trResults, htmlFragments) { /************************************************** Export methods for use as module **************************************************/ -module.exports = { studyBackrefs }; - -if (require.main === module) { - (async function() { - const { loadCrawlResults } = require('../lib/util'); - const crawl = await loadCrawlResults(process.argv[2], process.argv[3]); - let htmlFragments = {}; - try { - console.info('Downloading HTML spec fragments data…'); - htmlFragments = await fetch('https://html.spec.whatwg.org/multipage/fragment-links.json').then(r => r.json()); - console.info('- done'); - } catch (err) { - console.error('- failed: could not fetch HTML fragments data, may report false positive broken links on HTML spec'); - } +export default studyBackrefs; + +if (process.argv[1] === fileURLToPath(import.meta.url)) { + const crawl = await loadCrawlResults(process.argv[2], process.argv[3]); + let htmlFragments = {}; + try { + console.info('Downloading HTML spec fragments data…'); + htmlFragments = await fetch('https://html.spec.whatwg.org/multipage/fragment-links.json').then(r => r.json()); + console.info('- done'); + } catch (err) { + console.error('- failed: could not fetch HTML fragments data, may report false positive broken links on HTML spec'); + } - const results = studyBackrefs(crawl.ed, crawl.tr, htmlFragments, process.argv[4] ?? undefined); - console.log(results); - })(); + const results = studyBackrefs(crawl.ed, crawl.tr, htmlFragments, process.argv[4] ?? undefined); + console.log(results); } diff --git a/src/lib/study-crawl.js b/src/lib/study-crawl.js index 4a5ec215..be210dc4 100644 --- a/src/lib/study-crawl.js +++ b/src/lib/study-crawl.js @@ -24,14 +24,13 @@ * @module analyzer */ -const fs = require('fs'); -const path = require('path'); -const requireFromWorkingDirectory = require('./require-cwd'); -const { expandCrawlResult, isLatestLevelThatPasses } = require('reffy'); -const { studyBackrefs } = require('./study-backrefs'); -const checkMissingDefinitions = require('../cli/check-missing-dfns').checkSpecDefinitions; - -const {canonicalizeUrl, canonicalizesTo} = require("./canonicalize-url"); +import fs from 'node:fs'; +import path from 'node:path'; +import { expandCrawlResult, isLatestLevelThatPasses } from 'reffy'; +import studyBackrefs from './study-backrefs.js'; +import { checkSpecDefinitions } from '../cli/check-missing-dfns.js'; +import { canonicalizeUrl, canonicalizesTo } from "./canonicalize-url.js"; +import loadJSON from './load-json.js'; const array_concat = (a,b) => a.concat(b); const uniqueFilter = (item, idx, arr) => arr.indexOf(item) === idx; @@ -94,7 +93,7 @@ function filterSpecInfo(spec) { * a "report" property with "interesting" properties, see code comments inline * for details */ -function studyCrawlResults(results, options = {}) { +async function studyCrawlResults(results, options = {}) { const knownIdlNames = results .map(r => r.idlparsed?.idlNames ? Object.keys(r.idlparsed.idlNames) : [], []) .reduce(array_concat) @@ -143,7 +142,7 @@ function studyCrawlResults(results, options = {}) { const xrefsReport = studyBackrefs(sortedResults, options.trResults); const specsToInclude = options.include; - return sortedResults + return Promise.all(sortedResults .filter(spec => !specsToInclude || (specsToInclude.length === 0) || specsToInclude.some(toInclude => @@ -158,7 +157,7 @@ function studyCrawlResults(results, options = {}) { (toInclude.url && toInclude.url === spec.crawled) || (toInclude.url && toInclude.url === spec.nightly?.url) || (toInclude.html && toInclude.html === spec.html))) - .map(spec => { + .map(async spec => { spec.idlparsed = spec.idlparsed || {}; spec.css = spec.css || {}; spec.refs = spec.refs || {}; @@ -267,7 +266,7 @@ function studyCrawlResults(results, options = {}) { // CSS/IDL terms that do not have a corresponding dfn in the // specification - missingDfns: checkMissingDefinitions(spec), + missingDfns: await checkSpecDefinitions(spec), // Links to external specifications within the body of the spec // that do not have a corresponding entry in the references @@ -368,13 +367,13 @@ function studyCrawlResults(results, options = {}) { report }; return res; - }); + })); } async function studyCrawl(crawlResults, options = {}) { if (typeof crawlResults === 'string') { const crawlResultsPath = crawlResults; - crawlResults = requireFromWorkingDirectory(crawlResults); + crawlResults = await loadJSON(crawlResults); crawlResults = await expandCrawlResult(crawlResults, path.dirname(crawlResultsPath)); } else { @@ -385,12 +384,12 @@ async function studyCrawl(crawlResults, options = {}) { if (typeof options.trResults === 'string') { const crawlResultsPath = options.trResults; - options.trResults = requireFromWorkingDirectory(options.trResults); + options.trResults = await loadJSON(options.trResults); options.trResults = await expandCrawlResult(options.trResults, path.dirname(crawlResultsPath)); options.trResults = options.trResults.results; } - const results = studyCrawlResults(crawlResults.results, options); + const results = await studyCrawlResults(crawlResults.results, options); return { type: 'study', @@ -410,4 +409,4 @@ async function studyCrawl(crawlResults, options = {}) { /************************************************** Export methods for use as module **************************************************/ -module.exports.studyCrawl = studyCrawl; +export default studyCrawl; diff --git a/src/lib/study-refs.js b/src/lib/study-refs.js index 150e0727..f7542486 100644 --- a/src/lib/study-refs.js +++ b/src/lib/study-refs.js @@ -1,4 +1,5 @@ -const { recordCategorizedAnomaly } = require('./util'); +import { loadCrawlResults, recordCategorizedAnomaly } from './util.js'; +import { fileURLToPath } from 'node:url'; const possibleAnomalies = [ 'discontinuedReferences' @@ -21,13 +22,10 @@ function studyReferences (edResults) { return report; } -module.exports = { studyReferences }; +export default studyReferences; -if (require.main === module) { - (async function() { - const { loadCrawlResults } = require('../lib/util'); - const crawl = await loadCrawlResults(process.argv[2]); - const results = studyReferences(crawl.ed); - console.log(results); - })(); +if (process.argv[1] === fileURLToPath(import.meta.url)) { + const crawl = await loadCrawlResults(process.argv[2]); + const results = studyReferences(crawl.ed); + console.log(results); } diff --git a/src/lib/study-webidl.js b/src/lib/study-webidl.js index 8b2e30b8..fc09365f 100644 --- a/src/lib/study-webidl.js +++ b/src/lib/study-webidl.js @@ -24,8 +24,8 @@ * the crawl results parameter. */ -const { recordCategorizedAnomaly } = require('./util'); -const WebIDL2 = require('webidl2'); +import { recordCategorizedAnomaly } from './util.js'; +import * as WebIDL2 from 'webidl2'; const getSpecs = list => [...new Set(list.map(({ spec }) => spec))]; const specName = spec => spec.shortname ?? spec.url; @@ -669,4 +669,4 @@ function studyWebIdl (edResults, curatedResults) { /************************************************** Export methods for use as module **************************************************/ -module.exports = { studyWebIdl }; +export default studyWebIdl; diff --git a/src/lib/util.js b/src/lib/util.js index d9010289..7cecd6f0 100644 --- a/src/lib/util.js +++ b/src/lib/util.js @@ -1,10 +1,10 @@ -const requireFromWorkingDirectory = require('../lib/require-cwd'); -const { expandCrawlResult } = require('reffy'); +import { expandCrawlResult } from 'reffy'; +import loadJSON from './load-json.js'; async function loadCrawlResults (edCrawlResultsPath, trCrawlResultsPath) { let edCrawlResults, trCrawlResults; try { - edCrawlResults = requireFromWorkingDirectory(edCrawlResultsPath); + edCrawlResults = await loadJSON(edCrawlResultsPath); } catch (e) { throw new Error('Impossible to read ' + edCrawlResultsPath + ': ' + e); } @@ -12,7 +12,7 @@ async function loadCrawlResults (edCrawlResultsPath, trCrawlResultsPath) { if (trCrawlResultsPath) { try { - trCrawlResults = requireFromWorkingDirectory(trCrawlResultsPath); + trCrawlResults = await loadJSON(trCrawlResultsPath); } catch (e) { throw new Error('Impossible to read ' + trCrawlResultsPath + ': ' + e); } @@ -42,4 +42,4 @@ function recordCategorizedAnomaly (report, category, possibleAnomalies) { /************************************************** Export methods for use as module **************************************************/ -module.exports = { loadCrawlResults, recordCategorizedAnomaly }; +export { loadCrawlResults, recordCategorizedAnomaly }; diff --git a/src/reporting/clean-pending-regeneration.js b/src/reporting/clean-pending-regeneration.js index d5a1b875..9d81a936 100644 --- a/src/reporting/clean-pending-regeneration.js +++ b/src/reporting/clean-pending-regeneration.js @@ -4,12 +4,13 @@ * since the PR was created. */ -const core = require('@actions/core'); -const Octokit = require('../lib/octokit'); -const fs = require('fs'); -const path = require('path'); -const matter = require('gray-matter'); -const { loadCrawlResults } = require('../lib/util'); +import core from '@actions/core'; +import Octokit from '../lib/octokit.js'; +import fs from 'node:fs'; +import path from 'node:path'; +import matter from 'gray-matter'; +import { loadCrawlResults } from '../lib/util.js'; +import loadJSON from '../lib/load-json.js'; const owner = 'w3c'; const repo = 'strudy'; @@ -84,13 +85,8 @@ async function dropPendingReportsWhenPossible (edCrawlResultsPath) { /******************************************************************************* Retrieve GH_TOKEN from environment, prepare Octokit and kick things off *******************************************************************************/ -const GH_TOKEN = (() => { - try { - return require('../../config.json').GH_TOKEN; - } catch { - return process.env.GH_TOKEN; - } -})(); +const config = await loadJSON("config.json"); +const GH_TOKEN = config?.GH_TOKEN ?? process.env.GH_TOKEN; if (!GH_TOKEN) { console.error('GH_TOKEN must be set to some personal access token as an env variable or in a config.json file'); process.exit(1); diff --git a/src/reporting/clean-reports.js b/src/reporting/clean-reports.js index 42d64a8c..a16bb8d1 100644 --- a/src/reporting/clean-reports.js +++ b/src/reporting/clean-reports.js @@ -3,11 +3,14 @@ * a pull request to drop reports that have been addressed. */ -const core = require('@actions/core'); -const Octokit = require('../lib/octokit'); -const fs = require('fs'); -const path = require('path'); -const matter = require('gray-matter'); +import core from '@actions/core'; +import Octokit from '../lib/octokit.js'; +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from "node:url"; +import matter from 'gray-matter'; + +const scriptPath = path.dirname(fileURLToPath(import.meta.url)); /** * Check GitHub issues and PR referenced by patch files and drop patch files @@ -19,7 +22,7 @@ const matter = require('gray-matter'); * empty string when there are no patches to drop. */ async function dropReportsWhenPossible () { - const rootDir = path.join(__dirname, '../../issues'); + const rootDir = path.join(scriptPath, '../../issues'); console.log('Gather reports files'); let reports = []; @@ -91,13 +94,8 @@ async function dropReportsWhenPossible () { /******************************************************************************* Retrieve GH_TOKEN from environment, prepare Octokit and kick things off *******************************************************************************/ -const GH_TOKEN = (() => { - try { - return require('../../config.json').GH_TOKEN; - } catch { - return process.env.GH_TOKEN; - } -})(); +const config = await loadJSON("config.json"); +const GH_TOKEN = config?.GH_TOKEN ?? process.env.GH_TOKEN; if (!GH_TOKEN) { console.error('GH_TOKEN must be set to some personal access token as an env variable or in a config.json file'); process.exit(1); diff --git a/src/reporting/file-issue-for-review.js b/src/reporting/file-issue-for-review.js index 89056236..1bb1427a 100644 --- a/src/reporting/file-issue-for-review.js +++ b/src/reporting/file-issue-for-review.js @@ -2,23 +2,18 @@ creates a draft of an issue per spec and per anomaly type and submits as a pull request in this repo if no existing one matches */ -const { loadCrawlResults } = require('../lib/util'); -const { studyBackrefs } = require('../lib/study-backrefs'); -const { studyReferences } = require('../lib/study-refs'); -const path = require('path'); -const fs = require('fs').promises; -const { execSync } = require('child_process'); -const Octokit = require('../lib/octokit'); -const matter = require('gray-matter'); -const fetch = require('node-fetch'); +import { loadCrawlResults } from '../lib/util.js'; +import studyBackrefs from '../lib/study-backrefs.js'; +import studyReferences from '../lib/study-refs.js'; +import loadJSON from '../lib/load-json.js'; +import path from 'node:path'; +import fs from 'fs/promises'; +import { execSync } from 'node:child_process'; +import Octokit from '../lib/octokit.js'; +import matter from 'gray-matter'; -const GH_TOKEN = (() => { - try { - return require('../../config.json').GH_TOKEN; - } catch (err) { - return process.env.GH_TOKEN; - } -})(); +const config = await loadJSON("config.json"); +const GH_TOKEN = config?.GH_TOKEN ?? process.env.GH_TOKEN; const MAX_PR_BY_RUN = 10; @@ -77,215 +72,213 @@ ${issueReport} `; } -if (require.main === module) { - const knownAnomalyTypes = ['brokenLinks', 'outdatedSpecs', 'nonCanonicalRefs', 'discontinuedReferences']; - let edCrawlResultsPath = process.argv[2]; - let trCrawlResultsPath = process.argv[3]; - const anomalyFilter = process.argv.slice(4).filter(p => !p.startsWith('--')); - const unknownAnomalyType = anomalyFilter.find(p => !knownAnomalyTypes.includes(p)); - if (unknownAnomalyType) { - console.error(`Unknown report type ${unknownAnomalyType} - known types are ${knownAnomalyTypes.join(', ')}`); - process.exit(1); - } - const anomalyTypes = anomalyFilter.length ? anomalyFilter : knownAnomalyTypes; - const updateMode = process.argv.includes('--update') ? 'update-untracked' : (process.argv.includes('--update-tracked') ? 'update-tracked' : false); - const dryRun = process.argv.includes('--dry-run'); - const noGit = dryRun || updateMode || process.argv.includes('--no-git'); +const knownAnomalyTypes = ['brokenLinks', 'outdatedSpecs', 'nonCanonicalRefs', 'discontinuedReferences']; - if (!noGit && !GH_TOKEN) { - console.error('GH_TOKEN must be set to some personal access token as an env variable or in a config.json file'); - process.exit(1); - } +let edCrawlResultsPath = process.argv[2]; +let trCrawlResultsPath = process.argv[3]; +const anomalyFilter = process.argv.slice(4).filter(p => !p.startsWith('--')); +const unknownAnomalyType = anomalyFilter.find(p => !knownAnomalyTypes.includes(p)); +if (unknownAnomalyType) { + console.error(`Unknown report type ${unknownAnomalyType} - known types are ${knownAnomalyTypes.join(', ')}`); + process.exit(1); +} +const anomalyTypes = anomalyFilter.length ? anomalyFilter : knownAnomalyTypes; +const updateMode = process.argv.includes('--update') ? 'update-untracked' : (process.argv.includes('--update-tracked') ? 'update-tracked' : false); +const dryRun = process.argv.includes('--dry-run'); +const noGit = dryRun || updateMode || process.argv.includes('--no-git'); - // Target the index file if needed - if (!edCrawlResultsPath.endsWith('index.json')) { - edCrawlResultsPath = path.join(edCrawlResultsPath, 'index.json'); - } - if (!trCrawlResultsPath.endsWith('index.json')) { - trCrawlResultsPath = path.join(trCrawlResultsPath, 'index.json'); +if (!noGit && !GH_TOKEN) { + console.error('GH_TOKEN must be set to some personal access token as an env variable or in a config.json file'); + process.exit(1); +} + +// Target the index file if needed +if (!edCrawlResultsPath.endsWith('index.json')) { + edCrawlResultsPath = path.join(edCrawlResultsPath, 'index.json'); +} +if (!trCrawlResultsPath.endsWith('index.json')) { + trCrawlResultsPath = path.join(trCrawlResultsPath, 'index.json'); +} + +let existingReports = []; +if (updateMode) { + console.log('Compiling list of relevant existing issue reports…'); + // List all existing reports to serve as a comparison point + // to detect if any report can be deleted + // if the anomalies are no longer reported + const reportFiles = (await fs.readdir('issues')).map(p => 'issues/' + p); + for (const anomalyType of anomalyTypes) { + existingReports = existingReports.concat(reportFiles.filter(p => p.endsWith(`-${anomalyType.toLowerCase()}.md`))); } - (async function () { - let existingReports = []; - if (updateMode) { - console.log('Compiling list of relevant existing issue reports…'); - // List all existing reports to serve as a comparison point - // to detect if any report can be deleted - // if the anomalies are no longer reported - const reportFiles = (await fs.readdir('issues')).map(p => 'issues/' + p); - for (const anomalyType of anomalyTypes) { - existingReports = existingReports.concat(reportFiles.filter(p => p.endsWith(`-${anomalyType.toLowerCase()}.md`))); - } - console.log('- done'); - } - const nolongerRelevantReports = new Set(existingReports); + console.log('- done'); +} +const nolongerRelevantReports = new Set(existingReports); - // Donwload automatic map of multipages anchors in HTML spec - let htmlFragments = {}; - try { - console.log('Downloading HTML spec fragments data…'); - htmlFragments = await fetch('https://html.spec.whatwg.org/multipage/fragment-links.json').then(r => r.json()); - console.log('- done'); - } catch (err) { - console.log('- failed: could not fetch HTML fragments data, may report false positive broken links on HTML spec'); - } +// Donwload automatic map of multipages anchors in HTML spec +let htmlFragments = {}; +try { + console.log('Downloading HTML spec fragments data…'); + htmlFragments = await fetch('https://html.spec.whatwg.org/multipage/fragment-links.json').then(r => r.json()); + console.log('- done'); +} catch (err) { + console.log('- failed: could not fetch HTML fragments data, may report false positive broken links on HTML spec'); +} - console.log(`Opening crawl results ${edCrawlResultsPath} and ${trCrawlResultsPath}…`); - const crawl = await loadCrawlResults(edCrawlResultsPath, trCrawlResultsPath); - console.log('- done'); - console.log('Running references analysis…'); - // TODO: if we're not running all the reports, this could run only the - // relevant study function - const results = studyBackrefs(crawl.ed, crawl.tr, htmlFragments).concat(studyReferences(crawl.ed)); - console.log('- done'); - const currentBranch = noGit || execSync('git branch --show-current', { encoding: 'utf8' }).trim(); - const needsPush = {}; - for (const anomalyType of anomalyTypes) { - const anomalies = results.filter(r => r.name === anomalyType); - const specs = [...new Set(anomalies.map(a => a.specs.map(s => s.url)).flat())]; - for (const url of specs) { - const specAnomalies = anomalies.filter(a => a.specs[0].url === url); - const spec = specAnomalies[0].specs[0]; - console.log(`Compiling ${anomalyType} report for ${spec.title}…`); - // if we don't know the repo, we can't file an issue - if (!spec.nightly?.repository) { - console.log(`No known repo for ${spec.title}, skipping`); - continue; - } - if (spec.standing === "discontinued") { - console.log(`${spec.title} is discontinued, skipping`); +console.log(`Opening crawl results ${edCrawlResultsPath} and ${trCrawlResultsPath}…`); +const crawl = await loadCrawlResults(edCrawlResultsPath, trCrawlResultsPath); +console.log('- done'); +console.log('Running references analysis…'); +// TODO: if we're not running all the reports, this could run only the +// relevant study function +const results = studyBackrefs(crawl.ed, crawl.tr, htmlFragments).concat(studyReferences(crawl.ed)); +console.log('- done'); +const currentBranch = noGit || execSync('git branch --show-current', { encoding: 'utf8' }).trim(); +const needsPush = {}; +for (const anomalyType of anomalyTypes) { + const anomalies = results.filter(r => r.name === anomalyType); + const specs = [...new Set(anomalies.map(a => a.specs.map(s => s.url)).flat())]; + for (const url of specs) { + const specAnomalies = anomalies.filter(a => a.specs[0].url === url); + const spec = specAnomalies[0].specs[0]; + console.log(`Compiling ${anomalyType} report for ${spec.title}…`); + // if we don't know the repo, we can't file an issue + if (!spec.nightly?.repository) { + console.log(`No known repo for ${spec.title}, skipping`); + continue; + } + if (spec.standing === "discontinued") { + console.log(`${spec.title} is discontinued, skipping`); + continue; + } + const issueMoniker = `${spec.shortname}-${anomalyType.toLowerCase()}`; + // is there already a file with that moniker? + const issueFilename = path.join('issues/', issueMoniker + '.md'); + let tracked = 'N/A'; + let existingReportContent; + try { + if (!(await fs.stat(issueFilename)).isFile()) { + console.error(`${issueFilename} already exists but is not a file`); + continue; + } else { + if (!updateMode) { + console.log(`${issueFilename} already exists, bailing`); continue; - } - const issueMoniker = `${spec.shortname}-${anomalyType.toLowerCase()}`; - // is there already a file with that moniker? - const issueFilename = path.join('issues/', issueMoniker + '.md'); - let tracked = 'N/A'; - let existingReportContent; - try { - if (!(await fs.stat(issueFilename)).isFile()) { - console.error(`${issueFilename} already exists but is not a file`); - continue; - } else { - if (!updateMode) { - console.log(`${issueFilename} already exists, bailing`); - continue; - } else { - nolongerRelevantReports.delete(issueFilename); - try { - const existingReport = matter(await fs.readFile(issueFilename, 'utf-8')); - tracked = existingReport.data.Tracked; - existingReportContent = existingReport.content; - // only update tracked or untracked reports based on - // CLI parameter - if ((updateMode === 'update-untracked' && tracked !== 'N/A') || (updateMode === 'update-tracked' && tracked === 'N/A')) { - continue; - } - } catch (e) { - console.error('Failed to parse existing content', e); - continue; - } - } - } - } catch (err) { - // Intentionally blank - } - // if not, we create the file, add it in a branch - // and submit it as a pull request to the repo - const { title, content: issueReportContent } = issueWrapper(spec, specAnomalies, anomalyType); - if (updateMode) { - if (existingReportContent) { - const existingAnomalies = existingReportContent.split('\n').filter(l => l.startsWith('* [ ] ')).map(l => l.slice(6)); - if (existingAnomalies.every((a, i) => specAnomalies[i] === a) && existingAnomalies.length === specAnomalies.length) { - // no substantial change, skip - console.log(`Skipping ${title}, no change`); + } else { + nolongerRelevantReports.delete(issueFilename); + try { + const existingReport = matter(await fs.readFile(issueFilename, 'utf-8')); + tracked = existingReport.data.Tracked; + existingReportContent = existingReport.content; + // only update tracked or untracked reports based on + // CLI parameter + if ((updateMode === 'update-untracked' && tracked !== 'N/A') || (updateMode === 'update-tracked' && tracked === 'N/A')) { continue; } - } else { - // in update mode, we only care about existing reports + } catch (e) { + console.error('Failed to parse existing content', e); continue; } } - const issueReportData = matter(issueReportContent); - issueReportData.data = { - Repo: spec.nightly.repository, - Tracked: tracked, - Title: title - }; - let issueReport; - try { - issueReport = issueReportData.stringify(); - } catch (err) { - console.error(`Failed to stringify report of ${anomalyType} for ${title}: ${err}`, issueReportContent); + } + } catch (err) { + // Intentionally blank + } + // if not, we create the file, add it in a branch + // and submit it as a pull request to the repo + const { title, content: issueReportContent } = issueWrapper(spec, specAnomalies, anomalyType); + if (updateMode) { + if (existingReportContent) { + const existingAnomalies = existingReportContent.split('\n').filter(l => l.startsWith('* [ ] ')).map(l => l.slice(6)); + if (existingAnomalies.every((a, i) => specAnomalies[i] === a) && existingAnomalies.length === specAnomalies.length) { + // no substantial change, skip + console.log(`Skipping ${title}, no change`); continue; } - if (dryRun) { - console.log(`Would add ${issueFilename} with`); - console.log(issueReport); - console.log(); - } else { - await fs.writeFile(issueFilename, issueReport, 'utf-8'); - try { - if (!noGit) { - console.log(`Committing issue report as ${issueFilename} in branch ${issueMoniker}…`); - execSync(`git checkout -b ${issueMoniker}`); - execSync(`git add ${issueFilename}`); - execSync(`git commit -m "File report on ${issueReportData.data.Title}"`); - needsPush[issueMoniker] = { title: issueReportData.data.Title, report: issueReport, repo: spec.nightly.repository, specTitle: spec.title, uri: spec.crawled }; - console.log('- done'); - execSync(`git checkout ${currentBranch}`); - } - } catch (err) { - console.error(`Failed to commit error report for ${spec.title}`, err); - await fs.unlink(issueFilename); - execSync(`git checkout ${currentBranch}`); - } - } + } else { + // in update mode, we only care about existing reports + continue; } } - if (nolongerRelevantReports.size) { - console.log('The following reports are no longer relevant, deleting them', [...nolongerRelevantReports]); - for (const issueFilename of nolongerRelevantReports) { - await fs.unlink(issueFilename); - } + const issueReportData = matter(issueReportContent); + issueReportData.data = { + Repo: spec.nightly.repository, + Tracked: tracked, + Title: title + }; + let issueReport; + try { + issueReport = issueReportData.stringify(); + } catch (err) { + console.error(`Failed to stringify report of ${anomalyType} for ${title}: ${err}`, issueReportContent); + continue; } - if (Object.keys(needsPush).length) { - let counter = 0; - for (const branch in needsPush) { - if (counter > MAX_PR_BY_RUN) { - delete needsPush[branch]; - continue; - } - - // is there already a pull request targetting that branch? - const { data: pullrequests } = (await octokit.rest.pulls.list({ - owner: repoOwner, - repo: repoName, - head: `${repoOwner}:${branch}` - })); - if (pullrequests.length > 0) { - console.log(`A pull request from branch ${branch} already exists, bailing`); - delete needsPush[branch]; + if (dryRun) { + console.log(`Would add ${issueFilename} with`); + console.log(issueReport); + console.log(); + } else { + await fs.writeFile(issueFilename, issueReport, 'utf-8'); + try { + if (!noGit) { + console.log(`Committing issue report as ${issueFilename} in branch ${issueMoniker}…`); + execSync(`git checkout -b ${issueMoniker}`); + execSync(`git add ${issueFilename}`); + execSync(`git commit -m "File report on ${issueReportData.data.Title}"`); + needsPush[issueMoniker] = { title: issueReportData.data.Title, report: issueReport, repo: spec.nightly.repository, specTitle: spec.title, uri: spec.crawled }; + console.log('- done'); + execSync(`git checkout ${currentBranch}`); } - counter++; + } catch (err) { + console.error(`Failed to commit error report for ${spec.title}`, err); + await fs.unlink(issueFilename); + execSync(`git checkout ${currentBranch}`); } } - if (Object.keys(needsPush).length) { - console.log(`Pushing new branches ${Object.keys(needsPush).join(' ')}…`); - execSync(`git push origin ${Object.keys(needsPush).join(' ')}`); - console.log('- done'); - for (const branch in needsPush) { - const { title, specTitle, uri, repo, report } = needsPush[branch]; - console.log(`Creating pull request from branch ${branch}…`); - await octokit.rest.pulls.create({ - owner: repoOwner, - repo: repoName, - title, - body: prWrapper(specTitle, uri, repo, report), - head: `${repoOwner}:${branch}`, - base: 'main' - }); - console.log('- done'); - } + } +} +if (nolongerRelevantReports.size) { + console.log('The following reports are no longer relevant, deleting them', [...nolongerRelevantReports]); + for (const issueFilename of nolongerRelevantReports) { + await fs.unlink(issueFilename); + } +} +if (Object.keys(needsPush).length) { + let counter = 0; + for (const branch in needsPush) { + if (counter > MAX_PR_BY_RUN) { + delete needsPush[branch]; + continue; + } + + // is there already a pull request targetting that branch? + const { data: pullrequests } = (await octokit.rest.pulls.list({ + owner: repoOwner, + repo: repoName, + head: `${repoOwner}:${branch}` + })); + if (pullrequests.length > 0) { + console.log(`A pull request from branch ${branch} already exists, bailing`); + delete needsPush[branch]; } - })(); + counter++; + } +} +if (Object.keys(needsPush).length) { + console.log(`Pushing new branches ${Object.keys(needsPush).join(' ')}…`); + execSync(`git push origin ${Object.keys(needsPush).join(' ')}`); + console.log('- done'); + for (const branch in needsPush) { + const { title, specTitle, uri, repo, report } = needsPush[branch]; + console.log(`Creating pull request from branch ${branch}…`); + await octokit.rest.pulls.create({ + owner: repoOwner, + repo: repoName, + title, + body: prWrapper(specTitle, uri, repo, report), + head: `${repoOwner}:${branch}`, + base: 'main' + }); + console.log('- done'); + } } diff --git a/src/reporting/submit-issue.js b/src/reporting/submit-issue.js index 43b31d8f..bebac240 100644 --- a/src/reporting/submit-issue.js +++ b/src/reporting/submit-issue.js @@ -3,85 +3,77 @@ Can also be called on a specific issue report (typicall when it gets merged in) */ -const fs = require('fs').promises; -const matter = require('gray-matter'); -const { execSync } = require('child_process'); -const Octokit = require('../lib/octokit'); +import fs from 'fs/promises'; +import matter from 'gray-matter'; +import { execSync } from 'child_process'; +import Octokit from '../lib/octokit.js'; +import loadJSON from '../lib/load-json.js'; -const GH_TOKEN = (() => { - try { - return require('../../config.json').GH_TOKEN; - } catch (err) { - return process.env.GH_TOKEN; - } -})(); +const config = await loadJSON("config.json"); +const GH_TOKEN = config?.GH_TOKEN ?? process.env.GH_TOKEN; const octokit = new Octokit({ auth: GH_TOKEN // log: console }); -if (require.main === module) { - const targetIssueReport = process.argv[2]; - let issuesToSubmit = []; - (async function () { - execSync('git pull origin main'); - if (targetIssueReport) { - try { - if ((await fs.stat(targetIssueReport)).isFile()) { - issuesToSubmit.push(targetIssueReport); - } else { - console.error(`${targetIssueReport} is not a file`); - process.exit(2); - } - } catch (err) { - console.error(`${targetIssueReport} does not exist`); - process.exit(2); - } +const targetIssueReport = process.argv[2]; +let issuesToSubmit = []; +execSync('git pull origin main'); +if (targetIssueReport) { + try { + if ((await fs.stat(targetIssueReport)).isFile()) { + issuesToSubmit.push(targetIssueReport); } else { - issuesToSubmit = (await fs.readdir('issues')).filter(p => p.endsWith('.md')).map(p => 'issues/' + p); - } - let needsCommit = false; - for (const filename of issuesToSubmit) { - const issueReport = await fs.readFile(filename, 'utf-8'); - const issueData = matter(issueReport); - const { data: metadata, content: body } = issueData; - if (!(metadata?.Repo && metadata?.Tracked && metadata?.Title && body)) { - console.error(`Could not parse expected data from ${filename}.`, JSON.stringify(issueData, null, 2)); - continue; - } - if (metadata.Tracked !== 'N/A') { - console.log(`Issue report ${filename} already filed as ${metadata.Tracked}.`); - continue; - } - const m = metadata.Repo.match(/https:\/\/github\.com\/([^/]*)\/([^/]*)\/?$/); - if (!m) { - console.error(`Cannot parse ${metadata.Repo} as a github repository url.`); - continue; - } - const [, owner, repo] = m; - console.log(`Submitting issue ${metadata.Title}…`); - const ghRes = await octokit.rest.issues.create({ - owner, - repo, - title: metadata.Title, - body - }); - const issueUrl = ghRes?.data?.html_url; - console.log(`- filed ${issueUrl}`); - if (issueUrl) { - execSync('git pull origin main'); - console.log(`Saving updated report to ${filename}`); - metadata.Tracked = issueUrl; - await fs.writeFile(filename, issueData.stringify(), 'utf-8'); - console.log(issueData.stringify()); - execSync(`git add -u ${filename}`); - needsCommit = true; - } + console.error(`${targetIssueReport} is not a file`); + process.exit(2); } - if (needsCommit) { - execSync('git commit -m "Update issue reports with github issue ref"'); - execSync('git push origin main'); - } - })(); + } catch (err) { + console.error(`${targetIssueReport} does not exist`); + process.exit(2); + } +} else { + issuesToSubmit = (await fs.readdir('issues')).filter(p => p.endsWith('.md')).map(p => 'issues/' + p); +} +let needsCommit = false; +for (const filename of issuesToSubmit) { + const issueReport = await fs.readFile(filename, 'utf-8'); + const issueData = matter(issueReport); + const { data: metadata, content: body } = issueData; + if (!(metadata?.Repo && metadata?.Tracked && metadata?.Title && body)) { + console.error(`Could not parse expected data from ${filename}.`, JSON.stringify(issueData, null, 2)); + continue; + } + if (metadata.Tracked !== 'N/A') { + console.log(`Issue report ${filename} already filed as ${metadata.Tracked}.`); + continue; + } + const m = metadata.Repo.match(/https:\/\/github\.com\/([^/]*)\/([^/]*)\/?$/); + if (!m) { + console.error(`Cannot parse ${metadata.Repo} as a github repository url.`); + continue; + } + const [, owner, repo] = m; + console.log(`Submitting issue ${metadata.Title}…`); + const ghRes = await octokit.rest.issues.create({ + owner, + repo, + title: metadata.Title, + body + }); + const issueUrl = ghRes?.data?.html_url; + console.log(`- filed ${issueUrl}`); + if (issueUrl) { + execSync('git pull origin main'); + console.log(`Saving updated report to ${filename}`); + metadata.Tracked = issueUrl; + await fs.writeFile(filename, issueData.stringify(), 'utf-8'); + console.log(issueData.stringify()); + execSync(`git add -u ${filename}`); + needsCommit = true; + } +} +if (needsCommit) { + execSync('git commit -m "Update issue reports with github issue ref"'); + execSync('git push origin main'); } diff --git a/strudy.js b/strudy.js index d04ec3aa..a766d8fb 100644 --- a/strudy.js +++ b/strudy.js @@ -18,19 +18,19 @@ * @module crawler */ -const commander = require('commander'); -const { constants: fsConstants } = require('fs'); -const fs = require('fs/promises'); -const pandoc = require('node-pandoc'); -const path = require('path'); -const satisfies = require('semver/functions/satisfies'); -const requireFromWorkingDirectory = require('./src/lib/require-cwd'); -const { version, engines } = require('./package.json'); -const { studyCrawl } = require('./src/lib/study-crawl.js'); -const { generateReport } = require('./src/lib/generate-report.js'); - +import { Command } from 'commander'; +import { constants as fsConstants } from 'node:fs'; +import fs from 'node:fs/promises'; +import pandoc from 'node-pandoc'; +import path from 'node:path'; +import satisfies from 'semver/functions/satisfies.js'; +import packageContents from './package.json' with { type: 'json' }; +import studyCrawl from './src/lib/study-crawl.js'; +import generateReport from './src/lib/generate-report.js'; +import loadJSON from './src/lib/load-json.js'; // Warn if version of Node.js does not satisfy requirements +const { version, engines } = packageContents; if (engines && engines.node && !satisfies(process.version, engines.node)) { console.warn(` [WARNING] Node.js ${process.version} detected but Strudy needs Node.js ${engines.node}. @@ -68,7 +68,7 @@ async function isStudyReport(file) { } -const program = new commander.Command(); +const program = new Command(); program .name('strudy') .description('Analyzes a crawl report generated by Reffy') @@ -217,7 +217,7 @@ The --format option can only be set to "markdown" when --diff is used.`); let study = null; const isStudy = await isStudyReport(edReport); if (isStudy) { - study = requireFromWorkingDirectory(edReport); + study = await loadJSON(edReport); } if (!study) { diff --git a/test/study-backrefs.js b/test/study-backrefs.js index 9dc98ff9..ea755137 100644 --- a/test/study-backrefs.js +++ b/test/study-backrefs.js @@ -3,8 +3,8 @@ */ /* global describe, it */ -const { studyBackrefs } = require('../src/lib/study-backrefs'); -const { assertNbAnomalies, assertAnomaly } = require('./util'); +import studyBackrefs from '../src/lib/study-backrefs.js'; +import { assertNbAnomalies, assertAnomaly } from './util.js'; const specEdUrl = 'https://w3c.github.io/spec/'; const specEdUrl2 = 'https://w3c.github.io/spec2/'; diff --git a/test/study-refs.js b/test/study-refs.js index d9cc433e..11a9db18 100644 --- a/test/study-refs.js +++ b/test/study-refs.js @@ -3,8 +3,8 @@ */ /* global describe, it */ -const { studyReferences } = require('../src/lib/study-refs'); -const { assertNbAnomalies, assertAnomaly } = require('./util'); +import studyReferences from '../src/lib/study-refs.js'; +import { assertNbAnomalies, assertAnomaly } from './util.js'; const specEdUrl = 'https://w3c.github.io/spec/'; const specEdUrl2 = 'https://w3c.github.io/spec2/'; diff --git a/test/study-webidl.js b/test/study-webidl.js index 11935275..4660b0e0 100644 --- a/test/study-webidl.js +++ b/test/study-webidl.js @@ -4,8 +4,8 @@ */ /* global describe, it */ -const { studyWebIdl } = require('../src/lib/study-webidl'); -const { assertNbAnomalies, assertAnomaly } = require('./util'); +import studyWebIdl from '../src/lib/study-webidl.js'; +import { assertNbAnomalies, assertAnomaly } from './util.js'; describe('The Web IDL analyser', () => { const specUrl = 'https://www.w3.org/TR/spec'; diff --git a/test/util.js b/test/util.js index 885c5e48..4ced9ea6 100644 --- a/test/util.js +++ b/test/util.js @@ -1,4 +1,4 @@ -const assert = require('assert').strict; +import assert from 'node:assert/strict'; function assertNbAnomalies (report, length) { assert.deepEqual(report.length, length, @@ -30,4 +30,4 @@ function assertAnomaly (report, idx, value) { /************************************************** Export methods for use as module **************************************************/ -module.exports = { assertNbAnomalies, assertAnomaly }; +export { assertNbAnomalies, assertAnomaly };