electron / forge

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

Error during packaging on macOS #3549

Closed Amstergo closed 6 months ago

Amstergo commented 8 months ago

Pre-flight checklist

Electron Forge version

7.3.1

Electron version

29.1.5

Operating system

macOs 13.0.1

Last known working Electron Forge version

No response

Expected behavior

I expect my application to package without errors

Actual behavior

I am encountering an unhandled rejection error while packaging my Electron project using electron-forge package on macOS. The error message is as follows:

❯ Packaging application › Determining targets... ❯ Packaging for arm64 on darwin ✔ Copying files ⠋ Preparing native dependencies

An unhandled rejection has occurred inside Forge:
Error: Cannot copy '../../../../../loose-envify/cli.js' to a subdirectory of itself, '../../../../../loose-envify/cli.js'.
at /Users/desctop_extension/node_modules/fs-extra/lib/copy/copy.js:213:21
    at FSReqCallback.oncomplete (node:fs:192:23)

The issue only occurs on macOS during the packaging process. However, the project builds and runs successfully on Windows

Steps to reproduce

-

Additional information

Here are the project dependencies:

"@electron-forge/cli": "^7.3.1",
"@electron-forge/maker-deb": "^7.3.1",
"@electron-forge/maker-dmg": "^7.3.1",
"@electron-forge/maker-pkg": "^7.3.1",
"@electron-forge/maker-rpm": "^7.3.1",
"@electron-forge/maker-squirrel": "^7.3.1",
"@electron-forge/maker-wix": "^7.3.1",
"@electron-forge/maker-zip": "^7.3.1",
"@electron-forge/plugin-auto-unpack-natives": "^7.3.1",
"@electron-forge/plugin-electronegativity": "^7.3.1",
"@electron-forge/plugin-fuses": "^7.3.1",
"@electron-forge/plugin-vite": "^7.3.1",
"@electron/asar": "^3.2.9",
"@electron/fuses": "^1.7.0",

"electron": "29.1.5",

"vite": "^5.2.6",

node -v 18.19.1

file forge.config.ts

import type { ForgeConfig } from '@electron-forge/shared-types'
import MakerSquirrel from '@electron-forge/maker-squirrel'
import MakerZIP from '@electron-forge/maker-zip'
import MakerDmg from '@electron-forge/maker-dmg'
import { VitePlugin } from '@electron-forge/plugin-vite'
import { FusesPlugin } from '@electron-forge/plugin-fuses'
import { FuseV1Options, FuseVersion } from '@electron/fuses'
require('dotenv').config()

const config: ForgeConfig = {
  packagerConfig: {
    asar: true,
    ignore: ['/stats.html', '/.idea', '/.vscode'],
    icon: './assets/images/icons',
    osxSign: {
      identity: process.env.ELECTRON_OSXSIGN_IDENTITY as string,
      // @ts-ignore
      gatekeeperAssess: false,
      platform: 'darwin',
      hardenedRuntime: true,
      entitlements: './entitlements.plist',
      entitlementsInherit: './entitlements.plist',
      signatureFlags: 'library',
    },
    osxNotarize: {
      // @ts-ignore
      tool: 'notarytool',
      teamId: process.env.ELECTRON_NOTARIZE_APP_PROVIDER as string,
      // ascProvider: process.env.ELECTRON_NOTARIZE_APP_PROVIDER as string,
      appleId: process.env.ELECTRON_NOTARIZE_APPLE_ID as string,
      appleIdPassword: process.env.ELECTRON_NOTARIZE_APPLE_PASSWORD as string,
    },
  },
  rebuildConfig: {},
  makers: [
    new MakerSquirrel({
      authors: 'S',
      description: 'S app',
      setupIcon: './assets/images/con.ico',
      loadingGif: './assets/Logo.gif',
      // certificateFile: './convolo.pfx',
      // certificatePassword: process.env.ELECTRON_FORGE_CERTIFICATE_PASSWORD_WINDOWS as string,
    }),
    new MakerZIP({}, ['darwin']),
    new MakerDmg({
      background: './assets/images/logo512x512.png',
      format: 'ULFO',
      debug: true,
    }),
  ],
  publishers: [
    {
      name: '@electron-forge/publisher-electron-release-server',
      config: {
        baseUrl: process.env.ELECTRON_PUBLISHER_BASE_URL as string,
        username: process.env.ELECTRON_PUBLISHER_USER_NAME as string,
        password: process.env.ELECTRON_PUBLISHER_PASSWORD as string,
      },
    },
  ],
  plugins: [
    new VitePlugin({
      build: [
        {
          entry: 'src/main/main.ts',
          config: 'vite.main.config.ts',
        },
        {
          entry: 'src/main/preload.ts',
          config: 'vite.preload.config.ts',
        },
      ],
      renderer: [
        {
          name: 'main_window',
          config: 'vite.renderer.config.ts',
        },
      ],
    }),

    {
      name: '@electron-forge/plugin-auto-unpack-natives',
      config: {},
    },

    {
      name: '@electron-forge/plugin-electronegativity',
      config: {
        isSarif: true,
      },
    },

    new FusesPlugin({
      version: FuseVersion.V1,
      [FuseV1Options.RunAsNode]: false,
      [FuseV1Options.EnableCookieEncryption]: true,
      [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
      [FuseV1Options.EnableNodeCliInspectArguments]: false,
      [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
      [FuseV1Options.OnlyLoadAppFromAsar]: true,
    }),
  ],
}

export default config

file forge.env.d.ts

export {} // Make this a module

declare global {
  // This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Vite
  // plugin that tells the Electron app where to look for the Vite-bundled app code (depending on
  // whether you're running in development or production).
  const MAIN_WINDOW_VITE_DEV_SERVER_URL: string
  const MAIN_WINDOW_VITE_NAME: string

  namespace NodeJS {
    interface Process {
      // Used for hot reload after preload scripts.
      viteDevServers: Record<string, import('vite').ViteDevServer>
    }
  }

  type VitePluginConfig = ConstructorParameters<typeof import('@electron-forge/plugin-vite').VitePlugin>[0]

  interface VitePluginRuntimeKeys {
    VITE_DEV_SERVER_URL: `${string}_VITE_DEV_SERVER_URL`
    VITE_NAME: `${string}_VITE_NAME`
  }
}

declare module 'vite' {
  interface ConfigEnv<K extends keyof VitePluginConfig = keyof VitePluginConfig> {
    root: string
    forgeConfig: VitePluginConfig
    forgeConfigSelf: VitePluginConfig[K][number]
  }
}

file vite.base.config.ts

import { builtinModules } from 'node:module'
import type { AddressInfo } from 'node:net'
import type { ConfigEnv, Plugin, UserConfig } from 'vite'
import pkg from './package.json'

export const builtins = ['electron', ...builtinModules.map((m) => [m, `node:${m}`]).flat()]

export const external = [
  ...builtins,
  ...Object.keys('dependencies' in pkg ? (pkg.dependencies as Record<string, unknown>) : {}),
]

export function getBuildConfig(env: ConfigEnv<'build'>): UserConfig {
  const { root, mode, command } = env

  return {
    root,
    mode,
    build: {
      // Prevent multiple builds from interfering with each other.
      emptyOutDir: false,
      // 🚧 Multiple builds may conflict.
      outDir: '.vite/build',
      watch: command === 'serve' ? {} : null,
      minify: command === 'build',
    },
    clearScreen: false,
  }
}

export function getDefineKeys(names: string[]) {
  const define: { [name: string]: VitePluginRuntimeKeys } = {}

  return names.reduce((acc, name) => {
    const NAME = name.toUpperCase()
    console.log('NAME', NAME)
    const keys: VitePluginRuntimeKeys = {
      VITE_DEV_SERVER_URL: `${NAME}_VITE_DEV_SERVER_URL`,
      VITE_NAME: `${NAME}_VITE_NAME`,
    }

    return { ...acc, [name]: keys }
  }, define)
}

export function getBuildDefine(env: ConfigEnv<'build'>) {
  const { command, forgeConfig } = env
  const names = forgeConfig.renderer.filter(({ name }) => name != null).map(({ name }) => name!)
  const defineKeys = getDefineKeys(names)
  const define = Object.entries(defineKeys).reduce((acc, [name, keys]) => {
    const { VITE_DEV_SERVER_URL, VITE_NAME } = keys
    const def = {
      [VITE_DEV_SERVER_URL]: command === 'serve' ? JSON.stringify(process.env[VITE_DEV_SERVER_URL]) : undefined,
      [VITE_NAME]: JSON.stringify(name),
    }
    return { ...acc, ...def }
  }, {} as Record<string, any>)

  return define
}

export function pluginExposeRenderer(name: string): Plugin {
  const { VITE_DEV_SERVER_URL } = getDefineKeys([name])[name]

  return {
    name: '@electron-forge/plugin-vite:expose-renderer',
    configureServer(server) {
      process.viteDevServers ??= {}
      // Expose server for preload scripts hot reload.
      process.viteDevServers[name] = server

      server.httpServer?.once('listening', () => {
        const addressInfo = server.httpServer!.address() as AddressInfo
        // Expose env constant for main process use.
        process.env[VITE_DEV_SERVER_URL] = `http://localhost:${addressInfo?.port}`
      })
    },
  }
}

export function pluginHotRestart(command: 'reload' | 'restart'): Plugin {
  return {
    name: '@electron-forge/plugin-vite:hot-restart',
    closeBundle() {
      if (command === 'reload') {
        for (const server of Object.values(process.viteDevServers)) {
          // Preload scripts hot reload.
          server.ws.send({ type: 'full-reload' })
        }
      } else {
        // Main process hot restart.
        // https://github.com/electron/forge/blob/v7.2.0/packages/api/core/src/api/start.ts#L216-L223
        process.stdin.emit('data', 'rs')
      }
    },
  }
}

file vite.main.config.ts

import type { ConfigEnv, UserConfig } from 'vite'
import { defineConfig, mergeConfig } from 'vite'
import { getBuildConfig, getBuildDefine, external, pluginHotRestart } from './vite.base.config'

export default defineConfig((env) => {
  const forgeEnv = env as ConfigEnv<'build'>
  const { forgeConfigSelf } = forgeEnv
  const define = getBuildDefine(forgeEnv)
  const config: UserConfig = {
    build: {
      lib: {
        entry: forgeConfigSelf.entry!,
        fileName: () => '[name].js',
        formats: ['cjs'],
      },
      rollupOptions: {
        external,
      },
    },
    plugins: [pluginHotRestart('restart')],
    define,
    resolve: {
      // Load the Node.js entry.
      // browserField: false,
      mainFields: ['module', 'jsnext:main', 'jsnext'],
    },
  }

  return mergeConfig(getBuildConfig(forgeEnv), config)
})

vite.preload.config.ts

import type { ConfigEnv, UserConfig } from 'vite'
import { defineConfig, mergeConfig } from 'vite'
import { getBuildConfig, external, pluginHotRestart } from './vite.base.config'

// https://vitejs.dev/config
export default defineConfig((env) => {
  const forgeEnv = env as ConfigEnv<'build'>
  const { forgeConfigSelf } = forgeEnv
  const config: UserConfig = {
    build: {
      rollupOptions: {
        external,
        // Preload scripts may contain Web assets, so use the `build.rollupOptions.input` instead `build.lib.entry`.
        input: forgeConfigSelf.entry!,
        output: {
          format: 'cjs',
          // It should not be split chunks.
          inlineDynamicImports: true,
          entryFileNames: '[name].js',
          chunkFileNames: '[name].js',
          assetFileNames: '[name].[ext]',
        },
      },
    },
    plugins: [pluginHotRestart('reload')],
  }

  return mergeConfig(getBuildConfig(forgeEnv), config)
})

file vite.renderer.config.ts

import type { ConfigEnv, UserConfig } from 'vite'
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import svgr from 'vite-plugin-svgr'
import { visualizer } from 'rollup-plugin-visualizer'
import { pluginExposeRenderer } from './vite.base.config'

export default defineConfig((env) => {
  const forgeEnv = env as ConfigEnv<'renderer'>
  const { root, mode, forgeConfigSelf } = forgeEnv
  const name = forgeConfigSelf.name ?? ''

  return {
    root,
    mode,
    base: './',
    build: {
      outDir: `.vite/renderer/${name}`,
    },
    plugins: [
      pluginExposeRenderer(name),
      visualizer(),
      react(),
      svgr({
        svgrOptions: {},
        include: '**/*.svg',
      }),
    ],
    optimizeDeps: {
      include: ['@emotion/styled'],
    },
    resolve: {
      preserveSymlinks: true,
    },
    clearScreen: false,
  } as UserConfig
})

Please help me resolve this issue or provide guidance on how to further debug the problem. Thank you!

DennisKo commented 8 months ago

Same problem here. Pretty much the vanilla config from the vite typescript template

antoniaelsen commented 8 months ago

I am encountering this as well. Usually for semver/bin/semver.js or which/bin/which. I have the same error as OP when I use yarn as a package manager, with npm it's different but fails on the same package:

An unhandled rejection has occurred inside Forge:
Error: /var/folders/1d/nq17plrd0psgslq4n1n5gy_40000gn/T/electron-packager/tmp-MQcYl5/Electron.app/Contents/Resources/app/node_modules/@electron/get/node_modules/.bin/semver: file "../../../../../../../../../../../../var/folders/1d/nq17plrd0psgslq4n1n5gy_40000gn/T/electron-packager/tmp-MQcYl5/Electron.app/Contents/Resources/app/node_modules/@electron/get/node_modules/semver/bin/semver.js" links out of the package
at Filesystem.insertLink (/Users/antonia/projects/other/my-app/node_modules/@electron/asar/lib/filesystem.js:106:13)
    at handleFile (/Users/antonia/projects/other/my-app/node_modules/@electron/asar/lib/asar.js:132:20)
    at next (/Users/antonia/projects/other/my-app/node_modules/@electron/asar/lib/asar.js:148:11)
    at next (/Users/antonia/projects/other/my-app/node_modules/@electron/asar/lib/asar.js:149:12)
    at processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async MacApp.asarApp (/Users/antonia/projects/other/my-app/node_modules/@electron/packager/src/platform.ts:245:5)
    at async MacApp.buildApp (/Users/antonia/projects/other/my-app/node_modules/@electron/packager/src/platform.ts:150:5)
    at async MacApp.initialize (/Users/antonia/projects/other/my-app/node_modules/@electron/packager/src/platform.ts:141:7)
    at async MacApp.create (/Users/antonia/projects/other/my-app/node_modules/@electron/packager/src/mac.ts:435:5)
    at async Promise.all (index 0)
    at async packager (/Users/antonia/projects/other/my-app/node_modules/@electron/packager/src/packager.ts:246:20)
solidSpoon commented 7 months ago

I am encountering this as well. Usually for semver/bin/semver.js or which/bin/which. I have the same error as OP when I use yarn as a package manager, with npm it's different but fails on the same package:

An unhandled rejection has occurred inside Forge:
Error: /var/folders/1d/nq17plrd0psgslq4n1n5gy_40000gn/T/electron-packager/tmp-MQcYl5/Electron.app/Contents/Resources/app/node_modules/@electron/get/node_modules/.bin/semver: file "../../../../../../../../../../../../var/folders/1d/nq17plrd0psgslq4n1n5gy_40000gn/T/electron-packager/tmp-MQcYl5/Electron.app/Contents/Resources/app/node_modules/@electron/get/node_modules/semver/bin/semver.js" links out of the package
at Filesystem.insertLink (/Users/antonia/projects/other/my-app/node_modules/@electron/asar/lib/filesystem.js:106:13)
    at handleFile (/Users/antonia/projects/other/my-app/node_modules/@electron/asar/lib/asar.js:132:20)
    at next (/Users/antonia/projects/other/my-app/node_modules/@electron/asar/lib/asar.js:148:11)
    at next (/Users/antonia/projects/other/my-app/node_modules/@electron/asar/lib/asar.js:149:12)
    at processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async MacApp.asarApp (/Users/antonia/projects/other/my-app/node_modules/@electron/packager/src/platform.ts:245:5)
    at async MacApp.buildApp (/Users/antonia/projects/other/my-app/node_modules/@electron/packager/src/platform.ts:150:5)
    at async MacApp.initialize (/Users/antonia/projects/other/my-app/node_modules/@electron/packager/src/platform.ts:141:7)
    at async MacApp.create (/Users/antonia/projects/other/my-app/node_modules/@electron/packager/src/mac.ts:435:5)
    at async Promise.all (index 0)
    at async packager (/Users/antonia/projects/other/my-app/node_modules/@electron/packager/src/packager.ts:246:20)

Encountered the same issue here. I'm using a pretty standard configuration from the Vite TypeScript template. The problem arose when I added the dependency "@electron-forge/publisher-github": "^7.3.1". I'm running on macOS 14.1.2 (23B92).

jgresham commented 7 months ago

Same, using npm as my package installer

An unhandled rejection has occurred inside Forge:
Error: 
/var/folders/ck/jkwcs10946xf1cgp754gh96m0000gn/T/electron-packager/tmp-ONUw7C/Electron.app/Contents/Resources/app/node_modules/conf/node_modules/.bin/semver: 
file 
"../../../../../../../../../../../../var/folders/ck/jkwcs10946xf1cgp754gh96m0000gn/T/electron-packager/tmp-ONUw7C/Electron.app/Contents/Resources/app/node_modules/conf/node_modules/semver/bin/semver.js"
links out of the package
solidSpoon commented 7 months ago

This just work for me, but i don`t know why.

const config: ForgeConfig = {
    packagerConfig: {
-       asar: true,
+       asar: false,
        icon: './assets/icons/icon',
        extraResource: ["./drizzle"],
        executableName: 'dash-player',
        name: 'DashPlayer',
    },
.....
        // Fuses are used to enable/disable various Electron functionality
        // at package time, before code signing the application
        new FusesPlugin({
            version: FuseVersion.V1,
            [FuseV1Options.RunAsNode]: false,
            [FuseV1Options.EnableCookieEncryption]: true,
            [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
-           [FuseV1Options.EnableNodeCliInspectArguments]: false,
+           [FuseV1Options.EnableNodeCliInspectArguments]: true,
            [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
-           [FuseV1Options.OnlyLoadAppFromAsar]: true,
+           [FuseV1Options.OnlyLoadAppFromAsar]: false,
        }),
jgresham commented 7 months ago

This just work for me, but i don`t know why.

const config: ForgeConfig = {
    packagerConfig: {
-       asar: true,
+       asar: false,
        icon: './assets/icons/icon',
        extraResource: ["./drizzle"],
        executableName: 'dash-player',
        name: 'DashPlayer',
    },
.....
        // Fuses are used to enable/disable various Electron functionality
        // at package time, before code signing the application
        new FusesPlugin({
            version: FuseVersion.V1,
            [FuseV1Options.RunAsNode]: false,
            [FuseV1Options.EnableCookieEncryption]: true,
            [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
-           [FuseV1Options.EnableNodeCliInspectArguments]: false,
+           [FuseV1Options.EnableNodeCliInspectArguments]: true,
            [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
-           [FuseV1Options.OnlyLoadAppFromAsar]: true,
+           [FuseV1Options.OnlyLoadAppFromAsar]: false,
        }),

A decent interim solution, but its not preferable to disable asar https://www.electronjs.org/docs/latest/tutorial/asar-archives.

Also, unfortunately disabling asar leads to another error for me https://github.com/electron/forge/issues/3371

jgresham commented 7 months ago

I converted my repo from electron-builder to electron-forge and somewhere along the way npm or node_modules cache was stuck. Cloning my repo in a new directory and fresh npm install fixed this issue.

sethyuan commented 7 months ago

I too ran into the same issue, and after some investigation, I found that the files to be packaged into asar cannot have soft links because the path resolving algorithm is buggy.

erickzhao commented 7 months ago

Potentially related to this symlink issue https://github.com/electron/asar/pull/308

BlackHole1 commented 6 months ago

Fixed (Ref: https://github.com/electron/forge/pull/3592 / https://github.com/electron/asar/pull/308).

noah10 commented 5 months ago

Sorry to comment on a closed issue, but I'm hoping someone might be able to tell me (a) when this might be released or (b) how I can use it before it's released. (I've tried checking out electron/forge main and yarn linking to it as described in the "Running Forge Locally" section of "Contributing" and replacing electron-forge in package.json with my local version (which I'd prefer to avoid), but the problem persisted in both cases.)

solidSpoon commented 5 months ago

Sorry to comment on a closed issue, but I'm hoping someone might be able to tell me (a) when this might be released or (b) how I can use it before it's released. (I've tried checking out electron/forge main and yarn linking to it as described in the "Running Forge Locally" section of "Contributing" and replacing electron-forge in package.json with my local version (which I'd prefer to avoid), but the problem persisted in both cases.)

The problem is caused by @electron/asar, so just update @electron/asar dependency

image