Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/desktop-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,10 @@ jobs:
exit 1
fi
verify_app "$dmg_app"
xcrun stapler validate "$dmg_path"
# electron-builder notarizes and staples the .app, then packs the
# already-stapled bundle into the disk image; the image itself never
# receives a ticket. Validate the staple where it actually lives.
xcrun stapler validate "$dmg_app"
cleanup_mount
trap - EXIT

Expand Down
12 changes: 6 additions & 6 deletions apps/desktop/scripts/package-win.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,12 @@ export function windowsSigningArgs(env: NodeJS.ProcessEnv): readonly string[] {
)
}

const publisherName = hasAzureValue
? trimmedValue(env['AZURE_SIGNING_PUBLISHER_NAME'])!
: trimmedValue(env['WINDOWS_SIGNING_PUBLISHER_NAME'])!
if (!hasAzureValue) args.length = 0
args.push('--config.win.publisherName', publisherName)
return args
// Electron Builder 26 removed `win.publisherName`, and `win` rejects unknown
// keys, so passing it fails schema validation before any build work. The
// Azure path already carries the publisher in `azureSignOptions`; the
// certificate path now sets it where signtool reads it.
if (hasAzureValue) return args
return ['--config.win.signtoolOptions.publisherName', trimmedValue(env['WINDOWS_SIGNING_PUBLISHER_NAME'])!]
}

/** Require one complete signing method for a tagged Windows release. */
Expand Down
71 changes: 68 additions & 3 deletions apps/desktop/tests/package-win.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,56 @@
import { createRequire } from 'node:module'
import { describe, expect, it } from 'vitest'
import {
requireWindowsReleaseSigning,
windowsPackageInvocation,
windowsSigningArgs,
} from '../scripts/package-win'

interface SchemaNode {
readonly $ref?: string
readonly anyOf?: readonly SchemaNode[]
readonly properties?: Readonly<Record<string, SchemaNode>>
}

const requireFromTests = createRequire(import.meta.url)
const schema = requireFromTests(
requireFromTests.resolve('app-builder-lib/scheme.json', {
paths: [requireFromTests.resolve('electron-builder')],
}),
) as { readonly definitions: Readonly<Record<string, SchemaNode>> }

function referencedDefinition(node: SchemaNode): SchemaNode | undefined {
const reference = node.$ref ?? node.anyOf?.find(branch => branch.$ref !== undefined)?.$ref
return reference === undefined ? undefined : schema.definitions[reference.replace('#/definitions/', '')]
}

/**
* Assert that a `--config.<path>` option exists in the installed Electron Builder schema.
*
* `WindowsConfiguration` sets `additionalProperties: false`, so an option that
* the schema does not declare fails validation before any build work and takes
* the whole `win` object down with it. Comparing against the real schema keeps
* these arguments honest across Electron Builder upgrades, which is what an
* expected-array assertion cannot do.
* @param path - Dotted option path with the `--config.` prefix removed.
*/
function assertSchemaOption(path: string): void {
const [root, ...rest] = path.split('.')
expect(root).toBe('win')
let definition = schema.definitions['WindowsConfiguration']!
rest.forEach((segment, index) => {
const property = definition.properties?.[segment]
if (property === undefined) {
throw new Error(`Electron Builder has no option '${rest.slice(0, index + 1).join('.')}' under win`)
}
const next = referencedDefinition(property)
if (next !== undefined) definition = next
else if (index !== rest.length - 1) {
throw new Error(`Electron Builder option 'win.${rest.slice(0, index + 1).join('.')}' has no nested options`)
}
})
}

const signingEnvironment: NodeJS.ProcessEnv = {
AZURE_TENANT_ID: 'tenant-id',
AZURE_CLIENT_ID: 'client-id',
Expand All @@ -26,7 +72,6 @@ describe('Windows Azure signing configuration', () => {
'--config.win.azureSignOptions.codeSigningAccountName', 'signing-account',
'--config.win.azureSignOptions.certificateProfileName', 'certificate-profile',
'--config.win.azureSignOptions.publisherName', 'CN=Example Publisher, O=Example Publisher, L=Redmond, S=WA, C=US',
'--config.win.publisherName', 'CN=Example Publisher, O=Example Publisher, L=Redmond, S=WA, C=US',
])
})

Expand Down Expand Up @@ -56,6 +101,27 @@ describe('Windows Azure signing configuration', () => {
})
})

describe('Electron Builder option names', () => {
const certificateEnvironment: NodeJS.ProcessEnv = {
WIN_CSC_LINK: 'certificate.p12',
WIN_CSC_KEY_PASSWORD: 'password',
WINDOWS_SIGNING_PUBLISHER_NAME: 'CN=Example Publisher, O=Example Publisher',
}

it('emits only options the installed Electron Builder schema declares', () => {
for (const environment of [signingEnvironment, certificateEnvironment]) {
const options = windowsSigningArgs(environment).filter(argument => argument.startsWith('--config.'))
expect(options.length).toBeGreaterThan(0)
for (const option of options) assertSchemaOption(option.slice('--config.'.length))
}
})

it('rejects an option the schema does not declare', () => {
expect(() => { assertSchemaOption('win.publisherName') })
.toThrow("Electron Builder has no option 'publisherName' under win")
})
})

describe('Windows tagged-release signing', () => {
it('rejects an unsigned tagged release', () => {
expect(() => requireWindowsReleaseSigning({})).toThrow('Windows release signing is not configured')
Expand All @@ -70,7 +136,7 @@ describe('Windows tagged-release signing', () => {

expect(requireWindowsReleaseSigning(environment)).toBe('CN=Example Publisher, O=Example Publisher')
expect(windowsSigningArgs(environment)).toEqual([
'--config.win.publisherName', 'CN=Example Publisher, O=Example Publisher',
'--config.win.signtoolOptions.publisherName', 'CN=Example Publisher, O=Example Publisher',
])
})

Expand Down Expand Up @@ -111,7 +177,6 @@ describe('Windows package invocation', () => {
'--config.win.azureSignOptions.codeSigningAccountName', 'signing-account',
'--config.win.azureSignOptions.certificateProfileName', 'certificate-profile',
'--config.win.azureSignOptions.publisherName', 'CN=Example Publisher, O=Example Publisher, L=Redmond, S=WA, C=US',
'--config.win.publisherName', 'CN=Example Publisher, O=Example Publisher, L=Redmond, S=WA, C=US',
])
})

Expand Down
Loading