feat: release OpsXShell 2.1.0 native Tauri edition

This commit is contained in:
Logan.Li committed 2026-07-30 23:32:51 +08:00
1 parent e9a0d21758
commit a3dba0c6ed
311 files changed
+31748 -14897

No files matched your search

+57
View File
@@ -0,0 +1,57 @@
import { existsSync } from 'node:fs'
import { homedir } from 'node:os'
import { join } from 'node:path'
import { spawnSync } from 'node:child_process'
const mode = process.argv[2] ?? 'quick'
if (!['quick', 'full'].includes(mode)) {
console.error(`Unknown acceptance mode: ${mode}`)
process.exit(2)
}
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'
const cargoHomeBinary = join(homedir(), '.cargo', 'bin', process.platform === 'win32' ? 'cargo.exe' : 'cargo')
const cargo = process.env.CARGO || (existsSync(cargoHomeBinary) ? cargoHomeBinary : 'cargo')
const manifest = 'src-tauri/Cargo.toml'
const quickSteps = [
['Renderer type contract', npm, ['run', 'typecheck:ui']],
['Test and mock contracts', npm, ['run', 'typecheck:test']],
['Unit and foundation smoke tests', npm, ['test']],
['Rust formatting', cargo, ['fmt', '--manifest-path', manifest, '--', '--check']],
['Rust compile check', cargo, ['check', '--manifest-path', manifest]],
]
const fullSteps = [
['TypeScript and React lint', npm, ['run', 'lint']],
...quickSteps,
['Rust lint with warnings denied', cargo, ['clippy', '--manifest-path', manifest, '--all-targets', '--', '-D', 'warnings']],
['Rust tests', cargo, ['test', '--manifest-path', manifest]],
['Renderer production build', npm, ['run', 'build:ui']],
['Release tooling tests', npm, ['run', 'test:release']],
]
const steps = mode === 'full' ? fullSteps : quickSteps
const startedAt = Date.now()
for (const [label, command, args] of steps) {
console.log(`\n[acceptance] ${label}`)
const result = spawnSync(command, args, {
cwd: process.cwd(),
env: process.env,
stdio: 'inherit',
})
if (result.error) {
console.error(`[acceptance] Unable to run ${command}: ${result.error.message}`)
process.exit(1)
}
if (result.status !== 0) {
console.error(`[acceptance] FAILED: ${label}`)
process.exit(result.status ?? 1)
}
}
const elapsedSeconds = ((Date.now() - startedAt) / 1000).toFixed(1)
console.log(`\n[acceptance] PASS (${mode}, ${elapsedSeconds}s)`)
+16
View File
@@ -0,0 +1,16 @@
import { missingSigningVariables } from './release-tools.mjs'
const platform = process.argv[2] ?? (process.platform === 'darwin' ? 'macos' : process.platform === 'win32' ? 'windows' : '')
if (!platform) {
console.error('[release] Only macOS and Windows release targets are supported')
process.exit(2)
}
const requireSigning = process.env.REQUIRE_SIGNING === 'true'
const missing = missingSigningVariables(platform)
if (requireSigning && missing.length > 0) {
console.error(`[release] ${platform} signing is required but these variables are missing: ${missing.join(', ')}`)
process.exit(1)
}
console.log(`[release] ${platform} preflight passed (${requireSigning ? 'signed release required' : missing.length ? 'unsigned development build' : 'signing configured'})`)
+39
View File
@@ -0,0 +1,39 @@
import { readFileSync } from 'node:fs'
export const REQUIRED_SIGNING_ENV = {
macos: [
'APPLE_CERTIFICATE',
'APPLE_CERTIFICATE_PASSWORD',
'APPLE_SIGNING_IDENTITY',
'APPLE_ID',
'APPLE_PASSWORD',
'APPLE_TEAM_ID',
],
windows: ['WINDOWS_CERTIFICATE_BASE64', 'WINDOWS_CERTIFICATE_PASSWORD'],
}
export function missingSigningVariables(platform, env = process.env) {
const required = REQUIRED_SIGNING_ENV[platform]
if (!required) throw new Error(`Unsupported release platform: ${platform}`)
return required.filter(name => !env[name]?.trim())
}
export function readPeMachine(path) {
const buffer = readFileSync(path)
if (buffer.length < 64 || buffer.readUInt16LE(0) !== 0x5a4d) {
throw new Error(`${path} is not a PE executable`)
}
const peOffset = buffer.readUInt32LE(0x3c)
if (peOffset + 6 > buffer.length || buffer.toString('ascii', peOffset, peOffset + 4) !== 'PE\0\0') {
throw new Error(`${path} has an invalid PE header`)
}
return buffer.readUInt16LE(peOffset + 4)
}
export function assertWithinSize(path, maxBytes) {
const size = readFileSync(path).byteLength
if (size > maxBytes) {
throw new Error(`${path} is ${(size / 1024 / 1024).toFixed(1)} MB; limit is ${(maxBytes / 1024 / 1024).toFixed(1)} MB`)
}
return size
}
+25
View File
@@ -0,0 +1,25 @@
import assert from 'node:assert/strict'
import { mkdtempSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import test from 'node:test'
import { assertWithinSize, missingSigningVariables, readPeMachine, REQUIRED_SIGNING_ENV } from './release-tools.mjs'
test('signing preflight reports every missing secret', () => {
assert.deepEqual(missingSigningVariables('windows', {}), REQUIRED_SIGNING_ENV.windows)
const env = Object.fromEntries(REQUIRED_SIGNING_ENV.macos.map(key => [key, 'configured']))
assert.deepEqual(missingSigningVariables('macos', env), [])
})
test('PE reader accepts x86_64 and rejects malformed files', () => {
const dir = mkdtempSync(join(tmpdir(), 'opsxshell-release-'))
const pe = Buffer.alloc(128)
pe.writeUInt16LE(0x5a4d, 0)
pe.writeUInt32LE(64, 0x3c)
pe.write('PE\0\0', 64, 'ascii')
pe.writeUInt16LE(0x8664, 68)
const path = join(dir, 'app.exe')
writeFileSync(path, pe)
assert.equal(readPeMachine(path), 0x8664)
assert.throws(() => assertWithinSize(path, 64), /limit is/)
})
+62
View File
@@ -0,0 +1,62 @@
import { execFileSync, spawnSync } from 'node:child_process'
import { existsSync, readdirSync } from 'node:fs'
import { join } from 'node:path'
import { assertWithinSize, readPeMachine } from './release-tools.mjs'
const platform = process.argv[2] ?? (process.platform === 'darwin' ? 'macos' : process.platform === 'win32' ? 'windows' : '')
const root = process.argv[3] ?? (platform === 'macos'
? 'src-tauri/target/aarch64-apple-darwin/release/bundle'
: 'src-tauri/target/release/bundle')
const requireSigning = process.env.REQUIRE_SIGNING === 'true'
const maxBinaryBytes = Number(process.env.MAX_RELEASE_BINARY_MB ?? 40) * 1024 * 1024
const maxInstallerBytes = Number(process.env.MAX_RELEASE_INSTALLER_MB ?? 40) * 1024 * 1024
function walk(dir) {
if (!existsSync(dir)) return []
return readdirSync(dir, { withFileTypes: true }).flatMap(entry => {
const path = join(dir, entry.name)
return entry.isDirectory() ? walk(path) : [path]
})
}
function fail(message) {
console.error(`[release] ${message}`)
process.exit(1)
}
const files = walk(root)
if (files.length === 0) fail(`No release artifacts found under ${root}`)
if (platform === 'macos') {
const appExecutable = files.find(path => /\.app\/Contents\/MacOS\/[^/]+$/.test(path))
const dmg = files.find(path => path.includes('/dmg/') && path.endsWith('.dmg'))
if (!appExecutable || !dmg) fail('macOS release must contain an .app executable and a .dmg')
assertWithinSize(appExecutable, maxBinaryBytes)
assertWithinSize(dmg, maxInstallerBytes)
const architectures = execFileSync('lipo', ['-archs', appExecutable], { encoding: 'utf8' }).trim().split(/\s+/)
if (!architectures.includes('arm64')) fail(`Expected arm64 app, found: ${architectures.join(', ')}`)
if (requireSigning) {
execFileSync('codesign', ['--verify', '--deep', '--strict', appExecutable.slice(0, appExecutable.indexOf('.app/') + 4)], { stdio: 'inherit' })
const inspection = spawnSync('codesign', ['-dv', '--verbose=4', appExecutable.slice(0, appExecutable.indexOf('.app/') + 4)], { encoding: 'utf8' })
if (inspection.status !== 0) fail(`Unable to inspect macOS signature: ${inspection.stderr}`)
const detail = `${inspection.stdout}\n${inspection.stderr}`
if (/Signature=adhoc/.test(detail)) fail('macOS application is only ad-hoc signed')
execFileSync('spctl', ['--assess', '--type', 'execute', '--verbose=2', appExecutable.slice(0, appExecutable.indexOf('.app/') + 4)], { stdio: 'inherit' })
}
console.log(`[release] macOS arm64 artifacts verified: ${root}`)
} else if (platform === 'windows') {
const executables = files.filter(path => path.toLowerCase().endsWith('.exe'))
const installers = files.filter(path => /\.(?:msi|exe)$/i.test(path))
if (executables.length === 0 || installers.length === 0) fail('Windows release must contain an x64 executable or installer')
for (const executable of executables) {
assertWithinSize(executable, maxBinaryBytes)
if (readPeMachine(executable) !== 0x8664) fail(`${executable} is not an x86_64 PE executable`)
if (requireSigning) {
execFileSync('powershell', ['-NoProfile', '-Command', `$s = Get-AuthenticodeSignature -LiteralPath '${executable.replaceAll("'", "''")}'; if ($s.Status -ne 'Valid') { throw \"Invalid signature: $($s.Status)\" }`], { stdio: 'inherit' })
}
}
for (const installer of installers) assertWithinSize(installer, maxInstallerBytes)
console.log(`[release] Windows x64 artifacts verified: ${root}`)
} else {
fail(`Unsupported platform: ${platform}`)
}