mirror of
https://gh-proxy.org/https://github.com/klinsmaya/workdsh.git
synced 2026-11-08 17:36:57 +08:00
163 lines
6.0 KiB
TypeScript
163 lines
6.0 KiB
TypeScript
/** Build an unsigned macOS DMG smoke artifact on a native macOS host. */
|
|
|
|
import { spawnSync } from 'node:child_process'
|
|
import { existsSync, rmSync } from 'node:fs'
|
|
import { createRequire } from 'node:module'
|
|
import { dirname, resolve } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
import { withoutMacReleaseSecrets } from './release-preflight.ts'
|
|
import { electronBuilderEnvironment } from './electron-builder-environment.ts'
|
|
|
|
/** Injectable native macOS packaging boundary used by focused tests. */
|
|
export interface MacSmokePackageOptions {
|
|
/** 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
|
|
/** Dedicated smoke output directory, isolated from signed release artifacts. */
|
|
readonly outputDir: string
|
|
/** Remove only the dedicated generated smoke output before packaging. */
|
|
readonly resetOutput: () => void
|
|
/** Absolute electron-builder CLI module. */
|
|
readonly builderCli: string
|
|
/** Local Electron distribution when already installed, avoiding another download. */
|
|
readonly electronDist?: string
|
|
/** Absolute packaged-DMG 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
|
|
}
|
|
|
|
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)}`)
|
|
}
|
|
}
|
|
|
|
function defaultOptions(): MacSmokePackageOptions {
|
|
const desktopRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
const workspaceRoot = resolve(desktopRoot, '..')
|
|
const require = createRequire(import.meta.url)
|
|
const outputDir = resolve(desktopRoot, 'dist', 'mac-smoke', process.env.WORKDSH_MAC_ARCH ?? process.arch)
|
|
const electronDist = resolve(dirname(require.resolve('electron/package.json')), 'dist')
|
|
return {
|
|
env: process.env,
|
|
platform: process.platform,
|
|
arch: process.arch,
|
|
nodeVersion: process.versions.node,
|
|
workspaceRoot,
|
|
desktopRoot,
|
|
outputDir,
|
|
resetOutput: () => rmSync(outputDir, { recursive: true, force: true }),
|
|
builderCli: require.resolve('electron-builder/cli.js'),
|
|
...(existsSync(resolve(electronDist, 'Electron.app')) ? { electronDist } : {}),
|
|
verifier: fileURLToPath(new URL('./verify-mac-smoke.ts', import.meta.url)),
|
|
nodeExecutable: process.execPath,
|
|
run,
|
|
log: message => console.log(message),
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Run the headless release gates and package one unsigned macOS DMG smoke.
|
|
*
|
|
* The signed and notarized release stays a manual step on a credentialed
|
|
* machine; this smoke exists so macOS packaging regressions fail in CI before
|
|
* a manual release. The selected architecture is packaged independently.
|
|
* @param options - Injectable process and command boundaries.
|
|
*/
|
|
export function packageMacSmoke(options: MacSmokePackageOptions = defaultOptions()): void {
|
|
if (options.platform !== 'darwin') {
|
|
throw new Error('macOS DMG smoke must be built on a native macOS host')
|
|
}
|
|
if (options.arch !== 'x64' && options.arch !== 'arm64') {
|
|
throw new Error(`macOS DMG smoke requires x64 or arm64 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(
|
|
`macOS DMG smoke requires Node 22.19+ or Node 24.x with bundled Corepack; received ${options.nodeVersion}`,
|
|
)
|
|
}
|
|
|
|
const targetArch = options.env.WORKDSH_MAC_ARCH ?? options.arch
|
|
if (targetArch !== 'x64' && targetArch !== 'arm64') {
|
|
throw new Error(`unsupported macOS target architecture: ${targetArch}`)
|
|
}
|
|
const cleanEnvironment = withoutMacReleaseSecrets(options.env)
|
|
options.log('Building an unsigned macOS DMG smoke; signing and notarization are release-only steps.')
|
|
if (options.env.DSH_PACKAGE_CHECK_ALREADY_RAN !== '1') {
|
|
options.run(
|
|
'corepack',
|
|
['yarn', 'workspace', 'dsh-plugin-desktop', 'check:mac-package'],
|
|
options.workspaceRoot,
|
|
cleanEnvironment,
|
|
)
|
|
} else {
|
|
options.log('Skipping the macOS package preflight; the package gate already passed.')
|
|
}
|
|
options.resetOutput()
|
|
options.run(
|
|
options.nodeExecutable,
|
|
[
|
|
options.builderCli,
|
|
'--mac',
|
|
'dmg',
|
|
`--${targetArch}`,
|
|
'--publish',
|
|
'never',
|
|
'--config.mac.notarize=false',
|
|
'--config.npmRebuild=false',
|
|
`--config.directories.output=${options.outputDir}`,
|
|
...(options.electronDist === undefined ? [] : [`--config.electronDist=${options.electronDist}`]),
|
|
],
|
|
options.desktopRoot,
|
|
electronBuilderEnvironment({
|
|
...cleanEnvironment,
|
|
CSC_IDENTITY_AUTO_DISCOVERY: 'false',
|
|
}),
|
|
)
|
|
options.run(
|
|
options.nodeExecutable,
|
|
[options.verifier, options.outputDir, targetArch],
|
|
options.desktopRoot,
|
|
cleanEnvironment,
|
|
)
|
|
}
|
|
|
|
const invokedPath = process.argv[1]
|
|
if (invokedPath !== undefined && resolve(invokedPath) === fileURLToPath(import.meta.url)) {
|
|
try {
|
|
packageMacSmoke()
|
|
} catch (error) {
|
|
console.error(error instanceof Error ? error.message : String(error))
|
|
process.exitCode = 1
|
|
}
|
|
}
|