u2a 3.5.3 → 3.5.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "u2a",
3
- "version": "3.5.3",
3
+ "version": "3.5.4",
4
4
  "description": "URL to App - Turn any URL into a desktop application",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -83,6 +83,32 @@ function configureDebug(action) {
83
83
  }
84
84
  }
85
85
 
86
+ function configureAutoUpgrade(action) {
87
+ try {
88
+ if (action === 'status') {
89
+ const status = getSetting('autoupgrade_localapps');
90
+ logger.info(`Debug logs are currently ${status ? chalk.green('enabled') : chalk.yellow('disabled')}`);
91
+ logger.info(`Default setting is: ${DEFAULT_SETTINGS.autoupgrade_localapps ? chalk.green('enabled') : chalk.yellow('disabled')}`);
92
+ return;
93
+ } else if (action === 'enable') {
94
+ setSetting('autoupgrade_localapps', true);
95
+ logger.info(chalk.green('Automatic upgrades have been enabled'));
96
+ } else if (action === 'disable') {
97
+ setSetting('autoupgrade_localapps', false);
98
+ logger.info(chalk.yellow('Automatic upgrades have been disabled'));
99
+ } else if (action === 'reset') {
100
+ resetSetting('autoupgrade_localapps');
101
+ logger.info(`Automatic upgrades have been resetted to: ${DEFAULT_SETTINGS.autoupgrade_localapps ? chalk.green('enabled') : chalk.yellow('disabled')}`);
102
+ } else {
103
+ logger.error(`Invalid action: ${action}`);
104
+ logger.info('Available actions: status, enable, disable, reset');
105
+ return;
106
+ }
107
+ } catch (err) {
108
+ logger.error(`Error configuring auto upgrade`, err.message);
109
+ }
110
+ }
111
+
86
112
  async function resetSettings(action) {
87
113
  try {
88
114
  if (action === 'reset') {
@@ -118,6 +144,7 @@ async function configure(category, action) {
118
144
  logger.error('Missing category or action');
119
145
  logger.info('Usage: u2a configure [category] [action]');
120
146
  logger.info('Available categories:');
147
+ logger.info(' autoupgrade - Configure automatic upgrade on installed localapps on update');
121
148
  logger.info(' debug - Configure always showing debug logs');
122
149
  logger.info(' reports - Configure anonymous usage reports');
123
150
  logger.info(' settings - Resets settings (only reset action)');
@@ -143,6 +170,9 @@ async function configure(category, action) {
143
170
  case 'settings':
144
171
  await resetSettings(action);
145
172
  break;
173
+ case 'autoupgrade':
174
+ await configureAutoUpgrade(action);
175
+ break;
146
176
  default:
147
177
  logger.error(`Unknown configuration category: ${category}`);
148
178
  logger.info('Available categories: debug, reports, vcheck, settings');
@@ -9,7 +9,7 @@ const { getDomainName } = require('../utils/url');
9
9
 
10
10
  const logger = new Logger('remove');
11
11
 
12
- async function processRemoval(appName) {
12
+ async function processRemoval(appName, useInquirer) {
13
13
  try {
14
14
  const db = readDB();
15
15
 
@@ -18,18 +18,22 @@ async function processRemoval(appName) {
18
18
  return;
19
19
  }
20
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 ${appName}?`,
26
- default: false
21
+ if (useInquirer) {
22
+
23
+ const { confirm } = await inquirer.prompt([
24
+ {
25
+ type: 'confirm',
26
+ name: 'confirm',
27
+ message: `Are you sure you want to remove the application for ${appName}?`,
28
+ default: false
29
+ }
30
+ ]);
31
+
32
+ if (!confirm) {
33
+ logger.info('Operation canceled');
34
+ return;
27
35
  }
28
- ]);
29
36
 
30
- if (!confirm) {
31
- logger.info('Operation canceled');
32
- return;
33
37
  }
34
38
 
35
39
  const appInfo = db[appName];
@@ -47,7 +51,7 @@ async function processRemoval(appName) {
47
51
 
48
52
  const packageName = `u2a-${appName.replace(/\s+/g, '-')}`;
49
53
  let appDataPath;
50
-
54
+
51
55
  if (process.platform === 'win32') {
52
56
  appDataPath = path.join(process.env.APPDATA, packageName);
53
57
  } else if (process.platform === 'darwin') {
@@ -73,9 +77,9 @@ async function processRemoval(appName) {
73
77
  }
74
78
  }
75
79
 
76
- async function removeApp(appName) {
80
+ async function removeApp(appName, useInquirer = true) {
77
81
  const sAppName = sanitizeInput(appName);
78
- await processRemoval(sAppName);
82
+ await processRemoval(sAppName, useInquirer);
79
83
  }
80
84
 
81
85
  module.exports = {
@@ -0,0 +1,244 @@
1
+ const { app, BrowserWindow, Menu, shell, nativeTheme } = require('electron');
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+
5
+ const APP_NAME = "{APP_NAME}";
6
+ const APP_URL = "{APP_URL}";
7
+ const APP_ICON_PATH = "{APP_ICON_PATH}";
8
+ const WINDOW_WIDTH = {WINDOW_WIDTH};
9
+ const WINDOW_HEIGHT = {WINDOW_HEIGHT};
10
+
11
+ let mainWindow;
12
+ let splashWindow;
13
+
14
+ function logAppInfo() {
15
+ const packageJsonPath = path.join(__dirname, 'package.json');
16
+ let packageInfo = {};
17
+
18
+ try {
19
+ const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf-8');
20
+ packageInfo = JSON.parse(packageJsonContent);
21
+ } catch (error) {
22
+ console.error('Error reading package.json:', error.message);
23
+ }
24
+
25
+ console.log('\n--------------------------------');
26
+ console.log(' APPLICATION INFORMATION');
27
+ console.log('--------------------------------');
28
+ console.log(`Application: ${APP_NAME}`);
29
+ console.log(`URL: ${APP_URL}`);
30
+ console.log(`Started at: ${new Date().toLocaleString()}`);
31
+ console.log(`App directory: ${__dirname}`);
32
+ console.log(`Icon path: ${APP_ICON_PATH}`);
33
+
34
+ console.log('\n PACKAGE INFO:');
35
+ console.log(` - Name: ${packageInfo.name || 'N/A'}`);
36
+ console.log(` - Version: ${packageInfo.version || 'N/A'}`);
37
+ console.log(` - Description: ${packageInfo.description || 'N/A'}`);
38
+ console.log(` - Electron version: ${packageInfo.dependencies?.electron || 'N/A'}`);
39
+
40
+ if (packageInfo.build) {
41
+ console.log('\n BUILD CONFIG:');
42
+ console.log(` - App ID: ${packageInfo.build.appId || 'N/A'}`);
43
+ console.log(` - Product Name: ${packageInfo.build.productName || 'N/A'}`);
44
+ }
45
+
46
+ console.log('--------------------------------\n');
47
+ }
48
+
49
+ function createSplashScreen() {
50
+ splashWindow = new BrowserWindow({
51
+ width: 200,
52
+ height: 40,
53
+ transparent: true,
54
+ frame: false,
55
+ alwaysOnTop: true,
56
+ skipTaskbar: true,
57
+ resizable: false,
58
+ icon: APP_ICON_PATH,
59
+ webPreferences: {
60
+ nodeIntegration: false,
61
+ contextIsolation: true
62
+ }
63
+ });
64
+
65
+ const isDarkMode = nativeTheme.shouldUseDarkColors;
66
+ const bgColor = isDarkMode ? '#333333' : '#f5f5f5';
67
+ const loaderBgColor = isDarkMode ? '#555555' : '#e0e0e0';
68
+ const loaderColor = isDarkMode ? '#ffffff' : '#2563eb';
69
+ const shadowColor = isDarkMode ? 'rgba(0, 0, 0, 0.5)' : 'rgba(0, 0, 0, 0.2)';
70
+
71
+ const splashHtml = `
72
+ <!DOCTYPE html>
73
+ <html>
74
+ <head>
75
+ <meta charset="UTF-8">
76
+ <meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'; object-src 'none';">
77
+ <title>Loading</title>
78
+ <style>
79
+ html, body {
80
+ margin: 0;
81
+ padding: 0;
82
+ width: 100%;
83
+ height: 100%;
84
+ overflow: hidden;
85
+ background-color: transparent;
86
+ }
87
+
88
+ .container {
89
+ width: 100%;
90
+ height: 100%;
91
+ display: flex;
92
+ justify-content: center;
93
+ align-items: center;
94
+ }
95
+
96
+ .loader-container {
97
+ width: 180px;
98
+ height: 12px;
99
+ background-color: ${bgColor};
100
+ border-radius: 6px;
101
+ overflow: hidden;
102
+ box-shadow: 0 2px 8px ${shadowColor};
103
+ padding: 3px;
104
+ }
105
+
106
+ .loader-bg {
107
+ width: 100%;
108
+ height: 100%;
109
+ background-color: ${loaderBgColor};
110
+ border-radius: 4px;
111
+ overflow: hidden;
112
+ position: relative;
113
+ }
114
+
115
+ .loader {
116
+ position: absolute;
117
+ top: 0;
118
+ left: 0;
119
+ height: 100%;
120
+ width: 30%;
121
+ background-color: ${loaderColor};
122
+ border-radius: 4px;
123
+ animation: loading 1.5s infinite ease-in-out;
124
+ }
125
+
126
+ @keyframes loading {
127
+ 0% {
128
+ left: -30%;
129
+ }
130
+ 100% {
131
+ left: 100%;
132
+ }
133
+ }
134
+ </style>
135
+ </head>
136
+ <body>
137
+ <div class="container">
138
+ <div class="loader-container">
139
+ <div class="loader-bg">
140
+ <div class="loader"></div>
141
+ </div>
142
+ </div>
143
+ </div>
144
+ </body>
145
+ </html>
146
+ `;
147
+
148
+ const splashPath = path.join(app.getPath('temp'), `${APP_NAME}-splash.html`);
149
+ fs.writeFileSync(splashPath, splashHtml);
150
+
151
+ splashWindow.loadFile(splashPath);
152
+
153
+ const { screen } = require('electron');
154
+ const primaryDisplay = screen.getPrimaryDisplay();
155
+ const { width, height } = primaryDisplay.workAreaSize;
156
+
157
+ splashWindow.setPosition(
158
+ Math.floor(width / 2 - 100),
159
+ Math.floor(height / 2 - 20)
160
+ );
161
+ }
162
+
163
+ function createWindow() {
164
+ logAppInfo();
165
+
166
+ app.setAppUserModelId(APP_NAME);
167
+
168
+ mainWindow = new BrowserWindow({
169
+ width: WINDOW_WIDTH,
170
+ height: WINDOW_HEIGHT,
171
+ title: APP_NAME,
172
+ icon: APP_ICON_PATH,
173
+ show: false,
174
+ webPreferences: {
175
+ nodeIntegration: false,
176
+ contextIsolation: true
177
+ }
178
+ });
179
+
180
+ mainWindow.webContents.on('did-finish-load', () => {
181
+ setTimeout(() => {
182
+ if (splashWindow && !splashWindow.isDestroyed()) {
183
+ splashWindow.close();
184
+ }
185
+
186
+ if (!mainWindow.isVisible()) {
187
+ mainWindow.show();
188
+ mainWindow.focus();
189
+ }
190
+ }, 500);
191
+ });
192
+
193
+ mainWindow.loadURL(APP_URL);
194
+
195
+ mainWindow.webContents.on('context-menu', (e, params) => {
196
+ e.preventDefault();
197
+ mainWindow.webContents.executeJavaScript(`
198
+ window.addEventListener('contextmenu', (e) => {
199
+ e.stopPropagation();
200
+ }, true);
201
+ `);
202
+ });
203
+
204
+ mainWindow.webContents.on('new-window', (event, url) => {
205
+ event.preventDefault();
206
+ shell.openExternal(url);
207
+ });
208
+
209
+ const template = [];
210
+
211
+ const menu = Menu.buildFromTemplate(template);
212
+ Menu.setApplicationMenu(menu);
213
+
214
+ setTimeout(() => {
215
+ if (splashWindow && !splashWindow.isDestroyed()) {
216
+ splashWindow.close();
217
+ if (!mainWindow.isVisible()) {
218
+ mainWindow.show();
219
+ mainWindow.focus();
220
+ }
221
+ }
222
+ }, 10000);
223
+ }
224
+
225
+ if (process.platform === 'win32') {
226
+ app.setAppUserModelId(app.name);
227
+ }
228
+
229
+ app.whenReady().then(() => {
230
+ createSplashScreen();
231
+ createWindow();
232
+ });
233
+
234
+ app.on('window-all-closed', () => {
235
+ if (process.platform !== 'darwin') {
236
+ app.quit();
237
+ }
238
+ });
239
+
240
+ app.on('activate', () => {
241
+ if (BrowserWindow.getAllWindows().length === 0) {
242
+ createWindow();
243
+ }
244
+ });
@@ -1,3 +1,6 @@
1
+ //Todo: replace electron logo with the one of the app in popup windows
2
+
3
+
1
4
  const fs = require('fs');
2
5
  const path = require('path');
3
6
  const Logger = require('./logger');
@@ -8,254 +11,26 @@ function generateMainJs(appName, url, iconPath, options = {}) {
8
11
  const width = options.width || 1200;
9
12
  const height = options.height || 800;
10
13
 
11
- return `
12
- const { app, BrowserWindow, Menu, shell, nativeTheme } = require('electron');
13
- const path = require('path');
14
- const fs = require('fs');
15
-
16
- const APP_NAME = "${appName}";
17
- const APP_URL = "${url}";
18
- const APP_ICON_PATH = "${iconPath.replace(/\\/g, '\\\\')}";
19
- const WINDOW_WIDTH = ${width};
20
- const WINDOW_HEIGHT = ${height};
21
-
22
- let mainWindow;
23
- let splashWindow;
24
-
25
- function logAppInfo() {
26
- const packageJsonPath = path.join(__dirname, 'package.json');
27
- let packageInfo = {};
14
+ const templatePath = path.join(__dirname, 'app.js');
15
+ let template;
28
16
 
29
17
  try {
30
- const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf-8');
31
- packageInfo = JSON.parse(packageJsonContent);
18
+ template = fs.readFileSync(templatePath, 'utf8');
32
19
  } catch (error) {
33
- console.error('Error reading package.json:', error.message);
34
- }
35
-
36
- console.log('\\n--------------------------------');
37
- console.log(' APPLICATION INFORMATION');
38
- console.log('--------------------------------');
39
- console.log(\`Application: \${APP_NAME}\`);
40
- console.log(\`URL: \${APP_URL}\`);
41
- console.log(\`Started at: \${new Date().toLocaleString()}\`);
42
- console.log(\`App directory: \${__dirname}\`);
43
- console.log(\`Icon path: \${APP_ICON_PATH}\`);
44
-
45
- console.log('\\n PACKAGE INFO:');
46
- console.log(\` - Name: \${packageInfo.name || 'N/A'}\`);
47
- console.log(\` - Version: \${packageInfo.version || 'N/A'}\`);
48
- console.log(\` - Description: \${packageInfo.description || 'N/A'}\`);
49
- console.log(\` - Electron version: \${packageInfo.dependencies?.electron || 'N/A'}\`);
50
-
51
- if (packageInfo.build) {
52
- console.log('\\n BUILD CONFIG:');
53
- console.log(\` - App ID: \${packageInfo.build.appId || 'N/A'}\`);
54
- console.log(\` - Product Name: \${packageInfo.build.productName || 'N/A'}\`);
20
+ logger.error('Error reading electronApp.js template', error);
21
+ throw new Error('Failed to read Electron application template');
55
22
  }
56
23
 
57
- console.log('--------------------------------\\n');
58
- }
59
-
60
- function createSplashScreen() {
61
- splashWindow = new BrowserWindow({
62
- width: 200,
63
- height: 40,
64
- transparent: true,
65
- frame: false,
66
- alwaysOnTop: true,
67
- skipTaskbar: true,
68
- resizable: false,
69
- icon: APP_ICON_PATH,
70
- webPreferences: {
71
- nodeIntegration: false,
72
- contextIsolation: true
73
- }
74
- });
75
-
76
- const isDarkMode = nativeTheme.shouldUseDarkColors;
77
- const bgColor = isDarkMode ? '#333333' : '#f5f5f5';
78
- const loaderBgColor = isDarkMode ? '#555555' : '#e0e0e0';
79
- const loaderColor = isDarkMode ? '#ffffff' : '#2563eb';
80
- const shadowColor = isDarkMode ? 'rgba(0, 0, 0, 0.5)' : 'rgba(0, 0, 0, 0.2)';
81
-
82
- const splashHtml = \`
83
- <!DOCTYPE html>
84
- <html>
85
- <head>
86
- <meta charset="UTF-8">
87
- <meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'; object-src 'none';">
88
- <title>Loading</title>
89
- <style>
90
- html, body {
91
- margin: 0;
92
- padding: 0;
93
- width: 100%;
94
- height: 100%;
95
- overflow: hidden;
96
- background-color: transparent;
97
- }
98
-
99
- .container {
100
- width: 100%;
101
- height: 100%;
102
- display: flex;
103
- justify-content: center;
104
- align-items: center;
105
- }
106
-
107
- .loader-container {
108
- width: 180px;
109
- height: 12px;
110
- background-color: \${bgColor};
111
- border-radius: 6px;
112
- overflow: hidden;
113
- box-shadow: 0 2px 8px \${shadowColor};
114
- padding: 3px;
115
- }
116
-
117
- .loader-bg {
118
- width: 100%;
119
- height: 100%;
120
- background-color: \${loaderBgColor};
121
- border-radius: 4px;
122
- overflow: hidden;
123
- position: relative;
124
- }
125
-
126
- .loader {
127
- position: absolute;
128
- top: 0;
129
- left: 0;
130
- height: 100%;
131
- width: 30%;
132
- background-color: \${loaderColor};
133
- border-radius: 4px;
134
- animation: loading 1.5s infinite ease-in-out;
135
- }
136
-
137
- @keyframes loading {
138
- 0% {
139
- left: -30%;
140
- }
141
- 100% {
142
- left: 100%;
143
- }
144
- }
145
- </style>
146
- </head>
147
- <body>
148
- <div class="container">
149
- <div class="loader-container">
150
- <div class="loader-bg">
151
- <div class="loader"></div>
152
- </div>
153
- </div>
154
- </div>
155
- </body>
156
- </html>
157
- \`;
158
-
159
- const splashPath = path.join(app.getPath('temp'), \`\${APP_NAME}-splash.html\`);
160
- fs.writeFileSync(splashPath, splashHtml);
24
+ const mainJs = template
25
+ .replace('{APP_NAME}', appName)
26
+ .replace('{APP_URL}', url)
27
+ .replace('{APP_ICON_PATH}', iconPath.replace(/\\/g, '\\\\'))
28
+ .replace('{WINDOW_WIDTH}', width)
29
+ .replace('{WINDOW_HEIGHT}', height);
161
30
 
162
- splashWindow.loadFile(splashPath);
163
-
164
- const { screen } = require('electron');
165
- const primaryDisplay = screen.getPrimaryDisplay();
166
- const { width, height } = primaryDisplay.workAreaSize;
167
-
168
- splashWindow.setPosition(
169
- Math.floor(width / 2 - 100),
170
- Math.floor(height / 2 - 20)
171
- );
31
+ return mainJs;
172
32
  }
173
33
 
174
- function createWindow() {
175
- logAppInfo();
176
-
177
- app.setAppUserModelId(APP_NAME);
178
-
179
- mainWindow = new BrowserWindow({
180
- width: WINDOW_WIDTH,
181
- height: WINDOW_HEIGHT,
182
- title: APP_NAME,
183
- icon: APP_ICON_PATH,
184
- show: false,
185
- webPreferences: {
186
- nodeIntegration: false,
187
- contextIsolation: true
188
- }
189
- });
190
-
191
- mainWindow.webContents.on('did-finish-load', () => {
192
- setTimeout(() => {
193
- if (splashWindow && !splashWindow.isDestroyed()) {
194
- splashWindow.close();
195
- }
196
-
197
- if (!mainWindow.isVisible()) {
198
- mainWindow.show();
199
- mainWindow.focus();
200
- }
201
- }, 500);
202
- });
203
-
204
- mainWindow.loadURL(APP_URL);
205
-
206
- mainWindow.webContents.on('context-menu', (e, params) => {
207
- e.preventDefault();
208
- mainWindow.webContents.executeJavaScript(\`
209
- window.addEventListener('contextmenu', (e) => {
210
- e.stopPropagation();
211
- }, true);
212
- \`);
213
- });
214
-
215
- mainWindow.webContents.on('new-window', (event, url) => {
216
- event.preventDefault();
217
- shell.openExternal(url);
218
- });
219
-
220
- const template = [];
221
-
222
- const menu = Menu.buildFromTemplate(template);
223
- Menu.setApplicationMenu(menu);
224
-
225
- setTimeout(() => {
226
- if (splashWindow && !splashWindow.isDestroyed()) {
227
- splashWindow.close();
228
- if (!mainWindow.isVisible()) {
229
- mainWindow.show();
230
- mainWindow.focus();
231
- }
232
- }
233
- }, 10000);
234
- }
235
-
236
- if (process.platform === 'win32') {
237
- app.setAppUserModelId(app.name);
238
- }
239
-
240
- app.whenReady().then(() => {
241
- createSplashScreen();
242
- createWindow();
243
- });
244
-
245
- app.on('window-all-closed', () => {
246
- if (process.platform !== 'darwin') {
247
- app.quit();
248
- }
249
- });
250
-
251
- app.on('activate', () => {
252
- if (BrowserWindow.getAllWindows().length === 0) {
253
- createWindow();
254
- }
255
- });
256
- `;
257
- } // note for one day: put this in a different file
258
-
259
34
  async function createPackageJson(appName, iconPath, isExecutable = false, createSetup = false) {
260
35
  const u2aPackagePath = path.resolve(__dirname, '../../package.json');
261
36
 
@@ -300,7 +75,7 @@ async function createPackageJson(appName, iconPath, isExecutable = false, create
300
75
  };
301
76
 
302
77
  packageJson.dependencies = {};
303
-
78
+
304
79
  packageJson.scripts.package = "electron-packager . --overwrite --asar";
305
80
  packageJson.scripts.setup = "electron-builder";
306
81
  }
@@ -7,21 +7,21 @@ const logger = new Logger('builder');
7
7
 
8
8
  async function buildExecutable(appDir, appName, platform, iconPath, options) {
9
9
  logger.info(`Building executable for ${platform}...`);
10
-
10
+
11
11
  try {
12
12
  const installOptions = {
13
13
  cwd: appDir,
14
14
  stdio: ['ignore', 'pipe', 'pipe'],
15
15
  windowsHide: true
16
16
  };
17
-
17
+
18
18
  secureExec('npm install --save-dev electron-packager electron', installOptions);
19
-
19
+
20
20
  let platformFlag = '';
21
21
  let archFlag = `--arch=${options.arch || 'x64'}`;
22
22
  let iconOption = '';
23
-
24
- switch(platform) {
23
+
24
+ switch (platform) {
25
25
  case 'windows':
26
26
  platformFlag = '--platform=win32';
27
27
  iconOption = iconPath ? `--icon="${iconPath}"` : '';
@@ -40,23 +40,23 @@ async function buildExecutable(appDir, appName, platform, iconPath, options) {
40
40
  default:
41
41
  platformFlag = `--platform=${process.platform}`;
42
42
  }
43
-
43
+
44
44
  const packageCommand = `npx electron-packager . "${appName}" ${platformFlag} ${archFlag} --out=dist --overwrite --asar ${iconOption}`;
45
-
45
+
46
46
  logger.debug(`Executing: ${packageCommand}`);
47
-
47
+
48
48
  secureExec(packageCommand, installOptions);
49
-
49
+
50
50
  let distPlatform = '';
51
- switch(platform) {
51
+ switch (platform) {
52
52
  case 'windows': distPlatform = 'win32'; break;
53
53
  case 'darwin': distPlatform = 'darwin'; break;
54
54
  case 'linux': distPlatform = 'linux'; break;
55
55
  default: distPlatform = process.platform;
56
56
  }
57
-
57
+
58
58
  const outputPath = path.join(appDir, 'dist', `${appName}-${distPlatform}-x64`);
59
-
59
+
60
60
  if (fs.existsSync(outputPath)) {
61
61
  logger.debug(`Executable built successfully at: ${outputPath}`);
62
62
  return outputPath;
@@ -72,18 +72,18 @@ async function buildExecutable(appDir, appName, platform, iconPath, options) {
72
72
 
73
73
  async function buildSetup(appDir, platform, arch) {
74
74
  logger.info(`Building setup for ${platform}${arch ? ` (${arch})` : ''}...`);
75
-
75
+
76
76
  try {
77
77
  const installOptions = {
78
78
  cwd: appDir,
79
79
  stdio: ['ignore', 'pipe', 'pipe'],
80
80
  windowsHide: true
81
81
  };
82
-
82
+
83
83
  secureExec('npm install --save-dev electron-builder', installOptions);
84
-
84
+
85
85
  let builderArgs = '';
86
- switch(platform) {
86
+ switch (platform) {
87
87
  case 'windows':
88
88
  builderArgs = '--win';
89
89
  break;
@@ -96,15 +96,15 @@ async function buildSetup(appDir, platform, arch) {
96
96
  default:
97
97
  builderArgs = '';
98
98
  }
99
-
99
+
100
100
  if (arch) {
101
101
  builderArgs += ` --${arch}`;
102
102
  }
103
-
103
+
104
104
  const builderCommand = `npx electron-builder ${builderArgs}`;
105
105
  logger.debug(`Executing: ${builderCommand}`);
106
106
  secureExec(builderCommand, installOptions);
107
-
107
+
108
108
  const installerPath = path.join(appDir, 'installer');
109
109
  if (fs.existsSync(installerPath)) {
110
110
  logger.debug(`Setup created at: ${installerPath}`);
@@ -121,6 +121,6 @@ async function buildSetup(appDir, platform, arch) {
121
121
  }
122
122
 
123
123
  module.exports = {
124
- buildExecutable,
125
- buildSetup
124
+ buildExecutable,
125
+ buildSetup
126
126
  }
@@ -4,7 +4,7 @@ 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');
7
+ const { parseICO } = require('icojs');
8
8
  const sharp = require('sharp');
9
9
  const pngToIco = require('png-to-ico');
10
10
 
@@ -18,7 +18,7 @@ async function getFavicon(url) {
18
18
  try {
19
19
  const domain = getDomainName(url);
20
20
  const normalizedUrl = await normalizeUrl(url);
21
-
21
+
22
22
  const faviconUrl = `${normalizedUrl}/favicon.ico`;
23
23
  const iconResponse = await axios.get(faviconUrl, { responseType: 'arraybuffer' });
24
24
 
@@ -22,10 +22,10 @@ class Logger {
22
22
  }
23
23
 
24
24
  _format(status, message) {
25
- const timestamp = new Date().toLocaleTimeString('en-US', {
26
- hour: '2-digit',
27
- minute: '2-digit',
28
- second: '2-digit'
25
+ const timestamp = new Date().toLocaleTimeString('en-US', {
26
+ hour: '2-digit',
27
+ minute: '2-digit',
28
+ second: '2-digit'
29
29
  });
30
30
  return `[${timestamp}] - ${status} | ${message}`;
31
31
  }
@@ -56,7 +56,7 @@ class Logger {
56
56
  const formattedMessage = this._format('ERROR', `${message}${error ? ' ' + error : ''}`);
57
57
  console.log(chalk.red(formattedMessage));
58
58
  this._writeToFile(formattedMessage);
59
-
59
+
60
60
  if (error && error.stack) {
61
61
  this._writeToFile(`Stack trace: ${error.stack}`);
62
62
  }
@@ -1,9 +1,13 @@
1
+ // Todo: autoupgrade for installed localapps, will be useful if we change file structure
2
+
3
+
1
4
  const fs = require('fs');
2
5
  const path = require('path');
3
6
  const axios = require('axios');
4
7
  const Logger = require('./logger');
5
8
  const { setupConfig, CONFIG_DIR } = require('./config');
6
9
  const { initSettings, getSetting } = require('./settings');
10
+ const { upgrade } = require('./upgradeLA');
7
11
 
8
12
  setupConfig(); // builds ~/.u2a/*
9
13
 
@@ -18,7 +22,7 @@ const formatVersionLine = (label, version) => {
18
22
  const baseLength = `; ${label}: `.length + version.length;
19
23
  const spaceCount = Math.max(0, 30 - baseLength);
20
24
  const spaces = ' '.repeat(spaceCount);
21
-
25
+
22
26
  return `; ${label}: ${version}${spaces};`;
23
27
  };
24
28
 
@@ -29,7 +33,7 @@ async function run() {
29
33
  const postinstallData = JSON.parse(fs.readFileSync(postinstallJsonPath, 'utf8'));
30
34
  currentVersion = postinstallData.version || '';
31
35
  isUpgrade = true;
32
-
36
+
33
37
  let newVersion = '';
34
38
  try {
35
39
  const packageJsonPath = path.join(__dirname, '..', '..', 'package.json');
@@ -39,7 +43,7 @@ async function run() {
39
43
  logger.error(`Error reading package.json for display`, err.message);
40
44
  newVersion = 'unknown';
41
45
  }
42
-
46
+
43
47
  logger.info(';=============================;');
44
48
  logger.info('; u2a has been updated! ;');
45
49
  logger.info('; Successfully migrated from ;');
@@ -48,6 +52,7 @@ async function run() {
48
52
  logger.info(';=============================;');
49
53
 
50
54
  initSettings();
55
+ upgrade();
51
56
  } catch (err) {
52
57
  logger.error(`Error reading postinstall.json`, err.message);
53
58
  isUpgrade = false;
@@ -75,13 +80,14 @@ async function run() {
75
80
  logger.info('; \'u2a configure reports ;');
76
81
  logger.info('; disable\' ;');
77
82
  logger.info(';=============================;');
78
-
83
+
79
84
  initSettings(true);
80
85
  }
81
86
 
87
+
82
88
  const packageJsonPath = path.join(__dirname, '..', '..', 'package.json');
83
89
  let newVersion = '';
84
-
90
+
85
91
  try {
86
92
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
87
93
  newVersion = packageJson.version || '';
@@ -113,6 +119,37 @@ async function run() {
113
119
  } catch (err) {
114
120
  logger.error(`Error updating postinstall.json`, err.message);
115
121
  }
122
+
123
+ if (isUpgrade) {
124
+ try {
125
+ logger.info('Checking for local apps that need upgrading...');
126
+ const upgradeStats = await upgrade(currentVersion, newVersion);
127
+
128
+ if (upgradeStats.skipped) {
129
+ if (upgradeStats.reason === 'disabled') {
130
+ logger.warn('Automatic upgrade for local apps is disabled in settings, skipped.');
131
+ } else if (upgradeStats.reason === 'not-core-update') {
132
+ logger.warn('This is not a core update, skipping local apps upgrade');
133
+ } else if (upgradeStats.reason === 'new-install') {
134
+ logger.warn('New installation, no local apps to upgrade');
135
+ }
136
+ } else {
137
+ logger.warn(`Local apps upgrade results: ${upgradeStats.upgraded} upgraded, ${upgradeStats.failed} failed`);
138
+
139
+ if (upgradeStats.upgraded > 0) {
140
+ logger.info(';=============================;');
141
+ logger.info('; Local apps have been updated;');
142
+ logger.info(formatVersionLine("Updated", upgradeStats.upgraded.toString())); //not a version but who cares anyway
143
+ if (upgradeStats.failed > 0) {
144
+ logger.info(formatVersionLine("Skipped", upgradeStats.failed.toString()));
145
+ }
146
+ logger.info(';=============================;');
147
+ }
148
+ }
149
+ } catch (err) {
150
+ logger.error('Error while upgrading local apps:', err.message);
151
+ }
152
+ }
116
153
  }
117
154
 
118
155
  run().catch(err => {
@@ -2,12 +2,12 @@ const { execSync } = require('child_process');
2
2
  const { sanitizeCommand } = require('./sanitize');
3
3
 
4
4
  function secureExec(command, options = {}) {
5
- const sanitizedCommand = sanitizeCommand(command);
6
- const result = execSync(sanitizedCommand, options);
5
+ const sanitizedCommand = sanitizeCommand(command);
6
+ const result = execSync(sanitizedCommand, options);
7
+
8
+ return result;
9
+ }
7
10
 
8
- return result;
9
- }
10
-
11
11
  module.exports = {
12
- secureExec
12
+ secureExec
13
13
  }
@@ -8,6 +8,7 @@ const DEFAULT_SETTINGS = {
8
8
  send_anon_reports: true,
9
9
  version_check: true,
10
10
  always_show_debug: false,
11
+ autoupgrade_localapps: false,
11
12
 
12
13
  };
13
14
 
@@ -104,6 +105,7 @@ function resetSetting(key) {
104
105
 
105
106
 
106
107
  module.exports = {
108
+ DEFAULT_SETTINGS,
107
109
  initSettings,
108
110
  getSettings,
109
111
  saveSettings,
@@ -0,0 +1,141 @@
1
+ //seems still a bit dangerous, thats why this feature is disabled by default
2
+ //enable with 'u2a configure autoupgrade enable'
3
+
4
+
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const { readDB } = require('./config');
9
+ const Logger = require('./logger');
10
+ const { createApp } = require('../commands/create');
11
+ const { removeApp } = require('../commands/remove');
12
+ const { getSetting } = require('./settings');
13
+
14
+ const logger = new Logger('upgradeLA');
15
+
16
+ function isCoreUpdate(currentVersion, newVersion) {
17
+ try {
18
+ const [currentSecurity, currentCore, currentFeature] = currentVersion.split('.').map(Number);
19
+ const [newSecurity, newCore, newFeature] = newVersion.split('.').map(Number);
20
+
21
+ return (currentSecurity !== newSecurity || currentCore !== newCore);
22
+ } catch (err) {
23
+ logger.debug(`Error comparing versions: ${currentVersion} vs ${newVersion}`, err.message);
24
+ return false;
25
+ }
26
+ }
27
+
28
+ function extractDimensionsFromMainJs(mainJsPath) {
29
+ try {
30
+ if (!fs.existsSync(mainJsPath)) {
31
+ logger.warn(`main.js file not found at ${mainJsPath}`);
32
+ return { width: 1200, height: 800 };
33
+ }
34
+
35
+ const mainJsContent = fs.readFileSync(mainJsPath, 'utf8');
36
+
37
+ const widthMatch = mainJsContent.match(/width\s*:\s*(\d+)/);
38
+ const width = widthMatch ? parseInt(widthMatch[1], 10) : 1200;
39
+
40
+ const heightMatch = mainJsContent.match(/height\s*:\s*(\d+)/);
41
+ const height = heightMatch ? parseInt(heightMatch[1], 10) : 800;
42
+
43
+ logger.debug(`Extracted dimensions from main.js: ${width}x${height}`);
44
+ return { width, height };
45
+ } catch (err) {
46
+ logger.debug(`Error extracting dimensions from main.js: ${mainJsPath}`, err.message);
47
+ return { width: 1200, height: 800 };
48
+ }
49
+ }
50
+
51
+ async function upgradeLocalApp(appName, appData) {
52
+ logger.debug(`Upgrading local app: ${appName}`);
53
+
54
+ try {
55
+ const { url, path: appPath, icon } = appData;
56
+
57
+ const mainJsPath = path.join(appPath, 'main.js');
58
+ const { width, height } = extractDimensionsFromMainJs(mainJsPath);
59
+
60
+ const options = {
61
+ name: appName,
62
+ width: width,
63
+ height: height,
64
+ };
65
+
66
+ if (icon)
67
+ options.icon = icon;
68
+
69
+
70
+ await removeApp(appName, false);
71
+ logger.debug(`Removed existing app: ${appName}`);
72
+
73
+ await createApp(url, options);
74
+ logger.debug(`Successfully upgraded: ${appName}`);
75
+
76
+ return true;
77
+ } catch (err) {
78
+ logger.debug(`Failed to upgrade ${appName}`, err.message);
79
+ return false;
80
+ }
81
+ }
82
+
83
+ async function upgradeLocalApps(currentVersion, newVersion) {
84
+ logger.debug(`Checking for local apps that need upgrading`);
85
+
86
+ const autoUpgradeEnabled = getSetting('autoupgrade_localapps');
87
+ if (!autoUpgradeEnabled) {
88
+ logger.debug(`Automatic upgrade for local apps is disabled. Skipping upgrades.`);
89
+ return { skipped: true, reason: 'disabled', upgraded: 0, failed: 0, total: 0 };
90
+ }
91
+
92
+ if (!isCoreUpdate(currentVersion, newVersion)) {
93
+ logger.debug(`Not a core update (${currentVersion} → ${newVersion}). Skipping local app upgrades.`);
94
+ return { skipped: true, reason: 'not-core-update', upgraded: 0, failed: 0, total: 0 };
95
+ }
96
+
97
+ try {
98
+ const db = readDB();
99
+ const apps = Object.keys(db);
100
+ const stats = { skipped: false, upgraded: 0, failed: 0, total: apps.length };
101
+
102
+ logger.debug(`Found ${apps.length} local apps to check for upgrade`);
103
+
104
+ for (const appName of apps) {
105
+ const appData = db[appName];
106
+
107
+ if (!appData.path || !fs.existsSync(appData.path)) { //skips if no path but shouldnt happen
108
+ logger.debug(`Skipping ${appName}: Not a local app or path doesn't exist`);
109
+ continue;
110
+ }
111
+
112
+ const success = await upgradeLocalApp(appName, appData);
113
+ if (success) {
114
+ stats.upgraded++;
115
+ } else {
116
+ stats.failed++;
117
+ }
118
+ }
119
+
120
+ logger.debug(`Local apps upgrade completed: ${stats.upgraded} upgraded, ${stats.failed} failed, ${apps.length - stats.upgraded - stats.failed} skipped`);
121
+ return stats;
122
+ } catch (err) {
123
+ logger.debug(`Error upgrading local apps`, err.message);
124
+ return { skipped: false, upgraded: 0, failed: 0, total: 0, error: err.message };
125
+ }
126
+ }
127
+
128
+ async function upgrade(currentVersion, newVersion) {
129
+ logger.debug(`Starting local apps upgrade check: ${currentVersion} → ${newVersion}`);
130
+
131
+ if (currentVersion === '0.0.0') {
132
+ logger.debug('New installation detected, no apps to upgrade');
133
+ return { skipped: true, reason: 'new-install' };
134
+ }
135
+
136
+ return await upgradeLocalApps(currentVersion, newVersion);
137
+ }
138
+
139
+ module.exports = {
140
+ upgrade
141
+ };