From a138a86fb7b59098f5dac0c0a6b59c034eb9b222 Mon Sep 17 00:00:00 2001 From: Mike Maietta Date: Sun, 16 Jan 2022 10:42:43 -0800 Subject: [PATCH] chore: v23.0.0 alpha (#6556) * Adding INPUTxxx and OUTPUTxxx CHARSETS to makensis Fixes: #4898 #6232 #6259 * Adding additional details to error console logging * Breaking change: Removing Bintray support since it was sunset. Ref: https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter/ * fix: force strip path separators for backslashes on Windows * fix: Force authentication for local Mac Squirrel update server * Breaking Change: Fail-fast for signature verification failures. Adding `-LiteralPath` to update file for injected wildcards * Adding changeset and eslint * Fix error: `-OUTPUTCHARSET is disabled for non Win32 platforms.` * feat(mac): ElectronAsarIntegrity in electron@15 (#6511) * feat(mac): ElectronAsarIntegrity in electron@15 See: https://github.com/electron/electron/pull/30667 Fix: #6506 Fix: #6507 * fix(msi): MSI fails to install when deployed machine-wide via GPO (#6514) * fix(msi): MSI fails to install when deployed machine-wide via GPO * Disable advertised shortcuts, since MSIs with advertised Start Menu shortcuts that have a Shortcut Property fails to install when deployed machine-wide via GPO but works fine in all other contexts. This might be a bug in Windows or a misdiagnosis; see #6508 for more details. Closes #6508 * BREAKING CHANGE: Admins using advertisement must apply an MST to re-enable it. See #6508. * Don't set GitHub Releases draft title since it automatically pulls it from tag name. Fixes #3683 * feat(snap): add lzo to Snap compression options (also as new default) (#6201) * feat(msi): add fileAssociation support for MSI target (#6530) * fix(win): iconId sometimes containing invalid characters, and iconId config option being ignored. * fix(msi): change the fallback value for generated MSI Ids to a unique string for the product. * BREAKING CHANGE: remove MSI option `iconId` * fix: stabilizing tests by moving updater tests to its own node to explicitly segment env.___TOKEN integration tests from other standard unit tests * chore: synchronizing docs and schema plus prettier * Adding changset to set as alpha * Updating changeset documentation * feat(msi): support assisted installer for MSI target (#6550) * Add basic support for assisted installer, with UI to choose between per-user and per-machine. Supported config settings: runAfterFinish, perMachine, oneClick. Not supported: license (EULA), allowToChangeInstallationDirectory, etc. Also prevent oneClick's runAfterFinish from executing when installed silently. Co-authored-by: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com> Co-authored-by: Alex Plumley Co-authored-by: Mike Maietta Co-authored-by: Omer Akram Co-authored-by: Maximilian Federle --- .changeset/cool-ladybugs-matter.md | 5 + .changeset/giant-dryers-beg.md | 5 + .changeset/giant-dryers-copy.md | 6 + .changeset/healthy-peaches-deliver.md | 5 + .changeset/nervous-buckets-dance.md | 18 ++ .changeset/nervous-pandas-film.md | 5 + .changeset/pre.json | 20 ++ .changeset/serious-peas-help.md | 5 + .changeset/spicy-crabs-draw.md | 12 + .github/actions/pretest/action.yml | 42 ++++ .github/workflows/test.yaml | 85 +++---- docker/base/Dockerfile | 2 +- docs/api/electron-builder.md | 6 +- docs/configuration/snap.md | 3 + docs/generated/LinuxTargetSpecificOptions.md | 2 +- .../generated/PlatformSpecificBuildOptions.md | 2 +- package.json | 5 +- packages/app-builder-lib/scheme.json | 234 ++---------------- packages/app-builder-lib/src/asar/asar.ts | 29 ++- packages/app-builder-lib/src/asar/asarUtil.ts | 6 +- .../app-builder-lib/src/asar/integrity.ts | 104 ++++++-- .../src/electron/electronMac.ts | 2 +- .../src/options/FileAssociation.ts | 6 +- .../app-builder-lib/src/options/MsiOptions.ts | 5 - .../options/PlatformSpecificBuildOptions.ts | 3 +- .../src/options/SnapOptions.ts | 6 + .../src/options/linuxOptions.ts | 2 +- .../app-builder-lib/src/platformPackager.ts | 4 +- .../src/publish/BintrayPublisher.ts | 104 -------- .../src/publish/PublishManager.ts | 27 +- .../src/publish/s3/s3Publisher.ts | 2 +- .../src/publish/s3/spacesPublisher.ts | 2 +- .../src/publish/updateInfoBuilder.ts | 6 +- .../app-builder-lib/src/targets/MsiTarget.ts | 63 +++-- .../src/targets/nsis/NsisTarget.ts | 1 + packages/app-builder-lib/src/targets/snap.ts | 6 + .../templates/msi/WixUI_Assisted.wxs | 134 ---------- .../templates/msi/template.xml | 57 +++-- .../templates/snap/snapcraft.yaml | 1 + packages/builder-util-runtime/src/bintray.ts | 88 ------- packages/builder-util-runtime/src/index.ts | 1 - .../src/publishOptions.ts | 54 +--- packages/builder-util/package.json | 2 +- packages/builder-util/src/util.ts | 2 +- .../electron-publish/src/gitHubPublisher.ts | 3 +- packages/electron-publish/src/publisher.ts | 4 +- packages/electron-updater/src/AppUpdater.ts | 2 +- packages/electron-updater/src/MacUpdater.ts | 70 ++++-- .../electron-updater/src/providerFactory.ts | 5 - .../src/providers/BintrayProvider.ts | 54 ---- .../windowsExecutableCodeSignatureVerifier.ts | 11 +- pnpm-lock.yaml | 8 +- test/snapshots/globTest.js.snap | 2 +- test/snapshots/linux/snapTest.js.snap | 155 ++++++++++++ .../windows/assistedInstallerTest.js.snap | 130 ++++++++++ test/snapshots/windows/msiTest.js.snap | 12 + .../windows/oneClickInstallerTest.js.snap | 7 +- test/src/BuildTest.ts | 8 +- test/src/globTest.ts | 3 +- test/src/helpers/packTester.ts | 31 ++- test/src/linux/snapTest.ts | 41 +++ test/src/windows/oneClickInstallerTest.ts | 6 +- 62 files changed, 875 insertions(+), 856 deletions(-) create mode 100644 .changeset/cool-ladybugs-matter.md create mode 100644 .changeset/giant-dryers-beg.md create mode 100644 .changeset/giant-dryers-copy.md create mode 100644 .changeset/healthy-peaches-deliver.md create mode 100644 .changeset/nervous-buckets-dance.md create mode 100644 .changeset/nervous-pandas-film.md create mode 100644 .changeset/pre.json create mode 100644 .changeset/serious-peas-help.md create mode 100644 .changeset/spicy-crabs-draw.md create mode 100644 .github/actions/pretest/action.yml delete mode 100644 packages/app-builder-lib/src/publish/BintrayPublisher.ts delete mode 100644 packages/app-builder-lib/templates/msi/WixUI_Assisted.wxs delete mode 100644 packages/builder-util-runtime/src/bintray.ts delete mode 100644 packages/electron-updater/src/providers/BintrayProvider.ts create mode 100644 test/snapshots/windows/assistedInstallerTest.js.snap diff --git a/.changeset/cool-ladybugs-matter.md b/.changeset/cool-ladybugs-matter.md new file mode 100644 index 00000000000..f42457a9b47 --- /dev/null +++ b/.changeset/cool-ladybugs-matter.md @@ -0,0 +1,5 @@ +--- +"app-builder-lib": minor +--- + +feat(msi): support assisted installer for MSI target diff --git a/.changeset/giant-dryers-beg.md b/.changeset/giant-dryers-beg.md new file mode 100644 index 00000000000..517f30ba5e6 --- /dev/null +++ b/.changeset/giant-dryers-beg.md @@ -0,0 +1,5 @@ +--- +"app-builder-lib": minor +--- + +feat(msi): add fileAssociation support for MSI target diff --git a/.changeset/giant-dryers-copy.md b/.changeset/giant-dryers-copy.md new file mode 100644 index 00000000000..d599f14d0bc --- /dev/null +++ b/.changeset/giant-dryers-copy.md @@ -0,0 +1,6 @@ +--- +"app-builder-lib": patch +--- + +fix(win): iconId sometimes containing invalid characters, and iconId config option being ignored. +fix(msi): change the fallback value for generated MSI Ids to a unique string for the product. \ No newline at end of file diff --git a/.changeset/healthy-peaches-deliver.md b/.changeset/healthy-peaches-deliver.md new file mode 100644 index 00000000000..2dddfe5c267 --- /dev/null +++ b/.changeset/healthy-peaches-deliver.md @@ -0,0 +1,5 @@ +--- +"app-builder-lib": patch +--- + +fix(msi): MSI fails to install when deployed machine-wide via GPO diff --git a/.changeset/nervous-buckets-dance.md b/.changeset/nervous-buckets-dance.md new file mode 100644 index 00000000000..fcc7fa1ad10 --- /dev/null +++ b/.changeset/nervous-buckets-dance.md @@ -0,0 +1,18 @@ +--- +"app-builder-lib": major +"builder-util": major +"builder-util-runtime": major +"electron-publish": major +"electron-updater": major +--- + +Breaking changes +Removing Bintray support since it was sunset. Ref: https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter/ +Fail-fast for windows signature verification failures. Adding `-LiteralPath` to update file path to disregard injected wildcards +Force strip path separators for backslashes on Windows during update process +Force authentication for local mac squirrel update server + +Fixes: +fix(nsis): Adding --INPUTCHARSET to makensis. (#4898 #6232 #6259) + +Adding additional details to error console logging diff --git a/.changeset/nervous-pandas-film.md b/.changeset/nervous-pandas-film.md new file mode 100644 index 00000000000..1caf13eb2a1 --- /dev/null +++ b/.changeset/nervous-pandas-film.md @@ -0,0 +1,5 @@ +--- +"app-builder-lib": minor +--- + +feat(mac): ElectronAsarIntegrity in electron@15 diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 00000000000..1c76fbe24db --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,20 @@ +{ + "mode": "pre", + "tag": "alpha", + "initialVersions": { + "app-builder-lib": "22.14.13", + "builder-util": "22.14.13", + "builder-util-runtime": "8.9.2", + "dmg-builder": "22.14.13", + "electron-builder": "22.14.13", + "electron-builder-squirrel-windows": "22.14.13", + "electron-forge-maker-appimage": "22.14.13", + "electron-forge-maker-nsis": "22.14.13", + "electron-forge-maker-nsis-web": "22.14.13", + "electron-forge-maker-snap": "22.14.13", + "electron-publish": "22.14.13", + "electron-updater": "4.6.5", + "@electron-builder/test": "0.0.0" + }, + "changesets": [] +} diff --git a/.changeset/serious-peas-help.md b/.changeset/serious-peas-help.md new file mode 100644 index 00000000000..9dc1fc01af8 --- /dev/null +++ b/.changeset/serious-peas-help.md @@ -0,0 +1,5 @@ +--- +"app-builder-lib": major +--- + +BREAKING CHANGE: remove MSI option `iconId` diff --git a/.changeset/spicy-crabs-draw.md b/.changeset/spicy-crabs-draw.md new file mode 100644 index 00000000000..dd860ed685c --- /dev/null +++ b/.changeset/spicy-crabs-draw.md @@ -0,0 +1,12 @@ +--- +"app-builder-lib": minor +"builder-util": major +--- + +Default to LZO compression for snap packages. +This greatly improves cold startup performance (https://snapcraft.io/blog/why-lzo-was-chosen-as-the-new-compression-method). +LZO has already been adopted by most desktop-oriented snaps outside of the Electron realm. + +For the rare case where developers prefer a smaller file size (XZ) to vastly improved decompression performance (LZO), provided an option to override the default compression method. + +Consumers do not need to update their configuration unless they specifically want to stick to XZ compression. diff --git a/.github/actions/pretest/action.yml b/.github/actions/pretest/action.yml new file mode 100644 index 00000000000..76409820f1d --- /dev/null +++ b/.github/actions/pretest/action.yml @@ -0,0 +1,42 @@ +name: 'Setup Tests' +description: 'Set up pnpm, node w/ pnpm cache, install and audit deps, compile, and restore electron cache' +inputs: + cache-key: + description: 'The key to the electron cache' + required: true + cache-path: + description: 'The path to the electron cache' + required: true + +runs: + using: 'composite' + steps: + - uses: pnpm/action-setup@v2 + with: + version: 6.25.1 + + - uses: actions/setup-node@v2 + with: + node-version: '14' + cache: 'pnpm' + + - name: install dependencies + run: pnpm install --frozen-lockfile + shell: bash + + - name: Compile + run: pnpm compile + shell: bash + + - name: Cache Electron + uses: actions/cache@v2 + with: + path: ${{ inputs.cache-path }} + key: ${{ inputs.cache-key }} + +## Usage +# - name: Setup Tests +# uses: ./.github/actions/pretest +# with: +# cache-path: ~/Library/Caches/electron +# cache-key: v-11.0.0-macos-electron \ No newline at end of file diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index dd39e842c2f..b5fded08e8b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -26,8 +26,14 @@ jobs: with: fetch-depth: 0 - - name: Install deps and audit - uses: ./.github/actions/pnpm + - name: Setup Tests + uses: ./.github/actions/pretest + with: + cache-path: ~/.cache/electron + cache-key: v-11.0.0-linux-electron + + - name: Lint + run: pnpm pretest - name: Determine if Dockerfiles changed id: changed-files @@ -37,15 +43,6 @@ jobs: if: ${{ github.event.inputs.build-docker-locally == 'true' }} || contains(steps.changed-files.outputs.all_changed_files, 'Dockerfile') || contains(steps.changed-files.outputs.all_changed_files, 'docker') run: pnpm docker-images - - name: Compile and Lint - run: pnpm pretest - - - name: Cache ~/.cache/electron - uses: actions/cache@v2 - with: - path: ~/.cache/electron - key: v-11.0.0-electron - - name: Run tests in docker image run: pnpm test-linux env: @@ -54,60 +51,64 @@ jobs: test-mac: runs-on: macos-latest - strategy: - matrix: - testFiles: - - masTest,dmgTest,protonTest steps: - name: Checkout code repository uses: actions/checkout@v2 - - name: Install deps and audit - uses: ./.github/actions/pnpm + - name: Setup Tests + uses: ./.github/actions/pretest + with: + cache-path: ~/Library/Caches/electron + cache-key: v-11.0.0-macos-electron - - name: Compile - run: pnpm compile - - - name: Cache ~/Library/Caches/electron - uses: actions/cache@v2 + - name: Test + run: pnpm ci:test + env: + TEST_FILES: masTest,dmgTest,protonTest + FORCE_COLOR: 1 + + # Need to separate from other tests because logic is specific to when TOKEN env vars are set + test-updater: + runs-on: macos-latest + steps: + - name: Checkout code repository + uses: actions/checkout@v2 + + - name: Setup Tests + uses: ./.github/actions/pretest with: - path: ~/Library/Caches/electron - key: v-11.0.0-mac-electron + cache-path: ~/Library/Caches/electron + cache-key: v-11.0.0-update-electron - name: Test - run: node ./test/out/helpers/runTests.js + run: pnpm ci:test env: - TEST_FILES: ${{ matrix.testFiles }} + TEST_FILES: nsisUpdaterTest + KEYGEN_TOKEN: ${{ secrets.KEYGEN_TOKEN }} + # BITBUCKET_TOKEN: ${{ secrets.BITBUCKET_TOKEN }} + GH_TOKEN: ${{ secrets.GH_TOKEN }} FORCE_COLOR: 1 test-windows: - runs-on: windows-2019 + runs-on: windows-latest strategy: matrix: testFiles: - - installerTest,appxTest,msiTest,portableTest,assistedInstallerTest - - oneClickInstallerTest,nsisUpdaterTest,winCodeSignTest,winPackagerTest,webInstallerTest + - installerTest,appxTest,msiTest,portableTest,assistedInstallerTest,protonTest + - oneClickInstallerTest,winCodeSignTest,winPackagerTest,webInstallerTest steps: - name: Checkout code repository uses: actions/checkout@v2 - - name: Install deps and audit - uses: ./.github/actions/pnpm - - - name: Compile - run: pnpm compile - - - name: Cache ~\AppData\Local\Cache\electron - uses: actions/cache@v2 + - name: Setup Tests + uses: ./.github/actions/pretest with: - path: ~\AppData\Local\Cache\electron - key: v-11.0.0-windows-electron + cache-path: ~\AppData\Local\Cache\electron + cache-key: v-11.0.0-windows-electron - name: Test - run: node ./test/out/helpers/runTests.js + run: pnpm ci:test env: CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} - KEYGEN_TOKEN: ${{ secrets.KEYGEN_TOKEN }} - GH_TOKEN: ${{ secrets.GH_TOKEN }} TEST_FILES: ${{ matrix.testFiles }} FORCE_COLOR: 1 diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index 8e0b20b00a6..4f84550fae5 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -13,7 +13,7 @@ RUN apt-get -qq update && apt-get -qq dist-upgrade && \ apt-get -qq install --no-install-recommends \ qtbase5-dev build-essential autoconf libssl-dev gcc-multilib g++-multilib \ lzip rpm python libcurl4 git git-lfs ssh unzip libarchive-tools \ - libxtst6 libsecret-1-dev libopenjp2-tools \ + libxtst6 libsecret-1-dev libopenjp2-tools liblzo2-2 \ && \ # git-lfs git lfs install && \ diff --git a/docs/api/electron-builder.md b/docs/api/electron-builder.md index 0128cf42c2a..eccf30c32ae 100644 --- a/docs/api/electron-builder.md +++ b/docs/api/electron-builder.md @@ -1364,7 +1364,7 @@ return path.join(target.outDir, __${target.name}-${getArtifactArchName(arc

Kind: class of electron-publish
Properties

    -
  • providerName String
  • +
  • providerName “github” | “s3” | “spaces” | “generic” | “custom” | “snapStore” | “keygen” | “bitbucket”

Methods

    @@ -1709,7 +1709,7 @@ return path.join(target.outDir, __${target.name}-${getArtifactArchName(arc options -PublishConfiguration | String | GithubOptions | S3Options | SpacesOptions | GenericServerOptions | BintrayOptions | module:builder-util-runtime/out/publishOptions.CustomPublishOptions | module:builder-util-runtime/out/publishOptions.KeygenOptions | SnapStoreOptions | module:builder-util-runtime/out/publishOptions.BitbucketOptions | String +PublishConfiguration | String | GithubOptions | S3Options | SpacesOptions | GenericServerOptions | module:builder-util-runtime/out/publishOptions.CustomPublishOptions | module:builder-util-runtime/out/publishOptions.KeygenOptions | SnapStoreOptions | module:builder-util-runtime/out/publishOptions.BitbucketOptions | String If you want to override configuration in the app-update.yml. @@ -1834,7 +1834,7 @@ This is different from the normal quit event sequence.

    options -PublishConfiguration | String | GithubOptions | S3Options | SpacesOptions | GenericServerOptions | BintrayOptions | module:builder-util-runtime/out/publishOptions.CustomPublishOptions | module:builder-util-runtime/out/publishOptions.KeygenOptions | SnapStoreOptions | module:builder-util-runtime/out/publishOptions.BitbucketOptions | String +PublishConfiguration | String | GithubOptions | S3Options | SpacesOptions | GenericServerOptions | module:builder-util-runtime/out/publishOptions.CustomPublishOptions | module:builder-util-runtime/out/publishOptions.KeygenOptions | SnapStoreOptions | module:builder-util-runtime/out/publishOptions.BitbucketOptions | String If you want to override configuration in the app-update.yml. diff --git a/docs/configuration/snap.md b/docs/configuration/snap.md index 07dfca59b58..cb194be4a2b 100644 --- a/docs/configuration/snap.md +++ b/docs/configuration/snap.md @@ -57,6 +57,9 @@ The top-level [snap](configuration.md#Configuration-snap) key contains set of op
  • title String | “undefined” - An optional title for the snap, may contain uppercase letters and spaces. Defaults to productName. See snap format documentation.

  • +
  • +

    compression = lzo “xz” | “lzo” | “undefined” - Sets the compression type for the snap. Can be xz or lzo. Defaults to lzo when not specified.

    +

Inherited from CommonLinuxOptions:

    diff --git a/docs/generated/LinuxTargetSpecificOptions.md b/docs/generated/LinuxTargetSpecificOptions.md index c0050afc559..b600edfbb18 100644 --- a/docs/generated/LinuxTargetSpecificOptions.md +++ b/docs/generated/LinuxTargetSpecificOptions.md @@ -3,7 +3,7 @@

    depends Array<String> | “undefined” - Package dependencies.

  • -

    compression = xz “gz” | “bzip2” | “xz” | “undefined” - The compression type.

    +

    compression = xz “gz” | “bzip2” | “xz” | “lzo” | “undefined” - The compression type.

  • icon String

    diff --git a/docs/generated/PlatformSpecificBuildOptions.md b/docs/generated/PlatformSpecificBuildOptions.md index 3203d69b195..02c81210cba 100644 --- a/docs/generated/PlatformSpecificBuildOptions.md +++ b/docs/generated/PlatformSpecificBuildOptions.md @@ -46,7 +46,7 @@
  • icon String | “undefined” - The path to icon (.icns for MacOS and .ico for Windows), relative to build (build resources directory). Defaults to ${firstExt}.icns/${firstExt}.ico (if several extensions specified, first is used) or to application icon.

    -

    Not supported on Linux, file issue if need (default icon will be x-office-document).

    +

    Not supported on Linux, file issue if need (default icon will be x-office-document). Not supported on MSI.

  • role = Editor String - macOS-only The app’s role with respect to the type. The value can be Editor, Viewer, Shell, or None. Corresponds to CFBundleTypeRole.

    diff --git a/package.json b/package.json index 7f70444e0a2..7b9abf451d3 100644 --- a/package.json +++ b/package.json @@ -14,11 +14,11 @@ "lint": "eslint packages --ext .ts", "lint-staged": "lint-staged", "lint-deps": "node ./test/out/helpers/checkDeps.js", - "pretest": "pnpm compile && pnpm lint && pnpm lint-deps", + "pretest": "pnpm lint && pnpm lint-deps", "prettier": "prettier 'packages/**/*.{ts, js}' 'test/src/**/*.ts' --write", "///": "Please see https://github.com/electron-userland/electron-builder/blob/master/CONTRIBUTING.md#run-test-using-cli how to run particular test instead full (and very slow) run", "test": "node ./test/out/helpers/runTests.js skipArtifactPublisher", - "test-all": "pnpm pretest && node ./test/out/helpers/runTests.js", + "test-all": "pnpm compile && pnpm pretest && pnpm ci:test", "test-linux": "docker run --rm -e UPDATE_SNAPSHOT=${UPDATE_SNAPSHOT:-false} -e TEST_FILES=\"${TEST_FILES:-HoistedNodeModuleTest}\" -v $(pwd):/project -v $(pwd)-node-modules:/project/node_modules -v $HOME/Library/Caches/electron:/root/.cache/electron -v $HOME/Library/Caches/electron-builder:/root/.cache/electron-builder electronuserland/builder:wine-mono /bin/bash -c \"pnpm install && node ./test/out/helpers/runTests.js\"", "test-update": "UPDATE_SNAPSHOT=true pnpm test-all", "docker-images": "docker/build.sh", @@ -26,6 +26,7 @@ "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md", "generate-changeset": "pnpm changeset", "generate-docs": "pnpm compile && pnpm jsdoc && pnpm jsdoc2md2html", + "ci:test": "node ./test/out/helpers/runTests.js", "ci:version": "pnpm changelog && changeset version && node scripts/update-package-version-export.js && pnpm run generate-docs && git add .", "ci:publish": "pnpm compile && pnpm publish -r && changeset tag", "schema": "typescript-json-schema packages/app-builder-lib/tsconfig-scheme.json Configuration --out packages/app-builder-lib/scheme.json --noExtraProps --useTypeOfKeyword --strictNullChecks --required && node ./scripts/fix-schema.js", diff --git a/packages/app-builder-lib/scheme.json b/packages/app-builder-lib/scheme.json index e0698864005..0e6dc99cdfb 100644 --- a/packages/app-builder-lib/scheme.json +++ b/packages/app-builder-lib/scheme.json @@ -78,9 +78,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -108,9 +105,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -240,9 +234,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -270,9 +261,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -330,11 +318,6 @@ "AsarOptions": { "additionalProperties": false, "properties": { - "externalAllowed": { - "default": false, - "description": "Allows external asar files.", - "type": "boolean" - }, "ordering": { "type": [ "null", @@ -349,101 +332,6 @@ }, "type": "object" }, - "BintrayOptions": { - "additionalProperties": false, - "description": "[Bintray](https://bintray.com/) options. Requires an API key. An API key can be obtained from the user [profile](https://bintray.com/profile/edit) page (\"Edit Your Profile\" -> API Key).\nDefine `BT_TOKEN` environment variable.", - "properties": { - "component": { - "description": "The Bintray component (Debian only).", - "type": [ - "null", - "string" - ] - }, - "distribution": { - "default": "stable", - "description": "The Bintray distribution (Debian only).", - "type": [ - "null", - "string" - ] - }, - "owner": { - "description": "The owner.", - "type": [ - "null", - "string" - ] - }, - "package": { - "description": "The Bintray package name.", - "type": [ - "null", - "string" - ] - }, - "provider": { - "description": "The provider. Must be `bintray`.", - "enum": [ - "bintray" - ], - "type": "string" - }, - "publishAutoUpdate": { - "default": true, - "description": "Whether to publish auto update info files.\n\nAuto update relies only on the first provider in the list (you can specify several publishers).\nThus, probably, there`s no need to upload the metadata files for the other configured providers. But by default will be uploaded.", - "type": "boolean" - }, - "publisherName": { - "anyOf": [ - { - "items": { - "type": "string" - }, - "type": "array" - }, - { - "type": "null" - } - ] - }, - "repo": { - "default": "generic", - "description": "The Bintray repository name.", - "type": [ - "null", - "string" - ] - }, - "requestHeaders": { - "$ref": "#/definitions/OutgoingHttpHeaders", - "description": "Any custom request headers" - }, - "token": { - "type": [ - "null", - "string" - ] - }, - "updaterCacheDirName": { - "type": [ - "null", - "string" - ] - }, - "user": { - "description": "The Bintray user account. Used in cases where the owner is an organization.", - "type": [ - "null", - "string" - ] - } - }, - "required": [ - "provider" - ], - "type": "object" - }, "BitbucketOptions": { "additionalProperties": false, "description": "Bitbucket options.\nhttps://bitbucket.org/\nDefine `BITBUCKET_TOKEN` environment variable.\n\nFor converting an app password to a usable token, you can utilize this\n```typescript\nconvertAppPassword(owner: string, token: string) {\nconst base64encodedData = Buffer.from(`${owner}:${token.trim()}`).toString(\"base64\")\nreturn `Basic ${base64encodedData}`\n}\n```", @@ -634,6 +522,7 @@ "enum": [ "bzip2", "gz", + "lzo", "xz" ], "type": "string" @@ -755,9 +644,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -785,9 +671,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -951,9 +834,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -981,9 +861,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -1127,7 +1004,7 @@ }, "FileAssociation": { "additionalProperties": false, - "description": "File associations.\n\nmacOS (corresponds to [CFBundleDocumentTypes](https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/20001431-101685)) and NSIS only.\n\nOn Windows works only if [nsis.perMachine](https://electron.build/configuration/configuration#NsisOptions-perMachine) is set to `true`.", + "description": "File associations.\n\nmacOS (corresponds to [CFBundleDocumentTypes](https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/20001431-101685)), NSIS, and MSI only.\n\nOn Windows (NSIS) works only if [nsis.perMachine](https://electron.build/configuration/configuration#NsisOptions-perMachine) is set to `true`.", "properties": { "description": { "description": "*windows-only.* The description.", @@ -1151,7 +1028,7 @@ "description": "The extension (minus the leading period). e.g. `png`." }, "icon": { - "description": "The path to icon (`.icns` for MacOS and `.ico` for Windows), relative to `build` (build resources directory). Defaults to `${firstExt}.icns`/`${firstExt}.ico` (if several extensions specified, first is used) or to application icon.\n\nNot supported on Linux, file issue if need (default icon will be `x-office-document`).", + "description": "The path to icon (`.icns` for MacOS and `.ico` for Windows), relative to `build` (build resources directory). Defaults to `${firstExt}.icns`/`${firstExt}.ico` (if several extensions specified, first is used) or to application icon.\n\nNot supported on Linux, file issue if need (default icon will be `x-office-document`). Not supported on MSI.", "type": [ "null", "string" @@ -1336,9 +1213,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -1366,9 +1240,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -2002,9 +1873,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -2032,9 +1900,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -2146,6 +2011,7 @@ "enum": [ "bzip2", "gz", + "lzo", "xz" ], "type": "string" @@ -2260,9 +2126,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -2290,9 +2153,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -2760,9 +2620,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -2790,9 +2647,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -3371,9 +3225,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -3401,9 +3252,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -3625,10 +3473,6 @@ "description": "Whether to create start menu shortcut.", "type": "boolean" }, - "iconId": { - "description": "The [shortcut iconId](https://wixtoolset.org/documentation/manual/v4/reference/wxs/shortcut/). Optional, by default generated using app file name.", - "type": "string" - }, "menuCategory": { "default": false, "description": "Whether to create submenu for start menu shortcut and program files directory. If `true`, company name will be used. Or string value.", @@ -3661,9 +3505,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -3691,9 +3532,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -3955,9 +3793,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -3985,9 +3820,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -4285,9 +4117,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -4315,9 +4144,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -4612,9 +4438,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -4642,9 +4465,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -4752,9 +4572,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -4782,9 +4599,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -5139,6 +4953,22 @@ "string" ] }, + "compression": { + "anyOf": [ + { + "enum": [ + "lzo", + "xz" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": "lzo", + "description": "Sets the compression type for the snap. Can be xz or lzo. Defaults to lzo when not specified." + }, "confinement": { "anyOf": [ { @@ -5281,9 +5111,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -5311,9 +5138,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -5609,9 +5433,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -5639,9 +5460,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -6012,9 +5830,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -6042,9 +5857,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -6917,9 +6729,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, @@ -6947,9 +6756,6 @@ { "$ref": "#/definitions/GenericServerOptions" }, - { - "$ref": "#/definitions/BintrayOptions" - }, { "$ref": "#/definitions/CustomPublishOptions" }, diff --git a/packages/app-builder-lib/src/asar/asar.ts b/packages/app-builder-lib/src/asar/asar.ts index a52d38ced36..70724cb35a8 100644 --- a/packages/app-builder-lib/src/asar/asar.ts +++ b/packages/app-builder-lib/src/asar/asar.ts @@ -2,6 +2,20 @@ import { createFromBuffer } from "chromium-pickle-js" import { close, open, read, readFile, Stats } from "fs-extra" import * as path from "path" +/** @internal */ +export interface ReadAsarHeader { + readonly header: string + readonly size: number +} + +/** @internal */ +export interface NodeIntegrity { + algorithm: "SHA256" + hash: string + blockSize: number + blocks: Array +} + /** @internal */ export class Node { // we don't use Map because later it will be stringified @@ -16,6 +30,8 @@ export class Node { executable?: boolean link?: string + + integrity?: NodeIntegrity } /** @internal */ @@ -66,13 +82,16 @@ export class AsarFilesystem { return result } - addFileNode(file: string, dirNode: Node, size: number, unpacked: boolean, stat: Stats): Node { + addFileNode(file: string, dirNode: Node, size: number, unpacked: boolean, stat: Stats, integrity?: NodeIntegrity): Node { if (size > 4294967295) { throw new Error(`${file}: file size cannot be larger than 4.2GB`) } const node = new Node() node.size = size + if (integrity) { + node.integrity = integrity + } if (unpacked) { node.unpacked = true } else { @@ -114,7 +133,7 @@ export class AsarFilesystem { } } -export async function readAsar(archive: string): Promise { +export async function readAsarHeader(archive: string): Promise { const fd = await open(archive, "r") let size: number let headerBuf @@ -135,7 +154,11 @@ export async function readAsar(archive: string): Promise { } const headerPickle = createFromBuffer(headerBuf) - const header = headerPickle.createIterator().readString() + return { header: headerPickle.createIterator().readString(), size } +} + +export async function readAsar(archive: string): Promise { + const { header, size } = await readAsarHeader(archive) return new AsarFilesystem(archive, JSON.parse(header), size) } diff --git a/packages/app-builder-lib/src/asar/asarUtil.ts b/packages/app-builder-lib/src/asar/asarUtil.ts index 7f698b4e4b8..c3102ecd7c3 100644 --- a/packages/app-builder-lib/src/asar/asarUtil.ts +++ b/packages/app-builder-lib/src/asar/asarUtil.ts @@ -8,6 +8,7 @@ import { Packager } from "../packager" import { PlatformPackager } from "../platformPackager" import { getDestinationPath, ResolvedFileSet } from "../util/appFileCopier" import { AsarFilesystem, Node } from "./asar" +import { hashFile, hashFileContents } from "./integrity" import { detectUnpackedDirs } from "./unpackDetector" // eslint-disable-next-line @typescript-eslint/no-var-requires @@ -112,9 +113,10 @@ export class AsarPackager { } const dirNode = currentDirNode! - const newData = transformedFiles == null ? null : transformedFiles.get(i) + const newData = transformedFiles == null ? undefined : transformedFiles.get(i) const isUnpacked = dirNode.unpacked || (this.unpackPattern != null && this.unpackPattern(file, stat)) - this.fs.addFileNode(file, dirNode, newData == null ? stat.size : Buffer.byteLength(newData), isUnpacked, stat) + const integrity = newData === undefined ? await hashFile(file) : hashFileContents(newData) + this.fs.addFileNode(file, dirNode, newData == undefined ? stat.size : Buffer.byteLength(newData), isUnpacked, stat, integrity) if (isUnpacked) { if (!dirNode.unpacked && !dirToCreateForUnpackedFiles.has(fileParent)) { dirToCreateForUnpackedFiles.add(fileParent) diff --git a/packages/app-builder-lib/src/asar/integrity.ts b/packages/app-builder-lib/src/asar/integrity.ts index 53344799162..67e78b4d137 100644 --- a/packages/app-builder-lib/src/asar/integrity.ts +++ b/packages/app-builder-lib/src/asar/integrity.ts @@ -3,43 +3,107 @@ import { createHash } from "crypto" import { createReadStream } from "fs" import { readdir } from "fs/promises" import * as path from "path" +import { readAsarHeader, NodeIntegrity } from "./asar" export interface AsarIntegrityOptions { - /** - * Allows external asar files. - * - * @default false - */ - readonly externalAllowed?: boolean + readonly resourcesPath: string + readonly resourcesRelativePath: string } -export interface AsarIntegrity extends AsarIntegrityOptions { - checksums: { [key: string]: string } +export interface HeaderHash { + algorithm: "SHA256" + hash: string } -export async function computeData(resourcesPath: string, options?: AsarIntegrityOptions | null): Promise { +export interface AsarIntegrity { + [key: string]: HeaderHash +} + +export async function computeData({ resourcesPath, resourcesRelativePath }: AsarIntegrityOptions): Promise { // sort to produce constant result const names = (await readdir(resourcesPath)).filter(it => it.endsWith(".asar")).sort() - const checksums = await BluebirdPromise.map(names, it => hashFile(path.join(resourcesPath, it))) + const checksums = await BluebirdPromise.map(names, it => hashHeader(path.join(resourcesPath, it))) - const result: { [key: string]: string } = {} + const result: AsarIntegrity = {} for (let i = 0; i < names.length; i++) { - result[names[i]] = checksums[i] + result[path.join(resourcesRelativePath, names[i])] = checksums[i] + } + return result +} + +async function hashHeader(file: string): Promise { + const hash = createHash("sha256") + const { header } = await readAsarHeader(file) + hash.update(header) + return { + algorithm: "SHA256", + hash: hash.digest("hex"), } - return { checksums: result, ...options } } -function hashFile(file: string, algorithm = "sha512", encoding: "hex" | "base64" | "latin1" = "base64") { - return new Promise((resolve, reject) => { - const hash = createHash(algorithm) - hash.on("error", reject).setEncoding(encoding) +export function hashFile(file: string, blockSize = 4 * 1024 * 1024): Promise { + return new Promise((resolve, reject) => { + const hash = createHash("sha256") + + const blocks = new Array() + + let blockBytes = 0 + let blockHash = createHash("sha256") + + function updateBlockHash(chunk: Buffer) { + let off = 0 + while (off < chunk.length) { + const toHash = Math.min(blockSize - blockBytes, chunk.length - off) + blockHash.update(chunk.slice(off, off + toHash)) + off += toHash + blockBytes += toHash + + if (blockBytes === blockSize) { + blocks.push(blockHash.digest("hex")) + blockHash = createHash("sha256") + blockBytes = 0 + } + } + } createReadStream(file) + .on("data", it => { + // Note that `it` is a Buffer anyway so this cast is a no-op + updateBlockHash(Buffer.from(it)) + hash.update(it) + }) .on("error", reject) .on("end", () => { - hash.end() - resolve(hash.read() as string) + if (blockBytes !== 0) { + blocks.push(blockHash.digest("hex")) + } + resolve({ + algorithm: "SHA256", + hash: hash.digest("hex"), + blockSize, + blocks, + }) }) - .pipe(hash, { end: false }) }) } + +export function hashFileContents(contents: Buffer | string, blockSize = 4 * 1024 * 1024): NodeIntegrity { + const buffer = Buffer.from(contents) + const hash = createHash("sha256") + hash.update(buffer) + + const blocks = new Array() + + for (let off = 0; off < buffer.length; off += blockSize) { + const blockHash = createHash("sha256") + blockHash.update(buffer.slice(off, off + blockSize)) + blocks.push(blockHash.digest("hex")) + } + + return { + algorithm: "SHA256", + hash: hash.digest("hex"), + blockSize, + blocks, + } +} diff --git a/packages/app-builder-lib/src/electron/electronMac.ts b/packages/app-builder-lib/src/electron/electronMac.ts index ead3aa6b3c2..04f2023826f 100644 --- a/packages/app-builder-lib/src/electron/electronMac.ts +++ b/packages/app-builder-lib/src/electron/electronMac.ts @@ -214,7 +214,7 @@ export async function createMacApp(packager: MacPackager, appOutDir: string, asa } if (asarIntegrity != null) { - appPlist.AsarIntegrity = JSON.stringify(asarIntegrity) + appPlist.ElectronAsarIntegrity = asarIntegrity } const plistDataToWrite: any = { diff --git a/packages/app-builder-lib/src/options/FileAssociation.ts b/packages/app-builder-lib/src/options/FileAssociation.ts index 82eb6f37532..4be4e9c62be 100644 --- a/packages/app-builder-lib/src/options/FileAssociation.ts +++ b/packages/app-builder-lib/src/options/FileAssociation.ts @@ -1,9 +1,9 @@ /** * File associations. * - * macOS (corresponds to [CFBundleDocumentTypes](https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/20001431-101685)) and NSIS only. + * macOS (corresponds to [CFBundleDocumentTypes](https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/20001431-101685)), NSIS, and MSI only. * - * On Windows works only if [nsis.perMachine](https://electron.build/configuration/configuration#NsisOptions-perMachine) is set to `true`. + * On Windows (NSIS) works only if [nsis.perMachine](https://electron.build/configuration/configuration#NsisOptions-perMachine) is set to `true`. */ export interface FileAssociation { /** @@ -29,7 +29,7 @@ export interface FileAssociation { /** * The path to icon (`.icns` for MacOS and `.ico` for Windows), relative to `build` (build resources directory). Defaults to `${firstExt}.icns`/`${firstExt}.ico` (if several extensions specified, first is used) or to application icon. * - * Not supported on Linux, file issue if need (default icon will be `x-office-document`). + * Not supported on Linux, file issue if need (default icon will be `x-office-document`). Not supported on MSI. */ readonly icon?: string | null diff --git a/packages/app-builder-lib/src/options/MsiOptions.ts b/packages/app-builder-lib/src/options/MsiOptions.ts index 4132ebdb6c1..28d6da3d9d5 100644 --- a/packages/app-builder-lib/src/options/MsiOptions.ts +++ b/packages/app-builder-lib/src/options/MsiOptions.ts @@ -23,9 +23,4 @@ export interface MsiOptions extends CommonWindowsInstallerConfiguration, TargetS * Any additional arguments to be passed to the WiX installer compiler, such as `["-ext", "WixUtilExtension"]` */ readonly additionalWixArgs?: Array | null - - /** - * The [shortcut iconId](https://wixtoolset.org/documentation/manual/v4/reference/wxs/shortcut/). Optional, by default generated using app file name. - */ - readonly iconId?: string } diff --git a/packages/app-builder-lib/src/options/PlatformSpecificBuildOptions.ts b/packages/app-builder-lib/src/options/PlatformSpecificBuildOptions.ts index 1871786c99a..9a95b2a3ffc 100644 --- a/packages/app-builder-lib/src/options/PlatformSpecificBuildOptions.ts +++ b/packages/app-builder-lib/src/options/PlatformSpecificBuildOptions.ts @@ -1,4 +1,3 @@ -import { AsarIntegrityOptions } from "../asar/integrity" import { CompressionLevel, Publish, TargetConfiguration, TargetSpecificOptions } from "../core" import { FileAssociation } from "./FileAssociation" @@ -17,7 +16,7 @@ export interface FileSet { filter?: Array | string } -export interface AsarOptions extends AsarIntegrityOptions { +export interface AsarOptions { /** * Whether to automatically unpack executables files. * @default true diff --git a/packages/app-builder-lib/src/options/SnapOptions.ts b/packages/app-builder-lib/src/options/SnapOptions.ts index 66b217b6c36..e81f0b59308 100644 --- a/packages/app-builder-lib/src/options/SnapOptions.ts +++ b/packages/app-builder-lib/src/options/SnapOptions.ts @@ -125,6 +125,12 @@ export interface SnapOptions extends CommonLinuxOptions, TargetSpecificOptions { * An optional title for the snap, may contain uppercase letters and spaces. Defaults to `productName`. See [snap format documentation](https://snapcraft.io/docs/snap-format). */ readonly title?: string | null + + /** + * Sets the compression type for the snap. Can be xz or lzo. Defaults to lzo when not specified. + * @default lzo + */ + readonly compression?: "xz" | "lzo" | null } export interface PlugDescriptor { diff --git a/packages/app-builder-lib/src/options/linuxOptions.ts b/packages/app-builder-lib/src/options/linuxOptions.ts index 8b84e140c70..8bb1214ad1d 100644 --- a/packages/app-builder-lib/src/options/linuxOptions.ts +++ b/packages/app-builder-lib/src/options/linuxOptions.ts @@ -77,7 +77,7 @@ export interface LinuxTargetSpecificOptions extends CommonLinuxOptions, TargetSp * The compression type. * @default xz */ - readonly compression?: "gz" | "bzip2" | "xz" | null + readonly compression?: "gz" | "bzip2" | "xz" | "lzo" | null readonly icon?: string diff --git a/packages/app-builder-lib/src/platformPackager.ts b/packages/app-builder-lib/src/platformPackager.ts index 911397bed4d..ab4ebb7c5f6 100644 --- a/packages/app-builder-lib/src/platformPackager.ts +++ b/packages/app-builder-lib/src/platformPackager.ts @@ -280,10 +280,12 @@ export abstract class PlatformPackager } if (framework.beforeCopyExtraFiles != null) { + const resourcesRelativePath = this.platform === Platform.MAC ? "Resources" : isElectronBased(framework) ? "resources" : "" + await framework.beforeCopyExtraFiles({ packager: this, appOutDir, - asarIntegrity: asarOptions == null || disableAsarIntegrity ? null : await computeData(resourcesPath, asarOptions.externalAllowed ? { externalAllowed: true } : null), + asarIntegrity: asarOptions == null || disableAsarIntegrity ? null : await computeData({ resourcesPath, resourcesRelativePath }), platformName, }) } diff --git a/packages/app-builder-lib/src/publish/BintrayPublisher.ts b/packages/app-builder-lib/src/publish/BintrayPublisher.ts deleted file mode 100644 index 2a97989ba15..00000000000 --- a/packages/app-builder-lib/src/publish/BintrayPublisher.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { Arch, InvalidConfigurationError, isEmptyOrSpaces, isTokenCharValid, log, toLinuxArchString } from "builder-util" -import { BintrayOptions, configureRequestOptions, HttpError, HttpExecutor } from "builder-util-runtime" -import { BintrayClient, Version } from "builder-util-runtime/out/bintray" -import { httpExecutor } from "builder-util/out/nodeHttpExecutor" -import { ClientRequest, RequestOptions } from "http" -import { Lazy } from "lazy-val" -import { HttpPublisher, PublishContext, PublishOptions } from "electron-publish" - -export class BintrayPublisher extends HttpPublisher { - private readonly _versionPromise = new Lazy(() => this.init()) - - private readonly client: BintrayClient - - readonly providerName = "Bintray" - - constructor(context: PublishContext, info: BintrayOptions, private readonly version: string, private readonly options: PublishOptions = {}) { - super(context) - - let token = info.token - if (isEmptyOrSpaces(token)) { - token = process.env.BT_TOKEN - if (isEmptyOrSpaces(token)) { - throw new InvalidConfigurationError( - `Bintray token is not set, neither programmatically, nor using env "BT_TOKEN" (see https://www.electron.build/configuration/publish#bintrayoptions)` - ) - } - - token = token.trim() - - if (!isTokenCharValid(token)) { - throw new InvalidConfigurationError(`Bintray token (${JSON.stringify(token)}) contains invalid characters, please check env "BT_TOKEN"`) - } - } - - this.client = new BintrayClient(info, httpExecutor, this.context.cancellationToken, token) - } - - private async init(): Promise { - try { - return await this.client.getVersion(this.version) - } catch (e) { - if (e instanceof HttpError && e.statusCode === 404) { - if (this.options.publish !== "onTagOrDraft") { - log.info({ version: this.version }, "version doesn't exist, creating one") - return await this.client.createVersion(this.version) - } else { - log.warn({ reason: "version doesn't exist", version: this.version }, "skipped publishing") - } - } - - throw e - } - } - - protected async doUpload(fileName: string, arch: Arch, dataLength: number, requestProcessor: (request: ClientRequest, reject: (error: Error) => void) => void) { - const version = await this._versionPromise.value - if (version == null) { - log.warn({ file: fileName, reason: "version doesn't exist and is not created", version: this.version }, "skipped publishing") - return - } - - const options: RequestOptions = { - hostname: "api.bintray.com", - path: `/content/${this.client.owner}/${this.client.repo}/${this.client.packageName}/${encodeURI(`${version.name}/${fileName}`)}`, - method: "PUT", - headers: { - "Content-Length": dataLength, - "X-Bintray-Override": "1", - "X-Bintray-Publish": "1", - "X-Bintray-Debian-Architecture": toLinuxArchString(arch, "deb"), - }, - } - - if (this.client.distribution != null) { - options.headers!["X-Bintray-Debian-Distribution"] = this.client.distribution - } - - if (this.client.component != null) { - options.headers!["X-Bintray-Debian-Component"] = this.client.component - } - - return HttpExecutor.retryOnServerError(() => { - return httpExecutor.doApiRequest(configureRequestOptions(options, this.client.auth), this.context.cancellationToken, requestProcessor) - }) - } - - //noinspection JSUnusedGlobalSymbols - async deleteRelease(isForce = false): Promise { - if (!isForce && !this._versionPromise.hasValue) { - return - } - - const version = await this._versionPromise.value - if (version != null) { - await this.client.deleteVersion(version.name) - } - } - - toString() { - return `Bintray (user: ${this.client.user || this.client.owner}, owner: ${this.client.owner}, package: ${this.client.packageName}, repository: ${this.client.repo}, version: ${ - this.version - })` - } -} diff --git a/packages/app-builder-lib/src/publish/PublishManager.ts b/packages/app-builder-lib/src/publish/PublishManager.ts index 7a95f4a99b5..20a59a7b543 100644 --- a/packages/app-builder-lib/src/publish/PublishManager.ts +++ b/packages/app-builder-lib/src/publish/PublishManager.ts @@ -1,7 +1,6 @@ import BluebirdPromise from "bluebird-lst" import { Arch, asArray, AsyncTaskManager, InvalidConfigurationError, isEmptyOrSpaces, isPullRequest, log, safeStringifyJson, serializeToYaml } from "builder-util" import { - BintrayOptions, CancellationToken, GenericServerOptions, getS3LikeProviderBaseUrl, @@ -11,10 +10,10 @@ import { SnapStoreOptions, PublishConfiguration, PublishProvider, + BitbucketOptions, } from "builder-util-runtime" import _debug from "debug" import { getCiTag, PublishContext, Publisher, PublishOptions, UploadTask } from "electron-publish" -import { BintrayPublisher } from "./BintrayPublisher" import { GitHubPublisher } from "electron-publish/out/gitHubPublisher" import { MultiProgress } from "electron-publish/out/multiProgress" import S3Publisher from "./s3/s3Publisher" @@ -163,7 +162,7 @@ export class PublishManager implements PublishContext { } const providerName = publisher.providerName - if (this.publishOptions.publish === "onTagOrDraft" && getCiTag() == null && !(providerName === "GitHub" || providerName === "Bintray")) { + if (this.publishOptions.publish === "onTagOrDraft" && getCiTag() == null && providerName !== "bitbucket" && providerName !== "github") { log.info({ file: event.file, reason: "current build is not for a git tag", publishPolicy: "onTagOrDraft" }, `not published to ${providerName}`) return } @@ -298,9 +297,6 @@ export function createPublisher(context: PublishContext, version: string, publis case "github": return new GitHubPublisher(context, publishConfig as GithubOptions, version, options) - case "bintray": - return new BintrayPublisher(context, publishConfig as BintrayOptions, version, options) - case "keygen": return new KeygenPublisher(context, publishConfig as KeygenOptions, version) @@ -322,9 +318,6 @@ function requireProviderClass(provider: string, packager: Packager): any | null case "github": return GitHubPublisher - case "bintray": - return BintrayPublisher - case "generic": return null @@ -430,12 +423,14 @@ async function resolvePublishConfigurations( let serviceName: PublishProvider | null = null if (!isEmptyOrSpaces(process.env.GH_TOKEN) || !isEmptyOrSpaces(process.env.GITHUB_TOKEN)) { serviceName = "github" - } else if (!isEmptyOrSpaces(process.env.BT_TOKEN)) { - serviceName = "bintray" } else if (!isEmptyOrSpaces(process.env.KEYGEN_TOKEN)) { serviceName = "keygen" } else if (!isEmptyOrSpaces(process.env.BITBUCKET_TOKEN)) { serviceName = "bitbucket" + } else if (!isEmptyOrSpaces(process.env.BT_TOKEN)) { + throw new Error( + "Bintray has been sunset and is no longer supported by electron-builder. Ref: https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter/" + ) } if (serviceName != null) { @@ -485,7 +480,7 @@ async function getResolvedPublishConfig( options: PublishConfiguration, arch: Arch | null, errorIfCannot: boolean -): Promise { +): Promise { options = { ...options } expandPublishConfig(options, platformPackager, packager, arch) @@ -524,12 +519,12 @@ async function getResolvedPublishConfig( } const isGithub = provider === "github" - if (!isGithub && provider !== "bintray") { + if (!isGithub && provider !== "bitbucket") { return options } - let owner = isGithub ? (options as GithubOptions).owner : (options as BintrayOptions).owner - let project = isGithub ? (options as GithubOptions).repo : (options as BintrayOptions).package + let owner = isGithub ? (options as GithubOptions).owner : (options as BitbucketOptions).owner + let project = isGithub ? (options as GithubOptions).repo : (options as BitbucketOptions).slug if (isGithub && owner == null && project != null) { const index = project.indexOf("/") @@ -578,6 +573,6 @@ async function getResolvedPublishConfig( return { owner, repo: project, ...options } as GithubOptions } else { //tslint:disable-next-line:no-object-literal-type-assertion - return { owner, package: project, ...options } as BintrayOptions + return { owner, slug: project, ...options } as BitbucketOptions } } diff --git a/packages/app-builder-lib/src/publish/s3/s3Publisher.ts b/packages/app-builder-lib/src/publish/s3/s3Publisher.ts index 30c5e78839a..c751a3877fb 100644 --- a/packages/app-builder-lib/src/publish/s3/s3Publisher.ts +++ b/packages/app-builder-lib/src/publish/s3/s3Publisher.ts @@ -4,7 +4,7 @@ import { PublishContext } from "electron-publish" import { BaseS3Publisher } from "./BaseS3Publisher" export default class S3Publisher extends BaseS3Publisher { - readonly providerName = "S3" + readonly providerName = "s3" constructor(context: PublishContext, private readonly info: S3Options) { super(context, info) diff --git a/packages/app-builder-lib/src/publish/s3/spacesPublisher.ts b/packages/app-builder-lib/src/publish/s3/spacesPublisher.ts index d30380e7c20..192f90360c6 100644 --- a/packages/app-builder-lib/src/publish/s3/spacesPublisher.ts +++ b/packages/app-builder-lib/src/publish/s3/spacesPublisher.ts @@ -4,7 +4,7 @@ import { PublishContext } from "electron-publish" import { BaseS3Publisher } from "./BaseS3Publisher" export default class SpacesPublisher extends BaseS3Publisher { - readonly providerName = "Spaces" + readonly providerName = "spaces" constructor(context: PublishContext, private readonly info: SpacesOptions) { super(context, info) diff --git a/packages/app-builder-lib/src/publish/updateInfoBuilder.ts b/packages/app-builder-lib/src/publish/updateInfoBuilder.ts index 4297627e059..cf76837b250 100644 --- a/packages/app-builder-lib/src/publish/updateInfoBuilder.ts +++ b/packages/app-builder-lib/src/publish/updateInfoBuilder.ts @@ -113,10 +113,8 @@ export async function createUpdateInfoTasks(event: ArtifactCreated, _publishConf const tasks: Array = [] const electronUpdaterCompatibility = packager.platformSpecificBuildOptions.electronUpdaterCompatibility || packager.config.electronUpdaterCompatibility || ">=2.15" for (const publishConfiguration of publishConfigs) { - const isBintray = publishConfiguration.provider === "bintray" let dir = outDir - // Bintray uses different variant of channel file info, better to generate it to a separate dir by always - if (isBintray || (publishConfigs.length > 1 && publishConfiguration !== publishConfigs[0])) { + if (publishConfigs.length > 1 && publishConfiguration !== publishConfigs[0]) { dir = path.join(outDir, publishConfiguration.provider) } @@ -149,7 +147,7 @@ export async function createUpdateInfoTasks(event: ArtifactCreated, _publishConf await writeOldMacInfo(publishConfiguration, outDir, dir, channel, createdFiles, version, packager) } - const updateInfoFile = path.join(dir, (isBintray ? `${version}_` : "") + getUpdateInfoFileName(channel, packager, event.arch)) + const updateInfoFile = path.join(dir, getUpdateInfoFileName(channel, packager, event.arch)) if (createdFiles.has(updateInfoFile)) { continue } diff --git a/packages/app-builder-lib/src/targets/MsiTarget.ts b/packages/app-builder-lib/src/targets/MsiTarget.ts index 8624d95e13d..ea7c1de4ac6 100644 --- a/packages/app-builder-lib/src/targets/MsiTarget.ts +++ b/packages/app-builder-lib/src/targets/MsiTarget.ts @@ -1,5 +1,5 @@ import BluebirdPromise from "bluebird-lst" -import { Arch, log, deepAssign } from "builder-util" +import { Arch, asArray, log, deepAssign } from "builder-util" import { UUID } from "builder-util-runtime" import { getBinFromUrl } from "../binDownload" import { walk } from "builder-util/out/fs" @@ -11,6 +11,7 @@ import * as path from "path" import { MsiOptions } from "../" import { Target } from "../core" import { DesktopShortcutCreationPolicy, FinalCommonWindowsInstallerOptions, getEffectiveOptions } from "../options/CommonWindowsInstallerConfiguration" +import { normalizeExt } from "../platformPackager" import { getTemplatePath } from "../util/pathManager" import { VmManager } from "../vm/vm" import { WineVmManager } from "../vm/WineVm" @@ -20,8 +21,6 @@ import { createStageDir, getWindowsInstallationDirName } from "./targetUtil" const ELECTRON_BUILDER_UPGRADE_CODE_NS_UUID = UUID.parse("d752fe43-5d44-44d5-9fc9-6dd1bf19d5cc") const ROOT_DIR_ID = "APPLICATIONFOLDER" -const ASSISTED_UI_FILE_NAME = "WixUI_Assisted.wxs" - const projectTemplate = new Lazy<(data: any) => string>(async () => { const template = (await readFile(path.join(getTemplatePath("msi"), "template.xml"), "utf8")) .replace(/{{/g, "<%") @@ -40,6 +39,22 @@ export default class MsiTarget extends Target { super("msi") } + /** + * A product-specific string that can be used in an [MSI Identifier](https://docs.microsoft.com/en-us/windows/win32/msi/identifier). + */ + private get productMsiIdPrefix() { + const sanitizedId = this.packager.appInfo.productFilename.replace(/[^\w.]/g, "").replace(/^[^A-Za-z_]+/, "") + return sanitizedId.length > 0 ? sanitizedId : "App" + this.upgradeCode.replace(/-/g, "") + } + + private get iconId() { + return `${this.productMsiIdPrefix}Icon.exe` + } + + private get upgradeCode(): string { + return (this.options.upgradeCode || UUID.v5(this.packager.appInfo.id, ELECTRON_BUILDER_UPGRADE_CODE_NS_UUID)).toUpperCase() + } + async build(appOutDir: string, arch: Arch) { const packager = this.packager const artifactName = packager.expandArtifactBeautyNamePattern(this.options, "msi", arch) @@ -55,20 +70,9 @@ export default class MsiTarget extends Target { const commonOptions = getEffectiveOptions(this.options, this.packager) - if (commonOptions.isAssisted) { - // F*** *** *** *** *** *** *** *** *** *** *** *** *** WiX *** *** *** *** *** *** *** *** *** - // cannot understand how to set MSIINSTALLPERUSER on radio box change. In any case installed per user. - log.warn(`MSI DOESN'T SUPPORT assisted installer. Please use NSIS instead.`) - } - const projectFile = stageDir.getTempFile("project.wxs") const objectFiles = ["project.wixobj"] - const uiFile = commonOptions.isAssisted ? stageDir.getTempFile(ASSISTED_UI_FILE_NAME) : null await writeFile(projectFile, await this.writeManifest(appOutDir, arch, commonOptions)) - if (uiFile !== null) { - await writeFile(uiFile, await readFile(path.join(getTemplatePath("msi"), ASSISTED_UI_FILE_NAME), "utf8")) - objectFiles.push(ASSISTED_UI_FILE_NAME.replace(".wxs", ".wixobj")) - } await packager.info.callMsiProjectCreated(projectFile) @@ -78,9 +82,6 @@ export default class MsiTarget extends Target { // noinspection SpellCheckingInspection const candleArgs = ["-arch", arch === Arch.ia32 ? "x86" : arch === Arch.arm64 ? "arm64" : "x64", `-dappDir=${vm.toVmFile(appOutDir)}`].concat(this.getCommonWixArgs()) candleArgs.push("project.wxs") - if (uiFile !== null) { - candleArgs.push(ASSISTED_UI_FILE_NAME) - } await vm.exec(vm.toVmFile(path.join(vendorPath, "candle.exe")), candleArgs, { cwd: stageDir.dir, }) @@ -156,23 +157,22 @@ export default class MsiTarget extends Target { const compression = this.packager.compression const options = this.options const iconPath = await this.packager.getIconPath() - const iconId = `${appInfo.productFilename}Icon.exe`.replace(/\s/g, "") return (await projectTemplate.value)({ ...commonOptions, isCreateDesktopShortcut: commonOptions.isCreateDesktopShortcut !== DesktopShortcutCreationPolicy.NEVER, isRunAfterFinish: options.runAfterFinish !== false, iconPath: iconPath == null ? null : this.vm.toVmFile(iconPath), - iconId: iconId, + iconId: this.iconId, compressionLevel: compression === "store" ? "none" : "high", version: appInfo.getVersionInWeirdWindowsForm(), productName: appInfo.productName, - upgradeCode: (options.upgradeCode || UUID.v5(appInfo.id, ELECTRON_BUILDER_UPGRADE_CODE_NS_UUID)).toUpperCase(), + upgradeCode: this.upgradeCode, manufacturer: companyName || appInfo.productName, appDescription: appInfo.description, // https://stackoverflow.com/questions/1929038/compilation-error-ice80-the-64bitcomponent-uses-32bitdirectory programFilesId: arch === Arch.x64 ? "ProgramFiles64Folder" : "ProgramFilesFolder", // wix in the name because special wix format can be used in the name - installationDirectoryWixName: getWindowsInstallationDirName(appInfo, commonOptions.isPerMachine === true), + installationDirectoryWixName: getWindowsInstallationDirName(appInfo, commonOptions.isAssisted || commonOptions.isPerMachine === true), dirs, files, }) @@ -223,9 +223,8 @@ export default class MsiTarget extends Target { if (isMainExecutable && (isCreateDesktopShortcut || commonOptions.isCreateStartMenuShortcut)) { result += `>\n` const shortcutName = commonOptions.shortcutName - const iconId = `${appInfo.productFilename}Icon.exe`.replace(/\s/g, "") if (isCreateDesktopShortcut) { - result += `${fileSpace} \n` + result += `${fileSpace} \n` } const hasMenuCategory = commonOptions.menuCategory != null @@ -234,7 +233,7 @@ export default class MsiTarget extends Target { if (hasMenuCategory) { dirs.push(``) } - result += `${fileSpace} \n` + result += `${fileSpace} \n` result += `${fileSpace} \n` result += `${fileSpace} \n` } @@ -247,6 +246,22 @@ export default class MsiTarget extends Target { result += `/>` } + const fileAssociations = this.packager.fileAssociations + if (isMainExecutable && fileAssociations.length !== 0) { + for (const item of fileAssociations) { + const extensions = asArray(item.ext).map(normalizeExt) + for (const ext of extensions) { + result += `${fileSpace} \n` + result += `${fileSpace} \n` + result += `${fileSpace} \n` + result += `${fileSpace} \n` + result += `${fileSpace} \n` + } + } + } + return `${result}\n${fileSpace}` }) diff --git a/packages/app-builder-lib/src/targets/nsis/NsisTarget.ts b/packages/app-builder-lib/src/targets/nsis/NsisTarget.ts index e2fce06e137..577b6e3c3b7 100644 --- a/packages/app-builder-lib/src/targets/nsis/NsisTarget.ts +++ b/packages/app-builder-lib/src/targets/nsis/NsisTarget.ts @@ -543,6 +543,7 @@ export class NsisTarget extends Target { private async executeMakensis(defines: any, commands: any, script: string) { const args: Array = this.options.warningsAsErrors === false ? [] : ["-WX"] + args.push("-INPUTCHARSET", "UTF8") for (const name of Object.keys(defines)) { const value = defines[name] if (value == null) { diff --git a/packages/app-builder-lib/src/targets/snap.ts b/packages/app-builder-lib/src/targets/snap.ts index 4a3438d2390..b170db4a8b5 100644 --- a/packages/app-builder-lib/src/targets/snap.ts +++ b/packages/app-builder-lib/src/targets/snap.ts @@ -100,6 +100,7 @@ export default class SnapTarget extends Target { version: appInfo.version, title: options.title || appInfo.productName, summary: options.summary || appInfo.productName, + compression: options.compression, description: this.helper.getDescription(options), architectures: [toLinuxArchString(arch, "snap")], apps: { @@ -207,6 +208,10 @@ export default class SnapTarget extends Target { } } + if (snap.compression != null) { + args.push("--compression", snap.compression) + } + if (packager.packagerOptions.effectiveOptionComputed != null && (await packager.packagerOptions.effectiveOptionComputed({ snap, desktopFile, args }))) { return } @@ -221,6 +226,7 @@ export default class SnapTarget extends Target { if (this.isUseTemplateApp) { args.push("--template-url", `electron4:${snapArch}`) } + await executeAppBuilder(args) await packager.info.callArtifactBuildCompleted({ diff --git a/packages/app-builder-lib/templates/msi/WixUI_Assisted.wxs b/packages/app-builder-lib/templates/msi/WixUI_Assisted.wxs deleted file mode 100644 index 40af5c3dbdb..00000000000 --- a/packages/app-builder-lib/templates/msi/WixUI_Assisted.wxs +++ /dev/null @@ -1,134 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 - - - - - - - - - - - - - - - - - - - - - - - - - 1 - WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - NOT Installed - - - - - - - \ No newline at end of file diff --git a/packages/app-builder-lib/templates/msi/template.xml b/packages/app-builder-lib/templates/msi/template.xml index a21e9853310..9d1c583d329 100644 --- a/packages/app-builder-lib/templates/msi/template.xml +++ b/packages/app-builder-lib/templates/msi/template.xml @@ -18,34 +18,61 @@ + {{ if (iconPath) { }} {{ } -}} - {{ if (isAssisted || isRunAfterFinish) { }} - + {{ if (isRunAfterFinish) { }} + + {{ if (!isAssisted) { }} + + + NOT Installed AND UILevel >= 4 + + {{ } -}} {{ } -}} - {{ if (isPerMachine) { }} - + {{ } else { }} - + {{ } -}} + {{ if (isAssisted) { }} - - - - - - {{ } else if (isRunAfterFinish) { }} - - - - + + + {{ if (isPerMachine) { }} + + {{ } -}} + + {{ if (isRunAfterFinish) { }} + + + + WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 AND NOT Installed + + {{ } -}} + + + + 1 OR CostingComplete = 1 + NOT Installed + + 1 + !(wix.WixUISupportPerUser) AND NOT Privileged + NOT !(wix.WixUISupportPerUser) + WixAppFolder = "WixPerUserFolder" + WixAppFolder = "WixPerMachineFolder" + + 1 + 1 + + NOT Installed + {{ } -}} diff --git a/packages/app-builder-lib/templates/snap/snapcraft.yaml b/packages/app-builder-lib/templates/snap/snapcraft.yaml index 08ed9604b8e..bfbc6142686 100644 --- a/packages/app-builder-lib/templates/snap/snapcraft.yaml +++ b/packages/app-builder-lib/templates/snap/snapcraft.yaml @@ -1,6 +1,7 @@ base: core18 grade: stable confinement: strict +compression: lzo parts: launch-scripts: plugin: dump diff --git a/packages/builder-util-runtime/src/bintray.ts b/packages/builder-util-runtime/src/bintray.ts deleted file mode 100644 index 807451d8bd8..00000000000 --- a/packages/builder-util-runtime/src/bintray.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { CancellationToken } from "./CancellationToken" -import { configureRequestOptions, HttpExecutor, parseJson, RequestHeaders } from "./httpExecutor" -import { BintrayOptions } from "./publishOptions" - -export interface Version { - readonly name: string - //noinspection ReservedWordAsName - readonly package: string -} - -export interface File { - name: string - path: string - - sha1: string - sha256: string -} - -export class BintrayClient { - private readonly basePath: string - readonly auth: string | null - readonly repo: string - - readonly owner: string - readonly user: string - readonly component: string | null - readonly distribution: string | null - readonly packageName: string - - private requestHeaders: RequestHeaders | null = null - - setRequestHeaders(value: RequestHeaders | null) { - this.requestHeaders = value - } - - constructor(options: BintrayOptions, private readonly httpExecutor: HttpExecutor, private readonly cancellationToken: CancellationToken, apiKey?: string | null) { - if (options.owner == null) { - throw new Error("owner is not specified") - } - if (options.package == null) { - throw new Error("package is not specified") - } - - this.repo = options.repo || "generic" - this.packageName = options.package - this.owner = options.owner - this.user = options.user || options.owner - this.component = options.component || null - this.distribution = options.distribution || "stable" - this.auth = apiKey == null ? null : `Basic ${Buffer.from(`${this.user}:${apiKey}`).toString("base64")}` - this.basePath = `/packages/${this.owner}/${this.repo}/${this.packageName}` - } - - private bintrayRequest( - path: string, - auth: string | null, - data: { [name: string]: any } | null = null, - cancellationToken: CancellationToken, - method?: "GET" | "DELETE" | "PUT" - ): Promise { - return parseJson( - this.httpExecutor.request(configureRequestOptions({ hostname: "api.bintray.com", path, headers: this.requestHeaders || undefined }, auth, method), cancellationToken, data) - ) - } - - getVersion(version: string): Promise { - return this.bintrayRequest(`${this.basePath}/versions/${version}`, this.auth, null, this.cancellationToken) - } - - getVersionFiles(version: string): Promise> { - return this.bintrayRequest>(`${this.basePath}/versions/${version}/files`, this.auth, null, this.cancellationToken) - } - - createVersion(version: string): Promise { - return this.bintrayRequest( - `${this.basePath}/versions`, - this.auth, - { - name: version, - }, - this.cancellationToken - ) - } - - deleteVersion(version: string): Promise { - return this.bintrayRequest(`${this.basePath}/versions/${version}`, this.auth, null, this.cancellationToken, "DELETE") - } -} diff --git a/packages/builder-util-runtime/src/index.ts b/packages/builder-util-runtime/src/index.ts index 4893a9fb1d3..b3d845ac8de 100644 --- a/packages/builder-util-runtime/src/index.ts +++ b/packages/builder-util-runtime/src/index.ts @@ -14,7 +14,6 @@ export { configureRequestUrl, } from "./httpExecutor" export { - BintrayOptions, CustomPublishOptions, GenericServerOptions, GithubOptions, diff --git a/packages/builder-util-runtime/src/publishOptions.ts b/packages/builder-util-runtime/src/publishOptions.ts index 76a02491a68..82cf63b5d67 100644 --- a/packages/builder-util-runtime/src/publishOptions.ts +++ b/packages/builder-util-runtime/src/publishOptions.ts @@ -1,6 +1,6 @@ import { OutgoingHttpHeaders } from "http" -export type PublishProvider = "github" | "bintray" | "s3" | "spaces" | "generic" | "custom" | "snapStore" | "keygen" | "bitbucket" +export type PublishProvider = "github" | "s3" | "spaces" | "generic" | "custom" | "snapStore" | "keygen" | "bitbucket" // typescript-json-schema generates only PublishConfiguration if it is specified in the list, so, it is not added here export type AllPublishOptions = @@ -9,7 +9,6 @@ export type AllPublishOptions = | S3Options | SpacesOptions | GenericServerOptions - | BintrayOptions | CustomPublishOptions | KeygenOptions | SnapStoreOptions @@ -199,7 +198,7 @@ export interface KeygenOptions extends PublishConfiguration { * Bitbucket options. * https://bitbucket.org/ * Define `BITBUCKET_TOKEN` environment variable. - * + * * For converting an app password to a usable token, you can utilize this ```typescript convertAppPassword(owner: string, token: string) { @@ -288,9 +287,9 @@ export interface BaseS3Options extends PublishConfiguration { * AWS credentials are required, please see [getting your credentials](http://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/getting-your-credentials.html). * Define `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` [environment variables](http://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-environment.html). * Or in the [~/.aws/credentials](http://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-shared.html). - * + * * Example configuration: - * + * ```json { "build": @@ -419,48 +418,3 @@ function spacesUrl(options: SpacesOptions) { } return appendPath(`https://${options.name}.${options.region}.digitaloceanspaces.com`, options.path) } - -/** - * [Bintray](https://bintray.com/) options. Requires an API key. An API key can be obtained from the user [profile](https://bintray.com/profile/edit) page ("Edit Your Profile" -> API Key). - * Define `BT_TOKEN` environment variable. - */ -export interface BintrayOptions extends PublishConfiguration { - /** - * The provider. Must be `bintray`. - */ - readonly provider: "bintray" - - /** - * The Bintray package name. - */ - readonly package?: string | null - - /** - * The Bintray repository name. - * @default generic - */ - readonly repo?: string | null - - /** - * The owner. - */ - readonly owner?: string | null - - /** - * The Bintray component (Debian only). - */ - readonly component?: string | null - - /** - * The Bintray distribution (Debian only). - * @default stable - */ - readonly distribution?: string | null - - /** - * The Bintray user account. Used in cases where the owner is an organization. - */ - readonly user?: string | null - - readonly token?: string | null -} diff --git a/packages/builder-util/package.json b/packages/builder-util/package.json index 6ba38574ed9..a8729b06a05 100644 --- a/packages/builder-util/package.json +++ b/packages/builder-util/package.json @@ -18,7 +18,7 @@ "7zip-bin": "~5.1.1", "@types/debug": "^4.1.6", "@types/fs-extra": "^9.0.11", - "app-builder-bin": "3.7.1", + "app-builder-bin": "4.1.0", "bluebird-lst": "^1.0.9", "builder-util-runtime": "workspace:*", "chalk": "^4.1.1", diff --git a/packages/builder-util/src/util.ts b/packages/builder-util/src/util.ts index 17f434e4b15..ebfbc7575f6 100644 --- a/packages/builder-util/src/util.ts +++ b/packages/builder-util/src/util.ts @@ -260,7 +260,7 @@ export class ExecError extends Error { alreadyLogged = false constructor(command: string, readonly exitCode: number, out: string, errorOut: string, code = "ERR_ELECTRON_BUILDER_CANNOT_EXECUTE") { - super(`${command} exited with code ${code}${formatOut(out, "Output")}${formatOut(errorOut, "Error output")}`) + super(`${command} process failed ${code}${formatOut(String(exitCode), "Exit code")}${formatOut(out, "Output")}${formatOut(errorOut, "Error output")}`) ;(this as NodeJS.ErrnoException).code = code } } diff --git a/packages/electron-publish/src/gitHubPublisher.ts b/packages/electron-publish/src/gitHubPublisher.ts index 709a018757c..2bede0b52bc 100644 --- a/packages/electron-publish/src/gitHubPublisher.ts +++ b/packages/electron-publish/src/gitHubPublisher.ts @@ -31,7 +31,7 @@ export class GitHubPublisher extends HttpPublisher { private readonly token: string - readonly providerName = "GitHub" + readonly providerName = "github" private readonly releaseType: "draft" | "prerelease" | "release" @@ -218,7 +218,6 @@ export class GitHubPublisher extends HttpPublisher { private createRelease() { return this.githubRequest(`/repos/${this.info.owner}/${this.info.repo}/releases`, this.token, { tag_name: this.tag, - name: this.version, draft: this.releaseType === "draft", prerelease: this.releaseType === "prerelease", }) diff --git a/packages/electron-publish/src/publisher.ts b/packages/electron-publish/src/publisher.ts index 3b2537c95c5..83dc3480bd0 100644 --- a/packages/electron-publish/src/publisher.ts +++ b/packages/electron-publish/src/publisher.ts @@ -1,5 +1,5 @@ import { Arch, log } from "builder-util" -import { CancellationToken, ProgressCallbackTransform } from "builder-util-runtime" +import { CancellationToken, ProgressCallbackTransform, PublishProvider } from "builder-util-runtime" import { PADDING } from "builder-util/out/log" import * as chalk from "chalk" import { createReadStream, stat, Stats } from "fs-extra" @@ -37,7 +37,7 @@ export interface UploadTask { export abstract class Publisher { protected constructor(protected readonly context: PublishContext) {} - abstract get providerName(): string + abstract get providerName(): PublishProvider abstract upload(task: UploadTask): Promise diff --git a/packages/electron-updater/src/AppUpdater.ts b/packages/electron-updater/src/AppUpdater.ts index 46c80ce3476..48b90793c4c 100644 --- a/packages/electron-updater/src/AppUpdater.ts +++ b/packages/electron-updater/src/AppUpdater.ts @@ -582,7 +582,7 @@ export abstract class AppUpdater extends EventEmitter { // NodeJS URL doesn't decode automatically const urlPath = decodeURIComponent(taskOptions.fileInfo.url.pathname) if (urlPath.endsWith(`.${taskOptions.fileExtension}`)) { - return path.posix.basename(urlPath) + return path.basename(urlPath) } else { // url like /latest, generate name return `update.${taskOptions.fileExtension}` diff --git a/packages/electron-updater/src/MacUpdater.ts b/packages/electron-updater/src/MacUpdater.ts index a5064711dd2..a0bec469532 100644 --- a/packages/electron-updater/src/MacUpdater.ts +++ b/packages/electron-updater/src/MacUpdater.ts @@ -1,20 +1,22 @@ import { AllPublishOptions, newError, safeStringifyJson } from "builder-util-runtime" import { stat } from "fs-extra" import { createReadStream } from "fs" -import { createServer, IncomingMessage, ServerResponse } from "http" -import { AddressInfo } from "net" +import { createServer, IncomingMessage, Server, ServerResponse } from "http" import { AppAdapter } from "./AppAdapter" import { AppUpdater, DownloadUpdateOptions } from "./AppUpdater" import { ResolvedUpdateFileInfo, UpdateDownloadedEvent } from "./main" import { findFile } from "./providers/Provider" import AutoUpdater = Electron.AutoUpdater import { execFileSync } from "child_process" +import crypto from "crypto" export class MacUpdater extends AppUpdater { private readonly nativeUpdater: AutoUpdater = require("electron").autoUpdater private squirrelDownloadedUpdate = false + private server?: Server + constructor(options?: AllPublishOptions, app?: AppAdapter) { super(options, app) @@ -95,26 +97,53 @@ export class MacUpdater extends AppUpdater { const log = this._logger const logContext = `fileToProxy=${zipFileInfo.url.href}` this.debug(`Creating proxy server for native Squirrel.Mac (${logContext})`) - const server = createServer() + this.server?.close() + this.server = createServer() this.debug(`Proxy server for native Squirrel.Mac is created (${logContext})`) - server.on("close", () => { + this.server.on("close", () => { log.info(`Proxy server for native Squirrel.Mac is closed (${logContext})`) }) // must be called after server is listening, otherwise address is null - function getServerUrl(): string { - const address = server.address() as AddressInfo - return `http://127.0.0.1:${address.port}` + const getServerUrl = (s: Server): string => { + const address = s.address() + if (typeof address === "string") { + return address + } + return `http://127.0.0.1:${address?.port}` } return await new Promise>((resolve, reject) => { + const pass = crypto.randomBytes(64).toString("base64").replace(/\//g, "_").replace(/\+/g, "-") + const authInfo = Buffer.from(`autoupdater:${pass}`, "base64") + // insecure random is ok const fileUrl = `/${Date.now().toString(16)}-${Math.floor(Math.random() * 9999).toString(16)}.zip` - server.on("request", (request: IncomingMessage, response: ServerResponse) => { + this.server!.on("request", (request: IncomingMessage, response: ServerResponse) => { + // check for basic auth header + if (!request.headers.authorization || request.headers.authorization.indexOf("Basic ") === -1) { + response.statusCode = 401 + response.statusMessage = "Invalid Authentication Credentials" + response.end() + log.warn("No authenthication info") + } + + // verify auth credentials + const base64Credentials = request.headers.authorization!.split(" ")[1] + const credentials = Buffer.from(base64Credentials, "base64").toString("ascii") + const [username, password] = credentials.split(":") + if (username !== "autoupdater" || password !== pass) { + response.statusCode = 401 + response.statusMessage = "Invalid Authentication Credentials" + response.end() + log.warn("Invalid authenthication credentials") + return + } + const requestUrl = request.url! log.info(`${requestUrl} requested`) if (requestUrl === "/") { - const data = Buffer.from(`{ "url": "${getServerUrl()}${fileUrl}" }`) + const data = Buffer.from(`{ "url": "${getServerUrl(this.server!)}${fileUrl}" }`) response.writeHead(200, { "Content-Type": "application/json", "Content-Length": data.length }) response.end(data) return @@ -131,13 +160,9 @@ export class MacUpdater extends AppUpdater { let errorOccurred = false response.on("finish", () => { - try { - setImmediate(() => server.close()) - } finally { - if (!errorOccurred) { - this.nativeUpdater.removeListener("error", reject) - resolve([]) - } + if (!errorOccurred) { + this.nativeUpdater.removeListener("error", reject) + resolve([]) } }) @@ -161,11 +186,15 @@ export class MacUpdater extends AppUpdater { }) this.debug(`Proxy server for native Squirrel.Mac is starting to listen (${logContext})`) - server.listen(0, "127.0.0.1", () => { - this.debug(`Proxy server for native Squirrel.Mac is listening (address=${getServerUrl()}, ${logContext})`) + + this.server!.listen(0, "127.0.0.1", () => { + this.debug(`Proxy server for native Squirrel.Mac is listening (address=${getServerUrl(this.server!)}, ${logContext})`) this.nativeUpdater.setFeedURL({ - url: getServerUrl(), - headers: { "Cache-Control": "no-cache" }, + url: getServerUrl(this.server!), + headers: { + "Cache-Control": "no-cache", + Authorization: `Basic ${authInfo.toString("ascii")}`, + }, }) // The update has been downloaded and is ready to be served to Squirrel @@ -183,6 +212,7 @@ export class MacUpdater extends AppUpdater { } quitAndInstall(): void { + this.server?.close() if (this.squirrelDownloadedUpdate) { // update already fetched by Squirrel, it's ready to install this.nativeUpdater.quitAndInstall() diff --git a/packages/electron-updater/src/providerFactory.ts b/packages/electron-updater/src/providerFactory.ts index a9f7e93963c..21f6b22c089 100644 --- a/packages/electron-updater/src/providerFactory.ts +++ b/packages/electron-updater/src/providerFactory.ts @@ -1,7 +1,6 @@ import { AllPublishOptions, BaseS3Options, - BintrayOptions, BitbucketOptions, CustomPublishOptions, GenericServerOptions, @@ -12,7 +11,6 @@ import { PublishConfiguration, } from "builder-util-runtime" import { AppUpdater } from "./AppUpdater" -import { BintrayProvider } from "./providers/BintrayProvider" import { BitbucketProvider } from "./providers/BitbucketProvider" import { GenericProvider } from "./providers/GenericProvider" import { GitHubProvider } from "./providers/GitHubProvider" @@ -72,9 +70,6 @@ export function createClient(data: PublishConfiguration | AllPublishOptions, upd }) } - case "bintray": - return new BintrayProvider(data as BintrayOptions, runtimeOptions) - case "custom": { const options = data as CustomPublishOptions const constructor = options.updateProvider diff --git a/packages/electron-updater/src/providers/BintrayProvider.ts b/packages/electron-updater/src/providers/BintrayProvider.ts deleted file mode 100644 index c71a7a84488..00000000000 --- a/packages/electron-updater/src/providers/BintrayProvider.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { BintrayOptions, CancellationToken, newError, UpdateInfo } from "builder-util-runtime" -import { BintrayClient } from "builder-util-runtime/out/bintray" -import { URL } from "url" -import { ResolvedUpdateFileInfo } from "../main" -import { getChannelFilename, newBaseUrl } from "../util" -import { parseUpdateInfo, Provider, ProviderRuntimeOptions, resolveFiles } from "./Provider" - -export class BintrayProvider extends Provider { - private client: BintrayClient - private readonly baseUrl: URL - - constructor(configuration: BintrayOptions, runtimeOptions: ProviderRuntimeOptions) { - super(runtimeOptions) - - this.client = new BintrayClient(configuration, runtimeOptions.executor, new CancellationToken()) - this.baseUrl = newBaseUrl(`https://dl.bintray.com/${this.client.owner}/${this.client.repo}`) - } - - setRequestHeaders(value: any): void { - super.setRequestHeaders(value) - this.client.setRequestHeaders(value) - } - - async getLatestVersion(): Promise { - try { - const data = await this.client.getVersion("_latest") - const channelFilename = getChannelFilename(this.getDefaultChannelName()) - const files = await this.client.getVersionFiles(data.name) - const channelFile = files.find(it => it.name.endsWith(`_${channelFilename}`) || it.name.endsWith(`-${channelFilename}`)) - if (channelFile == null) { - // noinspection ExceptionCaughtLocallyJS - throw newError( - `Cannot find channel file "${channelFilename}", existing files:\n${files.map(it => JSON.stringify(it, null, 2)).join(",\n")}`, - "ERR_UPDATER_CHANNEL_FILE_NOT_FOUND" - ) - } - - const channelFileUrl = new URL(`https://dl.bintray.com/${this.client.owner}/${this.client.repo}/${channelFile.name}`) - return parseUpdateInfo(await this.httpRequest(channelFileUrl), channelFilename, channelFileUrl) - } catch (e) { - if ("statusCode" in e && e.statusCode === 404) { - throw newError( - `No latest version, please ensure that user, package and repository correctly configured. Or at least one version is published. ${e.stack || e.message}`, - "ERR_UPDATER_LATEST_VERSION_NOT_FOUND" - ) - } - throw e - } - } - - resolveFiles(updateInfo: UpdateInfo): Array { - return resolveFiles(updateInfo, this.baseUrl) - } -} diff --git a/packages/electron-updater/src/windowsExecutableCodeSignatureVerifier.ts b/packages/electron-updater/src/windowsExecutableCodeSignatureVerifier.ts index 3d87759af17..8381fe9d43f 100644 --- a/packages/electron-updater/src/windowsExecutableCodeSignatureVerifier.ts +++ b/packages/electron-updater/src/windowsExecutableCodeSignatureVerifier.ts @@ -39,7 +39,7 @@ export function verifySignature(publisherNames: Array, unescapedTempUpda "-InputFormat", "None", "-Command", - `Get-AuthenticodeSignature '${tempUpdateFile}' | ConvertTo-Json -Compress | ForEach-Object { [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($_)) }`, + `Get-AuthenticodeSignature -LiteralPath '${tempUpdateFile}' | ConvertTo-Json -Compress | ForEach-Object { [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($_)) }`, ], { timeout: 20 * 1000, @@ -64,8 +64,8 @@ export function verifySignature(publisherNames: Array, unescapedTempUpda const result = `publisherNames: ${publisherNames.join(" | ")}, raw info: ` + JSON.stringify(data, (name, value) => (name === "RawData" ? undefined : value), 2) logger.warn(`Sign verification failed, installer signed with incorrect certificate: ${result}`) resolve(result) - } catch (e) { - logger.warn(`Cannot execute Get-AuthenticodeSignature: ${error}. Ignoring signature validation due to unknown error.`) + } catch (e: any) { + handleError(logger, e, null) resolve(null) return } @@ -102,7 +102,7 @@ function handleError(logger: Logger, error: Error | null, stderr: string | null) try { execFileSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", "ConvertTo-Json test"], { timeout: 10 * 1000 } as any) - } catch (testError) { + } catch (testError: any) { logger.warn( `Cannot execute ConvertTo-Json: ${testError.message}. Ignoring signature validation due to unsupported powershell version. Please upgrade to powershell 3 or higher.` ) @@ -114,8 +114,7 @@ function handleError(logger: Logger, error: Error | null, stderr: string | null) } if (stderr) { - logger.warn(`Cannot execute Get-AuthenticodeSignature, stderr: ${stderr}. Ignoring signature validation due to unknown stderr.`) - return + throw new Error(`Cannot execute Get-AuthenticodeSignature, stderr: ${stderr}. Failing signature validation due to unknown stderr.`) } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5eb17c576b8..40fd061bfb0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -183,7 +183,7 @@ importers: '@types/js-yaml': 4.0.3 '@types/source-map-support': 0.5.4 7zip-bin: ~5.1.1 - app-builder-bin: 3.7.1 + app-builder-bin: 4.1.0 bluebird-lst: ^1.0.9 builder-util-runtime: workspace:* chalk: ^4.1.1 @@ -201,7 +201,7 @@ importers: '@types/debug': 4.1.7 '@types/fs-extra': 9.0.13 7zip-bin: 5.1.1 - app-builder-bin: 3.7.1 + app-builder-bin: 4.1.0 bluebird-lst: 1.0.9 builder-util-runtime: link:../builder-util-runtime chalk: 4.1.2 @@ -3145,8 +3145,8 @@ packages: normalize-path: 3.0.0 picomatch: 2.3.0 - /app-builder-bin/3.7.1: - resolution: {integrity: sha512-ql93vEUq6WsstGXD+SBLSIQw6SNnhbDEM0swzgugytMxLp3rT24Ag/jcC80ZHxiPRTdew1niuR7P3/FCrDqIjw==} + /app-builder-bin/4.1.0: + resolution: {integrity: sha512-rbdMe0sIVE95cpYqMQh4IFqhTDdB8LkKlTRcbO/Y3QleRYoIePejIbX774IYomYYzZbJfJuX7pLRiGvSdIxIYA==} dev: false /archiver-utils/2.1.0: diff --git a/test/snapshots/globTest.js.snap b/test/snapshots/globTest.js.snap index 64799a8e6c0..e753b3ef41f 100644 --- a/test/snapshots/globTest.js.snap +++ b/test/snapshots/globTest.js.snap @@ -99,7 +99,7 @@ Object { exports[`outside link 2`] = ` Object { "offset": "5281", - "size": 4, + "size": "@size", } `; diff --git a/test/snapshots/linux/snapTest.js.snap b/test/snapshots/linux/snapTest.js.snap index 0184fbe7db0..b7c3e1b2e49 100644 --- a/test/snapshots/linux/snapTest.js.snap +++ b/test/snapshots/linux/snapTest.js.snap @@ -44,6 +44,7 @@ Object { "amd64", ], "base": "core18", + "compression": "lzo", "confinement": "strict", "description": "Test Application (test quite “ #378)", "grade": "stable", @@ -115,6 +116,7 @@ Object { "amd64", ], "base": "core18", + "compression": "lzo", "confinement": "strict", "description": "Test Application (test quite “ #378)", "grade": "stable", @@ -306,6 +308,77 @@ Object { } `; +exports[`compression option 1`] = ` +Object { + "apps": Object { + "sep": Object { + "command": "command.sh", + "environment": Object { + "DISABLE_WAYLAND": "1", + "LD_LIBRARY_PATH": "$SNAP_LIBRARY_PATH:$SNAP/lib:$SNAP/usr/lib:$SNAP/lib/x86_64-linux-gnu:$SNAP/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH:$SNAP/lib:$SNAP/usr/lib:$SNAP/lib/x86_64-linux-gnu:$SNAP/usr/lib/x86_64-linux-gnu", + "PATH": "$SNAP/usr/sbin:$SNAP/usr/bin:$SNAP/sbin:$SNAP/bin:$PATH", + "SNAP_DESKTOP_RUNTIME": "$SNAP/gnome-platform", + "TMPDIR": "$XDG_RUNTIME_DIR", + }, + "plugs": Array [ + "desktop", + "desktop-legacy", + "home", + "x11", + "wayland", + "unity7", + "browser-support", + "network", + "gsettings", + "audio-playback", + "pulseaudio", + "opengl", + ], + }, + }, + "architectures": Array [ + "amd64", + ], + "base": "core18", + "compression": "xz", + "confinement": "strict", + "description": "Test Application (test quite “ #378)", + "grade": "stable", + "name": "sep", + "plugs": Object { + "gnome-3-28-1804": Object { + "default-provider": "gnome-3-28-1804", + "interface": "content", + "target": "$SNAP/gnome-platform", + }, + "gtk-3-themes": Object { + "default-provider": "gtk-common-themes", + "interface": "content", + "target": "$SNAP/data-dir/themes", + }, + "icon-themes": Object { + "default-provider": "gtk-common-themes", + "interface": "content", + "target": "$SNAP/data-dir/icons", + }, + "sound-themes": Object { + "default-provider": "gtk-common-themes", + "interface": "content", + "target": "$SNAP/data-dir/sounds", + }, + }, + "summary": "Sep", + "title": "Sep", + "version": "1.1.0", +} +`; + +exports[`compression option 2`] = ` +Object { + "linux": Array [], +} +`; + exports[`custom after, no desktop 1`] = ` Object { "apps": Object { @@ -338,6 +411,7 @@ Object { "amd64", ], "base": "core18", + "compression": "lzo", "confinement": "strict", "description": "Test Application (test quite “ #378)", "grade": "stable", @@ -409,6 +483,7 @@ Object { "amd64", ], "base": "core18", + "compression": "lzo", "confinement": "strict", "description": "Test Application (test quite “ #378)", "grade": "stable", @@ -447,6 +522,77 @@ Object { } `; +exports[`default compression 1`] = ` +Object { + "apps": Object { + "sep": Object { + "command": "command.sh", + "environment": Object { + "DISABLE_WAYLAND": "1", + "LD_LIBRARY_PATH": "$SNAP_LIBRARY_PATH:$SNAP/lib:$SNAP/usr/lib:$SNAP/lib/x86_64-linux-gnu:$SNAP/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH:$SNAP/lib:$SNAP/usr/lib:$SNAP/lib/x86_64-linux-gnu:$SNAP/usr/lib/x86_64-linux-gnu", + "PATH": "$SNAP/usr/sbin:$SNAP/usr/bin:$SNAP/sbin:$SNAP/bin:$PATH", + "SNAP_DESKTOP_RUNTIME": "$SNAP/gnome-platform", + "TMPDIR": "$XDG_RUNTIME_DIR", + }, + "plugs": Array [ + "desktop", + "desktop-legacy", + "home", + "x11", + "wayland", + "unity7", + "browser-support", + "network", + "gsettings", + "audio-playback", + "pulseaudio", + "opengl", + ], + }, + }, + "architectures": Array [ + "amd64", + ], + "base": "core18", + "compression": "lzo", + "confinement": "strict", + "description": "Test Application (test quite “ #378)", + "grade": "stable", + "name": "sep", + "plugs": Object { + "gnome-3-28-1804": Object { + "default-provider": "gnome-3-28-1804", + "interface": "content", + "target": "$SNAP/gnome-platform", + }, + "gtk-3-themes": Object { + "default-provider": "gtk-common-themes", + "interface": "content", + "target": "$SNAP/data-dir/themes", + }, + "icon-themes": Object { + "default-provider": "gtk-common-themes", + "interface": "content", + "target": "$SNAP/data-dir/icons", + }, + "sound-themes": Object { + "default-provider": "gtk-common-themes", + "interface": "content", + "target": "$SNAP/data-dir/sounds", + }, + }, + "summary": "Sep", + "title": "Sep", + "version": "1.1.0", +} +`; + +exports[`default compression 2`] = ` +Object { + "linux": Array [], +} +`; + exports[`default stagePackages 1`] = ` Object { "apps": Object { @@ -459,6 +605,7 @@ Object { "amd64", ], "base": "core18", + "compression": "lzo", "confinement": "classic", "description": "Test Application (test quite “ #378)", "grade": "stable", @@ -624,6 +771,7 @@ Object { "amd64", ], "base": "core18", + "compression": "lzo", "confinement": "classic", "description": "Test Application (test quite “ #378)", "grade": "stable", @@ -790,6 +938,7 @@ Object { "amd64", ], "base": "core18", + "compression": "lzo", "confinement": "classic", "description": "Test Application (test quite “ #378)", "grade": "stable", @@ -956,6 +1105,7 @@ Object { "amd64", ], "base": "core18", + "compression": "lzo", "confinement": "classic", "description": "Test Application (test quite “ #378)", "grade": "stable", @@ -1133,6 +1283,7 @@ Object { "amd64", ], "base": "core18", + "compression": "lzo", "confinement": "strict", "description": "Test Application (test quite “ #378)", "grade": "stable", @@ -1194,6 +1345,7 @@ Object { "amd64", ], "base": "core18", + "compression": "lzo", "confinement": "strict", "description": "Test Application (test quite “ #378)", "grade": "stable", @@ -1396,6 +1548,7 @@ Object { "amd64", ], "base": "core18", + "compression": "lzo", "confinement": "strict", "description": "Test Application (test quite “ #378)", "grade": "stable", @@ -1611,6 +1764,7 @@ Object { "amd64", ], "base": "core18", + "compression": "lzo", "confinement": "strict", "description": "Test Application (test quite “ #378)", "grade": "stable", @@ -1685,6 +1839,7 @@ Object { "amd64", ], "base": "core18", + "compression": "lzo", "confinement": "strict", "description": "Test Application (test quite “ #378)", "grade": "stable", diff --git a/test/snapshots/windows/assistedInstallerTest.js.snap b/test/snapshots/windows/assistedInstallerTest.js.snap new file mode 100644 index 00000000000..7fbe86534f6 --- /dev/null +++ b/test/snapshots/windows/assistedInstallerTest.js.snap @@ -0,0 +1,130 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`allowElevation false, app requestedExecutionLevel admin 1`] = ` +Object { + "win": Array [ + Object { + "arch": "x64", + "file": "test Setup 1.1.0.exe", + "safeArtifactName": "test-Setup-1.1.0.exe", + }, + ], +} +`; + +exports[`allowToChangeInstallationDirectory 1`] = ` +Object { + "win": Array [ + Object { + "file": "latest.yml", + "fileContent": Object { + "files": Array [ + Object { + "sha512": "@sha512", + "size": "@size", + "url": "Test-Custom-Installation-Dir-Setup-1.1.0.exe", + }, + ], + "path": "Test-Custom-Installation-Dir-Setup-1.1.0.exe", + "releaseDate": "@releaseDate", + "releaseNotes": "New release with new bugs and + +without features", + "sha512": "@sha512", + "version": "1.1.0", + }, + }, + Object { + "arch": "x64", + "file": "Test Custom Installation Dir Setup 1.1.0.exe", + "safeArtifactName": "Test-Custom-Installation-Dir-Setup-1.1.0.exe", + "updateInfo": Object { + "sha512": "@sha512", + "size": "@size", + }, + }, + Object { + "file": "Test Custom Installation Dir Setup 1.1.0.exe.blockmap", + "safeArtifactName": "Test-Custom-Installation-Dir-Setup-1.1.0.exe.blockmap", + "updateInfo": Object { + "sha512": "@sha512", + "size": "@size", + }, + }, + ], +} +`; + +exports[`allowToChangeInstallationDirectory 2`] = ` +Object { + "owner": "foo", + "provider": "github", + "repo": "bar", + "updaterCacheDirName": "test-custom-inst-dir-updater", +} +`; + +exports[`assisted 1`] = ` +Object { + "win": Array [ + Object { + "arch": "x64", + "file": "Test App ßW Setup 1.1.0.exe", + "safeArtifactName": "TestApp-Setup-1.1.0.exe", + "updateInfo": Object { + "sha512": "@sha512", + "size": "@size", + }, + }, + Object { + "file": "Test App ßW Setup 1.1.0.exe.blockmap", + "safeArtifactName": "TestApp-Setup-1.1.0.exe.blockmap", + "updateInfo": Object { + "sha512": "@sha512", + "size": "@size", + }, + }, + ], +} +`; + +exports[`assisted, MUI_HEADER 1`] = ` +Object { + "win": Array [], +} +`; + +exports[`assisted, MUI_HEADER as option 1`] = ` +Object { + "win": Array [ + Object { + "file": "Test App ßW Setup 1.1.0.exe", + "safeArtifactName": "TestApp-Setup-1.1.0.exe", + }, + ], +} +`; + +exports[`assisted, only perMachine 1`] = ` +Object { + "win": Array [ + Object { + "arch": "x64", + "file": "Test App ßW Setup 1.1.0.exe", + "safeArtifactName": "TestApp-Setup-1.1.0.exe", + "updateInfo": Object { + "sha512": "@sha512", + "size": "@size", + }, + }, + Object { + "file": "Test App ßW Setup 1.1.0.exe.blockmap", + "safeArtifactName": "TestApp-Setup-1.1.0.exe.blockmap", + "updateInfo": Object { + "sha512": "@sha512", + "size": "@size", + }, + }, + ], +} +`; diff --git a/test/snapshots/windows/msiTest.js.snap b/test/snapshots/windows/msiTest.js.snap index 31d54b26b4e..072a3ffa2df 100644 --- a/test/snapshots/windows/msiTest.js.snap +++ b/test/snapshots/windows/msiTest.js.snap @@ -47,3 +47,15 @@ Object { ], } `; + +exports[`wix args 1`] = ` +Object { + "win": Array [ + Object { + "arch": "x64", + "file": "Test WiX Args 1.1.0.msi", + "safeArtifactName": "Test-WiX-Args-1.1.0.msi", + }, + ], +} +`; diff --git a/test/snapshots/windows/oneClickInstallerTest.js.snap b/test/snapshots/windows/oneClickInstallerTest.js.snap index dc798ccb59f..d0ebed506d1 100644 --- a/test/snapshots/windows/oneClickInstallerTest.js.snap +++ b/test/snapshots/windows/oneClickInstallerTest.js.snap @@ -274,7 +274,7 @@ exports[`one-click 1`] = ` Object { "win": Array [ Object { - "file": "1.1.0_latest.yml", + "file": "latest.yml", "fileContent": Object { "files": Array [ Object { @@ -312,10 +312,9 @@ Object { exports[`one-click 2`] = ` Object { - "owner": "actperepo", - "package": "TestApp", - "provider": "bintray", + "provider": "generic", "updaterCacheDirName": "testapp-updater", + "url": "https://develar.s3.amazonaws.com/test/win/x64", } `; diff --git a/test/src/BuildTest.ts b/test/src/BuildTest.ts index ad5bdb1e75e..f73fc1c5d07 100644 --- a/test/src/BuildTest.ts +++ b/test/src/BuildTest.ts @@ -326,10 +326,14 @@ export function removeUnstableProperties(data: any) { JSON.stringify(data, (name, value) => { if (name === "offset") { return undefined - } else if (name.endsWith(".node") && value.size != null) { + } + if (name.endsWith(".node") && value.size != null) { // size differs on various OS value.size = "" - return value + } + // Keep existing test coverage + if (value.integrity) { + delete value.integrity } return value }) diff --git a/test/src/globTest.ts b/test/src/globTest.ts index afcc97c1ae3..e24b1ddc883 100644 --- a/test/src/globTest.ts +++ b/test/src/globTest.ts @@ -116,7 +116,8 @@ test.ifNotWindows( await fs.symlink(tempDir, path.join(projectDir, "o-dir")) }, packed: async context => { - expect((await readAsar(path.join(context.getResources(Platform.LINUX), "app.asar"))).getFile("o-dir/foo", false)).toMatchSnapshot() + const file = (await readAsar(path.join(context.getResources(Platform.LINUX), "app.asar"))).getFile("o-dir/foo", false) + expect(removeUnstableProperties(file)).toMatchSnapshot() }, } ) diff --git a/test/src/helpers/packTester.ts b/test/src/helpers/packTester.ts index ea00aa8762a..2712f060948 100644 --- a/test/src/helpers/packTester.ts +++ b/test/src/helpers/packTester.ts @@ -325,7 +325,7 @@ async function checkMacResult(packager: Packager, packagerOptions: PackagerOptio }) // checked manually, remove to avoid mismatch on CI server (where TRAVIS_BUILD_NUMBER is defined and different on each test run) - delete info.AsarIntegrity + delete info.ElectronAsarIntegrity delete info.CFBundleVersion delete info.BuildMachineOSBuild delete info.NSHumanReadableCopyright @@ -350,14 +350,12 @@ async function checkMacResult(packager: Packager, packagerOptions: PackagerOptio expect(info).toMatchSnapshot() - const checksumData = info.AsarIntegrity + const checksumData = info.ElectronAsarIntegrity if (checksumData != null) { - const data = JSON.parse(checksumData) - const checksums = data.checksums - for (const name of Object.keys(checksums)) { - checksums[name] = "hash" + for (const name of Object.keys(checksumData)) { + checksumData[name] = { algorithm: "SHA256", hash: "hash" } } - info.AsarIntegrity = JSON.stringify(data) + info.ElectronAsarIntegrity = JSON.stringify(checksumData) } if (checkOptions.checkMacApp != null) { @@ -523,6 +521,10 @@ export function removeUnstableProperties(data: any) { // to ensure that some property exists return `@${name}` } + // Keep existing test coverage + if (value.integrity) { + delete value.integrity + } return value }) ) @@ -530,8 +532,19 @@ export function removeUnstableProperties(data: any) { export async function verifyAsarFileTree(resourceDir: string) { const fs = await readAsar(path.join(resourceDir, "app.asar")) - // console.log(resourceDir + " " + JSON.stringify(fs.header, null, 2)) - expect(fs.header).toMatchSnapshot() + + const stableHeader = JSON.parse( + JSON.stringify(fs.header, (name, value) => { + // Keep existing test coverage + if (value.integrity) { + delete value.integrity + } + return value + }) + ) + + // console.log(resourceDir + " " + JSON.stringify(stableHeader, null, 2)) + expect(stableHeader).toMatchSnapshot() } export function toSystemIndependentPath(s: string): string { diff --git a/test/src/linux/snapTest.ts b/test/src/linux/snapTest.ts index 9853964bef4..15b3896fb0c 100644 --- a/test/src/linux/snapTest.ts +++ b/test/src/linux/snapTest.ts @@ -255,3 +255,44 @@ test.ifAll.ifDevOrLinuxCi( }, }) ) + +test.ifDevOrLinuxCi( + "default compression", + app({ + targets: snapTarget, + config: { + extraMetadata: { + name: "sep", + }, + productName: "Sep", + }, + effectiveOptionComputed: async ({ snap, args }) => { + expect(snap).toMatchSnapshot() + expect(snap.compression).toEqual("lzo") + expect(args).toEqual(expect.arrayContaining(["--compression", "lzo"])) + return true + }, + }) +) + +test.ifDevOrLinuxCi( + "compression option", + app({ + targets: snapTarget, + config: { + extraMetadata: { + name: "sep", + }, + productName: "Sep", + snap: { + compression: "xz", + }, + }, + effectiveOptionComputed: async ({ snap, args }) => { + expect(snap).toMatchSnapshot() + expect(snap.compression).toEqual("xz") + expect(args).toEqual(expect.arrayContaining(["--compression", "xz"])) + return true + }, + }) +) diff --git a/test/src/windows/oneClickInstallerTest.ts b/test/src/windows/oneClickInstallerTest.ts index f66b45bd60f..82286ab077e 100644 --- a/test/src/windows/oneClickInstallerTest.ts +++ b/test/src/windows/oneClickInstallerTest.ts @@ -35,9 +35,9 @@ test( publisherName: "Foo, Inc", }, publish: { - provider: "bintray", - owner: "actperepo", - package: "TestApp", + provider: "generic", + // tslint:disable:no-invalid-template-strings + url: "https://develar.s3.amazonaws.com/test/${os}/${arch}", }, nsis: { deleteAppDataOnUninstall: true,