From c654b9a32bbc4289d03de37a6342949b5c0758f8 Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Mon, 19 Oct 2026 15:15:12 +0800 Subject: [PATCH 1/3] fix(types): compile cleanly under app tsconfigs on RN 0.88 package.json `types` points at src/index.ts, so apps type-check this package's sources and skipLibCheck does not apply. Two errors surfaced after upgrading to 10.62.0: - client.ts: the private emitError delegate was never read (TS6133 under noUnusedLocals). Remove it; tests now call reporter.emitError directly. - clientDownload.ts: calling addListener on the union of DeviceEventEmitter and the NativeEventEmitter is not callable with RN 0.88 types (TS2349). Branch into two calls; emitted JS behaves the same. Co-Authored-By: Claude Opus 5.5 --- src/__tests__/client.test.ts | 2 +- src/__tests__/provider.render.test.tsx | 6 +++--- src/client.ts | 2 -- src/clientDownload.ts | 14 +++++++++++--- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/__tests__/client.test.ts b/src/__tests__/client.test.ts index 11749263..bf04870c 100644 --- a/src/__tests__/client.test.ts +++ b/src/__tests__/client.test.ts @@ -174,7 +174,7 @@ describe('Pushy server config', () => { async () => {} ); - (client as any).emitError(new Error('offline'), 'errorChecking'); + (client as any).reporter.emitError(new Error('offline'), 'errorChecking'); client.reportInvalidUpdateOnce('noArtifact', 'h2'); expect(report).toHaveBeenCalledWith( diff --git a/src/__tests__/provider.render.test.tsx b/src/__tests__/provider.render.test.tsx index 54c7563a..7b008799 100644 --- a/src/__tests__/provider.render.test.tsx +++ b/src/__tests__/provider.render.test.tsx @@ -199,7 +199,7 @@ describe('UpdateProvider rendering', () => { // Real client contract under the default throwError:false — the error is // emitted to onError listeners and checkUpdate resolves undefined. client.checkUpdate.mockImplementation(async () => { - client.emitError(checkError, 'errorChecking'); + (client as any).reporter.emitError(checkError, 'errorChecking'); return undefined; }); @@ -244,7 +244,7 @@ describe('UpdateProvider rendering', () => { }); const checkError = new Error('offline'); client.checkUpdate.mockImplementation(async () => { - client.emitError(checkError, 'errorChecking'); + (client as any).reporter.emitError(checkError, 'errorChecking'); return undefined; }); @@ -265,7 +265,7 @@ describe('UpdateProvider rendering', () => { dismissErrorAfter: 20, }); client.checkUpdate.mockImplementation(async () => { - client.emitError(new Error('offline'), 'errorChecking'); + (client as any).reporter.emitError(new Error('offline'), 'errorChecking'); return undefined; }); diff --git a/src/client.ts b/src/client.ts index bc41fecb..2e8ca8dd 100644 --- a/src/client.ts +++ b/src/client.ts @@ -252,8 +252,6 @@ export class Pushy { throwIfEnabled = (e: Error) => this.reporter.throwIfEnabled(e); onError: Reporter['onError'] = (listener) => this.reporter.onError(listener); wasEmitted = (e: unknown): boolean => this.reporter.wasEmitted(e); - private emitError: Reporter['emitError'] = (...args) => - this.reporter.emitError(...args); notifyAfterCheckUpdate: Reporter['notifyAfterCheckUpdate'] = (state) => this.reporter.notifyAfterCheckUpdate(state); diff --git a/src/clientDownload.ts b/src/clientDownload.ts index ff62e89e..d0d91ec7 100644 --- a/src/clientDownload.ts +++ b/src/clientDownload.ts @@ -177,9 +177,17 @@ export class Downloader { }; // @ts-expect-error harmony not in existing platforms const isHarmony = Platform.OS === 'harmony'; - const progressSubscription = ( - isHarmony ? DeviceEventEmitter : pushyNativeEventEmitter - ).addListener('RCTPushyDownloadProgress', onNativeProgress); + // Two separate calls: on RN 0.88 the two emitters' addListener signatures + // no longer unify, so calling it on the union type does not compile. + const progressSubscription = isHarmony + ? DeviceEventEmitter.addListener( + 'RCTPushyDownloadProgress', + onNativeProgress + ) + : pushyNativeEventEmitter.addListener( + 'RCTPushyDownloadProgress', + onNativeProgress + ); const maxRetries = Math.max( 0, Math.floor(this.host.options.maxRetries ?? 3) From aa495628818bdde27148a12ed99b5623fb98cc0f Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Mon, 19 Oct 2026 15:35:50 +0800 Subject: [PATCH 2/3] build(types): publish compiled declarations instead of the TS sources package.json `types` pointed at src/index.ts, so an app's tsc compiled this package's sources under the app's own tsconfig (noUnusedLocals, ...) and React Native version, where skipLibCheck does not reach. Emit declarations to lib/typescript with tsc --emitDeclarationOnly (tsconfig.build.json), point `types` at them and ship them; `main` stays src/index for Metro. prepack, the lint workflow and the publish action build them, and check-packlist requires the `types` file. Co-Authored-By: Claude Opus 5.5 --- .github/actions/publish-npm/action.yml | 6 ++++++ .github/workflows/lint.yml | 5 +++++ .gitignore | 3 +++ package.json | 6 ++++-- scripts/check-packlist.js | 4 +++- tsconfig.build.json | 16 ++++++++++++++++ tsconfig.json | 2 +- 7 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 tsconfig.build.json diff --git a/.github/actions/publish-npm/action.yml b/.github/actions/publish-npm/action.yml index 8904e0e2..f1e89c85 100644 --- a/.github/actions/publish-npm/action.yml +++ b/.github/actions/publish-npm/action.yml @@ -105,6 +105,12 @@ runs: shell: bash run: test -f harmony/pushy.har + # npm publish rebuilds them in prepack; build them here too so the + # file-list check below sees lib/typescript. + - name: Build type declarations + shell: bash + run: bun build:types + - name: Verify publish file list shell: bash run: node scripts/check-packlist.js --release diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 1dddd462..2a01940c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -39,5 +39,10 @@ jobs: - name: Run lint run: bun lint + # check-packlist.js requires the emitted declarations that + # package.json `types` points at. + - name: Build type declarations + run: bun build:types + - name: Verify publish file list run: node scripts/check-packlist.js diff --git a/.gitignore b/.gitignore index b48aa296..f7dc4b1d 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,9 @@ local.properties node_modules/ npm-debug.log +# Type declarations emitted by `bun build:types` (published, not committed) +lib/ + Example/**/.update Example/**/.pushy Example/**/.e2e-artifacts diff --git a/package.json b/package.json index 33430673..6afb1ded 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,11 @@ "version": "10.62.0", "description": "Hot update (OTA) for React Native with Hermes-optimized diff patches, crash rollback and staged rollout. A CodePush alternative for iOS, Android and HarmonyOS.", "main": "src/index", - "types": "src/index.ts", + "types": "lib/typescript/index.d.ts", "files": [ "src", "!src/__tests__", + "lib/typescript", "android/src", "!android/src/test", "android/lib", @@ -45,8 +46,9 @@ "LICENSE" ], "scripts": { - "prepack": "bun submodule && bun lint", + "prepack": "bun submodule && bun lint && bun build:types", "lint": "biome check src && tsc --noEmit && node scripts/check-harmony-types.js && node scripts/check-native-spec-parity.js && bun scripts/generate-contract.ts --check && node scripts/check-readme-options.js", + "build:types": "rm -rf lib/typescript && tsc -p tsconfig.build.json", "contract": "bun scripts/generate-contract.ts", "lint:harmony": "node scripts/check-harmony-types.js", "submodule": "git submodule update --init --recursive", diff --git a/scripts/check-packlist.js b/scripts/check-packlist.js index 4c6a0a0a..f6c89ef4 100644 --- a/scripts/check-packlist.js +++ b/scripts/check-packlist.js @@ -4,7 +4,7 @@ // must and must not be there. Run in lint CI and right before `npm publish` // (with --release, which additionally requires the Harmony HAR). const { execFileSync } = require('child_process'); -const { files: filesField } = require('../package.json'); +const { files: filesField, types } = require('../package.json'); const release = process.argv.includes('--release'); // Also compile the packed native sources (needs the submodules and a C @@ -56,6 +56,8 @@ const required = [ 'package.json', 'src/index.ts', 'src/NativePushy.ts', + // The declarations apps type-check against (bun build:types emits them). + types, 'android/build.gradle', 'android/src/main/java/cn/reactnative/modules/update/UpdateContext.java', 'ios/RCTPushy/RCTPushy.mm', diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 00000000..3feae524 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,16 @@ +{ + // Emits the published type declarations (lib/typescript), which + // package.json `types` points at: apps then type-check against .d.ts files + // their skipLibCheck covers, instead of compiling this package's sources + // under their own tsconfig and React Native version. + "extends": "./tsconfig.json", + "include": ["src"], + "exclude": ["src/__tests__"], + "compilerOptions": { + "noEmit": false, + "declaration": true, + "emitDeclarationOnly": true, + "rootDir": "src", + "outDir": "lib/typescript" + } +} diff --git a/tsconfig.json b/tsconfig.json index 37c0d63a..fef546b4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "@react-native/typescript-config", "include": ["**/*.ts", "**/*.tsx"], - "exclude": ["**/node_modules", "**/Pods", "**/harmony", "**/Example"], + "exclude": ["lib", "**/node_modules", "**/Pods", "**/harmony", "**/Example"], "compilerOptions": { // The RN base config lists "jest"; this package tests with bun:test and // no longer installs @types/jest, so a clean install would fail tsc. From facb4ce015d4c32fedeca495515ad400c93bead7 Mon Sep 17 00:00:00 2001 From: sunnylqm Date: Mon, 19 Oct 2026 15:41:53 +0800 Subject: [PATCH 3/3] ci: don't require the type declarations in the C++ job's packlist check Co-Authored-By: Claude Opus 5.5 --- scripts/check-packlist.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/check-packlist.js b/scripts/check-packlist.js index f6c89ef4..119addd8 100644 --- a/scripts/check-packlist.js +++ b/scripts/check-packlist.js @@ -57,7 +57,9 @@ const required = [ 'src/index.ts', 'src/NativePushy.ts', // The declarations apps type-check against (bun build:types emits them). - types, + // --compile runs in test.yml's C++ job, which has no JS toolchain to + // build them; the lint workflow and the publish action check them. + ...(compile ? [] : [types]), 'android/build.gradle', 'android/src/main/java/cn/reactnative/modules/update/UpdateContext.java', 'ios/RCTPushy/RCTPushy.mm',