import assert from 'node:assert/strict' import { execFileSync, spawnSync } from 'node:child_process' import { createHash } from 'node:crypto' import fs from 'node:fs' import os from 'node:os' import path from 'node:path' import { test } from 'vitest' import { ensureWindowsBundleTools } from './windows-bundle-tools.mjs' const repo = path.resolve(import.meta.dirname, '../../..') const sha256 = file => createHash('sha256').update(fs.readFileSync(file)).digest('hex') function run(command, args, cwd, env) { return execFileSync(command, args, { cwd, env, encoding: 'utf8', timeout: 60_000, stdio: ['ignore', 'pipe', 'pipe'] }) } function fixture(kit) { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'commit-msix-')) const desktop = path.join(root, 'apps/desktop') fs.mkdirSync(path.join(desktop, 'scripts'), { recursive: true }) fs.mkdirSync(path.join(root, 'scripts')) for (const file of ['stage-msixbundle.mjs', 'bundle-store-msixbundle.mjs', 'msix-shared.mjs', 'release-content-types.json']) { fs.copyFileSync(path.join(repo, 'scripts', file), path.join(root, 'scripts', file)) } fs.copyFileSync(path.join(repo, 'apps/desktop/scripts/windows-bundle-tools.mjs'), path.join(desktop, 'scripts/windows-bundle-tools.mjs')) fs.copyFileSync(path.join(repo, 'apps/desktop/product-identity.cjs'), path.join(desktop, 'product-identity.cjs')) fs.writeFileSync(path.join(desktop, 'package.json'), JSON.stringify({ name: 'fixture', version: '0.21.1' })) fs.writeFileSync(path.join(desktop, 'electron-builder.config.cjs'), `module.exports=${JSON.stringify({ directories: { buildResources: kit }, toolsets: { winCodeSign: { url: 'file://' + kit } } })}\n`) fs.symlinkSync(path.join(repo, 'node_modules'), path.join(root, 'node_modules'), 'junction') // Assembly does not depend on production artwork or the icon build step. const iconsScript = path.join(root, 'fixture-icons.ps1') fs.writeFileSync(iconsScript, String.raw` param([string]$Dir) $ErrorActionPreference = 'Stop' Add-Type -AssemblyName System.Drawing New-Item -ItemType Directory -Force $Dir | Out-Null foreach ($asset in @(@('StoreLogo.png',50), @('Square150x150Logo.png',150), @('Square44x44Logo.png',44))) { $image = New-Object System.Drawing.Bitmap([int]$asset[1], [int]$asset[1]) try { $image.Save((Join-Path $Dir $asset[0]), [System.Drawing.Imaging.ImageFormat]::Png) } finally { $image.Dispose() } } `) run('powershell.exe', ['-NoProfile', '-NonInteractive', '-File', iconsScript, '-Dir', path.join(root, 'icons')], root) const env = Object.fromEntries(Object.entries(process.env).filter(([key]) => !/^(AZURE_|CLOUDFLARE_|HERMES_|GITHUB_|GH_|NODE_OPTIONS$)/i.test(key))) Object.assign(env, { HERMES_PAYLOAD_TAG: '', CI: '', GIT_CONFIG_GLOBAL: path.join(root, 'git-config'), GIT_CONFIG_NOSYSTEM: '1', GIT_AUTHOR_NAME: 'Fixture', GIT_AUTHOR_EMAIL: 'fixture@example.invalid', GIT_COMMITTER_NAME: 'Fixture', GIT_COMMITTER_EMAIL: 'fixture@example.invalid', GIT_AUTHOR_DATE: '2026-09-10T10:20:30Z', GIT_COMMITTER_DATE: '2026-09-10T10:20:30Z' }) for (const args of [['init', '-q'], ['add', 'scripts', 'apps'], ['-c', 'commit.gpgsign=false', 'commit', '-qm', 'fixture']]) run('git', args, root, env) const commit = run('git', ['rev-parse', 'HEAD'], root, env).trim() env.HERMES_BUILD_COMMIT = commit env.HERMES_PAYLOAD_VERSION = '0.21.1' const release = path.join(desktop, 'release') fs.mkdirSync(release) return { root, desktop, env, commit, release } } function identity(root, env, variant) { return JSON.parse(run(process.execPath, ['--input-type=module', '-e', "import{appIdentity}from'./scripts/msix-shared.mjs';console.log(JSON.stringify(appIdentity(process.cwd()+'/apps/desktop','')))"], root, { ...env, HERMES_DESKTOP_VARIANT: variant })) } function makePackage(makeappx, root, release, metadata, variant, arch, env) { const content = path.join(root, `${variant}-${arch}`) fs.mkdirSync(content) fs.mkdirSync(path.join(content, 'assets')) for (const name of ['StoreLogo.png', 'Square150x150Logo.png', 'Square44x44Logo.png']) { fs.copyFileSync(path.join(root, 'icons', name), path.join(content, 'assets', name)) } const name = metadata.identity.store ? metadata.identity.storeMsix.identityName : metadata.identity.msixAppIdWithOrg const publisher = metadata.identity.store ? metadata.identity.storeMsix.publisher : 'CN=Fixture' fs.writeFileSync(path.join(content, 'index.html'), '