@fredlackey/devutils 0.0.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/README.md +156 -0
- package/bin/dev.js +16 -0
- package/files/README.md +0 -0
- package/files/claude/.claude/commands/setup-context.md +3 -0
- package/files/monorepos/_archive/README.md +36 -0
- package/files/monorepos/_legacy/README.md +36 -0
- package/files/monorepos/ai-docs/README.md +33 -0
- package/files/monorepos/apps/README.md +24 -0
- package/files/monorepos/docs/README.md +40 -0
- package/files/monorepos/packages/README.md +25 -0
- package/files/monorepos/research/README.md +29 -0
- package/files/monorepos/scripts/README.md +24 -0
- package/package.json +39 -0
- package/src/cli.js +68 -0
- package/src/commands/README.md +41 -0
- package/src/commands/configure.js +199 -0
- package/src/commands/identity.js +1630 -0
- package/src/commands/ignore.js +247 -0
- package/src/commands/install.js +173 -0
- package/src/commands/setup.js +212 -0
- package/src/commands/status.js +223 -0
- package/src/completion.js +284 -0
- package/src/constants.js +45 -0
- package/src/ignore/claude-code.txt +10 -0
- package/src/ignore/docker.txt +18 -0
- package/src/ignore/linux.txt +23 -0
- package/src/ignore/macos.txt +36 -0
- package/src/ignore/node.txt +55 -0
- package/src/ignore/terraform.txt +37 -0
- package/src/ignore/vscode.txt +18 -0
- package/src/ignore/windows.txt +35 -0
- package/src/index.js +0 -0
- package/src/installs/README.md +399 -0
- package/src/installs/adobe-creative-cloud.js +44 -0
- package/src/installs/appcleaner.js +44 -0
- package/src/installs/atomicparsley.js +44 -0
- package/src/installs/aws-cli.js +44 -0
- package/src/installs/balena-etcher.js +44 -0
- package/src/installs/bambu-studio.js +44 -0
- package/src/installs/bash-completion.js +44 -0
- package/src/installs/bash.js +44 -0
- package/src/installs/beyond-compare.js +44 -0
- package/src/installs/build-essential.js +44 -0
- package/src/installs/caffeine.js +44 -0
- package/src/installs/camtasia.js +44 -0
- package/src/installs/chatgpt.js +44 -0
- package/src/installs/chrome-canary.js +44 -0
- package/src/installs/chromium.js +44 -0
- package/src/installs/claude-code.js +44 -0
- package/src/installs/curl.js +44 -0
- package/src/installs/cursor.js +44 -0
- package/src/installs/dbschema.js +44 -0
- package/src/installs/docker.js +44 -0
- package/src/installs/drawio.js +44 -0
- package/src/installs/elmedia-player.js +44 -0
- package/src/installs/ffmpeg.js +44 -0
- package/src/installs/gemini-cli.js +44 -0
- package/src/installs/git.js +44 -0
- package/src/installs/gitego.js +44 -0
- package/src/installs/go.js +44 -0
- package/src/installs/google-chrome.js +44 -0
- package/src/installs/gpg.js +141 -0
- package/src/installs/homebrew.js +44 -0
- package/src/installs/imageoptim.js +44 -0
- package/src/installs/jq.js +44 -0
- package/src/installs/keyboard-maestro.js +44 -0
- package/src/installs/latex.js +44 -0
- package/src/installs/lftp.js +44 -0
- package/src/installs/messenger.js +44 -0
- package/src/installs/microsoft-office.js +44 -0
- package/src/installs/microsoft-teams.js +44 -0
- package/src/installs/node.js +44 -0
- package/src/installs/nordpass.js +44 -0
- package/src/installs/nvm.js +44 -0
- package/src/installs/openssh.js +134 -0
- package/src/installs/pandoc.js +44 -0
- package/src/installs/pinentry.js +44 -0
- package/src/installs/pngyu.js +44 -0
- package/src/installs/postman.js +44 -0
- package/src/installs/safari-tech-preview.js +44 -0
- package/src/installs/sfnt2woff.js +44 -0
- package/src/installs/shellcheck.js +44 -0
- package/src/installs/slack.js +44 -0
- package/src/installs/snagit.js +44 -0
- package/src/installs/spotify.js +44 -0
- package/src/installs/studio-3t.js +44 -0
- package/src/installs/sublime-text.js +44 -0
- package/src/installs/superwhisper.js +44 -0
- package/src/installs/tailscale.js +44 -0
- package/src/installs/termius.js +44 -0
- package/src/installs/terraform.js +44 -0
- package/src/installs/tidal.js +44 -0
- package/src/installs/tmux.js +44 -0
- package/src/installs/tree.js +44 -0
- package/src/installs/vim.js +44 -0
- package/src/installs/vlc.js +44 -0
- package/src/installs/vscode.js +44 -0
- package/src/installs/whatsapp.js +44 -0
- package/src/installs/woff2.js +44 -0
- package/src/installs/xcode.js +44 -0
- package/src/installs/yarn.js +44 -0
- package/src/installs/yq.js +44 -0
- package/src/installs/yt-dlp.js +44 -0
- package/src/installs/zoom.js +44 -0
- package/src/scripts/README.md +95 -0
- package/src/scripts/afk.js +23 -0
- package/src/scripts/backup-all.js +24 -0
- package/src/scripts/backup-source.js +24 -0
- package/src/scripts/brewd.js +23 -0
- package/src/scripts/brewi.js +24 -0
- package/src/scripts/brewr.js +24 -0
- package/src/scripts/brews.js +24 -0
- package/src/scripts/brewu.js +23 -0
- package/src/scripts/c.js +23 -0
- package/src/scripts/ccurl.js +24 -0
- package/src/scripts/certbot-crontab-init.js +24 -0
- package/src/scripts/certbot-init.js +25 -0
- package/src/scripts/ch.js +23 -0
- package/src/scripts/claude-danger.js +23 -0
- package/src/scripts/clean-dev.js +24 -0
- package/src/scripts/clear-dns-cache.js +23 -0
- package/src/scripts/clone.js +25 -0
- package/src/scripts/code-all.js +24 -0
- package/src/scripts/count-files.js +24 -0
- package/src/scripts/count-folders.js +24 -0
- package/src/scripts/count.js +24 -0
- package/src/scripts/d.js +23 -0
- package/src/scripts/datauri.js +24 -0
- package/src/scripts/delete-files.js +24 -0
- package/src/scripts/docker-clean.js +24 -0
- package/src/scripts/dp.js +23 -0
- package/src/scripts/e.js +24 -0
- package/src/scripts/empty-trash.js +23 -0
- package/src/scripts/evm.js +25 -0
- package/src/scripts/fetch-github-repos.js +25 -0
- package/src/scripts/get-channel.js +24 -0
- package/src/scripts/get-course.js +26 -0
- package/src/scripts/get-dependencies.js +25 -0
- package/src/scripts/get-folder.js +26 -0
- package/src/scripts/get-tunes.js +25 -0
- package/src/scripts/get-video.js +24 -0
- package/src/scripts/git-backup.js +25 -0
- package/src/scripts/git-clone.js +25 -0
- package/src/scripts/git-pup.js +23 -0
- package/src/scripts/git-push.js +24 -0
- package/src/scripts/h.js +24 -0
- package/src/scripts/hide-desktop-icons.js +23 -0
- package/src/scripts/hide-hidden-files.js +23 -0
- package/src/scripts/install-dependencies-from.js +25 -0
- package/src/scripts/ips.js +26 -0
- package/src/scripts/iso.js +24 -0
- package/src/scripts/killni.js +23 -0
- package/src/scripts/ll.js +24 -0
- package/src/scripts/local-ip.js +23 -0
- package/src/scripts/m.js +24 -0
- package/src/scripts/map.js +24 -0
- package/src/scripts/mkd.js +24 -0
- package/src/scripts/ncu-update-all.js +24 -0
- package/src/scripts/nginx-init.js +28 -0
- package/src/scripts/npmi.js +23 -0
- package/src/scripts/o.js +24 -0
- package/src/scripts/org-by-date.js +24 -0
- package/src/scripts/p.js +23 -0
- package/src/scripts/packages.js +25 -0
- package/src/scripts/path.js +23 -0
- package/src/scripts/ports.js +23 -0
- package/src/scripts/q.js +23 -0
- package/src/scripts/refresh-files.js +26 -0
- package/src/scripts/remove-smaller-files.js +24 -0
- package/src/scripts/rename-files-with-date.js +25 -0
- package/src/scripts/resize-image.js +25 -0
- package/src/scripts/rm-safe.js +24 -0
- package/src/scripts/s.js +24 -0
- package/src/scripts/set-git-public.js +23 -0
- package/src/scripts/show-desktop-icons.js +23 -0
- package/src/scripts/show-hidden-files.js +23 -0
- package/src/scripts/tpa.js +23 -0
- package/src/scripts/tpo.js +23 -0
- package/src/scripts/u.js +23 -0
- package/src/scripts/vpush.js +23 -0
- package/src/scripts/y.js +23 -0
- package/src/utils/README.md +95 -0
- package/src/utils/common/apps.js +143 -0
- package/src/utils/common/display.js +157 -0
- package/src/utils/common/network.js +185 -0
- package/src/utils/common/os.js +202 -0
- package/src/utils/common/package-manager.js +301 -0
- package/src/utils/common/privileges.js +138 -0
- package/src/utils/common/shell.js +195 -0
- package/src/utils/macos/apps.js +228 -0
- package/src/utils/macos/brew.js +315 -0
- package/src/utils/ubuntu/apt.js +301 -0
- package/src/utils/ubuntu/desktop.js +292 -0
- package/src/utils/ubuntu/snap.js +302 -0
- package/src/utils/ubuntu/systemd.js +286 -0
- package/src/utils/windows/choco.js +327 -0
- package/src/utils/windows/env.js +246 -0
- package/src/utils/windows/registry.js +269 -0
- package/src/utils/windows/shell.js +240 -0
- package/src/utils/windows/winget.js +378 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generic Application Detection Utilities
|
|
5
|
+
*
|
|
6
|
+
* Cross-platform utilities for detecting installed applications.
|
|
7
|
+
* Delegates to OS-specific implementations for actual detection.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const osUtils = require('./os');
|
|
11
|
+
const shell = require('./shell');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Cross-platform check if an application is installed
|
|
15
|
+
* Delegates to OS-specific implementations
|
|
16
|
+
* @param {string} appName - The application name to check
|
|
17
|
+
* @returns {Promise<boolean>}
|
|
18
|
+
*/
|
|
19
|
+
async function isInstalled(appName) {
|
|
20
|
+
const platform = osUtils.detect();
|
|
21
|
+
|
|
22
|
+
switch (platform.type) {
|
|
23
|
+
case 'macos': {
|
|
24
|
+
const macosApps = require('../macos/apps');
|
|
25
|
+
return macosApps.isAppInstalled(appName);
|
|
26
|
+
}
|
|
27
|
+
case 'ubuntu':
|
|
28
|
+
case 'debian':
|
|
29
|
+
case 'raspbian': {
|
|
30
|
+
const apt = require('../ubuntu/apt');
|
|
31
|
+
const snap = require('../ubuntu/snap');
|
|
32
|
+
// Check both apt and snap
|
|
33
|
+
const aptInstalled = await apt.isPackageInstalled(appName);
|
|
34
|
+
if (aptInstalled) return true;
|
|
35
|
+
const snapInstalled = await snap.isSnapInstalled(appName);
|
|
36
|
+
return snapInstalled;
|
|
37
|
+
}
|
|
38
|
+
case 'windows': {
|
|
39
|
+
const registry = require('../windows/registry');
|
|
40
|
+
return registry.isAppInstalled(appName);
|
|
41
|
+
}
|
|
42
|
+
default:
|
|
43
|
+
// Fallback: check if command exists in PATH
|
|
44
|
+
return shell.commandExists(appName);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Retrieves the installed version of an application
|
|
50
|
+
* @param {string} appName - The application name
|
|
51
|
+
* @returns {Promise<string|null>}
|
|
52
|
+
*/
|
|
53
|
+
async function getVersion(appName) {
|
|
54
|
+
const platform = osUtils.detect();
|
|
55
|
+
|
|
56
|
+
switch (platform.type) {
|
|
57
|
+
case 'macos': {
|
|
58
|
+
const macosApps = require('../macos/apps');
|
|
59
|
+
return macosApps.getAppVersion(appName);
|
|
60
|
+
}
|
|
61
|
+
case 'ubuntu':
|
|
62
|
+
case 'debian':
|
|
63
|
+
case 'raspbian': {
|
|
64
|
+
const apt = require('../ubuntu/apt');
|
|
65
|
+
const snap = require('../ubuntu/snap');
|
|
66
|
+
// Check apt first
|
|
67
|
+
const aptVersion = await apt.getPackageVersion(appName);
|
|
68
|
+
if (aptVersion) return aptVersion;
|
|
69
|
+
// Then snap
|
|
70
|
+
const snapVersion = await snap.getSnapVersion(appName);
|
|
71
|
+
return snapVersion;
|
|
72
|
+
}
|
|
73
|
+
case 'windows': {
|
|
74
|
+
const registry = require('../windows/registry');
|
|
75
|
+
return registry.getAppVersion(appName);
|
|
76
|
+
}
|
|
77
|
+
default:
|
|
78
|
+
// Fallback: try running appName --version
|
|
79
|
+
return getVersionFromCommand(appName);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Returns the installation path of an application
|
|
85
|
+
* @param {string} appName - The application name
|
|
86
|
+
* @returns {Promise<string|null>}
|
|
87
|
+
*/
|
|
88
|
+
async function getInstallPath(appName) {
|
|
89
|
+
const platform = osUtils.detect();
|
|
90
|
+
|
|
91
|
+
switch (platform.type) {
|
|
92
|
+
case 'macos': {
|
|
93
|
+
const macosApps = require('../macos/apps');
|
|
94
|
+
return macosApps.getAppBundlePath(appName);
|
|
95
|
+
}
|
|
96
|
+
case 'windows': {
|
|
97
|
+
const registry = require('../windows/registry');
|
|
98
|
+
return registry.getInstallPath(appName);
|
|
99
|
+
}
|
|
100
|
+
default:
|
|
101
|
+
// Fallback: try to find the executable
|
|
102
|
+
return shell.which(appName);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Tries to get version by running the command with --version flag
|
|
108
|
+
* @param {string} command - The command to check
|
|
109
|
+
* @returns {Promise<string|null>}
|
|
110
|
+
*/
|
|
111
|
+
async function getVersionFromCommand(command) {
|
|
112
|
+
const versionFlags = ['--version', '-v', '-V', 'version'];
|
|
113
|
+
|
|
114
|
+
for (const flag of versionFlags) {
|
|
115
|
+
const result = await shell.exec(`${command} ${flag}`);
|
|
116
|
+
if (result.code === 0 && result.stdout) {
|
|
117
|
+
// Try to extract version number from output
|
|
118
|
+
const versionMatch = result.stdout.match(/(\d+\.\d+\.?\d*)/);
|
|
119
|
+
if (versionMatch) {
|
|
120
|
+
return versionMatch[1];
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Checks if a command-line tool is available
|
|
130
|
+
* @param {string} command - The command to check
|
|
131
|
+
* @returns {boolean}
|
|
132
|
+
*/
|
|
133
|
+
function isCommandAvailable(command) {
|
|
134
|
+
return shell.commandExists(command);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
module.exports = {
|
|
138
|
+
isInstalled,
|
|
139
|
+
getVersion,
|
|
140
|
+
getInstallPath,
|
|
141
|
+
getVersionFromCommand,
|
|
142
|
+
isCommandAvailable
|
|
143
|
+
};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Display and GUI Detection Utilities
|
|
5
|
+
*
|
|
6
|
+
* Platform-agnostic utilities for detecting display server availability.
|
|
7
|
+
* Used to determine if GUI applications can be installed/run.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Checks if a display server is available
|
|
14
|
+
* On macOS: Always true (Aqua/Quartz is always present)
|
|
15
|
+
* On Windows: Always true (Windows Desktop is always present)
|
|
16
|
+
* On Linux: Checks for X11 or Wayland display
|
|
17
|
+
* @returns {boolean}
|
|
18
|
+
*/
|
|
19
|
+
function hasDisplay() {
|
|
20
|
+
const platform = process.platform;
|
|
21
|
+
|
|
22
|
+
// macOS always has a display (Aqua/Quartz)
|
|
23
|
+
if (platform === 'darwin') {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Windows always has a display (unless running as a service, which is rare)
|
|
28
|
+
if (platform === 'win32') {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Linux: check for X11 or Wayland
|
|
33
|
+
if (platform === 'linux') {
|
|
34
|
+
return hasX11Display() || hasWaylandDisplay();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Checks if running in a headless environment (no GUI)
|
|
42
|
+
* @returns {boolean}
|
|
43
|
+
*/
|
|
44
|
+
function isHeadless() {
|
|
45
|
+
return !hasDisplay();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Checks if X11 display is available
|
|
50
|
+
* @returns {boolean}
|
|
51
|
+
*/
|
|
52
|
+
function hasX11Display() {
|
|
53
|
+
// Check DISPLAY environment variable
|
|
54
|
+
if (process.env.DISPLAY) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Check if X server socket exists
|
|
59
|
+
const x11SocketPath = '/tmp/.X11-unix';
|
|
60
|
+
if (fs.existsSync(x11SocketPath)) {
|
|
61
|
+
try {
|
|
62
|
+
const sockets = fs.readdirSync(x11SocketPath);
|
|
63
|
+
return sockets.length > 0;
|
|
64
|
+
} catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Checks if Wayland display is available
|
|
74
|
+
* @returns {boolean}
|
|
75
|
+
*/
|
|
76
|
+
function hasWaylandDisplay() {
|
|
77
|
+
// Check WAYLAND_DISPLAY environment variable
|
|
78
|
+
if (process.env.WAYLAND_DISPLAY) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Check if Wayland socket exists in XDG_RUNTIME_DIR
|
|
83
|
+
const runtimeDir = process.env.XDG_RUNTIME_DIR;
|
|
84
|
+
if (runtimeDir) {
|
|
85
|
+
const waylandSocketPath = `${runtimeDir}/wayland-0`;
|
|
86
|
+
if (fs.existsSync(waylandSocketPath)) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Gets the type of display server in use
|
|
96
|
+
* @returns {'aqua'|'windows'|'x11'|'wayland'|null}
|
|
97
|
+
*/
|
|
98
|
+
function getDisplayType() {
|
|
99
|
+
const platform = process.platform;
|
|
100
|
+
|
|
101
|
+
if (platform === 'darwin') {
|
|
102
|
+
return 'aqua';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (platform === 'win32') {
|
|
106
|
+
return 'windows';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (platform === 'linux') {
|
|
110
|
+
if (hasWaylandDisplay()) {
|
|
111
|
+
return 'wayland';
|
|
112
|
+
}
|
|
113
|
+
if (hasX11Display()) {
|
|
114
|
+
return 'x11';
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Gets the display variable value (DISPLAY or WAYLAND_DISPLAY)
|
|
123
|
+
* @returns {string|null}
|
|
124
|
+
*/
|
|
125
|
+
function getDisplayVariable() {
|
|
126
|
+
if (process.env.WAYLAND_DISPLAY) {
|
|
127
|
+
return process.env.WAYLAND_DISPLAY;
|
|
128
|
+
}
|
|
129
|
+
if (process.env.DISPLAY) {
|
|
130
|
+
return process.env.DISPLAY;
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Checks if SSH session has X11 forwarding enabled
|
|
137
|
+
* @returns {boolean}
|
|
138
|
+
*/
|
|
139
|
+
function hasX11Forwarding() {
|
|
140
|
+
// Check if we're in an SSH session with DISPLAY set
|
|
141
|
+
if (process.env.SSH_CONNECTION && process.env.DISPLAY) {
|
|
142
|
+
// DISPLAY typically looks like "localhost:10.0" for X11 forwarding
|
|
143
|
+
const display = process.env.DISPLAY;
|
|
144
|
+
return display.includes(':');
|
|
145
|
+
}
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
module.exports = {
|
|
150
|
+
hasDisplay,
|
|
151
|
+
isHeadless,
|
|
152
|
+
hasX11Display,
|
|
153
|
+
hasWaylandDisplay,
|
|
154
|
+
getDisplayType,
|
|
155
|
+
getDisplayVariable,
|
|
156
|
+
hasX11Forwarding
|
|
157
|
+
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Network Connectivity Utilities
|
|
5
|
+
*
|
|
6
|
+
* Platform-agnostic utilities for checking network connectivity.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const dns = require('dns');
|
|
10
|
+
const https = require('https');
|
|
11
|
+
const http = require('http');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Checks if the machine has internet connectivity
|
|
15
|
+
* @param {number} [timeout=5000] - Timeout in milliseconds
|
|
16
|
+
* @returns {Promise<boolean>}
|
|
17
|
+
*/
|
|
18
|
+
async function isOnline(timeout = 5000) {
|
|
19
|
+
// Try multiple reliable hosts in case one is blocked
|
|
20
|
+
const hosts = [
|
|
21
|
+
'dns.google',
|
|
22
|
+
'cloudflare.com',
|
|
23
|
+
'1.1.1.1'
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
for (const host of hosts) {
|
|
27
|
+
const reachable = await canReach(host, timeout);
|
|
28
|
+
if (reachable) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Tests if a specific hostname is reachable
|
|
38
|
+
* @param {string} hostname - The hostname to check
|
|
39
|
+
* @param {number} [timeout=5000] - Timeout in milliseconds
|
|
40
|
+
* @returns {Promise<boolean>}
|
|
41
|
+
*/
|
|
42
|
+
async function canReach(hostname, timeout = 5000) {
|
|
43
|
+
return new Promise((resolve) => {
|
|
44
|
+
// First try DNS lookup
|
|
45
|
+
const timer = setTimeout(() => {
|
|
46
|
+
resolve(false);
|
|
47
|
+
}, timeout);
|
|
48
|
+
|
|
49
|
+
dns.lookup(hostname, (err) => {
|
|
50
|
+
clearTimeout(timer);
|
|
51
|
+
if (err) {
|
|
52
|
+
resolve(false);
|
|
53
|
+
} else {
|
|
54
|
+
resolve(true);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Tests if a URL is reachable via HTTP(S) request
|
|
62
|
+
* @param {string} url - The URL to check
|
|
63
|
+
* @param {number} [timeout=5000] - Timeout in milliseconds
|
|
64
|
+
* @returns {Promise<boolean>}
|
|
65
|
+
*/
|
|
66
|
+
async function canFetch(url, timeout = 5000) {
|
|
67
|
+
return new Promise((resolve) => {
|
|
68
|
+
const timer = setTimeout(() => {
|
|
69
|
+
resolve(false);
|
|
70
|
+
}, timeout);
|
|
71
|
+
|
|
72
|
+
const protocol = url.startsWith('https') ? https : http;
|
|
73
|
+
|
|
74
|
+
const req = protocol.get(url, { timeout }, (res) => {
|
|
75
|
+
clearTimeout(timer);
|
|
76
|
+
// Consider 2xx and 3xx as success
|
|
77
|
+
resolve(res.statusCode >= 200 && res.statusCode < 400);
|
|
78
|
+
res.destroy();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
req.on('error', () => {
|
|
82
|
+
clearTimeout(timer);
|
|
83
|
+
resolve(false);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
req.on('timeout', () => {
|
|
87
|
+
clearTimeout(timer);
|
|
88
|
+
req.destroy();
|
|
89
|
+
resolve(false);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Gets the public IP address of the machine
|
|
96
|
+
* @param {number} [timeout=5000] - Timeout in milliseconds
|
|
97
|
+
* @returns {Promise<string|null>}
|
|
98
|
+
*/
|
|
99
|
+
async function getPublicIP(timeout = 5000) {
|
|
100
|
+
const services = [
|
|
101
|
+
'https://api.ipify.org',
|
|
102
|
+
'https://icanhazip.com',
|
|
103
|
+
'https://ifconfig.me/ip'
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
for (const service of services) {
|
|
107
|
+
try {
|
|
108
|
+
const ip = await fetchText(service, timeout);
|
|
109
|
+
if (ip && isValidIP(ip.trim())) {
|
|
110
|
+
return ip.trim();
|
|
111
|
+
}
|
|
112
|
+
} catch {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Fetches text content from a URL
|
|
122
|
+
* @param {string} url - The URL to fetch
|
|
123
|
+
* @param {number} [timeout=5000] - Timeout in milliseconds
|
|
124
|
+
* @returns {Promise<string>}
|
|
125
|
+
*/
|
|
126
|
+
async function fetchText(url, timeout = 5000) {
|
|
127
|
+
return new Promise((resolve, reject) => {
|
|
128
|
+
const timer = setTimeout(() => {
|
|
129
|
+
reject(new Error('Timeout'));
|
|
130
|
+
}, timeout);
|
|
131
|
+
|
|
132
|
+
const protocol = url.startsWith('https') ? https : http;
|
|
133
|
+
|
|
134
|
+
const req = protocol.get(url, { timeout }, (res) => {
|
|
135
|
+
let data = '';
|
|
136
|
+
res.on('data', (chunk) => {
|
|
137
|
+
data += chunk;
|
|
138
|
+
});
|
|
139
|
+
res.on('end', () => {
|
|
140
|
+
clearTimeout(timer);
|
|
141
|
+
resolve(data);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
req.on('error', (err) => {
|
|
146
|
+
clearTimeout(timer);
|
|
147
|
+
reject(err);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
req.on('timeout', () => {
|
|
151
|
+
clearTimeout(timer);
|
|
152
|
+
req.destroy();
|
|
153
|
+
reject(new Error('Timeout'));
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Validates an IP address format
|
|
160
|
+
* @param {string} ip - The IP to validate
|
|
161
|
+
* @returns {boolean}
|
|
162
|
+
*/
|
|
163
|
+
function isValidIP(ip) {
|
|
164
|
+
// IPv4 regex
|
|
165
|
+
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
166
|
+
// IPv6 regex (simplified)
|
|
167
|
+
const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
|
|
168
|
+
|
|
169
|
+
if (ipv4Regex.test(ip)) {
|
|
170
|
+
const parts = ip.split('.');
|
|
171
|
+
return parts.every((part) => {
|
|
172
|
+
const num = parseInt(part, 10);
|
|
173
|
+
return num >= 0 && num <= 255;
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return ipv6Regex.test(ip);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
module.exports = {
|
|
181
|
+
isOnline,
|
|
182
|
+
canReach,
|
|
183
|
+
canFetch,
|
|
184
|
+
getPublicIP
|
|
185
|
+
};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Operating System Detection Utilities
|
|
5
|
+
*
|
|
6
|
+
* Platform-agnostic utilities for detecting the current operating system,
|
|
7
|
+
* architecture, and distribution. Uses Node.js APIs that work identically
|
|
8
|
+
* on all platforms.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const os = require('os');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Detects the current operating system and returns platform details
|
|
17
|
+
* @returns {{ type: string, packageManager: string|null, distro: string|null }}
|
|
18
|
+
*/
|
|
19
|
+
function detect() {
|
|
20
|
+
const platform = process.platform;
|
|
21
|
+
const result = {
|
|
22
|
+
type: 'unknown',
|
|
23
|
+
packageManager: null,
|
|
24
|
+
distro: null
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
if (platform === 'darwin') {
|
|
28
|
+
result.type = 'macos';
|
|
29
|
+
result.packageManager = 'brew';
|
|
30
|
+
result.distro = 'macos';
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (platform === 'win32') {
|
|
35
|
+
// Check for WSL
|
|
36
|
+
if (process.env.WSL_DISTRO_NAME) {
|
|
37
|
+
result.type = 'wsl';
|
|
38
|
+
result.packageManager = 'apt';
|
|
39
|
+
result.distro = process.env.WSL_DISTRO_NAME.toLowerCase();
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
result.type = 'windows';
|
|
43
|
+
result.packageManager = 'winget';
|
|
44
|
+
result.distro = 'windows';
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (platform === 'linux') {
|
|
49
|
+
// Check for WSL first
|
|
50
|
+
if (process.env.WSL_DISTRO_NAME) {
|
|
51
|
+
result.type = 'wsl';
|
|
52
|
+
result.packageManager = 'apt';
|
|
53
|
+
result.distro = process.env.WSL_DISTRO_NAME.toLowerCase();
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Try to detect Linux distribution
|
|
58
|
+
const distro = getDistro();
|
|
59
|
+
result.distro = distro;
|
|
60
|
+
|
|
61
|
+
// Debian-based (Ubuntu, Debian, Raspberry Pi OS)
|
|
62
|
+
if (fs.existsSync('/etc/debian_version')) {
|
|
63
|
+
if (distro === 'raspbian' || distro === 'raspberry') {
|
|
64
|
+
result.type = 'raspbian';
|
|
65
|
+
} else if (distro === 'ubuntu') {
|
|
66
|
+
result.type = 'ubuntu';
|
|
67
|
+
} else {
|
|
68
|
+
result.type = 'debian';
|
|
69
|
+
}
|
|
70
|
+
result.packageManager = 'apt';
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// RHEL-based (Amazon Linux, CentOS, Fedora, RHEL)
|
|
75
|
+
if (fs.existsSync('/etc/redhat-release')) {
|
|
76
|
+
if (distro === 'amzn' || distro === 'amazon') {
|
|
77
|
+
result.type = 'amazon_linux';
|
|
78
|
+
} else if (distro === 'fedora') {
|
|
79
|
+
result.type = 'fedora';
|
|
80
|
+
} else {
|
|
81
|
+
result.type = 'rhel';
|
|
82
|
+
}
|
|
83
|
+
// Use dnf if available, otherwise yum
|
|
84
|
+
result.packageManager = fs.existsSync('/usr/bin/dnf') ? 'dnf' : 'yum';
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Fallback for unknown Linux
|
|
89
|
+
result.type = 'linux';
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Checks if running on Windows (native, not WSL)
|
|
98
|
+
* @returns {boolean}
|
|
99
|
+
*/
|
|
100
|
+
function isWindows() {
|
|
101
|
+
return process.platform === 'win32' && !process.env.WSL_DISTRO_NAME;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Checks if running on macOS
|
|
106
|
+
* @returns {boolean}
|
|
107
|
+
*/
|
|
108
|
+
function isMacOS() {
|
|
109
|
+
return process.platform === 'darwin';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Checks if running on any Linux distribution
|
|
114
|
+
* @returns {boolean}
|
|
115
|
+
*/
|
|
116
|
+
function isLinux() {
|
|
117
|
+
return process.platform === 'linux';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Checks if running inside Windows Subsystem for Linux
|
|
122
|
+
* @returns {boolean}
|
|
123
|
+
*/
|
|
124
|
+
function isWSL() {
|
|
125
|
+
return !!process.env.WSL_DISTRO_NAME;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Returns the CPU architecture
|
|
130
|
+
* @returns {string} - 'x64', 'arm64', 'ia32', etc.
|
|
131
|
+
*/
|
|
132
|
+
function getArch() {
|
|
133
|
+
return process.arch;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* For Linux, returns the specific distribution name
|
|
138
|
+
* @returns {string|null} - Distribution name (ubuntu, debian, fedora, etc.) or null
|
|
139
|
+
*/
|
|
140
|
+
function getDistro() {
|
|
141
|
+
if (process.platform !== 'linux') {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Try /etc/os-release first (most modern Linux distros)
|
|
146
|
+
const osReleasePath = '/etc/os-release';
|
|
147
|
+
if (fs.existsSync(osReleasePath)) {
|
|
148
|
+
try {
|
|
149
|
+
const content = fs.readFileSync(osReleasePath, 'utf8');
|
|
150
|
+
const idMatch = content.match(/^ID=["']?([^"'\n]+)["']?/m);
|
|
151
|
+
if (idMatch) {
|
|
152
|
+
return idMatch[1].toLowerCase();
|
|
153
|
+
}
|
|
154
|
+
} catch (err) {
|
|
155
|
+
// Fall through to other methods
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Try /etc/lsb-release (Ubuntu and some others)
|
|
160
|
+
const lsbReleasePath = '/etc/lsb-release';
|
|
161
|
+
if (fs.existsSync(lsbReleasePath)) {
|
|
162
|
+
try {
|
|
163
|
+
const content = fs.readFileSync(lsbReleasePath, 'utf8');
|
|
164
|
+
const idMatch = content.match(/^DISTRIB_ID=["']?([^"'\n]+)["']?/m);
|
|
165
|
+
if (idMatch) {
|
|
166
|
+
return idMatch[1].toLowerCase();
|
|
167
|
+
}
|
|
168
|
+
} catch (err) {
|
|
169
|
+
// Fall through
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Returns the home directory path
|
|
178
|
+
* @returns {string}
|
|
179
|
+
*/
|
|
180
|
+
function getHomeDir() {
|
|
181
|
+
return os.homedir();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Returns the temporary directory path
|
|
186
|
+
* @returns {string}
|
|
187
|
+
*/
|
|
188
|
+
function getTempDir() {
|
|
189
|
+
return os.tmpdir();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
module.exports = {
|
|
193
|
+
detect,
|
|
194
|
+
isWindows,
|
|
195
|
+
isMacOS,
|
|
196
|
+
isLinux,
|
|
197
|
+
isWSL,
|
|
198
|
+
getArch,
|
|
199
|
+
getDistro,
|
|
200
|
+
getHomeDir,
|
|
201
|
+
getTempDir
|
|
202
|
+
};
|