Files
forgery/build.ts
T
2026-02-18 19:53:18 +08:00

127 lines
3.8 KiB
TypeScript

#!/usr/bin/env bun
import plugin from 'bun-plugin-tailwind'
import { existsSync } from 'fs'
import { rm } from 'fs/promises'
import path from 'path'
if (process.argv.includes('--help') || process.argv.includes('-h')) {
console.log(`
🏗️ Bun Build Script
Usage: bun run build.ts [options]
Common Options:
--outdir <path> Output directory (default: "dist")
--minify Enable minification (or --minify.whitespace, --minify.syntax, etc)
--sourcemap <type> Sourcemap type: none|linked|inline|external
--target <target> Build target: browser|bun|node
--format <format> Output format: esm|cjs|iife
--splitting Enable code splitting
--packages <type> Package handling: bundle|external
--public-path <path> Public path for assets
--env <mode> Environment handling: inline|disable|prefix*
--conditions <list> Package.json export conditions (comma separated)
--external <list> External packages (comma separated)
--banner <text> Add banner text to output
--footer <text> Add footer text to output
--define <obj> Define global constants (e.g. --define.VERSION=1.0.0)
--help, -h Show this help message
Example:
bun run build.ts --outdir=dist --minify --sourcemap=linked --external=react,react-dom
`)
process.exit(0)
}
const toCamelCase = (str: string): string =>
str.replace(/-([a-z])/g, (_, letter: string) => letter.toUpperCase())
const parseValue = (value: string): any => {
if (value === 'true') return true
if (value === 'false') return false
if (/^\d+$/.test(value)) return parseInt(value, 10)
if (/^\d*\.\d+$/.test(value)) return parseFloat(value)
if (value.includes(',')) return value.split(',').map((v) => v.trim())
return value
}
function parseArgs(): Partial<Bun.BuildConfig> {
const config: Record<string, unknown> = {}
const args = process.argv.slice(2)
for (let i = 0; i < args.length; i++) {
const arg = args[i]
if (arg === undefined) continue
if (!arg.startsWith('--')) continue
if (arg.startsWith('--no-')) {
const key = toCamelCase(arg.slice(5))
config[key] = false
continue
}
if (!arg.includes('=') && (i === args.length - 1 || args[i + 1]?.startsWith('--'))) {
const key = toCamelCase(arg.slice(2))
config[key] = true
continue
}
let key: string
let value: string
if (arg.includes('=')) {
;[key, value] = arg.slice(2).split('=', 2) as [string, string]
} else {
key = arg.slice(2)
value = args[++i] ?? ''
}
key = toCamelCase(key)
if (key.includes('.')) {
const parts = key.split('.')
if (parts.length > 2) {
console.warn(
`Warning: Deeply nested option "${key}" is not supported. Only single-level nesting (e.g., --minify.whitespace) is allowed.`,
)
continue
}
const parentKey = parts[0]!
const childKey = parts[1]!
const existing = config[parentKey]
if (typeof existing !== 'object' || existing === null || Array.isArray(existing)) {
config[parentKey] = {}
}
;(config[parentKey] as Record<string, unknown>)[childKey] = parseValue(value)
} else {
config[key] = parseValue(value)
}
}
return config as Partial<Bun.BuildConfig>
}
console.log('\n🚀 Starting build process...\n')
const cliConfig = parseArgs()
const outdir = cliConfig.outdir || path.join(process.cwd(), 'dist')
if (existsSync(outdir)) {
console.log(`🗑️ Cleaning previous build at ${outdir}`)
await rm(outdir, { recursive: true, force: true })
}
await Bun.build({
entrypoints: ['./src/ui/index.html'],
target: 'browser',
outdir: './dist',
minify: true,
plugins: [plugin],
define: {
'process.env.NODE_ENV': JSON.stringify('production'),
},
})