diff --git a/.gitignore b/.gitignore index d0ed1a2..738e767 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ node_modules dist entry-asar/*.js* entry-asar/*.ts +entry-asar/esm/*.mjs* *.app test/fixtures/apps coverage diff --git a/entry-asar/esm/has-asar.mts b/entry-asar/esm/has-asar.mts new file mode 100644 index 0000000..a4fd8ba --- /dev/null +++ b/entry-asar/esm/has-asar.mts @@ -0,0 +1,34 @@ +import { app } from 'electron'; +import { createRequire } from 'node:module'; +import path from 'node:path'; + +const require = createRequire(import.meta.url); + +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); + } + + process._archPath = require.resolve(`../${asarFile}`); + try { + await import(process._archPath); + } catch (err) { + console.error(err); + process.exit(1); + } +} diff --git a/entry-asar/esm/no-asar.mts b/entry-asar/esm/no-asar.mts new file mode 100644 index 0000000..82bed04 --- /dev/null +++ b/entry-asar/esm/no-asar.mts @@ -0,0 +1,34 @@ +import { app } from 'electron'; +import { createRequire } from 'node:module'; +import path from 'node:path'; + +const require = createRequire(import.meta.url); + +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); + } + + process._archPath = require.resolve(`../${appFolder}`); + try { + await import(process._archPath); + } catch (err) { + console.error(err); + process.exit(1); + } +} diff --git a/entry-asar/esm/tsconfig.json b/entry-asar/esm/tsconfig.json new file mode 100644 index 0000000..5dc7d68 --- /dev/null +++ b/entry-asar/esm/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "module": "nodenext", + "moduleResolution": "nodenext", + "target": "es2022", + "lib": ["es2022"], + "sourceMap": true, + "strict": true, + "rootDir": ".", + "outDir": ".", + "types": ["node"], + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "declaration": false, + "ignoreDeprecations": "6.0" + }, + "include": ["*.mts", "../ambient.d.ts"], + "exclude": [] +} diff --git a/package.json b/package.json index 991a421..01aee06 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,10 @@ "files": [ "dist/*", "entry-asar/*", + "entry-asar/esm/*", "!entry-asar/**/*.ts", + "!entry-asar/**/*.mts", + "!entry-asar/**/tsconfig.json", "README.md" ], "author": "Samuel Attard", @@ -28,7 +31,7 @@ "provenance": true }, "scripts": { - "build": "tsc -p tsconfig.json && tsc -p tsconfig.entry-asar.json", + "build": "tsc -p tsconfig.json && tsc -p tsconfig.entry-asar.json && tsc -p entry-asar/esm/tsconfig.json", "build:docs": "typedoc", "lint": "oxfmt --check . && oxlint", "lint:fix": "oxfmt --write . && oxlint --fix", @@ -44,6 +47,7 @@ "@types/node": "~22.10.7", "@types/plist": "^3.0.4", "cross-zip": "^4.0.0", + "electron43": "npm:electron@^43.0.0", "husky": "^9.1.7", "lint-staged": "^16.1.0", "oxfmt": "^0.44.0", diff --git a/src/asar-utils.ts b/src/asar-utils.ts index bbdcc14..0c0bc43 100644 --- a/src/asar-utils.ts +++ b/src/asar-utils.ts @@ -15,6 +15,11 @@ export enum AsarMode { HAS_ASAR, } +export enum EntrypointModule { + ESM = 'esm', + CJS = 'cjs', +} + export type MergeASARsOptions = { x64AsarPath: string; arm64AsarPath: string; @@ -36,6 +41,40 @@ export const detectAsarMode = async (appPath: string) => { return AsarMode.HAS_ASAR; }; +/** + * Determine an app entrypoint's module format from its package.json using + * Node's own resolution rules. Anything we cannot positively identify as ESM + * is treated as CJS, so we never ship an ESM shim on a guess. + */ +export const detectEntrypointModule = (packageJson: unknown): EntrypointModule => { + if (typeof packageJson !== 'object' || packageJson === null) { + return EntrypointModule.CJS; + } + const pj = packageJson as { main?: unknown; type?: unknown }; + const main = typeof pj.main === 'string' && pj.main.length > 0 ? pj.main : 'index.js'; + if (main.endsWith('.mjs')) return EntrypointModule.ESM; + if (main.endsWith('.cjs')) return EntrypointModule.CJS; + // .js / .json / .node / extensionless -> governed by the package "type" field + return pj.type === 'module' ? EntrypointModule.ESM : EntrypointModule.CJS; +}; + +/** + * Given the detected module format of each architecture's entrypoint, decide + * which shim to ship. Refuses to build a universal app when the two + * architectures disagree, since a single shim cannot satisfy both. + */ +export const resolveShimModule = ( + x64: EntrypointModule, + arm64: EntrypointModule, +): EntrypointModule => { + if (x64 !== arm64) { + throw new Error( + `Can't create universal binary: the x64 app entrypoint is ${x64.toUpperCase()} but the arm64 app entrypoint is ${arm64.toUpperCase()}. Both architectures must use the same module system (CommonJS or ESM).`, + ); + } + return x64; +}; + export const generateAsarIntegrity = (asarPath: string) => { return { algorithm: 'SHA256' as const, diff --git a/src/index.ts b/src/index.ts index 1fb179c..8ee8329 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,7 +7,15 @@ import { promisify } from 'node:util'; import * as asar from '@electron/asar'; import plist from 'plist'; -import { AsarMode, detectAsarMode, isUniversalMachO, mergeASARs } from './asar-utils.js'; +import { + AsarMode, + detectAsarMode, + detectEntrypointModule, + EntrypointModule, + isUniversalMachO, + mergeASARs, + resolveShimModule, +} from './asar-utils.js'; import { AppFile, AppFileType, @@ -270,17 +278,37 @@ 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'; + const arm64Pj = JSON.parse( + await fs.promises.readFile( + path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app', 'package.json'), + 'utf8', + ), + ); + const shimModule = resolveShimModule( + detectEntrypointModule(pj), + detectEntrypointModule(arm64Pj), + ); + + if (shimModule === EntrypointModule.ESM) { + 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', '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', @@ -353,10 +381,7 @@ 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( @@ -365,7 +390,32 @@ export const makeUniversalApp = async (opts: MakeUniversalOpts): Promise = ) ).toString('utf8'), ); - pj.main = 'index.js'; + const arm64Pj = JSON.parse( + asar + .extractFile( + path.resolve(opts.arm64AppPath, 'Contents', 'Resources', 'app.asar'), + 'package.json', + ) + .toString('utf8'), + ); + const shimModule = resolveShimModule( + detectEntrypointModule(pj), + detectEntrypointModule(arm64Pj), + ); + + if (shimModule === EntrypointModule.ESM) { + 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', '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/test/detect-entrypoint.spec.ts b/test/detect-entrypoint.spec.ts new file mode 100644 index 0000000..06eac9b --- /dev/null +++ b/test/detect-entrypoint.spec.ts @@ -0,0 +1,85 @@ +import { describe, expect, it } from 'vitest'; + +import { detectEntrypointModule, EntrypointModule, resolveShimModule } from '../dist/asar-utils.js'; + +describe('detectEntrypointModule', () => { + it('detects ESM when main has a .mjs extension', () => { + expect(detectEntrypointModule({ main: 'index.mjs' })).toBe(EntrypointModule.ESM); + }); + + it('detects ESM for a nested .mjs main', () => { + expect(detectEntrypointModule({ main: 'src/app.mjs' })).toBe(EntrypointModule.ESM); + }); + + it('detects CJS when main has a .cjs extension', () => { + expect(detectEntrypointModule({ main: 'index.cjs' })).toBe(EntrypointModule.CJS); + }); + + it('lets the .cjs extension win over a "module" type', () => { + expect(detectEntrypointModule({ main: 'index.cjs', type: 'module' })).toBe( + EntrypointModule.CJS, + ); + }); + + it('lets the .mjs extension win over a "commonjs" type', () => { + expect(detectEntrypointModule({ main: 'index.mjs', type: 'commonjs' })).toBe( + EntrypointModule.ESM, + ); + }); + + it('detects ESM for a .js main when type is "module"', () => { + expect(detectEntrypointModule({ main: 'index.js', type: 'module' })).toBe(EntrypointModule.ESM); + }); + + it('detects CJS for a .js main when type is "commonjs"', () => { + expect(detectEntrypointModule({ main: 'index.js', type: 'commonjs' })).toBe( + EntrypointModule.CJS, + ); + }); + + it('defaults a .js main with no type to CJS', () => { + expect(detectEntrypointModule({ main: 'index.js' })).toBe(EntrypointModule.CJS); + }); + + it('detects ESM when type is "module" and there is no main', () => { + expect(detectEntrypointModule({ type: 'module' })).toBe(EntrypointModule.ESM); + }); + + it('defaults an empty package.json to CJS', () => { + expect(detectEntrypointModule({})).toBe(EntrypointModule.CJS); + }); + + it('treats null as CJS', () => { + expect(detectEntrypointModule(null)).toBe(EntrypointModule.CJS); + }); + + it('treats a non-object as CJS', () => { + expect(detectEntrypointModule(42)).toBe(EntrypointModule.CJS); + }); +}); + +describe('resolveShimModule', () => { + it('returns CJS when both arches are CJS', () => { + expect(resolveShimModule(EntrypointModule.CJS, EntrypointModule.CJS)).toBe( + EntrypointModule.CJS, + ); + }); + + it('returns ESM when both arches are ESM', () => { + expect(resolveShimModule(EntrypointModule.ESM, EntrypointModule.ESM)).toBe( + EntrypointModule.ESM, + ); + }); + + it('throws when x64 is ESM but arm64 is CJS', () => { + expect(() => resolveShimModule(EntrypointModule.ESM, EntrypointModule.CJS)).toThrow( + /ESM[\s\S]*CJS/, + ); + }); + + it('throws when x64 is CJS but arm64 is ESM', () => { + expect(() => resolveShimModule(EntrypointModule.CJS, EntrypointModule.ESM)).toThrow( + /CJS[\s\S]*ESM/, + ); + }); +}); diff --git a/test/globalSetup.ts b/test/globalSetup.ts index baf0693..e113403 100644 --- a/test/globalSetup.ts +++ b/test/globalSetup.ts @@ -1,8 +1,49 @@ import { execFileSync } from 'node:child_process'; import fs from 'node:fs'; +import os from 'node:os'; import path from 'node:path'; -import { appsDir, asarsDir, downloadElectronZip, fixtureDir, templateApp } from './util.js'; +import { createPackage } from '@electron/asar'; + +import { + appsDir, + asarsDir, + downloadElectronZip, + ELECTRON_43_VERSION, + fixtureDir, + templateApp, +} from './util.js'; + +// Build an app source directory whose entrypoint is an ES module. The +// `package.json` and `index.mjs` are byte-identical across arches (so +// `makeUniversalApp`'s identical-SHA check for plain files passes); divergence +// is introduced via a uniquely-named `.bin` file. +// +// For the no-asar case the source directory is copied straight into +// `Contents/Resources/app`, which sits in the bundle tree that +// `makeUniversalApp` scans for mach-o parity. A `.bin` file is classified as a +// V8 snapshot (SNAPSHOT) and so is excluded from that parity check, exactly the +// way the existing non-ESM no-asar shim fixtures diverge (see the +// `should shim two different app folders` test, which uses `hello-world.bin` / +// `i-aint-got-no-rhythm.bin`). A loose plain file (e.g. a `.txt`) would instead +// be treated as a unique PLAIN file and trip the parity guard, so we avoid it. +const createEsmAppDir = async (name: string, extraFile?: string) => { + const dir = await fs.promises.mkdtemp(path.join(os.tmpdir(), `${name}-`)); + await fs.promises.writeFile( + path.join(dir, 'package.json'), + JSON.stringify({ name: 'app', type: 'module', main: 'index.mjs' }) + '\n', + 'utf8', + ); + await fs.promises.writeFile( + path.join(dir, 'index.mjs'), + `console.log('I am an ESM app', process.arch);\nprocess.exit(0);\n`, + 'utf8', + ); + if (extraFile) { + await fs.promises.writeFile(path.join(dir, extraFile), 'extra\n', 'utf8'); + } + return dir; +}; // generates binaries from hello-world.c // hello-world-universal, hello-world-x86_64, hello-world-arm64 @@ -35,6 +76,10 @@ export default async () => { // the concurrent test suite) so they all hit the cached zip. await downloadElectronZip('arm64'); await downloadElectronZip('x64'); + // The ESM fixtures below run on Electron 43, so warm that version's cache for + // both arches too before the parallel `templateApp` calls race on it. + await downloadElectronZip('arm64', ELECTRON_43_VERSION); + await downloadElectronZip('x64', ELECTRON_43_VERSION); await Promise.all([ templateApp('Arm64Asar.app', 'arm64', async (appPath) => { @@ -87,4 +132,73 @@ export default async () => { ); }), ]); + + // ESM entrypoint fixtures (regression coverage for ERR_REQUIRE_ESM when the + // x64/arm64 asars diverge). The two arches differ so a shim is generated. + const x64EsmAsarDir = await createEsmAppDir('X64AsarEsm'); + const arm64EsmAsarDir = await createEsmAppDir('Arm64AsarEsmExtraFile', 'extra-file.txt'); + // The no-asar fixtures are copied straight into the bundle tree, so they must + // diverge via uniquely-named `.bin` files (excluded from the mach-o parity + // guard as V8 snapshots) rather than a plain file. This mirrors the existing + // non-ESM `should shim two different app folders` test exactly. + const x64EsmNoAsarDir = await createEsmAppDir('X64NoAsarEsm', 'hello-world.bin'); + const arm64EsmNoAsarDir = await createEsmAppDir( + 'Arm64NoAsarEsmExtraFile', + 'i-aint-got-no-rhythm.bin', + ); + + await Promise.all([ + templateApp( + 'X64AsarEsm.app', + 'x64', + async (appPath) => { + await createPackage( + x64EsmAsarDir, + path.resolve(appPath, 'Contents', 'Resources', 'app.asar'), + ); + }, + ELECTRON_43_VERSION, + ), + + templateApp( + 'Arm64AsarEsmExtraFile.app', + 'arm64', + async (appPath) => { + await createPackage( + arm64EsmAsarDir, + path.resolve(appPath, 'Contents', 'Resources', 'app.asar'), + ); + }, + ELECTRON_43_VERSION, + ), + + templateApp( + 'X64NoAsarEsm.app', + 'x64', + async (appPath) => { + await fs.promises.cp( + x64EsmNoAsarDir, + path.resolve(appPath, 'Contents', 'Resources', 'app'), + { + recursive: true, + verbatimSymlinks: true, + }, + ); + }, + ELECTRON_43_VERSION, + ), + + templateApp( + 'Arm64NoAsarEsmExtraFile.app', + 'arm64', + async (appPath) => { + await fs.promises.cp( + arm64EsmNoAsarDir, + path.resolve(appPath, 'Contents', 'Resources', 'app'), + { recursive: true, verbatimSymlinks: true }, + ); + }, + ELECTRON_43_VERSION, + ), + ]); }; diff --git a/test/index.spec.ts b/test/index.spec.ts index 0ce3fcc..7e06f15 100644 --- a/test/index.spec.ts +++ b/test/index.spec.ts @@ -8,12 +8,13 @@ import { makeUniversalApp } from '../dist/index.js'; import { fsMove } from '../src/file-utils.js'; import { createStagingAppDir, + ensureUniversal, generateNativeApp, templateApp, VERIFY_APP_TIMEOUT, verifyApp, } from './util.js'; -import { createPackage, createPackageWithOptions } from '@electron/asar'; +import { createPackage, createPackageWithOptions, extractFile } from '@electron/asar'; const appsPath = path.resolve(import.meta.dirname, 'fixtures', 'apps'); @@ -121,6 +122,29 @@ describe.concurrent('makeUniversalApp', () => { }, ); + it( + 'should create a shim for ESM entrypoints if asars are different between architectures', + { timeout: VERIFY_APP_TIMEOUT }, + async ({ expect }) => { + const out = path.resolve(await mkOutDir(), 'ShimmedAsarEsm.app'); + await makeUniversalApp({ + x64AppPath: path.resolve(appsPath, 'X64AsarEsm.app'), + arm64AppPath: path.resolve(appsPath, 'Arm64AsarEsmExtraFile.app'), + outAppPath: out, + }); + // Launches both arches; asserts each prints its arch, proving the ESM + // shim loads the per-arch entrypoint. We can't snapshot the file tree + // here because no baseline is committed (it can only be generated on + // macOS), so assert the emitted shim + `main` directly instead. + await ensureUniversal(expect, out); + const appAsar = path.resolve(out, 'Contents', 'Resources', 'app.asar'); + const pj = JSON.parse(extractFile(appAsar, 'package.json').toString('utf8')); + expect(pj.main).toBe('index.mjs'); + const shim = extractFile(appAsar, 'index.mjs').toString('utf8'); + expect(shim).toContain('await import'); + }, + ); + it( 'should merge two different asars when `mergeASARs` is enabled', { timeout: VERIFY_APP_TIMEOUT }, @@ -376,6 +400,30 @@ describe.concurrent('makeUniversalApp', () => { }, ); + it( + 'should shim two different app folders with ESM entrypoints', + { timeout: VERIFY_APP_TIMEOUT }, + async ({ expect }) => { + const out = path.resolve(await mkOutDir(), 'ShimNoAsarEsm.app'); + await makeUniversalApp({ + x64AppPath: path.resolve(appsPath, 'X64NoAsarEsm.app'), + arm64AppPath: path.resolve(appsPath, 'Arm64NoAsarEsmExtraFile.app'), + outAppPath: out, + }); + // Launches both arches; asserts each prints its arch, proving the ESM + // shim loads the per-arch entrypoint. We can't snapshot the file tree + // here because no baseline is committed (it can only be generated on + // macOS), so assert the emitted shim + `main` directly instead. The + // no-asar path also packages the shim folder into `app.asar`. + await ensureUniversal(expect, out); + const appAsar = path.resolve(out, 'Contents', 'Resources', 'app.asar'); + const pj = JSON.parse(extractFile(appAsar, 'package.json').toString('utf8')); + expect(pj.main).toBe('index.mjs'); + const shim = extractFile(appAsar, 'index.mjs').toString('utf8'); + expect(shim).toContain('await import'); + }, + ); + it( 'different app dirs with different macho files (shim and lipo)', { timeout: VERIFY_APP_TIMEOUT }, diff --git a/test/util.ts b/test/util.ts index bedb1fa..d1e5af3 100644 --- a/test/util.ts +++ b/test/util.ts @@ -1,5 +1,6 @@ import { execFile as execFileCb } from 'node:child_process'; import fs from 'node:fs'; +import { createRequire } from 'node:module'; import os from 'node:os'; import path from 'node:path'; import { promisify } from 'node:util'; @@ -12,12 +13,24 @@ import type { ExpectStatic } from 'vitest'; const execFile = promisify(execFileCb); +const require = createRequire(import.meta.url); + import * as fileUtils from '../dist/file-utils.js'; +// The default Electron version used by the fixtures. The ESM integration +// fixtures override this with `ELECTRON_43_VERSION` below. +export const DEFAULT_ELECTRON_VERSION = '27.0.0'; + +// Resolved from the separately-declared `electron43` dev dependency so it tracks +// whatever `electron@^43` resolves to in the lockfile. +export const ELECTRON_43_VERSION = require('electron43/package.json').version as string; + // We do a LOT of verifications in `verifyApp` 😅 // exec universal binary -> verify ALL asars -> verify ALL app dirs -> verify ALL asar integrity entries // plus some tests create fixtures at runtime -export const VERIFY_APP_TIMEOUT = 80 * 1000; +// The heavy verifyApp integration tests run ~60s each on their own and the suite +// is contended under `maxConcurrency`, so give them plenty of headroom in CI. +export const VERIFY_APP_TIMEOUT = 180 * 1000; export const fixtureDir = path.resolve(import.meta.dirname, 'fixtures'); export const asarsDir = path.resolve(fixtureDir, 'asars'); @@ -204,10 +217,10 @@ export const createStagingAppDir = async ( }; }; -export const downloadElectronZip = (arch: string) => +export const downloadElectronZip = (arch: string, version: string = DEFAULT_ELECTRON_VERSION) => downloadArtifact({ artifactName: 'electron', - version: '27.0.0', + version, platform: 'darwin', arch, }); @@ -216,8 +229,9 @@ export const templateApp = async ( name: string, arch: string, modify: (appPath: string) => Promise, + version: string = DEFAULT_ELECTRON_VERSION, ) => { - const electronZip = await downloadElectronZip(arch); + const electronZip = await downloadElectronZip(arch, version); // unzip to a unique tmpdir so concurrent calls don't race on the intermediate // Electron.app path const extractDir = await fs.promises.mkdtemp(path.join(appsDir, '.extract-')); diff --git a/tsconfig.entry-asar.json b/tsconfig.entry-asar.json index d6df746..b798ead 100644 --- a/tsconfig.entry-asar.json +++ b/tsconfig.entry-asar.json @@ -14,6 +14,6 @@ "declaration": false, "ignoreDeprecations": "6.0" }, - "include": ["entry-asar"], - "exclude": [] + "include": ["entry-asar/*.ts"], + "exclude": ["entry-asar/esm"] } diff --git a/yarn.lock b/yarn.lock index 16dce8b..cfcd15e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,6 +5,13 @@ __metadata: version: 8 cacheKey: 10c0 +"@electron-internal/extract-zip@npm:^1.0.1": + version: 1.0.4 + resolution: "@electron-internal/extract-zip@npm:1.0.4" + checksum: 10c0/e841b38b08317ff5345e3454d0417c406d2258f0de5e654b55b49b28c46d3c0666febc5e14044a4ce110b8a9dd5d4d2b6d7e546a658d073094b3e335ca6db252 + languageName: node + linkType: hard + "@electron/asar@npm:^4.0.0": version: 4.0.0 resolution: "@electron/asar@npm:4.0.0" @@ -49,6 +56,7 @@ __metadata: "@types/plist": "npm:^3.0.4" cross-zip: "npm:^4.0.0" debug: "npm:^4.3.1" + electron43: "npm:electron@^43.0.0" husky: "npm:^9.1.7" lint-staged: "npm:^16.1.0" oxfmt: "npm:^0.44.0" @@ -706,6 +714,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^24.9.0": + version: 24.13.2 + resolution: "@types/node@npm:24.13.2" + dependencies: + undici-types: "npm:~7.18.0" + checksum: 10c0/d7d48a88a4feb0a6aac3cbfaf9ef3b12752b4b09447f88dd0b4c77c03b281e3d4330fe6982a99aedcd63fc16c7540a0c248b91eb2abb0b3edd884d7fe684e9ea + languageName: node + linkType: hard + "@types/node@npm:~22.10.7": version: 22.10.10 resolution: "@types/node@npm:22.10.10" @@ -1041,6 +1058,20 @@ __metadata: languageName: node linkType: hard +"electron43@npm:electron@^43.0.0": + version: 43.0.0 + resolution: "electron@npm:43.0.0" + dependencies: + "@electron-internal/extract-zip": "npm:^1.0.1" + "@electron/get": "npm:^5.0.0" + "@types/node": "npm:^24.9.0" + bin: + electron: cli.js + install-electron: install.js + checksum: 10c0/9f0bfb4e55dd97102941d009ce44ea1c5aa82325e877733284d75be8880ead11305ff45198d059e5127b441c94391ed159136cee23f2474e2416e2bbab2e9940 + languageName: node + linkType: hard + "emoji-regex@npm:^10.3.0": version: 10.4.0 resolution: "emoji-regex@npm:10.4.0" @@ -2182,6 +2213,13 @@ __metadata: languageName: node linkType: hard +"undici-types@npm:~7.18.0": + version: 7.18.2 + resolution: "undici-types@npm:7.18.2" + checksum: 10c0/85a79189113a238959d7a647368e4f7c5559c3a404ebdb8fc4488145ce9426fcd82252a844a302798dfc0e37e6fb178ff481ed03bc4caf634c5757d9ef43521d + languageName: node + linkType: hard + "undici@npm:^7.24.4": version: 7.28.0 resolution: "undici@npm:7.28.0"