u2a 2.1.6 → 3.0.0
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/README.md +43 -17
- package/package.json +26 -23
- package/src/commands/create.js +319 -53
- package/src/commands/remove.js +12 -13
- package/src/index.js +10 -2
- package/src/utils/favicon.js +51 -2
- package/src/utils/favicon256.ico +0 -0
- package/src/utils/logger.js +1 -1
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<div align="center">
|
|
2
|
-
<a href="
|
|
3
|
-
<img
|
|
4
|
-
alt="Image of this repo"
|
|
5
|
-
src="https://togp.xyz?owner=douxxtech&repo=urltoapp&theme=json-dark-all&cache=false"
|
|
6
|
-
type="image/svg+xml"
|
|
7
|
-
style="border-radius: 20px; overflow: hidden;"
|
|
2
|
+
<a href="https://urltoapp.xyz" style="display: block; text-align: center;">
|
|
3
|
+
<img
|
|
4
|
+
alt="Image of this repo"
|
|
5
|
+
src="https://togp.xyz?owner=douxxtech&repo=urltoapp&theme=json-dark-all&cache=false"
|
|
6
|
+
type="image/svg+xml"
|
|
7
|
+
style="border-radius: 20px; overflow: hidden;"
|
|
8
8
|
/>
|
|
9
9
|
<h1 align="center">U2A (URL to App)</h1>
|
|
10
10
|
</a>
|
|
@@ -25,6 +25,7 @@ U2A is a command-line utility that allows you to transform any web URL into a st
|
|
|
25
25
|
- 🔄 Automatic favicon retrieval for app icons
|
|
26
26
|
- 📋 Easy management of created applications
|
|
27
27
|
- 📊 Detailed logging for troubleshooting
|
|
28
|
+
- 🏷️ Customizable application names and window sizes
|
|
28
29
|
|
|
29
30
|
## Installation
|
|
30
31
|
|
|
@@ -39,17 +40,17 @@ npm install -g u2a
|
|
|
39
40
|
To create a desktop application from a website:
|
|
40
41
|
|
|
41
42
|
```bash
|
|
42
|
-
u2a create <url>
|
|
43
|
+
u2a create <url> [--name <appName>] [--width <width>] [--height <height>]
|
|
43
44
|
```
|
|
44
45
|
|
|
45
46
|
Example:
|
|
46
47
|
```bash
|
|
47
|
-
u2a create github.com
|
|
48
|
+
u2a create github.com --name "GitHub App" --width 1200 --height 800
|
|
48
49
|
```
|
|
49
50
|
|
|
50
51
|
This will:
|
|
51
52
|
1. Download the website's favicon (if available)
|
|
52
|
-
2. Create an Electron wrapper application
|
|
53
|
+
2. Create an Electron wrapper application with the specified name and window size
|
|
53
54
|
3. Add the application to your system menu/launcher
|
|
54
55
|
4. Track the application in the U2A database
|
|
55
56
|
|
|
@@ -62,7 +63,7 @@ u2a list
|
|
|
62
63
|
```
|
|
63
64
|
|
|
64
65
|
This will display a list of all created applications with their details:
|
|
65
|
-
-
|
|
66
|
+
- Application name
|
|
66
67
|
- Original URL
|
|
67
68
|
- Creation date
|
|
68
69
|
- Application directory
|
|
@@ -72,12 +73,12 @@ This will display a list of all created applications with their details:
|
|
|
72
73
|
To remove an application:
|
|
73
74
|
|
|
74
75
|
```bash
|
|
75
|
-
u2a remove <
|
|
76
|
+
u2a remove <appName>
|
|
76
77
|
```
|
|
77
78
|
|
|
78
79
|
Example:
|
|
79
80
|
```bash
|
|
80
|
-
u2a remove
|
|
81
|
+
u2a remove "GitHub App"
|
|
81
82
|
```
|
|
82
83
|
|
|
83
84
|
This will:
|
|
@@ -85,8 +86,37 @@ This will:
|
|
|
85
86
|
2. Delete the application files
|
|
86
87
|
3. Remove the entry from the U2A database
|
|
87
88
|
|
|
88
|
-
## How It Works
|
|
89
89
|
|
|
90
|
+
### Creating an executable
|
|
91
|
+
|
|
92
|
+
To directly create a windows, macos or linux executable, you can use the `--executable [windows|darwin|linux] [--arch <architecture>]` argument with the create command.
|
|
93
|
+
|
|
94
|
+
This will:
|
|
95
|
+
1. Temporarily install the application
|
|
96
|
+
2. Create an executable and move it to your working directory
|
|
97
|
+
3. Delete the application
|
|
98
|
+
|
|
99
|
+
> [!WARNING]
|
|
100
|
+
> To launch an executable, you will need all the files that are created by U2A.
|
|
101
|
+
|
|
102
|
+
### Creating a setup
|
|
103
|
+
|
|
104
|
+
You can also directly create a setup file, so people can install it on their machine. Use the `--executable [...] --setup` to do so.
|
|
105
|
+
|
|
106
|
+
This will:
|
|
107
|
+
1. Temporarily install the application
|
|
108
|
+
2. Create an executable and move it to your working directory
|
|
109
|
+
3. Create a setup file and move it to your working directory
|
|
110
|
+
4. Delete the application
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
> [!WARNING]
|
|
114
|
+
> To use the setup, you will need all the files that are created by U2A.
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
## How It Works
|
|
118
|
+
|
|
119
|
+
`This dont apply for executables and setup`
|
|
90
120
|
U2A creates a minimal Electron application that loads the specified website URL. It:
|
|
91
121
|
|
|
92
122
|
1. Downloads the site's favicon to use as the application icon
|
|
@@ -127,7 +157,3 @@ This project is licensed under the GNU General Public License v3.0 - see the [LI
|
|
|
127
157
|
## Author
|
|
128
158
|
|
|
129
159
|
Created by [Douxx](https://douxx.tech)
|
|
130
|
-
|
|
131
|
-
## Disclaimer
|
|
132
|
-
|
|
133
|
-
This tool is for personal use only. Always respect the terms of service of websites you convert to desktop apps.
|
package/package.json
CHANGED
|
@@ -1,39 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "u2a",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "URL to App - Turn any URL into a desktop application",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
|
|
7
|
+
"u2a": "./src/index.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
|
|
10
|
+
"start": "node src/index.js"
|
|
11
11
|
},
|
|
12
12
|
"keywords": [
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
"cli",
|
|
14
|
+
"webapp",
|
|
15
|
+
"electron",
|
|
16
|
+
"url",
|
|
17
|
+
"desktop",
|
|
18
|
+
"build",
|
|
19
|
+
"application"
|
|
20
20
|
],
|
|
21
21
|
"author": "Douxx",
|
|
22
22
|
"license": "GPL-3.0-only",
|
|
23
23
|
"dependencies": {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
"axios": "^1.6.0",
|
|
25
|
+
"chalk": "^4.1.2",
|
|
26
|
+
"commander": "^10.0.0",
|
|
27
|
+
"electron": "^22.0.0",
|
|
28
|
+
"icojs": "^0.19.5",
|
|
29
|
+
"inquirer": "^8.2.5",
|
|
30
|
+
"open": "^8.4.0",
|
|
31
|
+
"png-to-ico": "^2.1.8",
|
|
32
|
+
"sharp": "^0.33.5"
|
|
30
33
|
},
|
|
31
34
|
"homepage": "https://urltoapp.xyz",
|
|
32
35
|
"repository": {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "https://github.com/douxxtech/urltoapp"
|
|
38
|
+
},
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/douxxtech/urltoapp/issues"
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/commands/create.js
CHANGED
|
@@ -2,7 +2,7 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { execSync } = require('child_process');
|
|
4
4
|
const { normalizeUrl, getDomainName } = require('../utils/url');
|
|
5
|
-
const { getFavicon } = require('../utils/favicon');
|
|
5
|
+
const { getFavicon, processFavicon } = require('../utils/favicon');
|
|
6
6
|
const { APPS_DIR, readDB, writeDB } = require('../utils/config');
|
|
7
7
|
const Logger = require('../utils/logger');
|
|
8
8
|
const os = require('os');
|
|
@@ -11,14 +11,14 @@ const logger = new Logger('create');
|
|
|
11
11
|
|
|
12
12
|
function createWindowsShortcut(appInfo) {
|
|
13
13
|
try {
|
|
14
|
-
const {
|
|
14
|
+
const { appName, appDir, iconPath } = appInfo;
|
|
15
15
|
const startMenuPath = path.join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'U2A Apps');
|
|
16
16
|
|
|
17
17
|
if (!fs.existsSync(startMenuPath)) {
|
|
18
18
|
fs.mkdirSync(startMenuPath, { recursive: true });
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
const shortcutPath = path.join(startMenuPath, `${
|
|
21
|
+
const shortcutPath = path.join(startMenuPath, `${appName}.lnk`);
|
|
22
22
|
const targetPath = path.join(appDir, 'node_modules', '.bin', 'electron.cmd');
|
|
23
23
|
const workingDir = appDir;
|
|
24
24
|
|
|
@@ -29,11 +29,11 @@ function createWindowsShortcut(appInfo) {
|
|
|
29
29
|
$Shortcut.Arguments = "."
|
|
30
30
|
$Shortcut.WorkingDirectory = "${workingDir.replace(/\\/g, '\\\\')}"
|
|
31
31
|
$Shortcut.IconLocation = "${iconPath.replace(/\\/g, '\\\\')}"
|
|
32
|
-
$Shortcut.Description = "Application Web pour ${
|
|
32
|
+
$Shortcut.Description = "Application Web pour ${appName}"
|
|
33
33
|
$Shortcut.Save()
|
|
34
34
|
`;
|
|
35
35
|
|
|
36
|
-
const tempScriptPath = path.join(os.tmpdir(), `create_shortcut_${
|
|
36
|
+
const tempScriptPath = path.join(os.tmpdir(), `create_shortcut_${appName}.ps1`);
|
|
37
37
|
fs.writeFileSync(tempScriptPath, psScript);
|
|
38
38
|
|
|
39
39
|
execSync(`powershell -ExecutionPolicy Bypass -File "${tempScriptPath}"`, {
|
|
@@ -53,7 +53,7 @@ function createWindowsShortcut(appInfo) {
|
|
|
53
53
|
|
|
54
54
|
function createLinuxDesktopEntry(appInfo) {
|
|
55
55
|
try {
|
|
56
|
-
const {
|
|
56
|
+
const { appName, url, appDir, iconPath } = appInfo;
|
|
57
57
|
const appsDir = path.join(os.homedir(), '.local', 'share', 'applications');
|
|
58
58
|
|
|
59
59
|
if (!fs.existsSync(appsDir)) {
|
|
@@ -62,7 +62,7 @@ function createLinuxDesktopEntry(appInfo) {
|
|
|
62
62
|
|
|
63
63
|
const desktopEntry = `[Desktop Entry]
|
|
64
64
|
Type=Application
|
|
65
|
-
Name=${
|
|
65
|
+
Name=${appName}
|
|
66
66
|
Exec=${path.join(appDir, 'node_modules', '.bin', 'electron')} ${path.join(appDir, 'main.js')}
|
|
67
67
|
Icon=${iconPath}
|
|
68
68
|
Comment=Application Web pour ${url}
|
|
@@ -70,7 +70,7 @@ Categories=Network;WebBrowser;
|
|
|
70
70
|
Terminal=false
|
|
71
71
|
`;
|
|
72
72
|
|
|
73
|
-
const desktopFilePath = path.join(appsDir, `u2a-${
|
|
73
|
+
const desktopFilePath = path.join(appsDir, `u2a-${appName}.desktop`);
|
|
74
74
|
fs.writeFileSync(desktopFilePath, desktopEntry);
|
|
75
75
|
|
|
76
76
|
fs.chmodSync(desktopFilePath, '755');
|
|
@@ -85,15 +85,14 @@ Terminal=false
|
|
|
85
85
|
|
|
86
86
|
function createMacOSApp(appInfo) {
|
|
87
87
|
try {
|
|
88
|
-
const {
|
|
88
|
+
const { appName, appDir, iconPath } = appInfo;
|
|
89
89
|
const appsDir = path.join(os.homedir(), 'Applications', 'U2A Apps');
|
|
90
90
|
|
|
91
91
|
if (!fs.existsSync(appsDir)) {
|
|
92
92
|
fs.mkdirSync(appsDir, { recursive: true });
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
const
|
|
96
|
-
const appPath = path.join(appsDir, appName);
|
|
95
|
+
const appPath = path.join(appsDir, `${appName}.app`);
|
|
97
96
|
const macOsPath = path.join(appPath, 'Contents', 'MacOS');
|
|
98
97
|
const resourcesPath = path.join(appPath, 'Contents', 'Resources');
|
|
99
98
|
|
|
@@ -109,11 +108,11 @@ function createMacOSApp(appInfo) {
|
|
|
109
108
|
<key>CFBundleIconFile</key>
|
|
110
109
|
<string>icon.icns</string>
|
|
111
110
|
<key>CFBundleIdentifier</key>
|
|
112
|
-
<string>com.u2a.${
|
|
111
|
+
<string>com.u2a.${appName.replace(/\s+/g, '-')}</string>
|
|
113
112
|
<key>CFBundleName</key>
|
|
114
|
-
<string>${
|
|
113
|
+
<string>${appName}</string>
|
|
115
114
|
<key>CFBundleDisplayName</key>
|
|
116
|
-
<string>${
|
|
115
|
+
<string>${appName}</string>
|
|
117
116
|
<key>CFBundlePackageType</key>
|
|
118
117
|
<string>APPL</string>
|
|
119
118
|
<key>CFBundleVersion</key>
|
|
@@ -143,8 +142,8 @@ cd "${appDir}"
|
|
|
143
142
|
}
|
|
144
143
|
}
|
|
145
144
|
|
|
146
|
-
function addAppToOS(
|
|
147
|
-
const appInfo = {
|
|
145
|
+
function addAppToOS(appName, url, appDir, iconPath) {
|
|
146
|
+
const appInfo = { appName, url, appDir, iconPath };
|
|
148
147
|
let desktopPath = null;
|
|
149
148
|
|
|
150
149
|
if (process.platform === 'win32') {
|
|
@@ -160,41 +159,46 @@ function addAppToOS(domain, url, appDir, iconPath) {
|
|
|
160
159
|
return desktopPath;
|
|
161
160
|
}
|
|
162
161
|
|
|
163
|
-
function removeAppFromOS(
|
|
162
|
+
function removeAppFromOS(appName) {
|
|
164
163
|
try {
|
|
165
164
|
if (process.platform === 'win32') {
|
|
166
|
-
const startMenuPath = path.join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'U2A Apps', `${
|
|
165
|
+
const startMenuPath = path.join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'U2A Apps', `${appName}.lnk`);
|
|
167
166
|
if (fs.existsSync(startMenuPath)) {
|
|
168
167
|
fs.unlinkSync(startMenuPath);
|
|
169
168
|
logger.success(`Shortcut removed from the Start Menu: ${startMenuPath}`);
|
|
170
169
|
}
|
|
171
170
|
} else if (process.platform === 'darwin') {
|
|
172
|
-
const appPath = path.join(os.homedir(), 'Applications', 'U2A Apps', `${
|
|
171
|
+
const appPath = path.join(os.homedir(), 'Applications', 'U2A Apps', `${appName}.app`);
|
|
173
172
|
if (fs.existsSync(appPath)) {
|
|
174
173
|
fs.rmSync(appPath, { recursive: true, force: true });
|
|
175
174
|
logger.success(`macOS application removed: ${appPath}`);
|
|
176
175
|
}
|
|
177
176
|
} else if (process.platform === 'linux') {
|
|
178
|
-
const desktopFilePath = path.join(os.homedir(), '.local', 'share', 'applications', `u2a-${
|
|
177
|
+
const desktopFilePath = path.join(os.homedir(), '.local', 'share', 'applications', `u2a-${appName}.desktop`);
|
|
179
178
|
if (fs.existsSync(desktopFilePath)) {
|
|
180
179
|
fs.unlinkSync(desktopFilePath);
|
|
181
180
|
logger.success(`Linux desktop entry removed: ${desktopFilePath}`);
|
|
182
181
|
}
|
|
183
182
|
}
|
|
184
183
|
} catch (error) {
|
|
185
|
-
logger.error(`Error while removing desktop integration for ${
|
|
184
|
+
logger.error(`Error while removing desktop integration for ${appName}`, error);
|
|
186
185
|
}
|
|
187
186
|
}
|
|
188
187
|
|
|
189
|
-
function generateMainJs(
|
|
188
|
+
function generateMainJs(appName, url, iconPath, options = {}) {
|
|
189
|
+
const width = options.width || 1200;
|
|
190
|
+
const height = options.height || 800;
|
|
191
|
+
|
|
190
192
|
return `
|
|
191
193
|
const { app, BrowserWindow, Menu, shell } = require('electron');
|
|
192
194
|
const path = require('path');
|
|
193
195
|
const fs = require('fs');
|
|
194
196
|
|
|
195
|
-
const
|
|
197
|
+
const APP_NAME = "${appName}";
|
|
196
198
|
const APP_URL = "${url}";
|
|
197
199
|
const APP_ICON_PATH = "${iconPath.replace(/\\/g, '\\\\')}";
|
|
200
|
+
const WINDOW_WIDTH = ${width};
|
|
201
|
+
const WINDOW_HEIGHT = ${height};
|
|
198
202
|
|
|
199
203
|
let mainWindow;
|
|
200
204
|
let splashWindow;
|
|
@@ -214,7 +218,7 @@ function logAppInfo() {
|
|
|
214
218
|
console.log('\\n--------------------------------');
|
|
215
219
|
console.log(' APPLICATION INFORMATION');
|
|
216
220
|
console.log('--------------------------------');
|
|
217
|
-
console.log(\`Application: \${
|
|
221
|
+
console.log(\`Application: \${APP_NAME}\`);
|
|
218
222
|
console.log(\`URL: \${APP_URL}\`);
|
|
219
223
|
console.log(\`Started at: \${new Date().toLocaleString()}\`);
|
|
220
224
|
console.log(\`App directory: \${__dirname}\`);
|
|
@@ -390,7 +394,7 @@ function createSplashScreen() {
|
|
|
390
394
|
</head>
|
|
391
395
|
<body>
|
|
392
396
|
<div class="container">
|
|
393
|
-
<div class="domain">\${
|
|
397
|
+
<div class="domain">\${APP_NAME}</div>
|
|
394
398
|
<div class="spinner"></div>
|
|
395
399
|
<div id="loading-text" class="loading-text">Loading...</div>
|
|
396
400
|
<div class="progress-bar">
|
|
@@ -413,7 +417,7 @@ function createSplashScreen() {
|
|
|
413
417
|
</html>
|
|
414
418
|
\`;
|
|
415
419
|
|
|
416
|
-
const splashPath = path.join(app.getPath('temp'), \`\${
|
|
420
|
+
const splashPath = path.join(app.getPath('temp'), \`\${APP_NAME}-splash.html\`);
|
|
417
421
|
fs.writeFileSync(splashPath, splashHtml);
|
|
418
422
|
|
|
419
423
|
splashWindow.loadFile(splashPath);
|
|
@@ -423,12 +427,12 @@ function createSplashScreen() {
|
|
|
423
427
|
function createWindow() {
|
|
424
428
|
logAppInfo();
|
|
425
429
|
|
|
426
|
-
app.setAppUserModelId(
|
|
430
|
+
app.setAppUserModelId(APP_NAME);
|
|
427
431
|
|
|
428
432
|
mainWindow = new BrowserWindow({
|
|
429
|
-
width:
|
|
430
|
-
height:
|
|
431
|
-
title:
|
|
433
|
+
width: WINDOW_WIDTH,
|
|
434
|
+
height: WINDOW_HEIGHT,
|
|
435
|
+
title: APP_NAME,
|
|
432
436
|
icon: APP_ICON_PATH,
|
|
433
437
|
show: false,
|
|
434
438
|
webPreferences: {
|
|
@@ -470,7 +474,7 @@ function createWindow() {
|
|
|
470
474
|
});
|
|
471
475
|
|
|
472
476
|
mainWindow.webContents.on('did-start-loading', () => {
|
|
473
|
-
updateSplashScreen('Connecting to ' +
|
|
477
|
+
updateSplashScreen('Connecting to ' + APP_NAME + '...');
|
|
474
478
|
});
|
|
475
479
|
|
|
476
480
|
mainWindow.webContents.on('did-start-navigation', (event, url) => {
|
|
@@ -553,7 +557,10 @@ app.on('activate', () => {
|
|
|
553
557
|
`;
|
|
554
558
|
}
|
|
555
559
|
|
|
556
|
-
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
async function generatePackageJson(appName, iconPath, isExecutable = false, createSetup = false) {
|
|
557
564
|
const u2aPackagePath = path.resolve(__dirname, '../../package.json');
|
|
558
565
|
|
|
559
566
|
let u2aVersion = '1.0.0';
|
|
@@ -562,14 +569,19 @@ function generatePackageJson(domain, iconPath) {
|
|
|
562
569
|
const u2aPackage = JSON.parse(u2aPackageContent);
|
|
563
570
|
u2aVersion = u2aPackage.version || u2aVersion;
|
|
564
571
|
} catch (error) {
|
|
565
|
-
logger.error('Error while fetching u2a package.json', error)
|
|
572
|
+
logger.error('Error while fetching u2a package.json', error);
|
|
566
573
|
}
|
|
567
574
|
|
|
568
|
-
|
|
569
|
-
|
|
575
|
+
if (createSetup) {
|
|
576
|
+
iconPath = await processFavicon(iconPath);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const packageJson = {
|
|
580
|
+
name: `u2a-${appName.replace(/\s+/g, '-')}`,
|
|
570
581
|
version: u2aVersion,
|
|
571
|
-
description: `Web app for ${
|
|
582
|
+
description: `Web app for ${appName}`,
|
|
572
583
|
main: 'main.js',
|
|
584
|
+
author: `${appName}`,
|
|
573
585
|
scripts: {
|
|
574
586
|
start: 'electron .'
|
|
575
587
|
},
|
|
@@ -577,45 +589,243 @@ function generatePackageJson(domain, iconPath) {
|
|
|
577
589
|
electron: '^22.0.0'
|
|
578
590
|
},
|
|
579
591
|
build: {
|
|
580
|
-
appId: `com.u2a.${
|
|
581
|
-
productName:
|
|
592
|
+
appId: `com.u2a.${appName.replace(/\s+/g, '-')}`,
|
|
593
|
+
productName: appName,
|
|
582
594
|
icon: iconPath
|
|
583
595
|
}
|
|
584
596
|
};
|
|
597
|
+
|
|
598
|
+
if (isExecutable) {
|
|
599
|
+
packageJson.devDependencies = {
|
|
600
|
+
"electron-packager": "^17.1.1",
|
|
601
|
+
"electron-builder": "^24.6.3",
|
|
602
|
+
"electron": "^22.0.0"
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
packageJson.dependencies = {};
|
|
606
|
+
|
|
607
|
+
packageJson.scripts.package = "electron-packager . --overwrite --asar";
|
|
608
|
+
packageJson.scripts.setup = "electron-builder";
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
if (isExecutable && createSetup) {
|
|
612
|
+
packageJson.build = {
|
|
613
|
+
...packageJson.build,
|
|
614
|
+
appId: `com.u2a.${appName.replace(/\s+/g, '-')}`,
|
|
615
|
+
productName: appName,
|
|
616
|
+
directories: {
|
|
617
|
+
output: "installer"
|
|
618
|
+
},
|
|
619
|
+
files: [
|
|
620
|
+
"**/*",
|
|
621
|
+
"!**/node_modules/*/{CHANGELOG.md,README.md,README,readme.md,readme}",
|
|
622
|
+
"!**/node_modules/*/{test,__tests__,tests,powered-test,example,examples}",
|
|
623
|
+
"!**/node_modules/*.d.ts",
|
|
624
|
+
"!**/node_modules/.bin",
|
|
625
|
+
"!**/.{idea,git,cache,build,dist}",
|
|
626
|
+
"!dist/**/*",
|
|
627
|
+
"!installer/**/*"
|
|
628
|
+
],
|
|
629
|
+
win: {
|
|
630
|
+
target: "nsis",
|
|
631
|
+
icon: iconPath
|
|
632
|
+
},
|
|
633
|
+
mac: {
|
|
634
|
+
target: "dmg"
|
|
635
|
+
},
|
|
636
|
+
linux: {
|
|
637
|
+
target: "AppImage",
|
|
638
|
+
icon: iconPath
|
|
639
|
+
},
|
|
640
|
+
nsis: {
|
|
641
|
+
oneClick: false,
|
|
642
|
+
allowToChangeInstallationDirectory: true
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
return packageJson;
|
|
585
648
|
}
|
|
586
649
|
|
|
587
|
-
|
|
650
|
+
function copyFolderRecursiveSync(source, target) {
|
|
651
|
+
if (!fs.existsSync(target)) {
|
|
652
|
+
fs.mkdirSync(target, { recursive: true });
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const files = fs.readdirSync(source);
|
|
656
|
+
|
|
657
|
+
files.forEach((file) => {
|
|
658
|
+
const sourcePath = path.join(source, file);
|
|
659
|
+
const targetPath = path.join(target, file);
|
|
660
|
+
|
|
661
|
+
if (fs.lstatSync(sourcePath).isDirectory()) {
|
|
662
|
+
copyFolderRecursiveSync(sourcePath, targetPath);
|
|
663
|
+
} else {
|
|
664
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
async function buildExecutable(appDir, appName, platform, iconPath, options) {
|
|
670
|
+
logger.info(`Building executable for ${platform}...`);
|
|
671
|
+
|
|
672
|
+
try {
|
|
673
|
+
const installOptions = {
|
|
674
|
+
cwd: appDir,
|
|
675
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
676
|
+
windowsHide: true
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
execSync('npm install --save-dev electron-packager electron', installOptions);
|
|
680
|
+
|
|
681
|
+
let platformFlag = '';
|
|
682
|
+
let archFlag = `--arch=${options.arch || 'x64'}`;
|
|
683
|
+
let iconOption = '';
|
|
684
|
+
|
|
685
|
+
switch(platform) {
|
|
686
|
+
case 'windows':
|
|
687
|
+
platformFlag = '--platform=win32';
|
|
688
|
+
iconOption = iconPath ? `--icon="${iconPath}"` : '';
|
|
689
|
+
break;
|
|
690
|
+
case 'darwin':
|
|
691
|
+
platformFlag = '--platform=darwin';
|
|
692
|
+
if (iconPath && !iconPath.endsWith('.icns')) {
|
|
693
|
+
logger.warn('MacOs Icons are not supported at this time.');
|
|
694
|
+
}
|
|
695
|
+
iconOption = iconPath ? `--icon="${iconPath}"` : '';
|
|
696
|
+
break;
|
|
697
|
+
case 'linux':
|
|
698
|
+
platformFlag = '--platform=linux';
|
|
699
|
+
iconOption = iconPath ? `--icon="${iconPath}"` : '';
|
|
700
|
+
break;
|
|
701
|
+
default:
|
|
702
|
+
platformFlag = `--platform=${process.platform}`;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const packageCommand = `npx electron-packager . "${appName}" ${platformFlag} ${archFlag} --out=dist --overwrite --asar ${iconOption}`;
|
|
706
|
+
|
|
707
|
+
logger.debug(`Executing: ${packageCommand}`);
|
|
708
|
+
|
|
709
|
+
execSync(packageCommand, installOptions);
|
|
710
|
+
|
|
711
|
+
let distPlatform = '';
|
|
712
|
+
switch(platform) {
|
|
713
|
+
case 'windows': distPlatform = 'win32'; break;
|
|
714
|
+
case 'darwin': distPlatform = 'darwin'; break;
|
|
715
|
+
case 'linux': distPlatform = 'linux'; break;
|
|
716
|
+
default: distPlatform = process.platform;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
const outputPath = path.join(appDir, 'dist', `${appName}-${distPlatform}-x64`);
|
|
720
|
+
|
|
721
|
+
if (fs.existsSync(outputPath)) {
|
|
722
|
+
logger.debug(`Executable built successfully at: ${outputPath}`);
|
|
723
|
+
return outputPath;
|
|
724
|
+
} else {
|
|
725
|
+
logger.error(`Failed to find the built executable at: ${outputPath}`);
|
|
726
|
+
return null;
|
|
727
|
+
}
|
|
728
|
+
} catch (error) {
|
|
729
|
+
logger.error(`Error while building executable:`, error);
|
|
730
|
+
return null;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
function remove(path) {
|
|
735
|
+
try {
|
|
736
|
+
if (fs.existsSync(path)) {
|
|
737
|
+
fs.rmSync(path, { recursive: true, force: true });
|
|
738
|
+
logger.debug(`Dir/file removed: ${path}`);
|
|
739
|
+
}
|
|
740
|
+
} catch (error) {
|
|
741
|
+
logger.error(`Error while removing dir/file ${path}`, error);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
async function buildSetup(appDir, platform, arch) {
|
|
746
|
+
logger.info(`Building setup for ${platform}${arch ? ` (${arch})` : ''}...`);
|
|
747
|
+
|
|
748
|
+
try {
|
|
749
|
+
const installOptions = {
|
|
750
|
+
cwd: appDir,
|
|
751
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
752
|
+
windowsHide: true
|
|
753
|
+
};
|
|
754
|
+
|
|
755
|
+
execSync('npm install --save-dev electron-builder', installOptions);
|
|
756
|
+
|
|
757
|
+
let builderArgs = '';
|
|
758
|
+
switch(platform) {
|
|
759
|
+
case 'windows':
|
|
760
|
+
builderArgs = '--win';
|
|
761
|
+
break;
|
|
762
|
+
case 'darwin':
|
|
763
|
+
builderArgs = '--mac';
|
|
764
|
+
break;
|
|
765
|
+
case 'linux':
|
|
766
|
+
builderArgs = '--linux';
|
|
767
|
+
break;
|
|
768
|
+
default:
|
|
769
|
+
builderArgs = '';
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
if (arch) {
|
|
773
|
+
builderArgs += ` --${arch}`;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
const builderCommand = `npx electron-builder ${builderArgs}`;
|
|
777
|
+
logger.debug(`Executing: ${builderCommand}`);
|
|
778
|
+
execSync(builderCommand, installOptions);
|
|
779
|
+
|
|
780
|
+
const installerPath = path.join(appDir, 'installer');
|
|
781
|
+
if (fs.existsSync(installerPath)) {
|
|
782
|
+
logger.debug(`Setup created at: ${installerPath}`);
|
|
783
|
+
return installerPath;
|
|
784
|
+
} else {
|
|
785
|
+
logger.error(`Failed to find the built installer at: ${installerPath}`);
|
|
786
|
+
return null;
|
|
787
|
+
}
|
|
788
|
+
} catch (error) {
|
|
789
|
+
logger.error(`Error while building setup:`, error);
|
|
790
|
+
return null;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
async function createApp(url, options) {
|
|
588
795
|
logger.info(`Creating application for ${url}`);
|
|
589
796
|
|
|
590
797
|
try {
|
|
591
798
|
url = await normalizeUrl(url);
|
|
592
799
|
const domain = getDomainName(url);
|
|
800
|
+
const appName = options.name || domain;
|
|
593
801
|
|
|
594
802
|
const db = readDB();
|
|
595
|
-
if (db.hasOwnProperty(
|
|
596
|
-
logger.warn(`Application for ${
|
|
803
|
+
if (db.hasOwnProperty(appName)) {
|
|
804
|
+
logger.warn(`Application for ${appName} already exists`);
|
|
597
805
|
return;
|
|
598
806
|
}
|
|
599
807
|
|
|
600
808
|
const iconPath = await getFavicon(url);
|
|
601
809
|
|
|
602
|
-
const appDir = path.join(APPS_DIR,
|
|
810
|
+
const appDir = path.join(APPS_DIR, appName);
|
|
603
811
|
if (!fs.existsSync(appDir)) {
|
|
604
812
|
fs.mkdirSync(appDir, { recursive: true });
|
|
605
813
|
logger.debug(`Directory created: ${appDir}`);
|
|
606
814
|
}
|
|
607
815
|
|
|
608
816
|
const mainJsPath = path.join(appDir, 'main.js');
|
|
609
|
-
const mainJsContent = generateMainJs(
|
|
817
|
+
const mainJsContent = generateMainJs(appName, url, iconPath, options);
|
|
610
818
|
fs.writeFileSync(mainJsPath, mainJsContent);
|
|
611
819
|
logger.debug(`main.js file created`);
|
|
612
820
|
|
|
821
|
+
const isExecutable = !!options.executable;
|
|
822
|
+
const createSetup = !!options.setup;
|
|
613
823
|
const packageJsonPath = path.join(appDir, 'package.json');
|
|
614
|
-
const packageJsonContent = generatePackageJson(
|
|
824
|
+
const packageJsonContent = await generatePackageJson(appName, iconPath, isExecutable, createSetup);
|
|
615
825
|
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJsonContent, null, 2));
|
|
616
826
|
logger.debug(`package.json file created`);
|
|
617
827
|
|
|
618
|
-
logger.info(`Installing dependencies for ${
|
|
828
|
+
logger.info(`Installing dependencies for ${appName}`);
|
|
619
829
|
|
|
620
830
|
const installOptions = {
|
|
621
831
|
cwd: appDir,
|
|
@@ -623,32 +833,88 @@ async function createApp(url) {
|
|
|
623
833
|
windowsHide: true
|
|
624
834
|
};
|
|
625
835
|
|
|
626
|
-
|
|
627
|
-
logger.debug(`npm install completed
|
|
628
|
-
|
|
629
|
-
|
|
836
|
+
execSync('npm install --only=prod', installOptions);
|
|
837
|
+
logger.debug(`npm install completed`);
|
|
838
|
+
|
|
839
|
+
let executablePath = null;
|
|
840
|
+
let desktopPath = null;
|
|
841
|
+
|
|
842
|
+
if (isExecutable) {
|
|
843
|
+
const targetPlatform = options.executable === true ? process.platform : options.executable;
|
|
844
|
+
executablePath = await buildExecutable(appDir, appName, targetPlatform, iconPath, options);
|
|
845
|
+
|
|
846
|
+
if (options.setup) {
|
|
847
|
+
const setupPath = await buildSetup(appDir, targetPlatform, options.arch);
|
|
848
|
+
if (setupPath) {
|
|
849
|
+
logger.debug(`Setup installer created at: ${setupPath}`);
|
|
850
|
+
|
|
851
|
+
const currentDir = process.cwd();
|
|
852
|
+
const setupTargetDir = path.join(currentDir, `${appName}-setup`);
|
|
853
|
+
|
|
854
|
+
if (!fs.existsSync(setupTargetDir)) {
|
|
855
|
+
fs.mkdirSync(setupTargetDir, { recursive: true });
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
copyFolderRecursiveSync(setupPath, setupTargetDir);
|
|
859
|
+
logger.success(`Setup installer created at: ${setupTargetDir}`);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
if (executablePath) {
|
|
864
|
+
logger.debug(`Executable created at: ${executablePath}`);
|
|
865
|
+
|
|
866
|
+
const currentDir = process.cwd();
|
|
867
|
+
const targetDir = path.join(currentDir, `${appName}-executable`);
|
|
868
|
+
|
|
869
|
+
if (!fs.existsSync(targetDir)) {
|
|
870
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
copyFolderRecursiveSync(executablePath, targetDir);
|
|
874
|
+
|
|
875
|
+
logger.success(`Executable created at: ${targetDir}`);
|
|
876
|
+
|
|
877
|
+
executablePath = targetDir;
|
|
878
|
+
|
|
879
|
+
removeAppFromOS(appName);
|
|
880
|
+
remove(appDir);
|
|
881
|
+
remove(iconPath);
|
|
882
|
+
|
|
883
|
+
logger.debug(`Temporary application files removed after executable creation`);
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
} else {
|
|
887
|
+
desktopPath = addAppToOS(appName, url, appDir, iconPath);
|
|
888
|
+
}
|
|
630
889
|
|
|
631
890
|
const appData = {
|
|
632
891
|
url,
|
|
633
892
|
created: new Date().toISOString(),
|
|
634
893
|
path: appDir,
|
|
635
894
|
icon: iconPath,
|
|
636
|
-
desktopPath
|
|
895
|
+
desktopPath,
|
|
896
|
+
executablePath,
|
|
897
|
+
name: options.name,
|
|
898
|
+
width: options.width,
|
|
899
|
+
height: options.height
|
|
637
900
|
};
|
|
638
901
|
|
|
639
|
-
db[
|
|
902
|
+
db[appName] = appData;
|
|
640
903
|
writeDB(db);
|
|
641
904
|
|
|
642
905
|
logger.success(`Application successfully created for ${url}`);
|
|
643
906
|
if (desktopPath) {
|
|
644
907
|
logger.info(`A shortcut has been created in your system's applications directory`);
|
|
645
908
|
}
|
|
909
|
+
if (executablePath) {
|
|
910
|
+
logger.info(`A standalone executable has been created at: ${executablePath}`);
|
|
911
|
+
}
|
|
646
912
|
} catch (error) {
|
|
647
|
-
logger.error(`Error while creating an application for ${url}
|
|
913
|
+
logger.error(`Error while creating an application for ${url}: ${error}`);
|
|
648
914
|
}
|
|
649
915
|
}
|
|
650
916
|
|
|
651
917
|
module.exports = {
|
|
652
918
|
createApp,
|
|
653
919
|
removeAppFromOS
|
|
654
|
-
};
|
|
920
|
+
};
|
package/src/commands/remove.js
CHANGED
|
@@ -8,13 +8,12 @@ const path = require('path');
|
|
|
8
8
|
|
|
9
9
|
const logger = new Logger('remove');
|
|
10
10
|
|
|
11
|
-
async function removeApp(
|
|
11
|
+
async function removeApp(appName) {
|
|
12
12
|
try {
|
|
13
|
-
const domain = getDomainName(await normalizeUrl(url));
|
|
14
13
|
const db = readDB();
|
|
15
14
|
|
|
16
|
-
if (!db.hasOwnProperty(
|
|
17
|
-
logger.warn(`The application for ${
|
|
15
|
+
if (!db.hasOwnProperty(appName)) {
|
|
16
|
+
logger.warn(`The application for ${appName} does not exist`);
|
|
18
17
|
return;
|
|
19
18
|
}
|
|
20
19
|
|
|
@@ -22,7 +21,7 @@ async function removeApp(url) {
|
|
|
22
21
|
{
|
|
23
22
|
type: 'confirm',
|
|
24
23
|
name: 'confirm',
|
|
25
|
-
message: `Are you sure you want to remove the application for ${
|
|
24
|
+
message: `Are you sure you want to remove the application for ${appName}?`,
|
|
26
25
|
default: false
|
|
27
26
|
}
|
|
28
27
|
]);
|
|
@@ -32,25 +31,25 @@ async function removeApp(url) {
|
|
|
32
31
|
return;
|
|
33
32
|
}
|
|
34
33
|
|
|
35
|
-
const appInfo = db[
|
|
34
|
+
const appInfo = db[appName];
|
|
36
35
|
const appDir = appInfo.path;
|
|
37
36
|
|
|
38
|
-
removeAppFromOS(
|
|
39
|
-
logger.info(`Removing the application ${
|
|
37
|
+
removeAppFromOS(appName);
|
|
38
|
+
logger.info(`Removing the application ${appName}...`);
|
|
40
39
|
|
|
41
|
-
const iconPath = path.join(APPS_DIR, `${
|
|
40
|
+
const iconPath = path.join(APPS_DIR, `${appName}.ico`);
|
|
42
41
|
if (fs.existsSync(iconPath)) {
|
|
43
42
|
fs.unlinkSync(iconPath);
|
|
44
|
-
logger.success(`Icon for ${
|
|
43
|
+
logger.success(`Icon for ${appName} removed`);
|
|
45
44
|
}
|
|
46
45
|
|
|
47
46
|
fs.rmSync(appDir, { recursive: true, force: true });
|
|
48
|
-
delete db[
|
|
47
|
+
delete db[appName];
|
|
49
48
|
writeDB(db);
|
|
50
49
|
|
|
51
|
-
logger.success(`The application for ${
|
|
50
|
+
logger.success(`The application for ${appName} has been successfully removed`);
|
|
52
51
|
} catch (error) {
|
|
53
|
-
logger.error(`Error removing the application ${
|
|
52
|
+
logger.error(`Error removing the application ${appName}`, error);
|
|
54
53
|
}
|
|
55
54
|
}
|
|
56
55
|
|
package/src/index.js
CHANGED
|
@@ -14,10 +14,18 @@ program
|
|
|
14
14
|
.description('Convert websites into desktop applications')
|
|
15
15
|
.version(version);
|
|
16
16
|
|
|
17
|
-
program
|
|
17
|
+
program
|
|
18
18
|
.command('create <url>')
|
|
19
19
|
.description('Create a new application from a URL')
|
|
20
|
-
.
|
|
20
|
+
.option('--name <name>', 'Specify the application name')
|
|
21
|
+
.option('--width <width>', 'Specify the window width', parseInt)
|
|
22
|
+
.option('--height <height>', 'Specify the window height', parseInt)
|
|
23
|
+
.option('--executable [windows|darwin|linux]', 'Create a single executable for the target system')
|
|
24
|
+
.option('--arch [x64|armv7l|arm64|universal]', 'Specify the target architecture for the executable')
|
|
25
|
+
.option('--setup', 'Creates a setup file for the executable')
|
|
26
|
+
.action((url, options) => {
|
|
27
|
+
createApp(url, options);
|
|
28
|
+
});
|
|
21
29
|
|
|
22
30
|
program
|
|
23
31
|
.command('list')
|
package/src/utils/favicon.js
CHANGED
|
@@ -4,6 +4,10 @@ const axios = require('axios');
|
|
|
4
4
|
const { APPS_DIR } = require('./config');
|
|
5
5
|
const { normalizeUrl, getDomainName } = require('./url');
|
|
6
6
|
const Logger = require('./logger');
|
|
7
|
+
const { parseICO} = require('icojs');
|
|
8
|
+
const sharp = require('sharp');
|
|
9
|
+
const pngToIco = require('png-to-ico');
|
|
10
|
+
|
|
7
11
|
|
|
8
12
|
const logger = new Logger('favicon');
|
|
9
13
|
|
|
@@ -19,7 +23,7 @@ async function getFavicon(url) {
|
|
|
19
23
|
const iconResponse = await axios.get(faviconUrl, { responseType: 'arraybuffer' });
|
|
20
24
|
|
|
21
25
|
const contentType = iconResponse.headers['content-type'];
|
|
22
|
-
let fileExtension = '.ico';
|
|
26
|
+
let fileExtension = '.ico';
|
|
23
27
|
if (contentType.includes('png')) {
|
|
24
28
|
fileExtension = '.png';
|
|
25
29
|
} else if (contentType.includes('jpeg') || contentType.includes('jpg')) {
|
|
@@ -44,6 +48,51 @@ async function getFavicon(url) {
|
|
|
44
48
|
}
|
|
45
49
|
}
|
|
46
50
|
|
|
51
|
+
async function processFavicon(iconPath) {
|
|
52
|
+
const dir = path.dirname(iconPath);
|
|
53
|
+
const ext = path.extname(iconPath);
|
|
54
|
+
const baseName = path.basename(iconPath, ext);
|
|
55
|
+
|
|
56
|
+
if (baseName === 'favicon' && ext === '.ico') {
|
|
57
|
+
const newPath = path.join(dir, 'favicon256.ico');
|
|
58
|
+
fs.copyFileSync(iconPath, newPath);
|
|
59
|
+
logger.debug("Default favicon.ico updated to favicon256.ico");
|
|
60
|
+
return newPath;
|
|
61
|
+
} else {
|
|
62
|
+
try {
|
|
63
|
+
const icoBuffer = fs.readFileSync(iconPath);
|
|
64
|
+
const images = await parseICO(icoBuffer, 'image/png');
|
|
65
|
+
|
|
66
|
+
if (images && images.length > 0) {
|
|
67
|
+
const pngBuffer = Buffer.from(images[0].buffer);
|
|
68
|
+
const tempPngPath = iconPath + '.png';
|
|
69
|
+
const resizedPngPath = iconPath + '_resized.png';
|
|
70
|
+
|
|
71
|
+
fs.writeFileSync(tempPngPath, pngBuffer);
|
|
72
|
+
|
|
73
|
+
await sharp(tempPngPath)
|
|
74
|
+
.resize(256, 256)
|
|
75
|
+
.toFile(resizedPngPath);
|
|
76
|
+
|
|
77
|
+
fs.renameSync(resizedPngPath, tempPngPath);
|
|
78
|
+
|
|
79
|
+
const newIcoBuffer = await pngToIco([tempPngPath]);
|
|
80
|
+
fs.writeFileSync(iconPath, newIcoBuffer);
|
|
81
|
+
fs.unlinkSync(tempPngPath);
|
|
82
|
+
|
|
83
|
+
logger.warn(`To proceed to setup, favicon has been resized to 256x256. Quality loss is possible.`);
|
|
84
|
+
return iconPath;
|
|
85
|
+
}
|
|
86
|
+
} catch (error) {
|
|
87
|
+
logger.error('Error processing ICO file:', error);
|
|
88
|
+
return iconPath;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
47
95
|
module.exports = {
|
|
48
|
-
getFavicon
|
|
96
|
+
getFavicon,
|
|
97
|
+
processFavicon
|
|
49
98
|
};
|
|
Binary file
|
package/src/utils/logger.js
CHANGED
|
@@ -41,7 +41,7 @@ class Logger {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
error(message, error = null) {
|
|
44
|
-
const formattedMessage = this._format('ERROR', message);
|
|
44
|
+
const formattedMessage = this._format('ERROR', `${message} ${error}`);
|
|
45
45
|
console.log(chalk.red(formattedMessage));
|
|
46
46
|
this._writeToFile(formattedMessage);
|
|
47
47
|
|