codebuff 1.0.534 → 1.0.536

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/index.js +118 -77
  2. package/package.json +1 -1
  3. package/postinstall.js +1 -1
package/index.js CHANGED
@@ -22,6 +22,8 @@ function createConfig(packageName) {
22
22
  configDir,
23
23
  binaryName,
24
24
  binaryPath: path.join(configDir, binaryName),
25
+ metadataPath: path.join(configDir, 'codebuff-metadata.json'),
26
+ tempDownloadDir: path.join(configDir, '.download-temp'),
25
27
  userAgent: `${packageName}-cli`,
26
28
  requestTimeout: 20000,
27
29
  }
@@ -111,53 +113,64 @@ function streamToString(stream) {
111
113
  }
112
114
 
113
115
  function getCurrentVersion() {
114
- if (!fs.existsSync(CONFIG.binaryPath)) return null
115
-
116
116
  try {
117
- return new Promise((resolve, reject) => {
118
- const child = spawn(CONFIG.binaryPath, ['--version'], {
119
- cwd: os.homedir(),
120
- stdio: 'pipe',
121
- })
117
+ if (!fs.existsSync(CONFIG.metadataPath)) {
118
+ return null
119
+ }
120
+ const metadata = JSON.parse(fs.readFileSync(CONFIG.metadataPath, 'utf8'))
121
+ // Also verify the binary still exists
122
+ if (!fs.existsSync(CONFIG.binaryPath)) {
123
+ return null
124
+ }
125
+ return metadata.version || null
126
+ } catch (error) {
127
+ return null
128
+ }
129
+ }
122
130
 
123
- let output = ''
124
- let errorOutput = ''
131
+ function runSmokeTest(binaryPath) {
132
+ return new Promise((resolve) => {
133
+ if (!fs.existsSync(binaryPath)) {
134
+ resolve(false)
135
+ return
136
+ }
125
137
 
126
- child.stdout.on('data', (data) => {
127
- output += data.toString()
128
- })
138
+ const child = spawn(binaryPath, ['--version'], {
139
+ cwd: os.homedir(),
140
+ stdio: 'pipe',
141
+ })
129
142
 
130
- child.stderr.on('data', (data) => {
131
- errorOutput += data.toString()
132
- })
143
+ let output = ''
133
144
 
134
- const timeout = setTimeout(() => {
135
- child.kill('SIGTERM')
136
- setTimeout(() => {
137
- if (!child.killed) {
138
- child.kill('SIGKILL')
139
- }
140
- }, 4000)
141
- resolve('error')
142
- }, 4000)
143
-
144
- child.on('exit', (code) => {
145
- clearTimeout(timeout)
146
- if (code === 0) {
147
- resolve(output.trim())
148
- } else {
149
- resolve('error')
145
+ child.stdout.on('data', (data) => {
146
+ output += data.toString()
147
+ })
148
+
149
+ const timeout = setTimeout(() => {
150
+ child.kill('SIGTERM')
151
+ setTimeout(() => {
152
+ if (!child.killed) {
153
+ child.kill('SIGKILL')
150
154
  }
151
- })
155
+ }, 1000)
156
+ resolve(false)
157
+ }, 5000)
158
+
159
+ child.on('exit', (code) => {
160
+ clearTimeout(timeout)
161
+ // Check that it exits successfully and outputs something that looks like a version
162
+ if (code === 0 && output.trim().match(/^\d+(\.\d+)*$/)) {
163
+ resolve(true)
164
+ } else {
165
+ resolve(false)
166
+ }
167
+ })
152
168
 
153
- child.on('error', () => {
154
- clearTimeout(timeout)
155
- resolve('error')
156
- })
169
+ child.on('error', () => {
170
+ clearTimeout(timeout)
171
+ resolve(false)
157
172
  })
158
- } catch (error) {
159
- return 'error'
160
- }
173
+ })
161
174
  }
162
175
 
163
176
  function compareVersions(v1, v2) {
@@ -250,33 +263,21 @@ async function downloadBinary(version) {
250
263
  process.env.NEXT_PUBLIC_CODEBUFF_APP_URL || 'https://codebuff.com'
251
264
  }/api/releases/download/${version}/${fileName}`
252
265
 
266
+ // Ensure config directory exists
253
267
  fs.mkdirSync(CONFIG.configDir, { recursive: true })
254
268
 
255
- if (fs.existsSync(CONFIG.binaryPath)) {
256
- try {
257
- fs.unlinkSync(CONFIG.binaryPath)
258
- } catch (err) {
259
- // Fallback: try renaming the locked/undeletable binary
260
- const backupPath = CONFIG.binaryPath + `.old.${Date.now()}`
261
-
262
- try {
263
- fs.renameSync(CONFIG.binaryPath, backupPath)
264
- } catch (renameErr) {
265
- // If we can't unlink OR rename, we can't safely proceed
266
- throw new Error(
267
- `Failed to replace existing binary. ` +
268
- `unlink error: ${err.code || err.message}, ` +
269
- `rename error: ${renameErr.code || renameErr.message}`,
270
- )
271
- }
272
- }
269
+ // Clean up any previous temp download directory
270
+ if (fs.existsSync(CONFIG.tempDownloadDir)) {
271
+ fs.rmSync(CONFIG.tempDownloadDir, { recursive: true })
273
272
  }
273
+ fs.mkdirSync(CONFIG.tempDownloadDir, { recursive: true })
274
274
 
275
275
  term.write('Downloading...')
276
276
 
277
277
  const res = await httpGet(downloadUrl)
278
278
 
279
279
  if (res.statusCode !== 200) {
280
+ fs.rmSync(CONFIG.tempDownloadDir, { recursive: true })
280
281
  throw new Error(`Download failed: HTTP ${res.statusCode}`)
281
282
  }
282
283
 
@@ -302,31 +303,71 @@ async function downloadBinary(version) {
302
303
  }
303
304
  })
304
305
 
306
+ // Extract to temp directory
305
307
  await new Promise((resolve, reject) => {
306
308
  res
307
309
  .pipe(zlib.createGunzip())
308
- .pipe(tar.x({ cwd: CONFIG.configDir }))
310
+ .pipe(tar.x({ cwd: CONFIG.tempDownloadDir }))
309
311
  .on('finish', resolve)
310
312
  .on('error', reject)
311
313
  })
312
314
 
313
- try {
314
- const files = fs.readdirSync(CONFIG.configDir)
315
- const extractedPath = path.join(CONFIG.configDir, CONFIG.binaryName)
315
+ const tempBinaryPath = path.join(CONFIG.tempDownloadDir, CONFIG.binaryName)
316
+
317
+ // Verify the binary was extracted
318
+ if (!fs.existsSync(tempBinaryPath)) {
319
+ const files = fs.readdirSync(CONFIG.tempDownloadDir)
320
+ fs.rmSync(CONFIG.tempDownloadDir, { recursive: true })
321
+ throw new Error(
322
+ `Binary not found after extraction. Expected: ${CONFIG.binaryName}, Available files: ${files.join(', ')}`,
323
+ )
324
+ }
325
+
326
+ // Set executable permissions
327
+ if (process.platform !== 'win32') {
328
+ fs.chmodSync(tempBinaryPath, 0o755)
329
+ }
330
+
331
+ // Run smoke test on the downloaded binary
332
+ term.write('Verifying download...')
333
+ const smokeTestPassed = await runSmokeTest(tempBinaryPath)
316
334
 
317
- if (fs.existsSync(extractedPath)) {
318
- if (process.platform !== 'win32') {
319
- fs.chmodSync(extractedPath, 0o755)
335
+ if (!smokeTestPassed) {
336
+ fs.rmSync(CONFIG.tempDownloadDir, { recursive: true })
337
+ throw new Error('Downloaded binary failed smoke test (--version check)')
338
+ }
339
+
340
+ // Smoke test passed - move binary to final location
341
+ try {
342
+ if (fs.existsSync(CONFIG.binaryPath)) {
343
+ try {
344
+ fs.unlinkSync(CONFIG.binaryPath)
345
+ } catch (err) {
346
+ // Fallback: try renaming the locked/undeletable binary (Windows)
347
+ const backupPath = CONFIG.binaryPath + `.old.${Date.now()}`
348
+ try {
349
+ fs.renameSync(CONFIG.binaryPath, backupPath)
350
+ } catch (renameErr) {
351
+ throw new Error(
352
+ `Failed to replace existing binary. ` +
353
+ `unlink error: ${err.code || err.message}, ` +
354
+ `rename error: ${renameErr.code || renameErr.message}`,
355
+ )
356
+ }
320
357
  }
321
- } else {
322
- throw new Error(
323
- `Binary not found after extraction. Expected: ${extractedPath}, Available files: ${files.join(', ')}`,
324
- )
325
358
  }
326
- } catch (error) {
327
- term.clearLine()
328
- console.error(`Extraction failed: ${error.message}`)
329
- process.exit(1)
359
+ fs.renameSync(tempBinaryPath, CONFIG.binaryPath)
360
+
361
+ // Save version metadata for fast version checking
362
+ fs.writeFileSync(
363
+ CONFIG.metadataPath,
364
+ JSON.stringify({ version }, null, 2),
365
+ )
366
+ } finally {
367
+ // Clean up temp directory even if rename fails
368
+ if (fs.existsSync(CONFIG.tempDownloadDir)) {
369
+ fs.rmSync(CONFIG.tempDownloadDir, { recursive: true })
370
+ }
330
371
  }
331
372
 
332
373
  term.clearLine()
@@ -334,8 +375,8 @@ async function downloadBinary(version) {
334
375
  }
335
376
 
336
377
  async function ensureBinaryExists() {
337
- const currentVersion = await getCurrentVersion()
338
- if (currentVersion !== null && currentVersion !== 'error') {
378
+ const currentVersion = getCurrentVersion()
379
+ if (currentVersion !== null) {
339
380
  return
340
381
  }
341
382
 
@@ -358,14 +399,14 @@ async function ensureBinaryExists() {
358
399
 
359
400
  async function checkForUpdates(runningProcess, exitListener) {
360
401
  try {
361
- const currentVersion = await getCurrentVersion()
362
- if (!currentVersion) return
402
+ const currentVersion = getCurrentVersion()
363
403
 
364
404
  const latestVersion = await getLatestVersion()
365
405
  if (!latestVersion) return
366
406
 
367
407
  if (
368
- currentVersion === 'error' ||
408
+ // Download new version if current version is unknown or outdated.
409
+ currentVersion === null ||
369
410
  compareVersions(currentVersion, latestVersion) < 0
370
411
  ) {
371
412
  term.clearLine()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codebuff",
3
- "version": "1.0.534",
3
+ "version": "1.0.536",
4
4
  "description": "AI coding agent",
5
5
  "license": "MIT",
6
6
  "bin": {
package/postinstall.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const fs = require('fs');
4
- const path = require('path');
5
4
  const os = require('os');
5
+ const path = require('path');
6
6
 
7
7
  // Clean up old binary
8
8
  const binaryPath = path.join(