436a9631fc
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
91 lines
4.4 KiB
JavaScript
91 lines
4.4 KiB
JavaScript
"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
|