@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"Exclude": ["Cargo\\.lock$"]
|
"Exclude": ["Cargo\\.lock$", "dist"]
|
||||||
}
|
}
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -9,11 +9,13 @@ lerna-debug.log*
|
|||||||
|
|
||||||
node_modules
|
node_modules
|
||||||
dist-ssr
|
dist-ssr
|
||||||
|
dist/cli.js
|
||||||
*.local
|
*.local
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode/*
|
.vscode/*
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
|
!.vscode/settings.json
|
||||||
.idea
|
.idea
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.suo
|
*.suo
|
||||||
|
|||||||
4
.vscode/settings.json
vendored
Normal file
4
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"cSpell.words": ["loglevel", "Pake", "tauri"],
|
||||||
|
"typescript.preferences.importModuleSpecifierEnding": "js"
|
||||||
|
}
|
||||||
20
README.md
20
README.md
@@ -85,6 +85,26 @@
|
|||||||
- Windows 下不能安装到 C:\Program File,会直接闪退。建议安装到其他非管理员权限目录,比如 D:\Program Files (x86) 。
|
- Windows 下不能安装到 C:\Program File,会直接闪退。建议安装到其他非管理员权限目录,比如 D:\Program Files (x86) 。
|
||||||
- ~~Linux 下暂时不能存 cookie,即应用关闭后数据清空,账号自动推出(已修复)~~。
|
- ~~Linux 下暂时不能存 cookie,即应用关闭后数据清空,账号自动推出(已修复)~~。
|
||||||
|
|
||||||
|
## 使用命令行打包
|
||||||
|
|
||||||
|
Pake 提供了命令行工具,可以更快捷方便地打包。(目前仅支持 MacOS)
|
||||||
|
|
||||||
|
### 安装
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g pake-cli
|
||||||
|
```
|
||||||
|
|
||||||
|
如果安装失败提示没有权限,请使用 `sudo` 运行。
|
||||||
|
|
||||||
|
### 用法
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pake [options] url
|
||||||
|
```
|
||||||
|
|
||||||
|
更多用法可查看[文档](./bin//README.md)。
|
||||||
|
|
||||||
## 开发
|
## 开发
|
||||||
|
|
||||||
开始前参考 [Tauri](https://tauri.app/v1/guides/getting-started/prerequisites#setting-up-macos) 快速配置好环境。
|
开始前参考 [Tauri](https://tauri.app/v1/guides/getting-started/prerequisites#setting-up-macos) 快速配置好环境。
|
||||||
|
|||||||
72
bin/README.md
Normal file
72
bin/README.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
## 安装
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g pake-cli
|
||||||
|
```
|
||||||
|
|
||||||
|
如果安装失败提示没有权限,请使用 `sudo` 运行。
|
||||||
|
|
||||||
|
## 用法
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pake [options] url
|
||||||
|
```
|
||||||
|
打包完成后的应用程序默认为当前工作目录。
|
||||||
|
|
||||||
|
Note: 打包需要用 `Rust` 环境,如果没有 `Rust`,会提示确认安装。如遇安装失败或超时,可[自行安装](https://www.rust-lang.org/tools/install)。
|
||||||
|
|
||||||
|
Note: 目前仅支持 MacOs,后续会支持其他平台。
|
||||||
|
|
||||||
|
|
||||||
|
### url
|
||||||
|
url 为你需要打包的网页链接🔗,必须提供。
|
||||||
|
|
||||||
|
### [options]
|
||||||
|
|
||||||
|
提供了一些特定的选项,打包时可以传递对应参数达到定制化的效果。
|
||||||
|
|
||||||
|
#### [name]
|
||||||
|
应用名称,如输入时未指定,会提示你输入。
|
||||||
|
```shell
|
||||||
|
--name <value>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### [icon]
|
||||||
|
应用icon,支持本地/远程文件,默认为 Pake 自带图标。
|
||||||
|
- MacOS下必须为 `.icns`
|
||||||
|
```shell
|
||||||
|
--icon <path>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### [height]
|
||||||
|
打包后的应用窗口高度,默认 `800px`。
|
||||||
|
```
|
||||||
|
--height <number>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### [width]
|
||||||
|
打包后的应用窗口宽度,默认 `1280px`。
|
||||||
|
```
|
||||||
|
--width <number>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### [transparent]
|
||||||
|
是否开启沉浸式头部,默认为 `false` 不开启。
|
||||||
|
```
|
||||||
|
--transparent
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### [resize]
|
||||||
|
是否可以拖动大小,默认为 `true` 可拖动。
|
||||||
|
```
|
||||||
|
--no-resizable
|
||||||
|
```
|
||||||
|
|
||||||
|
#### [fullscreen]
|
||||||
|
打开应用后是否开启全屏,默认为 `false`。
|
||||||
|
```
|
||||||
|
--fullscreen <value>
|
||||||
|
```
|
||||||
12
bin/builders/BuilderFactory.ts
Normal file
12
bin/builders/BuilderFactory.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { IS_MAC } from '@/utils/platform.js';
|
||||||
|
import { IBuilder } from './base.js';
|
||||||
|
import MacBuilder from './MacBuilder.js';
|
||||||
|
|
||||||
|
export default class BuilderFactory {
|
||||||
|
static create(): IBuilder {
|
||||||
|
if (IS_MAC) {
|
||||||
|
return new MacBuilder();
|
||||||
|
}
|
||||||
|
throw new Error('The current system does not support');
|
||||||
|
}
|
||||||
|
}
|
||||||
0
bin/builders/LinuxBuilder.ts
Normal file
0
bin/builders/LinuxBuilder.ts
Normal file
65
bin/builders/MacBuilder.ts
Normal file
65
bin/builders/MacBuilder.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import fs from 'fs/promises';
|
||||||
|
import path from 'path';
|
||||||
|
import prompts from 'prompts';
|
||||||
|
import { checkRustInstalled, installRust } from '@/helpers/rust.js';
|
||||||
|
import { PakeAppOptions } from '@/types.js';
|
||||||
|
import { IBuilder } from './base.js';
|
||||||
|
import { shellExec } from '@/utils/shell.js';
|
||||||
|
import tauriConf from '../../src-tauri/tauri.conf.json';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import log from 'loglevel';
|
||||||
|
|
||||||
|
export default class MacBuilder implements IBuilder {
|
||||||
|
async prepare() {
|
||||||
|
if (checkRustInstalled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await prompts({
|
||||||
|
type: 'confirm',
|
||||||
|
message: 'We detected that you have not installed Rust. Install it now?',
|
||||||
|
name: 'value',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.value) {
|
||||||
|
// TODO 国内有可能会超时
|
||||||
|
await installRust();
|
||||||
|
} else {
|
||||||
|
log.error('Error: Pake need Rust to package your webapp!!!');
|
||||||
|
process.exit(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async build(url: string, options: PakeAppOptions) {
|
||||||
|
log.debug('PakeAppOptions', options);
|
||||||
|
|
||||||
|
const { width, height, fullscreen, transparent, resizable, identifier, name } = options;
|
||||||
|
|
||||||
|
const tauriConfWindowOptions = {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
fullscreen,
|
||||||
|
transparent,
|
||||||
|
resizable,
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO 下面这块逻辑还可以再拆 目前比较简单
|
||||||
|
Object.assign(tauriConf.tauri.windows[0], { url, ...tauriConfWindowOptions });
|
||||||
|
tauriConf.package.productName = name;
|
||||||
|
tauriConf.tauri.bundle.identifier = identifier;
|
||||||
|
tauriConf.tauri.bundle.icon = [options.icon];
|
||||||
|
|
||||||
|
const npmDirectory = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||||
|
const configJsonPath = path.join(npmDirectory, 'src-tauri/tauri.conf.json');
|
||||||
|
await fs.writeFile(configJsonPath, Buffer.from(JSON.stringify(tauriConf), 'utf-8'));
|
||||||
|
|
||||||
|
const code = await shellExec(`cd ${npmDirectory} && npm run build`);
|
||||||
|
const dmgName = `${name}_${'0.2.0'}_universal.dmg`;
|
||||||
|
const appPath = this.getBuildedAppPath(npmDirectory, dmgName);
|
||||||
|
await fs.copyFile(appPath, path.resolve(`${name}_universal.dmg`));
|
||||||
|
}
|
||||||
|
|
||||||
|
getBuildedAppPath(npmDirectory: string, dmgName: string) {
|
||||||
|
return path.join(npmDirectory, 'src-tauri/target/universal-apple-darwin/release/bundle/dmg', dmgName);
|
||||||
|
}
|
||||||
|
}
|
||||||
0
bin/builders/WinBulider.ts
Normal file
0
bin/builders/WinBulider.ts
Normal file
16
bin/builders/base.ts
Normal file
16
bin/builders/base.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { PakeAppOptions } from '@/types.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder接口
|
||||||
|
* 不同平台打包过程需要实现 prepare 和 build 方法
|
||||||
|
*/
|
||||||
|
export interface IBuilder {
|
||||||
|
/** 前置检查 */
|
||||||
|
prepare(): Promise<void>;
|
||||||
|
/**
|
||||||
|
* 开始打包
|
||||||
|
* @param url 打包url
|
||||||
|
* @param options 配置参数
|
||||||
|
*/
|
||||||
|
build(url: string, options: PakeAppOptions): Promise<void>;
|
||||||
|
}
|
||||||
11
bin/builders/common.ts
Normal file
11
bin/builders/common.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import prompts from 'prompts';
|
||||||
|
|
||||||
|
export async function promptText(message: string, initial?: string) {
|
||||||
|
const response = await prompts({
|
||||||
|
type: 'text',
|
||||||
|
name: 'content',
|
||||||
|
message,
|
||||||
|
initial,
|
||||||
|
});
|
||||||
|
return response.content;
|
||||||
|
}
|
||||||
36
bin/cli.ts
Normal file
36
bin/cli.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { program } from 'commander';
|
||||||
|
import { DEFAULT_PAKE_OPTIONS } from './defaults.js';
|
||||||
|
import { PakeCliOptions } from './types.js';
|
||||||
|
import { validateNumberInput, validateUrlInput } from './utils/validate.js';
|
||||||
|
import handleInputOptions from './options/index.js';
|
||||||
|
import BuilderFactory from './builders/BuilderFactory.js';
|
||||||
|
import log from 'loglevel';
|
||||||
|
|
||||||
|
program.version('0.0.1').description('A cli application can package a web page to desktop application');
|
||||||
|
|
||||||
|
program
|
||||||
|
.showHelpAfterError()
|
||||||
|
.argument('<url>', 'the web url you want to package', validateUrlInput)
|
||||||
|
.option('--name <string>', 'application name')
|
||||||
|
.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('--no-resizable', 'whether the window can be resizable', DEFAULT_PAKE_OPTIONS.resizable)
|
||||||
|
.option('--fullscreen', 'makes the packaged app start in full screen', DEFAULT_PAKE_OPTIONS.fullscreen)
|
||||||
|
.option('--transparent', 'transparent title bar', DEFAULT_PAKE_OPTIONS.transparent)
|
||||||
|
.option('--debug', 'debug', DEFAULT_PAKE_OPTIONS.transparent)
|
||||||
|
.action(async (url: string, options: PakeCliOptions) => {
|
||||||
|
log.setDefaultLevel('info')
|
||||||
|
if (options.debug) {
|
||||||
|
log.setLevel('debug');
|
||||||
|
}
|
||||||
|
|
||||||
|
const builder = BuilderFactory.create();
|
||||||
|
await builder.prepare();
|
||||||
|
|
||||||
|
const appOptions = await handleInputOptions(options, url);
|
||||||
|
|
||||||
|
builder.build(url, appOptions);
|
||||||
|
});
|
||||||
|
|
||||||
|
program.parse();
|
||||||
13
bin/defaults.ts
Normal file
13
bin/defaults.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { PakeCliOptions } from './types.js';
|
||||||
|
|
||||||
|
export const DEFAULT_PAKE_OPTIONS: PakeCliOptions = {
|
||||||
|
icon: '',
|
||||||
|
height: 800,
|
||||||
|
width: 1280,
|
||||||
|
fullscreen: false,
|
||||||
|
resizable: true,
|
||||||
|
transparent: false,
|
||||||
|
debug: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DEFAULT_APP_NAME = 'Pake';
|
||||||
21
bin/helpers/rust.ts
Normal file
21
bin/helpers/rust.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import ora from 'ora';
|
||||||
|
import shelljs from 'shelljs';
|
||||||
|
import { shellExec } from '../utils/shell.js';
|
||||||
|
|
||||||
|
const InstallRustScript = "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y";
|
||||||
|
export async function installRust() {
|
||||||
|
const spinner = ora('Downloading Rust').start();
|
||||||
|
try {
|
||||||
|
await shellExec(InstallRustScript);
|
||||||
|
spinner.succeed();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('install rust return code', error.message);
|
||||||
|
spinner.fail();
|
||||||
|
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkRustInstalled() {
|
||||||
|
return shelljs.exec('rustc --version', { silent: true }).code === 0;
|
||||||
|
}
|
||||||
8
bin/helpers/tauriConfig.ts
Normal file
8
bin/helpers/tauriConfig.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import crypto from 'crypto';
|
||||||
|
|
||||||
|
export function getIdentifier(name: string, url: string) {
|
||||||
|
const hash = crypto.createHash('md5');
|
||||||
|
hash.update(url);
|
||||||
|
const postFixHash = hash.digest('hex').substring(0, 6);
|
||||||
|
return `pake-${postFixHash}`;
|
||||||
|
}
|
||||||
91
bin/options/icon.ts
Normal file
91
bin/options/icon.ts
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
import { fileTypeFromBuffer } from 'file-type';
|
||||||
|
import { PakeAppOptions } from '../types.js';
|
||||||
|
import { dir } from 'tmp-promise';
|
||||||
|
import path from 'path';
|
||||||
|
import fs from 'fs/promises';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import log from 'loglevel';
|
||||||
|
|
||||||
|
export async function handleIcon(options: PakeAppOptions, url: string) {
|
||||||
|
if (options.icon) {
|
||||||
|
if (options.icon.startsWith('http')) {
|
||||||
|
return downloadIcon(options.icon);
|
||||||
|
} else {
|
||||||
|
return path.resolve(options.icon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!options.icon) {
|
||||||
|
return inferIcon(options.name, url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function inferIcon(name: string, url: string) {
|
||||||
|
log.info('You have not provided an app icon, use the default icon(can use --icon option to assign an icon)')
|
||||||
|
const npmDirectory = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||||
|
return path.join(npmDirectory, 'pake-default.icns');
|
||||||
|
}
|
||||||
|
|
||||||
|
// export async function getIconFromPageUrl(url: string) {
|
||||||
|
// const icon = await pageIcon(url);
|
||||||
|
// console.log(icon);
|
||||||
|
// if (icon.ext === '.ico') {
|
||||||
|
// const a = await ICO.parse(icon.data);
|
||||||
|
// icon.data = Buffer.from(a[0].buffer);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const iconDir = (await dir()).path;
|
||||||
|
// const iconPath = path.join(iconDir, `/icon.icns`);
|
||||||
|
|
||||||
|
// const out = png2icons.createICNS(icon.data, png2icons.BILINEAR, 0);
|
||||||
|
|
||||||
|
// await fs.writeFile(iconPath, out);
|
||||||
|
// return iconPath;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export async function getIconFromMacosIcons(name: string) {
|
||||||
|
// const data = {
|
||||||
|
// query: name,
|
||||||
|
// filters: 'approved:true',
|
||||||
|
// hitsPerPage: 10,
|
||||||
|
// page: 1,
|
||||||
|
// };
|
||||||
|
// const res = await axios.post('https://p1txh7zfb3-2.algolianet.com/1/indexes/macOSicons/query?x-algolia-agent=Algolia%20for%20JavaScript%20(4.13.1)%3B%20Browser', data, {
|
||||||
|
// headers: {
|
||||||
|
// 'x-algolia-api-key': '0ba04276e457028f3e11e38696eab32c',
|
||||||
|
// 'x-algolia-application-id': 'P1TXH7ZFB3',
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// if (!res.data.hits.length) {
|
||||||
|
// return '';
|
||||||
|
// } else {
|
||||||
|
// return downloadIcon(res.data.hits[0].icnsUrl);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
export async function downloadIcon(iconUrl: string) {
|
||||||
|
let iconResponse;
|
||||||
|
try {
|
||||||
|
iconResponse = await axios.get(iconUrl, {
|
||||||
|
responseType: 'arraybuffer',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response && error.response.status === 404) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const iconData = await iconResponse.data;
|
||||||
|
if (!iconData) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const fileDetails = await fileTypeFromBuffer(iconData);
|
||||||
|
if (!fileDetails) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { path } = await dir();
|
||||||
|
const iconPath = `${path}/icon.${fileDetails.ext}`;
|
||||||
|
await fs.writeFile(iconPath, iconData);
|
||||||
|
return iconPath;
|
||||||
|
}
|
||||||
22
bin/options/index.ts
Normal file
22
bin/options/index.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { promptText } from '@/builders/common.js';
|
||||||
|
import { getDomain } from '@/utils/url.js';
|
||||||
|
import { getIdentifier } from '../helpers/tauriConfig.js';
|
||||||
|
import { PakeAppOptions, PakeCliOptions } from '../types.js';
|
||||||
|
import { handleIcon } from './icon.js';
|
||||||
|
|
||||||
|
export default async function handleOptions(options: PakeCliOptions, url: string): Promise<PakeAppOptions> {
|
||||||
|
const appOptions: PakeAppOptions = {
|
||||||
|
...options,
|
||||||
|
identifier: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!appOptions.name) {
|
||||||
|
appOptions.name = await promptText('please input your application name', getDomain(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
appOptions.identifier = getIdentifier(appOptions.name, url);
|
||||||
|
|
||||||
|
appOptions.icon = await handleIcon(appOptions, url);
|
||||||
|
|
||||||
|
return appOptions;
|
||||||
|
}
|
||||||
29
bin/types.ts
Normal file
29
bin/types.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
export interface PakeCliOptions {
|
||||||
|
/** 应用名称 */
|
||||||
|
name?: string;
|
||||||
|
|
||||||
|
/** 应用icon */
|
||||||
|
icon: string;
|
||||||
|
|
||||||
|
/** 应用窗口宽度,默认 1280px */
|
||||||
|
width: number;
|
||||||
|
|
||||||
|
/** 应用窗口高度,默认 800px */
|
||||||
|
height: number;
|
||||||
|
|
||||||
|
/** 是否可以拖动,默认true */
|
||||||
|
resizable: boolean;
|
||||||
|
|
||||||
|
/** 是否可以全屏,默认 false */
|
||||||
|
fullscreen: boolean;
|
||||||
|
|
||||||
|
/** 是否开启沉浸式头部,默认为 false 不开启 ƒ*/
|
||||||
|
transparent: boolean;
|
||||||
|
|
||||||
|
/** 调试模式,会输出更多日志 */
|
||||||
|
debug: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PakeAppOptions extends PakeCliOptions {
|
||||||
|
identifier: string;
|
||||||
|
}
|
||||||
5
bin/utils/platform.ts
Normal file
5
bin/utils/platform.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export const IS_MAC = process.platform === 'darwin';
|
||||||
|
|
||||||
|
export const IS_WIN = process.platform === 'win32';
|
||||||
|
|
||||||
|
export const IS_LINUX = process.platform === 'linux';
|
||||||
13
bin/utils/shell.ts
Normal file
13
bin/utils/shell.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import shelljs from "shelljs";
|
||||||
|
|
||||||
|
export function shellExec(command: string) {
|
||||||
|
return new Promise<number>((resolve, reject) => {
|
||||||
|
shelljs.exec(command, { async: true, silent: false }, (code) => {
|
||||||
|
if (code === 0) {
|
||||||
|
resolve(0);
|
||||||
|
} else {
|
||||||
|
reject(new Error(`${code}`));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
1489
bin/utils/tlds.ts
Normal file
1489
bin/utils/tlds.ts
Normal file
File diff suppressed because it is too large
Load Diff
47
bin/utils/url.ts
Normal file
47
bin/utils/url.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import url from 'url';
|
||||||
|
import isurl from 'is-url';
|
||||||
|
import tlds from './tlds.js';
|
||||||
|
|
||||||
|
export function getDomain(inputUrl: string) {
|
||||||
|
const parsed = url.parse(inputUrl).host;
|
||||||
|
var parts = parsed.split('.');
|
||||||
|
if (parts[0] === 'www' && parts[1] !== 'com') {
|
||||||
|
parts.shift();
|
||||||
|
}
|
||||||
|
var ln = parts.length,
|
||||||
|
i = ln,
|
||||||
|
minLength = parts[parts.length - 1].length,
|
||||||
|
part;
|
||||||
|
|
||||||
|
// iterate backwards
|
||||||
|
while ((part = parts[--i])) {
|
||||||
|
// stop when we find a non-TLD part
|
||||||
|
if (
|
||||||
|
i === 0 || // 'asia.com' (last remaining must be the SLD)
|
||||||
|
i < ln - 2 || // TLDs only span 2 levels
|
||||||
|
part.length < minLength || // 'www.cn.com' (valid TLD as second-level domain)
|
||||||
|
tlds.indexOf(part) < 0 // officialy not a TLD
|
||||||
|
) {
|
||||||
|
return part;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendProtocol(inputUrl: string): string {
|
||||||
|
const parsed = url.parse(inputUrl);
|
||||||
|
if (!parsed.protocol) {
|
||||||
|
const urlWithProtocol = `https://${inputUrl}`;
|
||||||
|
return urlWithProtocol;
|
||||||
|
}
|
||||||
|
return inputUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeUrl(urlToNormalize: string): string {
|
||||||
|
const urlWithProtocol = appendProtocol(urlToNormalize);
|
||||||
|
|
||||||
|
if (isurl(urlWithProtocol)) {
|
||||||
|
return urlWithProtocol;
|
||||||
|
} else {
|
||||||
|
throw new Error(`Your url "${urlWithProtocol}" is invalid`);
|
||||||
|
}
|
||||||
|
}
|
||||||
18
bin/utils/validate.ts
Normal file
18
bin/utils/validate.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import * as Commander from 'commander';
|
||||||
|
import { normalizeUrl } from './url.js';
|
||||||
|
|
||||||
|
export function validateNumberInput(value: string) {
|
||||||
|
const parsedValue = Number(value);
|
||||||
|
if (isNaN(parsedValue)) {
|
||||||
|
throw new Commander.InvalidArgumentError('Not a number.');
|
||||||
|
}
|
||||||
|
return parsedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateUrlInput(url: string) {
|
||||||
|
try {
|
||||||
|
return normalizeUrl(url);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Commander.InvalidArgumentError(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
1918
dist/cli.js
vendored
Normal file
1918
dist/cli.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
42
package.json
42
package.json
@@ -1,6 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "pake",
|
"name": "pake-cli",
|
||||||
|
"version": "0.0.2",
|
||||||
"description": "用 Rust 来打包你的 App,底层使用 Tauri,当前支持微信读书、Flomo、Vercel",
|
"description": "用 Rust 来打包你的 App,底层使用 Tauri,当前支持微信读书、Flomo、Vercel",
|
||||||
|
"bin": {
|
||||||
|
"pake": "./cli.js"
|
||||||
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/tw93/pake.git"
|
"url": "https://github.com/tw93/pake.git"
|
||||||
@@ -16,13 +20,43 @@
|
|||||||
"build": "npm run tauri build -- --target universal-apple-darwin",
|
"build": "npm run tauri build -- --target universal-apple-darwin",
|
||||||
"build:windows": "npm run tauri build -- --target x86_64-pc-windows-msvc",
|
"build:windows": "npm run tauri build -- --target x86_64-pc-windows-msvc",
|
||||||
"build:linux": "npm run tauri build --release",
|
"build:linux": "npm run tauri build --release",
|
||||||
"tauri": "tauri"
|
"tauri": "tauri",
|
||||||
|
"cli": "rollup -c rollup.config.js --watch",
|
||||||
|
"cli:build": "rollup -c rollup.config.js",
|
||||||
|
"cli:publish": "npm run cli:build && npm publish"
|
||||||
},
|
},
|
||||||
|
"type": "module",
|
||||||
|
"exports": "./dist/pake.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "^1.2.0"
|
"@tauri-apps/api": "^1.2.0",
|
||||||
|
"@tauri-apps/cli": "^1.2.0",
|
||||||
|
"axios": "^1.1.3",
|
||||||
|
"commander": "^9.4.1",
|
||||||
|
"file-type": "^18.0.0",
|
||||||
|
"is-url": "^1.2.4",
|
||||||
|
"loglevel": "^1.8.1",
|
||||||
|
"ora": "^6.1.2",
|
||||||
|
"prompts": "^2.4.2",
|
||||||
|
"shelljs": "^0.8.5",
|
||||||
|
"tmp-promise": "^3.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "^1.2.0"
|
"@rollup/plugin-alias": "^4.0.2",
|
||||||
|
"@rollup/plugin-commonjs": "^23.0.2",
|
||||||
|
"@rollup/plugin-json": "^5.0.1",
|
||||||
|
"@rollup/plugin-typescript": "^9.0.2",
|
||||||
|
"@types/is-url": "^1.2.30",
|
||||||
|
"@types/page-icon": "^0.3.4",
|
||||||
|
"@types/prompts": "^2.4.1",
|
||||||
|
"@types/shelljs": "^0.8.11",
|
||||||
|
"@types/tmp": "^0.2.3",
|
||||||
|
"app-root-path": "^3.1.0",
|
||||||
|
"concurrently": "^7.5.0",
|
||||||
|
"rollup": "^3.3.0",
|
||||||
|
"rollup-plugin-typescript2": "^0.34.1",
|
||||||
|
"tsc-alias": "^1.7.1",
|
||||||
|
"tslib": "^2.4.1",
|
||||||
|
"typescript": "^4.8.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
pake-default.icns
Normal file
BIN
pake-default.icns
Normal file
Binary file not shown.
24
rollup.config.js
Normal file
24
rollup.config.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import appRootPath from 'app-root-path';
|
||||||
|
import typescript from '@rollup/plugin-typescript';
|
||||||
|
import alias from '@rollup/plugin-alias';
|
||||||
|
import commonjs from '@rollup/plugin-commonjs';
|
||||||
|
import json from '@rollup/plugin-json';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
input: 'bin/cli.ts',
|
||||||
|
output: {
|
||||||
|
file: 'dist/cli.js',
|
||||||
|
format: 'es'
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
json(),
|
||||||
|
typescript({
|
||||||
|
sourceMap: false,
|
||||||
|
}),
|
||||||
|
commonjs(),
|
||||||
|
alias({
|
||||||
|
entries: [{ find: '@', replacement: path.join(appRootPath.path, 'bin') }],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
};
|
||||||
BIN
src-tauri/icons/icon.icns
Normal file
BIN
src-tauri/icons/icon.icns
Normal file
Binary file not shown.
17
tsconfig.json
Normal file
17
tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "Node16",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"target": "es6",
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"moduleResolution": "Node16",
|
||||||
|
"sourceMap": true,
|
||||||
|
"outDir": "dist",
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["bin/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["bin/**/*"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user