Skip to content

chore: drop backoff dep and convert remaining require() calls to ESM#8195

Open
jherr wants to merge 1 commit intomainfrom
chore/backoff-and-require-cleanup
Open

chore: drop backoff dep and convert remaining require() calls to ESM#8195
jherr wants to merge 1 commit intomainfrom
chore/backoff-and-require-cleanup

Conversation

@jherr
Copy link
Copy Markdown
Contributor

@jherr jherr commented Apr 22, 2026

Summary

Continues the Milestone 1 dependency-pruning work from #8189 by finishing items B and C from the follow-up list:

  • Drop backoff / @types/backoff. Its only runtime use was in src/utils/deploy/upload-files.ts; replaced with a small inline Fibonacci retry helper that preserves the existing delay sequence (5s, 5s, 10s, 15s, 25s, … capped at UPLOAD_MAX_DELAY), the 0.5 jitter factor, and the failAfter(maxRetry) "6 attempts when maxRetry=5" semantics. tests/unit/utils/gh-auth.test.ts migrated off backoff to a plain polling loop.
  • Convert the 3 remaining require() calls to ESM.
    • src/commands/functions/functions-create.ts (x2): require(packageJson)JSON.parse(fs.readFileSync(...)) via a small readJsonFile helper.
    • src/commands/database/legacy/utils.ts: createRequire(join(dir, 'package.json'))('./package.json')JSON.parse(fs.readFileSync(join(dir, 'package.json'), 'utf8')).
    • src/commands/functions/functions-invoke.ts: processPayloadFromFlag becomes async; .json payloads use readFile + JSON.parse, other files go through dynamic import() (via pathToFileURL) with imported.default ?? imported to preserve the CJS interop the old require() provided.
  • New unit tests to lock in the changed behavior:
    • tests/unit/commands/database/legacy/utils.test.ts (8 cases) — exercises getPackageJSON happy path plus each of the shape-validation errors.
    • tests/unit/commands/functions/functions-invoke.test.ts (7 cases) — exercises processPayloadFromFlag with inline JSON strings, .json / .mjs / .cjs file payloads, missing paths, and load-time errors (processPayloadFromFlag is now exported for testability).

Test plan

  • npm run typecheck
  • npm run lint
  • npm exec vitest -- run tests/unit/utils/deploy/upload-files.test.ts (2/2 — retry-count-on-retries and no-retry-on-400 still pass against the inline Fibonacci implementation)
  • npm exec vitest -- run tests/unit/utils/gh-auth.test.ts (1/1)
  • npm exec vitest -- run tests/unit/commands/database/legacy/utils.test.ts (8/8, new)
  • npm exec vitest -- run tests/unit/commands/functions/functions-invoke.test.ts (7/7, new)
  • npm run test:unit — 337 passed. The 5 failures are pre-existing on main (copy-template-dir.test.ts × 4 hit a Node 25 fs.rmdirSync deprecation; generate-autocompletion.test.ts × 1 has a stale snapshot).
  • npm exec vitest -- run tests/integration/commands/functions-invoke/functions-invoke.test.ts (5/5 — validates the now-async processPayloadFromFlag path end-to-end)

Made with Cursor

@jherr jherr requested a review from a team as a code owner April 22, 2026 23:28
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 22, 2026

📝 Walkthrough

Summary by CodeRabbit

  • Chores
    • Removed an unused runtime dependency to reduce package footprint
  • Refactor
    • Reworked on-disk JSON and payload loading to use direct file reads and awaited module imports
    • Revised upload retry implementation to use deterministic, manual scheduling
  • Tests
    • Added and expanded unit tests to cover package JSON loading, payload handling, retry/polling behavior

Walkthrough

This PR removes the backoff dependency and related typings, replaces CommonJS createRequire package.json loading with direct fs.readFileSync + JSON.parse, rewrites the upload retry logic to a manual retry/count-and-delay implementation (replacing Fibonacci backoff), converts processPayloadFromFlag to an exported async function that supports reading JSON files and dynamic ESM/CJS imports, and updates tests to validate the new behaviors and to use deterministic polling instead of backoff-driven retries.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the two main changes: dropping the backoff dependency and converting require() calls to ESM imports.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, detailing the dependency removal, code conversions, new tests, and verification of all changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/backoff-and-require-cleanup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 22, 2026

📊 Benchmark results

Comparing with 0415e87

  • Dependency count: 1,059 ⬇️ 0.19% decrease vs. 0415e87
  • Package size: 354 MB ⬇️ 0.04% decrease vs. 0415e87
  • Number of ts-expect-error directives: 349 ⬇️ 2.01% decrease vs. 0415e87

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/utils/deploy/upload-files.ts (1)

80-127: Consider adding generic typing to retryUpload to preserve response types.

The retry attempt semantics are correct: with maxRetry = n, the function allows up to n + 1 total attempts (initial attempt plus n retries), matching the previous failAfter(maxRetry) behavior.

However, retryUpload currently returns Promise<unknown>, causing the response variable (lines 30 and 42) to lose type information from api.uploadDeployFile() and api.uploadDeployFunction(). Add a generic parameter to preserve the response type:

Suggested typing refinement
-const retryUpload = (uploadFn: (retryCount: number) => Promise<unknown>, maxRetry: number) =>
-  new Promise((resolve, reject) => {
+const retryUpload = <T>(uploadFn: (retryCount: number) => Promise<T>, maxRetry: number): Promise<T> =>
+  new Promise<T>((resolve, reject) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/deploy/upload-files.ts` around lines 80 - 127, The retryUpload
helper should be made generic so it preserves the resolved type from callers:
change retryUpload to accept a type parameter (e.g. <T>) and type uploadFn as
(retryCount: number) => Promise<T>, and have retryUpload return Promise<T>; keep
internal variables (lastError, retryCount, scheduleNextAttempt, tryUpload) the
same but ensure resolve is typed to accept T and reject remains unknown/any as
before; update any call sites of retryUpload (e.g., uses with
api.uploadDeployFile and api.uploadDeployFunction) to infer or pass the concrete
type if needed so the response variables keep their original types.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/utils/deploy/upload-files.ts`:
- Around line 80-127: The retryUpload helper should be made generic so it
preserves the resolved type from callers: change retryUpload to accept a type
parameter (e.g. <T>) and type uploadFn as (retryCount: number) => Promise<T>,
and have retryUpload return Promise<T>; keep internal variables (lastError,
retryCount, scheduleNextAttempt, tryUpload) the same but ensure resolve is typed
to accept T and reject remains unknown/any as before; update any call sites of
retryUpload (e.g., uses with api.uploadDeployFile and api.uploadDeployFunction)
to infer or pass the concrete type if needed so the response variables keep
their original types.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 63df52d9-8379-41d4-a471-a979f681d265

📥 Commits

Reviewing files that changed from the base of the PR and between 69f05af and a0d6ee5.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (8)
  • package.json
  • src/commands/database/legacy/utils.ts
  • src/commands/functions/functions-create.ts
  • src/commands/functions/functions-invoke.ts
  • src/utils/deploy/upload-files.ts
  • tests/unit/commands/database/legacy/utils.test.ts
  • tests/unit/commands/functions/functions-invoke.test.ts
  • tests/unit/utils/gh-auth.test.ts
💤 Files with no reviewable changes (1)
  • package.json

Continues the Milestone 1 dependency-pruning work from #8189.

- Replace the single `backoff` call site in `src/utils/deploy/upload-files.ts`
  with a small inline Fibonacci retry that preserves the existing delay
  sequence (5s, 5s, 10s, 15s, 25s, … capped at UPLOAD_MAX_DELAY) and the
  `failAfter(maxRetry)` semantics. Also migrate `tests/unit/utils/gh-auth.test.ts`
  off `backoff` to a plain polling loop.
- Convert the three remaining `require(path)` call sites to native ESM
  equivalents: `functions-create.ts` and `database/legacy/utils.ts` now use
  `JSON.parse(readFileSync(...))` for package.json lookups; `functions-invoke.ts`
  becomes async and supports both `.json` (readFile) and `.js/.mjs/.cjs`
  (dynamic `import()`) payloads.
- Remove `backoff` and `@types/backoff` from package.json.
- Add unit tests for `getPackageJSON` and export + test
  `processPayloadFromFlag` to lock in the JSON + dynamic-import behavior.

Made-with: Cursor
@jherr jherr force-pushed the chore/backoff-and-require-cleanup branch from a0d6ee5 to 38ed20d Compare April 24, 2026 17:47
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
tests/unit/commands/database/legacy/utils.test.ts (1)

56-74: Optional: consider covering the null / array edge case for dependencies et al.

Since typeof null === 'object' and typeof [] === 'object', a package.json with e.g. "dependencies": null or "dependencies": [] currently passes validation in getPackageJSON and is returned as-is. This is a pre-existing behavior in the function (not introduced by this refactor), but given you're adding a fresh test suite it may be worth either pinning that behavior with an explicit test or tightening the check in getPackageJSON to also reject null/arrays. Non-blocking.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/commands/database/legacy/utils.test.ts` around lines 56 - 74,
getPackageJSON currently treats null and arrays as valid because typeof
null/array === 'object'; update the validation inside getPackageJSON to require
a plain object for keys dependencies, devDependencies and scripts by checking
value !== null && !Array.isArray(value) && typeof value === 'object', and throw
the same style of error (e.g., "Expected object at package.json#<key>, got
<type>") when the check fails so tests like the ones in utils.test.ts will also
fail for null/arrays; adjust or add tests to assert that null and arrays are
rejected for these keys if you prefer tightening behavior.
src/commands/functions/functions-invoke.ts (1)

75-77: Prefer async file read inside async function.

Now that processPayloadFromFlag is async, consider using fs.promises.readFile to avoid blocking the event loop on disk I/O.

♻️ Proposed refactor
-import fs from 'fs'
+import fs from 'fs'
+import { readFile } from 'fs/promises'
         if (payloadpath.endsWith('.json')) {
-          return JSON.parse(fs.readFileSync(payloadpath, 'utf8'))
+          return JSON.parse(await readFile(payloadpath, 'utf8'))
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/functions/functions-invoke.ts` around lines 75 - 77, The sync
file read blocks the event loop inside the async function
processPayloadFromFlag; replace fs.readFileSync usage when handling
payloadpath.endsWith('.json') with an awaited async read from
fs.promises.readFile, then JSON.parse the resulting string and return it. Update
references to payloadpath and JSON.parse in processPayloadFromFlag to use: const
contents = await fs.promises.readFile(payloadpath, 'utf8'); return
JSON.parse(contents); and ensure errors propagate or are caught where
processPayloadFromFlag is invoked.
src/utils/deploy/upload-files.ts (1)

82-122: Optional: drop the redundant lastError binding.

lastError is only ever assigned error within the same catch block before being used in reject(lastError) on line 115. You can just reject with error and remove the outer binding to simplify the closure.

♻️ Proposed simplification
     let lastError: unknown
     let retryCount = 0
     let previousDelay = 0
     let nextDelay = UPLOAD_INITIAL_DELAY
@@
-      } catch (error) {
-        lastError = error
-        const status = (error as { status?: number } | null)?.status
-        const name = (error as { name?: string } | null)?.name
+      } catch (error) {
+        const { status, name } = (error ?? {}) as { status?: number; name?: string }
 
         if (status === 400 || status === 422) {
           reject(error)
           return
         }
 
         if ((typeof status === 'number' && status > 400) || name === 'FetchError') {
           retryCount += 1
           if (retryCount > maxRetry) {
-            reject(lastError)
+            reject(error)
             return
           }

And drop the let lastError: unknown declaration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/deploy/upload-files.ts` around lines 82 - 122, Remove the redundant
outer binding lastError and use the caught error directly: in the catch block
inside tryUpload (which calls uploadFn), delete the let lastError: unknown
declaration and the assignment lastError = error, then replace reject(lastError)
(on the max-retry path) with reject(error); keep the rest of the retry logic
(retryCount, maxRetry, scheduleNextAttempt, status/name checks) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/commands/functions/functions-invoke.ts`:
- Around line 75-77: The sync file read blocks the event loop inside the async
function processPayloadFromFlag; replace fs.readFileSync usage when handling
payloadpath.endsWith('.json') with an awaited async read from
fs.promises.readFile, then JSON.parse the resulting string and return it. Update
references to payloadpath and JSON.parse in processPayloadFromFlag to use: const
contents = await fs.promises.readFile(payloadpath, 'utf8'); return
JSON.parse(contents); and ensure errors propagate or are caught where
processPayloadFromFlag is invoked.

In `@src/utils/deploy/upload-files.ts`:
- Around line 82-122: Remove the redundant outer binding lastError and use the
caught error directly: in the catch block inside tryUpload (which calls
uploadFn), delete the let lastError: unknown declaration and the assignment
lastError = error, then replace reject(lastError) (on the max-retry path) with
reject(error); keep the rest of the retry logic (retryCount, maxRetry,
scheduleNextAttempt, status/name checks) unchanged.

In `@tests/unit/commands/database/legacy/utils.test.ts`:
- Around line 56-74: getPackageJSON currently treats null and arrays as valid
because typeof null/array === 'object'; update the validation inside
getPackageJSON to require a plain object for keys dependencies, devDependencies
and scripts by checking value !== null && !Array.isArray(value) && typeof value
=== 'object', and throw the same style of error (e.g., "Expected object at
package.json#<key>, got <type>") when the check fails so tests like the ones in
utils.test.ts will also fail for null/arrays; adjust or add tests to assert that
null and arrays are rejected for these keys if you prefer tightening behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 10007a25-ea27-42f1-9fe7-ce21729a6a6b

📥 Commits

Reviewing files that changed from the base of the PR and between a0d6ee5 and 38ed20d.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (8)
  • package.json
  • src/commands/database/legacy/utils.ts
  • src/commands/functions/functions-create.ts
  • src/commands/functions/functions-invoke.ts
  • src/utils/deploy/upload-files.ts
  • tests/unit/commands/database/legacy/utils.test.ts
  • tests/unit/commands/functions/functions-invoke.test.ts
  • tests/unit/utils/gh-auth.test.ts
💤 Files with no reviewable changes (1)
  • package.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/commands/functions/functions-create.ts
  • tests/unit/commands/functions/functions-invoke.test.ts
  • tests/unit/utils/gh-auth.test.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant