app-builder-lib 26.7.0 → 26.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/codeSign/windowsSignToolManager.d.ts +4 -5
- package/out/codeSign/windowsSignToolManager.js +76 -86
- package/out/codeSign/windowsSignToolManager.js.map +1 -1
- package/out/configuration.d.ts +34 -0
- package/out/configuration.js.map +1 -1
- package/out/index.d.ts +1 -1
- package/out/index.js.map +1 -1
- package/out/linuxPackager.js +1 -1
- package/out/linuxPackager.js.map +1 -1
- package/out/node-module-collector/index.d.ts +1 -2
- package/out/node-module-collector/nodeModulesCollector.js +2 -2
- package/out/node-module-collector/nodeModulesCollector.js.map +1 -1
- package/out/node-module-collector/types.d.ts +0 -4
- package/out/node-module-collector/types.js.map +1 -1
- package/out/node-module-collector/yarnNodeModulesCollector.d.ts +4 -10
- package/out/node-module-collector/yarnNodeModulesCollector.js +7 -197
- package/out/node-module-collector/yarnNodeModulesCollector.js.map +1 -1
- package/out/targets/AppxTarget.js +8 -7
- package/out/targets/AppxTarget.js.map +1 -1
- package/out/targets/FpmTarget.js +3 -3
- package/out/targets/FpmTarget.js.map +1 -1
- package/out/targets/MsiWrappedTarget.js +4 -3
- package/out/targets/MsiWrappedTarget.js.map +1 -1
- package/out/targets/appimage/AppImageTarget.d.ts +16 -0
- package/out/targets/{AppImageTarget.js → appimage/AppImageTarget.js} +65 -28
- package/out/targets/appimage/AppImageTarget.js.map +1 -0
- package/out/targets/appimage/appImageUtil.d.ts +23 -0
- package/out/targets/appimage/appImageUtil.js +266 -0
- package/out/targets/appimage/appImageUtil.js.map +1 -0
- package/out/targets/appimage/appLauncher.d.ts +3 -0
- package/out/targets/appimage/appLauncher.js +91 -0
- package/out/targets/appimage/appLauncher.js.map +1 -0
- package/out/targets/archive.js +2 -2
- package/out/targets/archive.js.map +1 -1
- package/out/toolsets/linux.d.ts +9 -0
- package/out/toolsets/linux.js +68 -0
- package/out/toolsets/linux.js.map +1 -0
- package/out/toolsets/windows.d.ts +42 -0
- package/out/toolsets/windows.js +143 -0
- package/out/toolsets/windows.js.map +1 -0
- package/out/util/packageMetadata.js +9 -5
- package/out/util/packageMetadata.js.map +1 -1
- package/out/version.d.ts +1 -1
- package/out/version.js +1 -1
- package/out/version.js.map +1 -1
- package/out/winPackager.js +4 -3
- package/out/winPackager.js.map +1 -1
- package/package.json +7 -7
- package/scheme.json +51 -0
- package/out/targets/AppImageTarget.d.ts +0 -14
- package/out/targets/AppImageTarget.js.map +0 -1
- package/out/targets/tools.d.ts +0 -2
- package/out/targets/tools.js +0 -46
- package/out/targets/tools.js.map +0 -1
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.APP_RUN_ENTRYPOINT = void 0;
|
|
3
4
|
const builder_util_1 = require("builder-util");
|
|
4
5
|
const fs_extra_1 = require("fs-extra");
|
|
5
6
|
const lazy_val_1 = require("lazy-val");
|
|
6
7
|
const path = require("path");
|
|
7
|
-
const core_1 = require("
|
|
8
|
-
const PublishManager_1 = require("
|
|
9
|
-
const appBuilder_1 = require("
|
|
10
|
-
const license_1 = require("
|
|
11
|
-
const targetUtil_1 = require("
|
|
8
|
+
const core_1 = require("../../core");
|
|
9
|
+
const PublishManager_1 = require("../../publish/PublishManager");
|
|
10
|
+
const appBuilder_1 = require("../../util/appBuilder");
|
|
11
|
+
const license_1 = require("../../util/license");
|
|
12
|
+
const targetUtil_1 = require("../targetUtil");
|
|
13
|
+
const appImageUtil_1 = require("./appImageUtil");
|
|
12
14
|
// https://unix.stackexchange.com/questions/375191/append-to-sub-directory-inside-squashfs-file
|
|
15
|
+
exports.APP_RUN_ENTRYPOINT = "AppRun";
|
|
13
16
|
class AppImageTarget extends core_1.Target {
|
|
14
|
-
constructor(
|
|
17
|
+
constructor(_ignored, packager, helper, outDir) {
|
|
15
18
|
super("appImage");
|
|
16
19
|
this.packager = packager;
|
|
17
20
|
this.helper = helper;
|
|
@@ -20,17 +23,17 @@ class AppImageTarget extends core_1.Target {
|
|
|
20
23
|
this.desktopEntry = new lazy_val_1.Lazy(() => {
|
|
21
24
|
var _a;
|
|
22
25
|
const args = ((_a = this.options.executableArgs) === null || _a === void 0 ? void 0 : _a.join(" ")) || "--no-sandbox";
|
|
23
|
-
return helper.computeDesktopEntry(this.options,
|
|
26
|
+
return helper.computeDesktopEntry(this.options, `${exports.APP_RUN_ENTRYPOINT} ${args} %U`, {
|
|
24
27
|
"X-AppImage-Version": `${packager.appInfo.buildVersion}`,
|
|
25
28
|
});
|
|
26
29
|
});
|
|
27
30
|
}
|
|
28
31
|
async build(appOutDir, arch) {
|
|
32
|
+
var _a;
|
|
29
33
|
const packager = this.packager;
|
|
30
34
|
const options = this.options;
|
|
31
35
|
// https://github.com/electron-userland/electron-builder/issues/775
|
|
32
36
|
// https://github.com/electron-userland/electron-builder/issues/1726
|
|
33
|
-
// tslint:disable-next-line:no-invalid-template-strings
|
|
34
37
|
const artifactName = packager.expandArtifactNamePattern(options, "AppImage", arch);
|
|
35
38
|
const artifactPath = path.join(this.outDir, artifactName);
|
|
36
39
|
await packager.info.emitArtifactBuildStarted({
|
|
@@ -38,23 +41,64 @@ class AppImageTarget extends core_1.Target {
|
|
|
38
41
|
file: artifactPath,
|
|
39
42
|
arch,
|
|
40
43
|
});
|
|
41
|
-
|
|
44
|
+
// Parallelize independent async operations
|
|
45
|
+
const [publishConfig, stageDir, desktopEntry, icons, license] = await Promise.all([
|
|
46
|
+
(0, PublishManager_1.getAppUpdatePublishConfiguration)(packager, options, arch, false),
|
|
47
|
+
(0, targetUtil_1.createStageDir)(this, packager, arch),
|
|
42
48
|
this.desktopEntry.value,
|
|
43
49
|
this.helper.icons,
|
|
44
|
-
(0, PublishManager_1.getAppUpdatePublishConfiguration)(packager, options, arch, false /* in any case validation will be done on publish */),
|
|
45
50
|
(0, license_1.getNotLocalizedLicenseFile)(options.license, this.packager, ["txt", "html"]),
|
|
46
|
-
(0, targetUtil_1.createStageDir)(this, packager, arch),
|
|
47
51
|
]);
|
|
48
|
-
const license = c[3];
|
|
49
|
-
const stageDir = c[4];
|
|
50
|
-
const publishConfig = c[2];
|
|
51
52
|
if (publishConfig != null) {
|
|
52
|
-
await (0, fs_extra_1.outputFile)(path.join(packager.getResourcesDir(
|
|
53
|
+
await (0, fs_extra_1.outputFile)(path.join(packager.getResourcesDir(appOutDir), "app-update.yml"), (0, builder_util_1.serializeToYaml)(publishConfig));
|
|
53
54
|
}
|
|
54
|
-
if (this.packager.packagerOptions.effectiveOptionComputed != null &&
|
|
55
|
-
|
|
55
|
+
if (this.packager.packagerOptions.effectiveOptionComputed != null && (await this.packager.packagerOptions.effectiveOptionComputed({ desktop: desktopEntry }))) {
|
|
56
|
+
await stageDir.cleanup();
|
|
56
57
|
return;
|
|
57
58
|
}
|
|
59
|
+
let updateInfo;
|
|
60
|
+
try {
|
|
61
|
+
const appimageTool = (_a = this.packager.config.toolsets) === null || _a === void 0 ? void 0 : _a.appimage;
|
|
62
|
+
if (appimageTool == null || appimageTool === "0.0.0") {
|
|
63
|
+
updateInfo = await this.buildFuse2AppImage({ stageDir, arch, artifactPath, appOutDir, options, packager, desktopEntry, icons, license });
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
updateInfo = await (0, appImageUtil_1.buildAppImage)({
|
|
67
|
+
appDir: appOutDir,
|
|
68
|
+
stageDir: stageDir.dir,
|
|
69
|
+
arch,
|
|
70
|
+
output: artifactPath,
|
|
71
|
+
options: {
|
|
72
|
+
productName: this.packager.appInfo.productName,
|
|
73
|
+
productFilename: this.packager.appInfo.productFilename,
|
|
74
|
+
executableName: this.packager.executableName,
|
|
75
|
+
license,
|
|
76
|
+
desktopEntry,
|
|
77
|
+
icons,
|
|
78
|
+
fileAssociations: this.packager.fileAssociations,
|
|
79
|
+
compression: this.packager.compression === "maximum" ? "xz" : undefined,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
builder_util_1.log.error({ error: error.message }, "failed to build AppImage");
|
|
86
|
+
await stageDir.cleanup().catch(() => { });
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
await stageDir.cleanup();
|
|
90
|
+
await packager.info.emitArtifactBuildCompleted({
|
|
91
|
+
file: artifactPath,
|
|
92
|
+
safeArtifactName: packager.computeSafeArtifactName(artifactName, "AppImage", arch, false),
|
|
93
|
+
target: this,
|
|
94
|
+
arch,
|
|
95
|
+
packager,
|
|
96
|
+
isWriteUpdateInfo: true,
|
|
97
|
+
updateInfo,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
async buildFuse2AppImage(props) {
|
|
101
|
+
const { stageDir, arch, artifactPath, appOutDir, options, packager, desktopEntry, icons, license } = props;
|
|
58
102
|
const args = [
|
|
59
103
|
"appimage",
|
|
60
104
|
"--stage",
|
|
@@ -69,9 +113,9 @@ class AppImageTarget extends core_1.Target {
|
|
|
69
113
|
JSON.stringify({
|
|
70
114
|
productName: this.packager.appInfo.productName,
|
|
71
115
|
productFilename: this.packager.appInfo.productFilename,
|
|
72
|
-
desktopEntry
|
|
116
|
+
desktopEntry,
|
|
73
117
|
executableName: this.packager.executableName,
|
|
74
|
-
icons
|
|
118
|
+
icons,
|
|
75
119
|
fileAssociations: this.packager.fileAssociations,
|
|
76
120
|
...options,
|
|
77
121
|
}),
|
|
@@ -82,15 +126,8 @@ class AppImageTarget extends core_1.Target {
|
|
|
82
126
|
if (packager.compression === "maximum") {
|
|
83
127
|
args.push("--compression", "xz");
|
|
84
128
|
}
|
|
85
|
-
await
|
|
86
|
-
|
|
87
|
-
safeArtifactName: packager.computeSafeArtifactName(artifactName, "AppImage", arch, false),
|
|
88
|
-
target: this,
|
|
89
|
-
arch,
|
|
90
|
-
packager,
|
|
91
|
-
isWriteUpdateInfo: true,
|
|
92
|
-
updateInfo: await (0, appBuilder_1.executeAppBuilderAsJson)(args),
|
|
93
|
-
});
|
|
129
|
+
const updateInfo = await (0, appBuilder_1.executeAppBuilderAsJson)(args);
|
|
130
|
+
return updateInfo;
|
|
94
131
|
}
|
|
95
132
|
}
|
|
96
133
|
exports.default = AppImageTarget;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AppImageTarget.js","sourceRoot":"","sources":["../../../src/targets/appimage/AppImageTarget.ts"],"names":[],"mappings":"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","sourcesContent":["import { IconInfo } from \"../../platformPackager\"\nimport { Arch, log, serializeToYaml } from \"builder-util\"\nimport { outputFile } from \"fs-extra\"\nimport { Lazy } from \"lazy-val\"\nimport * as path from \"path\"\nimport { Target } from \"../../core\"\nimport { LinuxPackager } from \"../../linuxPackager\"\nimport { AppImageOptions } from \"../../options/linuxOptions\"\nimport { getAppUpdatePublishConfiguration } from \"../../publish/PublishManager\"\nimport { executeAppBuilderAsJson, objectToArgs } from \"../../util/appBuilder\"\nimport { getNotLocalizedLicenseFile } from \"../../util/license\"\nimport { LinuxTargetHelper } from \"../LinuxTargetHelper\"\nimport { createStageDir, StageDir } from \"../targetUtil\"\nimport { buildAppImage } from \"./appImageUtil\"\nimport { BlockMapDataHolder } from \"builder-util-runtime\"\n\n// https://unix.stackexchange.com/questions/375191/append-to-sub-directory-inside-squashfs-file\n\nexport const APP_RUN_ENTRYPOINT = \"AppRun\"\n\nexport default class AppImageTarget extends Target {\n readonly options: AppImageOptions = { ...this.packager.platformSpecificBuildOptions, ...(this.packager.config as any)[this.name] }\n\n private readonly desktopEntry: Lazy<string>\n\n constructor(\n _ignored: string,\n private readonly packager: LinuxPackager,\n private readonly helper: LinuxTargetHelper,\n readonly outDir: string\n ) {\n super(\"appImage\")\n\n this.desktopEntry = new Lazy<string>(() => {\n const args = this.options.executableArgs?.join(\" \") || \"--no-sandbox\"\n return helper.computeDesktopEntry(this.options, `${APP_RUN_ENTRYPOINT} ${args} %U`, {\n \"X-AppImage-Version\": `${packager.appInfo.buildVersion}`,\n })\n })\n }\n\n async build(appOutDir: string, arch: Arch): Promise<any> {\n const packager = this.packager\n\n const options = this.options\n // https://github.com/electron-userland/electron-builder/issues/775\n // https://github.com/electron-userland/electron-builder/issues/1726\n const artifactName = packager.expandArtifactNamePattern(options, \"AppImage\", arch)\n const artifactPath = path.join(this.outDir, artifactName)\n\n await packager.info.emitArtifactBuildStarted({\n targetPresentableName: \"AppImage\",\n file: artifactPath,\n arch,\n })\n\n // Parallelize independent async operations\n const [publishConfig, stageDir, desktopEntry, icons, license] = await Promise.all([\n getAppUpdatePublishConfiguration(packager, options, arch, false),\n createStageDir(this, packager, arch),\n this.desktopEntry.value,\n this.helper.icons,\n getNotLocalizedLicenseFile(options.license, this.packager, [\"txt\", \"html\"]),\n ])\n\n if (publishConfig != null) {\n await outputFile(path.join(packager.getResourcesDir(appOutDir), \"app-update.yml\"), serializeToYaml(publishConfig))\n }\n\n if (this.packager.packagerOptions.effectiveOptionComputed != null && (await this.packager.packagerOptions.effectiveOptionComputed({ desktop: desktopEntry }))) {\n await stageDir.cleanup()\n return\n }\n\n let updateInfo: BlockMapDataHolder\n try {\n const appimageTool = this.packager.config.toolsets?.appimage\n if (appimageTool == null || appimageTool === \"0.0.0\") {\n updateInfo = await this.buildFuse2AppImage({ stageDir, arch, artifactPath, appOutDir, options, packager, desktopEntry, icons, license })\n } else {\n updateInfo = await buildAppImage({\n appDir: appOutDir,\n stageDir: stageDir.dir,\n arch,\n output: artifactPath,\n options: {\n productName: this.packager.appInfo.productName,\n productFilename: this.packager.appInfo.productFilename,\n executableName: this.packager.executableName,\n license,\n desktopEntry,\n icons,\n fileAssociations: this.packager.fileAssociations,\n compression: this.packager.compression === \"maximum\" ? \"xz\" : undefined,\n },\n })\n }\n } catch (error: any) {\n log.error({ error: error.message }, \"failed to build AppImage\")\n await stageDir.cleanup().catch(() => {})\n throw error\n }\n await stageDir.cleanup()\n\n await packager.info.emitArtifactBuildCompleted({\n file: artifactPath,\n safeArtifactName: packager.computeSafeArtifactName(artifactName, \"AppImage\", arch, false),\n target: this,\n arch,\n packager,\n isWriteUpdateInfo: true,\n updateInfo,\n })\n }\n\n private async buildFuse2AppImage(props: {\n stageDir: StageDir\n arch: Arch\n artifactPath: string\n appOutDir: string\n options: AppImageOptions\n packager: LinuxPackager\n desktopEntry: string\n icons: IconInfo[]\n license: string | null\n }): Promise<BlockMapDataHolder> {\n const { stageDir, arch, artifactPath, appOutDir, options, packager, desktopEntry, icons, license } = props\n\n const args = [\n \"appimage\",\n \"--stage\",\n stageDir.dir,\n \"--arch\",\n Arch[arch],\n \"--output\",\n artifactPath,\n \"--app\",\n appOutDir,\n \"--configuration\",\n JSON.stringify({\n productName: this.packager.appInfo.productName,\n productFilename: this.packager.appInfo.productFilename,\n desktopEntry,\n executableName: this.packager.executableName,\n icons,\n fileAssociations: this.packager.fileAssociations,\n ...options,\n }),\n ]\n objectToArgs(args, {\n license,\n })\n if (packager.compression === \"maximum\") {\n args.push(\"--compression\", \"xz\")\n }\n\n const updateInfo = await executeAppBuilderAsJson<BlockMapDataHolder>(args)\n return updateInfo\n }\n}\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Arch } from "builder-util";
|
|
2
|
+
import { FileAssociation } from "../../options/FileAssociation";
|
|
3
|
+
import { IconInfo } from "../../platformPackager";
|
|
4
|
+
import { BlockMapDataHolder } from "builder-util-runtime";
|
|
5
|
+
interface Options {
|
|
6
|
+
productName: string;
|
|
7
|
+
productFilename: string;
|
|
8
|
+
executableName: string;
|
|
9
|
+
desktopEntry: string;
|
|
10
|
+
icons: IconInfo[];
|
|
11
|
+
license?: string | null;
|
|
12
|
+
fileAssociations: FileAssociation[];
|
|
13
|
+
compression?: "xz" | "lzo" | "zstd";
|
|
14
|
+
}
|
|
15
|
+
export interface AppImageBuilderOptions {
|
|
16
|
+
appDir: string;
|
|
17
|
+
stageDir: string;
|
|
18
|
+
arch: Arch;
|
|
19
|
+
output: string;
|
|
20
|
+
options: Options;
|
|
21
|
+
}
|
|
22
|
+
export declare function buildAppImage(opts: AppImageBuilderOptions): Promise<BlockMapDataHolder>;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildAppImage = buildAppImage;
|
|
4
|
+
const builder_util_1 = require("builder-util");
|
|
5
|
+
const fs = require("fs-extra");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const linux_1 = require("../../toolsets/linux");
|
|
8
|
+
const appLauncher_1 = require("./appLauncher");
|
|
9
|
+
const differentialUpdateInfoBuilder_1 = require("../differentialUpdateInfoBuilder");
|
|
10
|
+
const AppImageTarget_1 = require("./AppImageTarget");
|
|
11
|
+
async function buildAppImage(opts) {
|
|
12
|
+
const { stageDir, output, appDir, options, arch } = opts;
|
|
13
|
+
try {
|
|
14
|
+
await fs.remove(output);
|
|
15
|
+
// Write AppRun launcher and related files
|
|
16
|
+
await writeAppLauncherAndRelatedFiles(opts);
|
|
17
|
+
const { runtimeLibraries: libraries, runtime, mksquashfs } = await (0, linux_1.getAppImageTools)(arch);
|
|
18
|
+
await (0, builder_util_1.copyDir)(libraries, path.join(stageDir, "usr", "lib"));
|
|
19
|
+
// Copy app directory to stage
|
|
20
|
+
// mksquashfs doesn't support merging, so we copy the entire app dir
|
|
21
|
+
await (0, builder_util_1.copyDir)(appDir, stageDir);
|
|
22
|
+
const runtimeData = await fs.readFile(runtime);
|
|
23
|
+
// Create squashfs with offset for runtime
|
|
24
|
+
const args = [stageDir, output, "-offset", runtimeData.length.toString(), "-all-root", "-noappend", "-no-progress", "-quiet", "-no-xattrs", "-no-fragments"];
|
|
25
|
+
if (options.compression) {
|
|
26
|
+
args.push("-comp", options.compression);
|
|
27
|
+
if (options.compression === "xz") {
|
|
28
|
+
args.push("-Xdict-size", "100%", "-b", "1048576");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
await (0, builder_util_1.exec)(mksquashfs, args, {
|
|
32
|
+
cwd: stageDir,
|
|
33
|
+
});
|
|
34
|
+
// Write runtime data at the beginning of the file
|
|
35
|
+
await writeRuntimeData(output, runtimeData);
|
|
36
|
+
// Make executable
|
|
37
|
+
await fs.chmod(output, 0o755);
|
|
38
|
+
// Append blockmap inside try block to ensure cleanup on failure
|
|
39
|
+
const updateInfo = await (0, differentialUpdateInfoBuilder_1.appendBlockmap)(output);
|
|
40
|
+
return updateInfo;
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
// Clean up partial build on failure
|
|
44
|
+
await fs.remove(output).catch(() => { });
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function writeRuntimeData(filePath, runtimeData) {
|
|
49
|
+
const fd = await fs.open(filePath, "r+");
|
|
50
|
+
try {
|
|
51
|
+
await fs.write(fd, runtimeData, 0, runtimeData.length, 0);
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
try {
|
|
55
|
+
await fs.close(fd);
|
|
56
|
+
}
|
|
57
|
+
catch (closeError) {
|
|
58
|
+
// Log but don't throw - preserve original error if any
|
|
59
|
+
builder_util_1.log.warn({ message: closeError.message, file: filePath }, `failed to close file descriptor`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Escapes a string for safe use in shell scripts by wrapping in single quotes
|
|
65
|
+
* and escaping any single quotes within the string.
|
|
66
|
+
*
|
|
67
|
+
* This allows strings with spaces, special characters, etc. to be safely used.
|
|
68
|
+
*/
|
|
69
|
+
function escapeShellString(str) {
|
|
70
|
+
// Escape single quotes by replacing ' with '\''
|
|
71
|
+
// Then wrap the whole string in single quotes
|
|
72
|
+
return `'${str.replace(/'/g, "'\\''")}'`;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Validates that critical executable/filename fields don't contain dangerous characters
|
|
76
|
+
* that could break paths or cause security issues even when escaped.
|
|
77
|
+
*/
|
|
78
|
+
function validateCriticalPathString(str, fieldName) {
|
|
79
|
+
// Only reject characters that would break filesystem paths or cause severe issues
|
|
80
|
+
// Allow quotes, spaces, etc. since they can be escaped
|
|
81
|
+
if (/[`${}|&;<>\n\r\0]/.test(str) || str.includes("/") || str.includes("\\")) {
|
|
82
|
+
throw new builder_util_1.InvalidConfigurationError(`${fieldName} contains characters that cannot be safely used in file paths: ${str}. ` +
|
|
83
|
+
`Please use only alphanumeric characters, hyphens, underscores, dots, spaces, and quotes.`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function writeAppLauncherAndRelatedFiles(opts) {
|
|
87
|
+
const { stageDir, options: { license, executableName, productFilename, productName, desktopEntry }, } = opts;
|
|
88
|
+
// Validate only critical path fields for severe path-breaking characters
|
|
89
|
+
// productName and productFilename can contain quotes, spaces, etc. - they'll be escaped
|
|
90
|
+
validateCriticalPathString(executableName, "executableName");
|
|
91
|
+
validateCriticalPathString(productFilename, "productFilename");
|
|
92
|
+
// Write desktop file
|
|
93
|
+
const desktopFileName = `${executableName}.desktop`;
|
|
94
|
+
await fs.writeFile(path.join(stageDir, desktopFileName), desktopEntry, { mode: 0o644 });
|
|
95
|
+
await (0, appLauncher_1.copyIcons)(opts);
|
|
96
|
+
const templateConfig = {
|
|
97
|
+
DesktopFileName: desktopFileName,
|
|
98
|
+
ExecutableName: executableName,
|
|
99
|
+
ProductName: productName,
|
|
100
|
+
ProductFilename: productFilename,
|
|
101
|
+
ResourceName: `appimagekit-${executableName}`,
|
|
102
|
+
};
|
|
103
|
+
const mimeTypeFile = await (0, appLauncher_1.copyMimeTypes)(opts);
|
|
104
|
+
if (mimeTypeFile) {
|
|
105
|
+
templateConfig.MimeTypeFile = mimeTypeFile;
|
|
106
|
+
}
|
|
107
|
+
let finalConfig = templateConfig;
|
|
108
|
+
// Copy license file if provided
|
|
109
|
+
if (license) {
|
|
110
|
+
// Validate license file exists
|
|
111
|
+
if (!(await (0, builder_util_1.exists)(license))) {
|
|
112
|
+
throw new builder_util_1.InvalidConfigurationError(`License file not found: ${license}`);
|
|
113
|
+
}
|
|
114
|
+
const licenseBaseName = path.basename(license);
|
|
115
|
+
const ext = path.extname(license).toLowerCase();
|
|
116
|
+
// Validate license filename for path safety
|
|
117
|
+
validateCriticalPathString(licenseBaseName, "licenseBaseName");
|
|
118
|
+
// Validate extension
|
|
119
|
+
if (![".txt", ".html"].includes(ext)) {
|
|
120
|
+
builder_util_1.log.warn({ license, expected: ".txt or .html" }, `license file has unexpected extension`);
|
|
121
|
+
}
|
|
122
|
+
await (0, builder_util_1.copyFile)(license, path.join(stageDir, licenseBaseName));
|
|
123
|
+
finalConfig = {
|
|
124
|
+
...templateConfig,
|
|
125
|
+
EulaFile: licenseBaseName,
|
|
126
|
+
IsHtmlEula: ext === ".html",
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const appRunContent = generateAppRunScript(finalConfig);
|
|
130
|
+
await fs.writeFile(path.join(stageDir, AppImageTarget_1.APP_RUN_ENTRYPOINT), appRunContent, { mode: 0o755 });
|
|
131
|
+
}
|
|
132
|
+
function hasEula(config) {
|
|
133
|
+
return "EulaFile" in config && typeof config.EulaFile === "string";
|
|
134
|
+
}
|
|
135
|
+
function generateAppRunScript(config) {
|
|
136
|
+
const eulaEnabled = hasEula(config);
|
|
137
|
+
return `#!/bin/bash
|
|
138
|
+
set -e
|
|
139
|
+
|
|
140
|
+
THIS="$0"
|
|
141
|
+
# http://stackoverflow.com/questions/3190818/
|
|
142
|
+
args=("$@")
|
|
143
|
+
NUMBER_OF_ARGS="$#"
|
|
144
|
+
|
|
145
|
+
if [ -z "$APPDIR" ] ; then
|
|
146
|
+
# Find the AppDir. It is the directory that contains AppRun.
|
|
147
|
+
# This assumes that this script resides inside the AppDir or a subdirectory.
|
|
148
|
+
# If this script is run inside an AppImage, then the AppImage runtime likely has already set $APPDIR
|
|
149
|
+
path="$(dirname "$(readlink -f "\${THIS}")")"
|
|
150
|
+
while [[ "$path" != "" && ! -e "$path/${AppImageTarget_1.APP_RUN_ENTRYPOINT}" ]]; do
|
|
151
|
+
path=\${path%/*}
|
|
152
|
+
done
|
|
153
|
+
APPDIR="$path"
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
export PATH="\${APPDIR}:\${APPDIR}/usr/sbin:\${PATH}"
|
|
157
|
+
export XDG_DATA_DIRS="./share/:/usr/share/gnome:/usr/local/share/:/usr/share/:\${XDG_DATA_DIRS}"
|
|
158
|
+
export LD_LIBRARY_PATH="\${APPDIR}/usr/lib:\${LD_LIBRARY_PATH}"
|
|
159
|
+
export XDG_DATA_DIRS="\${APPDIR}"/usr/share/:"\${XDG_DATA_DIRS}":/usr/share/gnome/:/usr/local/share/:/usr/share/
|
|
160
|
+
export GSETTINGS_SCHEMA_DIR="\${APPDIR}/usr/share/glib-2.0/schemas:\${GSETTINGS_SCHEMA_DIR}"
|
|
161
|
+
|
|
162
|
+
BIN="$APPDIR/${config.ExecutableName}"
|
|
163
|
+
|
|
164
|
+
if [ -z "$APPIMAGE_EXIT_AFTER_INSTALL" ] ; then
|
|
165
|
+
trap atexit EXIT
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
isEulaAccepted=1
|
|
169
|
+
|
|
170
|
+
atexit()
|
|
171
|
+
{
|
|
172
|
+
if [ $isEulaAccepted == 1 ] ; then
|
|
173
|
+
if [ $NUMBER_OF_ARGS -eq 0 ] ; then
|
|
174
|
+
exec "$BIN"
|
|
175
|
+
else
|
|
176
|
+
exec "$BIN" "\${args[@]}"
|
|
177
|
+
fi
|
|
178
|
+
fi
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
error()
|
|
182
|
+
{
|
|
183
|
+
if [ -x /usr/bin/zenity ] ; then
|
|
184
|
+
LD_LIBRARY_PATH="" zenity --error --text "\${1}" 2>/dev/null
|
|
185
|
+
elif [ -x /usr/bin/kdialog ] ; then
|
|
186
|
+
LD_LIBRARY_PATH="" kdialog --msgbox "\${1}" 2>/dev/null
|
|
187
|
+
elif [ -x /usr/bin/Xdialog ] ; then
|
|
188
|
+
LD_LIBRARY_PATH="" Xdialog --msgbox "\${1}" 2>/dev/null
|
|
189
|
+
else
|
|
190
|
+
echo "\${1}"
|
|
191
|
+
fi
|
|
192
|
+
exit 1
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
yesno()
|
|
196
|
+
{
|
|
197
|
+
TITLE=$1
|
|
198
|
+
TEXT=$2
|
|
199
|
+
if [ -x /usr/bin/zenity ] ; then
|
|
200
|
+
LD_LIBRARY_PATH="" zenity --question --title="$TITLE" --text="$TEXT" 2>/dev/null || exit 0
|
|
201
|
+
elif [ -x /usr/bin/kdialog ] ; then
|
|
202
|
+
LD_LIBRARY_PATH="" kdialog --title "$TITLE" --yesno "$TEXT" || exit 0
|
|
203
|
+
elif [ -x /usr/bin/Xdialog ] ; then
|
|
204
|
+
LD_LIBRARY_PATH="" Xdialog --title "$TITLE" --clear --yesno "$TEXT" 10 80 || exit 0
|
|
205
|
+
else
|
|
206
|
+
echo "zenity, kdialog, Xdialog missing. Skipping \${THIS}."
|
|
207
|
+
exit 0
|
|
208
|
+
fi
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
check_dep()
|
|
212
|
+
{
|
|
213
|
+
DEP=$1
|
|
214
|
+
if ! command -v "$DEP" &>/dev/null ; then
|
|
215
|
+
echo "$DEP is missing. Skipping \${THIS}."
|
|
216
|
+
exit 0
|
|
217
|
+
fi
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if [ -z "$APPIMAGE" ] ; then
|
|
221
|
+
APPIMAGE="$APPDIR/${AppImageTarget_1.APP_RUN_ENTRYPOINT}"
|
|
222
|
+
# not running from within an AppImage; hence using the AppRun for Exec=
|
|
223
|
+
fi
|
|
224
|
+
|
|
225
|
+
${eulaEnabled
|
|
226
|
+
? `if [ -z "$APPIMAGE_SILENT_INSTALL" ] ; then
|
|
227
|
+
EULA_MARK_DIR="\${XDG_CONFIG_HOME:-$HOME/.config}/${config.ProductFilename}"
|
|
228
|
+
EULA_MARK_FILE="$EULA_MARK_DIR/eulaAccepted"
|
|
229
|
+
# show EULA only if desktop file doesn't exist
|
|
230
|
+
if [ ! -e "$EULA_MARK_FILE" ] ; then
|
|
231
|
+
if [ -x /usr/bin/zenity ] ; then
|
|
232
|
+
# on cancel simply exits and our trap handler launches app, so, $isEulaAccepted is set here to 0 and then to 1 if EULA accepted
|
|
233
|
+
isEulaAccepted=0
|
|
234
|
+
LD_LIBRARY_PATH="" zenity --text-info --title=${escapeShellString(config.ProductName)} --filename="$APPDIR/${config.EulaFile}" --ok-label=Agree --cancel-label=Disagree ${config.IsHtmlEula ? "--html" : ""}
|
|
235
|
+
elif [ -x /usr/bin/Xdialog ] ; then
|
|
236
|
+
isEulaAccepted=0
|
|
237
|
+
LD_LIBRARY_PATH="" Xdialog --title ${escapeShellString(config.ProductName)} --textbox "$APPDIR/${config.EulaFile}" 30 80 --ok-label Agree --cancel-label Disagree
|
|
238
|
+
elif [ -x /usr/bin/kdialog ] ; then
|
|
239
|
+
# cannot find any option to force Agree/Disagree buttons for kdialog. And official example exactly with OK button https://techbase.kde.org/Development/Tutorials/Shell_Scripting_with_KDE_Dialogs#Example_21._--textbox_dialog_box
|
|
240
|
+
# in any case we pass labels text
|
|
241
|
+
isEulaAccepted=0
|
|
242
|
+
LD_LIBRARY_PATH="" kdialog --textbox "$APPDIR/${config.EulaFile}" --yes-label Agree --cancel-label "Disagree"
|
|
243
|
+
fi
|
|
244
|
+
|
|
245
|
+
case $? in
|
|
246
|
+
0)
|
|
247
|
+
isEulaAccepted=1
|
|
248
|
+
echo "License accepted"
|
|
249
|
+
mkdir -p "$EULA_MARK_DIR"
|
|
250
|
+
touch "$EULA_MARK_FILE"
|
|
251
|
+
;;
|
|
252
|
+
1)
|
|
253
|
+
echo "License not accepted"
|
|
254
|
+
exit 0
|
|
255
|
+
;;
|
|
256
|
+
-1)
|
|
257
|
+
echo "An unexpected error has occurred."
|
|
258
|
+
isEulaAccepted=1
|
|
259
|
+
;;
|
|
260
|
+
esac
|
|
261
|
+
fi
|
|
262
|
+
fi`
|
|
263
|
+
: ""}
|
|
264
|
+
`;
|
|
265
|
+
}
|
|
266
|
+
//# sourceMappingURL=appImageUtil.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"appImageUtil.js","sourceRoot":"","sources":["../../../src/targets/appimage/appImageUtil.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arch, copyDir, copyFile, exec, exists, InvalidConfigurationError, log } from \"builder-util\"\nimport * as fs from \"fs-extra\"\nimport * as path from \"path\"\nimport { FileAssociation } from \"../../options/FileAssociation\"\nimport { IconInfo } from \"../../platformPackager\"\nimport { getAppImageTools } from \"../../toolsets/linux\"\nimport { copyIcons, copyMimeTypes } from \"./appLauncher\"\nimport { appendBlockmap } from \"../differentialUpdateInfoBuilder\"\nimport { BlockMapDataHolder } from \"builder-util-runtime\"\nimport { APP_RUN_ENTRYPOINT } from \"./AppImageTarget\"\n\ninterface Options {\n productName: string\n productFilename: string\n executableName: string\n desktopEntry: string\n icons: IconInfo[]\n license?: string | null\n fileAssociations: FileAssociation[]\n compression?: \"xz\" | \"lzo\" | \"zstd\"\n}\n\nexport interface AppImageBuilderOptions {\n appDir: string\n stageDir: string\n arch: Arch\n output: string\n options: Options\n}\n\nexport async function buildAppImage(opts: AppImageBuilderOptions): Promise<BlockMapDataHolder> {\n const { stageDir, output, appDir, options, arch } = opts\n\n try {\n await fs.remove(output)\n\n // Write AppRun launcher and related files\n await writeAppLauncherAndRelatedFiles(opts)\n\n const { runtimeLibraries: libraries, runtime, mksquashfs } = await getAppImageTools(arch)\n await copyDir(libraries, path.join(stageDir, \"usr\", \"lib\"))\n\n // Copy app directory to stage\n // mksquashfs doesn't support merging, so we copy the entire app dir\n await copyDir(appDir, stageDir)\n\n const runtimeData = await fs.readFile(runtime)\n\n // Create squashfs with offset for runtime\n const args: string[] = [stageDir, output, \"-offset\", runtimeData.length.toString(), \"-all-root\", \"-noappend\", \"-no-progress\", \"-quiet\", \"-no-xattrs\", \"-no-fragments\"]\n if (options.compression) {\n args.push(\"-comp\", options.compression)\n\n if (options.compression === \"xz\") {\n args.push(\"-Xdict-size\", \"100%\", \"-b\", \"1048576\")\n }\n }\n await exec(mksquashfs, args, {\n cwd: stageDir,\n })\n\n // Write runtime data at the beginning of the file\n await writeRuntimeData(output, runtimeData)\n\n // Make executable\n await fs.chmod(output, 0o755)\n\n // Append blockmap inside try block to ensure cleanup on failure\n const updateInfo = await appendBlockmap(output)\n return updateInfo\n } catch (error) {\n // Clean up partial build on failure\n await fs.remove(output).catch(() => {})\n throw error\n }\n}\n\nasync function writeRuntimeData(filePath: string, runtimeData: Buffer): Promise<void> {\n const fd = await fs.open(filePath, \"r+\")\n try {\n await fs.write(fd, runtimeData, 0, runtimeData.length, 0)\n } finally {\n try {\n await fs.close(fd)\n } catch (closeError: any) {\n // Log but don't throw - preserve original error if any\n log.warn({ message: closeError.message, file: filePath }, `failed to close file descriptor`)\n }\n }\n}\n\n/**\n * Escapes a string for safe use in shell scripts by wrapping in single quotes\n * and escaping any single quotes within the string.\n *\n * This allows strings with spaces, special characters, etc. to be safely used.\n */\nfunction escapeShellString(str: string): string {\n // Escape single quotes by replacing ' with '\\''\n // Then wrap the whole string in single quotes\n return `'${str.replace(/'/g, \"'\\\\''\")}'`\n}\n\n/**\n * Validates that critical executable/filename fields don't contain dangerous characters\n * that could break paths or cause security issues even when escaped.\n */\nfunction validateCriticalPathString(str: string, fieldName: string): void {\n // Only reject characters that would break filesystem paths or cause severe issues\n // Allow quotes, spaces, etc. since they can be escaped\n if (/[`${}|&;<>\\n\\r\\0]/.test(str) || str.includes(\"/\") || str.includes(\"\\\\\")) {\n throw new InvalidConfigurationError(\n `${fieldName} contains characters that cannot be safely used in file paths: ${str}. ` +\n `Please use only alphanumeric characters, hyphens, underscores, dots, spaces, and quotes.`\n )\n }\n}\n\nasync function writeAppLauncherAndRelatedFiles(opts: AppImageBuilderOptions): Promise<void> {\n const {\n stageDir,\n options: { license, executableName, productFilename, productName, desktopEntry },\n } = opts\n\n // Validate only critical path fields for severe path-breaking characters\n // productName and productFilename can contain quotes, spaces, etc. - they'll be escaped\n validateCriticalPathString(executableName, \"executableName\")\n validateCriticalPathString(productFilename, \"productFilename\")\n\n // Write desktop file\n const desktopFileName = `${executableName}.desktop`\n await fs.writeFile(path.join(stageDir, desktopFileName), desktopEntry, { mode: 0o644 })\n await copyIcons(opts)\n\n const templateConfig: AppRunScriptBase = {\n DesktopFileName: desktopFileName,\n ExecutableName: executableName,\n ProductName: productName,\n ProductFilename: productFilename,\n ResourceName: `appimagekit-${executableName}`,\n }\n\n const mimeTypeFile = await copyMimeTypes(opts)\n if (mimeTypeFile) {\n templateConfig.MimeTypeFile = mimeTypeFile\n }\n\n let finalConfig: AppRunScript = templateConfig\n\n // Copy license file if provided\n if (license) {\n // Validate license file exists\n if (!(await exists(license))) {\n throw new InvalidConfigurationError(`License file not found: ${license}`)\n }\n\n const licenseBaseName = path.basename(license)\n const ext = path.extname(license).toLowerCase()\n\n // Validate license filename for path safety\n validateCriticalPathString(licenseBaseName, \"licenseBaseName\")\n\n // Validate extension\n if (![\".txt\", \".html\"].includes(ext)) {\n log.warn({ license, expected: \".txt or .html\" }, `license file has unexpected extension`)\n }\n\n await copyFile(license, path.join(stageDir, licenseBaseName))\n\n finalConfig = {\n ...templateConfig,\n EulaFile: licenseBaseName,\n IsHtmlEula: ext === \".html\",\n }\n }\n\n const appRunContent = generateAppRunScript(finalConfig)\n await fs.writeFile(path.join(stageDir, APP_RUN_ENTRYPOINT), appRunContent, { mode: 0o755 })\n}\n\ntype AppRunScriptBase = {\n ExecutableName: string\n DesktopFileName: string\n ProductFilename: string\n ProductName: string\n ResourceName: string\n MimeTypeFile?: string\n}\n\ntype AppRunScriptWithEula = AppRunScriptBase & {\n EulaFile: string\n IsHtmlEula: boolean\n}\n\ntype AppRunScript = AppRunScriptBase | AppRunScriptWithEula\n\nfunction hasEula(config: AppRunScript): config is AppRunScriptWithEula {\n return \"EulaFile\" in config && typeof config.EulaFile === \"string\"\n}\n\nfunction generateAppRunScript(config: AppRunScript): string {\n const eulaEnabled = hasEula(config)\n\n return `#!/bin/bash\nset -e\n\nTHIS=\"$0\"\n# http://stackoverflow.com/questions/3190818/\nargs=(\"$@\")\nNUMBER_OF_ARGS=\"$#\"\n\nif [ -z \"$APPDIR\" ] ; then\n # Find the AppDir. It is the directory that contains AppRun.\n # This assumes that this script resides inside the AppDir or a subdirectory.\n # If this script is run inside an AppImage, then the AppImage runtime likely has already set $APPDIR\n path=\"$(dirname \"$(readlink -f \"\\${THIS}\")\")\"\n while [[ \"$path\" != \"\" && ! -e \"$path/${APP_RUN_ENTRYPOINT}\" ]]; do\n path=\\${path%/*}\n done\n APPDIR=\"$path\"\nfi\n\nexport PATH=\"\\${APPDIR}:\\${APPDIR}/usr/sbin:\\${PATH}\"\nexport XDG_DATA_DIRS=\"./share/:/usr/share/gnome:/usr/local/share/:/usr/share/:\\${XDG_DATA_DIRS}\"\nexport LD_LIBRARY_PATH=\"\\${APPDIR}/usr/lib:\\${LD_LIBRARY_PATH}\"\nexport XDG_DATA_DIRS=\"\\${APPDIR}\"/usr/share/:\"\\${XDG_DATA_DIRS}\":/usr/share/gnome/:/usr/local/share/:/usr/share/\nexport GSETTINGS_SCHEMA_DIR=\"\\${APPDIR}/usr/share/glib-2.0/schemas:\\${GSETTINGS_SCHEMA_DIR}\"\n\nBIN=\"$APPDIR/${config.ExecutableName}\"\n\nif [ -z \"$APPIMAGE_EXIT_AFTER_INSTALL\" ] ; then\n trap atexit EXIT\nfi\n\nisEulaAccepted=1\n\natexit()\n{\n if [ $isEulaAccepted == 1 ] ; then\n if [ $NUMBER_OF_ARGS -eq 0 ] ; then\n exec \"$BIN\"\n else\n exec \"$BIN\" \"\\${args[@]}\"\n fi\n fi\n}\n\nerror()\n{\n if [ -x /usr/bin/zenity ] ; then\n LD_LIBRARY_PATH=\"\" zenity --error --text \"\\${1}\" 2>/dev/null\n elif [ -x /usr/bin/kdialog ] ; then\n LD_LIBRARY_PATH=\"\" kdialog --msgbox \"\\${1}\" 2>/dev/null\n elif [ -x /usr/bin/Xdialog ] ; then\n LD_LIBRARY_PATH=\"\" Xdialog --msgbox \"\\${1}\" 2>/dev/null\n else\n echo \"\\${1}\"\n fi\n exit 1\n}\n\nyesno()\n{\n TITLE=$1\n TEXT=$2\n if [ -x /usr/bin/zenity ] ; then\n LD_LIBRARY_PATH=\"\" zenity --question --title=\"$TITLE\" --text=\"$TEXT\" 2>/dev/null || exit 0\n elif [ -x /usr/bin/kdialog ] ; then\n LD_LIBRARY_PATH=\"\" kdialog --title \"$TITLE\" --yesno \"$TEXT\" || exit 0\n elif [ -x /usr/bin/Xdialog ] ; then\n LD_LIBRARY_PATH=\"\" Xdialog --title \"$TITLE\" --clear --yesno \"$TEXT\" 10 80 || exit 0\n else\n echo \"zenity, kdialog, Xdialog missing. Skipping \\${THIS}.\"\n exit 0\n fi\n}\n\ncheck_dep()\n{\n DEP=$1\n if ! command -v \"$DEP\" &>/dev/null ; then\n echo \"$DEP is missing. Skipping \\${THIS}.\"\n exit 0\n fi\n}\n\nif [ -z \"$APPIMAGE\" ] ; then\n APPIMAGE=\"$APPDIR/${APP_RUN_ENTRYPOINT}\"\n # not running from within an AppImage; hence using the AppRun for Exec=\nfi\n\n${\n eulaEnabled\n ? `if [ -z \"$APPIMAGE_SILENT_INSTALL\" ] ; then\n EULA_MARK_DIR=\"\\${XDG_CONFIG_HOME:-$HOME/.config}/${config.ProductFilename}\"\n EULA_MARK_FILE=\"$EULA_MARK_DIR/eulaAccepted\"\n # show EULA only if desktop file doesn't exist\n if [ ! -e \"$EULA_MARK_FILE\" ] ; then\n if [ -x /usr/bin/zenity ] ; then\n # on cancel simply exits and our trap handler launches app, so, $isEulaAccepted is set here to 0 and then to 1 if EULA accepted\n isEulaAccepted=0\n LD_LIBRARY_PATH=\"\" zenity --text-info --title=${escapeShellString(config.ProductName)} --filename=\"$APPDIR/${config.EulaFile}\" --ok-label=Agree --cancel-label=Disagree ${config.IsHtmlEula ? \"--html\" : \"\"}\n elif [ -x /usr/bin/Xdialog ] ; then\n isEulaAccepted=0\n LD_LIBRARY_PATH=\"\" Xdialog --title ${escapeShellString(config.ProductName)} --textbox \"$APPDIR/${config.EulaFile}\" 30 80 --ok-label Agree --cancel-label Disagree\n elif [ -x /usr/bin/kdialog ] ; then\n # cannot find any option to force Agree/Disagree buttons for kdialog. And official example exactly with OK button https://techbase.kde.org/Development/Tutorials/Shell_Scripting_with_KDE_Dialogs#Example_21._--textbox_dialog_box\n # in any case we pass labels text\n isEulaAccepted=0\n LD_LIBRARY_PATH=\"\" kdialog --textbox \"$APPDIR/${config.EulaFile}\" --yes-label Agree --cancel-label \"Disagree\"\n fi\n\n case $? in\n 0)\n isEulaAccepted=1\n echo \"License accepted\"\n mkdir -p \"$EULA_MARK_DIR\"\n touch \"$EULA_MARK_FILE\"\n ;;\n 1)\n echo \"License not accepted\"\n exit 0\n ;;\n -1)\n echo \"An unexpected error has occurred.\"\n isEulaAccepted=1\n ;;\n esac\n fi\nfi`\n : \"\"\n}\n`\n}\n"]}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.copyIcons = copyIcons;
|
|
4
|
+
exports.copyMimeTypes = copyMimeTypes;
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const fs = require("fs-extra");
|
|
7
|
+
const builder_util_1 = require("builder-util");
|
|
8
|
+
const ICON_DIR_RELATIVE_PATH = "usr/share/icons/hicolor";
|
|
9
|
+
const MIME_TYPE_DIR_RELATIVE_PATH = "usr/share/mime";
|
|
10
|
+
/**
|
|
11
|
+
* Escapes special XML characters to prevent injection
|
|
12
|
+
*/
|
|
13
|
+
function xmlEscape(str) {
|
|
14
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
15
|
+
}
|
|
16
|
+
async function copyIcons(options) {
|
|
17
|
+
const { stageDir, options: configuration } = options;
|
|
18
|
+
const iconCommonDir = path.join(stageDir, ICON_DIR_RELATIVE_PATH);
|
|
19
|
+
await fs.ensureDir(iconCommonDir);
|
|
20
|
+
const icons = configuration.icons;
|
|
21
|
+
if (!icons || icons.length === 0) {
|
|
22
|
+
throw new Error("At least one icon is required for AppImage");
|
|
23
|
+
}
|
|
24
|
+
const iconExtWithDot = path.extname(icons[0].file);
|
|
25
|
+
const iconFileName = `${configuration.executableName}${iconExtWithDot}`;
|
|
26
|
+
const maxIconIndex = icons.length - 1;
|
|
27
|
+
const iconInfoList = icons.map(icon => {
|
|
28
|
+
if (path.extname(icon.file) !== iconExtWithDot) {
|
|
29
|
+
throw new Error(`All icons must have the same extension: expected ${iconExtWithDot}, but got ${icon.file}`);
|
|
30
|
+
}
|
|
31
|
+
let iconSizeDir;
|
|
32
|
+
if (iconExtWithDot === ".svg") {
|
|
33
|
+
// SVG icons go in scalable/apps directory per freedesktop icon theme spec
|
|
34
|
+
iconSizeDir = "scalable/apps";
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
iconSizeDir = `${icon.size}x${icon.size}/apps`;
|
|
38
|
+
}
|
|
39
|
+
const iconRelativeToStageFile = path.join(ICON_DIR_RELATIVE_PATH, iconSizeDir, iconFileName);
|
|
40
|
+
const iconDir = path.join(iconCommonDir, iconSizeDir);
|
|
41
|
+
const iconFile = path.join(iconDir, iconFileName);
|
|
42
|
+
return { icon, iconDir, iconFile, iconRelativeToStageFile };
|
|
43
|
+
});
|
|
44
|
+
await Promise.all(iconInfoList.map(async ({ icon, iconDir, iconFile }) => {
|
|
45
|
+
await fs.ensureDir(iconDir);
|
|
46
|
+
await (0, builder_util_1.copyOrLinkFile)(icon.file, iconFile);
|
|
47
|
+
}));
|
|
48
|
+
// Create symlinks for the last (largest) icon
|
|
49
|
+
const { iconRelativeToStageFile } = iconInfoList[maxIconIndex];
|
|
50
|
+
await fs.symlink(iconRelativeToStageFile, path.join(stageDir, iconFileName));
|
|
51
|
+
await fs.symlink(iconRelativeToStageFile, path.join(stageDir, ".DirIcon"));
|
|
52
|
+
}
|
|
53
|
+
async function copyMimeTypes(options) {
|
|
54
|
+
const { stageDir, options: { fileAssociations, productName, executableName }, } = options;
|
|
55
|
+
if (!fileAssociations || fileAssociations.length === 0) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const mimeTypeParts = [];
|
|
59
|
+
for (const fileAssociation of fileAssociations) {
|
|
60
|
+
if (!fileAssociation.mimeType) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
// XML-escape to prevent injection
|
|
64
|
+
mimeTypeParts.push(`<mime-type type="${xmlEscape(fileAssociation.mimeType)}">`);
|
|
65
|
+
mimeTypeParts.push(` <comment>${xmlEscape(productName)} document</comment>`);
|
|
66
|
+
// Handle extension(s)
|
|
67
|
+
const extensions = Array.isArray(fileAssociation.ext) ? fileAssociation.ext : [fileAssociation.ext];
|
|
68
|
+
for (const ext of extensions) {
|
|
69
|
+
// Validate extension doesn't contain dangerous characters
|
|
70
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(ext)) {
|
|
71
|
+
builder_util_1.log.warn({ extension: ext }, `file extension contains unexpected characters and may not be supported`);
|
|
72
|
+
}
|
|
73
|
+
mimeTypeParts.push(` <glob pattern="*.${xmlEscape(ext)}"/>`);
|
|
74
|
+
}
|
|
75
|
+
mimeTypeParts.push(' <generic-icon name="x-office-document"/>');
|
|
76
|
+
mimeTypeParts.push("</mime-type>");
|
|
77
|
+
}
|
|
78
|
+
// If no mime-types were generated, return null
|
|
79
|
+
if (mimeTypeParts.length === 0) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const mimeTypeDir = path.join(stageDir, MIME_TYPE_DIR_RELATIVE_PATH);
|
|
83
|
+
const fileName = `${executableName}.xml`;
|
|
84
|
+
const mimeTypeFile = path.join(mimeTypeDir, fileName);
|
|
85
|
+
await fs.ensureDir(mimeTypeDir);
|
|
86
|
+
const xmlContent = ['<?xml version="1.0"?>', '<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">', ...mimeTypeParts, "</mime-info>"].join("\n");
|
|
87
|
+
// Use 0o644 (rw-r--r--) instead of 0o666 to avoid world-writable permissions
|
|
88
|
+
await fs.writeFile(mimeTypeFile, xmlContent, { mode: 0o644 });
|
|
89
|
+
return path.join(MIME_TYPE_DIR_RELATIVE_PATH, fileName);
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=appLauncher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"appLauncher.js","sourceRoot":"","sources":["../../../src/targets/appimage/appLauncher.ts"],"names":[],"mappings":"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","sourcesContent":["import * as path from \"path\"\nimport * as fs from \"fs-extra\"\nimport { copyOrLinkFile, log } from \"builder-util\"\nimport { AppImageBuilderOptions } from \"./appImageUtil\"\n\nconst ICON_DIR_RELATIVE_PATH = \"usr/share/icons/hicolor\"\nconst MIME_TYPE_DIR_RELATIVE_PATH = \"usr/share/mime\"\n\n/**\n * Escapes special XML characters to prevent injection\n */\nfunction xmlEscape(str: string): string {\n return str.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\").replace(/\"/g, \""\").replace(/'/g, \"'\")\n}\n\nexport async function copyIcons(options: AppImageBuilderOptions): Promise<void> {\n const { stageDir, options: configuration } = options\n const iconCommonDir = path.join(stageDir, ICON_DIR_RELATIVE_PATH)\n\n await fs.ensureDir(iconCommonDir)\n\n const icons = configuration.icons\n if (!icons || icons.length === 0) {\n throw new Error(\"At least one icon is required for AppImage\")\n }\n\n const iconExtWithDot = path.extname(icons[0].file)\n const iconFileName = `${configuration.executableName}${iconExtWithDot}`\n const maxIconIndex = icons.length - 1\n\n const iconInfoList = icons.map(icon => {\n if (path.extname(icon.file) !== iconExtWithDot) {\n throw new Error(`All icons must have the same extension: expected ${iconExtWithDot}, but got ${icon.file}`)\n }\n let iconSizeDir: string\n\n if (iconExtWithDot === \".svg\") {\n // SVG icons go in scalable/apps directory per freedesktop icon theme spec\n iconSizeDir = \"scalable/apps\"\n } else {\n iconSizeDir = `${icon.size}x${icon.size}/apps`\n }\n\n const iconRelativeToStageFile = path.join(ICON_DIR_RELATIVE_PATH, iconSizeDir, iconFileName)\n const iconDir = path.join(iconCommonDir, iconSizeDir)\n const iconFile = path.join(iconDir, iconFileName)\n return { icon, iconDir, iconFile, iconRelativeToStageFile }\n })\n await Promise.all(\n iconInfoList.map(async ({ icon, iconDir, iconFile }) => {\n await fs.ensureDir(iconDir)\n await copyOrLinkFile(icon.file, iconFile)\n })\n )\n // Create symlinks for the last (largest) icon\n const { iconRelativeToStageFile } = iconInfoList[maxIconIndex]\n await fs.symlink(iconRelativeToStageFile, path.join(stageDir, iconFileName))\n await fs.symlink(iconRelativeToStageFile, path.join(stageDir, \".DirIcon\"))\n}\n\nexport async function copyMimeTypes(options: AppImageBuilderOptions): Promise<string | null> {\n const {\n stageDir,\n options: { fileAssociations, productName, executableName },\n } = options\n\n if (!fileAssociations || fileAssociations.length === 0) {\n return null\n }\n\n const mimeTypeParts: string[] = []\n\n for (const fileAssociation of fileAssociations) {\n if (!fileAssociation.mimeType) {\n continue\n }\n\n // XML-escape to prevent injection\n mimeTypeParts.push(`<mime-type type=\"${xmlEscape(fileAssociation.mimeType)}\">`)\n mimeTypeParts.push(` <comment>${xmlEscape(productName)} document</comment>`)\n\n // Handle extension(s)\n const extensions = Array.isArray(fileAssociation.ext) ? fileAssociation.ext : [fileAssociation.ext]\n\n for (const ext of extensions) {\n // Validate extension doesn't contain dangerous characters\n if (!/^[a-zA-Z0-9_-]+$/.test(ext)) {\n log.warn({ extension: ext }, `file extension contains unexpected characters and may not be supported`)\n }\n mimeTypeParts.push(` <glob pattern=\"*.${xmlEscape(ext)}\"/>`)\n }\n\n mimeTypeParts.push(' <generic-icon name=\"x-office-document\"/>')\n mimeTypeParts.push(\"</mime-type>\")\n }\n\n // If no mime-types were generated, return null\n if (mimeTypeParts.length === 0) {\n return null\n }\n\n const mimeTypeDir = path.join(stageDir, MIME_TYPE_DIR_RELATIVE_PATH)\n const fileName = `${executableName}.xml`\n const mimeTypeFile = path.join(mimeTypeDir, fileName)\n\n await fs.ensureDir(mimeTypeDir)\n\n const xmlContent = ['<?xml version=\"1.0\"?>', '<mime-info xmlns=\"http://www.freedesktop.org/standards/shared-mime-info\">', ...mimeTypeParts, \"</mime-info>\"].join(\"\\n\")\n\n // Use 0o644 (rw-r--r--) instead of 0o666 to avoid world-writable permissions\n await fs.writeFile(mimeTypeFile, xmlContent, { mode: 0o644 })\n\n return path.join(MIME_TYPE_DIR_RELATIVE_PATH, fileName)\n}\n"]}
|
package/out/targets/archive.js
CHANGED
|
@@ -8,7 +8,7 @@ const builder_util_1 = require("builder-util");
|
|
|
8
8
|
const fs_extra_1 = require("fs-extra");
|
|
9
9
|
const path = require("path");
|
|
10
10
|
const tar_1 = require("tar");
|
|
11
|
-
const
|
|
11
|
+
const linux_1 = require("../toolsets/linux");
|
|
12
12
|
/** @internal */
|
|
13
13
|
async function tar(compression, format, outFile, dirToArchive, isMacApp, tempDirManager) {
|
|
14
14
|
const tarFile = await tempDirManager.getTempFile({ suffix: ".tar" });
|
|
@@ -33,7 +33,7 @@ async function tar(compression, format, outFile, dirToArchive, isMacApp, tempDir
|
|
|
33
33
|
// noinspection SpellCheckingInspection
|
|
34
34
|
let lzipPath = "lzip";
|
|
35
35
|
if (process.platform === "darwin") {
|
|
36
|
-
lzipPath = path.join(await (0,
|
|
36
|
+
lzipPath = path.join(await (0, linux_1.getLinuxToolsPath)(), "bin", lzipPath);
|
|
37
37
|
}
|
|
38
38
|
await (0, builder_util_1.exec)(lzipPath, [compression === "store" ? "-1" : "-9", "--keep" /* keep (don't delete) input files */, tarFile]);
|
|
39
39
|
// bloody lzip creates file in the same dir where input file with postfix `.lz`, option --output doesn't work
|