- pubkey: rotated to the keypair stored at C:\Users\陈晨\.heicode-updater\heicode_updater.key (private side is the user's; only the pubkey ships in tauri.conf.json). - scripts/release-desktop.mjs: one-shot release helper — uploads the signed bundle artifacts to Azure Blob (account heicodeblob, container msi, public-blob-read) and rewrites website/public/updater/latest.json to point at the new URLs. - UPDATER.md: rewritten with the concrete URLs, container, key paths, and step-by-step commands. No more generic placeholders. Azure Blob setup (done out-of-band, not in this commit): - Storage account heicodeblob set allowBlobPublicAccess=true - Container msi set to public-blob read - Smoke-tested: https://heicodeblob.blob.core.windows.net/msi/<x> returns 200 anonymously. The actual Azure connection string + private-key path live in scripts/.env.release, which is gitignored under .env.* and was verified excluded before this commit. NOTE: pubkey was rotated. Any MSI already in the wild signed by the *previous* key cannot self-update to this signing chain — those users need a fresh manual install. This is acceptable for pre-GA where no public release exists yet. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
144 lines
4.6 KiB
JavaScript
144 lines
4.6 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* End-to-end desktop release helper.
|
|
*
|
|
* 1. Reads the version from src-tauri/tauri.conf.json (you bumped it
|
|
* and rebuilt before running this).
|
|
* 2. Uploads every supplied bundle artifact + its `.sig` to the
|
|
* Azure Blob container `msi` (account `heicodeblob`). Container
|
|
* is configured public-blob-read, so the URLs are durable.
|
|
* 3. Generates `website/public/updater/latest.json` pointing at
|
|
* those URLs and inlining the signatures.
|
|
*
|
|
* After this script runs you still need to `git commit && git push`
|
|
* the website manifest so the static site redeploys.
|
|
*
|
|
* Usage (PowerShell example):
|
|
*
|
|
* $env:AZURE_STORAGE_CONNECTION_STRING = "<conn-str>"
|
|
* node scripts/release-desktop.mjs `
|
|
* --notes "0.1.1 — bug fixes" `
|
|
* --windows-x86_64 src-tauri/target/release/bundle/nsis/heicode_0.1.1_x64-setup.exe
|
|
*
|
|
* Tip: the connection string lives in scripts/.release-credentials
|
|
* locally (gitignored). `source` it before running on Mac/Linux, or
|
|
* use `Get-Content | ForEach-Object {...}` on Windows.
|
|
*/
|
|
import {
|
|
readFileSync, statSync, mkdirSync, writeFileSync, existsSync,
|
|
} from 'node:fs'
|
|
import { dirname, basename, resolve, join } from 'node:path'
|
|
import { argv, env, exit } from 'node:process'
|
|
import { execSync, spawnSync } from 'node:child_process'
|
|
|
|
const REPO_ROOT = resolve(import.meta.dirname, '..', '..', '..')
|
|
const CONTAINER = 'msi'
|
|
const ACCOUNT = 'heicodeblob'
|
|
const PUBLIC_HOST = `https://${ACCOUNT}.blob.core.windows.net/${CONTAINER}`
|
|
const MANIFEST_REL = 'website/public/updater/latest.json'
|
|
const TAURI_CONF = resolve(import.meta.dirname, '..', 'src-tauri', 'tauri.conf.json')
|
|
|
|
const PLATFORM_KEYS = [
|
|
'windows-x86_64',
|
|
'darwin-aarch64',
|
|
'darwin-x86_64',
|
|
'linux-x86_64',
|
|
]
|
|
|
|
function parseArgs(rawArgs) {
|
|
const out = {}
|
|
for (let i = 0; i < rawArgs.length; i++) {
|
|
const k = rawArgs[i]
|
|
if (!k?.startsWith('--')) continue
|
|
const next = rawArgs[i + 1]
|
|
if (!next || next.startsWith('--')) {
|
|
out[k.slice(2)] = true
|
|
} else {
|
|
out[k.slice(2)] = next
|
|
i++
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
function die(msg, code = 1) {
|
|
console.error(`error: ${msg}`)
|
|
exit(code)
|
|
}
|
|
|
|
function getTauriVersion() {
|
|
const json = JSON.parse(readFileSync(TAURI_CONF, 'utf8'))
|
|
if (!json.version) die('tauri.conf.json has no version')
|
|
return json.version
|
|
}
|
|
|
|
function uploadBlob(localPath, remoteName) {
|
|
if (!env.AZURE_STORAGE_CONNECTION_STRING) {
|
|
die('AZURE_STORAGE_CONNECTION_STRING is not set (see scripts/.release-credentials)')
|
|
}
|
|
const result = spawnSync(
|
|
'az',
|
|
[
|
|
'storage', 'blob', 'upload',
|
|
'--container-name', CONTAINER,
|
|
'--name', remoteName,
|
|
'--file', localPath,
|
|
'--overwrite',
|
|
'--no-progress',
|
|
],
|
|
{ stdio: ['ignore', 'pipe', 'pipe'], env, encoding: 'utf8' },
|
|
)
|
|
if (result.status !== 0) {
|
|
console.error(result.stderr)
|
|
die(`upload failed for ${remoteName}`)
|
|
}
|
|
return `${PUBLIC_HOST}/${remoteName}`
|
|
}
|
|
|
|
const args = parseArgs(argv.slice(2))
|
|
const version = getTauriVersion()
|
|
const versionPrefix = `desktop/${version}`
|
|
|
|
const platforms = {}
|
|
for (const key of PLATFORM_KEYS) {
|
|
const path = args[key]
|
|
if (!path || path === true) continue
|
|
const local = resolve(path)
|
|
try { statSync(local) } catch { die(`${key} artifact missing: ${local}`) }
|
|
const sigLocal = `${local}.sig`
|
|
let signature
|
|
try { signature = readFileSync(sigLocal, 'utf8').trim() }
|
|
catch { die(`signature missing: ${sigLocal} (rebuild with TAURI_SIGNING_PRIVATE_KEY set)`) }
|
|
|
|
const remoteArtifact = `${versionPrefix}/${basename(local)}`
|
|
const remoteSig = `${versionPrefix}/${basename(sigLocal)}`
|
|
console.log(`Uploading ${key}: ${remoteArtifact}`)
|
|
const url = uploadBlob(local, remoteArtifact)
|
|
uploadBlob(sigLocal, remoteSig)
|
|
|
|
platforms[key] = { signature, url }
|
|
}
|
|
|
|
if (Object.keys(platforms).length === 0) {
|
|
die('no platform artifacts supplied — pass at least one --<target> <path>')
|
|
}
|
|
|
|
const manifest = {
|
|
version,
|
|
notes: args.notes || `Heicode ${version}`,
|
|
pub_date: new Date().toISOString(),
|
|
platforms,
|
|
}
|
|
|
|
const manifestPath = resolve(REPO_ROOT, MANIFEST_REL)
|
|
if (!existsSync(dirname(manifestPath))) mkdirSync(dirname(manifestPath), { recursive: true })
|
|
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`)
|
|
console.log(`\nWrote ${manifestPath}`)
|
|
console.log(`Version : ${version}`)
|
|
console.log(`Platforms: ${Object.keys(platforms).join(', ')}`)
|
|
console.log('\nNext: commit + push website to redeploy Azure SWA.')
|
|
console.log(` cd ${join(REPO_ROOT, 'website')}`)
|
|
console.log(` git add public/updater/latest.json`)
|
|
console.log(` git commit -m "release: desktop ${version}"`)
|
|
console.log(' git push')
|