codeblog-app 2.2.6 → 2.3.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.
Files changed (69) hide show
  1. package/package.json +8 -71
  2. package/drizzle/0000_init.sql +0 -34
  3. package/drizzle/meta/_journal.json +0 -13
  4. package/drizzle.config.ts +0 -10
  5. package/src/ai/__tests__/chat.test.ts +0 -179
  6. package/src/ai/__tests__/provider.test.ts +0 -198
  7. package/src/ai/__tests__/tools.test.ts +0 -93
  8. package/src/ai/chat.ts +0 -224
  9. package/src/ai/configure.ts +0 -134
  10. package/src/ai/provider.ts +0 -302
  11. package/src/ai/tools.ts +0 -114
  12. package/src/auth/index.ts +0 -47
  13. package/src/auth/oauth.ts +0 -108
  14. package/src/cli/__tests__/commands.test.ts +0 -225
  15. package/src/cli/cmd/agent.ts +0 -97
  16. package/src/cli/cmd/chat.ts +0 -190
  17. package/src/cli/cmd/comment.ts +0 -67
  18. package/src/cli/cmd/config.ts +0 -153
  19. package/src/cli/cmd/feed.ts +0 -53
  20. package/src/cli/cmd/forum.ts +0 -106
  21. package/src/cli/cmd/login.ts +0 -45
  22. package/src/cli/cmd/logout.ts +0 -12
  23. package/src/cli/cmd/me.ts +0 -188
  24. package/src/cli/cmd/post.ts +0 -25
  25. package/src/cli/cmd/publish.ts +0 -64
  26. package/src/cli/cmd/scan.ts +0 -78
  27. package/src/cli/cmd/search.ts +0 -35
  28. package/src/cli/cmd/setup.ts +0 -352
  29. package/src/cli/cmd/tui.ts +0 -20
  30. package/src/cli/cmd/uninstall.ts +0 -281
  31. package/src/cli/cmd/update.ts +0 -123
  32. package/src/cli/cmd/vote.ts +0 -50
  33. package/src/cli/cmd/whoami.ts +0 -18
  34. package/src/cli/mcp-print.ts +0 -6
  35. package/src/cli/ui.ts +0 -250
  36. package/src/config/index.ts +0 -55
  37. package/src/flag/index.ts +0 -23
  38. package/src/global/index.ts +0 -38
  39. package/src/id/index.ts +0 -20
  40. package/src/index.ts +0 -200
  41. package/src/mcp/__tests__/client.test.ts +0 -149
  42. package/src/mcp/__tests__/e2e.ts +0 -327
  43. package/src/mcp/__tests__/integration.ts +0 -148
  44. package/src/mcp/client.ts +0 -148
  45. package/src/server/index.ts +0 -48
  46. package/src/storage/chat.ts +0 -71
  47. package/src/storage/db.ts +0 -85
  48. package/src/storage/schema.sql.ts +0 -39
  49. package/src/storage/schema.ts +0 -1
  50. package/src/tui/app.tsx +0 -184
  51. package/src/tui/commands.ts +0 -186
  52. package/src/tui/context/exit.tsx +0 -15
  53. package/src/tui/context/helper.tsx +0 -25
  54. package/src/tui/context/route.tsx +0 -24
  55. package/src/tui/context/theme.tsx +0 -470
  56. package/src/tui/routes/home.tsx +0 -660
  57. package/src/tui/routes/model.tsx +0 -210
  58. package/src/tui/routes/notifications.tsx +0 -87
  59. package/src/tui/routes/post.tsx +0 -102
  60. package/src/tui/routes/search.tsx +0 -105
  61. package/src/tui/routes/setup.tsx +0 -255
  62. package/src/tui/routes/trending.tsx +0 -107
  63. package/src/util/__tests__/context.test.ts +0 -31
  64. package/src/util/__tests__/lazy.test.ts +0 -37
  65. package/src/util/context.ts +0 -23
  66. package/src/util/error.ts +0 -46
  67. package/src/util/lazy.ts +0 -18
  68. package/src/util/log.ts +0 -142
  69. package/tsconfig.json +0 -11
@@ -1,255 +0,0 @@
1
- import { createSignal } from "solid-js"
2
- import { useKeyboard } from "@opentui/solid"
3
- import { useTheme, THEME_NAMES, THEMES } from "../context/theme"
4
-
5
- // High-contrast colors that are visible on ANY terminal background
6
- const HC = {
7
- title: "#ff6600",
8
- text: "#888888",
9
- selected: "#00cc00",
10
- dim: "#999999",
11
- }
12
-
13
- export function ThemeSetup() {
14
- const theme = useTheme()
15
- const modes = ["dark", "light"] as const
16
- const [step, setStep] = createSignal<"mode" | "theme">("mode")
17
- const [modeIdx, setModeIdx] = createSignal(0)
18
- const [themeIdx, setThemeIdx] = createSignal(0)
19
-
20
- useKeyboard((evt) => {
21
- if (step() === "mode") {
22
- if (evt.name === "up" || evt.name === "k") {
23
- setModeIdx((i) => (i - 1 + modes.length) % modes.length)
24
- evt.preventDefault()
25
- return
26
- }
27
- if (evt.name === "down" || evt.name === "j") {
28
- setModeIdx((i) => (i + 1) % modes.length)
29
- evt.preventDefault()
30
- return
31
- }
32
- if (evt.name === "return") {
33
- theme.setMode(modes[modeIdx()])
34
- setStep("theme")
35
- evt.preventDefault()
36
- return
37
- }
38
- }
39
-
40
- if (step() === "theme") {
41
- if (evt.name === "up" || evt.name === "k") {
42
- const next = (themeIdx() - 1 + THEME_NAMES.length) % THEME_NAMES.length
43
- setThemeIdx(next)
44
- theme.set(THEME_NAMES[next])
45
- evt.preventDefault()
46
- return
47
- }
48
- if (evt.name === "down" || evt.name === "j") {
49
- const next = (themeIdx() + 1) % THEME_NAMES.length
50
- setThemeIdx(next)
51
- theme.set(THEME_NAMES[next])
52
- evt.preventDefault()
53
- return
54
- }
55
- if (evt.name === "return") {
56
- theme.finishSetup()
57
- evt.preventDefault()
58
- return
59
- }
60
- if (evt.name === "escape") {
61
- setStep("mode")
62
- evt.preventDefault()
63
- return
64
- }
65
- }
66
- })
67
-
68
- return (
69
- <box flexDirection="column" flexGrow={1} alignItems="center" paddingLeft={2} paddingRight={2}>
70
- <box flexGrow={1} minHeight={0} />
71
-
72
- <box flexShrink={0} flexDirection="column" alignItems="center">
73
- <text fg={HC.title}>
74
- <span style={{ bold: true }}>{"★ Welcome to CodeBlog ★"}</span>
75
- </text>
76
- <box height={1} />
77
- </box>
78
-
79
- {step() === "mode" ? (
80
- <box flexShrink={0} flexDirection="column" width="100%" maxWidth={50}>
81
- <text fg={HC.title}>
82
- <span style={{ bold: true }}>{"What is your terminal background color?"}</span>
83
- </text>
84
- <box height={1} />
85
- {modes.map((m, i) => (
86
- <box flexDirection="row" paddingLeft={2}>
87
- <text fg={modeIdx() === i ? HC.selected : HC.dim}>
88
- {modeIdx() === i ? "❯ " : " "}
89
- </text>
90
- <text fg={modeIdx() === i ? HC.selected : HC.dim}>
91
- <span style={{ bold: modeIdx() === i }}>
92
- {m === "dark" ? "Dark background (black/dark terminal)" : "Light background (white/light terminal)"}
93
- </span>
94
- </text>
95
- </box>
96
- ))}
97
- <box height={1} />
98
- <text fg={HC.text}>{"↑↓ select · Enter confirm"}</text>
99
- </box>
100
- ) : (
101
- <box flexShrink={0} flexDirection="column" width="100%" maxWidth={60}>
102
- <text fg={theme.colors.text}>
103
- <span style={{ bold: true }}>{"Choose a color theme:"}</span>
104
- </text>
105
- <box height={1} />
106
- {THEME_NAMES.map((name, i) => {
107
- const c = THEMES[name][theme.mode]
108
- return (
109
- <box flexDirection="row" paddingLeft={2}>
110
- <text fg={themeIdx() === i ? c.primary : theme.colors.textMuted}>
111
- {themeIdx() === i ? "❯ " : " "}
112
- </text>
113
- <text fg={themeIdx() === i ? c.text : theme.colors.textMuted}>
114
- <span style={{ bold: themeIdx() === i }}>
115
- {name.padEnd(14)}
116
- </span>
117
- </text>
118
- <text fg={c.logo1}>{" ●"}</text>
119
- <text fg={c.logo2}>{"●"}</text>
120
- <text fg={c.primary}>{"●"}</text>
121
- <text fg={c.accent}>{"●"}</text>
122
- <text fg={c.success}>{"●"}</text>
123
- <text fg={c.error}>{"●"}</text>
124
- </box>
125
- )
126
- })}
127
- <box height={1} />
128
- <text fg={theme.colors.textMuted}>{"↑↓ select · Enter confirm · Esc back"}</text>
129
- </box>
130
- )}
131
-
132
- <box flexGrow={1} minHeight={0} />
133
- </box>
134
- )
135
- }
136
-
137
- export function ThemePicker(props: { onDone: () => void }) {
138
- const theme = useTheme()
139
- const [idx, setIdx] = createSignal(THEME_NAMES.indexOf(theme.name))
140
- const [tab, setTab] = createSignal<"theme" | "mode">("theme")
141
-
142
- useKeyboard((evt) => {
143
- if (tab() === "theme") {
144
- if (evt.name === "up" || evt.name === "k") {
145
- const next = (idx() - 1 + THEME_NAMES.length) % THEME_NAMES.length
146
- setIdx(next)
147
- theme.set(THEME_NAMES[next])
148
- evt.preventDefault()
149
- return
150
- }
151
- if (evt.name === "down" || evt.name === "j") {
152
- const next = (idx() + 1) % THEME_NAMES.length
153
- setIdx(next)
154
- theme.set(THEME_NAMES[next])
155
- evt.preventDefault()
156
- return
157
- }
158
- if (evt.name === "tab") {
159
- setTab("mode")
160
- evt.preventDefault()
161
- return
162
- }
163
- if (evt.name === "return" || evt.name === "escape") {
164
- props.onDone()
165
- evt.preventDefault()
166
- return
167
- }
168
- }
169
-
170
- if (tab() === "mode") {
171
- if (evt.name === "up" || evt.name === "down" || evt.name === "k" || evt.name === "j") {
172
- theme.setMode(theme.mode === "dark" ? "light" : "dark")
173
- evt.preventDefault()
174
- return
175
- }
176
- if (evt.name === "tab") {
177
- setTab("theme")
178
- evt.preventDefault()
179
- return
180
- }
181
- if (evt.name === "return" || evt.name === "escape") {
182
- props.onDone()
183
- evt.preventDefault()
184
- return
185
- }
186
- }
187
- })
188
-
189
- return (
190
- <box flexDirection="column" flexGrow={1} paddingLeft={2} paddingRight={2} paddingTop={1}>
191
- <box flexDirection="row" gap={4} flexShrink={0}>
192
- <text fg={theme.colors.primary}>
193
- <span style={{ bold: true }}>Theme Settings</span>
194
- </text>
195
- <box flexGrow={1} />
196
- <text fg={theme.colors.textMuted}>{"Tab: switch section · Enter/Esc: done"}</text>
197
- </box>
198
-
199
- <box flexDirection="row" flexGrow={1} paddingTop={1} gap={4}>
200
- {/* Theme list */}
201
- <box flexDirection="column" width={40}>
202
- <text fg={tab() === "theme" ? theme.colors.text : theme.colors.textMuted}>
203
- <span style={{ bold: true }}>{"Color Theme"}</span>
204
- </text>
205
- <box height={1} />
206
- {THEME_NAMES.map((name, i) => {
207
- const c = THEMES[name][theme.mode]
208
- return (
209
- <box flexDirection="row">
210
- <text fg={idx() === i ? c.primary : theme.colors.textMuted}>
211
- {idx() === i && tab() === "theme" ? "❯ " : " "}
212
- </text>
213
- <text fg={idx() === i ? c.text : theme.colors.textMuted}>
214
- <span style={{ bold: idx() === i }}>
215
- {name.padEnd(14)}
216
- </span>
217
- </text>
218
- <text fg={c.logo1}>{" ●"}</text>
219
- <text fg={c.logo2}>{"●"}</text>
220
- <text fg={c.primary}>{"●"}</text>
221
- <text fg={c.accent}>{"●"}</text>
222
- <text fg={c.success}>{"●"}</text>
223
- <text fg={c.error}>{"●"}</text>
224
- </box>
225
- )
226
- })}
227
- </box>
228
-
229
- {/* Mode toggle */}
230
- <box flexDirection="column" width={30}>
231
- <text fg={tab() === "mode" ? theme.colors.text : theme.colors.textMuted}>
232
- <span style={{ bold: true }}>{"Background Mode"}</span>
233
- </text>
234
- <box height={1} />
235
- <box flexDirection="row">
236
- <text fg={theme.mode === "dark" && tab() === "mode" ? theme.colors.primary : theme.colors.textMuted}>
237
- {theme.mode === "dark" && tab() === "mode" ? "❯ " : " "}
238
- </text>
239
- <text fg={theme.mode === "dark" ? theme.colors.text : theme.colors.textMuted}>
240
- <span style={{ bold: theme.mode === "dark" }}>{"Dark"}</span>
241
- </text>
242
- </box>
243
- <box flexDirection="row">
244
- <text fg={theme.mode === "light" && tab() === "mode" ? theme.colors.primary : theme.colors.textMuted}>
245
- {theme.mode === "light" && tab() === "mode" ? "❯ " : " "}
246
- </text>
247
- <text fg={theme.mode === "light" ? theme.colors.text : theme.colors.textMuted}>
248
- <span style={{ bold: theme.mode === "light" }}>{"Light"}</span>
249
- </text>
250
- </box>
251
- </box>
252
- </box>
253
- </box>
254
- )
255
- }
@@ -1,107 +0,0 @@
1
- import { createSignal, onMount, For, Show } from "solid-js"
2
- import { useKeyboard } from "@opentui/solid"
3
- import { useTheme } from "../context/theme"
4
- import { McpBridge } from "../../mcp/client"
5
-
6
- export function Trending() {
7
- const theme = useTheme()
8
- const [data, setData] = createSignal<any>(null)
9
- const [loading, setLoading] = createSignal(true)
10
- const [tab, setTab] = createSignal<"posts" | "tags" | "agents">("posts")
11
-
12
- onMount(async () => {
13
- try {
14
- const raw = await McpBridge.callToolJSON<any>("trending_topics", {})
15
- setData(raw.trending || raw)
16
- } catch {
17
- setData(null)
18
- }
19
- setLoading(false)
20
- })
21
-
22
- useKeyboard((evt) => {
23
- if (evt.name === "1") { setTab("posts"); evt.preventDefault() }
24
- if (evt.name === "2") { setTab("tags"); evt.preventDefault() }
25
- if (evt.name === "3") { setTab("agents"); evt.preventDefault() }
26
- })
27
-
28
- return (
29
- <box flexDirection="column" flexGrow={1}>
30
- <box paddingLeft={2} paddingRight={2} paddingTop={1} flexShrink={0} flexDirection="row" gap={1}>
31
- <text fg={theme.colors.accent}>
32
- <span style={{ bold: true }}>Trending</span>
33
- </text>
34
- <box flexGrow={1} />
35
- <text fg={theme.colors.textMuted}>esc:back</text>
36
- </box>
37
-
38
- <box paddingLeft={2} paddingTop={1} flexShrink={0} flexDirection="row" gap={2}>
39
- <text fg={tab() === "posts" ? theme.colors.primary : theme.colors.textMuted}>
40
- <span style={{ bold: tab() === "posts" }}>[1] Posts</span>
41
- </text>
42
- <text fg={tab() === "tags" ? theme.colors.primary : theme.colors.textMuted}>
43
- <span style={{ bold: tab() === "tags" }}>[2] Tags</span>
44
- </text>
45
- <text fg={tab() === "agents" ? theme.colors.primary : theme.colors.textMuted}>
46
- <span style={{ bold: tab() === "agents" }}>[3] Agents</span>
47
- </text>
48
- </box>
49
-
50
- <Show when={loading()}>
51
- <box paddingLeft={4} paddingTop={1}>
52
- <text fg={theme.colors.textMuted}>Loading trending...</text>
53
- </box>
54
- </Show>
55
-
56
- <Show when={!loading() && data()}>
57
- <Show when={tab() === "posts"}>
58
- <box flexDirection="column" paddingTop={1}>
59
- <For each={data()?.top_upvoted || data()?.posts || []}>
60
- {(post: any) => (
61
- <box flexDirection="row" paddingLeft={2} paddingRight={2}>
62
- <box width={6} justifyContent="flex-end" marginRight={1}>
63
- <text fg={theme.colors.success}>{`▲${post.score ?? post.upvotes ?? 0}`}</text>
64
- </box>
65
- <box flexDirection="column" flexGrow={1}>
66
- <text fg={theme.colors.text}>
67
- <span style={{ bold: true }}>{post.title}</span>
68
- </text>
69
- <text fg={theme.colors.textMuted}>{`👁${post.views ?? 0} 💬${post.comments ?? post.comment_count ?? 0} by ${post.agent ?? "anon"}`}</text>
70
- </box>
71
- </box>
72
- )}
73
- </For>
74
- </box>
75
- </Show>
76
-
77
- <Show when={tab() === "tags"}>
78
- <box flexDirection="column" paddingTop={1} paddingLeft={2}>
79
- <For each={data()?.trending_tags || data()?.tags || []}>
80
- {(tag: any) => (
81
- <box flexDirection="row" gap={2}>
82
- <text fg={theme.colors.primary}>{`#${tag.tag || tag.name || tag}`}</text>
83
- <text fg={theme.colors.textMuted}>{`${tag.count ?? ""} posts`}</text>
84
- </box>
85
- )}
86
- </For>
87
- </box>
88
- </Show>
89
-
90
- <Show when={tab() === "agents"}>
91
- <box flexDirection="column" paddingTop={1} paddingLeft={2}>
92
- <For each={data()?.top_agents || data()?.agents || []}>
93
- {(agent: any) => (
94
- <box flexDirection="row" gap={2}>
95
- <text fg={theme.colors.primary}>
96
- <span style={{ bold: true }}>{agent.name || agent.username || agent}</span>
97
- </text>
98
- <text fg={theme.colors.textMuted}>{`${agent.posts ?? agent.post_count ?? ""} posts`}</text>
99
- </box>
100
- )}
101
- </For>
102
- </box>
103
- </Show>
104
- </Show>
105
- </box>
106
- )
107
- }
@@ -1,31 +0,0 @@
1
- import { describe, test, expect } from "bun:test"
2
- import { Context } from "../context"
3
-
4
- describe("Context", () => {
5
- test("create and provide context", () => {
6
- const ctx = Context.create<string>("test")
7
-
8
- ctx.provide("hello", () => {
9
- expect(ctx.use()).toBe("hello")
10
- })
11
- })
12
-
13
- test("throws outside provider", () => {
14
- const ctx = Context.create<number>("num")
15
- expect(() => ctx.use()).toThrow(Context.NotFound)
16
- })
17
-
18
- test("nested contexts work correctly", () => {
19
- const ctx = Context.create<string>("nested")
20
-
21
- ctx.provide("outer", () => {
22
- expect(ctx.use()).toBe("outer")
23
-
24
- ctx.provide("inner", () => {
25
- expect(ctx.use()).toBe("inner")
26
- })
27
-
28
- expect(ctx.use()).toBe("outer")
29
- })
30
- })
31
- })
@@ -1,37 +0,0 @@
1
- import { describe, test, expect } from "bun:test"
2
- import { lazy } from "../lazy"
3
-
4
- describe("lazy", () => {
5
- test("initializes value on first call", () => {
6
- let count = 0
7
- const val = lazy(() => {
8
- count++
9
- return 42
10
- })
11
- expect(val()).toBe(42)
12
- expect(count).toBe(1)
13
- })
14
-
15
- test("caches value on subsequent calls", () => {
16
- let count = 0
17
- const val = lazy(() => {
18
- count++
19
- return "hello"
20
- })
21
- val()
22
- val()
23
- val()
24
- expect(count).toBe(1)
25
- })
26
-
27
- test("reset clears cached value", () => {
28
- let count = 0
29
- const val = lazy(() => {
30
- count++
31
- return count
32
- })
33
- expect(val()).toBe(1)
34
- val.reset()
35
- expect(val()).toBe(2)
36
- })
37
- })
@@ -1,23 +0,0 @@
1
- import { AsyncLocalStorage } from "async_hooks"
2
-
3
- export namespace Context {
4
- export class NotFound extends Error {
5
- constructor(public override readonly name: string) {
6
- super(`No context found for ${name}`)
7
- }
8
- }
9
-
10
- export function create<T>(name: string) {
11
- const storage = new AsyncLocalStorage<T>()
12
- return {
13
- use() {
14
- const result = storage.getStore()
15
- if (!result) throw new NotFound(name)
16
- return result
17
- },
18
- provide<R>(value: T, fn: () => R) {
19
- return storage.run(value, fn)
20
- },
21
- }
22
- }
23
- }
package/src/util/error.ts DELETED
@@ -1,46 +0,0 @@
1
- import z from "zod"
2
-
3
- export abstract class NamedError extends Error {
4
- abstract schema(): z.core.$ZodType
5
- abstract toObject(): { name: string; data: any }
6
-
7
- static create<Name extends string, Data extends z.core.$ZodType>(name: Name, data: Data) {
8
- const schema = z
9
- .object({
10
- name: z.literal(name),
11
- data,
12
- })
13
- .meta({ ref: name })
14
- const result = class extends NamedError {
15
- public static readonly Schema = schema
16
- public override readonly name = name as Name
17
-
18
- constructor(
19
- public readonly data: z.input<Data>,
20
- options?: ErrorOptions,
21
- ) {
22
- super(name, options)
23
- this.name = name
24
- }
25
-
26
- static isInstance(input: any): input is InstanceType<typeof result> {
27
- return typeof input === "object" && "name" in input && input.name === name
28
- }
29
-
30
- schema() {
31
- return schema
32
- }
33
-
34
- toObject() {
35
- return { name, data: this.data }
36
- }
37
- }
38
- Object.defineProperty(result, "name", { value: name })
39
- return result
40
- }
41
-
42
- public static readonly Unknown = NamedError.create(
43
- "UnknownError",
44
- z.object({ message: z.string() }),
45
- )
46
- }
package/src/util/lazy.ts DELETED
@@ -1,18 +0,0 @@
1
- export function lazy<T>(fn: () => T) {
2
- let value: T | undefined
3
- let loaded = false
4
-
5
- const result = (): T => {
6
- if (loaded) return value as T
7
- value = fn()
8
- loaded = true
9
- return value as T
10
- }
11
-
12
- result.reset = () => {
13
- loaded = false
14
- value = undefined
15
- }
16
-
17
- return result
18
- }
package/src/util/log.ts DELETED
@@ -1,142 +0,0 @@
1
- import path from "path"
2
- import fs from "fs/promises"
3
- import { Global } from "../global"
4
- import z from "zod"
5
-
6
- export namespace Log {
7
- export const Level = z.enum(["DEBUG", "INFO", "WARN", "ERROR"]).meta({ ref: "LogLevel", description: "Log level" })
8
- export type Level = z.infer<typeof Level>
9
-
10
- const levelPriority: Record<Level, number> = {
11
- DEBUG: 0,
12
- INFO: 1,
13
- WARN: 2,
14
- ERROR: 3,
15
- }
16
-
17
- let level: Level = "INFO"
18
-
19
- function shouldLog(input: Level): boolean {
20
- return levelPriority[input] >= levelPriority[level]
21
- }
22
-
23
- export type Logger = {
24
- debug(message?: any, extra?: Record<string, any>): void
25
- info(message?: any, extra?: Record<string, any>): void
26
- error(message?: any, extra?: Record<string, any>): void
27
- warn(message?: any, extra?: Record<string, any>): void
28
- tag(key: string, value: string): Logger
29
- clone(): Logger
30
- }
31
-
32
- const loggers = new Map<string, Logger>()
33
-
34
- export const Default = create({ service: "default" })
35
-
36
- export interface Options {
37
- print: boolean
38
- dev?: boolean
39
- level?: Level
40
- }
41
-
42
- let logpath = ""
43
- export function file() {
44
- return logpath
45
- }
46
- let write = (msg: any) => {
47
- process.stderr.write(msg)
48
- return msg.length
49
- }
50
-
51
- export async function init(options: Options) {
52
- if (options.level) level = options.level
53
- cleanup(Global.Path.log)
54
- if (options.print) return
55
- logpath = path.join(
56
- Global.Path.log,
57
- options.dev ? "dev.log" : new Date().toISOString().split(".")[0].replace(/:/g, "") + ".log",
58
- )
59
- const logfile = Bun.file(logpath)
60
- await fs.truncate(logpath).catch(() => {})
61
- const writer = logfile.writer()
62
- write = async (msg: any) => {
63
- const num = writer.write(msg)
64
- writer.flush()
65
- return num
66
- }
67
- }
68
-
69
- async function cleanup(dir: string) {
70
- const glob = new Bun.Glob("????-??-??T??????.log")
71
- const files = await Array.fromAsync(
72
- glob.scan({
73
- cwd: dir,
74
- absolute: true,
75
- }),
76
- )
77
- if (files.length <= 5) return
78
- const filesToDelete = files.slice(0, -10)
79
- await Promise.all(filesToDelete.map((file) => fs.unlink(file).catch(() => {})))
80
- }
81
-
82
- function formatError(error: Error, depth = 0): string {
83
- const result = error.message
84
- return error.cause instanceof Error && depth < 10
85
- ? result + " Caused by: " + formatError(error.cause, depth + 1)
86
- : result
87
- }
88
-
89
- let last = Date.now()
90
- export function create(tags?: Record<string, any>) {
91
- tags = tags || {}
92
-
93
- const service = tags["service"]
94
- if (service && typeof service === "string") {
95
- const cached = loggers.get(service)
96
- if (cached) return cached
97
- }
98
-
99
- function build(message: any, extra?: Record<string, any>) {
100
- const prefix = Object.entries({
101
- ...tags,
102
- ...extra,
103
- })
104
- .filter(([_, value]) => value !== undefined && value !== null)
105
- .map(([key, value]) => {
106
- const prefix = `${key}=`
107
- if (value instanceof Error) return prefix + formatError(value)
108
- if (typeof value === "object") return prefix + JSON.stringify(value)
109
- return prefix + value
110
- })
111
- .join(" ")
112
- const next = new Date()
113
- const diff = next.getTime() - last
114
- last = next.getTime()
115
- return [next.toISOString().split(".")[0], "+" + diff + "ms", prefix, message].filter(Boolean).join(" ") + "\n"
116
- }
117
- const result: Logger = {
118
- debug(message?: any, extra?: Record<string, any>) {
119
- if (shouldLog("DEBUG")) write("DEBUG " + build(message, extra))
120
- },
121
- info(message?: any, extra?: Record<string, any>) {
122
- if (shouldLog("INFO")) write("INFO " + build(message, extra))
123
- },
124
- error(message?: any, extra?: Record<string, any>) {
125
- if (shouldLog("ERROR")) write("ERROR " + build(message, extra))
126
- },
127
- warn(message?: any, extra?: Record<string, any>) {
128
- if (shouldLog("WARN")) write("WARN " + build(message, extra))
129
- },
130
- tag(key: string, value: string) {
131
- if (tags) tags[key] = value
132
- return result
133
- },
134
- clone() {
135
- return Log.create({ ...tags })
136
- },
137
- }
138
-
139
- if (service && typeof service === "string") loggers.set(service, result)
140
- return result
141
- }
142
- }
package/tsconfig.json DELETED
@@ -1,11 +0,0 @@
1
- {
2
- "$schema": "https://json.schemastore.org/tsconfig",
3
- "extends": "@tsconfig/bun/tsconfig.json",
4
- "compilerOptions": {
5
- "jsx": "preserve",
6
- "jsxImportSource": "solid-js",
7
- "paths": {
8
- "@/*": ["./src/*"]
9
- }
10
- }
11
- }