my-provider-dash-manager 0.2.8 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -4
- package/package.json +4 -2
- package/scripts/cli.js +156 -0
- package/scripts/install-latest.js +77 -71
package/README.md
CHANGED
|
@@ -31,12 +31,23 @@ panel lets you store a GitHub token, encrypted with Windows DPAPI, so the feed s
|
|
|
31
31
|
### Install from the command line
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
|
-
npx my-provider-dash-manager
|
|
35
|
-
npx my-provider-dash-manager -- --silent # install without the wizard
|
|
36
|
-
npx my-provider-dash-manager -- --download-only # just fetch the .exe
|
|
34
|
+
npx my-provider-dash-manager # install (or update) and launch
|
|
37
35
|
```
|
|
38
36
|
|
|
39
|
-
|
|
37
|
+
The same CLI manages the whole lifecycle — install it globally with
|
|
38
|
+
`npm i -g my-provider-dash-manager` and the commands are also available as `mpdm`:
|
|
39
|
+
|
|
40
|
+
| Command | What it does |
|
|
41
|
+
|---|---|
|
|
42
|
+
| `my-provider-dash-manager` | Install or update to the newest release, then launch |
|
|
43
|
+
| `… install` / `… update` | Install or update only |
|
|
44
|
+
| `… start` | Launch the installed app |
|
|
45
|
+
| `… status` | Installed version vs latest, plus where the app and your data live |
|
|
46
|
+
| `… where` | Print the install and data paths |
|
|
47
|
+
| `… uninstall` | Run the uninstaller (your accounts and sessions are kept) |
|
|
48
|
+
|
|
49
|
+
Flags: `--silent` (no wizard), `--download-only`, `--force`, `--version`, `--help`.
|
|
50
|
+
From a checkout of this repository the equivalent is `npm run install:latest`.
|
|
40
51
|
|
|
41
52
|
It prints the latest version and its release notes and verifies the download really is a Windows
|
|
42
53
|
executable before running it. No authentication is needed; a `GH_TOKEN` is used only if present.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "my-provider-dash-manager",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Manage many accounts per cloud service (Vercel, Netlify, Supabase, Cloudflare, Railway, Render, GitHub) in one desktop app \u2014 isolated persistent sessions, one tab per account.",
|
|
5
5
|
"main": "src/main.js",
|
|
6
6
|
"scripts": {
|
|
@@ -50,9 +50,11 @@
|
|
|
50
50
|
},
|
|
51
51
|
"license": "SEE LICENSE IN LICENSE",
|
|
52
52
|
"bin": {
|
|
53
|
-
"my-provider-dash-manager": "scripts/
|
|
53
|
+
"my-provider-dash-manager": "scripts/cli.js",
|
|
54
|
+
"mpdm": "scripts/cli.js"
|
|
54
55
|
},
|
|
55
56
|
"files": [
|
|
57
|
+
"scripts/cli.js",
|
|
56
58
|
"scripts/install-latest.js",
|
|
57
59
|
"README.md",
|
|
58
60
|
"LICENSE"
|
package/scripts/cli.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Command line for My Provider Dash Manager.
|
|
3
|
+
//
|
|
4
|
+
// npx my-provider-dash-manager install (or update) and launch the app
|
|
5
|
+
// npx my-provider-dash-manager install install/update only, do not launch
|
|
6
|
+
// npx my-provider-dash-manager update alias of install — always fetches the newest release
|
|
7
|
+
// npx my-provider-dash-manager start launch the installed app
|
|
8
|
+
// npx my-provider-dash-manager status what is installed, what is available, where data lives
|
|
9
|
+
// npx my-provider-dash-manager uninstall run the app's uninstaller
|
|
10
|
+
// npx my-provider-dash-manager where print the install and data paths
|
|
11
|
+
//
|
|
12
|
+
// Flags: --silent (no wizard) · --download-only · --version · --help
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const os = require('os');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const { spawn, execFileSync } = require('child_process');
|
|
17
|
+
const { latestRelease, downloadAsset } = require('./install-latest.js');
|
|
18
|
+
|
|
19
|
+
const PRODUCT = 'My Provider Dash Manager';
|
|
20
|
+
const DATA_DIR = path.join(process.env.APPDATA || os.homedir(), 'multi-acc-manager');
|
|
21
|
+
const args = process.argv.slice(2);
|
|
22
|
+
const flags = new Set(args.filter(a => a.startsWith('-')));
|
|
23
|
+
const cmd = (args.find(a => !a.startsWith('-')) || 'default').toLowerCase();
|
|
24
|
+
|
|
25
|
+
function pkgVersion() {
|
|
26
|
+
try { return require('../package.json').version; } catch { return 'unknown'; }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Where NSIS puts a per-user install, plus the usual per-machine fallbacks.
|
|
30
|
+
function installedExe() {
|
|
31
|
+
const candidates = [
|
|
32
|
+
path.join(process.env.LOCALAPPDATA || '', 'Programs', PRODUCT, `${PRODUCT}.exe`),
|
|
33
|
+
path.join(process.env.PROGRAMFILES || '', PRODUCT, `${PRODUCT}.exe`),
|
|
34
|
+
path.join(process.env['PROGRAMFILES(X86)'] || '', PRODUCT, `${PRODUCT}.exe`),
|
|
35
|
+
];
|
|
36
|
+
return candidates.find(p => p && fs.existsSync(p)) || null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function installedUninstaller() {
|
|
40
|
+
const exe = installedExe();
|
|
41
|
+
if (!exe) return null;
|
|
42
|
+
const dir = path.dirname(exe);
|
|
43
|
+
const u = fs.readdirSync(dir).find(f => /^Uninstall .*\.exe$/i.test(f));
|
|
44
|
+
return u ? path.join(dir, u) : null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Windows reports a four-part ProductVersion (0.2.3.0); releases are three-part (0.2.3).
|
|
48
|
+
function normaliseVersion(v) {
|
|
49
|
+
if (!v) return null;
|
|
50
|
+
const parts = String(v).trim().split('.').slice(0, 3);
|
|
51
|
+
while (parts.length < 3) parts.push('0');
|
|
52
|
+
return parts.join('.');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function installedVersion() {
|
|
56
|
+
const exe = installedExe();
|
|
57
|
+
if (!exe) return null;
|
|
58
|
+
try {
|
|
59
|
+
const out = execFileSync('powershell.exe', ['-NoProfile', '-Command',
|
|
60
|
+
`(Get-Item -LiteralPath '${exe.replace(/'/g, "''")}').VersionInfo.ProductVersion`],
|
|
61
|
+
{ encoding: 'utf8', timeout: 15000 });
|
|
62
|
+
return normaliseVersion(out.trim()) || null;
|
|
63
|
+
} catch { return null; }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function launch() {
|
|
67
|
+
const exe = installedExe();
|
|
68
|
+
if (!exe) throw new Error('The app is not installed yet — run: npx my-provider-dash-manager install');
|
|
69
|
+
spawn(exe, [], { detached: true, stdio: 'ignore' }).unref();
|
|
70
|
+
console.log(`Launched ${PRODUCT}.`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function install({ launchAfter }) {
|
|
74
|
+
const rel = await latestRelease();
|
|
75
|
+
const have = installedVersion();
|
|
76
|
+
if (have === rel.version && !flags.has('--force')) {
|
|
77
|
+
console.log(`Already on the latest version (${have}).`);
|
|
78
|
+
if (launchAfter) launch();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
console.log(have ? `Installed: ${have} → updating to ${rel.version}` : `Installing ${rel.version}`);
|
|
82
|
+
if (rel.notes) console.log(`\n${rel.notes.split('\n').slice(0, 10).join('\n')}\n`);
|
|
83
|
+
const dest = await downloadAsset(rel);
|
|
84
|
+
if (flags.has('--download-only')) { console.log(`\nSaved to ${dest}`); return; }
|
|
85
|
+
console.log(`\nRunning the installer${flags.has('--silent') ? ' (silent)' : ''}…`);
|
|
86
|
+
const child = spawn(dest, flags.has('--silent') ? ['/S'] : [], { detached: true, stdio: 'ignore' });
|
|
87
|
+
child.unref();
|
|
88
|
+
console.log('The installer is unsigned, so SmartScreen may ask you to confirm.');
|
|
89
|
+
if (launchAfter && flags.has('--silent')) {
|
|
90
|
+
setTimeout(() => { try { launch(); } catch (e) { console.error(e.message); } }, 15000);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function status() {
|
|
95
|
+
const have = installedVersion();
|
|
96
|
+
const exe = installedExe();
|
|
97
|
+
let latest = null;
|
|
98
|
+
try { latest = (await latestRelease()).version; } catch (e) { latest = `unavailable (${e.message})`; }
|
|
99
|
+
console.log(`${PRODUCT}`);
|
|
100
|
+
console.log(` installed: ${have || 'not installed'}`);
|
|
101
|
+
console.log(` latest: ${latest}`);
|
|
102
|
+
console.log(` cli: ${pkgVersion()}`);
|
|
103
|
+
console.log(` app: ${exe || '—'}`);
|
|
104
|
+
console.log(` data: ${fs.existsSync(DATA_DIR) ? DATA_DIR : DATA_DIR + ' (not created yet)'}`);
|
|
105
|
+
if (have && typeof latest === 'string' && have !== latest && !latest.startsWith('unavailable')) {
|
|
106
|
+
console.log(`\nAn update is available — run: npx my-provider-dash-manager update`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function where() {
|
|
111
|
+
console.log(`app: ${installedExe() || 'not installed'}`);
|
|
112
|
+
console.log(`data: ${DATA_DIR}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function uninstall() {
|
|
116
|
+
const u = installedUninstaller();
|
|
117
|
+
if (!u) throw new Error('No uninstaller found — the app does not appear to be installed.');
|
|
118
|
+
console.log('Starting the uninstaller. Your accounts and sessions in the data folder are NOT removed;');
|
|
119
|
+
console.log(`delete ${DATA_DIR} yourself if you want them gone.`);
|
|
120
|
+
spawn(u, [], { detached: true, stdio: 'ignore' }).unref();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function help() {
|
|
124
|
+
console.log(`${PRODUCT} — CLI ${pkgVersion()}
|
|
125
|
+
|
|
126
|
+
npx my-provider-dash-manager install (or update) and launch
|
|
127
|
+
npx my-provider-dash-manager install install/update only
|
|
128
|
+
npx my-provider-dash-manager update always fetch the newest release
|
|
129
|
+
npx my-provider-dash-manager start launch the installed app
|
|
130
|
+
npx my-provider-dash-manager status installed vs latest version, paths
|
|
131
|
+
npx my-provider-dash-manager where print install and data paths
|
|
132
|
+
npx my-provider-dash-manager uninstall run the uninstaller (keeps your data)
|
|
133
|
+
|
|
134
|
+
Flags: --silent --download-only --force --version --help
|
|
135
|
+
Windows only. https://github.com/FiredMosquito831/my-provider-dash-manager`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
(async () => {
|
|
139
|
+
if (flags.has('--help') || flags.has('-h') || cmd === 'help') return help();
|
|
140
|
+
if (flags.has('--version') || flags.has('-v') || cmd === 'version') return console.log(pkgVersion());
|
|
141
|
+
if (process.platform !== 'win32' && cmd !== 'status') {
|
|
142
|
+
throw new Error('This app is Windows-only.');
|
|
143
|
+
}
|
|
144
|
+
switch (cmd) {
|
|
145
|
+
case 'default': return install({ launchAfter: true });
|
|
146
|
+
case 'install': case 'update': case 'upgrade': return install({ launchAfter: false });
|
|
147
|
+
case 'start': case 'run': case 'launch': return launch();
|
|
148
|
+
case 'status': return status();
|
|
149
|
+
case 'where': case 'paths': return where();
|
|
150
|
+
case 'uninstall': case 'remove': return uninstall();
|
|
151
|
+
default: console.error(`Unknown command: ${cmd}\n`); return help();
|
|
152
|
+
}
|
|
153
|
+
})().catch(err => {
|
|
154
|
+
console.error(`\nFailed: ${err.message}`);
|
|
155
|
+
process.exit(1);
|
|
156
|
+
});
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
2
|
+
// Fetches the latest published release and runs its installer.
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// Auth: the repository is private, so a token is required — GH_TOKEN/GITHUB_TOKEN from the
|
|
9
|
-
// environment, or whatever `gh auth token` returns. Making the repo public removes that need.
|
|
4
|
+
// Used directly (`npm run install:latest`) and as the engine behind scripts/cli.js.
|
|
5
|
+
// The repository is public, so no authentication is needed; GH_TOKEN is used only if present
|
|
6
|
+
// (which keeps this working if the repo is ever made private again).
|
|
10
7
|
const fs = require('fs');
|
|
11
8
|
const os = require('os');
|
|
12
9
|
const path = require('path');
|
|
@@ -14,28 +11,25 @@ const https = require('https');
|
|
|
14
11
|
const { execFileSync, spawn } = require('child_process');
|
|
15
12
|
|
|
16
13
|
const REPO = 'FiredMosquito831/my-provider-dash-manager';
|
|
17
|
-
const args = process.argv.slice(2);
|
|
18
|
-
const silent = args.includes('--silent');
|
|
19
|
-
const downloadOnly = args.includes('--download-only');
|
|
20
14
|
|
|
21
15
|
function token() {
|
|
22
16
|
if (process.env.GH_TOKEN) return process.env.GH_TOKEN;
|
|
23
17
|
if (process.env.GITHUB_TOKEN) return process.env.GITHUB_TOKEN;
|
|
24
|
-
try { return execFileSync('gh', ['auth', 'token'], { encoding: 'utf8' }).trim(); }
|
|
18
|
+
try { return execFileSync('gh', ['auth', 'token'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim(); }
|
|
19
|
+
catch { return null; }
|
|
25
20
|
}
|
|
26
21
|
|
|
27
|
-
// accept:
|
|
28
|
-
//
|
|
22
|
+
// accept: JSON for API calls, octet-stream for asset bytes. Without the octet-stream Accept the
|
|
23
|
+
// asset endpoint returns metadata instead of the file — a silent failure that yields a 1.7 KB "exe".
|
|
29
24
|
function get(url, tok, accept = 'application/vnd.github+json', redirects = 0) {
|
|
30
25
|
return new Promise((resolve, reject) => {
|
|
31
|
-
const headers = { 'User-Agent': 'my-provider-dash-manager
|
|
26
|
+
const headers = { 'User-Agent': 'my-provider-dash-manager', Accept: accept };
|
|
32
27
|
if (tok) headers.Authorization = `Bearer ${tok}`;
|
|
33
28
|
https.get(url, { headers }, res => {
|
|
34
29
|
if ([301, 302, 307, 308].includes(res.statusCode) && res.headers.location) {
|
|
35
30
|
if (redirects > 5) return reject(new Error('too many redirects'));
|
|
36
31
|
res.resume();
|
|
37
|
-
|
|
38
|
-
return resolve(get(res.headers.location, null, accept, redirects + 1));
|
|
32
|
+
return resolve(get(res.headers.location, null, accept, redirects + 1)); // S3 rejects our auth header
|
|
39
33
|
}
|
|
40
34
|
resolve(res);
|
|
41
35
|
}).on('error', reject);
|
|
@@ -48,67 +42,79 @@ async function json(url, tok) {
|
|
|
48
42
|
let d = ''; res.setEncoding('utf8');
|
|
49
43
|
res.on('data', c => { d += c; }); res.on('end', () => resolve(d)); res.on('error', reject);
|
|
50
44
|
});
|
|
51
|
-
if (res.statusCode === 404) throw new Error('
|
|
52
|
-
if (res.statusCode === 401 || res.statusCode === 403) throw new Error('GitHub rejected the
|
|
45
|
+
if (res.statusCode === 404) throw new Error('No published release found for this repository.');
|
|
46
|
+
if (res.statusCode === 401 || res.statusCode === 403) throw new Error('GitHub rejected the request (rate limit or token).');
|
|
53
47
|
if (res.statusCode >= 400) throw new Error(`GitHub API returned HTTP ${res.statusCode}`);
|
|
54
48
|
return JSON.parse(body);
|
|
55
49
|
}
|
|
56
50
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (res.statusCode >= 400) return reject(new Error(`download failed: HTTP ${res.statusCode}`));
|
|
61
|
-
const out = fs.createWriteStream(dest);
|
|
62
|
-
let done = 0; let lastPct = -1;
|
|
63
|
-
res.on('data', c => {
|
|
64
|
-
done += c.length;
|
|
65
|
-
if (size) {
|
|
66
|
-
const pct = Math.floor((done / size) * 100);
|
|
67
|
-
if (pct !== lastPct && pct % 5 === 0) { lastPct = pct; process.stdout.write(`\r downloading… ${pct}%`); }
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
res.pipe(out);
|
|
71
|
-
out.on('finish', () => { process.stdout.write('\r downloading… 100%\n'); out.close(() => resolve(dest)); });
|
|
72
|
-
out.on('error', reject);
|
|
73
|
-
res.on('error', reject);
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
(async () => {
|
|
78
|
-
const tok = token();
|
|
79
|
-
if (!tok) console.warn('! No GitHub token found (GH_TOKEN or `gh auth login`) — this will fail while the repo is private.\n');
|
|
80
|
-
|
|
81
|
-
console.log(`Looking up the latest release of ${REPO}…`);
|
|
82
|
-
const rel = await json(`https://api.github.com/repos/${REPO}/releases/latest`, tok);
|
|
83
|
-
const version = String(rel.tag_name || '').replace(/^v/, '');
|
|
51
|
+
/** The newest release: { version, notes, asset, url }. */
|
|
52
|
+
async function latestRelease() {
|
|
53
|
+
const rel = await json(`https://api.github.com/repos/${REPO}/releases/latest`, token());
|
|
84
54
|
const asset = (rel.assets || []).find(a => a.name.endsWith('.exe'));
|
|
85
55
|
if (!asset) throw new Error(`Release ${rel.tag_name} has no .exe asset.`);
|
|
56
|
+
return {
|
|
57
|
+
version: String(rel.tag_name || '').replace(/^v/, ''),
|
|
58
|
+
notes: rel.body ? String(rel.body).trim() : '',
|
|
59
|
+
url: rel.html_url,
|
|
60
|
+
asset,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
86
63
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
64
|
+
/** Downloads the release asset to temp and verifies it really is a Windows executable. */
|
|
65
|
+
function downloadAsset(rel, onProgress) {
|
|
66
|
+
const dest = path.join(os.tmpdir(), rel.asset.name);
|
|
67
|
+
return new Promise(async (resolve, reject) => {
|
|
68
|
+
try {
|
|
69
|
+
const res = await get(rel.asset.url, token(), 'application/octet-stream');
|
|
70
|
+
if (res.statusCode >= 400) return reject(new Error(`download failed: HTTP ${res.statusCode}`));
|
|
71
|
+
const out = fs.createWriteStream(dest);
|
|
72
|
+
let done = 0; let lastPct = -1;
|
|
73
|
+
res.on('data', c => {
|
|
74
|
+
done += c.length;
|
|
75
|
+
const pct = Math.floor((done / rel.asset.size) * 100);
|
|
76
|
+
if (pct !== lastPct && pct % 5 === 0) {
|
|
77
|
+
lastPct = pct;
|
|
78
|
+
if (onProgress) onProgress(pct);
|
|
79
|
+
else process.stdout.write(`\r downloading… ${pct}%`);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
res.pipe(out);
|
|
83
|
+
res.on('error', reject);
|
|
84
|
+
out.on('error', reject);
|
|
85
|
+
out.on('finish', () => out.close(() => {
|
|
86
|
+
if (!onProgress) process.stdout.write('\r downloading… 100%\n');
|
|
87
|
+
try {
|
|
88
|
+
const stat = fs.statSync(dest);
|
|
89
|
+
const head = Buffer.alloc(2);
|
|
90
|
+
const fd = fs.openSync(dest, 'r'); fs.readSync(fd, head, 0, 2, 0); fs.closeSync(fd);
|
|
91
|
+
if (head.toString('latin1') !== 'MZ') throw new Error(`downloaded file is not a Windows executable (${stat.size} bytes)`);
|
|
92
|
+
if (rel.asset.size && stat.size !== rel.asset.size) throw new Error(`size mismatch: expected ${rel.asset.size}, got ${stat.size}`);
|
|
93
|
+
} catch (e) { return reject(e); }
|
|
94
|
+
resolve(dest);
|
|
95
|
+
}));
|
|
96
|
+
} catch (e) { reject(e); }
|
|
97
|
+
});
|
|
98
|
+
}
|
|
104
99
|
|
|
105
|
-
|
|
100
|
+
module.exports = { latestRelease, downloadAsset, REPO };
|
|
106
101
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
102
|
+
// Standalone use: download and run, honouring --silent / --download-only.
|
|
103
|
+
if (require.main === module) {
|
|
104
|
+
const args = process.argv.slice(2);
|
|
105
|
+
(async () => {
|
|
106
|
+
console.log(`Looking up the latest release of ${REPO}…`);
|
|
107
|
+
const rel = await latestRelease();
|
|
108
|
+
let installed = null;
|
|
109
|
+
try { installed = require('../package.json').version; } catch {}
|
|
110
|
+
console.log(` latest: ${rel.version}${installed ? ` (this checkout: ${installed})` : ''}`);
|
|
111
|
+
if (rel.notes) console.log(`\n${rel.notes.split('\n').slice(0, 12).join('\n')}\n`);
|
|
112
|
+
console.log(`Downloading ${rel.asset.name} (${(rel.asset.size / 1048576).toFixed(1)} MB)…`);
|
|
113
|
+
const dest = await downloadAsset(rel);
|
|
114
|
+
console.log(` verified: ${(fs.statSync(dest).size / 1048576).toFixed(1)} MB Windows executable`);
|
|
115
|
+
if (args.includes('--download-only')) return console.log(`\nSaved to ${dest}`);
|
|
116
|
+
console.log(`\nLaunching the installer${args.includes('--silent') ? ' (silent)' : ''}…`);
|
|
117
|
+
spawn(dest, args.includes('--silent') ? ['/S'] : [], { detached: true, stdio: 'ignore' }).unref();
|
|
118
|
+
console.log('The installer is unsigned, so SmartScreen may ask you to confirm.');
|
|
119
|
+
})().catch(err => { console.error(`\nFailed: ${err.message}`); process.exit(1); });
|
|
120
|
+
}
|