#!/usr/bin/env node // 为 Bun ZIP 交付生成 CDN 使用的 latest.json。该脚本不发布 npm,也不读取任何凭据。 import fs from 'node:fs'; import path from 'node:path'; import crypto from 'node:crypto'; import { fileURLToPath } from 'node:url'; const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const DIST = path.resolve(process.env.QIWEI_DIST_DIR || path.join(ROOT, 'dist')); const OUT = path.resolve(process.env.QIWEI_UPDATE_MANIFEST_OUT || path.join(DIST, 'latest.json')); const BASE_URL = String(process.env.QIWEI_UPDATE_BASE_URL || 'https://repos.fmode.cn/x/openclaw-skills/packages').replace(/\/$/, ''); function readJson(filePath) { return JSON.parse(fs.readFileSync(filePath, 'utf8').replace(/^\uFEFF/, '')); } function sha256(filePath) { return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex'); } function findArchive(platform, version) { const candidates = [ path.join(DIST, `qiwei-training-${platform}-${version}.zip`), path.join(DIST, `qiwei-training-${platform}.zip`), path.join(DIST, `qiwei-training-${platform}-latest.zip`), ]; return candidates.find(filePath => fs.existsSync(filePath)) || ''; } const platformDirs = { 'win-x64': ['qiwei-training-win-x64', 'qiwei-training-win-x64-v'], 'mac-arm64': ['qiwei-training-mac-arm64'], 'mac-x64': ['qiwei-training-mac-x64'], }; const firstDir = [DIST, ...Object.values(platformDirs).flat().map(name => path.join(DIST, name))] .find(dir => fs.existsSync(path.join(dir, 'version.json'))); if (!firstDir) throw new Error(`dist 中没有带 version.json 的 Bun 交付目录:${DIST}`); const baseVersion = readJson(path.join(firstDir, 'version.json')); const platforms = {}; for (const [platform, dirs] of Object.entries(platformDirs)) { const directory = [DIST, ...dirs.map(name => path.join(DIST, name))] .find(dir => fs.existsSync(path.join(dir, 'version.json')) && readJson(path.join(dir, 'version.json')).platform === platform); const version = directory ? readJson(path.join(directory, 'version.json')).version : baseVersion.version; const archive = findArchive(platform, version); if (!archive) continue; const stat = fs.statSync(archive); platforms[platform] = { url: `${BASE_URL}/${path.basename(archive)}`, sha256: sha256(archive), size: stat.size, }; } const manifest = { product: 'fmode-qiwei-training', version: baseVersion.version, publishedAt: new Date().toISOString(), notes: String(process.env.QIWEI_UPDATE_NOTES || '').trim(), platforms, }; fs.mkdirSync(path.dirname(OUT), { recursive: true }); fs.writeFileSync(OUT, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8'); process.stdout.write(`${JSON.stringify({ status: 'ok', output: OUT, version: manifest.version, platforms: Object.keys(platforms) }, null, 2)}\n`);