app-assets.ts 1.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041
  1. import { $ } from "bun"
  2. import { readdir } from "node:fs/promises"
  3. import path from "node:path"
  4. import { brotliCompressSync, constants } from "node:zlib"
  5. export async function buildAppArchive(channel: string) {
  6. const root = path.resolve(import.meta.dirname, "../../app")
  7. await $`bun run build`.cwd(root).env({ ...process.env, OPENCODE_CHANNEL: channel })
  8. const assets = Object.fromEntries(
  9. await Promise.all(
  10. (await files(path.join(root, "dist")))
  11. .filter((key) => !key.endsWith(".map"))
  12. .map(async (key) => {
  13. const source = path.join(root, "dist", key)
  14. const body = Buffer.from(await Bun.file(source).arrayBuffer())
  15. const encoding = isText(key) ? "utf8" : "base64"
  16. return [key, { encoding, content: body.toString(encoding) }] as const
  17. }),
  18. ),
  19. )
  20. return brotliCompressSync(JSON.stringify(assets), {
  21. params: { [constants.BROTLI_PARAM_QUALITY]: 11 },
  22. }).toString("base64")
  23. }
  24. function isText(key: string) {
  25. return key === "_headers" || /\.(?:css|html|js|json|svg|txt|webmanifest|xml)$/.test(key)
  26. }
  27. async function files(root: string, current = root): Promise<string[]> {
  28. return (
  29. await Promise.all(
  30. (await readdir(current, { withFileTypes: true })).map((entry) => {
  31. const target = path.join(current, entry.name)
  32. return entry.isDirectory() ? files(root, target) : [path.relative(root, target).replaceAll(path.sep, "/")]
  33. }),
  34. )
  35. )
  36. .flat()
  37. .toSorted()
  38. }