codebuff 1.0.535 → 1.0.537

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 (2) hide show
  1. package/index.js +118 -76
  2. package/package.json +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,52 +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) => {
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 = ''
131
+ function runSmokeTest(binaryPath) {
132
+ return new Promise((resolve) => {
133
+ if (!fs.existsSync(binaryPath)) {
134
+ resolve(false)
135
+ return
136
+ }
124
137
 
125
- child.stdout.on('data', (data) => {
126
- output += data.toString()
127
- })
138
+ const child = spawn(binaryPath, ['--version'], {
139
+ cwd: os.homedir(),
140
+ stdio: 'pipe',
141
+ })
128
142
 
129
- child.stderr.on('data', () => {
130
- // Ignore stderr output
131
- })
143
+ let output = ''
132
144
 
133
- const timeout = setTimeout(() => {
134
- child.kill('SIGTERM')
135
- setTimeout(() => {
136
- if (!child.killed) {
137
- child.kill('SIGKILL')
138
- }
139
- }, 4000)
140
- resolve('error')
141
- }, 4000)
142
-
143
- child.on('exit', (code) => {
144
- clearTimeout(timeout)
145
- if (code === 0) {
146
- resolve(output.trim())
147
- } else {
148
- 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')
149
154
  }
150
- })
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
+ })
151
168
 
152
- child.on('error', () => {
153
- clearTimeout(timeout)
154
- resolve('error')
155
- })
169
+ child.on('error', () => {
170
+ clearTimeout(timeout)
171
+ resolve(false)
156
172
  })
157
- } catch (error) {
158
- return 'error'
159
- }
173
+ })
160
174
  }
161
175
 
162
176
  function compareVersions(v1, v2) {
@@ -249,33 +263,21 @@ async function downloadBinary(version) {
249
263
  process.env.NEXT_PUBLIC_CODEBUFF_APP_URL || 'https://codebuff.com'
250
264
  }/api/releases/download/${version}/${fileName}`
251
265
 
266
+ // Ensure config directory exists
252
267
  fs.mkdirSync(CONFIG.configDir, { recursive: true })
253
268
 
254
- if (fs.existsSync(CONFIG.binaryPath)) {
255
- try {
256
- fs.unlinkSync(CONFIG.binaryPath)
257
- } catch (err) {
258
- // Fallback: try renaming the locked/undeletable binary
259
- const backupPath = CONFIG.binaryPath + `.old.${Date.now()}`
260
-
261
- try {
262
- fs.renameSync(CONFIG.binaryPath, backupPath)
263
- } catch (renameErr) {
264
- // If we can't unlink OR rename, we can't safely proceed
265
- throw new Error(
266
- `Failed to replace existing binary. ` +
267
- `unlink error: ${err.code || err.message}, ` +
268
- `rename error: ${renameErr.code || renameErr.message}`,
269
- )
270
- }
271
- }
269
+ // Clean up any previous temp download directory
270
+ if (fs.existsSync(CONFIG.tempDownloadDir)) {
271
+ fs.rmSync(CONFIG.tempDownloadDir, { recursive: true })
272
272
  }
273
+ fs.mkdirSync(CONFIG.tempDownloadDir, { recursive: true })
273
274
 
274
275
  term.write('Downloading...')
275
276
 
276
277
  const res = await httpGet(downloadUrl)
277
278
 
278
279
  if (res.statusCode !== 200) {
280
+ fs.rmSync(CONFIG.tempDownloadDir, { recursive: true })
279
281
  throw new Error(`Download failed: HTTP ${res.statusCode}`)
280
282
  }
281
283
 
@@ -301,31 +303,71 @@ async function downloadBinary(version) {
301
303
  }
302
304
  })
303
305
 
306
+ // Extract to temp directory
304
307
  await new Promise((resolve, reject) => {
305
308
  res
306
309
  .pipe(zlib.createGunzip())
307
- .pipe(tar.x({ cwd: CONFIG.configDir }))
310
+ .pipe(tar.x({ cwd: CONFIG.tempDownloadDir }))
308
311
  .on('finish', resolve)
309
312
  .on('error', reject)
310
313
  })
311
314
 
312
- try {
313
- const files = fs.readdirSync(CONFIG.configDir)
314
- 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)
315
334
 
316
- if (fs.existsSync(extractedPath)) {
317
- if (process.platform !== 'win32') {
318
- 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
+ }
319
357
  }
320
- } else {
321
- throw new Error(
322
- `Binary not found after extraction. Expected: ${extractedPath}, Available files: ${files.join(', ')}`,
323
- )
324
358
  }
325
- } catch (error) {
326
- term.clearLine()
327
- console.error(`Extraction failed: ${error.message}`)
328
- 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
+ }
329
371
  }
330
372
 
331
373
  term.clearLine()
@@ -333,8 +375,8 @@ async function downloadBinary(version) {
333
375
  }
334
376
 
335
377
  async function ensureBinaryExists() {
336
- const currentVersion = await getCurrentVersion()
337
- if (currentVersion !== null && currentVersion !== 'error') {
378
+ const currentVersion = getCurrentVersion()
379
+ if (currentVersion !== null) {
338
380
  return
339
381
  }
340
382
 
@@ -357,14 +399,14 @@ async function ensureBinaryExists() {
357
399
 
358
400
  async function checkForUpdates(runningProcess, exitListener) {
359
401
  try {
360
- const currentVersion = await getCurrentVersion()
361
- if (!currentVersion) return
402
+ const currentVersion = getCurrentVersion()
362
403
 
363
404
  const latestVersion = await getLatestVersion()
364
405
  if (!latestVersion) return
365
406
 
366
407
  if (
367
- currentVersion === 'error' ||
408
+ // Download new version if current version is unknown or outdated.
409
+ currentVersion === null ||
368
410
  compareVersions(currentVersion, latestVersion) < 0
369
411
  ) {
370
412
  term.clearLine()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codebuff",
3
- "version": "1.0.535",
3
+ "version": "1.0.537",
4
4
  "description": "AI coding agent",
5
5
  "license": "MIT",
6
6
  "bin": {