From 0dbe357ac5b4f3c51d9a6e9d7bbf0b1f142b5746 Mon Sep 17 00:00:00 2001 From: Mike Maietta Date: Wed, 9 Oct 2024 07:59:19 -0700 Subject: [PATCH] feat: allowing additional entries in .desktop file, such as `[Desktop Actions ]` (#8572) --- .changeset/sweet-masks-sparkle.md | 5 ++ packages/app-builder-lib/scheme.json | 89 +++++++++++++++++-- packages/app-builder-lib/src/index.ts | 2 +- .../src/options/linuxOptions.ts | 32 ++++++- .../src/targets/LinuxTargetHelper.ts | 15 +++- .../snapshots/linux/linuxPackagerTest.js.snap | 9 ++ test/src/ExtraBuildResourcesTest.ts | 4 +- test/src/ignoreTest.ts | 30 +++---- test/src/linux/linuxPackagerTest.ts | 24 ++++- typedoc.config.js | 2 +- 10 files changed, 175 insertions(+), 37 deletions(-) create mode 100644 .changeset/sweet-masks-sparkle.md diff --git a/.changeset/sweet-masks-sparkle.md b/.changeset/sweet-masks-sparkle.md new file mode 100644 index 00000000000..5b6bfa20088 --- /dev/null +++ b/.changeset/sweet-masks-sparkle.md @@ -0,0 +1,5 @@ +--- +"app-builder-lib": major +--- + +feat: allowing additional entries in .desktop file, such as `[Desktop Actions ]`. Requires changing configuration `desktop` property to object to be more extensible in the future diff --git a/packages/app-builder-lib/scheme.json b/packages/app-builder-lib/scheme.json index fdce744dd4a..dbb2c643a41 100644 --- a/packages/app-builder-lib/scheme.json +++ b/packages/app-builder-lib/scheme.json @@ -58,7 +58,15 @@ ] }, "desktop": { - "description": "The [Desktop file](https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html#desktop-files) entries (name to value)." + "anyOf": [ + { + "$ref": "#/definitions/LinuxDesktopFile" + }, + { + "type": "null" + } + ], + "description": "The [Desktop file](https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html#desktop-files)" }, "executableArgs": { "anyOf": [ @@ -576,7 +584,15 @@ ] }, "desktop": { - "description": "The [Desktop file](https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html#desktop-files) entries (name to value)." + "anyOf": [ + { + "$ref": "#/definitions/LinuxDesktopFile" + }, + { + "type": "null" + } + ], + "description": "The [Desktop file](https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html#desktop-files)" }, "executableArgs": { "anyOf": [ @@ -1111,7 +1127,15 @@ ] }, "desktop": { - "description": "The [Desktop file](https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html#desktop-files) entries (name to value)." + "anyOf": [ + { + "$ref": "#/definitions/LinuxDesktopFile" + }, + { + "type": "null" + } + ], + "description": "The [Desktop file](https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html#desktop-files)" }, "executableArgs": { "anyOf": [ @@ -1645,7 +1669,15 @@ ] }, "desktop": { - "description": "The [Desktop file](https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html#desktop-files) entries (name to value)." + "anyOf": [ + { + "$ref": "#/definitions/LinuxDesktopFile" + }, + { + "type": "null" + } + ], + "description": "The [Desktop file](https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html#desktop-files)" }, "detectUpdateChannel": { "default": true, @@ -1941,6 +1973,35 @@ }, "type": "object" }, + "LinuxDesktopFile": { + "additionalProperties": false, + "description": "Example Spec: https://specifications.freedesktop.org/desktop-entry-spec/latest/example.html", + "properties": { + "desktopActions": { + "anyOf": [ + { + "typeof": "function" + }, + { + "type": "null" + } + ], + "description": "`[Desktop Actions ]` metadata entries (name to value).\n\nConfig Example:\n```js\ndesktopActions: {\n NewWindow: {\n Name: 'New Window',\n Exec: 'app --new-window',\n }\n}\n```" + }, + "entry": { + "anyOf": [ + { + "typeof": "function" + }, + { + "type": "null" + } + ], + "description": "`[Desktop Entry]` metadata entries (name to value). Overwrites default values calculated by electron-builder" + } + }, + "type": "object" + }, "LinuxTargetSpecificOptions": { "additionalProperties": false, "properties": { @@ -2010,7 +2071,15 @@ ] }, "desktop": { - "description": "The [Desktop file](https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html#desktop-files) entries (name to value)." + "anyOf": [ + { + "$ref": "#/definitions/LinuxDesktopFile" + }, + { + "type": "null" + } + ], + "description": "The [Desktop file](https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html#desktop-files)" }, "executableArgs": { "anyOf": [ @@ -5463,7 +5532,15 @@ ] }, "desktop": { - "description": "The [Desktop file](https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html#desktop-files) entries (name to value)." + "anyOf": [ + { + "$ref": "#/definitions/LinuxDesktopFile" + }, + { + "type": "null" + } + ], + "description": "The [Desktop file](https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html#desktop-files)" }, "environment": { "anyOf": [ diff --git a/packages/app-builder-lib/src/index.ts b/packages/app-builder-lib/src/index.ts index 77f765169b1..cf5c3188817 100644 --- a/packages/app-builder-lib/src/index.ts +++ b/packages/app-builder-lib/src/index.ts @@ -33,7 +33,7 @@ export { MsiOptions } from "./options/MsiOptions" export { MsiWrappedOptions } from "./options/MsiWrappedOptions" export { CommonWindowsInstallerConfiguration } from "./options/CommonWindowsInstallerConfiguration" export { NsisOptions, NsisWebOptions, PortableOptions, CommonNsisOptions, CustomNsisBinary } from "./targets/nsis/nsisOptions" -export { LinuxConfiguration, DebOptions, CommonLinuxOptions, LinuxTargetSpecificOptions, AppImageOptions, FlatpakOptions } from "./options/linuxOptions" +export { LinuxConfiguration, DebOptions, CommonLinuxOptions, LinuxTargetSpecificOptions, AppImageOptions, FlatpakOptions, LinuxDesktopFile } from "./options/linuxOptions" export { SnapOptions, PlugDescriptor, SlotDescriptor } from "./options/SnapOptions" export { Metadata, AuthorMetadata, RepositoryInfo } from "./options/metadata" export { AppInfo } from "./appInfo" diff --git a/packages/app-builder-lib/src/options/linuxOptions.ts b/packages/app-builder-lib/src/options/linuxOptions.ts index 5eeed4d2d3e..77e4882cb36 100644 --- a/packages/app-builder-lib/src/options/linuxOptions.ts +++ b/packages/app-builder-lib/src/options/linuxOptions.ts @@ -1,5 +1,33 @@ import { PlatformSpecificBuildOptions, TargetConfigType, TargetSpecificOptions } from "../index" +/** + * Example Spec: https://specifications.freedesktop.org/desktop-entry-spec/latest/example.html + */ +export interface LinuxDesktopFile { + /** + * `[Desktop Entry]` metadata entries (name to value). Overwrites default values calculated by electron-builder + */ + entry?: { + [k: string]: string + } | null + /** + * `[Desktop Actions ]` metadata entries (name to value). + * + * Config Example: + * ```js + * desktopActions: { + * NewWindow: { + * Name: 'New Window', + * Exec: 'app --new-window', + * } + * } + * ``` + */ + desktopActions?: { + [ActionName: string]: any + } | null +} + export interface LinuxConfiguration extends CommonLinuxOptions, PlatformSpecificBuildOptions { /** * Target package type: list of `AppImage`, `flatpak`, `snap`, `deb`, `rpm`, `freebsd`, `pacman`, `p5p`, `apk`, `7z`, `zip`, `tar.xz`, `tar.lz`, `tar.gz`, `tar.bz2`, `dir`. @@ -56,9 +84,9 @@ export interface CommonLinuxOptions { readonly mimeTypes?: Array | null /** - * The [Desktop file](https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html#desktop-files) entries (name to value). + * The [Desktop file](https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html#desktop-files) */ - readonly desktop?: any | null + readonly desktop?: LinuxDesktopFile | null /** * The executable parameters. Pass to executableName diff --git a/packages/app-builder-lib/src/targets/LinuxTargetHelper.ts b/packages/app-builder-lib/src/targets/LinuxTargetHelper.ts index 248b939b4ac..8630802a609 100644 --- a/packages/app-builder-lib/src/targets/LinuxTargetHelper.ts +++ b/packages/app-builder-lib/src/targets/LinuxTargetHelper.ts @@ -103,7 +103,7 @@ export class LinuxTargetHelper { throw new Error("Specified exec is empty") } // https://github.com/electron-userland/electron-builder/issues/3418 - if (targetSpecificOptions.desktop != null && targetSpecificOptions.desktop.Exec != null) { + if (targetSpecificOptions.desktop?.entry?.Exec) { throw new Error("Please specify executable name as linux.executableName instead of linux.desktop.Exec") } @@ -140,7 +140,7 @@ export class LinuxTargetHelper { // https://github.com/electron/electron/blob/2-0-x/atom/browser/native_window_views.cc#L226 StartupWMClass: appInfo.productName, ...extra, - ...targetSpecificOptions.desktop, + ...(targetSpecificOptions.desktop?.entry ?? {}), } const description = this.getDescription(targetSpecificOptions) @@ -194,6 +194,17 @@ export class LinuxTargetHelper { data += `\n${name}=${desktopMeta[name]}` } data += "\n" + const desktopActions = targetSpecificOptions.desktop?.desktopActions ?? {} + for (const [actionName, config] of Object.entries(desktopActions)) { + if (!Object.keys(config ?? {}).length) { + continue + } + data += `\n[Desktop Action ${actionName}]` + for (const [key, value] of Object.entries(config ?? {})) { + data += `\n${key}=${value}` + } + data += "\n" + } return Promise.resolve(data) } } diff --git a/test/snapshots/linux/linuxPackagerTest.js.snap b/test/snapshots/linux/linuxPackagerTest.js.snap index 53227b18fad..bae7a36ca6b 100644 --- a/test/snapshots/linux/linuxPackagerTest.js.snap +++ b/test/snapshots/linux/linuxPackagerTest.js.snap @@ -11,6 +11,15 @@ StartupWMClass=Test App ßW X-Foo=bar Comment=Test Application (test quite “ #378) Categories=Development; + +[Desktop Action Gallery] +Exec=fooview --gallery +Name=Browse Gallery + +[Desktop Action Create] +Exec=fooview --create-new +Name=Create a new Foo! +Icon=fooview-new " `; diff --git a/test/src/ExtraBuildResourcesTest.ts b/test/src/ExtraBuildResourcesTest.ts index 985b43719ee..0059c559431 100644 --- a/test/src/ExtraBuildResourcesTest.ts +++ b/test/src/ExtraBuildResourcesTest.ts @@ -181,9 +181,9 @@ test.ifNotWindows( app({ targets: linuxDirTarget, config: { - electronDist: (_context) => { + electronDist: _context => { return Promise.resolve(getElectronCacheDir()) - } + }, }, }) ) diff --git a/test/src/ignoreTest.ts b/test/src/ignoreTest.ts index c9facb29121..8da0b126a96 100644 --- a/test/src/ignoreTest.ts +++ b/test/src/ignoreTest.ts @@ -92,16 +92,14 @@ test.ifNotCiMac( return Promise.all([ modifyPackageJson(projectDir, data => { data.devDependencies = { - "semver": "6.3.1", + semver: "6.3.1", ...data.devDependencies, } }), ]) }, packed: context => { - return Promise.all([ - assertThat(path.join(context.getResources(Platform.LINUX), "app", "node_modules", "semver")).doesNotExist(), - ]) + return Promise.all([assertThat(path.join(context.getResources(Platform.LINUX), "app", "node_modules", "semver")).doesNotExist()]) }, } ) @@ -124,7 +122,7 @@ test.ifDevOrLinuxCi( modifyPackageJson(projectDir, data => { data.dependencies = { "electron-updater": "6.3.9", - "semver":"6.3.1", + semver: "6.3.1", ...data.dependencies, } }), @@ -137,9 +135,7 @@ test.ifDevOrLinuxCi( assertThat(path.join(context.getResources(Platform.LINUX, archFromString(process.arch)), "app", "node_modules", "electron-updater", "node_modules")).isDirectory(), assertThat(path.join(context.getResources(Platform.LINUX, archFromString(process.arch)), "app", "others", "node_modules")).doesNotExist(), assertThat(path.join(context.getResources(Platform.LINUX, archFromString(process.arch)), "app", "submodule-1-test", "node_modules")).isDirectory(), - assertThat( - path.join(context.getResources(Platform.LINUX, archFromString(process.arch)), "app", "submodule-1-test", "node_modules", "package.json") - ).isFile(), + assertThat(path.join(context.getResources(Platform.LINUX, archFromString(process.arch)), "app", "submodule-1-test", "node_modules", "package.json")).isFile(), ]) }, } @@ -190,9 +186,9 @@ test.ifDevOrLinuxCi( targets: Platform.LINUX.createTarget(DIR_TARGET), config: { asar: false, - // should use **/ instead of */, + // should use **/ instead of */, // we use the related path to match, so the relative path is submodule-1-test/node_modules - // */ will not match submodule-1-test/node_modules + // */ will not match submodule-1-test/node_modules files: ["**/*", "**/submodule-1-test/node_modules/**"], }, }, @@ -211,9 +207,7 @@ test.ifDevOrLinuxCi( packed: context => { return Promise.all([ assertThat(path.join(context.getResources(Platform.LINUX, archFromString(process.arch)), "app", "submodule-1-test", "node_modules")).isDirectory(), - assertThat( - path.join(context.getResources(Platform.LINUX, archFromString(process.arch)), "app", "submodule-1-test", "node_modules", "package.json") - ).isFile(), + assertThat(path.join(context.getResources(Platform.LINUX, archFromString(process.arch)), "app", "submodule-1-test", "node_modules", "package.json")).isFile(), assertThat(path.join(context.getResources(Platform.LINUX, archFromString(process.arch)), "app", "submodule-2-test", "node_modules")).doesNotExist(), ]) }, @@ -243,9 +237,7 @@ test.ifDevOrLinuxCi( ]) }, packed: context => { - return Promise.all([ - assertThat(path.join(context.getResources(Platform.LINUX, archFromString(process.arch)), "app", "submodule-1-test", "node_modules")).doesNotExist(), - ]) + return Promise.all([assertThat(path.join(context.getResources(Platform.LINUX, archFromString(process.arch)), "app", "submodule-1-test", "node_modules")).doesNotExist()]) }, } ) @@ -273,10 +265,8 @@ test.ifDevOrLinuxCi( ]) }, packed: context => { - return Promise.all([ - assertThat(path.join(context.getResources(Platform.LINUX, archFromString(process.arch)), "app", "submodule-1-test", "node_modules")).doesNotExist(), - ]) + return Promise.all([assertThat(path.join(context.getResources(Platform.LINUX, archFromString(process.arch)), "app", "submodule-1-test", "node_modules")).doesNotExist()]) }, } ) -) \ No newline at end of file +) diff --git a/test/src/linux/linuxPackagerTest.ts b/test/src/linux/linuxPackagerTest.ts index 3100b8f1f7a..42ad80bbec7 100644 --- a/test/src/linux/linuxPackagerTest.ts +++ b/test/src/linux/linuxPackagerTest.ts @@ -112,9 +112,25 @@ test.ifNotWindows.ifNotCiMac( config: { linux: { executableName: "Foo", + // Example Spec: https://specifications.freedesktop.org/desktop-entry-spec/latest/example.html desktop: { - "X-Foo": "bar", - Terminal: "true", + entry: { + "X-Foo": "bar", + Terminal: "true", + }, + desktopActions: { + Gallery: { + Exec: "fooview --gallery", + Name: "Browse Gallery", + }, + Create: { + Exec: "fooview --create-new", + Name: "Create a new Foo!", + Icon: "fooview-new", + }, + EmptyEntry: {}, + NullEntry: null, + }, }, }, appImage: { @@ -293,7 +309,9 @@ test.ifNotWindows( config: { linux: { desktop: { - Exec: "foo", + entry: { + Exec: "foo", + }, }, }, }, diff --git a/typedoc.config.js b/typedoc.config.js index 98c9bed014a..be7f08a8f85 100644 --- a/typedoc.config.js +++ b/typedoc.config.js @@ -16,7 +16,7 @@ module.exports = { excludePrivate: true, excludeProtected: true, excludeNotDocumented: false, - includeVersion: false, + includeVersion: true, hideGroupHeadings: true, hidePageTitle: true, disableSources: true,