@holz/ansi-terminal-backend 0.0.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/CHANGELOG.md +7 -0
- package/dist/holz-ansi-terminal-backend.js +59 -0
- package/dist/holz-ansi-terminal-backend.js.map +1 -0
- package/dist/holz-ansi-terminal-backend.umd.cjs +2 -0
- package/dist/holz-ansi-terminal-backend.umd.cjs.map +1 -0
- package/package.json +47 -0
- package/src/__tests__/ansi-terminal-backend.test.ts +106 -0
- package/src/ansi-codes.ts +27 -0
- package/src/ansi-terminal-backend.ts +76 -0
- package/src/index.ts +4 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## Unreleased
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
var t = /* @__PURE__ */ ((r) => (r.Error = "error", r.Warn = "warn", r.Info = "info", r.Debug = "debug", r))(t || {});
|
|
2
|
+
function e(r) {
|
|
3
|
+
return `\x1B[${r}`;
|
|
4
|
+
}
|
|
5
|
+
const s = {
|
|
6
|
+
black: e("30m"),
|
|
7
|
+
red: e("31m"),
|
|
8
|
+
green: e("32m"),
|
|
9
|
+
yellow: e("33m"),
|
|
10
|
+
blue: e("34m"),
|
|
11
|
+
magenta: e("35m"),
|
|
12
|
+
cyan: e("36m"),
|
|
13
|
+
white: e("37m")
|
|
14
|
+
}, n = {
|
|
15
|
+
reset: e("0m"),
|
|
16
|
+
bold: e("1m"),
|
|
17
|
+
dim: e("2m")
|
|
18
|
+
};
|
|
19
|
+
class u {
|
|
20
|
+
constructor(o = {}) {
|
|
21
|
+
this.console = o.console ?? console;
|
|
22
|
+
}
|
|
23
|
+
processLog(o) {
|
|
24
|
+
const l = [
|
|
25
|
+
{
|
|
26
|
+
include: !0,
|
|
27
|
+
command: "%s",
|
|
28
|
+
content: i[o.level]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
include: !0,
|
|
32
|
+
command: "%s",
|
|
33
|
+
content: o.message
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
include: o.origin.length > 0,
|
|
37
|
+
command: "%s",
|
|
38
|
+
content: `${n.dim}${o.origin.join(":")}${n.reset}`
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
include: Object.keys(o.context).length > 0,
|
|
42
|
+
command: "%O",
|
|
43
|
+
content: o.context
|
|
44
|
+
}
|
|
45
|
+
].filter((c) => c.include), m = l.map((c) => c.command).join(" "), a = l.map((c) => c.content), d = o.level === t.Error ? "error" : "log";
|
|
46
|
+
this.console[d](m, ...a);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const i = {
|
|
50
|
+
[t.Debug]: `${n.bold}${s.cyan}debug${n.reset}`,
|
|
51
|
+
[t.Info]: `${n.bold}${s.green}info${n.reset} `,
|
|
52
|
+
[t.Warn]: `${n.bold}${s.yellow}warn${n.reset} `,
|
|
53
|
+
[t.Error]: `${n.bold}${s.red}error${n.reset}`
|
|
54
|
+
};
|
|
55
|
+
export {
|
|
56
|
+
u as AnsiTerminalBackend,
|
|
57
|
+
u as default
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=holz-ansi-terminal-backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"holz-ansi-terminal-backend.js","sources":["../../holz-core/dist/holz-core.js","../src/ansi-codes.ts","../src/ansi-terminal-backend.ts"],"sourcesContent":["var t = /* @__PURE__ */ ((s) => (s.Error = \"error\", s.Warn = \"warn\", s.Info = \"info\", s.Debug = \"debug\", s))(t || {});\nclass e {\n constructor(r, o) {\n this.processor = r, this.origin = o;\n }\n static create(r) {\n return new e(r, []);\n }\n /** Extend the logger to attach a class or module name to logs. */\n namespace(r) {\n return new e(this.processor, this.origin.concat(r));\n }\n /** Log a frequent and verbose progress update. */\n debug(r, o) {\n this.forwardLog(t.Debug, r, o);\n }\n /** Log a high-level progress update. */\n info(r, o) {\n this.forwardLog(t.Info, r, o);\n }\n /** Log something concerning. */\n warn(r, o) {\n this.forwardLog(t.Warn, r, o);\n }\n /** Log a critical failure. */\n error(r, o) {\n this.forwardLog(t.Error, r, o);\n }\n forwardLog(r, o, c = {}) {\n this.processor.processLog({\n message: o,\n level: r,\n origin: this.origin,\n context: c\n });\n }\n}\nconst a = e.create;\nclass i {\n constructor(r) {\n this.processors = r;\n }\n processLog(r) {\n this.processors.forEach((o) => {\n o.processLog(r);\n });\n }\n}\nfunction h(s) {\n return new i(s);\n}\nclass n {\n constructor(r, o) {\n this.predicate = r, this.processor = o;\n }\n processLog(r) {\n this.predicate(r) && this.processor.processLog(r);\n }\n}\nfunction g(s, r) {\n return new n(s, r);\n}\nexport {\n t as LogLevel,\n h as combine,\n a as createLogger,\n g as filter\n};\n//# sourceMappingURL=holz-core.js.map\n","function ansiCode(code: string) {\n return `\\x1b[${code}`;\n}\n\n/**\n * Unix only. This will break for win32 terminals and may spew garbage on\n * terminals without 4-bit color support.\n *\n * TODO: Strip colors on non-interactive TTYs.\n */\n\nexport const color = {\n black: ansiCode('30m'),\n red: ansiCode('31m'),\n green: ansiCode('32m'),\n yellow: ansiCode('33m'),\n blue: ansiCode('34m'),\n magenta: ansiCode('35m'),\n cyan: ansiCode('36m'),\n white: ansiCode('37m'),\n};\n\nexport const code = {\n reset: ansiCode('0m'),\n bold: ansiCode('1m'),\n dim: ansiCode('2m'),\n};\n","import type { Log, LogProcessor } from '@holz/core';\nimport { LogLevel } from '@holz/core';\nimport { color, code } from './ansi-codes';\n\n/**\n * A backend that prints logs to a 3-bit ansi terminal. This should work on\n * most Unix systems and Windows >= 10.\n *\n * NOTE: This is not smart enough to detect if the output is a TTY or if it\n * supports colors, nor is this the appropriate place to check. Without color\n * ques, the printed text is much less understandable. It is better to check\n * when constructing the logger instead.\n */\nexport default class AnsiTerminalBackend implements LogProcessor {\n private console: MinimalConsole;\n\n constructor(options: Options = {}) {\n this.console = options.console ?? console;\n }\n\n processLog(log: Log) {\n const segments = [\n {\n include: true,\n command: '%s',\n content: logLevelLabel[log.level],\n },\n {\n include: true,\n command: '%s',\n content: log.message,\n },\n {\n include: log.origin.length > 0,\n command: '%s',\n content: `${code.dim}${log.origin.join(':')}${code.reset}`,\n },\n {\n include: Object.keys(log.context).length > 0,\n command: '%O',\n content: log.context,\n },\n ].filter((segment) => segment.include);\n\n const command = segments.map((segment) => segment.command).join(' ');\n const values = segments.map((segment) => segment.content);\n\n // Errors should always use stderr.\n const channel: keyof MinimalConsole =\n log.level === LogLevel.Error ? 'error' : 'log';\n\n this.console[channel](command, ...values);\n }\n}\n\nconst logLevelLabel: Record<LogLevel, string> = {\n [LogLevel.Debug]: `${code.bold}${color.cyan}debug${code.reset}`,\n [LogLevel.Info]: `${code.bold}${color.green}info${code.reset} `,\n [LogLevel.Warn]: `${code.bold}${color.yellow}warn${code.reset} `,\n [LogLevel.Error]: `${code.bold}${color.red}error${code.reset}`,\n};\n\ninterface Options {\n console?: MinimalConsole;\n}\n\n/**\n * A subset of the Console interface. Must support printf-style interpolation.\n */\nexport interface MinimalConsole {\n /** Log to stdout. */\n log: Console['log'];\n\n /** Log to stderr. */\n error: Console['error'];\n}\n"],"names":["s","ansiCode","code","color","AnsiTerminalBackend","options","log","segments","logLevelLabel","segment","command","values","channel","LogLevel"],"mappings":"AAAA,IAAI,IAAqB,kBAACA,OAAOA,EAAE,QAAQ,SAASA,EAAE,OAAO,QAAQA,EAAE,OAAO,QAAQA,EAAE,QAAQ,SAASA,IAAI,KAAK,EAAE;ACApH,SAASC,EAASC,GAAc;AAC9B,SAAO,QAAQA;AACjB;AASO,MAAMC,IAAQ;AAAA,EACnB,OAAOF,EAAS,KAAK;AAAA,EACrB,KAAKA,EAAS,KAAK;AAAA,EACnB,OAAOA,EAAS,KAAK;AAAA,EACrB,QAAQA,EAAS,KAAK;AAAA,EACtB,MAAMA,EAAS,KAAK;AAAA,EACpB,SAASA,EAAS,KAAK;AAAA,EACvB,MAAMA,EAAS,KAAK;AAAA,EACpB,OAAOA,EAAS,KAAK;AACvB,GAEaC,IAAO;AAAA,EAClB,OAAOD,EAAS,IAAI;AAAA,EACpB,MAAMA,EAAS,IAAI;AAAA,EACnB,KAAKA,EAAS,IAAI;AACpB;ACbA,MAAqBG,EAA4C;AAAA,EAG/D,YAAYC,IAAmB,IAAI;AAC5B,SAAA,UAAUA,EAAQ,WAAW;AAAA,EACpC;AAAA,EAEA,WAAWC,GAAU;AACnB,UAAMC,IAAW;AAAA,MACf;AAAA,QACE,SAAS;AAAA,QACT,SAAS;AAAA,QACT,SAASC,EAAcF,EAAI,KAAK;AAAA,MAClC;AAAA,MACA;AAAA,QACE,SAAS;AAAA,QACT,SAAS;AAAA,QACT,SAASA,EAAI;AAAA,MACf;AAAA,MACA;AAAA,QACE,SAASA,EAAI,OAAO,SAAS;AAAA,QAC7B,SAAS;AAAA,QACT,SAAS,GAAGJ,EAAK,MAAMI,EAAI,OAAO,KAAK,GAAG,IAAIJ,EAAK;AAAA,MACrD;AAAA,MACA;AAAA,QACE,SAAS,OAAO,KAAKI,EAAI,OAAO,EAAE,SAAS;AAAA,QAC3C,SAAS;AAAA,QACT,SAASA,EAAI;AAAA,MACf;AAAA,IACA,EAAA,OAAO,CAACG,MAAYA,EAAQ,OAAO,GAE/BC,IAAUH,EAAS,IAAI,CAACE,MAAYA,EAAQ,OAAO,EAAE,KAAK,GAAG,GAC7DE,IAASJ,EAAS,IAAI,CAACE,MAAYA,EAAQ,OAAO,GAGlDG,IACJN,EAAI,UAAUO,EAAS,QAAQ,UAAU;AAE3C,SAAK,QAAQD,CAAO,EAAEF,GAAS,GAAGC,CAAM;AAAA,EAC1C;AACF;AAEA,MAAMH,IAA0C;AAAA,EAC9C,CAACK,EAAS,KAAK,GAAG,GAAGX,EAAK,OAAOC,EAAM,YAAYD,EAAK;AAAA,EACxD,CAACW,EAAS,IAAI,GAAG,GAAGX,EAAK,OAAOC,EAAM,YAAYD,EAAK;AAAA,EACvD,CAACW,EAAS,IAAI,GAAG,GAAGX,EAAK,OAAOC,EAAM,aAAaD,EAAK;AAAA,EACxD,CAACW,EAAS,KAAK,GAAG,GAAGX,EAAK,OAAOC,EAAM,WAAWD,EAAK;AACzD;"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
(function(c,n){typeof exports=="object"&&typeof module<"u"?n(exports):typeof define=="function"&&define.amd?define(["exports"],n):(c=typeof globalThis<"u"?globalThis:c||self,n(c["holz-ansi-terminal-backend"]={}))})(this,function(c){"use strict";var n=(r=>(r.Error="error",r.Warn="warn",r.Info="info",r.Debug="debug",r))(n||{});function e(r){return`\x1B[${r}`}const s={black:e("30m"),red:e("31m"),green:e("32m"),yellow:e("33m"),blue:e("34m"),magenta:e("35m"),cyan:e("36m"),white:e("37m")},o={reset:e("0m"),bold:e("1m"),dim:e("2m")};class l{constructor(t={}){this.console=t.console??console}processLog(t){const d=[{include:!0,command:"%s",content:m[t.level]},{include:!0,command:"%s",content:t.message},{include:t.origin.length>0,command:"%s",content:`${o.dim}${t.origin.join(":")}${o.reset}`},{include:Object.keys(t.context).length>0,command:"%O",content:t.context}].filter(i=>i.include),a=d.map(i=>i.command).join(" "),u=d.map(i=>i.content),f=t.level===n.Error?"error":"log";this.console[f](a,...u)}}const m={[n.Debug]:`${o.bold}${s.cyan}debug${o.reset}`,[n.Info]:`${o.bold}${s.green}info${o.reset} `,[n.Warn]:`${o.bold}${s.yellow}warn${o.reset} `,[n.Error]:`${o.bold}${s.red}error${o.reset}`};c.AnsiTerminalBackend=l,c.default=l,Object.defineProperties(c,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
|
|
2
|
+
//# sourceMappingURL=holz-ansi-terminal-backend.umd.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"holz-ansi-terminal-backend.umd.cjs","sources":["../../holz-core/dist/holz-core.js","../src/ansi-codes.ts","../src/ansi-terminal-backend.ts"],"sourcesContent":["var t = /* @__PURE__ */ ((s) => (s.Error = \"error\", s.Warn = \"warn\", s.Info = \"info\", s.Debug = \"debug\", s))(t || {});\nclass e {\n constructor(r, o) {\n this.processor = r, this.origin = o;\n }\n static create(r) {\n return new e(r, []);\n }\n /** Extend the logger to attach a class or module name to logs. */\n namespace(r) {\n return new e(this.processor, this.origin.concat(r));\n }\n /** Log a frequent and verbose progress update. */\n debug(r, o) {\n this.forwardLog(t.Debug, r, o);\n }\n /** Log a high-level progress update. */\n info(r, o) {\n this.forwardLog(t.Info, r, o);\n }\n /** Log something concerning. */\n warn(r, o) {\n this.forwardLog(t.Warn, r, o);\n }\n /** Log a critical failure. */\n error(r, o) {\n this.forwardLog(t.Error, r, o);\n }\n forwardLog(r, o, c = {}) {\n this.processor.processLog({\n message: o,\n level: r,\n origin: this.origin,\n context: c\n });\n }\n}\nconst a = e.create;\nclass i {\n constructor(r) {\n this.processors = r;\n }\n processLog(r) {\n this.processors.forEach((o) => {\n o.processLog(r);\n });\n }\n}\nfunction h(s) {\n return new i(s);\n}\nclass n {\n constructor(r, o) {\n this.predicate = r, this.processor = o;\n }\n processLog(r) {\n this.predicate(r) && this.processor.processLog(r);\n }\n}\nfunction g(s, r) {\n return new n(s, r);\n}\nexport {\n t as LogLevel,\n h as combine,\n a as createLogger,\n g as filter\n};\n//# sourceMappingURL=holz-core.js.map\n","function ansiCode(code: string) {\n return `\\x1b[${code}`;\n}\n\n/**\n * Unix only. This will break for win32 terminals and may spew garbage on\n * terminals without 4-bit color support.\n *\n * TODO: Strip colors on non-interactive TTYs.\n */\n\nexport const color = {\n black: ansiCode('30m'),\n red: ansiCode('31m'),\n green: ansiCode('32m'),\n yellow: ansiCode('33m'),\n blue: ansiCode('34m'),\n magenta: ansiCode('35m'),\n cyan: ansiCode('36m'),\n white: ansiCode('37m'),\n};\n\nexport const code = {\n reset: ansiCode('0m'),\n bold: ansiCode('1m'),\n dim: ansiCode('2m'),\n};\n","import type { Log, LogProcessor } from '@holz/core';\nimport { LogLevel } from '@holz/core';\nimport { color, code } from './ansi-codes';\n\n/**\n * A backend that prints logs to a 3-bit ansi terminal. This should work on\n * most Unix systems and Windows >= 10.\n *\n * NOTE: This is not smart enough to detect if the output is a TTY or if it\n * supports colors, nor is this the appropriate place to check. Without color\n * ques, the printed text is much less understandable. It is better to check\n * when constructing the logger instead.\n */\nexport default class AnsiTerminalBackend implements LogProcessor {\n private console: MinimalConsole;\n\n constructor(options: Options = {}) {\n this.console = options.console ?? console;\n }\n\n processLog(log: Log) {\n const segments = [\n {\n include: true,\n command: '%s',\n content: logLevelLabel[log.level],\n },\n {\n include: true,\n command: '%s',\n content: log.message,\n },\n {\n include: log.origin.length > 0,\n command: '%s',\n content: `${code.dim}${log.origin.join(':')}${code.reset}`,\n },\n {\n include: Object.keys(log.context).length > 0,\n command: '%O',\n content: log.context,\n },\n ].filter((segment) => segment.include);\n\n const command = segments.map((segment) => segment.command).join(' ');\n const values = segments.map((segment) => segment.content);\n\n // Errors should always use stderr.\n const channel: keyof MinimalConsole =\n log.level === LogLevel.Error ? 'error' : 'log';\n\n this.console[channel](command, ...values);\n }\n}\n\nconst logLevelLabel: Record<LogLevel, string> = {\n [LogLevel.Debug]: `${code.bold}${color.cyan}debug${code.reset}`,\n [LogLevel.Info]: `${code.bold}${color.green}info${code.reset} `,\n [LogLevel.Warn]: `${code.bold}${color.yellow}warn${code.reset} `,\n [LogLevel.Error]: `${code.bold}${color.red}error${code.reset}`,\n};\n\ninterface Options {\n console?: MinimalConsole;\n}\n\n/**\n * A subset of the Console interface. Must support printf-style interpolation.\n */\nexport interface MinimalConsole {\n /** Log to stdout. */\n log: Console['log'];\n\n /** Log to stderr. */\n error: Console['error'];\n}\n"],"names":["t","s","ansiCode","code","color","AnsiTerminalBackend","options","log","segments","logLevelLabel","segment","command","values","channel","LogLevel"],"mappings":"qPAAA,IAAIA,GAAsBC,IAAOA,EAAE,MAAQ,QAASA,EAAE,KAAO,OAAQA,EAAE,KAAO,OAAQA,EAAE,MAAQ,QAASA,IAAID,GAAK,EAAE,ECApH,SAASE,EAASC,EAAc,CAC9B,MAAO,QAAQA,GACjB,CASO,MAAMC,EAAQ,CACnB,MAAOF,EAAS,KAAK,EACrB,IAAKA,EAAS,KAAK,EACnB,MAAOA,EAAS,KAAK,EACrB,OAAQA,EAAS,KAAK,EACtB,KAAMA,EAAS,KAAK,EACpB,QAASA,EAAS,KAAK,EACvB,KAAMA,EAAS,KAAK,EACpB,MAAOA,EAAS,KAAK,CACvB,EAEaC,EAAO,CAClB,MAAOD,EAAS,IAAI,EACpB,KAAMA,EAAS,IAAI,EACnB,IAAKA,EAAS,IAAI,CACpB,ECbA,MAAqBG,CAA4C,CAG/D,YAAYC,EAAmB,GAAI,CAC5B,KAAA,QAAUA,EAAQ,SAAW,OACpC,CAEA,WAAWC,EAAU,CACnB,MAAMC,EAAW,CACf,CACE,QAAS,GACT,QAAS,KACT,QAASC,EAAcF,EAAI,KAAK,CAClC,EACA,CACE,QAAS,GACT,QAAS,KACT,QAASA,EAAI,OACf,EACA,CACE,QAASA,EAAI,OAAO,OAAS,EAC7B,QAAS,KACT,QAAS,GAAGJ,EAAK,MAAMI,EAAI,OAAO,KAAK,GAAG,IAAIJ,EAAK,OACrD,EACA,CACE,QAAS,OAAO,KAAKI,EAAI,OAAO,EAAE,OAAS,EAC3C,QAAS,KACT,QAASA,EAAI,OACf,CACA,EAAA,OAAQG,GAAYA,EAAQ,OAAO,EAE/BC,EAAUH,EAAS,IAAKE,GAAYA,EAAQ,OAAO,EAAE,KAAK,GAAG,EAC7DE,EAASJ,EAAS,IAAKE,GAAYA,EAAQ,OAAO,EAGlDG,EACJN,EAAI,QAAUO,EAAS,MAAQ,QAAU,MAE3C,KAAK,QAAQD,CAAO,EAAEF,EAAS,GAAGC,CAAM,CAC1C,CACF,CAEA,MAAMH,EAA0C,CAC9C,CAACK,EAAS,KAAK,EAAG,GAAGX,EAAK,OAAOC,EAAM,YAAYD,EAAK,QACxD,CAACW,EAAS,IAAI,EAAG,GAAGX,EAAK,OAAOC,EAAM,YAAYD,EAAK,SACvD,CAACW,EAAS,IAAI,EAAG,GAAGX,EAAK,OAAOC,EAAM,aAAaD,EAAK,SACxD,CAACW,EAAS,KAAK,EAAG,GAAGX,EAAK,OAAOC,EAAM,WAAWD,EAAK,OACzD"}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@holz/ansi-terminal-backend",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "An ANSI terminal backend for Holz",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/holz-ansi-terminal-backend.umd.cjs",
|
|
7
|
+
"module": "./dist/holz-ansi-terminal-backend.js",
|
|
8
|
+
"types": "./src/index.ts",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/PsychoLlama/holz",
|
|
12
|
+
"directory": "packages/holz-ansi-terminal-backend"
|
|
13
|
+
},
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"require": "./dist/holz-ansi-terminal-backend.umd.cjs",
|
|
17
|
+
"import": "./dist/holz-ansi-terminal-backend.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"author": "Jesse Gibson",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"src"
|
|
29
|
+
],
|
|
30
|
+
"keywords": [
|
|
31
|
+
"holz-backend",
|
|
32
|
+
"ansi",
|
|
33
|
+
"terminal"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"prepare": "vite build --sourcemap",
|
|
37
|
+
"test:unit": "vitest --color --passWithNoTests",
|
|
38
|
+
"test:coverage": "run test:unit --coverage"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@holz/core": "0.0.0",
|
|
42
|
+
"@types/node": "^18.14.0",
|
|
43
|
+
"@vitest/coverage-c8": "0.28.5",
|
|
44
|
+
"vite": "^4.0.0",
|
|
45
|
+
"vitest": "^0.28.5"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { format } from 'util';
|
|
2
|
+
import { createLogger } from '@holz/core';
|
|
3
|
+
import type { MinimalConsole } from '../ansi-terminal-backend';
|
|
4
|
+
import AnsiTerminalBackend from '../ansi-terminal-backend';
|
|
5
|
+
|
|
6
|
+
class MockConsole implements MinimalConsole {
|
|
7
|
+
log(...strings: Array<unknown>) {
|
|
8
|
+
this.stdout(format(...strings));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
error(...strings: Array<unknown>) {
|
|
12
|
+
this.stderr(format(...strings));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
stdout = vi.fn();
|
|
16
|
+
stderr = vi.fn();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe('Terminal backend', () => {
|
|
20
|
+
it('prints the message to the terminal', () => {
|
|
21
|
+
const terminal = new MockConsole();
|
|
22
|
+
const backend = new AnsiTerminalBackend({ console: terminal });
|
|
23
|
+
|
|
24
|
+
const logger = createLogger(backend);
|
|
25
|
+
logger.info('hello world');
|
|
26
|
+
|
|
27
|
+
expect(terminal.stdout).toHaveBeenCalledWith(
|
|
28
|
+
expect.stringContaining('hello world')
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('includes the log level', () => {
|
|
33
|
+
const terminal = new MockConsole();
|
|
34
|
+
const backend = new AnsiTerminalBackend({ console: terminal });
|
|
35
|
+
|
|
36
|
+
const logger = createLogger(backend);
|
|
37
|
+
logger.debug('shout');
|
|
38
|
+
logger.info('normal');
|
|
39
|
+
logger.warn('hmmmm');
|
|
40
|
+
logger.error('oh no');
|
|
41
|
+
|
|
42
|
+
expect(terminal.stdout).toHaveBeenCalledWith(
|
|
43
|
+
expect.stringContaining('debug')
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
expect(terminal.stdout).toHaveBeenCalledWith(
|
|
47
|
+
expect.stringContaining('info')
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
expect(terminal.stdout).toHaveBeenCalledWith(
|
|
51
|
+
expect.stringContaining('warn')
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
expect(terminal.stderr).toHaveBeenCalledWith(
|
|
55
|
+
expect.stringContaining('error')
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('includes the log namespace', () => {
|
|
60
|
+
const terminal = new MockConsole();
|
|
61
|
+
const backend = new AnsiTerminalBackend({ console: terminal });
|
|
62
|
+
const logger = createLogger(backend)
|
|
63
|
+
.namespace('my-lib')
|
|
64
|
+
.namespace('MyClass');
|
|
65
|
+
|
|
66
|
+
logger.debug('initialized');
|
|
67
|
+
|
|
68
|
+
expect(terminal.stdout).toHaveBeenCalledWith(
|
|
69
|
+
expect.stringContaining('my-lib:MyClass')
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('includes the log context', () => {
|
|
74
|
+
const terminal = new MockConsole();
|
|
75
|
+
const backend = new AnsiTerminalBackend({ console: terminal });
|
|
76
|
+
const logger = createLogger(backend);
|
|
77
|
+
|
|
78
|
+
logger.info('creating session', { sessionId: 3109 });
|
|
79
|
+
|
|
80
|
+
// Hard to test without replicating the implementation.
|
|
81
|
+
expect(terminal.stdout).toHaveBeenCalledWith(
|
|
82
|
+
expect.stringContaining('sessionId')
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
expect(terminal.stdout).toHaveBeenCalledWith(
|
|
86
|
+
expect.stringContaining('3109')
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('does not include the log context if it is empty', () => {
|
|
91
|
+
const terminal = new MockConsole();
|
|
92
|
+
const backend = new AnsiTerminalBackend({ console: terminal });
|
|
93
|
+
const logger = createLogger(backend);
|
|
94
|
+
|
|
95
|
+
logger.warn('activating death ray', {});
|
|
96
|
+
|
|
97
|
+
// Hard to test without replicating the implementation.
|
|
98
|
+
expect(terminal.stdout).not.toHaveBeenCalledWith(
|
|
99
|
+
expect.stringContaining('{')
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
expect(terminal.stdout).not.toHaveBeenCalledWith(
|
|
103
|
+
expect.stringContaining('}')
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
function ansiCode(code: string) {
|
|
2
|
+
return `\x1b[${code}`;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Unix only. This will break for win32 terminals and may spew garbage on
|
|
7
|
+
* terminals without 4-bit color support.
|
|
8
|
+
*
|
|
9
|
+
* TODO: Strip colors on non-interactive TTYs.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export const color = {
|
|
13
|
+
black: ansiCode('30m'),
|
|
14
|
+
red: ansiCode('31m'),
|
|
15
|
+
green: ansiCode('32m'),
|
|
16
|
+
yellow: ansiCode('33m'),
|
|
17
|
+
blue: ansiCode('34m'),
|
|
18
|
+
magenta: ansiCode('35m'),
|
|
19
|
+
cyan: ansiCode('36m'),
|
|
20
|
+
white: ansiCode('37m'),
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const code = {
|
|
24
|
+
reset: ansiCode('0m'),
|
|
25
|
+
bold: ansiCode('1m'),
|
|
26
|
+
dim: ansiCode('2m'),
|
|
27
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { Log, LogProcessor } from '@holz/core';
|
|
2
|
+
import { LogLevel } from '@holz/core';
|
|
3
|
+
import { color, code } from './ansi-codes';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A backend that prints logs to a 3-bit ansi terminal. This should work on
|
|
7
|
+
* most Unix systems and Windows >= 10.
|
|
8
|
+
*
|
|
9
|
+
* NOTE: This is not smart enough to detect if the output is a TTY or if it
|
|
10
|
+
* supports colors, nor is this the appropriate place to check. Without color
|
|
11
|
+
* ques, the printed text is much less understandable. It is better to check
|
|
12
|
+
* when constructing the logger instead.
|
|
13
|
+
*/
|
|
14
|
+
export default class AnsiTerminalBackend implements LogProcessor {
|
|
15
|
+
private console: MinimalConsole;
|
|
16
|
+
|
|
17
|
+
constructor(options: Options = {}) {
|
|
18
|
+
this.console = options.console ?? console;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
processLog(log: Log) {
|
|
22
|
+
const segments = [
|
|
23
|
+
{
|
|
24
|
+
include: true,
|
|
25
|
+
command: '%s',
|
|
26
|
+
content: logLevelLabel[log.level],
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
include: true,
|
|
30
|
+
command: '%s',
|
|
31
|
+
content: log.message,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
include: log.origin.length > 0,
|
|
35
|
+
command: '%s',
|
|
36
|
+
content: `${code.dim}${log.origin.join(':')}${code.reset}`,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
include: Object.keys(log.context).length > 0,
|
|
40
|
+
command: '%O',
|
|
41
|
+
content: log.context,
|
|
42
|
+
},
|
|
43
|
+
].filter((segment) => segment.include);
|
|
44
|
+
|
|
45
|
+
const command = segments.map((segment) => segment.command).join(' ');
|
|
46
|
+
const values = segments.map((segment) => segment.content);
|
|
47
|
+
|
|
48
|
+
// Errors should always use stderr.
|
|
49
|
+
const channel: keyof MinimalConsole =
|
|
50
|
+
log.level === LogLevel.Error ? 'error' : 'log';
|
|
51
|
+
|
|
52
|
+
this.console[channel](command, ...values);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const logLevelLabel: Record<LogLevel, string> = {
|
|
57
|
+
[LogLevel.Debug]: `${code.bold}${color.cyan}debug${code.reset}`,
|
|
58
|
+
[LogLevel.Info]: `${code.bold}${color.green}info${code.reset} `,
|
|
59
|
+
[LogLevel.Warn]: `${code.bold}${color.yellow}warn${code.reset} `,
|
|
60
|
+
[LogLevel.Error]: `${code.bold}${color.red}error${code.reset}`,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
interface Options {
|
|
64
|
+
console?: MinimalConsole;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* A subset of the Console interface. Must support printf-style interpolation.
|
|
69
|
+
*/
|
|
70
|
+
export interface MinimalConsole {
|
|
71
|
+
/** Log to stdout. */
|
|
72
|
+
log: Console['log'];
|
|
73
|
+
|
|
74
|
+
/** Log to stderr. */
|
|
75
|
+
error: Console['error'];
|
|
76
|
+
}
|
package/src/index.ts
ADDED