pulse-updates 1.0.17 → 1.0.18

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 +50 -19
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pulse-updates",
3
- "version": "1.0.17",
3
+ "version": "1.0.18",
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",
@@ -187,12 +187,14 @@ function findHermesc(platform) {
187
187
 
188
188
  /**
189
189
  * Locate hermesc, and if it isn't there yet, try to materialize it.
190
+ * Only used when the caller has REQUIRED Hermes (config.requireHermes / --require-hermes).
190
191
  * A React Native app may only carry hermesc inside the installed CocoaPods (it
191
192
  * does not always ship in node_modules for every RN version), so on macOS a
192
193
  * 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.
194
+ * pod-install once, then re-checking. If it still can't be found we throw, rather
195
+ * than silently shipping a plain-JS OTA bundle (readable source over the air).
196
+ * Apps that intentionally run without Hermes must NOT set requireHermes — they get
197
+ * the default best-effort path (compile if hermesc is present, plain JS otherwise).
196
198
  */
197
199
  function ensureHermesc(platform) {
198
200
  let hermesc = findHermesc(platform);
@@ -260,23 +262,42 @@ function createBundle(platform, bundleDir, entryFile = 'index.ts') {
260
262
  }
261
263
 
262
264
  /**
263
- * Compile bundle with Hermes
265
+ * Compile bundle with Hermes.
266
+ *
267
+ * Default (best-effort): if hermesc is found, compile to bytecode; if not, warn and
268
+ * ship the plain JS bundle — this is correct for apps that don't run Hermes (JSC,
269
+ * or an intentionally plain-JS runtime), which is why it stays the default.
270
+ *
271
+ * With options.requireHermes (config.requireHermes / --require-hermes): the app has
272
+ * declared it runs on Hermes, so a plain-JS OTA would be both readable source and a
273
+ * runtime mismatch. In that mode we locate-or-materialize hermesc (throwing if we
274
+ * can't), pass -fstrip-function-names to shrink the string table, and assert the
275
+ * output is bytecode before returning. eSound and Lyra run in this mode.
264
276
  */
265
- function compileWithHermes(bundlePath, platform) {
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);
277
+ function compileWithHermes(bundlePath, platform, options = {}) {
278
+ const requireHermes = !!options.requireHermes;
279
+
280
+ // In required mode, ensureHermesc throws if it can't obtain a compiler.
281
+ // Otherwise fall back to best-effort discovery.
282
+ const hermesc = requireHermes ? ensureHermesc(platform) : findHermesc(platform);
283
+ if (!hermesc) {
284
+ // Only reachable when Hermes is NOT required.
285
+ logWarning('Hermes compiler not found, using plain JS bundle');
286
+ return bundlePath;
287
+ }
269
288
 
270
289
  const hbcPath = bundlePath.replace('.bundle', '.hbc');
271
290
 
272
291
  log(` Compiling with Hermes...`, colors.dim);
273
292
 
274
293
  // -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];
294
+ // -fstrip-function-names (required mode only): drop JS function names from the
295
+ // bytecode string table (smaller bundle, less readable). Trade-off: JS function
296
+ // names disappear from crash stack traces, so it stays opt-in via requireHermes.
297
+ // Does NOT touch string literals.
298
+ const hermesArgs = ['-emit-binary'];
299
+ if (requireHermes) hermesArgs.push('-fstrip-function-names');
300
+ hermesArgs.push('-out', hbcPath, bundlePath);
280
301
 
281
302
  // Use spawnSync with larger buffer to avoid ENOBUFS error
282
303
  const result = spawnSync(hermesc, hermesArgs, {
@@ -286,12 +307,17 @@ function compileWithHermes(bundlePath, platform) {
286
307
  });
287
308
 
288
309
  if (result.error) {
289
- throw result.error;
310
+ if (requireHermes) throw result.error;
311
+ logWarning(`Hermes compilation failed: ${result.error.message}`);
312
+ return bundlePath;
290
313
  }
291
314
 
292
315
  if (result.status !== 0) {
293
316
  const stderr = result.stderr || '';
294
- throw new Error(`Hermes exited with code ${result.status}: ${stderr.slice(0, 500)}`);
317
+ const message = `Hermes exited with code ${result.status}: ${stderr.slice(0, 500)}`;
318
+ if (requireHermes) throw new Error(message);
319
+ logWarning(`Hermes compilation failed: ${message}`);
320
+ return bundlePath;
295
321
  }
296
322
 
297
323
  // Remove the plain JS bundle, keep only HBC
@@ -300,9 +326,8 @@ function compileWithHermes(bundlePath, platform) {
300
326
  const finalPath = bundlePath;
301
327
  fs.renameSync(hbcPath, finalPath);
302
328
 
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)) {
329
+ // In required mode, belt-and-suspenders: the launch asset MUST be Hermes bytecode.
330
+ if (requireHermes && !isHermesBytecode(finalPath)) {
306
331
  throw new Error(
307
332
  'Post-compile check failed: launch bundle is not Hermes bytecode. Refusing to publish a plain-JS OTA bundle.'
308
333
  );
@@ -714,6 +739,12 @@ function loadConfig(options) {
714
739
  skipBundle: options['skip-bundle'] || false,
715
740
  build: options.build || process.env.PULSE_BUILD || fileConfig.build || null,
716
741
  message: options.message || fileConfig.message || null,
742
+ // Opt-in: the app runs on Hermes, so a plain-JS OTA must never be shipped.
743
+ // Makes a missing/failing hermesc fatal (with a one-shot pod-install attempt on
744
+ // macOS), enables -fstrip-function-names, and asserts bytecode output.
745
+ // Leave unset for apps that intentionally run plain JS (best-effort default).
746
+ requireHermes: options['require-hermes'] || process.env.PULSE_REQUIRE_HERMES === '1' ||
747
+ fileConfig.requireHermes || false,
717
748
  };
718
749
  }
719
750
 
@@ -763,7 +794,7 @@ async function publish(options) {
763
794
 
764
795
  // Step 2: Compile with Hermes
765
796
  logStep('2/6', 'Compiling with Hermes...');
766
- compileWithHermes(bundlePath, config.platform);
797
+ compileWithHermes(bundlePath, config.platform, { requireHermes: config.requireHermes });
767
798
  logSuccess('Hermes compilation complete');
768
799
  } else {
769
800
  logStep('1/6', 'Skipping bundle creation (--skip-bundle)');