diff --git a/.prettierignore b/.prettierignore index e904c05..527ba24 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,3 +2,5 @@ dist node_modules package-lock.json __INTERNAL__ +CLAUDE.md +AGENTS.md diff --git a/README.md b/README.md index c8f7b7f..4a6e430 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@
+Reset Window Crossed: Yes (${crossedBuckets.join(', ')})
`, true ); - } - summary - .addRaw( - `Action Duration: ${ - hasStartTime ? formatMs(duration) : 'Unknown (data missing)' - }
`, + summary.addRaw( + 'Total Usage: Total usage cannot be computed - usage reset window was crossed.
', true - ) - .addRaw(`Total API Calls/Points Used: ${totalUsed}
`, true) - .write(); + ); + summary.addRaw(`Minimum API Calls/Points Used: ${totalUsed}
`, true); + } + summary.addRaw( + `Action Duration: ${ + hasStartTime ? deps.formatMs(duration) : 'Unknown (data missing)' + }
`, + true + ); + if (crossedBuckets.length === 0) { + summary.addRaw(`Total API Calls/Points Used: ${totalUsed}
`, true); + } + summary.write(); } catch (err) { - core.error(`[github-api-usage-tracker] Post step failed: ${err.message}`); + deps.core.error(`[github-api-usage-tracker] Post step failed: ${err.message}`); } } -run(); +if (require.main === require.cache[eval('__filename')]) { + run(); +} + +module.exports = { run, maybeWrite }; + + +/***/ }), + +/***/ 5042: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +const core = __nccwpck_require__(7484); +const https = __nccwpck_require__(5692); +const { log } = __nccwpck_require__(9630); + +function fetchRateLimit() { + const token = core.getInput('token'); + return new Promise((resolve, reject) => { + if (!token) return reject(new Error('No GitHub token provided')); -module.exports = __webpack_exports__; + const req = https.request( + { + hostname: 'api.github.com', + path: '/rate_limit', + headers: { + 'User-Agent': 'github-api-usage-tracker', + Authorization: `Bearer ${token}`, + Accept: 'application/vnd.github+json' + } + }, + (res) => { + let data = ''; + res.on('data', (c) => (data += c)); + res.on('end', () => { + log(`[github-api-usage-tracker] GitHub API response: ${res.statusCode}`); + if (res.statusCode < 200 || res.statusCode >= 300) { + return reject(new Error(`GitHub API returned ${res.statusCode}: ${data}`)); + } + try { + resolve(JSON.parse(data)); + } catch (e) { + reject(e); + } + }); + } + ); + + req.on('error', reject); + req.end(); + }); +} + +module.exports = { fetchRateLimit }; + + +/***/ }) + +/******/ }); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __nccwpck_require__(moduleId) { +/******/ // Check if module is in cache +/******/ var cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ var threw = true; +/******/ try { +/******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __nccwpck_require__); +/******/ threw = false; +/******/ } finally { +/******/ if(threw) delete __webpack_module_cache__[moduleId]; +/******/ } +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/compat */ +/******/ +/******/ if (typeof __nccwpck_require__ !== 'undefined') __nccwpck_require__.ab = __dirname + "/"; +/******/ +/************************************************************************/ +/******/ +/******/ // startup +/******/ // Load entry module and return exports +/******/ // This entry module is referenced by other modules so it can't be inlined +/******/ var __webpack_exports__ = __nccwpck_require__(7656); +/******/ module.exports = __webpack_exports__; +/******/ /******/ })() ; \ No newline at end of file diff --git a/dist/pre/index.js b/dist/pre/index.js index 49d427a..0d0504a 100644 --- a/dist/pre/index.js +++ b/dist/pre/index.js @@ -27831,6 +27831,56 @@ function parseBuckets(raw) { module.exports = { log, parseBuckets, VALID_BUCKETS }; +/***/ }), + +/***/ 7077: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +const core = __nccwpck_require__(7484); +const { fetchRateLimit } = __nccwpck_require__(5042); +const { log } = __nccwpck_require__(9630); + +async function run(overrides = {}) { + const deps = { + core, + fetchRateLimit, + log, + ...overrides + }; + try { + const token = deps.core.getInput('token'); + + if (!token) { + deps.core.error('GitHub token is required for API Usage Tracker'); + deps.core.saveState('skip_post', 'true'); + return; + } + + const startTime = Date.now(); + deps.core.saveState('start_time', String(startTime)); + + deps.log('[github-api-usage-tracker] Fetching initial rate limits...'); + + const limits = await deps.fetchRateLimit(); + const resources = limits.resources || {}; + + deps.log('[github-api-usage-tracker] Initial Snapshot:'); + deps.log('[github-api-usage-tracker] -----------------'); + deps.log(`[github-api-usage-tracker] ${JSON.stringify(resources, null, 2)}`); + + deps.core.saveState('starting_rate_limits', JSON.stringify(resources)); + } catch (err) { + deps.core.warning(`Pre step failed: ${err.message}`); + } +} + +if (require.main === require.cache[eval('__filename')]) { + run(); +} + +module.exports = { run }; + + /***/ }), /***/ 5042: @@ -27920,41 +27970,12 @@ module.exports = { fetchRateLimit }; /******/ if (typeof __nccwpck_require__ !== 'undefined') __nccwpck_require__.ab = __dirname + "/"; /******/ /************************************************************************/ -var __webpack_exports__ = {}; -const core = __nccwpck_require__(7484); -const { fetchRateLimit } = __nccwpck_require__(5042); -const { log } = __nccwpck_require__(9630); - -async function run() { - try { - const token = core.getInput('token'); - - if (!token) { - core.error('GitHub token is required for API Usage Tracker'); - core.saveState('skip_post', 'true'); - return; - } - - const startTime = Date.now(); - core.saveState('start_time', String(startTime)); - - log('[github-api-usage-tracker] Fetching initial rate limits...'); - - const limits = await fetchRateLimit(); - const resources = limits.resources || {}; - - log('[github-api-usage-tracker] Initial Snapshot:'); - log('[github-api-usage-tracker] -----------------'); - log(`[github-api-usage-tracker] ${JSON.stringify(resources, null, 2)}`); - - core.saveState('starting_rate_limits', JSON.stringify(resources)); - } catch (err) { - core.warning(`Pre step failed: ${err.message}`); - } -} - -run(); - -module.exports = __webpack_exports__; +/******/ +/******/ // startup +/******/ // Load entry module and return exports +/******/ // This entry module is referenced by other modules so it can't be inlined +/******/ var __webpack_exports__ = __nccwpck_require__(7077); +/******/ module.exports = __webpack_exports__; +/******/ /******/ })() ; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 68a8bce..046abb0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,13 +12,13 @@ "devDependencies": { "@eslint/js": "9.39.2", "@vercel/ncc": "0.38.4", - "@vitest/coverage-v8": "4.0.17", + "@vitest/coverage-v8": "4.0.18", "eslint": "9.39.2", - "globals": "17.0.0", + "globals": "17.1.0", "husky": "9.1.7", "prettier": "3.8.1", "rimraf": "6.1.2", - "vitest": "4.0.17" + "vitest": "4.0.18" }, "engines": { "node": ">=20" @@ -831,9 +831,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.55.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.2.tgz", - "integrity": "sha512-21J6xzayjy3O6NdnlO6aXi/urvSRjm6nCI6+nF6ra2YofKruGixN9kfT+dt55HVNwfDmpDHJcaS3JuP/boNnlA==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.56.0.tgz", + "integrity": "sha512-LNKIPA5k8PF1+jAFomGe3qN3bbIgJe/IlpDBwuVjrDKrJhVWywgnJvflMt/zkbVNLFtF1+94SljYQS6e99klnw==", "cpu": [ "arm" ], @@ -845,9 +845,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.55.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.2.tgz", - "integrity": "sha512-eXBg7ibkNUZ+sTwbFiDKou0BAckeV6kIigK7y5Ko4mB/5A1KLhuzEKovsmfvsL8mQorkoincMFGnQuIT92SKqA==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.56.0.tgz", + "integrity": "sha512-lfbVUbelYqXlYiU/HApNMJzT1E87UPGvzveGg2h0ktUNlOCxKlWuJ9jtfvs1sKHdwU4fzY7Pl8sAl49/XaEk6Q==", "cpu": [ "arm64" ], @@ -859,9 +859,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.55.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.2.tgz", - "integrity": "sha512-UCbaTklREjrc5U47ypLulAgg4njaqfOVLU18VrCrI+6E5MQjuG0lSWaqLlAJwsD7NpFV249XgB0Bi37Zh5Sz4g==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.56.0.tgz", + "integrity": "sha512-EgxD1ocWfhoD6xSOeEEwyE7tDvwTgZc8Bss7wCWe+uc7wO8G34HHCUH+Q6cHqJubxIAnQzAsyUsClt0yFLu06w==", "cpu": [ "arm64" ], @@ -873,9 +873,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.55.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.2.tgz", - "integrity": "sha512-dP67MA0cCMHFT2g5XyjtpVOtp7y4UyUxN3dhLdt11at5cPKnSm4lY+EhwNvDXIMzAMIo2KU+mc9wxaAQJTn7sQ==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.56.0.tgz", + "integrity": "sha512-1vXe1vcMOssb/hOF8iv52A7feWW2xnu+c8BV4t1F//m9QVLTfNVpEdja5ia762j/UEJe2Z1jAmEqZAK42tVW3g==", "cpu": [ "x64" ], @@ -887,9 +887,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.55.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.2.tgz", - "integrity": "sha512-WDUPLUwfYV9G1yxNRJdXcvISW15mpvod1Wv3ok+Ws93w1HjIVmCIFxsG2DquO+3usMNCpJQ0wqO+3GhFdl6Fow==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.56.0.tgz", + "integrity": "sha512-bof7fbIlvqsyv/DtaXSck4VYQ9lPtoWNFCB/JY4snlFuJREXfZnm+Ej6yaCHfQvofJDXLDMTVxWscVSuQvVWUQ==", "cpu": [ "arm64" ], @@ -901,9 +901,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.55.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.2.tgz", - "integrity": "sha512-Ng95wtHVEulRwn7R0tMrlUuiLVL/HXA8Lt/MYVpy88+s5ikpntzZba1qEulTuPnPIZuOPcW9wNEiqvZxZmgmqQ==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.56.0.tgz", + "integrity": "sha512-KNa6lYHloW+7lTEkYGa37fpvPq+NKG/EHKM8+G/g9WDU7ls4sMqbVRV78J6LdNuVaeeK5WB9/9VAFbKxcbXKYg==", "cpu": [ "x64" ], @@ -915,9 +915,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.55.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.2.tgz", - "integrity": "sha512-AEXMESUDWWGqD6LwO/HkqCZgUE1VCJ1OhbvYGsfqX2Y6w5quSXuyoy/Fg3nRqiwro+cJYFxiw5v4kB2ZDLhxrw==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.56.0.tgz", + "integrity": "sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A==", "cpu": [ "arm" ], @@ -929,9 +929,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.55.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.2.tgz", - "integrity": "sha512-ZV7EljjBDwBBBSv570VWj0hiNTdHt9uGznDtznBB4Caj3ch5rgD4I2K1GQrtbvJ/QiB+663lLgOdcADMNVC29Q==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.56.0.tgz", + "integrity": "sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw==", "cpu": [ "arm" ], @@ -943,9 +943,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.55.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.2.tgz", - "integrity": "sha512-uvjwc8NtQVPAJtq4Tt7Q49FOodjfbf6NpqXyW/rjXoV+iZ3EJAHLNAnKT5UJBc6ffQVgmXTUL2ifYiLABlGFqA==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.56.0.tgz", + "integrity": "sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ==", "cpu": [ "arm64" ], @@ -957,9 +957,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.55.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.2.tgz", - "integrity": "sha512-s3KoWVNnye9mm/2WpOZ3JeUiediUVw6AvY/H7jNA6qgKA2V2aM25lMkVarTDfiicn/DLq3O0a81jncXszoyCFA==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.56.0.tgz", + "integrity": "sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA==", "cpu": [ "arm64" ], @@ -971,9 +971,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.55.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.2.tgz", - "integrity": "sha512-gi21faacK+J8aVSyAUptML9VQN26JRxe484IbF+h3hpG+sNVoMXPduhREz2CcYr5my0NE3MjVvQ5bMKX71pfVA==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.56.0.tgz", + "integrity": "sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg==", "cpu": [ "loong64" ], @@ -985,9 +985,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.55.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.2.tgz", - "integrity": "sha512-qSlWiXnVaS/ceqXNfnoFZh4IiCA0EwvCivivTGbEu1qv2o+WTHpn1zNmCTAoOG5QaVr2/yhCoLScQtc/7RxshA==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.56.0.tgz", + "integrity": "sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA==", "cpu": [ "loong64" ], @@ -999,9 +999,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.55.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.2.tgz", - "integrity": "sha512-rPyuLFNoF1B0+wolH277E780NUKf+KoEDb3OyoLbAO18BbeKi++YN6gC/zuJoPPDlQRL3fIxHxCxVEWiem2yXw==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.56.0.tgz", + "integrity": "sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw==", "cpu": [ "ppc64" ], @@ -1013,9 +1013,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.55.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.2.tgz", - "integrity": "sha512-g+0ZLMook31iWV4PvqKU0i9E78gaZgYpSrYPed/4Bu+nGTgfOPtfs1h11tSSRPXSjC5EzLTjV/1A7L2Vr8pJoQ==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.56.0.tgz", + "integrity": "sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg==", "cpu": [ "ppc64" ], @@ -1027,9 +1027,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.55.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.2.tgz", - "integrity": "sha512-i+sGeRGsjKZcQRh3BRfpLsM3LX3bi4AoEVqmGDyc50L6KfYsN45wVCSz70iQMwPWr3E5opSiLOwsC9WB4/1pqg==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.56.0.tgz", + "integrity": "sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew==", "cpu": [ "riscv64" ], @@ -1041,9 +1041,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.55.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.2.tgz", - "integrity": "sha512-C1vLcKc4MfFV6I0aWsC7B2Y9QcsiEcvKkfxprwkPfLaN8hQf0/fKHwSF2lcYzA9g4imqnhic729VB9Fo70HO3Q==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.56.0.tgz", + "integrity": "sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ==", "cpu": [ "riscv64" ], @@ -1055,9 +1055,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.55.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.2.tgz", - "integrity": "sha512-68gHUK/howpQjh7g7hlD9DvTTt4sNLp1Bb+Yzw2Ki0xvscm2cOdCLZNJNhd2jW8lsTPrHAHuF751BygifW4bkQ==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.56.0.tgz", + "integrity": "sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ==", "cpu": [ "s390x" ], @@ -1069,9 +1069,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.55.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.2.tgz", - "integrity": "sha512-1e30XAuaBP1MAizaOBApsgeGZge2/Byd6wV4a8oa6jPdHELbRHBiw7wvo4dp7Ie2PE8TZT4pj9RLGZv9N4qwlw==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.56.0.tgz", + "integrity": "sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw==", "cpu": [ "x64" ], @@ -1083,9 +1083,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.55.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.2.tgz", - "integrity": "sha512-4BJucJBGbuGnH6q7kpPqGJGzZnYrpAzRd60HQSt3OpX/6/YVgSsJnNzR8Ot74io50SeVT4CtCWe/RYIAymFPwA==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.56.0.tgz", + "integrity": "sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA==", "cpu": [ "x64" ], @@ -1097,9 +1097,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.55.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.2.tgz", - "integrity": "sha512-cT2MmXySMo58ENv8p6/O6wI/h/gLnD3D6JoajwXFZH6X9jz4hARqUhWpGuQhOgLNXscfZYRQMJvZDtWNzMAIDw==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.56.0.tgz", + "integrity": "sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA==", "cpu": [ "x64" ], @@ -1111,9 +1111,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.55.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.2.tgz", - "integrity": "sha512-sZnyUgGkuzIXaK3jNMPmUIyJrxu/PjmATQrocpGA1WbCPX8H5tfGgRSuYtqBYAvLuIGp8SPRb1O4d1Fkb5fXaQ==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.56.0.tgz", + "integrity": "sha512-LhN/Reh+7F3RCgQIRbgw8ZMwUwyqJM+8pXNT6IIJAqm2IdKkzpCh/V9EdgOMBKuebIrzswqy4ATlrDgiOwbRcQ==", "cpu": [ "arm64" ], @@ -1125,9 +1125,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.55.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.2.tgz", - "integrity": "sha512-sDpFbenhmWjNcEbBcoTV0PWvW5rPJFvu+P7XoTY0YLGRupgLbFY0XPfwIbJOObzO7QgkRDANh65RjhPmgSaAjQ==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.56.0.tgz", + "integrity": "sha512-kbFsOObXp3LBULg1d3JIUQMa9Kv4UitDmpS+k0tinPBz3watcUiV2/LUDMMucA6pZO3WGE27P7DsfaN54l9ing==", "cpu": [ "arm64" ], @@ -1139,9 +1139,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.55.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.2.tgz", - "integrity": "sha512-GvJ03TqqaweWCigtKQVBErw2bEhu1tyfNQbarwr94wCGnczA9HF8wqEe3U/Lfu6EdeNP0p6R+APeHVwEqVxpUQ==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.56.0.tgz", + "integrity": "sha512-vSSgny54D6P4vf2izbtFm/TcWYedw7f8eBrOiGGecyHyQB9q4Kqentjaj8hToe+995nob/Wv48pDqL5a62EWtg==", "cpu": [ "ia32" ], @@ -1153,9 +1153,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.55.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.2.tgz", - "integrity": "sha512-KvXsBvp13oZz9JGe5NYS7FNizLe99Ny+W8ETsuCyjXiKdiGrcz2/J/N8qxZ/RSwivqjQguug07NLHqrIHrqfYw==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.56.0.tgz", + "integrity": "sha512-FeCnkPCTHQJFbiGG49KjV5YGW/8b9rrXAM2Mz2kiIoktq2qsJxRD5giEMEOD2lPdgs72upzefaUvS+nc8E3UzQ==", "cpu": [ "x64" ], @@ -1167,9 +1167,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.55.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.2.tgz", - "integrity": "sha512-xNO+fksQhsAckRtDSPWaMeT1uIM+JrDRXlerpnWNXhn1TdB3YZ6uKBMBTKP0eX9XtYEP978hHk1f8332i2AW8Q==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.56.0.tgz", + "integrity": "sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g==", "cpu": [ "x64" ], @@ -1230,14 +1230,14 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.17.tgz", - "integrity": "sha512-/6zU2FLGg0jsd+ePZcwHRy3+WpNTBBhDY56P4JTRqUN/Dp6CvOEa9HrikcQ4KfV2b2kAHUFB4dl1SuocWXSFEw==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.18.tgz", + "integrity": "sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.0.17", + "@vitest/utils": "4.0.18", "ast-v8-to-istanbul": "^0.3.10", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", @@ -1251,8 +1251,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.0.17", - "vitest": "4.0.17" + "@vitest/browser": "4.0.18", + "vitest": "4.0.18" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -1260,44 +1260,17 @@ } } }, - "node_modules/@vitest/coverage-v8/node_modules/@vitest/pretty-format": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.17.tgz", - "integrity": "sha512-Ah3VAYmjcEdHg6+MwFE17qyLqBHZ+ni2ScKCiW2XrlSBV4H3Z7vYfPfz7CWQ33gyu76oc0Ai36+kgLU3rfF4nw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/coverage-v8/node_modules/@vitest/utils": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.17.tgz", - "integrity": "sha512-RG6iy+IzQpa9SB8HAFHJ9Y+pTzI+h8553MrciN9eC6TFBErqrQaTas4vG+MVj8S4uKk8uTT2p0vgZPnTdxd96w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.0.17", - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, "node_modules/@vitest/expect": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.17.tgz", - "integrity": "sha512-mEoqP3RqhKlbmUmntNDDCJeTDavDR+fVYkSOw8qRwJFaW/0/5zA9zFeTrHqNtcmwh6j26yMmwx2PqUDPzt5ZAQ==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", + "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.0.17", - "@vitest/utils": "4.0.17", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" }, @@ -1305,41 +1278,14 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/expect/node_modules/@vitest/pretty-format": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.17.tgz", - "integrity": "sha512-Ah3VAYmjcEdHg6+MwFE17qyLqBHZ+ni2ScKCiW2XrlSBV4H3Z7vYfPfz7CWQ33gyu76oc0Ai36+kgLU3rfF4nw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/expect/node_modules/@vitest/utils": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.17.tgz", - "integrity": "sha512-RG6iy+IzQpa9SB8HAFHJ9Y+pTzI+h8553MrciN9eC6TFBErqrQaTas4vG+MVj8S4uKk8uTT2p0vgZPnTdxd96w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.0.17", - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, "node_modules/@vitest/mocker": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.17.tgz", - "integrity": "sha512-+ZtQhLA3lDh1tI2wxe3yMsGzbp7uuJSWBM1iTIKCbppWTSBN09PUC+L+fyNlQApQoR+Ps8twt2pbSSXg2fQVEQ==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", + "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.0.17", + "@vitest/spy": "4.0.18", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -1359,24 +1305,10 @@ } } }, - "node_modules/@vitest/runner": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.17.tgz", - "integrity": "sha512-JmuQyf8aMWoo/LmNFppdpkfRVHJcsgzkbCA+/Bk7VfNH7RE6Ut2qxegeyx2j3ojtJtKIbIGy3h+KxGfYfk28YQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.0.17", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner/node_modules/@vitest/pretty-format": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.17.tgz", - "integrity": "sha512-Ah3VAYmjcEdHg6+MwFE17qyLqBHZ+ni2ScKCiW2XrlSBV4H3Z7vYfPfz7CWQ33gyu76oc0Ai36+kgLU3rfF4nw==", + "node_modules/@vitest/pretty-format": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", + "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", "dev": true, "license": "MIT", "dependencies": { @@ -1386,28 +1318,28 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/runner/node_modules/@vitest/utils": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.17.tgz", - "integrity": "sha512-RG6iy+IzQpa9SB8HAFHJ9Y+pTzI+h8553MrciN9eC6TFBErqrQaTas4vG+MVj8S4uKk8uTT2p0vgZPnTdxd96w==", + "node_modules/@vitest/runner": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", + "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.17", - "tinyrainbow": "^3.0.3" + "@vitest/utils": "4.0.18", + "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.17.tgz", - "integrity": "sha512-npPelD7oyL+YQM2gbIYvlavlMVWUfNNGZPcu0aEUQXt7FXTuqhmgiYupPnAanhKvyP6Srs2pIbWo30K0RbDtRQ==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", + "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.17", + "@vitest/pretty-format": "4.0.18", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -1415,25 +1347,26 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/snapshot/node_modules/@vitest/pretty-format": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.17.tgz", - "integrity": "sha512-Ah3VAYmjcEdHg6+MwFE17qyLqBHZ+ni2ScKCiW2XrlSBV4H3Z7vYfPfz7CWQ33gyu76oc0Ai36+kgLU3rfF4nw==", + "node_modules/@vitest/spy": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", + "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", "dev": true, "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.0.3" - }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/spy": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.17.tgz", - "integrity": "sha512-I1bQo8QaP6tZlTomQNWKJE6ym4SHf3oLS7ceNjozxxgzavRAgZDc06T7kD8gb9bXKEgcLNt00Z+kZO6KaJ62Ew==", + "node_modules/@vitest/utils": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", + "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", "dev": true, "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "tinyrainbow": "^3.0.3" + }, "funding": { "url": "https://opencollective.com/vitest" } @@ -1902,6 +1835,24 @@ "dev": true, "license": "MIT" }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -2016,9 +1967,9 @@ } }, "node_modules/globals": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.0.0.tgz", - "integrity": "sha512-gv5BeD2EssA793rlFWVPMMCqefTlpusw6/2TbAVMy0FzcG8wKJn4O+NqJ4+XWmmwrayJgw5TzrmWjFgmz1XPqw==", + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.1.0.tgz", + "integrity": "sha512-8HoIcWI5fCvG5NADj4bDav+er9B9JMj2vyL2pI8D0eismKyUvPLTSs+Ln3wqhwcp306i73iyVnEKx3F6T47TGw==", "dev": true, "license": "MIT", "engines": { @@ -2491,6 +2442,19 @@ "dev": true, "license": "ISC" }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -2587,9 +2551,9 @@ } }, "node_modules/rollup": { - "version": "4.55.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.2.tgz", - "integrity": "sha512-PggGy4dhwx5qaW+CKBilA/98Ql9keyfnb7lh4SR6shQ91QQQi1ORJ1v4UinkdP2i87OBs9AQFooQylcrrRfIcg==", + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.56.0.tgz", + "integrity": "sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg==", "dev": true, "license": "MIT", "dependencies": { @@ -2603,31 +2567,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.55.2", - "@rollup/rollup-android-arm64": "4.55.2", - "@rollup/rollup-darwin-arm64": "4.55.2", - "@rollup/rollup-darwin-x64": "4.55.2", - "@rollup/rollup-freebsd-arm64": "4.55.2", - "@rollup/rollup-freebsd-x64": "4.55.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.55.2", - "@rollup/rollup-linux-arm-musleabihf": "4.55.2", - "@rollup/rollup-linux-arm64-gnu": "4.55.2", - "@rollup/rollup-linux-arm64-musl": "4.55.2", - "@rollup/rollup-linux-loong64-gnu": "4.55.2", - "@rollup/rollup-linux-loong64-musl": "4.55.2", - "@rollup/rollup-linux-ppc64-gnu": "4.55.2", - "@rollup/rollup-linux-ppc64-musl": "4.55.2", - "@rollup/rollup-linux-riscv64-gnu": "4.55.2", - "@rollup/rollup-linux-riscv64-musl": "4.55.2", - "@rollup/rollup-linux-s390x-gnu": "4.55.2", - "@rollup/rollup-linux-x64-gnu": "4.55.2", - "@rollup/rollup-linux-x64-musl": "4.55.2", - "@rollup/rollup-openbsd-x64": "4.55.2", - "@rollup/rollup-openharmony-arm64": "4.55.2", - "@rollup/rollup-win32-arm64-msvc": "4.55.2", - "@rollup/rollup-win32-ia32-msvc": "4.55.2", - "@rollup/rollup-win32-x64-gnu": "4.55.2", - "@rollup/rollup-win32-x64-msvc": "4.55.2", + "@rollup/rollup-android-arm-eabi": "4.56.0", + "@rollup/rollup-android-arm64": "4.56.0", + "@rollup/rollup-darwin-arm64": "4.56.0", + "@rollup/rollup-darwin-x64": "4.56.0", + "@rollup/rollup-freebsd-arm64": "4.56.0", + "@rollup/rollup-freebsd-x64": "4.56.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.56.0", + "@rollup/rollup-linux-arm-musleabihf": "4.56.0", + "@rollup/rollup-linux-arm64-gnu": "4.56.0", + "@rollup/rollup-linux-arm64-musl": "4.56.0", + "@rollup/rollup-linux-loong64-gnu": "4.56.0", + "@rollup/rollup-linux-loong64-musl": "4.56.0", + "@rollup/rollup-linux-ppc64-gnu": "4.56.0", + "@rollup/rollup-linux-ppc64-musl": "4.56.0", + "@rollup/rollup-linux-riscv64-gnu": "4.56.0", + "@rollup/rollup-linux-riscv64-musl": "4.56.0", + "@rollup/rollup-linux-s390x-gnu": "4.56.0", + "@rollup/rollup-linux-x64-gnu": "4.56.0", + "@rollup/rollup-linux-x64-musl": "4.56.0", + "@rollup/rollup-openbsd-x64": "4.56.0", + "@rollup/rollup-openharmony-arm64": "4.56.0", + "@rollup/rollup-win32-arm64-msvc": "4.56.0", + "@rollup/rollup-win32-ia32-msvc": "4.56.0", + "@rollup/rollup-win32-x64-gnu": "4.56.0", + "@rollup/rollup-win32-x64-msvc": "4.56.0", "fsevents": "~2.3.2" } }, @@ -2758,37 +2722,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/tinyrainbow": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", @@ -2918,51 +2851,20 @@ } } }, - "node_modules/vite/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/vitest": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.17.tgz", - "integrity": "sha512-FQMeF0DJdWY0iOnbv466n/0BudNdKj1l5jYgl5JVTwjSsZSlqyXFt/9+1sEyhR6CLowbZpV7O1sCHrzBhucKKg==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", + "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.0.17", - "@vitest/mocker": "4.0.17", - "@vitest/pretty-format": "4.0.17", - "@vitest/runner": "4.0.17", - "@vitest/snapshot": "4.0.17", - "@vitest/spy": "4.0.17", - "@vitest/utils": "4.0.17", + "@vitest/expect": "4.0.18", + "@vitest/mocker": "4.0.18", + "@vitest/pretty-format": "4.0.18", + "@vitest/runner": "4.0.18", + "@vitest/snapshot": "4.0.18", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", @@ -2990,10 +2892,10 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.0.17", - "@vitest/browser-preview": "4.0.17", - "@vitest/browser-webdriverio": "4.0.17", - "@vitest/ui": "4.0.17", + "@vitest/browser-playwright": "4.0.18", + "@vitest/browser-preview": "4.0.18", + "@vitest/browser-webdriverio": "4.0.18", + "@vitest/ui": "4.0.18", "happy-dom": "*", "jsdom": "*" }, @@ -3027,46 +2929,6 @@ } } }, - "node_modules/vitest/node_modules/@vitest/pretty-format": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.17.tgz", - "integrity": "sha512-Ah3VAYmjcEdHg6+MwFE17qyLqBHZ+ni2ScKCiW2XrlSBV4H3Z7vYfPfz7CWQ33gyu76oc0Ai36+kgLU3rfF4nw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/vitest/node_modules/@vitest/utils": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.17.tgz", - "integrity": "sha512-RG6iy+IzQpa9SB8HAFHJ9Y+pTzI+h8553MrciN9eC6TFBErqrQaTas4vG+MVj8S4uKk8uTT2p0vgZPnTdxd96w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.0.17", - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/vitest/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -3110,24 +2972,6 @@ "node": ">=0.10.0" } }, - "node_modules/yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 6c7e198..57a5295 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "repository": "https://github.com/hesreallyhim/github-api-usage-tracker", "type": "commonjs", "scripts": { - "bundle": "rimraf dist && ncc build src/pre.js -o dist/pre && ncc build src/post.js -o dist/post && ncc build src/noop.js -o dist/noop", + "bundle": "rimraf dist && ncc build src/pre.js -o dist/pre && ncc build src/post.js -o dist/post && ncc build src/checkpoint.js -o dist/checkpoint", "test:ci": "npm run lint && npm run format && npm test", "lint": "eslint .", "format": "prettier --check .", @@ -21,13 +21,13 @@ "devDependencies": { "@eslint/js": "9.39.2", "@vercel/ncc": "0.38.4", - "@vitest/coverage-v8": "4.0.17", + "@vitest/coverage-v8": "4.0.18", "eslint": "9.39.2", - "globals": "17.0.0", + "globals": "17.1.0", "husky": "9.1.7", "prettier": "3.8.1", "rimraf": "6.1.2", - "vitest": "4.0.17" + "vitest": "4.0.18" }, "engines": { "node": ">=20" diff --git a/src/checkpoint.js b/src/checkpoint.js new file mode 100644 index 0000000..0dc9f6f --- /dev/null +++ b/src/checkpoint.js @@ -0,0 +1,38 @@ +const core = require('@actions/core'); +const { fetchRateLimit } = require('./rate-limit'); +const { log } = require('./log'); + +async function run(overrides = {}) { + const deps = { + core, + fetchRateLimit, + log, + ...overrides + }; + try { + const token = deps.core.getInput('token'); + if (!token) { + deps.log('[github-api-usage-tracker] Skipping checkpoint snapshot due to missing token'); + return; + } + + deps.log('[github-api-usage-tracker] Fetching checkpoint rate limits...'); + const limits = await deps.fetchRateLimit(); + const resources = limits.resources || {}; + + deps.log('[github-api-usage-tracker] Checkpoint Snapshot:'); + deps.log('[github-api-usage-tracker] ---------------------'); + deps.log(`[github-api-usage-tracker] ${JSON.stringify(resources, null, 2)}`); + + deps.core.saveState('checkpoint_time', String(Date.now())); + deps.core.saveState('checkpoint_rate_limits', JSON.stringify(resources)); + } catch (err) { + deps.core.warning(`[github-api-usage-tracker] Main step snapshot failed: ${err.message}`); + } +} + +if (require.main === module) { + run(); +} + +module.exports = { run }; diff --git a/src/noop.js b/src/noop.js deleted file mode 100644 index 084db52..0000000 --- a/src/noop.js +++ /dev/null @@ -1 +0,0 @@ -// intentionally empty (noop main required by action metadata) diff --git a/src/post-utils.js b/src/post-utils.js index 82dd859..a42b01f 100644 --- a/src/post-utils.js +++ b/src/post-utils.js @@ -16,19 +16,29 @@ function formatMs(ms) { return `${hours}h ${mins}m ${secs}s`; } -function makeSummaryTable(resources) { +function makeSummaryTable(resources, options = {}) { + const useMinimumHeader = Boolean(options.useMinimumHeader); const summaryTable = [ [ { data: 'Bucket', header: true }, - { data: 'Used', header: true }, - { data: 'Remaining', header: true } + { data: 'Used (Start)', header: true }, + { data: 'Remaining (Start)', header: true }, + { data: 'Used (End)', header: true }, + { data: 'Remaining (End)', header: true }, + { data: useMinimumHeader ? 'Used (Minimum)' : 'Used (Total)', header: true } ] ]; + const formatValue = (value) => (Number.isFinite(value) ? String(value) : 'n/a'); for (const [bucket, info] of Object.entries(resources)) { + const used = info.used || {}; + const remaining = info.remaining || {}; summaryTable.push([ { data: bucket }, - { data: String(info.used) }, - { data: String(info.remaining) } + { data: formatValue(used.start) }, + { data: formatValue(remaining.start) }, + { data: formatValue(used.end) }, + { data: formatValue(remaining.end) }, + { data: formatValue(used.total) } ]); } @@ -37,13 +47,22 @@ function makeSummaryTable(resources) { /** * Computes usage stats for a single bucket using pre/post snapshots. + * An optional checkpoint snapshot can tighten the minimum when a reset is crossed. * * @param {object} startingBucket - bucket from the pre snapshot. * @param {object} endingBucket - bucket from the post snapshot. * @param {number} endTimeSeconds - post snapshot time in seconds. + * @param {object} [checkpointBucket] - bucket from the checkpoint snapshot. + * @param {number} [checkpointTimeSeconds] - checkpoint snapshot time in seconds. * @returns {object} usage details and validation status. */ -function computeBucketUsage(startingBucket, endingBucket, endTimeSeconds) { +function computeBucketUsage( + startingBucket, + endingBucket, + endTimeSeconds, + checkpointBucket, + checkpointTimeSeconds +) { const result = { valid: false, used: 0, @@ -79,7 +98,22 @@ function computeBucketUsage(startingBucket, endingBucket, endTimeSeconds) { if (startingLimit !== endingLimit) { result.warnings.push('limit_changed_across_reset'); } - used = startingLimit - startingRemaining + (endingLimit - endingRemaining); + used = endingLimit - endingRemaining; + + if ( + checkpointBucket && + Number.isFinite(checkpointTimeSeconds) && + Number.isFinite(resetPre) && + checkpointTimeSeconds < resetPre + ) { + const checkpointRemaining = Number(checkpointBucket.remaining); + if (Number.isFinite(checkpointRemaining)) { + const checkpointUsed = startingRemaining - checkpointRemaining; + if (checkpointUsed > 0) { + used += checkpointUsed; + } + } + } } else { if ( Number.isFinite(startingLimit) && diff --git a/src/post.js b/src/post.js index 39d8b0e..c84e387 100644 --- a/src/post.js +++ b/src/post.js @@ -30,30 +30,44 @@ const { formatMs, makeSummaryTable, computeBucketUsage } = require('./post-utils * * @param {string} pathname - file path to write to. * @param {object} data - data to write. + * @param {object} fsModule - fs implementation to use. + * @param {object} pathModule - path implementation to use. */ -function maybeWrite(pathname, data) { +function maybeWrite(pathname, data, fsModule, pathModule) { if (!pathname) return; - const dir = path.dirname(pathname); - if (dir && dir !== '.') fs.mkdirSync(dir, { recursive: true }); - fs.writeFileSync(pathname, JSON.stringify(data, null, 2)); + const dir = pathModule.dirname(pathname); + if (dir && dir !== '.') fsModule.mkdirSync(dir, { recursive: true }); + fsModule.writeFileSync(pathname, JSON.stringify(data, null, 2)); } -async function run() { - if (core.getState('skip_post') === 'true') { - log('[github-api-usage-tracker] Skipping post step due to missing token'); +async function run(overrides = {}) { + const deps = { + core, + fs, + path, + fetchRateLimit, + log, + parseBuckets, + formatMs, + makeSummaryTable, + computeBucketUsage, + ...overrides + }; + if (deps.core.getState('skip_post') === 'true') { + deps.log('[github-api-usage-tracker] Skipping post step due to missing token'); return; } try { - const buckets = parseBuckets(core.getInput('buckets')); + const buckets = deps.parseBuckets(deps.core.getInput('buckets')); if (buckets.length === 0) { - log('[github-api-usage-tracker] No valid buckets specified for tracking'); + deps.log('[github-api-usage-tracker] No valid buckets specified for tracking'); return; } - const startingState = core.getState('starting_rate_limits'); + const startingState = deps.core.getState('starting_rate_limits'); if (!startingState) { - core.error( + deps.core.error( '[github-api-usage-tracker] No starting rate limit data found; skipping post step' ); return; @@ -62,81 +76,108 @@ async function run() { try { startingResources = JSON.parse(startingState); } catch { - core.error( + deps.core.error( '[github-api-usage-tracker] Failed to parse starting rate limit data; skipping post step' ); return; } - const startTime = Number(core.getState('start_time')); + const startTime = Number(deps.core.getState('start_time')); const hasStartTime = Number.isFinite(startTime); if (!hasStartTime) { - core.error( + deps.core.error( '[github-api-usage-tracker] Invalid or missing start time; duration will be reported as unknown' ); } + const checkpointState = deps.core.getState('checkpoint_rate_limits'); + let checkpointResources; + let checkpointTimeSeconds = null; + if (checkpointState) { + try { + checkpointResources = JSON.parse(checkpointState); + } catch { + deps.core.warning( + '[github-api-usage-tracker] Failed to parse checkpoint rate limit data; ignoring checkpoint snapshot' + ); + } + } + if (checkpointResources) { + const checkpointTimeMs = Number(deps.core.getState('checkpoint_time')); + checkpointTimeSeconds = + Number.isFinite(checkpointTimeMs) && checkpointTimeMs > 0 + ? Math.floor(checkpointTimeMs / 1000) + : null; + } const endTime = Date.now(); const endTimeSeconds = Math.floor(endTime / 1000); const duration = hasStartTime ? endTime - startTime : null; - log('[github-api-usage-tracker] Fetching final rate limits...'); + deps.log('[github-api-usage-tracker] Fetching final rate limits...'); - const endingLimits = await fetchRateLimit(); + const endingLimits = await deps.fetchRateLimit(); const endingResources = endingLimits.resources || {}; - log('[github-api-usage-tracker] Final Snapshot:'); - log('[github-api-usage-tracker] -----------------'); - log(`[github-api-usage-tracker] ${JSON.stringify(endingResources, null, 2)}`); + deps.log('[github-api-usage-tracker] Final Snapshot:'); + deps.log('[github-api-usage-tracker] -----------------'); + deps.log(`[github-api-usage-tracker] ${JSON.stringify(endingResources, null, 2)}`); const data = {}; const crossedBuckets = []; let totalUsed = 0; + let totalIsMinimum = false; for (const bucket of buckets) { const startingBucket = startingResources[bucket]; const endingBucket = endingResources[bucket]; if (!startingBucket) { - core.warning( + deps.core.warning( `[github-api-usage-tracker] Starting rate limit bucket "${bucket}" not found; skipping` ); continue; } if (!endingBucket) { - core.warning( + deps.core.warning( `[github-api-usage-tracker] Ending rate limit bucket "${bucket}" not found; skipping` ); continue; } - const usage = computeBucketUsage(startingBucket, endingBucket, endTimeSeconds); + const checkpointBucket = checkpointResources ? checkpointResources[bucket] : undefined; + const usage = deps.computeBucketUsage( + startingBucket, + endingBucket, + endTimeSeconds, + checkpointBucket, + checkpointTimeSeconds + ); if (!usage.valid) { switch (usage.reason) { case 'invalid_remaining': - core.warning( + deps.core.warning( `[github-api-usage-tracker] Invalid remaining count for bucket "${bucket}"; skipping` ); break; case 'invalid_limit': - core.warning( + deps.core.warning( `[github-api-usage-tracker] Invalid limit for bucket "${bucket}" during reset crossing; skipping` ); break; case 'limit_changed_without_reset': - core.warning( + deps.core.warning( `[github-api-usage-tracker] Limit changed without reset for bucket "${bucket}"; skipping` ); break; case 'remaining_increased_without_reset': - core.warning( + deps.core.warning( `[github-api-usage-tracker] Remaining increased without reset for bucket "${bucket}"; skipping` ); break; case 'negative_usage': - core.warning( + deps.core.warning( `[github-api-usage-tracker] Negative usage for bucket "${bucket}" detected; skipping` ); break; default: - core.warning( + deps.core.warning( `[github-api-usage-tracker] Invalid usage data for bucket "${bucket}"; skipping` ); break; @@ -145,19 +186,41 @@ async function run() { } if (usage.warnings.includes('limit_changed_across_reset')) { - core.warning( + deps.core.warning( `[github-api-usage-tracker] Limit changed across reset for bucket "${bucket}"; results may reflect a token change` ); } + const startingRemaining = Number(startingBucket.remaining); + const startingLimit = Number(startingBucket.limit); + const endingRemaining = Number(endingBucket.remaining); + const endingLimit = Number(endingBucket.limit); + const startUsed = + Number.isFinite(startingLimit) && Number.isFinite(startingRemaining) + ? startingLimit - startingRemaining + : null; + const endUsed = + Number.isFinite(endingLimit) && Number.isFinite(endingRemaining) + ? endingLimit - endingRemaining + : null; data[bucket] = { - used: usage.used, - remaining: usage.remaining, + used: { + start: startUsed, + end: endUsed, + total: usage.used + }, + remaining: { + start: Number.isFinite(startingRemaining) ? startingRemaining : null, + end: Number.isFinite(endingRemaining) ? endingRemaining : null + }, crossed_reset: usage.crossed_reset }; if (usage.crossed_reset) { crossedBuckets.push(bucket); } + if (usage.crossed_reset) { + totalIsMinimum = true; + } totalUsed += usage.used; } @@ -165,38 +228,49 @@ async function run() { const output = { total: totalUsed, duration_ms: duration, - buckets_data: data + buckets_data: data, + crossed_reset: totalIsMinimum }; - core.setOutput('usage', JSON.stringify(output)); + deps.core.setOutput('usage', JSON.stringify(output, null, 2)); // Write JSON file if path specified - const outPath = (core.getInput('output_path') || '').trim(); - maybeWrite(outPath, output); + const outPath = (deps.core.getInput('output_path') || '').trim(); + maybeWrite(outPath, output, deps.fs, deps.path); - log( + deps.log( `[github-api-usage-tracker] Preparing summary table for ${Object.keys(data).length} bucket(s)` ); - const summary = core.summary + const summary = deps.core.summary .addHeading('GitHub API Usage Tracker Summary') - .addTable(makeSummaryTable(data)); + .addTable(deps.makeSummaryTable(data, { useMinimumHeader: totalIsMinimum })); if (crossedBuckets.length > 0) { summary.addRaw( `Reset Window Crossed: Yes (${crossedBuckets.join(', ')})
`, true ); - } - summary - .addRaw( - `Action Duration: ${ - hasStartTime ? formatMs(duration) : 'Unknown (data missing)' - }
`, + summary.addRaw( + 'Total Usage: Total usage cannot be computed - usage reset window was crossed.
', true - ) - .addRaw(`Total API Calls/Points Used: ${totalUsed}
`, true) - .write(); + ); + summary.addRaw(`Minimum API Calls/Points Used: ${totalUsed}
`, true); + } + summary.addRaw( + `Action Duration: ${ + hasStartTime ? deps.formatMs(duration) : 'Unknown (data missing)' + }
`, + true + ); + if (crossedBuckets.length === 0) { + summary.addRaw(`Total API Calls/Points Used: ${totalUsed}
`, true); + } + summary.write(); } catch (err) { - core.error(`[github-api-usage-tracker] Post step failed: ${err.message}`); + deps.core.error(`[github-api-usage-tracker] Post step failed: ${err.message}`); } } -run(); +if (require.main === module) { + run(); +} + +module.exports = { run, maybeWrite }; diff --git a/src/pre.js b/src/pre.js index aead8ad..db0a327 100644 --- a/src/pre.js +++ b/src/pre.js @@ -2,32 +2,42 @@ const core = require('@actions/core'); const { fetchRateLimit } = require('./rate-limit'); const { log } = require('./log'); -async function run() { +async function run(overrides = {}) { + const deps = { + core, + fetchRateLimit, + log, + ...overrides + }; try { - const token = core.getInput('token'); + const token = deps.core.getInput('token'); if (!token) { - core.error('GitHub token is required for API Usage Tracker'); - core.saveState('skip_post', 'true'); + deps.core.error('GitHub token is required for API Usage Tracker'); + deps.core.saveState('skip_post', 'true'); return; } const startTime = Date.now(); - core.saveState('start_time', String(startTime)); + deps.core.saveState('start_time', String(startTime)); - log('[github-api-usage-tracker] Fetching initial rate limits...'); + deps.log('[github-api-usage-tracker] Fetching initial rate limits...'); - const limits = await fetchRateLimit(); + const limits = await deps.fetchRateLimit(); const resources = limits.resources || {}; - log('[github-api-usage-tracker] Initial Snapshot:'); - log('[github-api-usage-tracker] -----------------'); - log(`[github-api-usage-tracker] ${JSON.stringify(resources, null, 2)}`); + deps.log('[github-api-usage-tracker] Initial Snapshot:'); + deps.log('[github-api-usage-tracker] -----------------'); + deps.log(`[github-api-usage-tracker] ${JSON.stringify(resources, null, 2)}`); - core.saveState('starting_rate_limits', JSON.stringify(resources)); + deps.core.saveState('starting_rate_limits', JSON.stringify(resources)); } catch (err) { - core.warning(`Pre step failed: ${err.message}`); + deps.core.warning(`Pre step failed: ${err.message}`); } } -run(); +if (require.main === module) { + run(); +} + +module.exports = { run }; diff --git a/tests/checkpoint.test.mjs b/tests/checkpoint.test.mjs new file mode 100644 index 0000000..c32d8a7 --- /dev/null +++ b/tests/checkpoint.test.mjs @@ -0,0 +1,66 @@ +import { describe, it, expect, vi } from 'vitest'; +import { createRequire } from 'module'; + +const require = createRequire(import.meta.url); +const { run } = require('../src/checkpoint.js'); + +describe('checkpoint step', () => { + const createCore = () => ({ + getInput: vi.fn(), + saveState: vi.fn(), + warning: vi.fn() + }); + + it('skips snapshot when token is missing', async () => { + const core = createCore(); + core.getInput.mockReturnValue(''); + const fetchRateLimit = vi.fn(); + const log = vi.fn(); + + await run({ core, fetchRateLimit, log }); + + expect(log).toHaveBeenCalledWith( + '[github-api-usage-tracker] Skipping checkpoint snapshot due to missing token' + ); + expect(fetchRateLimit).not.toHaveBeenCalled(); + expect(core.saveState).not.toHaveBeenCalled(); + }); + + it('stores checkpoint snapshot when token is present', async () => { + const now = new Date('2024-01-01T00:00:05Z'); + vi.useFakeTimers(); + vi.setSystemTime(now); + + const core = createCore(); + core.getInput.mockReturnValue('token'); + const fetchRateLimit = vi.fn().mockResolvedValue({ + resources: { core: { remaining: 4 } } + }); + const log = vi.fn(); + + await run({ core, fetchRateLimit, log }); + + expect(core.saveState).toHaveBeenCalledWith('checkpoint_time', String(now.getTime())); + expect(core.saveState).toHaveBeenCalledWith( + 'checkpoint_rate_limits', + JSON.stringify({ core: { remaining: 4 } }) + ); + expect(fetchRateLimit).toHaveBeenCalledTimes(1); + expect(log).toHaveBeenCalled(); + + vi.useRealTimers(); + }); + + it('warns when checkpoint fetch fails', async () => { + const core = createCore(); + core.getInput.mockReturnValue('token'); + const fetchRateLimit = vi.fn().mockRejectedValue(new Error('boom')); + const log = vi.fn(); + + await run({ core, fetchRateLimit, log }); + + expect(core.warning).toHaveBeenCalledWith( + '[github-api-usage-tracker] Main step snapshot failed: boom' + ); + }); +}); diff --git a/tests/post-utils.test.mjs b/tests/post-utils.test.mjs index 9395fb0..fd23173 100644 --- a/tests/post-utils.test.mjs +++ b/tests/post-utils.test.mjs @@ -22,18 +22,76 @@ describe('post utils', () => { it('builds a summary table with stringified counts', () => { const table = makeSummaryTable({ - core: { used: 3, remaining: 10 }, - search: { used: 1, remaining: 2 } + core: { + used: { start: 3, end: 5, total: 2 }, + remaining: { start: 10, end: 8 } + }, + search: { + used: { start: 1, end: 1, total: 0 }, + remaining: { start: 2, end: 2 } + } }); expect(table).toEqual([ [ { data: 'Bucket', header: true }, - { data: 'Used', header: true }, - { data: 'Remaining', header: true } + { data: 'Used (Start)', header: true }, + { data: 'Remaining (Start)', header: true }, + { data: 'Used (End)', header: true }, + { data: 'Remaining (End)', header: true }, + { data: 'Used (Total)', header: true } ], - [{ data: 'core' }, { data: '3' }, { data: '10' }], - [{ data: 'search' }, { data: '1' }, { data: '2' }] + [ + { data: 'core' }, + { data: '3' }, + { data: '10' }, + { data: '5' }, + { data: '8' }, + { data: '2' } + ], + [ + { data: 'search' }, + { data: '1' }, + { data: '2' }, + { data: '1' }, + { data: '2' }, + { data: '0' } + ] + ]); + }); + + it('uses minimum header and formats non-numeric values as n/a', () => { + const table = makeSummaryTable( + { + core: { + used: { start: null, end: undefined, total: NaN }, + remaining: { start: undefined, end: null } + } + }, + { useMinimumHeader: true } + ); + + expect(table[0][5]).toEqual({ data: 'Used (Minimum)', header: true }); + expect(table[1]).toEqual([ + { data: 'core' }, + { data: 'n/a' }, + { data: 'n/a' }, + { data: 'n/a' }, + { data: 'n/a' }, + { data: 'n/a' } + ]); + }); + + it('handles missing usage info in summary table rows', () => { + const table = makeSummaryTable({ core: {} }); + + expect(table[1]).toEqual([ + { data: 'core' }, + { data: 'n/a' }, + { data: 'n/a' }, + { data: 'n/a' }, + { data: 'n/a' }, + { data: 'n/a' } ]); }); }); @@ -81,13 +139,67 @@ describe('computeBucketUsage', () => { expect(result).toEqual({ valid: true, - used: 400, + used: 100, remaining: 900, crossed_reset: true, warnings: [] }); }); + it('adds checkpoint usage before reset to the minimum', () => { + const result = computeBucketUsage( + { limit: 1000, remaining: 700, reset: 1100 }, + { limit: 1000, remaining: 900 }, + 1300, + { limit: 1000, remaining: 650 }, + 1000 + ); + + expect(result).toEqual({ + valid: true, + used: 150, + remaining: 900, + crossed_reset: true, + warnings: [] + }); + }); + + it('ignores non-numeric checkpoint remaining values', () => { + const result = computeBucketUsage( + { limit: 10, remaining: 5, reset: 100 }, + { limit: 10, remaining: 7 }, + 200, + { limit: 10, remaining: 'nope' }, + 50 + ); + + expect(result).toEqual({ + valid: true, + used: 3, + remaining: 7, + crossed_reset: true, + warnings: [] + }); + }); + + it('does not add checkpoint usage when usage is not positive', () => { + const result = computeBucketUsage( + { limit: 10, remaining: 5, reset: 100 }, + { limit: 10, remaining: 7 }, + 200, + { limit: 10, remaining: 6 }, + 50 + ); + + expect(result).toEqual({ + valid: true, + used: 3, + remaining: 7, + crossed_reset: true, + warnings: [] + }); + }); + it('warns when limits change across a reset', () => { const result = computeBucketUsage( { limit: 1000, remaining: 600, reset: 1100 }, @@ -97,7 +209,7 @@ describe('computeBucketUsage', () => { expect(result).toEqual({ valid: true, - used: 700, + used: 300, remaining: 4700, crossed_reset: true, warnings: ['limit_changed_across_reset'] @@ -120,4 +232,68 @@ describe('computeBucketUsage', () => { reason: 'limit_changed_without_reset' }); }); + + it('returns missing bucket errors when inputs are absent', () => { + const result = computeBucketUsage(null, { limit: 10, remaining: 9 }, 1200); + + expect(result).toEqual({ + valid: false, + used: 0, + remaining: undefined, + crossed_reset: false, + warnings: [], + reason: 'missing_bucket' + }); + }); + + it('marks invalid remaining values as invalid', () => { + const result = computeBucketUsage( + { limit: 10, remaining: 'nope', reset: 1600 }, + { limit: 10, remaining: 9 }, + 1200 + ); + + expect(result).toEqual({ + valid: false, + used: 0, + remaining: undefined, + crossed_reset: false, + warnings: [], + reason: 'invalid_remaining' + }); + }); + + it('marks invalid limits across resets as invalid', () => { + const result = computeBucketUsage( + { limit: 'nope', remaining: 5, reset: 100 }, + { limit: 10, remaining: 3 }, + 1200 + ); + + expect(result).toEqual({ + valid: false, + used: 0, + remaining: undefined, + crossed_reset: true, + warnings: [], + reason: 'invalid_limit' + }); + }); + + it('marks negative usage across resets as invalid', () => { + const result = computeBucketUsage( + { limit: 10, remaining: 5, reset: 100 }, + { limit: 10, remaining: 20 }, + 1200 + ); + + expect(result).toEqual({ + valid: false, + used: 0, + remaining: undefined, + crossed_reset: true, + warnings: [], + reason: 'negative_usage' + }); + }); }); diff --git a/tests/post.test.mjs b/tests/post.test.mjs new file mode 100644 index 0000000..8e5b0c8 --- /dev/null +++ b/tests/post.test.mjs @@ -0,0 +1,327 @@ +import { describe, it, expect, vi } from 'vitest'; +import { createRequire } from 'module'; +import path from 'node:path'; + +const require = createRequire(import.meta.url); +const { run, maybeWrite } = require('../src/post.js'); + +const createSummary = () => { + const calls = { headings: [], tables: [], raws: [], writes: 0 }; + const summary = { + addHeading(text) { + calls.headings.push(text); + return summary; + }, + addTable(table) { + calls.tables.push(table); + return summary; + }, + addRaw(html, escape) { + calls.raws.push({ html, escape }); + return summary; + }, + write() { + calls.writes += 1; + return summary; + } + }; + return { summary, calls }; +}; + +const createCore = ({ inputs = {}, state = {} } = {}) => { + const { summary, calls } = createSummary(); + const core = { + getInput: vi.fn((key) => inputs[key]), + getState: vi.fn((key) => state[key]), + setOutput: vi.fn(), + error: vi.fn(), + warning: vi.fn(), + summary + }; + return { core, summaryCalls: calls }; +}; + +describe('post step', () => { + it('writes output without creating a directory for current paths', () => { + const fsStub = { mkdirSync: vi.fn(), writeFileSync: vi.fn() }; + const pathStub = { dirname: vi.fn().mockReturnValue('.') }; + + maybeWrite('usage.json', { ok: true }, fsStub, pathStub); + + expect(fsStub.mkdirSync).not.toHaveBeenCalled(); + expect(fsStub.writeFileSync).toHaveBeenCalledWith( + 'usage.json', + JSON.stringify({ ok: true }, null, 2) + ); + }); + + it('skips when skip_post is true', async () => { + const { core } = createCore({ state: { skip_post: 'true' } }); + const log = vi.fn(); + const parseBuckets = vi.fn(); + const fetchRateLimit = vi.fn(); + + await run({ core, log, parseBuckets, fetchRateLimit }); + + expect(log).toHaveBeenCalledWith( + '[github-api-usage-tracker] Skipping post step due to missing token' + ); + expect(parseBuckets).not.toHaveBeenCalled(); + expect(fetchRateLimit).not.toHaveBeenCalled(); + }); + + it('returns early when no valid buckets are provided', async () => { + const { core } = createCore({ inputs: { buckets: 'core' } }); + const log = vi.fn(); + const parseBuckets = vi.fn().mockReturnValue([]); + const fetchRateLimit = vi.fn(); + + await run({ core, log, parseBuckets, fetchRateLimit }); + + expect(log).toHaveBeenCalledWith( + '[github-api-usage-tracker] No valid buckets specified for tracking' + ); + expect(fetchRateLimit).not.toHaveBeenCalled(); + }); + + it('errors when starting state is missing', async () => { + const { core } = createCore({ inputs: { buckets: 'core' } }); + const log = vi.fn(); + const parseBuckets = vi.fn().mockReturnValue(['core']); + const fetchRateLimit = vi.fn(); + + await run({ core, log, parseBuckets, fetchRateLimit }); + + expect(core.error).toHaveBeenCalledWith( + '[github-api-usage-tracker] No starting rate limit data found; skipping post step' + ); + expect(fetchRateLimit).not.toHaveBeenCalled(); + }); + + it('errors when starting state is invalid JSON', async () => { + const { core } = createCore({ + inputs: { buckets: 'core' }, + state: { starting_rate_limits: '{', start_time: '0' } + }); + const log = vi.fn(); + const parseBuckets = vi.fn().mockReturnValue(['core']); + const fetchRateLimit = vi.fn(); + + await run({ core, log, parseBuckets, fetchRateLimit }); + + expect(core.error).toHaveBeenCalledWith( + '[github-api-usage-tracker] Failed to parse starting rate limit data; skipping post step' + ); + expect(fetchRateLimit).not.toHaveBeenCalled(); + }); + + it('handles missing buckets and invalid usage reasons', async () => { + const buckets = [ + 'missingStart', + 'missingEnd', + 'invalid_limit', + 'limit_changed_without_reset', + 'remaining_increased_without_reset', + 'negative_usage', + 'unknown_reason' + ]; + const startingResources = { + missingEnd: {}, + invalid_limit: {}, + limit_changed_without_reset: {}, + remaining_increased_without_reset: {}, + negative_usage: {}, + unknown_reason: {} + }; + const endingResources = { + invalid_limit: {}, + limit_changed_without_reset: {}, + remaining_increased_without_reset: {}, + negative_usage: {}, + unknown_reason: {} + }; + const { core } = createCore({ + inputs: { buckets: buckets.join(',') }, + state: { + starting_rate_limits: JSON.stringify(startingResources), + start_time: '0' + } + }); + const log = vi.fn(); + const parseBuckets = vi.fn().mockReturnValue(buckets); + const fetchRateLimit = vi.fn().mockResolvedValue({ resources: endingResources }); + const results = [ + { valid: false, reason: 'invalid_limit', warnings: [] }, + { valid: false, reason: 'limit_changed_without_reset', warnings: [] }, + { valid: false, reason: 'remaining_increased_without_reset', warnings: [] }, + { valid: false, reason: 'negative_usage', warnings: [] }, + { valid: false, reason: 'whatever', warnings: [] } + ]; + const computeBucketUsage = vi.fn(() => results.shift()); + + await run({ core, log, parseBuckets, fetchRateLimit, computeBucketUsage }); + + expect(core.warning).toHaveBeenCalledWith( + '[github-api-usage-tracker] Starting rate limit bucket "missingStart" not found; skipping' + ); + expect(core.warning).toHaveBeenCalledWith( + '[github-api-usage-tracker] Ending rate limit bucket "missingEnd" not found; skipping' + ); + expect(core.warning).toHaveBeenCalledWith( + '[github-api-usage-tracker] Invalid limit for bucket "invalid_limit" during reset crossing; skipping' + ); + expect(core.warning).toHaveBeenCalledWith( + '[github-api-usage-tracker] Limit changed without reset for bucket "limit_changed_without_reset"; skipping' + ); + expect(core.warning).toHaveBeenCalledWith( + '[github-api-usage-tracker] Remaining increased without reset for bucket "remaining_increased_without_reset"; skipping' + ); + expect(core.warning).toHaveBeenCalledWith( + '[github-api-usage-tracker] Negative usage for bucket "negative_usage" detected; skipping' + ); + expect(core.warning).toHaveBeenCalledWith( + '[github-api-usage-tracker] Invalid usage data for bucket "unknown_reason"; skipping' + ); + }); + + it('reports unknown duration when start time is invalid', async () => { + const { core, summaryCalls } = createCore({ + inputs: { buckets: 'core' }, + state: { + starting_rate_limits: JSON.stringify({ + core: { limit: 10, remaining: 7, reset: 9999 } + }), + start_time: 'nope', + checkpoint_rate_limits: 'not-json' + } + }); + const log = vi.fn(); + const parseBuckets = vi.fn().mockReturnValue(['core']); + const fetchRateLimit = vi.fn().mockResolvedValue({ + resources: { core: { limit: 10, remaining: 5, reset: 9999 } } + }); + const computeBucketUsage = vi.fn().mockReturnValue({ + valid: true, + used: 2, + crossed_reset: false, + warnings: [] + }); + + await run({ core, log, parseBuckets, fetchRateLimit, computeBucketUsage }); + + expect(core.error).toHaveBeenCalledWith( + '[github-api-usage-tracker] Invalid or missing start time; duration will be reported as unknown' + ); + expect(core.warning).toHaveBeenCalledWith( + '[github-api-usage-tracker] Failed to parse checkpoint rate limit data; ignoring checkpoint snapshot' + ); + expect(summaryCalls.raws.some((entry) => entry.html.includes('Unknown (data missing)'))).toBe( + true + ); + expect( + summaryCalls.raws.some((entry) => entry.html.includes('Total API Calls/Points Used')) + ).toBe(true); + }); + + it('reports errors when post step throws', async () => { + const { core } = createCore({ + inputs: { buckets: 'core' }, + state: { + starting_rate_limits: JSON.stringify({ core: { limit: 10, remaining: 7, reset: 9999 } }), + start_time: '0' + } + }); + const log = vi.fn(); + const parseBuckets = vi.fn().mockReturnValue(['core']); + const fetchRateLimit = vi.fn().mockRejectedValue(new Error('boom')); + + await run({ core, log, parseBuckets, fetchRateLimit }); + + expect(core.error).toHaveBeenCalledWith('[github-api-usage-tracker] Post step failed: boom'); + }); + + it('writes output and summary with checkpoint data', async () => { + const startTime = new Date('2024-01-01T00:00:00Z'); + const endTime = new Date('2024-01-01T00:00:10Z'); + vi.useFakeTimers(); + vi.setSystemTime(endTime); + + const startingResources = { + core: { limit: 10, remaining: 8, reset: 20 }, + search: { limit: 5, remaining: 4, reset: 20 } + }; + const endingResources = { + core: { limit: 10, remaining: 5, reset: 20 }, + search: { limit: 5, remaining: 4, reset: 20 } + }; + const checkpointResources = { + core: { limit: 10, remaining: 7, reset: 20 }, + search: { limit: 5, remaining: 4, reset: 20 } + }; + const { core, summaryCalls } = createCore({ + inputs: { buckets: 'core,search', output_path: 'out/usage.json' }, + state: { + starting_rate_limits: JSON.stringify(startingResources), + start_time: String(startTime.getTime()), + checkpoint_rate_limits: JSON.stringify(checkpointResources), + checkpoint_time: String(startTime.getTime() + 5000) + } + }); + const log = vi.fn(); + const parseBuckets = vi.fn().mockReturnValue(['core', 'search']); + const fetchRateLimit = vi.fn().mockResolvedValue({ resources: endingResources }); + const usageResults = [ + { + valid: true, + used: 3, + crossed_reset: true, + warnings: ['limit_changed_across_reset'] + }, + { valid: false, reason: 'invalid_remaining', warnings: [] } + ]; + const computeBucketUsage = vi.fn(() => usageResults.shift()); + const fsStub = { mkdirSync: vi.fn(), writeFileSync: vi.fn() }; + const pathStub = { dirname: (p) => path.dirname(p) }; + + await run({ + core, + log, + parseBuckets, + fetchRateLimit, + computeBucketUsage, + fs: fsStub, + path: pathStub + }); + + expect(parseBuckets).toHaveBeenCalledWith('core,search'); + expect(fetchRateLimit).toHaveBeenCalledTimes(1); + expect(computeBucketUsage).toHaveBeenCalledTimes(2); + expect(core.warning).toHaveBeenCalledWith( + '[github-api-usage-tracker] Limit changed across reset for bucket "core"; results may reflect a token change' + ); + expect(core.warning).toHaveBeenCalledWith( + '[github-api-usage-tracker] Invalid remaining count for bucket "search"; skipping' + ); + expect(core.setOutput).toHaveBeenCalledTimes(1); + const output = JSON.parse(core.setOutput.mock.calls[0][1]); + expect(output.total).toBe(3); + expect(output.crossed_reset).toBe(true); + expect(output.buckets_data.core.used.total).toBe(3); + expect(fsStub.mkdirSync).toHaveBeenCalledWith('out', { recursive: true }); + expect(fsStub.writeFileSync).toHaveBeenCalled(); + + expect(summaryCalls.headings).toContain('GitHub API Usage Tracker Summary'); + expect(summaryCalls.tables).toHaveLength(1); + expect(summaryCalls.raws.some((entry) => entry.html.includes('Reset Window Crossed'))).toBe( + true + ); + expect( + summaryCalls.raws.some((entry) => entry.html.includes('Minimum API Calls/Points Used')) + ).toBe(true); + expect(summaryCalls.raws.some((entry) => entry.html.includes('Action Duration'))).toBe(true); + expect(summaryCalls.raws.some((entry) => entry.html.includes('10s'))).toBe(true); + expect(summaryCalls.writes).toBe(1); + + vi.useRealTimers(); + }); +}); diff --git a/tests/pre.test.mjs b/tests/pre.test.mjs new file mode 100644 index 0000000..c8289ea --- /dev/null +++ b/tests/pre.test.mjs @@ -0,0 +1,64 @@ +import { describe, it, expect, vi } from 'vitest'; +import { createRequire } from 'module'; + +const require = createRequire(import.meta.url); +const { run } = require('../src/pre.js'); + +describe('pre step', () => { + const createCore = () => ({ + getInput: vi.fn(), + saveState: vi.fn(), + error: vi.fn(), + warning: vi.fn() + }); + + it('marks skip_post when token is missing', async () => { + const core = createCore(); + core.getInput.mockReturnValue(''); + const fetchRateLimit = vi.fn(); + const log = vi.fn(); + + await run({ core, fetchRateLimit, log }); + + expect(core.error).toHaveBeenCalledWith('GitHub token is required for API Usage Tracker'); + expect(core.saveState).toHaveBeenCalledWith('skip_post', 'true'); + expect(fetchRateLimit).not.toHaveBeenCalled(); + expect(log).not.toHaveBeenCalled(); + }); + + it('stores starting snapshot when token is present', async () => { + const now = new Date('2024-01-01T00:00:00Z'); + vi.useFakeTimers(); + vi.setSystemTime(now); + + const core = createCore(); + core.getInput.mockReturnValue('token'); + const fetchRateLimit = vi.fn().mockResolvedValue({ + resources: { core: { remaining: 5 } } + }); + const log = vi.fn(); + + await run({ core, fetchRateLimit, log }); + + expect(core.saveState).toHaveBeenCalledWith('start_time', String(now.getTime())); + expect(core.saveState).toHaveBeenCalledWith( + 'starting_rate_limits', + JSON.stringify({ core: { remaining: 5 } }) + ); + expect(fetchRateLimit).toHaveBeenCalledTimes(1); + expect(log).toHaveBeenCalled(); + + vi.useRealTimers(); + }); + + it('warns when rate limit fetch fails', async () => { + const core = createCore(); + core.getInput.mockReturnValue('token'); + const fetchRateLimit = vi.fn().mockRejectedValue(new Error('boom')); + const log = vi.fn(); + + await run({ core, fetchRateLimit, log }); + + expect(core.warning).toHaveBeenCalledWith('Pre step failed: boom'); + }); +}); diff --git a/tests/rate-limit.test.mjs b/tests/rate-limit.test.mjs index 6af926a..6e8e47c 100644 --- a/tests/rate-limit.test.mjs +++ b/tests/rate-limit.test.mjs @@ -5,8 +5,7 @@ import { createRequire } from 'module'; const require = createRequire(import.meta.url); const https = require('https'); -const rateLimitModule = await import('../src/rate-limit.js'); -const { fetchRateLimit } = rateLimitModule.default ?? rateLimitModule; +const { fetchRateLimit } = require('../src/rate-limit.js'); const originalToken = process.env.INPUT_TOKEN; let stdoutSpy; @@ -109,4 +108,17 @@ describe('fetchRateLimit', () => { await expect(fetchRateLimit()).rejects.toThrow(); }); + + it('rejects on request errors', async () => { + process.env.INPUT_TOKEN = 'token123'; + requestSpy = vi.spyOn(https, 'request').mockImplementation(() => { + const req = new EventEmitter(); + req.end = () => { + req.emit('error', new Error('network down')); + }; + return req; + }); + + await expect(fetchRateLimit()).rejects.toThrow('network down'); + }); }); diff --git a/vitest.config.js b/vitest.config.js new file mode 100644 index 0000000..33e0167 --- /dev/null +++ b/vitest.config.js @@ -0,0 +1,11 @@ +const { defineConfig } = require('vitest/config'); + +module.exports = defineConfig({ + test: { + coverage: { + provider: 'v8', + all: true, + include: ['src/**/*.js'] + } + } +});