@promise-inc/devlog 0.1.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/README.md ADDED
@@ -0,0 +1,55 @@
1
+ <img src="./assets/logo.svg" alt="devlog" height="36" />
2
+
3
+ Simple logger with automatic context (file + line). Zero dependencies.
4
+
5
+ <p align="center">
6
+ <img src="./assets/output.svg" alt="devlog output" width="680" />
7
+ </p>
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install @promise-inc/devlog
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```ts
18
+ import { log } from "@promise-inc/devlog";
19
+
20
+ log.info("User created", { userId: 42 });
21
+ log.warn("Deprecated method");
22
+ log.error("Failed to save", err);
23
+ log.success("Migration complete");
24
+ log.debug("Cache hit", { key: "users:1" });
25
+ ```
26
+
27
+ ## Custom instance
28
+
29
+ ```ts
30
+ import { createLogger } from "@promise-inc/devlog";
31
+
32
+ const logger = createLogger({
33
+ enabled: true,
34
+ debugEnabled: false,
35
+ });
36
+
37
+ logger.info("Custom logger");
38
+ ```
39
+
40
+ ## Environment variables
41
+
42
+ | Variable | Effect |
43
+ |----------|--------|
44
+ | `DEVLOG_ENABLED=false` | Disables all logs |
45
+ | `DEVLOG_ENABLED=true` | Enables all logs including debug |
46
+ | `NODE_ENV=production` | Disables debug level (unless `DEVLOG_ENABLED=true`) |
47
+
48
+ ## Output
49
+
50
+ - **Node.js** — Colored output with ANSI escape codes
51
+ - **Browser** — `console.groupCollapsed` with `%c` styling. Data nested inside collapsible groups
52
+
53
+ ---
54
+
55
+ Developed by [Promise Inc.](https://promise.codes)
@@ -0,0 +1,2 @@
1
+ import type { CallerInfo } from "./types.js";
2
+ export declare function getCallerInfo(): CallerInfo | null;
@@ -0,0 +1,43 @@
1
+ const NODE_STACK_REGEX = /at\s+.*\((.+):(\d+):(\d+)\)/;
2
+ const NODE_STACK_REGEX_NO_PARENS = /at\s+(.+):(\d+):(\d+)/;
3
+ const BROWSER_STACK_REGEX = /(?:@)(.+):(\d+):(\d+)/;
4
+ function parseLine(line) {
5
+ const nodeMatch = line.match(NODE_STACK_REGEX);
6
+ if (nodeMatch) {
7
+ return {
8
+ file: nodeMatch[1],
9
+ line: Number(nodeMatch[2]),
10
+ column: Number(nodeMatch[3]),
11
+ };
12
+ }
13
+ const nodeMatchNoParens = line.match(NODE_STACK_REGEX_NO_PARENS);
14
+ if (nodeMatchNoParens) {
15
+ return {
16
+ file: nodeMatchNoParens[1],
17
+ line: Number(nodeMatchNoParens[2]),
18
+ column: Number(nodeMatchNoParens[3]),
19
+ };
20
+ }
21
+ const browserMatch = line.match(BROWSER_STACK_REGEX);
22
+ if (browserMatch) {
23
+ return {
24
+ file: browserMatch[1],
25
+ line: Number(browserMatch[2]),
26
+ column: Number(browserMatch[3]),
27
+ };
28
+ }
29
+ return null;
30
+ }
31
+ export function getCallerInfo() {
32
+ const stack = new Error().stack;
33
+ if (!stack)
34
+ return null;
35
+ const lines = stack.split("\n");
36
+ // Skip: Error, getCallerInfo, logMethod, log.level → caller is at index 4+
37
+ for (let i = 4; i < lines.length; i++) {
38
+ const info = parseLine(lines[i]);
39
+ if (info)
40
+ return info;
41
+ }
42
+ return null;
43
+ }
package/dist/env.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export declare function isBrowser(): boolean;
2
+ export declare function isEnabled(): boolean;
3
+ export declare function isDebugEnabled(): boolean;
package/dist/env.js ADDED
@@ -0,0 +1,31 @@
1
+ export function isBrowser() {
2
+ return typeof window !== "undefined";
3
+ }
4
+ function getEnv(key) {
5
+ if (isBrowser()) {
6
+ return undefined;
7
+ }
8
+ try {
9
+ return process.env[key];
10
+ }
11
+ catch {
12
+ return undefined;
13
+ }
14
+ }
15
+ export function isEnabled() {
16
+ const value = getEnv("DEVLOG_ENABLED");
17
+ if (value === "false")
18
+ return false;
19
+ if (value === "true")
20
+ return true;
21
+ return true;
22
+ }
23
+ export function isDebugEnabled() {
24
+ const value = getEnv("DEVLOG_ENABLED");
25
+ if (value === "true")
26
+ return true;
27
+ if (value === "false")
28
+ return false;
29
+ const nodeEnv = getEnv("NODE_ENV");
30
+ return nodeEnv !== "production";
31
+ }
@@ -0,0 +1,2 @@
1
+ import type { LogEntry } from "./types.js";
2
+ export declare function formatEntry(entry: LogEntry): void;
@@ -0,0 +1,96 @@
1
+ import { isBrowser } from "./env.js";
2
+ const ANSI = {
3
+ reset: "\x1b[0m",
4
+ dim: "\x1b[2m",
5
+ bold: "\x1b[1m",
6
+ red: "\x1b[31m",
7
+ yellow: "\x1b[33m",
8
+ blue: "\x1b[34m",
9
+ green: "\x1b[32m",
10
+ cyan: "\x1b[36m",
11
+ gray: "\x1b[90m",
12
+ };
13
+ const LEVEL_COLORS = {
14
+ debug: ANSI.gray,
15
+ info: ANSI.blue,
16
+ success: ANSI.green,
17
+ warn: ANSI.yellow,
18
+ error: ANSI.red,
19
+ };
20
+ const LEVEL_LABELS = {
21
+ debug: "DEBUG",
22
+ info: "INFO",
23
+ success: "OK",
24
+ warn: "WARN",
25
+ error: "ERROR",
26
+ };
27
+ const BROWSER_LEVEL_STYLES = {
28
+ debug: "color: #6b7280; font-weight: normal;",
29
+ info: "color: #3b82f6; font-weight: bold;",
30
+ success: "color: #22c55e; font-weight: bold;",
31
+ warn: "color: #eab308; font-weight: bold;",
32
+ error: "color: #ef4444; font-weight: bold;",
33
+ };
34
+ const BROWSER_CONSOLE_METHOD = {
35
+ debug: "debug",
36
+ info: "log",
37
+ success: "log",
38
+ warn: "warn",
39
+ error: "error",
40
+ };
41
+ function formatTimestamp(date) {
42
+ return date.toLocaleTimeString("en-US", { hour12: false });
43
+ }
44
+ function shortenPath(filePath) {
45
+ const parts = filePath.split("/");
46
+ if (parts.length <= 3)
47
+ return filePath;
48
+ return parts.slice(-3).join("/");
49
+ }
50
+ function formatNode(entry) {
51
+ const color = LEVEL_COLORS[entry.level];
52
+ const label = LEVEL_LABELS[entry.level];
53
+ const time = formatTimestamp(entry.timestamp);
54
+ const callerStr = entry.caller
55
+ ? `${ANSI.dim}${shortenPath(entry.caller.file)}:${entry.caller.line}${ANSI.reset}`
56
+ : "";
57
+ const prefix = `${ANSI.dim}${time}${ANSI.reset} ${color}${ANSI.bold}[${label}]${ANSI.reset}`;
58
+ const line = callerStr
59
+ ? `${prefix} ${entry.message} ${callerStr}`
60
+ : `${prefix} ${entry.message}`;
61
+ const method = entry.level === "error" ? "error" : entry.level === "warn" ? "warn" : "log";
62
+ if (entry.data !== undefined) {
63
+ console[method](line);
64
+ console[method](entry.data);
65
+ }
66
+ else {
67
+ console[method](line);
68
+ }
69
+ }
70
+ function formatBrowser(entry) {
71
+ const label = LEVEL_LABELS[entry.level];
72
+ const style = BROWSER_LEVEL_STYLES[entry.level];
73
+ const time = formatTimestamp(entry.timestamp);
74
+ const method = BROWSER_CONSOLE_METHOD[entry.level];
75
+ const callerStr = entry.caller
76
+ ? `${shortenPath(entry.caller.file)}:${entry.caller.line}`
77
+ : "";
78
+ const hasData = entry.data !== undefined;
79
+ const groupLabel = `%c[${label}]%c ${time} ${entry.message}${callerStr ? ` (${callerStr})` : ""}`;
80
+ if (hasData) {
81
+ console.groupCollapsed(groupLabel, style, "color: inherit;");
82
+ console[method](entry.data);
83
+ console.groupEnd();
84
+ }
85
+ else {
86
+ console[method](groupLabel, style, "color: inherit;");
87
+ }
88
+ }
89
+ export function formatEntry(entry) {
90
+ if (isBrowser()) {
91
+ formatBrowser(entry);
92
+ }
93
+ else {
94
+ formatNode(entry);
95
+ }
96
+ }
@@ -0,0 +1,3 @@
1
+ export { createLogger } from "./logger.js";
2
+ export { log } from "./log.js";
3
+ export type { Logger, LoggerConfig, LogLevel, LogEntry, CallerInfo } from "./types.js";
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { createLogger } from "./logger.js";
2
+ export { log } from "./log.js";
package/dist/log.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare const log: import("./types.js").Logger;
package/dist/log.js ADDED
@@ -0,0 +1,2 @@
1
+ import { createLogger } from "./logger.js";
2
+ export const log = createLogger();
@@ -0,0 +1,2 @@
1
+ import type { Logger, LoggerConfig } from "./types.js";
2
+ export declare function createLogger(config?: LoggerConfig): Logger;
package/dist/logger.js ADDED
@@ -0,0 +1,35 @@
1
+ import { isEnabled, isDebugEnabled } from "./env.js";
2
+ import { getCallerInfo } from "./context.js";
3
+ import { formatEntry } from "./formatter.js";
4
+ function shouldLog(level, config) {
5
+ const enabled = config?.enabled ?? isEnabled();
6
+ if (!enabled)
7
+ return false;
8
+ if (level === "debug") {
9
+ return config?.debugEnabled ?? isDebugEnabled();
10
+ }
11
+ return true;
12
+ }
13
+ function logMethod(level, config) {
14
+ return (message, data) => {
15
+ if (!shouldLog(level, config))
16
+ return;
17
+ const caller = getCallerInfo();
18
+ formatEntry({
19
+ level,
20
+ message,
21
+ data,
22
+ timestamp: new Date(),
23
+ caller,
24
+ });
25
+ };
26
+ }
27
+ export function createLogger(config) {
28
+ return {
29
+ info: logMethod("info", config),
30
+ warn: logMethod("warn", config),
31
+ error: logMethod("error", config),
32
+ success: logMethod("success", config),
33
+ debug: logMethod("debug", config),
34
+ };
35
+ }
@@ -0,0 +1,31 @@
1
+ export declare const LOG_LEVELS: {
2
+ readonly debug: 0;
3
+ readonly info: 1;
4
+ readonly success: 2;
5
+ readonly warn: 3;
6
+ readonly error: 4;
7
+ };
8
+ export type LogLevel = keyof typeof LOG_LEVELS;
9
+ export interface CallerInfo {
10
+ file: string;
11
+ line: number;
12
+ column: number;
13
+ }
14
+ export interface LogEntry {
15
+ level: LogLevel;
16
+ message: string;
17
+ data?: unknown;
18
+ timestamp: Date;
19
+ caller: CallerInfo | null;
20
+ }
21
+ export interface LoggerConfig {
22
+ enabled?: boolean;
23
+ debugEnabled?: boolean;
24
+ }
25
+ export interface Logger {
26
+ info: (message: string, data?: unknown) => void;
27
+ warn: (message: string, data?: unknown) => void;
28
+ error: (message: string, data?: unknown) => void;
29
+ success: (message: string, data?: unknown) => void;
30
+ debug: (message: string, data?: unknown) => void;
31
+ }
package/dist/types.js ADDED
@@ -0,0 +1,7 @@
1
+ export const LOG_LEVELS = {
2
+ debug: 0,
3
+ info: 1,
4
+ success: 2,
5
+ warn: 3,
6
+ error: 4,
7
+ };
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@promise-inc/devlog",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Simple logger with automatic context (file + line)",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "clean": "rm -rf dist"
17
+ },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "license": "MIT",
22
+ "devDependencies": {
23
+ "@types/node": "^25.1.0",
24
+ "typescript": "^5.9.3"
25
+ }
26
+ }