UNPKG

deskit

Version:

Create .desktop launchers for AppImages and Web Apps.

326 lines (317 loc) 11.4 kB
#!/usr/bin/env node "use strict"; // src/index.ts var import_node_fs4 = require("fs"); var import_node_path4 = require("path"); var import_node_process2 = require("process"); var import_cac = require("cac"); var import_ora = require("ora"); // src/lib/index.ts var import_node_crypto = require("crypto"); var import_node_fs = require("fs"); var import_node_path = require("path"); var import_node_process = require("process"); var PREFIX = "deskit"; var HOME = process.env.HOME || process.env.USERPROFILE || ""; var SHORTCUTS_DIR = (0, import_node_path.join)(HOME, ".local/share/applications"); var ICONS_DIR = (0, import_node_path.join)(HOME, ".local/share/icons"); var STATIC_DIR = (0, import_node_path.join)(HOME, `.local/share/${PREFIX}`); var TEMP_DIR = (0, import_node_path.join)(HOME, `.temp/${PREFIX}`); var checksum = async (input) => { const hash = (0, import_node_crypto.createHash)("md5"); if (typeof input === "string" && (0, import_node_fs.statSync)(input).isFile()) { const stream = (0, import_node_fs.createReadStream)(input, { autoClose: true, encoding: "utf-8" }); for await (const chunk of stream) { hash.update(chunk); } } else { hash.update(input.toString()); } return hash.digest("hex"); }; var installedApps = () => { try { return Promise.resolve( (0, import_node_fs.readdirSync)(STATIC_DIR).map((id) => { const desktopFileContent = (0, import_node_fs.readFileSync)( (0, import_node_path.resolve)(SHORTCUTS_DIR, `${id}.desktop`), { encoding: "utf-8" } ); const name = desktopFileContent.match(/^Name=(.+)$/m)?.[1] || "unknown"; const version2 = desktopFileContent.match(/^X-Version=(.+)$/m)?.[1] || "unknown"; const type = desktopFileContent.match(/^X-Type=(.+)$/m)?.[1] || "unknown"; return { name, version: version2.toLowerCase() === "latest" ? "latest" : `v${version2}`, id, type }; }) ); } catch (_e) { return Promise.resolve([]); } }; var uninstall = (appId) => { try { (0, import_node_fs.rmSync)((0, import_node_path.resolve)(SHORTCUTS_DIR, `${appId}.desktop`), { force: true }); (0, import_node_fs.rmSync)((0, import_node_path.resolve)(STATIC_DIR, appId), { force: true }); (0, import_node_fs.rmSync)((0, import_node_path.resolve)(ICONS_DIR, appId), { force: true }); (0, import_node_fs.rmdirSync)((0, import_node_path.resolve)(HOME, `.local/share/ice/profiles/${appId}`), { recursive: true }); return Promise.resolve(); } catch (_e) { return Promise.resolve(); } }; var resolveInputAsPath = (input) => { try { const fullPath = (0, import_node_path.resolve)((0, import_node_process.cwd)(), input); if ((0, import_node_fs.statSync)(fullPath).isFile()) { return fullPath; } return null; } catch (_e) { return null; } }; var resolveInputAsUrl = (input) => { try { const url = new URL(input); return url; } catch (_e) { return null; } }; // src/modules/appimage.ts var import_node_child_process = require("child_process"); var import_node_fs2 = require("fs"); var import_node_path2 = require("path"); var appimage = async (filePath) => { const appId = await checksum(filePath); (0, import_node_fs2.mkdirSync)(STATIC_DIR, { recursive: true }); const appPath = (0, import_node_path2.resolve)(STATIC_DIR, appId); (0, import_node_fs2.copyFileSync)(filePath, appPath); (0, import_node_fs2.chmodSync)(appPath, 493); const extractDirRoot = (0, import_node_path2.resolve)(TEMP_DIR, appId); (0, import_node_fs2.mkdirSync)(extractDirRoot, { recursive: true }); (0, import_node_child_process.execSync)(`${appPath} --appimage-extract`, { cwd: extractDirRoot }); const extractDir = (0, import_node_path2.resolve)(extractDirRoot, "squashfs-root"); const originalDesktopFileContent = (() => { const desktopFileName = (0, import_node_fs2.readdirSync)(extractDir).find( (f) => f.endsWith(".desktop") ); if (!desktopFileName) return ""; [ "[Desktop Entry]", `Name=${(0, import_node_path2.basename)(filePath)}`, "Exec=", "Terminal=false", "Type=Application", "X-Version=0.0.0", "Icon=" ].join("\n"); return (0, import_node_fs2.readFileSync)((0, import_node_path2.resolve)(extractDir, desktopFileName), "utf8"); })(); const icon = (() => { const dirIconPath = (0, import_node_path2.resolve)(extractDir, ".DirIcon"); if ((0, import_node_fs2.existsSync)(dirIconPath)) { let iconSourcePath = dirIconPath; const stat = (0, import_node_fs2.statSync)(dirIconPath); if (stat.isSymbolicLink()) { iconSourcePath = (0, import_node_fs2.realpathSync)(dirIconPath); } (0, import_node_fs2.mkdirSync)(ICONS_DIR, { recursive: true }); const iconTargetPath = (0, import_node_path2.resolve)(ICONS_DIR, appId); (0, import_node_fs2.copyFileSync)(iconSourcePath, iconTargetPath); return iconTargetPath; } return ""; })(); const name = (() => { const match = originalDesktopFileContent.match(/^Name=(.+)$/m); if (match?.[1]) return match[1]; return (0, import_node_path2.basename)(filePath); })(); const version2 = (() => { const match = originalDesktopFileContent.match( /^X-AppImage-Version=(.+)$/m ); if (match?.[1]) return match[1]; return "0.0.0"; })(); const desktopFileContent = (() => { let ret = [ "[Desktop Entry]", `Name=${name}`, `Icon=${icon}`, `X-Version=${version2}`, "X-Type=AppImage", `Exec=${appPath}` ]; for (const line of originalDesktopFileContent.split("\n")) { if (!ret.map((r) => r.split("=")[0]).includes(line.split("=")[0])) { ret = [...ret, line]; } } return ret.join("\n"); })(); (0, import_node_fs2.mkdirSync)(SHORTCUTS_DIR, { recursive: true }); const desktopFileTarget = (0, import_node_path2.join)(SHORTCUTS_DIR, `${appId}.desktop`); (0, import_node_fs2.writeFileSync)(desktopFileTarget, desktopFileContent, { encoding: "utf-8" }); (0, import_node_fs2.chmodSync)(desktopFileTarget, 493); (0, import_node_fs2.rmSync)(extractDirRoot, { recursive: true, force: true }); return desktopFileTarget; }; // src/modules/webapp.ts var import_node_fs3 = require("fs"); var import_node_path3 = require("path"); var import_jsdom = require("jsdom"); var webapp = async (webappUrl) => { const appId = await checksum(webappUrl); const profilePath = (0, import_node_path3.resolve)(HOME, `.local/share/ice/profiles/${appId}`); (0, import_node_fs3.mkdirSync)(profilePath, { recursive: true }); (0, import_node_fs3.mkdirSync)(STATIC_DIR, { recursive: true }); const appPath = (0, import_node_path3.resolve)(STATIC_DIR, appId); (0, import_node_fs3.writeFileSync)( appPath, ` #!/bin/bash if command -v google-chrome-stable > /dev/null 2>&1; then BROWSER_CMD="google-chrome-stable" elif command -v google-chrome > /dev/null 2>&1; then BROWSER_CMD="google-chrome" elif command -v chromium-browser > /dev/null 2>&1; then BROWSER_CMD="chromium-browser" elif command -v chromium > /dev/null 2>&1; then BROWSER_CMD="chromium" else exit 1 fi # Run the browser with the URL and additional arguments "$BROWSER_CMD" --app="${webappUrl.toString()}" --class=Deskit-${appId} --name=Deskit-${appId} --user-data-dir=${profilePath} ` ); (0, import_node_fs3.chmodSync)(appPath, 493); const dom = await (async () => { const content = await fetch(webappUrl.toString(), { headers: { accept: "text/html,application/xhtml+xml,application/xml;q=0.9", "accept-language": "en-US,en;q=0.9,fa-IR;q=0.8,fa;q=0.7", "cache-control": "max-age=0", priority: "u=0, i", "sec-fetch-dest": "document", "sec-fetch-mode": "navigate", "sec-fetch-site": "same-origin", "sec-fetch-user": "?1" }, referrerPolicy: "strict-origin-when-cross-origin", method: "GET", mode: "cors" }).then((res) => res.text()); return new import_jsdom.JSDOM(content).window.document; })(); const icon = await (async () => { const iconEls = [...dom.head.querySelectorAll('link[rel*="icon"]')]; for (const iconEl of iconEls) { const href = iconEl.getAttribute("href"); if (href) { const iconUrl = new URL(href, webappUrl); const iconPath = (0, import_node_path3.resolve)(ICONS_DIR, appId); const iconContent = await fetch(iconUrl.toString()).then( (res) => res.arrayBuffer() ); (0, import_node_fs3.mkdirSync)(ICONS_DIR, { recursive: true }); (0, import_node_fs3.writeFileSync)(iconPath, Buffer.from(iconContent)); return iconPath; } } return null; })(); const name = await (async () => { const titleEl = dom.head.querySelector("title"); const ret = titleEl?.innerHTML || webappUrl.hostname; if (ret.includes("|")) { const parts = ret.split("|"); return parts[parts.length - 1].trim(); } return ret; })(); const version2 = "latest"; const desktopFileContent = (() => { const ret = [ "[Desktop Entry]", `Name=${name}`, `Icon=${icon}`, `X-Version=${version2}`, "X-Type=WebApp", `Exec=${appPath}`, "Terminal=false", "Type=Application" ]; return ret.join("\n"); })(); (0, import_node_fs3.mkdirSync)(SHORTCUTS_DIR, { recursive: true }); const desktopFileTarget = (0, import_node_path3.join)(SHORTCUTS_DIR, `${appId}.desktop`); (0, import_node_fs3.writeFileSync)(desktopFileTarget, desktopFileContent, { encoding: "utf-8" }); (0, import_node_fs3.chmodSync)(desktopFileTarget, 493); return desktopFileTarget; }; // src/modules/index.ts var installers = { appimage, webapp }; // src/index.ts var cli = (0, import_cac.cac)("deskit"); cli.command("install [appimage_path|webapp_url]", "Install given input").alias("i").alias("add").action(async (input) => { const webappUrl = resolveInputAsUrl(input); const appimagePath = resolveInputAsPath(input); if (appimagePath) { await (0, import_ora.oraPromise)(installers.appimage(appimagePath)); } else if (webappUrl) { await (0, import_ora.oraPromise)(installers.webapp(webappUrl)); } else { import_node_process2.stderr.write("unknown input"); import_node_process2.stdout.write("\n"); (0, import_node_process2.exit)(1); } }); cli.command("list", "List of installed apps").alias("ls").action(async () => { const apps = await installedApps(); const RESET = "\x1B[0m"; const DIM = "\x1B[2m"; import_node_process2.stdout.write( apps.map( (app) => `${DIM}[${app.type}]${RESET} ${app.name} (${app.version}) ${DIM}${app.id}${RESET}` ).join("\n") ); import_node_process2.stdout.write("\n"); }); cli.command("uninstall [input]", "Uninstall given input").alias("u").alias("remove").action(async (input) => { await (0, import_ora.oraPromise)(uninstall(input)); }); var version = JSON.parse( (0, import_node_fs4.readFileSync)((0, import_node_path4.resolve)(__dirname, "../package.json"), { encoding: "utf-8" }) ).version; cli.version(version); cli.help(); cli.parse();