diff --git a/packages/app-builder-lib/src/appInfo.ts b/packages/app-builder-lib/src/appInfo.ts index 3b1d738756e..344494ec5e6 100644 --- a/packages/app-builder-lib/src/appInfo.ts +++ b/packages/app-builder-lib/src/appInfo.ts @@ -3,7 +3,7 @@ import { prerelease, SemVer } from "semver" import { PlatformSpecificBuildOptions } from "./options/PlatformSpecificBuildOptions" import { Packager } from "./packager" import { expandMacro } from "./util/macroExpander" -import { sanitizeFileName } from "./util/sanitizeFileName" +import { sanitizeFileName } from "./util/filename" // fpm bug - rpm build --description is not escaped, well... decided to replace quite to smart quote // http://leancrew.com/all-this/2010/11/smart-quotes-in-javascript/ diff --git a/packages/app-builder-lib/src/linuxPackager.ts b/packages/app-builder-lib/src/linuxPackager.ts index 6a1d9581ec4..759a325fa68 100644 --- a/packages/app-builder-lib/src/linuxPackager.ts +++ b/packages/app-builder-lib/src/linuxPackager.ts @@ -10,7 +10,7 @@ import FpmTarget from "./targets/fpm" import { LinuxTargetHelper } from "./targets/LinuxTargetHelper" import SnapTarget from "./targets/snap" import { createCommonTarget } from "./targets/targetFactory" -import { sanitizeFileName } from "./util/sanitizeFileName" +import { sanitizeFileName } from "./util/filename" export class LinuxPackager extends PlatformPackager { readonly executableName: string diff --git a/packages/app-builder-lib/src/options/CommonWindowsInstallerConfiguration.ts b/packages/app-builder-lib/src/options/CommonWindowsInstallerConfiguration.ts index f563987b5d8..66ce3d27aa1 100644 --- a/packages/app-builder-lib/src/options/CommonWindowsInstallerConfiguration.ts +++ b/packages/app-builder-lib/src/options/CommonWindowsInstallerConfiguration.ts @@ -1,5 +1,5 @@ import { InvalidConfigurationError, isEmptyOrSpaces } from "builder-util" -import { sanitizeFileName } from "../util/sanitizeFileName" +import { sanitizeFileName } from "../util/filename" import { WinPackager } from "../winPackager" export interface CommonWindowsInstallerConfiguration { diff --git a/packages/app-builder-lib/src/publish/KeygenPublisher.ts b/packages/app-builder-lib/src/publish/KeygenPublisher.ts index 9ce3b9e4290..9dde27710e1 100644 --- a/packages/app-builder-lib/src/publish/KeygenPublisher.ts +++ b/packages/app-builder-lib/src/publish/KeygenPublisher.ts @@ -4,7 +4,8 @@ import { ClientRequest, RequestOptions } from "http" import { HttpPublisher, PublishContext } from "electron-publish" import { KeygenOptions } from "builder-util-runtime/out/publishOptions" import { configureRequestOptions, HttpExecutor, parseJson } from "builder-util-runtime" -import * as path from "path" +import { getCompleteExtname } from "../util/filename" + export class KeygenPublisher extends HttpPublisher { readonly providerName = "keygen" readonly hostname = "api.keygen.sh" @@ -78,7 +79,7 @@ export class KeygenPublisher extends HttpPublisher { type: "release", attributes: { filename: fileName, - filetype: path.extname(fileName), + filetype: getCompleteExtname(fileName), filesize: dataLength, version: this.version, platform: this.info.platform, diff --git a/packages/app-builder-lib/src/util/filename.ts b/packages/app-builder-lib/src/util/filename.ts new file mode 100644 index 00000000000..58cf72ee83f --- /dev/null +++ b/packages/app-builder-lib/src/util/filename.ts @@ -0,0 +1,39 @@ +// @ts-ignore +import * as _sanitizeFileName from "sanitize-filename" +import * as path from "path" + +export function sanitizeFileName(s: string): string { + return _sanitizeFileName(s) +} + +// Get the filetype from a filename. Returns a string of one or more file extensions, +// e.g. .zip, .dmg, .tar.gz, .tar.bz2, .exe.blockmap. We'd normally use `path.extname()`, +// but it doesn't support multiple extensions, e.g. Foo-1.0.0.dmg.blockmap should be +// .dmg.blockmap, not .blockmap. +export function getCompleteExtname(filename: string): string { + let extname = path.extname(filename) + + switch (extname) { + // Append leading extension for blockmap filetype + case ".blockmap": { + extname = path.extname(filename.replace(extname, "")) + extname + + break + } + // Append leading extension for known compressed tar formats + case ".bz2": + case ".gz": + case ".lz": + case ".xz": + case ".7z": { + const ext = path.extname(filename.replace(extname, "")) + if (ext === ".tar") { + extname = ext + extname + } + + break + } + } + + return extname +} diff --git a/packages/app-builder-lib/src/util/sanitizeFileName.ts b/packages/app-builder-lib/src/util/sanitizeFileName.ts deleted file mode 100644 index b2e6dbe5fbc..00000000000 --- a/packages/app-builder-lib/src/util/sanitizeFileName.ts +++ /dev/null @@ -1,6 +0,0 @@ -// @ts-ignore -import * as _sanitizeFileName from "sanitize-filename" - -export function sanitizeFileName(s: string): string { - return _sanitizeFileName(s) -} diff --git a/packages/dmg-builder/src/dmg.ts b/packages/dmg-builder/src/dmg.ts index 0b064fe67bb..5fab8143900 100644 --- a/packages/dmg-builder/src/dmg.ts +++ b/packages/dmg-builder/src/dmg.ts @@ -3,7 +3,7 @@ import { findIdentity, isSignAllowed } from "app-builder-lib/out/codeSign/macCod import MacPackager from "app-builder-lib/out/macPackager" import { createBlockmap } from "app-builder-lib/out/targets/differentialUpdateInfoBuilder" import { executeAppBuilderAsJson } from "app-builder-lib/out/util/appBuilder" -import { sanitizeFileName } from "app-builder-lib/out/util/sanitizeFileName" +import { sanitizeFileName } from "app-builder-lib/out/util/filename" import { Arch, AsyncTaskManager, exec, getArchSuffix, InvalidConfigurationError, isEmptyOrSpaces, log, spawn, retry } from "builder-util" import { CancellationToken } from "builder-util-runtime" import { copyDir, copyFile, exists, statOrNull } from "builder-util/out/fs" diff --git a/packages/electron-builder-squirrel-windows/src/SquirrelWindowsTarget.ts b/packages/electron-builder-squirrel-windows/src/SquirrelWindowsTarget.ts index 1d727bcbe00..29b4be11aff 100644 --- a/packages/electron-builder-squirrel-windows/src/SquirrelWindowsTarget.ts +++ b/packages/electron-builder-squirrel-windows/src/SquirrelWindowsTarget.ts @@ -1,4 +1,4 @@ -import { sanitizeFileName } from "app-builder-lib/out/util/sanitizeFileName" +import { sanitizeFileName } from "app-builder-lib/out/util/filename" import { InvalidConfigurationError, log, isEmptyOrSpaces } from "builder-util" import { getBinFromUrl } from "app-builder-lib/out/binDownload" import { Arch, getArchSuffix, SquirrelWindowsOptions, Target } from "app-builder-lib" diff --git a/packages/electron-builder/src/cli/create-self-signed-cert.ts b/packages/electron-builder/src/cli/create-self-signed-cert.ts index ba787005969..132b5f23363 100644 --- a/packages/electron-builder/src/cli/create-self-signed-cert.ts +++ b/packages/electron-builder/src/cli/create-self-signed-cert.ts @@ -1,4 +1,4 @@ -import { sanitizeFileName } from "app-builder-lib/out/util/sanitizeFileName" +import { sanitizeFileName } from "app-builder-lib/out/util/filename" import { exec, log, spawn, TmpDir } from "builder-util" import { unlinkIfExists } from "builder-util/out/fs" import * as chalk from "chalk" diff --git a/test/src/filenameUtilTest.ts b/test/src/filenameUtilTest.ts new file mode 100644 index 00000000000..4be6415995a --- /dev/null +++ b/test/src/filenameUtilTest.ts @@ -0,0 +1,41 @@ +import { getCompleteExtname } from "app-builder-lib/out/util/filename" + +// [inputFilename, expectedExtname] +const tests = [ + ["Foo-v1.exe.blockmap", ".exe.blockmap"], + ["Foo-1.0.0.dmg.blockmap", ".dmg.blockmap"], + ["Foo-v1.0.0.exe.blockmap", ".exe.blockmap"], + ["Foo-1.0.0-mac.zip.blockmap", ".zip.blockmap"], + ["Foo-1.0.0.exe", ".exe"], + ["foo-1.0.0.exe", ".exe"], + ["foo.bar-1.0.0.dmg", ".dmg"], + ["Foo-2.0.0.rc1.dmg", ".dmg"], + ["Foo-1.0.0-mac.dmg", ".dmg"], + ["Foo-v1.0.0.zip", ".zip"], + ["Foo-1.0.0.tar.gz", ".tar.gz"], + ["Foo-1.0.0.tar.7z", ".tar.7z"], + ["Foo-1.0.0.7z", ".7z"], + ["Foo-1.0.0.test.7z", ".7z"], + ["Foo-1.0.0.tar.xz", ".tar.xz"], + ["Foo-1.0.0.tar.lz", ".tar.lz"], + ["Foo-1.0.0.tar.bz2", ".tar.bz2"], + ["Foo.v2.tar.bz2", ".tar.bz2"], + ["Foo-v1.0.0.tar.bz2", ".tar.bz2"], + ["Application.test.dmg", ".dmg"], + ["Program.1.0.0.beta1.exe", ".exe"], + ["application.dmg", ".dmg"], + ["latest.yml", ".yml"], + [".gitignore", ""], + [".config.yml", ".yml"], + ["code.h", ".h"], +] + +describe("getCompleteExtname", () => { + for (const [filename, expected] of tests) { + test(`get complete extname for ${filename}`, () => { + const extname = getCompleteExtname(filename) + + expect(extname).toBe(expected) + }) + } +})