1
0

node-assets.ts 3.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. import { createHash } from "node:crypto"
  2. import { copyFile, mkdir, readdir, readFile, stat } from "node:fs/promises"
  3. import path from "node:path"
  4. import { fileURLToPath } from "node:url"
  5. import { getNodeAssets } from "@opentui/core/node-assets"
  6. import { attentionSoundAssets, type NodeTarget, photonWasmAsset, shellParserWasmAssets } from "../src/node/target"
  7. const dir = path.resolve(import.meta.dirname, "..")
  8. // Bun's compiler discovers file imports and embeds them in its virtual filesystem. Vite only bundles the JavaScript
  9. // portion of the Node executable, while SEA embeds only the assets explicitly listed in its build configuration.
  10. // Collect and stage those files under stable keys so the SEA prelude can extract them to real paths at startup;
  11. // native addons, helper executables, and other path-based consumers cannot use assets directly from SEA memory.
  12. export type NodeAsset = {
  13. readonly key: string
  14. readonly source: string
  15. }
  16. async function files(root: string, current = root): Promise<string[]> {
  17. return (
  18. await Promise.all(
  19. (await readdir(current, { withFileTypes: true })).map((entry) => {
  20. const target = path.join(current, entry.name)
  21. return entry.isDirectory() ? files(root, target) : [path.relative(root, target)]
  22. }),
  23. )
  24. ).flat()
  25. }
  26. export async function collectNodeAssets(target: NodeTarget) {
  27. const ptyEntry = fileURLToPath(import.meta.resolve(target.nodePtyPackage))
  28. const ptyRoot = path.resolve(path.dirname(ptyEntry), "..")
  29. const assets: NodeAsset[] = [
  30. ...getNodeAssets({
  31. platform: target.platform,
  32. arch: target.arch,
  33. ...(target.platform === "linux" ? { libc: "glibc" as const } : {}),
  34. }),
  35. { key: target.parcelWatcherAsset, source: fileURLToPath(import.meta.resolve(target.parcelWatcherPackage)) },
  36. { key: target.fffAsset, source: fileURLToPath(import.meta.resolve(target.fffPackage)) },
  37. { key: target.fffFfiAsset, source: fileURLToPath(import.meta.resolve(target.fffFfiPackage)) },
  38. {
  39. key: photonWasmAsset,
  40. source: fileURLToPath(import.meta.resolve(photonWasmAsset)),
  41. },
  42. ...Object.values(shellParserWasmAssets).map((key) => ({
  43. key,
  44. source: fileURLToPath(import.meta.resolve(key)),
  45. })),
  46. ...attentionSoundAssets.map((key) => ({
  47. key,
  48. source: path.resolve(dir, "../ui/src/assets/audio", path.basename(key)),
  49. })),
  50. ...(await files(ptyRoot))
  51. .filter((relative) => !relative.endsWith(".map") && !relative.endsWith(".pdb"))
  52. .map((relative) => ({
  53. key: `${target.nodePtyPackage}/${relative}`,
  54. source: path.join(ptyRoot, relative),
  55. })),
  56. ]
  57. const unique = [...new Map(assets.map((asset) => [asset.key, asset])).values()]
  58. await Promise.all(unique.map((asset) => stat(asset.source)))
  59. return unique
  60. }
  61. export async function hashNodeAssets(assets: readonly NodeAsset[]) {
  62. const hash = createHash("sha256")
  63. for (const asset of assets.toSorted((left, right) => left.key.localeCompare(right.key))) {
  64. hash.update(asset.key)
  65. hash.update(await readFile(asset.source))
  66. }
  67. return hash.digest("hex").slice(0, 16)
  68. }
  69. export async function copyNodeAssets(assets: readonly NodeAsset[]) {
  70. const root = path.join(dir, "dist-node", "assets")
  71. await Promise.all(
  72. assets.map(async (asset) => {
  73. const target = path.join(root, asset.key)
  74. await mkdir(path.dirname(target), { recursive: true })
  75. await copyFile(asset.source, target)
  76. }),
  77. )
  78. }
  79. export async function seaAssetMap() {
  80. const root = path.join(dir, "dist-node", "assets")
  81. return Object.fromEntries((await files(root)).map((key) => [key.replaceAll(path.sep, "/"), path.join(root, key)]))
  82. }