Skip to content

feat: 进度百分比 + 下载重试 + 降级链测试 + Cresc 测试 + globals.d.ts#583

Open
sunnylqm wants to merge 16 commits into
masterfrom
feat/progress-retry-strict-tests
Open

feat: 进度百分比 + 下载重试 + 降级链测试 + Cresc 测试 + globals.d.ts#583
sunnylqm wants to merge 16 commits into
masterfrom
feat/progress-retry-strict-tests

Conversation

@sunnylqm

@sunnylqm sunnylqm commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

变更概览

新功能

  • 进度百分比: ProgressData 新增 progress 字段 (0-100),通过 computeProgress(received, total) 自动计算,用户无需手动算
  • 下载自动重试: ClientOptions 新增 maxRetries 选项(默认 3 次),下载失败后自动重试整个降级链

测试补充

  • downloadUpdate 降级链测试 (7 个用例):
    • diff 成功 → 直接使用 diff
    • diff 失败 → 回退到 pdiff
    • diff + pdiff 失败 → 回退到 full
    • 全部失败 → 抛出错误
    • 重试成功(第 1 次失败,第 2 次成功)
    • 默认 3 次重试验证(1 initial + 3 retries = 4 calls)
    • 重试耗尽 → 抛出错误
  • Cresc 类测试 (4 个用例):
    • 使用正确的 Cresc endpoints
    • 默认 locale 为 en
    • instanceof Pushy 验证
    • 自定义 server 覆盖默认 endpoints
  • computeProgress 工具函数测试 (6 个用例)

开发者体验

  • 新增 src/globals.d.ts,为 DEV 提供类型声明
  • 进度回调中的 progressData 参数增加 ProgressData 类型标注

改动文件

  • src/type.ts — ProgressData 增加 progress 字段,ClientOptions 增加 maxRetries
  • src/utils.ts — 新增 computeProgress 工具函数
  • src/client.ts — 进度百分比注入 + 重试循环 + 类型标注
  • src/globals.d.ts — DEV 类型声明
  • src/tests/client.test.ts — 降级链测试 + Cresc 测试
  • src/tests/utils.test.ts — computeProgress 测试

验证

  • bun run lint ✅
  • bun test → 66 pass, 0 fail ✅

Summary by CodeRabbit

  • New Features
    • Added a download-progress percentage to update download callbacks.
    • Added configurable retry attempts for failed update downloads (default: 3).
  • Bug Fixes
    • Improved update download fallback order (diff → pdiff → full) with retry behavior.
    • Improved final error reporting when all download methods fail.
  • Tests
    • Added coverage for fallback and retry behavior.
    • Added unit tests for progress-percentage edge cases.
  • Documentation/Types
    • Extended public types for the new progress field and retry option.
    • Added TypeScript typing for the React Native __DEV__ flag.

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds computed download progress, retry-aware update downloads, fallback-chain tests, an ambient boolean declaration for __DEV__, and CLI entry resolution changes for the e2etest script.

Changes

Client download flow and support types

Layer / File(s) Summary
Progress helper and progress type
src/type.ts, src/utils.ts, src/__tests__/utils.test.ts
ProgressData gains progress, computeProgress returns a floored percentage with a zero-total fallback, and utils tests cover edge cases and large values.
Retry-aware download flow
src/type.ts, src/client.ts
ClientOptions gains maxRetries, downloadUpdate forwards computed progress, and update downloads retry through diff, pdiff, and full requests until success or exhaustion.
Fallback chain tests
src/__tests__/client.test.ts
downloadUpdate tests cover diff, pdiff, and full fallback ordering, all-failure errors, configured retries, default retries, and retry exhaustion.
Runtime flag and CLI artifact resolution
src/globals.d.ts, Example/e2etest/scripts/prepare-local-update-artifacts.ts
__DEV__ is declared as an ambient boolean global, and the e2etest artifact script resolves the CLI entry from package.json, invokes hdiff and hdiffFromApk, and logs completion around manifest writing.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

  • reactnativecn/react-native-update#580: Both PRs modify src/client.ts’s downloadUpdate control flow; this one adds an early no-op guard, while the current PR adds progress computation and retry fallback behavior.

Poem

Hop hop, I carry progress in my paws,
From diff to pdiff, then full by download laws.
My whiskers twitch at __DEV__'s gentle glow,
And retries bloom like carrots in a row.
🐰

🚥 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is specific and covers the main additions: progress percentage, download retries, and related test/type updates.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/progress-retry-strict-tests

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.

@sunnylqm sunnylqm force-pushed the feat/progress-retry-strict-tests branch from 398f57f to ebd47be Compare June 26, 2026 06:30
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{}

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/__tests__/client.test.ts (1)

373-406: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

maxRetries destructured here is never used.

setupDownloadMocks accepts maxRetries but never forwards it; the actual retry count is controlled solely by new Pushy({ ..., maxRetries }) in each test. The parameter (passed at Lines 472, 495, 527) is dead and misleading—drop it or wire it into the constructed client to avoid implying it has an effect.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/__tests__/client.test.ts` around lines 373 - 406, The setupDownloadMocks
helper in client.test.ts destructures maxRetries but never uses it, so the
parameter is misleading and dead. Remove maxRetries from the helper signature
and its destructuring in setupDownloadMocks, or alternatively thread it through
to the Pushy client setup if the tests are meant to control retry behavior; use
the setupDownloadMocks and Pushy test setup as the places to update.
src/client.ts (1)

528-607: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Retries fire immediately with no backoff.

On persistent failures the loop re-runs the full diff→pdiff→full chain back-to-back (up to maxRetries + 1 times) with no delay. For server/network-induced failures this hammers the backend instantly and is unlikely to recover. Consider adding a short (ideally exponential) backoff between attempts.

♻️ Example backoff between attempts
     for (let attempt = 0; attempt <= maxRetries; attempt++) {
       if (attempt > 0) {
         log(`retry attempt ${attempt}/${maxRetries}`);
+        await new Promise(r => setTimeout(r, Math.min(1000 * 2 ** (attempt - 1), 10000)));
         errorMessages.length = 0;
         lastError = undefined;
         succeeded = '';
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client.ts` around lines 528 - 607, The retry loop in client.ts for the
update download flow retries immediately with no delay, which can hammer the
backend on repeated failures. Add a short backoff between attempts inside the
for-loop around the diff/pdiff/full download chain, ideally exponential and only
between failed attempts, while keeping the existing success break behavior and
preserving the current logging/error handling in the retry path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/type.ts`:
- Around line 36-37: The `progress` field in `RCTPushyDownloadProgress` is being
treated as always present, but that is not true across all emission paths.
Update the `type.ts` definition and the related progress flow in
`downloadAndInstallApk`/`onDownloadProgress` so the runtime contract matches
reality: either make `progress` optional in the shared type or ensure every
producer, including the APK download path and any native
`RCTPushyDownloadProgress` payloads, computes and sets it consistently like
`wrapProgress` does in `downloadUpdate`.

---

Nitpick comments:
In `@src/__tests__/client.test.ts`:
- Around line 373-406: The setupDownloadMocks helper in client.test.ts
destructures maxRetries but never uses it, so the parameter is misleading and
dead. Remove maxRetries from the helper signature and its destructuring in
setupDownloadMocks, or alternatively thread it through to the Pushy client setup
if the tests are meant to control retry behavior; use the setupDownloadMocks and
Pushy test setup as the places to update.

In `@src/client.ts`:
- Around line 528-607: The retry loop in client.ts for the update download flow
retries immediately with no delay, which can hammer the backend on repeated
failures. Add a short backoff between attempts inside the for-loop around the
diff/pdiff/full download chain, ideally exponential and only between failed
attempts, while keeping the existing success break behavior and preserving the
current logging/error handling in the retry path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: eb1e47da-d9d4-4940-8b63-bdbabb7aedd9

📥 Commits

Reviewing files that changed from the base of the PR and between 311cefd and ebd47be.

📒 Files selected for processing (6)
  • src/__tests__/client.test.ts
  • src/__tests__/utils.test.ts
  • src/client.ts
  • src/globals.d.ts
  • src/type.ts
  • src/utils.ts

Comment thread src/type.ts Outdated
…c tests, globals.d.ts

Features:
- Add progress percentage (0-100) to ProgressData via computeProgress utility
- Add maxRetries option for automatic download retry on failure
- Download progress callbacks now include computed progress field

Tests:
- Add comprehensive downloadUpdate fallback chain tests (diff→pdiff→full)
- Add retry mechanism tests (success on retry, exhaust retries)
- Add Cresc class tests (endpoints, locale, instanceof, custom server)
- Add computeProgress unit tests

Developer Experience:
- Add src/globals.d.ts for __DEV__ type declaration
- Type-hint progressData callbacks with ProgressData type
@sunnylqm sunnylqm force-pushed the feat/progress-retry-strict-tests branch from ebd47be to 7df1727 Compare June 26, 2026 06:57
@sunnylqm

Copy link
Copy Markdown
Contributor Author

All 3 CodeRabbit findings addressed:

  1. progress field → Changed to progress?: number (optional), since native RCTPushyDownloadProgress and downloadAndInstallApk path do not populate it.

  2. Dead maxRetries param → Removed from setupDownloadMocks helper signature and all call sites.

  3. No backoff on retry → Added exponential backoff (1s, 2s, 4s, ... capped at 10s) between retry attempts. Tests bypass delays via setTimeout override.

Lint ✅ | 66 tests pass ✅

sunnylqm added 15 commits June 27, 2026 12:05
…b/index.js

CLI refactored in ebd5a45 moved command dispatch from index.ts to bin.ts,
but e2e script still pointed at lib/index.js which only re-exports.
pushy bundle exited with code 0 without producing .ppk files.

Read the bin field from the CLI's package.json for forward-compatibility.
The prepare step was exiting with code 0 but never generating diffs
or writing manifest.json. Added step-by-step logging to diagnose:
- 'Generating ppk diff...' / 'Ppk diff generated.'
- 'Generating package diff...' / 'Package diff generated.'
- 'Manifest written to ...'
- 'prepare-local-update-artifacts completed successfully.'

Inlined diff calls (removed generatePpkDiff/generateAndroidPackageDiff
wrapper functions) for more direct error propagation.
The in-process require of lib/diff.js + calling diffCommands.hdiff()
was hanging because yauzl's async entry callbacks don't keep the
Node.js event loop alive properly when called from a script that
has no other active handles.

Switched to running diff commands as CLI subprocesses via runPushy(),
matching the pattern already used for bundleTo(). Each diff command
runs in its own node process with a proper event loop.

Also removed the DiffCommandRunner type and lib/diff.js require since
they're no longer needed.
The CLI's loadModule('node-hdiffpatch') searches from process.cwd(),
which is the project root when running as a subprocess. Install the
module to the project's node_modules instead of the CLI's so it can
be found.

This ensures pushy hdiff/hdiffFromApk commands can resolve the native
diff module when run as CLI subprocesses.
macOS 26 runner requires explicit tap trust for Homebrew security.
Fixes: Refusing to load formula wix/brew/applesimutils from untrusted tap
The local e2e server appears to start (health check passes) but returns
404 for artifact files. Added:
- stdout/stderr capture from bun server subprocess
- spawn error handler
- HTTP response status logging in waitForRequestReady
Root cause was CLI's loadModule not checking NODE_PATH dirs.
Fixed in react-native-update-cli@151ea0f.
- prepare script: verify ppk.patch and apk.patch exist after generation
- server: log 404 responses with resolved path and existence check
- server: add /debug/artifacts endpoint listing all files
- globalSetup: capture server stdout/stderr to .server.log
- globalSetup: list artifacts dir and query debug endpoint before warmServer
Root cause: loadModule('node-hdiffpatch') in CLI's lib/diff.js uses
require.resolve with paths=['.', ...NODE_PATH]. The '.' resolves to
cliRoot/lib/, and NODE_PATH only had cliRoot/node_modules/. But
node-hdiffpatch was installed in projectRoot/node_modules/, which was
not in the search path. The module silently failed to load, the diff
command exited without error (or with a swallowed error), and the
output file was never created.

Fix: add projectRoot/node_modules to NODE_PATH so the CLI can find
node-hdiffpatch installed in the project's node_modules.
pushy hdiff exits 0 but doesn't create the output file. Capture and
log all stdout/stderr from the CLI subprocess to understand why.
…rectly

pushy hdiff exits 0 in CI but doesn't create output files. The root
cause is that the CLI's bin.ts flow (loadSession, argument parsing)
interferes with the hdiff command somehow - the handler either never
runs or silently fails.

Fix: use a standalone wrapper script (run-hdiff-wrapper.js) that
requires the CLI's lib/diff module directly and calls the hdiff/hdiffFromApk
handlers with the correct arguments. This bypasses the bin.ts flow
entirely while reusing the same diff logic.

Also adds projectRoot/node_modules to NODE_PATH so the CLI's
loadModule can find node-hdiffpatch installed in the project.
- Use createRequire from node:module for reliable module loading
- Add extensive logging to diagnose wrapper execution in CI
- Validate input files exist before calling diff handlers
- Verify output file creation after diff completes
__dirname in TS-compiled scripts points to .e2e-artifacts/.ts-build/scripts/,
but run-hdiff-wrapper.js lives in the source scripts/ directory and is not
copied to the build output. Use projectRoot + 'scripts' to resolve correctly.
The CLI's loadModule uses require.resolve with paths that only include
the CLI's own lib/ and node_modules/. It cannot find node-hdiffpatch
installed in the project's node_modules.

Fix: pre-load node-hdiffpatch from project root in the wrapper script
and pass it as options.customHdiffModule to the diff handler. This
bypasses the loadModule resolution entirely.
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