u2a 1.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/LICENSE +674 -0
- package/README.md +123 -0
- package/package.json +32 -0
- package/src/commands/create.js +333 -0
- package/src/commands/list.js +33 -0
- package/src/commands/remove.js +59 -0
- package/src/index.js +42 -0
- package/src/utils/config.js +48 -0
- package/src/utils/favicon.ico +0 -0
- package/src/utils/favicon.js +49 -0
- package/src/utils/logger.js +62 -0
- package/src/utils/url.js +20 -0
package/README.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# U2A (URL to App)
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
**Convert any website into a desktop application with a single command.**
|
|
8
|
+
|
|
9
|
+
U2A is a command-line utility that allows you to transform any web URL into a standalone desktop application using Electron. It works across Windows, macOS, and Linux platforms.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- 🚀 Transform any website into a desktop app with one command
|
|
14
|
+
- 🖥️ Cross-platform support (Windows, macOS, Linux)
|
|
15
|
+
- 🔄 Automatic favicon retrieval for app icons
|
|
16
|
+
- 📋 Easy management of created applications
|
|
17
|
+
- 📊 Detailed logging for troubleshooting
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install -g u2a
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
### Creating an App
|
|
28
|
+
|
|
29
|
+
To create a desktop application from a website:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
u2a create <url>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
```bash
|
|
37
|
+
u2a create github.com
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
This will:
|
|
41
|
+
1. Download the website's favicon (if available)
|
|
42
|
+
2. Create an Electron wrapper application
|
|
43
|
+
3. Add the application to your system menu/launcher
|
|
44
|
+
4. Track the application in the U2A database
|
|
45
|
+
|
|
46
|
+
### Listing Your Apps
|
|
47
|
+
|
|
48
|
+
To see all the applications you've created:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
u2a list
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
This will display a list of all created applications with their details:
|
|
55
|
+
- Domain name
|
|
56
|
+
- Original URL
|
|
57
|
+
- Creation date
|
|
58
|
+
- Application directory
|
|
59
|
+
|
|
60
|
+
### Removing an App
|
|
61
|
+
|
|
62
|
+
To remove an application:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
u2a remove <url>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Example:
|
|
69
|
+
```bash
|
|
70
|
+
u2a remove github.com
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
This will:
|
|
74
|
+
1. Remove the application from your system menu/launcher
|
|
75
|
+
2. Delete the application files
|
|
76
|
+
3. Remove the entry from the U2A database
|
|
77
|
+
|
|
78
|
+
## How It Works
|
|
79
|
+
|
|
80
|
+
U2A creates a minimal Electron application that loads the specified website URL. It:
|
|
81
|
+
|
|
82
|
+
1. Downloads the site's favicon to use as the application icon
|
|
83
|
+
2. Generates a main.js file with Electron configuration
|
|
84
|
+
3. Creates a package.json with necessary dependencies
|
|
85
|
+
4. Installs required Node modules
|
|
86
|
+
5. Adds appropriate desktop integration for your operating system
|
|
87
|
+
6. Maintains a database of created applications for easy management
|
|
88
|
+
|
|
89
|
+
## System Requirements
|
|
90
|
+
|
|
91
|
+
- Node.js 12.0 or higher
|
|
92
|
+
- npm 6.0 or higher
|
|
93
|
+
- Windows, macOS, or Linux
|
|
94
|
+
|
|
95
|
+
## Configuration
|
|
96
|
+
|
|
97
|
+
U2A stores all configuration, application data, and logs in the `.u2a` directory in your home folder:
|
|
98
|
+
|
|
99
|
+
- `~/.u2a/apps/` - Application files
|
|
100
|
+
- `~/.u2a/logs/` - Log files
|
|
101
|
+
- `~/.u2a/db.json` - Application database
|
|
102
|
+
|
|
103
|
+
## Troubleshooting
|
|
104
|
+
|
|
105
|
+
If you encounter any issues, check the log files in the `~/.u2a/logs/` directory. Each component has its own log file with detailed information.
|
|
106
|
+
|
|
107
|
+
Set the `DEBUG` environment variable to see additional debug messages:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
DEBUG=1 u2a create example.com
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## License
|
|
114
|
+
|
|
115
|
+
This project is licensed under the GNU General Public License v3.0 - see the [LICENSE](LICENSE) file for details.
|
|
116
|
+
|
|
117
|
+
## Author
|
|
118
|
+
|
|
119
|
+
Created by [Douxx](https://douxx.tech)
|
|
120
|
+
|
|
121
|
+
## Disclaimer
|
|
122
|
+
|
|
123
|
+
This tool is for personal use only. Always respect the terms of service of websites you convert to desktop apps.
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "u2a",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "URL to App - Turn any URL into a desktop application",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"u2a": "./src/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node src/index.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"cli",
|
|
14
|
+
"webapp",
|
|
15
|
+
"electron",
|
|
16
|
+
"url",
|
|
17
|
+
"desktop",
|
|
18
|
+
"build",
|
|
19
|
+
"application"
|
|
20
|
+
],
|
|
21
|
+
"author": "Douxx",
|
|
22
|
+
"license": "GPL-3.0-only",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"axios": "^1.6.0",
|
|
25
|
+
"chalk": "^4.1.2",
|
|
26
|
+
"commander": "^10.0.0",
|
|
27
|
+
"electron": "^22.0.0",
|
|
28
|
+
"inquirer": "^8.2.5",
|
|
29
|
+
"open": "^8.4.0"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://douxx.tech"
|
|
32
|
+
}
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const { normalizeUrl, getDomainName } = require('../utils/url');
|
|
5
|
+
const { getFavicon } = require('../utils/favicon');
|
|
6
|
+
const { APPS_DIR, readDB, writeDB } = require('../utils/config');
|
|
7
|
+
const Logger = require('../utils/logger');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
const { isContext } = require('vm');
|
|
10
|
+
|
|
11
|
+
const logger = new Logger('create');
|
|
12
|
+
|
|
13
|
+
function createWindowsShortcut(appInfo) {
|
|
14
|
+
try {
|
|
15
|
+
const { domain, appDir, iconPath } = appInfo;
|
|
16
|
+
const startMenuPath = path.join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'U2A Apps');
|
|
17
|
+
|
|
18
|
+
if (!fs.existsSync(startMenuPath)) {
|
|
19
|
+
fs.mkdirSync(startMenuPath, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const shortcutPath = path.join(startMenuPath, `${domain}.lnk`);
|
|
23
|
+
const targetPath = path.join(appDir, 'node_modules', '.bin', 'electron.cmd');
|
|
24
|
+
const workingDir = appDir;
|
|
25
|
+
|
|
26
|
+
const psScript = `
|
|
27
|
+
$WshShell = New-Object -comObject WScript.Shell
|
|
28
|
+
$Shortcut = $WshShell.CreateShortcut("${shortcutPath.replace(/\\/g, '\\\\')}")
|
|
29
|
+
$Shortcut.TargetPath = "${targetPath.replace(/\\/g, '\\\\')}"
|
|
30
|
+
$Shortcut.Arguments = "."
|
|
31
|
+
$Shortcut.WorkingDirectory = "${workingDir.replace(/\\/g, '\\\\')}"
|
|
32
|
+
$Shortcut.IconLocation = "${iconPath.replace(/\\/g, '\\\\')}"
|
|
33
|
+
$Shortcut.Description = "Application Web pour ${domain}"
|
|
34
|
+
$Shortcut.Save()
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
const tempScriptPath = path.join(os.tmpdir(), `create_shortcut_${domain}.ps1`);
|
|
38
|
+
fs.writeFileSync(tempScriptPath, psScript);
|
|
39
|
+
|
|
40
|
+
execSync(`powershell -ExecutionPolicy Bypass -File "${tempScriptPath}"`, {
|
|
41
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
42
|
+
windowsHide: true
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
fs.unlinkSync(tempScriptPath);
|
|
46
|
+
|
|
47
|
+
logger.success(`Shortcut created in the Start Menu: ${shortcutPath}`);
|
|
48
|
+
return shortcutPath;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
logger.error(`Error while creating the Windows shortcut`, error);
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
function createLinuxDesktopEntry(appInfo) {
|
|
57
|
+
try {
|
|
58
|
+
const { domain, url, appDir, iconPath } = appInfo;
|
|
59
|
+
const appsDir = path.join(os.homedir(), '.local', 'share', 'applications');
|
|
60
|
+
|
|
61
|
+
if (!fs.existsSync(appsDir)) {
|
|
62
|
+
fs.mkdirSync(appsDir, { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const desktopEntry = `[Desktop Entry]
|
|
66
|
+
Type=Application
|
|
67
|
+
Name=${domain}
|
|
68
|
+
Exec=${path.join(appDir, 'node_modules', '.bin', 'electron')} ${path.join(appDir, 'main.js')}
|
|
69
|
+
Icon=${iconPath}
|
|
70
|
+
Comment=Application Web pour ${url}
|
|
71
|
+
Categories=Network;WebBrowser;
|
|
72
|
+
Terminal=false
|
|
73
|
+
`;
|
|
74
|
+
|
|
75
|
+
const desktopFilePath = path.join(appsDir, `u2a-${domain}.desktop`);
|
|
76
|
+
fs.writeFileSync(desktopFilePath, desktopEntry);
|
|
77
|
+
|
|
78
|
+
fs.chmodSync(desktopFilePath, '755');
|
|
79
|
+
|
|
80
|
+
logger.success(`Desktop entry created for Linux: ${desktopFilePath}`);
|
|
81
|
+
return desktopFilePath;
|
|
82
|
+
} catch (error) {
|
|
83
|
+
logger.error(`Error while creating the Linux desktop entry`, error);
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function createMacOSApp(appInfo) {
|
|
89
|
+
try {
|
|
90
|
+
const { domain, appDir, iconPath } = appInfo;
|
|
91
|
+
const appsDir = path.join(os.homedir(), 'Applications', 'U2A Apps');
|
|
92
|
+
|
|
93
|
+
if (!fs.existsSync(appsDir)) {
|
|
94
|
+
fs.mkdirSync(appsDir, { recursive: true });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const appName = `${domain}.app`;
|
|
98
|
+
const appPath = path.join(appsDir, appName);
|
|
99
|
+
const macOsPath = path.join(appPath, 'Contents', 'MacOS');
|
|
100
|
+
const resourcesPath = path.join(appPath, 'Contents', 'Resources');
|
|
101
|
+
|
|
102
|
+
fs.mkdirSync(path.join(appPath, 'Contents', 'MacOS'), { recursive: true });
|
|
103
|
+
fs.mkdirSync(resourcesPath, { recursive: true });
|
|
104
|
+
|
|
105
|
+
const infoPlist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
106
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
107
|
+
<plist version="1.0">
|
|
108
|
+
<dict>
|
|
109
|
+
<key>CFBundleExecutable</key>
|
|
110
|
+
<string>AppRunner</string>
|
|
111
|
+
<key>CFBundleIconFile</key>
|
|
112
|
+
<string>icon.icns</string>
|
|
113
|
+
<key>CFBundleIdentifier</key>
|
|
114
|
+
<string>com.u2a.${domain}</string>
|
|
115
|
+
<key>CFBundleName</key>
|
|
116
|
+
<string>${domain}</string>
|
|
117
|
+
<key>CFBundleDisplayName</key>
|
|
118
|
+
<string>${domain}</string>
|
|
119
|
+
<key>CFBundlePackageType</key>
|
|
120
|
+
<string>APPL</string>
|
|
121
|
+
<key>CFBundleVersion</key>
|
|
122
|
+
<string>1.0</string>
|
|
123
|
+
<key>CFBundleShortVersionString</key>
|
|
124
|
+
<string>1.0</string>
|
|
125
|
+
</dict>
|
|
126
|
+
</plist>`;
|
|
127
|
+
|
|
128
|
+
fs.writeFileSync(path.join(appPath, 'Contents', 'Info.plist'), infoPlist);
|
|
129
|
+
|
|
130
|
+
const shellScript = `#!/bin/bash
|
|
131
|
+
cd "${appDir}"
|
|
132
|
+
"${path.join(appDir, 'node_modules', '.bin', 'electron')}" "${path.join(appDir, 'main.js')}"`;
|
|
133
|
+
|
|
134
|
+
const shellScriptPath = path.join(macOsPath, 'AppRunner');
|
|
135
|
+
fs.writeFileSync(shellScriptPath, shellScript);
|
|
136
|
+
fs.chmodSync(shellScriptPath, '755');
|
|
137
|
+
|
|
138
|
+
fs.copyFileSync(iconPath, path.join(resourcesPath, 'icon.icns'));
|
|
139
|
+
|
|
140
|
+
logger.success(`macOS application created: ${appPath}`);
|
|
141
|
+
return appPath;
|
|
142
|
+
} catch (error) {
|
|
143
|
+
logger.error(`Error while creating the macOS application`, error);
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function addAppToOS(domain, url, appDir, iconPath) {
|
|
149
|
+
const appInfo = { domain, url, appDir, iconPath };
|
|
150
|
+
let desktopPath = null;
|
|
151
|
+
|
|
152
|
+
if (process.platform === 'win32') {
|
|
153
|
+
desktopPath = createWindowsShortcut(appInfo);
|
|
154
|
+
} else if (process.platform === 'darwin') {
|
|
155
|
+
desktopPath = createMacOSApp(appInfo);
|
|
156
|
+
} else if (process.platform === 'linux') {
|
|
157
|
+
desktopPath = createLinuxDesktopEntry(appInfo);
|
|
158
|
+
} else {
|
|
159
|
+
logger.warn(`Desktop integration not supported for platform ${process.platform}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return desktopPath;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function removeAppFromOS(domain) {
|
|
166
|
+
try {
|
|
167
|
+
if (process.platform === 'win32') {
|
|
168
|
+
const startMenuPath = path.join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'U2A Apps', `${domain}.lnk`);
|
|
169
|
+
if (fs.existsSync(startMenuPath)) {
|
|
170
|
+
fs.unlinkSync(startMenuPath);
|
|
171
|
+
logger.success(`Shortcut removed from the Start Menu: ${startMenuPath}`);
|
|
172
|
+
}
|
|
173
|
+
} else if (process.platform === 'darwin') {
|
|
174
|
+
const appPath = path.join(os.homedir(), 'Applications', 'U2A Apps', `${domain}.app`);
|
|
175
|
+
if (fs.existsSync(appPath)) {
|
|
176
|
+
fs.rmSync(appPath, { recursive: true, force: true });
|
|
177
|
+
logger.success(`macOS application removed: ${appPath}`);
|
|
178
|
+
}
|
|
179
|
+
} else if (process.platform === 'linux') {
|
|
180
|
+
const desktopFilePath = path.join(os.homedir(), '.local', 'share', 'applications', `u2a-${domain}.desktop`);
|
|
181
|
+
if (fs.existsSync(desktopFilePath)) {
|
|
182
|
+
fs.unlinkSync(desktopFilePath);
|
|
183
|
+
logger.success(`Linux desktop entry removed: ${desktopFilePath}
|
|
184
|
+
|
|
185
|
+
`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
} catch (error) {
|
|
189
|
+
logger.error(`Error while removing desktop integration for ${domain}`, error);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function generateMainJs(domain, url, iconPath) {
|
|
194
|
+
return `
|
|
195
|
+
const { app, BrowserWindow, Menu, shell } = require('electron');
|
|
196
|
+
const path = require('path');
|
|
197
|
+
|
|
198
|
+
let mainWindow;
|
|
199
|
+
|
|
200
|
+
function createWindow() {
|
|
201
|
+
app.setAppUserModelId("${domain}");
|
|
202
|
+
|
|
203
|
+
mainWindow = new BrowserWindow({
|
|
204
|
+
width: 1200,
|
|
205
|
+
height: 800,
|
|
206
|
+
title: "${domain}",
|
|
207
|
+
icon: "${iconPath.replace(/\\/g, '\\\\')}",
|
|
208
|
+
webPreferences: {
|
|
209
|
+
nodeIntegration: false,
|
|
210
|
+
contextIsolation: true
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
mainWindow.loadURL("${url}");
|
|
215
|
+
|
|
216
|
+
mainWindow.webContents.on('new-window', (event, url) => {
|
|
217
|
+
event.preventDefault();
|
|
218
|
+
shell.openExternal(url);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
const template = [];
|
|
222
|
+
|
|
223
|
+
const menu = Menu.buildFromTemplate(template);
|
|
224
|
+
Menu.setApplicationMenu(menu);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (process.platform === 'win32') {
|
|
228
|
+
app.setAppUserModelId(app.name);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
app.whenReady().then(createWindow);
|
|
232
|
+
|
|
233
|
+
app.on('window-all-closed', () => {
|
|
234
|
+
if (process.platform !== 'darwin') {
|
|
235
|
+
app.quit();
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
app.on('activate', () => {
|
|
240
|
+
if (BrowserWindow.getAllWindows().length === 0) {
|
|
241
|
+
createWindow();
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
`;
|
|
245
|
+
}
|
|
246
|
+
function generatePackageJson(domain, iconPath) {
|
|
247
|
+
return {
|
|
248
|
+
name: `u2a-${domain}`,
|
|
249
|
+
version: '1.0.0',
|
|
250
|
+
description: `Web app for ${domain}`,
|
|
251
|
+
main: 'main.js',
|
|
252
|
+
scripts: {
|
|
253
|
+
start: 'electron .'
|
|
254
|
+
},
|
|
255
|
+
dependencies: {
|
|
256
|
+
electron: '^22.0.0'
|
|
257
|
+
},
|
|
258
|
+
build: {
|
|
259
|
+
appId: `com.u2a.${domain.replace(/\./g, '-')}`,
|
|
260
|
+
productName: domain,
|
|
261
|
+
icon: iconPath
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async function createApp(url) {
|
|
267
|
+
logger.info(`Creating application for ${url}`);
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
url = normalizeUrl(url);
|
|
271
|
+
const domain = getDomainName(url);
|
|
272
|
+
|
|
273
|
+
const db = readDB();
|
|
274
|
+
if (db.hasOwnProperty(domain)) {
|
|
275
|
+
logger.warn(`Application for ${domain} already exists`);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const iconPath = await getFavicon(url);
|
|
280
|
+
|
|
281
|
+
const appDir = path.join(APPS_DIR, domain);
|
|
282
|
+
if (!fs.existsSync(appDir)) {
|
|
283
|
+
fs.mkdirSync(appDir, { recursive: true });
|
|
284
|
+
logger.debug(`Directory created: ${appDir}`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const mainJsPath = path.join(appDir, 'main.js');
|
|
288
|
+
const mainJsContent = generateMainJs(domain, url, iconPath);
|
|
289
|
+
fs.writeFileSync(mainJsPath, mainJsContent);
|
|
290
|
+
logger.debug(`main.js file created`);
|
|
291
|
+
|
|
292
|
+
const packageJsonPath = path.join(appDir, 'package.json');
|
|
293
|
+
const packageJsonContent = generatePackageJson(domain, iconPath);
|
|
294
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJsonContent, null, 2));
|
|
295
|
+
logger.debug(`package.json file created`);
|
|
296
|
+
|
|
297
|
+
logger.info(`Installing dependencies for ${domain}`);
|
|
298
|
+
|
|
299
|
+
const installOptions = {
|
|
300
|
+
cwd: appDir,
|
|
301
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
302
|
+
windowsHide: true
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const stdout = execSync('npm install --only=prod', installOptions);
|
|
306
|
+
logger.debug(`npm install completed: ${stdout.toString().trim()}`);
|
|
307
|
+
|
|
308
|
+
const desktopPath = addAppToOS(domain, url, appDir, iconPath);
|
|
309
|
+
|
|
310
|
+
const appData = {
|
|
311
|
+
url,
|
|
312
|
+
created: new Date().toISOString(),
|
|
313
|
+
path: appDir,
|
|
314
|
+
icon: iconPath,
|
|
315
|
+
desktopPath
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
db[domain] = appData;
|
|
319
|
+
writeDB(db);
|
|
320
|
+
|
|
321
|
+
logger.success(`Application successfully created for ${url}`);
|
|
322
|
+
if (desktopPath) {
|
|
323
|
+
logger.info(`A shortcut has been created in your system's applications directory`);
|
|
324
|
+
}
|
|
325
|
+
} catch (error) {
|
|
326
|
+
logger.error(`Error while creating an application for ${url}`, error);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
module.exports = {
|
|
331
|
+
createApp,
|
|
332
|
+
removeAppFromOS
|
|
333
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const { readDB } = require('../utils/config');
|
|
2
|
+
const Logger = require('../utils/logger');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
|
|
5
|
+
const logger = new Logger('list');
|
|
6
|
+
|
|
7
|
+
function listApps() {
|
|
8
|
+
const apps = readDB();
|
|
9
|
+
|
|
10
|
+
if (Object.keys(apps).length === 0) {
|
|
11
|
+
logger.warn('No applications have been created');
|
|
12
|
+
logger.info('Create one with: u2a create <url>');
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
logger.info('Available applications:');
|
|
17
|
+
console.log(chalk.gray('--------------------------------'));
|
|
18
|
+
|
|
19
|
+
Object.entries(apps).forEach(([domain, info]) => {
|
|
20
|
+
logger.debug(`Retrieved information for ${domain}:`, info);
|
|
21
|
+
console.log(chalk.green(`\n${domain}:`));
|
|
22
|
+
console.log(chalk.blue(` URL: ${info.url}`));
|
|
23
|
+
console.log(chalk.blue(` Created on: ${new Date(info.created).toLocaleString()}`));
|
|
24
|
+
console.log(chalk.blue(` Directory: ${info.path}`));
|
|
25
|
+
console.log(chalk.gray(' ----------'));
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
logger.info('To remove an application: u2a remove <domain>');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = {
|
|
32
|
+
listApps
|
|
33
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const inquirer = require('inquirer');
|
|
3
|
+
const { normalizeUrl, getDomainName } = require('../utils/url');
|
|
4
|
+
const { readDB, writeDB, APPS_DIR } = require('../utils/config');
|
|
5
|
+
const Logger = require('../utils/logger');
|
|
6
|
+
const { removeAppFromOS } = require('./create');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const logger = new Logger('remove');
|
|
10
|
+
|
|
11
|
+
async function removeApp(url) {
|
|
12
|
+
try {
|
|
13
|
+
const domain = getDomainName(normalizeUrl(url));
|
|
14
|
+
const db = readDB();
|
|
15
|
+
|
|
16
|
+
if (!db.hasOwnProperty(domain)) {
|
|
17
|
+
logger.warn(`The application for ${domain} does not exist`);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { confirm } = await inquirer.prompt([
|
|
22
|
+
{
|
|
23
|
+
type: 'confirm',
|
|
24
|
+
name: 'confirm',
|
|
25
|
+
message: `Are you sure you want to remove the application for ${domain}?`,
|
|
26
|
+
default: false
|
|
27
|
+
}
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
if (!confirm) {
|
|
31
|
+
logger.info('Operation canceled');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const appInfo = db[domain];
|
|
36
|
+
const appDir = appInfo.path;
|
|
37
|
+
|
|
38
|
+
removeAppFromOS(domain);
|
|
39
|
+
logger.info(`Removing the application ${domain}...`);
|
|
40
|
+
|
|
41
|
+
const iconPath = path.join(APPS_DIR, `${domain}.ico`);
|
|
42
|
+
if (fs.existsSync(iconPath)) {
|
|
43
|
+
fs.unlinkSync(iconPath);
|
|
44
|
+
logger.success(`Icon for ${domain} removed`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fs.rmSync(appDir, { recursive: true, force: true });
|
|
48
|
+
delete db[domain];
|
|
49
|
+
writeDB(db);
|
|
50
|
+
|
|
51
|
+
logger.success(`The application for ${domain} has been successfully removed`);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
logger.error(`Error removing the application ${url}`, error);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = {
|
|
58
|
+
removeApp
|
|
59
|
+
};
|
package/src/index.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { program } = require('commander');
|
|
4
|
+
const { createApp } = require('./commands/create');
|
|
5
|
+
const { listApps } = require('./commands/list');
|
|
6
|
+
const { removeApp } = require('./commands/remove');
|
|
7
|
+
const { version } = require('../package.json');
|
|
8
|
+
const { setupConfig } = require('./utils/config');
|
|
9
|
+
|
|
10
|
+
setupConfig();
|
|
11
|
+
|
|
12
|
+
program
|
|
13
|
+
.name('u2a')
|
|
14
|
+
.description('Convert websites into desktop applications')
|
|
15
|
+
.version(version);
|
|
16
|
+
|
|
17
|
+
program
|
|
18
|
+
.command('create <url>')
|
|
19
|
+
.description('Create a new application from a URL')
|
|
20
|
+
.action(createApp);
|
|
21
|
+
|
|
22
|
+
program
|
|
23
|
+
.command('list')
|
|
24
|
+
.description('List all available applications')
|
|
25
|
+
.action(listApps);
|
|
26
|
+
|
|
27
|
+
program
|
|
28
|
+
.command('remove <url>')
|
|
29
|
+
.description('Remove an existing application')
|
|
30
|
+
.action(removeApp);
|
|
31
|
+
|
|
32
|
+
program.on('command:*', () => {
|
|
33
|
+
console.error(`\nInvalid command: ${program.args.join(' ')}`);
|
|
34
|
+
console.log(`\nUse --help to see the list of available commands.`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
program.parse(process.argv);
|
|
39
|
+
|
|
40
|
+
if (process.argv.length <= 2) {
|
|
41
|
+
program.help();
|
|
42
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
const CONFIG_DIR = path.join(os.homedir(), '.u2a');
|
|
6
|
+
const APPS_DIR = path.join(CONFIG_DIR, 'apps');
|
|
7
|
+
const LOGS_DIR = path.join(CONFIG_DIR, 'logs');
|
|
8
|
+
const DB_PATH = path.join(CONFIG_DIR, 'db.json');
|
|
9
|
+
|
|
10
|
+
function setupConfig() {
|
|
11
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
12
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
13
|
+
}
|
|
14
|
+
if (!fs.existsSync(APPS_DIR)) {
|
|
15
|
+
fs.mkdirSync(APPS_DIR, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
if (!fs.existsSync(LOGS_DIR)) {
|
|
18
|
+
fs.mkdirSync(LOGS_DIR, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
if (!fs.existsSync(DB_PATH)) {
|
|
21
|
+
fs.writeFileSync(DB_PATH, JSON.stringify({}, null, 2));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function readDB() {
|
|
26
|
+
return JSON.parse(fs.readFileSync(DB_PATH, 'utf-8'));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function writeDB(data) {
|
|
30
|
+
fs.writeFileSync(DB_PATH, JSON.stringify(data, null, 2));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function addAppToDB(domain, appData) {
|
|
34
|
+
const db = readDB();
|
|
35
|
+
db[domain] = appData;
|
|
36
|
+
writeDB(db);
|
|
37
|
+
logger.debug(`Application recorded in the database:`, domain);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = {
|
|
41
|
+
CONFIG_DIR,
|
|
42
|
+
APPS_DIR,
|
|
43
|
+
LOGS_DIR,
|
|
44
|
+
setupConfig,
|
|
45
|
+
readDB,
|
|
46
|
+
writeDB,
|
|
47
|
+
addAppToDB
|
|
48
|
+
};
|
|
Binary file
|