vite-plugin-better-console 1.0.2
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/README.md +238 -0
- package/dist/caller.d.ts +93 -0
- package/dist/config.d.ts +116 -0
- package/dist/format.d.ts +137 -0
- package/dist/index.d.ts +39 -0
- package/dist/logger.d.ts +156 -0
- package/dist/plugin.d.ts +6 -0
- package/dist/plugin.js +144 -0
- package/dist/runtime/index.d.ts +34 -0
- package/dist/runtime/index.js +352 -0
- package/dist/types.d.ts +125 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# vite-plugin-better-console
|
|
2
|
+
|
|
3
|
+
Zero-dependency Vite plugin that replaces the plain `console.log` workflow with a structured, colour-coded, dev-only logger. Every call is automatically **silent in production** â no dead code, no bundle bloat.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
| | |
|
|
10
|
+
|---|---|
|
|
11
|
+
| đ¨ **Colour-coded badges** | per log level (debug / info / warn / error) |
|
|
12
|
+
| đ **Auto caller info** | file, line, and function name from the stack |
|
|
13
|
+
| đĸ **Log level filtering** | suppress noisy debug logs in staging |
|
|
14
|
+
| đ§Š **Smart value formatting** | Map, Set, Date, Promise, Error, Array, Object all pretty-printed |
|
|
15
|
+
| âą **Timestamps** | locale, ISO, time-only, date-only, or relative (`+42ms`) |
|
|
16
|
+
| đ **Production safe** | entire logger tree is a no-op when `mode === 'production'` |
|
|
17
|
+
| đ **Zero imports in app code** | plugin auto-injects into your entry files |
|
|
18
|
+
| đĻž **Full TypeScript** | types for every option, augments `console.better` globally |
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install vite-plugin-better-console
|
|
26
|
+
# or
|
|
27
|
+
pnpm add vite-plugin-better-console
|
|
28
|
+
# or
|
|
29
|
+
yarn add vite-plugin-better-console
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Requires **Vite 5+**.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Quick start
|
|
37
|
+
|
|
38
|
+
### 1. Register the plugin
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
// vite.config.ts
|
|
42
|
+
import { defineConfig } from 'vite'
|
|
43
|
+
import { betterConsole } from 'vite-plugin-better-console'
|
|
44
|
+
|
|
45
|
+
export default defineConfig({
|
|
46
|
+
plugins: [betterConsole()],
|
|
47
|
+
})
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 2. Use `console.better` anywhere in your app
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
// No import needed â the plugin injects it automatically
|
|
54
|
+
console.better('user loaded', { id: 1, name: 'Ada' })
|
|
55
|
+
console.better.debug('cache key', cacheKey)
|
|
56
|
+
console.better.warn('slow response', response, { comment: 'over 2 s' })
|
|
57
|
+
console.better.error('fetch failed', error)
|
|
58
|
+
console.better.table('users', users)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Configuration
|
|
64
|
+
|
|
65
|
+
All options are optional.
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
betterConsole({
|
|
69
|
+
// ââ Plugin ââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
70
|
+
inject: true, // auto-inject into detected entries (default: true)
|
|
71
|
+
injectEntries: ['src/main.ts'], // additional explicit entries
|
|
72
|
+
injectPattern: '**/bootstrap.{ts,js}', // glob or RegExp for extra targets
|
|
73
|
+
|
|
74
|
+
// ââ Logger ââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
75
|
+
warnInProduction: true, // one-time banner if called in production
|
|
76
|
+
logLevel: 'debug', // 'debug' | 'info' | 'warn' | 'error' | 'silent'
|
|
77
|
+
collapsed: true, // collapse groups by default
|
|
78
|
+
showPath: true, // show caller file path
|
|
79
|
+
showLine: true, // show caller line number
|
|
80
|
+
showFn: true, // show caller function name
|
|
81
|
+
showType: true, // show value type
|
|
82
|
+
timestamp: true, // show timestamp
|
|
83
|
+
timestampFormat: 'locale', // 'locale'|'iso'|'time-only'|'date-only'|'relative'
|
|
84
|
+
|
|
85
|
+
// ââ Custom badge labels âââââââââââââââââââââââââââââââââââ
|
|
86
|
+
badge: {
|
|
87
|
+
debug: 'đ',
|
|
88
|
+
info: 'âšī¸',
|
|
89
|
+
warn: 'â ī¸',
|
|
90
|
+
error: 'đ´',
|
|
91
|
+
},
|
|
92
|
+
// badge: false â disable the prefix, show only the label
|
|
93
|
+
|
|
94
|
+
// ââ CSS theme âââââââââââââââââââââââââââââââââââââââââââââ
|
|
95
|
+
theme: {
|
|
96
|
+
badge_debug: 'color:#7c3aed;font-weight:700',
|
|
97
|
+
badge_info: 'color:#1d4ed8;font-weight:700',
|
|
98
|
+
badge_warn: 'color:#b45309;font-weight:700',
|
|
99
|
+
badge_error: 'color:#dc2626;font-weight:700',
|
|
100
|
+
label: 'color:#94a3b8;font-size:11px',
|
|
101
|
+
value: 'color:#0ea5e9;font-weight:600',
|
|
102
|
+
meta: 'color:#64748b;font-size:11px',
|
|
103
|
+
},
|
|
104
|
+
})
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Option reference
|
|
108
|
+
|
|
109
|
+
| Option | Type | Default | Description |
|
|
110
|
+
|--------|------|---------|-------------|
|
|
111
|
+
| `inject` | `boolean` | `true` | Auto-inject runtime into entry files |
|
|
112
|
+
| `injectEntries` | `string[]` | `[]` | Additional entry globs |
|
|
113
|
+
| `injectPattern` | `string \| RegExp` | â | Extra glob/regex targets |
|
|
114
|
+
| `warnInProduction` | `boolean` | `true` | Show one-time prod warning |
|
|
115
|
+
| `logLevel` | `LogLevel` | `'debug'` | Minimum level to print |
|
|
116
|
+
| `collapsed` | `boolean` | `true` | Collapse groups by default |
|
|
117
|
+
| `showPath` | `boolean` | `true` | Show file path |
|
|
118
|
+
| `showLine` | `boolean` | `true` | Show line number |
|
|
119
|
+
| `showFn` | `boolean` | `true` | Show function name |
|
|
120
|
+
| `showType` | `boolean` | `true` | Show value type |
|
|
121
|
+
| `timestamp` | `boolean` | `true` | Show timestamp |
|
|
122
|
+
| `timestampFormat` | `TimestampFormat` | `'locale'` | Timestamp format |
|
|
123
|
+
| `badge` | `Record<LogLevel,string> \| false` | built-in | Badge prefix per level |
|
|
124
|
+
| `theme` | `ThemeStyles` | built-in | CSS `%c` strings |
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Per-call options
|
|
129
|
+
|
|
130
|
+
Override any global option for a single call:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
console.better('api response', data, {
|
|
134
|
+
level: 'warn', // treat as warn
|
|
135
|
+
comment: 'slow path', // note appended at the end
|
|
136
|
+
collapsed: false, // expand this group
|
|
137
|
+
enabled: isDebugMode, // conditional logging
|
|
138
|
+
timestamp: false, // skip timestamp
|
|
139
|
+
// Manual caller override:
|
|
140
|
+
path: 'src/App.vue',
|
|
141
|
+
line: 42,
|
|
142
|
+
fn: 'handleSubmit',
|
|
143
|
+
})
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Timestamp formats
|
|
149
|
+
|
|
150
|
+
| Format | Example |
|
|
151
|
+
|--------|---------|
|
|
152
|
+
| `'locale'` | `06 Jun 2026 14:32:11` |
|
|
153
|
+
| `'iso'` | `2026-06-06T14:32:11.000Z` |
|
|
154
|
+
| `'time-only'` | `14:32:11` |
|
|
155
|
+
| `'date-only'` | `06 Jun 2026` |
|
|
156
|
+
| `'relative'` | `+142ms` (since page load) |
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Value smart-formatting
|
|
161
|
+
|
|
162
|
+
| Type | Output |
|
|
163
|
+
|------|--------|
|
|
164
|
+
| `Array` / `Object` | `console.table` |
|
|
165
|
+
| `Map` | `console.table` with `{ key, value }` rows |
|
|
166
|
+
| `Set` | `console.table` with `{ index, value }` rows |
|
|
167
|
+
| `Date` | ISO + local string |
|
|
168
|
+
| `Promise` | `Promise { <pending> }` + resolved/rejected |
|
|
169
|
+
| `Error` | Full stack trace |
|
|
170
|
+
| `Function` | `Æ functionName` |
|
|
171
|
+
| `null` / `undefined` | Styled italic |
|
|
172
|
+
| Primitives | `console.info` |
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Standalone usage (without Vite)
|
|
177
|
+
|
|
178
|
+
You can use the runtime directly â useful in Node scripts, tests, or frameworks
|
|
179
|
+
that don't use Vite:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
import { createLogger, attachRuntime } from 'vite-plugin-better-console/runtime'
|
|
183
|
+
|
|
184
|
+
// Standalone logger
|
|
185
|
+
const log = createLogger({ dev: true, logLevel: 'info' })
|
|
186
|
+
log('server ready', { port: 3000 })
|
|
187
|
+
log.warn('rate limit', { ip: '1.2.3.4' })
|
|
188
|
+
|
|
189
|
+
// Or attach to console globally
|
|
190
|
+
attachRuntime(createLogger({ dev: process.env.NODE_ENV !== 'production' }))
|
|
191
|
+
console.better.info('attached')
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## How it works
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
vite.config.ts
|
|
200
|
+
ââ betterConsole()
|
|
201
|
+
â
|
|
202
|
+
ââ config() â injects __BETTER_CONSOLE_DEV__ / _WARN_ / _OPTIONS__ defines
|
|
203
|
+
ââ configResolved() â collects Rollup entry paths
|
|
204
|
+
ââ transformIndexHtml() â collects <script type="module" src="âĻ"> entries
|
|
205
|
+
ââ resolveId() â handles `virtual:better-console`
|
|
206
|
+
ââ load() â virtual module: `import 'vite-plugin-better-console/runtime'`
|
|
207
|
+
ââ transform() â prepends the virtual import to each entry
|
|
208
|
+
(dev only â production builds skip this entirely)
|
|
209
|
+
|
|
210
|
+
Browser
|
|
211
|
+
ââ runtime/index.js (auto-executed)
|
|
212
|
+
ââ resolveOptions() â reads injected JSON + defaults
|
|
213
|
+
ââ createLogger() â builds the LogFn
|
|
214
|
+
ââ attachRuntime() â sets console.better = log
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## TypeScript support
|
|
220
|
+
|
|
221
|
+
`console.better` is globally typed â no import needed in your app:
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
// tsconfig.json â add to types or reference
|
|
225
|
+
/// <reference types="vite-plugin-better-console/runtime" />
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Or add to `vite-env.d.ts`:
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
/// <reference types="vite-plugin-better-console/runtime" />
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## License
|
|
237
|
+
|
|
238
|
+
MIT
|
package/dist/caller.d.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// âââ Caller Detection ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
2
|
+
//
|
|
3
|
+
// Parses the V8 / SpiderMonkey / JavaScriptCore stack trace format to find
|
|
4
|
+
// the first frame that is NOT part of this library's own internals.
|
|
5
|
+
//
|
|
6
|
+
// Supported formats:
|
|
7
|
+
// Chrome/Node: " at FnName (file:///path/to/file.js:12:5)"
|
|
8
|
+
// Chrome anon: " at file:///path/to/file.js:12:5"
|
|
9
|
+
// Firefox: "FnName@file:///path/to/file.js:12:5"
|
|
10
|
+
// Safari: "FnName@file:///path/to/file.js:12:5"
|
|
11
|
+
// âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
12
|
+
|
|
13
|
+
export interface CallerInfo {
|
|
14
|
+
path?: string
|
|
15
|
+
line?: string
|
|
16
|
+
col?: string
|
|
17
|
+
fn?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Tokens that identify internal frames we want to skip.
|
|
22
|
+
* These are matched case-insensitively against "path fn".
|
|
23
|
+
*/
|
|
24
|
+
const SKIP_TOKENS = [
|
|
25
|
+
'vite-plugin-better-console',
|
|
26
|
+
'betterConsole',
|
|
27
|
+
'getCaller',
|
|
28
|
+
'createLogger',
|
|
29
|
+
'attachRuntime',
|
|
30
|
+
'logAtLevel',
|
|
31
|
+
'printMeta',
|
|
32
|
+
'printValue',
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
// Chrome / Node: `at [async] [FnName] (path:line:col)` or `at path:line:col`
|
|
36
|
+
const RE_V8 =
|
|
37
|
+
/^\s*at\s+(?:async\s+)?(?:([\w$.<>\[\] ]+?)\s+\()?(?:file:\/\/)?(.+?):(\d+)(?::(\d+))?\)?$/
|
|
38
|
+
|
|
39
|
+
// Firefox / Safari: `[FnName@]path:line:col`
|
|
40
|
+
const RE_FF =
|
|
41
|
+
/^\s*(?:([\w$.<>\[\]]+)@)?(?:file:\/\/)?(.+?):(\d+)(?::(\d+))?$/
|
|
42
|
+
|
|
43
|
+
function parseFrame(raw: string): CallerInfo | null {
|
|
44
|
+
let m = raw.match(RE_V8)
|
|
45
|
+
if (m) {
|
|
46
|
+
const [, fn, path, line, col] = m
|
|
47
|
+
if (!path) return null
|
|
48
|
+
return { fn: fn?.trim() || undefined, path: cleanPath(path), line, col }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
m = raw.match(RE_FF)
|
|
52
|
+
if (m) {
|
|
53
|
+
const [, fn, path, line, col] = m
|
|
54
|
+
if (!path) return null
|
|
55
|
+
return { fn: fn?.trim() || undefined, path: cleanPath(path), line, col }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function cleanPath(raw: string): string {
|
|
62
|
+
return raw
|
|
63
|
+
.replace(/^file:\/\//, '')
|
|
64
|
+
.replace(/\\/g, '/')
|
|
65
|
+
.replace(/\?.*$/, '') // strip HMR query strings e.g. ?t=123456
|
|
66
|
+
.replace(/^\/\//, '/')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function isInternal(info: CallerInfo): boolean {
|
|
70
|
+
const haystack = `${info.path ?? ''} ${info.fn ?? ''}`.toLowerCase()
|
|
71
|
+
return SKIP_TOKENS.some((tok) => haystack.includes(tok.toLowerCase()))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Returns the first non-internal caller from the current stack.
|
|
76
|
+
* `extraSkip` bumps the start position when wrapped inside additional helpers.
|
|
77
|
+
*/
|
|
78
|
+
export function getCaller(extraSkip = 0): CallerInfo {
|
|
79
|
+
const stack = new Error().stack
|
|
80
|
+
if (!stack) return {}
|
|
81
|
+
|
|
82
|
+
// Slice off "Error\n" + the frames that belong to getCaller itself
|
|
83
|
+
const lines = stack.split('\n').slice(1 + extraSkip)
|
|
84
|
+
|
|
85
|
+
for (const line of lines) {
|
|
86
|
+
const frame = parseFrame(line)
|
|
87
|
+
if (!frame) continue
|
|
88
|
+
if (isInternal(frame)) continue
|
|
89
|
+
return frame
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {}
|
|
93
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { LogLevel, LoggerOptions, ThemeStyles, TimestampFormat } from './types.js'
|
|
2
|
+
import { LOG_LEVEL_RANK } from './types.js'
|
|
3
|
+
|
|
4
|
+
// âââ Global define injected by the Vite plugin âââââââââââââââââââââââââââââââ
|
|
5
|
+
declare const __BETTER_CONSOLE_DEV__: boolean | undefined
|
|
6
|
+
declare const __BETTER_CONSOLE_WARN__: boolean | undefined
|
|
7
|
+
declare const __BETTER_CONSOLE_OPTIONS__: string | undefined
|
|
8
|
+
|
|
9
|
+
// âââ Default theme âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
10
|
+
|
|
11
|
+
export const DEFAULT_THEME: Required<ThemeStyles> = {
|
|
12
|
+
badge_debug:
|
|
13
|
+
'display:inline-block;padding:2px 8px;border-radius:4px;font-size:11px;font-weight:700;letter-spacing:.5px;background:#f3f0ff;color:#6d28d9;border:1px solid #c4b5fd',
|
|
14
|
+
badge_info:
|
|
15
|
+
'display:inline-block;padding:2px 8px;border-radius:4px;font-size:11px;font-weight:700;letter-spacing:.5px;background:#eff6ff;color:#1d4ed8;border:1px solid #93c5fd',
|
|
16
|
+
badge_warn:
|
|
17
|
+
'display:inline-block;padding:2px 8px;border-radius:4px;font-size:11px;font-weight:700;letter-spacing:.5px;background:#fffbeb;color:#b45309;border:1px solid #fcd34d',
|
|
18
|
+
badge_error:
|
|
19
|
+
'display:inline-block;padding:2px 8px;border-radius:4px;font-size:11px;font-weight:700;letter-spacing:.5px;background:#fef2f2;color:#dc2626;border:1px solid #fca5a5',
|
|
20
|
+
label: 'color:#94a3b8;font-size:11px;min-width:68px;display:inline-block',
|
|
21
|
+
value: 'color:#0ea5e9;font-weight:600',
|
|
22
|
+
meta: 'color:#64748b;font-size:11px',
|
|
23
|
+
divider: 'color:#cbd5e1',
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// âââ Default logger options âââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
27
|
+
|
|
28
|
+
export const DEFAULT_OPTIONS: Required<Omit<LoggerOptions, 'dev' | 'warnInProduction' | 'theme' | 'badge'>> = {
|
|
29
|
+
logLevel: 'debug',
|
|
30
|
+
collapsed: true,
|
|
31
|
+
showPath: true,
|
|
32
|
+
showLine: true,
|
|
33
|
+
showFn: true,
|
|
34
|
+
showType: true,
|
|
35
|
+
timestamp: true,
|
|
36
|
+
timestampFormat: 'locale',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// âââ Dev / warn detection âââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
40
|
+
|
|
41
|
+
export function detectDev(): boolean {
|
|
42
|
+
// 1. Plugin compile-time define (most reliable)
|
|
43
|
+
if (typeof __BETTER_CONSOLE_DEV__ !== 'undefined') {
|
|
44
|
+
return __BETTER_CONSOLE_DEV__
|
|
45
|
+
}
|
|
46
|
+
// 2. Vite HMR env
|
|
47
|
+
try {
|
|
48
|
+
if (import.meta.env?.PROD) return false
|
|
49
|
+
if (import.meta.env?.DEV) return true
|
|
50
|
+
} catch { /* not in Vite context */ }
|
|
51
|
+
// 3. Node env
|
|
52
|
+
if (typeof process !== 'undefined') {
|
|
53
|
+
return process.env?.NODE_ENV !== 'production'
|
|
54
|
+
}
|
|
55
|
+
return true
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function detectWarnInProduction(): boolean {
|
|
59
|
+
if (typeof __BETTER_CONSOLE_WARN__ !== 'undefined') {
|
|
60
|
+
return __BETTER_CONSOLE_WARN__
|
|
61
|
+
}
|
|
62
|
+
return true
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// âââ Injected options (from vite.config) âââââââââââââââââââââââââââââââââââââ
|
|
66
|
+
|
|
67
|
+
function parseInjectedOptions(): Partial<LoggerOptions> {
|
|
68
|
+
if (typeof __BETTER_CONSOLE_OPTIONS__ === 'undefined') return {}
|
|
69
|
+
try {
|
|
70
|
+
const raw = __BETTER_CONSOLE_OPTIONS__
|
|
71
|
+
return (typeof raw === 'string' ? JSON.parse(raw) : raw) ?? {}
|
|
72
|
+
} catch {
|
|
73
|
+
return {}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// âââ Resolve âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
78
|
+
|
|
79
|
+
export type ResolvedLoggerOptions = {
|
|
80
|
+
dev: boolean
|
|
81
|
+
warnInProduction: boolean
|
|
82
|
+
logLevel: LogLevel
|
|
83
|
+
collapsed: boolean
|
|
84
|
+
showPath: boolean
|
|
85
|
+
showLine: boolean
|
|
86
|
+
showFn: boolean
|
|
87
|
+
showType: boolean
|
|
88
|
+
timestamp: boolean
|
|
89
|
+
timestampFormat: TimestampFormat
|
|
90
|
+
theme: Required<ThemeStyles>
|
|
91
|
+
badge: Partial<Record<LogLevel, string>> | false
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function resolveOptions(overrides: LoggerOptions = {}): ResolvedLoggerOptions {
|
|
95
|
+
const injected = parseInjectedOptions()
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
...DEFAULT_OPTIONS,
|
|
99
|
+
...injected,
|
|
100
|
+
...overrides,
|
|
101
|
+
dev: overrides.dev ?? detectDev(),
|
|
102
|
+
warnInProduction: overrides.warnInProduction ?? detectWarnInProduction(),
|
|
103
|
+
theme: {
|
|
104
|
+
...DEFAULT_THEME,
|
|
105
|
+
...injected.theme,
|
|
106
|
+
...overrides.theme,
|
|
107
|
+
},
|
|
108
|
+
badge: overrides.badge ?? injected.badge ?? {},
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// âââ Level check âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
113
|
+
|
|
114
|
+
export function shouldLog(level: LogLevel, minLevel: LogLevel = 'debug'): boolean {
|
|
115
|
+
return (LOG_LEVEL_RANK[level] ?? 0) >= (LOG_LEVEL_RANK[minLevel] ?? 0)
|
|
116
|
+
}
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { TimestampFormat } from './types.js'
|
|
2
|
+
import type { ResolvedLoggerOptions } from './config.js'
|
|
3
|
+
|
|
4
|
+
// âââ Type detection âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
5
|
+
|
|
6
|
+
export function typeOf(value: unknown): string {
|
|
7
|
+
return Object.prototype.toString.call(value).slice(8, -1)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// âââ Timestamp ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
11
|
+
|
|
12
|
+
let _start = Date.now()
|
|
13
|
+
|
|
14
|
+
export function formatTimestamp(date: Date, format: TimestampFormat): string {
|
|
15
|
+
if (typeof format === 'function') return format(date)
|
|
16
|
+
|
|
17
|
+
switch (format) {
|
|
18
|
+
case 'iso':
|
|
19
|
+
return date.toISOString()
|
|
20
|
+
|
|
21
|
+
case 'time-only':
|
|
22
|
+
return date.toLocaleTimeString('en-GB', {
|
|
23
|
+
hour: '2-digit',
|
|
24
|
+
minute: '2-digit',
|
|
25
|
+
second: '2-digit',
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
case 'date-only':
|
|
29
|
+
return date.toLocaleDateString('en-GB', {
|
|
30
|
+
year: 'numeric',
|
|
31
|
+
month: 'short',
|
|
32
|
+
day: '2-digit',
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
case 'relative': {
|
|
36
|
+
const ms = date.getTime() - _start
|
|
37
|
+
if (ms < 1000) return `+${ms}ms`
|
|
38
|
+
if (ms < 60_000) return `+${(ms / 1000).toFixed(2)}s`
|
|
39
|
+
return `+${(ms / 60_000).toFixed(1)}min`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
case 'locale':
|
|
43
|
+
default: {
|
|
44
|
+
const d = date.toLocaleDateString('en-GB', {
|
|
45
|
+
year: 'numeric',
|
|
46
|
+
month: 'short',
|
|
47
|
+
day: '2-digit',
|
|
48
|
+
})
|
|
49
|
+
const t = date.toLocaleTimeString('en-GB', {
|
|
50
|
+
hour: '2-digit',
|
|
51
|
+
minute: '2-digit',
|
|
52
|
+
second: '2-digit',
|
|
53
|
+
})
|
|
54
|
+
return `${d} ${t}`
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// âââ Meta row printer ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
60
|
+
|
|
61
|
+
export function printMeta(
|
|
62
|
+
label: string,
|
|
63
|
+
value: unknown,
|
|
64
|
+
opts: ResolvedLoggerOptions,
|
|
65
|
+
): void {
|
|
66
|
+
if (value == null || value === '') return
|
|
67
|
+
|
|
68
|
+
console.info(
|
|
69
|
+
`%c${label.padEnd(10)}%c${value}`,
|
|
70
|
+
opts.theme.label,
|
|
71
|
+
opts.theme.value,
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// âââ Value pretty-printer ââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
76
|
+
|
|
77
|
+
export function printValue(value: unknown): void {
|
|
78
|
+
const kind = typeOf(value)
|
|
79
|
+
|
|
80
|
+
switch (kind) {
|
|
81
|
+
case 'Map': {
|
|
82
|
+
const rows = Array.from(value as Map<unknown, unknown>, ([k, v]) => ({ key: k, value: v }))
|
|
83
|
+
console.table(rows)
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
case 'Set': {
|
|
88
|
+
const rows = Array.from(value as Set<unknown>, (v, i) => ({ index: i, value: v }))
|
|
89
|
+
console.table(rows)
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
case 'Array':
|
|
94
|
+
case 'Object':
|
|
95
|
+
console.table(value)
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
case 'Date': {
|
|
99
|
+
const d = value as Date
|
|
100
|
+
console.info(`${d.toISOString()} (local: ${d.toLocaleString()})`)
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
case 'Promise':
|
|
105
|
+
console.info('%cPromise { <pending> }', 'color:#94a3b8;font-style:italic')
|
|
106
|
+
;(value as Promise<unknown>).then(
|
|
107
|
+
(r) => console.info(' âŗ resolved â', r),
|
|
108
|
+
(e) => console.info(' âŗ rejected â', e),
|
|
109
|
+
)
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
case 'Error': {
|
|
113
|
+
const err = value as Error
|
|
114
|
+
console.info(err.stack ?? err.message ?? String(err))
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
case 'Function':
|
|
119
|
+
console.info(`Æ ${(value as Function).name || '(anonymous)'}`)
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
case 'RegExp':
|
|
123
|
+
console.info(String(value))
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
case 'Null':
|
|
127
|
+
console.info('%cnull', 'color:#94a3b8;font-style:italic')
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
case 'Undefined':
|
|
131
|
+
console.info('%cundefined', 'color:#94a3b8;font-style:italic')
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
default:
|
|
135
|
+
if (value !== undefined) console.info(value)
|
|
136
|
+
}
|
|
137
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Runtime entry point â imported by the virtual module injected into app entries.
|
|
2
|
+
// This file intentionally runs side-effects so that console.log is patched
|
|
3
|
+
// before any app code executes.
|
|
4
|
+
|
|
5
|
+
export type { LogLevel, LoggerOptions, LogCallOptions, LogFn, ThemeStyles, BetterConsoleOptions } from './types.js'
|
|
6
|
+
export { LOG_LEVEL_RANK } from './types.js'
|
|
7
|
+
export { DEFAULT_THEME, DEFAULT_OPTIONS, resolveOptions, shouldLog, detectDev, detectWarnInProduction } from './config.js'
|
|
8
|
+
export { getCaller } from './caller.js'
|
|
9
|
+
export { typeOf, formatTimestamp, printMeta, printValue } from './format.js'
|
|
10
|
+
export { createLogger } from './logger.js'
|
|
11
|
+
|
|
12
|
+
import { createLogger } from './logger.js'
|
|
13
|
+
import { resolveOptions } from './config.js'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Attach a logger instance to `console.log` (aliased as `console.better`).
|
|
17
|
+
* Calling this more than once is safe â subsequent calls overwrite the reference.
|
|
18
|
+
*/
|
|
19
|
+
export function attachRuntime(logger?: ReturnType<typeof createLogger>) {
|
|
20
|
+
const instance = logger ?? createLogger(resolveOptions())
|
|
21
|
+
|
|
22
|
+
if (typeof console !== 'undefined') {
|
|
23
|
+
// Primary alias â idiomatic usage
|
|
24
|
+
;(console as any).log = (console as any).log // keep original intact
|
|
25
|
+
;(console as any).better = instance
|
|
26
|
+
|
|
27
|
+
// Legacy alias kept for users who prefer the old name
|
|
28
|
+
;(console as any).logger = instance
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return instance
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ââ Auto-attach on import âââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
35
|
+
// When the Vite plugin injects `import 'vite-plugin-better-console/runtime'`
|
|
36
|
+
// into an entry file this runs exactly once before app code.
|
|
37
|
+
|
|
38
|
+
const _logger = attachRuntime()
|
|
39
|
+
export default _logger
|