index.ts 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. import { z } from "zod"
  2. import { Global } from "../global"
  3. import { Log } from "../util/log"
  4. import path from "path"
  5. import { NamedError } from "../util/error"
  6. import { readableStreamToText } from "bun"
  7. export namespace BunProc {
  8. const log = Log.create({ service: "bun" })
  9. export async function run(cmd: string[], options?: Bun.SpawnOptions.OptionsObject<any, any, any>) {
  10. log.info("running", {
  11. cmd: [which(), ...cmd],
  12. ...options,
  13. })
  14. const result = Bun.spawn([which(), ...cmd], {
  15. ...options,
  16. stdout: "pipe",
  17. stderr: "pipe",
  18. env: {
  19. ...process.env,
  20. ...options?.env,
  21. BUN_BE_BUN: "1",
  22. },
  23. })
  24. const code = await result.exited
  25. const stdout = result.stdout
  26. ? typeof result.stdout === "number"
  27. ? result.stdout
  28. : await readableStreamToText(result.stdout)
  29. : undefined
  30. const stderr = result.stderr
  31. ? typeof result.stderr === "number"
  32. ? result.stderr
  33. : await readableStreamToText(result.stderr)
  34. : undefined
  35. log.info("done", {
  36. code,
  37. stdout,
  38. stderr,
  39. })
  40. if (code !== 0) {
  41. throw new Error(`Command failed with exit code ${result.exitCode}`)
  42. }
  43. return result
  44. }
  45. export function which() {
  46. return process.execPath
  47. }
  48. export const InstallFailedError = NamedError.create(
  49. "BunInstallFailedError",
  50. z.object({
  51. pkg: z.string(),
  52. version: z.string(),
  53. }),
  54. )
  55. export async function install(pkg: string, version = "latest") {
  56. const mod = path.join(Global.Path.cache, "node_modules", pkg)
  57. const pkgjson = Bun.file(path.join(Global.Path.cache, "package.json"))
  58. const parsed = await pkgjson.json().catch(async () => {
  59. const result = { dependencies: {} }
  60. await Bun.write(pkgjson.name!, JSON.stringify(result, null, 2))
  61. return result
  62. })
  63. if (parsed.dependencies[pkg] === version) return mod
  64. // Build command arguments
  65. const args = ["add", "--force", "--exact", "--cwd", Global.Path.cache, pkg + "@" + version]
  66. // Let Bun handle registry resolution:
  67. // - If .npmrc files exist, Bun will use them automatically
  68. // - If no .npmrc files exist, Bun will default to https://registry.npmjs.org
  69. // - No need to pass --registry flag
  70. log.info("installing package using Bun's default registry resolution", { pkg, version })
  71. await BunProc.run(args, {
  72. cwd: Global.Path.cache,
  73. }).catch((e) => {
  74. throw new InstallFailedError(
  75. { pkg, version },
  76. {
  77. cause: e,
  78. },
  79. )
  80. })
  81. parsed.dependencies[pkg] = version
  82. await Bun.write(pkgjson.name!, JSON.stringify(parsed, null, 2))
  83. return mod
  84. }
  85. }