Initial commit
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+91
@@ -0,0 +1,91 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.copyIcons = copyIcons;
|
||||
exports.copyMimeTypes = copyMimeTypes;
|
||||
const path = require("path");
|
||||
const fs = require("fs-extra");
|
||||
const builder_util_1 = require("builder-util");
|
||||
const ICON_DIR_RELATIVE_PATH = "usr/share/icons/hicolor";
|
||||
const MIME_TYPE_DIR_RELATIVE_PATH = "usr/share/mime/packages";
|
||||
/**
|
||||
* Escapes special XML characters to prevent injection
|
||||
*/
|
||||
function xmlEscape(str) {
|
||||
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
||||
}
|
||||
async function copyIcons(options) {
|
||||
const { stageDir, options: configuration } = options;
|
||||
const iconCommonDir = path.join(stageDir, ICON_DIR_RELATIVE_PATH);
|
||||
await fs.ensureDir(iconCommonDir);
|
||||
const icons = configuration.icons;
|
||||
if (!icons || icons.length === 0) {
|
||||
throw new Error("At least one icon is required for AppImage");
|
||||
}
|
||||
const iconExtWithDot = path.extname(icons[0].file);
|
||||
const iconFileName = `${configuration.executableName}${iconExtWithDot}`;
|
||||
const maxIconIndex = icons.length - 1;
|
||||
const iconInfoList = icons.map(icon => {
|
||||
if (path.extname(icon.file) !== iconExtWithDot) {
|
||||
throw new Error(`All icons must have the same extension: expected ${iconExtWithDot}, but got ${icon.file}`);
|
||||
}
|
||||
let iconSizeDir;
|
||||
if (iconExtWithDot === ".svg") {
|
||||
// SVG icons go in scalable/apps directory per freedesktop icon theme spec
|
||||
iconSizeDir = "scalable/apps";
|
||||
}
|
||||
else {
|
||||
iconSizeDir = `${icon.size}x${icon.size}/apps`;
|
||||
}
|
||||
const iconRelativeToStageFile = path.join(ICON_DIR_RELATIVE_PATH, iconSizeDir, iconFileName);
|
||||
const iconDir = path.join(iconCommonDir, iconSizeDir);
|
||||
const iconFile = path.join(iconDir, iconFileName);
|
||||
return { icon, iconDir, iconFile, iconRelativeToStageFile };
|
||||
});
|
||||
await Promise.all(iconInfoList.map(async ({ icon, iconDir, iconFile }) => {
|
||||
await fs.ensureDir(iconDir);
|
||||
await (0, builder_util_1.copyOrLinkFile)(icon.file, iconFile);
|
||||
}));
|
||||
// Create symlinks for the last (largest) icon
|
||||
const { iconRelativeToStageFile } = iconInfoList[maxIconIndex];
|
||||
await fs.symlink(iconRelativeToStageFile, path.join(stageDir, iconFileName));
|
||||
await fs.symlink(iconRelativeToStageFile, path.join(stageDir, ".DirIcon"));
|
||||
}
|
||||
async function copyMimeTypes(options) {
|
||||
const { stageDir, options: { fileAssociations, productName, executableName }, } = options;
|
||||
if (!fileAssociations || fileAssociations.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const mimeTypeParts = [];
|
||||
for (const fileAssociation of fileAssociations) {
|
||||
if (!fileAssociation.mimeType) {
|
||||
continue;
|
||||
}
|
||||
// XML-escape to prevent injection
|
||||
mimeTypeParts.push(`<mime-type type="${xmlEscape(fileAssociation.mimeType)}">`);
|
||||
mimeTypeParts.push(` <comment>${xmlEscape(productName)} document</comment>`);
|
||||
// Handle extension(s)
|
||||
const extensions = Array.isArray(fileAssociation.ext) ? fileAssociation.ext : [fileAssociation.ext];
|
||||
for (const ext of extensions) {
|
||||
// Validate extension doesn't contain dangerous characters
|
||||
if (!/^[a-zA-Z0-9_-]+$/.test(ext)) {
|
||||
builder_util_1.log.warn({ extension: ext }, `file extension contains unexpected characters and may not be supported`);
|
||||
}
|
||||
mimeTypeParts.push(` <glob pattern="*.${xmlEscape(ext)}"/>`);
|
||||
}
|
||||
mimeTypeParts.push(' <generic-icon name="x-office-document"/>');
|
||||
mimeTypeParts.push("</mime-type>");
|
||||
}
|
||||
// If no mime-types were generated, return null
|
||||
if (mimeTypeParts.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const mimeTypeDir = path.join(stageDir, MIME_TYPE_DIR_RELATIVE_PATH);
|
||||
const fileName = `${executableName}.xml`;
|
||||
const mimeTypeFile = path.join(mimeTypeDir, fileName);
|
||||
await fs.ensureDir(mimeTypeDir);
|
||||
const xmlContent = ['<?xml version="1.0"?>', '<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">', ...mimeTypeParts, "</mime-info>"].join("\n");
|
||||
// Use 0o644 (rw-r--r--) instead of 0o666 to avoid world-writable permissions
|
||||
await fs.writeFile(mimeTypeFile, xmlContent, { mode: 0o644 });
|
||||
return path.join(MIME_TYPE_DIR_RELATIVE_PATH, fileName);
|
||||
}
|
||||
//# sourceMappingURL=appLauncher.js.map
|
||||
Reference in New Issue
Block a user