codecane 1.0.420-beta.260 → 1.0.420-beta.261
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.
- package/index.js +131 -82
- package/package.json +4 -2
- package/postinstall.js +36 -0
package/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const { spawn } = require('child_process')
|
|
4
4
|
const fs = require('fs')
|
|
5
5
|
const https = require('https')
|
|
6
|
+
const os = require('os')
|
|
6
7
|
const path = require('path')
|
|
7
8
|
const zlib = require('zlib')
|
|
8
9
|
|
|
@@ -11,17 +12,18 @@ const tar = require('tar')
|
|
|
11
12
|
const packageName = 'codecane'
|
|
12
13
|
|
|
13
14
|
function createConfig(packageName) {
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
const binDir = path.join(packageDir, 'bin')
|
|
15
|
+
const homeDir = os.homedir()
|
|
16
|
+
const configDir = path.join(homeDir, '.config', 'manicode')
|
|
17
17
|
const binaryName =
|
|
18
18
|
process.platform === 'win32' ? `${packageName}.exe` : packageName
|
|
19
19
|
|
|
20
20
|
return {
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
homeDir,
|
|
22
|
+
configDir,
|
|
23
23
|
binaryName,
|
|
24
|
-
binaryPath: path.join(
|
|
24
|
+
binaryPath: path.join(configDir, binaryName),
|
|
25
|
+
metadataPath: path.join(configDir, 'codecane-metadata.json'),
|
|
26
|
+
tempDownloadDir: path.join(configDir, '.download-temp-staging'),
|
|
25
27
|
userAgent: `${packageName}-cli`,
|
|
26
28
|
requestTimeout: 20000,
|
|
27
29
|
}
|
|
@@ -111,57 +113,75 @@ function streamToString(stream) {
|
|
|
111
113
|
}
|
|
112
114
|
|
|
113
115
|
function getCurrentVersion() {
|
|
114
|
-
if (!fs.existsSync(CONFIG.binaryPath)) return null
|
|
115
|
-
|
|
116
116
|
try {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
131
|
+
function runSmokeTest(binaryPath) {
|
|
132
|
+
return new Promise((resolve) => {
|
|
133
|
+
if (!fs.existsSync(binaryPath)) {
|
|
134
|
+
resolve(false)
|
|
135
|
+
return
|
|
136
|
+
}
|
|
124
137
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
138
|
+
const child = spawn(binaryPath, ['--version'], {
|
|
139
|
+
cwd: os.homedir(),
|
|
140
|
+
stdio: 'pipe',
|
|
141
|
+
})
|
|
128
142
|
|
|
129
|
-
|
|
130
|
-
// Ignore stderr output
|
|
131
|
-
})
|
|
143
|
+
let output = ''
|
|
132
144
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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+)*(-beta\.\d+)?$/)) {
|
|
163
|
+
resolve(true)
|
|
164
|
+
} else {
|
|
165
|
+
resolve(false)
|
|
166
|
+
}
|
|
167
|
+
})
|
|
151
168
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
})
|
|
169
|
+
child.on('error', () => {
|
|
170
|
+
clearTimeout(timeout)
|
|
171
|
+
resolve(false)
|
|
156
172
|
})
|
|
157
|
-
}
|
|
158
|
-
return 'error'
|
|
159
|
-
}
|
|
173
|
+
})
|
|
160
174
|
}
|
|
161
175
|
|
|
162
176
|
function compareVersions(v1, v2) {
|
|
163
177
|
if (!v1 || !v2) return 0
|
|
164
178
|
|
|
179
|
+
// Always update if the current version is not a valid semver
|
|
180
|
+
// e.g. 1.0.420-beta.1
|
|
181
|
+
if (!v1.match(/^\d+(\.\d+)*$/)) {
|
|
182
|
+
return -1
|
|
183
|
+
}
|
|
184
|
+
|
|
165
185
|
const parseVersion = (version) => {
|
|
166
186
|
const parts = version.split('-')
|
|
167
187
|
const mainParts = parts[0].split('.').map(Number)
|
|
@@ -243,32 +263,21 @@ async function downloadBinary(version) {
|
|
|
243
263
|
process.env.NEXT_PUBLIC_CODEBUFF_APP_URL || 'https://codebuff.com'
|
|
244
264
|
}/api/releases/download/${version}/${fileName}`
|
|
245
265
|
|
|
246
|
-
//
|
|
247
|
-
fs.mkdirSync(CONFIG.
|
|
266
|
+
// Ensure config directory exists
|
|
267
|
+
fs.mkdirSync(CONFIG.configDir, { recursive: true })
|
|
248
268
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
} catch (err) {
|
|
253
|
-
const backupPath = CONFIG.binaryPath + `.old.${Date.now()}`
|
|
254
|
-
|
|
255
|
-
try {
|
|
256
|
-
fs.renameSync(CONFIG.binaryPath, backupPath)
|
|
257
|
-
} catch (renameErr) {
|
|
258
|
-
throw new Error(
|
|
259
|
-
`Failed to replace existing binary. ` +
|
|
260
|
-
`unlink error: ${err.code || err.message}, ` +
|
|
261
|
-
`rename error: ${renameErr.code || renameErr.message}`,
|
|
262
|
-
)
|
|
263
|
-
}
|
|
264
|
-
}
|
|
269
|
+
// Clean up any previous temp download directory
|
|
270
|
+
if (fs.existsSync(CONFIG.tempDownloadDir)) {
|
|
271
|
+
fs.rmSync(CONFIG.tempDownloadDir, { recursive: true })
|
|
265
272
|
}
|
|
273
|
+
fs.mkdirSync(CONFIG.tempDownloadDir, { recursive: true })
|
|
266
274
|
|
|
267
275
|
term.write('Downloading...')
|
|
268
276
|
|
|
269
277
|
const res = await httpGet(downloadUrl)
|
|
270
278
|
|
|
271
279
|
if (res.statusCode !== 200) {
|
|
280
|
+
fs.rmSync(CONFIG.tempDownloadDir, { recursive: true })
|
|
272
281
|
throw new Error(`Download failed: HTTP ${res.statusCode}`)
|
|
273
282
|
}
|
|
274
283
|
|
|
@@ -294,31 +303,71 @@ async function downloadBinary(version) {
|
|
|
294
303
|
}
|
|
295
304
|
})
|
|
296
305
|
|
|
306
|
+
// Extract to temp directory
|
|
297
307
|
await new Promise((resolve, reject) => {
|
|
298
308
|
res
|
|
299
309
|
.pipe(zlib.createGunzip())
|
|
300
|
-
.pipe(tar.x({ cwd: CONFIG.
|
|
310
|
+
.pipe(tar.x({ cwd: CONFIG.tempDownloadDir }))
|
|
301
311
|
.on('finish', resolve)
|
|
302
312
|
.on('error', reject)
|
|
303
313
|
})
|
|
304
314
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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
|
+
}
|
|
308
330
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
331
|
+
// Run smoke test on the downloaded binary
|
|
332
|
+
term.write('Verifying download...')
|
|
333
|
+
const smokeTestPassed = await runSmokeTest(tempBinaryPath)
|
|
334
|
+
|
|
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
|
+
}
|
|
312
357
|
}
|
|
313
|
-
} else {
|
|
314
|
-
throw new Error(
|
|
315
|
-
`Binary not found after extraction. Expected: ${extractedPath}, Available files: ${files.join(', ')}`,
|
|
316
|
-
)
|
|
317
358
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
+
}
|
|
322
371
|
}
|
|
323
372
|
|
|
324
373
|
term.clearLine()
|
|
@@ -326,8 +375,8 @@ async function downloadBinary(version) {
|
|
|
326
375
|
}
|
|
327
376
|
|
|
328
377
|
async function ensureBinaryExists() {
|
|
329
|
-
const currentVersion =
|
|
330
|
-
if (currentVersion !== null
|
|
378
|
+
const currentVersion = getCurrentVersion()
|
|
379
|
+
if (currentVersion !== null) {
|
|
331
380
|
return
|
|
332
381
|
}
|
|
333
382
|
|
|
@@ -350,14 +399,14 @@ async function ensureBinaryExists() {
|
|
|
350
399
|
|
|
351
400
|
async function checkForUpdates(runningProcess, exitListener) {
|
|
352
401
|
try {
|
|
353
|
-
const currentVersion =
|
|
354
|
-
if (!currentVersion) return
|
|
402
|
+
const currentVersion = getCurrentVersion()
|
|
355
403
|
|
|
356
404
|
const latestVersion = await getLatestVersion()
|
|
357
405
|
if (!latestVersion) return
|
|
358
406
|
|
|
359
407
|
if (
|
|
360
|
-
|
|
408
|
+
// Download new version if current version is unknown or outdated.
|
|
409
|
+
currentVersion === null ||
|
|
361
410
|
compareVersions(currentVersion, latestVersion) < 0
|
|
362
411
|
) {
|
|
363
412
|
term.clearLine()
|
package/package.json
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codecane",
|
|
3
|
-
"version": "1.0.420-beta.
|
|
3
|
+
"version": "1.0.420-beta.261",
|
|
4
4
|
"description": "AI coding agent CLI (staging)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
7
7
|
"codecane": "index.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"postinstall": "node
|
|
10
|
+
"postinstall": "node postinstall.js",
|
|
11
|
+
"preuninstall": "node -e \"const fs = require('fs'); const path = require('path'); const os = require('os'); const binaryPath = path.join(os.homedir(), '.config', 'manicode', process.platform === 'win32' ? 'codecane.exe' : 'codecane'); try { fs.unlinkSync(binaryPath) } catch (e) { /* ignore if file doesn't exist */ }\""
|
|
11
12
|
},
|
|
12
13
|
"files": [
|
|
13
14
|
"index.js",
|
|
15
|
+
"postinstall.js",
|
|
14
16
|
"README.md"
|
|
15
17
|
],
|
|
16
18
|
"os": [
|
package/postinstall.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
// Clean up old binary
|
|
8
|
+
const binaryPath = path.join(
|
|
9
|
+
os.homedir(),
|
|
10
|
+
'.config',
|
|
11
|
+
'manicode',
|
|
12
|
+
process.platform === 'win32' ? 'codecane.exe' : 'codecane'
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
fs.unlinkSync(binaryPath);
|
|
17
|
+
} catch (e) {
|
|
18
|
+
/* ignore if file doesn't exist */
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Print welcome message
|
|
22
|
+
console.log('\n');
|
|
23
|
+
console.log('🧪 Welcome to Codecane (Staging)!');
|
|
24
|
+
console.log('\n');
|
|
25
|
+
console.log('⚠️ This is a staging/beta release for testing purposes.');
|
|
26
|
+
console.log('\n');
|
|
27
|
+
console.log('To get started:');
|
|
28
|
+
console.log(' 1. cd to your project directory');
|
|
29
|
+
console.log(' 2. Run: codecane');
|
|
30
|
+
console.log('\n');
|
|
31
|
+
console.log('Example:');
|
|
32
|
+
console.log(' $ cd ~/my-project');
|
|
33
|
+
console.log(' $ codecane');
|
|
34
|
+
console.log('\n');
|
|
35
|
+
console.log('For more information, visit: https://codebuff.com/docs');
|
|
36
|
+
console.log('\n');
|