diff --git a/apps/desktop/scripts/desktop-build-provenance.d.mts b/apps/desktop/scripts/desktop-build-commit.d.mts similarity index 50% rename from apps/desktop/scripts/desktop-build-provenance.d.mts rename to apps/desktop/scripts/desktop-build-commit.d.mts index db6796515a..82118e24e1 100644 --- a/apps/desktop/scripts/desktop-build-provenance.d.mts +++ b/apps/desktop/scripts/desktop-build-commit.d.mts @@ -1,5 +1,5 @@ /** Commit a build came from, and whether its checkout carried uncommitted changes. */ -export interface DesktopBuildProvenance { +export interface DesktopBuildCommit { readonly commit: string readonly dirty: boolean } @@ -13,20 +13,20 @@ export const DESKTOP_BUILD_DIRTY_ENV: 'DSH_DESKTOP_BUILD_DIRTY' /** * Read the checkout's current commit and whether it carries uncommitted changes. * @param repositoryRoot - Directory to inspect. - * @returns Provenance of the tree being packaged. + * @returns The commit being packaged and whether its tree was modified. */ -export function readDesktopBuildProvenance(repositoryRoot: string): DesktopBuildProvenance +export function readDesktopBuildCommit(repositoryRoot: string): DesktopBuildCommit /** - * Resolve provenance a parent packaging process recorded. + * Resolve the commit a parent packaging process recorded. * @param env - Packaging environment. - * @returns Provenance, or undefined outside a packaging run. + * @returns The packaged commit, or undefined outside a packaging run. */ -export function resolveDesktopBuildProvenance(env: NodeJS.ProcessEnv): DesktopBuildProvenance | undefined +export function resolveDesktopBuildCommit(env: NodeJS.ProcessEnv): DesktopBuildCommit | undefined /** - * Describe provenance as the environment variables child processes read. - * @param provenance - Provenance to pass down. + * Describe the packaged commit as the environment variables child processes read. + * @param packaged - Commit and tree state to pass down. * @returns Variables to merge into a child environment. */ -export function desktopBuildProvenanceEnvironment(provenance: DesktopBuildProvenance): Record +export function desktopBuildCommitEnvironment(packaged: DesktopBuildCommit): Record diff --git a/apps/desktop/scripts/desktop-build-provenance.mjs b/apps/desktop/scripts/desktop-build-commit.mjs similarity index 65% rename from apps/desktop/scripts/desktop-build-provenance.mjs rename to apps/desktop/scripts/desktop-build-commit.mjs index b2c45a4789..2f1dc27cfd 100644 --- a/apps/desktop/scripts/desktop-build-provenance.mjs +++ b/apps/desktop/scripts/desktop-build-commit.mjs @@ -19,9 +19,9 @@ export const DESKTOP_BUILD_DIRTY_ENV = 'DSH_DESKTOP_BUILD_DIRTY' /** * Read the checkout's current commit and whether it carries uncommitted changes. * @param {string} repositoryRoot - Directory to inspect. - * @returns {{ commit: string, dirty: boolean }} Provenance of the tree being packaged. + * @returns {{ commit: string, dirty: boolean }} The commit being packaged and whether its tree was modified. */ -export function readDesktopBuildProvenance(repositoryRoot) { +export function readDesktopBuildCommit(repositoryRoot) { const git = (args) => execFileSync('git', args, { cwd: repositoryRoot, encoding: 'utf8' }).trim() return { commit: git(['rev-parse', 'HEAD']), @@ -30,29 +30,29 @@ export function readDesktopBuildProvenance(repositoryRoot) { } /** - * Resolve provenance a parent packaging process recorded. + * Resolve the commit a parent packaging process recorded. * @param {NodeJS.ProcessEnv} env - Packaging environment. - * @returns {{ commit: string, dirty: boolean } | undefined} Provenance, or undefined outside a packaging run. + * @returns {{ commit: string, dirty: boolean } | undefined} The packaged commit, or undefined outside a packaging run. */ -export function resolveDesktopBuildProvenance(env) { +export function resolveDesktopBuildCommit(env) { const commit = env[DESKTOP_BUILD_COMMIT_ENV]?.trim() if (commit === undefined || commit === '') return undefined - if (!/^[0-9a-f]{40}$/u.test(commit)) throw new Error(`desktop build provenance: ${DESKTOP_BUILD_COMMIT_ENV} must be a commit hash`) + if (!/^[0-9a-f]{40}$/u.test(commit)) throw new Error(`desktop build commit: ${DESKTOP_BUILD_COMMIT_ENV} must be a commit hash`) const dirty = env[DESKTOP_BUILD_DIRTY_ENV]?.trim() if (dirty !== undefined && dirty !== '' && dirty !== '0' && dirty !== '1') { - throw new Error(`desktop build provenance: ${DESKTOP_BUILD_DIRTY_ENV} must be 0 or 1`) + throw new Error(`desktop build commit: ${DESKTOP_BUILD_DIRTY_ENV} must be 0 or 1`) } return { commit, dirty: dirty === '1' } } /** - * Describe provenance as the environment variables child processes read. - * @param {{ commit: string, dirty: boolean }} provenance - Provenance to pass down. + * Describe the packaged commit as the environment variables child processes read. + * @param {{ commit: string, dirty: boolean }} packaged - Commit and tree state to pass down. * @returns {Record} Variables to merge into a child environment. */ -export function desktopBuildProvenanceEnvironment(provenance) { +export function desktopBuildCommitEnvironment(packaged) { return { - [DESKTOP_BUILD_COMMIT_ENV]: provenance.commit, - [DESKTOP_BUILD_DIRTY_ENV]: provenance.dirty ? '1' : '0', + [DESKTOP_BUILD_COMMIT_ENV]: packaged.commit, + [DESKTOP_BUILD_DIRTY_ENV]: packaged.dirty ? '1' : '0', } } diff --git a/apps/desktop/scripts/desktop-toolchain-preflight.ts b/apps/desktop/scripts/desktop-toolchain-preflight.ts index 812e7b1828..ff855cfd09 100644 --- a/apps/desktop/scripts/desktop-toolchain-preflight.ts +++ b/apps/desktop/scripts/desktop-toolchain-preflight.ts @@ -70,7 +70,7 @@ async function probeWindowsInstallerToolchain(environment: NodeJS.ProcessEnv): P /** * Probe every external tool one packaging run needs. - * @param platform - Target platform, which decides the installer toolchain probes. + * @param platform - Target platform; a Windows target already requires a Windows build host. * @param environment - Packaging environment used to locate Windows tooling. * @returns Every probe that failed, empty when the host can run the packaging sequence. */ @@ -87,7 +87,7 @@ export async function probeDesktopToolchain( /** * Probe the toolchain and fail with every problem the host has. - * @param platform - Target platform, which decides the installer toolchain probes. + * @param platform - Target platform; a Windows target already requires a Windows build host. * @param environment - Packaging environment used to locate Windows tooling. * @returns Resolves when every probe passes. */ diff --git a/apps/desktop/scripts/electron-builder-config.mjs b/apps/desktop/scripts/electron-builder-config.mjs index 327918073d..dafd999a4f 100644 --- a/apps/desktop/scripts/electron-builder-config.mjs +++ b/apps/desktop/scripts/electron-builder-config.mjs @@ -19,7 +19,7 @@ import { scrubWindowsSigningEnvironment, } from './windows-sign.mjs' import { resolveDesktopAutoUpdateConfig } from './desktop-auto-update-environment.mjs' -import { resolveDesktopBuildProvenance } from './desktop-build-provenance.mjs' +import { resolveDesktopBuildCommit } from './desktop-build-commit.mjs' import { resolveDesktopBuildVersion } from './desktop-build-version.mjs' import { resolveDesktopPolicyEnvironment } from './desktop-policy-environment.mjs' import { desktopTargetBuildPaths, resolveDesktopBuildTarget } from './desktop-build-paths.mjs' @@ -88,18 +88,18 @@ export function createElectronBuilderConfig( } const update = unsigned ? undefined : resolveDesktopAutoUpdateConfig(env, resolvedPlatform, resolvedArch) if (preparedRuntime !== undefined) buildPaths.dsh = preparedRuntime - // electron-builder merges extraMetadata into the packaged manifest, so a build identifier here reaches + // electron-builder merges extraMetadata into the packaged manifest, so a build version here reaches // the artifact names, the update feed, and the installed app.getVersion() the updater compares against. const productVersion = JSON.parse(readFileSync(fileURLToPath(new URL('../package.json', import.meta.url)), 'utf8')).version - const releaseId = resolveDesktopBuildVersion(env, productVersion) - const provenance = resolveDesktopBuildProvenance(env) + const buildVersion = resolveDesktopBuildVersion(env, productVersion) + const packaged = resolveDesktopBuildCommit(env) return { appId, extraMetadata: { dshDesktopAppId: appId, dshMandatoryUpdatePolicy: policy, - ...releaseId === productVersion ? {} : { version: releaseId }, - ...provenance === undefined ? {} : { dshBuildCommit: provenance.commit, dshBuildDirty: provenance.dirty }, + ...buildVersion === productVersion ? {} : { version: buildVersion }, + ...packaged === undefined ? {} : { dshBuildCommit: packaged.commit, dshBuildDirty: packaged.dirty }, }, productName: 'DeepSeek Harness', artifactName: 'deepseek-harness-${version}-${os}-${arch}.${ext}', diff --git a/apps/desktop/scripts/package-target.ts b/apps/desktop/scripts/package-target.ts index a038ff1893..8f06ea3c50 100644 --- a/apps/desktop/scripts/package-target.ts +++ b/apps/desktop/scripts/package-target.ts @@ -21,7 +21,7 @@ import { notarizeMacOS } from './notarize-macos.mjs' import { resolveMacOSNotarizationEnvironment } from './desktop-release-environment.mjs' import { DESKTOP_BUILD_VERSION_ENV, resolveDesktopBuildVersion, validateDesktopBuildVersion } from './desktop-build-version.mjs' import { suggestDesktopBuildVersion } from './desktop-build-version-discovery.ts' -import { desktopBuildProvenanceEnvironment, readDesktopBuildProvenance, resolveDesktopBuildProvenance } from './desktop-build-provenance.mjs' +import { desktopBuildCommitEnvironment, readDesktopBuildCommit, resolveDesktopBuildCommit } from './desktop-build-commit.mjs' import { requireDesktopToolchain } from './desktop-toolchain-preflight.ts' import { withMacOSNotarizationProxy } from './macos-notarization-proxy.ts' @@ -144,7 +144,7 @@ function writeReleaseRecord( throw new Error(`desktop package: desktop version ${desktopVersion} does not match dsh version ${dshVersion}`) } const buildVersion = resolveDesktopBuildVersion(environment, dshVersion) - const provenance = resolveDesktopBuildProvenance(environment) + const packaged = resolveDesktopBuildCommit(environment) const update = resolveDesktopAutoUpdateConfig(environment, target.platform, target.arch) const recordPath = join(artifactsRoot, desktopBuildRecordFilename(target.name)) const temporaryPath = `${recordPath}.tmp` @@ -155,7 +155,7 @@ function writeReleaseRecord( environment: update.environment, publicUrl: update.publicUrl, // Upload tags the commit a production release was packaged from, which is no longer discoverable from the build tree. - ...provenance === undefined ? {} : { commit: provenance.commit, dirty: provenance.dirty }, + ...packaged === undefined ? {} : { commit: packaged.commit, dirty: packaged.dirty }, }, null, 2)}\n`) renameSync(temporaryPath, recordPath) } @@ -325,14 +325,14 @@ async function main(): Promise { return } process.stdout.write(`desktop package: ${target.name} publishes ${buildVersion}${buildVersion === productVersion ? '' : ` for product version ${productVersion}`}\n`) - const provenance = readDesktopBuildProvenance(REPOSITORY_ROOT) - Object.assign(environment, desktopBuildProvenanceEnvironment(provenance)) + const packaged = readDesktopBuildCommit(REPOSITORY_ROOT) + Object.assign(environment, desktopBuildCommitEnvironment(packaged)) const secrets = Object.entries(environment).filter(([name]) => /KEY|SECRET|TOKEN|PASSWORD|APPLE_ID/iu.test(name)).map(([, value]) => value ?? '') const run = createPackagingRun(join(APP_ROOT, '.desktop-build', 'packaging-runs'), { target: target.name, unsigned: invocation.unsigned, directory: invocation.directory, prepareOnly: invocation.prepareOnly, version: buildVersion, productVersion, node: process.version, - commit: provenance.commit, - dirty: provenance.dirty, + commit: packaged.commit, + dirty: packaged.dirty, }, { parallel: target.platform === 'darwin', secrets }) console.log(`DESKTOP_PACKAGING_RECORD ${run.directory}`) const previousDirectory = process.env.DSH_DESKTOP_PACKAGING_RUN_DIR diff --git a/apps/desktop/scripts/upload-target.ts b/apps/desktop/scripts/upload-target.ts index e61c552e7b..7942828845 100644 --- a/apps/desktop/scripts/upload-target.ts +++ b/apps/desktop/scripts/upload-target.ts @@ -96,7 +96,7 @@ async function main(): Promise { function recordProductionRelease(plan: DesktopUploadPlan): void { const tag = desktopReleaseTag(plan.version) if (plan.commit === undefined) { - process.stdout.write(`desktop upload: this package predates recorded provenance; tag it by hand as ${tag}\n`) + process.stdout.write(`desktop upload: this package was built before builds recorded their commit; tag it by hand as ${tag}\n`) return } if (plan.dirty === true) { diff --git a/apps/desktop/tests/package-target-errors.spec.ts b/apps/desktop/tests/package-target-errors.spec.ts index 4022973816..9c8297ef20 100644 --- a/apps/desktop/tests/package-target-errors.spec.ts +++ b/apps/desktop/tests/package-target-errors.spec.ts @@ -12,6 +12,8 @@ vi.mock('../scripts/desktop-package-environment.mjs', () => ({ vi.mock('../scripts/macos-signing-keychain.mjs', () => ({ withMacOSSigningKeychain: async (_environment: object, action: (environment: object) => Promise) => action({}), })) +// This test fakes the host platform, so the real probes would report the absent Windows compilers rather than the failure under test. +vi.mock('../scripts/desktop-toolchain-preflight.ts', () => ({ requireDesktopToolchain: async () => {} })) vi.mock('node:fs', async importOriginal => ({ ...await importOriginal(), rmSync: () => { throw new AggregateError([new Error('credential-sentinel')], 'restore:mac-proxy recovery required') }, @@ -51,6 +53,7 @@ it.each(['win32', 'darwin'] as const)('records and prints redacted parent failur expect(JSON.parse(await readFile(join(state.directory, 'result.json'), 'utf8'))).toMatchObject({ success: false, stages: [ { stage: 'configuration', success: true }, + { stage: 'toolchain', success: true }, { stage: platform === 'win32' ? 'windows-package' : 'macos-package', success: false }, ], })