From 7282de1c81034fc2878402ba18627169ee3c6f85 Mon Sep 17 00:00:00 2001 From: Jan Hannemann Date: Mon, 26 Jan 2026 16:44:07 -0800 Subject: [PATCH 01/13] feat: run electron with identity --- package.json | 1 + src/command-line.ts | 9 +- src/runner.ts | 14 ++- src/static/FiddleAppxManifest.xml | 52 +++++++++++ src/windows-identity.ts | 141 ++++++++++++++++++++++++++++++ static/FiddleAppxManifest.xml | 52 +++++++++++ 6 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 src/static/FiddleAppxManifest.xml create mode 100644 src/windows-identity.ts create mode 100644 static/FiddleAppxManifest.xml diff --git a/package.json b/package.json index 4d12e91..031d471 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "bin": "dist/cli.js", "files": [ "dist", + "static", "README.md" ], "publishConfig": { diff --git a/src/command-line.ts b/src/command-line.ts index 8c19205..9a16a3d 100644 --- a/src/command-line.ts +++ b/src/command-line.ts @@ -17,6 +17,7 @@ export async function runFromCommandLine(argv: string[]): Promise { type Cmd = 'bisect' | 'test' | undefined; let cmd: Cmd = undefined; + let runWithIdentity: boolean = false; let fiddle: Fiddle | undefined = undefined; d('argv', inspect(argv)); @@ -27,7 +28,12 @@ export async function runFromCommandLine(argv: string[]): Promise { } else if (param === 'test' || param === 'start' || param === 'run') { d('it is test'); cmd = 'test'; - } else if (versions.isVersion(param)) { + } + else if (param === 'test:msix' || param === 'start:msix' || param === 'run:msix') { + cmd = 'test'; + runWithIdentity = true; + } + else if (versions.isVersion(param)) { versionArgs.push(param); } else { fiddle = await fiddleFactory.create(param); @@ -56,6 +62,7 @@ export async function runFromCommandLine(argv: string[]): Promise { if (cmd === 'test' && versionArgs.length === 1) { const result = await runner.run(versionArgs[0], fiddle, { out: process.stdout, + runWithIdentity: runWithIdentity, }); const vals = ['test_passed', 'test_failed', 'test_error', 'system_error']; process.exitCode = vals.indexOf(result.status); diff --git a/src/runner.ts b/src/runner.ts index fdfb6c0..b938e59 100644 --- a/src/runner.ts +++ b/src/runner.ts @@ -13,6 +13,9 @@ import { Installer } from './installer.js'; import { ElectronVersions, Versions } from './versions.js'; import { Fiddle, FiddleFactory, FiddleSource } from './fiddle.js'; import { DefaultPaths, Paths } from './paths.js'; +import { registerElectronIdentity } from './windows-identity.js'; + +const MSIX_EXEC_ALIAS = 'ElectronFiddleMSIX.exe'; export interface RunnerOptions { // extra arguments to be appended to the electron invocation @@ -25,6 +28,7 @@ export interface RunnerOptions { showConfig?: boolean; // whether to run the fiddle from asar runFromAsar?: boolean; + runWithIdentity?: boolean; } const DefaultRunnerOpts: RunnerOptions = { @@ -153,7 +157,7 @@ export class Runner { // set up the electron binary and the fiddle const electronExec = await this.getExec(version); - let exec = electronExec; + let exec = process.platform === 'win32' && opts.runWithIdentity ? MSIX_EXEC_ALIAS : electronExec; let args = [...(opts.args || []), fiddle.mainPath]; if (opts.headless) ({ exec, args } = Runner.headless(exec, args)); @@ -204,6 +208,14 @@ export class Runner { fiddle: FiddleSource, opts: RunnerSpawnOptions = DefaultRunnerOpts, ): Promise { + + if (process.platform === 'win32' && opts.runWithIdentity) { + const electron_version = version instanceof SemVer ? version.version : version; + const electronExec = await this.getExec(electron_version); + const electronDir = path.dirname(electronExec); + await registerElectronIdentity(electron_version, electronDir); + } + const subprocess = await this.spawn(version, fiddle, opts); return new Promise((resolve) => { diff --git a/src/static/FiddleAppxManifest.xml b/src/static/FiddleAppxManifest.xml new file mode 100644 index 0000000..6e87fa9 --- /dev/null +++ b/src/static/FiddleAppxManifest.xml @@ -0,0 +1,52 @@ + + + + + true + $DISPLAY_NAME$ + Electron + assets\icon.png + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/windows-identity.ts b/src/windows-identity.ts new file mode 100644 index 0000000..568bbfa --- /dev/null +++ b/src/windows-identity.ts @@ -0,0 +1,141 @@ +import { spawn } from 'node:child_process'; +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const SOURCE_MANIFEST_FILENAME = 'FiddleAppxManifest.xml'; +const TARGET_MANIFEST_FILENAME = 'AppxManifest.xml'; +const SPARSE_PACKAGE_NAME = 'Electron.Fiddle.MSIX'; + +/** + * Map Node.js os.arch() to Windows AppxManifest ProcessorArchitecture values. + */ +function getAppxArchitecture(): string { + const arch = os.arch(); + switch (arch) { + case 'x64': + return 'x64'; + case 'ia32': + return 'x86'; + case 'arm64': + return 'arm64'; + default: + return 'x64'; + } +} + +/** + * Execute a PowerShell command and return the result. + */ +function executePowerShell(command: string): Promise { + return new Promise((resolve, reject) => { + const ps = spawn('powershell.exe', [ + '-NoProfile', + '-ExecutionPolicy', + 'Bypass', + '-Command', + command, + ]); + + let stdout = ''; + let stderr = ''; + + ps.stdout.on('data', (data: any) => { + stdout += data.toString(); + }); + + ps.stderr.on('data', (data: any) => { + stderr += data.toString(); + }); + + ps.on('close', (code: number) => { + if (code === 0) { + resolve(stdout.trim()); + } else { + reject(new Error(`PowerShell command failed: ${stderr || stdout}`)); + } + }); + + ps.on('error', (err: any) => { + reject(err); + }); + }); +} + +/** + * Unregister any previously registered sparse packages with our package name. + */ +async function unregisterSparsePackage(): Promise { + try { + const result = await executePowerShell( + `Get-AppxPackage -Name "${SPARSE_PACKAGE_NAME}" | Select-Object -ExpandProperty PackageFullName`, + ); + + const packages = result + .trim() + .split('\n') + .filter((p) => p.trim().length > 0); + + for (const pkg of packages) { + console.log(`Unregistering sparse package: ${pkg}`); + executePowerShell(`Remove-AppxPackage -Package "${pkg.trim()}"`); + console.log(`Successfully unregistered: ${pkg}`); + } + } catch (error: any) { + console.log('No existing sparse package to unregister'); + } +} + +/** + * Register the sparse package for an Electron installation. + * This gives Electron a Windows app identity. Same as an MSIX package. + * + * @param version - The Electron version string to display in the manifest. + * @param electronDir - The directory containing the Electron executable. + */ +export async function registerElectronIdentity( + version: string, + electronDir: string, +): Promise { + if (process.platform !== 'win32') { + return; + } + + const electronExe = path.join(electronDir, 'electron.exe'); + + // Check if Electron is actually installed + if (!fs.existsSync(electronExe)) { + console.log( + `Electron not found at ${electronDir}, skipping identity registration`, + ); + return; + } + + try { + const __dirname = path.dirname(fileURLToPath(import.meta.url)); + const sourcePath = path.join(__dirname, '..', 'static', SOURCE_MANIFEST_FILENAME); + const targetPath = path.join(electronDir, TARGET_MANIFEST_FILENAME); + + // Read manifest and replace placeholders + let manifest = fs.readFileSync(sourcePath, 'utf8'); + const displayName = `Electron (${version}) MSIX`; + const architecture = getAppxArchitecture(); + manifest = manifest.replace(/\$DISPLAY_NAME\$/g, displayName); + manifest = manifest.replace(/\$ARCHITECTURE\$/g, architecture); + + console.log(`Writing manifest with version ${version} to ${targetPath}`); + fs.writeFileSync(targetPath, manifest, 'utf8'); + + await unregisterSparsePackage(); + + console.log(`Registering sparse package from: ${electronDir}`); + await executePowerShell( + `Add-AppxPackage -ExternalLocation "${electronDir}" -Register "${targetPath}"`, + ); + + console.log('Sparse package registered successfully'); + } catch (error: any) { + console.error('Failed to register sparse package:', error.message); + } +} diff --git a/static/FiddleAppxManifest.xml b/static/FiddleAppxManifest.xml new file mode 100644 index 0000000..f5f46c0 --- /dev/null +++ b/static/FiddleAppxManifest.xml @@ -0,0 +1,52 @@ + + + + + true + $DISPLAY_NAME$ + Electron + assets\icon.png + + + + + + + + + + + + + + + + + + + + + + + + + From 3f0a4476296924e7a24c258663e4dc149fb2dd4d Mon Sep 17 00:00:00 2001 From: Jan Hannemann Date: Mon, 26 Jan 2026 16:44:56 -0800 Subject: [PATCH 02/13] fix: tests --- tests/runner.test.ts | 82 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/tests/runner.test.ts b/tests/runner.test.ts index 4ff8dd3..508e983 100644 --- a/tests/runner.test.ts +++ b/tests/runner.test.ts @@ -5,7 +5,7 @@ import path from 'node:path'; import { Writable } from 'node:stream'; import fs from 'graceful-fs'; -import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; +import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { Installer, @@ -14,8 +14,12 @@ import { Runner, TestResult, } from '../src/index.js'; +import * as windowsIdentity from '../src/windows-identity.js'; vi.mock('child_process'); +vi.mock('../src/windows-identity.js', () => ({ + registerElectronIdentity: vi.fn().mockResolvedValue(undefined), +})); const mockStdout = vi.fn(); @@ -55,6 +59,10 @@ afterAll(() => { fs.rmSync(tmpdir, { recursive: true, force: true }); }); +beforeEach(() => { + vi.clearAllMocks(); +}); + async function createFakeRunner({ pathToExecutable = '/path/to/electron/executable', generatedFiddle = { @@ -228,6 +236,30 @@ describe('Runner', () => { expect.anything(), ); }); + + (process.platform === 'win32' ? it : it.skip)( + 'spawns a subprocess with MSIX execution alias when runWithIdentity is true on Windows', + async () => { + const runner = await createFakeRunner({}); + vi.mocked(child_process.spawn).mockReturnValueOnce( + mockSubprocess as unknown as child_process.ChildProcess, + ); + + await runner.spawn('12.0.1', '642fa8daaebea6044c9079e3f8a46390', { + out: { + write: mockStdout, + } as Pick as Writable, + runWithIdentity: true, + }); + + expect(child_process.spawn).toHaveBeenCalledTimes(1); + expect(child_process.spawn).toHaveBeenCalledWith( + 'ElectronFiddleMSIX.exe', + ['/path/to/fiddle/'], + expect.anything(), + ); + }, + ); }); describe('run()', () => { @@ -252,6 +284,54 @@ describe('Runner', () => { expect(result).toStrictEqual({ status }); }, ); + + (process.platform === 'win32' ? it : it.skip)( + 'calls registerElectronIdentity when runWithIdentity is true on Windows', + async () => { + const runner = await createFakeRunner({}); + const fakeSubprocess = new EventEmitter(); + runner.spawn = vi.fn().mockResolvedValue(fakeSubprocess); + + // delay to ensure that the listeners in run() are set up. + process.nextTick(() => { + fakeSubprocess.emit('exit', 0); + }); + + await runner.run('12.0.1', '642fa8daaebea6044c9079e3f8a46390', { + runWithIdentity: true, + }); + + expect(windowsIdentity.registerElectronIdentity).toHaveBeenCalledTimes( + 1, + ); + expect(windowsIdentity.registerElectronIdentity).toHaveBeenCalledWith( + '12.0.1', + '/path/to/electron', + ); + }, + ); + + (process.platform !== 'win32' ? it : it.skip)( + 'does not call registerElectronIdentity when not on Windows', + async () => { + const runner = await createFakeRunner({}); + const fakeSubprocess = new EventEmitter(); + runner.spawn = vi.fn().mockResolvedValue(fakeSubprocess); + + // delay to ensure that the listeners in run() are set up. + process.nextTick(() => { + fakeSubprocess.emit('exit', 0); + }); + + await runner.run('12.0.1', '642fa8daaebea6044c9079e3f8a46390', { + runWithIdentity: true, + }); + + expect( + windowsIdentity.registerElectronIdentity, + ).not.toHaveBeenCalled(); + }, + ); }); describe('bisect()', () => { From a032884c2345fe27f233fc2314900d94e5c46ecd Mon Sep 17 00:00:00 2001 From: Jan Hannemann Date: Mon, 26 Jan 2026 16:52:46 -0800 Subject: [PATCH 03/13] feat: documentation --- README.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ src/runner.ts | 1 + 2 files changed, 49 insertions(+) diff --git a/README.md b/README.md index a5943f7..a2d1238 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,20 @@ Run fiddles from anywhere, on any Electron release # fiddle-core test ver (gist | repo URL | folder) # fiddle-core bisect ver1 ver2 (gist | repo URL | folder) # +# Run with Windows MSIX identity (Windows only): +# fiddle-core run:msix ver (gist | repo URL | folder) +# fiddle-core test:msix ver (gist | repo URL | folder) +# fiddle-core start:msix ver (gist | repo URL | folder) +# # Examples: $ fiddle-core run 12.0.0 /path/to/fiddle $ fiddle-core test 12.0.0 642fa8daaebea6044c9079e3f8a46390 $ fiddle-core bisect 8.0.0 13.0.0 https://github.com/my/testcase.git +# Run with Windows MSIX identity (gives Electron a Windows app identity) +$ fiddle-core run:msix 30.0.0 /path/to/fiddle + $ fiddle-core bisect 8.0.0 13.0.0 642fa8daaebea6044c9079e3f8a46390 ... @@ -75,6 +83,10 @@ const result = await runner.run('15.0.0-alpha.1', files); // bisect a regression test across a range of Electron versions const result = await runner.bisect('10.0.0', '13.1.7', path_or_gist_or_git_repo); +// run with Windows MSIX identity (Windows only) +// This registers Electron as a sparse MSIX package, giving it a Windows app identity +const result = await runner.run('30.0.0', fiddle, { runWithIdentity: true }); + // see also `Runner.spawn()` in Advanced Use ``` @@ -185,6 +197,42 @@ const runner = await Runner.create(); const result = await runner.run('/path/to/electron/build', fiddle); ``` +### Running with Windows MSIX Identity + +On Windows, you can run Electron with a [sparse MSIX package](https://learn.microsoft.com/en-us/windows/msix/overview) identity. This gives Electron a Windows app identity, which is required for certain Windows features like: + +- Toast notifications with app icon +- Taskbar jump lists +- Share contract +- Background tasks +- And other Windows Runtime APIs that require package identity + +```ts +import { Runner } from '@electron/fiddle-core'; + +const runner = await Runner.create(); + +// Run fiddle with Windows MSIX identity +const result = await runner.run('30.0.0', fiddle, { + runWithIdentity: true, +}); +``` + +When `runWithIdentity` is `true`, fiddle-core will: +1. Generate an AppxManifest.xml in the Electron installation directory +2. Register Electron as a sparse MSIX package using `Add-AppxPackage` +3. Run Electron via its registered execution alias + +**Note:** This feature is only available on Windows 10 version 1803 (build 17134) and later. On other platforms, the `runWithIdentity` option is ignored. + +**CLI Usage:** +```sh +# Use the :msix suffix with run, test, or start commands +$ fiddle-core run:msix 30.0.0 /path/to/fiddle +$ fiddle-core test:msix 30.0.0 642fa8daaebea6044c9079e3f8a46390 +$ fiddle-core start:msix 30.0.0 https://github.com/my/testcase.git +``` + ### Using Custom Paths ```ts diff --git a/src/runner.ts b/src/runner.ts index b938e59..f05f6eb 100644 --- a/src/runner.ts +++ b/src/runner.ts @@ -28,6 +28,7 @@ export interface RunnerOptions { showConfig?: boolean; // whether to run the fiddle from asar runFromAsar?: boolean; + // whether to run Electron with Windows MSIX identity (Windows only). runWithIdentity?: boolean; } From 306f471555b03f872d7b67f73cfdb97a41302bd2 Mon Sep 17 00:00:00 2001 From: Jan Hannemann Date: Mon, 26 Jan 2026 16:55:19 -0800 Subject: [PATCH 04/13] fix: shorter docs --- README.md | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/README.md b/README.md index a2d1238..703776b 100644 --- a/README.md +++ b/README.md @@ -199,13 +199,7 @@ const result = await runner.run('/path/to/electron/build', fiddle); ### Running with Windows MSIX Identity -On Windows, you can run Electron with a [sparse MSIX package](https://learn.microsoft.com/en-us/windows/msix/overview) identity. This gives Electron a Windows app identity, which is required for certain Windows features like: - -- Toast notifications with app icon -- Taskbar jump lists -- Share contract -- Background tasks -- And other Windows Runtime APIs that require package identity +On Windows, you can run Electron with a [sparse MSIX package](https://learn.microsoft.com/en-us/windows/msix/overview) identity. This gives Electron a Windows app identity, which is required for certain Windows features. ```ts import { Runner } from '@electron/fiddle-core'; @@ -223,14 +217,10 @@ When `runWithIdentity` is `true`, fiddle-core will: 2. Register Electron as a sparse MSIX package using `Add-AppxPackage` 3. Run Electron via its registered execution alias -**Note:** This feature is only available on Windows 10 version 1803 (build 17134) and later. On other platforms, the `runWithIdentity` option is ignored. - **CLI Usage:** ```sh # Use the :msix suffix with run, test, or start commands $ fiddle-core run:msix 30.0.0 /path/to/fiddle -$ fiddle-core test:msix 30.0.0 642fa8daaebea6044c9079e3f8a46390 -$ fiddle-core start:msix 30.0.0 https://github.com/my/testcase.git ``` ### Using Custom Paths From cd221e9917aea3e9c469420be76569aaed139a1c Mon Sep 17 00:00:00 2001 From: Jan Hannemann Date: Mon, 26 Jan 2026 16:59:11 -0800 Subject: [PATCH 05/13] fix: move doc to proper section --- README.md | 47 +++++++++++++++++++++-------------------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 703776b..0bb94c6 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,27 @@ const result = await runner.run('30.0.0', fiddle, { runWithIdentity: true }); // see also `Runner.spawn()` in Advanced Use ``` +### Running with Windows MSIX Identity + +On Windows, you can run Electron with a [sparse MSIX package](https://learn.microsoft.com/en-us/windows/msix/overview) identity. This gives Electron a Windows app identity, which is required for certain Windows features. + +```ts +import { Runner } from '@electron/fiddle-core'; + +const runner = await Runner.create(); + +// Run fiddle with Windows MSIX identity +const result = await runner.run('30.0.0', fiddle, { + runWithIdentity: true, +}); +``` + +When `runWithIdentity` is `true`, fiddle-core will: +1. Generate an AppxManifest.xml in the Electron installation directory +2. Register Electron as a sparse MSIX package using `Add-AppxPackage` +3. Run Electron via its registered execution alias + + ### Managing Electron Installations ```ts @@ -197,32 +218,6 @@ const runner = await Runner.create(); const result = await runner.run('/path/to/electron/build', fiddle); ``` -### Running with Windows MSIX Identity - -On Windows, you can run Electron with a [sparse MSIX package](https://learn.microsoft.com/en-us/windows/msix/overview) identity. This gives Electron a Windows app identity, which is required for certain Windows features. - -```ts -import { Runner } from '@electron/fiddle-core'; - -const runner = await Runner.create(); - -// Run fiddle with Windows MSIX identity -const result = await runner.run('30.0.0', fiddle, { - runWithIdentity: true, -}); -``` - -When `runWithIdentity` is `true`, fiddle-core will: -1. Generate an AppxManifest.xml in the Electron installation directory -2. Register Electron as a sparse MSIX package using `Add-AppxPackage` -3. Run Electron via its registered execution alias - -**CLI Usage:** -```sh -# Use the :msix suffix with run, test, or start commands -$ fiddle-core run:msix 30.0.0 /path/to/fiddle -``` - ### Using Custom Paths ```ts From 751c3ff20ea9e22633830829f7719925e229769c Mon Sep 17 00:00:00 2001 From: Jan Hannemann Date: Wed, 28 Jan 2026 15:36:12 -0800 Subject: [PATCH 06/13] fix: remove unused static file --- src/static/FiddleAppxManifest.xml | 52 ------------------------------- 1 file changed, 52 deletions(-) delete mode 100644 src/static/FiddleAppxManifest.xml diff --git a/src/static/FiddleAppxManifest.xml b/src/static/FiddleAppxManifest.xml deleted file mode 100644 index 6e87fa9..0000000 --- a/src/static/FiddleAppxManifest.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - true - $DISPLAY_NAME$ - Electron - assets\icon.png - - - - - - - - - - - - - - - - - - - - - - - - - From d5c355cc0f943d31af60cc671720b05321a17e4e Mon Sep 17 00:00:00 2001 From: Jan Hannemann Date: Wed, 28 Jan 2026 15:36:39 -0800 Subject: [PATCH 07/13] fix: linting --- src/command-line.ts | 10 ++++++---- src/runner.ts | 9 ++++++--- src/windows-identity.ts | 22 ++++++++++++++-------- tests/runner.test.ts | 14 ++++++++++---- 4 files changed, 36 insertions(+), 19 deletions(-) diff --git a/src/command-line.ts b/src/command-line.ts index 9a16a3d..b870b17 100644 --- a/src/command-line.ts +++ b/src/command-line.ts @@ -28,12 +28,14 @@ export async function runFromCommandLine(argv: string[]): Promise { } else if (param === 'test' || param === 'start' || param === 'run') { d('it is test'); cmd = 'test'; - } - else if (param === 'test:msix' || param === 'start:msix' || param === 'run:msix') { + } else if ( + param === 'test:msix' || + param === 'start:msix' || + param === 'run:msix' + ) { cmd = 'test'; runWithIdentity = true; - } - else if (versions.isVersion(param)) { + } else if (versions.isVersion(param)) { versionArgs.push(param); } else { fiddle = await fiddleFactory.create(param); diff --git a/src/runner.ts b/src/runner.ts index f05f6eb..f39a6a4 100644 --- a/src/runner.ts +++ b/src/runner.ts @@ -158,7 +158,10 @@ export class Runner { // set up the electron binary and the fiddle const electronExec = await this.getExec(version); - let exec = process.platform === 'win32' && opts.runWithIdentity ? MSIX_EXEC_ALIAS : electronExec; + let exec = + process.platform === 'win32' && opts.runWithIdentity + ? MSIX_EXEC_ALIAS + : electronExec; let args = [...(opts.args || []), fiddle.mainPath]; if (opts.headless) ({ exec, args } = Runner.headless(exec, args)); @@ -209,9 +212,9 @@ export class Runner { fiddle: FiddleSource, opts: RunnerSpawnOptions = DefaultRunnerOpts, ): Promise { - if (process.platform === 'win32' && opts.runWithIdentity) { - const electron_version = version instanceof SemVer ? version.version : version; + const electron_version = + version instanceof SemVer ? version.version : version; const electronExec = await this.getExec(electron_version); const electronDir = path.dirname(electronExec); await registerElectronIdentity(electron_version, electronDir); diff --git a/src/windows-identity.ts b/src/windows-identity.ts index 568bbfa..9e30647 100644 --- a/src/windows-identity.ts +++ b/src/windows-identity.ts @@ -41,11 +41,11 @@ function executePowerShell(command: string): Promise { let stdout = ''; let stderr = ''; - ps.stdout.on('data', (data: any) => { + ps.stdout.on('data', (data: Buffer) => { stdout += data.toString(); }); - ps.stderr.on('data', (data: any) => { + ps.stderr.on('data', (data: Buffer) => { stderr += data.toString(); }); @@ -57,7 +57,7 @@ function executePowerShell(command: string): Promise { } }); - ps.on('error', (err: any) => { + ps.on('error', (err: Error) => { reject(err); }); }); @@ -79,10 +79,10 @@ async function unregisterSparsePackage(): Promise { for (const pkg of packages) { console.log(`Unregistering sparse package: ${pkg}`); - executePowerShell(`Remove-AppxPackage -Package "${pkg.trim()}"`); + await executePowerShell(`Remove-AppxPackage -Package "${pkg.trim()}"`); console.log(`Successfully unregistered: ${pkg}`); } - } catch (error: any) { + } catch { console.log('No existing sparse package to unregister'); } } @@ -114,7 +114,12 @@ export async function registerElectronIdentity( try { const __dirname = path.dirname(fileURLToPath(import.meta.url)); - const sourcePath = path.join(__dirname, '..', 'static', SOURCE_MANIFEST_FILENAME); + const sourcePath = path.join( + __dirname, + '..', + 'static', + SOURCE_MANIFEST_FILENAME, + ); const targetPath = path.join(electronDir, TARGET_MANIFEST_FILENAME); // Read manifest and replace placeholders @@ -135,7 +140,8 @@ export async function registerElectronIdentity( ); console.log('Sparse package registered successfully'); - } catch (error: any) { - console.error('Failed to register sparse package:', error.message); + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error); + console.error('Failed to register sparse package:', message); } } diff --git a/tests/runner.test.ts b/tests/runner.test.ts index 508e983..3acb0e8 100644 --- a/tests/runner.test.ts +++ b/tests/runner.test.ts @@ -5,7 +5,15 @@ import path from 'node:path'; import { Writable } from 'node:stream'; import fs from 'graceful-fs'; -import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; +import { + afterAll, + beforeAll, + beforeEach, + describe, + expect, + it, + vi, +} from 'vitest'; import { Installer, @@ -327,9 +335,7 @@ describe('Runner', () => { runWithIdentity: true, }); - expect( - windowsIdentity.registerElectronIdentity, - ).not.toHaveBeenCalled(); + expect(windowsIdentity.registerElectronIdentity).not.toHaveBeenCalled(); }, ); }); From 39cb9c57d2ecdaa99afe40b147ac94d2bddf5a00 Mon Sep 17 00:00:00 2001 From: Jan Hannemann Date: Mon, 2 Feb 2026 11:14:29 -0800 Subject: [PATCH 08/13] Update tests/runner.test.ts Co-authored-by: David Sanders --- tests/runner.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/runner.test.ts b/tests/runner.test.ts index 3acb0e8..f0690a8 100644 --- a/tests/runner.test.ts +++ b/tests/runner.test.ts @@ -319,7 +319,7 @@ describe('Runner', () => { }, ); - (process.platform !== 'win32' ? it : it.skip)( + it.skipIf(process.platform === 'win32')( 'does not call registerElectronIdentity when not on Windows', async () => { const runner = await createFakeRunner({}); From 740152d421c51bf1f2db71aac530d35852b6fc0b Mon Sep 17 00:00:00 2001 From: Jan Hannemann Date: Mon, 2 Feb 2026 11:14:36 -0800 Subject: [PATCH 09/13] Update tests/runner.test.ts Co-authored-by: David Sanders --- tests/runner.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/runner.test.ts b/tests/runner.test.ts index f0690a8..16f6acf 100644 --- a/tests/runner.test.ts +++ b/tests/runner.test.ts @@ -293,7 +293,7 @@ describe('Runner', () => { }, ); - (process.platform === 'win32' ? it : it.skip)( + it.runIf(process.platform === 'win32')( 'calls registerElectronIdentity when runWithIdentity is true on Windows', async () => { const runner = await createFakeRunner({}); From 63d27f264244ef57ab0ef79e9a03edf4b74d6210 Mon Sep 17 00:00:00 2001 From: Jan Hannemann Date: Mon, 2 Feb 2026 11:14:43 -0800 Subject: [PATCH 10/13] Update tests/runner.test.ts Co-authored-by: David Sanders --- tests/runner.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/runner.test.ts b/tests/runner.test.ts index 16f6acf..8d114df 100644 --- a/tests/runner.test.ts +++ b/tests/runner.test.ts @@ -245,7 +245,7 @@ describe('Runner', () => { ); }); - (process.platform === 'win32' ? it : it.skip)( + it.runIf(process.platform === 'win32')( 'spawns a subprocess with MSIX execution alias when runWithIdentity is true on Windows', async () => { const runner = await createFakeRunner({}); From 2ae3f117158800b57cde820416464459d469a241 Mon Sep 17 00:00:00 2001 From: Jan Hannemann Date: Mon, 2 Feb 2026 11:14:50 -0800 Subject: [PATCH 11/13] Update src/windows-identity.ts Co-authored-by: David Sanders --- src/windows-identity.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/windows-identity.ts b/src/windows-identity.ts index 9e30647..9959991 100644 --- a/src/windows-identity.ts +++ b/src/windows-identity.ts @@ -75,11 +75,12 @@ async function unregisterSparsePackage(): Promise { const packages = result .trim() .split('\n') - .filter((p) => p.trim().length > 0); + .map((p) => p.trim()) + .filter((p) => p.length > 0); for (const pkg of packages) { console.log(`Unregistering sparse package: ${pkg}`); - await executePowerShell(`Remove-AppxPackage -Package "${pkg.trim()}"`); + await executePowerShell(`Remove-AppxPackage -Package "${pkg}"`); console.log(`Successfully unregistered: ${pkg}`); } } catch { From f44c9b4f891d8affbffd8202359d1a281d66ae76 Mon Sep 17 00:00:00 2001 From: Jan Hannemann Date: Mon, 2 Feb 2026 11:15:26 -0800 Subject: [PATCH 12/13] Update src/runner.ts Co-authored-by: David Sanders --- src/runner.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/runner.ts b/src/runner.ts index f39a6a4..8781d1c 100644 --- a/src/runner.ts +++ b/src/runner.ts @@ -213,11 +213,11 @@ export class Runner { opts: RunnerSpawnOptions = DefaultRunnerOpts, ): Promise { if (process.platform === 'win32' && opts.runWithIdentity) { - const electron_version = + const electronVersion = version instanceof SemVer ? version.version : version; - const electronExec = await this.getExec(electron_version); + const electronExec = await this.getExec(electronVersion); const electronDir = path.dirname(electronExec); - await registerElectronIdentity(electron_version, electronDir); + await registerElectronIdentity(electronVersion, electronDir); } const subprocess = await this.spawn(version, fiddle, opts); From b13fcc8a9e4cffc3d530aa5ea94bff1efe55a9a2 Mon Sep 17 00:00:00 2001 From: David Sanders Date: Tue, 3 Feb 2026 14:00:58 -0800 Subject: [PATCH 13/13] docs: update --- etc/fiddle-core.api.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/etc/fiddle-core.api.md b/etc/fiddle-core.api.md index 9fc618c..1f600f3 100644 --- a/etc/fiddle-core.api.md +++ b/etc/fiddle-core.api.md @@ -251,6 +251,8 @@ export interface RunnerOptions { // (undocumented) runFromAsar?: boolean; // (undocumented) + runWithIdentity?: boolean; + // (undocumented) showConfig?: boolean; }