pake-cli 3.15.6 โ 3.16.0
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/README.md +1 -1
- package/dist/cli.js +574 -168
- package/llms.txt +15 -2
- package/package.json +3 -4
- package/src-tauri/Cargo.lock +6 -5
- package/src-tauri/Cargo.toml +7 -2
- package/src-tauri/src/app/auth.rs +478 -0
- package/src-tauri/src/app/config.rs +5 -0
- package/src-tauri/src/app/mod.rs +1 -1
- package/src-tauri/src/app/window.rs +93 -32
- package/src-tauri/src/inject/event.js +65 -89
- package/src-tauri/src/inject/find.js +39 -24
- package/src-tauri/src/inject/frame_links.js +62 -0
- package/src-tauri/src/inject/fullscreen.js +9 -4
- package/src-tauri/src/inject/link_policy.js +60 -0
- package/src-tauri/src/inject/style.js +28 -18
- package/src-tauri/src/inject/styles.js +100 -0
- package/src-tauri/tauri.conf.json +1 -1
- package/src-tauri/src/app/cert.rs +0 -187
package/dist/cli.js
CHANGED
|
@@ -4,26 +4,26 @@ import chalk from 'chalk';
|
|
|
4
4
|
import updateNotifier from 'update-notifier';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import fsExtra from 'fs-extra';
|
|
7
|
-
import { fileURLToPath } from 'url';
|
|
8
7
|
import prompts from 'prompts';
|
|
9
8
|
import os from 'os';
|
|
10
9
|
import { execa, execaSync } from 'execa';
|
|
11
10
|
import crypto from 'crypto';
|
|
12
11
|
import ora from 'ora';
|
|
13
12
|
import fs from 'fs';
|
|
13
|
+
import { fileURLToPath } from 'url';
|
|
14
|
+
import { setTimeout as setTimeout$1 } from 'timers/promises';
|
|
14
15
|
import fs$1 from 'fs/promises';
|
|
15
16
|
import { dir } from 'tmp-promise';
|
|
16
17
|
import { fileTypeFromBuffer } from 'file-type';
|
|
17
|
-
import icongen from 'icon-gen';
|
|
18
|
-
import sharp from 'sharp';
|
|
19
18
|
import * as psl from 'psl';
|
|
20
19
|
import { InvalidArgumentError, program as program$1, Option } from 'commander';
|
|
21
20
|
|
|
22
21
|
var name = "pake-cli";
|
|
23
|
-
var version = "3.
|
|
22
|
+
var version = "3.16.0";
|
|
24
23
|
var description = "๐คฑ๐ป Turn any webpage into a desktop app with one command. ๐คฑ๐ป ไธ้ฎๆๅ
็ฝ้กต็ๆ่ฝป้ๆก้ขๅบ็จใ";
|
|
24
|
+
var homepage = "https://faberon.io/projects/pake";
|
|
25
25
|
var engines = {
|
|
26
|
-
node: ">=20.
|
|
26
|
+
node: ">=20.9.0"
|
|
27
27
|
};
|
|
28
28
|
var packageManager = "pnpm@10.26.2";
|
|
29
29
|
var bin = {
|
|
@@ -80,7 +80,6 @@ var dependencies = {
|
|
|
80
80
|
execa: "^9.6.1",
|
|
81
81
|
"file-type": "^21.3.4",
|
|
82
82
|
"fs-extra": "^11.3.3",
|
|
83
|
-
"icon-gen": "^5.0.0",
|
|
84
83
|
loglevel: "^1.9.2",
|
|
85
84
|
ora: "^9.3.0",
|
|
86
85
|
prompts: "^2.4.2",
|
|
@@ -100,7 +99,6 @@ var devDependencies = {
|
|
|
100
99
|
"@types/prompts": "^2.4.9",
|
|
101
100
|
"@types/tmp": "^0.2.6",
|
|
102
101
|
"@types/update-notifier": "^6.0.8",
|
|
103
|
-
"app-root-path": "^3.1.0",
|
|
104
102
|
"cross-env": "^10.1.0",
|
|
105
103
|
prettier: "^3.8.1",
|
|
106
104
|
rollup: "^4.59.0",
|
|
@@ -124,6 +122,7 @@ var packageJson = {
|
|
|
124
122
|
name: name,
|
|
125
123
|
version: version,
|
|
126
124
|
description: description,
|
|
125
|
+
homepage: homepage,
|
|
127
126
|
engines: engines,
|
|
128
127
|
packageManager: packageManager,
|
|
129
128
|
bin: bin,
|
|
@@ -140,40 +139,6 @@ var packageJson = {
|
|
|
140
139
|
pnpm: pnpm
|
|
141
140
|
};
|
|
142
141
|
|
|
143
|
-
// Convert the current module URL to a file path
|
|
144
|
-
const currentModulePath = fileURLToPath(import.meta.url);
|
|
145
|
-
// Resolve the parent directory of the current module
|
|
146
|
-
const npmDirectory = path.join(path.dirname(currentModulePath), '..');
|
|
147
|
-
const tauriConfigDirectory = path.join(npmDirectory, 'src-tauri', '.pake');
|
|
148
|
-
|
|
149
|
-
// Load configs from npm package directory, not from project source
|
|
150
|
-
const tauriSrcDir = path.join(npmDirectory, 'src-tauri');
|
|
151
|
-
const pakeConf = fsExtra.readJSONSync(path.join(tauriSrcDir, 'pake.json'));
|
|
152
|
-
const CommonConf = fsExtra.readJSONSync(path.join(tauriSrcDir, 'tauri.conf.json'));
|
|
153
|
-
const WinConf = fsExtra.readJSONSync(path.join(tauriSrcDir, 'tauri.windows.conf.json'));
|
|
154
|
-
const MacConf = fsExtra.readJSONSync(path.join(tauriSrcDir, 'tauri.macos.conf.json'));
|
|
155
|
-
const LinuxConf = fsExtra.readJSONSync(path.join(tauriSrcDir, 'tauri.linux.conf.json'));
|
|
156
|
-
const platformConfigs = {
|
|
157
|
-
win32: WinConf,
|
|
158
|
-
darwin: MacConf,
|
|
159
|
-
linux: LinuxConf,
|
|
160
|
-
};
|
|
161
|
-
const { platform: platform$2 } = process;
|
|
162
|
-
// @ts-ignore
|
|
163
|
-
const platformConfig = platformConfigs[platform$2];
|
|
164
|
-
let tauriConfig = {
|
|
165
|
-
...CommonConf,
|
|
166
|
-
bundle: platformConfig.bundle,
|
|
167
|
-
app: {
|
|
168
|
-
...CommonConf.app,
|
|
169
|
-
trayIcon: {
|
|
170
|
-
...(platformConfig?.app?.trayIcon ?? {}),
|
|
171
|
-
},
|
|
172
|
-
},
|
|
173
|
-
build: CommonConf.build,
|
|
174
|
-
pake: pakeConf,
|
|
175
|
-
};
|
|
176
|
-
|
|
177
142
|
// Stable exit-code contract: 0 success, 2 invalid input, 3 build/network
|
|
178
143
|
// failure, 4 missing environment, 1 unexpected. Documented in cli-usage docs.
|
|
179
144
|
const ERROR_EXIT_CODES = {
|
|
@@ -282,10 +247,10 @@ function isCnMirrorEnabled(value = process.env[CN_MIRROR_ENV]) {
|
|
|
282
247
|
return TRUE_VALUES.has((value ?? '').trim().toLowerCase());
|
|
283
248
|
}
|
|
284
249
|
|
|
285
|
-
const { platform: platform$
|
|
286
|
-
const IS_MAC = platform$
|
|
287
|
-
const IS_WIN = platform$
|
|
288
|
-
const IS_LINUX = platform$
|
|
250
|
+
const { platform: platform$2 } = process;
|
|
251
|
+
const IS_MAC = platform$2 === 'darwin';
|
|
252
|
+
const IS_WIN = platform$2 === 'win32';
|
|
253
|
+
const IS_LINUX = platform$2 === 'linux';
|
|
289
254
|
// Distro IDs / ID_LIKE families that ship an RPM-based package manager.
|
|
290
255
|
const RPM_FAMILY_IDS = new Set([
|
|
291
256
|
'rhel',
|
|
@@ -374,10 +339,295 @@ function getDefaultLinuxTargets() {
|
|
|
374
339
|
return detectLinuxPackageFamily() === 'rpm' ? 'rpm,appimage' : 'deb,appimage';
|
|
375
340
|
}
|
|
376
341
|
|
|
342
|
+
// Convert the current module URL to a file path
|
|
343
|
+
const currentModulePath = fileURLToPath(import.meta.url);
|
|
344
|
+
// Resolve the parent directory of the current module
|
|
345
|
+
const packageDirectory = path.join(path.dirname(currentModulePath), '..');
|
|
346
|
+
let npmDirectory = packageDirectory;
|
|
347
|
+
let tauriConfigDirectory = path.join(npmDirectory, 'src-tauri', '.pake');
|
|
348
|
+
// A CLI invocation owns one build workspace. Keep template resolution separate
|
|
349
|
+
// from generated paths so packaging never rewrites the installed package.
|
|
350
|
+
function setBuildDirectory(directory) {
|
|
351
|
+
npmDirectory = directory;
|
|
352
|
+
tauriConfigDirectory = path.join(directory, 'src-tauri', '.pake');
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const logger = {
|
|
356
|
+
info(...msg) {
|
|
357
|
+
log.info(...msg.map((m) => chalk.white(m)));
|
|
358
|
+
},
|
|
359
|
+
debug(...msg) {
|
|
360
|
+
log.debug(...msg);
|
|
361
|
+
},
|
|
362
|
+
error(...msg) {
|
|
363
|
+
log.error(...msg.map((m) => chalk.red(m)));
|
|
364
|
+
},
|
|
365
|
+
warn(...msg) {
|
|
366
|
+
log.warn(...msg.map((m) => chalk.yellow(m)));
|
|
367
|
+
},
|
|
368
|
+
success(...msg) {
|
|
369
|
+
log.info(...msg.map((m) => chalk.green(m)));
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Error class used for user-facing CLI errors.
|
|
375
|
+
*
|
|
376
|
+
* The top-level catch in `bin/cli.ts` prints `message` directly without a
|
|
377
|
+
* stack trace and exits with the code mapped from `code` (see
|
|
378
|
+
* ERROR_EXIT_CODES in utils/output.ts). Use this for predictable failures
|
|
379
|
+
* (invalid names, missing files, etc.) so users see a clean message instead
|
|
380
|
+
* of a Node.js stack dump. `code` and `hint` also feed the `--json` result.
|
|
381
|
+
*/
|
|
382
|
+
class PakeError extends Error {
|
|
383
|
+
constructor(message, options) {
|
|
384
|
+
super(message);
|
|
385
|
+
this.isUserError = true;
|
|
386
|
+
this.name = 'PakeError';
|
|
387
|
+
this.code = options?.code;
|
|
388
|
+
this.hint = options?.hint;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
function isPakeError(error) {
|
|
392
|
+
return (error instanceof PakeError ||
|
|
393
|
+
(typeof error === 'object' &&
|
|
394
|
+
error !== null &&
|
|
395
|
+
error.isUserError === true));
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/** Check the local launcher and native binding, not just an installed manifest. */
|
|
399
|
+
async function hasReadyTauriCli(directory) {
|
|
400
|
+
const modules = path.join(directory, 'node_modules');
|
|
401
|
+
const launcher = path.join(modules, '.bin', process.platform === 'win32' ? 'tauri.cmd' : 'tauri');
|
|
402
|
+
const entry = path.join(modules, '@tauri-apps', 'cli', 'tauri.js');
|
|
403
|
+
try {
|
|
404
|
+
await fsExtra.access(launcher, process.platform === 'win32' ? fsExtra.constants.F_OK : fsExtra.constants.X_OK);
|
|
405
|
+
await execa(process.execPath, [entry, '--version'], {
|
|
406
|
+
cwd: directory,
|
|
407
|
+
stdio: 'ignore',
|
|
408
|
+
timeout: 10000,
|
|
409
|
+
});
|
|
410
|
+
return true;
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
let cancellation;
|
|
418
|
+
function beginBuildCancellation() {
|
|
419
|
+
const scope = { controller: new AbortController() };
|
|
420
|
+
cancellation = scope;
|
|
421
|
+
const cancel = (signal) => scope.controller.abort(new PakeError(`Build cancelled (${signal}).`, { code: 'BUILD_FAILED' }));
|
|
422
|
+
const interrupt = () => cancel('SIGINT');
|
|
423
|
+
const terminate = () => cancel('SIGTERM');
|
|
424
|
+
process.on('SIGINT', interrupt);
|
|
425
|
+
process.on('SIGTERM', terminate);
|
|
426
|
+
return () => {
|
|
427
|
+
process.off('SIGINT', interrupt);
|
|
428
|
+
process.off('SIGTERM', terminate);
|
|
429
|
+
if (cancellation === scope)
|
|
430
|
+
cancellation = undefined;
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
function getBuildCancellationSignal() {
|
|
434
|
+
return cancellation?.controller.signal;
|
|
435
|
+
}
|
|
436
|
+
function preventBuildWorkspaceCleanup(reason) {
|
|
437
|
+
if (cancellation)
|
|
438
|
+
cancellation.unsafeCleanup = reason;
|
|
439
|
+
}
|
|
440
|
+
function throwIfBuildCancelled() {
|
|
441
|
+
getBuildCancellationSignal()?.throwIfAborted();
|
|
442
|
+
}
|
|
443
|
+
/** Copy only build inputs; cached or previously generated user content is not a template. */
|
|
444
|
+
async function createBuildWorkspace(sourceDirectory) {
|
|
445
|
+
const directory = await fsExtra.mkdtemp(path.join(os.tmpdir(), 'pake-build-'));
|
|
446
|
+
try {
|
|
447
|
+
for (const file of [
|
|
448
|
+
'package.json',
|
|
449
|
+
'pnpm-lock.yaml',
|
|
450
|
+
'package-lock.json',
|
|
451
|
+
'rust-toolchain.toml',
|
|
452
|
+
'rust-toolchain',
|
|
453
|
+
]) {
|
|
454
|
+
const source = path.join(sourceDirectory, file);
|
|
455
|
+
if (await fsExtra.pathExists(source))
|
|
456
|
+
await fsExtra.copy(source, path.join(directory, file));
|
|
457
|
+
}
|
|
458
|
+
const sourceTauri = path.join(sourceDirectory, 'src-tauri');
|
|
459
|
+
await fsExtra.copy(sourceTauri, path.join(directory, 'src-tauri'), {
|
|
460
|
+
filter: (source) => !['target', '.pake', 'gen'].includes(path.relative(sourceTauri, source).split(path.sep)[0]),
|
|
461
|
+
});
|
|
462
|
+
await fsExtra.ensureDir(path.join(directory, 'dist'));
|
|
463
|
+
// Keep the CLI runnable while local input staging replaces this workspace's dist.
|
|
464
|
+
await fsExtra.copy(path.join(sourceDirectory, 'dist', 'cli.js'), path.join(directory, 'dist', 'cli.js'));
|
|
465
|
+
const modules = path.join(sourceDirectory, 'node_modules');
|
|
466
|
+
if (await hasReadyTauriCli(sourceDirectory)) {
|
|
467
|
+
await fsExtra.symlink(modules, path.join(directory, 'node_modules'), process.platform === 'win32' ? 'junction' : 'dir');
|
|
468
|
+
}
|
|
469
|
+
return directory;
|
|
470
|
+
}
|
|
471
|
+
catch (error) {
|
|
472
|
+
await fsExtra.remove(directory);
|
|
473
|
+
throw error;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
/** Hold the cache through artifact copying, beyond Cargo's own compile lock. */
|
|
477
|
+
async function acquireBuildCache(targetDirectory) {
|
|
478
|
+
await fsExtra.ensureDir(targetDirectory);
|
|
479
|
+
const lock = path.join(targetDirectory, '.pake-build.lock');
|
|
480
|
+
const started = Date.now();
|
|
481
|
+
let announced = false;
|
|
482
|
+
for (;;) {
|
|
483
|
+
throwIfBuildCancelled();
|
|
484
|
+
try {
|
|
485
|
+
const handle = await fsExtra.open(lock, 'wx');
|
|
486
|
+
try {
|
|
487
|
+
fsExtra.writeFileSync(handle, String(process.pid));
|
|
488
|
+
}
|
|
489
|
+
finally {
|
|
490
|
+
fsExtra.closeSync(handle);
|
|
491
|
+
}
|
|
492
|
+
let released = false;
|
|
493
|
+
return async () => {
|
|
494
|
+
if (!released) {
|
|
495
|
+
await fsExtra.remove(lock);
|
|
496
|
+
released = true;
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
catch (error) {
|
|
501
|
+
if (error.code !== 'EEXIST')
|
|
502
|
+
throw error;
|
|
503
|
+
}
|
|
504
|
+
try {
|
|
505
|
+
const owner = Number(await fsExtra.readFile(lock, 'utf8'));
|
|
506
|
+
if (Number.isInteger(owner) && owner > 0) {
|
|
507
|
+
try {
|
|
508
|
+
process.kill(owner, 0);
|
|
509
|
+
}
|
|
510
|
+
catch (error) {
|
|
511
|
+
if (error.code === 'ESRCH') {
|
|
512
|
+
// Read-then-unlink cannot atomically reclaim a dead owner's lock:
|
|
513
|
+
// another waiter may already have acquired a new one at this path.
|
|
514
|
+
throw new PakeError('A previous Pake process left a compilation cache lock.', {
|
|
515
|
+
code: 'BUILD_FAILED',
|
|
516
|
+
hint: `After stopping other Pake builds, remove ${lock} and retry.`,
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
catch (error) {
|
|
523
|
+
if (error.code === 'ENOENT')
|
|
524
|
+
continue;
|
|
525
|
+
throw error;
|
|
526
|
+
}
|
|
527
|
+
if (Date.now() - started > 900000) {
|
|
528
|
+
throw new PakeError('Another Pake build is still using the compilation cache.', {
|
|
529
|
+
code: 'BUILD_FAILED',
|
|
530
|
+
hint: 'Wait for that build to finish, then retry.',
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
if (!announced) {
|
|
534
|
+
logger.info('Waiting for another Pake build to finish using the compilation cache...');
|
|
535
|
+
announced = true;
|
|
536
|
+
}
|
|
537
|
+
await setTimeout$1(200);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
async function enterBuildWorkspace() {
|
|
541
|
+
const previousTarget = process.env.CARGO_TARGET_DIR;
|
|
542
|
+
const targetDirectory = path.resolve(packageDirectory, previousTarget || 'src-tauri/target');
|
|
543
|
+
const release = await acquireBuildCache(targetDirectory);
|
|
544
|
+
let directory;
|
|
545
|
+
try {
|
|
546
|
+
directory = await createBuildWorkspace(packageDirectory);
|
|
547
|
+
}
|
|
548
|
+
catch (error) {
|
|
549
|
+
await release();
|
|
550
|
+
throw error;
|
|
551
|
+
}
|
|
552
|
+
setBuildDirectory(directory);
|
|
553
|
+
process.env.CARGO_TARGET_DIR = targetDirectory;
|
|
554
|
+
const leave = async () => {
|
|
555
|
+
setBuildDirectory(packageDirectory);
|
|
556
|
+
if (previousTarget === undefined)
|
|
557
|
+
delete process.env.CARGO_TARGET_DIR;
|
|
558
|
+
else
|
|
559
|
+
process.env.CARGO_TARGET_DIR = previousTarget;
|
|
560
|
+
if (cancellation?.unsafeCleanup) {
|
|
561
|
+
logger.warn(`Build processes could not be confirmed stopped; keeping workspace ${directory} and its cache lock: ${cancellation.unsafeCleanup}`);
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
try {
|
|
565
|
+
await fsExtra.remove(directory);
|
|
566
|
+
}
|
|
567
|
+
catch (error) {
|
|
568
|
+
logger.warn(`Could not remove the temporary build workspace ${directory}: ${String(error)}`);
|
|
569
|
+
}
|
|
570
|
+
finally {
|
|
571
|
+
try {
|
|
572
|
+
await release();
|
|
573
|
+
}
|
|
574
|
+
catch (error) {
|
|
575
|
+
logger.warn(`Could not release the compilation cache lock: ${String(error)}`);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
if (getBuildCancellationSignal()?.aborted) {
|
|
580
|
+
await leave();
|
|
581
|
+
throwIfBuildCancelled();
|
|
582
|
+
}
|
|
583
|
+
return leave;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
async function terminateBuildTree(pid) {
|
|
587
|
+
if (process.platform === 'win32') {
|
|
588
|
+
await execa('taskkill', ['/pid', String(pid), '/T', '/F'], {
|
|
589
|
+
timeout: 5000,
|
|
590
|
+
windowsHide: true,
|
|
591
|
+
});
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
const killGroup = (signal) => {
|
|
595
|
+
try {
|
|
596
|
+
process.kill(-pid, signal);
|
|
597
|
+
}
|
|
598
|
+
catch (error) {
|
|
599
|
+
if (error.code !== 'ESRCH')
|
|
600
|
+
throw error;
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
killGroup('SIGTERM');
|
|
604
|
+
const started = Date.now();
|
|
605
|
+
for (;;) {
|
|
606
|
+
// The package manager can exit before its compiler descendants. Only
|
|
607
|
+
// release the cache once no live member of their process group remains.
|
|
608
|
+
// Zombies have already exited and cannot write artifacts.
|
|
609
|
+
const { stdout } = await execa('ps', ['-axo', 'pgid=,stat='], {
|
|
610
|
+
timeout: 1000,
|
|
611
|
+
});
|
|
612
|
+
const alive = stdout.split('\n').some((line) => {
|
|
613
|
+
const [group, state] = line.trim().split(/\s+/);
|
|
614
|
+
return Number(group) === pid && state && !state.startsWith('Z');
|
|
615
|
+
});
|
|
616
|
+
if (!alive)
|
|
617
|
+
return;
|
|
618
|
+
if (Date.now() - started > 5000)
|
|
619
|
+
throw new Error('Build process group did not stop.');
|
|
620
|
+
if (Date.now() - started >= 250)
|
|
621
|
+
killGroup('SIGKILL');
|
|
622
|
+
await setTimeout$1(25);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
377
625
|
async function shellExec(command, timeout = 300000, env) {
|
|
626
|
+
const signal = getBuildCancellationSignal();
|
|
627
|
+
signal?.throwIfAborted();
|
|
378
628
|
try {
|
|
379
|
-
const
|
|
380
|
-
cwd: npmDirectory,
|
|
629
|
+
const subprocess = execa(command.executable, command.args, {
|
|
630
|
+
cwd: command.cwd ?? npmDirectory,
|
|
381
631
|
// Use 'inherit' to show all output directly to user in real-time.
|
|
382
632
|
// This ensures linuxdeploy and other tool outputs are visible during builds.
|
|
383
633
|
// In machine mode (--json) stdout is reserved for the final JSON result,
|
|
@@ -385,22 +635,53 @@ async function shellExec(command, timeout = 300000, env) {
|
|
|
385
635
|
stdin: 'inherit',
|
|
386
636
|
stdout: isMachineMode() ? process.stderr : 'inherit',
|
|
387
637
|
stderr: 'inherit',
|
|
388
|
-
shell:
|
|
638
|
+
shell: false,
|
|
639
|
+
detached: Boolean(signal) && process.platform !== 'win32',
|
|
389
640
|
timeout,
|
|
390
641
|
env: env ? { ...process.env, ...env } : process.env,
|
|
391
642
|
});
|
|
392
|
-
|
|
643
|
+
let termination;
|
|
644
|
+
const cancel = () => {
|
|
645
|
+
if (termination || subprocess.pid === undefined)
|
|
646
|
+
return;
|
|
647
|
+
termination = terminateBuildTree(subprocess.pid).catch((error) => {
|
|
648
|
+
preventBuildWorkspaceCleanup(String(error));
|
|
649
|
+
subprocess.kill('SIGKILL');
|
|
650
|
+
});
|
|
651
|
+
};
|
|
652
|
+
signal?.addEventListener('abort', cancel, { once: true });
|
|
653
|
+
if (signal?.aborted)
|
|
654
|
+
cancel();
|
|
655
|
+
try {
|
|
656
|
+
const { exitCode } = await subprocess;
|
|
657
|
+
return exitCode;
|
|
658
|
+
}
|
|
659
|
+
catch (error) {
|
|
660
|
+
// A timed-out or failed package manager can leave compiler descendants.
|
|
661
|
+
// Use the same tree barrier before the caller releases its cache lock.
|
|
662
|
+
if (signal)
|
|
663
|
+
cancel();
|
|
664
|
+
throw error;
|
|
665
|
+
}
|
|
666
|
+
finally {
|
|
667
|
+
signal?.removeEventListener('abort', cancel);
|
|
668
|
+
await termination;
|
|
669
|
+
signal?.throwIfAborted();
|
|
670
|
+
}
|
|
393
671
|
}
|
|
394
672
|
catch (error) {
|
|
673
|
+
if (signal?.aborted)
|
|
674
|
+
throw signal.reason;
|
|
675
|
+
const description = JSON.stringify([command.executable, ...command.args]);
|
|
395
676
|
const exitCode = error.exitCode ?? 'unknown';
|
|
396
677
|
const errorMessage = error.message || 'Unknown error occurred';
|
|
397
678
|
if (error.timedOut) {
|
|
398
|
-
throw new Error(`Command timed out after ${timeout}ms:
|
|
679
|
+
throw new Error(`Command timed out after ${timeout}ms: ${description}. Try increasing timeout or check network connectivity.`);
|
|
399
680
|
}
|
|
400
681
|
// AppImage/linuxdeploy guidance is added by the caller (BaseBuilder), which
|
|
401
682
|
// knows the build target. We only have the command line here (the tool's
|
|
402
683
|
// diagnostics stream to the terminal via stdio:inherit, not into the error).
|
|
403
|
-
throw new Error(`Error occurred while executing command
|
|
684
|
+
throw new Error(`Error occurred while executing command ${description}. Exit code: ${exitCode}. Details: ${errorMessage}`);
|
|
404
685
|
}
|
|
405
686
|
}
|
|
406
687
|
|
|
@@ -451,25 +732,54 @@ function ensureRustEnv() {
|
|
|
451
732
|
ensureCargoBinOnPath();
|
|
452
733
|
}
|
|
453
734
|
async function installRust() {
|
|
454
|
-
const rustInstallScriptForUnix = isCnMirrorEnabled()
|
|
455
|
-
? 'export RUSTUP_DIST_SERVER="https://rsproxy.cn" && export RUSTUP_UPDATE_ROOT="https://rsproxy.cn/rustup" && curl --proto "=https" --tlsv1.2 -sSf https://rsproxy.cn/rustup-init.sh | sh'
|
|
456
|
-
: "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y";
|
|
457
|
-
const rustInstallScriptForWindows = 'winget install --id Rustlang.Rustup';
|
|
458
735
|
const spinner = getSpinner('Downloading Rust...');
|
|
459
736
|
try {
|
|
460
|
-
|
|
737
|
+
if (IS_WIN) {
|
|
738
|
+
await shellExec({
|
|
739
|
+
executable: 'winget',
|
|
740
|
+
args: ['install', '--id', 'Rustlang.Rustup'],
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
else {
|
|
744
|
+
const useCnMirror = isCnMirrorEnabled();
|
|
745
|
+
const tempDir = await fsExtra.mkdtemp(path.join(os.tmpdir(), 'pake-rustup-'));
|
|
746
|
+
try {
|
|
747
|
+
const scriptPath = path.join(tempDir, 'rustup-init.sh');
|
|
748
|
+
await shellExec({
|
|
749
|
+
executable: 'curl',
|
|
750
|
+
args: [
|
|
751
|
+
'--proto',
|
|
752
|
+
'=https',
|
|
753
|
+
'--tlsv1.2',
|
|
754
|
+
'-sSf',
|
|
755
|
+
'-o',
|
|
756
|
+
scriptPath,
|
|
757
|
+
useCnMirror
|
|
758
|
+
? 'https://rsproxy.cn/rustup-init.sh'
|
|
759
|
+
: 'https://sh.rustup.rs',
|
|
760
|
+
],
|
|
761
|
+
});
|
|
762
|
+
await shellExec({
|
|
763
|
+
executable: 'sh',
|
|
764
|
+
args: useCnMirror ? [scriptPath] : [scriptPath, '-y'],
|
|
765
|
+
}, 300000, useCnMirror
|
|
766
|
+
? {
|
|
767
|
+
RUSTUP_DIST_SERVER: 'https://rsproxy.cn',
|
|
768
|
+
RUSTUP_UPDATE_ROOT: 'https://rsproxy.cn/rustup',
|
|
769
|
+
}
|
|
770
|
+
: undefined);
|
|
771
|
+
}
|
|
772
|
+
finally {
|
|
773
|
+
await fsExtra.remove(tempDir);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
461
776
|
spinner.succeed(chalk.green('โ Rust installed successfully!'));
|
|
462
777
|
ensureRustEnv();
|
|
463
778
|
}
|
|
464
779
|
catch (error) {
|
|
465
780
|
spinner.fail(chalk.red('โ Rust installation failed!'));
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
}
|
|
469
|
-
else {
|
|
470
|
-
console.error(error);
|
|
471
|
-
}
|
|
472
|
-
process.exit(1);
|
|
781
|
+
// The CLI owns error reporting and workspace/cache cleanup.
|
|
782
|
+
throw error;
|
|
473
783
|
}
|
|
474
784
|
}
|
|
475
785
|
function checkRustInstalled() {
|
|
@@ -489,9 +799,13 @@ async function combineFiles(files, output) {
|
|
|
489
799
|
const fileContent = await fs$1.readFile(file, 'utf-8');
|
|
490
800
|
return `window.addEventListener('DOMContentLoaded', (_event) => {
|
|
491
801
|
const css = ${JSON.stringify(fileContent)};
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
802
|
+
if (typeof window.__PAKE_INJECT_STYLE__ === 'function') {
|
|
803
|
+
window.__PAKE_INJECT_STYLE__(css);
|
|
804
|
+
} else {
|
|
805
|
+
const style = document.createElement('style');
|
|
806
|
+
style.textContent = css;
|
|
807
|
+
document.head.appendChild(style);
|
|
808
|
+
}
|
|
495
809
|
});`;
|
|
496
810
|
}
|
|
497
811
|
const fileContent = await fs$1.readFile(file);
|
|
@@ -507,24 +821,6 @@ async function combineFiles(files, output) {
|
|
|
507
821
|
return files;
|
|
508
822
|
}
|
|
509
823
|
|
|
510
|
-
const logger = {
|
|
511
|
-
info(...msg) {
|
|
512
|
-
log.info(...msg.map((m) => chalk.white(m)));
|
|
513
|
-
},
|
|
514
|
-
debug(...msg) {
|
|
515
|
-
log.debug(...msg);
|
|
516
|
-
},
|
|
517
|
-
error(...msg) {
|
|
518
|
-
log.error(...msg.map((m) => chalk.red(m)));
|
|
519
|
-
},
|
|
520
|
-
warn(...msg) {
|
|
521
|
-
log.warn(...msg.map((m) => chalk.yellow(m)));
|
|
522
|
-
},
|
|
523
|
-
success(...msg) {
|
|
524
|
-
log.info(...msg.map((m) => chalk.green(m)));
|
|
525
|
-
},
|
|
526
|
-
};
|
|
527
|
-
|
|
528
824
|
function generateSafeFilename(name) {
|
|
529
825
|
return name
|
|
530
826
|
.replace(/[<>:"/\\|?*]/g, '_')
|
|
@@ -562,31 +858,6 @@ function generateIdentifierSafeName(name) {
|
|
|
562
858
|
return cleaned;
|
|
563
859
|
}
|
|
564
860
|
|
|
565
|
-
/**
|
|
566
|
-
* Error class used for user-facing CLI errors.
|
|
567
|
-
*
|
|
568
|
-
* The top-level catch in `bin/cli.ts` prints `message` directly without a
|
|
569
|
-
* stack trace and exits with the code mapped from `code` (see
|
|
570
|
-
* ERROR_EXIT_CODES in utils/output.ts). Use this for predictable failures
|
|
571
|
-
* (invalid names, missing files, etc.) so users see a clean message instead
|
|
572
|
-
* of a Node.js stack dump. `code` and `hint` also feed the `--json` result.
|
|
573
|
-
*/
|
|
574
|
-
class PakeError extends Error {
|
|
575
|
-
constructor(message, options) {
|
|
576
|
-
super(message);
|
|
577
|
-
this.isUserError = true;
|
|
578
|
-
this.name = 'PakeError';
|
|
579
|
-
this.code = options?.code;
|
|
580
|
-
this.hint = options?.hint;
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
function isPakeError(error) {
|
|
584
|
-
return (error instanceof PakeError ||
|
|
585
|
-
(typeof error === 'object' &&
|
|
586
|
-
error !== null &&
|
|
587
|
-
error.isUserError === true));
|
|
588
|
-
}
|
|
589
|
-
|
|
590
861
|
const LINUX_TARGET_TYPES = ['deb', 'appimage', 'rpm', 'zst'];
|
|
591
862
|
// Returns the valid Linux build targets from a comma-separated targets
|
|
592
863
|
// string, preserving LINUX_TARGET_TYPES order. Unknown entries are dropped.
|
|
@@ -689,8 +960,11 @@ async function stageLocalTree(sourceDir) {
|
|
|
689
960
|
const resolvedPackage = await fsExtra
|
|
690
961
|
.realpath(npmDirectory)
|
|
691
962
|
.catch(() => path.resolve(npmDirectory));
|
|
963
|
+
const installedPackage = await fsExtra.realpath(packageDirectory);
|
|
692
964
|
const packageDist = path.join(resolvedPackage, 'dist');
|
|
693
|
-
if (resolvedSource ===
|
|
965
|
+
if (resolvedSource === installedPackage ||
|
|
966
|
+
installedPackage.startsWith(resolvedSource + path.sep) ||
|
|
967
|
+
resolvedSource === resolvedPackage ||
|
|
694
968
|
resolvedPackage.startsWith(resolvedSource + path.sep) ||
|
|
695
969
|
resolvedSource === packageDist ||
|
|
696
970
|
resolvedSource.startsWith(packageDist + path.sep)) {
|
|
@@ -922,7 +1196,7 @@ async function mergeIcons(options, name, tauriConf, platform, safeAppName) {
|
|
|
922
1196
|
delete tauriConf.app.trayIcon;
|
|
923
1197
|
}
|
|
924
1198
|
async function injectCustomCode(options, tauriConf) {
|
|
925
|
-
const { inject, proxyUrl, multiInstance, multiWindow, wasm } = options;
|
|
1199
|
+
const { inject, proxyUrl, basicAuth, multiInstance, multiWindow, wasm } = options;
|
|
926
1200
|
const injectFilePath = path.join(npmDirectory, 'src-tauri/src/inject/custom.js');
|
|
927
1201
|
if (inject?.length > 0) {
|
|
928
1202
|
const injectArray = Array.isArray(inject) ? inject : [inject];
|
|
@@ -939,6 +1213,7 @@ async function injectCustomCode(options, tauriConf) {
|
|
|
939
1213
|
await fsExtra.writeFile(injectFilePath, '');
|
|
940
1214
|
}
|
|
941
1215
|
tauriConf.pake.proxy_url = proxyUrl || '';
|
|
1216
|
+
tauriConf.pake.basic_auth = basicAuth;
|
|
942
1217
|
tauriConf.pake.multi_instance = multiInstance;
|
|
943
1218
|
tauriConf.pake.multi_window = multiWindow;
|
|
944
1219
|
if (wasm) {
|
|
@@ -1041,6 +1316,34 @@ async function mergeConfig(url, options, tauriConf) {
|
|
|
1041
1316
|
await writeAllConfigs(tauriConf, platform);
|
|
1042
1317
|
}
|
|
1043
1318
|
|
|
1319
|
+
// Load configs from npm package directory, not from project source
|
|
1320
|
+
const tauriSrcDir = path.join(npmDirectory, 'src-tauri');
|
|
1321
|
+
const pakeConf = fsExtra.readJSONSync(path.join(tauriSrcDir, 'pake.json'));
|
|
1322
|
+
const CommonConf = fsExtra.readJSONSync(path.join(tauriSrcDir, 'tauri.conf.json'));
|
|
1323
|
+
const WinConf = fsExtra.readJSONSync(path.join(tauriSrcDir, 'tauri.windows.conf.json'));
|
|
1324
|
+
const MacConf = fsExtra.readJSONSync(path.join(tauriSrcDir, 'tauri.macos.conf.json'));
|
|
1325
|
+
const LinuxConf = fsExtra.readJSONSync(path.join(tauriSrcDir, 'tauri.linux.conf.json'));
|
|
1326
|
+
const platformConfigs = {
|
|
1327
|
+
win32: WinConf,
|
|
1328
|
+
darwin: MacConf,
|
|
1329
|
+
linux: LinuxConf,
|
|
1330
|
+
};
|
|
1331
|
+
const { platform: platform$1 } = process;
|
|
1332
|
+
// @ts-ignore
|
|
1333
|
+
const platformConfig = platformConfigs[platform$1];
|
|
1334
|
+
let tauriConfig = {
|
|
1335
|
+
...CommonConf,
|
|
1336
|
+
bundle: platformConfig.bundle,
|
|
1337
|
+
app: {
|
|
1338
|
+
...CommonConf.app,
|
|
1339
|
+
trayIcon: {
|
|
1340
|
+
...(platformConfig?.app?.trayIcon ?? {}),
|
|
1341
|
+
},
|
|
1342
|
+
},
|
|
1343
|
+
build: CommonConf.build,
|
|
1344
|
+
pake: pakeConf,
|
|
1345
|
+
};
|
|
1346
|
+
|
|
1044
1347
|
/**
|
|
1045
1348
|
* Returns build environment variables overrides for macOS, where Rust crates
|
|
1046
1349
|
* sometimes need explicit C/C++ flags and a deterministic SDK target. Other
|
|
@@ -1132,11 +1435,12 @@ async function detectPackageManager() {
|
|
|
1132
1435
|
return 'pnpm';
|
|
1133
1436
|
}
|
|
1134
1437
|
function getInstallCommand(packageManager, useCnMirror) {
|
|
1135
|
-
const
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1438
|
+
const args = ['install'];
|
|
1439
|
+
if (useCnMirror)
|
|
1440
|
+
args.push('--registry=https://registry.npmmirror.com');
|
|
1441
|
+
if (packageManager === 'npm')
|
|
1442
|
+
args.push('--legacy-peer-deps');
|
|
1443
|
+
return { executable: packageManager, args };
|
|
1140
1444
|
}
|
|
1141
1445
|
async function copyFileWithSamePathGuard(sourcePath, destinationPath) {
|
|
1142
1446
|
if (path.resolve(sourcePath) === path.resolve(destinationPath)) {
|
|
@@ -1268,7 +1572,7 @@ class BaseBuilder {
|
|
|
1268
1572
|
}
|
|
1269
1573
|
async prepare() {
|
|
1270
1574
|
const tauriSrcPath = path.join(npmDirectory, 'src-tauri');
|
|
1271
|
-
const tauriTargetPath =
|
|
1575
|
+
const tauriTargetPath = this.getCargoTargetDir();
|
|
1272
1576
|
const tauriTargetPathExists = await fsExtra.pathExists(tauriTargetPath);
|
|
1273
1577
|
if (!IS_MAC && !tauriTargetPathExists) {
|
|
1274
1578
|
logger.warn('โผ The first use requires installing system dependencies.');
|
|
@@ -1297,9 +1601,21 @@ class BaseBuilder {
|
|
|
1297
1601
|
});
|
|
1298
1602
|
}
|
|
1299
1603
|
}
|
|
1300
|
-
const spinner = getSpinner('Installing package...');
|
|
1301
1604
|
const useCnMirror = isCnMirrorEnabled();
|
|
1302
1605
|
await configureCargoRegistry(tauriSrcPath, useCnMirror);
|
|
1606
|
+
// Workspaces reuse installed dependencies. Reinstalling through their
|
|
1607
|
+
// node_modules link would mutate the shared CLI installation.
|
|
1608
|
+
if (await hasReadyTauriCli(npmDirectory)) {
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
// Dependencies may disappear after the workspace linked them. Reinstall
|
|
1612
|
+
// privately even in that case, without writing through to the source tree.
|
|
1613
|
+
const modules = path.join(npmDirectory, 'node_modules');
|
|
1614
|
+
if (npmDirectory !== packageDirectory &&
|
|
1615
|
+
(await fsExtra.lstat(modules).catch(() => null))?.isSymbolicLink()) {
|
|
1616
|
+
await fsExtra.unlink(modules);
|
|
1617
|
+
}
|
|
1618
|
+
const spinner = getSpinner('Installing package...');
|
|
1303
1619
|
const packageManager = await detectPackageManager();
|
|
1304
1620
|
const timeout = getInstallTimeout();
|
|
1305
1621
|
const buildEnv = getBuildEnvironment();
|
|
@@ -1335,23 +1651,24 @@ class BaseBuilder {
|
|
|
1335
1651
|
}
|
|
1336
1652
|
async start(url) {
|
|
1337
1653
|
logger.info('Pake dev server starting...');
|
|
1338
|
-
await mergeConfig(url, this.options, tauriConfig);
|
|
1654
|
+
await mergeConfig(url, this.options, structuredClone(tauriConfig));
|
|
1339
1655
|
const packageManager = await detectPackageManager();
|
|
1340
1656
|
const configPath = path.join(npmDirectory, 'src-tauri', '.pake', 'tauri.conf.json');
|
|
1341
1657
|
const features = this.getBuildFeatures();
|
|
1342
|
-
const
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1658
|
+
const args = ['run', 'tauri'];
|
|
1659
|
+
if (packageManager === 'npm')
|
|
1660
|
+
args.push('--');
|
|
1661
|
+
args.push('dev', '--config', configPath);
|
|
1662
|
+
if (features.length > 0)
|
|
1663
|
+
args.push('--features', features.join(','));
|
|
1664
|
+
await shellExec({ executable: packageManager, args });
|
|
1346
1665
|
}
|
|
1347
1666
|
async buildAndCopy(url, target, logSuccess = true) {
|
|
1348
1667
|
const { name = 'pake-app' } = this.options;
|
|
1349
|
-
await mergeConfig(url, this.options, tauriConfig);
|
|
1668
|
+
await mergeConfig(url, this.options, structuredClone(tauriConfig));
|
|
1350
1669
|
const packageManager = await detectPackageManager();
|
|
1351
1670
|
// Build app
|
|
1352
1671
|
const buildSpinner = getSpinner('Building app...');
|
|
1353
|
-
// Let spinner run for a moment so user can see it, then stop before package manager command
|
|
1354
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1355
1672
|
buildSpinner.stop();
|
|
1356
1673
|
// Show static message to keep the status visible. Info, not warn: warn
|
|
1357
1674
|
// entries feed the --json warnings array and this is a status line.
|
|
@@ -1368,7 +1685,7 @@ class BaseBuilder {
|
|
|
1368
1685
|
if (isLinuxAppImage && !buildEnv.NO_STRIP && this.options.debug) {
|
|
1369
1686
|
logger.warn('โ AppImage strip step can fail on glibc 2.38+; Pake will auto-retry with NO_STRIP=1.');
|
|
1370
1687
|
}
|
|
1371
|
-
const buildCommand =
|
|
1688
|
+
const buildCommand = this.getBuildCommand(packageManager);
|
|
1372
1689
|
const buildTimeout = getBuildTimeout();
|
|
1373
1690
|
try {
|
|
1374
1691
|
await shellExec(buildCommand, buildTimeout, resolveExecEnv());
|
|
@@ -1476,24 +1793,23 @@ class BaseBuilder {
|
|
|
1476
1793
|
return BaseBuilder.ARCH_DISPLAY_NAMES[arch] || arch;
|
|
1477
1794
|
}
|
|
1478
1795
|
buildBaseCommand(packageManager, configPath, target) {
|
|
1479
|
-
const
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
let fullCommand = `${baseCommand}${argSeparator} -c "${configPath}"`;
|
|
1796
|
+
const args = ['run', this.options.debug ? 'build:debug' : 'build'];
|
|
1797
|
+
if (packageManager === 'npm')
|
|
1798
|
+
args.push('--');
|
|
1799
|
+
args.push('-c', configPath);
|
|
1484
1800
|
if (target) {
|
|
1485
|
-
|
|
1801
|
+
args.push('--target', target);
|
|
1486
1802
|
}
|
|
1487
1803
|
// Enable verbose output in debug mode to help diagnose build issues.
|
|
1488
1804
|
// This provides detailed logs from Tauri CLI and bundler tools.
|
|
1489
1805
|
if (this.options.debug) {
|
|
1490
|
-
|
|
1806
|
+
args.push('--verbose');
|
|
1491
1807
|
}
|
|
1492
1808
|
const features = this.getBuildFeatures();
|
|
1493
1809
|
if (features.length > 0) {
|
|
1494
|
-
|
|
1810
|
+
args.push('--features', features.join(','));
|
|
1495
1811
|
}
|
|
1496
|
-
return
|
|
1812
|
+
return { executable: packageManager, args };
|
|
1497
1813
|
}
|
|
1498
1814
|
getBuildFeatures() {
|
|
1499
1815
|
const features = ['cli-build'];
|
|
@@ -1509,10 +1825,10 @@ class BaseBuilder {
|
|
|
1509
1825
|
getBuildCommand(packageManager = 'pnpm') {
|
|
1510
1826
|
// Use temporary config directory to avoid modifying source files
|
|
1511
1827
|
const configPath = path.join(npmDirectory, 'src-tauri', '.pake', 'tauri.conf.json');
|
|
1512
|
-
|
|
1828
|
+
const fullCommand = this.buildBaseCommand(packageManager, configPath);
|
|
1513
1829
|
// For macOS, use app bundles by default unless DMG is explicitly requested
|
|
1514
1830
|
if (IS_MAC && this.options.targets === 'app') {
|
|
1515
|
-
fullCommand
|
|
1831
|
+
fullCommand.args.push('--bundles', 'app');
|
|
1516
1832
|
}
|
|
1517
1833
|
return fullCommand;
|
|
1518
1834
|
}
|
|
@@ -1663,7 +1979,7 @@ class MacBuilder extends BaseBuilder {
|
|
|
1663
1979
|
else {
|
|
1664
1980
|
arch = this.getArchDisplayName(this.resolveTargetArch(this.buildArch));
|
|
1665
1981
|
}
|
|
1666
|
-
return `${name}_${
|
|
1982
|
+
return `${name}_${this.options.appVersion}_${arch}`;
|
|
1667
1983
|
}
|
|
1668
1984
|
getReportArch() {
|
|
1669
1985
|
return this.getActualArch();
|
|
@@ -1726,9 +2042,9 @@ class WinBuilder extends BaseBuilder {
|
|
|
1726
2042
|
}
|
|
1727
2043
|
getFileName() {
|
|
1728
2044
|
const { name } = this.options;
|
|
1729
|
-
const language =
|
|
2045
|
+
const language = this.options.installerLanguage;
|
|
1730
2046
|
const targetArch = this.getArchDisplayName(this.buildArch);
|
|
1731
|
-
return `${name}_${
|
|
2047
|
+
return `${name}_${this.options.appVersion}_${targetArch}_${language}`;
|
|
1732
2048
|
}
|
|
1733
2049
|
getBuildCommand(packageManager = 'pnpm') {
|
|
1734
2050
|
const configPath = path.join('src-tauri', '.pake', 'tauri.conf.json');
|
|
@@ -1784,7 +2100,7 @@ class LinuxBuilder extends BaseBuilder {
|
|
|
1784
2100
|
}
|
|
1785
2101
|
getFileName() {
|
|
1786
2102
|
const { name = 'pake-app', targets } = this.options;
|
|
1787
|
-
const version =
|
|
2103
|
+
const version = this.options.appVersion;
|
|
1788
2104
|
const buildType = this.currentBuildType || targets.split(',').map((t) => t.trim())[0];
|
|
1789
2105
|
let arch;
|
|
1790
2106
|
if (this.buildArch === 'arm64') {
|
|
@@ -1862,7 +2178,7 @@ class LinuxBuilder extends BaseBuilder {
|
|
|
1862
2178
|
];
|
|
1863
2179
|
for (const { tool, pacmanPackage } of requiredTools) {
|
|
1864
2180
|
try {
|
|
1865
|
-
await
|
|
2181
|
+
await execa(tool, ['--version'], { stdio: 'ignore' });
|
|
1866
2182
|
}
|
|
1867
2183
|
catch {
|
|
1868
2184
|
throw new Error(`Building a zst package requires "${tool}". Install it first, e.g. "sudo pacman -S ${pacmanPackage}".`);
|
|
@@ -1872,24 +2188,30 @@ class LinuxBuilder extends BaseBuilder {
|
|
|
1872
2188
|
async createArchPackageFromDeb({ removeSourceDeb, }) {
|
|
1873
2189
|
const { name = 'pake-app' } = this.options;
|
|
1874
2190
|
const packageName = generateLinuxPackageName(name);
|
|
1875
|
-
const version =
|
|
2191
|
+
const version = this.options.appVersion;
|
|
1876
2192
|
const arch = this.buildArch === 'arm64' ? 'aarch64' : 'x86_64';
|
|
1877
2193
|
const debPath = path.resolve(`${name}.deb`);
|
|
1878
2194
|
const packagePath = path.resolve(`${name}-${version}-1-${arch}.pkg.tar.zst`);
|
|
1879
|
-
|
|
2195
|
+
await this.ensureArchPackagingTools();
|
|
2196
|
+
const workDir = await fsExtra.mkdtemp(path.join(os.tmpdir(), 'pake-arch-'));
|
|
1880
2197
|
const dataDir = path.join(workDir, 'data');
|
|
1881
2198
|
const controlDir = path.join(workDir, 'control');
|
|
1882
|
-
await this.ensureArchPackagingTools();
|
|
1883
|
-
await fsExtra.remove(workDir);
|
|
1884
|
-
await fsExtra.ensureDir(dataDir);
|
|
1885
|
-
await fsExtra.ensureDir(controlDir);
|
|
1886
2199
|
try {
|
|
1887
|
-
await
|
|
2200
|
+
await fsExtra.ensureDir(dataDir);
|
|
2201
|
+
await fsExtra.ensureDir(controlDir);
|
|
2202
|
+
await shellExec({
|
|
2203
|
+
executable: 'ar',
|
|
2204
|
+
args: ['x', debPath],
|
|
2205
|
+
cwd: controlDir,
|
|
2206
|
+
});
|
|
1888
2207
|
const dataArchive = (await fsExtra.readdir(controlDir)).find((file) => file.startsWith('data.tar'));
|
|
1889
2208
|
if (!dataArchive) {
|
|
1890
2209
|
throw new Error(`Could not find data.tar payload in ${debPath}`);
|
|
1891
2210
|
}
|
|
1892
|
-
await shellExec(
|
|
2211
|
+
await shellExec({
|
|
2212
|
+
executable: 'tar',
|
|
2213
|
+
args: ['-xf', path.join(controlDir, dataArchive), '-C', dataDir],
|
|
2214
|
+
});
|
|
1893
2215
|
// Drop the desktop entry auto-generated by the Tauri deb bundler;
|
|
1894
2216
|
// the payload already ships Pake's own com.pake.<name>.desktop.
|
|
1895
2217
|
await fsExtra.remove(path.join(dataDir, 'usr', 'share', 'applications', `${packageName}.desktop`));
|
|
@@ -1929,7 +2251,19 @@ post_remove() {
|
|
|
1929
2251
|
update-desktop-database -q usr/share/applications
|
|
1930
2252
|
}
|
|
1931
2253
|
`);
|
|
1932
|
-
await shellExec(
|
|
2254
|
+
await shellExec({
|
|
2255
|
+
executable: 'bsdtar',
|
|
2256
|
+
args: [
|
|
2257
|
+
'--zstd',
|
|
2258
|
+
'-cf',
|
|
2259
|
+
packagePath,
|
|
2260
|
+
'-C',
|
|
2261
|
+
dataDir,
|
|
2262
|
+
'.PKGINFO',
|
|
2263
|
+
'.INSTALL',
|
|
2264
|
+
'usr',
|
|
2265
|
+
],
|
|
2266
|
+
});
|
|
1933
2267
|
await this.recordArtifact(packagePath, 'zst');
|
|
1934
2268
|
logger.success('โ Build success!');
|
|
1935
2269
|
logger.success('โ App installer located in', packagePath);
|
|
@@ -1967,14 +2301,15 @@ post_remove() {
|
|
|
1967
2301
|
const buildTarget = this.buildArch === 'arm64'
|
|
1968
2302
|
? (this.getTauriTarget(this.buildArch, 'linux') ?? undefined)
|
|
1969
2303
|
: undefined;
|
|
1970
|
-
|
|
2304
|
+
const fullCommand = this.buildBaseCommand(packageManager, configPath, buildTarget);
|
|
1971
2305
|
// --no-bundle: build the executable only, skipping .deb/.rpm/.appimage
|
|
1972
2306
|
// packaging entirely (e.g. RPM-based distros where the bundler aborts).
|
|
1973
2307
|
if (this.options.bundle === false) {
|
|
1974
|
-
|
|
2308
|
+
fullCommand.args.push('--no-bundle');
|
|
2309
|
+
return fullCommand;
|
|
1975
2310
|
}
|
|
1976
2311
|
if (this.currentBuildType) {
|
|
1977
|
-
fullCommand
|
|
2312
|
+
fullCommand.args.push('--bundles', this.currentBuildType);
|
|
1978
2313
|
}
|
|
1979
2314
|
// Enable verbose output for AppImage builds when debugging or PAKE_VERBOSE is set.
|
|
1980
2315
|
// AppImage builds often fail with minimal error messages from linuxdeploy,
|
|
@@ -1983,7 +2318,7 @@ post_remove() {
|
|
|
1983
2318
|
(this.options.targets.includes('appimage') ||
|
|
1984
2319
|
this.options.debug ||
|
|
1985
2320
|
process.env.PAKE_VERBOSE)) {
|
|
1986
|
-
fullCommand
|
|
2321
|
+
fullCommand.args.push('--verbose');
|
|
1987
2322
|
}
|
|
1988
2323
|
return fullCommand;
|
|
1989
2324
|
}
|
|
@@ -2114,6 +2449,9 @@ function getIconSourcePriority(url, appName) {
|
|
|
2114
2449
|
: ['domain', 'dashboard'];
|
|
2115
2450
|
}
|
|
2116
2451
|
|
|
2452
|
+
async function loadSharp$1() {
|
|
2453
|
+
return (await import('sharp')).default;
|
|
2454
|
+
}
|
|
2117
2455
|
const ICO_HEADER_SIZE = 6;
|
|
2118
2456
|
const ICO_DIR_ENTRY_SIZE = 16;
|
|
2119
2457
|
const ICO_TYPE_ICON = 1;
|
|
@@ -2247,6 +2585,7 @@ async function pickLargestFrameAsPng(buffer, entries) {
|
|
|
2247
2585
|
// Fallback: let sharp render directly from the ICO buffer. sharp picks the
|
|
2248
2586
|
// largest embedded frame on its own.
|
|
2249
2587
|
try {
|
|
2588
|
+
const sharp = await loadSharp$1();
|
|
2250
2589
|
return await sharp(buffer).png().toBuffer();
|
|
2251
2590
|
}
|
|
2252
2591
|
catch {
|
|
@@ -2268,6 +2607,7 @@ async function ensureMultiResolutionIco(sourcePath, outputPath, preferredSize =
|
|
|
2268
2607
|
if (!sourcePng) {
|
|
2269
2608
|
return await writeIcoWithPreferredSize(sourcePath, outputPath, preferredSize);
|
|
2270
2609
|
}
|
|
2610
|
+
const sharp = await loadSharp$1();
|
|
2271
2611
|
const frames = await Promise.all(desiredSizes.map(async (size) => {
|
|
2272
2612
|
// Reuse an existing exact-size PNG frame when possible to keep any
|
|
2273
2613
|
// hand-tuned small icon (e.g. a 16x16 with deliberate pixel hinting).
|
|
@@ -2334,6 +2674,9 @@ function buildIcoFromPngBuffers(frames) {
|
|
|
2334
2674
|
return output;
|
|
2335
2675
|
}
|
|
2336
2676
|
|
|
2677
|
+
async function loadSharp() {
|
|
2678
|
+
return (await import('sharp')).default;
|
|
2679
|
+
}
|
|
2337
2680
|
const ICON_CONFIG = {
|
|
2338
2681
|
minFileSize: 100,
|
|
2339
2682
|
supportedFormats: [
|
|
@@ -2354,8 +2697,20 @@ const ICON_CONFIG = {
|
|
|
2354
2697
|
const PLATFORM_CONFIG = {
|
|
2355
2698
|
win: { format: '.ico', sizes: [...WIN_STANDARD_ICO_SIZES] },
|
|
2356
2699
|
linux: { format: '.png', size: 512 },
|
|
2357
|
-
macos: { format: '.icns'
|
|
2700
|
+
macos: { format: '.icns' },
|
|
2358
2701
|
};
|
|
2702
|
+
const MACOS_ICONSET_FILES = [
|
|
2703
|
+
['icon_16x16.png', 16],
|
|
2704
|
+
['icon_16x16@2x.png', 32],
|
|
2705
|
+
['icon_32x32.png', 32],
|
|
2706
|
+
['icon_32x32@2x.png', 64],
|
|
2707
|
+
['icon_128x128.png', 128],
|
|
2708
|
+
['icon_128x128@2x.png', 256],
|
|
2709
|
+
['icon_256x256.png', 256],
|
|
2710
|
+
['icon_256x256@2x.png', 512],
|
|
2711
|
+
['icon_512x512.png', 512],
|
|
2712
|
+
['icon_512x512@2x.png', 1024],
|
|
2713
|
+
];
|
|
2359
2714
|
const API_KEYS = {
|
|
2360
2715
|
logoDev: ['pk_JLLMUKGZRpaG5YclhXaTkg', 'pk_Ph745P8mQSeYFfW2Wk039A'],
|
|
2361
2716
|
brandfetch: ['1idqvJC0CeFSeyp3Yf7', '1idej-yhU_ThggIHFyG'],
|
|
@@ -2414,6 +2769,7 @@ async function preprocessIcon(inputPath) {
|
|
|
2414
2769
|
if (!shouldNormalize) {
|
|
2415
2770
|
return inputPath;
|
|
2416
2771
|
}
|
|
2772
|
+
const sharp = await loadSharp();
|
|
2417
2773
|
const { path: tempDir } = await dir();
|
|
2418
2774
|
const outputPath = path.join(tempDir, 'icon-normalized.png');
|
|
2419
2775
|
await sharp(inputPath).ensureAlpha().png().toFile(outputPath);
|
|
@@ -2431,6 +2787,7 @@ async function preprocessIcon(inputPath) {
|
|
|
2431
2787
|
*/
|
|
2432
2788
|
async function applyMacOSMask(inputPath) {
|
|
2433
2789
|
try {
|
|
2790
|
+
const sharp = await loadSharp();
|
|
2434
2791
|
const { path: tempDir } = await dir();
|
|
2435
2792
|
const outputPath = path.join(tempDir, 'icon-macos-rounded.png');
|
|
2436
2793
|
// 1. Create a 1024x1024 rounded rect mask
|
|
@@ -2474,6 +2831,32 @@ async function applyMacOSMask(inputPath) {
|
|
|
2474
2831
|
return inputPath;
|
|
2475
2832
|
}
|
|
2476
2833
|
}
|
|
2834
|
+
async function generateMacOSIcns(inputPath, outputDir, iconName) {
|
|
2835
|
+
const sharp = await loadSharp();
|
|
2836
|
+
const iconsetPath = path.join(outputDir, `${iconName}.iconset`);
|
|
2837
|
+
const outputPath = path.join(outputDir, `${iconName}${PLATFORM_CONFIG.macos.format}`);
|
|
2838
|
+
await fsExtra.ensureDir(iconsetPath);
|
|
2839
|
+
const source = sharp(inputPath);
|
|
2840
|
+
await Promise.all(MACOS_ICONSET_FILES.map(async ([fileName, size]) => {
|
|
2841
|
+
await source
|
|
2842
|
+
.clone()
|
|
2843
|
+
.resize(size, size, {
|
|
2844
|
+
fit: 'contain',
|
|
2845
|
+
background: ICON_CONFIG.transparentBackground,
|
|
2846
|
+
})
|
|
2847
|
+
.ensureAlpha()
|
|
2848
|
+
.png()
|
|
2849
|
+
.toFile(path.join(iconsetPath, fileName));
|
|
2850
|
+
}));
|
|
2851
|
+
await execa('/usr/bin/iconutil', [
|
|
2852
|
+
'-c',
|
|
2853
|
+
'icns',
|
|
2854
|
+
iconsetPath,
|
|
2855
|
+
'-o',
|
|
2856
|
+
outputPath,
|
|
2857
|
+
]);
|
|
2858
|
+
return outputPath;
|
|
2859
|
+
}
|
|
2477
2860
|
/**
|
|
2478
2861
|
* Converts icon to platform-specific format
|
|
2479
2862
|
*/
|
|
@@ -2488,6 +2871,7 @@ async function convertIconFormat(inputPath, appName) {
|
|
|
2488
2871
|
const iconName = getIconBaseName(appName);
|
|
2489
2872
|
// Generate platform-specific format
|
|
2490
2873
|
if (IS_WIN) {
|
|
2874
|
+
const sharp = await loadSharp();
|
|
2491
2875
|
const icoPath = path.join(platformOutputDir, `${iconName}_256${PLATFORM_CONFIG.win.format}`);
|
|
2492
2876
|
const sourceBuffer = await fsExtra.readFile(processedInputPath);
|
|
2493
2877
|
const frames = await Promise.all(PLATFORM_CONFIG.win.sizes.map(async (size) => {
|
|
@@ -2506,6 +2890,7 @@ async function convertIconFormat(inputPath, appName) {
|
|
|
2506
2890
|
return icoPath;
|
|
2507
2891
|
}
|
|
2508
2892
|
if (IS_LINUX) {
|
|
2893
|
+
const sharp = await loadSharp();
|
|
2509
2894
|
const outputPath = path.join(platformOutputDir, `${iconName}_${PLATFORM_CONFIG.linux.size}${PLATFORM_CONFIG.linux.format}`);
|
|
2510
2895
|
// Ensure we convert to proper PNG format with correct size
|
|
2511
2896
|
await sharp(processedInputPath)
|
|
@@ -2520,11 +2905,7 @@ async function convertIconFormat(inputPath, appName) {
|
|
|
2520
2905
|
}
|
|
2521
2906
|
// macOS
|
|
2522
2907
|
const macIconPath = await applyMacOSMask(processedInputPath);
|
|
2523
|
-
await
|
|
2524
|
-
report: false,
|
|
2525
|
-
icns: { name: iconName, sizes: PLATFORM_CONFIG.macos.sizes },
|
|
2526
|
-
});
|
|
2527
|
-
const outputPath = path.join(platformOutputDir, `${iconName}${PLATFORM_CONFIG.macos.format}`);
|
|
2908
|
+
const outputPath = await generateMacOSIcns(macIconPath, platformOutputDir, iconName);
|
|
2528
2909
|
return (await fsExtra.pathExists(outputPath)) ? outputPath : null;
|
|
2529
2910
|
}
|
|
2530
2911
|
catch (error) {
|
|
@@ -2539,6 +2920,7 @@ async function isLinuxBundleIconReady(iconPath) {
|
|
|
2539
2920
|
return false;
|
|
2540
2921
|
}
|
|
2541
2922
|
try {
|
|
2923
|
+
const sharp = await loadSharp();
|
|
2542
2924
|
const { width, height } = await sharp(iconPath).metadata();
|
|
2543
2925
|
return (width === PLATFORM_CONFIG.linux.size &&
|
|
2544
2926
|
height === PLATFORM_CONFIG.linux.size);
|
|
@@ -2784,7 +3166,6 @@ async function downloadIcon(iconUrl, showSpinner = true, customTimeout) {
|
|
|
2784
3166
|
const response = await fetch(iconUrl, {
|
|
2785
3167
|
signal: controller.signal,
|
|
2786
3168
|
});
|
|
2787
|
-
clearTimeout(timeoutId);
|
|
2788
3169
|
if (!response.ok) {
|
|
2789
3170
|
if (response.status === 404 && !showSpinner) {
|
|
2790
3171
|
return null;
|
|
@@ -2801,7 +3182,6 @@ async function downloadIcon(iconUrl, showSpinner = true, customTimeout) {
|
|
|
2801
3182
|
return await saveIconFile(arrayBuffer, extension);
|
|
2802
3183
|
}
|
|
2803
3184
|
catch (error) {
|
|
2804
|
-
clearTimeout(timeoutId);
|
|
2805
3185
|
if (showSpinner) {
|
|
2806
3186
|
if (error instanceof Error && error.name === 'AbortError') {
|
|
2807
3187
|
logger.error('Icon download timed out!');
|
|
@@ -2812,6 +3192,9 @@ async function downloadIcon(iconUrl, showSpinner = true, customTimeout) {
|
|
|
2812
3192
|
}
|
|
2813
3193
|
return null;
|
|
2814
3194
|
}
|
|
3195
|
+
finally {
|
|
3196
|
+
clearTimeout(timeoutId);
|
|
3197
|
+
}
|
|
2815
3198
|
}
|
|
2816
3199
|
/**
|
|
2817
3200
|
* Saves icon file to temporary location
|
|
@@ -2987,6 +3370,7 @@ const DEFAULT_PAKE_OPTIONS = {
|
|
|
2987
3370
|
useLocalFile: false,
|
|
2988
3371
|
systemTrayIcon: '',
|
|
2989
3372
|
proxyUrl: '',
|
|
3373
|
+
basicAuth: false,
|
|
2990
3374
|
debug: false,
|
|
2991
3375
|
json: false,
|
|
2992
3376
|
inject: [],
|
|
@@ -3089,6 +3473,9 @@ ${green('|_| \\__,_|_|\\_\\___| can turn any webpage into a desktop app with
|
|
|
3089
3473
|
.option('--debug', 'Debug build and more output', DEFAULT_PAKE_OPTIONS.debug)
|
|
3090
3474
|
.option('--json', 'Machine-readable output: logs to stderr, one JSON result on stdout', DEFAULT_PAKE_OPTIONS.json)
|
|
3091
3475
|
.option('--config <path>', 'Load options from a JSON config file (fields mirror CLI options, see schema/pake.schema.json)')
|
|
3476
|
+
.addOption(new Option('--basic-auth', 'Prompt for HTTP Basic credentials at runtime (macOS only)')
|
|
3477
|
+
.default(DEFAULT_PAKE_OPTIONS.basicAuth)
|
|
3478
|
+
.hideHelp())
|
|
3092
3479
|
.addOption(new Option('--proxy-url <url>', 'Proxy URL for all network requests (http://, https://, socks5://)')
|
|
3093
3480
|
.default(DEFAULT_PAKE_OPTIONS.proxyUrl)
|
|
3094
3481
|
.hideHelp())
|
|
@@ -3386,6 +3773,8 @@ program.action(async (urlArg, options) => {
|
|
|
3386
3773
|
let phase = 'input';
|
|
3387
3774
|
let appName = null;
|
|
3388
3775
|
let url = urlArg;
|
|
3776
|
+
let leaveWorkspace;
|
|
3777
|
+
let endCancellation;
|
|
3389
3778
|
try {
|
|
3390
3779
|
// Heal a dist_bak stranded by an earlier crashed local-input run before
|
|
3391
3780
|
// building, or this build would embed that run's staged files.
|
|
@@ -3432,13 +3821,22 @@ program.action(async (urlArg, options) => {
|
|
|
3432
3821
|
if (options.debug) {
|
|
3433
3822
|
log.setLevel('debug');
|
|
3434
3823
|
}
|
|
3824
|
+
endCancellation = beginBuildCancellation();
|
|
3825
|
+
phase = 'prepare';
|
|
3826
|
+
leaveWorkspace = await enterBuildWorkspace();
|
|
3827
|
+
phase = 'input';
|
|
3435
3828
|
const appOptions = await handleOptions(options, url);
|
|
3829
|
+
throwIfBuildCancelled();
|
|
3436
3830
|
appName = appOptions.name ?? null;
|
|
3437
3831
|
const builder = BuilderProvider.create(appOptions);
|
|
3438
3832
|
phase = 'prepare';
|
|
3439
3833
|
await builder.prepare();
|
|
3834
|
+
throwIfBuildCancelled();
|
|
3440
3835
|
phase = 'build';
|
|
3441
3836
|
await builder.build(url);
|
|
3837
|
+
throwIfBuildCancelled();
|
|
3838
|
+
await leaveWorkspace();
|
|
3839
|
+
leaveWorkspace = undefined;
|
|
3442
3840
|
if (jsonMode) {
|
|
3443
3841
|
printJsonResult({
|
|
3444
3842
|
ok: true,
|
|
@@ -3457,6 +3855,10 @@ program.action(async (urlArg, options) => {
|
|
|
3457
3855
|
if (isCommanderExit(error) && error.exitCode === 0) {
|
|
3458
3856
|
return;
|
|
3459
3857
|
}
|
|
3858
|
+
if (leaveWorkspace) {
|
|
3859
|
+
await leaveWorkspace();
|
|
3860
|
+
leaveWorkspace = undefined;
|
|
3861
|
+
}
|
|
3460
3862
|
const classified = classifyError(error, phase);
|
|
3461
3863
|
if (jsonMode) {
|
|
3462
3864
|
printJsonResult({
|
|
@@ -3489,10 +3891,14 @@ program.action(async (urlArg, options) => {
|
|
|
3489
3891
|
process.exitCode = ERROR_EXIT_CODES[classified.code];
|
|
3490
3892
|
}
|
|
3491
3893
|
finally {
|
|
3492
|
-
//
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3894
|
+
// Failed builds discard their private inputs without touching the package.
|
|
3895
|
+
try {
|
|
3896
|
+
if (leaveWorkspace)
|
|
3897
|
+
await leaveWorkspace();
|
|
3898
|
+
}
|
|
3899
|
+
finally {
|
|
3900
|
+
endCancellation?.();
|
|
3901
|
+
}
|
|
3496
3902
|
}
|
|
3497
3903
|
});
|
|
3498
3904
|
program.parseAsync().catch((error) => {
|