chore(ci): upload auto-update needed files & merge latest-mac.yml
Close #900
This commit is contained in:
@@ -305,6 +305,14 @@ jobs:
|
||||
run: |
|
||||
pnpm run -F @proj-airi/stage-tamagotchi rename-artifacts ${{ matrix.target }} --release --auto-tag
|
||||
|
||||
- name: Upload macOS update info (Automatic + macOS Only)
|
||||
if: ${{ github.event_name == 'release' && (matrix.os == 'macos-latest' || matrix.os == 'macos-15-intel') }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: latest-mac-yml-${{ matrix.arch }}
|
||||
path: apps/stage-tamagotchi/bundle/latest-mac.yml
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload To GitHub Releases (Automatic)
|
||||
if: ${{ github.event_name == 'release' }}
|
||||
uses: softprops/action-gh-release@v2
|
||||
@@ -316,9 +324,52 @@ jobs:
|
||||
apps/stage-tamagotchi/bundle/${{ env.PRODUCT_NAME }}-*.deb
|
||||
apps/stage-tamagotchi/bundle/${{ env.PRODUCT_NAME }}-*.rpm
|
||||
apps/stage-tamagotchi/bundle/${{ env.PRODUCT_NAME }}-*.flatpak
|
||||
apps/stage-tamagotchi/bundle/latest-*.yml
|
||||
append_body: true
|
||||
|
||||
# - name: Clean up keychain and provisioning profile (macOS Only)
|
||||
# if: ${{ always() && (matrix.os == 'macos-15-intel' || matrix.os == 'macos-latest') }}
|
||||
# run: |
|
||||
# security delete-keychain $RUNNER_TEMP/app-signing.keychain-db
|
||||
|
||||
merge-mac-latest:
|
||||
name: Merge macOS latest-mac.yml
|
||||
if: ${{ github.event_name == 'release' }}
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
run_install: false
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: pnpm
|
||||
- run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Download latest-mac.yml (x64)
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: latest-mac-yml-x64
|
||||
path: artifacts/x64
|
||||
|
||||
- name: Download latest-mac.yml (arm64)
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: latest-mac-yml-arm64
|
||||
path: artifacts/arm64
|
||||
|
||||
- name: Merge latest-mac.yml
|
||||
run: |
|
||||
pnpm -F @proj-airi/stage-tamagotchi exec tsx scripts/merge-latest-mac.ts \
|
||||
--input artifacts/x64/latest-mac.yml \
|
||||
--input artifacts/arm64/latest-mac.yml \
|
||||
--output apps/stage-tamagotchi/bundle/latest-mac.yml
|
||||
|
||||
- name: Upload merged latest-mac.yml
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: apps/stage-tamagotchi/bundle/latest-mac.yml
|
||||
tag_name: ${{ github.event.release.tag_name }}
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"build:mac": "pnpm run build && electron-builder --mac",
|
||||
"build:linux": "pnpm run build && electron-builder --linux",
|
||||
"rename-artifacts": "mkdir -p bundle && tsx scripts/rename-artifacts.ts",
|
||||
"merge-latest-mac": "tsx scripts/merge-latest-mac.ts",
|
||||
"artifacts-metadata": "tsx scripts/artifacts-metadata.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
import { existsSync, readdirSync } from 'node:fs'
|
||||
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
||||
import { dirname, resolve } from 'node:path'
|
||||
import { exit } from 'node:process'
|
||||
|
||||
import { cac } from 'cac'
|
||||
|
||||
import * as yaml from 'yaml'
|
||||
|
||||
interface UpdateInfoFile {
|
||||
url: string
|
||||
sha2?: string
|
||||
sha512?: string
|
||||
size?: number
|
||||
}
|
||||
|
||||
interface UpdateInfo {
|
||||
files?: UpdateInfoFile[]
|
||||
path?: string
|
||||
sha2?: string
|
||||
sha512?: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
type Platform = 'x64' | 'arm64' | 'both' | 'none'
|
||||
|
||||
function detectPlatform(updateInfo: UpdateInfo): Platform {
|
||||
const urls: string[] = []
|
||||
if (Array.isArray(updateInfo.files)) {
|
||||
for (const file of updateInfo.files) {
|
||||
if (typeof file?.url === 'string') {
|
||||
urls.push(file.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (typeof updateInfo.path === 'string') {
|
||||
urls.push(updateInfo.path)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line regexp/no-unused-capturing-group
|
||||
const hasArm64 = urls.some(url => /(^|[-_/])arm64([-.]|$)/i.test(url))
|
||||
// eslint-disable-next-line regexp/no-unused-capturing-group
|
||||
const hasX64FromName = urls.some(url => /(^|[-_/])x64([-.]|$)/i.test(url))
|
||||
const hasMacZip = urls.some(url => /-mac\.zip$/i.test(url) && !/arm64/i.test(url))
|
||||
const hasX64 = hasX64FromName || hasMacZip
|
||||
|
||||
if (hasX64 && hasArm64) {
|
||||
return 'both'
|
||||
}
|
||||
if (hasX64) {
|
||||
return 'x64'
|
||||
}
|
||||
if (hasArm64) {
|
||||
return 'arm64'
|
||||
}
|
||||
return 'none'
|
||||
}
|
||||
|
||||
function mergeFiles(arm64: UpdateInfo, x64: UpdateInfo): UpdateInfo {
|
||||
const arm64Files = Array.isArray(arm64.files) ? arm64.files : []
|
||||
const x64Files = Array.isArray(x64.files) ? x64.files : []
|
||||
|
||||
const byUrl = new Map<string, UpdateInfoFile>()
|
||||
for (const file of [...arm64Files, ...x64Files]) {
|
||||
if (file?.url) {
|
||||
byUrl.set(file.url, file)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...arm64,
|
||||
files: [...byUrl.values()],
|
||||
}
|
||||
}
|
||||
|
||||
async function readUpdateInfo(filePath: string): Promise<UpdateInfo> {
|
||||
const raw = await readFile(filePath, 'utf8')
|
||||
return yaml.parse(raw) as UpdateInfo
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const cli = cac('merge-latest-mac')
|
||||
.option('--input <path>', 'Input latest-mac yml file', { default: [], type: [String] })
|
||||
.option('--dir <path>', 'Scan directory for latest-mac*.yml files', { default: '' })
|
||||
.option('--output <path>', 'Output file path', { default: '' })
|
||||
|
||||
const args = cli.parse()
|
||||
const inputs = (args.options.input as string[]).filter(Boolean)
|
||||
const dir = String(args.options.dir || '').trim()
|
||||
|
||||
let files: string[] = []
|
||||
if (inputs.length > 0) {
|
||||
files = inputs.map(file => resolve(file))
|
||||
}
|
||||
else {
|
||||
const scanDir = dir || resolve('bundle')
|
||||
const candidates = readdirSync(scanDir).filter(
|
||||
file => file.startsWith('latest-mac') && file.endsWith('.yml'),
|
||||
)
|
||||
files = candidates.map(file => resolve(scanDir, file))
|
||||
}
|
||||
|
||||
if (files.length === 0) {
|
||||
throw new Error('No latest-mac*.yml files found')
|
||||
}
|
||||
|
||||
const entries: { filePath: string, updateInfo: UpdateInfo, platform: Platform }[] = []
|
||||
for (const filePath of files) {
|
||||
if (!existsSync(filePath)) {
|
||||
continue
|
||||
}
|
||||
const updateInfo = await readUpdateInfo(filePath)
|
||||
const platform = detectPlatform(updateInfo)
|
||||
entries.push({ filePath, updateInfo, platform })
|
||||
}
|
||||
|
||||
if (entries.length === 0) {
|
||||
throw new Error('No readable latest-mac*.yml files found')
|
||||
}
|
||||
|
||||
const outputPath = String(args.options.output || '').trim()
|
||||
|| resolve(dir || 'bundle', 'latest-mac.yml')
|
||||
await mkdir(dirname(outputPath), { recursive: true })
|
||||
|
||||
const mergedEntry = entries.find(entry => entry.platform === 'both')
|
||||
if (mergedEntry) {
|
||||
await writeFile(outputPath, yaml.stringify(mergedEntry.updateInfo), 'utf8')
|
||||
return
|
||||
}
|
||||
|
||||
const x64Entries = entries.filter(entry => entry.platform === 'x64')
|
||||
const arm64Entries = entries.filter(entry => entry.platform === 'arm64')
|
||||
|
||||
if (x64Entries.length === 0 && arm64Entries.length === 0) {
|
||||
throw new Error('No x64 or arm64 update info found')
|
||||
}
|
||||
|
||||
if (x64Entries.length === 0) {
|
||||
await writeFile(outputPath, yaml.stringify(arm64Entries[0].updateInfo), 'utf8')
|
||||
return
|
||||
}
|
||||
|
||||
if (arm64Entries.length === 0) {
|
||||
await writeFile(outputPath, yaml.stringify(x64Entries[0].updateInfo), 'utf8')
|
||||
return
|
||||
}
|
||||
|
||||
const merged = mergeFiles(arm64Entries[0].updateInfo, x64Entries[0].updateInfo)
|
||||
await writeFile(outputPath, yaml.stringify(merged), 'utf8')
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error)
|
||||
exit(1)
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
import process from 'node:process'
|
||||
|
||||
import { mkdirSync, readdirSync, renameSync } from 'node:fs'
|
||||
import { existsSync, mkdirSync, readdirSync, renameSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
|
||||
import { cac } from 'cac'
|
||||
@@ -73,6 +73,14 @@ async function main() {
|
||||
const renameFrom = join(srcPrefix, filename.outputFilename)
|
||||
const renameTo = join(bundlePrefix, filename.releaseArtifactFilename)
|
||||
console.info('renaming, from:', renameFrom, 'to:', renameTo)
|
||||
if (!existsSync(renameFrom)) {
|
||||
const message = `missing artifact: ${renameFrom}`
|
||||
if (filename.optional) {
|
||||
console.warn(message)
|
||||
continue
|
||||
}
|
||||
throw new Error(message)
|
||||
}
|
||||
renameSync(renameFrom, renameTo)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ interface FilenameOutputEntry {
|
||||
releaseArtifactFilename: string
|
||||
productName: string
|
||||
version: string
|
||||
optional?: boolean
|
||||
}
|
||||
|
||||
export function mapArchFor(
|
||||
@@ -118,6 +119,28 @@ export function mapArchFor(
|
||||
}
|
||||
}
|
||||
|
||||
function getLatestUpdateFilename(target: string): string | null {
|
||||
switch (target) {
|
||||
case 'x86_64-pc-windows-msvc':
|
||||
return 'latest.yml'
|
||||
case 'x86_64-unknown-linux-gnu':
|
||||
return 'latest-linux.yml'
|
||||
case 'aarch64-unknown-linux-gnu':
|
||||
return 'latest-linux-arm64.yml'
|
||||
case 'aarch64-apple-darwin':
|
||||
case 'x86_64-apple-darwin':
|
||||
return 'latest-mac.yml'
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function getMacZipFilename(productName: string, version: string, target: string): string {
|
||||
const arch = mapArchFor(target, 'zip')
|
||||
const archPrefix = arch === 'x64' ? '' : `${arch}-`
|
||||
return `${productName}-${version}-${archPrefix}mac.zip`
|
||||
}
|
||||
|
||||
export async function getFilenames(target: string, options: { release: boolean, autoTag: boolean, tag: string[] }): Promise<FilenameOutputEntry[]> {
|
||||
const electronBuilder = await getElectronBuilderConfig()
|
||||
const version = await getVersion(options)
|
||||
@@ -153,6 +176,15 @@ export async function getFilenames(target: string, options: { release: boolean,
|
||||
productName,
|
||||
version,
|
||||
},
|
||||
{
|
||||
target: 'x86_64-pc-windows-msvc',
|
||||
extension: 'latest.yml',
|
||||
outputFilename: 'latest.yml',
|
||||
releaseArtifactFilename: 'latest.yml',
|
||||
productName,
|
||||
version,
|
||||
optional: true,
|
||||
},
|
||||
]
|
||||
case 'x86_64-unknown-linux-gnu':
|
||||
{
|
||||
@@ -239,6 +271,19 @@ export async function getFilenames(target: string, options: { release: boolean,
|
||||
)
|
||||
}
|
||||
|
||||
const latestUpdateFilename = getLatestUpdateFilename(target)
|
||||
if (latestUpdateFilename) {
|
||||
artifacts.push({
|
||||
target: 'x86_64-unknown-linux-gnu',
|
||||
extension: latestUpdateFilename,
|
||||
outputFilename: latestUpdateFilename,
|
||||
releaseArtifactFilename: latestUpdateFilename,
|
||||
productName,
|
||||
version,
|
||||
optional: true,
|
||||
})
|
||||
}
|
||||
|
||||
return artifacts
|
||||
}
|
||||
case 'aarch64-unknown-linux-gnu':
|
||||
@@ -326,10 +371,24 @@ export async function getFilenames(target: string, options: { release: boolean,
|
||||
)
|
||||
}
|
||||
|
||||
const latestUpdateFilename = getLatestUpdateFilename(target)
|
||||
if (latestUpdateFilename) {
|
||||
artifacts.push({
|
||||
target: 'aarch64-unknown-linux-gnu',
|
||||
extension: latestUpdateFilename,
|
||||
outputFilename: latestUpdateFilename,
|
||||
releaseArtifactFilename: latestUpdateFilename,
|
||||
productName,
|
||||
version,
|
||||
optional: true,
|
||||
})
|
||||
}
|
||||
|
||||
return artifacts
|
||||
}
|
||||
case 'aarch64-apple-darwin':
|
||||
return [
|
||||
{
|
||||
const artifacts: FilenameOutputEntry[] = [
|
||||
{
|
||||
target: 'aarch64-apple-darwin',
|
||||
extension: 'dmg',
|
||||
@@ -351,8 +410,32 @@ export async function getFilenames(target: string, options: { release: boolean,
|
||||
version,
|
||||
},
|
||||
]
|
||||
|
||||
artifacts.push(
|
||||
{
|
||||
target: 'aarch64-apple-darwin',
|
||||
extension: 'zip',
|
||||
outputFilename: getMacZipFilename(productName, beforeVersion, target),
|
||||
releaseArtifactFilename: getMacZipFilename(productName, version, target),
|
||||
productName,
|
||||
version,
|
||||
},
|
||||
{
|
||||
target: 'aarch64-apple-darwin',
|
||||
extension: 'latest-mac.yml',
|
||||
outputFilename: 'latest-mac.yml',
|
||||
releaseArtifactFilename: 'latest-mac.yml',
|
||||
productName,
|
||||
version,
|
||||
optional: true,
|
||||
},
|
||||
)
|
||||
|
||||
return artifacts
|
||||
}
|
||||
case 'x86_64-apple-darwin':
|
||||
return [
|
||||
{
|
||||
const artifacts: FilenameOutputEntry[] = [
|
||||
{
|
||||
target: 'x86_64-apple-darwin',
|
||||
extension: 'dmg',
|
||||
@@ -374,6 +457,29 @@ export async function getFilenames(target: string, options: { release: boolean,
|
||||
version,
|
||||
},
|
||||
]
|
||||
|
||||
artifacts.push(
|
||||
{
|
||||
target: 'x86_64-apple-darwin',
|
||||
extension: 'zip',
|
||||
outputFilename: getMacZipFilename(productName, beforeVersion, target),
|
||||
releaseArtifactFilename: getMacZipFilename(productName, version, target),
|
||||
productName,
|
||||
version,
|
||||
},
|
||||
{
|
||||
target: 'x86_64-apple-darwin',
|
||||
extension: 'latest-mac.yml',
|
||||
outputFilename: 'latest-mac.yml',
|
||||
releaseArtifactFilename: 'latest-mac.yml',
|
||||
productName,
|
||||
version,
|
||||
optional: true,
|
||||
},
|
||||
)
|
||||
|
||||
return artifacts
|
||||
}
|
||||
default:
|
||||
console.error('Target is not supported')
|
||||
process.exit(1)
|
||||
|
||||
Reference in New Issue
Block a user