diff --git a/.gitignore b/.gitignore index 81ef253..bcda070 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,9 @@ node_modules dist -entry-asar/*.js* -entry-asar/*.ts +entry-asar/cjs/*.js* +entry-asar/cjs/*.d.ts +entry-asar/esm/*.?js* +entry-asar/esm/*.d.?ts *.app test/fixtures/apps coverage diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..f1c7ab5 --- /dev/null +++ b/.npmignore @@ -0,0 +1,5 @@ +# npmignore overrides .gitignore for yarn pack +# Only exclude source files, not built files +entry-asar/**/*.mts +entry-asar/**/*.ts +entry-asar/**/tsconfig.json diff --git a/entry-asar/has-asar.ts b/entry-asar/cjs/has-asar.ts similarity index 100% rename from entry-asar/has-asar.ts rename to entry-asar/cjs/has-asar.ts diff --git a/entry-asar/no-asar.ts b/entry-asar/cjs/no-asar.ts similarity index 100% rename from entry-asar/no-asar.ts rename to entry-asar/cjs/no-asar.ts diff --git a/entry-asar/cjs/tsconfig.json b/entry-asar/cjs/tsconfig.json new file mode 100644 index 0000000..ea4d52f --- /dev/null +++ b/entry-asar/cjs/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": ".", + }, + "include": [ + ".", + "../ambient.d.ts" + ], + "exclude": [] +} diff --git a/entry-asar/esm/has-asar.mts b/entry-asar/esm/has-asar.mts new file mode 100644 index 0000000..3bee10f --- /dev/null +++ b/entry-asar/esm/has-asar.mts @@ -0,0 +1,28 @@ +import { app } from 'electron'; +import { createRequire } from 'node:module'; +import path from 'node:path'; + +if (process.arch === 'arm64') { + await setPaths('arm64'); +} else { + await setPaths('x64'); +} + +async function setPaths(platform: string) { + // This should return the full path, ending in something like + // Notion.app/Contents/Resources/app.asar + const appPath = app.getAppPath(); + const asarFile = `app-${platform}.asar`; + + // Maybe we'll handle this in Electron one day + if (path.basename(appPath) === 'app.asar') { + const platformAppPath = path.join(path.dirname(appPath), asarFile); + + // This is an undocumented API. It exists. + app.setAppPath(platformAppPath); + } + + const require = createRequire(import.meta.url); + process._archPath = require.resolve(`../${asarFile}`); + await import(process._archPath); +} diff --git a/entry-asar/esm/no-asar.mts b/entry-asar/esm/no-asar.mts new file mode 100644 index 0000000..72b28e7 --- /dev/null +++ b/entry-asar/esm/no-asar.mts @@ -0,0 +1,29 @@ +import { app } from 'electron'; +import { createRequire } from 'node:module'; +import path from 'node:path'; + +if (process.arch === 'arm64') { + await setPaths('arm64'); +} else { + await setPaths('x64'); +} + +async function setPaths(platform: string) { + // This should return the full path, ending in something like + // Notion.app/Contents/Resources/app + const appPath = app.getAppPath(); + const appFolder = `app-${platform}`; + + // Maybe we'll handle this in Electron one day + if (path.basename(appPath) === 'app') { + const platformAppPath = path.join(path.dirname(appPath), appFolder); + + // This is an undocumented private API. It exists. + app.setAppPath(platformAppPath); + } + + const require = createRequire(import.meta.url); + process._archPath = require.resolve(`../${appFolder}`); + + await import(process._archPath); +} diff --git a/entry-asar/esm/tsconfig.json b/entry-asar/esm/tsconfig.json new file mode 100644 index 0000000..adf967d --- /dev/null +++ b/entry-asar/esm/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "module": "ESNext", + "target":"ESNext", + "outDir": ".", + "moduleResolution": "bundler" + }, + "include": [ + ".", + "../ambient.d.ts" + ], + "exclude": [] +} diff --git a/package.json b/package.json index f6a4688..5e5a976 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,12 @@ }, "files": [ "dist/*", - "entry-asar/*", - "!entry-asar/**/*.ts", + "entry-asar/**/*", + "!entry-asar/**/has-asar.ts", + "!entry-asar/**/no-asar.ts", + "!entry-asar/**/has-asar.mts", + "!entry-asar/**/no-asar.mts", + "!entry-asar/**/tsconfig.json", "README.md" ], "author": "Samuel Attard", @@ -28,7 +32,7 @@ "provenance": true }, "scripts": { - "build": "tsc -p tsconfig.json && tsc -p tsconfig.entry-asar.json", + "build": "tsc -p tsconfig.json && tsc -p entry-asar/esm/tsconfig.json && tsc -p entry-asar/cjs/tsconfig.json", "build:docs": "npx typedoc", "lint": "prettier --check \"{src,entry-asar,test}/**/*.ts\" \"*.ts\"", "prettier:write": "prettier --write \"{src,entry-asar,test}/**/*.ts\" \"*.ts\"", diff --git a/src/index.ts b/src/index.ts index 8593496..4c0f6b6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -254,17 +254,30 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise = const entryAsar = path.resolve(tmpDir, 'entry-asar'); await fs.promises.mkdir(entryAsar, { recursive: true }); - await fs.promises.cp( - path.resolve(import.meta.dirname, '..', 'entry-asar', 'no-asar.js'), - path.resolve(entryAsar, 'index.js'), - ); + let pj = JSON.parse( await fs.promises.readFile( path.resolve(opts.x64AppPath, 'Contents', 'Resources', 'app', 'package.json'), 'utf8', ), ); - pj.main = 'index.js'; + + // Load a shim that redirects to the correct folder for the architecture. + // This needs to be a different file depending on if the app entrypoint is CommonJS or ESM. + if (pj.type === 'module' || pj.main.endsWith('.mjs')) { + await fs.promises.cp( + path.resolve(import.meta.dirname, '..', 'entry-asar', 'esm', 'no-asar.mjs'), + path.resolve(entryAsar, 'index.mjs'), + ); + pj.main = 'index.mjs'; + } else { + await fs.promises.cp( + path.resolve(import.meta.dirname, '..', 'entry-asar', 'cjs', 'no-asar.js'), + path.resolve(entryAsar, 'index.js'), + ); + pj.main = 'index.js'; + } + await fs.promises.writeFile( path.resolve(entryAsar, 'package.json'), JSON.stringify(pj) + '\n', @@ -337,10 +350,6 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise = const entryAsar = path.resolve(tmpDir, 'entry-asar'); await fs.promises.mkdir(entryAsar, { recursive: true }); - await fs.promises.cp( - path.resolve(import.meta.dirname, '..', 'entry-asar', 'has-asar.js'), - path.resolve(entryAsar, 'index.js'), - ); let pj = JSON.parse( ( await asar.extractFile( @@ -349,7 +358,23 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise = ) ).toString('utf8'), ); - pj.main = 'index.js'; + + // Load a shim that redirects to the correct `app.asar` for the architecture. + // This needs to be a different file depending on if the app entrypoint is CommonJS or ESM. + if (pj.type === 'module' || pj.main.endsWith('.mjs')) { + await fs.promises.cp( + path.resolve(import.meta.dirname, '..', 'entry-asar', 'esm', 'has-asar.mjs'), + path.resolve(entryAsar, 'index.mjs'), + ); + pj.main = 'index.mjs'; + } else { + await fs.promises.cp( + path.resolve(import.meta.dirname, '..', 'entry-asar', 'cjs', 'has-asar.js'), + path.resolve(entryAsar, 'index.js'), + ); + pj.main = 'index.js'; + } + await fs.promises.writeFile( path.resolve(entryAsar, 'package.json'), JSON.stringify(pj) + '\n', diff --git a/tsconfig.entry-asar.json b/tsconfig.entry-asar.json deleted file mode 100644 index 7f3e899..0000000 --- a/tsconfig.entry-asar.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "target": "es2017", - "lib": [ - "es2017" - ], - "sourceMap": true, - "strict": true, - "outDir": "entry-asar", - "types": [ - "node", - ], - "allowSyntheticDefaultImports": true, - "moduleResolution": "node", - "esModuleInterop": true, - "declaration": false - }, - "include": [ - "entry-asar" - ], - "exclude": [] -}