Skip to content

Commit 88598a9

Browse files
sunnylqmclaude
andcommitted
fix(hermes-base): surface the real hermesc error when a base compile fails
The failure reason was the last three lines of hermesc stderr. hermesc echoes each diagnostic as message/source/caret, and a minified bundle puts a whole module on one line, so a single warning could be 100k+ characters and pushed the actual error out of the window — the report showed only warning noise. Add summarizeHermescStderr(): drop caret lines and the source echo before them, prefer error/fatal/assertion lines over warnings, and cap each kept line. Also write the full stderr to <outputFolder>/hermes-base-error.log and point at it, so the untruncated compiler output stays available. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent ef375f3 commit 88598a9

4 files changed

Lines changed: 85 additions & 4 deletions

File tree

src/bundle-runner.ts

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,37 @@ async function checkGradleConfig(): Promise<GradleConfig> {
552552
* simply stays in the intermediate directory (never packed into the ppk) so
553553
* `address at` stack frames can still be symbolicated later.
554554
*/
555+
/**
556+
* hermesc echoes every diagnostic as three lines (header, the offending source
557+
* line, a caret line). Minified bundles put a whole module on one line, so a
558+
* single warning can be 100k+ characters and blindly keeping the tail of stderr
559+
* buries the actual error under warning noise. Keep the diagnostic headers,
560+
* prefer errors over warnings, and cap the length of whatever survives.
561+
*/
562+
export function summarizeHermescStderr(stderr: string): string {
563+
const raw = String(stderr ?? '').split(/\r?\n/);
564+
const kept: string[] = [];
565+
for (let i = 0; i < raw.length; i++) {
566+
const line = raw[i].trim();
567+
if (!line) continue;
568+
if (/^\^~*$/.test(line)) continue; // caret line
569+
// the line before a caret is the echoed source, never the message
570+
const next = raw[i + 1]?.trim();
571+
if (next && /^\^~*$/.test(next)) continue;
572+
kept.push(line.length > 300 ? `${line.slice(0, 300)}…` : line);
573+
}
574+
if (kept.length === 0) return '';
575+
const isError = (line: string) =>
576+
/(?::\s*|^)(?:fatal error|error)\b/i.test(line) ||
577+
/^(?:Assertion|Stack dump|PLEASE submit a bug report)/i.test(line);
578+
const errors = kept.filter(isError);
579+
if (errors.length > 0) return errors.slice(-3).join(' ');
580+
const notWarnings = kept.filter(
581+
(line) => !/:\s*(?:warning|note):/i.test(line),
582+
);
583+
return (notWarnings.length > 0 ? notWarnings : kept).slice(-3).join(' ');
584+
}
585+
555586
export function buildHermescArgs(bundlePath: string): string[] {
556587
return [
557588
'-emit-binary',
@@ -604,14 +635,21 @@ async function compileHermesByteCode(
604635
if (attempt.status === 0) {
605636
usedBase = true;
606637
} else {
607-
const stderr = attempt.stderr
608-
? String(attempt.stderr).trim().split('\n').slice(-3).join(' ')
609-
: '';
638+
const fullStderr = attempt.stderr ? String(attempt.stderr) : '';
639+
const reason = summarizeHermescStderr(fullStderr);
610640
console.warn(
611641
t('hermesBaseCompileFailed', {
612-
reason: stderr || `exit ${attempt.status}`,
642+
reason: reason || `exit ${attempt.status}`,
613643
}),
614644
);
645+
if (fullStderr.trim()) {
646+
// the summary drops warning noise; keep everything for bug reports
647+
const logPath = path.join(outputFolder, 'hermes-base-error.log');
648+
try {
649+
fs.writeFileSync(logPath, fullStderr);
650+
console.warn(t('hermesBaseCompileFailedLog', { file: logPath }));
651+
} catch {}
652+
}
615653
fs.copyFileSync(jsBackup, bundlePath);
616654
}
617655
}

src/locales/en.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,8 @@ This can reduce the risk of inconsistent dependencies and supply chain attacks.
220220
hermesBaseDownloading: 'Hermes base: downloading {{- url}}',
221221
hermesBaseCompileFailed:
222222
'Hermes base compile failed ({{- reason}}); retrying without -base-bytecode',
223+
hermesBaseCompileFailedLog:
224+
'Hermes base: full compiler output written to {{- file}}',
223225
hermesBaseVerified:
224226
'Hermes base: bytecode verified equivalent to a plain compile',
225227
hermesBaseVerifyFailed:

src/locales/zh.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ export default {
203203
hermesBaseDownloading: 'Hermes base:下载 {{- url}}',
204204
hermesBaseCompileFailed:
205205
'Hermes base 编译失败({{- reason}}),改为不带 -base-bytecode 重编',
206+
hermesBaseCompileFailedLog: 'Hermes base:完整编译错误已写入 {{- file}}',
206207
hermesBaseVerified: 'Hermes base:字节码与普通编译等价,校验通过',
207208
hermesBaseVerifyFailed:
208209
'Hermes base:字节码与普通编译不等价,放弃 base 重新编译',

tests/bundle-runner.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
resolveExpoCli,
1212
resolveHermesCommand,
1313
resolveSentryUploadMode,
14+
summarizeHermescStderr,
1415
} from '../src/bundle-runner';
1516

1617
function mkTempDir(prefix: string): string {
@@ -457,3 +458,42 @@ describe('buildHermescArgs', () => {
457458
]);
458459
});
459460
});
461+
462+
describe('summarizeHermescStderr', () => {
463+
const warning = [
464+
'index.bundlejs:204:115354: warning: the variable "clearTimeout" was not declared in anonymous function',
465+
`__d(function(e,n,t){${'x'.repeat(2000)}})`,
466+
`${' '.repeat(500)}^~~~~~~~~~~~~`,
467+
].join('\n');
468+
469+
test('surfaces the error instead of the warning noise around it', () => {
470+
const stderr = [
471+
warning,
472+
'index.bundlejs:1:1: error: base bytecode is not compatible',
473+
'var a = 1;',
474+
'^~~',
475+
warning,
476+
].join('\n');
477+
expect(summarizeHermescStderr(stderr)).toBe(
478+
'index.bundlejs:1:1: error: base bytecode is not compatible',
479+
);
480+
});
481+
482+
test('drops source and caret lines and caps the remaining length', () => {
483+
const summary = summarizeHermescStderr(warning);
484+
expect(summary).toContain('was not declared in anonymous function');
485+
expect(summary).not.toContain('__d(function');
486+
expect(summary.length).toBeLessThan(400);
487+
});
488+
489+
test('keeps crash output that carries no error: prefix', () => {
490+
const stderr = `${warning}\nAssertion \`baseBCProvider\` failed.`;
491+
expect(summarizeHermescStderr(stderr)).toBe(
492+
'Assertion `baseBCProvider` failed.',
493+
);
494+
});
495+
496+
test('returns an empty string for empty stderr', () => {
497+
expect(summarizeHermescStderr('')).toBe('');
498+
});
499+
});

0 commit comments

Comments
 (0)