mirror of
https://gh-proxy.org/https://github.com/klinsmaya/workdsh.git
synced 2026-11-10 02:17:22 +08:00
188 lines
6.1 KiB
TypeScript
188 lines
6.1 KiB
TypeScript
/** Build an unsigned Windows x64 artifact on a native Windows host. */
|
|
|
|
import { spawnSync } from 'node:child_process'
|
|
import { createRequire } from 'node:module'
|
|
import { dirname, join, resolve } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
const WINDOWS_SIGNING_KEYS = [
|
|
'CSC_IDENTITY_AUTO_DISCOVERY',
|
|
'CSC_KEY_PASSWORD',
|
|
'CSC_LINK',
|
|
'CSC_NAME',
|
|
'WIN_CSC_KEY_PASSWORD',
|
|
'WIN_CSC_LINK',
|
|
] as const
|
|
|
|
/** Injectable native Windows packaging boundary used by focused tests. */
|
|
export interface WindowsPackageOptions {
|
|
/** Environment inherited by the packaging command. */
|
|
readonly env: NodeJS.ProcessEnv
|
|
/** Platform executing the package build. */
|
|
readonly platform: NodeJS.Platform
|
|
/** Node architecture executing the package build. */
|
|
readonly arch: string
|
|
/** Node version executing the package build. */
|
|
readonly nodeVersion: string
|
|
/** Repository root containing the Yarn workspace. */
|
|
readonly workspaceRoot: string
|
|
/** Desktop package root containing electron-builder configuration. */
|
|
readonly desktopRoot: string
|
|
/** Absolute native Windows command interpreter. */
|
|
readonly commandShell: string
|
|
/** Absolute electron-builder CLI module. */
|
|
readonly builderCli: string
|
|
/** Absolute packaged-installer verification script. */
|
|
readonly verifier: string
|
|
/** Node executable used to run package-local scripts. */
|
|
readonly nodeExecutable: string
|
|
/** Execute one packaging command. */
|
|
readonly run: (
|
|
command: string,
|
|
args: readonly string[],
|
|
cwd: string,
|
|
env: NodeJS.ProcessEnv,
|
|
) => void
|
|
/** Report non-secret packaging progress. */
|
|
readonly log: (message: string) => void
|
|
}
|
|
|
|
/**
|
|
* Remove all certificate discovery and secret variables from an unsigned build.
|
|
* @param environment - Environment that may contain Windows signing configuration.
|
|
* @returns A copy suitable for checks and unsigned packaging.
|
|
*/
|
|
export function withoutWindowsSigningSecrets(
|
|
environment: NodeJS.ProcessEnv,
|
|
): NodeJS.ProcessEnv {
|
|
const sanitized = { ...environment }
|
|
const signingKeys = new Set<string>(WINDOWS_SIGNING_KEYS)
|
|
for (const key of Object.keys(sanitized)) {
|
|
if (signingKeys.has(key.toUpperCase())) delete sanitized[key]
|
|
}
|
|
return sanitized
|
|
}
|
|
|
|
function run(
|
|
command: string,
|
|
args: readonly string[],
|
|
cwd: string,
|
|
env: NodeJS.ProcessEnv,
|
|
): void {
|
|
const result = spawnSync(command, args, { cwd, env, stdio: 'inherit' })
|
|
if (result.error !== undefined) throw result.error
|
|
if (result.status !== 0) {
|
|
throw new Error(`${command} ${args.join(' ')} exited with ${String(result.status)}`)
|
|
}
|
|
}
|
|
|
|
/** Create the native packaging options for a verifier entry point. */
|
|
export function createWindowsPackageOptions(verifier = './verify-win-installer.ts'): WindowsPackageOptions {
|
|
const desktopRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
const workspaceRoot = resolve(desktopRoot, '..')
|
|
const require = createRequire(import.meta.url)
|
|
const windowsRoot = process.env.SystemRoot ?? process.env.WINDIR
|
|
return {
|
|
env: process.env,
|
|
platform: process.platform,
|
|
arch: process.arch,
|
|
nodeVersion: process.versions.node,
|
|
workspaceRoot,
|
|
desktopRoot,
|
|
commandShell: windowsRoot === undefined || windowsRoot.length === 0
|
|
? 'cmd.exe'
|
|
: join(windowsRoot, 'System32', 'cmd.exe'),
|
|
builderCli: require.resolve('electron-builder/cli.js'),
|
|
verifier: fileURLToPath(new URL(verifier, import.meta.url)),
|
|
nodeExecutable: process.execPath,
|
|
run,
|
|
log: message => console.log(message),
|
|
}
|
|
}
|
|
|
|
/** Run the shared host and Node release gates before packaging. */
|
|
function assertWindowsPackageHost(options: WindowsPackageOptions, artifact: string): void {
|
|
if (options.platform !== 'win32') {
|
|
throw new Error(`Windows ${artifact} must be built on a native Windows host`)
|
|
}
|
|
if (options.arch !== 'x64') {
|
|
throw new Error(`Windows ${artifact} requires x64 Node; received ${options.arch}`)
|
|
}
|
|
const versionMatch = /^(\d+)\.(\d+)\./u.exec(options.nodeVersion)
|
|
const major = Number(versionMatch?.[1])
|
|
const minor = Number(versionMatch?.[2])
|
|
if (!((major === 22 && minor >= 19) || major === 24)) {
|
|
throw new Error(
|
|
`Windows ${artifact} requires Node 22.19+ or Node 24.x with bundled Corepack; received ${options.nodeVersion}`,
|
|
)
|
|
}
|
|
}
|
|
|
|
/** Run the gates and package one unsigned x64 Windows artifact. */
|
|
export function packageWindowsArtifact(
|
|
options: WindowsPackageOptions,
|
|
target: 'nsis' | 'zip',
|
|
artifact: 'installer' | 'portable archive',
|
|
): void {
|
|
assertWindowsPackageHost(options, artifact)
|
|
|
|
const cleanEnvironment = withoutWindowsSigningSecrets(options.env)
|
|
options.log(`Building an unsigned Windows x64 ${artifact}; Authenticode is a separate release step.`)
|
|
if (options.env.DSH_PACKAGE_CHECK_ALREADY_RAN !== '1') {
|
|
options.run(
|
|
options.commandShell,
|
|
[
|
|
'/d',
|
|
'/s',
|
|
'/c',
|
|
'corepack yarn workspace dsh-plugin-desktop check:win-package',
|
|
],
|
|
options.workspaceRoot,
|
|
cleanEnvironment,
|
|
)
|
|
} else {
|
|
options.log('Skipping the Windows package preflight; the package gate already passed.')
|
|
}
|
|
options.run(
|
|
options.nodeExecutable,
|
|
[
|
|
options.builderCli,
|
|
'--win',
|
|
target,
|
|
'--x64',
|
|
'--publish',
|
|
'never',
|
|
'--config.win.signExecutable=false',
|
|
'--config.npmRebuild=false',
|
|
],
|
|
options.desktopRoot,
|
|
{
|
|
...cleanEnvironment,
|
|
CSC_IDENTITY_AUTO_DISCOVERY: 'false',
|
|
},
|
|
)
|
|
options.run(
|
|
options.nodeExecutable,
|
|
[options.verifier],
|
|
options.desktopRoot,
|
|
cleanEnvironment,
|
|
)
|
|
}
|
|
|
|
/** Run the headless release gates and package one unsigned x64 NSIS installer. */
|
|
export function packageWindowsInstaller(
|
|
options: WindowsPackageOptions = createWindowsPackageOptions(),
|
|
): void {
|
|
packageWindowsArtifact(options, 'nsis', 'installer')
|
|
}
|
|
|
|
const invokedPath = process.argv[1]
|
|
if (invokedPath !== undefined && resolve(invokedPath) === fileURLToPath(import.meta.url)) {
|
|
try {
|
|
packageWindowsInstaller()
|
|
} catch (error) {
|
|
console.error(error instanceof Error ? error.message : String(error))
|
|
process.exitCode = 1
|
|
}
|
|
}
|