From 516711fdac7a5f4f512f1e9a97126416feaf246b Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Wed, 29 Apr 2026 17:10:52 +0100 Subject: [PATCH 1/5] install are the types wrong and add a github action Co-authored-by: Copilot --- .github/workflows/attw-check.yml | 128 +++++++++ package-lock.json | 441 +++++++++++++++++++++++++++++++ package.json | 2 + 3 files changed, 571 insertions(+) create mode 100644 .github/workflows/attw-check.yml diff --git a/.github/workflows/attw-check.yml b/.github/workflows/attw-check.yml new file mode 100644 index 0000000000..7ba4476d15 --- /dev/null +++ b/.github/workflows/attw-check.yml @@ -0,0 +1,128 @@ +name: "Are the types wrong?" +on: [pull_request] +permissions: + pull-requests: write + +jobs: + build: + name: Are the types wrong? + runs-on: ubuntu-latest + steps: + - name: Setup node + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: 22 + + - name: Checkout PR branch + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Install and build + env: + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + ELECTRON_DISABLE_SANDBOX: 1 + run: | + npm ci + npm run build + + - name: Run ATTW check + continue-on-error: true + env: + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + ELECTRON_DISABLE_SANDBOX: 1 + run: | + npm run test:attw || true + + - name: Parse ATTW results and comment + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + const fs = require('fs'); + + // Read and parse the results file + let results; + try { + const content = fs.readFileSync('attw-results.json', 'utf8'); + // The file contains multiple JSON objects, one per line + const lines = content.trim().split('\n').filter(line => line.trim()); + results = lines.map(line => JSON.parse(line)); + } catch (error) { + console.log('Error reading attw-results.json:', error.message); + return; + } + + // Filter for packages with problems + const packagesWithProblems = results.filter(result => { + return result.problems && Object.keys(result.problems).length > 0; + }); + + // Build comment body + let commentBody = '## šŸ“¦ Are The Types Wrong? Report\n\n'; + + if (packagesWithProblems.length === 0) { + commentBody += 'āœ… All packages passed the type check!\n'; + } else { + commentBody += `āŒ Found issues in ${packagesWithProblems.length} package(s):\n\n`; + + for (const result of packagesWithProblems) { + const packageName = result.analysis.packageName; + const problemCount = Object.keys(result.problems).length; + + commentBody += `### \`${packageName}\`\n\n`; + commentBody += `**${problemCount} problem(s) detected:**\n\n`; + + // Group problems by kind + const problemsByKind = {}; + for (const [entrypoint, issues] of Object.entries(result.problems)) { + for (const issue of issues) { + const kind = issue.kind; + if (!problemsByKind[kind]) { + problemsByKind[kind] = []; + } + problemsByKind[kind].push({ entrypoint, issue }); + } + } + + // Display problems by kind + for (const [kind, problems] of Object.entries(problemsByKind)) { + commentBody += `**${kind}** (${problems.length} occurrence(s)):\n`; + for (const { entrypoint, issue } of problems.slice(0, 5)) { + commentBody += `- \`${entrypoint}\`: ${issue.message || 'No message'}\n`; + } + if (problems.length > 5) { + commentBody += `- ... and ${problems.length - 5} more\n`; + } + commentBody += '\n'; + } + } + } + + commentBody += '\n---\n'; + commentBody += '*This check helps ensure TypeScript types are correctly exported and work across different module systems.*\n'; + + // Post or update comment + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + + const botComment = comments.find(comment => + comment.user.type === 'Bot' && + comment.body.includes('Are The Types Wrong? Report') + ); + + if (botComment) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: botComment.id, + body: commentBody + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: commentBody + }); + } diff --git a/package-lock.json b/package-lock.json index ba06c1cb06..2724224e45 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "packages/*" ], "devDependencies": { + "@arethetypeswrong/cli": "^0.18.2", "@babel/core": "^7.24.5", "@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-object-rest-spread": "^7.20.7", @@ -104,6 +105,12 @@ "node": ">=6.0.0" } }, + "node_modules/@andrewbranch/untar.js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@andrewbranch/untar.js/-/untar.js-1.0.3.tgz", + "integrity": "sha512-Jh15/qVmrLGhkKJBdXlK1+9tY4lZruYjsgkDFj08ZmDiWVBLJcqkok7Z0/R0In+i1rScBpJlSvrTS2Lm41Pbnw==", + "dev": true + }, "node_modules/@angular-devkit/architect": { "version": "0.1902.24", "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1902.24.tgz", @@ -1597,6 +1604,124 @@ "zone.js": "~0.15.0" } }, + "node_modules/@arethetypeswrong/cli": { + "version": "0.18.2", + "resolved": "https://registry.npmjs.org/@arethetypeswrong/cli/-/cli-0.18.2.tgz", + "integrity": "sha512-PcFM20JNlevEDKBg4Re29Rtv2xvjvQZzg7ENnrWFSS0PHgdP2njibVFw+dRUhNkPgNfac9iUqO0ohAXqQL4hbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@arethetypeswrong/core": "0.18.2", + "chalk": "^4.1.2", + "cli-table3": "^0.6.3", + "commander": "^10.0.1", + "marked": "^9.1.2", + "marked-terminal": "^7.1.0", + "semver": "^7.5.4" + }, + "bin": { + "attw": "dist/index.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@arethetypeswrong/cli/node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/@arethetypeswrong/cli/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@arethetypeswrong/cli/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@arethetypeswrong/core": { + "version": "0.18.2", + "resolved": "https://registry.npmjs.org/@arethetypeswrong/core/-/core-0.18.2.tgz", + "integrity": "sha512-GiwTmBFOU1/+UVNqqCGzFJYfBXEytUkiI+iRZ6Qx7KmUVtLm00sYySkfe203C9QtPG11yOz1ZaMek8dT/xnlgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@andrewbranch/untar.js": "^1.0.3", + "@loaderkit/resolve": "^1.0.2", + "cjs-module-lexer": "^1.2.3", + "fflate": "^0.8.2", + "lru-cache": "^11.0.1", + "semver": "^7.5.4", + "typescript": "5.6.1-rc", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@arethetypeswrong/core/node_modules/lru-cache": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", + "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@arethetypeswrong/core/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@arethetypeswrong/core/node_modules/typescript": { + "version": "5.6.1-rc", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.1-rc.tgz", + "integrity": "sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/@babel/cli": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.28.6.tgz", @@ -3857,6 +3982,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@braidai/lang": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@braidai/lang/-/lang-1.1.2.tgz", + "integrity": "sha512-qBcknbBufNHlui137Hft8xauQMTZDKdophmLFv05r2eNmdIv/MlPuP4TdUknHG68UdWLgVZwgxVe735HzJNIwA==", + "dev": true, + "license": "ISC" + }, "node_modules/@bugsnag/browser": { "resolved": "packages/browser", "link": true @@ -4218,6 +4350,17 @@ "node": ">=18" } }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/@cucumber/create-meta": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@cucumber/create-meta/-/create-meta-5.0.0.tgz", @@ -7336,6 +7479,16 @@ "win32" ] }, + "node_modules/@loaderkit/resolve": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@loaderkit/resolve/-/resolve-1.0.5.tgz", + "integrity": "sha512-fhkdGM57xhJ7CO91MUgbQlb0ClP0AJ9vB3yoVnBTslYJqrJOCVEbOprZcxZlexdMbmTBPQqVcQYr+j4oRRtIZA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@braidai/lang": "^1.0.0" + } + }, "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", @@ -16169,6 +16322,104 @@ "node": ">=8" } }, + "node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "dev": true, + "license": "ISC", + "dependencies": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/cli-highlight/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cli-highlight/node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-highlight/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/cli-highlight/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/cli-highlight/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cli-highlight/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/cli-spinners": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz", @@ -18559,6 +18810,13 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, + "node_modules/emojilib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", + "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", + "dev": true, + "license": "MIT" + }, "node_modules/emojis-list": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", @@ -20318,6 +20576,13 @@ } } }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "dev": true, + "license": "MIT" + }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -21908,6 +22173,16 @@ "node": ">= 12" } }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, "node_modules/hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -26202,6 +26477,99 @@ "dev": true, "license": "MIT" }, + "node_modules/marked": { + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/marked/-/marked-9.1.6.tgz", + "integrity": "sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 16" + } + }, + "node_modules/marked-terminal": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.3.0.tgz", + "integrity": "sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "ansi-regex": "^6.1.0", + "chalk": "^5.4.1", + "cli-highlight": "^2.1.11", + "cli-table3": "^0.6.5", + "node-emoji": "^2.2.0", + "supports-hyperlinks": "^3.1.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "marked": ">=1 <16" + } + }, + "node_modules/marked-terminal/node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/marked-terminal/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/marked-terminal/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/marked-terminal/node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, "node_modules/matcher": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", @@ -28325,6 +28693,22 @@ "node": ">= 0.10.5" } }, + "node_modules/node-emoji": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", + "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.6.0", + "char-regex": "^1.0.2", + "emojilib": "^2.4.0", + "skin-tone": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/node-exports-info": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", @@ -29850,6 +30234,23 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true, + "license": "MIT" + }, "node_modules/parse5-sax-parser": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-7.0.0.tgz", @@ -33321,6 +33722,19 @@ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "license": "MIT" }, + "node_modules/skin-tone": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", + "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-emoji-modifier-base": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -34431,6 +34845,23 @@ "node": ">=8" } }, + "node_modules/supports-hyperlinks": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -35825,6 +36256,16 @@ "node": ">=4" } }, + "node_modules/unicode-emoji-modifier-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", + "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/unicode-match-property-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", diff --git a/package.json b/package.json index f924116f64..4d6a190c9b 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "devDependencies": { + "@arethetypeswrong/cli": "^0.18.2", "@babel/core": "^7.24.5", "@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-object-rest-spread": "^7.20.7", @@ -85,6 +86,7 @@ "build": "lerna run build", "clean": "lerna run clean", "build:electron": "lerna run build --scope '@bugsnag/plugin-electron-ipc' --scope '@bugsnag/plugin-electron-app' --scope '@bugsnag/plugin-electron-client-state-persistence'", + "test:attw": "lerna exec --concurrency 1 -- attw --pack . -f json > attw-results.json", "test:lint": "eslint --report-unused-disable-directives .", "test:lint-native": "bash scripts/cppcheck.sh", "test:unit:electron-runner": "xvfb-maybe --auto-servernum -- jest -c config/electron-jest.config.js --rootDir .", From 54aff9902d9f374b6ee3c4c99b265a3b58423505 Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Wed, 29 Apr 2026 17:18:17 +0100 Subject: [PATCH 2/5] update attw check script Co-authored-by: Copilot --- .github/workflows/attw-check.yml | 68 ++-------- scripts/parse-attw-results.js | 219 +++++++++++++++++++++++++++++++ 2 files changed, 231 insertions(+), 56 deletions(-) create mode 100755 scripts/parse-attw-results.js diff --git a/.github/workflows/attw-check.yml b/.github/workflows/attw-check.yml index 7ba4476d15..ec27c32613 100644 --- a/.github/workflows/attw-check.yml +++ b/.github/workflows/attw-check.yml @@ -36,69 +36,25 @@ jobs: uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 with: script: | - const fs = require('fs'); + const { execSync } = require('child_process'); - // Read and parse the results file - let results; + // Run the parse script to get markdown output + let commentBody; try { - const content = fs.readFileSync('attw-results.json', 'utf8'); - // The file contains multiple JSON objects, one per line - const lines = content.trim().split('\n').filter(line => line.trim()); - results = lines.map(line => JSON.parse(line)); + commentBody = execSync('node scripts/parse-attw-results.js --format=markdown', { + encoding: 'utf8', + stdio: ['pipe', 'pipe', 'pipe'] + }); } catch (error) { - console.log('Error reading attw-results.json:', error.message); - return; + // Script exits with code 1 if there are problems, but we still want the output + commentBody = error.stdout || error.message; } - // Filter for packages with problems - const packagesWithProblems = results.filter(result => { - return result.problems && Object.keys(result.problems).length > 0; - }); - - // Build comment body - let commentBody = '## šŸ“¦ Are The Types Wrong? Report\n\n'; - - if (packagesWithProblems.length === 0) { - commentBody += 'āœ… All packages passed the type check!\n'; - } else { - commentBody += `āŒ Found issues in ${packagesWithProblems.length} package(s):\n\n`; - - for (const result of packagesWithProblems) { - const packageName = result.analysis.packageName; - const problemCount = Object.keys(result.problems).length; - - commentBody += `### \`${packageName}\`\n\n`; - commentBody += `**${problemCount} problem(s) detected:**\n\n`; - - // Group problems by kind - const problemsByKind = {}; - for (const [entrypoint, issues] of Object.entries(result.problems)) { - for (const issue of issues) { - const kind = issue.kind; - if (!problemsByKind[kind]) { - problemsByKind[kind] = []; - } - problemsByKind[kind].push({ entrypoint, issue }); - } - } - - // Display problems by kind - for (const [kind, problems] of Object.entries(problemsByKind)) { - commentBody += `**${kind}** (${problems.length} occurrence(s)):\n`; - for (const { entrypoint, issue } of problems.slice(0, 5)) { - commentBody += `- \`${entrypoint}\`: ${issue.message || 'No message'}\n`; - } - if (problems.length > 5) { - commentBody += `- ... and ${problems.length - 5} more\n`; - } - commentBody += '\n'; - } - } + if (!commentBody || !commentBody.includes('Are The Types Wrong? Report')) { + console.log('No valid output from parse script'); + return; } - commentBody += '\n---\n'; - commentBody += '*This check helps ensure TypeScript types are correctly exported and work across different module systems.*\n'; - // Post or update comment const { data: comments } = await github.rest.issues.listComments({ owner: context.repo.owner, diff --git a/scripts/parse-attw-results.js b/scripts/parse-attw-results.js new file mode 100755 index 0000000000..1aa209494b --- /dev/null +++ b/scripts/parse-attw-results.js @@ -0,0 +1,219 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +/** + * Parse ATTW results and generate a report + * Usage: node scripts/parse-attw-results.js [--format=markdown|json|text] + */ + +function parseResults(filePath) { + const content = fs.readFileSync(filePath, 'utf8'); + const lines = content.split('\n'); + const results = []; + + let currentJson = ''; + let braceCount = 0; + let inJson = false; + + for (const line of lines) { + // Skip empty lines and lines that don't start a JSON object if we're not already in one + if (!inJson && !line.trim().startsWith('{')) { + continue; + } + + // Start tracking a new JSON object + if (line.trim().startsWith('{') && braceCount === 0) { + inJson = true; + currentJson = ''; + braceCount = 0; + } + + if (inJson) { + currentJson += line + '\n'; + + // Count braces to know when we've completed a JSON object + for (const char of line) { + if (char === '{') braceCount++; + if (char === '}') braceCount--; + } + + // When braceCount returns to 0, we have a complete JSON object + if (braceCount === 0) { + try { + const parsed = JSON.parse(currentJson); + results.push(parsed); + } catch (err) { + console.error('Error parsing JSON object:', err.message); + } + inJson = false; + currentJson = ''; + } + } + } + + return results; +} + +function analyzeResults(results) { + const packagesWithProblems = results.filter(result => { + return result.problems && Object.keys(result.problems).length > 0; + }); + + const analysis = { + totalPackages: results.length, + packagesWithProblems: packagesWithProblems.length, + packagesWithoutProblems: results.length - packagesWithProblems.length, + details: [] + }; + + for (const result of packagesWithProblems) { + const packageName = result.analysis.packageName; + const problemCount = Object.keys(result.problems).length; + + // Group problems by kind + const problemsByKind = {}; + for (const [entrypoint, issues] of Object.entries(result.problems)) { + for (const issue of issues) { + const kind = issue.kind; + if (!problemsByKind[kind]) { + problemsByKind[kind] = []; + } + // Extract useful info from the issue + const issueInfo = { + entrypoint, + fileName: issue.fileName, + moduleSpecifier: issue.moduleSpecifier, + resolutionOption: issue.resolutionOption, + // Get the last line of trace which often has the key error message + traceMessage: issue.trace && issue.trace.length > 0 + ? issue.trace[issue.trace.length - 1] + : null + }; + problemsByKind[kind].push(issueInfo); + } + } + + analysis.details.push({ + packageName, + problemCount, + problemsByKind + }); + } + + return analysis; +} + +function formatAsText(analysis) { + let output = '\n=== Are The Types Wrong? Report ===\n\n'; + + output += `Total packages checked: ${analysis.totalPackages}\n`; + output += `Packages with problems: ${analysis.packagesWithProblems}\n`; + output += `Packages without problems: ${analysis.packagesWithoutProblems}\n\n`; + + if (analysis.packagesWithProblems === 0) { + output += 'āœ… All packages passed the type check!\n'; + } else { + output += `āŒ Found issues in ${analysis.packagesWithProblems} package(s):\n\n`; + + for (const pkg of analysis.details) { + output += `šŸ“¦ ${pkg.packageName}\n`; + output += ` ${pkg.problemCount} problem(s) detected\n\n`; + + for (const [kind, problems] of Object.entries(pkg.problemsByKind)) { + output += ` ${kind} (${problems.length} occurrence(s)):\n`; + for (const issue of problems.slice(0, 3)) { + if (issue.moduleSpecifier) { + output += ` - ${issue.moduleSpecifier} (in ${issue.fileName?.split('/').pop() || 'unknown'})\n`; + } else { + output += ` - ${issue.entrypoint}\n`; + } + if (issue.traceMessage) { + output += ` ${issue.traceMessage}\n`; + } + } + if (problems.length > 3) { + output += ` - ... and ${problems.length - 3} more\n`; + } + output += '\n'; + } + } + } + + return output; +} + +function formatAsMarkdown(analysis) { + let output = '## šŸ“¦ Are The Types Wrong? Report\n\n'; + + output += `**Total packages checked:** ${analysis.totalPackages} \n`; + output += `**Packages with problems:** ${analysis.packagesWithProblems} \n`; + output += `**Packages without problems:** ${analysis.packagesWithoutProblems}\n\n`; + + if (analysis.packagesWithProblems === 0) { + output += 'āœ… All packages passed the type check!\n'; + } else { + output += `āŒ Found issues in ${analysis.packagesWithProblems} package(s):\n\n`; + + for (const pkg of analysis.details) { + output += `### \`${pkg.packageName}\`\n\n`; + output += `**${pkg.problemCount} problem(s) detected:**\n\n`; + + for (const [kind, problems] of Object.entries(pkg.problemsByKind)) { + output += `**${kind}** (${problems.length} occurrence(s)):\n`; + for (const issue of problems.slice(0, 3)) { + if (issue.moduleSpecifier) { + output += `- \`${issue.moduleSpecifier}\` in \`${issue.fileName?.split('/').pop() || 'unknown'}\`\n`; + } else { + output += `- \`${issue.entrypoint}\`\n`; + } + } + if (problems.length > 3) { + output += `- ... and ${problems.length - 3} more\n`; + } + output += '\n'; + } + } + } + + output += '\n---\n'; + output += '*This check helps ensure TypeScript types are correctly exported and work across different module systems.*\n'; + + return output; +} + +function main() { + const args = process.argv.slice(2); + const formatArg = args.find(arg => arg.startsWith('--format=')); + const format = formatArg ? formatArg.split('=')[1] : 'text'; + + const resultsPath = path.join(process.cwd(), 'attw-results.json'); + + if (!fs.existsSync(resultsPath)) { + console.error('āŒ Error: attw-results.json not found'); + console.error('Run "npm run test:attw" first to generate the results file'); + process.exit(1); + } + + try { + const results = parseResults(resultsPath); + const analysis = analyzeResults(results); + + if (format === 'json') { + console.log(JSON.stringify(analysis, null, 2)); + } else if (format === 'markdown') { + console.log(formatAsMarkdown(analysis)); + } else { + console.log(formatAsText(analysis)); + } + + // Exit with error code if there are problems + process.exit(analysis.packagesWithProblems > 0 ? 1 : 0); + } catch (error) { + console.error('āŒ Error parsing results:', error.message); + process.exit(1); + } +} + +main(); From a72472cbeb0ec16fad0c12832a74376b67466f23 Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Wed, 29 Apr 2026 17:26:21 +0100 Subject: [PATCH 3/5] fix type check on electron-filestore package --- packages/electron-filestore/package.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/electron-filestore/package.json b/packages/electron-filestore/package.json index f3d54978ef..ef62232680 100644 --- a/packages/electron-filestore/package.json +++ b/packages/electron-filestore/package.json @@ -10,6 +10,10 @@ "type": "git", "url": "git@github.com:bugsnag/bugsnag-js.git" }, + "files": [ + "filestore.js", + "lib/minidump-io.js" + ], "publishConfig": { "access": "public" } From b5581342a1bc454796c80d1d074687bafcd87434 Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Wed, 29 Apr 2026 17:27:33 +0100 Subject: [PATCH 4/5] fix type check for react-native-cli package --- packages/react-native-cli/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-native-cli/package.json b/packages/react-native-cli/package.json index c5878c8d49..b3144aacdd 100644 --- a/packages/react-native-cli/package.json +++ b/packages/react-native-cli/package.json @@ -2,6 +2,7 @@ "name": "@bugsnag/react-native-cli", "version": "8.3.2", "description": "A tool to help integrate Bugsnag with a React Native app", + "types": "dist/bin/cli.d.ts", "bin": { "bugsnag-react-native-cli": "bin/cli" }, From 2d3cf843857046ef69f8fd137dff33f2e9bfae7c Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Wed, 29 Apr 2026 17:46:22 +0100 Subject: [PATCH 5/5] update attw check --- .gitignore | 2 + package.json | 2 +- scripts/parse-attw-results.js | 79 ++++++++++++++++------------ scripts/run-attw.js | 98 +++++++++++++++++++++++++++++++++++ 4 files changed, 147 insertions(+), 34 deletions(-) create mode 100755 scripts/run-attw.js diff --git a/.gitignore b/.gitignore index 34a6b214c6..8d82661335 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,5 @@ Gemfile.lock *.iml zscaler-root-ca.crt* .ts38-validation +# Arethetypeswrong working files +attw-results.json \ No newline at end of file diff --git a/package.json b/package.json index 4d6a190c9b..d04cfbbeb0 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "build": "lerna run build", "clean": "lerna run clean", "build:electron": "lerna run build --scope '@bugsnag/plugin-electron-ipc' --scope '@bugsnag/plugin-electron-app' --scope '@bugsnag/plugin-electron-client-state-persistence'", - "test:attw": "lerna exec --concurrency 1 -- attw --pack . -f json > attw-results.json", + "test:attw": "node scripts/run-attw.js", "test:lint": "eslint --report-unused-disable-directives .", "test:lint-native": "bash scripts/cppcheck.sh", "test:unit:electron-runner": "xvfb-maybe --auto-servernum -- jest -c config/electron-jest.config.js --rootDir .", diff --git a/scripts/parse-attw-results.js b/scripts/parse-attw-results.js index 1aa209494b..3229e8775e 100755 --- a/scripts/parse-attw-results.js +++ b/scripts/parse-attw-results.js @@ -10,50 +10,63 @@ const path = require('path'); function parseResults(filePath) { const content = fs.readFileSync(filePath, 'utf8'); - const lines = content.split('\n'); - const results = []; - let currentJson = ''; - let braceCount = 0; - let inJson = false; - - for (const line of lines) { - // Skip empty lines and lines that don't start a JSON object if we're not already in one - if (!inJson && !line.trim().startsWith('{')) { - continue; + try { + // Try parsing as a JSON array first (new format) + const parsed = JSON.parse(content); + if (Array.isArray(parsed)) { + return parsed; } + // If it's a single object, wrap it in an array + return [parsed]; + } catch (error) { + // Fall back to parsing multiple JSON objects separated by newlines (legacy format) + console.warn('āš ļø Using legacy multi-object JSON parsing. Consider running with the new script.'); + const lines = content.split('\n'); + const results = []; - // Start tracking a new JSON object - if (line.trim().startsWith('{') && braceCount === 0) { - inJson = true; - currentJson = ''; - braceCount = 0; - } + let currentJson = ''; + let braceCount = 0; + let inJson = false; - if (inJson) { - currentJson += line + '\n'; + for (const line of lines) { + // Skip empty lines and lines that don't start a JSON object if we're not already in one + if (!inJson && !line.trim().startsWith('{')) { + continue; + } - // Count braces to know when we've completed a JSON object - for (const char of line) { - if (char === '{') braceCount++; - if (char === '}') braceCount--; + // Start tracking a new JSON object + if (line.trim().startsWith('{') && braceCount === 0) { + inJson = true; + currentJson = ''; + braceCount = 0; } - // When braceCount returns to 0, we have a complete JSON object - if (braceCount === 0) { - try { - const parsed = JSON.parse(currentJson); - results.push(parsed); - } catch (err) { - console.error('Error parsing JSON object:', err.message); + if (inJson) { + currentJson += line + '\n'; + + // Count braces to know when we've completed a JSON object + for (const char of line) { + if (char === '{') braceCount++; + if (char === '}') braceCount--; + } + + // When braceCount returns to 0, we have a complete JSON object + if (braceCount === 0) { + try { + const parsed = JSON.parse(currentJson); + results.push(parsed); + } catch (err) { + console.error('Error parsing JSON object:', err.message); + } + inJson = false; + currentJson = ''; } - inJson = false; - currentJson = ''; } } + + return results; } - - return results; } function analyzeResults(results) { diff --git a/scripts/run-attw.js b/scripts/run-attw.js new file mode 100755 index 0000000000..690b607dbe --- /dev/null +++ b/scripts/run-attw.js @@ -0,0 +1,98 @@ +#!/usr/bin/env node + +const { execSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +/** + * Run ATTW checks on all packages and output a properly formatted JSON file + */ + +function getPackages() { + try { + const output = execSync('npx lerna list --all --json', { + encoding: 'utf8', + stdio: ['pipe', 'pipe', 'pipe'] + }); + return JSON.parse(output); + } catch (error) { + console.error('Failed to get package list from lerna:', error.message); + process.exit(1); + } +} + +function runAttwForPackage(packagePath) { + try { + const output = execSync('npx attw --pack . -f json', { + cwd: packagePath, + encoding: 'utf8', + stdio: ['pipe', 'pipe', 'pipe'] + }); + + // Parse the JSON output (handle pretty-printed multi-line JSON) + return JSON.parse(output); + } catch (error) { + // attw may exit with non-zero code if problems are found + // but the output should still be valid JSON + if (error.stdout) { + try { + return JSON.parse(error.stdout); + } catch { + console.error(`Failed to parse ATTW output for ${packagePath}:`, error.message); + return null; + } + } + console.error(`Failed to run ATTW for ${packagePath}:`, error.message); + return null; + } +} + +function main() { + console.log('Getting package list...'); + const packages = getPackages(); + console.log(`Found ${packages.length} packages`); + + const results = []; + let checked = 0; + let skipped = 0; + + for (const pkg of packages) { + // Skip private packages + if (pkg.private) { + console.log(`Skipping private package: ${pkg.name}`); + skipped++; + continue; + } + + console.log(`Checking ${pkg.name}...`); + const result = runAttwForPackage(pkg.location); + + if (result) { + results.push(result); + checked++; + } else { + skipped++; + } + } + + // Write the results as a proper JSON array + const outputPath = path.join(process.cwd(), 'attw-results.json'); + fs.writeFileSync(outputPath, JSON.stringify(results, null, 2), 'utf8'); + + console.log(`\nāœ… ATTW check complete!`); + console.log(` Packages checked: ${checked}`); + console.log(` Packages skipped: ${skipped}`); + console.log(` Results written to: ${outputPath}`); + + // Exit with error code if any package has problems + const packagesWithProblems = results.filter(r => + r.problems && Object.keys(r.problems).length > 0 + ).length; + + if (packagesWithProblems > 0) { + console.log(`\nāš ļø ${packagesWithProblems} package(s) have type issues`); + process.exit(1); + } +} + +main();