deskit
Version:
Create .desktop launchers for AppImages and Web Apps.
326 lines (317 loc) • 11.4 kB
JavaScript
;
// 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();