@@ -6,9 +6,54 @@ import type { CommandContext } from './types';
66import { translateOptions } from './utils' ;
77import { isPPKBundleFileName , scriptName , tempDir } from './utils/constants' ;
88import { t } from './utils/i18n' ;
9- import { enumZipEntries , readEntry } from './utils/zip-entries' ;
9+ import {
10+ enumZipEntries ,
11+ readEntry ,
12+ readEntryPrefix ,
13+ } from './utils/zip-entries' ;
14+ import {
15+ ZIP_ENTRY_SNIFF_BYTES ,
16+ zipOptionsForManifestEntry ,
17+ zipOptionsForPatchEntry ,
18+ zipOptionsForPayloadEntry ,
19+ } from './utils/zip-options' ;
1020
1121type Diff = ( oldSource ?: Buffer , newSource ?: Buffer ) => Buffer ;
22+ type HpatchCover = {
23+ oldPos : number | string | bigint ;
24+ newPos : number | string | bigint ;
25+ len : number | string | bigint ;
26+ } ;
27+ type HpatchCompatiblePlan = {
28+ covers ?: HpatchCover [ ] ;
29+ } ;
30+ type HdiffWithCoversOptions = {
31+ mode ?: 'replace' | 'merge' | 'native-coalesce' ;
32+ } ;
33+ type HdiffModule = {
34+ diff ?: Diff ;
35+ diffWithCovers ?: (
36+ oldSource : Buffer ,
37+ newSource : Buffer ,
38+ covers : HpatchCover [ ] ,
39+ options ?: HdiffWithCoversOptions ,
40+ ) => { diff ?: Buffer } ;
41+ } ;
42+ type BsdiffModule = {
43+ diff ?: Diff ;
44+ } ;
45+ type ChiffModule = {
46+ hpatchCompatiblePlanResult ?: (
47+ oldSource : Buffer ,
48+ newSource : Buffer ,
49+ ) => HpatchCompatiblePlan ;
50+ hpatchApproximatePlanResult ?: (
51+ oldSource : Buffer ,
52+ newSource : Buffer ,
53+ ) => HpatchCompatiblePlan ;
54+ } ;
55+ type ChiffHpatchPolicy = 'off' | 'costed' ;
56+ type ChiffHpatchExactPolicy = 'off' | 'on' ;
1257type EntryMap = Record < string , { crc32 : number ; fileName : string } > ;
1358type CrcMap = Record < number , string > ;
1459type CopyMap = Record < string , string > ;
@@ -28,22 +73,134 @@ type DiffCommandConfig = {
2873
2974export { enumZipEntries , readEntry } ;
3075
31- const loadDiffModule = ( pkgName : string ) : Diff | undefined => {
76+ const loadModule = < T > ( pkgName : string ) : T | undefined => {
3277 const resolvePaths = [ '.' , npm . packages , yarn . packages ] ;
3378
3479 try {
3580 const resolved = require . resolve ( pkgName , { paths : resolvePaths } ) ;
36- const mod = require ( resolved ) ;
37- if ( mod ?. diff ) {
38- return mod . diff as Diff ;
39- }
81+ return require ( resolved ) as T ;
4082 } catch { }
4183
4284 return undefined ;
4385} ;
4486
45- const hdiff = loadDiffModule ( 'node-hdiffpatch' ) ;
46- const bsdiff = loadDiffModule ( 'node-bsdiff' ) ;
87+ const hdiff = loadModule < HdiffModule > ( 'node-hdiffpatch' ) ;
88+ const bsdiff = loadModule < BsdiffModule > ( 'node-bsdiff' ) ;
89+ const chiff = loadModule < ChiffModule > ( '@chiff/node' ) ;
90+
91+ // Structured covers are experimental and can be expensive on real Hermes input.
92+ // Keep native hdiff as the default unless the server explicitly opts in.
93+ function resolveChiffHpatchPolicy ( policy ?: unknown ) : ChiffHpatchPolicy {
94+ const value = String (
95+ policy ?? process . env . RNU_CHIFF_HPATCH_POLICY ?? 'off' ,
96+ ) . toLowerCase ( ) ;
97+ if (
98+ value === 'costed' ||
99+ value === 'on' ||
100+ value === 'true' ||
101+ value === '1'
102+ ) {
103+ return 'costed' ;
104+ }
105+ return 'off' ;
106+ }
107+
108+ function resolveChiffHpatchMinNativeBytes ( value ?: unknown ) : number {
109+ const raw = value ?? process . env . RNU_CHIFF_HPATCH_MIN_NATIVE_BYTES ?? 4096 ;
110+ const parsed = Number ( raw ) ;
111+ if ( ! Number . isFinite ( parsed ) || parsed < 0 ) {
112+ return 4096 ;
113+ }
114+ return Math . floor ( parsed ) ;
115+ }
116+
117+ function resolveChiffHpatchExactPolicy ( policy ?: unknown ) : ChiffHpatchExactPolicy {
118+ const value = String (
119+ policy ?? process . env . RNU_CHIFF_HPATCH_EXACT_COVERS ?? 'off' ,
120+ ) . toLowerCase ( ) ;
121+ if ( value === 'on' || value === 'true' || value === '1' ) {
122+ return 'on' ;
123+ }
124+ return 'off' ;
125+ }
126+
127+ function createChiffAwareHdiff (
128+ hdiffModule : HdiffModule ,
129+ chiffModule : ChiffModule | undefined ,
130+ policy : ChiffHpatchPolicy ,
131+ minNativeBytes : number ,
132+ exactPolicy : ChiffHpatchExactPolicy ,
133+ ) : Diff {
134+ const baseDiff = hdiffModule . diff ;
135+ if ( ! baseDiff ) {
136+ throw new Error ( t ( 'nodeHdiffpatchRequired' , { scriptName } ) ) ;
137+ }
138+
139+ if ( policy === 'off' ) {
140+ return baseDiff ;
141+ }
142+
143+ return ( oldSource ?: Buffer , newSource ?: Buffer ) => {
144+ const nativeDiff = baseDiff ( oldSource , newSource ) ;
145+ if ( ! oldSource || ! newSource || ! hdiffModule . diffWithCovers ) {
146+ return nativeDiff ;
147+ }
148+
149+ let bestDiff = nativeDiff ;
150+ const tryDiffWithCovers = (
151+ covers : HpatchCover [ ] ,
152+ mode : 'replace' | 'merge' | 'native-coalesce' ,
153+ ) => {
154+ try {
155+ const result = hdiffModule . diffWithCovers ?.(
156+ oldSource ,
157+ newSource ,
158+ covers ,
159+ { mode } ,
160+ ) ;
161+ if (
162+ Buffer . isBuffer ( result ?. diff ) &&
163+ result . diff . length < bestDiff . length
164+ ) {
165+ bestDiff = result . diff ;
166+ }
167+ } catch { }
168+ } ;
169+
170+ tryDiffWithCovers ( [ ] , 'native-coalesce' ) ;
171+
172+ if ( nativeDiff . length < minNativeBytes ) {
173+ return bestDiff ;
174+ }
175+
176+ try {
177+ const approximatePlan = chiffModule ?. hpatchApproximatePlanResult ?.(
178+ oldSource ,
179+ newSource ,
180+ ) ;
181+ if ( Array . isArray ( approximatePlan ?. covers ) ) {
182+ tryDiffWithCovers ( approximatePlan . covers , 'merge' ) ;
183+ }
184+ } catch { }
185+
186+ if (
187+ exactPolicy === 'off' ||
188+ ! chiffModule ?. hpatchCompatiblePlanResult
189+ ) {
190+ return bestDiff ;
191+ }
192+
193+ try {
194+ const plan = chiffModule . hpatchCompatiblePlanResult ( oldSource , newSource ) ;
195+ if ( Array . isArray ( plan . covers ) ) {
196+ tryDiffWithCovers ( plan . covers , 'replace' ) ;
197+ tryDiffWithCovers ( plan . covers , 'merge' ) ;
198+ }
199+ } catch { }
200+
201+ return bestDiff ;
202+ } ;
203+ }
47204
48205function basename ( fn : string ) : string | undefined {
49206 const m = / ^ ( .+ \/ ) [ ^ \/ ] + \/ ? $ / . exec ( fn ) ;
@@ -123,6 +280,7 @@ async function diffFromPPK(
123280 zipfile . addBuffer (
124281 diffFn ( originSource , newSource ) ,
125282 `${ entry . fileName } .patch` ,
283+ zipOptionsForPatchEntry ( ) ,
126284 ) ;
127285 //console.log('End diff');
128286 } else {
@@ -150,6 +308,11 @@ async function diffFromPPK(
150308 addEntry ( basePath ) ;
151309 }
152310
311+ const entryPrefix = await readEntryPrefix (
312+ entry ,
313+ nextZipfile ,
314+ ZIP_ENTRY_SNIFF_BYTES ,
315+ ) ;
153316 await new Promise < void > ( ( resolve , reject ) => {
154317 nextZipfile . openReadStream ( entry , ( err , readStream ) => {
155318 if ( err ) {
@@ -160,7 +323,11 @@ async function diffFromPPK(
160323 new Error ( `Unable to read zip entry: ${ entry . fileName } ` ) ,
161324 ) ;
162325 }
163- zipfile . addReadStream ( readStream , entry . fileName ) ;
326+ zipfile . addReadStream (
327+ readStream ,
328+ entry . fileName ,
329+ zipOptionsForPayloadEntry ( entry . fileName , entryPrefix ) ,
330+ ) ;
164331 readStream . on ( 'end' , ( ) => {
165332 //console.log('add finished');
166333 resolve ( void 0 ) ;
@@ -183,6 +350,7 @@ async function diffFromPPK(
183350 zipfile . addBuffer (
184351 Buffer . from ( JSON . stringify ( { copies, deletes } ) ) ,
185352 '__diff.json' ,
353+ zipOptionsForManifestEntry ( ) ,
186354 ) ;
187355 zipfile . end ( ) ;
188356 await writePromise ;
@@ -248,6 +416,7 @@ async function diffFromPackage(
248416 zipfile . addBuffer (
249417 diffFn ( originSource , newSource ) ,
250418 `${ entry . fileName } .patch` ,
419+ zipOptionsForPatchEntry ( ) ,
251420 ) ;
252421 //console.log('End diff');
253422 } else {
@@ -263,6 +432,11 @@ async function diffFromPackage(
263432 return ;
264433 }
265434
435+ const entryPrefix = await readEntryPrefix (
436+ entry ,
437+ nextZipfile ,
438+ ZIP_ENTRY_SNIFF_BYTES ,
439+ ) ;
266440 await new Promise < void > ( ( resolve , reject ) => {
267441 nextZipfile . openReadStream ( entry , ( err , readStream ) => {
268442 if ( err ) {
@@ -273,7 +447,11 @@ async function diffFromPackage(
273447 new Error ( `Unable to read zip entry: ${ entry . fileName } ` ) ,
274448 ) ;
275449 }
276- zipfile . addReadStream ( readStream , entry . fileName ) ;
450+ zipfile . addReadStream (
451+ readStream ,
452+ entry . fileName ,
453+ zipOptionsForPayloadEntry ( entry . fileName , entryPrefix ) ,
454+ ) ;
277455 readStream . on ( 'end' , ( ) => {
278456 //console.log('add finished');
279457 resolve ( void 0 ) ;
@@ -283,13 +461,22 @@ async function diffFromPackage(
283461 }
284462 } ) ;
285463
286- zipfile . addBuffer ( Buffer . from ( JSON . stringify ( { copies } ) ) , '__diff.json' ) ;
464+ zipfile . addBuffer (
465+ Buffer . from ( JSON . stringify ( { copies } ) ) ,
466+ '__diff.json' ,
467+ zipOptionsForManifestEntry ( ) ,
468+ ) ;
287469 zipfile . end ( ) ;
288470 await writePromise ;
289471}
290472
291473type DiffCommandOptions = {
292474 customDiff ?: Diff ;
475+ customHdiffModule ?: HdiffModule ;
476+ customChiffModule ?: ChiffModule ;
477+ chiffHpatchPolicy ?: ChiffHpatchPolicy ;
478+ chiffHpatchMinNativeBytes ?: number | string ;
479+ chiffHpatchExactCovers ?: ChiffHpatchExactPolicy | boolean | string | number ;
293480 [ key : string ] : any ;
294481} ;
295482
@@ -302,16 +489,23 @@ function resolveDiffImplementation(
302489 }
303490
304491 if ( useHdiff ) {
305- if ( ! hdiff ) {
492+ const hdiffModule = options . customHdiffModule ?? hdiff ;
493+ if ( ! hdiffModule ?. diff ) {
306494 throw new Error ( t ( 'nodeHdiffpatchRequired' , { scriptName } ) ) ;
307495 }
308- return hdiff ;
496+ return createChiffAwareHdiff (
497+ hdiffModule ,
498+ options . customChiffModule ?? chiff ,
499+ resolveChiffHpatchPolicy ( options . chiffHpatchPolicy ) ,
500+ resolveChiffHpatchMinNativeBytes ( options . chiffHpatchMinNativeBytes ) ,
501+ resolveChiffHpatchExactPolicy ( options . chiffHpatchExactCovers ) ,
502+ ) ;
309503 }
310504
311- if ( ! bsdiff ) {
505+ if ( ! bsdiff ?. diff ) {
312506 throw new Error ( t ( 'nodeBsdiffRequired' , { scriptName } ) ) ;
313507 }
314- return bsdiff ;
508+ return bsdiff . diff ;
315509}
316510
317511function diffArgsCheck (
0 commit comments