From f55167a6a30e84f255271d7744843648d6c37432 Mon Sep 17 00:00:00 2001 From: Really Him Date: Fri, 23 Jan 2026 12:31:21 -0500 Subject: [PATCH 01/15] docs: enhance banner --- assets/header-banner-v2.svg | 44 +++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/assets/header-banner-v2.svg b/assets/header-banner-v2.svg index 3170905..4f931f0 100644 --- a/assets/header-banner-v2.svg +++ b/assets/header-banner-v2.svg @@ -42,8 +42,8 @@ - - + + @@ -58,12 +58,12 @@ - - + + - - - + + + @@ -71,8 +71,25 @@ - - + + + + + + + + + + + + + + + + + @@ -131,11 +148,14 @@ END - - - + + + + + + From f2bd90e5397ade4a8ae72604c4cfc825d60a1de2 Mon Sep 17 00:00:00 2001 From: Really Him Date: Fri, 23 Jan 2026 12:44:00 -0500 Subject: [PATCH 02/15] docs: enhance banner --- assets/header-banner-v2.svg | 111 ++++++++++++++++++++++++++++-------- 1 file changed, 86 insertions(+), 25 deletions(-) diff --git a/assets/header-banner-v2.svg b/assets/header-banner-v2.svg index 4f931f0..7dd1086 100644 --- a/assets/header-banner-v2.svg +++ b/assets/header-banner-v2.svg @@ -1,4 +1,46 @@ + @@ -42,9 +84,11 @@ - + + - + + @@ -61,9 +105,9 @@ - + - + @@ -75,20 +119,18 @@ - + - - - - + + - - + + @@ -122,12 +164,12 @@ - - + + - - - + + + @@ -135,8 +177,23 @@ - - + + + + + + + + + + + + + + + @@ -148,19 +205,23 @@ END - + - - + + - - + + - - + + + + + + From 2928d0713ed272b8b88a4c9f0a7ad47e9d00859b Mon Sep 17 00:00:00 2001 From: Really Him Date: Fri, 23 Jan 2026 14:14:09 -0500 Subject: [PATCH 03/15] docs: document dependency inversion pattern --- src/README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/README.md diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..d30adbe --- /dev/null +++ b/src/README.md @@ -0,0 +1,32 @@ +# Implementation Notes + +## Code Structure + +### Separation of module code from test plumbing + +The entry point files (`pre.js`, `post.js`, `checkpoint.js`) use a dependency injection pattern to enable unit testing without module mocking. To keep the code readable, each file is organized into two clearly demarcated sections: + +``` +// ─── CORE LOGIC ────────────────────────────────────────────────────── +async function executePreStep(deps) { + // The actual business logic lives here. + // Readers should focus on this section. +} + +// ─── TEST HARNESS ──────────────────────────────────────────────────── +async function run(overrides = {}) { + const deps = { core, fetchRateLimit, log, ...overrides }; + return executePreStep(deps); +} + +if (require.main === module) run(); +module.exports = { run }; +``` + +**Core Logic** (top): Contains the actual business logic in a function that receives a `deps` object. This is what readers should focus on when understanding what the code does. + +**Test Harness** (bottom): Contains the dependency injection wiring. The `run(overrides)` function assembles default dependencies and allows tests to substitute mocks via the `overrides` parameter. Readers can skip this section entirely when trying to understand the module's behavior. + +This structure mirrors common patterns in other languages (e.g., Python's `if __name__ == '__main__':` at the bottom) and provides a clear "stop reading here" signal for anyone reviewing the logic. + +**Note:** Pure utility modules like `post-utils.js` and `log.js` do not use this pattern since they have no external dependencies and can be tested directly. From 92eed317abf5b81a7c5030206581632aab00a6b2 Mon Sep 17 00:00:00 2001 From: Really Him Date: Fri, 23 Jan 2026 18:47:24 -0500 Subject: [PATCH 04/15] refactor: revert DI-related design changes --- dist/checkpoint/index.js | 83 +++--- dist/post/index.js | 285 +++++++++----------- dist/pre/index.js | 93 +++---- package-lock.json | 538 ++++++++++++++++++++++++-------------- package.json | 4 +- src/checkpoint.js | 34 +-- src/post.js | 100 +++---- src/pre.js | 36 +-- tests/checkpoint.test.mjs | 66 ----- tests/post-utils.test.mjs | 135 ---------- tests/post.test.mjs | 327 ----------------------- tests/pre.test.mjs | 64 ----- tests/rate-limit.test.mjs | 16 +- 13 files changed, 612 insertions(+), 1169 deletions(-) delete mode 100644 tests/checkpoint.test.mjs delete mode 100644 tests/post.test.mjs delete mode 100644 tests/pre.test.mjs diff --git a/dist/checkpoint/index.js b/dist/checkpoint/index.js index 0d36ab2..5edc0fa 100644 --- a/dist/checkpoint/index.js +++ b/dist/checkpoint/index.js @@ -27766,51 +27766,6 @@ function parseParams (str) { module.exports = parseParams -/***/ }), - -/***/ 3568: -/***/ ((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.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 === require.cache[eval('__filename')]) { - run(); -} - -module.exports = { run }; - - /***/ }), /***/ 9630: @@ -27965,12 +27920,36 @@ module.exports = { fetchRateLimit }; /******/ 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__(3568); -/******/ module.exports = __webpack_exports__; -/******/ +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) { + log('[github-api-usage-tracker] Skipping checkpoint snapshot due to missing token'); + return; + } + + log('[github-api-usage-tracker] Fetching checkpoint rate limits...'); + const limits = await fetchRateLimit(); + const resources = limits.resources || {}; + + log('[github-api-usage-tracker] Checkpoint Snapshot:'); + log('[github-api-usage-tracker] ---------------------'); + log(`[github-api-usage-tracker] ${JSON.stringify(resources, null, 2)}`); + + core.saveState('checkpoint_time', String(Date.now())); + core.saveState('checkpoint_rate_limits', JSON.stringify(resources)); + } catch (err) { + core.warning(`[github-api-usage-tracker] Main step snapshot failed: ${err.message}`); + } +} + +run(); + +module.exports = __webpack_exports__; /******/ })() ; \ No newline at end of file diff --git a/dist/post/index.js b/dist/post/index.js index b82bd63..43c5fe0 100644 --- a/dist/post/index.js +++ b/dist/post/index.js @@ -27984,9 +27984,94 @@ module.exports = { formatMs, makeSummaryTable, computeBucketUsage }; /***/ }), -/***/ 7656: +/***/ 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')); + + 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 + "/"; +/******/ +/************************************************************************/ +var __webpack_exports__ = {}; /** * Retrieves a numeric state value from the GitHub Actions state. * @@ -28019,44 +28104,30 @@ const { formatMs, makeSummaryTable, computeBucketUsage } = __nccwpck_require__(5 * * @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, fsModule, pathModule) { +function maybeWrite(pathname, data) { if (!pathname) return; - const dir = pathModule.dirname(pathname); - if (dir && dir !== '.') fsModule.mkdirSync(dir, { recursive: true }); - fsModule.writeFileSync(pathname, JSON.stringify(data, null, 2)); + const dir = path.dirname(pathname); + if (dir && dir !== '.') fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync(pathname, JSON.stringify(data, null, 2)); } -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'); +async function run() { + if (core.getState('skip_post') === 'true') { + log('[github-api-usage-tracker] Skipping post step due to missing token'); return; } try { - const buckets = deps.parseBuckets(deps.core.getInput('buckets')); + const buckets = parseBuckets(core.getInput('buckets')); if (buckets.length === 0) { - deps.log('[github-api-usage-tracker] No valid buckets specified for tracking'); + log('[github-api-usage-tracker] No valid buckets specified for tracking'); return; } - const startingState = deps.core.getState('starting_rate_limits'); + const startingState = core.getState('starting_rate_limits'); if (!startingState) { - deps.core.error( + core.error( '[github-api-usage-tracker] No starting rate limit data found; skipping post step' ); return; @@ -28065,32 +28136,32 @@ async function run(overrides = {}) { try { startingResources = JSON.parse(startingState); } catch { - deps.core.error( + core.error( '[github-api-usage-tracker] Failed to parse starting rate limit data; skipping post step' ); return; } - const startTime = Number(deps.core.getState('start_time')); + const startTime = Number(core.getState('start_time')); const hasStartTime = Number.isFinite(startTime); if (!hasStartTime) { - deps.core.error( + 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'); + const checkpointState = core.getState('checkpoint_rate_limits'); let checkpointResources; let checkpointTimeSeconds = null; if (checkpointState) { try { checkpointResources = JSON.parse(checkpointState); } catch { - deps.core.warning( + 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')); + const checkpointTimeMs = Number(core.getState('checkpoint_time')); checkpointTimeSeconds = Number.isFinite(checkpointTimeMs) && checkpointTimeMs > 0 ? Math.floor(checkpointTimeMs / 1000) @@ -28100,14 +28171,14 @@ async function run(overrides = {}) { const endTimeSeconds = Math.floor(endTime / 1000); const duration = hasStartTime ? endTime - startTime : null; - deps.log('[github-api-usage-tracker] Fetching final rate limits...'); + log('[github-api-usage-tracker] Fetching final rate limits...'); - const endingLimits = await deps.fetchRateLimit(); + const endingLimits = await fetchRateLimit(); const endingResources = endingLimits.resources || {}; - 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)}`); + log('[github-api-usage-tracker] Final Snapshot:'); + log('[github-api-usage-tracker] -----------------'); + log(`[github-api-usage-tracker] ${JSON.stringify(endingResources, null, 2)}`); const data = {}; const crossedBuckets = []; @@ -28118,20 +28189,20 @@ async function run(overrides = {}) { const startingBucket = startingResources[bucket]; const endingBucket = endingResources[bucket]; if (!startingBucket) { - deps.core.warning( + core.warning( `[github-api-usage-tracker] Starting rate limit bucket "${bucket}" not found; skipping` ); continue; } if (!endingBucket) { - deps.core.warning( + core.warning( `[github-api-usage-tracker] Ending rate limit bucket "${bucket}" not found; skipping` ); continue; } const checkpointBucket = checkpointResources ? checkpointResources[bucket] : undefined; - const usage = deps.computeBucketUsage( + const usage = computeBucketUsage( startingBucket, endingBucket, endTimeSeconds, @@ -28141,32 +28212,32 @@ async function run(overrides = {}) { if (!usage.valid) { switch (usage.reason) { case 'invalid_remaining': - deps.core.warning( + core.warning( `[github-api-usage-tracker] Invalid remaining count for bucket "${bucket}"; skipping` ); break; case 'invalid_limit': - deps.core.warning( + core.warning( `[github-api-usage-tracker] Invalid limit for bucket "${bucket}" during reset crossing; skipping` ); break; case 'limit_changed_without_reset': - deps.core.warning( + core.warning( `[github-api-usage-tracker] Limit changed without reset for bucket "${bucket}"; skipping` ); break; case 'remaining_increased_without_reset': - deps.core.warning( + core.warning( `[github-api-usage-tracker] Remaining increased without reset for bucket "${bucket}"; skipping` ); break; case 'negative_usage': - deps.core.warning( + core.warning( `[github-api-usage-tracker] Negative usage for bucket "${bucket}" detected; skipping` ); break; default: - deps.core.warning( + core.warning( `[github-api-usage-tracker] Invalid usage data for bucket "${bucket}"; skipping` ); break; @@ -28175,7 +28246,7 @@ async function run(overrides = {}) { } if (usage.warnings.includes('limit_changed_across_reset')) { - deps.core.warning( + core.warning( `[github-api-usage-tracker] Limit changed across reset for bucket "${bucket}"; results may reflect a token change` ); } @@ -28220,18 +28291,18 @@ async function run(overrides = {}) { buckets_data: data, crossed_reset: totalIsMinimum }; - deps.core.setOutput('usage', JSON.stringify(output, null, 2)); + core.setOutput('usage', JSON.stringify(output, null, 2)); // Write JSON file if path specified - const outPath = (deps.core.getInput('output_path') || '').trim(); - maybeWrite(outPath, output, deps.fs, deps.path); + const outPath = (core.getInput('output_path') || '').trim(); + maybeWrite(outPath, output); - deps.log( + log( `[github-api-usage-tracker] Preparing summary table for ${Object.keys(data).length} bucket(s)` ); - const summary = deps.core.summary + const summary = core.summary .addHeading('GitHub API Usage Tracker Summary') - .addTable(deps.makeSummaryTable(data, { useMinimumHeader: totalIsMinimum })); + .addTable(makeSummaryTable(data, { useMinimumHeader: totalIsMinimum })); if (crossedBuckets.length > 0) { summary.addRaw( `

Reset Window Crossed: Yes (${crossedBuckets.join(', ')})

`, @@ -28245,7 +28316,7 @@ async function run(overrides = {}) { } summary.addRaw( `

Action Duration: ${ - hasStartTime ? deps.formatMs(duration) : 'Unknown (data missing)' + hasStartTime ? formatMs(duration) : 'Unknown (data missing)' }

`, true ); @@ -28254,112 +28325,12 @@ async function run(overrides = {}) { } summary.write(); } catch (err) { - deps.core.error(`[github-api-usage-tracker] Post step failed: ${err.message}`); + core.error(`[github-api-usage-tracker] Post step failed: ${err.message}`); } } -if (require.main === require.cache[eval('__filename')]) { - run(); -} - -module.exports = { run, maybeWrite }; +run(); - -/***/ }), - -/***/ 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')); - - 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__; -/******/ +module.exports = __webpack_exports__; /******/ })() ; \ No newline at end of file diff --git a/dist/pre/index.js b/dist/pre/index.js index 0d0504a..49d427a 100644 --- a/dist/pre/index.js +++ b/dist/pre/index.js @@ -27831,56 +27831,6 @@ 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: @@ -27970,12 +27920,41 @@ module.exports = { fetchRateLimit }; /******/ 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__(7077); -/******/ module.exports = __webpack_exports__; -/******/ +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__; /******/ })() ; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 046abb0..89b60d7 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.18", + "@vitest/coverage-v8": "4.0.17", "eslint": "9.39.2", "globals": "17.1.0", "husky": "9.1.7", "prettier": "3.8.1", "rimraf": "6.1.2", - "vitest": "4.0.18" + "vitest": "4.0.17" }, "engines": { "node": ">=20" @@ -831,9 +831,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "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==", + "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==", "cpu": [ "arm" ], @@ -845,9 +845,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "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==", + "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==", "cpu": [ "arm64" ], @@ -859,9 +859,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "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==", + "version": "4.55.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.2.tgz", + "integrity": "sha512-UCbaTklREjrc5U47ypLulAgg4njaqfOVLU18VrCrI+6E5MQjuG0lSWaqLlAJwsD7NpFV249XgB0Bi37Zh5Sz4g==", "cpu": [ "arm64" ], @@ -873,9 +873,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "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==", + "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==", "cpu": [ "x64" ], @@ -887,9 +887,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "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==", + "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==", "cpu": [ "arm64" ], @@ -901,9 +901,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "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==", + "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==", "cpu": [ "x64" ], @@ -915,9 +915,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "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==", + "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==", "cpu": [ "arm" ], @@ -929,9 +929,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "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==", + "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==", "cpu": [ "arm" ], @@ -943,9 +943,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "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==", + "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==", "cpu": [ "arm64" ], @@ -957,9 +957,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "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==", + "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==", "cpu": [ "arm64" ], @@ -971,9 +971,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "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==", + "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==", "cpu": [ "loong64" ], @@ -985,9 +985,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "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==", + "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==", "cpu": [ "loong64" ], @@ -999,9 +999,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "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==", + "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==", "cpu": [ "ppc64" ], @@ -1013,9 +1013,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "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==", + "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==", "cpu": [ "ppc64" ], @@ -1027,9 +1027,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "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==", + "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==", "cpu": [ "riscv64" ], @@ -1041,9 +1041,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "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==", + "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==", "cpu": [ "riscv64" ], @@ -1055,9 +1055,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "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==", + "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==", "cpu": [ "s390x" ], @@ -1069,9 +1069,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "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==", + "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==", "cpu": [ "x64" ], @@ -1083,9 +1083,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "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==", + "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==", "cpu": [ "x64" ], @@ -1097,9 +1097,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "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==", + "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==", "cpu": [ "x64" ], @@ -1111,9 +1111,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "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==", + "version": "4.55.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.2.tgz", + "integrity": "sha512-sZnyUgGkuzIXaK3jNMPmUIyJrxu/PjmATQrocpGA1WbCPX8H5tfGgRSuYtqBYAvLuIGp8SPRb1O4d1Fkb5fXaQ==", "cpu": [ "arm64" ], @@ -1125,9 +1125,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "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==", + "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==", "cpu": [ "arm64" ], @@ -1139,9 +1139,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "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==", + "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==", "cpu": [ "ia32" ], @@ -1153,9 +1153,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "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==", + "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==", "cpu": [ "x64" ], @@ -1167,9 +1167,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "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==", + "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==", "cpu": [ "x64" ], @@ -1230,14 +1230,14 @@ } }, "node_modules/@vitest/coverage-v8": { - "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==", + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.17.tgz", + "integrity": "sha512-/6zU2FLGg0jsd+ePZcwHRy3+WpNTBBhDY56P4JTRqUN/Dp6CvOEa9HrikcQ4KfV2b2kAHUFB4dl1SuocWXSFEw==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.0.18", + "@vitest/utils": "4.0.17", "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.18", - "vitest": "4.0.18" + "@vitest/browser": "4.0.17", + "vitest": "4.0.17" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -1260,17 +1260,44 @@ } } }, + "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.18", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", - "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.17.tgz", + "integrity": "sha512-mEoqP3RqhKlbmUmntNDDCJeTDavDR+fVYkSOw8qRwJFaW/0/5zA9zFeTrHqNtcmwh6j26yMmwx2PqUDPzt5ZAQ==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.0.18", - "@vitest/utils": "4.0.18", + "@vitest/spy": "4.0.17", + "@vitest/utils": "4.0.17", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" }, @@ -1278,14 +1305,41 @@ "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.18", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", - "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.17.tgz", + "integrity": "sha512-+ZtQhLA3lDh1tI2wxe3yMsGzbp7uuJSWBM1iTIKCbppWTSBN09PUC+L+fyNlQApQoR+Ps8twt2pbSSXg2fQVEQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.0.18", + "@vitest/spy": "4.0.17", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -1305,10 +1359,24 @@ } } }, - "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==", + "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==", "dev": true, "license": "MIT", "dependencies": { @@ -1318,28 +1386,28 @@ "url": "https://opencollective.com/vitest" } }, - "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==", + "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==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.0.18", - "pathe": "^2.0.3" + "@vitest/pretty-format": "4.0.17", + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", - "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.17.tgz", + "integrity": "sha512-npPelD7oyL+YQM2gbIYvlavlMVWUfNNGZPcu0aEUQXt7FXTuqhmgiYupPnAanhKvyP6Srs2pIbWo30K0RbDtRQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.18", + "@vitest/pretty-format": "4.0.17", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -1347,26 +1415,25 @@ "url": "https://opencollective.com/vitest" } }, - "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==", + "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==", "dev": true, "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, "funding": { "url": "https://opencollective.com/vitest" } }, - "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==", + "node_modules/@vitest/spy": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.17.tgz", + "integrity": "sha512-I1bQo8QaP6tZlTomQNWKJE6ym4SHf3oLS7ceNjozxxgzavRAgZDc06T7kD8gb9bXKEgcLNt00Z+kZO6KaJ62Ew==", "dev": true, "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.0.18", - "tinyrainbow": "^3.0.3" - }, "funding": { "url": "https://opencollective.com/vitest" } @@ -1835,24 +1902,6 @@ "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", @@ -2442,19 +2491,6 @@ "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", @@ -2551,9 +2587,9 @@ } }, "node_modules/rollup": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.56.0.tgz", - "integrity": "sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg==", + "version": "4.55.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.2.tgz", + "integrity": "sha512-PggGy4dhwx5qaW+CKBilA/98Ql9keyfnb7lh4SR6shQ91QQQi1ORJ1v4UinkdP2i87OBs9AQFooQylcrrRfIcg==", "dev": true, "license": "MIT", "dependencies": { @@ -2567,31 +2603,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@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", + "@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", "fsevents": "~2.3.2" } }, @@ -2722,6 +2758,37 @@ "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", @@ -2851,20 +2918,51 @@ } } }, + "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.18", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", - "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.17.tgz", + "integrity": "sha512-FQMeF0DJdWY0iOnbv466n/0BudNdKj1l5jYgl5JVTwjSsZSlqyXFt/9+1sEyhR6CLowbZpV7O1sCHrzBhucKKg==", "dev": true, "license": "MIT", "dependencies": { - "@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", + "@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", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", @@ -2892,10 +2990,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.18", - "@vitest/browser-preview": "4.0.18", - "@vitest/browser-webdriverio": "4.0.18", - "@vitest/ui": "4.0.18", + "@vitest/browser-playwright": "4.0.17", + "@vitest/browser-preview": "4.0.17", + "@vitest/browser-webdriverio": "4.0.17", + "@vitest/ui": "4.0.17", "happy-dom": "*", "jsdom": "*" }, @@ -2929,6 +3027,46 @@ } } }, + "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", @@ -2972,6 +3110,24 @@ "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 57a5295..d6b1ea7 100644 --- a/package.json +++ b/package.json @@ -21,13 +21,13 @@ "devDependencies": { "@eslint/js": "9.39.2", "@vercel/ncc": "0.38.4", - "@vitest/coverage-v8": "4.0.18", + "@vitest/coverage-v8": "4.0.17", "eslint": "9.39.2", "globals": "17.1.0", "husky": "9.1.7", "prettier": "3.8.1", "rimraf": "6.1.2", - "vitest": "4.0.18" + "vitest": "4.0.17" }, "engines": { "node": ">=20" diff --git a/src/checkpoint.js b/src/checkpoint.js index 0dc9f6f..6fa2e40 100644 --- a/src/checkpoint.js +++ b/src/checkpoint.js @@ -2,37 +2,27 @@ const core = require('@actions/core'); const { fetchRateLimit } = require('./rate-limit'); const { log } = require('./log'); -async function run(overrides = {}) { - const deps = { - core, - fetchRateLimit, - log, - ...overrides - }; +async function run() { try { - const token = deps.core.getInput('token'); + const token = core.getInput('token'); if (!token) { - deps.log('[github-api-usage-tracker] Skipping checkpoint snapshot due to missing token'); + 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(); + log('[github-api-usage-tracker] Fetching checkpoint rate limits...'); + const limits = await 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)}`); + log('[github-api-usage-tracker] Checkpoint Snapshot:'); + log('[github-api-usage-tracker] ---------------------'); + 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)); + core.saveState('checkpoint_time', String(Date.now())); + core.saveState('checkpoint_rate_limits', JSON.stringify(resources)); } catch (err) { - deps.core.warning(`[github-api-usage-tracker] Main step snapshot failed: ${err.message}`); + core.warning(`[github-api-usage-tracker] Main step snapshot failed: ${err.message}`); } } -if (require.main === module) { - run(); -} - -module.exports = { run }; +run(); diff --git a/src/post.js b/src/post.js index c84e387..9621777 100644 --- a/src/post.js +++ b/src/post.js @@ -30,44 +30,30 @@ 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, fsModule, pathModule) { +function maybeWrite(pathname, data) { if (!pathname) return; - const dir = pathModule.dirname(pathname); - if (dir && dir !== '.') fsModule.mkdirSync(dir, { recursive: true }); - fsModule.writeFileSync(pathname, JSON.stringify(data, null, 2)); + const dir = path.dirname(pathname); + if (dir && dir !== '.') fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync(pathname, JSON.stringify(data, null, 2)); } -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'); +async function run() { + if (core.getState('skip_post') === 'true') { + log('[github-api-usage-tracker] Skipping post step due to missing token'); return; } try { - const buckets = deps.parseBuckets(deps.core.getInput('buckets')); + const buckets = parseBuckets(core.getInput('buckets')); if (buckets.length === 0) { - deps.log('[github-api-usage-tracker] No valid buckets specified for tracking'); + log('[github-api-usage-tracker] No valid buckets specified for tracking'); return; } - const startingState = deps.core.getState('starting_rate_limits'); + const startingState = core.getState('starting_rate_limits'); if (!startingState) { - deps.core.error( + core.error( '[github-api-usage-tracker] No starting rate limit data found; skipping post step' ); return; @@ -76,32 +62,32 @@ async function run(overrides = {}) { try { startingResources = JSON.parse(startingState); } catch { - deps.core.error( + core.error( '[github-api-usage-tracker] Failed to parse starting rate limit data; skipping post step' ); return; } - const startTime = Number(deps.core.getState('start_time')); + const startTime = Number(core.getState('start_time')); const hasStartTime = Number.isFinite(startTime); if (!hasStartTime) { - deps.core.error( + 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'); + const checkpointState = core.getState('checkpoint_rate_limits'); let checkpointResources; let checkpointTimeSeconds = null; if (checkpointState) { try { checkpointResources = JSON.parse(checkpointState); } catch { - deps.core.warning( + 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')); + const checkpointTimeMs = Number(core.getState('checkpoint_time')); checkpointTimeSeconds = Number.isFinite(checkpointTimeMs) && checkpointTimeMs > 0 ? Math.floor(checkpointTimeMs / 1000) @@ -111,14 +97,14 @@ async function run(overrides = {}) { const endTimeSeconds = Math.floor(endTime / 1000); const duration = hasStartTime ? endTime - startTime : null; - deps.log('[github-api-usage-tracker] Fetching final rate limits...'); + log('[github-api-usage-tracker] Fetching final rate limits...'); - const endingLimits = await deps.fetchRateLimit(); + const endingLimits = await fetchRateLimit(); const endingResources = endingLimits.resources || {}; - 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)}`); + log('[github-api-usage-tracker] Final Snapshot:'); + log('[github-api-usage-tracker] -----------------'); + log(`[github-api-usage-tracker] ${JSON.stringify(endingResources, null, 2)}`); const data = {}; const crossedBuckets = []; @@ -129,20 +115,20 @@ async function run(overrides = {}) { const startingBucket = startingResources[bucket]; const endingBucket = endingResources[bucket]; if (!startingBucket) { - deps.core.warning( + core.warning( `[github-api-usage-tracker] Starting rate limit bucket "${bucket}" not found; skipping` ); continue; } if (!endingBucket) { - deps.core.warning( + core.warning( `[github-api-usage-tracker] Ending rate limit bucket "${bucket}" not found; skipping` ); continue; } const checkpointBucket = checkpointResources ? checkpointResources[bucket] : undefined; - const usage = deps.computeBucketUsage( + const usage = computeBucketUsage( startingBucket, endingBucket, endTimeSeconds, @@ -152,32 +138,32 @@ async function run(overrides = {}) { if (!usage.valid) { switch (usage.reason) { case 'invalid_remaining': - deps.core.warning( + core.warning( `[github-api-usage-tracker] Invalid remaining count for bucket "${bucket}"; skipping` ); break; case 'invalid_limit': - deps.core.warning( + core.warning( `[github-api-usage-tracker] Invalid limit for bucket "${bucket}" during reset crossing; skipping` ); break; case 'limit_changed_without_reset': - deps.core.warning( + core.warning( `[github-api-usage-tracker] Limit changed without reset for bucket "${bucket}"; skipping` ); break; case 'remaining_increased_without_reset': - deps.core.warning( + core.warning( `[github-api-usage-tracker] Remaining increased without reset for bucket "${bucket}"; skipping` ); break; case 'negative_usage': - deps.core.warning( + core.warning( `[github-api-usage-tracker] Negative usage for bucket "${bucket}" detected; skipping` ); break; default: - deps.core.warning( + core.warning( `[github-api-usage-tracker] Invalid usage data for bucket "${bucket}"; skipping` ); break; @@ -186,7 +172,7 @@ async function run(overrides = {}) { } if (usage.warnings.includes('limit_changed_across_reset')) { - deps.core.warning( + core.warning( `[github-api-usage-tracker] Limit changed across reset for bucket "${bucket}"; results may reflect a token change` ); } @@ -231,18 +217,18 @@ async function run(overrides = {}) { buckets_data: data, crossed_reset: totalIsMinimum }; - deps.core.setOutput('usage', JSON.stringify(output, null, 2)); + core.setOutput('usage', JSON.stringify(output, null, 2)); // Write JSON file if path specified - const outPath = (deps.core.getInput('output_path') || '').trim(); - maybeWrite(outPath, output, deps.fs, deps.path); + const outPath = (core.getInput('output_path') || '').trim(); + maybeWrite(outPath, output); - deps.log( + log( `[github-api-usage-tracker] Preparing summary table for ${Object.keys(data).length} bucket(s)` ); - const summary = deps.core.summary + const summary = core.summary .addHeading('GitHub API Usage Tracker Summary') - .addTable(deps.makeSummaryTable(data, { useMinimumHeader: totalIsMinimum })); + .addTable(makeSummaryTable(data, { useMinimumHeader: totalIsMinimum })); if (crossedBuckets.length > 0) { summary.addRaw( `

Reset Window Crossed: Yes (${crossedBuckets.join(', ')})

`, @@ -256,7 +242,7 @@ async function run(overrides = {}) { } summary.addRaw( `

Action Duration: ${ - hasStartTime ? deps.formatMs(duration) : 'Unknown (data missing)' + hasStartTime ? formatMs(duration) : 'Unknown (data missing)' }

`, true ); @@ -265,12 +251,8 @@ async function run(overrides = {}) { } summary.write(); } catch (err) { - deps.core.error(`[github-api-usage-tracker] Post step failed: ${err.message}`); + core.error(`[github-api-usage-tracker] Post step failed: ${err.message}`); } } -if (require.main === module) { - run(); -} - -module.exports = { run, maybeWrite }; +run(); diff --git a/src/pre.js b/src/pre.js index db0a327..aead8ad 100644 --- a/src/pre.js +++ b/src/pre.js @@ -2,42 +2,32 @@ const core = require('@actions/core'); const { fetchRateLimit } = require('./rate-limit'); const { log } = require('./log'); -async function run(overrides = {}) { - const deps = { - core, - fetchRateLimit, - log, - ...overrides - }; +async function run() { try { - const token = deps.core.getInput('token'); + const token = core.getInput('token'); if (!token) { - deps.core.error('GitHub token is required for API Usage Tracker'); - deps.core.saveState('skip_post', 'true'); + core.error('GitHub token is required for API Usage Tracker'); + core.saveState('skip_post', 'true'); return; } const startTime = Date.now(); - deps.core.saveState('start_time', String(startTime)); + core.saveState('start_time', String(startTime)); - deps.log('[github-api-usage-tracker] Fetching initial rate limits...'); + log('[github-api-usage-tracker] Fetching initial rate limits...'); - const limits = await deps.fetchRateLimit(); + const limits = await 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)}`); + log('[github-api-usage-tracker] Initial Snapshot:'); + log('[github-api-usage-tracker] -----------------'); + log(`[github-api-usage-tracker] ${JSON.stringify(resources, null, 2)}`); - deps.core.saveState('starting_rate_limits', JSON.stringify(resources)); + core.saveState('starting_rate_limits', JSON.stringify(resources)); } catch (err) { - deps.core.warning(`Pre step failed: ${err.message}`); + core.warning(`Pre step failed: ${err.message}`); } } -if (require.main === module) { - run(); -} - -module.exports = { run }; +run(); diff --git a/tests/checkpoint.test.mjs b/tests/checkpoint.test.mjs deleted file mode 100644 index c32d8a7..0000000 --- a/tests/checkpoint.test.mjs +++ /dev/null @@ -1,66 +0,0 @@ -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 fd23173..0a3b754 100644 --- a/tests/post-utils.test.mjs +++ b/tests/post-utils.test.mjs @@ -59,41 +59,6 @@ describe('post utils', () => { ] ]); }); - - 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' } - ]); - }); }); describe('computeBucketUsage', () => { @@ -164,42 +129,6 @@ describe('computeBucketUsage', () => { }); }); - 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 }, @@ -232,68 +161,4 @@ 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 deleted file mode 100644 index 8e5b0c8..0000000 --- a/tests/post.test.mjs +++ /dev/null @@ -1,327 +0,0 @@ -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 deleted file mode 100644 index c8289ea..0000000 --- a/tests/pre.test.mjs +++ /dev/null @@ -1,64 +0,0 @@ -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 6e8e47c..6af926a 100644 --- a/tests/rate-limit.test.mjs +++ b/tests/rate-limit.test.mjs @@ -5,7 +5,8 @@ import { createRequire } from 'module'; const require = createRequire(import.meta.url); const https = require('https'); -const { fetchRateLimit } = require('../src/rate-limit.js'); +const rateLimitModule = await import('../src/rate-limit.js'); +const { fetchRateLimit } = rateLimitModule.default ?? rateLimitModule; const originalToken = process.env.INPUT_TOKEN; let stdoutSpy; @@ -108,17 +109,4 @@ 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'); - }); }); From 82a880889bc3441f432d551c36c264d972abbf08 Mon Sep 17 00:00:00 2001 From: Really Him Date: Fri, 23 Jan 2026 22:25:27 -0500 Subject: [PATCH 05/15] refactor: refactor logic in post-utils --- src/README.md | 19 ++++ src/post-utils.js | 27 +++++- src/post.js | 40 ++------- tests/post-utils.test.mjs | 179 +++++++++++++++++++++++++++++++++++++- 4 files changed, 230 insertions(+), 35 deletions(-) diff --git a/src/README.md b/src/README.md index d30adbe..565687b 100644 --- a/src/README.md +++ b/src/README.md @@ -30,3 +30,22 @@ module.exports = { run }; This structure mirrors common patterns in other languages (e.g., Python's `if __name__ == '__main__':` at the bottom) and provides a clear "stop reading here" signal for anyone reviewing the logic. **Note:** Pure utility modules like `post-utils.js` and `log.js` do not use this pattern since they have no external dependencies and can be tested directly. + +--- + +## Update: Pattern Reverted (2026-01-23) + +The dependency injection pattern described above was reverted. Investigation revealed: + +1. **Root cause**: Vitest's `vi.mock()` does not intercept `require()` calls in CommonJS modules. The DI pattern was a workaround for this Vitest limitation. + +2. **Coverage before DI**: 36% (entry points at 0%) +3. **Coverage after DI**: 99% + +However, the 0% coverage on entry points was partly because we hadn't attempted to test them via other means. The new approach: + +- **Extract pure logic** into utility modules (`post-utils.js`) where it can be tested directly +- **Keep entry points thin** - just orchestration/wiring +- **Integration test** entry points via `act` (local GitHub Actions runner) if needed + +This follows the "functional core, imperative shell" pattern and avoids shaping production code around testing infrastructure limitations. diff --git a/src/post-utils.js b/src/post-utils.js index a42b01f..8ea0474 100644 --- a/src/post-utils.js +++ b/src/post-utils.js @@ -141,4 +141,29 @@ function computeBucketUsage( return result; } -module.exports = { formatMs, makeSummaryTable, computeBucketUsage }; +/** + * Returns a warning message for invalid bucket usage. + * + * @param {string} reason - the reason code from computeBucketUsage. + * @param {string} bucket - the bucket name. + * @returns {string} - formatted warning message. + */ +function getUsageWarningMessage(reason, bucket) { + const prefix = '[github-api-usage-tracker]'; + switch (reason) { + case 'invalid_remaining': + return `${prefix} Invalid remaining count for bucket "${bucket}"; skipping`; + case 'invalid_limit': + return `${prefix} Invalid limit for bucket "${bucket}" during reset crossing; skipping`; + case 'limit_changed_without_reset': + return `${prefix} Limit changed without reset for bucket "${bucket}"; skipping`; + case 'remaining_increased_without_reset': + return `${prefix} Remaining increased without reset for bucket "${bucket}"; skipping`; + case 'negative_usage': + return `${prefix} Negative usage for bucket "${bucket}" detected; skipping`; + default: + return `${prefix} Invalid usage data for bucket "${bucket}"; skipping`; + } +} + +module.exports = { formatMs, makeSummaryTable, computeBucketUsage, getUsageWarningMessage }; diff --git a/src/post.js b/src/post.js index 9621777..e742141 100644 --- a/src/post.js +++ b/src/post.js @@ -23,7 +23,12 @@ const fs = require('fs'); const path = require('path'); const { fetchRateLimit } = require('./rate-limit'); const { log, parseBuckets } = require('./log'); -const { formatMs, makeSummaryTable, computeBucketUsage } = require('./post-utils'); +const { + formatMs, + makeSummaryTable, + computeBucketUsage, + getUsageWarningMessage +} = require('./post-utils'); /** * Writes JSON-stringified data to a file if a valid pathname is provided. @@ -136,38 +141,7 @@ async function run() { checkpointTimeSeconds ); if (!usage.valid) { - switch (usage.reason) { - case 'invalid_remaining': - core.warning( - `[github-api-usage-tracker] Invalid remaining count for bucket "${bucket}"; skipping` - ); - break; - case 'invalid_limit': - core.warning( - `[github-api-usage-tracker] Invalid limit for bucket "${bucket}" during reset crossing; skipping` - ); - break; - case 'limit_changed_without_reset': - core.warning( - `[github-api-usage-tracker] Limit changed without reset for bucket "${bucket}"; skipping` - ); - break; - case 'remaining_increased_without_reset': - core.warning( - `[github-api-usage-tracker] Remaining increased without reset for bucket "${bucket}"; skipping` - ); - break; - case 'negative_usage': - core.warning( - `[github-api-usage-tracker] Negative usage for bucket "${bucket}" detected; skipping` - ); - break; - default: - core.warning( - `[github-api-usage-tracker] Invalid usage data for bucket "${bucket}"; skipping` - ); - break; - } + core.warning(getUsageWarningMessage(usage.reason, bucket)); continue; } diff --git a/tests/post-utils.test.mjs b/tests/post-utils.test.mjs index 0a3b754..3f0ad6e 100644 --- a/tests/post-utils.test.mjs +++ b/tests/post-utils.test.mjs @@ -2,7 +2,12 @@ import { describe, it, expect } from 'vitest'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); -const { formatMs, makeSummaryTable, computeBucketUsage } = require('../src/post-utils.js'); +const { + formatMs, + makeSummaryTable, + computeBucketUsage, + getUsageWarningMessage +} = require('../src/post-utils.js'); describe('post utils', () => { it('formats sub-minute durations in seconds', () => { @@ -59,9 +64,106 @@ describe('post utils', () => { ] ]); }); + + it('uses minimum header when option is set', () => { + const table = makeSummaryTable( + { core: { used: { start: 1, end: 2, total: 1 }, remaining: { start: 10, end: 9 } } }, + { useMinimumHeader: true } + ); + expect(table[0][5]).toEqual({ data: 'Used (Minimum)', header: true }); + }); + + it('formats non-numeric values as n/a', () => { + const table = makeSummaryTable({ + core: { + used: { start: null, end: undefined, total: NaN }, + remaining: { start: undefined, end: null } + } + }); + 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 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' } + ]); + }); }); describe('computeBucketUsage', () => { + it('returns missing_bucket error when starting bucket is null', () => { + 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('returns invalid_remaining error for non-numeric remaining', () => { + 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('returns invalid_limit error when crossed reset with invalid limits', () => { + 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('returns negative_usage error when usage is negative across reset', () => { + 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' + }); + }); + it('computes usage within the same window', () => { const result = computeBucketUsage( { limit: 1000, remaining: 900, reset: 1600 }, @@ -129,6 +231,40 @@ describe('computeBucketUsage', () => { }); }); + it('ignores checkpoint when remaining is non-numeric', () => { + 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 }, @@ -162,3 +298,44 @@ describe('computeBucketUsage', () => { }); }); }); + +describe('getUsageWarningMessage', () => { + const bucket = 'core'; + const prefix = '[github-api-usage-tracker]'; + + it('returns message for invalid_remaining', () => { + expect(getUsageWarningMessage('invalid_remaining', bucket)).toBe( + `${prefix} Invalid remaining count for bucket "core"; skipping` + ); + }); + + it('returns message for invalid_limit', () => { + expect(getUsageWarningMessage('invalid_limit', bucket)).toBe( + `${prefix} Invalid limit for bucket "core" during reset crossing; skipping` + ); + }); + + it('returns message for limit_changed_without_reset', () => { + expect(getUsageWarningMessage('limit_changed_without_reset', bucket)).toBe( + `${prefix} Limit changed without reset for bucket "core"; skipping` + ); + }); + + it('returns message for remaining_increased_without_reset', () => { + expect(getUsageWarningMessage('remaining_increased_without_reset', bucket)).toBe( + `${prefix} Remaining increased without reset for bucket "core"; skipping` + ); + }); + + it('returns message for negative_usage', () => { + expect(getUsageWarningMessage('negative_usage', bucket)).toBe( + `${prefix} Negative usage for bucket "core" detected; skipping` + ); + }); + + it('returns default message for unknown reason', () => { + expect(getUsageWarningMessage('unknown_reason', bucket)).toBe( + `${prefix} Invalid usage data for bucket "core"; skipping` + ); + }); +}); From 6964e4f5d64b930726cd39ddeb5a7a665042ddb8 Mon Sep 17 00:00:00 2001 From: Really Him Date: Fri, 23 Jan 2026 22:45:38 -0500 Subject: [PATCH 06/15] refactor: simplify logic for missing token --- src/checkpoint.js | 10 ++- src/post-utils.js | 45 +++++++++++- src/post.js | 139 +++++++++----------------------------- src/pre.js | 2 +- tests/post-utils.test.mjs | 29 +++++++- 5 files changed, 110 insertions(+), 115 deletions(-) diff --git a/src/checkpoint.js b/src/checkpoint.js index 6fa2e40..a78cb10 100644 --- a/src/checkpoint.js +++ b/src/checkpoint.js @@ -3,13 +3,11 @@ const { fetchRateLimit } = require('./rate-limit'); const { log } = require('./log'); async function run() { + if (core.getState('skip_rest') === 'true') { + log('[github-api-usage-tracker] Skipping checkpoint step'); + return; + } try { - const token = core.getInput('token'); - if (!token) { - log('[github-api-usage-tracker] Skipping checkpoint snapshot due to missing token'); - return; - } - log('[github-api-usage-tracker] Fetching checkpoint rate limits...'); const limits = await fetchRateLimit(); const resources = limits.resources || {}; diff --git a/src/post-utils.js b/src/post-utils.js index 8ea0474..2b58c2e 100644 --- a/src/post-utils.js +++ b/src/post-utils.js @@ -166,4 +166,47 @@ function getUsageWarningMessage(reason, bucket) { } } -module.exports = { formatMs, makeSummaryTable, computeBucketUsage, getUsageWarningMessage }; +/** + * Builds the data object for a single bucket from snapshots and computed usage. + * + * @param {object} startingBucket - bucket from the pre snapshot. + * @param {object} endingBucket - bucket from the post snapshot. + * @param {object} usage - computed usage from computeBucketUsage. + * @returns {object} - bucket data with used/remaining info. + */ +function buildBucketData(startingBucket, endingBucket, usage) { + 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; + + return { + 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 + }; +} + +module.exports = { + formatMs, + makeSummaryTable, + computeBucketUsage, + getUsageWarningMessage, + buildBucketData +}; diff --git a/src/post.js b/src/post.js index e742141..c2677b0 100644 --- a/src/post.js +++ b/src/post.js @@ -1,23 +1,3 @@ -/** - * Retrieves a numeric state value from the GitHub Actions state. - * - * @param {string} key - The state key to retrieve. - * @returns {number|undefined} - The numeric value if valid and finite, otherwise undefined. - */ - -/** - * Writes a summary table of API resource usage to the GitHub Actions summary. - * - * @param {Object.} resources - Object mapping bucket names to usage info. - */ - -/** - * Main post-action function that calculates and reports GitHub API usage. - * Fetches final rate limits, compares with starting values, and outputs usage data. - * - * @async - * @returns {Promise} - */ const core = require('@actions/core'); const fs = require('fs'); const path = require('path'); @@ -27,14 +7,12 @@ const { formatMs, makeSummaryTable, computeBucketUsage, - getUsageWarningMessage + getUsageWarningMessage, + buildBucketData } = require('./post-utils'); /** * Writes JSON-stringified data to a file if a valid pathname is provided. - * - * @param {string} pathname - file path to write to. - * @param {object} data - data to write. */ function maybeWrite(pathname, data) { if (!pathname) return; @@ -44,73 +22,49 @@ function maybeWrite(pathname, data) { } async function run() { - if (core.getState('skip_post') === 'true') { - log('[github-api-usage-tracker] Skipping post step due to missing token'); + if (core.getState('skip_rest') === 'true') { + log('[github-api-usage-tracker] Skipping post step'); return; } try { const buckets = parseBuckets(core.getInput('buckets')); - if (buckets.length === 0) { log('[github-api-usage-tracker] No valid buckets specified for tracking'); return; } + // Get starting state (saved by pre.js) const startingState = core.getState('starting_rate_limits'); if (!startingState) { - core.error( - '[github-api-usage-tracker] No starting rate limit data found; skipping post step' - ); - return; - } - let startingResources; - try { - startingResources = JSON.parse(startingState); - } catch { - core.error( - '[github-api-usage-tracker] Failed to parse starting rate limit data; skipping post step' - ); + core.error('[github-api-usage-tracker] No starting rate limit data found; skipping'); return; } + const startingResources = JSON.parse(startingState); const startTime = Number(core.getState('start_time')); const hasStartTime = Number.isFinite(startTime); - if (!hasStartTime) { - core.error( - '[github-api-usage-tracker] Invalid or missing start time; duration will be reported as unknown' - ); - } - const checkpointState = core.getState('checkpoint_rate_limits'); - let checkpointResources; - let checkpointTimeSeconds = null; - if (checkpointState) { - try { - checkpointResources = JSON.parse(checkpointState); - } catch { - core.warning( - '[github-api-usage-tracker] Failed to parse checkpoint rate limit data; ignoring checkpoint snapshot' - ); - } - } - if (checkpointResources) { - const checkpointTimeMs = Number(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; + // Get checkpoint state if available (saved by checkpoint.js) + const checkpointState = core.getState('checkpoint_rate_limits'); + const checkpointResources = checkpointState ? JSON.parse(checkpointState) : null; + const checkpointTimeMs = checkpointResources ? Number(core.getState('checkpoint_time')) : null; + const checkpointTimeSeconds = + Number.isFinite(checkpointTimeMs) && checkpointTimeMs > 0 + ? Math.floor(checkpointTimeMs / 1000) + : null; + + // Fetch final rate limits log('[github-api-usage-tracker] Fetching final rate limits...'); - const endingLimits = await fetchRateLimit(); const endingResources = endingLimits.resources || {}; + const endTime = Date.now(); + const endTimeSeconds = Math.floor(endTime / 1000); + const duration = hasStartTime ? endTime - startTime : null; log('[github-api-usage-tracker] Final Snapshot:'); log('[github-api-usage-tracker] -----------------'); log(`[github-api-usage-tracker] ${JSON.stringify(endingResources, null, 2)}`); + // Process each bucket const data = {}; const crossedBuckets = []; let totalUsed = 0; @@ -119,16 +73,13 @@ async function run() { for (const bucket of buckets) { const startingBucket = startingResources[bucket]; const endingBucket = endingResources[bucket]; + if (!startingBucket) { - core.warning( - `[github-api-usage-tracker] Starting rate limit bucket "${bucket}" not found; skipping` - ); + core.warning(`[github-api-usage-tracker] Starting bucket "${bucket}" not found; skipping`); continue; } if (!endingBucket) { - core.warning( - `[github-api-usage-tracker] Ending rate limit bucket "${bucket}" not found; skipping` - ); + core.warning(`[github-api-usage-tracker] Ending bucket "${bucket}" not found; skipping`); continue; } @@ -140,6 +91,7 @@ async function run() { checkpointBucket, checkpointTimeSeconds ); + if (!usage.valid) { core.warning(getUsageWarningMessage(usage.reason, bucket)); continue; @@ -151,34 +103,9 @@ async function run() { ); } - 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: { - 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 - }; + data[bucket] = buildBucketData(startingBucket, endingBucket, usage); if (usage.crossed_reset) { crossedBuckets.push(bucket); - } - if (usage.crossed_reset) { totalIsMinimum = true; } totalUsed += usage.used; @@ -197,32 +124,32 @@ async function run() { const outPath = (core.getInput('output_path') || '').trim(); maybeWrite(outPath, output); + // Build summary log( `[github-api-usage-tracker] Preparing summary table for ${Object.keys(data).length} bucket(s)` ); const summary = core.summary .addHeading('GitHub API Usage Tracker Summary') .addTable(makeSummaryTable(data, { useMinimumHeader: totalIsMinimum })); + if (crossedBuckets.length > 0) { summary.addRaw( `

Reset Window Crossed: Yes (${crossedBuckets.join(', ')})

`, true ); summary.addRaw( - '

Total Usage: Total usage cannot be computed - usage reset window was crossed.

', + '

Total Usage: Cannot be computed - reset window was crossed.

', true ); summary.addRaw(`

Minimum API Calls/Points Used: ${totalUsed}

`, true); + } else { + summary.addRaw(`

Total API Calls/Points Used: ${totalUsed}

`, true); } + summary.addRaw( - `

Action Duration: ${ - hasStartTime ? formatMs(duration) : 'Unknown (data missing)' - }

`, + `

Action Duration: ${hasStartTime ? formatMs(duration) : 'Unknown'}

`, 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}`); diff --git a/src/pre.js b/src/pre.js index aead8ad..f88c238 100644 --- a/src/pre.js +++ b/src/pre.js @@ -8,7 +8,7 @@ async function run() { if (!token) { core.error('GitHub token is required for API Usage Tracker'); - core.saveState('skip_post', 'true'); + core.saveState('skip_rest', 'true'); return; } diff --git a/tests/post-utils.test.mjs b/tests/post-utils.test.mjs index 3f0ad6e..f5432a4 100644 --- a/tests/post-utils.test.mjs +++ b/tests/post-utils.test.mjs @@ -6,7 +6,8 @@ const { formatMs, makeSummaryTable, computeBucketUsage, - getUsageWarningMessage + getUsageWarningMessage, + buildBucketData } = require('../src/post-utils.js'); describe('post utils', () => { @@ -339,3 +340,29 @@ describe('getUsageWarningMessage', () => { ); }); }); + +describe('buildBucketData', () => { + it('builds data object with valid numeric values', () => { + const startingBucket = { limit: 1000, remaining: 900 }; + const endingBucket = { limit: 1000, remaining: 850 }; + const usage = { used: 50, crossed_reset: false }; + + expect(buildBucketData(startingBucket, endingBucket, usage)).toEqual({ + used: { start: 100, end: 150, total: 50 }, + remaining: { start: 900, end: 850 }, + crossed_reset: false + }); + }); + + it('returns null for non-numeric values', () => { + const startingBucket = { limit: 'invalid', remaining: 900 }; + const endingBucket = { limit: 1000, remaining: 'bad' }; + const usage = { used: 50, crossed_reset: true }; + + expect(buildBucketData(startingBucket, endingBucket, usage)).toEqual({ + used: { start: null, end: null, total: 50 }, + remaining: { start: 900, end: null }, + crossed_reset: true + }); + }); +}); From 94037daed3ecb1ebbe55802d23d45324d2cd7cb6 Mon Sep 17 00:00:00 2001 From: Really Him Date: Fri, 23 Jan 2026 22:54:22 -0500 Subject: [PATCH 07/15] refactor: refactor post for complexity --- src/post-utils.js | 37 +++++++++++++++++++++++++++++- src/post.js | 32 ++++++-------------------- tests/post-utils.test.mjs | 48 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 90 insertions(+), 27 deletions(-) diff --git a/src/post-utils.js b/src/post-utils.js index 2b58c2e..cbe849c 100644 --- a/src/post-utils.js +++ b/src/post-utils.js @@ -203,10 +203,45 @@ function buildBucketData(startingBucket, endingBucket, usage) { }; } +/** + * Builds the summary content object for the job summary. + * + * @param {object} data - bucket data keyed by bucket name. + * @param {string[]} crossedBuckets - list of buckets that crossed reset. + * @param {number} totalUsed - total API calls/points used. + * @param {number|null} duration - action duration in milliseconds. + * @returns {object} - summary content with table and HTML sections. + */ +function buildSummaryContent(data, crossedBuckets, totalUsed, duration) { + const totalIsMinimum = crossedBuckets.length > 0; + const table = makeSummaryTable(data, { useMinimumHeader: totalIsMinimum }); + + const sections = []; + + if (totalIsMinimum) { + sections.push( + `

Reset Window Crossed: Yes (${crossedBuckets.join(', ')})

` + ); + sections.push( + '

Total Usage: Cannot be computed - reset window was crossed.

' + ); + sections.push(`

Minimum API Calls/Points Used: ${totalUsed}

`); + } else { + sections.push(`

Total API Calls/Points Used: ${totalUsed}

`); + } + + sections.push( + `

Action Duration: ${duration !== null ? formatMs(duration) : 'Unknown'}

` + ); + + return { table, sections }; +} + module.exports = { formatMs, makeSummaryTable, computeBucketUsage, getUsageWarningMessage, - buildBucketData + buildBucketData, + buildSummaryContent }; diff --git a/src/post.js b/src/post.js index c2677b0..057cb52 100644 --- a/src/post.js +++ b/src/post.js @@ -4,11 +4,10 @@ const path = require('path'); const { fetchRateLimit } = require('./rate-limit'); const { log, parseBuckets } = require('./log'); const { - formatMs, - makeSummaryTable, computeBucketUsage, getUsageWarningMessage, - buildBucketData + buildBucketData, + buildSummaryContent } = require('./post-utils'); /** @@ -68,7 +67,6 @@ async function run() { const data = {}; const crossedBuckets = []; let totalUsed = 0; - let totalIsMinimum = false; for (const bucket of buckets) { const startingBucket = startingResources[bucket]; @@ -106,7 +104,6 @@ async function run() { data[bucket] = buildBucketData(startingBucket, endingBucket, usage); if (usage.crossed_reset) { crossedBuckets.push(bucket); - totalIsMinimum = true; } totalUsed += usage.used; } @@ -116,7 +113,7 @@ async function run() { total: totalUsed, duration_ms: duration, buckets_data: data, - crossed_reset: totalIsMinimum + crossed_reset: crossedBuckets.length > 0 }; core.setOutput('usage', JSON.stringify(output, null, 2)); @@ -128,28 +125,13 @@ async function run() { log( `[github-api-usage-tracker] Preparing summary table for ${Object.keys(data).length} bucket(s)` ); + const summaryContent = buildSummaryContent(data, crossedBuckets, totalUsed, duration); const summary = core.summary .addHeading('GitHub API Usage Tracker Summary') - .addTable(makeSummaryTable(data, { useMinimumHeader: totalIsMinimum })); - - if (crossedBuckets.length > 0) { - summary.addRaw( - `

Reset Window Crossed: Yes (${crossedBuckets.join(', ')})

`, - true - ); - summary.addRaw( - '

Total Usage: Cannot be computed - reset window was crossed.

', - true - ); - summary.addRaw(`

Minimum API Calls/Points Used: ${totalUsed}

`, true); - } else { - summary.addRaw(`

Total API Calls/Points Used: ${totalUsed}

`, true); + .addTable(summaryContent.table); + for (const section of summaryContent.sections) { + summary.addRaw(section, true); } - - summary.addRaw( - `

Action Duration: ${hasStartTime ? formatMs(duration) : 'Unknown'}

`, - true - ); summary.write(); } catch (err) { core.error(`[github-api-usage-tracker] Post step failed: ${err.message}`); diff --git a/tests/post-utils.test.mjs b/tests/post-utils.test.mjs index f5432a4..c14dfb2 100644 --- a/tests/post-utils.test.mjs +++ b/tests/post-utils.test.mjs @@ -7,7 +7,8 @@ const { makeSummaryTable, computeBucketUsage, getUsageWarningMessage, - buildBucketData + buildBucketData, + buildSummaryContent } = require('../src/post-utils.js'); describe('post utils', () => { @@ -366,3 +367,48 @@ describe('buildBucketData', () => { }); }); }); + +describe('buildSummaryContent', () => { + it('builds summary without reset crossing', () => { + const data = { + core: { + used: { start: 100, end: 150, total: 50 }, + remaining: { start: 900, end: 850 } + } + }; + const result = buildSummaryContent(data, [], 50, 60000); + + expect(result.table).toEqual(makeSummaryTable(data, { useMinimumHeader: false })); + expect(result.sections).toHaveLength(2); + expect(result.sections[0]).toBe('

Total API Calls/Points Used: 50

'); + expect(result.sections[1]).toBe('

Action Duration: 1m 0s

'); + }); + + it('builds summary with reset crossing', () => { + const data = { + core: { used: { start: 100, end: 50, total: 150 }, remaining: { start: 900, end: 950 } } + }; + const result = buildSummaryContent(data, ['core'], 150, 120000); + + expect(result.table).toEqual(makeSummaryTable(data, { useMinimumHeader: true })); + expect(result.sections).toHaveLength(4); + expect(result.sections[0]).toBe('

Reset Window Crossed: Yes (core)

'); + expect(result.sections[1]).toBe( + '

Total Usage: Cannot be computed - reset window was crossed.

' + ); + expect(result.sections[2]).toBe('

Minimum API Calls/Points Used: 150

'); + expect(result.sections[3]).toBe('

Action Duration: 2m 0s

'); + }); + + it('shows unknown duration when null', () => { + const result = buildSummaryContent({}, [], 0, null); + expect(result.sections[1]).toBe('

Action Duration: Unknown

'); + }); + + it('lists multiple crossed buckets', () => { + const result = buildSummaryContent({}, ['core', 'search'], 100, 1000); + expect(result.sections[0]).toBe( + '

Reset Window Crossed: Yes (core, search)

' + ); + }); +}); From 82e00cbdda4299f0dcd456b17579ea15e72401de Mon Sep 17 00:00:00 2001 From: Really Him Date: Fri, 23 Jan 2026 23:07:10 -0500 Subject: [PATCH 08/15] refactor: use log, warning, error utils --- src/checkpoint.js | 14 +++++++------- src/log.js | 31 +++++++++++++++++++++++++++---- src/post-utils.js | 15 +++++++-------- src/post.js | 32 +++++++++++++++----------------- src/pre.js | 14 +++++++------- tests/log.test.mjs | 4 ++-- tests/post-utils.test.mjs | 13 ++++++------- 7 files changed, 71 insertions(+), 52 deletions(-) diff --git a/src/checkpoint.js b/src/checkpoint.js index a78cb10..b72fb98 100644 --- a/src/checkpoint.js +++ b/src/checkpoint.js @@ -1,25 +1,25 @@ const core = require('@actions/core'); const { fetchRateLimit } = require('./rate-limit'); -const { log } = require('./log'); +const { log, warn } = require('./log'); async function run() { if (core.getState('skip_rest') === 'true') { - log('[github-api-usage-tracker] Skipping checkpoint step'); + log('Skipping checkpoint step'); return; } try { - log('[github-api-usage-tracker] Fetching checkpoint rate limits...'); + log('Fetching checkpoint rate limits...'); const limits = await fetchRateLimit(); const resources = limits.resources || {}; - log('[github-api-usage-tracker] Checkpoint Snapshot:'); - log('[github-api-usage-tracker] ---------------------'); - log(`[github-api-usage-tracker] ${JSON.stringify(resources, null, 2)}`); + log('Checkpoint Snapshot:'); + log('---------------------'); + log(JSON.stringify(resources, null, 2)); core.saveState('checkpoint_time', String(Date.now())); core.saveState('checkpoint_rate_limits', JSON.stringify(resources)); } catch (err) { - core.warning(`[github-api-usage-tracker] Main step snapshot failed: ${err.message}`); + warn(`Main step snapshot failed: ${err.message}`); } } diff --git a/src/log.js b/src/log.js index 9717134..a273a46 100644 --- a/src/log.js +++ b/src/log.js @@ -1,5 +1,10 @@ const core = require('@actions/core'); +/** + * Prefix for all log messages. + */ +const PREFIX = '[github-api-usage-tracker]'; + /** * List of valid GitHub API rate limit buckets. */ @@ -17,12 +22,30 @@ const VALID_BUCKETS = [ ]; /** - * Logs a message using GitHub Actions debug logging. + * Logs a debug message with prefix. * * @param {string} message - message to log. */ function log(message) { - core.debug(message); + core.debug(`${PREFIX} ${message}`); +} + +/** + * Logs a warning message with prefix. + * + * @param {string} message - message to log. + */ +function warn(message) { + core.warning(`${PREFIX} ${message}`); +} + +/** + * Logs an error message with prefix. + * + * @param {string} message - message to log. + */ +function error(message) { + core.error(`${PREFIX} ${message}`); } /** @@ -48,11 +71,11 @@ function parseBuckets(raw) { } } if (invalidBuckets.length > 0) { - core.warning( + warn( `Invalid bucket(s) selected: ${invalidBuckets.join(', ')}, valid options are: ${VALID_BUCKETS.join(', ')}` ); } return buckets; } -module.exports = { log, parseBuckets, VALID_BUCKETS }; +module.exports = { PREFIX, log, warn, error, parseBuckets, VALID_BUCKETS }; diff --git a/src/post-utils.js b/src/post-utils.js index cbe849c..d5b505a 100644 --- a/src/post-utils.js +++ b/src/post-utils.js @@ -142,27 +142,26 @@ function computeBucketUsage( } /** - * Returns a warning message for invalid bucket usage. + * Returns a warning message for invalid bucket usage (without prefix). * * @param {string} reason - the reason code from computeBucketUsage. * @param {string} bucket - the bucket name. * @returns {string} - formatted warning message. */ function getUsageWarningMessage(reason, bucket) { - const prefix = '[github-api-usage-tracker]'; switch (reason) { case 'invalid_remaining': - return `${prefix} Invalid remaining count for bucket "${bucket}"; skipping`; + return `Invalid remaining count for bucket "${bucket}"; skipping`; case 'invalid_limit': - return `${prefix} Invalid limit for bucket "${bucket}" during reset crossing; skipping`; + return `Invalid limit for bucket "${bucket}" during reset crossing; skipping`; case 'limit_changed_without_reset': - return `${prefix} Limit changed without reset for bucket "${bucket}"; skipping`; + return `Limit changed without reset for bucket "${bucket}"; skipping`; case 'remaining_increased_without_reset': - return `${prefix} Remaining increased without reset for bucket "${bucket}"; skipping`; + return `Remaining increased without reset for bucket "${bucket}"; skipping`; case 'negative_usage': - return `${prefix} Negative usage for bucket "${bucket}" detected; skipping`; + return `Negative usage for bucket "${bucket}" detected; skipping`; default: - return `${prefix} Invalid usage data for bucket "${bucket}"; skipping`; + return `Invalid usage data for bucket "${bucket}"; skipping`; } } diff --git a/src/post.js b/src/post.js index 057cb52..3d21172 100644 --- a/src/post.js +++ b/src/post.js @@ -2,7 +2,7 @@ const core = require('@actions/core'); const fs = require('fs'); const path = require('path'); const { fetchRateLimit } = require('./rate-limit'); -const { log, parseBuckets } = require('./log'); +const { log, warn, error, parseBuckets } = require('./log'); const { computeBucketUsage, getUsageWarningMessage, @@ -22,20 +22,20 @@ function maybeWrite(pathname, data) { async function run() { if (core.getState('skip_rest') === 'true') { - log('[github-api-usage-tracker] Skipping post step'); + log('Skipping post step'); return; } try { const buckets = parseBuckets(core.getInput('buckets')); if (buckets.length === 0) { - log('[github-api-usage-tracker] No valid buckets specified for tracking'); + log('No valid buckets specified for tracking'); return; } // Get starting state (saved by pre.js) const startingState = core.getState('starting_rate_limits'); if (!startingState) { - core.error('[github-api-usage-tracker] No starting rate limit data found; skipping'); + error('No starting rate limit data found; skipping'); return; } const startingResources = JSON.parse(startingState); @@ -52,16 +52,16 @@ async function run() { : null; // Fetch final rate limits - log('[github-api-usage-tracker] Fetching final rate limits...'); + log('Fetching final rate limits...'); const endingLimits = await fetchRateLimit(); const endingResources = endingLimits.resources || {}; const endTime = Date.now(); const endTimeSeconds = Math.floor(endTime / 1000); const duration = hasStartTime ? endTime - startTime : null; - log('[github-api-usage-tracker] Final Snapshot:'); - log('[github-api-usage-tracker] -----------------'); - log(`[github-api-usage-tracker] ${JSON.stringify(endingResources, null, 2)}`); + log('Final Snapshot:'); + log('-----------------'); + log(JSON.stringify(endingResources, null, 2)); // Process each bucket const data = {}; @@ -73,11 +73,11 @@ async function run() { const endingBucket = endingResources[bucket]; if (!startingBucket) { - core.warning(`[github-api-usage-tracker] Starting bucket "${bucket}" not found; skipping`); + warn(`Starting bucket "${bucket}" not found; skipping`); continue; } if (!endingBucket) { - core.warning(`[github-api-usage-tracker] Ending bucket "${bucket}" not found; skipping`); + warn(`Ending bucket "${bucket}" not found; skipping`); continue; } @@ -91,13 +91,13 @@ async function run() { ); if (!usage.valid) { - core.warning(getUsageWarningMessage(usage.reason, bucket)); + warn(getUsageWarningMessage(usage.reason, bucket)); continue; } if (usage.warnings.includes('limit_changed_across_reset')) { - core.warning( - `[github-api-usage-tracker] Limit changed across reset for bucket "${bucket}"; results may reflect a token change` + warn( + `Limit changed across reset for bucket "${bucket}"; results may reflect a token change` ); } @@ -122,9 +122,7 @@ async function run() { maybeWrite(outPath, output); // Build summary - log( - `[github-api-usage-tracker] Preparing summary table for ${Object.keys(data).length} bucket(s)` - ); + log(`Preparing summary table for ${Object.keys(data).length} bucket(s)`); const summaryContent = buildSummaryContent(data, crossedBuckets, totalUsed, duration); const summary = core.summary .addHeading('GitHub API Usage Tracker Summary') @@ -134,7 +132,7 @@ async function run() { } summary.write(); } catch (err) { - core.error(`[github-api-usage-tracker] Post step failed: ${err.message}`); + error(`Post step failed: ${err.message}`); } } diff --git a/src/pre.js b/src/pre.js index f88c238..a1859e3 100644 --- a/src/pre.js +++ b/src/pre.js @@ -1,13 +1,13 @@ const core = require('@actions/core'); const { fetchRateLimit } = require('./rate-limit'); -const { log } = require('./log'); +const { log, warn, error } = require('./log'); async function run() { try { const token = core.getInput('token'); if (!token) { - core.error('GitHub token is required for API Usage Tracker'); + error('GitHub token is required for API Usage Tracker'); core.saveState('skip_rest', 'true'); return; } @@ -15,18 +15,18 @@ async function run() { const startTime = Date.now(); core.saveState('start_time', String(startTime)); - log('[github-api-usage-tracker] Fetching initial rate limits...'); + log('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)}`); + log('Initial Snapshot:'); + log('-----------------'); + log(JSON.stringify(resources, null, 2)); core.saveState('starting_rate_limits', JSON.stringify(resources)); } catch (err) { - core.warning(`Pre step failed: ${err.message}`); + warn(`Pre step failed: ${err.message}`); } } diff --git a/tests/log.test.mjs b/tests/log.test.mjs index ba9a8ba..b5753f7 100644 --- a/tests/log.test.mjs +++ b/tests/log.test.mjs @@ -18,10 +18,10 @@ describe('log helpers', () => { stdoutSpy.mockRestore(); }); - it('logs using core.debug', () => { + it('logs using core.debug with prefix', () => { log('hello'); const output = stdoutSpy.mock.calls.map((call) => String(call[0])).join(''); - expect(output).toContain('::debug::hello'); + expect(output).toContain('::debug::[github-api-usage-tracker] hello'); }); it('parses valid buckets and warns on invalid ones', () => { diff --git a/tests/post-utils.test.mjs b/tests/post-utils.test.mjs index c14dfb2..69e7dfa 100644 --- a/tests/post-utils.test.mjs +++ b/tests/post-utils.test.mjs @@ -303,41 +303,40 @@ describe('computeBucketUsage', () => { describe('getUsageWarningMessage', () => { const bucket = 'core'; - const prefix = '[github-api-usage-tracker]'; it('returns message for invalid_remaining', () => { expect(getUsageWarningMessage('invalid_remaining', bucket)).toBe( - `${prefix} Invalid remaining count for bucket "core"; skipping` + 'Invalid remaining count for bucket "core"; skipping' ); }); it('returns message for invalid_limit', () => { expect(getUsageWarningMessage('invalid_limit', bucket)).toBe( - `${prefix} Invalid limit for bucket "core" during reset crossing; skipping` + 'Invalid limit for bucket "core" during reset crossing; skipping' ); }); it('returns message for limit_changed_without_reset', () => { expect(getUsageWarningMessage('limit_changed_without_reset', bucket)).toBe( - `${prefix} Limit changed without reset for bucket "core"; skipping` + 'Limit changed without reset for bucket "core"; skipping' ); }); it('returns message for remaining_increased_without_reset', () => { expect(getUsageWarningMessage('remaining_increased_without_reset', bucket)).toBe( - `${prefix} Remaining increased without reset for bucket "core"; skipping` + 'Remaining increased without reset for bucket "core"; skipping' ); }); it('returns message for negative_usage', () => { expect(getUsageWarningMessage('negative_usage', bucket)).toBe( - `${prefix} Negative usage for bucket "core" detected; skipping` + 'Negative usage for bucket "core" detected; skipping' ); }); it('returns default message for unknown reason', () => { expect(getUsageWarningMessage('unknown_reason', bucket)).toBe( - `${prefix} Invalid usage data for bucket "core"; skipping` + 'Invalid usage data for bucket "core"; skipping' ); }); }); From 90190de899de8180924f4b7a025328afd931b90a Mon Sep 17 00:00:00 2001 From: Really Him Date: Fri, 23 Jan 2026 23:21:55 -0500 Subject: [PATCH 09/15] refactor: small refactors --- src/post-utils.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/post-utils.js b/src/post-utils.js index d5b505a..3981472 100644 --- a/src/post-utils.js +++ b/src/post-utils.js @@ -216,22 +216,21 @@ function buildSummaryContent(data, crossedBuckets, totalUsed, duration) { const table = makeSummaryTable(data, { useMinimumHeader: totalIsMinimum }); const sections = []; + const push = (htmlArray) => sections.push(...htmlArray); if (totalIsMinimum) { - sections.push( - `

Reset Window Crossed: Yes (${crossedBuckets.join(', ')})

` - ); - sections.push( - '

Total Usage: Cannot be computed - reset window was crossed.

' - ); - sections.push(`

Minimum API Calls/Points Used: ${totalUsed}

`); + push([ + `

Reset Window Crossed: Yes (${crossedBuckets.join(', ')})

`, + '

Total Usage: Cannot be computed - reset window was crossed.

', + `

Minimum API Calls/Points Used: ${totalUsed}

` + ]); } else { - sections.push(`

Total API Calls/Points Used: ${totalUsed}

`); + push([`

Total API Calls/Points Used: ${totalUsed}

`]); } - sections.push( + push([ `

Action Duration: ${duration !== null ? formatMs(duration) : 'Unknown'}

` - ); + ]); return { table, sections }; } From 20bc8e6bb04c17184894d7027fa9435349db941b Mon Sep 17 00:00:00 2001 From: Really Him Date: Sat, 24 Jan 2026 00:12:12 -0500 Subject: [PATCH 10/15] refactor: more refactoring and coverage --- src/post-utils.js | 274 ++++++++++++++++++++++++++++---------- src/post.js | 79 +++-------- tests/post-utils.test.mjs | 101 +++++++++++++- 3 files changed, 318 insertions(+), 136 deletions(-) diff --git a/src/post-utils.js b/src/post-utils.js index 3981472..00cbd71 100644 --- a/src/post-utils.js +++ b/src/post-utils.js @@ -1,3 +1,16 @@ +const fs = require('fs'); +const path = require('path'); + +/** + * Writes JSON-stringified data to a file if a valid pathname is provided. + */ +function maybeWriteJson(pathname, data) { + if (!pathname) return; + const dir = path.dirname(pathname); + if (dir && dir !== '.') fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync(pathname, JSON.stringify(data, null, 2)); +} + /** * Converts milliseconds to a human-readable duration string. * @@ -45,6 +58,66 @@ function makeSummaryTable(resources, options = {}) { return summaryTable; } +/** + * Computes usage when the reset window was crossed. + * Returns { used, warnings } on success, or { error } on failure. + */ +function computeUsageAcrossReset(ctx) { + const { startingLimit, endingLimit, endingRemaining, startingRemaining } = ctx; + const { checkpointBucket, checkpointTimeSeconds, resetPre } = ctx; + + if (!Number.isFinite(startingLimit) || !Number.isFinite(endingLimit)) { + return { error: 'invalid_limit' }; + } + + const warnings = []; + if (startingLimit !== endingLimit) { + warnings.push('limit_changed_across_reset'); + } + + let used = endingLimit - endingRemaining; + + // Add checkpoint usage if available and before reset + if ( + checkpointBucket && + Number.isFinite(checkpointTimeSeconds) && + checkpointTimeSeconds < resetPre + ) { + const checkpointRemaining = Number(checkpointBucket.remaining); + if (Number.isFinite(checkpointRemaining)) { + const checkpointUsed = startingRemaining - checkpointRemaining; + if (checkpointUsed > 0) { + used += checkpointUsed; + } + } + } + + return { used, warnings }; +} + +/** + * Computes usage within the same reset window. + * Returns { used, warnings } on success, or { error } on failure. + */ +function computeUsageWithinWindow(ctx) { + const { startingLimit, endingLimit, startingRemaining, endingRemaining } = ctx; + + if ( + Number.isFinite(startingLimit) && + Number.isFinite(endingLimit) && + startingLimit !== endingLimit + ) { + return { error: 'limit_changed_without_reset' }; + } + + const used = startingRemaining - endingRemaining; + if (used < 0) { + return { error: 'remaining_increased_without_reset' }; + } + + return { used, warnings: [] }; +} + /** * Computes usage stats for a single bucket using pre/post snapshots. * An optional checkpoint snapshot can tighten the minimum when a reset is crossed. @@ -63,82 +136,61 @@ function computeBucketUsage( checkpointBucket, checkpointTimeSeconds ) { - const result = { + const fail = (reason) => ({ valid: false, used: 0, remaining: undefined, crossed_reset: false, - warnings: [] - }; + warnings: [], + reason + }); if (!startingBucket || !endingBucket) { - result.reason = 'missing_bucket'; - return result; + return fail('missing_bucket'); } const startingRemaining = Number(startingBucket.remaining); const endingRemaining = Number(endingBucket.remaining); if (!Number.isFinite(startingRemaining) || !Number.isFinite(endingRemaining)) { - result.reason = 'invalid_remaining'; - return result; + return fail('invalid_remaining'); } const startingLimit = Number(startingBucket.limit); const endingLimit = Number(endingBucket.limit); const resetPre = Number(startingBucket.reset); const crossedReset = Number.isFinite(resetPre) && endTimeSeconds >= resetPre; - result.crossed_reset = crossedReset; - let used; - if (crossedReset) { - if (!Number.isFinite(startingLimit) || !Number.isFinite(endingLimit)) { - result.reason = 'invalid_limit'; - return result; - } - if (startingLimit !== endingLimit) { - result.warnings.push('limit_changed_across_reset'); - } - 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) && - Number.isFinite(endingLimit) && - startingLimit !== endingLimit - ) { - result.reason = 'limit_changed_without_reset'; - return result; - } - used = startingRemaining - endingRemaining; - if (used < 0) { - result.reason = 'remaining_increased_without_reset'; - return result; - } + const ctx = { + startingLimit, + endingLimit, + startingRemaining, + endingRemaining, + resetPre, + checkpointBucket, + checkpointTimeSeconds + }; + + const computation = crossedReset ? computeUsageAcrossReset(ctx) : computeUsageWithinWindow(ctx); + + if (computation.error) { + const result = fail(computation.error); + result.crossed_reset = crossedReset; + return result; } - if (used < 0) { - result.reason = 'negative_usage'; + if (computation.used < 0) { + const result = fail('negative_usage'); + result.crossed_reset = crossedReset; return result; } - result.valid = true; - result.used = used; - result.remaining = endingRemaining; - return result; + return { + valid: true, + used: computation.used, + remaining: endingRemaining, + crossed_reset: crossedReset, + warnings: computation.warnings + }; } /** @@ -165,6 +217,13 @@ function getUsageWarningMessage(reason, bucket) { } } +/** Returns a finite number or null. */ +const finiteOrNull = (v) => (Number.isFinite(v) ? v : null); + +/** Computes used (limit - remaining) if both are finite, else null. */ +const computeUsed = (limit, remaining) => + Number.isFinite(limit) && Number.isFinite(remaining) ? limit - remaining : null; + /** * Builds the data object for a single bucket from snapshots and computed usage. * @@ -174,29 +233,20 @@ function getUsageWarningMessage(reason, bucket) { * @returns {object} - bucket data with used/remaining info. */ function buildBucketData(startingBucket, endingBucket, usage) { - 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; + const startRemaining = Number(startingBucket.remaining); + const startLimit = Number(startingBucket.limit); + const endRemaining = Number(endingBucket.remaining); + const endLimit = Number(endingBucket.limit); return { used: { - start: startUsed, - end: endUsed, + start: computeUsed(startLimit, startRemaining), + end: computeUsed(endLimit, endRemaining), total: usage.used }, remaining: { - start: Number.isFinite(startingRemaining) ? startingRemaining : null, - end: Number.isFinite(endingRemaining) ? endingRemaining : null + start: finiteOrNull(startRemaining), + end: finiteOrNull(endRemaining) }, crossed_reset: usage.crossed_reset }; @@ -235,11 +285,93 @@ function buildSummaryContent(data, crossedBuckets, totalUsed, duration) { return { table, sections }; } +/** + * Parses checkpoint time from milliseconds to seconds. + * @param {number|null} checkpointTimeMs - checkpoint time in milliseconds. + * @returns {number|null} - checkpoint time in seconds, or null if invalid. + */ +function parseCheckpointTime(checkpointTimeMs) { + return Number.isFinite(checkpointTimeMs) && checkpointTimeMs > 0 + ? Math.floor(checkpointTimeMs / 1000) + : null; +} + +/** + * Processes all buckets and computes usage data. + * + * @param {object} params - processing parameters. + * @param {string[]} params.buckets - list of bucket names to process. + * @param {object} params.startingResources - starting rate limit resources. + * @param {object} params.endingResources - ending rate limit resources. + * @param {object|null} params.checkpointResources - checkpoint resources (optional). + * @param {number} params.endTimeSeconds - end time in seconds. + * @param {number|null} params.checkpointTimeSeconds - checkpoint time in seconds. + * @returns {object} - { data, crossedBuckets, totalUsed, warnings }. + */ +function processBuckets({ + buckets, + startingResources, + endingResources, + checkpointResources, + endTimeSeconds, + checkpointTimeSeconds +}) { + const data = {}; + const crossedBuckets = []; + const warnings = []; + let totalUsed = 0; + + for (const bucket of buckets) { + const startingBucket = startingResources[bucket]; + const endingBucket = endingResources[bucket]; + + if (!startingBucket) { + warnings.push(`Starting bucket "${bucket}" not found; skipping`); + continue; + } + if (!endingBucket) { + warnings.push(`Ending bucket "${bucket}" not found; skipping`); + continue; + } + + const checkpointBucket = checkpointResources ? checkpointResources[bucket] : undefined; + const usage = computeBucketUsage( + startingBucket, + endingBucket, + endTimeSeconds, + checkpointBucket, + checkpointTimeSeconds + ); + + if (!usage.valid) { + warnings.push(getUsageWarningMessage(usage.reason, bucket)); + continue; + } + + if (usage.warnings.includes('limit_changed_across_reset')) { + warnings.push( + `Limit changed across reset for bucket "${bucket}"; results may reflect a token change` + ); + } + + data[bucket] = buildBucketData(startingBucket, endingBucket, usage); + if (usage.crossed_reset) { + crossedBuckets.push(bucket); + } + totalUsed += usage.used; + } + + return { data, crossedBuckets, totalUsed, warnings }; +} + module.exports = { + maybeWriteJson, formatMs, makeSummaryTable, computeBucketUsage, getUsageWarningMessage, buildBucketData, - buildSummaryContent + buildSummaryContent, + parseCheckpointTime, + processBuckets }; diff --git a/src/post.js b/src/post.js index 3d21172..2fa2fed 100644 --- a/src/post.js +++ b/src/post.js @@ -1,25 +1,13 @@ const core = require('@actions/core'); -const fs = require('fs'); -const path = require('path'); const { fetchRateLimit } = require('./rate-limit'); const { log, warn, error, parseBuckets } = require('./log'); const { - computeBucketUsage, - getUsageWarningMessage, - buildBucketData, - buildSummaryContent + maybeWriteJson, + buildSummaryContent, + parseCheckpointTime, + processBuckets } = require('./post-utils'); -/** - * Writes JSON-stringified data to a file if a valid pathname is provided. - */ -function maybeWrite(pathname, data) { - if (!pathname) return; - const dir = path.dirname(pathname); - if (dir && dir !== '.') fs.mkdirSync(dir, { recursive: true }); - fs.writeFileSync(pathname, JSON.stringify(data, null, 2)); -} - async function run() { if (core.getState('skip_rest') === 'true') { log('Skipping post step'); @@ -46,10 +34,7 @@ async function run() { const checkpointState = core.getState('checkpoint_rate_limits'); const checkpointResources = checkpointState ? JSON.parse(checkpointState) : null; const checkpointTimeMs = checkpointResources ? Number(core.getState('checkpoint_time')) : null; - const checkpointTimeSeconds = - Number.isFinite(checkpointTimeMs) && checkpointTimeMs > 0 - ? Math.floor(checkpointTimeMs / 1000) - : null; + const checkpointTimeSeconds = parseCheckpointTime(checkpointTimeMs); // Fetch final rate limits log('Fetching final rate limits...'); @@ -64,49 +49,15 @@ async function run() { log(JSON.stringify(endingResources, null, 2)); // Process each bucket - const data = {}; - const crossedBuckets = []; - let totalUsed = 0; - - for (const bucket of buckets) { - const startingBucket = startingResources[bucket]; - const endingBucket = endingResources[bucket]; - - if (!startingBucket) { - warn(`Starting bucket "${bucket}" not found; skipping`); - continue; - } - if (!endingBucket) { - warn(`Ending bucket "${bucket}" not found; skipping`); - continue; - } - - const checkpointBucket = checkpointResources ? checkpointResources[bucket] : undefined; - const usage = computeBucketUsage( - startingBucket, - endingBucket, - endTimeSeconds, - checkpointBucket, - checkpointTimeSeconds - ); - - if (!usage.valid) { - warn(getUsageWarningMessage(usage.reason, bucket)); - continue; - } - - if (usage.warnings.includes('limit_changed_across_reset')) { - warn( - `Limit changed across reset for bucket "${bucket}"; results may reflect a token change` - ); - } - - data[bucket] = buildBucketData(startingBucket, endingBucket, usage); - if (usage.crossed_reset) { - crossedBuckets.push(bucket); - } - totalUsed += usage.used; - } + const { data, crossedBuckets, totalUsed, warnings } = processBuckets({ + buckets, + startingResources, + endingResources, + checkpointResources, + endTimeSeconds, + checkpointTimeSeconds + }); + warnings.forEach((msg) => warn(msg)); // Set output const output = { @@ -119,7 +70,7 @@ async function run() { // Write JSON file if path specified const outPath = (core.getInput('output_path') || '').trim(); - maybeWrite(outPath, output); + maybeWriteJson(outPath, output); // Build summary log(`Preparing summary table for ${Object.keys(data).length} bucket(s)`); diff --git a/tests/post-utils.test.mjs b/tests/post-utils.test.mjs index 69e7dfa..d536a4a 100644 --- a/tests/post-utils.test.mjs +++ b/tests/post-utils.test.mjs @@ -8,7 +8,9 @@ const { computeBucketUsage, getUsageWarningMessage, buildBucketData, - buildSummaryContent + buildSummaryContent, + parseCheckpointTime, + processBuckets } = require('../src/post-utils.js'); describe('post utils', () => { @@ -411,3 +413,100 @@ describe('buildSummaryContent', () => { ); }); }); + +describe('parseCheckpointTime', () => { + it('converts valid milliseconds to seconds', () => { + expect(parseCheckpointTime(5000)).toBe(5); + expect(parseCheckpointTime(1500)).toBe(1); + }); + + it('returns null for zero or negative values', () => { + expect(parseCheckpointTime(0)).toBeNull(); + expect(parseCheckpointTime(-1000)).toBeNull(); + }); + + it('returns null for non-finite values', () => { + expect(parseCheckpointTime(null)).toBeNull(); + expect(parseCheckpointTime(undefined)).toBeNull(); + expect(parseCheckpointTime(NaN)).toBeNull(); + expect(parseCheckpointTime(Infinity)).toBeNull(); + }); +}); + +describe('processBuckets', () => { + const makeResources = (remaining, limit = 1000, reset = 2000) => ({ + core: { remaining, limit, reset } + }); + + it('processes valid buckets and returns usage data', () => { + const result = processBuckets({ + buckets: ['core'], + startingResources: makeResources(900), + endingResources: makeResources(850), + checkpointResources: null, + endTimeSeconds: 1500, + checkpointTimeSeconds: null + }); + + expect(result.totalUsed).toBe(50); + expect(result.crossedBuckets).toEqual([]); + expect(result.warnings).toEqual([]); + expect(result.data.core.used.total).toBe(50); + }); + + it('collects warnings for missing starting bucket', () => { + const result = processBuckets({ + buckets: ['core'], + startingResources: {}, + endingResources: makeResources(850), + checkpointResources: null, + endTimeSeconds: 1500, + checkpointTimeSeconds: null + }); + + expect(result.warnings).toContain('Starting bucket "core" not found; skipping'); + expect(result.data).toEqual({}); + }); + + it('collects warnings for missing ending bucket', () => { + const result = processBuckets({ + buckets: ['core'], + startingResources: makeResources(900), + endingResources: {}, + checkpointResources: null, + endTimeSeconds: 1500, + checkpointTimeSeconds: null + }); + + expect(result.warnings).toContain('Ending bucket "core" not found; skipping'); + }); + + it('tracks crossed reset buckets', () => { + const result = processBuckets({ + buckets: ['core'], + startingResources: makeResources(700, 1000, 1000), + endingResources: makeResources(900), + checkpointResources: null, + endTimeSeconds: 1500, + checkpointTimeSeconds: null + }); + + expect(result.crossedBuckets).toEqual(['core']); + expect(result.totalUsed).toBe(100); + }); + + it('warns on limit change across reset', () => { + const result = processBuckets({ + buckets: ['core'], + startingResources: { core: { remaining: 600, limit: 1000, reset: 1000 } }, + endingResources: { core: { remaining: 4700, limit: 5000, reset: 2000 } }, + checkpointResources: null, + endTimeSeconds: 1500, + checkpointTimeSeconds: null + }); + + expect(result.warnings).toContain( + 'Limit changed across reset for bucket "core"; results may reflect a token change' + ); + }); +}); From e9a8241e3228dbbd399c90823ddc8eb193dc8f4a Mon Sep 17 00:00:00 2001 From: Really Him Date: Sat, 24 Jan 2026 00:20:35 -0500 Subject: [PATCH 11/15] chore: bump vitest --- package-lock.json | 538 ++++++++++++++++------------------------------ package.json | 4 +- 2 files changed, 193 insertions(+), 349 deletions(-) diff --git a/package-lock.json b/package-lock.json index 89b60d7..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.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", @@ -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 d6b1ea7..57a5295 100644 --- a/package.json +++ b/package.json @@ -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.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" From 306160d5dd269c772578bd4ef6234b89bb8390a1 Mon Sep 17 00:00:00 2001 From: Really Him Date: Sat, 24 Jan 2026 00:27:40 -0500 Subject: [PATCH 12/15] chore: run bundle --- dist/checkpoint/index.js | 53 +++- dist/post/index.js | 630 ++++++++++++++++++++++----------------- dist/pre/index.js | 47 ++- 3 files changed, 431 insertions(+), 299 deletions(-) diff --git a/dist/checkpoint/index.js b/dist/checkpoint/index.js index 5edc0fa..2a54a6f 100644 --- a/dist/checkpoint/index.js +++ b/dist/checkpoint/index.js @@ -27773,6 +27773,11 @@ module.exports = parseParams const core = __nccwpck_require__(7484); +/** + * Prefix for all log messages. + */ +const PREFIX = '[github-api-usage-tracker]'; + /** * List of valid GitHub API rate limit buckets. */ @@ -27790,12 +27795,30 @@ const VALID_BUCKETS = [ ]; /** - * Logs a message using GitHub Actions debug logging. + * Logs a debug message with prefix. * * @param {string} message - message to log. */ function log(message) { - core.debug(message); + core.debug(`${PREFIX} ${message}`); +} + +/** + * Logs a warning message with prefix. + * + * @param {string} message - message to log. + */ +function warn(message) { + core.warning(`${PREFIX} ${message}`); +} + +/** + * Logs an error message with prefix. + * + * @param {string} message - message to log. + */ +function error(message) { + core.error(`${PREFIX} ${message}`); } /** @@ -27821,14 +27844,14 @@ function parseBuckets(raw) { } } if (invalidBuckets.length > 0) { - core.warning( + warn( `Invalid bucket(s) selected: ${invalidBuckets.join(', ')}, valid options are: ${VALID_BUCKETS.join(', ')}` ); } return buckets; } -module.exports = { log, parseBuckets, VALID_BUCKETS }; +module.exports = { PREFIX, log, warn, error, parseBuckets, VALID_BUCKETS }; /***/ }), @@ -27923,28 +27946,26 @@ module.exports = { fetchRateLimit }; var __webpack_exports__ = {}; const core = __nccwpck_require__(7484); const { fetchRateLimit } = __nccwpck_require__(5042); -const { log } = __nccwpck_require__(9630); +const { log, warn } = __nccwpck_require__(9630); async function run() { + if (core.getState('skip_rest') === 'true') { + log('Skipping checkpoint step'); + return; + } try { - const token = core.getInput('token'); - if (!token) { - log('[github-api-usage-tracker] Skipping checkpoint snapshot due to missing token'); - return; - } - - log('[github-api-usage-tracker] Fetching checkpoint rate limits...'); + log('Fetching checkpoint rate limits...'); const limits = await fetchRateLimit(); const resources = limits.resources || {}; - log('[github-api-usage-tracker] Checkpoint Snapshot:'); - log('[github-api-usage-tracker] ---------------------'); - log(`[github-api-usage-tracker] ${JSON.stringify(resources, null, 2)}`); + log('Checkpoint Snapshot:'); + log('---------------------'); + log(JSON.stringify(resources, null, 2)); core.saveState('checkpoint_time', String(Date.now())); core.saveState('checkpoint_rate_limits', JSON.stringify(resources)); } catch (err) { - core.warning(`[github-api-usage-tracker] Main step snapshot failed: ${err.message}`); + warn(`Main step snapshot failed: ${err.message}`); } } diff --git a/dist/post/index.js b/dist/post/index.js index 43c5fe0..689042d 100644 --- a/dist/post/index.js +++ b/dist/post/index.js @@ -27773,6 +27773,11 @@ module.exports = parseParams const core = __nccwpck_require__(7484); +/** + * Prefix for all log messages. + */ +const PREFIX = '[github-api-usage-tracker]'; + /** * List of valid GitHub API rate limit buckets. */ @@ -27790,12 +27795,30 @@ const VALID_BUCKETS = [ ]; /** - * Logs a message using GitHub Actions debug logging. + * Logs a debug message with prefix. * * @param {string} message - message to log. */ function log(message) { - core.debug(message); + core.debug(`${PREFIX} ${message}`); +} + +/** + * Logs a warning message with prefix. + * + * @param {string} message - message to log. + */ +function warn(message) { + core.warning(`${PREFIX} ${message}`); +} + +/** + * Logs an error message with prefix. + * + * @param {string} message - message to log. + */ +function error(message) { + core.error(`${PREFIX} ${message}`); } /** @@ -27821,20 +27844,33 @@ function parseBuckets(raw) { } } if (invalidBuckets.length > 0) { - core.warning( + warn( `Invalid bucket(s) selected: ${invalidBuckets.join(', ')}, valid options are: ${VALID_BUCKETS.join(', ')}` ); } return buckets; } -module.exports = { log, parseBuckets, VALID_BUCKETS }; +module.exports = { PREFIX, log, warn, error, parseBuckets, VALID_BUCKETS }; /***/ }), /***/ 5828: -/***/ ((module) => { +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +const fs = __nccwpck_require__(9896); +const path = __nccwpck_require__(6928); + +/** + * Writes JSON-stringified data to a file if a valid pathname is provided. + */ +function maybeWriteJson(pathname, data) { + if (!pathname) return; + const dir = path.dirname(pathname); + if (dir && dir !== '.') fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync(pathname, JSON.stringify(data, null, 2)); +} /** * Converts milliseconds to a human-readable duration string. @@ -27883,6 +27919,66 @@ function makeSummaryTable(resources, options = {}) { return summaryTable; } +/** + * Computes usage when the reset window was crossed. + * Returns { used, warnings } on success, or { error } on failure. + */ +function computeUsageAcrossReset(ctx) { + const { startingLimit, endingLimit, endingRemaining, startingRemaining } = ctx; + const { checkpointBucket, checkpointTimeSeconds, resetPre } = ctx; + + if (!Number.isFinite(startingLimit) || !Number.isFinite(endingLimit)) { + return { error: 'invalid_limit' }; + } + + const warnings = []; + if (startingLimit !== endingLimit) { + warnings.push('limit_changed_across_reset'); + } + + let used = endingLimit - endingRemaining; + + // Add checkpoint usage if available and before reset + if ( + checkpointBucket && + Number.isFinite(checkpointTimeSeconds) && + checkpointTimeSeconds < resetPre + ) { + const checkpointRemaining = Number(checkpointBucket.remaining); + if (Number.isFinite(checkpointRemaining)) { + const checkpointUsed = startingRemaining - checkpointRemaining; + if (checkpointUsed > 0) { + used += checkpointUsed; + } + } + } + + return { used, warnings }; +} + +/** + * Computes usage within the same reset window. + * Returns { used, warnings } on success, or { error } on failure. + */ +function computeUsageWithinWindow(ctx) { + const { startingLimit, endingLimit, startingRemaining, endingRemaining } = ctx; + + if ( + Number.isFinite(startingLimit) && + Number.isFinite(endingLimit) && + startingLimit !== endingLimit + ) { + return { error: 'limit_changed_without_reset' }; + } + + const used = startingRemaining - endingRemaining; + if (used < 0) { + return { error: 'remaining_increased_without_reset' }; + } + + return { used, warnings: [] }; +} + /** * Computes usage stats for a single bucket using pre/post snapshots. * An optional checkpoint snapshot can tighten the minimum when a reset is crossed. @@ -27901,85 +27997,245 @@ function computeBucketUsage( checkpointBucket, checkpointTimeSeconds ) { - const result = { + const fail = (reason) => ({ valid: false, used: 0, remaining: undefined, crossed_reset: false, - warnings: [] - }; + warnings: [], + reason + }); if (!startingBucket || !endingBucket) { - result.reason = 'missing_bucket'; - return result; + return fail('missing_bucket'); } const startingRemaining = Number(startingBucket.remaining); const endingRemaining = Number(endingBucket.remaining); if (!Number.isFinite(startingRemaining) || !Number.isFinite(endingRemaining)) { - result.reason = 'invalid_remaining'; - return result; + return fail('invalid_remaining'); } const startingLimit = Number(startingBucket.limit); const endingLimit = Number(endingBucket.limit); const resetPre = Number(startingBucket.reset); const crossedReset = Number.isFinite(resetPre) && endTimeSeconds >= resetPre; - result.crossed_reset = crossedReset; - let used; - if (crossedReset) { - if (!Number.isFinite(startingLimit) || !Number.isFinite(endingLimit)) { - result.reason = 'invalid_limit'; - return result; - } - if (startingLimit !== endingLimit) { - result.warnings.push('limit_changed_across_reset'); - } - used = endingLimit - endingRemaining; + const ctx = { + startingLimit, + endingLimit, + startingRemaining, + endingRemaining, + resetPre, + checkpointBucket, + checkpointTimeSeconds + }; - 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; - } - } - } + const computation = crossedReset ? computeUsageAcrossReset(ctx) : computeUsageWithinWindow(ctx); + + if (computation.error) { + const result = fail(computation.error); + result.crossed_reset = crossedReset; + return result; + } + + if (computation.used < 0) { + const result = fail('negative_usage'); + result.crossed_reset = crossedReset; + return result; + } + + return { + valid: true, + used: computation.used, + remaining: endingRemaining, + crossed_reset: crossedReset, + warnings: computation.warnings + }; +} + +/** + * Returns a warning message for invalid bucket usage (without prefix). + * + * @param {string} reason - the reason code from computeBucketUsage. + * @param {string} bucket - the bucket name. + * @returns {string} - formatted warning message. + */ +function getUsageWarningMessage(reason, bucket) { + switch (reason) { + case 'invalid_remaining': + return `Invalid remaining count for bucket "${bucket}"; skipping`; + case 'invalid_limit': + return `Invalid limit for bucket "${bucket}" during reset crossing; skipping`; + case 'limit_changed_without_reset': + return `Limit changed without reset for bucket "${bucket}"; skipping`; + case 'remaining_increased_without_reset': + return `Remaining increased without reset for bucket "${bucket}"; skipping`; + case 'negative_usage': + return `Negative usage for bucket "${bucket}" detected; skipping`; + default: + return `Invalid usage data for bucket "${bucket}"; skipping`; + } +} + +/** Returns a finite number or null. */ +const finiteOrNull = (v) => (Number.isFinite(v) ? v : null); + +/** Computes used (limit - remaining) if both are finite, else null. */ +const computeUsed = (limit, remaining) => + Number.isFinite(limit) && Number.isFinite(remaining) ? limit - remaining : null; + +/** + * Builds the data object for a single bucket from snapshots and computed usage. + * + * @param {object} startingBucket - bucket from the pre snapshot. + * @param {object} endingBucket - bucket from the post snapshot. + * @param {object} usage - computed usage from computeBucketUsage. + * @returns {object} - bucket data with used/remaining info. + */ +function buildBucketData(startingBucket, endingBucket, usage) { + const startRemaining = Number(startingBucket.remaining); + const startLimit = Number(startingBucket.limit); + const endRemaining = Number(endingBucket.remaining); + const endLimit = Number(endingBucket.limit); + + return { + used: { + start: computeUsed(startLimit, startRemaining), + end: computeUsed(endLimit, endRemaining), + total: usage.used + }, + remaining: { + start: finiteOrNull(startRemaining), + end: finiteOrNull(endRemaining) + }, + crossed_reset: usage.crossed_reset + }; +} + +/** + * Builds the summary content object for the job summary. + * + * @param {object} data - bucket data keyed by bucket name. + * @param {string[]} crossedBuckets - list of buckets that crossed reset. + * @param {number} totalUsed - total API calls/points used. + * @param {number|null} duration - action duration in milliseconds. + * @returns {object} - summary content with table and HTML sections. + */ +function buildSummaryContent(data, crossedBuckets, totalUsed, duration) { + const totalIsMinimum = crossedBuckets.length > 0; + const table = makeSummaryTable(data, { useMinimumHeader: totalIsMinimum }); + + const sections = []; + const push = (htmlArray) => sections.push(...htmlArray); + + if (totalIsMinimum) { + push([ + `

Reset Window Crossed: Yes (${crossedBuckets.join(', ')})

`, + '

Total Usage: Cannot be computed - reset window was crossed.

', + `

Minimum API Calls/Points Used: ${totalUsed}

` + ]); } else { - if ( - Number.isFinite(startingLimit) && - Number.isFinite(endingLimit) && - startingLimit !== endingLimit - ) { - result.reason = 'limit_changed_without_reset'; - return result; + push([`

Total API Calls/Points Used: ${totalUsed}

`]); + } + + push([ + `

Action Duration: ${duration !== null ? formatMs(duration) : 'Unknown'}

` + ]); + + return { table, sections }; +} + +/** + * Parses checkpoint time from milliseconds to seconds. + * @param {number|null} checkpointTimeMs - checkpoint time in milliseconds. + * @returns {number|null} - checkpoint time in seconds, or null if invalid. + */ +function parseCheckpointTime(checkpointTimeMs) { + return Number.isFinite(checkpointTimeMs) && checkpointTimeMs > 0 + ? Math.floor(checkpointTimeMs / 1000) + : null; +} + +/** + * Processes all buckets and computes usage data. + * + * @param {object} params - processing parameters. + * @param {string[]} params.buckets - list of bucket names to process. + * @param {object} params.startingResources - starting rate limit resources. + * @param {object} params.endingResources - ending rate limit resources. + * @param {object|null} params.checkpointResources - checkpoint resources (optional). + * @param {number} params.endTimeSeconds - end time in seconds. + * @param {number|null} params.checkpointTimeSeconds - checkpoint time in seconds. + * @returns {object} - { data, crossedBuckets, totalUsed, warnings }. + */ +function processBuckets({ + buckets, + startingResources, + endingResources, + checkpointResources, + endTimeSeconds, + checkpointTimeSeconds +}) { + const data = {}; + const crossedBuckets = []; + const warnings = []; + let totalUsed = 0; + + for (const bucket of buckets) { + const startingBucket = startingResources[bucket]; + const endingBucket = endingResources[bucket]; + + if (!startingBucket) { + warnings.push(`Starting bucket "${bucket}" not found; skipping`); + continue; + } + if (!endingBucket) { + warnings.push(`Ending bucket "${bucket}" not found; skipping`); + continue; + } + + const checkpointBucket = checkpointResources ? checkpointResources[bucket] : undefined; + const usage = computeBucketUsage( + startingBucket, + endingBucket, + endTimeSeconds, + checkpointBucket, + checkpointTimeSeconds + ); + + if (!usage.valid) { + warnings.push(getUsageWarningMessage(usage.reason, bucket)); + continue; } - used = startingRemaining - endingRemaining; - if (used < 0) { - result.reason = 'remaining_increased_without_reset'; - return result; + + if (usage.warnings.includes('limit_changed_across_reset')) { + warnings.push( + `Limit changed across reset for bucket "${bucket}"; results may reflect a token change` + ); } - } - if (used < 0) { - result.reason = 'negative_usage'; - return result; + data[bucket] = buildBucketData(startingBucket, endingBucket, usage); + if (usage.crossed_reset) { + crossedBuckets.push(bucket); + } + totalUsed += usage.used; } - result.valid = true; - result.used = used; - result.remaining = endingRemaining; - return result; + return { data, crossedBuckets, totalUsed, warnings }; } -module.exports = { formatMs, makeSummaryTable, computeBucketUsage }; +module.exports = { + maybeWriteJson, + formatMs, + makeSummaryTable, + computeBucketUsage, + getUsageWarningMessage, + buildBucketData, + buildSummaryContent, + parseCheckpointTime, + processBuckets +}; /***/ }), @@ -28072,260 +28328,92 @@ module.exports = { fetchRateLimit }; /******/ /************************************************************************/ var __webpack_exports__ = {}; -/** - * Retrieves a numeric state value from the GitHub Actions state. - * - * @param {string} key - The state key to retrieve. - * @returns {number|undefined} - The numeric value if valid and finite, otherwise undefined. - */ - -/** - * Writes a summary table of API resource usage to the GitHub Actions summary. - * - * @param {Object.} resources - Object mapping bucket names to usage info. - */ - -/** - * Main post-action function that calculates and reports GitHub API usage. - * Fetches final rate limits, compares with starting values, and outputs usage data. - * - * @async - * @returns {Promise} - */ const core = __nccwpck_require__(7484); -const fs = __nccwpck_require__(9896); -const path = __nccwpck_require__(6928); const { fetchRateLimit } = __nccwpck_require__(5042); -const { log, parseBuckets } = __nccwpck_require__(9630); -const { formatMs, makeSummaryTable, computeBucketUsage } = __nccwpck_require__(5828); - -/** - * Writes JSON-stringified data to a file if a valid pathname is provided. - * - * @param {string} pathname - file path to write to. - * @param {object} data - data to write. - */ -function maybeWrite(pathname, data) { - 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 { log, warn, error, parseBuckets } = __nccwpck_require__(9630); +const { + maybeWriteJson, + buildSummaryContent, + parseCheckpointTime, + processBuckets +} = __nccwpck_require__(5828); async function run() { - if (core.getState('skip_post') === 'true') { - log('[github-api-usage-tracker] Skipping post step due to missing token'); + if (core.getState('skip_rest') === 'true') { + log('Skipping post step'); return; } try { const buckets = parseBuckets(core.getInput('buckets')); - if (buckets.length === 0) { - log('[github-api-usage-tracker] No valid buckets specified for tracking'); + log('No valid buckets specified for tracking'); return; } + // Get starting state (saved by pre.js) const startingState = core.getState('starting_rate_limits'); if (!startingState) { - core.error( - '[github-api-usage-tracker] No starting rate limit data found; skipping post step' - ); - return; - } - let startingResources; - try { - startingResources = JSON.parse(startingState); - } catch { - core.error( - '[github-api-usage-tracker] Failed to parse starting rate limit data; skipping post step' - ); + error('No starting rate limit data found; skipping'); return; } + const startingResources = JSON.parse(startingState); const startTime = Number(core.getState('start_time')); const hasStartTime = Number.isFinite(startTime); - if (!hasStartTime) { - core.error( - '[github-api-usage-tracker] Invalid or missing start time; duration will be reported as unknown' - ); - } - const checkpointState = core.getState('checkpoint_rate_limits'); - let checkpointResources; - let checkpointTimeSeconds = null; - if (checkpointState) { - try { - checkpointResources = JSON.parse(checkpointState); - } catch { - core.warning( - '[github-api-usage-tracker] Failed to parse checkpoint rate limit data; ignoring checkpoint snapshot' - ); - } - } - if (checkpointResources) { - const checkpointTimeMs = Number(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...'); + // Get checkpoint state if available (saved by checkpoint.js) + const checkpointState = core.getState('checkpoint_rate_limits'); + const checkpointResources = checkpointState ? JSON.parse(checkpointState) : null; + const checkpointTimeMs = checkpointResources ? Number(core.getState('checkpoint_time')) : null; + const checkpointTimeSeconds = parseCheckpointTime(checkpointTimeMs); + // Fetch final rate limits + log('Fetching final rate limits...'); const endingLimits = await fetchRateLimit(); const endingResources = endingLimits.resources || {}; + const endTime = Date.now(); + const endTimeSeconds = Math.floor(endTime / 1000); + const duration = hasStartTime ? endTime - startTime : null; - log('[github-api-usage-tracker] Final Snapshot:'); - log('[github-api-usage-tracker] -----------------'); - 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( - `[github-api-usage-tracker] Starting rate limit bucket "${bucket}" not found; skipping` - ); - continue; - } - if (!endingBucket) { - core.warning( - `[github-api-usage-tracker] Ending rate limit bucket "${bucket}" not found; skipping` - ); - continue; - } - - const checkpointBucket = checkpointResources ? checkpointResources[bucket] : undefined; - const usage = computeBucketUsage( - startingBucket, - endingBucket, - endTimeSeconds, - checkpointBucket, - checkpointTimeSeconds - ); - if (!usage.valid) { - switch (usage.reason) { - case 'invalid_remaining': - core.warning( - `[github-api-usage-tracker] Invalid remaining count for bucket "${bucket}"; skipping` - ); - break; - case 'invalid_limit': - core.warning( - `[github-api-usage-tracker] Invalid limit for bucket "${bucket}" during reset crossing; skipping` - ); - break; - case 'limit_changed_without_reset': - core.warning( - `[github-api-usage-tracker] Limit changed without reset for bucket "${bucket}"; skipping` - ); - break; - case 'remaining_increased_without_reset': - core.warning( - `[github-api-usage-tracker] Remaining increased without reset for bucket "${bucket}"; skipping` - ); - break; - case 'negative_usage': - core.warning( - `[github-api-usage-tracker] Negative usage for bucket "${bucket}" detected; skipping` - ); - break; - default: - core.warning( - `[github-api-usage-tracker] Invalid usage data for bucket "${bucket}"; skipping` - ); - break; - } - continue; - } - - if (usage.warnings.includes('limit_changed_across_reset')) { - 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: { - 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; - } + log('Final Snapshot:'); + log('-----------------'); + log(JSON.stringify(endingResources, null, 2)); + + // Process each bucket + const { data, crossedBuckets, totalUsed, warnings } = processBuckets({ + buckets, + startingResources, + endingResources, + checkpointResources, + endTimeSeconds, + checkpointTimeSeconds + }); + warnings.forEach((msg) => warn(msg)); // Set output const output = { total: totalUsed, duration_ms: duration, buckets_data: data, - crossed_reset: totalIsMinimum + crossed_reset: crossedBuckets.length > 0 }; core.setOutput('usage', JSON.stringify(output, null, 2)); // Write JSON file if path specified const outPath = (core.getInput('output_path') || '').trim(); - maybeWrite(outPath, output); + maybeWriteJson(outPath, output); - log( - `[github-api-usage-tracker] Preparing summary table for ${Object.keys(data).length} bucket(s)` - ); + // Build summary + log(`Preparing summary table for ${Object.keys(data).length} bucket(s)`); + const summaryContent = buildSummaryContent(data, crossedBuckets, totalUsed, duration); const summary = core.summary .addHeading('GitHub API Usage Tracker Summary') - .addTable(makeSummaryTable(data, { useMinimumHeader: totalIsMinimum })); - if (crossedBuckets.length > 0) { - summary.addRaw( - `

Reset Window Crossed: Yes (${crossedBuckets.join(', ')})

`, - true - ); - summary.addRaw( - '

Total Usage: Total usage cannot be computed - usage reset window was crossed.

', - true - ); - summary.addRaw(`

Minimum API Calls/Points Used: ${totalUsed}

`, true); - } - summary.addRaw( - `

Action Duration: ${ - hasStartTime ? formatMs(duration) : 'Unknown (data missing)' - }

`, - true - ); - if (crossedBuckets.length === 0) { - summary.addRaw(`

Total API Calls/Points Used: ${totalUsed}

`, true); + .addTable(summaryContent.table); + for (const section of summaryContent.sections) { + summary.addRaw(section, true); } summary.write(); } catch (err) { - core.error(`[github-api-usage-tracker] Post step failed: ${err.message}`); + error(`Post step failed: ${err.message}`); } } diff --git a/dist/pre/index.js b/dist/pre/index.js index 49d427a..c87a367 100644 --- a/dist/pre/index.js +++ b/dist/pre/index.js @@ -27773,6 +27773,11 @@ module.exports = parseParams const core = __nccwpck_require__(7484); +/** + * Prefix for all log messages. + */ +const PREFIX = '[github-api-usage-tracker]'; + /** * List of valid GitHub API rate limit buckets. */ @@ -27790,12 +27795,30 @@ const VALID_BUCKETS = [ ]; /** - * Logs a message using GitHub Actions debug logging. + * Logs a debug message with prefix. * * @param {string} message - message to log. */ function log(message) { - core.debug(message); + core.debug(`${PREFIX} ${message}`); +} + +/** + * Logs a warning message with prefix. + * + * @param {string} message - message to log. + */ +function warn(message) { + core.warning(`${PREFIX} ${message}`); +} + +/** + * Logs an error message with prefix. + * + * @param {string} message - message to log. + */ +function error(message) { + core.error(`${PREFIX} ${message}`); } /** @@ -27821,14 +27844,14 @@ function parseBuckets(raw) { } } if (invalidBuckets.length > 0) { - core.warning( + warn( `Invalid bucket(s) selected: ${invalidBuckets.join(', ')}, valid options are: ${VALID_BUCKETS.join(', ')}` ); } return buckets; } -module.exports = { log, parseBuckets, VALID_BUCKETS }; +module.exports = { PREFIX, log, warn, error, parseBuckets, VALID_BUCKETS }; /***/ }), @@ -27923,33 +27946,33 @@ module.exports = { fetchRateLimit }; var __webpack_exports__ = {}; const core = __nccwpck_require__(7484); const { fetchRateLimit } = __nccwpck_require__(5042); -const { log } = __nccwpck_require__(9630); +const { log, warn, error } = __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'); + error('GitHub token is required for API Usage Tracker'); + core.saveState('skip_rest', 'true'); return; } const startTime = Date.now(); core.saveState('start_time', String(startTime)); - log('[github-api-usage-tracker] Fetching initial rate limits...'); + log('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)}`); + log('Initial Snapshot:'); + log('-----------------'); + log(JSON.stringify(resources, null, 2)); core.saveState('starting_rate_limits', JSON.stringify(resources)); } catch (err) { - core.warning(`Pre step failed: ${err.message}`); + warn(`Pre step failed: ${err.message}`); } } From 5a649ecd5387e92a4d01b2df5fbd7f700d96c426 Mon Sep 17 00:00:00 2001 From: Really Him Date: Sat, 24 Jan 2026 04:00:39 -0500 Subject: [PATCH 13/15] chore: rate limit timeout --- dist/checkpoint/index.js | 26 ++++++++++++++++++++++---- dist/post/index.js | 28 +++++++++++++++++++++++----- dist/pre/index.js | 26 ++++++++++++++++++++++---- src/post.js | 2 +- src/rate-limit.js | 26 ++++++++++++++++++++++---- tests/rate-limit.test.mjs | 6 ++++++ 6 files changed, 96 insertions(+), 18 deletions(-) diff --git a/dist/checkpoint/index.js b/dist/checkpoint/index.js index 2a54a6f..d539c5c 100644 --- a/dist/checkpoint/index.js +++ b/dist/checkpoint/index.js @@ -27863,10 +27863,22 @@ const core = __nccwpck_require__(7484); const https = __nccwpck_require__(5692); const { log } = __nccwpck_require__(9630); +const REQUEST_TIMEOUT_MS = 30_000; + function fetchRateLimit() { const token = core.getInput('token'); return new Promise((resolve, reject) => { if (!token) return reject(new Error('No GitHub token provided')); + let settled = false; + const finalize = (err, data) => { + if (settled) return; + settled = true; + if (err) { + reject(err); + } else { + resolve(data); + } + }; const req = https.request( { @@ -27884,18 +27896,24 @@ function fetchRateLimit() { 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}`)); + return finalize(new Error(`GitHub API returned ${res.statusCode}: ${data}`)); } try { - resolve(JSON.parse(data)); + finalize(null, JSON.parse(data)); } catch (e) { - reject(e); + finalize(e); } }); } ); - req.on('error', reject); + req.setTimeout(REQUEST_TIMEOUT_MS); + req.on('timeout', () => { + const err = new Error(`GitHub API request timed out after ${REQUEST_TIMEOUT_MS}ms`); + req.destroy(err); + finalize(err); + }); + req.on('error', finalize); req.end(); }); } diff --git a/dist/post/index.js b/dist/post/index.js index 689042d..04c50d7 100644 --- a/dist/post/index.js +++ b/dist/post/index.js @@ -28247,10 +28247,22 @@ const core = __nccwpck_require__(7484); const https = __nccwpck_require__(5692); const { log } = __nccwpck_require__(9630); +const REQUEST_TIMEOUT_MS = 30_000; + function fetchRateLimit() { const token = core.getInput('token'); return new Promise((resolve, reject) => { if (!token) return reject(new Error('No GitHub token provided')); + let settled = false; + const finalize = (err, data) => { + if (settled) return; + settled = true; + if (err) { + reject(err); + } else { + resolve(data); + } + }; const req = https.request( { @@ -28268,18 +28280,24 @@ function fetchRateLimit() { 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}`)); + return finalize(new Error(`GitHub API returned ${res.statusCode}: ${data}`)); } try { - resolve(JSON.parse(data)); + finalize(null, JSON.parse(data)); } catch (e) { - reject(e); + finalize(e); } }); } ); - req.on('error', reject); + req.setTimeout(REQUEST_TIMEOUT_MS); + req.on('timeout', () => { + const err = new Error(`GitHub API request timed out after ${REQUEST_TIMEOUT_MS}ms`); + req.destroy(err); + finalize(err); + }); + req.on('error', finalize); req.end(); }); } @@ -28411,7 +28429,7 @@ async function run() { for (const section of summaryContent.sections) { summary.addRaw(section, true); } - summary.write(); + await summary.write(); } catch (err) { error(`Post step failed: ${err.message}`); } diff --git a/dist/pre/index.js b/dist/pre/index.js index c87a367..e6e5861 100644 --- a/dist/pre/index.js +++ b/dist/pre/index.js @@ -27863,10 +27863,22 @@ const core = __nccwpck_require__(7484); const https = __nccwpck_require__(5692); const { log } = __nccwpck_require__(9630); +const REQUEST_TIMEOUT_MS = 30_000; + function fetchRateLimit() { const token = core.getInput('token'); return new Promise((resolve, reject) => { if (!token) return reject(new Error('No GitHub token provided')); + let settled = false; + const finalize = (err, data) => { + if (settled) return; + settled = true; + if (err) { + reject(err); + } else { + resolve(data); + } + }; const req = https.request( { @@ -27884,18 +27896,24 @@ function fetchRateLimit() { 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}`)); + return finalize(new Error(`GitHub API returned ${res.statusCode}: ${data}`)); } try { - resolve(JSON.parse(data)); + finalize(null, JSON.parse(data)); } catch (e) { - reject(e); + finalize(e); } }); } ); - req.on('error', reject); + req.setTimeout(REQUEST_TIMEOUT_MS); + req.on('timeout', () => { + const err = new Error(`GitHub API request timed out after ${REQUEST_TIMEOUT_MS}ms`); + req.destroy(err); + finalize(err); + }); + req.on('error', finalize); req.end(); }); } diff --git a/src/post.js b/src/post.js index 2fa2fed..4138a76 100644 --- a/src/post.js +++ b/src/post.js @@ -81,7 +81,7 @@ async function run() { for (const section of summaryContent.sections) { summary.addRaw(section, true); } - summary.write(); + await summary.write(); } catch (err) { error(`Post step failed: ${err.message}`); } diff --git a/src/rate-limit.js b/src/rate-limit.js index 60edc2b..f9c63d7 100644 --- a/src/rate-limit.js +++ b/src/rate-limit.js @@ -2,10 +2,22 @@ const core = require('@actions/core'); const https = require('https'); const { log } = require('./log'); +const REQUEST_TIMEOUT_MS = 30_000; + function fetchRateLimit() { const token = core.getInput('token'); return new Promise((resolve, reject) => { if (!token) return reject(new Error('No GitHub token provided')); + let settled = false; + const finalize = (err, data) => { + if (settled) return; + settled = true; + if (err) { + reject(err); + } else { + resolve(data); + } + }; const req = https.request( { @@ -23,18 +35,24 @@ function fetchRateLimit() { 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}`)); + return finalize(new Error(`GitHub API returned ${res.statusCode}: ${data}`)); } try { - resolve(JSON.parse(data)); + finalize(null, JSON.parse(data)); } catch (e) { - reject(e); + finalize(e); } }); } ); - req.on('error', reject); + req.setTimeout(REQUEST_TIMEOUT_MS); + req.on('timeout', () => { + const err = new Error(`GitHub API request timed out after ${REQUEST_TIMEOUT_MS}ms`); + req.destroy(err); + finalize(err); + }); + req.on('error', finalize); req.end(); }); } diff --git a/tests/rate-limit.test.mjs b/tests/rate-limit.test.mjs index 6af926a..46a5e40 100644 --- a/tests/rate-limit.test.mjs +++ b/tests/rate-limit.test.mjs @@ -52,6 +52,8 @@ describe('fetchRateLimit', () => { process.env.INPUT_TOKEN = 'token123'; requestSpy = vi.spyOn(https, 'request').mockImplementation((options, callback) => { const req = new EventEmitter(); + req.setTimeout = () => {}; + req.destroy = () => {}; req.end = () => { const res = new EventEmitter(); res.statusCode = 200; @@ -80,6 +82,8 @@ describe('fetchRateLimit', () => { process.env.INPUT_TOKEN = 'token123'; requestSpy = vi.spyOn(https, 'request').mockImplementation((options, callback) => { const req = new EventEmitter(); + req.setTimeout = () => {}; + req.destroy = () => {}; req.end = () => { const res = new EventEmitter(); res.statusCode = 401; @@ -97,6 +101,8 @@ describe('fetchRateLimit', () => { process.env.INPUT_TOKEN = 'token123'; requestSpy = vi.spyOn(https, 'request').mockImplementation((options, callback) => { const req = new EventEmitter(); + req.setTimeout = () => {}; + req.destroy = () => {}; req.end = () => { const res = new EventEmitter(); res.statusCode = 200; From 2456aa7324adab799dd1c9a6f2e6bcbe202e2ece Mon Sep 17 00:00:00 2001 From: Really Him Date: Sat, 24 Jan 2026 04:01:03 -0500 Subject: [PATCH 14/15] docs: update README, and SECURITY --- README.md | 2 +- SECURITY.md | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 SECURITY.md diff --git a/README.md b/README.md index 88d5bcb..e225615 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![GitHub Actions](https://img.shields.io/badge/GitHub-Action-2088FF?style=for-the-badge&logo=github-actions&logoColor=white)](https://github.com/features/actions) [![Node.js](https://img.shields.io/badge/Node.js-20+-339933?style=for-the-badge&logo=node.js&logoColor=white)](https://nodejs.org/) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=for-the-badge)](./docs/LICENSE) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=for-the-badge)](./LICENSE) --- diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..42c2cbf --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,6 @@ +# Security Policy + +## Reporting a Vulnerability + +Please report security vulnerabilities by opening a Private Vulnerability +report in the action repo. From 564f589385870a5611e664bce0e51805ea818876 Mon Sep 17 00:00:00 2001 From: Really Him Date: Sat, 24 Jan 2026 04:13:49 -0500 Subject: [PATCH 15/15] ci: update workflows to ensure bundle not stale --- .github/workflows/ci.yml | 4 ++++ .github/workflows/release-please.yml | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fa79403..d66d690 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,5 +23,9 @@ jobs: cache: npm - name: Install run: npm ci + - name: Bundle + run: npm run bundle + - name: Verify bundled output is up to date + run: git diff --exit-code -- dist - name: CI run: npm run test:ci diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index b8bc165..4a80202 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -19,6 +19,19 @@ jobs: if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }} runs-on: ubuntu-latest steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + - name: Install + run: npm ci + - name: Bundle + run: npm run bundle + - name: Verify bundled output is up to date + run: git diff --exit-code -- dist - name: Run Release Please uses: googleapis/release-please-action@16a9c90856f42705d54a6fda1823352bdc62cf38 with: