add builder and updater
This commit is contained in:
60
scripts/releaseArtifacts.mjs
Normal file
60
scripts/releaseArtifacts.mjs
Normal file
@ -0,0 +1,60 @@
|
||||
import { readFile, writeFile } from 'node:fs/promises'
|
||||
import { join } from 'node:path'
|
||||
import { createHash } from 'node:crypto'
|
||||
import { parse } from 'yaml'
|
||||
import { validateFeedUrl } from '../build/config.mjs'
|
||||
|
||||
/** Hash an artifact for local tamper/corruption checks and upload verification. */
|
||||
export function digest(data, algorithm = 'sha256', encoding = 'hex') {
|
||||
return createHash(algorithm).update(data).digest(encoding)
|
||||
}
|
||||
|
||||
/** Read only the x64 stable installer referenced by electron-builder metadata. */
|
||||
export async function readReleaseArtifacts(directory) {
|
||||
const metadata = await readFile(join(directory, 'latest.yml'))
|
||||
const info = parse(metadata.toString('utf8'))
|
||||
if (!/^\d+\.\d+\.\d+$/.test(info?.version) || info.files?.length !== 1) {
|
||||
throw new Error('Expected one Windows x64 installer for a stable semantic version.')
|
||||
}
|
||||
const installer = `Capsule-${info.version}-x64-Setup.exe`
|
||||
if (info.files[0].url !== installer || (info.path && info.path !== installer)) {
|
||||
throw new Error('Release metadata must reference the expected local installer filename.')
|
||||
}
|
||||
const binary = await readFile(join(directory, installer))
|
||||
if (info.files[0].sha512 !== digest(binary, 'sha512', 'base64') || info.files[0].size !== binary.length) {
|
||||
throw new Error('Installer does not match latest.yml checksum or size.')
|
||||
}
|
||||
const blockmap = await readFile(join(directory, `${installer}.blockmap`))
|
||||
return {
|
||||
version: info.version,
|
||||
files: [
|
||||
{ name: installer, data: binary },
|
||||
{ name: `${installer}.blockmap`, data: blockmap },
|
||||
// Publish the pointer only after both immutable artifacts are available.
|
||||
{ name: 'latest.yml', data: metadata }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/** Mark a successful signed build with hashes of its exact publishable files. */
|
||||
export async function writeReleaseManifest(directory, feedUrl) {
|
||||
const release = await readReleaseArtifacts(directory)
|
||||
const manifest = {
|
||||
version: release.version,
|
||||
feedUrl: validateFeedUrl(feedUrl),
|
||||
files: release.files.map(({ name, data }) => ({ name, sha256: digest(data) }))
|
||||
}
|
||||
await writeFile(join(directory, 'release-ready.json'), JSON.stringify(manifest, null, 2))
|
||||
}
|
||||
|
||||
/** Refuse local builds, missing files, stale metadata, and changed release assets. */
|
||||
export async function verifyRelease(directory) {
|
||||
const manifest = JSON.parse(await readFile(join(directory, 'release-ready.json'), 'utf8'))
|
||||
validateFeedUrl(manifest.feedUrl)
|
||||
const release = await readReleaseArtifacts(directory)
|
||||
if (release.version !== manifest.version || manifest.files?.length !== release.files.length ||
|
||||
release.files.some(({ name, data }, index) => manifest.files[index].name !== name || manifest.files[index].sha256 !== digest(data))) {
|
||||
throw new Error('Release artifacts have changed since the signed build. Rebuild before publishing.')
|
||||
}
|
||||
return { ...release, feedUrl: manifest.feedUrl }
|
||||
}
|
||||
Reference in New Issue
Block a user