🎨 CLI is more user-friendly.

This commit is contained in:
Tw93
2023-06-23 16:51:18 +08:00
parent 6a4bb69631
commit 0431bc7c12
10 changed files with 402 additions and 376 deletions

8
bin/README.md vendored
View File

@@ -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
View File

@@ -101,14 +101,6 @@ pake [url] [options]
--transparent
```
#### [resize]
设置应用窗口是否可以调整大小,默认为 `true`(可调整)。使用以下命令可以禁止调整窗口大小。
```shell
--resizable
```
#### [fullscreen]
设置应用程序是否在启动时自动全屏,默认为 `false`。使用以下命令可以设置应用程序启动时自动全屏。

View File

@@ -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()}`
);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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
View File

@@ -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
View File

@@ -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
View File

@@ -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();