codeblog-app 2.3.4 → 2.4.0
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/package.json +7 -7
- package/src/cli/auto-update.ts +59 -0
- package/src/cli/cmd/update.ts +5 -83
- package/src/cli/perform-update.ts +68 -0
- package/src/index.ts +6 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "codeblog-app",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.4.0",
|
|
5
5
|
"description": "CLI client for CodeBlog — the forum where AI writes the posts",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|
|
@@ -58,11 +58,11 @@
|
|
|
58
58
|
"typescript": "5.8.2"
|
|
59
59
|
},
|
|
60
60
|
"optionalDependencies": {
|
|
61
|
-
"codeblog-app-darwin-arm64": "2.
|
|
62
|
-
"codeblog-app-darwin-x64": "2.
|
|
63
|
-
"codeblog-app-linux-arm64": "2.
|
|
64
|
-
"codeblog-app-linux-x64": "2.
|
|
65
|
-
"codeblog-app-windows-x64": "2.
|
|
61
|
+
"codeblog-app-darwin-arm64": "2.4.0",
|
|
62
|
+
"codeblog-app-darwin-x64": "2.4.0",
|
|
63
|
+
"codeblog-app-linux-arm64": "2.4.0",
|
|
64
|
+
"codeblog-app-linux-x64": "2.4.0",
|
|
65
|
+
"codeblog-app-windows-x64": "2.4.0"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"@ai-sdk/anthropic": "^3.0.44",
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"@opentui/core": "^0.1.79",
|
|
74
74
|
"@opentui/solid": "^0.1.79",
|
|
75
75
|
"ai": "^6.0.86",
|
|
76
|
-
"codeblog-mcp": "2.
|
|
76
|
+
"codeblog-mcp": "2.4.0",
|
|
77
77
|
"drizzle-orm": "1.0.0-beta.12-a5629fb",
|
|
78
78
|
"fuzzysort": "^3.1.0",
|
|
79
79
|
"hono": "4.10.7",
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { performUpdate } from "./perform-update"
|
|
2
|
+
import { UI } from "./ui"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Checks for a newer CLI version on startup and auto-updates if available.
|
|
6
|
+
* Equivalent to running `codeblog update` automatically.
|
|
7
|
+
* On any failure, silently continues — never blocks normal usage.
|
|
8
|
+
*/
|
|
9
|
+
export async function checkAndAutoUpdate(): Promise<void> {
|
|
10
|
+
try {
|
|
11
|
+
// Skip if disabled via env
|
|
12
|
+
if (process.env.CODEBLOG_NO_AUTO_UPDATE === "1") return
|
|
13
|
+
|
|
14
|
+
// Skip if running the `update` command (avoid double-update)
|
|
15
|
+
const cmd = process.argv[2]
|
|
16
|
+
if (cmd === "update") return
|
|
17
|
+
|
|
18
|
+
// Fetch latest version from npm registry
|
|
19
|
+
const controller = new AbortController()
|
|
20
|
+
const timeout = setTimeout(() => controller.abort(), 10_000)
|
|
21
|
+
let res: Response
|
|
22
|
+
try {
|
|
23
|
+
res = await fetch("https://registry.npmjs.org/codeblog-app/latest", { signal: controller.signal })
|
|
24
|
+
} catch (e: any) {
|
|
25
|
+
clearTimeout(timeout)
|
|
26
|
+
if (e.name === "AbortError") {
|
|
27
|
+
UI.error("Version check timed out (10s). Please check your network and try again.")
|
|
28
|
+
} else {
|
|
29
|
+
UI.error(`Failed to check for updates: ${e.message}`)
|
|
30
|
+
}
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
clearTimeout(timeout)
|
|
34
|
+
if (!res.ok) return
|
|
35
|
+
|
|
36
|
+
const data = (await res.json()) as { version: string }
|
|
37
|
+
const latest = data.version
|
|
38
|
+
const pkg = await import("../../package.json")
|
|
39
|
+
const current = pkg.version
|
|
40
|
+
|
|
41
|
+
if (current === latest) return
|
|
42
|
+
|
|
43
|
+
// Run the same flow as `codeblog update`
|
|
44
|
+
UI.info(`Current version: v${current}`)
|
|
45
|
+
UI.info(`Updating v${current} → v${latest}...`)
|
|
46
|
+
|
|
47
|
+
await performUpdate(latest)
|
|
48
|
+
UI.success(`Updated to v${latest}!`)
|
|
49
|
+
|
|
50
|
+
// Re-exec: run the updated binary with the same arguments
|
|
51
|
+
const proc = Bun.spawn([process.execPath, ...process.argv.slice(1)], {
|
|
52
|
+
stdio: ["inherit", "inherit", "inherit"],
|
|
53
|
+
})
|
|
54
|
+
const code = await proc.exited
|
|
55
|
+
process.exit(code)
|
|
56
|
+
} catch (e) {
|
|
57
|
+
UI.error(e instanceof Error ? e.message : String(e))
|
|
58
|
+
}
|
|
59
|
+
}
|
package/src/cli/cmd/update.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CommandModule } from "yargs"
|
|
2
2
|
import { UI } from "../ui"
|
|
3
|
+
import { performUpdate } from "../perform-update"
|
|
3
4
|
|
|
4
5
|
export const UpdateCommand: CommandModule = {
|
|
5
6
|
command: "update",
|
|
@@ -49,91 +50,12 @@ export const UpdateCommand: CommandModule = {
|
|
|
49
50
|
|
|
50
51
|
UI.info(`Updating v${current} → v${latest}...`)
|
|
51
52
|
|
|
52
|
-
const os = process.platform === "win32" ? "windows" : process.platform === "darwin" ? "darwin" : "linux"
|
|
53
|
-
const arch = process.arch === "arm64" ? "arm64" : "x64"
|
|
54
|
-
const platform = `${os}-${arch}`
|
|
55
|
-
const pkg_name = `codeblog-app-${platform}`
|
|
56
|
-
const url = `https://registry.npmjs.org/${pkg_name}/-/${pkg_name}-${latest}.tgz`
|
|
57
|
-
|
|
58
|
-
const tmpdir = (await import("os")).tmpdir()
|
|
59
|
-
const path = await import("path")
|
|
60
|
-
const fs = await import("fs/promises")
|
|
61
|
-
const tmp = path.join(tmpdir, `codeblog-update-${Date.now()}`)
|
|
62
|
-
await fs.mkdir(tmp, { recursive: true })
|
|
63
|
-
|
|
64
|
-
UI.info("Downloading...")
|
|
65
|
-
const tgz = path.join(tmp, "pkg.tgz")
|
|
66
|
-
const dlController = new AbortController()
|
|
67
|
-
const dlTimeout = setTimeout(() => dlController.abort(), 60_000)
|
|
68
|
-
let dlRes: Response
|
|
69
53
|
try {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (e.name === "AbortError") {
|
|
75
|
-
UI.error("Download timed out (60s). Please check your network and try again.")
|
|
76
|
-
} else {
|
|
77
|
-
UI.error(`Download failed: ${e.message}`)
|
|
78
|
-
}
|
|
54
|
+
await performUpdate(latest)
|
|
55
|
+
UI.success(`Updated to v${latest}!`)
|
|
56
|
+
} catch (e) {
|
|
57
|
+
UI.error(e instanceof Error ? e.message : String(e))
|
|
79
58
|
process.exitCode = 1
|
|
80
|
-
return
|
|
81
|
-
}
|
|
82
|
-
clearTimeout(dlTimeout)
|
|
83
|
-
if (!dlRes.ok) {
|
|
84
|
-
UI.error(`Failed to download update for ${platform} (HTTP ${dlRes.status})`)
|
|
85
|
-
await fs.rm(tmp, { recursive: true, force: true }).catch(() => {})
|
|
86
|
-
process.exitCode = 1
|
|
87
|
-
return
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const arrayBuf = await dlRes.arrayBuffer()
|
|
91
|
-
await fs.writeFile(tgz, Buffer.from(arrayBuf))
|
|
92
|
-
|
|
93
|
-
UI.info("Extracting...")
|
|
94
|
-
const proc = Bun.spawn(["tar", "-xzf", tgz, "-C", tmp], { stdout: "ignore", stderr: "ignore" })
|
|
95
|
-
await proc.exited
|
|
96
|
-
|
|
97
|
-
const bin = process.execPath
|
|
98
|
-
const ext = os === "windows" ? ".exe" : ""
|
|
99
|
-
const src = path.join(tmp, "package", "bin", `codeblog${ext}`)
|
|
100
|
-
|
|
101
|
-
UI.info("Installing...")
|
|
102
|
-
// On macOS/Linux, remove the running binary first to avoid ETXTBSY
|
|
103
|
-
if (os !== "windows") {
|
|
104
|
-
try {
|
|
105
|
-
await fs.unlink(bin)
|
|
106
|
-
} catch {
|
|
107
|
-
// ignore if removal fails
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
await fs.copyFile(src, bin)
|
|
111
|
-
if (os !== "windows") {
|
|
112
|
-
await fs.chmod(bin, 0o755)
|
|
113
59
|
}
|
|
114
|
-
if (os === "darwin") {
|
|
115
|
-
await Bun.spawn(["codesign", "--remove-signature", bin], { stdout: "ignore", stderr: "ignore" }).exited
|
|
116
|
-
const cs = Bun.spawn(["codesign", "--sign", "-", "--force", bin], { stdout: "ignore", stderr: "ignore" })
|
|
117
|
-
if ((await cs.exited) !== 0) {
|
|
118
|
-
await fs.rm(tmp, { recursive: true, force: true })
|
|
119
|
-
UI.error("Update installed but macOS code signing failed. Please reinstall with install.sh.")
|
|
120
|
-
process.exitCode = 1
|
|
121
|
-
return
|
|
122
|
-
}
|
|
123
|
-
const verify = Bun.spawn(["codesign", "--verify", "--deep", "--strict", bin], {
|
|
124
|
-
stdout: "ignore",
|
|
125
|
-
stderr: "ignore",
|
|
126
|
-
})
|
|
127
|
-
if ((await verify.exited) !== 0) {
|
|
128
|
-
await fs.rm(tmp, { recursive: true, force: true })
|
|
129
|
-
UI.error("Update installed but signature verification failed. Please reinstall with install.sh.")
|
|
130
|
-
process.exitCode = 1
|
|
131
|
-
return
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
await fs.rm(tmp, { recursive: true, force: true })
|
|
136
|
-
|
|
137
|
-
UI.success(`Updated to v${latest}!`)
|
|
138
60
|
},
|
|
139
61
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import path from "path"
|
|
2
|
+
import { tmpdir } from "os"
|
|
3
|
+
import { mkdir, writeFile, rm, unlink, copyFile, chmod } from "fs/promises"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Downloads and installs a specific version of the codeblog CLI binary.
|
|
7
|
+
* Shared between the `update` command and auto-update logic.
|
|
8
|
+
*
|
|
9
|
+
* Throws on failure so callers can handle errors as they see fit.
|
|
10
|
+
*/
|
|
11
|
+
export async function performUpdate(latest: string): Promise<void> {
|
|
12
|
+
const os = process.platform === "win32" ? "windows" : process.platform === "darwin" ? "darwin" : "linux"
|
|
13
|
+
const arch = process.arch === "arm64" ? "arm64" : "x64"
|
|
14
|
+
const platform = `${os}-${arch}`
|
|
15
|
+
const pkgName = `codeblog-app-${platform}`
|
|
16
|
+
const url = `https://registry.npmjs.org/${pkgName}/-/${pkgName}-${latest}.tgz`
|
|
17
|
+
|
|
18
|
+
const tmp = path.join(tmpdir(), `codeblog-update-${Date.now()}`)
|
|
19
|
+
await mkdir(tmp, { recursive: true })
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
// Download
|
|
23
|
+
const tgz = path.join(tmp, "pkg.tgz")
|
|
24
|
+
const dlController = new AbortController()
|
|
25
|
+
const dlTimeout = setTimeout(() => dlController.abort(), 60_000)
|
|
26
|
+
let dlRes: Response
|
|
27
|
+
try {
|
|
28
|
+
dlRes = await fetch(url, { signal: dlController.signal })
|
|
29
|
+
} catch (e: any) {
|
|
30
|
+
clearTimeout(dlTimeout)
|
|
31
|
+
if (e.name === "AbortError") throw new Error("Download timed out (60s)")
|
|
32
|
+
throw new Error(`Download failed: ${e.message}`)
|
|
33
|
+
}
|
|
34
|
+
clearTimeout(dlTimeout)
|
|
35
|
+
if (!dlRes.ok) throw new Error(`Failed to download update for ${platform} (HTTP ${dlRes.status})`)
|
|
36
|
+
|
|
37
|
+
const arrayBuf = await dlRes.arrayBuffer()
|
|
38
|
+
await writeFile(tgz, Buffer.from(arrayBuf))
|
|
39
|
+
|
|
40
|
+
// Extract
|
|
41
|
+
const proc = Bun.spawn(["tar", "-xzf", tgz, "-C", tmp], { stdout: "ignore", stderr: "ignore" })
|
|
42
|
+
await proc.exited
|
|
43
|
+
|
|
44
|
+
// Install
|
|
45
|
+
const bin = process.execPath
|
|
46
|
+
const ext = os === "windows" ? ".exe" : ""
|
|
47
|
+
const src = path.join(tmp, "package", "bin", `codeblog${ext}`)
|
|
48
|
+
|
|
49
|
+
if (os !== "windows") {
|
|
50
|
+
try { await unlink(bin) } catch {}
|
|
51
|
+
}
|
|
52
|
+
await copyFile(src, bin)
|
|
53
|
+
if (os !== "windows") {
|
|
54
|
+
await chmod(bin, 0o755)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// macOS codesign
|
|
58
|
+
if (os === "darwin") {
|
|
59
|
+
await Bun.spawn(["codesign", "--remove-signature", bin], { stdout: "ignore", stderr: "ignore" }).exited
|
|
60
|
+
const cs = Bun.spawn(["codesign", "--sign", "-", "--force", bin], { stdout: "ignore", stderr: "ignore" })
|
|
61
|
+
if ((await cs.exited) !== 0) throw new Error("macOS code signing failed")
|
|
62
|
+
const verify = Bun.spawn(["codesign", "--verify", "--deep", "--strict", bin], { stdout: "ignore", stderr: "ignore" })
|
|
63
|
+
if ((await verify.exited) !== 0) throw new Error("macOS signature verification failed")
|
|
64
|
+
}
|
|
65
|
+
} finally {
|
|
66
|
+
await rm(tmp, { recursive: true, force: true }).catch(() => {})
|
|
67
|
+
}
|
|
68
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { UI } from "./cli/ui"
|
|
|
5
5
|
import { EOL } from "os"
|
|
6
6
|
import { McpBridge } from "./mcp/client"
|
|
7
7
|
import { Auth } from "./auth"
|
|
8
|
+
import { checkAndAutoUpdate } from "./cli/auto-update"
|
|
8
9
|
|
|
9
10
|
// Commands
|
|
10
11
|
import { SetupCommand } from "./cli/cmd/setup"
|
|
@@ -161,6 +162,11 @@ const hasSubcommand = args.length > 0 && !args[0]!.startsWith("-")
|
|
|
161
162
|
const isHelp = args.includes("--help") || args.includes("-h")
|
|
162
163
|
const isVersion = args.includes("--version") || args.includes("-v")
|
|
163
164
|
|
|
165
|
+
// Auto-update check on startup
|
|
166
|
+
if (!isHelp && !isVersion) {
|
|
167
|
+
await checkAndAutoUpdate()
|
|
168
|
+
}
|
|
169
|
+
|
|
164
170
|
if (!hasSubcommand && !isHelp && !isVersion) {
|
|
165
171
|
await Log.init({ print: false })
|
|
166
172
|
Log.Default.info("codeblog", { version: VERSION, args: [] })
|