pulse-updates 1.0.16 → 1.0.17

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/scripts/publish.mjs +91 -33
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pulse-updates",
3
- "version": "1.0.16",
3
+ "version": "1.0.17",
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",
@@ -157,12 +157,15 @@ function getMimeType(ext) {
157
157
  */
158
158
  function findHermesc(platform) {
159
159
  const possiblePaths = [
160
- // iOS paths
160
+ // iOS paths (present once Pods are installed)
161
161
  'ios/Pods/hermes-engine/destroot/bin/hermesc',
162
+ // hermesc as shipped inside react-native itself (host-arch dirs vary by RN version)
162
163
  'node_modules/react-native/sdks/hermesc/osx-bin/hermesc',
163
164
  'node_modules/react-native/sdks/hermesc/linux64-bin/hermesc',
164
- // Android paths
165
+ 'node_modules/react-native/sdks/hermesc/win64-bin/hermesc.exe',
166
+ // Android paths (present once an Android build has run)
165
167
  'node_modules/react-native/ReactAndroid/hermes-engine/build/hermes/bin/hermesc',
168
+ 'node_modules/react-native/ReactAndroid/hermes-engine/build/hermes/tools/hermesc/hermesc',
166
169
  ];
167
170
 
168
171
  for (const p of possiblePaths) {
@@ -172,12 +175,59 @@ function findHermesc(platform) {
172
175
  }
173
176
  }
174
177
 
175
- // Try to find via which
178
+ // Try to find via PATH
176
179
  try {
177
- return execSync('which hermesc', { encoding: 'utf8' }).trim();
180
+ const viaPath = execSync('command -v hermesc', { encoding: 'utf8' }).trim();
181
+ if (viaPath) return viaPath;
178
182
  } catch {
179
- return null;
183
+ /* not on PATH */
180
184
  }
185
+ return null;
186
+ }
187
+
188
+ /**
189
+ * Locate hermesc, and if it isn't there yet, try to materialize it.
190
+ * A React Native app may only carry hermesc inside the installed CocoaPods (it
191
+ * does not always ship in node_modules for every RN version), so on macOS a
192
+ * missing compiler often means "Pods aren't installed" — which we fix by running
193
+ * pod-install once, then re-checking. Anything else is a hard stop: we NEVER
194
+ * silently fall back to a plain-JS OTA bundle (that would ship readable source
195
+ * over the air). Hermes is mandatory on every publish.
196
+ */
197
+ function ensureHermesc(platform) {
198
+ let hermesc = findHermesc(platform);
199
+ if (hermesc) return hermesc;
200
+
201
+ const onMac = process.platform === 'darwin';
202
+ const hasPodfile = fs.existsSync(path.resolve('ios/Podfile'));
203
+ if (onMac && hasPodfile) {
204
+ logWarning('Hermes compiler not found — installing iOS Pods to obtain it (one-shot)...');
205
+ try {
206
+ // Prefer a repo pod-install helper if the app provides one, else npx pod-install.
207
+ const repoHelper = path.resolve('scripts/pod-install-if-mac.js');
208
+ const cmd = fs.existsSync(repoHelper)
209
+ ? `node ${repoHelper}`
210
+ : 'npx pod-install ios';
211
+ execSync(cmd, { stdio: 'inherit' });
212
+ } catch (error) {
213
+ throw new Error(
214
+ `Hermes compiler not found and automatic pod install failed (${error.message}). ` +
215
+ 'Run `npx pod-install ios` (or `cd ios && pod install`) and publish again.'
216
+ );
217
+ }
218
+ hermesc = findHermesc(platform);
219
+ }
220
+
221
+ if (!hermesc) {
222
+ throw new Error(
223
+ 'Hermes compiler (hermesc) could not be located. Pulse publishes Hermes bytecode only — ' +
224
+ 'a plain-JS OTA bundle must never be shipped. Install it before publishing:\n' +
225
+ ' • macOS: `npx pod-install ios` (materializes ios/Pods/hermes-engine/.../hermesc)\n' +
226
+ ' • or ensure react-native ships hermesc under node_modules/react-native/sdks/hermesc/\n' +
227
+ ' • or put hermesc on PATH.'
228
+ );
229
+ }
230
+ return hermesc;
181
231
  }
182
232
 
183
233
  /**
@@ -213,43 +263,51 @@ function createBundle(platform, bundleDir, entryFile = 'index.ts') {
213
263
  * Compile bundle with Hermes
214
264
  */
215
265
  function compileWithHermes(bundlePath, platform) {
216
- const hermesc = findHermesc(platform);
217
- if (!hermesc) {
218
- logWarning('Hermes compiler not found, using plain JS bundle');
219
- return bundlePath;
220
- }
266
+ // Mandatory: locate (and if needed materialize) hermesc. Throws rather than
267
+ // degrading to a plain-JS bundle — see ensureHermesc.
268
+ const hermesc = ensureHermesc(platform);
221
269
 
222
270
  const hbcPath = bundlePath.replace('.bundle', '.hbc');
223
271
 
224
272
  log(` Compiling with Hermes...`, colors.dim);
225
273
 
226
- try {
227
- // Use spawnSync with larger buffer to avoid ENOBUFS error
228
- const result = spawnSync(hermesc, ['-emit-binary', '-out', hbcPath, bundlePath], {
229
- stdio: ['pipe', 'pipe', 'pipe'],
230
- maxBuffer: 100 * 1024 * 1024, // 100MB buffer
231
- encoding: 'utf8',
232
- });
274
+ // -emit-binary: Hermes bytecode.
275
+ // -fstrip-function-names: drop JS function names from the string table (smaller
276
+ // bundle, and function names are the biggest readability gift to anyone
277
+ // inspecting the bytecode). Trade-off: JS function names disappear from crash
278
+ // stack traces. Does NOT touch string literals.
279
+ const hermesArgs = ['-emit-binary', '-fstrip-function-names', '-out', hbcPath, bundlePath];
280
+
281
+ // Use spawnSync with larger buffer to avoid ENOBUFS error
282
+ const result = spawnSync(hermesc, hermesArgs, {
283
+ stdio: ['pipe', 'pipe', 'pipe'],
284
+ maxBuffer: 100 * 1024 * 1024, // 100MB buffer
285
+ encoding: 'utf8',
286
+ });
233
287
 
234
- if (result.error) {
235
- throw result.error;
236
- }
288
+ if (result.error) {
289
+ throw result.error;
290
+ }
237
291
 
238
- if (result.status !== 0) {
239
- const stderr = result.stderr || '';
240
- throw new Error(`Hermes exited with code ${result.status}: ${stderr.slice(0, 500)}`);
241
- }
292
+ if (result.status !== 0) {
293
+ const stderr = result.stderr || '';
294
+ throw new Error(`Hermes exited with code ${result.status}: ${stderr.slice(0, 500)}`);
295
+ }
242
296
 
243
- // Remove the plain JS bundle, keep only HBC
244
- fs.unlinkSync(bundlePath);
245
- // Rename .hbc to .bundle for compatibility
246
- const finalPath = bundlePath;
247
- fs.renameSync(hbcPath, finalPath);
248
- return finalPath;
249
- } catch (error) {
250
- logWarning(`Hermes compilation failed: ${error.message}`);
251
- return bundlePath;
297
+ // Remove the plain JS bundle, keep only HBC
298
+ fs.unlinkSync(bundlePath);
299
+ // Rename .hbc to .bundle for compatibility
300
+ const finalPath = bundlePath;
301
+ fs.renameSync(hbcPath, finalPath);
302
+
303
+ // Belt-and-suspenders: the launch asset MUST be Hermes bytecode. If for any
304
+ // reason it isn't, fail the publish instead of shipping readable JS OTA.
305
+ if (!isHermesBytecode(finalPath)) {
306
+ throw new Error(
307
+ 'Post-compile check failed: launch bundle is not Hermes bytecode. Refusing to publish a plain-JS OTA bundle.'
308
+ );
252
309
  }
310
+ return finalPath;
253
311
  }
254
312
 
255
313
  /**