From 67905e01a79b6acf61397d85d2e20331e3033251 Mon Sep 17 00:00:00 2001 From: sdfasd Date: Sun, 30 Aug 2020 00:56:57 -0700 Subject: [PATCH] Refactor afterPack hook to be more maintainable Nothing is hardcoded in the hook any more, everything can be configured through the electron-builder configuration. --- afterpack.js | 156 +++++++++++++++++++++++++++--------------- electron-builder.yaml | 28 +++++++- package-lock.json | 15 ++++ package.json | 2 + 4 files changed, 141 insertions(+), 60 deletions(-) diff --git a/afterpack.js b/afterpack.js index 0a45596..a2022bd 100644 --- a/afterpack.js +++ b/afterpack.js @@ -1,14 +1,25 @@ 'use strict'; /* - This is a hack to get around the issues with electron-builder not including nested node_modules - We do this by simply making the asar ourselves using the parameters from the build. + This is a hack to get around the issues with electron-builder not including nested node_modules. + We do this by simply making the asar ourselves using the parameters from the build. + This takes the asar file that we have created, unpacks it, then copies in the nested node_modules + directories from the app directory. + + This also preserves any asar unpacked files that you may have had in the build, and unpacks any + nested node_modules files that might have been unpacked as well. + + This also assumes that you have some sort of beforeBuild hook that already trims and massages the + nested node_modules files to be what you want; if not, you may have some bloat (and some potential + breakage) */ +// Make sure you have these in your devDependencies in your root project. const glob = require('fast-glob'); const fs = require('fs-extra'); const { promisify } = require('util') const rimraf = promisify(require('rimraf')) const asar = require('asar'); +const micromatch = require("micromatch") const commonExclude = [ "!**/{test,__tests__,tests,powered-test,example,examples,CHANGELOG.md,README.md,README,readme.md,readme}", @@ -21,79 +32,110 @@ const commonExclude = [ "!**/{npm-debug.log,yarn.lock,.yarn-integrity,.yarn-metadata.json}" ] -exports.default = async function afterPackHook(context){ +function concatUnique(a,b) { + if (!Array.isArray(a) || !Array.isArray(b)){ + throw Error("both parameters must be arrays") + } + let arr = a.concat(b) + let seen = {}; + return arr.filter(function(item) { + return seen.hasOwnProperty(item) ? false : (seen[item] = true); + }); +} +// we're using the platform-specific parameters over the global parameters. +function globalOrPlatformParam(local,global){ + if (typeof local != 'undefined' && local.length != 0){ + return local; + } else if (typeof global != 'undefined' && global.length != 0){ + return global; + } + return undefined; +} + +function globalOrPlatformArrayParam(local, global){ + let param = globalOrPlatformParam(local, global) || [] + if (typeof param == "string"){ + param = [param] + } + return param; +} + +exports.default = async function afterPackHook(context){ + // Get build parameters from the context. const appDir = context.packager.info._appDir + "/" const platform = context.packager.platform.nodeName + const globalFilesParam = context.packager.info._configuration.files; + const platformFilesParam = context.packager.platformSpecificBuildOptions.files + const globalUnpackAsarParam = context.packager.info._configuration.asarUnpack; + const platformUnpackAsarParam = context.packager.platformSpecificBuildOptions.asarUnpack; + let resourcesDir = context.appOutDir + "/resources/" - // exception for resources dir for mac + // exception for resources dir for mac; win and linux use the same directory. if (platform == "darwin"){ resourcesDir = context.appOutDir + "/" + context.packager.appInfo.productFilename + ".app/Contents/Resources/" } - const asarAppDir = resourcesDir + "app/" + const asarAppUnpackedDir = resourcesDir + "app.asar.unpacked/" - let globPatterns = context.packager.platformSpecificBuildOptions.files || context.packager._configuration.files || [] - let asarUnpackPattern = context.packager.platformSpecificBuildOptions.asarUnpack - - // a limitation of this method is that the asarUnpack option can only be a single string - if (typeof asarUnpackPattern != "string"){ - if (Array.isArray(asarUnpackPattern) && asarUnpackPattern.length == 1){ - asarUnpackPattern = asarUnpackPattern[0] - } else { - throw Error("asarUnpack pattern can only be one string!") - } - } - - // remove current asar files - await rimraf(resourcesDir + "app.asar") - await rimraf(resourcesDir + "app") - await rimraf(resourcesDir + "app.asar.unpacked") + // The Platform-specific build options override the common config, so attempt to use those first. + let globPatterns = globalOrPlatformArrayParam(platformFilesParam, globalFilesParam) // electron-builder automatically adds this to the files parameter, so we have to too if (!globPatterns.includes("**/*")){ globPatterns.push("**/*") } - globPatterns = globPatterns.concat(commonExclude) - - // Take the files from the app directory and copy them to make the asar.app directory - // according to the glob specified in the electron-builder config for this target - let files = glob.sync(globPatterns, { dot:true, cwd: appDir}) - - await new Promise ((resolve) => { - files.forEach(async (file, index, array)=>{ - await fs.copy(appDir + file, asarAppDir + file) - if (index == array.length -1){ - resolve() - } - }) - }).catch(console.error) + // Add the common exclusions to the patterns + globPatterns = concatUnique(globPatterns,commonExclude); - // The only part that's hardcoded and not dependent on the electron-build config, - // remove the unnecessary node-native files from the asar.app dir - let unpackedFiles = glob.sync(asarUnpackPattern, {dot:true, cwd: asarAppDir}) - await new Promise ((resolve) => { - unpackedFiles.forEach(async (file, index, array)=>{ - if (platform == "win32") { - if (file.includes(".node") && (file.includes("_linux") || file.includes("_darwin"))){ - await fs.remove(asarAppDir + file) - } - } - else{ - if (file.includes(".node") && !file.includes("_" + platform) ){ - await fs.remove(asarAppDir + file) - } - } + // Find all the nested node_modules files in the app directory + let nestedNMFiles = glob.sync("+(**/node_modules/**/*|!node_modules/**/*)", {cwd: appDir, dot:true}) - if (index == array.length -1){ - resolve() - } - }) - }).catch(console.error) + // filter out the ones not matched by the file globs in the builder config + let filteredNestedFiles = micromatch(nestedNMFiles, globPatterns, {matchBase:true}) - // build the asar from the newly created app dir, unpacking the files necessary according to the glob - await asar.createPackageWithOptions(asarAppDir, resourcesDir + "app.asar", {unpack: asarUnpackPattern}) + // This is the directory we're copying everything to, to create the asar + let asarAppDir = resourcesDir + "app/" + + // Get a listing of all the files in the app.asar.unpacked dir, so we can create a franken-glob + // to pass to asar when packing. + let unpackedFileList = glob.sync("**/*", {cwd:asarAppUnpackedDir, dot:true}) + let unpackPattern = "{" + unpackedFileList.forEach((file,idx,arr)=>{ + unpackPattern += asarAppDir + file + ',' + }) + + // Get any user-defined asarUnpack patterns in case we need to unpack some hoisted node_modules, + // and add any to the pattern. + let userUnpackGlob = globalOrPlatformArrayParam(platformUnpackAsarParam, globalUnpackAsarParam) + let nestedFilesToUnpack = micromatch(nestedNMFiles, userUnpackGlob, {matchBase:true}) + nestedFilesToUnpack.forEach((file)=>{ + unpackPattern += asarAppDir + file + ',' + }) + // trailing commas don't matter in this pattern. + unpackPattern += '}' + + // Combine the nested files to unpack with the filtered nested files, as electron-builder + // sometimes does weird things when file globs and the asarUnpack globs are used together; + // Files that don't match the file globs but do match the asarUnpack glob are included in the build. + let nestedFilesToCopy = concatUnique(filteredNestedFiles, nestedFilesToUnpack); + + // Now we have the necessary file lists, create the asar. + + // Unpack everything in app.asar to the resources/app dir. + // This also copies all the files in 'app.asar.unpacked' to here as well. + asar.extractAll(resourcesDir + "app.asar", asarAppDir) + await rimraf(resourcesDir + "app.asar") + await rimraf(asarAppUnpackedDir) + + // Copy the nested node_modules files to the extracted directory. + nestedFilesToCopy.forEach((file)=>{ + fs.copySync(appDir + file, asarAppDir + file) + }) + // build the asar + await asar.createPackageWithOptions(asarAppDir, resourcesDir + "app.asar", {unpack: unpackPattern}) await rimraf(asarAppDir) + return true }; diff --git a/electron-builder.yaml b/electron-builder.yaml index ee83865..485e382 100644 --- a/electron-builder.yaml +++ b/electron-builder.yaml @@ -4,36 +4,58 @@ productName: Lightcord directories: app: distApp output: builds +# This is to get around the bug in electron-builder with not including nested node_modules. afterPack: "./afterpack.js" win: target: - target: zip arch: - ia32 + - target: portable + arch: + - ia32 icon: app.ico publisherName: Lightcord files: + - "**/*" - "!**/*.ts" - "!**/*.so" - "!**/*.4" - "!**/*.dylib" - asarUnpack: "**/*.{node,dll}" + - "!**/*_darwin.node" + - "!**/*_linux.node" + # This gets passed to the afterPack hook to help recreate the asar file. This must be a single string. + asarUnpack: + - "**/*.node" + - "**/*.dll" linux: target: - zip + - AppImage icon: discord.png files: + - "**/*" - "!**/*.ts" - "!**/*.dll" - "!**/*.dylib" - asarUnpack: "**/*.{node,so.4}" + - "!**/discord*.node" + - "**/*_linux.node" + asarUnpack: + - "**/*_linux.node" + - "**/*.so.4" mac: target: - zip icon: app_icon_darwin.icns files: + - "**/*" - "!**/*.ts" - "!**/*.dll" - "!**/*.so" - "!**/*.4" - asarUnpack: "**/*.{node,dylib}" + - "!**/discord*.node" + - "**/*_darwin.node" + asarUnpack: + - "**/*_linux.node" + - "**/*.dylib" + diff --git a/package-lock.json b/package-lock.json index 1c4f318..2e6e430 100644 --- a/package-lock.json +++ b/package-lock.json @@ -97,6 +97,12 @@ "integrity": "sha512-+KQ+/koZ7sJXnf5cnCANofY6yXAdYJNEoVZEuWcwJfuWbUp9u6l09I7KhwD+ivU+cdz7JId4V5ukxscWtHdSuw==", "dev": true }, + "@types/braces": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/braces/-/braces-3.0.0.tgz", + "integrity": "sha512-TbH79tcyi9FHwbyboOKeRachRq63mSuWYXOflsNO9ZyE5ClQ/JaozNKl+aWUq87qPNsXasXxi2AbgfwIJ+8GQw==", + "dev": true + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", @@ -141,6 +147,15 @@ "@types/node": "*" } }, + "@types/micromatch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.1.tgz", + "integrity": "sha512-my6fLBvpY70KattTNzYOK6KU1oR1+UCz9ug/JbcF5UrEmeCt9P7DV2t7L8+t18mMPINqGQCE4O8PLOPbI84gxw==", + "dev": true, + "requires": { + "@types/braces": "*" + } + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", diff --git a/package.json b/package.json index b96167a..e3513ab 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "devDependencies": { "@types/auto-launch": "^5.0.1", "@types/electron-devtools-installer": "^2.2.0", + "@types/micromatch": "^4.0.1", "@types/mkdirp": "^1.0.0", "@types/node": "12.12.39", "@types/rimraf": "^3.0.0", @@ -48,6 +49,7 @@ "electron-builder": "^22.8.0", "fast-glob": "^3.2.4", "fs-extra": "^9.0.1", + "micromatch": "^4.0.2", "terser": "^4.7.0", "typescript": "^3.9.7", "yazl": "^2.5.1"