build.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. import { chmodSync, existsSync, mkdirSync } from 'fs'
  2. import { dirname } from 'path'
  3. const pkg = await Bun.file(new URL('../package.json', import.meta.url)).json() as {
  4. name: string
  5. version: string
  6. }
  7. const args = process.argv.slice(2)
  8. const compile = args.includes('--compile')
  9. const dev = args.includes('--dev')
  10. function isBunVersionAtLeast(major: number, minor: number, patch: number): boolean {
  11. const parts = Bun.version.split('.').map(v => Number(v))
  12. const [ma = 0, mi = 0, pa = 0] = parts
  13. if (ma !== major) return ma > major
  14. if (mi !== minor) return mi > minor
  15. return pa >= patch
  16. }
  17. if (compile && !isBunVersionAtLeast(1, 3, 11)) {
  18. console.error(`bun >= 1.3.11 is required for --compile (current: ${Bun.version})`)
  19. process.exit(1)
  20. }
  21. const fullExperimentalFeatures = [
  22. 'AGENT_MEMORY_SNAPSHOT',
  23. 'AGENT_TRIGGERS',
  24. 'AGENT_TRIGGERS_REMOTE',
  25. 'AWAY_SUMMARY',
  26. 'BASH_CLASSIFIER',
  27. 'BRIDGE_MODE',
  28. 'BUILTIN_EXPLORE_PLAN_AGENTS',
  29. 'CACHED_MICROCOMPACT',
  30. 'CCR_AUTO_CONNECT',
  31. 'CCR_MIRROR',
  32. 'CCR_REMOTE_SETUP',
  33. 'COMPACTION_REMINDERS',
  34. 'CONNECTOR_TEXT',
  35. 'EXTRACT_MEMORIES',
  36. 'HISTORY_PICKER',
  37. 'HOOK_PROMPTS',
  38. 'KAIROS_BRIEF',
  39. 'KAIROS_CHANNELS',
  40. 'LODESTONE',
  41. 'MCP_RICH_OUTPUT',
  42. 'MESSAGE_ACTIONS',
  43. 'NATIVE_CLIPBOARD_IMAGE',
  44. 'NEW_INIT',
  45. 'POWERSHELL_AUTO_MODE',
  46. 'PROMPT_CACHE_BREAK_DETECTION',
  47. 'QUICK_SEARCH',
  48. 'SHOT_STATS',
  49. 'TEAMMEM',
  50. 'TOKEN_BUDGET',
  51. 'TREE_SITTER_BASH',
  52. 'TREE_SITTER_BASH_SHADOW',
  53. 'ULTRAPLAN',
  54. 'ULTRATHINK',
  55. 'UNATTENDED_RETRY',
  56. 'VERIFICATION_AGENT',
  57. 'VOICE_MODE',
  58. ] as const
  59. function runCommand(cmd: string[]): string | null {
  60. const proc = Bun.spawnSync({
  61. cmd,
  62. cwd: process.cwd(),
  63. stdout: 'pipe',
  64. stderr: 'pipe',
  65. })
  66. if (proc.exitCode !== 0) {
  67. return null
  68. }
  69. return new TextDecoder().decode(proc.stdout).trim() || null
  70. }
  71. function getDevVersion(baseVersion: string): string {
  72. const timestamp = new Date().toISOString()
  73. const date = timestamp.slice(0, 10).replaceAll('-', '')
  74. const time = timestamp.slice(11, 19).replaceAll(':', '')
  75. const sha = runCommand(['git', 'rev-parse', '--short=8', 'HEAD']) ?? 'unknown'
  76. return `${baseVersion}-dev.${date}.t${time}.sha${sha}`
  77. }
  78. function getVersionChangelog(): string {
  79. return (
  80. runCommand(['git', 'log', '--format=%h %s', '-20']) ??
  81. 'Local development build'
  82. )
  83. }
  84. const defaultFeatures = ['VOICE_MODE']
  85. const featureSet = new Set(defaultFeatures)
  86. for (let i = 0; i < args.length; i += 1) {
  87. const arg = args[i]
  88. if (arg === '--feature-set' && args[i + 1]) {
  89. if (args[i + 1] === 'dev-full') {
  90. for (const feature of fullExperimentalFeatures) {
  91. featureSet.add(feature)
  92. }
  93. }
  94. i += 1
  95. continue
  96. }
  97. if (arg === '--feature-set=dev-full') {
  98. for (const feature of fullExperimentalFeatures) {
  99. featureSet.add(feature)
  100. }
  101. continue
  102. }
  103. if (arg === '--feature' && args[i + 1]) {
  104. featureSet.add(args[i + 1]!)
  105. i += 1
  106. continue
  107. }
  108. if (arg.startsWith('--feature=')) {
  109. featureSet.add(arg.slice('--feature='.length))
  110. }
  111. }
  112. const features = [...featureSet]
  113. const outfile = compile
  114. ? dev
  115. ? './dist/cli-dev'
  116. : './dist/cli'
  117. : dev
  118. ? './cli-dev'
  119. : './cli'
  120. const buildTime = new Date().toISOString()
  121. const version = dev ? getDevVersion(pkg.version) : pkg.version
  122. const outdir = dirname(outfile)
  123. if (outdir !== '.' && outdir !== '') {
  124. mkdirSync(outdir, { recursive: true })
  125. }
  126. const externals = [
  127. '@ant/*',
  128. 'audio-capture-napi',
  129. 'image-processor-napi',
  130. 'modifiers-napi',
  131. 'url-handler-napi',
  132. ]
  133. const defines = {
  134. 'process.env.USER_TYPE': JSON.stringify('external'),
  135. 'process.env.CLAUDE_CODE_FORCE_FULL_LOGO': JSON.stringify('true'),
  136. ...(dev
  137. ? { 'process.env.NODE_ENV': JSON.stringify('development') }
  138. : {}),
  139. ...(dev
  140. ? {
  141. 'process.env.CLAUDE_CODE_EXPERIMENTAL_BUILD': JSON.stringify('true'),
  142. }
  143. : {}),
  144. 'process.env.CLAUDE_CODE_VERIFY_PLAN': JSON.stringify('false'),
  145. 'process.env.CCR_FORCE_BUNDLE': JSON.stringify('true'),
  146. 'MACRO.VERSION': JSON.stringify(version),
  147. 'MACRO.BUILD_TIME': JSON.stringify(buildTime),
  148. 'MACRO.PACKAGE_URL': JSON.stringify(pkg.name),
  149. 'MACRO.NATIVE_PACKAGE_URL': 'undefined',
  150. 'MACRO.FEEDBACK_CHANNEL': JSON.stringify('github'),
  151. 'MACRO.ISSUES_EXPLAINER': JSON.stringify(
  152. 'This reconstructed source snapshot does not include Anthropic internal issue routing.',
  153. ),
  154. 'MACRO.VERSION_CHANGELOG': JSON.stringify(
  155. dev ? getVersionChangelog() : 'https://github.com/paoloanzn/claude-code',
  156. ),
  157. } as const
  158. const cmd = [
  159. 'bun',
  160. 'build',
  161. './src/entrypoints/cli.tsx',
  162. ...(compile ? ['--compile'] : []),
  163. '--target',
  164. 'bun',
  165. '--format',
  166. 'esm',
  167. '--outfile',
  168. outfile,
  169. '--minify',
  170. ...(compile ? ['--bytecode'] : []),
  171. '--packages',
  172. 'bundle',
  173. '--conditions',
  174. 'bun',
  175. ]
  176. for (const external of externals) {
  177. cmd.push('--external', external)
  178. }
  179. for (const feature of features) {
  180. cmd.push(`--feature=${feature}`)
  181. }
  182. for (const [key, value] of Object.entries(defines)) {
  183. cmd.push('--define', `${key}=${value}`)
  184. }
  185. const proc = Bun.spawnSync({
  186. cmd,
  187. cwd: process.cwd(),
  188. stdout: 'inherit',
  189. stderr: 'inherit',
  190. })
  191. if (proc.exitCode !== 0) {
  192. process.exit(proc.exitCode ?? 1)
  193. }
  194. if (existsSync(outfile)) {
  195. chmodSync(outfile, 0o755)
  196. }
  197. console.log(`Built ${outfile}`)