@variablelab/loggy 0.0.0-stage → 0.2.1

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 CHANGED
@@ -1,3 +1,9 @@
1
- # Temporary Holding Version
1
+ # Loggy
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Console wrapper to make logging fun again
4
+
5
+ ## Installation
6
+
7
+ ```sh
8
+ pnpm add @variablelab/loggy
9
+ ```
@@ -0,0 +1,54 @@
1
+ import { FormatOptions } from "consola";
2
+ import createDebug from "debug";
3
+
4
+ //#region src/types.d.ts
5
+ type AnyLogFn = (...args: unknown[]) => void;
6
+ type LogFn = (opts: LogFnOptions | string, ...args: unknown[]) => void;
7
+ type Formatters = Record<string, (arg: unknown) => string>;
8
+ type AnyLogger = {
9
+ namespace: string;
10
+ debug: AnyLogFn;
11
+ error: AnyLogFn;
12
+ info: LogFn;
13
+ trace: LogFn;
14
+ warn: LogFn;
15
+ child: (options: CreateOptions) => AnyLogger;
16
+ subdebug: (namespace: string) => AnyLogFn;
17
+ start: LogFn;
18
+ success: LogFn;
19
+ };
20
+ type CreateOptions = {
21
+ namespace: string;
22
+ tag?: string;
23
+ formatOptions?: FormatOptions;
24
+ formatters?: Formatters;
25
+ };
26
+ type LoggerOptions = {
27
+ tag?: string;
28
+ namespace: string;
29
+ formatOptions: FormatOptions;
30
+ formatters: Formatters;
31
+ };
32
+ type LogFnOptions = {
33
+ tag: string;
34
+ message: string;
35
+ };
36
+ //#endregion
37
+ //#region src/loggy.d.ts
38
+ declare class Loggy implements AnyLogger {
39
+ #private;
40
+ constructor(options: LoggerOptions);
41
+ get namespace(): string;
42
+ debug(...args: unknown[]): void;
43
+ error(...args: unknown[]): void;
44
+ info(opts: LogFnOptions | string, ...args: unknown[]): void;
45
+ trace(opts: LogFnOptions | string, ...args: unknown[]): void;
46
+ warn(opts: LogFnOptions | string, ...args: unknown[]): void;
47
+ child(options: CreateOptions): Loggy;
48
+ start(opts: LogFnOptions | string, ...args: unknown[]): void;
49
+ success(opts: LogFnOptions | string, ...args: unknown[]): void;
50
+ subdebug(namespace: string): createDebug.Debugger;
51
+ }
52
+ declare function createLoggy(options: CreateOptions): Loggy;
53
+ //#endregion
54
+ export { AnyLogFn, AnyLogger, CreateOptions, type FormatOptions, Formatters, LogFn, LogFnOptions, LoggerOptions, Loggy, createLoggy };
package/dist/index.mjs ADDED
@@ -0,0 +1,111 @@
1
+ import util from "node:util";
2
+ import { createConsola } from "consola";
3
+ import { colors } from "consola/utils";
4
+ import createDebug from "debug";
5
+ //#region src/const.ts
6
+ const DEFAULT_FORMAT_OPTIONS = {
7
+ colors: true,
8
+ depth: 3,
9
+ maxArrayLength: 50,
10
+ breakLength: 80,
11
+ date: false
12
+ };
13
+ const DEFAULT_FORMATTERS = {
14
+ o: (arg) => {
15
+ return util.inspect(arg, DEFAULT_FORMAT_OPTIONS).split("\n").map((str) => str.trim()).join(" ");
16
+ },
17
+ O: (arg) => {
18
+ return util.inspect(arg, DEFAULT_FORMAT_OPTIONS);
19
+ }
20
+ };
21
+ //#endregion
22
+ //#region src/loggy.ts
23
+ function isLogFnOptions(arg) {
24
+ try {
25
+ return typeof arg.message === "string" && typeof arg.tag === "string";
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
30
+ var Loggy = class Loggy {
31
+ #options;
32
+ #debug;
33
+ #consola;
34
+ constructor(options) {
35
+ this.#options = options;
36
+ this.#debug = createDebug(`${options.namespace}:root`);
37
+ this.#consola = createConsola({ formatOptions: options.formatOptions });
38
+ }
39
+ get namespace() {
40
+ return this.#options.namespace;
41
+ }
42
+ debug(...args) {
43
+ this.#debug(...args);
44
+ }
45
+ error(...args) {
46
+ this.#consola.error(this.#format(...args));
47
+ }
48
+ info(opts, ...args) {
49
+ this.#consola.info(this.#format(opts, ...args));
50
+ }
51
+ trace(opts, ...args) {
52
+ this.#consola.trace(this.#format(opts, ...args));
53
+ }
54
+ warn(opts, ...args) {
55
+ this.#consola.warn(this.#format(opts, ...args));
56
+ }
57
+ child(options) {
58
+ return new Loggy({
59
+ ...this.#options,
60
+ ...options,
61
+ namespace: `${this.#options.namespace}:${options.namespace}`
62
+ });
63
+ }
64
+ start(opts, ...args) {
65
+ this.#consola.start(this.#format(opts, ...args));
66
+ }
67
+ success(opts, ...args) {
68
+ this.#consola.success(this.#format(opts, ...args));
69
+ }
70
+ subdebug(namespace) {
71
+ return createDebug(`${this.#options.namespace}:${namespace}`);
72
+ }
73
+ #format(...args) {
74
+ const [firstArg, ...restArgs] = args;
75
+ const tag = isLogFnOptions(firstArg) ? firstArg.tag : this.#options.tag;
76
+ const formattedArgs = isLogFnOptions(firstArg) ? [firstArg.message, ...restArgs] : args;
77
+ if (typeof formattedArgs[0] !== "string") formattedArgs.unshift("%O");
78
+ const [message, ...replacements] = formattedArgs;
79
+ let replacementIndex = -1;
80
+ if (typeof message !== "string") throw new TypeError("message must be a string");
81
+ let formattedMessage = message.replace(/%([a-zA-Z%])/g, (match, formatKey) => {
82
+ if (formatKey === "%") return "%";
83
+ replacementIndex++;
84
+ const formatter = this.#options.formatters[formatKey];
85
+ if (typeof formatter !== "function") return match;
86
+ const replacement = replacements[replacementIndex];
87
+ const formattedMatch = formatter(replacement);
88
+ replacements.splice(replacementIndex, 1);
89
+ replacementIndex--;
90
+ return formattedMatch;
91
+ }).split("\n").join(`\n${" ".repeat(2)}`);
92
+ formattedMessage = !tag ? formattedMessage : `${colors.cyan(`[${tag}]`)} ${formattedMessage}`;
93
+ return util.formatWithOptions(this.#options.formatOptions, formattedMessage, ...replacements);
94
+ }
95
+ };
96
+ function createLoggy(options) {
97
+ return new Loggy({
98
+ namespace: options.namespace,
99
+ tag: options.tag,
100
+ formatOptions: {
101
+ ...DEFAULT_FORMAT_OPTIONS,
102
+ ...options.formatOptions
103
+ },
104
+ formatters: {
105
+ ...DEFAULT_FORMATTERS,
106
+ ...options.formatters
107
+ }
108
+ });
109
+ }
110
+ //#endregion
111
+ export { Loggy, createLoggy };
package/package.json CHANGED
@@ -1,6 +1,47 @@
1
1
  {
2
2
  "name": "@variablelab/loggy",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.1",
4
+ "description": "Console wrapper to make logging fun again",
5
+ "homepage": "https://github.com/withvariablelab/dx/tree/main/shared/loggy#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/withvariablelab/dx/issues"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/withvariablelab/dx.git",
12
+ "directory": "shared/loggy"
13
+ },
14
+ "license": "MIT",
15
+ "author": "rcrd <rcrd@variable.land>",
16
+ "type": "module",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.mts",
20
+ "default": "./dist/index.mjs"
21
+ }
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "src",
26
+ "!src/**/__tests__",
27
+ "!src/**/*.test.*"
28
+ ],
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "engines": {
33
+ "node": ">=20.0.0"
34
+ },
35
+ "dependencies": {
36
+ "consola": "3.4.2",
37
+ "debug": "4.4.3"
38
+ },
39
+ "devDependencies": {
40
+ "@types/debug": "4.1.12",
41
+ "@rrlab/tsdown-config": "^0.1.0"
42
+ },
43
+ "scripts": {
44
+ "build": "tsdown",
45
+ "tscheck": "rr tsc"
46
+ }
6
47
  }
package/src/const.ts ADDED
@@ -0,0 +1,23 @@
1
+ import util from "node:util";
2
+ import type { FormatOptions, Formatters } from "./types.ts";
3
+
4
+ export const DEFAULT_FORMAT_OPTIONS: FormatOptions = {
5
+ colors: true,
6
+ depth: 3,
7
+ maxArrayLength: 50,
8
+ breakLength: 80,
9
+ date: false,
10
+ };
11
+
12
+ export const DEFAULT_FORMATTERS: Formatters = {
13
+ o: (arg: unknown) => {
14
+ return util
15
+ .inspect(arg, DEFAULT_FORMAT_OPTIONS)
16
+ .split("\n")
17
+ .map((str) => str.trim())
18
+ .join(" ");
19
+ },
20
+ O: (arg: unknown) => {
21
+ return util.inspect(arg, DEFAULT_FORMAT_OPTIONS);
22
+ },
23
+ };
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./loggy.ts";
2
+ export * from "./types.ts";
package/src/loggy.ts ADDED
@@ -0,0 +1,139 @@
1
+ import util from "node:util";
2
+ import { type ConsolaInstance, createConsola } from "consola";
3
+ import { colors } from "consola/utils";
4
+ import createDebug from "debug";
5
+ import { DEFAULT_FORMAT_OPTIONS, DEFAULT_FORMATTERS } from "./const.ts";
6
+ import type { AnyLogger, CreateOptions, LogFnOptions, LoggerOptions } from "./types.ts";
7
+
8
+ function isLogFnOptions(arg: unknown): arg is LogFnOptions {
9
+ try {
10
+ // @ts-expect-error
11
+ return typeof arg.message === "string" && typeof arg.tag === "string";
12
+ } catch {
13
+ return false;
14
+ }
15
+ }
16
+
17
+ export class Loggy implements AnyLogger {
18
+ #options: LoggerOptions;
19
+ #debug: ReturnType<typeof createDebug>;
20
+ #consola: ConsolaInstance;
21
+
22
+ constructor(options: LoggerOptions) {
23
+ this.#options = options;
24
+ this.#debug = createDebug(`${options.namespace}:root`);
25
+ this.#consola = createConsola({
26
+ formatOptions: options.formatOptions,
27
+ });
28
+ }
29
+
30
+ get namespace() {
31
+ return this.#options.namespace;
32
+ }
33
+
34
+ debug(...args: unknown[]) {
35
+ // @ts-expect-error - it really accepts this signature
36
+ this.#debug(...args);
37
+ }
38
+
39
+ error(...args: unknown[]) {
40
+ this.#consola.error(this.#format(...args));
41
+ }
42
+
43
+ info(opts: LogFnOptions | string, ...args: unknown[]) {
44
+ this.#consola.info(this.#format(opts, ...args));
45
+ }
46
+
47
+ trace(opts: LogFnOptions | string, ...args: unknown[]) {
48
+ this.#consola.trace(this.#format(opts, ...args));
49
+ }
50
+
51
+ warn(opts: LogFnOptions | string, ...args: unknown[]) {
52
+ this.#consola.warn(this.#format(opts, ...args));
53
+ }
54
+
55
+ child(options: CreateOptions) {
56
+ return new Loggy({
57
+ ...this.#options,
58
+ ...options,
59
+ namespace: `${this.#options.namespace}:${options.namespace}`,
60
+ });
61
+ }
62
+
63
+ start(opts: LogFnOptions | string, ...args: unknown[]) {
64
+ this.#consola.start(this.#format(opts, ...args));
65
+ }
66
+
67
+ success(opts: LogFnOptions | string, ...args: unknown[]) {
68
+ this.#consola.success(this.#format(opts, ...args));
69
+ }
70
+
71
+ subdebug(namespace: string) {
72
+ return createDebug(`${this.#options.namespace}:${namespace}`);
73
+ }
74
+
75
+ #format(...args: unknown[]) {
76
+ const [firstArg, ...restArgs] = args;
77
+
78
+ const tag = isLogFnOptions(firstArg) ? firstArg.tag : this.#options.tag;
79
+ const formattedArgs = isLogFnOptions(firstArg) ? [firstArg.message, ...restArgs] : args;
80
+
81
+ if (typeof formattedArgs[0] !== "string") {
82
+ formattedArgs.unshift("%O");
83
+ }
84
+
85
+ const [message, ...replacements] = formattedArgs;
86
+ let replacementIndex = -1;
87
+
88
+ if (typeof message !== "string") {
89
+ throw new TypeError("message must be a string");
90
+ }
91
+
92
+ let formattedMessage = message
93
+ .replace(
94
+ /%([a-zA-Z%])/g, // matches %o, %O, %%, etc.
95
+ (match: string, formatKey: string) => {
96
+ if (formatKey === "%") {
97
+ return "%";
98
+ }
99
+
100
+ replacementIndex++;
101
+
102
+ const formatter = this.#options.formatters[formatKey];
103
+
104
+ if (typeof formatter !== "function") {
105
+ return match;
106
+ }
107
+
108
+ const replacement = replacements[replacementIndex];
109
+ const formattedMatch = formatter(replacement);
110
+
111
+ replacements.splice(replacementIndex, 1);
112
+ replacementIndex--;
113
+
114
+ return formattedMatch;
115
+ },
116
+ )
117
+ .split("\n")
118
+ .join(`\n${" ".repeat(2)}`);
119
+
120
+ formattedMessage = !tag ? formattedMessage : `${colors.cyan(`[${tag}]`)} ${formattedMessage}`;
121
+
122
+ return util.formatWithOptions(this.#options.formatOptions, formattedMessage, ...replacements);
123
+ }
124
+ }
125
+
126
+ export function createLoggy(options: CreateOptions) {
127
+ return new Loggy({
128
+ namespace: options.namespace,
129
+ tag: options.tag,
130
+ formatOptions: {
131
+ ...DEFAULT_FORMAT_OPTIONS,
132
+ ...options.formatOptions,
133
+ },
134
+ formatters: {
135
+ ...DEFAULT_FORMATTERS,
136
+ ...options.formatters,
137
+ },
138
+ });
139
+ }
package/src/types.ts ADDED
@@ -0,0 +1,43 @@
1
+ import type { FormatOptions } from "consola";
2
+
3
+ export type AnyLogFn = (...args: unknown[]) => void;
4
+
5
+ export type LogFn = (opts: LogFnOptions | string, ...args: unknown[]) => void;
6
+
7
+ export type Formatters = Record<string, (arg: unknown) => string>;
8
+
9
+ export type AnyLogger = {
10
+ namespace: string;
11
+ debug: AnyLogFn;
12
+ error: AnyLogFn;
13
+ info: LogFn;
14
+ trace: LogFn;
15
+ warn: LogFn;
16
+ child: (options: CreateOptions) => AnyLogger;
17
+ // { extras
18
+ subdebug: (namespace: string) => AnyLogFn;
19
+ start: LogFn;
20
+ success: LogFn;
21
+ // }
22
+ };
23
+
24
+ export type CreateOptions = {
25
+ namespace: string;
26
+ tag?: string;
27
+ formatOptions?: FormatOptions;
28
+ formatters?: Formatters;
29
+ };
30
+
31
+ export type LoggerOptions = {
32
+ tag?: string;
33
+ namespace: string;
34
+ formatOptions: FormatOptions;
35
+ formatters: Formatters;
36
+ };
37
+
38
+ export type LogFnOptions = {
39
+ tag: string;
40
+ message: string;
41
+ };
42
+
43
+ export type { FormatOptions };