my-provider-dash-manager 0.4.0 → 0.4.1
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/LICENSE +63 -63
- package/package.json +1 -1
- package/scripts/cli.js +156 -156
- package/scripts/install-latest.js +120 -120
package/LICENSE
CHANGED
|
@@ -1,63 +1,63 @@
|
|
|
1
|
-
# My Provider Dash Manager — Licence
|
|
2
|
-
|
|
3
|
-
Copyright © 2026 Eduard Secureanu (GitHub: [FiredMosquito831](https://github.com/FiredMosquito831)).
|
|
4
|
-
All rights reserved.
|
|
5
|
-
|
|
6
|
-
Source-available. You may **use** this software, personally and commercially. You may **not** modify
|
|
7
|
-
it, redistribute changed versions, or present it as your own work.
|
|
8
|
-
|
|
9
|
-
## 1. Permitted
|
|
10
|
-
|
|
11
|
-
1. **Use, without charge, for any purpose — personal or commercial.** You may run the application on
|
|
12
|
-
any number of machines, including inside a company, and use it in the course of paid work.
|
|
13
|
-
2. **Reading and studying the source**, including for security review.
|
|
14
|
-
3. **Redistributing the software verbatim and unmodified**, keeping this licence and the attribution
|
|
15
|
-
below intact, provided you charge nothing for the software itself.
|
|
16
|
-
4. **Private, local changes solely to make the software run in your own environment** (for example a
|
|
17
|
-
build fix). Such changes may not be published or distributed.
|
|
18
|
-
|
|
19
|
-
## 2. Not permitted
|
|
20
|
-
|
|
21
|
-
1. **Modifying the software** for distribution, or publishing modified or derivative versions, forks
|
|
22
|
-
with changes, ports, or rebrands.
|
|
23
|
-
2. **Presenting the work, in whole or in part, as your own**, or removing or obscuring authorship,
|
|
24
|
-
copyright notices, or this licence.
|
|
25
|
-
3. **Sublicensing, selling, or renting the software**, or including it in a paid product or service
|
|
26
|
-
offering where the software itself is what is being paid for.
|
|
27
|
-
4. Using the author's name to endorse anything without written permission.
|
|
28
|
-
|
|
29
|
-
## 3. Attribution
|
|
30
|
-
|
|
31
|
-
If you mention, describe, review, demonstrate, publish about, or otherwise refer to this software
|
|
32
|
-
anywhere — an article, video, README, documentation, presentation, social post, or product page —
|
|
33
|
-
you must credit the author and link the project repository:
|
|
34
|
-
|
|
35
|
-
> My Provider Dash Manager by FiredMosquito831 —
|
|
36
|
-
> https://github.com/FiredMosquito831/my-provider-dash-manager
|
|
37
|
-
|
|
38
|
-
## 4. Contributions
|
|
39
|
-
|
|
40
|
-
Contributions are welcome only as issues, or as pull requests that the author may accept at their
|
|
41
|
-
sole discretion. By submitting a contribution you assign to the author the rights necessary to
|
|
42
|
-
license it under these terms.
|
|
43
|
-
|
|
44
|
-
## 5. No warranty; no liability
|
|
45
|
-
|
|
46
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
|
47
|
-
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
48
|
-
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
49
|
-
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
50
|
-
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
51
|
-
|
|
52
|
-
The software manages sessions and credentials for third-party services. You are responsible for
|
|
53
|
-
complying with those services' terms, and for the security of the machine it runs on.
|
|
54
|
-
|
|
55
|
-
## 6. Termination
|
|
56
|
-
|
|
57
|
-
Any breach of sections 1–3 terminates the rights granted here automatically. Rights are reinstated if
|
|
58
|
-
the breach is corrected promptly after notice.
|
|
59
|
-
|
|
60
|
-
---
|
|
61
|
-
|
|
62
|
-
*This is a bespoke source-available licence, not an OSI-approved open-source licence: it deliberately
|
|
63
|
-
withholds the right to modify and redistribute. Third-party dependencies keep their own licences.*
|
|
1
|
+
# My Provider Dash Manager — Licence
|
|
2
|
+
|
|
3
|
+
Copyright © 2026 Eduard Secureanu (GitHub: [FiredMosquito831](https://github.com/FiredMosquito831)).
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Source-available. You may **use** this software, personally and commercially. You may **not** modify
|
|
7
|
+
it, redistribute changed versions, or present it as your own work.
|
|
8
|
+
|
|
9
|
+
## 1. Permitted
|
|
10
|
+
|
|
11
|
+
1. **Use, without charge, for any purpose — personal or commercial.** You may run the application on
|
|
12
|
+
any number of machines, including inside a company, and use it in the course of paid work.
|
|
13
|
+
2. **Reading and studying the source**, including for security review.
|
|
14
|
+
3. **Redistributing the software verbatim and unmodified**, keeping this licence and the attribution
|
|
15
|
+
below intact, provided you charge nothing for the software itself.
|
|
16
|
+
4. **Private, local changes solely to make the software run in your own environment** (for example a
|
|
17
|
+
build fix). Such changes may not be published or distributed.
|
|
18
|
+
|
|
19
|
+
## 2. Not permitted
|
|
20
|
+
|
|
21
|
+
1. **Modifying the software** for distribution, or publishing modified or derivative versions, forks
|
|
22
|
+
with changes, ports, or rebrands.
|
|
23
|
+
2. **Presenting the work, in whole or in part, as your own**, or removing or obscuring authorship,
|
|
24
|
+
copyright notices, or this licence.
|
|
25
|
+
3. **Sublicensing, selling, or renting the software**, or including it in a paid product or service
|
|
26
|
+
offering where the software itself is what is being paid for.
|
|
27
|
+
4. Using the author's name to endorse anything without written permission.
|
|
28
|
+
|
|
29
|
+
## 3. Attribution
|
|
30
|
+
|
|
31
|
+
If you mention, describe, review, demonstrate, publish about, or otherwise refer to this software
|
|
32
|
+
anywhere — an article, video, README, documentation, presentation, social post, or product page —
|
|
33
|
+
you must credit the author and link the project repository:
|
|
34
|
+
|
|
35
|
+
> My Provider Dash Manager by FiredMosquito831 —
|
|
36
|
+
> https://github.com/FiredMosquito831/my-provider-dash-manager
|
|
37
|
+
|
|
38
|
+
## 4. Contributions
|
|
39
|
+
|
|
40
|
+
Contributions are welcome only as issues, or as pull requests that the author may accept at their
|
|
41
|
+
sole discretion. By submitting a contribution you assign to the author the rights necessary to
|
|
42
|
+
license it under these terms.
|
|
43
|
+
|
|
44
|
+
## 5. No warranty; no liability
|
|
45
|
+
|
|
46
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
|
47
|
+
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
48
|
+
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
49
|
+
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
50
|
+
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
51
|
+
|
|
52
|
+
The software manages sessions and credentials for third-party services. You are responsible for
|
|
53
|
+
complying with those services' terms, and for the security of the machine it runs on.
|
|
54
|
+
|
|
55
|
+
## 6. Termination
|
|
56
|
+
|
|
57
|
+
Any breach of sections 1–3 terminates the rights granted here automatically. Rights are reinstated if
|
|
58
|
+
the breach is corrected promptly after notice.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
*This is a bespoke source-available licence, not an OSI-approved open-source licence: it deliberately
|
|
63
|
+
withholds the right to modify and redistribute. Third-party dependencies keep their own licences.*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "my-provider-dash-manager",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
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": {
|
package/scripts/cli.js
CHANGED
|
@@ -1,156 +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
|
+
#!/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,120 +1,120 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Fetches the latest published release and runs its installer.
|
|
3
|
-
//
|
|
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).
|
|
7
|
-
const fs = require('fs');
|
|
8
|
-
const os = require('os');
|
|
9
|
-
const path = require('path');
|
|
10
|
-
const https = require('https');
|
|
11
|
-
const { execFileSync, spawn } = require('child_process');
|
|
12
|
-
|
|
13
|
-
const REPO = 'FiredMosquito831/my-provider-dash-manager';
|
|
14
|
-
|
|
15
|
-
function token() {
|
|
16
|
-
if (process.env.GH_TOKEN) return process.env.GH_TOKEN;
|
|
17
|
-
if (process.env.GITHUB_TOKEN) return process.env.GITHUB_TOKEN;
|
|
18
|
-
try { return execFileSync('gh', ['auth', 'token'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim(); }
|
|
19
|
-
catch { return null; }
|
|
20
|
-
}
|
|
21
|
-
|
|
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".
|
|
24
|
-
function get(url, tok, accept = 'application/vnd.github+json', redirects = 0) {
|
|
25
|
-
return new Promise((resolve, reject) => {
|
|
26
|
-
const headers = { 'User-Agent': 'my-provider-dash-manager', Accept: accept };
|
|
27
|
-
if (tok) headers.Authorization = `Bearer ${tok}`;
|
|
28
|
-
https.get(url, { headers }, res => {
|
|
29
|
-
if ([301, 302, 307, 308].includes(res.statusCode) && res.headers.location) {
|
|
30
|
-
if (redirects > 5) return reject(new Error('too many redirects'));
|
|
31
|
-
res.resume();
|
|
32
|
-
return resolve(get(res.headers.location, null, accept, redirects + 1)); // S3 rejects our auth header
|
|
33
|
-
}
|
|
34
|
-
resolve(res);
|
|
35
|
-
}).on('error', reject);
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async function json(url, tok) {
|
|
40
|
-
const res = await get(url, tok);
|
|
41
|
-
const body = await new Promise((resolve, reject) => {
|
|
42
|
-
let d = ''; res.setEncoding('utf8');
|
|
43
|
-
res.on('data', c => { d += c; }); res.on('end', () => resolve(d)); res.on('error', reject);
|
|
44
|
-
});
|
|
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).');
|
|
47
|
-
if (res.statusCode >= 400) throw new Error(`GitHub API returned HTTP ${res.statusCode}`);
|
|
48
|
-
return JSON.parse(body);
|
|
49
|
-
}
|
|
50
|
-
|
|
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());
|
|
54
|
-
const asset = (rel.assets || []).find(a => a.name.endsWith('.exe'));
|
|
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
|
-
}
|
|
63
|
-
|
|
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
|
-
}
|
|
99
|
-
|
|
100
|
-
module.exports = { latestRelease, downloadAsset, REPO };
|
|
101
|
-
|
|
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
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Fetches the latest published release and runs its installer.
|
|
3
|
+
//
|
|
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).
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const https = require('https');
|
|
11
|
+
const { execFileSync, spawn } = require('child_process');
|
|
12
|
+
|
|
13
|
+
const REPO = 'FiredMosquito831/my-provider-dash-manager';
|
|
14
|
+
|
|
15
|
+
function token() {
|
|
16
|
+
if (process.env.GH_TOKEN) return process.env.GH_TOKEN;
|
|
17
|
+
if (process.env.GITHUB_TOKEN) return process.env.GITHUB_TOKEN;
|
|
18
|
+
try { return execFileSync('gh', ['auth', 'token'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim(); }
|
|
19
|
+
catch { return null; }
|
|
20
|
+
}
|
|
21
|
+
|
|
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".
|
|
24
|
+
function get(url, tok, accept = 'application/vnd.github+json', redirects = 0) {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const headers = { 'User-Agent': 'my-provider-dash-manager', Accept: accept };
|
|
27
|
+
if (tok) headers.Authorization = `Bearer ${tok}`;
|
|
28
|
+
https.get(url, { headers }, res => {
|
|
29
|
+
if ([301, 302, 307, 308].includes(res.statusCode) && res.headers.location) {
|
|
30
|
+
if (redirects > 5) return reject(new Error('too many redirects'));
|
|
31
|
+
res.resume();
|
|
32
|
+
return resolve(get(res.headers.location, null, accept, redirects + 1)); // S3 rejects our auth header
|
|
33
|
+
}
|
|
34
|
+
resolve(res);
|
|
35
|
+
}).on('error', reject);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function json(url, tok) {
|
|
40
|
+
const res = await get(url, tok);
|
|
41
|
+
const body = await new Promise((resolve, reject) => {
|
|
42
|
+
let d = ''; res.setEncoding('utf8');
|
|
43
|
+
res.on('data', c => { d += c; }); res.on('end', () => resolve(d)); res.on('error', reject);
|
|
44
|
+
});
|
|
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).');
|
|
47
|
+
if (res.statusCode >= 400) throw new Error(`GitHub API returned HTTP ${res.statusCode}`);
|
|
48
|
+
return JSON.parse(body);
|
|
49
|
+
}
|
|
50
|
+
|
|
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());
|
|
54
|
+
const asset = (rel.assets || []).find(a => a.name.endsWith('.exe'));
|
|
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
|
+
}
|
|
63
|
+
|
|
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
|
+
}
|
|
99
|
+
|
|
100
|
+
module.exports = { latestRelease, downloadAsset, REPO };
|
|
101
|
+
|
|
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
|
+
}
|