pulse-updates 1.0.17 → 1.0.19
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/package.json +1 -1
- package/scripts/publish.mjs +108 -19
package/package.json
CHANGED
package/scripts/publish.mjs
CHANGED
|
@@ -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.
|
|
194
|
-
* silently
|
|
195
|
-
*
|
|
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
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
|
276
|
-
//
|
|
277
|
-
//
|
|
278
|
-
//
|
|
279
|
-
const hermesArgs = ['-emit-binary'
|
|
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
|
-
|
|
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
|
-
//
|
|
304
|
-
|
|
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
|
);
|
|
@@ -621,6 +646,43 @@ function detectRuntimeVersion(platform) {
|
|
|
621
646
|
return null;
|
|
622
647
|
}
|
|
623
648
|
|
|
649
|
+
/**
|
|
650
|
+
* Detect whether the app is configured to run on Hermes, from its native build
|
|
651
|
+
* config. Used to auto-resolve requireHermes when it isn't explicitly set: a Hermes
|
|
652
|
+
* app should never receive a plain-JS OTA, so detection turns enforcement on for it
|
|
653
|
+
* without the developer having to declare the flag. Returns true / false / null
|
|
654
|
+
* (null = couldn't determine → caller stays best-effort).
|
|
655
|
+
*/
|
|
656
|
+
function detectHermesEnabled(platform) {
|
|
657
|
+
try {
|
|
658
|
+
if (platform === 'android') {
|
|
659
|
+
const gp = path.resolve('android/gradle.properties');
|
|
660
|
+
if (fs.existsSync(gp)) {
|
|
661
|
+
const m = fs.readFileSync(gp, 'utf8').match(/^\s*hermesEnabled\s*=\s*(\S+)/m);
|
|
662
|
+
if (m) return /^true$/i.test(m[1].trim());
|
|
663
|
+
}
|
|
664
|
+
} else if (platform === 'ios') {
|
|
665
|
+
const podfile = path.resolve('ios/Podfile');
|
|
666
|
+
if (fs.existsSync(podfile)) {
|
|
667
|
+
const txt = fs.readFileSync(podfile, 'utf8');
|
|
668
|
+
// ENV['USE_HERMES'] = '1' / USE_HERMES => true, or :hermes_enabled => true
|
|
669
|
+
const useHermes = txt.match(/USE_HERMES['"\]\s]*[=>]+\s*['"]?(\w+)/);
|
|
670
|
+
if (useHermes) return /^(1|true|yes)$/i.test(useHermes[1]);
|
|
671
|
+
const flag = txt.match(/:hermes_enabled\s*=>\s*(\w+)/);
|
|
672
|
+
if (flag) return /^true$/i.test(flag[1]);
|
|
673
|
+
}
|
|
674
|
+
// Fallback: the Pods lockfile lists hermes-engine when Hermes is on.
|
|
675
|
+
const lock = path.resolve('ios/Podfile.lock');
|
|
676
|
+
if (fs.existsSync(lock)) {
|
|
677
|
+
return /hermes-engine/.test(fs.readFileSync(lock, 'utf8'));
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
} catch {
|
|
681
|
+
/* detection is best-effort */
|
|
682
|
+
}
|
|
683
|
+
return null; // unknown
|
|
684
|
+
}
|
|
685
|
+
|
|
624
686
|
/**
|
|
625
687
|
* Extract base API URL from full manifest URL
|
|
626
688
|
* e.g., "https://pulse.example.com/pulse/manifest/my-app" -> "https://pulse.example.com"
|
|
@@ -703,6 +765,27 @@ function loadConfig(options) {
|
|
|
703
765
|
}
|
|
704
766
|
}
|
|
705
767
|
|
|
768
|
+
// Resolve Hermes enforcement (tri-state): explicit true/false wins; when left
|
|
769
|
+
// unset, auto-detect from the app's native config — a Hermes app is enforced,
|
|
770
|
+
// anything else stays best-effort.
|
|
771
|
+
let requireHermes =
|
|
772
|
+
options['require-hermes'] ? true
|
|
773
|
+
: options['no-require-hermes'] ? false
|
|
774
|
+
: process.env.PULSE_REQUIRE_HERMES != null ? (process.env.PULSE_REQUIRE_HERMES === '1')
|
|
775
|
+
: fileConfig.requireHermes; // true | false | undefined
|
|
776
|
+
if (requireHermes === undefined) {
|
|
777
|
+
const detected = platform ? detectHermesEnabled(platform) : null;
|
|
778
|
+
requireHermes = detected === true;
|
|
779
|
+
log(
|
|
780
|
+
` Auto-detected Hermes enforcement: ${
|
|
781
|
+
detected === true ? 'ON (app uses Hermes)'
|
|
782
|
+
: detected === false ? 'OFF (app not on Hermes)'
|
|
783
|
+
: 'OFF (could not determine — best-effort)'
|
|
784
|
+
}`,
|
|
785
|
+
colors.dim
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
|
|
706
789
|
return {
|
|
707
790
|
apiUrl,
|
|
708
791
|
apiKey: options['api-key'] || process.env.PULSE_API_KEY || fileConfig.apiKey,
|
|
@@ -714,6 +797,12 @@ function loadConfig(options) {
|
|
|
714
797
|
skipBundle: options['skip-bundle'] || false,
|
|
715
798
|
build: options.build || process.env.PULSE_BUILD || fileConfig.build || null,
|
|
716
799
|
message: options.message || fileConfig.message || null,
|
|
800
|
+
// Whether to enforce Hermes (resolved above): explicit config/flag/env, or
|
|
801
|
+
// auto-detected from the app's native build config when left unset. When on, a
|
|
802
|
+
// missing/failing hermesc is fatal (one-shot pod-install attempt on macOS),
|
|
803
|
+
// -fstrip-function-names is passed, and bytecode output is asserted — so a
|
|
804
|
+
// Hermes app can never ship a readable plain-JS OTA.
|
|
805
|
+
requireHermes,
|
|
717
806
|
};
|
|
718
807
|
}
|
|
719
808
|
|
|
@@ -763,7 +852,7 @@ async function publish(options) {
|
|
|
763
852
|
|
|
764
853
|
// Step 2: Compile with Hermes
|
|
765
854
|
logStep('2/6', 'Compiling with Hermes...');
|
|
766
|
-
compileWithHermes(bundlePath, config.platform);
|
|
855
|
+
compileWithHermes(bundlePath, config.platform, { requireHermes: config.requireHermes });
|
|
767
856
|
logSuccess('Hermes compilation complete');
|
|
768
857
|
} else {
|
|
769
858
|
logStep('1/6', 'Skipping bundle creation (--skip-bundle)');
|