electron / forge

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

The 'path' argument must be of type string. Received undefined #3578

Open a1392136 opened 1 month ago

a1392136 commented 1 month ago

Pre-flight checklist

Electron Forge version

7.3.1

Electron version

v29.1.6

Operating system

Windows11

Last known working Electron Forge version

No response

Expected behavior

After packaging, the application runs normally

Actual behavior

It can be used normally with electron-forge start before it is packaged. Startup error after packaging: uncaught Exception TypeError [ERR_INVALID_ARG_TYPE]: The 'path' argument must be of type string. Received undefined image

Steps to reproduce

package.json

{
   ...
   "main": ".vite/build/main.js",
   "scripts": {
      "start": "electron-forge start",
      "package": "electron-forge package",
      "make": "electron-forge make",
      "publish": "electron-forge publish",
      "rebuild-sqlite": "npx electron-rebuild -f -w better-sqlite3"
   },
   "devDependencies": {
      "@electron-forge/cli": "^7.3.1",
      "@electron-forge/maker-deb": "^7.3.1",
      "@electron-forge/maker-rpm": "^7.3.1",
      "@electron-forge/maker-squirrel": "^7.3.1",
      "@electron-forge/maker-zip": "^7.3.1",
      "@electron-forge/plugin-auto-unpack-natives": "^7.3.1",
      "@electron-forge/plugin-fuses": "^7.3.1",
      "@electron-forge/plugin-vite": "^7.3.1",
      "@electron-forge/shared-types": "^7.3.1",
      "@electron/fuses": "^1.7.0",
      "@swc/cli": "^0.3.10",
      "@swc/core": "^1.4.8",
      "@types/lodash": "^4.17.0",
      "@types/node": "^20.12.7",
      "@types/react": "^18.2.74",
      "@types/react-dom": "^18.2.23",
      "autoprefixer": "^10.4.19",
      "electron": "29.1.6",
      "electron-playwright-helpers": "^1.7.1",
      "electron-rebuild": "^3.2.9",
      "postcss": "^8.4.38",
      "prettier": "^3.2.5",
      "prettier-plugin-tailwindcss": "^0.5.12",
      "tailwindcss": "^3.4.1",
      "ts-node": "^10.9.2",
      "typescript": "~5.4.3",
      "vite": "^5.2.3"
   },
   "dependencies": {
      "@ant-design/pro-components": "^2.7.1",
      "@emotion/css": "^11.11.2",
      "@radix-ui/react-avatar": "^1.0.4",
      "@radix-ui/react-dialog": "^1.0.5",
      "@radix-ui/react-label": "^2.0.2",
      "@radix-ui/react-menubar": "^1.0.4",
      "@radix-ui/react-select": "^2.0.0",
      "@radix-ui/react-slot": "^1.0.2",
      "@radix-ui/react-tabs": "^1.0.4",
      "@radix-ui/react-tooltip": "^1.0.7",
      "@types/es6-shim": "^0.31.45",
      "@uiw/react-markdown-editor": "^6.1.1",
      "@vitejs/plugin-react": "^4.2.1",
      "antd": "^5.16.0",
      "better-sqlite3": "^9.4.5",
      "class-variance-authority": "^0.7.0",
      "clsx": "^2.1.0",
      "electron-squirrel-startup": "^1.0.0",
      "localforage": "^1.10.0",
      "lucide-react": "^0.364.0",
      "match-sorter": "^6.3.4",
      "react": "^18.2.0",
      "react-dom": "^18.2.0",
      "react-query": "^3.39.3",
      "react-router-dom": "^6.22.3",
      "reflect-metadata": "^0.2.2",
      "sort-by": "^1.2.0",
      "tailwind-merge": "^2.2.2",
      "tailwindcss-animate": "^1.0.7",
      "typeorm": "^0.3.20",
      "vaul": "^0.9.0",
      "zod": "^3.22.4"
   }
}

tsconfig.json

{
  "compilerOptions": {
    "target": "ESNext",
    "outDir": "dist",
    "module": "commonjs",
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "esModuleInterop": true,
    "sourceMap": true,
    "jsx": "react",
    "resolveJsonModule": true,
    "strictPropertyInitialization": false,
    "forceConsistentCasingInFileNames": true,
    "allowJs": false,
    "skipLibCheck": true,
    "strict": true,
    "baseUrl": ".",
    "paths": {
      "@/*": [
        "./src/*"
      ]
    },
    "include": [
      "./src"
    ],
    "exclude": [
      "node_modules"
    ]
  }
}

Additional information

vite.ase.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";
import path from "path";

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>) : {}),
];

// @ts-ignore
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,
        resolve: {
            alias: {
                "@": path.resolve(__dirname, "./src"),
            },
        },
    };
}

export function getDefineKeys(names: string[]) {
    const define: { // @ts-ignore
        [name: string]: VitePluginRuntimeKeys } = {};

    return names.reduce((acc, name) => {
        const NAME = name.toUpperCase();
        // @ts-ignore
        const keys: VitePluginRuntimeKeys = {
            VITE_DEV_SERVER_URL: `${NAME}_VITE_DEV_SERVER_URL`,
            VITE_NAME: `${NAME}_VITE_NAME`,
        };

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

// @ts-ignore
export function getBuildDefine(env: ConfigEnv<"build">) {
    const { command, forgeConfig } = env;
    // @ts-ignore
    const names = forgeConfig.renderer.filter(({ name }) => name != null).map(({ name }) => name!);
    const defineKeys = getDefineKeys(names);
    return 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>
    );
}

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

    return {
        name: "@electron-forge/plugin-vite:expose-renderer",
        configureServer(server) {
            // @ts-ignore
            process.viteDevServers ??= {};
            // Expose server for preload scripts hot reload.
            // @ts-ignore
            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") {
                // @ts-ignore
                for (const server of Object.values(process.viteDevServers)) {
                    // Preload scripts hot reload.
                    // @ts-ignore
                    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");
            }
        },
    };
}

vite.main.config.ts

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

// https://vitejs.dev/config
export default defineConfig((env) => {
  // @ts-ignore
  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.
      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";
import path from "path";

// 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);
});

vite.renderer.config.ts

import path from "path";
import react from "@vitejs/plugin-react";
import type { ConfigEnv, UserConfig } from "vite";
import { defineConfig } from "vite";
import { pluginExposeRenderer } from "./vite.base.config";

// https://vitejs.dev/config
export default defineConfig((env) => {
    // @ts-ignore
    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), react()],
        resolve: {
            preserveSymlinks: true,
            alias: {
                "@": path.resolve(__dirname, "./src"),
            },
        },
        clearScreen: false,
    } as UserConfig;
});

forge.config.ts

import type { ForgeConfig } from "@electron-forge/shared-types";
import { MakerZIP } from "@electron-forge/maker-zip";
import { MakerRpm } from "@electron-forge/maker-rpm";
import { VitePlugin } from "@electron-forge/plugin-vite";
import { FusesPlugin } from "@electron-forge/plugin-fuses";
import { FuseV1Options, FuseVersion } from "@electron/fuses";
import path from "path";
import configs from "./package.json";

const iconPath = path.resolve(__dirname, "src/assets/favicon");
const iconPathWithSuffix = iconPath + ".ico";
const appVersion = configs.version;

const config: ForgeConfig = {
  packagerConfig: {
    asar: true,
    icon: iconPath,
    appVersion: appVersion,
    extraResource: [path.resolve(__dirname, "data")],
  },
  rebuildConfig: {},
  makers: [
    {
      name: "@electron-forge/maker-squirrel",
      config: {
        iconUrl: iconPathWithSuffix,
        setupIcon: iconPathWithSuffix,
      },
    },
    {
      name: "@electron-forge/maker-deb",
      config: {
        options: {
          // Path to a single image that will act as icon for the application
          icon: iconPathWithSuffix,
        },
      },
    },
    new MakerZIP({}, ["darwin"]),
    new MakerRpm({}),
  ],
  plugins: [
    new VitePlugin({
      // `build` can specify multiple entry builds, which can be Main process, Preload scripts, Worker process, etc.
      // If you are familiar with Vite configuration, it will look really familiar.
      build: [
        {
          // `entry` is just an alias for `build.lib.entry` in the corresponding file of `config`.
          entry: "src/main.ts",
          config: "vite.main.config.ts",
        },
        {
          entry: "src/preload.ts",
          config: "vite.preload.config.ts",
        },
      ],
      renderer: [
        {
          name: "main_window",
          config: "vite.renderer.config.ts",
        },
      ],
    }),
    // 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.EnableEmbeddedAsarIntegrityValidation]: true,
      [FuseV1Options.OnlyLoadAppFromAsar]: true,
    }),
  ],
};

export default config;

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];
    }
}
caoxiemeihao commented 1 month ago

Can you provide a minimal reproduction repo?

a1392136 commented 1 month ago

Can you provide a minimal reproduction repo?

Thank you for taking the time to reply I spent the afternoon re-combing through my code and found out that it was a problem with the typeorm entity. But I still have a problem, when the error appears after clicking confirm, the process will run in the background without ending. How do I end the background process in code.

image

vabaly commented 1 day ago

Can you provide a minimal reproduction repo?你能提供一个最小的复制仓库吗?

Thank you for taking the time to reply感谢您抽出时间回复 I spent the afternoon re-combing through my code and found out that it was a problem with the typeorm entity. 我花了一个下午重新梳理我的代码,发现是 typeorm 实体的问题。 But I still have a problem, when the error appears after clicking confirm, the process will run in the background without ending. 但我还有一个问题,当点击确认后出现错误时,该进程将在后台运行而不会结束。 How do I end the background process in code. 如何在代码中结束后台进程。

image

How was this problem resolved in the end?