forked from mxx1111/mdlook
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrelease-desktop.mjs
More file actions
165 lines (141 loc) · 5.38 KB
/
Copy pathrelease-desktop.mjs
File metadata and controls
165 lines (141 loc) · 5.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import { execFileSync } from 'node:child_process'
import crypto from 'node:crypto'
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
const root = path.resolve(import.meta.dirname, '..')
const webDir = path.join(root, 'apps/web')
const tauriDir = path.join(webDir, 'src-tauri')
const tauriConfigPath = path.join(tauriDir, 'tauri.conf.json')
const cargoTomlPath = path.join(tauriDir, 'Cargo.toml')
const cargoLockPath = path.join(tauriDir, 'Cargo.lock')
const latestPath = path.join(webDir, 'public/download/latest.json')
const bumpScriptPath = path.join(root, 'scripts/bump-desktop-version.mjs')
const releaseDir = path.join(root, 'dist-release')
function run(command, args, options = {}) {
return execFileSync(command, args, {
cwd: root,
encoding: 'utf8',
stdio: options.capture ? 'pipe' : 'inherit',
...options,
})
}
function readJson(file) {
return JSON.parse(fs.readFileSync(file, 'utf8'))
}
function writeJson(file, value) {
fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`)
}
function sha256(file) {
const hash = crypto.createHash('sha256')
hash.update(fs.readFileSync(file))
return hash.digest('hex')
}
function releaseDate() {
const parts = new Intl.DateTimeFormat('en-CA', {
timeZone: 'Asia/Shanghai',
year: 'numeric',
month: '2-digit',
day: '2-digit',
}).formatToParts(new Date())
const values = Object.fromEntries(parts.map(part => [part.type, part.value]))
return `${values.year}-${values.month}-${values.day}`
}
function architecture() {
if (process.arch === 'arm64')
return { artifact: 'aarch64', description: '适用于 M1、M2、M3、M4 及后续 M 系列 Mac' }
if (process.arch === 'x64')
return { artifact: 'x64', description: '适用于 Intel 芯片 Mac' }
throw new Error(`Unsupported desktop release architecture: ${process.arch}`)
}
function appVersion(appPath) {
if (process.platform !== 'darwin')
throw new Error('Desktop release verification currently requires macOS')
return run('/usr/libexec/PlistBuddy', [
'-c',
'Print :CFBundleShortVersionString',
path.join(appPath, 'Contents/Info.plist'),
], { capture: true }).trim()
}
function usage() {
console.log(`Usage: pnpm desktop:release [patch|minor|major|x.y.z]
Builds and verifies the macOS app and DMG, then writes:
dist-release/mdlook_<version>_<arch>.dmg
dist-release/latest.json
apps/web/public/download/latest.json`)
}
const requested = process.argv[2] ?? 'patch'
if (requested === '-h' || requested === '--help') {
usage()
process.exit(0)
}
const allowed = /^(patch|minor|major|\d+\.\d+\.\d+)$/
if (!allowed.test(requested)) {
usage()
process.exit(1)
}
const status = run('git', ['status', '--porcelain'], { capture: true }).trim()
if (status)
throw new Error('Desktop release requires a clean Git worktree. Commit or stash changes first.')
const rollbackPaths = [tauriConfigPath, cargoTomlPath, cargoLockPath, latestPath]
const originals = new Map(rollbackPaths.map(file => [file, fs.readFileSync(file)]))
let completed = false
try {
run(process.execPath, [bumpScriptPath, requested])
const version = readJson(tauriConfigPath).version
const sourceCommit = run('git', ['rev-parse', '--short=12', 'HEAD'], { capture: true }).trim()
const arch = architecture()
console.log(`Building mdlook desktop ${version} from ${sourceCommit}...`)
run('pnpm', ['--filter', '@md/web', 'tauri', 'build'])
const appPath = path.join(tauriDir, 'target/release/bundle/macos/mdlook.app')
const dmgName = `mdlook_${version}_${arch.artifact}.dmg`
const builtDmgPath = path.join(tauriDir, 'target/release/bundle/dmg', dmgName)
if (!fs.existsSync(appPath))
throw new Error(`App bundle not found: ${appPath}`)
if (!fs.existsSync(builtDmgPath))
throw new Error(`DMG not found: ${builtDmgPath}`)
const bundledVersion = appVersion(appPath)
if (bundledVersion !== version)
throw new Error(`App version mismatch: config=${version}, bundle=${bundledVersion}`)
run('codesign', ['--verify', '--deep', '--strict', appPath])
run('hdiutil', ['verify', builtDmgPath])
const stat = fs.statSync(builtDmgPath)
const digest = sha256(builtDmgPath)
const manifest = {
schemaVersion: 1,
version,
releasedAt: releaseDate(),
sourceCommit,
files: [
{
label: process.arch === 'arm64' ? 'macOS Apple 芯片版' : 'macOS Intel 芯片版',
description: arch.description,
size: `${(stat.size / 1024 / 1024).toFixed(1)} MB`,
sizeBytes: stat.size,
url: `/downloads/${dmgName}`,
sha256: digest,
recommended: process.arch === 'arm64',
},
],
}
fs.mkdirSync(releaseDir, { recursive: true })
const releaseDmgPath = path.join(releaseDir, dmgName)
fs.copyFileSync(builtDmgPath, releaseDmgPath)
writeJson(latestPath, manifest)
writeJson(path.join(releaseDir, 'latest.json'), manifest)
console.log('\nDesktop release prepared and verified:')
console.log(` version: ${version}`)
console.log(` app: ${appPath}`)
console.log(` dmg: ${releaseDmgPath}`)
console.log(` bytes: ${stat.size}`)
console.log(` sha256: ${digest}`)
console.log(` manifest: ${path.join(releaseDir, 'latest.json')}`)
completed = true
}
finally {
if (!completed) {
for (const [file, content] of originals)
fs.writeFileSync(file, content)
console.error('Desktop release failed; tracked version and manifest files were restored.')
}
}