Files
mp-pilates/education/reformer/build.mjs
2026-10-18 17:26:16 +08:00

41 lines
3.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { build } from 'esbuild'
import { readFile, writeFile, mkdir, mkdtemp, rm } from 'node:fs/promises'
import { dirname, resolve, join } from 'node:path'
import { fileURLToPath, pathToFileURL } from 'node:url'
import { tmpdir } from 'node:os'
import { gzipSync } from 'node:zlib'
const here = dirname(fileURLToPath(import.meta.url)), root = resolve(here, '../..')
const temp = await mkdtemp(join(tmpdir(), 'reformer-build-'))
try {
await build({ entryPoints: [join(here, 'src/geometry.ts')], bundle: true, platform: 'node', format: 'esm', outfile: join(temp, 'geometry.mjs') })
const { createFigure, partsInventory } = await import(pathToFileURL(join(temp, 'geometry.mjs')).href)
await build({ entryPoints: [join(root, 'packages/app/src/content/reformer.ts')], bundle: true, platform: 'node', format: 'esm', outfile: join(temp, 'content.mjs') })
const { reformerParts, reformerLesson } = await import(pathToFileURL(join(temp, 'content.mjs')).href)
const live = await build({ entryPoints: [join(here, 'src/controller.ts')], bundle: true, platform: 'browser', format: 'iife', target: ['es2019'], minify: true, write: false })
const [template, css, illustration] = await Promise.all(['page.html', 'page.css', 'illustration.css'].map(name => readFile(join(here, 'src', name), 'utf8')))
const approved = reformerLesson.review.status === 'approved' && reformerLesson.review.coach && reformerLesson.review.reviewedAt
if (process.argv.includes('--release') && !approved) throw new Error('正式发布构建需要教练审核记录:status=approved、coach、reviewedAt。审阅预览请使用 pnpm build:education。')
const figure = createFigure()
const replacements = {
__THIRD_PARTY_LICENSE__: `Anatomy SVG kit · https://github.com/wheresryan22/anatomy\n${await readFile(join(here, 'vendor/anatomy/LICENSE'), 'utf8')}`,
'/*__CSS__*/': `${css}\n${illustration}`,
'/*__SCRIPT__*/': live.outputFiles[0].text.replaceAll('</script', '<\\/script'),
__FIGURE__: figure,
__PART_BUTTONS__: reformerParts.map(p => `<button data-part-button="${p.id}" aria-pressed="${p.id === 'carriage'}">${p.name}</button>`).join(''),
__STATIC_PARTS__: reformerParts.map(p => `<dt>${p.number} · ${p.name}</dt><dd>${p.description} ${p.observation}</dd>`).join(''),
__SPRING_BUTTONS__: reformerLesson.springs.map((s, i) => `<button class="spring-button" data-spring-button="${s.id}" aria-pressed="${i === 0 || i === 3}" aria-label="${s.label}弹簧,${s.level},切换连接" style="--spring-color:${s.color}"><span class="spring-color"></span><span>${s.label}</span><span class="spring-state">${i === 0 || i === 3 ? '已连接' : '未连接'}</span></button>`).join(''),
__REFERENCE__: reformerLesson.reference,
__SOURCES__: reformerLesson.sources.map(s => `<a href="${s.url}" target="_blank" rel="noopener noreferrer">${s.title} ↗</a>`).join(''),
}
let html = template
for (const [key, value] of Object.entries(replacements)) html = html.replace(key, () => value)
const output = join(root, 'packages/server/public/learn/reformer')
await mkdir(output, { recursive: true })
await writeFile(join(output, 'index.html'), html)
// Keep an inspectable standalone SVG. Native mini-programs use a checked-in PNG raster.
const svg = figure.replace('><title', `><style>${illustration}</style><title`)
await writeFile(join(output, 'reformer.svg'), svg)
console.log(`Reformer built: ${Math.round(Buffer.byteLength(html) / 1024)} KiB HTML, ${Math.round(gzipSync(html).length / 1024)} KiB gzip; no runtime requests. ${partsInventory.length} part categories.`)
} finally { await rm(temp, { recursive: true, force: true }) }