#!/usr/bin/env node 'use strict' const assert = require('node:assert/strict') const fs = require('node:fs') const fsp = require('node:fs/promises') const path = require('node:path') const { execFileSync, spawnSync } = require('node:child_process') const ROOT = path.resolve(__dirname, '..') const REGISTRY_KEY = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\8a4ee941-d893-569b-8bcb-2a55d270c04b' const STAMP = new Date().toISOString().replace(/[:.]/g, '-') const RUN_ROOT = path.join(ROOT, '.tmp', 'updates-installer-marker', STAMP) const LATEST_REPORT = path.join(ROOT, '.tmp', 'updates-installer-marker', 'latest-report.json') function parseRegistryValue(output, name) { const expression = new RegExp(`^\\s*${name}\\s+REG_\\w+\\s+(.+?)\\s*$`, 'mi') const match = expression.exec(output) return match ? match[1].replace(/^"(.*)"$/, '$1') : null } function queryRegistryValue(name) { const output = execFileSync('reg.exe', ['query', REGISTRY_KEY, '/v', name], { encoding: 'utf8', windowsHide: true, timeout: 10_000, maxBuffer: 64 * 1024 }) return parseRegistryValue(output, name) } function registryExecutablePath(value) { const quoted = /^\s*"([^"]+\.exe)"/i.exec(value) if (quoted) return quoted[1] const plain = /^\s*(.+?\.exe)(?:\s|$|,)/i.exec(value) return plain ? plain[1] : null } function relativeImport(from, to) { return `./${path.relative(path.dirname(from), to).replace(/\\/g, '/')}` } function createRunnerSource(filename) { const installer = path.join(ROOT, 'src', 'main', 'updates', 'installer.ts') const physical = path.join(ROOT, 'src', 'main', 'updates', 'physical-files.ts') return ` import { app } from 'electron' import fsPromises from 'node:fs/promises' import path from 'node:path' import { createNsisInstaller, queryNsisRegistryValue } from ${JSON.stringify(relativeImport(filename, installer))} import { physicalRealPath, physicalStat } from ${JSON.stringify(relativeImport(filename, physical))} function decodeConfig() { return JSON.parse(Buffer.from(process.env.YCODE_MARKER_CONFIG || '', 'base64').toString('utf8')) } async function captureError(operation) { try { await operation() return null } catch (error) { return { code: error && typeof error === 'object' && 'code' in error ? error.code : null, message: error instanceof Error ? error.message : String(error) } } } async function main() { const config = decodeConfig() const patched = await fsPromises.stat(config.appPath) const physicalAsar = await physicalStat(config.appPath) const physicalResources = await physicalStat(path.join(config.installationDirectory, 'resources')) const validAdapter = createNsisInstaller({ platform: 'win32', isPackaged: true, execPath: config.executablePath, appPath: config.appPath, cacheDirectory: config.cacheDirectory, resolveRealPath: physicalRealPath, readStat: physicalStat, queryRegistryValue: queryNsisRegistryValue }) const environment = await validAdapter.validateEnvironment(1) const directoryFixtureAdapter = createNsisInstaller({ platform: 'win32', isPackaged: true, execPath: config.directoryFixture.executablePath, appPath: config.directoryFixture.appPath, cacheDirectory: config.cacheDirectory, resolveRealPath: physicalRealPath, readStat: physicalStat, queryRegistryValue: async name => { if (name === 'UninstallString') return '"' + config.directoryFixture.uninstallerPath + '" /currentuser' if (name === 'DisplayIcon') return config.directoryFixture.executablePath + ',0' return null } }) const directoryError = await captureError(() => directoryFixtureAdapter.validateEnvironment(0)) const wrongRegistryAdapter = createNsisInstaller({ platform: 'win32', isPackaged: true, execPath: config.executablePath, appPath: config.appPath, cacheDirectory: config.cacheDirectory, resolveRealPath: physicalRealPath, readStat: physicalStat, queryRegistryValue: async name => { if (name === 'UninstallString') return '"C:\\\\Wrong\\\\Uninstall YCode.exe" /currentuser' if (name === 'DisplayIcon') return config.executablePath + ',0' return null } }) const wrongRegistryError = await captureError(() => wrongRegistryAdapter.validateEnvironment(0)) const result = { electronVersion: process.versions.electron, nodeVersion: process.versions.node, installationDirectory: environment.installationDirectory, executablePath: environment.executablePath, appPath: config.appPath, patchedFs: { isFile: patched.isFile(), isDirectory: patched.isDirectory() }, physicalAsar: { isFile: physicalAsar.isFile(), isDirectory: physicalAsar.isDirectory() }, physicalResources: { isFile: physicalResources.isFile(), isDirectory: physicalResources.isDirectory() }, directoryError, wrongRegistryError, installerLaunched: false } console.log('YCODE_MARKER_RESULT=' + Buffer.from(JSON.stringify(result)).toString('base64')) app.exit(0) } main().catch(error => { console.error(error) app.exit(1) }) ` } async function main() { if (process.platform !== 'win32') throw new Error('Installer marker verification requires Windows') await fsp.mkdir(RUN_ROOT, { recursive: true }) const uninstallString = queryRegistryValue('UninstallString') const displayIcon = queryRegistryValue('DisplayIcon') const displayVersion = queryRegistryValue('DisplayVersion') const uninstallerPath = uninstallString ? registryExecutablePath(uninstallString) : null const executablePath = displayIcon ? registryExecutablePath(displayIcon.replace(/,\s*-?\d+\s*$/, '')) : null if (!uninstallerPath || !executablePath) throw new Error('Current HKCU YCode registry paths are incomplete') const installationDirectory = path.dirname(executablePath) if (path.dirname(uninstallerPath).toLowerCase() !== installationDirectory.toLowerCase()) { throw new Error('Current HKCU uninstaller and icon paths disagree') } const appPath = path.join(installationDirectory, 'resources', 'app.asar') const cacheDirectory = path.join(RUN_ROOT, 'cache') await fsp.mkdir(cacheDirectory, { recursive: true }) const directoryFixtureRoot = path.join(RUN_ROOT, 'directory-marker-fixture') const directoryFixtureResources = path.join(directoryFixtureRoot, 'resources') await fsp.mkdir(path.join(directoryFixtureResources, 'app.asar'), { recursive: true }) const directoryFixtureExe = path.join(directoryFixtureRoot, 'YCode.exe') const directoryFixtureUninstaller = path.join(directoryFixtureRoot, 'Uninstall YCode.exe') await fsp.writeFile(directoryFixtureExe, 'fixture') await fsp.writeFile(directoryFixtureUninstaller, 'fixture') const runnerEntry = path.join(RUN_ROOT, 'runner-entry.ts') const runnerOutput = path.join(RUN_ROOT, 'runner.cjs') await fsp.writeFile(runnerEntry, createRunnerSource(runnerEntry), 'utf8') const esbuild = path.join(ROOT, 'node_modules', 'esbuild', 'bin', 'esbuild') execFileSync(process.execPath, [ esbuild, runnerEntry, '--bundle', '--platform=node', '--format=cjs', '--external:electron', `--outfile=${runnerOutput}`, '--log-level=warning' ], { cwd: ROOT, stdio: 'inherit' }) const electronBinary = require('electron') const config = { installationDirectory, executablePath, appPath, cacheDirectory, directoryFixture: { executablePath: directoryFixtureExe, uninstallerPath: directoryFixtureUninstaller, appPath: path.join(directoryFixtureResources, 'app.asar') } } const env = { ...process.env, YCODE_MARKER_CONFIG: Buffer.from(JSON.stringify(config)).toString('base64') } delete env.ELECTRON_RUN_AS_NODE delete env.ELECTRON_NO_ASAR const child = spawnSync(electronBinary, [runnerOutput], { cwd: RUN_ROOT, env, encoding: 'utf8', windowsHide: true, timeout: 45_000, maxBuffer: 4 * 1024 * 1024 }) if (child.error) throw child.error if (child.status !== 0) { throw new Error(`Electron marker runner failed (${child.status}): ${child.stderr || child.stdout}`) } const marker = child.stdout.split(/\r?\n/).find(line => line.startsWith('YCODE_MARKER_RESULT=')) if (!marker) throw new Error(`Electron marker runner returned no result: ${child.stderr || child.stdout}`) const result = JSON.parse(Buffer.from(marker.slice('YCODE_MARKER_RESULT='.length), 'base64').toString('utf8')) assert.equal(result.installationDirectory.toLowerCase(), installationDirectory.toLowerCase()) assert.equal(result.patchedFs.isFile, false) assert.equal(result.patchedFs.isDirectory, true) assert.equal(result.physicalAsar.isFile, true) assert.equal(result.physicalAsar.isDirectory, false) assert.equal(result.physicalResources.isFile, false) assert.equal(result.physicalResources.isDirectory, true) assert.equal(result.directoryError?.code, 'installer-marker') assert.match(result.directoryError?.message || '', /app\.asar/) assert.equal(result.wrongRegistryError?.code, 'installer-registry') assert.equal(result.installerLaunched, false) const report = { schemaVersion: 1, passed: true, registryDisplayVersion: displayVersion, uninstallerPath, executablePath, appPath, runnerOutput, result } const reportPath = path.join(RUN_ROOT, 'report.json') await fsp.writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8') await fsp.mkdir(path.dirname(LATEST_REPORT), { recursive: true }) await fsp.writeFile(LATEST_REPORT, `${JSON.stringify(report, null, 2)}\n`, 'utf8') console.log(`PASS installer marker: patched fs directory, original-fs file, directory and registry rejection`) console.log(`report: ${reportPath}`) } main().catch(error => { console.error(error) process.exitCode = 1 })