Skip to content

Commit 29c438d

Browse files
sunnylqmclaude
andcommitted
fix: parser correctness, API pagination, sourcemap exclusion, misc cleanups
- binary.ts: versionName scrub now only strips UTF-16 NUL padding (was deleting spaces and all non-ASCII chars, e.g. Chinese version names); readDimension complexToFloat uses the raw value for unit extraction - zip.ts: regex entry matching also tests the original (case-preserved) entry name, fixing AAB manifest lookup that never matched - check-lockfile.ts: recognize pnpm-workspace.yaml as monorepo root - api.ts: getAllPackages pages through results (server caps limit at 100); query() throws on 200-with-non-JSON body instead of returning undefined - bundle-runner.ts: harmony Sentry properties, spawn error listener, close-callback error propagation, compose-source-maps resolution - bundle-pack.ts: derive sourcemap exclusion from actual bundleName; drop dead '.'/'..' entries - app.ts: deleteApp returns after cancel instead of deleting /app/undefined - http-helper.ts: clear ping timeout so the process isn't held alive ~5s - provider.ts: listPackages requires appId - utils/index.ts: guard NaN buildTime; close zip fd on read errors - .gitignore: fix broken '.pushytest-output/' line (now ignores .pushy and test-output*/), drop personal bench entries; remove pr.md, .prettierrc.json Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 8587cc3 commit 29c438d

17 files changed

Lines changed: 179 additions & 127 deletions

File tree

.gitignore

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,5 @@ lib/
107107
.DS_Store
108108
# react-native-update
109109
.update
110-
.pushytest-output/
111-
test-output2/
112-
bench.ts
113-
bench2.ts
114-
test-output3/
115-
test-output4/
116-
bench-node.js
110+
.pushy
111+
test-output*/

.prettierrc.json

Lines changed: 0 additions & 4 deletions
This file was deleted.

pr.md

Lines changed: 0 additions & 10 deletions
This file was deleted.

src/api.ts

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,13 @@ async function query(url: string, options: RuntimeRequestInit) {
140140
try {
141141
json = JSON.parse(text);
142142
} catch (_e) {
143-
if (resp.status === 200 && text) {
144-
console.warn(
145-
`Warning: API returned 200 with non-JSON body (${text.length} bytes)`,
143+
if (resp.status === 200) {
144+
// a proxy/gateway likely replaced the response; surface it instead of
145+
// returning undefined and crashing callers on destructuring
146+
throw createRequestError(
147+
`API returned 200 with non-JSON body (${text.length} bytes)`,
148+
fullUrl,
149+
resp.status,
146150
);
147151
}
148152
}
@@ -282,6 +286,27 @@ export async function uploadFile(fn: string, key?: string) {
282286
}
283287

284288
export const getAllPackages = async (appId: string) => {
285-
const { data } = await get(`/app/${appId}/package/list?limit=1000`);
286-
return data as Package[] | undefined | null;
289+
// the server caps limit at 100, so page through with offset
290+
const limit = 100;
291+
let offset = 0;
292+
let allPackages: Package[] | undefined | null;
293+
while (true) {
294+
const { data, count } = await get(
295+
`/app/${appId}/package/list?offset=${offset}&limit=${limit}`,
296+
);
297+
const packages = data as Package[] | undefined | null;
298+
if (allPackages === undefined || allPackages === null) {
299+
allPackages = packages;
300+
} else if (packages) {
301+
allPackages.push(...packages);
302+
}
303+
if (!packages || packages.length === 0) {
304+
break;
305+
}
306+
offset += packages.length;
307+
if (offset >= Number(count || 0)) {
308+
break;
309+
}
310+
}
311+
return allPackages;
287312
};

src/app.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ export function getAppCommands() {
147147
const id = args[0] || (await chooseApp(platform)).id;
148148
if (!id) {
149149
console.log(t('cancelled'));
150+
return;
150151
}
151152
await doDelete(`/app/${id}`);
152153
console.log(t('operationSuccess'));

src/bundle-pack.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,20 @@ import { ZipFile as YazlZipFile } from 'yazl';
44
import { t } from './utils/i18n';
55
import { zipOptionsForPayloadFile } from './utils/zip-options';
66

7-
const ignorePackingFileNames = [
8-
'.',
9-
'..',
10-
'index.bundlejs.map',
11-
'bundle.harmony.js.map',
12-
];
137
const ignorePackingExtensions = ['DS_Store', 'txt.map'];
148

15-
export async function packBundle(dir: string, output: string): Promise<void> {
9+
export async function packBundle(
10+
dir: string,
11+
output: string,
12+
bundleName?: string,
13+
): Promise<void> {
14+
const ignorePackingFileNames = [
15+
'index.bundlejs.map',
16+
'bundle.harmony.js.map',
17+
];
18+
if (bundleName) {
19+
ignorePackingFileNames.push(`${bundleName}.map`);
20+
}
1621
console.log(t('packing'));
1722
fs.ensureDirSync(path.dirname(output));
1823
await new Promise<void>((resolve, reject) => {

src/bundle-runner.ts

Lines changed: 63 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,11 @@ export async function runReactNativeBundleCommand({
241241
process.env.SENTRY_PROPERTIES = 'ios/sentry.properties';
242242
} else if (platform === 'android') {
243243
process.env.SENTRY_PROPERTIES = 'android/sentry.properties';
244+
} else if (
245+
platform === 'harmony' &&
246+
fs.existsSync('harmony/sentry.properties')
247+
) {
248+
process.env.SENTRY_PROPERTIES = 'harmony/sentry.properties';
244249
}
245250
}
246251

@@ -302,20 +307,24 @@ export async function runReactNativeBundleCommand({
302307
console.error(data.toString().trim());
303308
});
304309

310+
reactNativeBundleProcess.once('error', reject);
311+
305312
reactNativeBundleProcess.on('close', async (exitCode) => {
306313
if (exitCode) {
307314
reject(new Error(t('bundleCommandError', { code: exitCode })));
308315
return;
309316
}
310317

311-
let hermesEnabled: boolean | undefined = false;
318+
try {
319+
let hermesEnabled: boolean | undefined = false;
312320

313-
if (forceHermes) {
314-
hermesEnabled = true;
315-
console.log(t('forceHermes'));
316-
} else if (platform === 'android') {
317-
const gradleProperties = await new Promise<{ hermesEnabled?: boolean }>(
318-
(resolve) => {
321+
if (forceHermes) {
322+
hermesEnabled = true;
323+
console.log(t('forceHermes'));
324+
} else if (platform === 'android') {
325+
const gradleProperties = await new Promise<{
326+
hermesEnabled?: boolean;
327+
}>((resolve) => {
319328
properties.parse(
320329
'./android/gradle.properties',
321330
{ path: true },
@@ -331,42 +340,48 @@ export async function runReactNativeBundleCommand({
331340
resolve(props);
332341
},
333342
);
334-
},
335-
);
336-
hermesEnabled = gradleProperties.hermesEnabled;
343+
});
344+
hermesEnabled = gradleProperties.hermesEnabled;
345+
346+
if (typeof hermesEnabled !== 'boolean') {
347+
hermesEnabled = gradleConfig.enableHermes;
348+
}
349+
} else if (
350+
platform === 'ios' &&
351+
fs.existsSync('ios/Pods/hermes-engine')
352+
) {
353+
hermesEnabled = true;
354+
}
337355

338-
if (typeof hermesEnabled !== 'boolean') {
339-
hermesEnabled = gradleConfig.enableHermes;
356+
if (hermesEnabled) {
357+
await compileHermesByteCode(
358+
bundleName,
359+
outputFolder,
360+
sourcemapOutput,
361+
!isSentry,
362+
);
340363
}
341-
} else if (
342-
platform === 'ios' &&
343-
fs.existsSync('ios/Pods/hermes-engine')
344-
) {
345-
hermesEnabled = true;
346-
}
347364

348-
if (hermesEnabled) {
349-
await compileHermesByteCode(
350-
bundleName,
351-
outputFolder,
352-
sourcemapOutput,
353-
!isSentry,
354-
);
355-
}
365+
if (platform === 'harmony') {
366+
const harmonyRawAssetsPath =
367+
'harmony/entry/src/main/resources/rawfile/assets';
368+
fs.ensureDirSync(harmonyRawAssetsPath);
369+
fs.copySync(outputFolder, harmonyRawAssetsPath, {
370+
overwrite: true,
371+
// sourcemaps must not ship inside the native package
372+
filter: (src) => !src.endsWith('.map'),
373+
});
374+
fs.moveSync(
375+
`${harmonyRawAssetsPath}/bundle.harmony.js`,
376+
`${harmonyRawAssetsPath}/../bundle.harmony.js`,
377+
{ overwrite: true },
378+
);
379+
}
356380

357-
if (platform === 'harmony') {
358-
const harmonyRawAssetsPath =
359-
'harmony/entry/src/main/resources/rawfile/assets';
360-
fs.ensureDirSync(harmonyRawAssetsPath);
361-
fs.copySync(outputFolder, harmonyRawAssetsPath, { overwrite: true });
362-
fs.moveSync(
363-
`${harmonyRawAssetsPath}/bundle.harmony.js`,
364-
`${harmonyRawAssetsPath}/../bundle.harmony.js`,
365-
{ overwrite: true },
366-
);
381+
resolve();
382+
} catch (error) {
383+
reject(error);
367384
}
368-
369-
resolve();
370385
});
371386
});
372387
}
@@ -529,9 +544,15 @@ async function compileHermesByteCode(
529544
hermesCommand,
530545
);
531546
if (sourcemapOutput) {
532-
const composerPath =
533-
'node_modules/react-native/scripts/compose-source-maps.js';
534-
if (!fs.existsSync(composerPath)) {
547+
let composerPath: string;
548+
try {
549+
// resolve through the project so hoisted node_modules (monorepos) work
550+
composerPath = require.resolve(
551+
'react-native/scripts/compose-source-maps.js',
552+
{ paths: [process.cwd()] },
553+
);
554+
} catch {
555+
console.warn(t('composeSourceMapsNotFound'));
535556
return;
536557
}
537558
console.log(t('composingSourceMap'));
@@ -571,7 +592,7 @@ export async function copyDebugidForSentry(
571592
},
572593
);
573594
} catch {
574-
console.error(t('sentryCliNotFound'));
595+
console.error(t('sentryReactNativeNotFound'));
575596
return;
576597
}
577598

src/bundle.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,12 @@ function normalizeBundleOptions(
7575
platform: string,
7676
): NormalizedBundleOptions {
7777
return {
78-
bundleName: getStringOption(
79-
translatedOptions,
80-
'bundleName',
81-
'index.bundlejs',
82-
),
78+
// harmony bundles always use this fixed name (runReactNativeBundleCommand
79+
// forces it), so sourcemap paths and Sentry uploads must match
80+
bundleName:
81+
platform === 'harmony'
82+
? 'bundle.harmony.js'
83+
: getStringOption(translatedOptions, 'bundleName', 'index.bundlejs'),
8384
entryFile: getStringOption(translatedOptions, 'entryFile', 'index.js'),
8485
intermediaDir: getStringOption(
8586
translatedOptions,
@@ -222,7 +223,11 @@ export const bundleCommands = {
222223
isSentry: bundleParams.sentry,
223224
});
224225

225-
await packBundle(path.resolve(normalized.intermediaDir), realOutput);
226+
await packBundle(
227+
path.resolve(normalized.intermediaDir),
228+
realOutput,
229+
normalized.bundleName,
230+
);
226231

227232
if (normalized.name) {
228233
await publishBundleVersion(realOutput, platform, {

src/locales/en.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ This can reduce the risk of inconsistent dependencies and supply chain attacks.
132132
'Set {{rollout}}% rollout for OTA update {{version}} on native version(s) {{versions}}',
133133
rolloutRangeError: 'rollout must be an integer between 1-100',
134134
runningHermesc: 'Running hermesc: {{- command}} {{- args}}',
135+
sentryReactNativeNotFound:
136+
'Cannot find @sentry/react-native, please make sure it is properly installed',
135137
sentryCliNotFound:
136138
'Cannot find Sentry CLI tool, please make sure @sentry/cli is properly installed',
137139
sentryReleaseCreated: 'Sentry release created for version: {{version}}',
@@ -182,6 +184,8 @@ This can reduce the risk of inconsistent dependencies and supply chain attacks.
182184
nodeHdiffpatchRequired:
183185
'This function needs "node-hdiffpatch". Please run "{{scriptName}} install node-hdiffpatch" to install',
184186
apkExtracted: 'APK extracted to {{output}}',
187+
composeSourceMapsNotFound:
188+
'Cannot find react-native/scripts/compose-source-maps.js, skipping hermes sourcemap composing. The uploaded sourcemap may not match the compiled bundle.',
185189
installPackageRequired:
186190
'Please specify the package to install, e.g. "{{scriptName}} install node-hdiffpatch"',
187191
installFailed: 'Failed to install {{packages}}: {{error}}',

src/locales/zh.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ export default {
123123
'已在原生版本 {{versions}} 上设置灰度发布 {{rollout}}% 热更包 {{version}}',
124124
rolloutRangeError: 'rollout 必须是 1-100 的整数',
125125
runningHermesc: '运行 hermesc:{{- command}} {{- args}}',
126+
sentryReactNativeNotFound: '无法找到 @sentry/react-native,请确保已正确安装',
126127
sentryCliNotFound: '无法找到 Sentry CLI 工具,请确保已正确安装 @sentry/cli',
127128
sentryReleaseCreated: '已为版本 {{version}} 创建 Sentry release',
128129
totalApps: '共 {{count}} 个 {{platform}} 应用',
@@ -168,6 +169,8 @@ export default {
168169
nodeHdiffpatchRequired:
169170
'此功能需要 "node-hdiffpatch"。请运行 "{{scriptName}} install node-hdiffpatch" 来安装',
170171
apkExtracted: 'APK 已提取到 {{output}}',
172+
composeSourceMapsNotFound:
173+
'找不到 react-native/scripts/compose-source-maps.js,跳过 hermes sourcemap 合成。上传的 sourcemap 可能与编译后的 bundle 不匹配。',
171174
installPackageRequired:
172175
'请指定要安装的包,例如 "{{scriptName}} install node-hdiffpatch"',
173176
installFailed: '安装 {{packages}} 失败: {{error}}',

0 commit comments

Comments
 (0)