pulse-updates 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -392,7 +392,10 @@ npx pulse-updates register-capabilities --platform <ios|android> [--api-key <key
392
392
 
393
393
  Run it at build time so the server can warn when a future JS-only update references native modules
394
394
  the installed binary doesn't have. When the release pipeline already has the exact embedded bundle,
395
- pass it with `--bundle <path>`; otherwise the command builds the production Metro graph automatically.
395
+ pass it with `--bundle <path>`. On Android the command automatically reuses the newest Gradle packager
396
+ source map (the exact pre-Hermes graph retained under `android/app/build/intermediates/sourcemaps`), so
397
+ a normal Gradle release build needs no extra path. If no embedded or generated source exists, it builds
398
+ an unminified, scan-only Metro graph; that fallback is never uploaded or used as the app's launch bundle.
396
399
 
397
400
  ### Configuration File
398
401
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pulse-updates",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "OTA updates for React Native - lightweight alternative to expo-updates",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -36,7 +36,21 @@ export function extractNativeModulesFromSource(source) {
36
36
  }
37
37
 
38
38
  export function extractNativeModules(bundlePath) {
39
- return extractNativeModulesFromSource(fs.readFileSync(bundlePath, 'utf8'));
39
+ if (isHermesBytecode(bundlePath)) {
40
+ throw new Error(
41
+ `Capability source is Hermes bytecode: ${bundlePath}. ` +
42
+ 'Pass the pre-Hermes bundle or its index.android.bundle.packager.map source map.'
43
+ );
44
+ }
45
+ const raw = fs.readFileSync(bundlePath, 'utf8');
46
+ if (bundlePath.endsWith('.map')) {
47
+ const sourceMap = JSON.parse(raw);
48
+ if (!Array.isArray(sourceMap.sourcesContent)) {
49
+ throw new Error(`Capability source map has no sourcesContent: ${bundlePath}`);
50
+ }
51
+ return extractNativeModulesFromSource(sourceMap.sourcesContent.filter(Boolean).join('\n'));
52
+ }
53
+ return extractNativeModulesFromSource(raw);
40
54
  }
41
55
 
42
56
  /**
@@ -235,9 +249,10 @@ function ensureHermesc(platform) {
235
249
  /**
236
250
  * Create JS bundle using Metro
237
251
  */
238
- function createBundle(platform, bundleDir, entryFile = 'index.ts') {
252
+ function createBundle(platform, bundleDir, entryFile = 'index.ts', options = {}) {
239
253
  const bundleOutput = path.join(bundleDir, platform, 'index.bundle');
240
254
  const assetsDir = path.join(bundleDir, platform, 'assets');
255
+ const minify = options.minify ?? true;
241
256
 
242
257
  // Clean and create directories
243
258
  fs.rmSync(path.join(bundleDir, platform), { recursive: true, force: true });
@@ -250,11 +265,13 @@ function createBundle(platform, bundleDir, entryFile = 'index.ts') {
250
265
  'npx react-native bundle',
251
266
  `--platform ${platform}`,
252
267
  '--dev false',
268
+ options.resetCache ? '--reset-cache' : null,
253
269
  `--entry-file ${entryFile}`,
254
270
  `--bundle-output ${bundleOutput}`,
255
271
  `--assets-dest ${assetsDir}`,
256
- '--minify true',
257
- ].join(' ');
272
+ options.sourceMap ? `--sourcemap-output ${bundleOutput}.map` : null,
273
+ `--minify ${minify}`,
274
+ ].filter(Boolean).join(' ');
258
275
 
259
276
  execSync(cmd, { stdio: 'inherit' });
260
277
 
@@ -1129,14 +1146,50 @@ export function resolveCapabilitiesBundle(options, config, bundleFactory = creat
1129
1146
  return resolved;
1130
1147
  }
1131
1148
 
1149
+ if (config.platform === 'android') {
1150
+ const generated = findLatestGeneratedAndroidCapabilitySource(config.projectRoot || process.cwd());
1151
+ if (generated) {
1152
+ log(` Using the latest Gradle-generated Android capability source: ${generated}`, colors.dim);
1153
+ return generated;
1154
+ }
1155
+ }
1156
+
1132
1157
  logStep('1/2', `Building the ${config.platform} production bundle for capability discovery...`);
1133
1158
  return bundleFactory(
1134
1159
  config.platform,
1135
1160
  path.resolve(config.bundleDir || './dist'),
1136
- config.entryFile || 'index.ts'
1161
+ config.entryFile || 'index.ts',
1162
+ // Capability discovery scans source text and does not ship this bundle. Keep
1163
+ // it unminified: worklet transformers may create short-lived virtual modules
1164
+ // that Metro's minifier tries to hash after they have already been removed.
1165
+ // The release pipeline should still pass --bundle whenever it has the exact
1166
+ // embedded artifact, which remains the authoritative path.
1167
+ { minify: false, resetCache: true, sourceMap: true }
1137
1168
  );
1138
1169
  }
1139
1170
 
1171
+ /** Find the pre-Hermes graph most recently produced by a React Native Android Gradle task. */
1172
+ export function findLatestGeneratedAndroidCapabilitySource(projectRoot = process.cwd()) {
1173
+ const buildRoot = path.join(projectRoot, 'android', 'app', 'build');
1174
+ if (!fs.existsSync(buildRoot)) return null;
1175
+
1176
+ const candidates = [];
1177
+ const visit = (directory) => {
1178
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
1179
+ const fullPath = path.join(directory, entry.name);
1180
+ if (entry.isDirectory()) visit(fullPath);
1181
+ else if (entry.isFile() && entry.name === 'index.android.bundle.packager.map') {
1182
+ candidates.push({ path: fullPath, modifiedAt: fs.statSync(fullPath).mtimeMs });
1183
+ } else if (entry.isFile() && entry.name === 'index.android.bundle' && !isHermesBytecode(fullPath)) {
1184
+ candidates.push({ path: fullPath, modifiedAt: fs.statSync(fullPath).mtimeMs });
1185
+ }
1186
+ }
1187
+ };
1188
+ visit(buildRoot);
1189
+ candidates.sort((a, b) => b.modifiedAt - a.modifiedAt);
1190
+ return candidates[0]?.path || null;
1191
+ }
1192
+
1140
1193
 
1141
1194
  /**
1142
1195
  * The experiments an app is running, and how their split is holding.