download-assets.mjs 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
  1. // 下载可下载资产到 design-archive/,生成带 sha256 的索引
  2. import { readFileSync, writeFileSync, mkdirSync, createWriteStream } from 'node:fs'
  3. import { createHash } from 'node:crypto'
  4. import { join } from 'node:path'
  5. import { Readable } from 'node:stream'
  6. import { pipeline } from 'node:stream/promises'
  7. const raw = '.scratch/lanhu-cockpit-asset-replacement/research/raw'
  8. const outDir = '.scratch/lanhu-cockpit-asset-replacement/design-archive'
  9. mkdirSync(outDir, { recursive: true })
  10. const index = JSON.parse(readFileSync(join(raw, 'asset-index.json'), 'utf8'))
  11. const slug = (s) => s.replace(/[^\w\u4e00-\u9fa5.-]+/g, '_').slice(0, 40)
  12. const extOf = (u) => {
  13. const m = /\.([a-z0-9]+)(\?|$)/i.exec(u)
  14. return m ? m[1].toLowerCase() : 'bin'
  15. }
  16. const tasks = []
  17. // 1) 图片填充(去重 URL,保留首次出现的来源画板)
  18. const seen = new Map()
  19. for (const f of index.imageFills) {
  20. if (!seen.has(f.url)) seen.set(f.url, f)
  21. }
  22. for (const [url, f] of seen) {
  23. tasks.push({ kind: 'image-fill', artboard: f.artboard, layer: f.path, w: f.w, h: f.h, url })
  24. }
  25. // 2) 项目级 assets(无名字,按画板顺序编号)
  26. const perArtboard = new Map()
  27. for (const a of index.assetUrls) {
  28. const n = (perArtboard.get(a.artboard) || 0) + 1
  29. perArtboard.set(a.artboard, n)
  30. tasks.push({ kind: 'slice', artboard: a.artboard, layer: `slice-${String(n).padStart(2, '0')}`, w: null, h: null, url: a.url })
  31. }
  32. const results = []
  33. for (const t of tasks) {
  34. const ext = extOf(t.url)
  35. const hash8 = (t.url.match(/([0-9a-f]{8})[0-9a-f]*\.[a-z0-9]+$/i) || [, 'unknown'])[1]
  36. const name = `${slug(t.artboard)}__${t.kind}__${slug(t.layer)}__${hash8}.${ext}`
  37. const dest = join(outDir, name)
  38. const res = await fetch(t.url)
  39. if (!res.ok) {
  40. results.push({ ...t, local: null, error: `HTTP ${res.status}` })
  41. continue
  42. }
  43. await pipeline(Readable.fromWeb(res.body), createWriteStream(dest))
  44. const buf = readFileSync(dest)
  45. results.push({
  46. kind: t.kind,
  47. artboard: t.artboard,
  48. layer: t.layer,
  49. w: t.w,
  50. h: t.h,
  51. url: t.url,
  52. local: name,
  53. bytes: buf.length,
  54. sha256: createHash('sha256').update(buf).digest('hex'),
  55. })
  56. }
  57. writeFileSync(join(outDir, 'index.json'), JSON.stringify({ downloadedAt: new Date().toISOString(), count: results.length, files: results }, null, 1))
  58. console.log('downloaded:', results.filter((r) => !r.error).length, 'of', tasks.length)
  59. for (const r of results) console.log(` ${r.error ? 'ERR ' + r.error : r.local} | ${r.kind} | ${r.artboard} | ${r.layer} | ${r.bytes ?? ''}`)