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 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
@@ -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
+ }
@@ -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
+ }
@@ -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
+ }
@@ -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