Skip to content

Commit 5240ac9

Browse files
committed
Merge branch 'dev'
2 parents c90b66b + 4048ac2 commit 5240ac9

File tree

9 files changed

+241
-63
lines changed

9 files changed

+241
-63
lines changed

bin/builders/BaseBuilder.ts

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -162,11 +162,15 @@ export default abstract class BaseBuilder {
162162
// Show static message to keep the status visible
163163
logger.warn('✸ Building app...');
164164

165-
const buildEnv = {
166-
...this.getBuildEnvironment(),
167-
...(process.env.NO_STRIP && { NO_STRIP: process.env.NO_STRIP }),
165+
const baseEnv = this.getBuildEnvironment();
166+
let buildEnv: Record<string, string> = {
167+
...(baseEnv ?? {}),
168+
...(process.env.NO_STRIP ? { NO_STRIP: process.env.NO_STRIP } : {}),
168169
};
169170

171+
const resolveExecEnv = () =>
172+
Object.keys(buildEnv).length > 0 ? buildEnv : undefined;
173+
170174
// Warn users about potential AppImage build failures on modern Linux systems.
171175
// The linuxdeploy tool bundled in Tauri uses an older strip tool that doesn't
172176
// recognize the .relr.dyn section introduced in glibc 2.38+.
@@ -181,11 +185,31 @@ export default abstract class BaseBuilder {
181185
}
182186
}
183187

184-
await shellExec(
185-
`cd "${npmDirectory}" && ${this.getBuildCommand(packageManager)}`,
186-
this.getBuildTimeout(),
187-
buildEnv,
188-
);
188+
const buildCommand = `cd "${npmDirectory}" && ${this.getBuildCommand(packageManager)}`;
189+
const buildTimeout = this.getBuildTimeout();
190+
191+
try {
192+
await shellExec(buildCommand, buildTimeout, resolveExecEnv());
193+
} catch (error) {
194+
const shouldRetryWithoutStrip =
195+
process.platform === 'linux' &&
196+
this.options.targets === 'appimage' &&
197+
!buildEnv.NO_STRIP &&
198+
this.isLinuxDeployStripError(error);
199+
200+
if (shouldRetryWithoutStrip) {
201+
logger.warn(
202+
'⚠ AppImage build failed during linuxdeploy strip step, retrying with NO_STRIP=1 automatically.',
203+
);
204+
buildEnv = {
205+
...buildEnv,
206+
NO_STRIP: '1',
207+
};
208+
await shellExec(buildCommand, buildTimeout, resolveExecEnv());
209+
} else {
210+
throw error;
211+
}
212+
}
189213

190214
// Copy app
191215
const fileName = this.getFileName();
@@ -216,6 +240,21 @@ export default abstract class BaseBuilder {
216240

217241
abstract getFileName(): string;
218242

243+
private isLinuxDeployStripError(error: unknown): boolean {
244+
if (!(error instanceof Error) || !error.message) {
245+
return false;
246+
}
247+
const message = error.message.toLowerCase();
248+
return (
249+
message.includes('linuxdeploy') ||
250+
message.includes('failed to run linuxdeploy') ||
251+
message.includes('strip:') ||
252+
message.includes('unable to recognise the format of the input file') ||
253+
message.includes('appimage tool failed') ||
254+
message.includes('strip tool')
255+
);
256+
}
257+
219258
// 架构映射配置
220259
protected static readonly ARCH_MAPPINGS: Record<
221260
string,

bin/utils/shell.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,13 @@ export async function shellExec(
3131

3232
// Provide helpful guidance for common Linux AppImage build failures
3333
// caused by strip tool incompatibility with modern glibc (2.38+)
34+
const lowerError = errorMessage.toLowerCase();
35+
3436
if (
3537
process.platform === 'linux' &&
36-
(errorMessage.includes('linuxdeploy') ||
37-
errorMessage.includes('appimage') ||
38-
errorMessage.includes('strip'))
38+
(lowerError.includes('linuxdeploy') ||
39+
lowerError.includes('appimage') ||
40+
lowerError.includes('strip'))
3941
) {
4042
errorMsg +=
4143
'\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' +
@@ -50,6 +52,18 @@ export async function shellExec(
5052
' • Update binutils: sudo apt install binutils (or pacman -S binutils)\n' +
5153
' • Detailed guide: https://github.com/tw93/Pake/blob/main/docs/faq.md\n' +
5254
'━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━';
55+
56+
if (
57+
lowerError.includes('fuse') ||
58+
lowerError.includes('operation not permitted') ||
59+
lowerError.includes('/dev/fuse')
60+
) {
61+
errorMsg +=
62+
'\n\nDocker / Container hint:\n' +
63+
' AppImage tooling needs access to /dev/fuse. When running inside Docker, add:\n' +
64+
' --privileged --device /dev/fuse --security-opt apparmor=unconfined\n' +
65+
' or run on the host directly.';
66+
}
5367
}
5468

5569
throw new Error(errorMsg);

dist/cli.js

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import sharp from 'sharp';
2323
import * as psl from 'psl';
2424

2525
var name = "pake-cli";
26-
var version = "3.4.1";
26+
var version = "3.4.2";
2727
var description = "🤱🏻 Turn any webpage into a desktop app with one command. 🤱🏻 一键打包网页生成轻量桌面应用。";
2828
var engines = {
2929
node: ">=18.0.0"
@@ -223,10 +223,11 @@ async function shellExec(command, timeout = 300000, env) {
223223
let errorMsg = `Error occurred while executing command "${command}". Exit code: ${exitCode}. Details: ${errorMessage}`;
224224
// Provide helpful guidance for common Linux AppImage build failures
225225
// caused by strip tool incompatibility with modern glibc (2.38+)
226+
const lowerError = errorMessage.toLowerCase();
226227
if (process.platform === 'linux' &&
227-
(errorMessage.includes('linuxdeploy') ||
228-
errorMessage.includes('appimage') ||
229-
errorMessage.includes('strip'))) {
228+
(lowerError.includes('linuxdeploy') ||
229+
lowerError.includes('appimage') ||
230+
lowerError.includes('strip'))) {
230231
errorMsg +=
231232
'\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' +
232233
'Linux AppImage Build Failed\n' +
@@ -240,6 +241,15 @@ async function shellExec(command, timeout = 300000, env) {
240241
' • Update binutils: sudo apt install binutils (or pacman -S binutils)\n' +
241242
' • Detailed guide: https://github.com/tw93/Pake/blob/main/docs/faq.md\n' +
242243
'━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━';
244+
if (lowerError.includes('fuse') ||
245+
lowerError.includes('operation not permitted') ||
246+
lowerError.includes('/dev/fuse')) {
247+
errorMsg +=
248+
'\n\nDocker / Container hint:\n' +
249+
' AppImage tooling needs access to /dev/fuse. When running inside Docker, add:\n' +
250+
' --privileged --device /dev/fuse --security-opt apparmor=unconfined\n' +
251+
' or run on the host directly.';
252+
}
243253
}
244254
throw new Error(errorMsg);
245255
}
@@ -816,10 +826,12 @@ class BaseBuilder {
816826
buildSpinner.stop();
817827
// Show static message to keep the status visible
818828
logger.warn('✸ Building app...');
819-
const buildEnv = {
820-
...this.getBuildEnvironment(),
821-
...(process.env.NO_STRIP && { NO_STRIP: process.env.NO_STRIP }),
829+
const baseEnv = this.getBuildEnvironment();
830+
let buildEnv = {
831+
...(baseEnv ?? {}),
832+
...(process.env.NO_STRIP ? { NO_STRIP: process.env.NO_STRIP } : {}),
822833
};
834+
const resolveExecEnv = () => Object.keys(buildEnv).length > 0 ? buildEnv : undefined;
823835
// Warn users about potential AppImage build failures on modern Linux systems.
824836
// The linuxdeploy tool bundled in Tauri uses an older strip tool that doesn't
825837
// recognize the .relr.dyn section introduced in glibc 2.38+.
@@ -829,7 +841,28 @@ class BaseBuilder {
829841
logger.warn('⚠ If build fails, retry with: NO_STRIP=1 pake <url> --targets appimage');
830842
}
831843
}
832-
await shellExec(`cd "${npmDirectory}" && ${this.getBuildCommand(packageManager)}`, this.getBuildTimeout(), buildEnv);
844+
const buildCommand = `cd "${npmDirectory}" && ${this.getBuildCommand(packageManager)}`;
845+
const buildTimeout = this.getBuildTimeout();
846+
try {
847+
await shellExec(buildCommand, buildTimeout, resolveExecEnv());
848+
}
849+
catch (error) {
850+
const shouldRetryWithoutStrip = process.platform === 'linux' &&
851+
this.options.targets === 'appimage' &&
852+
!buildEnv.NO_STRIP &&
853+
this.isLinuxDeployStripError(error);
854+
if (shouldRetryWithoutStrip) {
855+
logger.warn('⚠ AppImage build failed during linuxdeploy strip step, retrying with NO_STRIP=1 automatically.');
856+
buildEnv = {
857+
...buildEnv,
858+
NO_STRIP: '1',
859+
};
860+
await shellExec(buildCommand, buildTimeout, resolveExecEnv());
861+
}
862+
else {
863+
throw error;
864+
}
865+
}
833866
// Copy app
834867
const fileName = this.getFileName();
835868
const fileType = this.getFileType(target);
@@ -852,6 +885,18 @@ class BaseBuilder {
852885
getFileType(target) {
853886
return target;
854887
}
888+
isLinuxDeployStripError(error) {
889+
if (!(error instanceof Error) || !error.message) {
890+
return false;
891+
}
892+
const message = error.message.toLowerCase();
893+
return (message.includes('linuxdeploy') ||
894+
message.includes('failed to run linuxdeploy') ||
895+
message.includes('strip:') ||
896+
message.includes('unable to recognise the format of the input file') ||
897+
message.includes('appimage tool failed') ||
898+
message.includes('strip tool'));
899+
}
855900
/**
856901
* 解析目标架构
857902
*/
@@ -1209,7 +1254,8 @@ class LinuxBuilder extends BaseBuilder {
12091254
// Enable verbose output for AppImage builds when debugging or PAKE_VERBOSE is set.
12101255
// AppImage builds often fail with minimal error messages from linuxdeploy,
12111256
// so verbose mode helps diagnose issues like strip failures and missing dependencies.
1212-
if (this.options.targets === 'appimage' && (this.options.debug || process.env.PAKE_VERBOSE)) {
1257+
if (this.options.targets === 'appimage' &&
1258+
(this.options.debug || process.env.PAKE_VERBOSE)) {
12131259
fullCommand += ' --verbose';
12141260
}
12151261
return fullCommand;

docs/cli-usage.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -438,16 +438,19 @@ After completing the above steps, your application should be successfully packag
438438
## Docker
439439

440440
```shell
441-
# On Linux, you can run the Pake CLI via Docker
442-
docker run -it --rm \ # Run interactively, remove container after exit
443-
-v YOUR_DIR:/output \ # Files from container's /output will be in YOU_DIR
441+
# Run the Pake CLI via Docker (AppImage builds need FUSE access)
442+
docker run --rm --privileged \
443+
--device /dev/fuse \
444+
--security-opt apparmor=unconfined \
445+
-v YOUR_DIR:/output \
444446
ghcr.io/tw93/pake \
445447
<arguments>
446448

447449
# For example:
448-
docker run -it --rm \
450+
docker run --rm --privileged \
451+
--device /dev/fuse \
452+
--security-opt apparmor=unconfined \
449453
-v ./packages:/output \
450454
ghcr.io/tw93/pake \
451-
https://example.com --name myapp --icon ./icon.png
452-
455+
https://example.com --name myapp --icon ./icon.png --targets appimage
453456
```

docs/cli-usage_CN.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -436,16 +436,19 @@ pake ./my-app/index.html --name "my-app" --use-local-file
436436
## Docker 使用
437437

438438
```shell
439-
# 在Linux上,您可以通过 Docker 运行 Pake CLI。
440-
docker run -it --rm \ # Run interactively, remove container after exit
441-
-v YOUR_DIR:/output \ # Files from container's /output will be in YOU_DIR
439+
# 在 Linux 上通过 Docker 运行 Pake CLI(AppImage 构建需要 FUSE 权限)
440+
docker run --rm --privileged \
441+
--device /dev/fuse \
442+
--security-opt apparmor=unconfined \
443+
-v YOUR_DIR:/output \
442444
ghcr.io/tw93/pake \
443445
<arguments>
444446

445-
# For example:
446-
docker run -it --rm \
447+
# 例如:
448+
docker run --rm --privileged \
449+
--device /dev/fuse \
450+
--security-opt apparmor=unconfined \
447451
-v ./packages:/output \
448452
ghcr.io/tw93/pake \
449-
https://example.com --name MyApp --icon ./icon.png
450-
453+
https://example.com --name MyApp --icon ./icon.png --targets appimage
451454
```

docs/faq.md

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ Error: failed to run linuxdeploy
1616
Error: strip: Unable to recognise the format of the input file
1717
```
1818

19-
**Solution 1: Use NO_STRIP (Recommended)**
19+
**Solution 1: Automatic NO_STRIP Retry (Recommended)**
2020

21-
Simply add `NO_STRIP=true` before your build command:
21+
Pake CLI now automatically retries AppImage builds with `NO_STRIP=1` when linuxdeploy fails to strip the binary. To skip the strip step from the very first attempt (or when scripting your own builds), set the variable manually:
2222

2323
```bash
24-
NO_STRIP=true pake https://example.com --name MyApp --targets appimage
24+
NO_STRIP=1 pake https://example.com --name MyApp --targets appimage
2525
```
2626

2727
This bypasses the library stripping process that often causes issues on certain Linux distributions.
@@ -50,7 +50,7 @@ sudo apt install -y \
5050
pkg-config
5151
```
5252

53-
Then try building again with `NO_STRIP=true`.
53+
Then try building again (you can still pre-set `NO_STRIP=1` if you prefer).
5454

5555
**Solution 3: Use DEB Format Instead**
5656

@@ -60,16 +60,21 @@ DEB packages are more stable on Debian-based systems:
6060
pake https://example.com --name MyApp --targets deb
6161
```
6262

63-
**Solution 4: Use Docker**
63+
**Solution 4: Use Docker (with FUSE access)**
6464

65-
Build in a clean environment without installing dependencies:
65+
Build in a clean environment without installing dependencies. AppImage tooling needs access to `/dev/fuse`, so run the container in privileged mode (or grant FUSE explicitly):
6666

6767
```bash
68-
docker run --rm -v $(pwd)/output:/app/output \
68+
docker run --rm --privileged \
69+
--device /dev/fuse \
70+
--security-opt apparmor=unconfined \
71+
-v $(pwd)/output:/output \
6972
ghcr.io/tw93/pake:latest \
70-
pake https://example.com --name MyApp --targets appimage
73+
https://example.com --name MyApp --targets appimage
7174
```
7275

76+
> **Tip:** The generated AppImage may be owned by root. Run `sudo chown $(id -nu):$(id -ng) ./output/MyApp.AppImage` afterwards.
77+
7378
**Why This Happens:**
7479

7580
This is a known issue with Tauri's linuxdeploy tool, which can fail when:
@@ -78,7 +83,7 @@ This is a known issue with Tauri's linuxdeploy tool, which can fail when:
7883
- Building on newer distributions (Arch, Debian Trixie, etc.)
7984
- Missing WebKit2GTK or GTK development libraries
8085

81-
The `NO_STRIP=true` environment variable is the official workaround recommended by the Tauri community.
86+
The `NO_STRIP=1` environment variable is the official workaround recommended by the Tauri community.
8287

8388
---
8489

0 commit comments

Comments
 (0)