Skip to content

Instantly share code, notes, and snippets.

@robin-hartmann
Last active January 3, 2024 08:00
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save robin-hartmann/ad6ffc19091c9e661542fbf178647047 to your computer and use it in GitHub Desktop.
npm workspaces support for electron-forge
// based on https://github.com/electron-userland/electron-forge/issues/2306#issuecomment-1034882039
'use strict';
/**
* @typedef {{
* new (options: { path: string }): {
* loadActual(): Promise<Node>
* }
* }} Arborist
*/
const fs = require('fs/promises');
const path = require('path');
/** @type {Arborist} */
// @ts-ignore missing types for @npmcli/arborist
const arborist = require('@npmcli/arborist');
const { findRoot } = require('@manypkg/find-root');
/**
* @typedef {{
* workspace: boolean;
* type: 'prod' | 'dev' | 'peer' | 'optional'
* to: Node;
* }} Edge
*/
/**
* @typedef {{
* isLink: boolean;
* location: string;
* realpath: string;
* target: Node;
* edgesOut: Map<string, Edge>;
* }} Node
*/
/** @type {(node: Node) => Node} */
const resolveLink = (node) => (node.isLink ? resolveLink(node.target) : node);
/** @type {(node: Node, realPath: string) => Node | undefined} */
const getWorkspaceByPath = (node, realPath) =>
[...node.edgesOut.values()]
.filter((depEdge) => depEdge.workspace)
.map((depEdge) => resolveLink(depEdge.to))
.find((depNode) => depNode.realpath === realPath);
/** @type {(node: Node) => Node[]} */
const collectProdDeps = (node) =>
[...node.edgesOut.values()]
.filter((depEdge) => depEdge.type === 'prod')
.map((depEdge) => resolveLink(depEdge.to))
.flatMap((depNode) => [depNode, ...collectProdDeps(depNode)]);
/** @type {(source: string, destination: string) => Promise<void>} */
const bundle = async (source, destination) => {
const root = await findRoot(source);
const rootNode = await new arborist({ path: root.rootDir }).loadActual();
const sourceNode = getWorkspaceByPath(rootNode, source);
if (!sourceNode) {
throw new Error("couldn't find source node");
}
const prodDeps = collectProdDeps(sourceNode);
for (const dep of prodDeps) {
const dest = path.join(destination, dep.location);
await fs.cp(dep.realpath, dest, {
recursive: true,
errorOnExist: false,
});
}
};
module.exports = { bundle };
'use strict';
const { bundle } = require('./bundler');
module.exports = {
packagerConfig: {
prune: false, // required for the workaround below to work
},
hooks: {
packageAfterCopy: async (
/** @type {any} */ forgeConfig,
/** @type {string} */ buildPath,
/** @type {string} */ electronVersion,
/** @type {string} */ platform,
/** @type {string} */ arch,
) => {
// this is a workaround until we find a proper solution
// for running electron-forge in a mono repository
await bundle(__dirname, buildPath);
},
},
};
@G-Ray
Copy link

G-Ray commented May 31, 2023

Thank you @robin-hartmann !
I needed to add recursive: true to fs.cp.
Also, in order to copy modules from workspaces I wrote:

for (const dep of prodDeps) {
  const dest = dep.isWorkspace
    ? path.join(destination, `node_modules/${dep.packageName}`)
    : path.join(destination, dep.location)

  await fs.cp(dep.realpath, dest, {
    recursive: true,
    errorOnExist: false,
    dereference: true,
  })
}

@gjtiquia
Copy link

gjtiquia commented Jan 3, 2024

Thanks @robin-hartmann and @G-Ray! Was a headache for the past few days and this solution worked!

My project uses Vite and TypeScript, so if anyone is interested I rewrote the above code in TypeScript, combining @G-Ray 's code snippet in this gist.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment