u2a 3.4.2 → 3.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/commands/create.js +5 -632
- package/src/commands/remove.js +19 -1
- package/src/utils/appGenerator.js +350 -0
- package/src/utils/builder.js +125 -0
- package/src/utils/osIntegration.js +188 -0
package/package.json
CHANGED
package/src/commands/create.js
CHANGED
|
@@ -1,531 +1,18 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
3
4
|
const { normalizeUrl, getDomainName } = require('../utils/url');
|
|
4
5
|
const { getFavicon, processFavicon } = require('../utils/favicon');
|
|
5
6
|
const { APPS_DIR, readDB, writeDB } = require('../utils/config');
|
|
6
7
|
const Logger = require('../utils/logger');
|
|
7
|
-
const os = require('os');
|
|
8
8
|
const { sanitizeInput } = require('../utils/sanitize');
|
|
9
9
|
const { secureExec } = require('../utils/securexec');
|
|
10
|
+
const { createPackageJson, generateMainJs } = require('../utils/appGenerator');
|
|
11
|
+
const { addAppToOS, removeAppFromOS } = require('../utils/osIntegration');
|
|
12
|
+
const { buildExecutable, buildSetup } = require('../utils/builder');
|
|
10
13
|
|
|
11
14
|
const logger = new Logger('create');
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
function createWindowsShortcut(appInfo) {
|
|
15
|
-
try {
|
|
16
|
-
const { appName, appDir, iconPath } = appInfo;
|
|
17
|
-
const startMenuPath = path.join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'U2A Apps');
|
|
18
|
-
|
|
19
|
-
if (!fs.existsSync(startMenuPath)) {
|
|
20
|
-
fs.mkdirSync(startMenuPath, { recursive: true });
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const shortcutPath = path.join(startMenuPath, `${appName}.lnk`);
|
|
24
|
-
const targetPath = path.join(appDir, 'node_modules', '.bin', 'electron.cmd');
|
|
25
|
-
const workingDir = appDir;
|
|
26
|
-
|
|
27
|
-
const psScript = `
|
|
28
|
-
$WshShell = New-Object -comObject WScript.Shell
|
|
29
|
-
$Shortcut = $WshShell.CreateShortcut("${shortcutPath.replace(/\\/g, '\\\\')}")
|
|
30
|
-
$Shortcut.TargetPath = "${targetPath.replace(/\\/g, '\\\\')}"
|
|
31
|
-
$Shortcut.Arguments = "."
|
|
32
|
-
$Shortcut.WorkingDirectory = "${workingDir.replace(/\\/g, '\\\\')}"
|
|
33
|
-
$Shortcut.IconLocation = "${iconPath.replace(/\\/g, '\\\\')}"
|
|
34
|
-
$Shortcut.Description = "Application Web pour ${appName}"
|
|
35
|
-
$Shortcut.Save()
|
|
36
|
-
`;
|
|
37
|
-
|
|
38
|
-
const tempScriptPath = path.join(os.tmpdir(), `create_shortcut_${appName}.ps1`);
|
|
39
|
-
fs.writeFileSync(tempScriptPath, psScript);
|
|
40
|
-
|
|
41
|
-
secureExec(`powershell -ExecutionPolicy Bypass -File "${tempScriptPath}"`, {
|
|
42
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
43
|
-
windowsHide: true
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
fs.unlinkSync(tempScriptPath);
|
|
47
|
-
|
|
48
|
-
logger.success(`Shortcut created in the Start Menu: ${shortcutPath}`);
|
|
49
|
-
return shortcutPath;
|
|
50
|
-
} catch (error) {
|
|
51
|
-
logger.error(`Error while creating the Windows shortcut`, error);
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function createLinuxDesktopEntry(appInfo) {
|
|
57
|
-
try {
|
|
58
|
-
const { appName, 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=${appName}
|
|
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-${appName}.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 { appName, 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 appPath = path.join(appsDir, `${appName}.app`);
|
|
98
|
-
const macOsPath = path.join(appPath, 'Contents', 'MacOS');
|
|
99
|
-
const resourcesPath = path.join(appPath, 'Contents', 'Resources');
|
|
100
|
-
|
|
101
|
-
fs.mkdirSync(path.join(appPath, 'Contents', 'MacOS'), { recursive: true });
|
|
102
|
-
fs.mkdirSync(resourcesPath, { recursive: true });
|
|
103
|
-
|
|
104
|
-
const infoPlist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
105
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
106
|
-
<plist version="1.0">
|
|
107
|
-
<dict>
|
|
108
|
-
<key>CFBundleExecutable</key>
|
|
109
|
-
<string>AppRunner</string>
|
|
110
|
-
<key>CFBundleIconFile</key>
|
|
111
|
-
<string>icon.icns</string>
|
|
112
|
-
<key>CFBundleIdentifier</key>
|
|
113
|
-
<string>com.u2a.${appName.replace(/\s+/g, '-')}</string>
|
|
114
|
-
<key>CFBundleName</key>
|
|
115
|
-
<string>${appName}</string>
|
|
116
|
-
<key>CFBundleDisplayName</key>
|
|
117
|
-
<string>${appName}</string>
|
|
118
|
-
<key>CFBundlePackageType</key>
|
|
119
|
-
<string>APPL</string>
|
|
120
|
-
<key>CFBundleVersion</key>
|
|
121
|
-
<string>1.0</string>
|
|
122
|
-
<key>CFBundleShortVersionString</key>
|
|
123
|
-
<string>1.0</string>
|
|
124
|
-
</dict>
|
|
125
|
-
</plist>`;
|
|
126
|
-
|
|
127
|
-
fs.writeFileSync(path.join(appPath, 'Contents', 'Info.plist'), infoPlist);
|
|
128
|
-
|
|
129
|
-
const shellScript = `#!/bin/bash
|
|
130
|
-
cd "${appDir}"
|
|
131
|
-
"${path.join(appDir, 'node_modules', '.bin', 'electron')}" "${path.join(appDir, 'main.js')}"`;
|
|
132
|
-
|
|
133
|
-
const shellScriptPath = path.join(macOsPath, 'AppRunner');
|
|
134
|
-
fs.writeFileSync(shellScriptPath, shellScript);
|
|
135
|
-
fs.chmodSync(shellScriptPath, '755');
|
|
136
|
-
|
|
137
|
-
fs.copyFileSync(iconPath, path.join(resourcesPath, 'icon.icns'));
|
|
138
|
-
|
|
139
|
-
logger.success(`macOS application created: ${appPath}`);
|
|
140
|
-
return appPath;
|
|
141
|
-
} catch (error) {
|
|
142
|
-
logger.error(`Error while creating the macOS application`, error);
|
|
143
|
-
return null;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function addAppToOS(appName, url, appDir, iconPath) {
|
|
148
|
-
const appInfo = { appName, url, appDir, iconPath };
|
|
149
|
-
let desktopPath = null;
|
|
150
|
-
|
|
151
|
-
if (process.platform === 'win32') {
|
|
152
|
-
desktopPath = createWindowsShortcut(appInfo);
|
|
153
|
-
} else if (process.platform === 'darwin') {
|
|
154
|
-
desktopPath = createMacOSApp(appInfo);
|
|
155
|
-
} else if (process.platform === 'linux') {
|
|
156
|
-
desktopPath = createLinuxDesktopEntry(appInfo);
|
|
157
|
-
} else {
|
|
158
|
-
logger.warn(`Desktop integration not supported for platform ${process.platform}`);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return desktopPath;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function removeAppFromOS(appName) {
|
|
165
|
-
try {
|
|
166
|
-
if (process.platform === 'win32') {
|
|
167
|
-
const startMenuPath = path.join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'U2A Apps', `${appName}.lnk`);
|
|
168
|
-
if (fs.existsSync(startMenuPath)) {
|
|
169
|
-
fs.unlinkSync(startMenuPath);
|
|
170
|
-
logger.success(`Shortcut removed from the Start Menu: ${startMenuPath}`);
|
|
171
|
-
}
|
|
172
|
-
} else if (process.platform === 'darwin') {
|
|
173
|
-
const appPath = path.join(os.homedir(), 'Applications', 'U2A Apps', `${appName}.app`);
|
|
174
|
-
if (fs.existsSync(appPath)) {
|
|
175
|
-
fs.rmSync(appPath, { recursive: true, force: true });
|
|
176
|
-
logger.success(`macOS application removed: ${appPath}`);
|
|
177
|
-
}
|
|
178
|
-
} else if (process.platform === 'linux') {
|
|
179
|
-
const desktopFilePath = path.join(os.homedir(), '.local', 'share', 'applications', `u2a-${appName}.desktop`);
|
|
180
|
-
if (fs.existsSync(desktopFilePath)) {
|
|
181
|
-
fs.unlinkSync(desktopFilePath);
|
|
182
|
-
logger.success(`Linux desktop entry removed: ${desktopFilePath}`);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
} catch (error) {
|
|
186
|
-
logger.error(`Error while removing desktop integration for ${appName}`, error);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function generateMainJs(appName, url, iconPath, options = {}) {
|
|
191
|
-
const width = options.width || 1200;
|
|
192
|
-
const height = options.height || 800;
|
|
193
|
-
|
|
194
|
-
return `
|
|
195
|
-
const { app, BrowserWindow, Menu, shell, nativeTheme } = require('electron');
|
|
196
|
-
const path = require('path');
|
|
197
|
-
const fs = require('fs');
|
|
198
|
-
|
|
199
|
-
const APP_NAME = "${appName}";
|
|
200
|
-
const APP_URL = "${url}";
|
|
201
|
-
const APP_ICON_PATH = "${iconPath.replace(/\\/g, '\\\\')}";
|
|
202
|
-
const WINDOW_WIDTH = ${width};
|
|
203
|
-
const WINDOW_HEIGHT = ${height};
|
|
204
|
-
|
|
205
|
-
let mainWindow;
|
|
206
|
-
let splashWindow;
|
|
207
|
-
|
|
208
|
-
function logAppInfo() {
|
|
209
|
-
const packageJsonPath = path.join(__dirname, 'package.json');
|
|
210
|
-
let packageInfo = {};
|
|
211
|
-
|
|
212
|
-
try {
|
|
213
|
-
const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf-8');
|
|
214
|
-
packageInfo = JSON.parse(packageJsonContent);
|
|
215
|
-
} catch (error) {
|
|
216
|
-
console.error('Error reading package.json:', error.message);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
console.log('\\n--------------------------------');
|
|
220
|
-
console.log(' APPLICATION INFORMATION');
|
|
221
|
-
console.log('--------------------------------');
|
|
222
|
-
console.log(\`Application: \${APP_NAME}\`);
|
|
223
|
-
console.log(\`URL: \${APP_URL}\`);
|
|
224
|
-
console.log(\`Started at: \${new Date().toLocaleString()}\`);
|
|
225
|
-
console.log(\`App directory: \${__dirname}\`);
|
|
226
|
-
console.log(\`Icon path: \${APP_ICON_PATH}\`);
|
|
227
|
-
|
|
228
|
-
console.log('\\n PACKAGE INFO:');
|
|
229
|
-
console.log(\` - Name: \${packageInfo.name || 'N/A'}\`);
|
|
230
|
-
console.log(\` - Version: \${packageInfo.version || 'N/A'}\`);
|
|
231
|
-
console.log(\` - Description: \${packageInfo.description || 'N/A'}\`);
|
|
232
|
-
console.log(\` - Electron version: \${packageInfo.dependencies?.electron || 'N/A'}\`);
|
|
233
|
-
|
|
234
|
-
if (packageInfo.build) {
|
|
235
|
-
console.log('\\n BUILD CONFIG:');
|
|
236
|
-
console.log(\` - App ID: \${packageInfo.build.appId || 'N/A'}\`);
|
|
237
|
-
console.log(\` - Product Name: \${packageInfo.build.productName || 'N/A'}\`);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
console.log('--------------------------------\\n');
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
function createSplashScreen() {
|
|
244
|
-
splashWindow = new BrowserWindow({
|
|
245
|
-
width: 200,
|
|
246
|
-
height: 40,
|
|
247
|
-
transparent: true,
|
|
248
|
-
frame: false,
|
|
249
|
-
alwaysOnTop: true,
|
|
250
|
-
skipTaskbar: true,
|
|
251
|
-
resizable: false,
|
|
252
|
-
icon: APP_ICON_PATH,
|
|
253
|
-
webPreferences: {
|
|
254
|
-
nodeIntegration: false,
|
|
255
|
-
contextIsolation: true
|
|
256
|
-
}
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
const isDarkMode = nativeTheme.shouldUseDarkColors;
|
|
260
|
-
const bgColor = isDarkMode ? '#333333' : '#f5f5f5';
|
|
261
|
-
const loaderBgColor = isDarkMode ? '#555555' : '#e0e0e0';
|
|
262
|
-
const loaderColor = isDarkMode ? '#ffffff' : '#2563eb';
|
|
263
|
-
const shadowColor = isDarkMode ? 'rgba(0, 0, 0, 0.5)' : 'rgba(0, 0, 0, 0.2)';
|
|
264
|
-
|
|
265
|
-
const splashHtml = \`
|
|
266
|
-
<!DOCTYPE html>
|
|
267
|
-
<html>
|
|
268
|
-
<head>
|
|
269
|
-
<meta charset="UTF-8">
|
|
270
|
-
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'; object-src 'none';">
|
|
271
|
-
<title>Loading</title>
|
|
272
|
-
<style>
|
|
273
|
-
html, body {
|
|
274
|
-
margin: 0;
|
|
275
|
-
padding: 0;
|
|
276
|
-
width: 100%;
|
|
277
|
-
height: 100%;
|
|
278
|
-
overflow: hidden;
|
|
279
|
-
background-color: transparent;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
.container {
|
|
283
|
-
width: 100%;
|
|
284
|
-
height: 100%;
|
|
285
|
-
display: flex;
|
|
286
|
-
justify-content: center;
|
|
287
|
-
align-items: center;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
.loader-container {
|
|
291
|
-
width: 180px;
|
|
292
|
-
height: 12px;
|
|
293
|
-
background-color: \${bgColor};
|
|
294
|
-
border-radius: 6px;
|
|
295
|
-
overflow: hidden;
|
|
296
|
-
box-shadow: 0 2px 8px \${shadowColor};
|
|
297
|
-
padding: 3px;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
.loader-bg {
|
|
301
|
-
width: 100%;
|
|
302
|
-
height: 100%;
|
|
303
|
-
background-color: \${loaderBgColor};
|
|
304
|
-
border-radius: 4px;
|
|
305
|
-
overflow: hidden;
|
|
306
|
-
position: relative;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
.loader {
|
|
310
|
-
position: absolute;
|
|
311
|
-
top: 0;
|
|
312
|
-
left: 0;
|
|
313
|
-
height: 100%;
|
|
314
|
-
width: 30%;
|
|
315
|
-
background-color: \${loaderColor};
|
|
316
|
-
border-radius: 4px;
|
|
317
|
-
animation: loading 1.5s infinite ease-in-out;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
@keyframes loading {
|
|
321
|
-
0% {
|
|
322
|
-
left: -30%;
|
|
323
|
-
}
|
|
324
|
-
100% {
|
|
325
|
-
left: 100%;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
</style>
|
|
329
|
-
</head>
|
|
330
|
-
<body>
|
|
331
|
-
<div class="container">
|
|
332
|
-
<div class="loader-container">
|
|
333
|
-
<div class="loader-bg">
|
|
334
|
-
<div class="loader"></div>
|
|
335
|
-
</div>
|
|
336
|
-
</div>
|
|
337
|
-
</div>
|
|
338
|
-
</body>
|
|
339
|
-
</html>
|
|
340
|
-
\`;
|
|
341
|
-
|
|
342
|
-
const splashPath = path.join(app.getPath('temp'), \`\${APP_NAME}-splash.html\`);
|
|
343
|
-
fs.writeFileSync(splashPath, splashHtml);
|
|
344
|
-
|
|
345
|
-
splashWindow.loadFile(splashPath);
|
|
346
|
-
|
|
347
|
-
const { screen } = require('electron');
|
|
348
|
-
const primaryDisplay = screen.getPrimaryDisplay();
|
|
349
|
-
const { width, height } = primaryDisplay.workAreaSize;
|
|
350
|
-
|
|
351
|
-
splashWindow.setPosition(
|
|
352
|
-
Math.floor(width / 2 - 100),
|
|
353
|
-
Math.floor(height / 2 - 20)
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
function createWindow() {
|
|
358
|
-
logAppInfo();
|
|
359
|
-
|
|
360
|
-
app.setAppUserModelId(APP_NAME);
|
|
361
|
-
|
|
362
|
-
mainWindow = new BrowserWindow({
|
|
363
|
-
width: WINDOW_WIDTH,
|
|
364
|
-
height: WINDOW_HEIGHT,
|
|
365
|
-
title: APP_NAME,
|
|
366
|
-
icon: APP_ICON_PATH,
|
|
367
|
-
show: false,
|
|
368
|
-
webPreferences: {
|
|
369
|
-
nodeIntegration: false,
|
|
370
|
-
contextIsolation: true
|
|
371
|
-
}
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
mainWindow.webContents.on('did-finish-load', () => {
|
|
375
|
-
setTimeout(() => {
|
|
376
|
-
if (splashWindow && !splashWindow.isDestroyed()) {
|
|
377
|
-
splashWindow.close();
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
if (!mainWindow.isVisible()) {
|
|
381
|
-
mainWindow.show();
|
|
382
|
-
mainWindow.focus();
|
|
383
|
-
}
|
|
384
|
-
}, 500);
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
mainWindow.loadURL(APP_URL);
|
|
388
|
-
|
|
389
|
-
mainWindow.webContents.on('context-menu', (e, params) => {
|
|
390
|
-
e.preventDefault();
|
|
391
|
-
mainWindow.webContents.executeJavaScript(\`
|
|
392
|
-
window.addEventListener('contextmenu', (e) => {
|
|
393
|
-
e.stopPropagation();
|
|
394
|
-
}, true);
|
|
395
|
-
\`);
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
mainWindow.webContents.on('new-window', (event, url) => {
|
|
399
|
-
event.preventDefault();
|
|
400
|
-
shell.openExternal(url);
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
const template = [];
|
|
404
|
-
|
|
405
|
-
const menu = Menu.buildFromTemplate(template);
|
|
406
|
-
Menu.setApplicationMenu(menu);
|
|
407
|
-
|
|
408
|
-
setTimeout(() => {
|
|
409
|
-
if (splashWindow && !splashWindow.isDestroyed()) {
|
|
410
|
-
splashWindow.close();
|
|
411
|
-
if (!mainWindow.isVisible()) {
|
|
412
|
-
mainWindow.show();
|
|
413
|
-
mainWindow.focus();
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
}, 10000);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
if (process.platform === 'win32') {
|
|
420
|
-
app.setAppUserModelId(app.name);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
app.whenReady().then(() => {
|
|
424
|
-
createSplashScreen();
|
|
425
|
-
createWindow();
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
app.on('window-all-closed', () => {
|
|
429
|
-
if (process.platform !== 'darwin') {
|
|
430
|
-
app.quit();
|
|
431
|
-
}
|
|
432
|
-
});
|
|
433
|
-
|
|
434
|
-
app.on('activate', () => {
|
|
435
|
-
if (BrowserWindow.getAllWindows().length === 0) {
|
|
436
|
-
createWindow();
|
|
437
|
-
}
|
|
438
|
-
});
|
|
439
|
-
`;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
async function generatePackageJson(appName, iconPath, isExecutable = false, createSetup = false) {
|
|
443
|
-
const u2aPackagePath = path.resolve(__dirname, '../../package.json');
|
|
444
|
-
|
|
445
|
-
let u2aVersion = '1.0.0';
|
|
446
|
-
try {
|
|
447
|
-
const u2aPackageContent = fs.readFileSync(u2aPackagePath, 'utf8');
|
|
448
|
-
const u2aPackage = JSON.parse(u2aPackageContent);
|
|
449
|
-
u2aVersion = u2aPackage.version || u2aVersion;
|
|
450
|
-
} catch (error) {
|
|
451
|
-
logger.error('Error while fetching u2a package.json', error);
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
if (createSetup) {
|
|
455
|
-
iconPath = await processFavicon(iconPath);
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
const packageJson = {
|
|
459
|
-
name: `u2a-${appName.replace(/\s+/g, '-')}`,
|
|
460
|
-
version: u2aVersion,
|
|
461
|
-
description: `Web app for ${appName}`,
|
|
462
|
-
main: 'main.js',
|
|
463
|
-
author: `${appName}`,
|
|
464
|
-
scripts: {
|
|
465
|
-
start: 'electron .'
|
|
466
|
-
},
|
|
467
|
-
dependencies: {
|
|
468
|
-
electron: '^22.0.0'
|
|
469
|
-
},
|
|
470
|
-
build: {
|
|
471
|
-
appId: `com.u2a.${appName.replace(/\s+/g, '-')}`,
|
|
472
|
-
productName: appName,
|
|
473
|
-
icon: iconPath
|
|
474
|
-
}
|
|
475
|
-
};
|
|
476
|
-
|
|
477
|
-
if (isExecutable) {
|
|
478
|
-
packageJson.devDependencies = {
|
|
479
|
-
"electron-packager": "^17.1.1",
|
|
480
|
-
"electron-builder": "^24.6.3",
|
|
481
|
-
"electron": "^22.0.0"
|
|
482
|
-
};
|
|
483
|
-
|
|
484
|
-
packageJson.dependencies = {};
|
|
485
|
-
|
|
486
|
-
packageJson.scripts.package = "electron-packager . --overwrite --asar";
|
|
487
|
-
packageJson.scripts.setup = "electron-builder";
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
if (isExecutable && createSetup) {
|
|
491
|
-
packageJson.build = {
|
|
492
|
-
...packageJson.build,
|
|
493
|
-
appId: `com.u2a.${appName.replace(/\s+/g, '-')}`,
|
|
494
|
-
productName: appName,
|
|
495
|
-
directories: {
|
|
496
|
-
output: "installer"
|
|
497
|
-
},
|
|
498
|
-
files: [
|
|
499
|
-
"**/*",
|
|
500
|
-
"!**/node_modules/*/{CHANGELOG.md,README.md,README,readme.md,readme}",
|
|
501
|
-
"!**/node_modules/*/{test,__tests__,tests,powered-test,example,examples}",
|
|
502
|
-
"!**/node_modules/*.d.ts",
|
|
503
|
-
"!**/node_modules/.bin",
|
|
504
|
-
"!**/.{idea,git,cache,build,dist}",
|
|
505
|
-
"!dist/**/*",
|
|
506
|
-
"!installer/**/*"
|
|
507
|
-
],
|
|
508
|
-
win: {
|
|
509
|
-
target: "nsis",
|
|
510
|
-
icon: iconPath
|
|
511
|
-
},
|
|
512
|
-
mac: {
|
|
513
|
-
target: "dmg"
|
|
514
|
-
},
|
|
515
|
-
linux: {
|
|
516
|
-
target: "AppImage",
|
|
517
|
-
icon: iconPath
|
|
518
|
-
},
|
|
519
|
-
nsis: {
|
|
520
|
-
oneClick: false,
|
|
521
|
-
allowToChangeInstallationDirectory: true
|
|
522
|
-
}
|
|
523
|
-
};
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
return packageJson;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
16
|
function copyFolderRecursiveSync(source, target) {
|
|
530
17
|
if (!fs.existsSync(target)) {
|
|
531
18
|
fs.mkdirSync(target, { recursive: true });
|
|
@@ -545,71 +32,6 @@ function copyFolderRecursiveSync(source, target) {
|
|
|
545
32
|
});
|
|
546
33
|
}
|
|
547
34
|
|
|
548
|
-
async function buildExecutable(appDir, appName, platform, iconPath, options) {
|
|
549
|
-
logger.info(`Building executable for ${platform}...`);
|
|
550
|
-
|
|
551
|
-
try {
|
|
552
|
-
const installOptions = {
|
|
553
|
-
cwd: appDir,
|
|
554
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
555
|
-
windowsHide: true
|
|
556
|
-
};
|
|
557
|
-
|
|
558
|
-
secureExec('npm install --save-dev electron-packager electron', installOptions);
|
|
559
|
-
|
|
560
|
-
let platformFlag = '';
|
|
561
|
-
let archFlag = `--arch=${options.arch || 'x64'}`;
|
|
562
|
-
let iconOption = '';
|
|
563
|
-
|
|
564
|
-
switch(platform) {
|
|
565
|
-
case 'windows':
|
|
566
|
-
platformFlag = '--platform=win32';
|
|
567
|
-
iconOption = iconPath ? `--icon="${iconPath}"` : '';
|
|
568
|
-
break;
|
|
569
|
-
case 'darwin':
|
|
570
|
-
platformFlag = '--platform=darwin';
|
|
571
|
-
if (iconPath && !iconPath.endsWith('.icns')) {
|
|
572
|
-
logger.warn('MacOs Icons are not supported at this time.');
|
|
573
|
-
}
|
|
574
|
-
iconOption = iconPath ? `--icon="${iconPath}"` : '';
|
|
575
|
-
break;
|
|
576
|
-
case 'linux':
|
|
577
|
-
platformFlag = '--platform=linux';
|
|
578
|
-
iconOption = iconPath ? `--icon="${iconPath}"` : '';
|
|
579
|
-
break;
|
|
580
|
-
default:
|
|
581
|
-
platformFlag = `--platform=${process.platform}`;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
const packageCommand = `npx electron-packager . "${appName}" ${platformFlag} ${archFlag} --out=dist --overwrite --asar ${iconOption}`;
|
|
585
|
-
|
|
586
|
-
logger.debug(`Executing: ${packageCommand}`);
|
|
587
|
-
|
|
588
|
-
secureExec(packageCommand, installOptions);
|
|
589
|
-
|
|
590
|
-
let distPlatform = '';
|
|
591
|
-
switch(platform) {
|
|
592
|
-
case 'windows': distPlatform = 'win32'; break;
|
|
593
|
-
case 'darwin': distPlatform = 'darwin'; break;
|
|
594
|
-
case 'linux': distPlatform = 'linux'; break;
|
|
595
|
-
default: distPlatform = process.platform;
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
const outputPath = path.join(appDir, 'dist', `${appName}-${distPlatform}-x64`);
|
|
599
|
-
|
|
600
|
-
if (fs.existsSync(outputPath)) {
|
|
601
|
-
logger.debug(`Executable built successfully at: ${outputPath}`);
|
|
602
|
-
return outputPath;
|
|
603
|
-
} else {
|
|
604
|
-
logger.error(`Failed to find the built executable at: ${outputPath}`);
|
|
605
|
-
return null;
|
|
606
|
-
}
|
|
607
|
-
} catch (error) {
|
|
608
|
-
logger.error(`Error while building executable:`, error);
|
|
609
|
-
return null;
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
|
|
613
35
|
function remove(path) {
|
|
614
36
|
try {
|
|
615
37
|
if (fs.existsSync(path)) {
|
|
@@ -621,55 +43,6 @@ function remove(path) {
|
|
|
621
43
|
}
|
|
622
44
|
}
|
|
623
45
|
|
|
624
|
-
async function buildSetup(appDir, platform, arch) {
|
|
625
|
-
logger.info(`Building setup for ${platform}${arch ? ` (${arch})` : ''}...`);
|
|
626
|
-
|
|
627
|
-
try {
|
|
628
|
-
const installOptions = {
|
|
629
|
-
cwd: appDir,
|
|
630
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
631
|
-
windowsHide: true
|
|
632
|
-
};
|
|
633
|
-
|
|
634
|
-
secureExec('npm install --save-dev electron-builder', installOptions);
|
|
635
|
-
|
|
636
|
-
let builderArgs = '';
|
|
637
|
-
switch(platform) {
|
|
638
|
-
case 'windows':
|
|
639
|
-
builderArgs = '--win';
|
|
640
|
-
break;
|
|
641
|
-
case 'darwin':
|
|
642
|
-
builderArgs = '--mac';
|
|
643
|
-
break;
|
|
644
|
-
case 'linux':
|
|
645
|
-
builderArgs = '--linux';
|
|
646
|
-
break;
|
|
647
|
-
default:
|
|
648
|
-
builderArgs = '';
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
if (arch) {
|
|
652
|
-
builderArgs += ` --${arch}`;
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
const builderCommand = `npx electron-builder ${builderArgs}`;
|
|
656
|
-
logger.debug(`Executing: ${builderCommand}`);
|
|
657
|
-
secureExec(builderCommand, installOptions);
|
|
658
|
-
|
|
659
|
-
const installerPath = path.join(appDir, 'installer');
|
|
660
|
-
if (fs.existsSync(installerPath)) {
|
|
661
|
-
logger.debug(`Setup created at: ${installerPath}`);
|
|
662
|
-
return installerPath;
|
|
663
|
-
} else {
|
|
664
|
-
logger.error(`Failed to find the built installer at: ${installerPath}`);
|
|
665
|
-
return null;
|
|
666
|
-
}
|
|
667
|
-
} catch (error) {
|
|
668
|
-
logger.error(`Error while building setup:`, error);
|
|
669
|
-
return null;
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
|
|
673
46
|
async function createApp(url, options) {
|
|
674
47
|
logger.info(`Creating application for ${url}`);
|
|
675
48
|
|
|
@@ -700,7 +73,7 @@ async function createApp(url, options) {
|
|
|
700
73
|
const isExecutable = !!options.executable;
|
|
701
74
|
const createSetup = !!options.setup;
|
|
702
75
|
const packageJsonPath = path.join(appDir, 'package.json');
|
|
703
|
-
const packageJsonContent = await
|
|
76
|
+
const packageJsonContent = await createPackageJson(appName, iconPath, isExecutable, createSetup);
|
|
704
77
|
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJsonContent, null, 2));
|
|
705
78
|
logger.debug(`package.json file created`);
|
|
706
79
|
|
package/src/commands/remove.js
CHANGED
|
@@ -34,8 +34,8 @@ async function processRemoval(appName) {
|
|
|
34
34
|
const appInfo = db[appName];
|
|
35
35
|
const appDir = appInfo.path;
|
|
36
36
|
|
|
37
|
-
removeAppFromOS(appName);
|
|
38
37
|
logger.info(`Removing the application ${appName}...`);
|
|
38
|
+
removeAppFromOS(appName);
|
|
39
39
|
|
|
40
40
|
const iconPath = path.join(APPS_DIR, `${appName}.ico`);
|
|
41
41
|
if (fs.existsSync(iconPath)) {
|
|
@@ -43,6 +43,24 @@ async function processRemoval(appName) {
|
|
|
43
43
|
logger.success(`Icon for ${appName} removed`);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
const packageName = `u2a-${appName.replace(/\s+/g, '-')}`;
|
|
47
|
+
let appDataPath;
|
|
48
|
+
|
|
49
|
+
if (process.platform === 'win32') {
|
|
50
|
+
appDataPath = path.join(process.env.APPDATA, packageName);
|
|
51
|
+
} else if (process.platform === 'darwin') {
|
|
52
|
+
appDataPath = path.join(os.homedir(), 'Library', 'Application Support', packageName);
|
|
53
|
+
} else if (process.platform === 'linux') {
|
|
54
|
+
appDataPath = path.join(os.homedir(), '.config', packageName);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (appDataPath && fs.existsSync(appDataPath)) {
|
|
58
|
+
fs.rmSync(appDataPath, { recursive: true, force: true });
|
|
59
|
+
logger.success(`Application data folder removed: ${appDataPath}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
fs.rmSync(appDir, { recursive: true, force: true });
|
|
63
|
+
|
|
46
64
|
fs.rmSync(appDir, { recursive: true, force: true });
|
|
47
65
|
delete db[appName];
|
|
48
66
|
writeDB(db);
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const Logger = require('./logger');
|
|
4
|
+
|
|
5
|
+
const logger = new Logger('appGenerator');
|
|
6
|
+
|
|
7
|
+
function generateMainJs(appName, url, iconPath, options = {}) {
|
|
8
|
+
const width = options.width || 1200;
|
|
9
|
+
const height = options.height || 800;
|
|
10
|
+
|
|
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 = {};
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf-8');
|
|
31
|
+
packageInfo = JSON.parse(packageJsonContent);
|
|
32
|
+
} 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'}\`);
|
|
55
|
+
}
|
|
56
|
+
|
|
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);
|
|
161
|
+
|
|
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
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
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
|
+
}
|
|
258
|
+
|
|
259
|
+
async function createPackageJson(appName, iconPath, isExecutable = false, createSetup = false) {
|
|
260
|
+
const u2aPackagePath = path.resolve(__dirname, '../../package.json');
|
|
261
|
+
|
|
262
|
+
let u2aVersion = '1.0.0';
|
|
263
|
+
try {
|
|
264
|
+
const u2aPackageContent = fs.readFileSync(u2aPackagePath, 'utf8');
|
|
265
|
+
const u2aPackage = JSON.parse(u2aPackageContent);
|
|
266
|
+
u2aVersion = u2aPackage.version || u2aVersion;
|
|
267
|
+
} catch (error) {
|
|
268
|
+
logger.error('Error while fetching u2a package.json', error);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (createSetup) {
|
|
272
|
+
const { processFavicon } = require('./favicon');
|
|
273
|
+
iconPath = await processFavicon(iconPath);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const packageJson = {
|
|
277
|
+
name: `u2a-${appName.replace(/\s+/g, '-')}`,
|
|
278
|
+
version: u2aVersion,
|
|
279
|
+
description: `Web app for ${appName}`,
|
|
280
|
+
main: 'main.js',
|
|
281
|
+
author: `${appName}`,
|
|
282
|
+
scripts: {
|
|
283
|
+
start: 'electron .'
|
|
284
|
+
},
|
|
285
|
+
dependencies: {
|
|
286
|
+
electron: '^22.0.0'
|
|
287
|
+
},
|
|
288
|
+
build: {
|
|
289
|
+
appId: `com.u2a.${appName.replace(/\s+/g, '-')}`,
|
|
290
|
+
productName: appName,
|
|
291
|
+
icon: iconPath
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
if (isExecutable) {
|
|
296
|
+
packageJson.devDependencies = {
|
|
297
|
+
"electron-packager": "^17.1.1",
|
|
298
|
+
"electron-builder": "^24.6.3",
|
|
299
|
+
"electron": "^22.0.0"
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
packageJson.dependencies = {};
|
|
303
|
+
|
|
304
|
+
packageJson.scripts.package = "electron-packager . --overwrite --asar";
|
|
305
|
+
packageJson.scripts.setup = "electron-builder";
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (isExecutable && createSetup) {
|
|
309
|
+
packageJson.build = {
|
|
310
|
+
...packageJson.build,
|
|
311
|
+
appId: `com.u2a.${appName.replace(/\s+/g, '-')}`,
|
|
312
|
+
productName: appName,
|
|
313
|
+
directories: {
|
|
314
|
+
output: "installer"
|
|
315
|
+
},
|
|
316
|
+
files: [
|
|
317
|
+
"**/*",
|
|
318
|
+
"!**/node_modules/*/{CHANGELOG.md,README.md,README,readme.md,readme}",
|
|
319
|
+
"!**/node_modules/*/{test,__tests__,tests,powered-test,example,examples}",
|
|
320
|
+
"!**/node_modules/*.d.ts",
|
|
321
|
+
"!**/node_modules/.bin",
|
|
322
|
+
"!**/.{idea,git,cache,build,dist}",
|
|
323
|
+
"!dist/**/*",
|
|
324
|
+
"!installer/**/*"
|
|
325
|
+
],
|
|
326
|
+
win: {
|
|
327
|
+
target: "nsis",
|
|
328
|
+
icon: iconPath
|
|
329
|
+
},
|
|
330
|
+
mac: {
|
|
331
|
+
target: "dmg"
|
|
332
|
+
},
|
|
333
|
+
linux: {
|
|
334
|
+
target: "AppImage",
|
|
335
|
+
icon: iconPath
|
|
336
|
+
},
|
|
337
|
+
nsis: {
|
|
338
|
+
oneClick: false,
|
|
339
|
+
allowToChangeInstallationDirectory: true
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return packageJson;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
module.exports = {
|
|
348
|
+
generateMainJs,
|
|
349
|
+
createPackageJson
|
|
350
|
+
};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const Logger = require('./logger');
|
|
4
|
+
const { secureExec } = require('./securexec');
|
|
5
|
+
|
|
6
|
+
const logger = new Logger('builder');
|
|
7
|
+
|
|
8
|
+
async function buildExecutable(appDir, appName, platform, iconPath, options) {
|
|
9
|
+
logger.info(`Building executable for ${platform}...`);
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const installOptions = {
|
|
13
|
+
cwd: appDir,
|
|
14
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
15
|
+
windowsHide: true
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
secureExec('npm install --save-dev electron-packager electron', installOptions);
|
|
19
|
+
|
|
20
|
+
let platformFlag = '';
|
|
21
|
+
let archFlag = `--arch=${options.arch || 'x64'}`;
|
|
22
|
+
let iconOption = '';
|
|
23
|
+
|
|
24
|
+
switch(platform) {
|
|
25
|
+
case 'windows':
|
|
26
|
+
platformFlag = '--platform=win32';
|
|
27
|
+
iconOption = iconPath ? `--icon="${iconPath}"` : '';
|
|
28
|
+
break;
|
|
29
|
+
case 'darwin':
|
|
30
|
+
platformFlag = '--platform=darwin';
|
|
31
|
+
if (iconPath && !iconPath.endsWith('.icns')) {
|
|
32
|
+
logger.warn('MacOs Icons are not supported at this time.');
|
|
33
|
+
}
|
|
34
|
+
iconOption = iconPath ? `--icon="${iconPath}"` : '';
|
|
35
|
+
break;
|
|
36
|
+
case 'linux':
|
|
37
|
+
platformFlag = '--platform=linux';
|
|
38
|
+
iconOption = iconPath ? `--icon="${iconPath}"` : '';
|
|
39
|
+
break;
|
|
40
|
+
default:
|
|
41
|
+
platformFlag = `--platform=${process.platform}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const packageCommand = `npx electron-packager . "${appName}" ${platformFlag} ${archFlag} --out=dist --overwrite --asar ${iconOption}`;
|
|
45
|
+
|
|
46
|
+
logger.debug(`Executing: ${packageCommand}`);
|
|
47
|
+
|
|
48
|
+
secureExec(packageCommand, installOptions);
|
|
49
|
+
|
|
50
|
+
let distPlatform = '';
|
|
51
|
+
switch(platform) {
|
|
52
|
+
case 'windows': distPlatform = 'win32'; break;
|
|
53
|
+
case 'darwin': distPlatform = 'darwin'; break;
|
|
54
|
+
case 'linux': distPlatform = 'linux'; break;
|
|
55
|
+
default: distPlatform = process.platform;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const outputPath = path.join(appDir, 'dist', `${appName}-${distPlatform}-x64`);
|
|
59
|
+
|
|
60
|
+
if (fs.existsSync(outputPath)) {
|
|
61
|
+
logger.debug(`Executable built successfully at: ${outputPath}`);
|
|
62
|
+
return outputPath;
|
|
63
|
+
} else {
|
|
64
|
+
logger.error(`Failed to find the built executable at: ${outputPath}`);
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
} catch (error) {
|
|
68
|
+
logger.error(`Error while building executable:`, error);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function buildSetup(appDir, platform, arch) {
|
|
74
|
+
logger.info(`Building setup for ${platform}${arch ? ` (${arch})` : ''}...`);
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const installOptions = {
|
|
78
|
+
cwd: appDir,
|
|
79
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
80
|
+
windowsHide: true
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
secureExec('npm install --save-dev electron-builder', installOptions);
|
|
84
|
+
|
|
85
|
+
let builderArgs = '';
|
|
86
|
+
switch(platform) {
|
|
87
|
+
case 'windows':
|
|
88
|
+
builderArgs = '--win';
|
|
89
|
+
break;
|
|
90
|
+
case 'darwin':
|
|
91
|
+
builderArgs = '--mac';
|
|
92
|
+
break;
|
|
93
|
+
case 'linux':
|
|
94
|
+
builderArgs = '--linux';
|
|
95
|
+
break;
|
|
96
|
+
default:
|
|
97
|
+
builderArgs = '';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (arch) {
|
|
101
|
+
builderArgs += ` --${arch}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const builderCommand = `npx electron-builder ${builderArgs}`;
|
|
105
|
+
logger.debug(`Executing: ${builderCommand}`);
|
|
106
|
+
secureExec(builderCommand, installOptions);
|
|
107
|
+
|
|
108
|
+
const installerPath = path.join(appDir, 'installer');
|
|
109
|
+
if (fs.existsSync(installerPath)) {
|
|
110
|
+
logger.debug(`Setup created at: ${installerPath}`);
|
|
111
|
+
return installerPath;
|
|
112
|
+
} else {
|
|
113
|
+
logger.error(`Failed to find the built installer at: ${installerPath}`);
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
} catch (error) {
|
|
117
|
+
logger.error(`Error while building setup:`, error);
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = {
|
|
123
|
+
buildExecutable,
|
|
124
|
+
buildSetup
|
|
125
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const Logger = require('./logger');
|
|
5
|
+
const { secureExec } = require('./securexec');
|
|
6
|
+
|
|
7
|
+
const logger = new Logger('osIntegration');
|
|
8
|
+
|
|
9
|
+
function createWindowsShortcut(appInfo) {
|
|
10
|
+
try {
|
|
11
|
+
const { appName, appDir, iconPath } = appInfo;
|
|
12
|
+
const startMenuPath = path.join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'U2A Apps');
|
|
13
|
+
|
|
14
|
+
if (!fs.existsSync(startMenuPath)) {
|
|
15
|
+
fs.mkdirSync(startMenuPath, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const shortcutPath = path.join(startMenuPath, `${appName}.lnk`);
|
|
19
|
+
const targetPath = path.join(appDir, 'node_modules', '.bin', 'electron.cmd');
|
|
20
|
+
const workingDir = appDir;
|
|
21
|
+
|
|
22
|
+
const psScript = `
|
|
23
|
+
$WshShell = New-Object -comObject WScript.Shell
|
|
24
|
+
$Shortcut = $WshShell.CreateShortcut("${shortcutPath.replace(/\\/g, '\\\\')}")
|
|
25
|
+
$Shortcut.TargetPath = "${targetPath.replace(/\\/g, '\\\\')}"
|
|
26
|
+
$Shortcut.Arguments = "."
|
|
27
|
+
$Shortcut.WorkingDirectory = "${workingDir.replace(/\\/g, '\\\\')}"
|
|
28
|
+
$Shortcut.IconLocation = "${iconPath.replace(/\\/g, '\\\\')}"
|
|
29
|
+
$Shortcut.Description = "Application Web pour ${appName}"
|
|
30
|
+
$Shortcut.Save()
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
const tempScriptPath = path.join(os.tmpdir(), `create_shortcut_${appName}.ps1`);
|
|
34
|
+
fs.writeFileSync(tempScriptPath, psScript);
|
|
35
|
+
|
|
36
|
+
secureExec(`powershell -ExecutionPolicy Bypass -File "${tempScriptPath}"`, {
|
|
37
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
38
|
+
windowsHide: true
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
fs.unlinkSync(tempScriptPath);
|
|
42
|
+
|
|
43
|
+
logger.success(`Shortcut created in the Start Menu: ${shortcutPath}`);
|
|
44
|
+
return shortcutPath;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
logger.error(`Error while creating the Windows shortcut`, error);
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function createLinuxDesktopEntry(appInfo) {
|
|
52
|
+
try {
|
|
53
|
+
const { appName, url, appDir, iconPath } = appInfo;
|
|
54
|
+
const appsDir = path.join(os.homedir(), '.local', 'share', 'applications');
|
|
55
|
+
|
|
56
|
+
if (!fs.existsSync(appsDir)) {
|
|
57
|
+
fs.mkdirSync(appsDir, { recursive: true });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const desktopEntry = `[Desktop Entry]
|
|
61
|
+
Type=Application
|
|
62
|
+
Name=${appName}
|
|
63
|
+
Exec=${path.join(appDir, 'node_modules', '.bin', 'electron')} ${path.join(appDir, 'main.js')}
|
|
64
|
+
Icon=${iconPath}
|
|
65
|
+
Comment=Application Web pour ${url}
|
|
66
|
+
Categories=Network;WebBrowser;
|
|
67
|
+
Terminal=false
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
const desktopFilePath = path.join(appsDir, `u2a-${appName}.desktop`);
|
|
71
|
+
fs.writeFileSync(desktopFilePath, desktopEntry);
|
|
72
|
+
|
|
73
|
+
fs.chmodSync(desktopFilePath, '755');
|
|
74
|
+
|
|
75
|
+
logger.success(`Desktop entry created for Linux: ${desktopFilePath}`);
|
|
76
|
+
return desktopFilePath;
|
|
77
|
+
} catch (error) {
|
|
78
|
+
logger.error(`Error while creating the Linux desktop entry`, error);
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function createMacOSApp(appInfo) {
|
|
84
|
+
try {
|
|
85
|
+
const { appName, appDir, iconPath } = appInfo;
|
|
86
|
+
const appsDir = path.join(os.homedir(), 'Applications', 'U2A Apps');
|
|
87
|
+
|
|
88
|
+
if (!fs.existsSync(appsDir)) {
|
|
89
|
+
fs.mkdirSync(appsDir, { recursive: true });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const appPath = path.join(appsDir, `${appName}.app`);
|
|
93
|
+
const macOsPath = path.join(appPath, 'Contents', 'MacOS');
|
|
94
|
+
const resourcesPath = path.join(appPath, 'Contents', 'Resources');
|
|
95
|
+
|
|
96
|
+
fs.mkdirSync(path.join(appPath, 'Contents', 'MacOS'), { recursive: true });
|
|
97
|
+
fs.mkdirSync(resourcesPath, { recursive: true });
|
|
98
|
+
|
|
99
|
+
const infoPlist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
100
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
101
|
+
<plist version="1.0">
|
|
102
|
+
<dict>
|
|
103
|
+
<key>CFBundleExecutable</key>
|
|
104
|
+
<string>AppRunner</string>
|
|
105
|
+
<key>CFBundleIconFile</key>
|
|
106
|
+
<string>icon.icns</string>
|
|
107
|
+
<key>CFBundleIdentifier</key>
|
|
108
|
+
<string>com.u2a.${appName.replace(/\s+/g, '-')}</string>
|
|
109
|
+
<key>CFBundleName</key>
|
|
110
|
+
<string>${appName}</string>
|
|
111
|
+
<key>CFBundleDisplayName</key>
|
|
112
|
+
<string>${appName}</string>
|
|
113
|
+
<key>CFBundlePackageType</key>
|
|
114
|
+
<string>APPL</string>
|
|
115
|
+
<key>CFBundleVersion</key>
|
|
116
|
+
<string>1.0</string>
|
|
117
|
+
<key>CFBundleShortVersionString</key>
|
|
118
|
+
<string>1.0</string>
|
|
119
|
+
</dict>
|
|
120
|
+
</plist>`;
|
|
121
|
+
|
|
122
|
+
fs.writeFileSync(path.join(appPath, 'Contents', 'Info.plist'), infoPlist);
|
|
123
|
+
|
|
124
|
+
const shellScript = `#!/bin/bash
|
|
125
|
+
cd "${appDir}"
|
|
126
|
+
"${path.join(appDir, 'node_modules', '.bin', 'electron')}" "${path.join(appDir, 'main.js')}"`;
|
|
127
|
+
|
|
128
|
+
const shellScriptPath = path.join(macOsPath, 'AppRunner');
|
|
129
|
+
fs.writeFileSync(shellScriptPath, shellScript);
|
|
130
|
+
fs.chmodSync(shellScriptPath, '755');
|
|
131
|
+
|
|
132
|
+
fs.copyFileSync(iconPath, path.join(resourcesPath, 'icon.icns'));
|
|
133
|
+
|
|
134
|
+
logger.success(`macOS application created: ${appPath}`);
|
|
135
|
+
return appPath;
|
|
136
|
+
} catch (error) {
|
|
137
|
+
logger.error(`Error while creating the macOS application`, error);
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function addAppToOS(appName, url, appDir, iconPath) {
|
|
143
|
+
const appInfo = { appName, url, appDir, iconPath };
|
|
144
|
+
let desktopPath = null;
|
|
145
|
+
|
|
146
|
+
if (process.platform === 'win32') {
|
|
147
|
+
desktopPath = createWindowsShortcut(appInfo);
|
|
148
|
+
} else if (process.platform === 'darwin') {
|
|
149
|
+
desktopPath = createMacOSApp(appInfo);
|
|
150
|
+
} else if (process.platform === 'linux') {
|
|
151
|
+
desktopPath = createLinuxDesktopEntry(appInfo);
|
|
152
|
+
} else {
|
|
153
|
+
logger.warn(`Desktop integration not supported for platform ${process.platform}`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return desktopPath;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function removeAppFromOS(appName) {
|
|
160
|
+
try {
|
|
161
|
+
if (process.platform === 'win32') {
|
|
162
|
+
const startMenuPath = path.join(process.env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'U2A Apps', `${appName}.lnk`);
|
|
163
|
+
if (fs.existsSync(startMenuPath)) {
|
|
164
|
+
fs.unlinkSync(startMenuPath);
|
|
165
|
+
logger.success(`Shortcut removed from the Start Menu: ${startMenuPath}`);
|
|
166
|
+
}
|
|
167
|
+
} else if (process.platform === 'darwin') {
|
|
168
|
+
const appPath = path.join(os.homedir(), 'Applications', 'U2A Apps', `${appName}.app`);
|
|
169
|
+
if (fs.existsSync(appPath)) {
|
|
170
|
+
fs.rmSync(appPath, { recursive: true, force: true });
|
|
171
|
+
logger.success(`macOS application removed: ${appPath}`);
|
|
172
|
+
}
|
|
173
|
+
} else if (process.platform === 'linux') {
|
|
174
|
+
const desktopFilePath = path.join(os.homedir(), '.local', 'share', 'applications', `u2a-${appName}.desktop`);
|
|
175
|
+
if (fs.existsSync(desktopFilePath)) {
|
|
176
|
+
fs.unlinkSync(desktopFilePath);
|
|
177
|
+
logger.success(`Linux desktop entry removed: ${desktopFilePath}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
} catch (error) {
|
|
181
|
+
logger.error(`Error while removing desktop integration for ${appName}`, error);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
module.exports = {
|
|
186
|
+
addAppToOS,
|
|
187
|
+
removeAppFromOS
|
|
188
|
+
};
|