training-update-manifest.mjs 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
  1. #!/usr/bin/env node
  2. // 为 Bun ZIP 交付生成 CDN 使用的 latest.json。该脚本不发布 npm,也不读取任何凭据。
  3. import fs from 'node:fs';
  4. import path from 'node:path';
  5. import crypto from 'node:crypto';
  6. import { fileURLToPath } from 'node:url';
  7. const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
  8. const DIST = path.resolve(process.env.QIWEI_DIST_DIR || path.join(ROOT, 'dist'));
  9. const OUT = path.resolve(process.env.QIWEI_UPDATE_MANIFEST_OUT || path.join(DIST, 'latest.json'));
  10. const BASE_URL = String(process.env.QIWEI_UPDATE_BASE_URL || 'https://repos.fmode.cn/x/openclaw-skills/packages').replace(/\/$/, '');
  11. function readJson(filePath) { return JSON.parse(fs.readFileSync(filePath, 'utf8').replace(/^\uFEFF/, '')); }
  12. function sha256(filePath) { return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex'); }
  13. function findArchive(platform, version) {
  14. const candidates = [
  15. path.join(DIST, `qiwei-training-${platform}-${version}.zip`),
  16. path.join(DIST, `qiwei-training-${platform}.zip`),
  17. path.join(DIST, `qiwei-training-${platform}-latest.zip`),
  18. ];
  19. return candidates.find(filePath => fs.existsSync(filePath)) || '';
  20. }
  21. const platformDirs = {
  22. 'win-x64': ['qiwei-training-win-x64', 'qiwei-training-win-x64-v'],
  23. 'mac-arm64': ['qiwei-training-mac-arm64'],
  24. 'mac-x64': ['qiwei-training-mac-x64'],
  25. };
  26. const firstDir = [DIST, ...Object.values(platformDirs).flat().map(name => path.join(DIST, name))]
  27. .find(dir => fs.existsSync(path.join(dir, 'version.json')));
  28. if (!firstDir) throw new Error(`dist 中没有带 version.json 的 Bun 交付目录:${DIST}`);
  29. const baseVersion = readJson(path.join(firstDir, 'version.json'));
  30. const platforms = {};
  31. for (const [platform, dirs] of Object.entries(platformDirs)) {
  32. const directory = [DIST, ...dirs.map(name => path.join(DIST, name))]
  33. .find(dir => fs.existsSync(path.join(dir, 'version.json')) && readJson(path.join(dir, 'version.json')).platform === platform);
  34. const version = directory ? readJson(path.join(directory, 'version.json')).version : baseVersion.version;
  35. const archive = findArchive(platform, version);
  36. if (!archive) continue;
  37. const stat = fs.statSync(archive);
  38. platforms[platform] = {
  39. url: `${BASE_URL}/${path.basename(archive)}`,
  40. sha256: sha256(archive),
  41. size: stat.size,
  42. };
  43. }
  44. const manifest = {
  45. product: 'fmode-qiwei-training',
  46. version: baseVersion.version,
  47. publishedAt: new Date().toISOString(),
  48. notes: String(process.env.QIWEI_UPDATE_NOTES || '').trim(),
  49. platforms,
  50. };
  51. fs.mkdirSync(path.dirname(OUT), { recursive: true });
  52. fs.writeFileSync(OUT, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
  53. process.stdout.write(`${JSON.stringify({ status: 'ok', output: OUT, version: manifest.version, platforms: Object.keys(platforms) }, null, 2)}\n`);