pake-cli 0.0.1-beta.5 → 0.0.1-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.gitkeep +0 -0
- package/dist/cli.js +336 -0
- package/package.json +1 -1
- package/rollup.config.js +0 -2
package/dist/.gitkeep
ADDED
|
File without changes
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import * as Commander from 'commander';
|
|
2
|
+
import { program } from 'commander';
|
|
3
|
+
import url from 'url';
|
|
4
|
+
import isurl from 'is-url';
|
|
5
|
+
import crypto from 'crypto';
|
|
6
|
+
import axios from 'axios';
|
|
7
|
+
import { fileTypeFromBuffer } from 'file-type';
|
|
8
|
+
import { dir } from 'tmp-promise';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import pageIcon from 'page-icon';
|
|
11
|
+
import png2icons from 'png2icons';
|
|
12
|
+
import ICO from 'icojs';
|
|
13
|
+
import fs from 'fs/promises';
|
|
14
|
+
import prompts from 'prompts';
|
|
15
|
+
import ora from 'ora';
|
|
16
|
+
import shelljs from 'shelljs';
|
|
17
|
+
import appRootPath from 'app-root-path';
|
|
18
|
+
|
|
19
|
+
/******************************************************************************
|
|
20
|
+
Copyright (c) Microsoft Corporation.
|
|
21
|
+
|
|
22
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
23
|
+
purpose with or without fee is hereby granted.
|
|
24
|
+
|
|
25
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
26
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
27
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
28
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
29
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
30
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
31
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
32
|
+
***************************************************************************** */
|
|
33
|
+
|
|
34
|
+
function __awaiter(thisArg, _arguments, P, generator) {
|
|
35
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
36
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
37
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
38
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
39
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
40
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const DEFAULT_PAKE_OPTIONS = {
|
|
45
|
+
icon: '',
|
|
46
|
+
height: 800,
|
|
47
|
+
width: 1280,
|
|
48
|
+
fullscreen: false,
|
|
49
|
+
resizable: true,
|
|
50
|
+
transparent: false,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
function appendProtocol(inputUrl) {
|
|
54
|
+
const parsed = url.parse(inputUrl);
|
|
55
|
+
if (!parsed.protocol) {
|
|
56
|
+
const urlWithProtocol = `https://${inputUrl}`;
|
|
57
|
+
return urlWithProtocol;
|
|
58
|
+
}
|
|
59
|
+
return inputUrl;
|
|
60
|
+
}
|
|
61
|
+
function normalizeUrl(urlToNormalize) {
|
|
62
|
+
const urlWithProtocol = appendProtocol(urlToNormalize);
|
|
63
|
+
if (isurl(urlWithProtocol)) {
|
|
64
|
+
return urlWithProtocol;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
throw new Error(`Your url "${urlWithProtocol}" is invalid`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function validateNumberInput(value) {
|
|
72
|
+
const parsedValue = Number(value);
|
|
73
|
+
if (isNaN(parsedValue)) {
|
|
74
|
+
throw new Commander.InvalidArgumentError('Not a number.');
|
|
75
|
+
}
|
|
76
|
+
return parsedValue;
|
|
77
|
+
}
|
|
78
|
+
function validateUrlInput(url) {
|
|
79
|
+
try {
|
|
80
|
+
return normalizeUrl(url);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
throw new Commander.InvalidArgumentError(error.message);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function getIdentifier(name, url) {
|
|
88
|
+
const hash = crypto.createHash('md5');
|
|
89
|
+
hash.update(url);
|
|
90
|
+
const postFixHash = hash.digest('hex').substring(0, 6);
|
|
91
|
+
return `pake-${postFixHash}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function handleIcon(options, url) {
|
|
95
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
96
|
+
console.log(options.icon);
|
|
97
|
+
if (options.icon) {
|
|
98
|
+
if (options.icon.startsWith('http')) {
|
|
99
|
+
return downloadIcon(options.icon);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
return path.resolve(options.icon);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (!options.icon) {
|
|
106
|
+
return inferIcon(options.name, url);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function inferIcon(name, url) {
|
|
111
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
112
|
+
let icon = yield getIconFromMacosIcons(name);
|
|
113
|
+
if (!icon) {
|
|
114
|
+
icon = yield getIconFromPageUrl(url);
|
|
115
|
+
}
|
|
116
|
+
return icon;
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function getIconFromPageUrl(url) {
|
|
120
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
121
|
+
const icon = yield pageIcon(url);
|
|
122
|
+
console.log(icon);
|
|
123
|
+
if (icon.ext === '.ico') {
|
|
124
|
+
const a = yield ICO.parse(icon.data);
|
|
125
|
+
icon.data = Buffer.from(a[0].buffer);
|
|
126
|
+
}
|
|
127
|
+
const iconDir = (yield dir()).path;
|
|
128
|
+
const iconPath = path.join(iconDir, `/icon.icns`);
|
|
129
|
+
const out = png2icons.createICNS(icon.data, png2icons.BILINEAR, 0);
|
|
130
|
+
yield fs.writeFile(iconPath, out);
|
|
131
|
+
return iconPath;
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
function getIconFromMacosIcons(name) {
|
|
135
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
136
|
+
const data = {
|
|
137
|
+
query: name,
|
|
138
|
+
filters: 'approved:true',
|
|
139
|
+
hitsPerPage: 10,
|
|
140
|
+
page: 1,
|
|
141
|
+
};
|
|
142
|
+
const res = yield axios.post('https://p1txh7zfb3-2.algolianet.com/1/indexes/macOSicons/query?x-algolia-agent=Algolia%20for%20JavaScript%20(4.13.1)%3B%20Browser', data, {
|
|
143
|
+
headers: {
|
|
144
|
+
'x-algolia-api-key': '0ba04276e457028f3e11e38696eab32c',
|
|
145
|
+
'x-algolia-application-id': 'P1TXH7ZFB3',
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
if (!res.data.hits.length) {
|
|
149
|
+
return '';
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
return downloadIcon(res.data.hits[0].icnsUrl);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
function downloadIcon(iconUrl) {
|
|
157
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
158
|
+
let iconResponse;
|
|
159
|
+
try {
|
|
160
|
+
iconResponse = yield axios.get(iconUrl, {
|
|
161
|
+
responseType: 'arraybuffer',
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
if (error.response && error.response.status === 404) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
const iconData = yield iconResponse.data;
|
|
171
|
+
if (!iconData) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
const fileDetails = yield fileTypeFromBuffer(iconData);
|
|
175
|
+
if (!fileDetails) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
const { path } = yield dir();
|
|
179
|
+
const iconPath = `${path}/icon.${fileDetails.ext}`;
|
|
180
|
+
yield fs.writeFile(iconPath, iconData);
|
|
181
|
+
return iconPath;
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36';
|
|
186
|
+
function getTitleByURL(url) {
|
|
187
|
+
var _a, _b, _c;
|
|
188
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
189
|
+
const { data } = yield axios.get(url, {
|
|
190
|
+
headers: {
|
|
191
|
+
// Fake user agent for pages like http://messenger.com
|
|
192
|
+
'User-Agent': USER_AGENT,
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
const title = (_c = (_b = (_a = /<\s*title.*?>(?<title>.+?)<\s*\/title\s*?>/i.exec(data)) === null || _a === void 0 ? void 0 : _a.groups) === null || _b === void 0 ? void 0 : _b.title) !== null && _c !== void 0 ? _c : 'Webapp';
|
|
196
|
+
return title;
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function handleOptions(options, url) {
|
|
201
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
202
|
+
const appOptions = Object.assign(Object.assign({}, options), { identifier: '' });
|
|
203
|
+
if (!appOptions.title) {
|
|
204
|
+
appOptions.title = yield getTitleByURL(url);
|
|
205
|
+
}
|
|
206
|
+
if (!appOptions.name) {
|
|
207
|
+
appOptions.name = appOptions.title;
|
|
208
|
+
}
|
|
209
|
+
appOptions.identifier = getIdentifier(appOptions.name, url);
|
|
210
|
+
appOptions.icon = yield handleIcon(appOptions, url);
|
|
211
|
+
return appOptions;
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const IS_MAC = process.platform === 'darwin';
|
|
216
|
+
process.platform === 'win32';
|
|
217
|
+
process.platform === 'linux';
|
|
218
|
+
|
|
219
|
+
function shellExec(command) {
|
|
220
|
+
return new Promise((resolve, reject) => {
|
|
221
|
+
shelljs.exec(command, { async: true, silent: false }, (code) => {
|
|
222
|
+
if (code === 0) {
|
|
223
|
+
resolve(0);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
reject(new Error(`${code}`));
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const InstallRustScript = "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y";
|
|
233
|
+
function installRust() {
|
|
234
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
235
|
+
const spinner = ora('Downloading Rust').start();
|
|
236
|
+
try {
|
|
237
|
+
yield shellExec(InstallRustScript);
|
|
238
|
+
spinner.succeed();
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
console.error('install rust return code', error.message);
|
|
242
|
+
spinner.fail();
|
|
243
|
+
process.exit(1);
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
function checkRustInstalled() {
|
|
248
|
+
return shelljs.exec('rustc --version', { silent: true }).code === 0;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
class MacBuilder {
|
|
252
|
+
prepare() {
|
|
253
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
254
|
+
if (checkRustInstalled()) {
|
|
255
|
+
console.log('Rust has been installed');
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
console.warn('Rust is not installed, show prompt');
|
|
259
|
+
const res = yield prompts({
|
|
260
|
+
type: 'confirm',
|
|
261
|
+
message: 'Detect you have not installed Rust, install it now?',
|
|
262
|
+
name: 'value',
|
|
263
|
+
});
|
|
264
|
+
if (res.value) {
|
|
265
|
+
// TODO 国内有可能会超时
|
|
266
|
+
yield installRust();
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
console.error('Error: Pake need Rust to package your webapp!!!');
|
|
270
|
+
process.exit(2);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
build(url, options) {
|
|
275
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
276
|
+
console.log('PakeAppOptions', options);
|
|
277
|
+
const tauriConfPath = path.join(appRootPath.path, 'src-tauri/tauri.conf.json');
|
|
278
|
+
const tauriConfString = yield fs.readFile(tauriConfPath, 'utf-8');
|
|
279
|
+
try {
|
|
280
|
+
const tauriConf = JSON.parse(tauriConfString);
|
|
281
|
+
const { width, height, fullscreen, transparent, title, resizable, identifier, name } = options;
|
|
282
|
+
const tauriConfWindowOptions = {
|
|
283
|
+
width,
|
|
284
|
+
height,
|
|
285
|
+
fullscreen,
|
|
286
|
+
transparent,
|
|
287
|
+
title,
|
|
288
|
+
resizable,
|
|
289
|
+
};
|
|
290
|
+
Object.assign(tauriConf.tauri.windows[0], Object.assign({ url }, tauriConfWindowOptions));
|
|
291
|
+
tauriConf.package.productName = name;
|
|
292
|
+
tauriConf.tauri.bundle.identifier = identifier;
|
|
293
|
+
tauriConf.tauri.bundle.icon = [options.icon];
|
|
294
|
+
yield fs.writeFile(tauriConfPath, JSON.stringify(tauriConf, null, 2));
|
|
295
|
+
const code = yield shellExec(`${path.join(appRootPath.path, '/node_modules/.bin/tauri')} build --config ${tauriConfPath} --target universal-apple-darwin`);
|
|
296
|
+
const dmgName = `${name}_${'0.2.0'}_universal.dmg`;
|
|
297
|
+
yield fs.copyFile(this.getBuildedAppPath(dmgName), path.resolve(dmgName));
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
console.error('handle tauri.conf.json error', error);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
getBuildedAppPath(dmgName) {
|
|
306
|
+
return path.join(appRootPath.path, 'src-tauri/target/universal-apple-darwin/release/bundle/dmg', dmgName);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
class BuilderFactory {
|
|
311
|
+
static create() {
|
|
312
|
+
if (IS_MAC) {
|
|
313
|
+
return new MacBuilder();
|
|
314
|
+
}
|
|
315
|
+
throw new Error('The current system does not support');
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
program.version('0.0.1').description('A cli application can build website to app, driven by tauri');
|
|
320
|
+
program
|
|
321
|
+
.argument('<url>', 'the web url you want to package', validateUrlInput)
|
|
322
|
+
.option('--name <string>', 'application name')
|
|
323
|
+
.option('--title <string>', 'application window title')
|
|
324
|
+
.option('--icon <string>', 'application icon', DEFAULT_PAKE_OPTIONS.icon)
|
|
325
|
+
.option('--height <number>', 'application window height', validateNumberInput, DEFAULT_PAKE_OPTIONS.height)
|
|
326
|
+
.option('--width <number>', 'application window width', validateNumberInput, DEFAULT_PAKE_OPTIONS.width)
|
|
327
|
+
.option('--no-resizable', 'whether the application window can be resizable', DEFAULT_PAKE_OPTIONS.resizable)
|
|
328
|
+
.option('--fullscreen', 'makes the packaged app start in full screen', DEFAULT_PAKE_OPTIONS.fullscreen)
|
|
329
|
+
.option('--transparent', 'transparent title bar', DEFAULT_PAKE_OPTIONS.transparent)
|
|
330
|
+
.action((url, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
331
|
+
const builder = BuilderFactory.create();
|
|
332
|
+
yield builder.prepare();
|
|
333
|
+
const appOptions = yield handleOptions(options, url);
|
|
334
|
+
builder.build(url, appOptions);
|
|
335
|
+
}));
|
|
336
|
+
program.parse();
|
package/package.json
CHANGED
package/rollup.config.js
CHANGED
|
@@ -2,7 +2,6 @@ import path from 'path';
|
|
|
2
2
|
import appRootPath from 'app-root-path';
|
|
3
3
|
import typescript from '@rollup/plugin-typescript';
|
|
4
4
|
import alias from '@rollup/plugin-alias';
|
|
5
|
-
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
|
6
5
|
import commonjs from '@rollup/plugin-commonjs';
|
|
7
6
|
import json from '@rollup/plugin-json';
|
|
8
7
|
export default {
|
|
@@ -17,7 +16,6 @@ export default {
|
|
|
17
16
|
}),
|
|
18
17
|
json(),
|
|
19
18
|
commonjs(),
|
|
20
|
-
nodeResolve(),
|
|
21
19
|
alias({
|
|
22
20
|
entries: [{ find: '@', replacement: path.join(appRootPath.path, 'bin') }],
|
|
23
21
|
}),
|