windows-store-appselectronelectron-builderdesktop-bridge

Package APPX in APPXBUNDLE


I currently have one UWP app submitted in the Windows Store, now I want to publish an update. This update is based on Electron, so its not an UWP app anymore.

I just wanted to submit the Electron Application to the Windows Store, but I am getting this error message:A previous submission for this app was released with a Windows 10 .msixbundle or .appxbundle. Subsequent submissions must continue to contain a Windows 10 .msixbundle or .appxbundle.

The Electron app is packaged with electron-builder and the resulting file is an appx file. Previously I packaged the app with Visual Studio and the resulting file was an appxbundle.

According to this error message I must submit an .msixbundle or .appxbundle file. Can I just create an .appxbundle that contains the Electron .appx file and then submit the app to the Windows Store?

Thanks


Solution

  • Here is the promised answer to my own question.

    As I already mentioned you must stick with the bundle format you initially submitted your App to the Microsoft App Store. So .appxbundle in my case.

    I am using electron-builder which spits out an .appx which I then bundle into an .appxbundle using an afterAllArtifactBuildHook

    Here is the afterAllArtifactBuildHook.js located in the build/hooks folder of my electron project. It automatically utilizes the already cached winCodeSign tools from electron-builder so it ensures everything matches.

    Note: This is geared towords macOS and a functional Parallels desktop installation with Windows 10 already setup to build .appx files

    var path = require("path"),
        fs = require("fs"),
        glob = require("glob"),
        childProcess = require('child_process');
    
    exports.default = function(buildResult) {
        buildResult.artifactPaths.forEach(function(appxPath) {
            if (appxPath.endsWith(".appx")) {
                convertAppx2AppxBundle(appxPath);
            }
        });
    };
    
    /**
     * Converts a Unix Path to a Windows conforming path by replacing any "/" with "\"
     * @return {string}
     */
    String.prototype.windowsify = function windowsify() {
        return this.replace(/\//g, "\\");
    }
    
    /**
     * Converts the given appx to an appxbundle used for Windows Store submission
     * @note This function utalizes the parallels desktop tool "prlctl" to execute windows commands via macOS
     * @param appxPath
     */
    function convertAppx2AppxBundle(appxPath) {
        try {
            console.log("Converting Appx to Appxbundle...")
            // We'll use electron-builder's winCodeSign tools which are conveniently already available in the following dir
            var electronBuilderDir = path.join(process.env.HOME, "Library/Caches/electron-builder"), // Don't use "~" it will just not work, trust me...
                // Will use the first matching windowsCodeSigning path as we may have multiple versions installed
                makeAppX = glob.sync(path.join(electronBuilderDir, "winCodeSign", "winCodeSign-*", "windows-10", "x64", "makeappx.exe"))[0],
                appxBundleMapFile = // This file is required by the makeappx.exe to generate the appxbundle
                    '[Files]\n' +
                    '"\\\\Mac\\AllFiles' + appxPath.replace(/\//g, "\\") + '"\t\t"Loxone.appx"';
    
            // Save the generated AppxBundle.map file to the filesystem to make it available for the Windows VM
            fs.writeFileSync(path.join(__dirname, "..", "AppxBundle.map"), appxBundleMapFile, { flag: "w" });
    
            var prlctlArgs = [
                'exec',
                '"Windows 10"', // the name of the specific Windows VM
                '--current-user', // We want to execute the following command with the current user
                // From this point on its the default Windows CLI command for converting an .appx to an .apppxbundle
                // Its important to know that these parts must conform to the Windows style, hence the windowsify function
                // We also need to use double quotation for the network shares due to how childProcess.execSync works...
                '"\\\\\\Mac\\AllFiles' + makeAppX.windowsify() + '"',
                'bundle',
                '/f',
                '"\\\\\\Mac\\AllFiles' + (__dirname.replace("/hooks", "") + "/AppxBundle.map").windowsify() + '"',
                '/p',
                '"\\\\\\Mac\\AllFiles' + appxPath.windowsify() + 'bundle"',
                '/o'
            ];
            // Now just execute the command to convert the appx to an appxbundle
            childProcess.execSync('prlctl ' + prlctlArgs.join(" "), { stdio: 'inherit' });
            console.log("Done converting Appx to Appxbundle");
        } catch (e) {
            console.warn("Couldn't convert appx two appxbundle!");
            console.error(e);
        }
    }