Skip to content

Commit 516be8d

Browse files
authored
Refactor Hermes detection and improve CLI safety checks (#81)
Refactor Hermes detection across Android, iOS, Expo, and CI environments; harden intermediate-directory cleanup against protected paths and symlink redirection; add proxy-aware networking, lazy command loading, non-interactive guards, stricter app selection, improved diagnostics, Provider API fixes, and Node 18 smoke validation. Review follow-ups include Podfile comment handling, appKey validation scoped to selectApp, credential-safe workflow permissions, URL redaction, preserved network error causes, upload timeout retries, cwd-aware dependency caching, and comprehensive regression coverage.
1 parent 31201ef commit 516be8d

48 files changed

Lines changed: 2064 additions & 222 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/test.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,18 @@ concurrency:
1212
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
1313
cancel-in-progress: true
1414

15+
permissions:
16+
contents: read
17+
1518
jobs:
1619
lint:
1720
runs-on: blacksmith-4vcpu-ubuntu-2404
1821
timeout-minutes: 10
1922

2023
steps:
2124
- uses: actions/checkout@v7
25+
with:
26+
persist-credentials: false
2227

2328
- uses: oven-sh/setup-bun@v2
2429

@@ -43,6 +48,8 @@ jobs:
4348

4449
steps:
4550
- uses: actions/checkout@v7
51+
with:
52+
persist-credentials: false
4653

4754
- uses: oven-sh/setup-bun@v2
4855

@@ -68,6 +75,33 @@ jobs:
6875
path: coverage
6976
if-no-files-found: error
7077

78+
node18-smoke:
79+
runs-on: blacksmith-4vcpu-ubuntu-2404
80+
timeout-minutes: 10
81+
82+
steps:
83+
- uses: actions/checkout@v7
84+
with:
85+
persist-credentials: false
86+
87+
- uses: oven-sh/setup-bun@v2
88+
89+
- name: Install Dependency
90+
run: bun install --frozen-lockfile
91+
92+
- name: Build package
93+
run: bun run build
94+
95+
# Node 18 goes on PATH only after the build: typescript >= 7 ships an
96+
# extensionless ESM bin/tsc that Node 18.17 cannot load.
97+
- name: Set up the oldest supported Node.js
98+
uses: actions/setup-node@v7
99+
with:
100+
node-version: '18.17.0'
101+
102+
- name: Load every built module and run the offline commands
103+
run: node scripts/smoke-lib.js
104+
71105
publish-dry-run:
72106
runs-on: blacksmith-4vcpu-ubuntu-2404
73107
timeout-minutes: 10
@@ -76,6 +110,7 @@ jobs:
76110
- uses: actions/checkout@v7
77111
with:
78112
fetch-depth: 0
113+
persist-credentials: false
79114

80115
- uses: oven-sh/setup-bun@v2
81116

README.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ const publishResult = await provider.publish({
8585
- `hdiffFromApp`: Generate hdiff from APP files
8686
- `hdiffFromIpa`: Generate hdiff from IPA files
8787

88-
Hermes projects: `bundle` always runs hermesc with `-output-source-map`, so the debug info section is stripped from the bytecode (15–40% smaller, same as React Native's own release builds). The Hermes sourcemap stays in the intermediate directory (`.pushy/intermedia/<platform>/<bundle>.map`, never packed into the ppk) and is composed with the packager map — `--sourcemap` is on by default since 2.23 (`--sourcemap false` opts out). When `bundle` publishes, that final map is uploaded and archived with the version (`sourceMapKey`), so `pushy symbolicate` can map crash stacks — including Hermes `address at` frames — back to source later. `pushy publish <ppk> --sourcemap <file.map>` archives a map for a ppk built elsewhere; publishing without a map prints a warning.
88+
Hermes projects: `bundle` always runs hermesc with `-output-source-map`, so the debug info section is stripped from the bytecode (15–40% smaller, same as React Native's own release builds). The Hermes sourcemap stays in the intermediate directory (`.pushy/intermedia/<platform>/<bundle>.map`, never packed into the ppk) and is composed with the packager map — `--sourcemap` is on by default since 2.23 (`--no-sourcemap` opts out). When `bundle` publishes, that final map is uploaded and archived with the version (`sourceMapKey`), so `pushy symbolicate` can map crash stacks — including Hermes `address at` frames — back to source later. `pushy publish <ppk> --sourcemap <file.map>` archives a map for a ppk built elsewhere; publishing without a map prints a warning.
8989

9090
Hermes delta mode (`-base-bytecode`): by default (`--hermesBase auto`) `bundle` compiles against the previous HBC of the same app, which keeps Hermes string IDs stable and makes hot-update patches 5–30× smaller. The base comes from the server (`GET /app/:id/hermesBase`), verified by sha256 and kept in a local cache (`.pushy/cache/<sha256>`, 500 MB / 20 files, `PUSHY_CACHE_DIR` / `--cacheMaxMb` to tune, `pushy cache [clean]` to inspect or clear). `--hermesBase none` disables it; `--hermesBase <file.hbc|.ppk|.apk|.ipa>` uses a local artifact (for example the store build). `--verifyHermesBase` (default on) additionally compiles without the base (concurrently with the base compile) and compares both disassemblies; on any mismatch or failure the CLI silently falls back to the plain compile, so the feature can never block a release. Only hermesc builds that include the upstream delta-mode fix are used (classic `react-native/sdks/hermesc`, or `hermes-compiler` ≥ 250829098). If a base compile fails, the full hermesc output is written to `hermes-base-error.log` next to the intermediate directory. `--resetCache false` skips Metro's `--reset-cache` and reuses its transform cache, which makes repeated bundles much faster.
9191

@@ -131,20 +131,23 @@ Hermes delta mode (`-base-bytecode`): by default (`--hermesBase auto`) `bundle`
131131
interface CLIProvider {
132132
bundle(options: BundleOptions): Promise<CommandResult>;
133133
publish(options: PublishOptions): Promise<CommandResult>;
134+
symbolicate(options: SymbolicateOptions): Promise<CommandResult>;
134135
upload(options: UploadOptions): Promise<CommandResult>;
135136

137+
createApp(name: string, platform: Platform): Promise<CommandResult>;
138+
listApps(platform?: Platform): Promise<CommandResult>;
136139
getSelectedApp(
137140
platform?: Platform,
141+
config?: string,
138142
): Promise<{ appId: string; platform: Platform }>;
139-
listApps(platform?: Platform): Promise<CommandResult>;
140-
createApp(name: string, platform: Platform): Promise<CommandResult>;
141143

142144
listVersions(appId: string): Promise<CommandResult>;
143145
updateVersion(
144146
appId: string,
145147
versionId: string,
146-
updates: Partial<Version>,
148+
updates: UpdateVersionOptions,
147149
): Promise<CommandResult>;
150+
listPackages(appId: string): Promise<CommandResult>;
148151

149152
getPlatform(platform?: Platform): Promise<Platform>;
150153
loadSession(): Promise<Session>;
@@ -158,8 +161,12 @@ interface CLIProvider {
158161
```bash
159162
export PUSHY_REGISTRY=https://your-api-endpoint.com
160163
export NO_INTERACTIVE=true
164+
export RNU_LANG=en # CLI language (default: zh for pushy, en for cresc)
165+
export RNU_DEBUG=1 # print stack traces for errors
161166
```
162167

168+
`HTTPS_PROXY` / `HTTP_PROXY` / `NO_PROXY` (upper or lower case) are honored by every request: the API, package and bundle uploads, source map and Hermes base downloads, and the registry version check.
169+
163170
## Sentry Sourcemaps
164171

165172
When `ios/sentry.properties` or `android/sentry.properties` exists, `bundle` uploads sourcemaps for OTA packages. The default matching path is Sentry Debug IDs; the CLI no longer infers release/dist from the native package.

README.zh-CN.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ const publishResult = await provider.publish({
7676
- `hdiffFromApp`: 基于 APP 文件生成 hdiff
7777
- `hdiffFromIpa`: 基于 IPA 文件生成 hdiff
7878

79-
Hermes 工程:`bundle` 调用 hermesc 时始终带 `-output-source-map`,因此字节码不含 debug info 段(小 15%~40%,与 React Native 自身 release 构建一致)。Hermes sourcemap 保留在中间目录(`.pushy/intermedia/<platform>/<bundle>.map`,不会打进 ppk),并与 packager map 合成——自 2.23 起 `--sourcemap` 默认开启(`--sourcemap false` 关闭)。`bundle` 发布时会把这份最终 map 上传并随版本归档(`sourceMapKey`),之后用 `pushy symbolicate` 即可把崩溃堆栈(含 Hermes 的 `address at` 帧)还原到源码。别处打好的 ppk 可用 `pushy publish <ppk> --sourcemap <file.map>` 归档;不带 map 发布会打印警告。
79+
Hermes 工程:`bundle` 调用 hermesc 时始终带 `-output-source-map`,因此字节码不含 debug info 段(小 15%~40%,与 React Native 自身 release 构建一致)。Hermes sourcemap 保留在中间目录(`.pushy/intermedia/<platform>/<bundle>.map`,不会打进 ppk),并与 packager map 合成——自 2.23 起 `--sourcemap` 默认开启(`--no-sourcemap` 关闭)。`bundle` 发布时会把这份最终 map 上传并随版本归档(`sourceMapKey`),之后用 `pushy symbolicate` 即可把崩溃堆栈(含 Hermes 的 `address at` 帧)还原到源码。别处打好的 ppk 可用 `pushy publish <ppk> --sourcemap <file.map>` 归档;不带 map 发布会打印警告。
8080

8181
Hermes delta 模式(`-base-bytecode`):默认 `--hermesBase auto`,`bundle` 会以同一应用上一版的 HBC 为 base 编译,让 Hermes 字符串 ID 跨版本稳定,热更 patch 可缩小 5~30 倍。base 由服务端(`GET /app/:id/hermesBase`)给出、按 sha256 校验并存入本地缓存(`.pushy/cache/<sha256>`,默认 500 MB / 20 个,可用 `PUSHY_CACHE_DIR` / `--cacheMaxMb` 调整,`pushy cache [clean]` 查看或清空)。`--hermesBase none` 关闭;`--hermesBase <file.hbc|.ppk|.apk|.ipa>` 指定本地文件(比如商店包)作 base。`--verifyHermesBase`(默认开)会并行再做一次普通编译并比对两份反汇编;任何不一致或失败都静默回退到普通编译,不会阻塞发版。只有包含上游 delta 模式修复的 hermesc 才会启用(经典 `react-native/sdks/hermesc`,或 `hermes-compiler` ≥ 250829098)。base 编译失败时,完整的 hermesc 输出会写到中间目录旁边的 `hermes-base-error.log`。`--resetCache false` 可跳过 Metro 的 `--reset-cache`,复用其转换缓存,重复打包会快很多。
8282

@@ -122,20 +122,23 @@ Hermes delta 模式(`-base-bytecode`):默认 `--hermesBase auto`,`bundle
122122
interface CLIProvider {
123123
bundle(options: BundleOptions): Promise<CommandResult>;
124124
publish(options: PublishOptions): Promise<CommandResult>;
125+
symbolicate(options: SymbolicateOptions): Promise<CommandResult>;
125126
upload(options: UploadOptions): Promise<CommandResult>;
126127

128+
createApp(name: string, platform: Platform): Promise<CommandResult>;
129+
listApps(platform?: Platform): Promise<CommandResult>;
127130
getSelectedApp(
128131
platform?: Platform,
132+
config?: string,
129133
): Promise<{ appId: string; platform: Platform }>;
130-
listApps(platform?: Platform): Promise<CommandResult>;
131-
createApp(name: string, platform: Platform): Promise<CommandResult>;
132134

133135
listVersions(appId: string): Promise<CommandResult>;
134136
updateVersion(
135137
appId: string,
136138
versionId: string,
137-
updates: Partial<Version>,
139+
updates: UpdateVersionOptions,
138140
): Promise<CommandResult>;
141+
listPackages(appId: string): Promise<CommandResult>;
139142

140143
getPlatform(platform?: Platform): Promise<Platform>;
141144
loadSession(): Promise<Session>;
@@ -149,8 +152,12 @@ interface CLIProvider {
149152
```bash
150153
export PUSHY_REGISTRY=https://your-api-endpoint.com
151154
export NO_INTERACTIVE=true
155+
export RNU_LANG=en # 界面语言(默认:pushy 为 zh,cresc 为 en)
156+
export RNU_DEBUG=1 # 出错时打印完整堆栈
152157
```
153158

159+
所有请求都遵循 `HTTPS_PROXY` / `HTTP_PROXY` / `NO_PROXY`(大小写均可):API 调用、原生包与热更包上传、source map 与 Hermes base 下载、registry 版本检查。
160+
154161
## Sentry Sourcemap
155162

156163
当项目存在 `ios/sentry.properties``android/sentry.properties` 时,`bundle` 会为 OTA 包上传 sourcemap。默认使用 Sentry Debug ID 匹配,不再根据原生包推导 release/dist。

biome.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"linter": {
66
"enabled": true,
77
"rules": {
8-
"recommended": true,
8+
"preset": "recommended",
99
"suspicious": {
1010
"noExplicitAny": "off",
1111
"noAssignInExpressions": "off",

bun.lock

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cli.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,13 @@
2323
}
2424
}
2525
},
26-
"deleteApp": {},
26+
"deleteApp": {
27+
"options": {
28+
"platform": {
29+
"hasValue": true
30+
}
31+
}
32+
},
2733
"selectApp": {
2834
"options": {
2935
"platform": {

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
},
2828
"files": [
2929
"lib",
30-
"src",
3130
"proto",
3231
"cli.json"
3332
],
@@ -39,6 +38,7 @@
3938
"lint": "bun run typecheck && biome check .",
4039
"lint:fix": "bun run typecheck && biome check --write .",
4140
"test": "bun test",
41+
"smoke": "node scripts/smoke-lib.js",
4242
"test:coverage": "bun test --coverage --coverage-reporter=text --coverage-reporter=lcov",
4343
"benchmark:diff-stream": "bun scripts/benchmark-diff-stream.ts"
4444
},
@@ -71,6 +71,7 @@
7171
"fs-extra": "^11.4.0",
7272
"global-dirs": "^4.0.0",
7373
"gradle-to-js": "^2.0.1",
74+
"https-proxy-agent": "^7.0.6",
7475
"i18next": "^26.4.0",
7576
"node-fetch": "^2.6.1",
7677
"plist": "^5.0.0",
@@ -81,6 +82,7 @@
8182
"registry-auth-token": "^5.1.1",
8283
"source-map": "0.6.1",
8384
"tty-table": "5.0",
85+
"undici": "^6.28.0",
8486
"yauzl": "^3.4.0",
8587
"yazl": "3.3.1"
8688
},

0 commit comments

Comments
 (0)