deskify-cli 1.0.2 ā 1.0.5
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 +8 -13
- package/deskify.js +11 -1
- package/package.json +1 -1
- package/src/flows/create.js +69 -23
- package/src/flows/manage.js +240 -0
- package/src/utils/prompts.js +2 -4
package/README.md
CHANGED
|
@@ -84,19 +84,14 @@ npm install -g .
|
|
|
84
84
|
|
|
85
85
|
## How It Works
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
G --> H[Run Sudo chmod/chown on chrome-sandbox]
|
|
96
|
-
H --> I[Generate ~/.local/share/applications/App.desktop]
|
|
97
|
-
I --> J[Run update-desktop-database]
|
|
98
|
-
J --> K[App appears in System Search & Dock with proper icon!]
|
|
99
|
-
```
|
|
87
|
+
Deskify automates the following sequence:
|
|
88
|
+
1. **Pre-flight Checks**: Verifies Node.js, NPM, and system dependencies are available.
|
|
89
|
+
2. **Icon Extraction**: Automatically crawls or queries high-resolution favicons for your application.
|
|
90
|
+
3. **Compilation**: Packages the web app into an Electron wrapper via Nativefier.
|
|
91
|
+
4. **App Organization**: Places the package folders inside a clean `~/Apps` installation directory.
|
|
92
|
+
5. **StartupWMClass Mapping**: Parses the package details to map window instances correctly to the taskbar icon.
|
|
93
|
+
6. **Sandbox Configuration**: Sets the appropriate root permissions on the Chrome sandbox binary.
|
|
94
|
+
7. **Shortcut Registration**: Creates the launcher database entry (`.desktop`) and updates GNOME shortcuts.
|
|
100
95
|
|
|
101
96
|
---
|
|
102
97
|
|
package/deskify.js
CHANGED
|
@@ -12,6 +12,7 @@ const { colors, log } = require('./src/utils/colors');
|
|
|
12
12
|
const { selectOption, waitForKey } = require('./src/utils/prompts');
|
|
13
13
|
const { startSpinner, stopSpinner } = require('./src/utils/spinner');
|
|
14
14
|
const { createFlow } = require('./src/flows/create');
|
|
15
|
+
const { manageFlow } = require('./src/flows/manage');
|
|
15
16
|
const { uninstallFlow } = require('./src/flows/uninstall');
|
|
16
17
|
|
|
17
18
|
function printHeader() {
|
|
@@ -67,6 +68,7 @@ async function main() {
|
|
|
67
68
|
|
|
68
69
|
const options = [
|
|
69
70
|
'Create a new Desktop App',
|
|
71
|
+
'List & Manage existing Apps',
|
|
70
72
|
'Uninstall an existing App',
|
|
71
73
|
'Exit'
|
|
72
74
|
];
|
|
@@ -89,6 +91,14 @@ async function main() {
|
|
|
89
91
|
await waitForKey();
|
|
90
92
|
}
|
|
91
93
|
} else if (selectedIdx === 1) {
|
|
94
|
+
try {
|
|
95
|
+
await manageFlow(hasDesktopUtils);
|
|
96
|
+
} catch (e) {
|
|
97
|
+
log.error('An error occurred during application management:');
|
|
98
|
+
console.error(e);
|
|
99
|
+
await waitForKey();
|
|
100
|
+
}
|
|
101
|
+
} else if (selectedIdx === 2) {
|
|
92
102
|
try {
|
|
93
103
|
const result = await uninstallFlow(hasDesktopUtils);
|
|
94
104
|
if (result && result.success) {
|
|
@@ -103,7 +113,7 @@ async function main() {
|
|
|
103
113
|
console.error(e);
|
|
104
114
|
await waitForKey();
|
|
105
115
|
}
|
|
106
|
-
} else if (selectedIdx ===
|
|
116
|
+
} else if (selectedIdx === 3) {
|
|
107
117
|
log.info('Thank you for using Deskify! Goodbye! š');
|
|
108
118
|
process.exit(0);
|
|
109
119
|
}
|
package/package.json
CHANGED
package/src/flows/create.js
CHANGED
|
@@ -24,12 +24,25 @@ function printHeader() {
|
|
|
24
24
|
console.log(`${colors.dim}--------------------------------------------------${colors.reset}\n`);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
async function createFlow(hasDesktopUtils) {
|
|
27
|
+
async function createFlow(hasDesktopUtils, existingConfig = null) {
|
|
28
28
|
console.clear();
|
|
29
29
|
printHeader();
|
|
30
|
-
|
|
30
|
+
if (existingConfig) {
|
|
31
|
+
log.bold(`--- Edit/Update Desktop Application: ${existingConfig.appName} ---`);
|
|
32
|
+
} else {
|
|
33
|
+
log.bold('--- Create a New Desktop Application ---');
|
|
34
|
+
}
|
|
31
35
|
console.log('');
|
|
32
36
|
|
|
37
|
+
let defaultTargetUrl = existingConfig ? existingConfig.url : '';
|
|
38
|
+
let defaultAppName = existingConfig ? existingConfig.appName : '';
|
|
39
|
+
let defaultPersist = existingConfig ? existingConfig.persist : true;
|
|
40
|
+
let defaultInternalUrls = existingConfig ? existingConfig.internalUrls : '';
|
|
41
|
+
let defaultWidth = existingConfig ? existingConfig.width : '1200';
|
|
42
|
+
let defaultHeight = existingConfig ? existingConfig.height : '800';
|
|
43
|
+
let defaultInstallPath = existingConfig ? path.dirname(existingConfig.appFolder) : '~/Apps';
|
|
44
|
+
let defaultNoSandbox = existingConfig ? existingConfig.noSandbox : false;
|
|
45
|
+
|
|
33
46
|
let targetUrl = '';
|
|
34
47
|
let appName = '';
|
|
35
48
|
let iconPath = '';
|
|
@@ -38,11 +51,12 @@ async function createFlow(hasDesktopUtils) {
|
|
|
38
51
|
let width = '1200';
|
|
39
52
|
let height = '800';
|
|
40
53
|
let installPath = '';
|
|
54
|
+
let noSandbox = false;
|
|
41
55
|
|
|
42
56
|
try {
|
|
43
57
|
let targetUrlInput = '';
|
|
44
58
|
while (!targetUrlInput) {
|
|
45
|
-
targetUrlInput = await ask('Enter Website URL (e.g., https://nextcloud.com)');
|
|
59
|
+
targetUrlInput = await ask('Enter Website URL (e.g., https://nextcloud.com)', defaultTargetUrl);
|
|
46
60
|
if (!targetUrlInput) {
|
|
47
61
|
log.error('URL cannot be empty. Please try again.');
|
|
48
62
|
}
|
|
@@ -53,7 +67,7 @@ async function createFlow(hasDesktopUtils) {
|
|
|
53
67
|
|
|
54
68
|
log.info(`Parsed Target URL: ${colors.bright}${targetUrl}${colors.reset}`);
|
|
55
69
|
|
|
56
|
-
appName = await ask('Enter Application Name', urlDetails.nameDefault);
|
|
70
|
+
appName = await ask('Enter Application Name', defaultAppName || urlDetails.nameDefault);
|
|
57
71
|
|
|
58
72
|
const useFavicon = await askYesNo('Download and use website favicon as app icon?', true);
|
|
59
73
|
|
|
@@ -81,12 +95,13 @@ async function createFlow(hasDesktopUtils) {
|
|
|
81
95
|
}
|
|
82
96
|
}
|
|
83
97
|
|
|
84
|
-
persistSession = await askYesNo('Persist session cookies, local storage, and cache?',
|
|
85
|
-
internalUrlsRegex = await ask('Internal URL pattern regex', urlDetails.regexDefault);
|
|
86
|
-
width = await ask('Window width (pixels)',
|
|
87
|
-
height = await ask('Window height (pixels)',
|
|
98
|
+
persistSession = await askYesNo('Persist session cookies, local storage, and cache?', defaultPersist);
|
|
99
|
+
internalUrlsRegex = await ask('Internal URL pattern regex', defaultInternalUrls || urlDetails.regexDefault);
|
|
100
|
+
width = await ask('Window width (pixels)', defaultWidth);
|
|
101
|
+
height = await ask('Window height (pixels)', defaultHeight);
|
|
102
|
+
noSandbox = await askYesNo('Disable Chromium sandbox (avoids requiring sudo)?', defaultNoSandbox);
|
|
88
103
|
|
|
89
|
-
const installPathInput = await ask('Installation directory',
|
|
104
|
+
const installPathInput = await ask('Installation directory', defaultInstallPath);
|
|
90
105
|
installPath = expandHome(installPathInput);
|
|
91
106
|
|
|
92
107
|
console.log(`\n${colors.bright}--- Build Configuration Summary ---${colors.reset}`);
|
|
@@ -95,6 +110,7 @@ async function createFlow(hasDesktopUtils) {
|
|
|
95
110
|
console.log(`Persist: ${persistSession ? 'Yes' : 'No'}`);
|
|
96
111
|
console.log(`Internal URLs: ${internalUrlsRegex}`);
|
|
97
112
|
console.log(`Dimensions: ${width}x${height}`);
|
|
113
|
+
console.log(`Disable Sandbox: ${noSandbox ? 'Yes (Sudo-free)' : 'No (Requires Sudo)'}`);
|
|
98
114
|
console.log(`Install Dir: ${installPath}`);
|
|
99
115
|
if (iconPath) {
|
|
100
116
|
console.log(`Icon Path: ${iconPath}`);
|
|
@@ -114,6 +130,16 @@ async function createFlow(hasDesktopUtils) {
|
|
|
114
130
|
throw e;
|
|
115
131
|
}
|
|
116
132
|
|
|
133
|
+
// Deleting the existing application folder before rebuild to avoid folder name collisions
|
|
134
|
+
if (existingConfig && existingConfig.appFolder && fs.existsSync(existingConfig.appFolder)) {
|
|
135
|
+
log.info(`Deleting existing application folder for update: ${existingConfig.appFolder}`);
|
|
136
|
+
try {
|
|
137
|
+
fs.rmSync(existingConfig.appFolder, { recursive: true, force: true });
|
|
138
|
+
} catch (e) {
|
|
139
|
+
log.warn(`Could not delete existing folder: ${e.message}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
117
143
|
if (!fs.existsSync(installPath)) {
|
|
118
144
|
log.info(`Creating installation directory: ${installPath}`);
|
|
119
145
|
fs.mkdirSync(installPath, { recursive: true });
|
|
@@ -210,20 +236,24 @@ async function createFlow(hasDesktopUtils) {
|
|
|
210
236
|
log.warn('Could not find internal package.json. StartupWMClass might be inaccurate.');
|
|
211
237
|
}
|
|
212
238
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
if (fs.existsSync(chromeSandboxPath)) {
|
|
216
|
-
log.info(`Applying root ownership and SUID bit to: ${chromeSandboxPath}`);
|
|
217
|
-
try {
|
|
218
|
-
execSync(`sudo chown root:root "${chromeSandboxPath}" && sudo chmod 4755 "${chromeSandboxPath}"`, { stdio: 'inherit' });
|
|
219
|
-
log.success('Sandbox permissions applied successfully!');
|
|
220
|
-
} catch (e) {
|
|
221
|
-
log.error('Failed to configure chrome-sandbox permissions.');
|
|
222
|
-
log.info('You may need to run this command manually:');
|
|
223
|
-
console.log(` sudo chown root:root "${chromeSandboxPath}" && sudo chmod 4755 "${chromeSandboxPath}"`);
|
|
224
|
-
}
|
|
239
|
+
if (noSandbox) {
|
|
240
|
+
log.info('\n[4/4] Skipping chrome-sandbox permissions adjustment (sandbox disabled)...');
|
|
225
241
|
} else {
|
|
226
|
-
log.
|
|
242
|
+
log.bold('\n[4/4] Setting up sandbox execution permissions (requires sudo)...');
|
|
243
|
+
const chromeSandboxPath = path.join(appFolder, 'chrome-sandbox');
|
|
244
|
+
if (fs.existsSync(chromeSandboxPath)) {
|
|
245
|
+
log.info(`Applying root ownership and SUID bit to: ${chromeSandboxPath}`);
|
|
246
|
+
try {
|
|
247
|
+
execSync(`sudo chown root:root "${chromeSandboxPath}" && sudo chmod 4755 "${chromeSandboxPath}"`, { stdio: 'inherit' });
|
|
248
|
+
log.success('Sandbox permissions applied successfully!');
|
|
249
|
+
} catch (e) {
|
|
250
|
+
log.error('Failed to configure chrome-sandbox permissions.');
|
|
251
|
+
log.info('You may need to run this command manually:');
|
|
252
|
+
console.log(` sudo chown root:root "${chromeSandboxPath}" && sudo chmod 4755 "${chromeSandboxPath}"`);
|
|
253
|
+
}
|
|
254
|
+
} else {
|
|
255
|
+
log.warn('chrome-sandbox file not found in build directory. Skipping permission adjustment.');
|
|
256
|
+
}
|
|
227
257
|
}
|
|
228
258
|
|
|
229
259
|
log.bold('\n[5/5] Generating Desktop Shortcut launcher...');
|
|
@@ -232,6 +262,16 @@ async function createFlow(hasDesktopUtils) {
|
|
|
232
262
|
fs.mkdirSync(desktopEntriesDir, { recursive: true });
|
|
233
263
|
}
|
|
234
264
|
|
|
265
|
+
// Delete the old desktop shortcut if updating to avoid leftovers (e.g. if the name changed)
|
|
266
|
+
if (existingConfig && existingConfig.filePath && fs.existsSync(existingConfig.filePath)) {
|
|
267
|
+
try {
|
|
268
|
+
fs.unlinkSync(existingConfig.filePath);
|
|
269
|
+
log.info(`Cleaned up old desktop shortcut: ${existingConfig.filePath}`);
|
|
270
|
+
} catch (e) {
|
|
271
|
+
log.warn(`Could not delete old desktop shortcut: ${e.message}`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
235
275
|
const desktopFilePath = path.join(desktopEntriesDir, `${appName.replace(/\s+/g, '')}.desktop`);
|
|
236
276
|
const execPath = path.join(appFolder, appName);
|
|
237
277
|
const finalIconPath = fs.existsSync(destIconPath) ? destIconPath : (iconPath || 'electron');
|
|
@@ -239,13 +279,19 @@ async function createFlow(hasDesktopUtils) {
|
|
|
239
279
|
const desktopContent = `[Desktop Entry]
|
|
240
280
|
Name=${appName}
|
|
241
281
|
Comment=${appName} Web Desktop App
|
|
242
|
-
Exec=${execPath}
|
|
282
|
+
Exec=${execPath}${noSandbox ? ' --no-sandbox' : ''}
|
|
243
283
|
Icon=${finalIconPath}
|
|
244
284
|
Terminal=false
|
|
245
285
|
Type=Application
|
|
246
286
|
Categories=Network;WebBrowser;Application;
|
|
247
287
|
StartupWMClass=${startupWMClass}
|
|
248
288
|
X-Generated-By=deskify
|
|
289
|
+
X-Deskify-URL=${targetUrl}
|
|
290
|
+
X-Deskify-Width=${width}
|
|
291
|
+
X-Deskify-Height=${height}
|
|
292
|
+
X-Deskify-Persist=${persistSession}
|
|
293
|
+
X-Deskify-NoSandbox=${noSandbox}
|
|
294
|
+
X-Deskify-InternalUrls=${internalUrlsRegex}
|
|
249
295
|
`;
|
|
250
296
|
|
|
251
297
|
try {
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* manage.js
|
|
3
|
+
* Application list and management flow for the deskify CLI.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { execSync, spawn } = require('child_process');
|
|
9
|
+
const { colors, log } = require('../utils/colors');
|
|
10
|
+
const { expandHome, selectOption, askYesNo, waitForKey } = require('../utils/prompts');
|
|
11
|
+
const { createFlow } = require('./create');
|
|
12
|
+
|
|
13
|
+
function printHeader() {
|
|
14
|
+
console.clear();
|
|
15
|
+
console.log(`${colors.cyan}${colors.bright}`);
|
|
16
|
+
console.log(` _ _ _ __ `);
|
|
17
|
+
console.log(` __| | ___ ___| |_( )/ _|_ _ `);
|
|
18
|
+
console.log(` / _\` |/ _ \\/ __| |/ /| |_| | | |`);
|
|
19
|
+
console.log(` | (_| | __/\\__ \\ <| _| |_| |`);
|
|
20
|
+
console.log(` \\__,_|\\___||___/_|\\_\\\\_| \\__, |`);
|
|
21
|
+
console.log(` |___/ `);
|
|
22
|
+
console.log(` Web to Linux Desktop App Packager${colors.reset}`);
|
|
23
|
+
console.log(`${colors.dim}--------------------------------------------------${colors.reset}\n`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function getDeskifyApps() {
|
|
27
|
+
const desktopEntriesDir = expandHome('~/.local/share/applications');
|
|
28
|
+
if (!fs.existsSync(desktopEntriesDir)) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const files = fs.readdirSync(desktopEntriesDir);
|
|
33
|
+
const apps = [];
|
|
34
|
+
|
|
35
|
+
for (const file of files) {
|
|
36
|
+
if (file.endsWith('.desktop')) {
|
|
37
|
+
const filePath = path.join(desktopEntriesDir, file);
|
|
38
|
+
try {
|
|
39
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
40
|
+
if (content.includes('X-Generated-By=deskify')) {
|
|
41
|
+
const nameMatch = content.match(/^Name=(.+)$/m);
|
|
42
|
+
const execMatch = content.match(/^Exec=(.+)$/m);
|
|
43
|
+
const iconMatch = content.match(/^Icon=(.+)$/m);
|
|
44
|
+
|
|
45
|
+
const urlMatch = content.match(/^X-Deskify-URL=(.+)$/m);
|
|
46
|
+
const widthMatch = content.match(/^X-Deskify-Width=(.+)$/m);
|
|
47
|
+
const heightMatch = content.match(/^X-Deskify-Height=(.+)$/m);
|
|
48
|
+
const persistMatch = content.match(/^X-Deskify-Persist=(.+)$/m);
|
|
49
|
+
const noSandboxMatch = content.match(/^X-Deskify-NoSandbox=(.+)$/m);
|
|
50
|
+
const internalUrlsMatch = content.match(/^X-Deskify-InternalUrls=(.+)$/m);
|
|
51
|
+
|
|
52
|
+
const appName = nameMatch ? nameMatch[1].trim() : file.replace('.desktop', '');
|
|
53
|
+
let execRaw = execMatch ? execMatch[1].trim() : '';
|
|
54
|
+
|
|
55
|
+
if (execRaw.startsWith('"') && execRaw.endsWith('"')) {
|
|
56
|
+
execRaw = execRaw.slice(1, -1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let execPath = execRaw;
|
|
60
|
+
let hasNoSandboxFlag = execRaw.includes('--no-sandbox');
|
|
61
|
+
if (hasNoSandboxFlag) {
|
|
62
|
+
execPath = execRaw.replace('--no-sandbox', '').trim();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let appFolder = execPath ? path.dirname(execPath) : '';
|
|
66
|
+
|
|
67
|
+
apps.push({
|
|
68
|
+
file,
|
|
69
|
+
filePath,
|
|
70
|
+
appName,
|
|
71
|
+
execPath,
|
|
72
|
+
appFolder,
|
|
73
|
+
icon: iconMatch ? iconMatch[1].trim() : '',
|
|
74
|
+
url: urlMatch ? urlMatch[1].trim() : '',
|
|
75
|
+
width: widthMatch ? widthMatch[1].trim() : '1200',
|
|
76
|
+
height: heightMatch ? heightMatch[1].trim() : '800',
|
|
77
|
+
persist: persistMatch ? persistMatch[1].trim() === 'true' : true,
|
|
78
|
+
noSandbox: noSandboxMatch ? noSandboxMatch[1].trim() === 'true' : hasNoSandboxFlag,
|
|
79
|
+
internalUrls: internalUrlsMatch ? internalUrlsMatch[1].trim() : ''
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
} catch (e) {}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return apps;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function uninstallSingleApp(hasDesktopUtils, app) {
|
|
89
|
+
log.warn(`\nAre you sure you want to permanently delete ${colors.red}${app.appName}${colors.reset}?`);
|
|
90
|
+
log.warn('This will delete:');
|
|
91
|
+
log.warn(` - Shortcut: ${app.filePath}`);
|
|
92
|
+
if (app.appFolder && fs.existsSync(app.appFolder)) {
|
|
93
|
+
log.warn(` - App Folder: ${app.appFolder}`);
|
|
94
|
+
}
|
|
95
|
+
console.log('');
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const confirm = await askYesNo('Confirm uninstallation?', false);
|
|
99
|
+
if (!confirm) {
|
|
100
|
+
log.info('Uninstallation cancelled.');
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
} catch (e) {
|
|
104
|
+
if (e.message === 'ESC') {
|
|
105
|
+
log.warn('\nUninstallation cancelled by user (ESC pressed).');
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
throw e;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
if (fs.existsSync(app.filePath)) {
|
|
113
|
+
fs.unlinkSync(app.filePath);
|
|
114
|
+
log.success(`Deleted shortcut file: ${app.file}`);
|
|
115
|
+
}
|
|
116
|
+
} catch (e) {
|
|
117
|
+
log.error(`Failed to delete shortcut file: ${e.message}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (app.appFolder && fs.existsSync(app.appFolder)) {
|
|
121
|
+
log.info(`Deleting application files at: ${app.appFolder}...`);
|
|
122
|
+
try {
|
|
123
|
+
fs.rmSync(app.appFolder, { recursive: true, force: true });
|
|
124
|
+
log.success('Application directory deleted successfully!');
|
|
125
|
+
} catch (e) {
|
|
126
|
+
log.error(`Failed to delete directory: ${e.message}`);
|
|
127
|
+
log.info('You may need to run this command manually as root/sudo:');
|
|
128
|
+
console.log(` sudo rm -rf "${app.appFolder}"`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (hasDesktopUtils) {
|
|
133
|
+
log.info('Updating desktop shortcuts registry database...');
|
|
134
|
+
try {
|
|
135
|
+
const desktopEntriesDir = expandHome('~/.local/share/applications');
|
|
136
|
+
execSync(`update-desktop-database "${desktopEntriesDir}"`, { stdio: 'ignore' });
|
|
137
|
+
log.success('Desktop shortcuts database updated!');
|
|
138
|
+
} catch (e) {}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
log.success(`\nš ${app.appName} has been uninstalled successfully! šļø\n`);
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function manageSingleApp(hasDesktopUtils, app) {
|
|
146
|
+
while (true) {
|
|
147
|
+
console.clear();
|
|
148
|
+
printHeader();
|
|
149
|
+
log.bold(`--- Application: ${app.appName} ---`);
|
|
150
|
+
console.log('');
|
|
151
|
+
console.log(`Name: ${colors.bright}${app.appName}${colors.reset}`);
|
|
152
|
+
console.log(`URL: ${colors.cyan}${app.url || 'N/A'}${colors.reset}`);
|
|
153
|
+
console.log(`Sandbox: ${app.noSandbox ? colors.green + 'Disabled (Sudo-free)' : colors.yellow + 'Enabled (Requires Sudo setup)'}${colors.reset}`);
|
|
154
|
+
console.log(`Persist: ${app.persist ? 'Yes' : 'No'}`);
|
|
155
|
+
console.log(`Window Size: ${app.width}x${app.height}`);
|
|
156
|
+
console.log(`Folder: ${app.appFolder}`);
|
|
157
|
+
console.log(`Shortcut: ${app.filePath}`);
|
|
158
|
+
console.log('');
|
|
159
|
+
|
|
160
|
+
const options = [
|
|
161
|
+
'Launch Application',
|
|
162
|
+
'Edit / Update Configuration',
|
|
163
|
+
'Uninstall Application',
|
|
164
|
+
'Back to List'
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
const selectedIdx = await selectOption('Choose action', options, 0);
|
|
168
|
+
|
|
169
|
+
if (selectedIdx === 0) {
|
|
170
|
+
log.info(`Launching ${app.appName} in background...`);
|
|
171
|
+
try {
|
|
172
|
+
const runExec = app.noSandbox ? `"${app.execPath}" --no-sandbox` : `"${app.execPath}"`;
|
|
173
|
+
const child = spawn(runExec, [], {
|
|
174
|
+
shell: true,
|
|
175
|
+
detached: true,
|
|
176
|
+
stdio: 'ignore'
|
|
177
|
+
});
|
|
178
|
+
child.unref();
|
|
179
|
+
log.success('Launched successfully!');
|
|
180
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
181
|
+
} catch (e) {
|
|
182
|
+
log.error(`Failed to launch application: ${e.message}`);
|
|
183
|
+
await waitForKey();
|
|
184
|
+
}
|
|
185
|
+
} else if (selectedIdx === 1) {
|
|
186
|
+
const result = await createFlow(hasDesktopUtils, app);
|
|
187
|
+
if (result && result.success) {
|
|
188
|
+
const refreshedApps = await getDeskifyApps();
|
|
189
|
+
const updated = refreshedApps.find(a => a.filePath === app.filePath || a.appName === app.appName);
|
|
190
|
+
if (updated) {
|
|
191
|
+
app = updated;
|
|
192
|
+
} else {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
await waitForKey();
|
|
196
|
+
}
|
|
197
|
+
} else if (selectedIdx === 2) {
|
|
198
|
+
const success = await uninstallSingleApp(hasDesktopUtils, app);
|
|
199
|
+
if (success) {
|
|
200
|
+
await waitForKey();
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
} else if (selectedIdx === 3) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function manageFlow(hasDesktopUtils) {
|
|
210
|
+
while (true) {
|
|
211
|
+
console.clear();
|
|
212
|
+
printHeader();
|
|
213
|
+
log.bold('--- List & Manage Existing Applications ---');
|
|
214
|
+
console.log('');
|
|
215
|
+
|
|
216
|
+
const apps = await getDeskifyApps();
|
|
217
|
+
|
|
218
|
+
if (apps.length === 0) {
|
|
219
|
+
log.info('No applications generated by deskify were found.');
|
|
220
|
+
await waitForKey();
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const options = apps.map(app => `${app.appName} ${colors.dim}(${app.url || 'No URL stored'})${colors.reset}`);
|
|
225
|
+
options.push('Back to Main Menu');
|
|
226
|
+
|
|
227
|
+
const selectedIdx = await selectOption('Select an application to manage', options, 0);
|
|
228
|
+
|
|
229
|
+
if (selectedIdx === options.length - 1) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const targetApp = apps[selectedIdx];
|
|
234
|
+
await manageSingleApp(hasDesktopUtils, targetApp);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
module.exports = {
|
|
239
|
+
manageFlow
|
|
240
|
+
};
|
package/src/utils/prompts.js
CHANGED
|
@@ -25,8 +25,7 @@ function ask(questionText, defaultValue = '') {
|
|
|
25
25
|
|
|
26
26
|
let buffer = '';
|
|
27
27
|
const rl = readline.createInterface({
|
|
28
|
-
input: process.stdin
|
|
29
|
-
output: process.stdout
|
|
28
|
+
input: process.stdin
|
|
30
29
|
});
|
|
31
30
|
|
|
32
31
|
readline.emitKeypressEvents(process.stdin, rl);
|
|
@@ -91,8 +90,7 @@ function selectOption(questionText, options, defaultIndex = 0) {
|
|
|
91
90
|
return new Promise((resolve) => {
|
|
92
91
|
let selectedIndex = defaultIndex;
|
|
93
92
|
const rl = readline.createInterface({
|
|
94
|
-
input: process.stdin
|
|
95
|
-
output: process.stdout
|
|
93
|
+
input: process.stdin
|
|
96
94
|
});
|
|
97
95
|
|
|
98
96
|
readline.emitKeypressEvents(process.stdin, rl);
|