pulse-updates 1.0.16 → 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.
- package/package.json +1 -1
- package/scripts/publish.mjs +121 -32
package/package.json
CHANGED
package/scripts/publish.mjs
CHANGED
|
@@ -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
|
-
|
|
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,61 @@ function findHermesc(platform) {
|
|
|
172
175
|
}
|
|
173
176
|
}
|
|
174
177
|
|
|
175
|
-
// Try to find via
|
|
178
|
+
// Try to find via PATH
|
|
176
179
|
try {
|
|
177
|
-
|
|
180
|
+
const viaPath = execSync('command -v hermesc', { encoding: 'utf8' }).trim();
|
|
181
|
+
if (viaPath) return viaPath;
|
|
178
182
|
} catch {
|
|
179
|
-
|
|
183
|
+
/* not on PATH */
|
|
184
|
+
}
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
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).
|
|
191
|
+
* A React Native app may only carry hermesc inside the installed CocoaPods (it
|
|
192
|
+
* does not always ship in node_modules for every RN version), so on macOS a
|
|
193
|
+
* missing compiler often means "Pods aren't installed" — which we fix by running
|
|
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).
|
|
198
|
+
*/
|
|
199
|
+
function ensureHermesc(platform) {
|
|
200
|
+
let hermesc = findHermesc(platform);
|
|
201
|
+
if (hermesc) return hermesc;
|
|
202
|
+
|
|
203
|
+
const onMac = process.platform === 'darwin';
|
|
204
|
+
const hasPodfile = fs.existsSync(path.resolve('ios/Podfile'));
|
|
205
|
+
if (onMac && hasPodfile) {
|
|
206
|
+
logWarning('Hermes compiler not found — installing iOS Pods to obtain it (one-shot)...');
|
|
207
|
+
try {
|
|
208
|
+
// Prefer a repo pod-install helper if the app provides one, else npx pod-install.
|
|
209
|
+
const repoHelper = path.resolve('scripts/pod-install-if-mac.js');
|
|
210
|
+
const cmd = fs.existsSync(repoHelper)
|
|
211
|
+
? `node ${repoHelper}`
|
|
212
|
+
: 'npx pod-install ios';
|
|
213
|
+
execSync(cmd, { stdio: 'inherit' });
|
|
214
|
+
} catch (error) {
|
|
215
|
+
throw new Error(
|
|
216
|
+
`Hermes compiler not found and automatic pod install failed (${error.message}). ` +
|
|
217
|
+
'Run `npx pod-install ios` (or `cd ios && pod install`) and publish again.'
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
hermesc = findHermesc(platform);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (!hermesc) {
|
|
224
|
+
throw new Error(
|
|
225
|
+
'Hermes compiler (hermesc) could not be located. Pulse publishes Hermes bytecode only — ' +
|
|
226
|
+
'a plain-JS OTA bundle must never be shipped. Install it before publishing:\n' +
|
|
227
|
+
' • macOS: `npx pod-install ios` (materializes ios/Pods/hermes-engine/.../hermesc)\n' +
|
|
228
|
+
' • or ensure react-native ships hermesc under node_modules/react-native/sdks/hermesc/\n' +
|
|
229
|
+
' • or put hermesc on PATH.'
|
|
230
|
+
);
|
|
180
231
|
}
|
|
232
|
+
return hermesc;
|
|
181
233
|
}
|
|
182
234
|
|
|
183
235
|
/**
|
|
@@ -210,11 +262,26 @@ function createBundle(platform, bundleDir, entryFile = 'index.ts') {
|
|
|
210
262
|
}
|
|
211
263
|
|
|
212
264
|
/**
|
|
213
|
-
* 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.
|
|
214
276
|
*/
|
|
215
|
-
function compileWithHermes(bundlePath, platform) {
|
|
216
|
-
const
|
|
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);
|
|
217
283
|
if (!hermesc) {
|
|
284
|
+
// Only reachable when Hermes is NOT required.
|
|
218
285
|
logWarning('Hermes compiler not found, using plain JS bundle');
|
|
219
286
|
return bundlePath;
|
|
220
287
|
}
|
|
@@ -223,33 +290,49 @@ function compileWithHermes(bundlePath, platform) {
|
|
|
223
290
|
|
|
224
291
|
log(` Compiling with Hermes...`, colors.dim);
|
|
225
292
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
293
|
+
// -emit-binary: Hermes bytecode.
|
|
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);
|
|
301
|
+
|
|
302
|
+
// Use spawnSync with larger buffer to avoid ENOBUFS error
|
|
303
|
+
const result = spawnSync(hermesc, hermesArgs, {
|
|
304
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
305
|
+
maxBuffer: 100 * 1024 * 1024, // 100MB buffer
|
|
306
|
+
encoding: 'utf8',
|
|
307
|
+
});
|
|
237
308
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
309
|
+
if (result.error) {
|
|
310
|
+
if (requireHermes) throw result.error;
|
|
311
|
+
logWarning(`Hermes compilation failed: ${result.error.message}`);
|
|
312
|
+
return bundlePath;
|
|
313
|
+
}
|
|
242
314
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
return finalPath;
|
|
249
|
-
} catch (error) {
|
|
250
|
-
logWarning(`Hermes compilation failed: ${error.message}`);
|
|
315
|
+
if (result.status !== 0) {
|
|
316
|
+
const stderr = result.stderr || '';
|
|
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}`);
|
|
251
320
|
return bundlePath;
|
|
252
321
|
}
|
|
322
|
+
|
|
323
|
+
// Remove the plain JS bundle, keep only HBC
|
|
324
|
+
fs.unlinkSync(bundlePath);
|
|
325
|
+
// Rename .hbc to .bundle for compatibility
|
|
326
|
+
const finalPath = bundlePath;
|
|
327
|
+
fs.renameSync(hbcPath, finalPath);
|
|
328
|
+
|
|
329
|
+
// In required mode, belt-and-suspenders: the launch asset MUST be Hermes bytecode.
|
|
330
|
+
if (requireHermes && !isHermesBytecode(finalPath)) {
|
|
331
|
+
throw new Error(
|
|
332
|
+
'Post-compile check failed: launch bundle is not Hermes bytecode. Refusing to publish a plain-JS OTA bundle.'
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
return finalPath;
|
|
253
336
|
}
|
|
254
337
|
|
|
255
338
|
/**
|
|
@@ -656,6 +739,12 @@ function loadConfig(options) {
|
|
|
656
739
|
skipBundle: options['skip-bundle'] || false,
|
|
657
740
|
build: options.build || process.env.PULSE_BUILD || fileConfig.build || null,
|
|
658
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,
|
|
659
748
|
};
|
|
660
749
|
}
|
|
661
750
|
|
|
@@ -705,7 +794,7 @@ async function publish(options) {
|
|
|
705
794
|
|
|
706
795
|
// Step 2: Compile with Hermes
|
|
707
796
|
logStep('2/6', 'Compiling with Hermes...');
|
|
708
|
-
compileWithHermes(bundlePath, config.platform);
|
|
797
|
+
compileWithHermes(bundlePath, config.platform, { requireHermes: config.requireHermes });
|
|
709
798
|
logSuccess('Hermes compilation complete');
|
|
710
799
|
} else {
|
|
711
800
|
logStep('1/6', 'Skipping bundle creation (--skip-bundle)');
|