postinstall.mjs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. #!/usr/bin/env node
  2. import fs from "fs"
  3. import path from "path"
  4. import os from "os"
  5. import { fileURLToPath } from "url"
  6. import { createRequire } from "module"
  7. const __dirname = path.dirname(fileURLToPath(import.meta.url))
  8. const require = createRequire(import.meta.url)
  9. function detectPlatformAndArch() {
  10. // Map platform names
  11. let platform
  12. switch (os.platform()) {
  13. case "darwin":
  14. platform = "darwin"
  15. break
  16. case "linux":
  17. platform = "linux"
  18. break
  19. case "win32":
  20. platform = "windows"
  21. break
  22. default:
  23. platform = os.platform()
  24. break
  25. }
  26. // Map architecture names
  27. let arch
  28. switch (os.arch()) {
  29. case "x64":
  30. arch = "x64"
  31. break
  32. case "arm64":
  33. arch = "arm64"
  34. break
  35. case "arm":
  36. arch = "arm"
  37. break
  38. default:
  39. arch = os.arch()
  40. break
  41. }
  42. return { platform, arch }
  43. }
  44. function findBinary() {
  45. const { platform, arch } = detectPlatformAndArch()
  46. const packageName = `opencode-${platform}-${arch}`
  47. const binaryName = platform === "windows" ? "opencode.exe" : "opencode"
  48. try {
  49. // Use require.resolve to find the package
  50. const packageJsonPath = require.resolve(`${packageName}/package.json`)
  51. const packageDir = path.dirname(packageJsonPath)
  52. const binaryPath = path.join(packageDir, "bin", binaryName)
  53. if (!fs.existsSync(binaryPath)) {
  54. throw new Error(`Binary not found at ${binaryPath}`)
  55. }
  56. return { binaryPath, binaryName }
  57. } catch (error) {
  58. throw new Error(`Could not find package ${packageName}: ${error.message}`)
  59. }
  60. }
  61. function prepareBinDirectory(binaryName) {
  62. const binDir = path.join(__dirname, "bin")
  63. const targetPath = path.join(binDir, binaryName)
  64. // Ensure bin directory exists
  65. if (!fs.existsSync(binDir)) {
  66. fs.mkdirSync(binDir, { recursive: true })
  67. }
  68. // Remove existing binary/symlink if it exists
  69. if (fs.existsSync(targetPath)) {
  70. fs.unlinkSync(targetPath)
  71. }
  72. return { binDir, targetPath }
  73. }
  74. function copyBinary(sourcePath, binaryName) {
  75. const { targetPath } = prepareBinDirectory(binaryName)
  76. fs.copyFileSync(sourcePath, targetPath)
  77. console.log(`opencode binary installed: ${targetPath}`)
  78. // Verify the file exists after operation
  79. if (!fs.existsSync(targetPath)) {
  80. throw new Error(`Failed to copy binary to ${targetPath}`)
  81. }
  82. }
  83. function symlinkBinary(sourcePath, binaryName) {
  84. const { targetPath } = prepareBinDirectory(binaryName)
  85. fs.symlinkSync(sourcePath, targetPath)
  86. console.log(`opencode binary symlinked: ${targetPath} -> ${sourcePath}`)
  87. // Verify the file exists after operation
  88. if (!fs.existsSync(targetPath)) {
  89. throw new Error(`Failed to symlink binary to ${targetPath}`)
  90. }
  91. }
  92. async function regenerateWindowsCmdWrappers() {
  93. console.log("Windows + npm detected: Forcing npm to rebuild bin links")
  94. try {
  95. const { execSync } = require("child_process")
  96. const pkgPath = path.join(__dirname, "..")
  97. // npm_config_global is string | undefined
  98. // if it exists, the value is true
  99. const isGlobal = process.env.npm_config_global === "true" || pkgPath.includes(path.join("npm", "node_modules"))
  100. // The npm rebuild command does 2 things - Execute lifecycle scripts and rebuild bin links
  101. // We want to skip lifecycle scripts to avoid infinite loops, so we use --ignore-scripts
  102. const cmd = `npm rebuild opencode-ai --ignore-scripts${isGlobal ? " -g" : ""}`
  103. const opts = {
  104. stdio: "inherit",
  105. shell: true,
  106. ...(isGlobal ? {} : { cwd: path.join(pkgPath, "..", "..") }), // For local, run from project root
  107. }
  108. console.log(`Running: ${cmd}`)
  109. execSync(cmd, opts)
  110. console.log("Successfully rebuilt npm bin links")
  111. } catch (error) {
  112. console.error("Error rebuilding npm links:", error.message)
  113. console.error("npm rebuild failed. You may need to manually run: npm rebuild opencode-ai --ignore-scripts")
  114. }
  115. }
  116. async function main() {
  117. try {
  118. if (os.platform() === "win32") {
  119. // NPM eg format - npm/11.4.2 node/v24.4.1 win32 x64
  120. // Bun eg format - bun/1.2.19 npm/? node/v24.3.0 win32 x64
  121. // pnpm eg format - pnpm/8.10.0 npm/? node/v20.10.0 win32 x64
  122. const userAgent = process.env.npm_config_user_agent || ""
  123. if (userAgent.startsWith("npm")) {
  124. await regenerateWindowsCmdWrappers()
  125. return
  126. }
  127. if (userAgent.startsWith("bun")) {
  128. console.log("Windows + bun detected: Setting up binary")
  129. const { binaryPath, binaryName } = findBinary()
  130. copyBinary(binaryPath, binaryName)
  131. return
  132. }
  133. if (userAgent.startsWith("pnpm")) {
  134. console.log("Windows + pnpm detected: Setting up binary")
  135. const { binaryPath, binaryName } = findBinary()
  136. copyBinary(binaryPath, binaryName)
  137. return
  138. }
  139. // Unknown package manager on Windows
  140. console.log("Windows detected but unknown package manager, skipping postinstall")
  141. return
  142. }
  143. const { binaryPath, binaryName } = findBinary()
  144. symlinkBinary(binaryPath, binaryName)
  145. } catch (error) {
  146. console.error("Failed to setup opencode binary:", error.message)
  147. process.exit(1)
  148. }
  149. }
  150. try {
  151. main()
  152. } catch (error) {
  153. console.error("Postinstall script error:", error.message)
  154. process.exit(0)
  155. }