🎨 CLI is more user-friendly.
This commit is contained in:
8
bin/README.md
vendored
8
bin/README.md
vendored
@@ -107,14 +107,6 @@ Determine whether the application launches in full screen. Default is `false`. U
|
||||
--fullscreen
|
||||
```
|
||||
|
||||
#### [resize]
|
||||
|
||||
Determine whether the window is resizable. Default is `true`. Use the following command to disable window resizing.
|
||||
|
||||
```shell
|
||||
--resizable
|
||||
```
|
||||
|
||||
#### [multi-arch]
|
||||
|
||||
Package the application to support both Intel and M1 chips, exclusively for macOS. Default is `false`.
|
||||
|
||||
8
bin/README_CN.md
vendored
8
bin/README_CN.md
vendored
@@ -101,14 +101,6 @@ pake [url] [options]
|
||||
--transparent
|
||||
```
|
||||
|
||||
#### [resize]
|
||||
|
||||
设置应用窗口是否可以调整大小,默认为 `true`(可调整)。使用以下命令可以禁止调整窗口大小。
|
||||
|
||||
```shell
|
||||
--resizable
|
||||
```
|
||||
|
||||
#### [fullscreen]
|
||||
|
||||
设置应用程序是否在启动时自动全屏,默认为 `false`。使用以下命令可以设置应用程序启动时自动全屏。
|
||||
|
||||
64
bin/builders/BaseBuilder.ts
vendored
64
bin/builders/BaseBuilder.ts
vendored
@@ -2,20 +2,25 @@ import path from 'path';
|
||||
import fsExtra from "fs-extra";
|
||||
import prompts from 'prompts';
|
||||
|
||||
import logger from '@/options/logger';
|
||||
import { PakeAppOptions } from '@/types';
|
||||
import { checkRustInstalled, installRust } from '@/helpers/rust';
|
||||
import { mergeConfig } from "@/helpers/merge";
|
||||
import tauriConfig from '@/helpers/tauriConfig';
|
||||
import { npmDirectory } from '@/utils/dir';
|
||||
import { getSpinner } from "@/utils/info";
|
||||
import { shellExec } from '@/utils/shell';
|
||||
import { isChinaDomain } from '@/utils/ip';
|
||||
import { getSpinner } from "@/utils/info";
|
||||
import { npmDirectory } from '@/utils/dir';
|
||||
import { IS_MAC } from "@/utils/platform";
|
||||
import { checkRustInstalled, installRust } from '@/helpers/rust';
|
||||
import { PakeAppOptions } from '@/types';
|
||||
import logger from '@/options/logger';
|
||||
|
||||
export default abstract class BaseBuilder {
|
||||
abstract build(url: string, options: PakeAppOptions): Promise<void>;
|
||||
protected options: PakeAppOptions;
|
||||
|
||||
protected constructor(options: PakeAppOptions) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
async prepare() {
|
||||
// Windows and Linux need to install necessary build tools.
|
||||
if (!IS_MAC) {
|
||||
logger.info('The first use requires installing system dependencies.');
|
||||
logger.info('See more in https://tauri.app/v1/guides/getting-started/prerequisites#installing.');
|
||||
@@ -52,9 +57,50 @@ export default abstract class BaseBuilder {
|
||||
spinner.succeed('Package installed.');
|
||||
}
|
||||
|
||||
protected async runBuildCommand(command: string = "npm run build") {
|
||||
async buildAndCopy(url: string) {
|
||||
const { name } = this.options;
|
||||
await mergeConfig(url, this.options, tauriConfig);
|
||||
await this.runBuildCommand();
|
||||
|
||||
const fileName = this.getFileName();
|
||||
const appPath = this.getBuildAppPath(npmDirectory, fileName);
|
||||
const distPath = path.resolve(`${name}.${this.getExtension()}`);
|
||||
await fsExtra.copy(appPath, distPath);
|
||||
await fsExtra.remove(appPath);
|
||||
logger.success('✔ Build success!');
|
||||
logger.success('✔ App installer located in', distPath);
|
||||
}
|
||||
|
||||
abstract build(url: string): Promise<void>;
|
||||
|
||||
abstract getFileName(): string;
|
||||
|
||||
abstract getExtension(): string;
|
||||
|
||||
protected getArch() {
|
||||
return process.arch === "x64" ? "amd64" : process.arch;
|
||||
}
|
||||
|
||||
protected getBuildCommand(): string {
|
||||
return "npm run build";
|
||||
}
|
||||
|
||||
protected runBuildCommand() {
|
||||
const spinner = getSpinner('Building app...');
|
||||
setTimeout(() => spinner.stop(), 3000);
|
||||
await shellExec(`cd "${npmDirectory}" && ${command}`);
|
||||
return shellExec(`cd ${npmDirectory} && ${this.getBuildCommand()}`);
|
||||
}
|
||||
|
||||
protected getBasePath(): string {
|
||||
return 'src-tauri/target/release/bundle/';
|
||||
}
|
||||
|
||||
protected getBuildAppPath(npmDirectory: string, fileName: string): string {
|
||||
return path.join(
|
||||
npmDirectory,
|
||||
this.getBasePath(),
|
||||
this.getExtension().toLowerCase(),
|
||||
`${fileName}.${this.getExtension()}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
7
bin/builders/BuilderProvider.ts
vendored
7
bin/builders/BuilderProvider.ts
vendored
@@ -2,21 +2,22 @@ import BaseBuilder from './BaseBuilder';
|
||||
import MacBuilder from './MacBuilder';
|
||||
import WinBuilder from './WinBuilder';
|
||||
import LinuxBuilder from './LinuxBuilder';
|
||||
import { PakeAppOptions } from '@/types';
|
||||
|
||||
const { platform } = process;
|
||||
|
||||
const buildersMap: Record<string, new () => BaseBuilder> = {
|
||||
const buildersMap: Record<string, new (options: PakeAppOptions) => BaseBuilder> = {
|
||||
darwin: MacBuilder,
|
||||
win32: WinBuilder,
|
||||
linux: LinuxBuilder,
|
||||
};
|
||||
|
||||
export default class BuilderProvider {
|
||||
static create(): BaseBuilder {
|
||||
static create(options: PakeAppOptions): BaseBuilder {
|
||||
const Builder = buildersMap[platform];
|
||||
if (!Builder) {
|
||||
throw new Error('The current system is not supported!');
|
||||
}
|
||||
return new Builder();
|
||||
return new Builder(options);
|
||||
}
|
||||
}
|
||||
|
||||
59
bin/builders/LinuxBuilder.ts
vendored
59
bin/builders/LinuxBuilder.ts
vendored
@@ -1,48 +1,31 @@
|
||||
import path from 'path';
|
||||
import fsExtra from "fs-extra";
|
||||
|
||||
import BaseBuilder from './BaseBuilder';
|
||||
import logger from '@/options/logger';
|
||||
import tauriConfig from '@/helpers/tauriConfig';
|
||||
import { npmDirectory } from '@/utils/dir';
|
||||
import { mergeConfig } from "@/helpers/merge";
|
||||
import { PakeAppOptions } from '@/types';
|
||||
import tauriConfig from '@/helpers/tauriConfig';
|
||||
|
||||
export default class LinuxBuilder extends BaseBuilder {
|
||||
async build(url: string, options: PakeAppOptions) {
|
||||
const { name } = options;
|
||||
await mergeConfig(url, options, tauriConfig);
|
||||
await this.runBuildCommand();
|
||||
constructor(options: PakeAppOptions) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
const arch = process.arch === "x64" ? "amd64" : process.arch;
|
||||
|
||||
if (options.targets === "deb" || options.targets === "all") {
|
||||
const debName = `${name}_${tauriConfig.package.version}_${arch}.deb`;
|
||||
const appPath = this.getBuildAppPath(npmDirectory, "deb", debName);
|
||||
const distPath = path.resolve(`${name}.deb`);
|
||||
await fsExtra.copy(appPath, distPath);
|
||||
await fsExtra.remove(appPath);
|
||||
logger.success('✔ Build Deb success!');
|
||||
logger.success('✔ Deb app installer located in', distPath);
|
||||
}
|
||||
|
||||
if (options.targets === "appimage" || options.targets === "all") {
|
||||
const appImageName = `${name}_${tauriConfig.package.version}_${arch}.AppImage`;
|
||||
const appImagePath = this.getBuildAppPath(npmDirectory, "appimage", appImageName);
|
||||
const distAppPath = path.resolve(`${name}.AppImage`);
|
||||
await fsExtra.copy(appImagePath, distAppPath);
|
||||
await fsExtra.remove(appImagePath);
|
||||
logger.success('✔ Build AppImage success!');
|
||||
logger.success('✔ AppImage installer located in', distAppPath);
|
||||
async build(url: string) {
|
||||
const targetTypes = ['deb', 'appimage'];
|
||||
for (const type of targetTypes) {
|
||||
if (this.options.targets === type || this.options.targets === "all") {
|
||||
await this.buildAndCopy(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getBuildAppPath(npmDirectory: string, packageType: string, packageName: string) {
|
||||
return path.join(
|
||||
npmDirectory,
|
||||
'src-tauri/target/release/bundle/',
|
||||
packageType,
|
||||
packageName
|
||||
);
|
||||
getFileName(): string {
|
||||
const { name } = this.options;
|
||||
const arch = this.getArch();
|
||||
return `${name}_${tauriConfig.package.version}_${arch}`;
|
||||
}
|
||||
|
||||
getExtension(): string {
|
||||
if (this.options.targets === 'appimage') {
|
||||
return 'AppImage';
|
||||
}
|
||||
return this.options.targets;
|
||||
}
|
||||
}
|
||||
|
||||
58
bin/builders/MacBuilder.ts
vendored
58
bin/builders/MacBuilder.ts
vendored
@@ -1,36 +1,38 @@
|
||||
import path from 'path';
|
||||
import fsExtra from "fs-extra";
|
||||
|
||||
import BaseBuilder from './BaseBuilder';
|
||||
import logger from '@/options/logger';
|
||||
import tauriConfig from '@/helpers/tauriConfig';
|
||||
import { npmDirectory } from '@/utils/dir';
|
||||
import { mergeConfig } from "@/helpers/merge";
|
||||
import { PakeAppOptions } from '@/types';
|
||||
import BaseBuilder from './BaseBuilder';
|
||||
|
||||
export default class MacBuilder extends BaseBuilder {
|
||||
async build(url: string, options: PakeAppOptions) {
|
||||
const { name } = options;
|
||||
await mergeConfig(url, options, tauriConfig);
|
||||
let dmgName: string;
|
||||
if (options.multiArch) {
|
||||
await this.runBuildCommand('npm run build:mac');
|
||||
dmgName = `${name}_${tauriConfig.package.version}_universal.dmg`;
|
||||
} else {
|
||||
await this.runBuildCommand();
|
||||
let arch = process.arch === "arm64" ? "aarch64" : process.arch;
|
||||
dmgName = `${name}_${tauriConfig.package.version}_${arch}.dmg`;
|
||||
}
|
||||
const appPath = this.getBuildAppPath(npmDirectory, dmgName, options.multiArch);
|
||||
const distPath = path.resolve(`${name}.dmg`);
|
||||
await fsExtra.copy(appPath, distPath);
|
||||
await fsExtra.remove(appPath);
|
||||
logger.success('✔ Build success!');
|
||||
logger.success('✔ App installer located in', distPath);
|
||||
constructor(options: PakeAppOptions) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
getBuildAppPath(npmDirectory: string, dmgName: string, multiArch: boolean) {
|
||||
const dmgPath = multiArch ? 'src-tauri/target/universal-apple-darwin/release/bundle/dmg' : 'src-tauri/target/release/bundle/dmg';
|
||||
return path.join(npmDirectory, dmgPath, dmgName);
|
||||
async build(url: string) {
|
||||
await this.buildAndCopy(url);
|
||||
}
|
||||
|
||||
getFileName(): string {
|
||||
const { name } = this.options;
|
||||
let arch: string;
|
||||
if (this.options.multiArch) {
|
||||
arch = 'universal';
|
||||
} else {
|
||||
arch = process.arch === "arm64" ? "aarch64" : process.arch;
|
||||
}
|
||||
return `${name}_${tauriConfig.package.version}_${arch}`;
|
||||
}
|
||||
|
||||
getExtension(): string {
|
||||
return "dmg";
|
||||
}
|
||||
|
||||
protected getBuildCommand(): string {
|
||||
return this.options.multiArch ? 'npm run build:mac' : super.getBuildCommand();
|
||||
}
|
||||
|
||||
protected getBasePath(): string {
|
||||
return this.options.multiArch
|
||||
? 'src-tauri/target/universal-apple-darwin/release/bundle'
|
||||
: super.getBasePath();
|
||||
}
|
||||
}
|
||||
|
||||
43
bin/builders/WinBuilder.ts
vendored
43
bin/builders/WinBuilder.ts
vendored
@@ -1,35 +1,24 @@
|
||||
import path from 'path';
|
||||
import fsExtra from 'fs-extra';
|
||||
|
||||
import BaseBuilder from './BaseBuilder';
|
||||
import logger from '@/options/logger';
|
||||
import tauriConfig from '@/helpers/tauriConfig';
|
||||
import { npmDirectory } from '@/utils/dir';
|
||||
import { mergeConfig } from '@/helpers/merge';
|
||||
import { PakeAppOptions } from '@/types';
|
||||
import tauriConfig from '@/helpers/tauriConfig';
|
||||
|
||||
export default class WinBuilder extends BaseBuilder {
|
||||
async build(url: string, options: PakeAppOptions) {
|
||||
const { name } = options;
|
||||
await mergeConfig(url, options, tauriConfig);
|
||||
await this.runBuildCommand();
|
||||
|
||||
const language = tauriConfig.tauri.bundle.windows.wix.language[0];
|
||||
const arch = process.arch;
|
||||
const msiName = `${name}_${tauriConfig.package.version}_${arch}_${language}.msi`;
|
||||
const appPath = this.getBuildAppPath(npmDirectory, msiName);
|
||||
const distPath = path.resolve(`${name}.msi`);
|
||||
await fsExtra.copy(appPath, distPath);
|
||||
await fsExtra.remove(appPath);
|
||||
logger.success('✔ Build success!');
|
||||
logger.success('✔ App installer located in', distPath);
|
||||
constructor(options: PakeAppOptions) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
getBuildAppPath(npmDirectory: string, msiName: string) {
|
||||
return path.join(
|
||||
npmDirectory,
|
||||
'src-tauri/target/release/bundle/msi',
|
||||
msiName
|
||||
);
|
||||
async build(url: string) {
|
||||
await this.buildAndCopy(url);
|
||||
}
|
||||
|
||||
getFileName(): string {
|
||||
const { name } = this.options;
|
||||
const arch = this.getArch();
|
||||
const language = tauriConfig.tauri.bundle.windows.wix.language[0];
|
||||
return `${name}_${tauriConfig.package.version}_${arch}_${language}`;
|
||||
}
|
||||
|
||||
getExtension(): string {
|
||||
return "msi";
|
||||
}
|
||||
}
|
||||
|
||||
5
bin/cli.ts
vendored
5
bin/cli.ts
vendored
@@ -21,7 +21,6 @@ program
|
||||
.option('--icon <string>', 'Application icon', DEFAULT.icon)
|
||||
.option('--height <number>', 'Window height', validateNumberInput, DEFAULT.height)
|
||||
.option('--width <number>', 'Window width', validateNumberInput, DEFAULT.width)
|
||||
.option('--resizable', 'Whether the window can be resizable', DEFAULT.resizable)
|
||||
.option('--fullscreen', 'Start the packaged app in full screen', DEFAULT.fullscreen)
|
||||
.option('--transparent', 'Transparent title bar', DEFAULT.transparent)
|
||||
.option('--user-agent <string>', 'Custom user agent', DEFAULT.userAgent)
|
||||
@@ -55,9 +54,9 @@ program
|
||||
const appOptions = await handleInputOptions(options, url);
|
||||
log.debug('PakeAppOptions', appOptions);
|
||||
|
||||
const builder = BuilderProvider.create();
|
||||
const builder = BuilderProvider.create(appOptions);
|
||||
await builder.prepare();
|
||||
await builder.build(url, appOptions);
|
||||
await builder.build(url);
|
||||
});
|
||||
|
||||
program.parse();
|
||||
|
||||
2
bin/utils/info.ts
vendored
2
bin/utils/info.ts
vendored
@@ -27,7 +27,7 @@ export function capitalizeFirstLetter(string: string) {
|
||||
|
||||
export function getSpinner(text: string) {
|
||||
const loadingType = {
|
||||
"interval": 100,
|
||||
"interval": 80,
|
||||
"frames": [
|
||||
"✶",
|
||||
"✵",
|
||||
|
||||
524
dist/cli.js
vendored
524
dist/cli.js
vendored
@@ -5,12 +5,12 @@ import path from 'path';
|
||||
import fsExtra from 'fs-extra';
|
||||
import prompts from 'prompts';
|
||||
import shelljs from 'shelljs';
|
||||
import crypto from 'crypto';
|
||||
import ora from 'ora';
|
||||
import { fileURLToPath } from 'url';
|
||||
import dns from 'dns';
|
||||
import http from 'http';
|
||||
import { promisify } from 'util';
|
||||
import crypto from 'crypto';
|
||||
import ora from 'ora';
|
||||
import updateNotifier from 'update-notifier';
|
||||
import axios from 'axios';
|
||||
import { dir } from 'tmp-promise';
|
||||
@@ -120,193 +120,6 @@ var packageJson = {
|
||||
devDependencies: devDependencies
|
||||
};
|
||||
|
||||
const logger = {
|
||||
info(...msg) {
|
||||
log.info(...msg.map((m) => chalk.blue.bold(m)));
|
||||
},
|
||||
debug(...msg) {
|
||||
log.debug(...msg);
|
||||
},
|
||||
error(...msg) {
|
||||
log.error(...msg.map((m) => chalk.red.bold(m)));
|
||||
},
|
||||
warn(...msg) {
|
||||
log.info(...msg.map((m) => chalk.yellow.bold(m)));
|
||||
},
|
||||
success(...msg) {
|
||||
log.info(...msg.map((m) => chalk.green.bold(m)));
|
||||
}
|
||||
};
|
||||
|
||||
// Convert the current module URL to a file path
|
||||
const currentModulePath = fileURLToPath(import.meta.url);
|
||||
// Resolve the parent directory of the current module
|
||||
const npmDirectory = path.join(path.dirname(currentModulePath), '..');
|
||||
|
||||
function shellExec(command) {
|
||||
return new Promise((resolve, reject) => {
|
||||
shelljs.exec(command, { async: true, silent: false, cwd: npmDirectory }, (code) => {
|
||||
if (code === 0) {
|
||||
resolve(0);
|
||||
}
|
||||
else {
|
||||
reject(new Error(`${code}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const resolve = promisify(dns.resolve);
|
||||
const ping = async (host) => {
|
||||
const lookup = promisify(dns.lookup);
|
||||
const ip = await lookup(host);
|
||||
const start = new Date();
|
||||
// Prevent timeouts from affecting user experience.
|
||||
const requestPromise = new Promise((resolve, reject) => {
|
||||
const req = http.get(`http://${ip.address}`, (res) => {
|
||||
const delay = new Date().getTime() - start.getTime();
|
||||
res.resume();
|
||||
resolve(delay);
|
||||
});
|
||||
req.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
const timeoutPromise = new Promise((_, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(new Error('Request timed out after 3 seconds'));
|
||||
}, 1000);
|
||||
});
|
||||
return Promise.race([requestPromise, timeoutPromise]);
|
||||
};
|
||||
async function isChinaDomain(domain) {
|
||||
try {
|
||||
const [ip] = await resolve(domain);
|
||||
return await isChinaIP(ip, domain);
|
||||
}
|
||||
catch (error) {
|
||||
logger.debug(`${domain} can't be parse!`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
async function isChinaIP(ip, domain) {
|
||||
try {
|
||||
const delay = await ping(ip);
|
||||
logger.debug(`${domain} latency is ${delay} ms`);
|
||||
return delay > 1000;
|
||||
}
|
||||
catch (error) {
|
||||
logger.debug(`ping ${domain} failed!`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Generates an identifier based on the given URL.
|
||||
function getIdentifier(url) {
|
||||
const postFixHash = crypto.createHash('md5')
|
||||
.update(url)
|
||||
.digest('hex')
|
||||
.substring(0, 6);
|
||||
return `pake-${postFixHash}`;
|
||||
}
|
||||
async function promptText(message, initial) {
|
||||
const response = await prompts({
|
||||
type: 'text',
|
||||
name: 'content',
|
||||
message,
|
||||
initial,
|
||||
});
|
||||
return response.content;
|
||||
}
|
||||
function capitalizeFirstLetter(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
function getSpinner(text) {
|
||||
const loadingType = {
|
||||
"interval": 100,
|
||||
"frames": [
|
||||
"✶",
|
||||
"✵",
|
||||
"✸",
|
||||
"✹",
|
||||
"✺",
|
||||
"✹",
|
||||
"✷",
|
||||
]
|
||||
};
|
||||
return ora({ text: `${text}\n`, spinner: loadingType }).start();
|
||||
}
|
||||
|
||||
const { platform: platform$2 } = process;
|
||||
const IS_MAC = platform$2 === 'darwin';
|
||||
const IS_WIN = platform$2 === 'win32';
|
||||
const IS_LINUX = platform$2 === 'linux';
|
||||
|
||||
async function installRust() {
|
||||
const isInChina = await isChinaDomain("sh.rustup.rs");
|
||||
const rustInstallScriptForMac = isInChina
|
||||
? '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'
|
||||
: "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y";
|
||||
const rustInstallScriptForWindows = 'winget install --id Rustlang.Rustup';
|
||||
const spinner = getSpinner('Downloading Rust...');
|
||||
try {
|
||||
await shellExec(IS_WIN ? rustInstallScriptForWindows : rustInstallScriptForMac);
|
||||
spinner.succeed('Rust installed successfully.');
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error installing Rust:', error.message);
|
||||
spinner.fail('Rust installation failed.');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
function checkRustInstalled() {
|
||||
return shelljs.exec('rustc --version', { silent: true }).code === 0;
|
||||
}
|
||||
|
||||
class BaseBuilder {
|
||||
async prepare() {
|
||||
// Windows and Linux need to install necessary build tools.
|
||||
if (!IS_MAC) {
|
||||
logger.info('The first use requires installing system dependencies.');
|
||||
logger.info('See more in https://tauri.app/v1/guides/getting-started/prerequisites#installing.');
|
||||
}
|
||||
if (!checkRustInstalled()) {
|
||||
const res = await prompts({
|
||||
type: 'confirm',
|
||||
message: 'Rust not detected. Install now?',
|
||||
name: 'value',
|
||||
});
|
||||
if (res.value) {
|
||||
await installRust();
|
||||
}
|
||||
else {
|
||||
logger.error('Error: Rust required to package your webapp!');
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
const isChina = await isChinaDomain("www.npmjs.com");
|
||||
const spinner = getSpinner('Installing package...');
|
||||
if (isChina) {
|
||||
logger.info("Located in China, using npm/rsProxy CN mirror.");
|
||||
const rustProjectDir = path.join(npmDirectory, 'src-tauri', ".cargo");
|
||||
await fsExtra.ensureDir(rustProjectDir);
|
||||
const projectCnConf = path.join(npmDirectory, "src-tauri", "rust_proxy.toml");
|
||||
const projectConf = path.join(rustProjectDir, "config");
|
||||
await fsExtra.copy(projectCnConf, projectConf);
|
||||
await shellExec(`cd "${npmDirectory}" && npm install --registry=https://registry.npmmirror.com`);
|
||||
}
|
||||
else {
|
||||
await shellExec(`cd "${npmDirectory}" && npm install`);
|
||||
}
|
||||
spinner.succeed('Package installed.');
|
||||
}
|
||||
async runBuildCommand(command = "npm run build") {
|
||||
const spinner = getSpinner('Building app...');
|
||||
setTimeout(() => spinner.stop(), 2000);
|
||||
await shellExec(`cd "${npmDirectory}" && ${command}`);
|
||||
}
|
||||
}
|
||||
|
||||
var windows = [
|
||||
{
|
||||
url: "https://weread.qq.com/",
|
||||
@@ -494,9 +307,9 @@ const platformConfigs = {
|
||||
darwin: MacConf,
|
||||
linux: LinuxConf
|
||||
};
|
||||
const { platform: platform$1 } = process;
|
||||
const { platform: platform$2 } = process;
|
||||
// @ts-ignore
|
||||
const platformConfig = platformConfigs[platform$1];
|
||||
const platformConfig = platformConfigs[platform$2];
|
||||
let tauriConfig = {
|
||||
tauri: {
|
||||
...CommonConf.tauri,
|
||||
@@ -507,6 +320,149 @@ let tauriConfig = {
|
||||
pake: pakeConf
|
||||
};
|
||||
|
||||
// Generates an identifier based on the given URL.
|
||||
function getIdentifier(url) {
|
||||
const postFixHash = crypto.createHash('md5')
|
||||
.update(url)
|
||||
.digest('hex')
|
||||
.substring(0, 6);
|
||||
return `pake-${postFixHash}`;
|
||||
}
|
||||
async function promptText(message, initial) {
|
||||
const response = await prompts({
|
||||
type: 'text',
|
||||
name: 'content',
|
||||
message,
|
||||
initial,
|
||||
});
|
||||
return response.content;
|
||||
}
|
||||
function capitalizeFirstLetter(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
function getSpinner(text) {
|
||||
const loadingType = {
|
||||
"interval": 80,
|
||||
"frames": [
|
||||
"✶",
|
||||
"✵",
|
||||
"✸",
|
||||
"✹",
|
||||
"✺",
|
||||
"✹",
|
||||
"✷",
|
||||
]
|
||||
};
|
||||
return ora({ text: `${text}\n`, spinner: loadingType }).start();
|
||||
}
|
||||
|
||||
const { platform: platform$1 } = process;
|
||||
const IS_MAC = platform$1 === 'darwin';
|
||||
const IS_WIN = platform$1 === 'win32';
|
||||
const IS_LINUX = platform$1 === 'linux';
|
||||
|
||||
// Convert the current module URL to a file path
|
||||
const currentModulePath = fileURLToPath(import.meta.url);
|
||||
// Resolve the parent directory of the current module
|
||||
const npmDirectory = path.join(path.dirname(currentModulePath), '..');
|
||||
|
||||
function shellExec(command) {
|
||||
return new Promise((resolve, reject) => {
|
||||
shelljs.exec(command, { async: true, silent: false, cwd: npmDirectory }, (code) => {
|
||||
if (code === 0) {
|
||||
resolve(0);
|
||||
}
|
||||
else {
|
||||
reject(new Error(`${code}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const logger = {
|
||||
info(...msg) {
|
||||
log.info(...msg.map((m) => chalk.blue.bold(m)));
|
||||
},
|
||||
debug(...msg) {
|
||||
log.debug(...msg);
|
||||
},
|
||||
error(...msg) {
|
||||
log.error(...msg.map((m) => chalk.red.bold(m)));
|
||||
},
|
||||
warn(...msg) {
|
||||
log.info(...msg.map((m) => chalk.yellow.bold(m)));
|
||||
},
|
||||
success(...msg) {
|
||||
log.info(...msg.map((m) => chalk.green.bold(m)));
|
||||
}
|
||||
};
|
||||
|
||||
const resolve = promisify(dns.resolve);
|
||||
const ping = async (host) => {
|
||||
const lookup = promisify(dns.lookup);
|
||||
const ip = await lookup(host);
|
||||
const start = new Date();
|
||||
// Prevent timeouts from affecting user experience.
|
||||
const requestPromise = new Promise((resolve, reject) => {
|
||||
const req = http.get(`http://${ip.address}`, (res) => {
|
||||
const delay = new Date().getTime() - start.getTime();
|
||||
res.resume();
|
||||
resolve(delay);
|
||||
});
|
||||
req.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
const timeoutPromise = new Promise((_, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(new Error('Request timed out after 3 seconds'));
|
||||
}, 1000);
|
||||
});
|
||||
return Promise.race([requestPromise, timeoutPromise]);
|
||||
};
|
||||
async function isChinaDomain(domain) {
|
||||
try {
|
||||
const [ip] = await resolve(domain);
|
||||
return await isChinaIP(ip, domain);
|
||||
}
|
||||
catch (error) {
|
||||
logger.debug(`${domain} can't be parse!`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
async function isChinaIP(ip, domain) {
|
||||
try {
|
||||
const delay = await ping(ip);
|
||||
logger.debug(`${domain} latency is ${delay} ms`);
|
||||
return delay > 1000;
|
||||
}
|
||||
catch (error) {
|
||||
logger.debug(`ping ${domain} failed!`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
async function installRust() {
|
||||
const isInChina = await isChinaDomain("sh.rustup.rs");
|
||||
const rustInstallScriptForMac = isInChina
|
||||
? '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'
|
||||
: "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y";
|
||||
const rustInstallScriptForWindows = 'winget install --id Rustlang.Rustup';
|
||||
const spinner = getSpinner('Downloading Rust...');
|
||||
try {
|
||||
await shellExec(IS_WIN ? rustInstallScriptForWindows : rustInstallScriptForMac);
|
||||
spinner.succeed('Rust installed successfully.');
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error installing Rust:', error.message);
|
||||
spinner.fail('Rust installation failed.');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
function checkRustInstalled() {
|
||||
return shelljs.exec('rustc --version', { silent: true }).code === 0;
|
||||
}
|
||||
|
||||
async function mergeConfig(url, options, tauriConf) {
|
||||
const { width, height, fullscreen, transparent, resizable, userAgent, showMenu, showSystemTray, systemTrayIcon, iterCopyFile, identifier, name, } = options;
|
||||
const { platform } = process;
|
||||
@@ -659,80 +615,147 @@ async function mergeConfig(url, options, tauriConf) {
|
||||
await fsExtra.writeJson(configJsonPath, tauriConf2, { spaces: 4 });
|
||||
}
|
||||
|
||||
class MacBuilder extends BaseBuilder {
|
||||
async build(url, options) {
|
||||
const { name } = options;
|
||||
await mergeConfig(url, options, tauriConfig);
|
||||
let dmgName;
|
||||
if (options.multiArch) {
|
||||
await this.runBuildCommand('npm run build:mac');
|
||||
dmgName = `${name}_${tauriConfig.package.version}_universal.dmg`;
|
||||
class BaseBuilder {
|
||||
constructor(options) {
|
||||
this.options = options;
|
||||
}
|
||||
async prepare() {
|
||||
if (!IS_MAC) {
|
||||
logger.info('The first use requires installing system dependencies.');
|
||||
logger.info('See more in https://tauri.app/v1/guides/getting-started/prerequisites#installing.');
|
||||
}
|
||||
if (!checkRustInstalled()) {
|
||||
const res = await prompts({
|
||||
type: 'confirm',
|
||||
message: 'Rust not detected. Install now?',
|
||||
name: 'value',
|
||||
});
|
||||
if (res.value) {
|
||||
await installRust();
|
||||
}
|
||||
else {
|
||||
logger.error('Error: Rust required to package your webapp!');
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
const isChina = await isChinaDomain("www.npmjs.com");
|
||||
const spinner = getSpinner('Installing package...');
|
||||
if (isChina) {
|
||||
logger.info("Located in China, using npm/rsProxy CN mirror.");
|
||||
const rustProjectDir = path.join(npmDirectory, 'src-tauri', ".cargo");
|
||||
await fsExtra.ensureDir(rustProjectDir);
|
||||
const projectCnConf = path.join(npmDirectory, "src-tauri", "rust_proxy.toml");
|
||||
const projectConf = path.join(rustProjectDir, "config");
|
||||
await fsExtra.copy(projectCnConf, projectConf);
|
||||
await shellExec(`cd "${npmDirectory}" && npm install --registry=https://registry.npmmirror.com`);
|
||||
}
|
||||
else {
|
||||
await this.runBuildCommand();
|
||||
let arch = process.arch === "arm64" ? "aarch64" : process.arch;
|
||||
dmgName = `${name}_${tauriConfig.package.version}_${arch}.dmg`;
|
||||
await shellExec(`cd "${npmDirectory}" && npm install`);
|
||||
}
|
||||
const appPath = this.getBuildAppPath(npmDirectory, dmgName, options.multiArch);
|
||||
const distPath = path.resolve(`${name}.dmg`);
|
||||
spinner.succeed('Package installed.');
|
||||
}
|
||||
async buildAndCopy(url) {
|
||||
const { name } = this.options;
|
||||
await mergeConfig(url, this.options, tauriConfig);
|
||||
await this.runBuildCommand();
|
||||
const fileName = this.getFileName();
|
||||
const appPath = this.getBuildAppPath(npmDirectory, fileName);
|
||||
const distPath = path.resolve(`${name}.${this.getExtension()}`);
|
||||
await fsExtra.copy(appPath, distPath);
|
||||
await fsExtra.remove(appPath);
|
||||
logger.success('✔ Build success!');
|
||||
logger.success('✔ App installer located in', distPath);
|
||||
}
|
||||
getBuildAppPath(npmDirectory, dmgName, multiArch) {
|
||||
const dmgPath = multiArch ? 'src-tauri/target/universal-apple-darwin/release/bundle/dmg' : 'src-tauri/target/release/bundle/dmg';
|
||||
return path.join(npmDirectory, dmgPath, dmgName);
|
||||
getArch() {
|
||||
return process.arch === "x64" ? "amd64" : process.arch;
|
||||
}
|
||||
getBuildCommand() {
|
||||
return "npm run build";
|
||||
}
|
||||
runBuildCommand() {
|
||||
const spinner = getSpinner('Building app...');
|
||||
setTimeout(() => spinner.stop(), 3000);
|
||||
return shellExec(`cd ${npmDirectory} && ${this.getBuildCommand()}`);
|
||||
}
|
||||
getBasePath() {
|
||||
return 'src-tauri/target/release/bundle/';
|
||||
}
|
||||
getBuildAppPath(npmDirectory, fileName) {
|
||||
return path.join(npmDirectory, this.getBasePath(), this.getExtension().toLowerCase(), `${fileName}.${this.getExtension()}`);
|
||||
}
|
||||
}
|
||||
|
||||
class MacBuilder extends BaseBuilder {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
}
|
||||
async build(url) {
|
||||
await this.buildAndCopy(url);
|
||||
}
|
||||
getFileName() {
|
||||
const { name } = this.options;
|
||||
let arch;
|
||||
if (this.options.multiArch) {
|
||||
arch = 'universal';
|
||||
}
|
||||
else {
|
||||
arch = process.arch === "arm64" ? "aarch64" : process.arch;
|
||||
}
|
||||
return `${name}_${tauriConfig.package.version}_${arch}`;
|
||||
}
|
||||
getExtension() {
|
||||
return "dmg";
|
||||
}
|
||||
getBuildCommand() {
|
||||
return this.options.multiArch ? 'npm run build:mac' : super.getBuildCommand();
|
||||
}
|
||||
getBasePath() {
|
||||
return this.options.multiArch
|
||||
? 'src-tauri/target/universal-apple-darwin/release/bundle'
|
||||
: super.getBasePath();
|
||||
}
|
||||
}
|
||||
|
||||
class WinBuilder extends BaseBuilder {
|
||||
async build(url, options) {
|
||||
const { name } = options;
|
||||
await mergeConfig(url, options, tauriConfig);
|
||||
await this.runBuildCommand();
|
||||
const language = tauriConfig.tauri.bundle.windows.wix.language[0];
|
||||
const arch = process.arch;
|
||||
const msiName = `${name}_${tauriConfig.package.version}_${arch}_${language}.msi`;
|
||||
const appPath = this.getBuildAppPath(npmDirectory, msiName);
|
||||
const distPath = path.resolve(`${name}.msi`);
|
||||
await fsExtra.copy(appPath, distPath);
|
||||
await fsExtra.remove(appPath);
|
||||
logger.success('✔ Build success!');
|
||||
logger.success('✔ App installer located in', distPath);
|
||||
constructor(options) {
|
||||
super(options);
|
||||
}
|
||||
getBuildAppPath(npmDirectory, msiName) {
|
||||
return path.join(npmDirectory, 'src-tauri/target/release/bundle/msi', msiName);
|
||||
async build(url) {
|
||||
await this.buildAndCopy(url);
|
||||
}
|
||||
getFileName() {
|
||||
const { name } = this.options;
|
||||
const arch = this.getArch();
|
||||
const language = tauriConfig.tauri.bundle.windows.wix.language[0];
|
||||
return `${name}_${tauriConfig.package.version}_${arch}_${language}`;
|
||||
}
|
||||
getExtension() {
|
||||
return "msi";
|
||||
}
|
||||
}
|
||||
|
||||
class LinuxBuilder extends BaseBuilder {
|
||||
async build(url, options) {
|
||||
const { name } = options;
|
||||
await mergeConfig(url, options, tauriConfig);
|
||||
await this.runBuildCommand();
|
||||
const arch = process.arch === "x64" ? "amd64" : process.arch;
|
||||
if (options.targets === "deb" || options.targets === "all") {
|
||||
const debName = `${name}_${tauriConfig.package.version}_${arch}.deb`;
|
||||
const appPath = this.getBuildAppPath(npmDirectory, "deb", debName);
|
||||
const distPath = path.resolve(`${name}.deb`);
|
||||
await fsExtra.copy(appPath, distPath);
|
||||
await fsExtra.remove(appPath);
|
||||
logger.success('✔ Build Deb success!');
|
||||
logger.success('✔ Deb app installer located in', distPath);
|
||||
}
|
||||
if (options.targets === "appimage" || options.targets === "all") {
|
||||
const appImageName = `${name}_${tauriConfig.package.version}_${arch}.AppImage`;
|
||||
const appImagePath = this.getBuildAppPath(npmDirectory, "appimage", appImageName);
|
||||
const distAppPath = path.resolve(`${name}.AppImage`);
|
||||
await fsExtra.copy(appImagePath, distAppPath);
|
||||
await fsExtra.remove(appImagePath);
|
||||
logger.success('✔ Build AppImage success!');
|
||||
logger.success('✔ AppImage installer located in', distAppPath);
|
||||
constructor(options) {
|
||||
super(options);
|
||||
}
|
||||
async build(url) {
|
||||
const targetTypes = ['deb', 'appimage'];
|
||||
for (const type of targetTypes) {
|
||||
if (this.options.targets === type || this.options.targets === "all") {
|
||||
await this.buildAndCopy(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
getBuildAppPath(npmDirectory, packageType, packageName) {
|
||||
return path.join(npmDirectory, 'src-tauri/target/release/bundle/', packageType, packageName);
|
||||
getFileName() {
|
||||
const { name } = this.options;
|
||||
const arch = this.getArch();
|
||||
return `${name}_${tauriConfig.package.version}_${arch}`;
|
||||
}
|
||||
getExtension() {
|
||||
if (this.options.targets === 'appimage') {
|
||||
return 'AppImage';
|
||||
}
|
||||
return this.options.targets;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -743,12 +766,12 @@ const buildersMap = {
|
||||
linux: LinuxBuilder,
|
||||
};
|
||||
class BuilderProvider {
|
||||
static create() {
|
||||
static create(options) {
|
||||
const Builder = buildersMap[platform];
|
||||
if (!Builder) {
|
||||
throw new Error('The current system is not supported!');
|
||||
}
|
||||
return new Builder();
|
||||
return new Builder(options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -929,7 +952,6 @@ program
|
||||
.option('--icon <string>', 'Application icon', DEFAULT_PAKE_OPTIONS.icon)
|
||||
.option('--height <number>', 'Window height', validateNumberInput, DEFAULT_PAKE_OPTIONS.height)
|
||||
.option('--width <number>', 'Window width', validateNumberInput, DEFAULT_PAKE_OPTIONS.width)
|
||||
.option('--resizable', 'Whether the window can be resizable', DEFAULT_PAKE_OPTIONS.resizable)
|
||||
.option('--fullscreen', 'Start the packaged app in full screen', DEFAULT_PAKE_OPTIONS.fullscreen)
|
||||
.option('--transparent', 'Transparent title bar', DEFAULT_PAKE_OPTIONS.transparent)
|
||||
.option('--user-agent <string>', 'Custom user agent', DEFAULT_PAKE_OPTIONS.userAgent)
|
||||
@@ -958,8 +980,8 @@ program
|
||||
}
|
||||
const appOptions = await handleOptions(options, url);
|
||||
log.debug('PakeAppOptions', appOptions);
|
||||
const builder = BuilderProvider.create();
|
||||
const builder = BuilderProvider.create(appOptions);
|
||||
await builder.prepare();
|
||||
await builder.build(url, appOptions);
|
||||
await builder.build(url);
|
||||
});
|
||||
program.parse();
|
||||
|
||||
Reference in New Issue
Block a user