u2a 2.1.5 → 2.1.8

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 CHANGED
@@ -1,10 +1,10 @@
1
1
  <div align="center">
2
2
  <a href="#" 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;"
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
- - Domain name
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 <url>
76
+ u2a remove <appName>
76
77
  ```
77
78
 
78
79
  Example:
79
80
  ```bash
80
- u2a remove github.com
81
+ u2a remove "GitHub App"
81
82
  ```
82
83
 
83
84
  This will:
@@ -127,7 +128,3 @@ This project is licensed under the GNU General Public License v3.0 - see the [LI
127
128
  ## Author
128
129
 
129
130
  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,6 +1,6 @@
1
1
  {
2
2
  "name": "u2a",
3
- "version": "2.1.5",
3
+ "version": "2.1.8",
4
4
  "description": "URL to App - Turn any URL into a desktop application",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -11,14 +11,14 @@ const logger = new Logger('create');
11
11
 
12
12
  function createWindowsShortcut(appInfo) {
13
13
  try {
14
- const { domain, appDir, iconPath } = appInfo;
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, `${domain}.lnk`);
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 ${domain}"
32
+ $Shortcut.Description = "Application Web pour ${appName}"
33
33
  $Shortcut.Save()
34
34
  `;
35
35
 
36
- const tempScriptPath = path.join(os.tmpdir(), `create_shortcut_${domain}.ps1`);
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}"`, {
@@ -51,10 +51,9 @@ function createWindowsShortcut(appInfo) {
51
51
  }
52
52
  }
53
53
 
54
-
55
54
  function createLinuxDesktopEntry(appInfo) {
56
55
  try {
57
- const { domain, url, appDir, iconPath } = appInfo;
56
+ const { appName, url, appDir, iconPath } = appInfo;
58
57
  const appsDir = path.join(os.homedir(), '.local', 'share', 'applications');
59
58
 
60
59
  if (!fs.existsSync(appsDir)) {
@@ -63,7 +62,7 @@ function createLinuxDesktopEntry(appInfo) {
63
62
 
64
63
  const desktopEntry = `[Desktop Entry]
65
64
  Type=Application
66
- Name=${domain}
65
+ Name=${appName}
67
66
  Exec=${path.join(appDir, 'node_modules', '.bin', 'electron')} ${path.join(appDir, 'main.js')}
68
67
  Icon=${iconPath}
69
68
  Comment=Application Web pour ${url}
@@ -71,7 +70,7 @@ Categories=Network;WebBrowser;
71
70
  Terminal=false
72
71
  `;
73
72
 
74
- const desktopFilePath = path.join(appsDir, `u2a-${domain}.desktop`);
73
+ const desktopFilePath = path.join(appsDir, `u2a-${appName}.desktop`);
75
74
  fs.writeFileSync(desktopFilePath, desktopEntry);
76
75
 
77
76
  fs.chmodSync(desktopFilePath, '755');
@@ -86,15 +85,14 @@ Terminal=false
86
85
 
87
86
  function createMacOSApp(appInfo) {
88
87
  try {
89
- const { domain, appDir, iconPath } = appInfo;
88
+ const { appName, appDir, iconPath } = appInfo;
90
89
  const appsDir = path.join(os.homedir(), 'Applications', 'U2A Apps');
91
90
 
92
91
  if (!fs.existsSync(appsDir)) {
93
92
  fs.mkdirSync(appsDir, { recursive: true });
94
93
  }
95
94
 
96
- const appName = `${domain}.app`;
97
- const appPath = path.join(appsDir, appName);
95
+ const appPath = path.join(appsDir, `${appName}.app`);
98
96
  const macOsPath = path.join(appPath, 'Contents', 'MacOS');
99
97
  const resourcesPath = path.join(appPath, 'Contents', 'Resources');
100
98
 
@@ -110,11 +108,11 @@ function createMacOSApp(appInfo) {
110
108
  <key>CFBundleIconFile</key>
111
109
  <string>icon.icns</string>
112
110
  <key>CFBundleIdentifier</key>
113
- <string>com.u2a.${domain}</string>
111
+ <string>com.u2a.${appName.replace(/\s+/g, '-')}</string>
114
112
  <key>CFBundleName</key>
115
- <string>${domain}</string>
113
+ <string>${appName}</string>
116
114
  <key>CFBundleDisplayName</key>
117
- <string>${domain}</string>
115
+ <string>${appName}</string>
118
116
  <key>CFBundlePackageType</key>
119
117
  <string>APPL</string>
120
118
  <key>CFBundleVersion</key>
@@ -144,8 +142,8 @@ cd "${appDir}"
144
142
  }
145
143
  }
146
144
 
147
- function addAppToOS(domain, url, appDir, iconPath) {
148
- const appInfo = { domain, url, appDir, iconPath };
145
+ function addAppToOS(appName, url, appDir, iconPath) {
146
+ const appInfo = { appName, url, appDir, iconPath };
149
147
  let desktopPath = null;
150
148
 
151
149
  if (process.platform === 'win32') {
@@ -161,43 +159,46 @@ function addAppToOS(domain, url, appDir, iconPath) {
161
159
  return desktopPath;
162
160
  }
163
161
 
164
- function removeAppFromOS(domain) {
162
+ function removeAppFromOS(appName) {
165
163
  try {
166
164
  if (process.platform === 'win32') {
167
- const startMenuPath = path.join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'U2A Apps', `${domain}.lnk`);
165
+ const startMenuPath = path.join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'U2A Apps', `${appName}.lnk`);
168
166
  if (fs.existsSync(startMenuPath)) {
169
167
  fs.unlinkSync(startMenuPath);
170
168
  logger.success(`Shortcut removed from the Start Menu: ${startMenuPath}`);
171
169
  }
172
170
  } else if (process.platform === 'darwin') {
173
- const appPath = path.join(os.homedir(), 'Applications', 'U2A Apps', `${domain}.app`);
171
+ const appPath = path.join(os.homedir(), 'Applications', 'U2A Apps', `${appName}.app`);
174
172
  if (fs.existsSync(appPath)) {
175
173
  fs.rmSync(appPath, { recursive: true, force: true });
176
174
  logger.success(`macOS application removed: ${appPath}`);
177
175
  }
178
176
  } else if (process.platform === 'linux') {
179
- const desktopFilePath = path.join(os.homedir(), '.local', 'share', 'applications', `u2a-${domain}.desktop`);
177
+ const desktopFilePath = path.join(os.homedir(), '.local', 'share', 'applications', `u2a-${appName}.desktop`);
180
178
  if (fs.existsSync(desktopFilePath)) {
181
179
  fs.unlinkSync(desktopFilePath);
182
- logger.success(`Linux desktop entry removed: ${desktopFilePath}
183
-
184
- `);
180
+ logger.success(`Linux desktop entry removed: ${desktopFilePath}`);
185
181
  }
186
182
  }
187
183
  } catch (error) {
188
- logger.error(`Error while removing desktop integration for ${domain}`, error);
184
+ logger.error(`Error while removing desktop integration for ${appName}`, error);
189
185
  }
190
186
  }
191
187
 
192
- function generateMainJs(domain, url, iconPath) {
188
+ function generateMainJs(appName, url, iconPath, options = {}) {
189
+ const width = options.width || 1200;
190
+ const height = options.height || 800;
191
+
193
192
  return `
194
193
  const { app, BrowserWindow, Menu, shell } = require('electron');
195
194
  const path = require('path');
196
195
  const fs = require('fs');
197
196
 
198
- const APP_DOMAIN = "${domain}";
197
+ const APP_NAME = "${appName}";
199
198
  const APP_URL = "${url}";
200
199
  const APP_ICON_PATH = "${iconPath.replace(/\\/g, '\\\\')}";
200
+ const WINDOW_WIDTH = ${width};
201
+ const WINDOW_HEIGHT = ${height};
201
202
 
202
203
  let mainWindow;
203
204
  let splashWindow;
@@ -217,7 +218,7 @@ function logAppInfo() {
217
218
  console.log('\\n--------------------------------');
218
219
  console.log(' APPLICATION INFORMATION');
219
220
  console.log('--------------------------------');
220
- console.log(\`Application: \${APP_DOMAIN}\`);
221
+ console.log(\`Application: \${APP_NAME}\`);
221
222
  console.log(\`URL: \${APP_URL}\`);
222
223
  console.log(\`Started at: \${new Date().toLocaleString()}\`);
223
224
  console.log(\`App directory: \${__dirname}\`);
@@ -240,17 +241,14 @@ function logAppInfo() {
240
241
 
241
242
  function updateSplashScreen(message, isError = false) {
242
243
  if (splashWindow && !splashWindow.isDestroyed()) {
243
- const script = \`
244
- document.getElementById('loading-text').innerText = "\${message.replace(/"/g, '\\"')}";
245
- \${isError ?
246
- \`document.getElementById('loading-text').classList.add('error');
247
- document.getElementById('errors-container').style.display = 'block';
248
- const errorItem = document.createElement('li');
249
- errorItem.textContent = "\${message.replace(/"/g, '\\"')}";
250
- document.getElementById('errors-list').appendChild(errorItem);\`
251
- : ''}
252
- \`;
253
- splashWindow.webContents.executeJavaScript(script).catch(err => console.error('Failed to update splash screen:', err));
244
+ splashWindow.webContents.executeJavaScript(\`
245
+ try {
246
+ document.getElementById('loading-text').innerText = "\${message.replace(/"/g, '\\"')}";
247
+ } catch (e) {
248
+ console.error('Failed to update splash screen:', e);
249
+ }
250
+ \`).catch(err => console.error('Failed to update splash screen:', err));
251
+
254
252
  }
255
253
  }
256
254
 
@@ -273,6 +271,7 @@ function createSplashScreen() {
273
271
  <html>
274
272
  <head>
275
273
  <meta charset="UTF-8">
274
+ <meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'; object-src 'none';">
276
275
  <title>Loading...</title>
277
276
  <style>
278
277
  body {
@@ -395,7 +394,7 @@ function createSplashScreen() {
395
394
  </head>
396
395
  <body>
397
396
  <div class="container">
398
- <div class="domain">\${APP_DOMAIN}</div>
397
+ <div class="domain">\${APP_NAME}</div>
399
398
  <div class="spinner"></div>
400
399
  <div id="loading-text" class="loading-text">Loading...</div>
401
400
  <div class="progress-bar">
@@ -418,7 +417,7 @@ function createSplashScreen() {
418
417
  </html>
419
418
  \`;
420
419
 
421
- const splashPath = path.join(app.getPath('temp'), \`\${APP_DOMAIN}-splash.html\`);
420
+ const splashPath = path.join(app.getPath('temp'), \`\${APP_NAME}-splash.html\`);
422
421
  fs.writeFileSync(splashPath, splashHtml);
423
422
 
424
423
  splashWindow.loadFile(splashPath);
@@ -428,12 +427,12 @@ function createSplashScreen() {
428
427
  function createWindow() {
429
428
  logAppInfo();
430
429
 
431
- app.setAppUserModelId(APP_DOMAIN);
430
+ app.setAppUserModelId(APP_NAME);
432
431
 
433
432
  mainWindow = new BrowserWindow({
434
- width: 1200,
435
- height: 800,
436
- title: APP_DOMAIN,
433
+ width: WINDOW_WIDTH,
434
+ height: WINDOW_HEIGHT,
435
+ title: APP_NAME,
437
436
  icon: APP_ICON_PATH,
438
437
  show: false,
439
438
  webPreferences: {
@@ -444,7 +443,6 @@ function createWindow() {
444
443
 
445
444
  mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription, validatedURL, isMainFrame) => {
446
445
  const errorMessage = \`Load error (\${errorCode}): \${errorDescription}\`;
447
- console.error(errorMessage);
448
446
  loadErrors.push(errorMessage);
449
447
 
450
448
  updateSplashScreen(errorMessage, true);
@@ -469,7 +467,6 @@ function createWindow() {
469
467
  mainWindow.webContents.on('console-message', (event, level, message, line, sourceId) => {
470
468
  if (level === 2) {
471
469
  const errorMessage = \`Console: \${message} (line \${line})\`;
472
- console.error(errorMessage);
473
470
  loadErrors.push(errorMessage);
474
471
 
475
472
  updateSplashScreen('JavaScript error detected', true);
@@ -477,7 +474,7 @@ function createWindow() {
477
474
  });
478
475
 
479
476
  mainWindow.webContents.on('did-start-loading', () => {
480
- updateSplashScreen('Connecting to ' + APP_DOMAIN + '...');
477
+ updateSplashScreen('Connecting to ' + APP_NAME + '...');
481
478
  });
482
479
 
483
480
  mainWindow.webContents.on('did-start-navigation', (event, url) => {
@@ -560,11 +557,9 @@ app.on('activate', () => {
560
557
  `;
561
558
  }
562
559
 
563
-
564
-
565
- function generatePackageJson(domain, iconPath) {
560
+ function generatePackageJson(appName, iconPath) {
566
561
  const u2aPackagePath = path.resolve(__dirname, '../../package.json');
567
-
562
+
568
563
  let u2aVersion = '1.0.0';
569
564
  try {
570
565
  const u2aPackageContent = fs.readFileSync(u2aPackagePath, 'utf8');
@@ -573,11 +568,11 @@ function generatePackageJson(domain, iconPath) {
573
568
  } catch (error) {
574
569
  logger.error('Error while fetching u2a package.json', error)
575
570
  }
576
-
571
+
577
572
  return {
578
- name: `u2a-${domain}`,
573
+ name: `u2a-${appName.replace(/\s+/g, '-')}`,
579
574
  version: u2aVersion,
580
- description: `Web app for ${domain}`,
575
+ description: `Web app for ${appName}`,
581
576
  main: 'main.js',
582
577
  scripts: {
583
578
  start: 'electron .'
@@ -586,45 +581,46 @@ function generatePackageJson(domain, iconPath) {
586
581
  electron: '^22.0.0'
587
582
  },
588
583
  build: {
589
- appId: `com.u2a.${domain.replace(/\./g, '-')}`,
590
- productName: domain,
584
+ appId: `com.u2a.${appName.replace(/\s+/g, '-')}`,
585
+ productName: appName,
591
586
  icon: iconPath
592
587
  }
593
588
  };
594
589
  }
595
590
 
596
- async function createApp(url) {
591
+ async function createApp(url, options) {
597
592
  logger.info(`Creating application for ${url}`);
598
593
 
599
594
  try {
600
595
  url = await normalizeUrl(url);
601
596
  const domain = getDomainName(url);
597
+ const appName = options.name || domain;
602
598
 
603
599
  const db = readDB();
604
- if (db.hasOwnProperty(domain)) {
605
- logger.warn(`Application for ${domain} already exists`);
600
+ if (db.hasOwnProperty(appName)) {
601
+ logger.warn(`Application for ${appName} already exists`);
606
602
  return;
607
603
  }
608
604
 
609
605
  const iconPath = await getFavicon(url);
610
606
 
611
- const appDir = path.join(APPS_DIR, domain);
607
+ const appDir = path.join(APPS_DIR, appName);
612
608
  if (!fs.existsSync(appDir)) {
613
609
  fs.mkdirSync(appDir, { recursive: true });
614
610
  logger.debug(`Directory created: ${appDir}`);
615
611
  }
616
612
 
617
613
  const mainJsPath = path.join(appDir, 'main.js');
618
- const mainJsContent = generateMainJs(domain, url, iconPath);
614
+ const mainJsContent = generateMainJs(appName, url, iconPath, options);
619
615
  fs.writeFileSync(mainJsPath, mainJsContent);
620
616
  logger.debug(`main.js file created`);
621
617
 
622
618
  const packageJsonPath = path.join(appDir, 'package.json');
623
- const packageJsonContent = generatePackageJson(domain, iconPath);
619
+ const packageJsonContent = generatePackageJson(appName, iconPath);
624
620
  fs.writeFileSync(packageJsonPath, JSON.stringify(packageJsonContent, null, 2));
625
621
  logger.debug(`package.json file created`);
626
622
 
627
- logger.info(`Installing dependencies for ${domain}`);
623
+ logger.info(`Installing dependencies for ${appName}`);
628
624
 
629
625
  const installOptions = {
630
626
  cwd: appDir,
@@ -635,17 +631,20 @@ async function createApp(url) {
635
631
  const stdout = execSync('npm install --only=prod', installOptions);
636
632
  logger.debug(`npm install completed: ${stdout.toString().trim()}`);
637
633
 
638
- const desktopPath = addAppToOS(domain, url, appDir, iconPath);
634
+ const desktopPath = addAppToOS(appName, url, appDir, iconPath);
639
635
 
640
636
  const appData = {
641
637
  url,
642
638
  created: new Date().toISOString(),
643
639
  path: appDir,
644
640
  icon: iconPath,
645
- desktopPath
641
+ desktopPath,
642
+ name: options.name,
643
+ width: options.width,
644
+ height: options.height
646
645
  };
647
646
 
648
- db[domain] = appData;
647
+ db[appName] = appData;
649
648
  writeDB(db);
650
649
 
651
650
  logger.success(`Application successfully created for ${url}`);
@@ -8,13 +8,12 @@ const path = require('path');
8
8
 
9
9
  const logger = new Logger('remove');
10
10
 
11
- async function removeApp(url) {
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(domain)) {
17
- logger.warn(`The application for ${domain} does not exist`);
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 ${domain}?`,
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[domain];
34
+ const appInfo = db[appName];
36
35
  const appDir = appInfo.path;
37
36
 
38
- removeAppFromOS(domain);
39
- logger.info(`Removing the application ${domain}...`);
37
+ removeAppFromOS(appName);
38
+ logger.info(`Removing the application ${appName}...`);
40
39
 
41
- const iconPath = path.join(APPS_DIR, `${domain}.ico`);
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 ${domain} removed`);
43
+ logger.success(`Icon for ${appName} removed`);
45
44
  }
46
45
 
47
46
  fs.rmSync(appDir, { recursive: true, force: true });
48
- delete db[domain];
47
+ delete db[appName];
49
48
  writeDB(db);
50
49
 
51
- logger.success(`The application for ${domain} has been successfully removed`);
50
+ logger.success(`The application for ${appName} has been successfully removed`);
52
51
  } catch (error) {
53
- logger.error(`Error removing the application ${url}`, error);
52
+ logger.error(`Error removing the application ${appName}`, error);
54
53
  }
55
54
  }
56
55
 
package/src/index.js CHANGED
@@ -14,10 +14,15 @@ 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
- .action(createApp);
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
+ .action((url, options) => {
24
+ createApp(url, options);
25
+ });
21
26
 
22
27
  program
23
28
  .command('list')