mynth-logger 2.0.10 → 2.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.
@@ -0,0 +1,106 @@
1
+ id: drop-if-single-stmt-braces
2
+ language: TypeScript
3
+ rule:
4
+ all:
5
+ - pattern: |
6
+ if ($COND) {
7
+ $STMT
8
+ }
9
+ - not:
10
+ pattern: |
11
+ if ($COND) {
12
+ $STMT
13
+ } else $ALT
14
+ fix: |
15
+ if ($COND)
16
+ $STMT
17
+ ---
18
+ id: inline-if-then-keep-else-braces
19
+ language: TypeScript
20
+ rule:
21
+ pattern: |
22
+ if ($COND) {
23
+ $THEN
24
+ } else {
25
+ $ELSE1
26
+ $ELSE2
27
+ $$$ELSE_REST
28
+ }
29
+ fix: |
30
+ if ($COND) $THEN
31
+ else {
32
+ $ELSE1
33
+ $ELSE2
34
+ $$$ELSE_REST
35
+ }
36
+ ---
37
+ id: drop-if-else-single-stmt-braces
38
+ language: TypeScript
39
+ rule:
40
+ pattern: |
41
+ if ($COND) {
42
+ $THEN
43
+ } else {
44
+ $ELSE
45
+ }
46
+ fix: |
47
+ if ($COND)
48
+ $THEN
49
+ else
50
+ $ELSE
51
+ ---
52
+ id: drop-while-single-stmt-braces
53
+ language: TypeScript
54
+ rule:
55
+ pattern: |
56
+ while ($COND) {
57
+ $STMT
58
+ }
59
+ fix: |
60
+ while ($COND)
61
+ $STMT
62
+ ---
63
+ id: drop-for-single-stmt-braces
64
+ language: TypeScript
65
+ rule:
66
+ pattern: |
67
+ for ($INIT; $COND; $UPDATE) {
68
+ $STMT
69
+ }
70
+ fix: |
71
+ for ($INIT; $COND; $UPDATE)
72
+ $STMT
73
+ ---
74
+ id: drop-for-of-single-stmt-braces
75
+ language: TypeScript
76
+ rule:
77
+ pattern: |
78
+ for ($LEFT of $RIGHT) {
79
+ $STMT
80
+ }
81
+ fix: |
82
+ for ($LEFT of $RIGHT)
83
+ $STMT
84
+ ---
85
+ id: drop-for-in-single-stmt-braces
86
+ language: TypeScript
87
+ rule:
88
+ pattern: |
89
+ for ($LEFT in $RIGHT) {
90
+ $STMT
91
+ }
92
+ fix: |
93
+ for ($LEFT in $RIGHT)
94
+ $STMT
95
+ ---
96
+ id: drop-do-while-single-stmt-braces
97
+ language: TypeScript
98
+ rule:
99
+ pattern: |
100
+ do {
101
+ $STMT
102
+ } while ($COND);
103
+ fix: |
104
+ do
105
+ $STMT
106
+ while ($COND);
@@ -12,19 +12,16 @@ jobs:
12
12
  timeout-minutes: 10
13
13
  steps:
14
14
  - name: Checkout repository
15
- uses: actions/checkout@v3
15
+ uses: actions/checkout@v6.0.2
16
16
 
17
17
  - name: Set up Node.js
18
- uses: actions/setup-node@v3
18
+ uses: actions/setup-node@v6.2.0
19
19
  with:
20
- node-version: 18.18
21
- check-latest: true
20
+ node-version: 24
22
21
 
23
- - name: Update npm
24
- run: npm install -g npm@"<11"
22
+ - uses: pnpm/action-setup@v4
25
23
 
26
- - name: Install dependencies
27
- run: npm ci --include dev
24
+ - run: pnpm install
28
25
 
29
26
  - name: Run tests
30
27
  run: npm run test
@@ -34,42 +31,37 @@ jobs:
34
31
  timeout-minutes: 10
35
32
  steps:
36
33
  - name: Checkout repository
37
- uses: actions/checkout@v3
34
+ uses: actions/checkout@v6.0.2
38
35
 
39
36
  - name: Set up Node.js
40
- uses: actions/setup-node@v3
37
+ uses: actions/setup-node@v6.2.0
41
38
  with:
42
- node-version: 18.18
43
- check-latest: true
39
+ node-version: 24
44
40
 
45
- - name: Update npm
46
- run: npm install -g npm@"<11"
41
+ - uses: pnpm/action-setup@v4
47
42
 
48
- - name: Install dependencies
49
- run: npm ci --include dev
43
+ - run: pnpm install
50
44
 
51
45
  - name: Run tests
52
- run: npx tsx tests/app.ts
46
+ run: pnpm tsx tests/app.ts
53
47
 
54
48
  lint:
55
49
  runs-on: ubuntu-latest
56
50
  timeout-minutes: 10
57
51
 
58
52
  steps:
59
- - name: Checkout code
60
- uses: actions/checkout@v3
53
+ - name: Checkout repository
54
+ uses: actions/checkout@v6.0.2
61
55
 
62
56
  - name: Set up Node.js
63
- uses: actions/setup-node@v3
57
+ uses: actions/setup-node@v6.2.0
64
58
  with:
65
- node-version: 18.18
66
- check-latest: true
59
+ node-version: 24
67
60
 
68
- - name: Update npm
69
- run: npm install -g npm@"<11"
61
+ - uses: pnpm/action-setup@v4
70
62
 
71
63
  - name: Install dependencies
72
- run: npm install --include dev
64
+ run: pnpm install
73
65
 
74
66
  - name: Lint
75
- run: npm run lint
67
+ run: pnpm lint
@@ -18,21 +18,19 @@ jobs:
18
18
  contents: write
19
19
  steps:
20
20
  - name: Checkout code
21
- uses: actions/checkout@v3
21
+ uses: actions/checkout@v6.0.2
22
22
  with:
23
23
  ssh-key: ${{ secrets.DEPLOY_KEY }}
24
24
 
25
25
  - name: Set up Node.js
26
- uses: actions/setup-node@v3
26
+ uses: actions/setup-node@v6.2.0
27
27
  with:
28
- node-version: 18.18
29
- check-latest: true
28
+ node-version: 24
30
29
 
31
- - name: Update npm
32
- run: npm install -g npm@"<11"
30
+ - uses: pnpm/action-setup@v4
33
31
 
34
32
  - name: Install dependencies
35
- run: npm install
33
+ run: pnpm install
36
34
 
37
35
  - name: Bump version and commit
38
36
  run: |
@@ -41,7 +39,7 @@ jobs:
41
39
  npm version ${{ github.event.inputs.versionType }} -m "chore(release): bump to %s version"
42
40
 
43
41
  - name: Build
44
- run: npm run build
42
+ run: pnpm build
45
43
 
46
44
  - name: Publish to npm
47
45
  run: npm publish
package/biome.json ADDED
@@ -0,0 +1,151 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.4.2/schema.json",
3
+ "vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true },
4
+ "files": { "ignoreUnknown": false },
5
+ "formatter": {
6
+ "enabled": true,
7
+ "formatWithErrors": false,
8
+ "indentStyle": "space",
9
+ "indentWidth": 2,
10
+ "lineEnding": "lf",
11
+ "lineWidth": 80,
12
+ "attributePosition": "auto",
13
+ "bracketSameLine": false,
14
+ "bracketSpacing": true,
15
+ "expand": "auto",
16
+ "useEditorconfig": true,
17
+ "includes": ["**", "!**/*.hbs", "!**/*.md", "!**/pnpm-lock.yaml"]
18
+ },
19
+ "linter": {
20
+ "enabled": true,
21
+ "rules": {
22
+ "recommended": false,
23
+ "complexity": {
24
+ "noAdjacentSpacesInRegex": "error",
25
+ "noArguments": "error",
26
+ "noExtraBooleanCast": "error",
27
+ "noUselessCatch": "error",
28
+ "noUselessEscapeInRegex": "error",
29
+ "noUselessTypeConstraint": "error",
30
+ "useArrowFunction": "error"
31
+ },
32
+ "correctness": {
33
+ "noConstantCondition": "error",
34
+ "noConstAssign": "error",
35
+ "noEmptyCharacterClassInRegex": "error",
36
+ "noEmptyPattern": "error",
37
+ "noGlobalObjectCalls": "error",
38
+ "noInvalidBuiltinInstantiation": "error",
39
+ "noInvalidConstructorSuper": "error",
40
+ "noNonoctalDecimalEscape": "error",
41
+ "noPrecisionLoss": "error",
42
+ "noSelfAssign": "error",
43
+ "noSetterReturn": "error",
44
+ "noSwitchDeclarations": "error",
45
+ "noUndeclaredVariables": "error",
46
+ "noUnreachable": "error",
47
+ "noUnreachableSuper": "error",
48
+ "noUnsafeFinally": "error",
49
+ "noUnsafeOptionalChaining": "error",
50
+ "noUnusedImports": "error",
51
+ "noUnusedLabels": "error",
52
+ "noUnusedPrivateClassMembers": "error",
53
+ "noUnusedVariables": "error",
54
+ "useIsNan": "error",
55
+ "useValidForDirection": "error",
56
+ "useValidTypeof": "error",
57
+ "useYield": "error"
58
+ },
59
+ "style": {
60
+ "useConsistentArrowReturn": {
61
+ "level": "error",
62
+ "fix": "safe",
63
+ "options": { "style": "asNeeded" }
64
+ },
65
+ "noCommonJs": "error",
66
+ "noNamespace": "error",
67
+ "useArrayLiterals": "error",
68
+ "useAsConstAssertion": "error",
69
+ "useConst": "error"
70
+ },
71
+ "suspicious": {
72
+ "noAsyncPromiseExecutor": "error",
73
+ "noCatchAssign": "error",
74
+ "noClassAssign": "error",
75
+ "noCompareNegZero": "error",
76
+ "noConstantBinaryExpressions": "error",
77
+ "noControlCharactersInRegex": "error",
78
+ "noDebugger": "error",
79
+ "noDuplicateCase": "error",
80
+ "noDuplicateClassMembers": "error",
81
+ "noDuplicateElseIf": "error",
82
+ "noDuplicateObjectKeys": "error",
83
+ "noDuplicateParameters": "error",
84
+ "noEmptyBlockStatements": "error",
85
+ "noExplicitAny": "error",
86
+ "noExtraNonNullAssertion": "error",
87
+ "noFallthroughSwitchClause": "error",
88
+ "noFunctionAssign": "error",
89
+ "noGlobalAssign": "error",
90
+ "noImportAssign": "error",
91
+ "noIrregularWhitespace": "error",
92
+ "noMisleadingCharacterClass": "error",
93
+ "noMisleadingInstantiator": "error",
94
+ "noNonNullAssertedOptionalChain": "error",
95
+ "noPrototypeBuiltins": "error",
96
+ "noRedeclare": "error",
97
+ "noShadowRestrictedNames": "error",
98
+ "noSparseArray": "error",
99
+ "noUnassignedVariables": "error",
100
+ "noUnsafeDeclarationMerging": "error",
101
+ "noUnsafeNegation": "error",
102
+ "noUselessRegexBackrefs": "error",
103
+ "noVar": "error",
104
+ "noWith": "error",
105
+ "useGetterReturn": "error",
106
+ "useNamespaceKeyword": "error"
107
+ }
108
+ },
109
+ "includes": ["**", "!**/*.js", "!**/vite.config.ts"]
110
+ },
111
+ "javascript": {
112
+ "formatter": {
113
+ "jsxQuoteStyle": "double",
114
+ "quoteProperties": "asNeeded",
115
+ "trailingCommas": "all",
116
+ "semicolons": "always",
117
+ "arrowParentheses": "always",
118
+ "bracketSameLine": false,
119
+ "quoteStyle": "double",
120
+ "attributePosition": "auto",
121
+ "bracketSpacing": true
122
+ },
123
+ "globals": []
124
+ },
125
+ "html": {
126
+ "formatter": {
127
+ "indentScriptAndStyle": false,
128
+ "selfCloseVoidElements": "always"
129
+ }
130
+ },
131
+ "overrides": [
132
+ { "includes": ["*.yml.j2"] },
133
+ {
134
+ "includes": ["**/*.{ts,tsx,mjs,cjs,js}"],
135
+ "javascript": { "globals": [] },
136
+ "linter": {
137
+ "rules": {
138
+ "correctness": { "noUnusedVariables": "error" },
139
+ "suspicious": {
140
+ "noEmptyBlockStatements": "off",
141
+ "noExplicitAny": "warn"
142
+ }
143
+ }
144
+ }
145
+ }
146
+ ],
147
+ "assist": {
148
+ "enabled": true,
149
+ "actions": { "source": { "organizeImports": "on" } }
150
+ }
151
+ }
@@ -0,0 +1,13 @@
1
+ declare enum color {
2
+ green = "2404635",
3
+ red = "11606811",
4
+ yellow = "11644443"
5
+ }
6
+ declare const discord: {
7
+ configure: (webhookUrl: string) => void;
8
+ debug: (title: string, message: string, color?: color | string) => void;
9
+ error: (title: string, message: string, color?: color | string) => void;
10
+ info: (title: string, message: string, color?: color | string) => void;
11
+ log: (title: string, message: string, color?: color | string) => void;
12
+ };
13
+ export { color, discord };
@@ -0,0 +1,33 @@
1
+ import { type } from "arktype";
2
+ var color;
3
+ (function (color) {
4
+ color["green"] = "2404635";
5
+ color["red"] = "11606811";
6
+ color["yellow"] = "11644443";
7
+ })(color || (color = {}));
8
+ const green = color.green;
9
+ const red = color.red;
10
+ const yellow = color.yellow;
11
+ const log = (level, color, title, message) => {
12
+ const settings = {
13
+ color,
14
+ discord: true,
15
+ title,
16
+ };
17
+ console[level](message, settings);
18
+ };
19
+ const discord = {
20
+ configure: (webhookUrl) => {
21
+ type("string.url").assert(webhookUrl);
22
+ const settings = {
23
+ discord: true,
24
+ setWebhookUrl: webhookUrl,
25
+ };
26
+ console.debug(settings);
27
+ },
28
+ debug: (title, message, color = yellow) => log("debug", color, title, message),
29
+ error: (title, message, color = red) => log("error", color, title, message),
30
+ info: (title, message, color = green) => log("info", color, title, message),
31
+ log: (title, message, color = green) => log("log", color, title, message),
32
+ };
33
+ export { color, discord };
@@ -1,5 +1,6 @@
1
1
  import { stringify } from "@ungap/structured-clone/json";
2
2
  import { type } from "arktype";
3
+ import { redact } from "./redact.js";
3
4
  const ErrorType = type({
4
5
  message: "string",
5
6
  "stack?": "string",
@@ -9,8 +10,8 @@ const formatItem = (item) => {
9
10
  return "undefined";
10
11
  // Remove colors from strings
11
12
  if (typeof item === "string")
12
- // eslint-disable-next-line no-control-regex
13
- return item.replace(/\x1b\[[0-9;]*m/g, "");
13
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: stripping ANSI escape codes
14
+ return redact(item.replace(/\x1b\[[0-9;]*m/g, ""));
14
15
  // Check if this is an Error
15
16
  const error = ErrorType(item);
16
17
  if (!(error instanceof type.errors))
@@ -23,11 +24,9 @@ const formatItem = (item) => {
23
24
  return String(item);
24
25
  }
25
26
  })();
26
- return stringified.replace(/^'|'$/g, "");
27
- };
28
- const format = (items) => {
29
- return Array.from(items)
30
- .map((item) => formatItem(item))
31
- .join(" ");
27
+ return redact(stringified.replace(/^'|'$/g, ""));
32
28
  };
29
+ const format = (items) => Array.from(items)
30
+ .map((item) => formatItem(item))
31
+ .join(" ");
33
32
  export { format };
@@ -1 +1,2 @@
1
+ export * from "./discord.js";
1
2
  export * from "./logging.js";
package/dist/src/index.js CHANGED
@@ -1 +1,2 @@
1
+ export * from "./discord.js";
1
2
  export * from "./logging.js";
@@ -0,0 +1,2 @@
1
+ declare const redact: (text: string) => string;
2
+ export { redact };
@@ -0,0 +1,68 @@
1
+ import { DeepRedact } from "@hackylabs/deep-redact/index.ts";
2
+ import { validateMnemonic } from "@scure/bip39";
3
+ import { wordlist } from "@scure/bip39/wordlists/english.js";
4
+ const replacement = "[REDACTED]";
5
+ /**
6
+ * Create a redactor that censors things that *look like secrets* inside
7
+ * strings:
8
+ * - long hex (optionally 0x-prefixed)
9
+ * - long base64 blobs
10
+ * - long base58 blobs
11
+ * - mnemonic seed phrases (validated via BIP39 english wordlist)
12
+ */
13
+ // Generic replacer: redact ALL matches within the string.
14
+ const replaceAllMatches = (value, pattern) => value.replace(pattern, replacement);
15
+ // Mnemonic replacer: only redact if the captured phrase is a valid BIP39 mnemonic.
16
+ const replaceBip39MnemonicMatches = (value, pattern) => value.replace(pattern, (match, phrase) => {
17
+ // Normalize spacing; validateMnemonic expects words separated by single spaces
18
+ const normalized = phrase.trim().toLowerCase().replace(/\s+/g, " ");
19
+ if (!validateMnemonic(normalized, wordlist))
20
+ return match;
21
+ // Replace only the phrase portion (preserves surrounding quotes/keywords if any)
22
+ return match.replace(phrase, replacement);
23
+ });
24
+ // --- Patterns ---
25
+ // 1) Hex secrets (API keys, hashes, tokens)
26
+ // - 32+ hex chars (optionally 0x)
27
+ // - word boundaries help avoid eating normal words
28
+ const HEX = /\b(?:0x)?[a-fA-F0-9]{32,}\b/g;
29
+ // 2) Base64 blobs (JWT parts, keys, encoded payloads)
30
+ // - requires a decent minimum length to reduce false positives
31
+ // - supports optional padding
32
+ const BASE64 = /\b(?:[A-Za-z0-9+/]{4}){8,}(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?\b/g;
33
+ // 3) Base58 blobs (common in crypto keys/ids)
34
+ // - base58 alphabet excludes 0, O, I, l
35
+ // - require 32+ chars to reduce false positives
36
+ const BASE58 = /\b[1-9A-HJ-NP-Za-km-z]{32,}\b/g;
37
+ // 4) Mnemonic seed phrases
38
+ // We validate candidates with @scure/bip39 to avoid false positives.
39
+ // - 12 to 24 words separated by whitespace
40
+ const WORD = "[a-zA-Z]{2,8}";
41
+ const PHRASE_12_TO_24 = `(?:${WORD}\\s+){11,23}${WORD}`;
42
+ const MNEMONIC_WITH_KEYWORD = new RegExp(
43
+ // keyword then up to ~40 chars (like ":" or whitespace) then the phrase
44
+ String.raw `\b(?:mnemonic|seed|recovery\s+phrase|secret\s+phrase)\b[\s:=-]{0,40}(${PHRASE_12_TO_24})\b`, "gi");
45
+ const MNEMONIC_QUOTED = new RegExp(
46
+ // quoted/bracketed phrase alone
47
+ String.raw `(?:["'(\[])\s*(${PHRASE_12_TO_24})\s*(?:["')\]])`, "gi");
48
+ // NEW: Bare mnemonic phrases (no keyword, no quotes).
49
+ // This is what your console.log example is: just the phrase by itself.
50
+ // Validation keeps false-positives low.
51
+ const MNEMONIC_BARE = new RegExp(String.raw `\b(${PHRASE_12_TO_24})\b`, "gi");
52
+ const redactor = new DeepRedact({
53
+ // stringTests runs regex checks against string values (including flat strings)
54
+ // and lets us partially redact via replacer.
55
+ stringTests: [
56
+ { pattern: HEX, replacer: replaceAllMatches },
57
+ { pattern: BASE64, replacer: replaceAllMatches },
58
+ { pattern: BASE58, replacer: replaceAllMatches },
59
+ { pattern: MNEMONIC_WITH_KEYWORD, replacer: replaceBip39MnemonicMatches },
60
+ { pattern: MNEMONIC_QUOTED, replacer: replaceBip39MnemonicMatches },
61
+ { pattern: MNEMONIC_BARE, replacer: replaceBip39MnemonicMatches },
62
+ ],
63
+ replacement,
64
+ // serialise mainly matters for objects; keeping it false avoids surprises.
65
+ serialise: false,
66
+ });
67
+ const redact = (text) => redactor.redact(text);
68
+ export { redact };
@@ -1,6 +1,19 @@
1
1
  import { LogObject } from "consola";
2
+ declare const Discord: import("arktype/internal/variants/object.ts").ObjectType<{
3
+ discord: true;
4
+ color: string;
5
+ title: string;
6
+ webhookUrl?: string | undefined;
7
+ }, {}>;
8
+ declare const ConfigureDiscord: import("arktype/internal/variants/object.ts").ObjectType<{
9
+ discord: true;
10
+ setWebhookUrl: string;
11
+ }, {}>;
12
+ type Discord = typeof Discord.infer;
13
+ type ConfigureDiscord = typeof ConfigureDiscord.infer;
2
14
  declare const Reporter: {
3
15
  webhookUrl: string;
4
16
  log: (logObj: LogObject) => void;
5
17
  };
6
18
  export default Reporter;
19
+ export { ConfigureDiscord, Discord };
@@ -1,6 +1,6 @@
1
- import { format } from "../format.js";
2
1
  import { type } from "arktype";
3
2
  import got from "got";
3
+ import { format } from "../format.js";
4
4
  const Discord = type({
5
5
  discord: "true",
6
6
  color: "string",
@@ -73,3 +73,4 @@ const sendToDiscord = async (description, options, webhookUrl) => {
73
73
  }
74
74
  };
75
75
  export default Reporter;
76
+ export { ConfigureDiscord, Discord };
package/dist/tests/app.js CHANGED
@@ -1,4 +1,4 @@
1
- import { setupLogging } from "mynth-logger";
1
+ import { setupLogging } from "../src/index.js";
2
2
  const run = async () => {
3
3
  setupLogging();
4
4
  console.log("Hello, this is a log");
@@ -6,6 +6,8 @@ const run = async () => {
6
6
  console.debug("Hello, this is a debug log");
7
7
  console.warn("Hello, this is a warn log");
8
8
  console.error("Hello, this is an error log");
9
+ console.log("This is my seed phrase: ordinary quality amount solid fox guess peasant merit midnight noodle final brown pretty stable six fox beef engage waste uniform evoke flat survey crane");
10
+ console.log("This is my private key: 4fa7614bdd07ab15b11d2365466536ba160c06050ade0888a3aa985a5522ab22");
9
11
  try {
10
12
  throw new Error("An Error was thrown");
11
13
  }
@@ -41,7 +43,7 @@ const run = async () => {
41
43
  console.log("This is MyObject", new MyObject("a", 3));
42
44
  console.log(new MyObject("without a prefix", 100));
43
45
  try {
44
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
46
+ // biome-ignore lint/suspicious/noExplicitAny: ok
45
47
  throw MyObject("Error invoking", 1);
46
48
  }
47
49
  catch (error) {
@@ -1,16 +1,22 @@
1
- import test from "ava";
2
- import { setupLogging } from "mynth-logger";
3
- test.before(() => {
1
+ import { beforeAll, describe, expect, it } from "vitest";
2
+ import { setupLogging } from "../src/index.js";
3
+ beforeAll(() => {
4
4
  setupLogging();
5
5
  });
6
- test.serial("logs display to terminal", (t) => {
7
- console.debug("Hello ava");
8
- t.pass();
9
- });
10
- test.serial("can log various objects", (t) => {
11
- console.debug("Message", { message: true });
12
- console.debug(undefined);
13
- console.debug("bigint", 100n);
14
- console.debug(false, true, "true", "false");
15
- t.pass();
6
+ describe("logging", () => {
7
+ it("logs display to terminal", () => {
8
+ console.debug("Hello vitest");
9
+ expect(true).toBe(true);
10
+ });
11
+ it("can log various objects", () => {
12
+ console.debug("Message", { message: true });
13
+ console.debug(undefined);
14
+ console.debug("bigint", 100n);
15
+ console.debug(false, true, "true", "false");
16
+ expect(true).toBe(true);
17
+ });
18
+ it("redacts private key", () => {
19
+ console.info("a723cc20646a45cccd045b4bd13c0f73e505e6fc7f0c5006ab62e0410a2ef9ba");
20
+ expect(true).toBe(true);
21
+ });
16
22
  });
package/package.json CHANGED
@@ -1,16 +1,29 @@
1
1
  {
2
2
  "name": "mynth-logger",
3
- "version": "2.0.10",
3
+ "version": "2.1.0",
4
4
  "description": "Package to format logs for mynth microservices.",
5
5
  "main": "dist/src/index.js",
6
6
  "typings": "dist/src/index.d.ts",
7
+ "packageManager": "pnpm@10.30.0",
8
+ "engines": {
9
+ "node": "==24"
10
+ },
7
11
  "type": "module",
8
12
  "scripts": {
9
- "build": "rm -rf dist && npx tsc",
10
- "test": "npx ava",
11
- "logs": "npx tsc && node dist/src/run.js",
12
- "prettier": "npx prettier -w '**/*.{js,jsx,ts,tsx,json,yml.j2,yml,yaml,.*}'",
13
- "lint": "concurrently \"npx prettier --check '**/*.{js,jsx,ts,tsx,json,yml.j2,yml,yaml,.*}'\" \"npx eslint . --max-warnings=0\""
13
+ "build": "rm -rf dist && pnpm tsc",
14
+ "test": "pnpm vitest --run",
15
+ "logs": "pnpm tsc && node dist/src/run.js",
16
+ "lint": "pnpm npm-run-all lint:prettier lint:check lint:lint",
17
+ "lint:check": "pnpm biome check .",
18
+ "lint:lint": "pnpm biome lint .",
19
+ "lint:prettier": "pnpm biome format .",
20
+ "prettier": "pnpm npm-run-all prettier:format prettier:check prettier:lint",
21
+ "prettier:check": "pnpm biome check --write .",
22
+ "prettier:format": "pnpm biome format --write .",
23
+ "prettier:lint": "pnpm biome lint --write .",
24
+ "slop": "pnpm npm-run-all slop:braces slop:arrows prettier",
25
+ "slop:arrows": "pnpm convert-to-arrow .",
26
+ "slop:braces": "pnpm exec sg scan --rule .ast-grep/rules/strip-braces.yml -U ."
14
27
  },
15
28
  "publishConfig": {
16
29
  "registry": "https://registry.npmjs.org"
@@ -22,37 +35,23 @@
22
35
  "url": "git@github.com-mynth:MynthAI/mynth-logger.git"
23
36
  },
24
37
  "peerDependencies": {
25
- "arktype": "^2.0.4"
38
+ "arktype": "^2.1.29"
26
39
  },
27
40
  "dependencies": {
28
- "@ungap/structured-clone": "^1.2.0",
29
- "consola": "^3.2.3",
30
- "got": "^13.0.0"
41
+ "@hackylabs/deep-redact": "^3.0.4",
42
+ "@scure/bip39": "^2.0.1",
43
+ "@ungap/structured-clone": "^1.3.0",
44
+ "consola": "^3.4.2",
45
+ "got": "^14.6.6"
31
46
  },
32
47
  "devDependencies": {
33
- "@types/node": "^22.5.5",
34
- "@types/ungap__structured-clone": "^1.2.0",
35
- "@typescript-eslint/eslint-plugin": "^7.11.0",
36
- "ava": "^6.1.3",
37
- "concurrently": "^9.0.1",
38
- "eslint": "^8.47.0",
39
- "eslint-plugin-ava": "^14.0.0",
40
- "eslint-plugin-file-extension-in-import-ts": "^2.1.1",
41
- "eslint-plugin-import": "^2.30.0",
42
- "prettier": "^3.3.3",
43
- "tsx": "^4.19.1",
44
- "typescript": "^5.6.2"
45
- },
46
- "ava": {
47
- "files": [
48
- "**/*.test.ts"
49
- ],
50
- "extensions": {
51
- "ts": "module"
52
- },
53
- "nodeArguments": [
54
- "--loader=tsx",
55
- "--no-warnings"
56
- ]
48
+ "@ast-grep/cli": "^0.40.5",
49
+ "@biomejs/biome": "^2.4.2",
50
+ "@types/node": "^25.2.3",
51
+ "convert-to-arrow": "^1.0.21",
52
+ "npm-run-all": "^4.1.5",
53
+ "tsx": "^4.21.0",
54
+ "typescript": "^5.9.3",
55
+ "vitest": "^4.0.18"
57
56
  }
58
57
  }
@@ -0,0 +1,3 @@
1
+ onlyBuiltDependencies:
2
+ - '@ast-grep/cli'
3
+ - esbuild
package/src/discord.ts ADDED
@@ -0,0 +1,46 @@
1
+ import { type } from "arktype";
2
+ import { ConfigureDiscord, Discord } from "./reporters/discord.js";
3
+
4
+ enum color {
5
+ green = "2404635",
6
+ red = "11606811",
7
+ yellow = "11644443",
8
+ }
9
+ const green = color.green;
10
+ const red = color.red;
11
+ const yellow = color.yellow;
12
+
13
+ const log = (
14
+ level: "debug" | "info" | "log" | "error",
15
+ color: color | string,
16
+ title: string,
17
+ message: string,
18
+ ) => {
19
+ const settings: Discord = {
20
+ color,
21
+ discord: true,
22
+ title,
23
+ };
24
+ console[level](message, settings);
25
+ };
26
+
27
+ const discord = {
28
+ configure: (webhookUrl: string) => {
29
+ type("string.url").assert(webhookUrl);
30
+ const settings: ConfigureDiscord = {
31
+ discord: true,
32
+ setWebhookUrl: webhookUrl,
33
+ };
34
+ console.debug(settings);
35
+ },
36
+ debug: (title: string, message: string, color: color | string = yellow) =>
37
+ log("debug", color, title, message),
38
+ error: (title: string, message: string, color: color | string = red) =>
39
+ log("error", color, title, message),
40
+ info: (title: string, message: string, color: color | string = green) =>
41
+ log("info", color, title, message),
42
+ log: (title: string, message: string, color: color | string = green) =>
43
+ log("log", color, title, message),
44
+ };
45
+
46
+ export { color, discord };
package/src/format.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { stringify } from "@ungap/structured-clone/json";
2
2
  import { type } from "arktype";
3
+ import { redact } from "./redact.js";
3
4
 
4
5
  const ErrorType = type({
5
6
  message: "string",
@@ -11,8 +12,8 @@ const formatItem = (item: unknown): string => {
11
12
 
12
13
  // Remove colors from strings
13
14
  if (typeof item === "string")
14
- // eslint-disable-next-line no-control-regex
15
- return item.replace(/\x1b\[[0-9;]*m/g, "");
15
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: stripping ANSI escape codes
16
+ return redact(item.replace(/\x1b\[[0-9;]*m/g, ""));
16
17
 
17
18
  // Check if this is an Error
18
19
  const error = ErrorType(item);
@@ -26,13 +27,12 @@ const formatItem = (item: unknown): string => {
26
27
  }
27
28
  })();
28
29
 
29
- return stringified.replace(/^'|'$/g, "");
30
+ return redact(stringified.replace(/^'|'$/g, ""));
30
31
  };
31
32
 
32
- const format = (items: unknown[]): string => {
33
- return Array.from(items)
33
+ const format = (items: unknown[]): string =>
34
+ Array.from(items)
34
35
  .map((item) => formatItem(item))
35
36
  .join(" ");
36
- };
37
37
 
38
38
  export { format };
package/src/index.ts CHANGED
@@ -1 +1,2 @@
1
+ export * from "./discord.js";
1
2
  export * from "./logging.js";
package/src/logging.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { createConsola, ConsolaOptions } from "consola";
1
+ import { ConsolaOptions, createConsola } from "consola";
2
2
  import DatadogReporter from "./reporters/datadog.js";
3
3
  import DiscordReporter from "./reporters/discord.js";
4
4
 
package/src/redact.ts ADDED
@@ -0,0 +1,89 @@
1
+ import { DeepRedact } from "@hackylabs/deep-redact/index.ts";
2
+ import { validateMnemonic } from "@scure/bip39";
3
+ import { wordlist } from "@scure/bip39/wordlists/english.js";
4
+
5
+ const replacement = "[REDACTED]";
6
+
7
+ /**
8
+ * Create a redactor that censors things that *look like secrets* inside
9
+ * strings:
10
+ * - long hex (optionally 0x-prefixed)
11
+ * - long base64 blobs
12
+ * - long base58 blobs
13
+ * - mnemonic seed phrases (validated via BIP39 english wordlist)
14
+ */
15
+
16
+ // Generic replacer: redact ALL matches within the string.
17
+ const replaceAllMatches = (value: string, pattern: RegExp) =>
18
+ value.replace(pattern, replacement);
19
+
20
+ // Mnemonic replacer: only redact if the captured phrase is a valid BIP39 mnemonic.
21
+ const replaceBip39MnemonicMatches = (value: string, pattern: RegExp) =>
22
+ value.replace(pattern, (match: string, phrase: string) => {
23
+ // Normalize spacing; validateMnemonic expects words separated by single spaces
24
+ const normalized = phrase.trim().toLowerCase().replace(/\s+/g, " ");
25
+ if (!validateMnemonic(normalized, wordlist)) return match;
26
+
27
+ // Replace only the phrase portion (preserves surrounding quotes/keywords if any)
28
+ return match.replace(phrase, replacement);
29
+ });
30
+
31
+ // --- Patterns ---
32
+ // 1) Hex secrets (API keys, hashes, tokens)
33
+ // - 32+ hex chars (optionally 0x)
34
+ // - word boundaries help avoid eating normal words
35
+ const HEX = /\b(?:0x)?[a-fA-F0-9]{32,}\b/g;
36
+
37
+ // 2) Base64 blobs (JWT parts, keys, encoded payloads)
38
+ // - requires a decent minimum length to reduce false positives
39
+ // - supports optional padding
40
+ const BASE64 =
41
+ /\b(?:[A-Za-z0-9+/]{4}){8,}(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?\b/g;
42
+
43
+ // 3) Base58 blobs (common in crypto keys/ids)
44
+ // - base58 alphabet excludes 0, O, I, l
45
+ // - require 32+ chars to reduce false positives
46
+ const BASE58 = /\b[1-9A-HJ-NP-Za-km-z]{32,}\b/g;
47
+
48
+ // 4) Mnemonic seed phrases
49
+ // We validate candidates with @scure/bip39 to avoid false positives.
50
+ // - 12 to 24 words separated by whitespace
51
+ const WORD = "[a-zA-Z]{2,8}";
52
+ const PHRASE_12_TO_24 = `(?:${WORD}\\s+){11,23}${WORD}`;
53
+
54
+ const MNEMONIC_WITH_KEYWORD = new RegExp(
55
+ // keyword then up to ~40 chars (like ":" or whitespace) then the phrase
56
+ String.raw`\b(?:mnemonic|seed|recovery\s+phrase|secret\s+phrase)\b[\s:=-]{0,40}(${PHRASE_12_TO_24})\b`,
57
+ "gi",
58
+ );
59
+
60
+ const MNEMONIC_QUOTED = new RegExp(
61
+ // quoted/bracketed phrase alone
62
+ String.raw`(?:["'(\[])\s*(${PHRASE_12_TO_24})\s*(?:["')\]])`,
63
+ "gi",
64
+ );
65
+
66
+ // NEW: Bare mnemonic phrases (no keyword, no quotes).
67
+ // This is what your console.log example is: just the phrase by itself.
68
+ // Validation keeps false-positives low.
69
+ const MNEMONIC_BARE = new RegExp(String.raw`\b(${PHRASE_12_TO_24})\b`, "gi");
70
+
71
+ const redactor = new DeepRedact({
72
+ // stringTests runs regex checks against string values (including flat strings)
73
+ // and lets us partially redact via replacer.
74
+ stringTests: [
75
+ { pattern: HEX, replacer: replaceAllMatches },
76
+ { pattern: BASE64, replacer: replaceAllMatches },
77
+ { pattern: BASE58, replacer: replaceAllMatches },
78
+ { pattern: MNEMONIC_WITH_KEYWORD, replacer: replaceBip39MnemonicMatches },
79
+ { pattern: MNEMONIC_QUOTED, replacer: replaceBip39MnemonicMatches },
80
+ { pattern: MNEMONIC_BARE, replacer: replaceBip39MnemonicMatches },
81
+ ],
82
+ replacement,
83
+ // serialise mainly matters for objects; keeping it false avoids surprises.
84
+ serialise: false,
85
+ });
86
+
87
+ const redact = (text: string) => redactor.redact(text) as string;
88
+
89
+ export { redact };
@@ -1,4 +1,4 @@
1
- import { LogObject, LogLevel } from "consola";
1
+ import { LogLevel, LogObject } from "consola";
2
2
  import { format } from "../format.js";
3
3
 
4
4
  const levelMap: Record<LogLevel, string> = {
@@ -1,7 +1,7 @@
1
- import { LogObject } from "consola";
2
- import { format } from "../format.js";
3
1
  import { type } from "arktype";
2
+ import { LogObject } from "consola";
4
3
  import got from "got";
4
+ import { format } from "../format.js";
5
5
 
6
6
  const Discord = type({
7
7
  discord: "true",
@@ -16,6 +16,7 @@ const ConfigureDiscord = type({
16
16
  });
17
17
 
18
18
  type Discord = typeof Discord.infer;
19
+ type ConfigureDiscord = typeof ConfigureDiscord.infer;
19
20
 
20
21
  const NullDiscord = { discord: false } as const;
21
22
 
@@ -74,7 +75,7 @@ const getDiscord = (args: unknown[]): [Discord | NullDiscord, unknown[]] => {
74
75
  const sendToDiscord = async (
75
76
  description: string,
76
77
  options: Discord,
77
- webhookUrl: string
78
+ webhookUrl: string,
78
79
  ) => {
79
80
  const data = {
80
81
  json: {
@@ -97,3 +98,4 @@ const sendToDiscord = async (
97
98
  };
98
99
 
99
100
  export default Reporter;
101
+ export { ConfigureDiscord, Discord };
@@ -0,0 +1,10 @@
1
+ declare module "@ungap/structured-clone/json" {
2
+ export function stringify(
3
+ value: unknown,
4
+ replacer?:
5
+ | ((this: unknown, key: string, value: unknown) => unknown)
6
+ | (number | string)[]
7
+ | null,
8
+ space?: string | number,
9
+ ): string;
10
+ }
package/tests/app.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { setupLogging } from "mynth-logger";
1
+ import { setupLogging } from "../src/index.js";
2
2
 
3
3
  const run = async () => {
4
4
  setupLogging();
@@ -8,6 +8,12 @@ const run = async () => {
8
8
  console.debug("Hello, this is a debug log");
9
9
  console.warn("Hello, this is a warn log");
10
10
  console.error("Hello, this is an error log");
11
+ console.log(
12
+ "This is my seed phrase: ordinary quality amount solid fox guess peasant merit midnight noodle final brown pretty stable six fox beef engage waste uniform evoke flat survey crane",
13
+ );
14
+ console.log(
15
+ "This is my private key: 4fa7614bdd07ab15b11d2365466536ba160c06050ade0888a3aa985a5522ab22",
16
+ );
11
17
 
12
18
  try {
13
19
  throw new Error("An Error was thrown");
@@ -36,7 +42,7 @@ const run = async () => {
36
42
  class MyObject {
37
43
  constructor(
38
44
  public a: string,
39
- public b: number
45
+ public b: number,
40
46
  ) {}
41
47
  }
42
48
 
@@ -44,7 +50,7 @@ const run = async () => {
44
50
  console.log(new MyObject("without a prefix", 100));
45
51
 
46
52
  try {
47
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
53
+ // biome-ignore lint/suspicious/noExplicitAny: ok
48
54
  throw (MyObject as any)("Error invoking", 1);
49
55
  } catch (error) {
50
56
  console.error(error);
@@ -1,19 +1,28 @@
1
- import test from "ava";
2
- import { setupLogging } from "mynth-logger";
1
+ import { beforeAll, describe, expect, it } from "vitest";
2
+ import { setupLogging } from "../src/index.js";
3
3
 
4
- test.before(() => {
4
+ beforeAll(() => {
5
5
  setupLogging();
6
6
  });
7
7
 
8
- test.serial("logs display to terminal", (t) => {
9
- console.debug("Hello ava");
10
- t.pass();
11
- });
8
+ describe("logging", () => {
9
+ it("logs display to terminal", () => {
10
+ console.debug("Hello vitest");
11
+ expect(true).toBe(true);
12
+ });
13
+
14
+ it("can log various objects", () => {
15
+ console.debug("Message", { message: true });
16
+ console.debug(undefined);
17
+ console.debug("bigint", 100n);
18
+ console.debug(false, true, "true", "false");
19
+ expect(true).toBe(true);
20
+ });
12
21
 
13
- test.serial("can log various objects", (t) => {
14
- console.debug("Message", { message: true });
15
- console.debug(undefined);
16
- console.debug("bigint", 100n);
17
- console.debug(false, true, "true", "false");
18
- t.pass();
22
+ it("redacts private key", () => {
23
+ console.info(
24
+ "a723cc20646a45cccd045b4bd13c0f73e505e6fc7f0c5006ab62e0410a2ef9ba",
25
+ );
26
+ expect(true).toBe(true);
27
+ });
19
28
  });
package/tsconfig.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "baseUrl": "./src",
4
- "target": "es2022",
5
- "module": "es2022",
6
- "moduleResolution": "bundler",
4
+ "target": "ESNext",
5
+ "module": "NodeNext",
6
+ "moduleResolution": "nodenext",
7
7
  "strict": true,
8
8
  "esModuleInterop": true,
9
9
  "skipLibCheck": true,
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: "node",
6
+ exclude: ["node_modules", "dist"],
7
+ },
8
+ });
package/.eslintignore DELETED
@@ -1 +0,0 @@
1
- dist
package/.eslintrc.json DELETED
@@ -1,69 +0,0 @@
1
- {
2
- "parser": "@typescript-eslint/parser",
3
- "extends": [
4
- "eslint:recommended",
5
- "plugin:ava/recommended",
6
- "plugin:@typescript-eslint/recommended",
7
- "plugin:import/errors",
8
- "plugin:import/warnings",
9
- "plugin:import/typescript"
10
- ],
11
- "plugins": [
12
- "ava",
13
- "import",
14
- "@typescript-eslint",
15
- "file-extension-in-import-ts"
16
- ],
17
- "settings": {
18
- "import/resolver": {
19
- "node": {
20
- "paths": ["src"]
21
- }
22
- }
23
- },
24
- "rules": {
25
- "padding-line-between-statements": [
26
- "error",
27
- { "blankLine": "always", "prev": "*", "next": "block" },
28
- { "blankLine": "always", "prev": "block", "next": "*" },
29
- { "blankLine": "always", "prev": "*", "next": "block-like" },
30
- { "blankLine": "always", "prev": "block-like", "next": "*" }
31
- ],
32
- "@typescript-eslint/no-unused-vars": [
33
- "warn",
34
- {
35
- "argsIgnorePattern": "^_",
36
- "varsIgnorePattern": "^_",
37
- "caughtErrorsIgnorePattern": "^_"
38
- }
39
- ],
40
- "@typescript-eslint/naming-convention": [
41
- "error",
42
- {
43
- "selector": ["parameter", "variable"],
44
- "leadingUnderscore": "forbid",
45
- "format": null
46
- },
47
- {
48
- "selector": "parameter",
49
- "leadingUnderscore": "require",
50
- "format": null,
51
- "modifiers": ["unused"]
52
- }
53
- ],
54
- "no-constant-condition": "off",
55
- "file-extension-in-import-ts/file-extension-in-import-ts": "error",
56
- "import/no-unresolved": ["error", { "ignore": ["\\.js$"] }],
57
- "no-restricted-imports": ["error", { "patterns": ["src/*"] }],
58
- "import/no-unresolved": "off"
59
- },
60
- "overrides": [
61
- {
62
- "files": ["tests/**/*.ts"],
63
- "rules": {
64
- "file-extension-in-import-ts/file-extension-in-import-ts": "off",
65
- "ava/no-ignored-test-files": "off"
66
- }
67
- }
68
- ]
69
- }
package/.prettierignore DELETED
@@ -1,2 +0,0 @@
1
- *.md
2
- dist
package/.prettierrc DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "printWidth": 80,
3
- "tabWidth": 2,
4
- "singleQuote": false,
5
- "trailingComma": "es5",
6
- "semi": true,
7
- "useTabs": false
8
- }
package/src/index.d.ts DELETED
@@ -1,3 +0,0 @@
1
- declare function setupLogging(): void;
2
-
3
- export { setupLogging };