diff --git a/.gitignore b/.gitignore index 66c28c6..e7aca0a 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ dist-ssr # Editor directories and files .vscode/* !.vscode/extensions.json +!.vscode/settings.json .idea .DS_Store *.suo diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..4b0fe8e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "cSpell.words": [ + "loglevel", + "Pake", + "tauri" + ], + "typescript.preferences.importModuleSpecifierEnding": "js" +} \ No newline at end of file diff --git a/README.md b/README.md index 8d90bae..74e676e 100644 --- a/README.md +++ b/README.md @@ -82,8 +82,28 @@ ## 注意点 -- Windows 下不能安装到 C:\Program File,会直接闪退。建议安装到其他目录,比如 D:\Program Files。 -- Linux 下暂时不能存 cookie,即应用关闭后数据清空,账号自动推出。 +- Windows 下不能安装到 C:\Program File,会直接闪退。建议安装到其他目录,比如 D:\Program Files。 +- Linux 下暂时不能存 cookie,即应用关闭后数据清空,账号自动推出。 + +## 使用命令行打包 + +Pake 提供了命令行工具,可以更快捷方便地打包。(目前仅支持 MacOS) + +### 安装 + +```bash +npm install -g pake +``` + +如果安装失败提示没有权限,请使用 `sudo` 运行。 + +### 用法 + +```bash +pake [options] url +``` + +更多用法可查看[文档](./bin//README.md)。 ## 开发 @@ -114,6 +134,8 @@ chmod +x ./script/build.sh && ./script/build.sh ## 打新包 +### 自己构建 + 1. 修改 `src-tauri` 目录下的 `tauri.conf.json` 中的 `url、productName、icon、identifier` 这 4 个字段,其中 icon 可以从 icons 目录选择一个,也可以去 [macOSicons](https://macosicons.com/#/) 下载符合产品名称的 2. 关于窗口属性设置,可以在 `tauri.conf.json` 修改 `windows` 属性对应的 `width/height`,是否全屏 `fullscreen`,是否可以调整大小 `resizable`,假如想适配 Mac 沉浸式头部,可以将 `transparent` 设置成 `true`,找到 Header 元素加一个 `padding-top` 样式即可,不想适配改成 `false` 也行 3. `npm run dev` 本地调试看看效果,此外可以使用 `npm run dev:debug` 进行容器调试 @@ -221,9 +243,9 @@ chmod +x ./script/build.sh && ./script/build.sh ## 支持 -- 我有两只猫,一只叫汤圆,一只叫可乐,假如觉得 Pake 让你生活更美好,可以给汤圆可乐 喂罐头 🥩🍤。 -- 如果你喜欢 Pake,可以在 Github Star,更欢迎 [推荐](https://twitter.com/intent/tweet?url=https://github.com/tw93/Pake&text=Pake%20%E4%B8%80%E4%B8%AA%E5%BE%88%E7%AE%80%E5%8D%95%E7%9A%84%E7%94%A8%20Rust%20%E6%89%93%E5%8C%85%E7%BD%91%E9%A1%B5%E7%94%9F%E6%88%90%20Mac%20App%20%E7%9A%84%E5%B7%A5%E5%85%B7%EF%BC%8C%E7%9B%B8%E6%AF%94%E4%BC%A0%E7%BB%9F%E7%9A%84%20Electron%20%E5%A5%97%E5%A3%B3%E6%89%93%E5%8C%85%EF%BC%8C%E5%A4%A7%E5%B0%8F%E8%A6%81%E5%B0%8F%E5%B0%86%E8%BF%91%2040%20%E5%80%8D%EF%BC%8C%E4%B8%80%E8%88%AC%202M%20%E5%B7%A6%E5%8F%B3%EF%BC%8C%E5%BA%95%E5%B1%82%E4%BD%BF%E7%94%A8Tauri%20%EF%BC%8C%E6%80%A7%E8%83%BD%E4%BD%93%E9%AA%8C%E8%BE%83%20JS%20%E6%A1%86%E6%9E%B6%E8%A6%81%E8%BD%BB%E5%BF%AB%E4%B8%8D%E5%B0%91%EF%BC%8C%E5%86%85%E5%AD%98%E5%B0%8F%E5%BE%88%E5%A4%9A%EF%BC%8C%E6%94%AF%E6%8C%81%E5%BE%AE%E4%BF%A1%E8%AF%BB%E4%B9%A6%E3%80%81Twitter%E3%80%81Youtube%E3%80%81RunCode%E3%80%81Flomo%E3%80%81%E8%AF%AD%E9%9B%80%E7%AD%89%EF%BC%8C%E5%8F%AF%E4%BB%A5%E5%BE%88%E6%96%B9%E4%BE%BF%E4%BA%8C%E6%AC%A1%E5%BC%80%E5%8F%91~) 给你志同道合的朋友使用。 -- 可以关注我的 [Twitter](https://twitter.com/HiTw93) 获取到最新的 Pake 更新消息,也欢迎加入 [Telegram](https://t.me/miaoyan) 聊天群。 +- 我有两只猫,一只叫汤圆,一只叫可乐,假如觉得 Pake 让你生活更美好,可以给汤圆可乐 喂罐头 🥩🍤。 +- 如果你喜欢 Pake,可以在 Github Star,更欢迎 [推荐](https://twitter.com/intent/tweet?url=https://github.com/tw93/Pake&text=Pake%20%E4%B8%80%E4%B8%AA%E5%BE%88%E7%AE%80%E5%8D%95%E7%9A%84%E7%94%A8%20Rust%20%E6%89%93%E5%8C%85%E7%BD%91%E9%A1%B5%E7%94%9F%E6%88%90%20Mac%20App%20%E7%9A%84%E5%B7%A5%E5%85%B7%EF%BC%8C%E7%9B%B8%E6%AF%94%E4%BC%A0%E7%BB%9F%E7%9A%84%20Electron%20%E5%A5%97%E5%A3%B3%E6%89%93%E5%8C%85%EF%BC%8C%E5%A4%A7%E5%B0%8F%E8%A6%81%E5%B0%8F%E5%B0%86%E8%BF%91%2040%20%E5%80%8D%EF%BC%8C%E4%B8%80%E8%88%AC%202M%20%E5%B7%A6%E5%8F%B3%EF%BC%8C%E5%BA%95%E5%B1%82%E4%BD%BF%E7%94%A8Tauri%20%EF%BC%8C%E6%80%A7%E8%83%BD%E4%BD%93%E9%AA%8C%E8%BE%83%20JS%20%E6%A1%86%E6%9E%B6%E8%A6%81%E8%BD%BB%E5%BF%AB%E4%B8%8D%E5%B0%91%EF%BC%8C%E5%86%85%E5%AD%98%E5%B0%8F%E5%BE%88%E5%A4%9A%EF%BC%8C%E6%94%AF%E6%8C%81%E5%BE%AE%E4%BF%A1%E8%AF%BB%E4%B9%A6%E3%80%81Twitter%E3%80%81Youtube%E3%80%81RunCode%E3%80%81Flomo%E3%80%81%E8%AF%AD%E9%9B%80%E7%AD%89%EF%BC%8C%E5%8F%AF%E4%BB%A5%E5%BE%88%E6%96%B9%E4%BE%BF%E4%BA%8C%E6%AC%A1%E5%BC%80%E5%8F%91~) 给你志同道合的朋友使用。 +- 可以关注我的 [Twitter](https://twitter.com/HiTw93) 获取到最新的 Pake 更新消息,也欢迎加入 [Telegram](https://t.me/miaoyan) 聊天群。 ## 最后 diff --git a/bin/README.md b/bin/README.md new file mode 100644 index 0000000..423f857 --- /dev/null +++ b/bin/README.md @@ -0,0 +1,72 @@ +## 安装 + +```bash +npm install -g pake +``` + +如果安装失败提示没有权限,请使用 `sudo` 运行。 + +## 用法 + +```bash +pake [options] url +``` +打包完成后的应用程序默认为当前工作目录。 + +Note: 打包需要用 `Rust` 环境,如果没有 `Rust`,会提示确认安装。如遇安装失败或超时,可[自行安装](https://www.rust-lang.org/tools/install)。 + +Note: 目前仅支持 MacOs,后续会支持其他平台。 + + +### url +url 为你需要打包的网页链接🔗,必须提供。 + +### [options] + +提供了一些特定的选项,打包时可以传递对应参数达到定制化的效果。 + +#### [name] +应用名称,如输入时未指定,会提示你输入。 +```shell +--name +``` + +#### [icon] +应用icon,支持本地/远程文件,默认为 Pake 自带图标。 +- MacOS下必须为 `.icns` +```shell +--icon +``` + +#### [height] +打包后的应用窗口高度,默认 `800px`。 +``` +--height +``` + + +#### [width] +打包后的应用窗口宽度,默认 `1280px`。 +``` +--width +``` + + +#### [transparent] +是否开启沉浸式头部,默认为 `false` 不开启。 +``` +--transparent +``` + + +#### [resize] +是否可以拖动大小,默认为 `true` 可拖动。 +``` +--no-resizable +``` + +#### [fullscreen] +打开应用后是否开启全屏,默认为 `false`。 +``` +--fullscreen +``` diff --git a/bin/builders/BuilderFactory.ts b/bin/builders/BuilderFactory.ts new file mode 100644 index 0000000..18ec650 --- /dev/null +++ b/bin/builders/BuilderFactory.ts @@ -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'); + } +} diff --git a/bin/builders/LinuxBuilder.ts b/bin/builders/LinuxBuilder.ts new file mode 100644 index 0000000..e69de29 diff --git a/bin/builders/MacBuilder.ts b/bin/builders/MacBuilder.ts new file mode 100644 index 0000000..55c6578 --- /dev/null +++ b/bin/builders/MacBuilder.ts @@ -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: 'Detect 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); + } +} diff --git a/bin/builders/WinBulider.ts b/bin/builders/WinBulider.ts new file mode 100644 index 0000000..e69de29 diff --git a/bin/builders/base.ts b/bin/builders/base.ts new file mode 100644 index 0000000..c92e239 --- /dev/null +++ b/bin/builders/base.ts @@ -0,0 +1,16 @@ +import { PakeAppOptions } from '@/types.js'; + +/** + * Builder接口 + * 不同平台打包过程需要实现 prepare 和 build 方法 + */ +export interface IBuilder { + /** 前置检查 */ + prepare(): Promise; + /** + * 开始打包 + * @param url 打包url + * @param options 配置参数 + */ + build(url: string, options: PakeAppOptions): Promise; +} diff --git a/bin/builders/common.ts b/bin/builders/common.ts new file mode 100644 index 0000000..6f881d6 --- /dev/null +++ b/bin/builders/common.ts @@ -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; +} diff --git a/bin/cli.ts b/bin/cli.ts new file mode 100644 index 0000000..fddffdb --- /dev/null +++ b/bin/cli.ts @@ -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('', 'the web url you want to package', validateUrlInput) + .option('--name ', 'application name') + .option('--icon ', 'application icon', DEFAULT_PAKE_OPTIONS.icon) + .option('--height ', 'window height', validateNumberInput, DEFAULT_PAKE_OPTIONS.height) + .option('--width ', '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(); diff --git a/bin/defaults.ts b/bin/defaults.ts new file mode 100644 index 0000000..5f808d2 --- /dev/null +++ b/bin/defaults.ts @@ -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'; diff --git a/bin/helpers/rust.ts b/bin/helpers/rust.ts new file mode 100644 index 0000000..af9811c --- /dev/null +++ b/bin/helpers/rust.ts @@ -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; +} diff --git a/bin/helpers/tauriConfig.ts b/bin/helpers/tauriConfig.ts new file mode 100644 index 0000000..fb44d61 --- /dev/null +++ b/bin/helpers/tauriConfig.ts @@ -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}`; +} diff --git a/bin/options/icon.ts b/bin/options/icon.ts new file mode 100644 index 0000000..7e1c5d8 --- /dev/null +++ b/bin/options/icon.ts @@ -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; +} diff --git a/bin/options/index.ts b/bin/options/index.ts new file mode 100644 index 0000000..efcf4f6 --- /dev/null +++ b/bin/options/index.ts @@ -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 { + 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; +} diff --git a/bin/types.ts b/bin/types.ts new file mode 100644 index 0000000..30ce729 --- /dev/null +++ b/bin/types.ts @@ -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; +} diff --git a/bin/utils/platform.ts b/bin/utils/platform.ts new file mode 100644 index 0000000..c08cd48 --- /dev/null +++ b/bin/utils/platform.ts @@ -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'; diff --git a/bin/utils/shell.ts b/bin/utils/shell.ts new file mode 100644 index 0000000..42b4015 --- /dev/null +++ b/bin/utils/shell.ts @@ -0,0 +1,13 @@ +import shelljs from 'shelljs'; + +export function shellExec(command: string) { + return new Promise((resolve, reject) => { + shelljs.exec(command, { async: true, silent: false}, (code) => { + if (code === 0) { + resolve(0); + } else { + reject(new Error(`${code}`)); + } + }); + }); +} diff --git a/bin/utils/tlds.ts b/bin/utils/tlds.ts new file mode 100644 index 0000000..5718aca --- /dev/null +++ b/bin/utils/tlds.ts @@ -0,0 +1,1489 @@ +const tlds = [ + 'aaa', + 'aarp', + 'abarth', + 'abb', + 'abbott', + 'abbvie', + 'abc', + 'able', + 'abogado', + 'abudhabi', + 'ac', + 'academy', + 'accenture', + 'accountant', + 'accountants', + 'aco', + 'actor', + 'ad', + 'adac', + 'ads', + 'adult', + 'ae', + 'aeg', + 'aero', + 'aetna', + 'af', + 'afl', + 'africa', + 'ag', + 'agakhan', + 'agency', + 'ai', + 'aig', + 'airbus', + 'airforce', + 'airtel', + 'akdn', + 'al', + 'alfaromeo', + 'alibaba', + 'alipay', + 'allfinanz', + 'allstate', + 'ally', + 'alsace', + 'alstom', + 'am', + 'amazon', + 'americanexpress', + 'americanfamily', + 'amex', + 'amfam', + 'amica', + 'amsterdam', + 'analytics', + 'android', + 'anquan', + 'anz', + 'ao', + 'aol', + 'apartments', + 'app', + 'apple', + 'aq', + 'aquarelle', + 'ar', + 'arab', + 'aramco', + 'archi', + 'army', + 'arpa', + 'art', + 'arte', + 'as', + 'asda', + 'asia', + 'associates', + 'at', + 'athleta', + 'attorney', + 'au', + 'auction', + 'audi', + 'audible', + 'audio', + 'auspost', + 'author', + 'auto', + 'autos', + 'avianca', + 'aw', + 'aws', + 'ax', + 'axa', + 'az', + 'azure', + 'ba', + 'baby', + 'baidu', + 'banamex', + 'bananarepublic', + 'band', + 'bank', + 'bar', + 'barcelona', + 'barclaycard', + 'barclays', + 'barefoot', + 'bargains', + 'baseball', + 'basketball', + 'bauhaus', + 'bayern', + 'bb', + 'bbc', + 'bbt', + 'bbva', + 'bcg', + 'bcn', + 'bd', + 'be', + 'beats', + 'beauty', + 'beer', + 'bentley', + 'berlin', + 'best', + 'bestbuy', + 'bet', + 'bf', + 'bg', + 'bh', + 'bharti', + 'bi', + 'bible', + 'bid', + 'bike', + 'bing', + 'bingo', + 'bio', + 'biz', + 'bj', + 'black', + 'blackfriday', + 'blockbuster', + 'blog', + 'bloomberg', + 'blue', + 'bm', + 'bms', + 'bmw', + 'bn', + 'bnpparibas', + 'bo', + 'boats', + 'boehringer', + 'bofa', + 'bom', + 'bond', + 'boo', + 'book', + 'booking', + 'bosch', + 'bostik', + 'boston', + 'bot', + 'boutique', + 'box', + 'br', + 'bradesco', + 'bridgestone', + 'broadway', + 'broker', + 'brother', + 'brussels', + 'bs', + 'bt', + 'build', + 'builders', + 'business', + 'buy', + 'buzz', + 'bv', + 'bw', + 'by', + 'bz', + 'bzh', + 'ca', + 'cab', + 'cafe', + 'cal', + 'call', + 'calvinklein', + 'cam', + 'camera', + 'camp', + 'canon', + 'capetown', + 'capital', + 'capitalone', + 'car', + 'caravan', + 'cards', + 'care', + 'career', + 'careers', + 'cars', + 'casa', + 'case', + 'cash', + 'casino', + 'cat', + 'catering', + 'catholic', + 'cba', + 'cbn', + 'cbre', + 'cbs', + 'cc', + 'cd', + 'center', + 'ceo', + 'cern', + 'cf', + 'cfa', + 'cfd', + 'cg', + 'ch', + 'chanel', + 'channel', + 'charity', + 'chase', + 'chat', + 'cheap', + 'chintai', + 'christmas', + 'chrome', + 'church', + 'ci', + 'cipriani', + 'circle', + 'cisco', + 'citadel', + 'citi', + 'citic', + 'city', + 'cityeats', + 'ck', + 'cl', + 'claims', + 'cleaning', + 'click', + 'clinic', + 'clinique', + 'clothing', + 'cloud', + 'club', + 'clubmed', + 'cm', + 'cn', + 'co', + 'coach', + 'codes', + 'coffee', + 'college', + 'cologne', + 'com', + 'comcast', + 'commbank', + 'community', + 'company', + 'compare', + 'computer', + 'comsec', + 'condos', + 'construction', + 'consulting', + 'contact', + 'contractors', + 'cooking', + 'cookingchannel', + 'cool', + 'coop', + 'corsica', + 'country', + 'coupon', + 'coupons', + 'courses', + 'cpa', + 'cr', + 'credit', + 'creditcard', + 'creditunion', + 'cricket', + 'crown', + 'crs', + 'cruise', + 'cruises', + 'cu', + 'cuisinella', + 'cv', + 'cw', + 'cx', + 'cy', + 'cymru', + 'cyou', + 'cz', + 'dabur', + 'dad', + 'dance', + 'data', + 'date', + 'dating', + 'datsun', + 'day', + 'dclk', + 'dds', + 'de', + 'deal', + 'dealer', + 'deals', + 'degree', + 'delivery', + 'dell', + 'deloitte', + 'delta', + 'democrat', + 'dental', + 'dentist', + 'desi', + 'design', + 'dev', + 'dhl', + 'diamonds', + 'diet', + 'digital', + 'direct', + 'directory', + 'discount', + 'discover', + 'dish', + 'diy', + 'dj', + 'dk', + 'dm', + 'dnp', + 'do', + 'docs', + 'doctor', + 'dog', + 'domains', + 'dot', + 'download', + 'drive', + 'dtv', + 'dubai', + 'dunlop', + 'dupont', + 'durban', + 'dvag', + 'dvr', + 'dz', + 'earth', + 'eat', + 'ec', + 'eco', + 'edeka', + 'edu', + 'education', + 'ee', + 'eg', + 'email', + 'emerck', + 'energy', + 'engineer', + 'engineering', + 'enterprises', + 'epson', + 'equipment', + 'er', + 'ericsson', + 'erni', + 'es', + 'esq', + 'estate', + 'et', + 'etisalat', + 'eu', + 'eurovision', + 'eus', + 'events', + 'exchange', + 'expert', + 'exposed', + 'express', + 'extraspace', + 'fage', + 'fail', + 'fairwinds', + 'faith', + 'family', + 'fan', + 'fans', + 'farm', + 'farmers', + 'fashion', + 'fast', + 'fedex', + 'feedback', + 'ferrari', + 'ferrero', + 'fi', + 'fiat', + 'fidelity', + 'fido', + 'film', + 'final', + 'finance', + 'financial', + 'fire', + 'firestone', + 'firmdale', + 'fish', + 'fishing', + 'fit', + 'fitness', + 'fj', + 'fk', + 'flickr', + 'flights', + 'flir', + 'florist', + 'flowers', + 'fly', + 'fm', + 'fo', + 'foo', + 'food', + 'foodnetwork', + 'football', + 'ford', + 'forex', + 'forsale', + 'forum', + 'foundation', + 'fox', + 'fr', + 'free', + 'fresenius', + 'frl', + 'frogans', + 'frontdoor', + 'frontier', + 'ftr', + 'fujitsu', + 'fun', + 'fund', + 'furniture', + 'futbol', + 'fyi', + 'ga', + 'gal', + 'gallery', + 'gallo', + 'gallup', + 'game', + 'games', + 'gap', + 'garden', + 'gay', + 'gb', + 'gbiz', + 'gd', + 'gdn', + 'ge', + 'gea', + 'gent', + 'genting', + 'george', + 'gf', + 'gg', + 'ggee', + 'gh', + 'gi', + 'gift', + 'gifts', + 'gives', + 'giving', + 'gl', + 'glass', + 'gle', + 'global', + 'globo', + 'gm', + 'gmail', + 'gmbh', + 'gmo', + 'gmx', + 'gn', + 'godaddy', + 'gold', + 'goldpoint', + 'golf', + 'goo', + 'goodyear', + 'goog', + 'google', + 'gop', + 'got', + 'gov', + 'gp', + 'gq', + 'gr', + 'grainger', + 'graphics', + 'gratis', + 'green', + 'gripe', + 'grocery', + 'group', + 'gs', + 'gt', + 'gu', + 'guardian', + 'gucci', + 'guge', + 'guide', + 'guitars', + 'guru', + 'gw', + 'gy', + 'hair', + 'hamburg', + 'hangout', + 'haus', + 'hbo', + 'hdfc', + 'hdfcbank', + 'health', + 'healthcare', + 'help', + 'helsinki', + 'here', + 'hermes', + 'hgtv', + 'hiphop', + 'hisamitsu', + 'hitachi', + 'hiv', + 'hk', + 'hkt', + 'hm', + 'hn', + 'hockey', + 'holdings', + 'holiday', + 'homedepot', + 'homegoods', + 'homes', + 'homesense', + 'honda', + 'horse', + 'hospital', + 'host', + 'hosting', + 'hot', + 'hoteles', + 'hotels', + 'hotmail', + 'house', + 'how', + 'hr', + 'hsbc', + 'ht', + 'hu', + 'hughes', + 'hyatt', + 'hyundai', + 'ibm', + 'icbc', + 'ice', + 'icu', + 'id', + 'ie', + 'ieee', + 'ifm', + 'ikano', + 'il', + 'im', + 'imamat', + 'imdb', + 'immo', + 'immobilien', + 'in', + 'inc', + 'industries', + 'infiniti', + 'info', + 'ing', + 'ink', + 'institute', + 'insurance', + 'insure', + 'int', + 'international', + 'intuit', + 'investments', + 'io', + 'ipiranga', + 'iq', + 'ir', + 'irish', + 'is', + 'ismaili', + 'ist', + 'istanbul', + 'it', + 'itau', + 'itv', + 'jaguar', + 'java', + 'jcb', + 'je', + 'jeep', + 'jetzt', + 'jewelry', + 'jio', + 'jll', + 'jm', + 'jmp', + 'jnj', + 'jo', + 'jobs', + 'joburg', + 'jot', + 'joy', + 'jp', + 'jpmorgan', + 'jprs', + 'juegos', + 'juniper', + 'kaufen', + 'kddi', + 'ke', + 'kerryhotels', + 'kerrylogistics', + 'kerryproperties', + 'kfh', + 'kg', + 'kh', + 'ki', + 'kia', + 'kids', + 'kim', + 'kinder', + 'kindle', + 'kitchen', + 'kiwi', + 'km', + 'kn', + 'koeln', + 'komatsu', + 'kosher', + 'kp', + 'kpmg', + 'kpn', + 'kr', + 'krd', + 'kred', + 'kuokgroup', + 'kw', + 'ky', + 'kyoto', + 'kz', + 'la', + 'lacaixa', + 'lamborghini', + 'lamer', + 'lancaster', + 'lancia', + 'land', + 'landrover', + 'lanxess', + 'lasalle', + 'lat', + 'latino', + 'latrobe', + 'law', + 'lawyer', + 'lb', + 'lc', + 'lds', + 'lease', + 'leclerc', + 'lefrak', + 'legal', + 'lego', + 'lexus', + 'lgbt', + 'li', + 'lidl', + 'life', + 'lifeinsurance', + 'lifestyle', + 'lighting', + 'like', + 'lilly', + 'limited', + 'limo', + 'lincoln', + 'linde', + 'link', + 'lipsy', + 'live', + 'living', + 'lk', + 'llc', + 'llp', + 'loan', + 'loans', + 'locker', + 'locus', + 'loft', + 'lol', + 'london', + 'lotte', + 'lotto', + 'love', + 'lpl', + 'lplfinancial', + 'lr', + 'ls', + 'lt', + 'ltd', + 'ltda', + 'lu', + 'lundbeck', + 'luxe', + 'luxury', + 'lv', + 'ly', + 'ma', + 'macys', + 'madrid', + 'maif', + 'maison', + 'makeup', + 'man', + 'management', + 'mango', + 'map', + 'market', + 'marketing', + 'markets', + 'marriott', + 'marshalls', + 'maserati', + 'mattel', + 'mba', + 'mc', + 'mckinsey', + 'md', + 'me', + 'med', + 'media', + 'meet', + 'melbourne', + 'meme', + 'memorial', + 'men', + 'menu', + 'merckmsd', + 'mg', + 'mh', + 'miami', + 'microsoft', + 'mil', + 'mini', + 'mint', + 'mit', + 'mitsubishi', + 'mk', + 'ml', + 'mlb', + 'mls', + 'mm', + 'mma', + 'mn', + 'mo', + 'mobi', + 'mobile', + 'moda', + 'moe', + 'moi', + 'mom', + 'monash', + 'money', + 'monster', + 'mormon', + 'mortgage', + 'moscow', + 'moto', + 'motorcycles', + 'mov', + 'movie', + 'mp', + 'mq', + 'mr', + 'ms', + 'msd', + 'mt', + 'mtn', + 'mtr', + 'mu', + 'museum', + 'music', + 'mutual', + 'mv', + 'mw', + 'mx', + 'my', + 'mz', + 'na', + 'nab', + 'nagoya', + 'name', + 'natura', + 'navy', + 'nba', + 'nc', + 'ne', + 'nec', + 'net', + 'netbank', + 'netflix', + 'network', + 'neustar', + 'new', + 'news', + 'next', + 'nextdirect', + 'nexus', + 'nf', + 'nfl', + 'ng', + 'ngo', + 'nhk', + 'ni', + 'nico', + 'nike', + 'nikon', + 'ninja', + 'nissan', + 'nissay', + 'nl', + 'no', + 'nokia', + 'northwesternmutual', + 'norton', + 'now', + 'nowruz', + 'nowtv', + 'np', + 'nr', + 'nra', + 'nrw', + 'ntt', + 'nu', + 'nyc', + 'nz', + 'obi', + 'observer', + 'office', + 'okinawa', + 'olayan', + 'olayangroup', + 'oldnavy', + 'ollo', + 'om', + 'omega', + 'one', + 'ong', + 'onl', + 'online', + 'ooo', + 'open', + 'oracle', + 'orange', + 'org', + 'organic', + 'origins', + 'osaka', + 'otsuka', + 'ott', + 'ovh', + 'pa', + 'page', + 'panasonic', + 'paris', + 'pars', + 'partners', + 'parts', + 'party', + 'passagens', + 'pay', + 'pccw', + 'pe', + 'pet', + 'pf', + 'pfizer', + 'pg', + 'ph', + 'pharmacy', + 'phd', + 'philips', + 'phone', + 'photo', + 'photography', + 'photos', + 'physio', + 'pics', + 'pictet', + 'pictures', + 'pid', + 'pin', + 'ping', + 'pink', + 'pioneer', + 'pizza', + 'pk', + 'pl', + 'place', + 'play', + 'playstation', + 'plumbing', + 'plus', + 'pm', + 'pn', + 'pnc', + 'pohl', + 'poker', + 'politie', + 'porn', + 'post', + 'pr', + 'pramerica', + 'praxi', + 'press', + 'prime', + 'pro', + 'prod', + 'productions', + 'prof', + 'progressive', + 'promo', + 'properties', + 'property', + 'protection', + 'pru', + 'prudential', + 'ps', + 'pt', + 'pub', + 'pw', + 'pwc', + 'py', + 'qa', + 'qpon', + 'quebec', + 'quest', + 'racing', + 'radio', + 're', + 'read', + 'realestate', + 'realtor', + 'realty', + 'recipes', + 'red', + 'redstone', + 'redumbrella', + 'rehab', + 'reise', + 'reisen', + 'reit', + 'reliance', + 'ren', + 'rent', + 'rentals', + 'repair', + 'report', + 'republican', + 'rest', + 'restaurant', + 'review', + 'reviews', + 'rexroth', + 'rich', + 'richardli', + 'ricoh', + 'ril', + 'rio', + 'rip', + 'ro', + 'rocher', + 'rocks', + 'rodeo', + 'rogers', + 'room', + 'rs', + 'rsvp', + 'ru', + 'rugby', + 'ruhr', + 'run', + 'rw', + 'rwe', + 'ryukyu', + 'sa', + 'saarland', + 'safe', + 'safety', + 'sakura', + 'sale', + 'salon', + 'samsclub', + 'samsung', + 'sandvik', + 'sandvikcoromant', + 'sanofi', + 'sap', + 'sarl', + 'sas', + 'save', + 'saxo', + 'sb', + 'sbi', + 'sbs', + 'sc', + 'sca', + 'scb', + 'schaeffler', + 'schmidt', + 'scholarships', + 'school', + 'schule', + 'schwarz', + 'science', + 'scot', + 'sd', + 'se', + 'search', + 'seat', + 'secure', + 'security', + 'seek', + 'select', + 'sener', + 'services', + 'ses', + 'seven', + 'sew', + 'sex', + 'sexy', + 'sfr', + 'sg', + 'sh', + 'shangrila', + 'sharp', + 'shaw', + 'shell', + 'shia', + 'shiksha', + 'shoes', + 'shop', + 'shopping', + 'shouji', + 'show', + 'showtime', + 'si', + 'silk', + 'sina', + 'singles', + 'site', + 'sj', + 'sk', + 'ski', + 'skin', + 'sky', + 'skype', + 'sl', + 'sling', + 'sm', + 'smart', + 'smile', + 'sn', + 'sncf', + 'so', + 'soccer', + 'social', + 'softbank', + 'software', + 'sohu', + 'solar', + 'solutions', + 'song', + 'sony', + 'soy', + 'spa', + 'space', + 'sport', + 'spot', + 'sr', + 'srl', + 'ss', + 'st', + 'stada', + 'staples', + 'star', + 'statebank', + 'statefarm', + 'stc', + 'stcgroup', + 'stockholm', + 'storage', + 'store', + 'stream', + 'studio', + 'study', + 'style', + 'su', + 'sucks', + 'supplies', + 'supply', + 'support', + 'surf', + 'surgery', + 'suzuki', + 'sv', + 'swatch', + 'swiss', + 'sx', + 'sy', + 'sydney', + 'systems', + 'sz', + 'tab', + 'taipei', + 'talk', + 'taobao', + 'target', + 'tatamotors', + 'tatar', + 'tattoo', + 'tax', + 'taxi', + 'tc', + 'tci', + 'td', + 'tdk', + 'team', + 'tech', + 'technology', + 'tel', + 'temasek', + 'tennis', + 'teva', + 'tf', + 'tg', + 'th', + 'thd', + 'theater', + 'theatre', + 'tiaa', + 'tickets', + 'tienda', + 'tiffany', + 'tips', + 'tires', + 'tirol', + 'tj', + 'tjmaxx', + 'tjx', + 'tk', + 'tkmaxx', + 'tl', + 'tm', + 'tmall', + 'tn', + 'to', + 'today', + 'tokyo', + 'tools', + 'top', + 'toray', + 'toshiba', + 'total', + 'tours', + 'town', + 'toyota', + 'toys', + 'tr', + 'trade', + 'trading', + 'training', + 'travel', + 'travelchannel', + 'travelers', + 'travelersinsurance', + 'trust', + 'trv', + 'tt', + 'tube', + 'tui', + 'tunes', + 'tushu', + 'tv', + 'tvs', + 'tw', + 'tz', + 'ua', + 'ubank', + 'ubs', + 'ug', + 'uk', + 'unicom', + 'university', + 'uno', + 'uol', + 'ups', + 'us', + 'uy', + 'uz', + 'va', + 'vacations', + 'vana', + 'vanguard', + 'vc', + 've', + 'vegas', + 'ventures', + 'verisign', + 'vermögensberater', + 'vermögensberatung', + 'versicherung', + 'vet', + 'vg', + 'vi', + 'viajes', + 'video', + 'vig', + 'viking', + 'villas', + 'vin', + 'vip', + 'virgin', + 'visa', + 'vision', + 'viva', + 'vivo', + 'vlaanderen', + 'vn', + 'vodka', + 'volkswagen', + 'volvo', + 'vote', + 'voting', + 'voto', + 'voyage', + 'vu', + 'vuelos', + 'wales', + 'walmart', + 'walter', + 'wang', + 'wanggou', + 'watch', + 'watches', + 'weather', + 'weatherchannel', + 'webcam', + 'weber', + 'website', + 'wed', + 'wedding', + 'weibo', + 'weir', + 'wf', + 'whoswho', + 'wien', + 'wiki', + 'williamhill', + 'win', + 'windows', + 'wine', + 'winners', + 'wme', + 'wolterskluwer', + 'woodside', + 'work', + 'works', + 'world', + 'wow', + 'ws', + 'wtc', + 'wtf', + 'xbox', + 'xerox', + 'xfinity', + 'xihuan', + 'xin', + 'xxx', + 'xyz', + 'yachts', + 'yahoo', + 'yamaxun', + 'yandex', + 'ye', + 'yodobashi', + 'yoga', + 'yokohama', + 'you', + 'youtube', + 'yt', + 'yun', + 'za', + 'zappos', + 'zara', + 'zero', + 'zip', + 'zm', + 'zone', + 'zuerich', + 'zw', + 'ελ', + 'ευ', + 'бг', + 'бел', + 'дети', + 'ею', + 'католик', + 'ком', + 'мкд', + 'мон', + 'москва', + 'онлайн', + 'орг', + 'рус', + 'рф', + 'сайт', + 'срб', + 'укр', + 'қаз', + 'հայ', + 'ישראל', + 'קום', + 'ابوظبي', + 'اتصالات', + 'ارامكو', + 'الاردن', + 'البحرين', + 'الجزائر', + 'السعودية', + 'العليان', + 'المغرب', + 'امارات', + 'ایران', + 'بارت', + 'بازار', + 'بيتك', + 'بھارت', + 'تونس', + 'سودان', + 'سورية', + 'شبكة', + 'عراق', + 'عرب', + 'عمان', + 'فلسطين', + 'قطر', + 'كاثوليك', + 'كوم', + 'مصر', + 'مليسيا', + 'موريتانيا', + 'موقع', + 'همراه', + 'پاکستان', + 'ڀارت', + 'कॉम', + 'नेट', + 'भारत', + 'भारतम्', + 'भारोत', + 'संगठन', + 'বাংলা', + 'ভারত', + 'ভাৰত', + 'ਭਾਰਤ', + 'ભારત', + 'ଭାରତ', + 'இந்தியா', + 'இலங்கை', + 'சிங்கப்பூர்', + 'భారత్', + 'ಭಾರತ', + 'ഭാരതം', + 'ලංකා', + 'คอม', + 'ไทย', + 'ລາວ', + 'გე', + 'みんな', + 'アマゾン', + 'クラウド', + 'グーグル', + 'コム', + 'ストア', + 'セール', + 'ファッション', + 'ポイント', + '世界', + '中信', + '中国', + '中國', + '中文网', + '亚马逊', + '企业', + '佛山', + '信息', + '健康', + '八卦', + '公司', + '公益', + '台湾', + '台灣', + '商城', + '商店', + '商标', + '嘉里', + '嘉里大酒店', + '在线', + '大拿', + '天主教', + '娱乐', + '家電', + '广东', + '微博', + '慈善', + '我爱你', + '手机', + '招聘', + '政务', + '政府', + '新加坡', + '新闻', + '时尚', + '書籍', + '机构', + '淡马锡', + '游戏', + '澳門', + '点看', + '移动', + '组织机构', + '网址', + '网店', + '网站', + '网络', + '联通', + '诺基亚', + '谷歌', + '购物', + '通販', + '集团', + '電訊盈科', + '飞利浦', + '食品', + '餐厅', + '香格里拉', + '香港', + '닷넷', + '닷컴', + '삼성', + '한국', +]; + +export default tlds; \ No newline at end of file diff --git a/bin/utils/url.ts b/bin/utils/url.ts new file mode 100644 index 0000000..8ae79f8 --- /dev/null +++ b/bin/utils/url.ts @@ -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`); + } +} diff --git a/bin/utils/validate.ts b/bin/utils/validate.ts new file mode 100644 index 0000000..a97821c --- /dev/null +++ b/bin/utils/validate.ts @@ -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); + } +} diff --git a/cli.js b/cli.js new file mode 100755 index 0000000..d17768a --- /dev/null +++ b/cli.js @@ -0,0 +1,2 @@ +#!/usr/bin/env node +import './dist/cli.js'; diff --git a/dist/cli.js b/dist/cli.js new file mode 100644 index 0000000..9a3d28e --- /dev/null +++ b/dist/cli.js @@ -0,0 +1,1917 @@ +import * as Commander from 'commander'; +import { program } from 'commander'; +import url, { fileURLToPath } from 'url'; +import isurl from 'is-url'; +import prompts from 'prompts'; +import crypto from 'crypto'; +import axios from 'axios'; +import { fileTypeFromBuffer } from 'file-type'; +import { dir } from 'tmp-promise'; +import path from 'path'; +import fs from 'fs/promises'; +import log from 'loglevel'; +import ora from 'ora'; +import shelljs from 'shelljs'; + +/****************************************************************************** +Copyright (c) Microsoft Corporation. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +***************************************************************************** */ + +function __awaiter(thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +} + +const DEFAULT_PAKE_OPTIONS = { + icon: '', + height: 800, + width: 1280, + fullscreen: false, + resizable: true, + transparent: false, + debug: false, +}; + +const tlds = [ + 'aaa', + 'aarp', + 'abarth', + 'abb', + 'abbott', + 'abbvie', + 'abc', + 'able', + 'abogado', + 'abudhabi', + 'ac', + 'academy', + 'accenture', + 'accountant', + 'accountants', + 'aco', + 'actor', + 'ad', + 'adac', + 'ads', + 'adult', + 'ae', + 'aeg', + 'aero', + 'aetna', + 'af', + 'afl', + 'africa', + 'ag', + 'agakhan', + 'agency', + 'ai', + 'aig', + 'airbus', + 'airforce', + 'airtel', + 'akdn', + 'al', + 'alfaromeo', + 'alibaba', + 'alipay', + 'allfinanz', + 'allstate', + 'ally', + 'alsace', + 'alstom', + 'am', + 'amazon', + 'americanexpress', + 'americanfamily', + 'amex', + 'amfam', + 'amica', + 'amsterdam', + 'analytics', + 'android', + 'anquan', + 'anz', + 'ao', + 'aol', + 'apartments', + 'app', + 'apple', + 'aq', + 'aquarelle', + 'ar', + 'arab', + 'aramco', + 'archi', + 'army', + 'arpa', + 'art', + 'arte', + 'as', + 'asda', + 'asia', + 'associates', + 'at', + 'athleta', + 'attorney', + 'au', + 'auction', + 'audi', + 'audible', + 'audio', + 'auspost', + 'author', + 'auto', + 'autos', + 'avianca', + 'aw', + 'aws', + 'ax', + 'axa', + 'az', + 'azure', + 'ba', + 'baby', + 'baidu', + 'banamex', + 'bananarepublic', + 'band', + 'bank', + 'bar', + 'barcelona', + 'barclaycard', + 'barclays', + 'barefoot', + 'bargains', + 'baseball', + 'basketball', + 'bauhaus', + 'bayern', + 'bb', + 'bbc', + 'bbt', + 'bbva', + 'bcg', + 'bcn', + 'bd', + 'be', + 'beats', + 'beauty', + 'beer', + 'bentley', + 'berlin', + 'best', + 'bestbuy', + 'bet', + 'bf', + 'bg', + 'bh', + 'bharti', + 'bi', + 'bible', + 'bid', + 'bike', + 'bing', + 'bingo', + 'bio', + 'biz', + 'bj', + 'black', + 'blackfriday', + 'blockbuster', + 'blog', + 'bloomberg', + 'blue', + 'bm', + 'bms', + 'bmw', + 'bn', + 'bnpparibas', + 'bo', + 'boats', + 'boehringer', + 'bofa', + 'bom', + 'bond', + 'boo', + 'book', + 'booking', + 'bosch', + 'bostik', + 'boston', + 'bot', + 'boutique', + 'box', + 'br', + 'bradesco', + 'bridgestone', + 'broadway', + 'broker', + 'brother', + 'brussels', + 'bs', + 'bt', + 'build', + 'builders', + 'business', + 'buy', + 'buzz', + 'bv', + 'bw', + 'by', + 'bz', + 'bzh', + 'ca', + 'cab', + 'cafe', + 'cal', + 'call', + 'calvinklein', + 'cam', + 'camera', + 'camp', + 'canon', + 'capetown', + 'capital', + 'capitalone', + 'car', + 'caravan', + 'cards', + 'care', + 'career', + 'careers', + 'cars', + 'casa', + 'case', + 'cash', + 'casino', + 'cat', + 'catering', + 'catholic', + 'cba', + 'cbn', + 'cbre', + 'cbs', + 'cc', + 'cd', + 'center', + 'ceo', + 'cern', + 'cf', + 'cfa', + 'cfd', + 'cg', + 'ch', + 'chanel', + 'channel', + 'charity', + 'chase', + 'chat', + 'cheap', + 'chintai', + 'christmas', + 'chrome', + 'church', + 'ci', + 'cipriani', + 'circle', + 'cisco', + 'citadel', + 'citi', + 'citic', + 'city', + 'cityeats', + 'ck', + 'cl', + 'claims', + 'cleaning', + 'click', + 'clinic', + 'clinique', + 'clothing', + 'cloud', + 'club', + 'clubmed', + 'cm', + 'cn', + 'co', + 'coach', + 'codes', + 'coffee', + 'college', + 'cologne', + 'com', + 'comcast', + 'commbank', + 'community', + 'company', + 'compare', + 'computer', + 'comsec', + 'condos', + 'construction', + 'consulting', + 'contact', + 'contractors', + 'cooking', + 'cookingchannel', + 'cool', + 'coop', + 'corsica', + 'country', + 'coupon', + 'coupons', + 'courses', + 'cpa', + 'cr', + 'credit', + 'creditcard', + 'creditunion', + 'cricket', + 'crown', + 'crs', + 'cruise', + 'cruises', + 'cu', + 'cuisinella', + 'cv', + 'cw', + 'cx', + 'cy', + 'cymru', + 'cyou', + 'cz', + 'dabur', + 'dad', + 'dance', + 'data', + 'date', + 'dating', + 'datsun', + 'day', + 'dclk', + 'dds', + 'de', + 'deal', + 'dealer', + 'deals', + 'degree', + 'delivery', + 'dell', + 'deloitte', + 'delta', + 'democrat', + 'dental', + 'dentist', + 'desi', + 'design', + 'dev', + 'dhl', + 'diamonds', + 'diet', + 'digital', + 'direct', + 'directory', + 'discount', + 'discover', + 'dish', + 'diy', + 'dj', + 'dk', + 'dm', + 'dnp', + 'do', + 'docs', + 'doctor', + 'dog', + 'domains', + 'dot', + 'download', + 'drive', + 'dtv', + 'dubai', + 'dunlop', + 'dupont', + 'durban', + 'dvag', + 'dvr', + 'dz', + 'earth', + 'eat', + 'ec', + 'eco', + 'edeka', + 'edu', + 'education', + 'ee', + 'eg', + 'email', + 'emerck', + 'energy', + 'engineer', + 'engineering', + 'enterprises', + 'epson', + 'equipment', + 'er', + 'ericsson', + 'erni', + 'es', + 'esq', + 'estate', + 'et', + 'etisalat', + 'eu', + 'eurovision', + 'eus', + 'events', + 'exchange', + 'expert', + 'exposed', + 'express', + 'extraspace', + 'fage', + 'fail', + 'fairwinds', + 'faith', + 'family', + 'fan', + 'fans', + 'farm', + 'farmers', + 'fashion', + 'fast', + 'fedex', + 'feedback', + 'ferrari', + 'ferrero', + 'fi', + 'fiat', + 'fidelity', + 'fido', + 'film', + 'final', + 'finance', + 'financial', + 'fire', + 'firestone', + 'firmdale', + 'fish', + 'fishing', + 'fit', + 'fitness', + 'fj', + 'fk', + 'flickr', + 'flights', + 'flir', + 'florist', + 'flowers', + 'fly', + 'fm', + 'fo', + 'foo', + 'food', + 'foodnetwork', + 'football', + 'ford', + 'forex', + 'forsale', + 'forum', + 'foundation', + 'fox', + 'fr', + 'free', + 'fresenius', + 'frl', + 'frogans', + 'frontdoor', + 'frontier', + 'ftr', + 'fujitsu', + 'fun', + 'fund', + 'furniture', + 'futbol', + 'fyi', + 'ga', + 'gal', + 'gallery', + 'gallo', + 'gallup', + 'game', + 'games', + 'gap', + 'garden', + 'gay', + 'gb', + 'gbiz', + 'gd', + 'gdn', + 'ge', + 'gea', + 'gent', + 'genting', + 'george', + 'gf', + 'gg', + 'ggee', + 'gh', + 'gi', + 'gift', + 'gifts', + 'gives', + 'giving', + 'gl', + 'glass', + 'gle', + 'global', + 'globo', + 'gm', + 'gmail', + 'gmbh', + 'gmo', + 'gmx', + 'gn', + 'godaddy', + 'gold', + 'goldpoint', + 'golf', + 'goo', + 'goodyear', + 'goog', + 'google', + 'gop', + 'got', + 'gov', + 'gp', + 'gq', + 'gr', + 'grainger', + 'graphics', + 'gratis', + 'green', + 'gripe', + 'grocery', + 'group', + 'gs', + 'gt', + 'gu', + 'guardian', + 'gucci', + 'guge', + 'guide', + 'guitars', + 'guru', + 'gw', + 'gy', + 'hair', + 'hamburg', + 'hangout', + 'haus', + 'hbo', + 'hdfc', + 'hdfcbank', + 'health', + 'healthcare', + 'help', + 'helsinki', + 'here', + 'hermes', + 'hgtv', + 'hiphop', + 'hisamitsu', + 'hitachi', + 'hiv', + 'hk', + 'hkt', + 'hm', + 'hn', + 'hockey', + 'holdings', + 'holiday', + 'homedepot', + 'homegoods', + 'homes', + 'homesense', + 'honda', + 'horse', + 'hospital', + 'host', + 'hosting', + 'hot', + 'hoteles', + 'hotels', + 'hotmail', + 'house', + 'how', + 'hr', + 'hsbc', + 'ht', + 'hu', + 'hughes', + 'hyatt', + 'hyundai', + 'ibm', + 'icbc', + 'ice', + 'icu', + 'id', + 'ie', + 'ieee', + 'ifm', + 'ikano', + 'il', + 'im', + 'imamat', + 'imdb', + 'immo', + 'immobilien', + 'in', + 'inc', + 'industries', + 'infiniti', + 'info', + 'ing', + 'ink', + 'institute', + 'insurance', + 'insure', + 'int', + 'international', + 'intuit', + 'investments', + 'io', + 'ipiranga', + 'iq', + 'ir', + 'irish', + 'is', + 'ismaili', + 'ist', + 'istanbul', + 'it', + 'itau', + 'itv', + 'jaguar', + 'java', + 'jcb', + 'je', + 'jeep', + 'jetzt', + 'jewelry', + 'jio', + 'jll', + 'jm', + 'jmp', + 'jnj', + 'jo', + 'jobs', + 'joburg', + 'jot', + 'joy', + 'jp', + 'jpmorgan', + 'jprs', + 'juegos', + 'juniper', + 'kaufen', + 'kddi', + 'ke', + 'kerryhotels', + 'kerrylogistics', + 'kerryproperties', + 'kfh', + 'kg', + 'kh', + 'ki', + 'kia', + 'kids', + 'kim', + 'kinder', + 'kindle', + 'kitchen', + 'kiwi', + 'km', + 'kn', + 'koeln', + 'komatsu', + 'kosher', + 'kp', + 'kpmg', + 'kpn', + 'kr', + 'krd', + 'kred', + 'kuokgroup', + 'kw', + 'ky', + 'kyoto', + 'kz', + 'la', + 'lacaixa', + 'lamborghini', + 'lamer', + 'lancaster', + 'lancia', + 'land', + 'landrover', + 'lanxess', + 'lasalle', + 'lat', + 'latino', + 'latrobe', + 'law', + 'lawyer', + 'lb', + 'lc', + 'lds', + 'lease', + 'leclerc', + 'lefrak', + 'legal', + 'lego', + 'lexus', + 'lgbt', + 'li', + 'lidl', + 'life', + 'lifeinsurance', + 'lifestyle', + 'lighting', + 'like', + 'lilly', + 'limited', + 'limo', + 'lincoln', + 'linde', + 'link', + 'lipsy', + 'live', + 'living', + 'lk', + 'llc', + 'llp', + 'loan', + 'loans', + 'locker', + 'locus', + 'loft', + 'lol', + 'london', + 'lotte', + 'lotto', + 'love', + 'lpl', + 'lplfinancial', + 'lr', + 'ls', + 'lt', + 'ltd', + 'ltda', + 'lu', + 'lundbeck', + 'luxe', + 'luxury', + 'lv', + 'ly', + 'ma', + 'macys', + 'madrid', + 'maif', + 'maison', + 'makeup', + 'man', + 'management', + 'mango', + 'map', + 'market', + 'marketing', + 'markets', + 'marriott', + 'marshalls', + 'maserati', + 'mattel', + 'mba', + 'mc', + 'mckinsey', + 'md', + 'me', + 'med', + 'media', + 'meet', + 'melbourne', + 'meme', + 'memorial', + 'men', + 'menu', + 'merckmsd', + 'mg', + 'mh', + 'miami', + 'microsoft', + 'mil', + 'mini', + 'mint', + 'mit', + 'mitsubishi', + 'mk', + 'ml', + 'mlb', + 'mls', + 'mm', + 'mma', + 'mn', + 'mo', + 'mobi', + 'mobile', + 'moda', + 'moe', + 'moi', + 'mom', + 'monash', + 'money', + 'monster', + 'mormon', + 'mortgage', + 'moscow', + 'moto', + 'motorcycles', + 'mov', + 'movie', + 'mp', + 'mq', + 'mr', + 'ms', + 'msd', + 'mt', + 'mtn', + 'mtr', + 'mu', + 'museum', + 'music', + 'mutual', + 'mv', + 'mw', + 'mx', + 'my', + 'mz', + 'na', + 'nab', + 'nagoya', + 'name', + 'natura', + 'navy', + 'nba', + 'nc', + 'ne', + 'nec', + 'net', + 'netbank', + 'netflix', + 'network', + 'neustar', + 'new', + 'news', + 'next', + 'nextdirect', + 'nexus', + 'nf', + 'nfl', + 'ng', + 'ngo', + 'nhk', + 'ni', + 'nico', + 'nike', + 'nikon', + 'ninja', + 'nissan', + 'nissay', + 'nl', + 'no', + 'nokia', + 'northwesternmutual', + 'norton', + 'now', + 'nowruz', + 'nowtv', + 'np', + 'nr', + 'nra', + 'nrw', + 'ntt', + 'nu', + 'nyc', + 'nz', + 'obi', + 'observer', + 'office', + 'okinawa', + 'olayan', + 'olayangroup', + 'oldnavy', + 'ollo', + 'om', + 'omega', + 'one', + 'ong', + 'onl', + 'online', + 'ooo', + 'open', + 'oracle', + 'orange', + 'org', + 'organic', + 'origins', + 'osaka', + 'otsuka', + 'ott', + 'ovh', + 'pa', + 'page', + 'panasonic', + 'paris', + 'pars', + 'partners', + 'parts', + 'party', + 'passagens', + 'pay', + 'pccw', + 'pe', + 'pet', + 'pf', + 'pfizer', + 'pg', + 'ph', + 'pharmacy', + 'phd', + 'philips', + 'phone', + 'photo', + 'photography', + 'photos', + 'physio', + 'pics', + 'pictet', + 'pictures', + 'pid', + 'pin', + 'ping', + 'pink', + 'pioneer', + 'pizza', + 'pk', + 'pl', + 'place', + 'play', + 'playstation', + 'plumbing', + 'plus', + 'pm', + 'pn', + 'pnc', + 'pohl', + 'poker', + 'politie', + 'porn', + 'post', + 'pr', + 'pramerica', + 'praxi', + 'press', + 'prime', + 'pro', + 'prod', + 'productions', + 'prof', + 'progressive', + 'promo', + 'properties', + 'property', + 'protection', + 'pru', + 'prudential', + 'ps', + 'pt', + 'pub', + 'pw', + 'pwc', + 'py', + 'qa', + 'qpon', + 'quebec', + 'quest', + 'racing', + 'radio', + 're', + 'read', + 'realestate', + 'realtor', + 'realty', + 'recipes', + 'red', + 'redstone', + 'redumbrella', + 'rehab', + 'reise', + 'reisen', + 'reit', + 'reliance', + 'ren', + 'rent', + 'rentals', + 'repair', + 'report', + 'republican', + 'rest', + 'restaurant', + 'review', + 'reviews', + 'rexroth', + 'rich', + 'richardli', + 'ricoh', + 'ril', + 'rio', + 'rip', + 'ro', + 'rocher', + 'rocks', + 'rodeo', + 'rogers', + 'room', + 'rs', + 'rsvp', + 'ru', + 'rugby', + 'ruhr', + 'run', + 'rw', + 'rwe', + 'ryukyu', + 'sa', + 'saarland', + 'safe', + 'safety', + 'sakura', + 'sale', + 'salon', + 'samsclub', + 'samsung', + 'sandvik', + 'sandvikcoromant', + 'sanofi', + 'sap', + 'sarl', + 'sas', + 'save', + 'saxo', + 'sb', + 'sbi', + 'sbs', + 'sc', + 'sca', + 'scb', + 'schaeffler', + 'schmidt', + 'scholarships', + 'school', + 'schule', + 'schwarz', + 'science', + 'scot', + 'sd', + 'se', + 'search', + 'seat', + 'secure', + 'security', + 'seek', + 'select', + 'sener', + 'services', + 'ses', + 'seven', + 'sew', + 'sex', + 'sexy', + 'sfr', + 'sg', + 'sh', + 'shangrila', + 'sharp', + 'shaw', + 'shell', + 'shia', + 'shiksha', + 'shoes', + 'shop', + 'shopping', + 'shouji', + 'show', + 'showtime', + 'si', + 'silk', + 'sina', + 'singles', + 'site', + 'sj', + 'sk', + 'ski', + 'skin', + 'sky', + 'skype', + 'sl', + 'sling', + 'sm', + 'smart', + 'smile', + 'sn', + 'sncf', + 'so', + 'soccer', + 'social', + 'softbank', + 'software', + 'sohu', + 'solar', + 'solutions', + 'song', + 'sony', + 'soy', + 'spa', + 'space', + 'sport', + 'spot', + 'sr', + 'srl', + 'ss', + 'st', + 'stada', + 'staples', + 'star', + 'statebank', + 'statefarm', + 'stc', + 'stcgroup', + 'stockholm', + 'storage', + 'store', + 'stream', + 'studio', + 'study', + 'style', + 'su', + 'sucks', + 'supplies', + 'supply', + 'support', + 'surf', + 'surgery', + 'suzuki', + 'sv', + 'swatch', + 'swiss', + 'sx', + 'sy', + 'sydney', + 'systems', + 'sz', + 'tab', + 'taipei', + 'talk', + 'taobao', + 'target', + 'tatamotors', + 'tatar', + 'tattoo', + 'tax', + 'taxi', + 'tc', + 'tci', + 'td', + 'tdk', + 'team', + 'tech', + 'technology', + 'tel', + 'temasek', + 'tennis', + 'teva', + 'tf', + 'tg', + 'th', + 'thd', + 'theater', + 'theatre', + 'tiaa', + 'tickets', + 'tienda', + 'tiffany', + 'tips', + 'tires', + 'tirol', + 'tj', + 'tjmaxx', + 'tjx', + 'tk', + 'tkmaxx', + 'tl', + 'tm', + 'tmall', + 'tn', + 'to', + 'today', + 'tokyo', + 'tools', + 'top', + 'toray', + 'toshiba', + 'total', + 'tours', + 'town', + 'toyota', + 'toys', + 'tr', + 'trade', + 'trading', + 'training', + 'travel', + 'travelchannel', + 'travelers', + 'travelersinsurance', + 'trust', + 'trv', + 'tt', + 'tube', + 'tui', + 'tunes', + 'tushu', + 'tv', + 'tvs', + 'tw', + 'tz', + 'ua', + 'ubank', + 'ubs', + 'ug', + 'uk', + 'unicom', + 'university', + 'uno', + 'uol', + 'ups', + 'us', + 'uy', + 'uz', + 'va', + 'vacations', + 'vana', + 'vanguard', + 'vc', + 've', + 'vegas', + 'ventures', + 'verisign', + 'vermögensberater', + 'vermögensberatung', + 'versicherung', + 'vet', + 'vg', + 'vi', + 'viajes', + 'video', + 'vig', + 'viking', + 'villas', + 'vin', + 'vip', + 'virgin', + 'visa', + 'vision', + 'viva', + 'vivo', + 'vlaanderen', + 'vn', + 'vodka', + 'volkswagen', + 'volvo', + 'vote', + 'voting', + 'voto', + 'voyage', + 'vu', + 'vuelos', + 'wales', + 'walmart', + 'walter', + 'wang', + 'wanggou', + 'watch', + 'watches', + 'weather', + 'weatherchannel', + 'webcam', + 'weber', + 'website', + 'wed', + 'wedding', + 'weibo', + 'weir', + 'wf', + 'whoswho', + 'wien', + 'wiki', + 'williamhill', + 'win', + 'windows', + 'wine', + 'winners', + 'wme', + 'wolterskluwer', + 'woodside', + 'work', + 'works', + 'world', + 'wow', + 'ws', + 'wtc', + 'wtf', + 'xbox', + 'xerox', + 'xfinity', + 'xihuan', + 'xin', + 'xxx', + 'xyz', + 'yachts', + 'yahoo', + 'yamaxun', + 'yandex', + 'ye', + 'yodobashi', + 'yoga', + 'yokohama', + 'you', + 'youtube', + 'yt', + 'yun', + 'za', + 'zappos', + 'zara', + 'zero', + 'zip', + 'zm', + 'zone', + 'zuerich', + 'zw', + 'ελ', + 'ευ', + 'бг', + 'бел', + 'дети', + 'ею', + 'католик', + 'ком', + 'мкд', + 'мон', + 'москва', + 'онлайн', + 'орг', + 'рус', + 'рф', + 'сайт', + 'срб', + 'укр', + 'қаз', + 'հայ', + 'ישראל', + 'קום', + 'ابوظبي', + 'اتصالات', + 'ارامكو', + 'الاردن', + 'البحرين', + 'الجزائر', + 'السعودية', + 'العليان', + 'المغرب', + 'امارات', + 'ایران', + 'بارت', + 'بازار', + 'بيتك', + 'بھارت', + 'تونس', + 'سودان', + 'سورية', + 'شبكة', + 'عراق', + 'عرب', + 'عمان', + 'فلسطين', + 'قطر', + 'كاثوليك', + 'كوم', + 'مصر', + 'مليسيا', + 'موريتانيا', + 'موقع', + 'همراه', + 'پاکستان', + 'ڀارت', + 'कॉम', + 'नेट', + 'भारत', + 'भारतम्', + 'भारोत', + 'संगठन', + 'বাংলা', + 'ভারত', + 'ভাৰত', + 'ਭਾਰਤ', + 'ભારત', + 'ଭାରତ', + 'இந்தியா', + 'இலங்கை', + 'சிங்கப்பூர்', + 'భారత్', + 'ಭಾರತ', + 'ഭാരതം', + 'ලංකා', + 'คอม', + 'ไทย', + 'ລາວ', + 'გე', + 'みんな', + 'アマゾン', + 'クラウド', + 'グーグル', + 'コム', + 'ストア', + 'セール', + 'ファッション', + 'ポイント', + '世界', + '中信', + '中国', + '中國', + '中文网', + '亚马逊', + '企业', + '佛山', + '信息', + '健康', + '八卦', + '公司', + '公益', + '台湾', + '台灣', + '商城', + '商店', + '商标', + '嘉里', + '嘉里大酒店', + '在线', + '大拿', + '天主教', + '娱乐', + '家電', + '广东', + '微博', + '慈善', + '我爱你', + '手机', + '招聘', + '政务', + '政府', + '新加坡', + '新闻', + '时尚', + '書籍', + '机构', + '淡马锡', + '游戏', + '澳門', + '点看', + '移动', + '组织机构', + '网址', + '网店', + '网站', + '网络', + '联通', + '诺基亚', + '谷歌', + '购物', + '通販', + '集团', + '電訊盈科', + '飞利浦', + '食品', + '餐厅', + '香格里拉', + '香港', + '닷넷', + '닷컴', + '삼성', + '한국', +]; + +function getDomain(inputUrl) { + 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) { + const parsed = url.parse(inputUrl); + if (!parsed.protocol) { + const urlWithProtocol = `https://${inputUrl}`; + return urlWithProtocol; + } + return inputUrl; +} +function normalizeUrl(urlToNormalize) { + const urlWithProtocol = appendProtocol(urlToNormalize); + if (isurl(urlWithProtocol)) { + return urlWithProtocol; + } + else { + throw new Error(`Your url "${urlWithProtocol}" is invalid`); + } +} + +function validateNumberInput(value) { + const parsedValue = Number(value); + if (isNaN(parsedValue)) { + throw new Commander.InvalidArgumentError('Not a number.'); + } + return parsedValue; +} +function validateUrlInput(url) { + try { + return normalizeUrl(url); + } + catch (error) { + throw new Commander.InvalidArgumentError(error.message); + } +} + +function promptText(message, initial) { + return __awaiter(this, void 0, void 0, function* () { + const response = yield prompts({ + type: 'text', + name: 'content', + message, + initial, + }); + return response.content; + }); +} + +function getIdentifier(name, url) { + const hash = crypto.createHash('md5'); + hash.update(url); + const postFixHash = hash.digest('hex').substring(0, 6); + return `pake-${postFixHash}`; +} + +function handleIcon(options, url) { + return __awaiter(this, void 0, void 0, function* () { + 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); + } + }); +} +function inferIcon(name, url) { + return __awaiter(this, void 0, void 0, function* () { + 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); +// } +// } +function downloadIcon(iconUrl) { + return __awaiter(this, void 0, void 0, function* () { + let iconResponse; + try { + iconResponse = yield axios.get(iconUrl, { + responseType: 'arraybuffer', + }); + } + catch (error) { + if (error.response && error.response.status === 404) { + return null; + } + throw error; + } + const iconData = yield iconResponse.data; + if (!iconData) { + return null; + } + const fileDetails = yield fileTypeFromBuffer(iconData); + if (!fileDetails) { + return null; + } + const { path } = yield dir(); + const iconPath = `${path}/icon.${fileDetails.ext}`; + yield fs.writeFile(iconPath, iconData); + return iconPath; + }); +} + +function handleOptions(options, url) { + return __awaiter(this, void 0, void 0, function* () { + const appOptions = Object.assign(Object.assign({}, options), { identifier: '' }); + if (!appOptions.name) { + appOptions.name = yield promptText('please input your application name', getDomain(url)); + } + appOptions.identifier = getIdentifier(appOptions.name, url); + appOptions.icon = yield handleIcon(appOptions); + return appOptions; + }); +} + +const IS_MAC = process.platform === 'darwin'; +process.platform === 'win32'; +process.platform === 'linux'; + +function shellExec(command) { + return new Promise((resolve, reject) => { + shelljs.exec(command, { async: true, silent: false }, (code) => { + if (code === 0) { + resolve(0); + } + else { + reject(new Error(`${code}`)); + } + }); + }); +} + +const InstallRustScript = "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y"; +function installRust() { + return __awaiter(this, void 0, void 0, function* () { + const spinner = ora('Downloading Rust').start(); + try { + yield shellExec(InstallRustScript); + spinner.succeed(); + } + catch (error) { + console.error('install rust return code', error.message); + spinner.fail(); + process.exit(1); + } + }); +} +function checkRustInstalled() { + return shelljs.exec('rustc --version', { silent: true }).code === 0; +} + +var tauri = { + windows: [ + { + url: "https://weread.qq.com/", + transparent: true, + fullscreen: false, + width: 1200, + height: 728, + resizable: true + } + ], + allowlist: { + all: true + }, + bundle: { + icon: [ + "icons/weread.icns", + "png/weread_256.ico", + "png/weread_32.ico", + "png/weread_512.png" + ], + identifier: "com.tw93.weread", + active: true, + category: "DeveloperTool", + copyright: "", + deb: { + depends: [ + "libwebkit2gtk-4.0-dev", + "build-essential", + "curl", + "wget", + "libssl-dev", + "libgtk-3-dev", + "libayatana-appindicator3-dev", + "librsvg2-dev" + ], + files: { + "/usr/share/applications/com-tw93-weread.desktop": "assets/com-tw93-weread.desktop" + } + }, + externalBin: [ + ], + longDescription: "", + macOS: { + entitlements: null, + exceptionDomain: "", + frameworks: [ + ], + providerShortName: null, + signingIdentity: null + }, + resources: [ + ], + shortDescription: "", + targets: [ + "deb", + "msi", + "dmg" + ], + windows: { + certificateThumbprint: null, + digestAlgorithm: "sha256", + timestampUrl: "", + wix: { + language: [ + "en-US", + "zh-CN" + ] + } + } + }, + security: { + csp: null + }, + updater: { + active: false + } +}; +var build = { + devPath: "../dist", + distDir: "../dist", + beforeBuildCommand: "", + beforeDevCommand: "" +}; +var tauriConf = { + "package": { + productName: "weread", + version: "0.2.0" +}, + tauri: tauri, + build: build +}; + +class MacBuilder { + prepare() { + return __awaiter(this, void 0, void 0, function* () { + if (checkRustInstalled()) { + return; + } + const res = yield prompts({ + type: 'confirm', + message: 'Detect you have not installed Rust, install it now?', + name: 'value', + }); + if (res.value) { + // TODO 国内有可能会超时 + yield installRust(); + } + else { + log.error('Error: Pake need Rust to package your webapp!!!'); + process.exit(2); + } + }); + } + build(url, options) { + return __awaiter(this, void 0, void 0, function* () { + 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], Object.assign({ 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'); + yield fs.writeFile(configJsonPath, Buffer.from(JSON.stringify(tauriConf), 'utf-8')); + yield shellExec(`cd ${npmDirectory} && npm run build`); + const dmgName = `${name}_${'0.2.0'}_universal.dmg`; + const appPath = this.getBuildedAppPath(npmDirectory, dmgName); + yield fs.copyFile(appPath, path.resolve(`${name}_universal.dmg`)); + }); + } + getBuildedAppPath(npmDirectory, dmgName) { + return path.join(npmDirectory, 'src-tauri/target/universal-apple-darwin/release/bundle/dmg', dmgName); + } +} + +class BuilderFactory { + static create() { + if (IS_MAC) { + return new MacBuilder(); + } + throw new Error('The current system does not support'); + } +} + +program.version('0.0.1').description('A cli application can package a web page to desktop application'); +program + .showHelpAfterError() + .argument('', 'the web url you want to package', validateUrlInput) + .option('--name ', 'application name') + .option('--icon ', 'application icon', DEFAULT_PAKE_OPTIONS.icon) + .option('--height ', 'window height', validateNumberInput, DEFAULT_PAKE_OPTIONS.height) + .option('--width ', '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((url, options) => __awaiter(void 0, void 0, void 0, function* () { + log.setDefaultLevel('info'); + if (options.debug) { + log.setLevel('debug'); + } + const builder = BuilderFactory.create(); + yield builder.prepare(); + const appOptions = yield handleOptions(options, url); + builder.build(url, appOptions); +})); +program.parse(); diff --git a/package.json b/package.json index e846685..eb41178 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,10 @@ { - "name": "pake", + "name": "pake-cli", + "version": "0.0.1", "description": "用 Rust 来打包你的 App,底层使用 Tauri,当前支持微信读书、Flomo、Vercel", + "bin": { + "pake": "./cli.js" + }, "repository": { "type": "git", "url": "https://github.com/tw93/pake.git" @@ -16,13 +20,42 @@ "build": "npm run tauri build -- --target universal-apple-darwin", "build:windows": "npm run tauri build -- --target x86_64-pc-windows-msvc", "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" }, + "type": "module", + "exports": "./dist/pake.js", "license": "MIT", "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": { - "@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" } } diff --git a/pake-default.icns b/pake-default.icns new file mode 100644 index 0000000..577d52f Binary files /dev/null and b/pake-default.icns differ diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..6fdfbba --- /dev/null +++ b/rollup.config.js @@ -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') }], + }), + ], +}; diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns new file mode 100644 index 0000000..577d52f Binary files /dev/null and b/src-tauri/icons/icon.icns differ diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..d5cb79e --- /dev/null +++ b/tsconfig.json @@ -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/**/*"] +}