build.ts 4.7 KB

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