Search code examples
node.jselectronelectron-packager

electron-packager is not packaging one of my directories with it. Any way to fix this?


So, I'm working with Electron.js recently and I've packaged a few applications. Today I decided to mess around with file saving and loading and such, and I got a very solid GUI working for saving, reading, editing, and deleting text files. The way it works is by writing all its files to a /saves/ directory inside the source code, and reading from the same place. It works perfectly when run using electron ., and so I know the issue is not with my code (so for that reason, I won't even include my code here, because I know what the issue is and I know it's not to do with my code)

The issue here is that, when I package my code using electron-packager . electron-tutorial-app --overwrite --asar --platform=win32 --arch=ia32 --icon=assets/icons/win/icon.ico --prune=true --out=release-builds --version-string.CompanyName=CE --version-string.FileDescription=CE --version-string.ProductName=\"Electron Tutorial App\" and run the outputted .exe file, I get the following error: Error: ENOENT: no such file or directory, scandir 'saves'

When I check the packaged files, I see that my /saves/ directory is, in fact, missing. It didn't get packaged along with the rest of the application, and it is now unable to be found. If I manually create a /saves/ directory inside the folder with the .exe file, then it works perfectly fine...

So what I'm wondering is, is there a way to tell electron-packager to package the /saves/ directory along with the rest of the project? Would that require me to add a flag to my command, or perhaps change my package.json ?

Here's my package.json:

{
    "name": "reading-and-writing-files",
    "version": "1.0.0",
    "description": "",
    "main": "main.js",
    "scripts": {
        "start": "electron .",
        "package-win": "electron-packager . electron-tutorial-app --overwrite --asar --platform=win32 --arch=ia32 --icon=assets/icons/win/icon.ico --prune=true --out=release-builds --version-string.CompanyName=CE --version-string.FileDescription=CE --version-string.ProductName=\"Reading and Writing Files\""
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "devDependencies": {
        "electron": "11.2.0",
        "electron-packager": "^15.2.0"
    }
}

Solution

  • By using the --asar switch in your package-win script, you're telling Electron Packager to write all your app's sources, including any directories, into an Asar archive. This is a TAR-like archive file located under resources/app.asar in your build directory (in your case, the complete path will be release-builds/<your app name and plattform>/resources/app.asar). Your app will run off of that just fine, because Electron handles loading files from the archive for you.

    However, as this is an archive, you cannot simply write files to it, and it seems that you're constructing the path to your /saves/ directory yourself and not using __dirname, which would point to the Asar archive.

    That being said, one possible workaround is to either not use Asar at all (by removing the --asar switch from your package-win script), which will leave your app's sources pretty much unprotected from curious eyes, or by checking if the saves/ directory exists and create it if not upon app startup, which seems like the way to go.

    To do so, add the following code anywhere in your main process' file, in your case main.js (I recommend putting this code near the top):

    const fs = require ("fs"),
          { app } = require ("electron"); // Remove if already loaded
    
    if (__dirname.includes (".asar")) {                            // (1)
        var exePath = app.getPath ("exe"),                         // (2)
            splitPath = exePath.replace (/\\/g, "/").split (/\//); // (3)
        
        splitPath.pop ();                                          // (4)
        exePath = splitPath.join ("/");                            // (5)
        
        var savesPath = exePath + "/path/to/saves";                // (6)
        if (!fs.existsSync (savesPath)) {
            fs.mkdirSync (savesPath);
        }
    }
    

    Explanations: Before doing anything (1), we check if we're running in production (i.e. off an Asar archive), by checking if the path to the current file includes ".asar". Then, we ask Electron to return the path to the current executable file (2). This can be used to determine the desired path of the saves directory. In (3) we remove all backslashes, which may occur on Windows, and replace them by forward slashes to ensure cross-platform compatibility and split the string by those so we get an array of path segments (i.e. directories). Then, we remove the last element from this array because this will be the name of your .exe on Windows or simply executable file on Linux or macOS (4). Then, we join the array again by using forward slashes in (5). This can then be used as a base path. In (6), we finally construct the path to the saves directory. Please note that this path will be relative to your executable file (on Windows .exe), so using /saves instead of /path/to/saves should suffice if you want the directory to reside directly in the same directory as your executable file. If no such path exists, your app will then create the directory.

    But please note that this method has some caveats: If the path exists, but is not a folder, your app will not attempt to create the directory, because it does not check if the existing path is a directory or a file or ... But in doing so, you would then have to delete this "offending" path, which is -- by some, including me -- considered a bad practice, because you do not know if the contents of the file are valuable to your user or not.