electron / forge

:electron: A complete tool for building and publishing Electron applications
https://electronforge.io
MIT License
6.49k stars 516 forks source link

publisher-github: Inconsistent behavior between Linux/Windows and macOS when the package already exists in the release #2821

Open guizmaii opened 2 years ago

guizmaii commented 2 years ago

Pre-flight checklist

Electron Forge version

6.0.0-beta.63

Electron version

v18.1.0

Operating system

macOS

Last known working Electron Forge version

No response

Expected behavior

Here's my forge.config.js:

// See https://www.electronforge.io/configuration
//
// An interesting configuration example: https://github.com/electron/fiddle/blob/main/forge.config.js
//
const path = require('path');
const packageJson = require('./package.json');

const { version } = packageJson;
const iconDir = path.resolve(__dirname, 'assets', 'icons');

module.exports = {
  packagerConfig: { // https://electron.github.io/electron-packager/main/interfaces/electronpackager.options.
    // General packager configs
    // Inspired by https://github.com/electron/fiddle/blob/main/forge.config.js
    name: 'My App',
    executableName: 'my-app',
    asar: true,
    appBundleId: 'com.my.app',
    appCategoryType: 'public.app-category.developer-tools',
    protocols: [
      {
        name: 'My App Launch Protocol',
        schemes: ['my-app'],
      },
    ],
    win32metadata: {
      CompanyName: 'MyCompany',
      OriginalFilename: 'My App',
    },

    osxNotarize: { // https://www.npmjs.com/package/electron-notarize#method-notarizeopts-promisevoid
      tool: "notarytool",
      appleId: process.env.APPLE_ID,
      appleIdPassword: process.env.APPLE_APP_SPECIFIC_PASSWORD,
      teamId: "..."
    },

    // macOS code-signing configs. See https://www.electronjs.org/docs/latest/tutorial/code-signing#electron-forge
    osxSign: { // https://www.npmjs.com/package/electron-osx-sign#opts
      identity: "...",
      'hardened-runtime': true,
      entitlements: "./static/entitlements.plist",
      'entitlements-inherit': './static/entitlements.plist',
      keychain: "build.keychain"
    },
  },
  makers: [
    {
      name: "@electron-forge/maker-squirrel",
      config: (arch) => ({ // https://js.electronforge.io/maker/squirrel/interfaces/makersquirrelconfig
        name: "my-app",
        authors: "MyCompany",
        exe: 'my-app.exe',
        //iconUrl: path.resolve(iconDir, 'my-icon.png'),
        noMsi: true,
        setupExe: `my-app-${version}-win32-${arch}-setup.exe`,
        //setupIcon: path.resolve(iconDir, 'my-icon.png'),
        certificateFile: path.resolve(__dirname, 'static', 'mycertificate.pfx'),
        certificatePassword: process.env.PFX_PASSWORD,
      })
    },
    {
      name: '@electron-forge/maker-dmg',
      config: (arch) => ({ // https://js.electronforge.io/maker/dmg/interfaces/makerdmgconfig
        background: './assets/icons/my-icon.png',
        format: 'ULFO',
        icon: './assets/icons/my-icon.png',
        name: `My App ${arch}`,
        overwrite: true
      })
    },
    {
      name: "@electron-forge/maker-deb",
      config: { // https://js.electronforge.io/maker/deb/interfaces/makerdebconfigoptions
        categories: ["Development"],
        homepage: "https://www.mycompany.com/",
        icon: "./assets/icons/my-icon.png",
        maintainer: "MyCompany",
        mimeType: ['x-scheme-handler/my-app'],
      }
    },
  ],
  plugins: [
    [
      "@electron-forge/plugin-webpack",
      {
        mainConfig: "./webpack.main.config.js",
        renderer: {
          config: "./webpack.renderer.config.js",
          entryPoints: [
            {
              html: "./src/index.html",
              js: "./src/renderer.ts",
              name: "main_window"
            }
          ]
        }
      }
    ],
    [
      '@electron-forge/plugin-electronegativity', // https://www.electronforge.io/config/plugins/electronegativity
      {
        isSarif: true,
        output: "out/electronegativity-result"
      }
    ]
  ],
  publishers: [
    {
      name: '@electron-forge/publisher-github',
      config: {
        repository: {
          owner: 'myCompany',
          name: 'my-repo',
        },
        draft: process.env.PUBLISHER_GITHUB_DRAFT,
        prerelease: process.env.PUBLISHER_GITHUB_PRERELEASE,
      },
    },
  ],
}

Here's my Github Action job that is publishing the app in "draft" mode:

jobs:
  ci:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ windows-latest, macos-latest, ubuntu-latest ]
        arch: [ x64 ]
        include:
          - os: macOS-latest
            arch: arm64

      ...

      - name: Publish
        shell: bash
        run: |
          echo matrix.arch ${{ matrix.arch }}

          ...  # Signing and notarizing if necessary

          yarn electron-forge publish --arch=${{ matrix.arch }} --auth-token=${GITHUB_TOKEN}
        env:
          # See 'forge.config.js' Github publisher config
          PUBLISHER_GITHUB_DRAFT: true
          PUBLISHER_GITHUB_PRERELEASE: false
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

          ....

As you can see in my CI definition, I publish my app for Linux x64, Windows x64, macOS x64 and macOS arm64

If I launch this job a first time with a version number that is not yet published, all the 4 apps will be correctly published.

My issue is that if I relaunch this job a second time with the same version number, then the Linux and Windows will be be published correctly while the 2 macOS versions will fail with the following error:

Making for the following targets: dmg
✔ Making for target: dmg - On platform: darwin - For arch: arm64
- Resolving publish target: @electron-forge/publisher-github
✔ Resolving publish target: @electron-forge/publisher-github
- Searching for target release: 0.0.1
✔ Searching for target release: 0.0.1
- Uploading Artifacts 0/1 to v0.0.1
✖ Uploading Artifacts 0/1 to v0.0.1

An unhandled error has occurred inside Forge:
Validation Failed: {"resource":"ReleaseAsset","code":"already_exists","field":"name"}
HttpError: Validation Failed: {"resource":"ReleaseAsset","code":"already_exists","field":"name"}
    at /Users/runner/work/my-app/my-app/node_modules/@octokit/request/dist-src/fetch-wrapper.js:68:27
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at Job.doExecute (/Users/runner/work/my-app/my-app/node_modules/bottleneck/light.js:405:18)
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
error Command failed with exit code 1.
Error: Process completed with exit code 1.

Expected behavior: The macOS versions are published too, overriding the previously published versions

Actual behavior

See "Expected behavior" part

Steps to reproduce

See "Expected behavior" part

Additional information

No response

guizmaii commented 2 years ago

Ok so, weirdly enough, the postMake hook script that I developed with the help of @erickzhao to fix a AssertionError [ERR_ASSERTION]: Volume name is not longer than 27 chars error explained here: https://discord.com/channels/745037351163527189/746375169240727755/968261323500711986 fixes the issue I was reporting here 🤷‍♂️

Good news 😄

Here's the postMake hook script if you have one of these issues:

  hooks: {
    postMake: (forgeConfig, makeResults) => { 
      if (DEBUG_HOOKS) {
        console.log("'forgeConfig': ", forgeConfig)
        console.log("Initial 'makeResults': ", makeResults)
      }

      const isMacApp = makeResult => makeResult.platform === 'darwin'

      const macBuilds = makeResults.filter(r => isMacApp(r))

      if (macBuilds.length === 0) {
        return makeResults
      } else {
        macBuilds.forEach(r => {
          const newDmgName = `mycompany-myapp-${version}-${r.arch}`
          const initialDmgName = 'mycompany-myapp'

          r.packageJSON.name = newDmgName
          const oldPath = r.artifacts[0]
          r.artifacts[0] = r.artifacts[0].replace(`${initialDmgName}.dmg`, `${newDmgName}.dmg`)
          const newPath = r.artifacts[0]

          fs.renameSync(oldPath, newPath)
        })

        if (DEBUG_HOOKS) {
          console.log("Final 'makeResults': ", makeResults)
        }

        return makeResults
      }
    },
  },

to make this script work you need to have configured the @electron-forge/maker-dmg this way:

    {
      name: '@electron-forge/maker-dmg',
      config: {
        background: './assets/icons/conduktor-icon.png',
        format: 'ULFO',
        icon: './assets/icons/conduktor-icon.png',
        name: `mycompany-myapp`, // this `name` value needs to match the `initialDmgName` value in the the previous script
        overwrite: true
      }
    },