Skip to content

Update flush-dns extension #18964

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions extensions/flush-dns/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Flush DNS Changelog

## [Windows Support] - {PR_MERGE_DATE}

- Added Windows support.

## [macOS Sequoia Support] - 2025-03-02

- Added macOS Sequoia support.
Expand Down
7 changes: 4 additions & 3 deletions extensions/flush-dns/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://www.raycast.com/schemas/extension.json",
"name": "flush-dns",
"title": "Flush DNS",
"title": "Flush Dns",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: 'Flush Dns' should be 'Flush DNS' since DNS is an acronym

Suggested change
"title": "Flush Dns",
"title": "Flush DNS",

"description": "Flush the DNS cache",
"icon": "extension_icon.png",
"author": "rasmusbe",
Expand All @@ -16,7 +16,7 @@
"commands": [
{
"name": "index",
"title": "Flush DNS",
"title": "Flush Dns",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'Flush Dns' should be 'Flush DNS' since DNS is an acronym

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Linter was complaining, that why i changed

"description": "Flush the DNS cache",
"mode": "no-view"
}
Expand All @@ -41,6 +41,7 @@
},
"version": "1.0.0",
"platforms": [
"macOS"
"macOS",
"Windows"
]
}
180 changes: 128 additions & 52 deletions extensions/flush-dns/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,66 +1,142 @@
import { confirmAlert, showHUD } from "@raycast/api";
import { SpawnSyncReturns, execSync } from "node:child_process";
import { execSync } from "node:child_process";
import { platform } from "node:os";

const isSpawnReturn = (e: unknown): e is SpawnSyncReturns<Buffer> => {
return typeof e === "object" && e !== null && "status" in e && "stdout" in e && "stderr" in e;
};
type ShellType = string | undefined;

const commands = {
mDNSResponder: "sudo /usr/bin/killall -HUP mDNSResponder",
dscacheutil: "sudo /usr/bin/dscacheutil -flushcache",
mdnsflushcache: "sudo /usr/bin/discoveryutil mdnsflushcache",
} as const satisfies Record<string, string>;
interface CommandSet {
commands: Record<string, string>;
shell: ShellType;
}

const OS_COMMANDS = {
darwin: {
commands: {
dscacheutil: "/usr/bin/dscacheutil -flushcache",
mDNSResponder: "/usr/bin/killall -HUP mDNSResponder",
mdnsflushcache: "/usr/bin/discoveryutil mdnsflushcache",
},
shell: "/bin/bash",
},
win32: {
commands: {
ipconfig: "ipconfig /flushdns",
},
shell: undefined, // Uses cmd.exe by default
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be undefined or "cmd.exe" in that case. Feels like cmd.exe is making more sense, but I don't have a strong opinion regarding this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

raycast windows don't have showHUD api right so hard to know if throwing error, but Nodejs docs says right here node uses process.env.ComSpec as default shell as cmd.exe

},
} as const satisfies Record<string, CommandSet>;

type SupportedPlatform = "darwin" | "win32";

interface Win32Commands {
ipconfig: string;
}

export default async function main() {
const osVersion = execSync("sw_vers -productVersion").toString().trim();

const runCommands: (keyof typeof commands)[] = [];

if (osVersion.match(/^1[1-5]/)) {
console.log(`OS Version: ${osVersion} parsed as 11-15`);
runCommands.push("dscacheutil", "mDNSResponder");
} else if (osVersion.match(/^10\.([7-9]|1[1-4])/) || osVersion.match(/^10\.10\.[4-5]/)) {
console.log(`OS Version: ${osVersion} parsed as 10.7-9, 10.10.4-5, 10.11-14`);
runCommands.push("mDNSResponder");
} else if (osVersion.match(/^10\.10\.[0-3]/)) {
console.log(`OS Version: ${osVersion} parsed as 10.10.0-3`);
runCommands.push("mdnsflushcache");
} else if (osVersion.startsWith("10.6")) {
console.log(`OS Version: ${osVersion} parsed as 10.6`);
runCommands.push("dscacheutil");
} else {
const flush = await confirmAlert({
title: `OS Version ${osVersion} is not supported.`,
message: "Would you like to try flushing the DNS cache anyway?",
primaryAction: {
title: "Flush DNS Cache",
},
dismissAction: {
title: "Cancel",
},
});
if (!flush) return;
runCommands.push("dscacheutil", "mDNSResponder");
const osPlatform = platform();

if (!["darwin", "win32"].includes(osPlatform)) {
await showHUD("🚫 Unsupported operating system");
return;
}

const command = runCommands.map((key) => commands[key]).join("; ");
const osCommandSet = OS_COMMANDS[osPlatform as SupportedPlatform];

let command: string;
let shell: ShellType;

switch (osPlatform) {
case "darwin": {
const macResult = await getMacOSCommands(osCommandSet);
if (!macResult) return;

const osaCommand = `osascript -e 'do shell script "${command}" with administrator privileges'`;
command = macResult.command;
shell = macResult.shell;
break;
}

case "win32": {
const { ipconfig } = osCommandSet.commands as Win32Commands;
command = ipconfig;
shell = osCommandSet.shell;
break;
}

default:
await showHUD("🚫 Unsupported OS");
return;
}

console.log(`Running command: ${osaCommand}`);
console.log(`🚀 Executing DNS flush command: ${command} in shell: ${shell}`);

await showHUD("Administrator Privileges Required");
try {
execSync(osaCommand, { shell: "/bin/bash" });
await showHUD("DNS Cache Flushed");
} catch (e) {
if (isSpawnReturn(e)) {
console.error(`Command exited with status ${e.status}`);
console.error(`stdout: ${e.stdout.toString()}`);
console.error(`stderr: ${e.stderr.toString()}`);
} else {
console.error(e);
execSync(command, { shell, stdio: "ignore" });
await showHUD("✅ DNS Cache Flushed Successfully");
} catch (error) {
await handleExecutionError(error, osPlatform);
}
}

async function getMacOSCommands(osCommandSet: CommandSet): Promise<{ command: string; shell: ShellType } | null> {
try {
const osVersion = execSync("sw_vers -productVersion").toString().trim();

const versionPatterns: Record<string, string[]> = {
"^1[1-5]": ["dscacheutil", "mDNSResponder"],
"^10\\.([7-9]|1[1-4])|^10\\.10\\.[4-5]": ["mDNSResponder"],
"^10\\.10\\.[0-3]": ["mdnsflushcache"],
"^10\\.6": ["dscacheutil"],
};

let matched = false;
let runCommands: string[] = [];

for (const [pattern, cmds] of Object.entries(versionPatterns)) {
if (new RegExp(pattern).test(osVersion)) {
runCommands = cmds;
matched = true;
break;
}
}

if (!matched) {
const confirmed = await confirmAlert({
title: `⚠️ OS Version ${osVersion} Not Tested`,
message: "Attempt to flush DNS cache anyway?",
primaryAction: { title: "Flush DNS" },
});

if (!confirmed) return null;
runCommands = ["dscacheutil", "mDNSResponder"];
}
await showHUD("Error Flushing DNS Cache");

const commandList = runCommands.map((key) => osCommandSet.commands[key]).join("; ");
return {
command: `osascript -e 'do shell script "${commandList}" with administrator privileges'`,
shell: osCommandSet.shell,
};
} catch (e) {
console.error("❌ Error determining macOS version:", e);
await showHUD("Failed to determine macOS version");
return null;
}
}

async function handleExecutionError(error: unknown, os: string) {
let errorMessage = "⚠️ Error flushing DNS cache";

if (typeof error === "object" && error !== null && "stderr" in error) {
const stderr = (error as { stderr?: Buffer }).stderr?.toString() || "";
if (stderr) {
console.error(".Stderr:", stderr);
if (os === "win32" && stderr.includes("The requested operation requires elevation")) {
errorMessage = "⚠️ Run Raycast as Administrator";
} else {
errorMessage = stderr.split("\n")[0].substring(0, 64);
}
}
}

console.error("💥 Execution failed:", error);
await showHUD(errorMessage);
}
Loading