-
Notifications
You must be signed in to change notification settings - Fork 154
CodeSigningPlugin signs too late (assetEmitted) β incompatible with in-memory asset consumers (e.g. Zephyr)Β #1377
Description
Describe the bug
π CodeSigningPlugin signs too late (assetEmitted) β incompatible with in-memory asset consumers (e.g. Zephyr)
Problem
CodeSigningPlugin currently signs bundles using compiler.hooks.assetEmitted.
When using withZephyr(), Zephyr captures and uploads assets during the processAssets phase at PROCESS_ASSETS_STAGE_REPORT (5000), which occurs before assetEmitted.
As a result, bundles are uploaded to the CDN unsigned, making verifyScriptSignature: 'strict' ineffective when using Re.Pack together with Zephyr.
Root Cause
There is a timing mismatch in the compilation lifecycle:
processAssets (ANALYSE 2000) β no signing happens here
processAssets (REPORT 5000) β Zephyr uploads assets (unsigned β)
emit β assets written to disk
assetEmitted β CodeSigningPlugin signs (too late)
Zephyr registers its upload step at PROCESS_ASSETS_STAGE_REPORT:
compilation.hooks.processAssets.tapPromise(
{
name: pluginName,
stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_REPORT, // 5000
},
async (assets) => {
// uploads to CDN
}
);Expected Behavior
Assets should be signed before they are consumed by other plugins/tools during the compilation pipeline.
Proposed Solution
Move the signing step earlier in the lifecycle by using processAssets, specifically at PROCESS_ASSETS_STAGE_ANALYSE (2000), while assets are still in memory.
processAssets (ANALYSE 2000) β sign here in memory β
processAssets (REPORT 5000) β Zephyr uploads signed assets β
emit β assets written to disk (already signed) β
Suggested Implementation
Instead of signing in assetEmitted, perform signing during processAssets and update assets in memory using compilation.updateAsset():
compiler.hooks.thisCompilation.tap('RepackCodeSigningPlugin', (compilation) => {
const { Compilation, sources } = compiler.webpack;
compilation.hooks.processAssets.tap(
{
name: 'RepackCodeSigningPlugin',
stage: Compilation.PROCESS_ASSETS_STAGE_ANALYSE,
},
() => {
for (const chunk of compilation.chunks) {
for (const file of chunk.files) {
// apply existing signing logic here
compilation.updateAsset(file, new sources.RawSource(signed));
}
}
}
);
});Compatibility Consideration
To preserve backward compatibility, this could be introduced as a configurable option:
type CodeSigningPluginConfig = {
signingStage?: 'assetEmitted' | 'processAssets';
};assetEmittedβ current default behaviorprocessAssetsβ enables in-memory signing (recommended for Zephyr)
Verified
This approach has been tested using a custom plugin:
- Bundles are signed in memory before
PROCESS_ASSETS_STAGE_REPORT - Zephyr uploads already signed assets
- Output files on disk remain correctly signed
verifyScriptSignature: 'strict'works as expected end-to-end
Environment
@callstack/repack: [version]- Zephyr integration: [version]
- Platform: Android / iOS
System Info
macOS (Apple Silicon)
Node: 22.19.0
Pnpm: 9.15.3
React Native: 0.78.2
Re.Pack: 5.2.5
Rspack: 1.3.4
Build type: Release
Platform: Android / iOSRe.Pack Version
5.2.5
Reproduction
Not a minimal reproduction repository, but the issue can be consistently reproduced when using CodeSigningPlugin together with Zephyr integration (withZephyr()). Happy to provide a minimal reproduction if needed.
Steps to reproduce
- Configure Re.Pack with CodeSigningPlugin enabled
- Integrate Zephyr using withZephyr()
- Build the project (e.g. release bundle)
- Observe that Zephyr uploads assets during processAssets
- Inspect uploaded bundle β it is not signed
- Inspect output on disk β bundle is signed
Result:
Assets uploaded to CDN are unsigned, while local output is signed.