@api3/commons 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/LICENSE +21 -0
- package/README.md +92 -0
- package/dist/eslint/internal.d.ts +15 -0
- package/dist/eslint/internal.d.ts.map +1 -0
- package/dist/eslint/internal.js +24 -0
- package/dist/eslint/internal.js.map +1 -0
- package/dist/eslint/jest.d.ts +28 -0
- package/dist/eslint/jest.d.ts.map +1 -0
- package/dist/eslint/jest.js +37 -0
- package/dist/eslint/jest.js.map +1 -0
- package/dist/eslint/next-js.d.ts +27 -0
- package/dist/eslint/next-js.d.ts.map +1 -0
- package/dist/eslint/next-js.js +33 -0
- package/dist/eslint/next-js.js.map +1 -0
- package/dist/eslint/react.d.ts +91 -0
- package/dist/eslint/react.d.ts.map +1 -0
- package/dist/eslint/react.js +86 -0
- package/dist/eslint/react.js.map +1 -0
- package/dist/eslint/universal.d.ts +177 -0
- package/dist/eslint/universal.d.ts.map +1 -0
- package/dist/eslint/universal.js +220 -0
- package/dist/eslint/universal.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/logger/index.d.ts +34 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/logger/index.js +85 -0
- package/dist/logger/index.js.map +1 -0
- package/package.json +70 -0
- package/src/eslint/README.md +92 -0
- package/src/eslint/internal.js +24 -0
- package/src/eslint/jest.js +35 -0
- package/src/eslint/next-js.js +31 -0
- package/src/eslint/react.js +89 -0
- package/src/eslint/universal.js +227 -0
- package/src/index.ts +2 -0
- package/src/logger/README.md +44 -0
- package/src/logger/index.ts +110 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
2
|
+
const { merge } = require('lodash');
|
|
3
|
+
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
5
|
+
const { universalRestrictedImportsConfig, universalImportOrderConfig } = require('./internal');
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
parser: '@typescript-eslint/parser',
|
|
9
|
+
parserOptions: {
|
|
10
|
+
ecmaVersion: 2022, // Enable parsing of modern ECMAScript features.
|
|
11
|
+
ecmaFeatures: {
|
|
12
|
+
jsx: true, // Support JSX syntax.
|
|
13
|
+
},
|
|
14
|
+
sourceType: 'module', // Enable ES6 import/export syntax.
|
|
15
|
+
},
|
|
16
|
+
settings: {
|
|
17
|
+
react: {
|
|
18
|
+
version: 'detect', // Automatically detect the version of React.
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
env: {
|
|
22
|
+
node: true,
|
|
23
|
+
browser: true,
|
|
24
|
+
},
|
|
25
|
+
extends: ['plugin:react/all', 'plugin:react-hooks/recommended'],
|
|
26
|
+
plugins: ['react', '@typescript-eslint', 'import', 'lodash'],
|
|
27
|
+
rules: {
|
|
28
|
+
'import/order': [
|
|
29
|
+
'error',
|
|
30
|
+
merge({}, universalImportOrderConfig, {
|
|
31
|
+
// Prioritize react imports.
|
|
32
|
+
pathGroups: [
|
|
33
|
+
{
|
|
34
|
+
pattern: 'react',
|
|
35
|
+
group: 'builtin',
|
|
36
|
+
position: 'before',
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
}),
|
|
40
|
+
],
|
|
41
|
+
/* Overrides for "react" plugin */
|
|
42
|
+
'react/destructuring-assignment': ['error', 'always', { destructureInSignature: 'ignore' }],
|
|
43
|
+
'react/forbid-component-props': ['error', { forbid: [] }],
|
|
44
|
+
'react/forbid-dom-props': ['error', { forbid: [] }],
|
|
45
|
+
'react/function-component-definition': 'off', // Arrow functions are managed by other rules.
|
|
46
|
+
'react/jsx-curly-brace-presence': ['error', { props: 'never', children: 'never', propElementValues: 'always' }],
|
|
47
|
+
'react/jsx-curly-newline': 'off', // Conflicts with prettier.
|
|
48
|
+
'react/jsx-filename-extension': 'off', // We use .tsx extension.
|
|
49
|
+
'react/jsx-handler-names': 'off',
|
|
50
|
+
'react/jsx-indent': 'off', // Conflicts with prettier.
|
|
51
|
+
'react/jsx-indent-props': 'off', // Conflicts with prettier.
|
|
52
|
+
'react/jsx-max-depth': 'off', // Conflicts with prettier.
|
|
53
|
+
'react/jsx-max-props-per-line': 'off', // Conflicts with prettier.
|
|
54
|
+
'react/jsx-newline': 'off', // Conflicts with prettier.
|
|
55
|
+
'react/jsx-no-bind': 'off', // Conflicts with prettier.
|
|
56
|
+
'react/jsx-no-leaked-render': 'off',
|
|
57
|
+
'react/jsx-no-literals': 'off',
|
|
58
|
+
'react/jsx-one-expression-per-line': 'off', // Conflicts with prettier.
|
|
59
|
+
'react/jsx-props-no-spreading': 'off',
|
|
60
|
+
'react/jsx-sort-props': 'off',
|
|
61
|
+
'react/no-multi-comp': 'off',
|
|
62
|
+
'react/no-unescaped-entities': 'off',
|
|
63
|
+
'react/no-unused-prop-types': 'off',
|
|
64
|
+
'react/prefer-read-only-props': 'off',
|
|
65
|
+
'react/prop-types': 'off',
|
|
66
|
+
'react/react-in-jsx-scope': 'off',
|
|
67
|
+
'react/require-default-props': 'off',
|
|
68
|
+
'react/self-closing-comp': ['error', { component: true, html: true }],
|
|
69
|
+
'react/void-dom-elements-no-children': 'error',
|
|
70
|
+
|
|
71
|
+
/* Overrides for "@typescript-eslint" plugin */
|
|
72
|
+
'@typescript-eslint/no-restricted-imports': [
|
|
73
|
+
'error',
|
|
74
|
+
merge({}, universalRestrictedImportsConfig, {
|
|
75
|
+
paths: [
|
|
76
|
+
{
|
|
77
|
+
name: 'react',
|
|
78
|
+
importNames: ['default'],
|
|
79
|
+
message:
|
|
80
|
+
'Starting from React version 17, there is no need to globally import React. Use named imports for specific React APIs.',
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
}),
|
|
84
|
+
],
|
|
85
|
+
|
|
86
|
+
/* Overrides for "lodash" plugin */
|
|
87
|
+
'lodash/import-scope': ['error', 'method'],
|
|
88
|
+
},
|
|
89
|
+
};
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
2
|
+
const { universalRestrictedImportsConfig, universalImportOrderConfig } = require('./internal');
|
|
3
|
+
|
|
4
|
+
module.exports = {
|
|
5
|
+
parser: '@typescript-eslint/parser',
|
|
6
|
+
parserOptions: {
|
|
7
|
+
ecmaVersion: 2022, // Allows for the parsing of modern ECMAScript features.
|
|
8
|
+
sourceType: 'module', // Allows for the use of imports.
|
|
9
|
+
},
|
|
10
|
+
env: {
|
|
11
|
+
node: true,
|
|
12
|
+
browser: true,
|
|
13
|
+
},
|
|
14
|
+
extends: [
|
|
15
|
+
'eslint:recommended',
|
|
16
|
+
'plugin:@typescript-eslint/all',
|
|
17
|
+
'plugin:import/recommended',
|
|
18
|
+
'plugin:import/typescript',
|
|
19
|
+
'plugin:unicorn/recommended',
|
|
20
|
+
'plugin:promise/recommended',
|
|
21
|
+
'plugin:lodash/recommended',
|
|
22
|
+
],
|
|
23
|
+
plugins: [
|
|
24
|
+
'@typescript-eslint',
|
|
25
|
+
'deprecation',
|
|
26
|
+
'functional',
|
|
27
|
+
'prefer-arrow',
|
|
28
|
+
'unicorn',
|
|
29
|
+
'check-file',
|
|
30
|
+
'import',
|
|
31
|
+
'lodash',
|
|
32
|
+
],
|
|
33
|
+
rules: {
|
|
34
|
+
/* Rule definitions and overrides for standard ESLint rules */
|
|
35
|
+
camelcase: 'error',
|
|
36
|
+
curly: ['error', 'multi-line', 'consistent'],
|
|
37
|
+
eqeqeq: 'error',
|
|
38
|
+
'no-await-in-loop': 'off', // Too restrictive, often false yields to more verbose code.
|
|
39
|
+
'no-console': ['error', { allow: ['info', 'groupCollapsed', 'groupEnd'] }],
|
|
40
|
+
'no-constant-condition': 'off', // Writing a "while(true)"" loop is often the most readable way to express the intent.
|
|
41
|
+
'no-fallthrough': 'off', // Does not work well with typescript exhaustive enums.
|
|
42
|
+
'no-inline-comments': 'off',
|
|
43
|
+
'no-lonely-if': 'error',
|
|
44
|
+
'no-nested-ternary': 'error',
|
|
45
|
+
'no-new-wrappers': 'error',
|
|
46
|
+
'no-return-await': 'off', // Superceded by @typescript-eslint/return-await.
|
|
47
|
+
'no-unexpected-multiline': 'off', // Conflicts with prettier.
|
|
48
|
+
'no-unused-expressions': 'off', // Superceded by @typescript-eslint/no-unused-expressions.
|
|
49
|
+
'object-shorthand': 'error',
|
|
50
|
+
'prefer-destructuring': [
|
|
51
|
+
'error',
|
|
52
|
+
{
|
|
53
|
+
array: false, // For arrays it is often confusing to use destructuring.
|
|
54
|
+
object: true,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
enforceForRenamedProperties: false,
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
'prefer-exponentiation-operator': 'error',
|
|
61
|
+
'prefer-named-capture-group': 'error',
|
|
62
|
+
'prefer-object-spread': 'error',
|
|
63
|
+
'prefer-spread': 'error',
|
|
64
|
+
'prefer-template': 'error',
|
|
65
|
+
radix: 'error',
|
|
66
|
+
// Sort keys does not have a fixer, but sorting lines can be trivially done by IDE (or some plugin). This rule has a
|
|
67
|
+
// nice configuration option "minKeys" which can specify how many keys should be present in an object before sorting
|
|
68
|
+
// is enforced. This is useful for small objects, where sorting is not necessary. Also, it allows creating groups
|
|
69
|
+
// (separated by newlines) which are sorted independently.
|
|
70
|
+
'sort-keys': ['error', 'asc', { caseSensitive: true, natural: true, minKeys: 10, allowLineSeparatedGroups: true }],
|
|
71
|
+
'spaced-comment': [
|
|
72
|
+
'error',
|
|
73
|
+
'always',
|
|
74
|
+
{
|
|
75
|
+
line: {
|
|
76
|
+
markers: ['/'],
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
|
|
81
|
+
/* Rules to setup enforcement of arrow functions over regular functions */
|
|
82
|
+
'prefer-arrow/prefer-arrow-functions': [
|
|
83
|
+
'error',
|
|
84
|
+
{
|
|
85
|
+
disallowPrototype: true,
|
|
86
|
+
singleReturnOnly: false,
|
|
87
|
+
classPropertiesAllowed: false,
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
|
|
91
|
+
/* Rules to enforce kebab-case folder structure */
|
|
92
|
+
'check-file/folder-naming-convention': [
|
|
93
|
+
'error',
|
|
94
|
+
{
|
|
95
|
+
'**/': 'KEBAB_CASE',
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
'unicorn/filename-case': [
|
|
99
|
+
'error',
|
|
100
|
+
{
|
|
101
|
+
case: 'kebabCase',
|
|
102
|
+
ignore: [],
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
|
|
106
|
+
/* Rule overrides for "unicorn" plugin */
|
|
107
|
+
'unicorn/consistent-function-scoping': 'off', // Disabling due to the rule's constraints conflicting with established patterns, especially in test suites where local helper or mocking functions are prevalent and do not necessitate exports.
|
|
108
|
+
'unicorn/no-abusive-eslint-disable': 'off', // Already covered by different ruleset.
|
|
109
|
+
'unicorn/no-array-callback-reference': 'error', // Explicitly turned on, because it was initially disabled and "point free" notation was enforced using "functional/prefer-tacit". That said, the point free pattern is dangerous in JS. See: https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-array-callback-reference.md.
|
|
110
|
+
'unicorn/no-array-reduce': 'off', // We are OK with using reduce occasionally, but I agree with the author that the code using reduce can easily get complex.
|
|
111
|
+
'unicorn/no-nested-ternary': 'off', // This rule is smarter than the standard ESLint rule, but conflicts with prettier so it needs to be turned off. Nested ternaries are very unreadable so it's OK if all of them are flagged.
|
|
112
|
+
'unicorn/no-null': 'off', // We use both null and undefined for representing three state objects. We could use a string union instead, but using combination of null and undefined is less verbose.
|
|
113
|
+
'unicorn/no-useless-undefined': ['error', { checkArguments: false }], // We need to disable "checkArguments", because if a function expects a value of type "T | undefined" the undefined value needs to be passed explicitly.
|
|
114
|
+
'unicorn/prefer-module': 'off', // We use CJS for configuration files and tests. There is no rush to migrate to ESM and the configuration files are probably not yet ready for ESM yet.
|
|
115
|
+
'unicorn/prevent-abbreviations': 'off', // This rule reports many false positives and leads to more verbose code.
|
|
116
|
+
|
|
117
|
+
/* Rule overrides for "import" plugin */
|
|
118
|
+
'import/no-default-export': 'error',
|
|
119
|
+
'import/no-duplicates': 'error',
|
|
120
|
+
'import/no-named-as-default': 'off',
|
|
121
|
+
'import/no-unresolved': 'off', // Does not accept exports keyword. See: https://github.com/import-js/eslint-plugin-import/issues/1810.
|
|
122
|
+
'import/order': ['error', universalImportOrderConfig],
|
|
123
|
+
|
|
124
|
+
/* Rule overrides for "@typescript-eslint" plugin */
|
|
125
|
+
'@typescript-eslint/comma-dangle': 'off', // Conflicts with prettier.
|
|
126
|
+
'@typescript-eslint/consistent-type-exports': [
|
|
127
|
+
'error',
|
|
128
|
+
{
|
|
129
|
+
fixMixedExportsWithInlineTypeSpecifier: true,
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
'@typescript-eslint/consistent-type-imports': [
|
|
133
|
+
'error',
|
|
134
|
+
{
|
|
135
|
+
prefer: 'type-imports',
|
|
136
|
+
disallowTypeAnnotations: false, // It is quite common to do so. See: https://typescript-eslint.io/rules/consistent-type-imports/#disallowtypeannotations.
|
|
137
|
+
fixStyle: 'inline-type-imports',
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
'@typescript-eslint/explicit-function-return-type': 'off', // Prefer inferring types to explicit annotations.
|
|
141
|
+
'@typescript-eslint/explicit-module-boundary-types': 'off', // We export lot of functions in order to test them. Typing them all is not a good idea.
|
|
142
|
+
'@typescript-eslint/indent': 'off', // Conflicts with prettier.
|
|
143
|
+
'@typescript-eslint/init-declarations': 'off', // Too restrictive, TS is able to infer if value is initialized or not. This pattern does not work with declaring a variable and then initializing it conditionally (or later).
|
|
144
|
+
'@typescript-eslint/lines-around-comment': 'off', // Do not agree with this rule.
|
|
145
|
+
'@typescript-eslint/member-delimiter-style': 'off', // Conflicts with prettier.
|
|
146
|
+
'@typescript-eslint/member-ordering': 'off', // Does not have a fixer. Also, sometimes it's beneficial to group related members together.
|
|
147
|
+
'@typescript-eslint/naming-convention': 'off',
|
|
148
|
+
'@typescript-eslint/no-confusing-void-expression': [
|
|
149
|
+
'error',
|
|
150
|
+
{
|
|
151
|
+
ignoreArrowShorthand: true, // See: https://typescript-eslint.io/rules/no-confusing-void-expression/#ignorearrowshorthand.
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
'@typescript-eslint/no-empty-function': 'off', // Too restrictive, often false yields to more verbose code.
|
|
155
|
+
'@typescript-eslint/no-explicit-any': 'off', // Using "any" is sometimes necessary.
|
|
156
|
+
'@typescript-eslint/no-extra-parens': 'off', // Conflicts with prettier.
|
|
157
|
+
'@typescript-eslint/no-magic-numbers': 'off', // Too restrictive. There is often nothing wrong with inlining numbers.
|
|
158
|
+
'@typescript-eslint/no-misused-promises': [
|
|
159
|
+
'error',
|
|
160
|
+
{
|
|
161
|
+
checksVoidReturn: {
|
|
162
|
+
arguments: false, // It's common to pass async function where one expects a function returning void.
|
|
163
|
+
attributes: false, // It's common to pass async function where one expects a function returning void.
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
'@typescript-eslint/no-non-null-assertion': 'off', // Too restrictive. The inference is often not powerful enough or there is not enough context.
|
|
168
|
+
'@typescript-eslint/no-require-imports': 'off', // We use a similar rule called "@typescript-eslint/no-var-imports" which bans require imports alltogether.
|
|
169
|
+
'@typescript-eslint/no-restricted-imports': ['error', universalRestrictedImportsConfig],
|
|
170
|
+
'@typescript-eslint/no-shadow': 'off', // It is often valid to shadow variable (e.g. for the lack of a better name).
|
|
171
|
+
'@typescript-eslint/no-type-alias': 'off', // The rule is deprecated and "@typescript-eslint/consistent-type-definitions" is used instead.
|
|
172
|
+
'@typescript-eslint/no-unnecessary-condition': 'off', // Suggests removing useful conditionals for index signatures and arrays. Would require enabling additional strict checks in TS, which is hard to ask.
|
|
173
|
+
'@typescript-eslint/no-unsafe-argument': 'off', // Too restrictive, often false yields to more verbose code.
|
|
174
|
+
'@typescript-eslint/no-unsafe-assignment': 'off', // Too restrictive, often false yields to more verbose code.
|
|
175
|
+
'@typescript-eslint/no-unsafe-member-access': 'off', // Too restrictive, often false yields to more verbose code.
|
|
176
|
+
'@typescript-eslint/no-unsafe-return': 'off', // Too restrictive, often false yields to more verbose code.
|
|
177
|
+
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_', vars: 'all' }],
|
|
178
|
+
'@typescript-eslint/no-use-before-define': 'off', // Too restrictive, does not have a fixer and is not important.
|
|
179
|
+
'@typescript-eslint/object-curly-spacing': 'off', // Conflicts with prettier.
|
|
180
|
+
'@typescript-eslint/prefer-nullish-coalescing': [
|
|
181
|
+
'error',
|
|
182
|
+
{
|
|
183
|
+
ignoreConditionalTests: true, // Its more intuitive to use logical operators in conditionals.
|
|
184
|
+
},
|
|
185
|
+
],
|
|
186
|
+
'@typescript-eslint/prefer-readonly-parameter-types': 'off', // Too restrictive, often false yields to more verbose code.
|
|
187
|
+
'@typescript-eslint/quotes': 'off', // Conflicts with prettier.
|
|
188
|
+
'@typescript-eslint/semi': 'off', // Conflicts with prettier.
|
|
189
|
+
'@typescript-eslint/space-before-function-paren': 'off', // Conflicts with prettier.
|
|
190
|
+
'@typescript-eslint/strict-boolean-expressions': 'off', // While the rule is reasonable, it is often convenient and intended to just check whether the value is not null or undefined. Enabling this rule would make the code more verbose. See: https://typescript-eslint.io/rules/strict-boolean-expressions/
|
|
191
|
+
'@typescript-eslint/unbound-method': 'off', // Reports issues for common patterns in tests (e.g. "expect(logger.warn)..."). Often the issue yields false positives.
|
|
192
|
+
|
|
193
|
+
/* Rule overrides for "functional" plugin */
|
|
194
|
+
'functional/no-classes': 'error', // Functions are all we need.
|
|
195
|
+
'functional/no-promise-reject': 'error',
|
|
196
|
+
'functional/no-try-statements': 'error', // Use go utils instead.
|
|
197
|
+
'functional/prefer-tacit': 'off', // The rule is dangerous. See: https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-array-callback-reference.md.
|
|
198
|
+
|
|
199
|
+
/* Overrides for "lodash" plugin */
|
|
200
|
+
'lodash/import-scope': ['error', 'member'], // We prefer member imports in node.js code. This is not recommended for FE projects, because lodash can't be tree shaken (written in CJS not ESM). This rule should be overridden for FE projects (and we do so in React ruleset).
|
|
201
|
+
'lodash/path-style': 'off', // Can potentially trigger TS errors. Both variants have use cases when they are more readable.
|
|
202
|
+
'lodash/prefer-lodash-method': 'off', // Disagree with this rule. Using the native method is often simpler.
|
|
203
|
+
|
|
204
|
+
/* Rule overrides for other plugins and rules */
|
|
205
|
+
// This rule unfortunately does not detect deprecated properties. See:
|
|
206
|
+
// https://github.com/gund/eslint-plugin-deprecation/issues/13/
|
|
207
|
+
'deprecation/deprecation': 'error',
|
|
208
|
+
},
|
|
209
|
+
overrides: [
|
|
210
|
+
// Overrides for Jest tests.
|
|
211
|
+
{
|
|
212
|
+
files: ['**/*.test.ts', '**/*.test.tsx'],
|
|
213
|
+
env: {
|
|
214
|
+
jest: true,
|
|
215
|
+
},
|
|
216
|
+
plugins: ['jest'],
|
|
217
|
+
extends: ['plugin:jest/all', 'plugin:jest-formatting/recommended'],
|
|
218
|
+
rules: {
|
|
219
|
+
'jest/prefer-expect-assertions': 'off', // Enabling this option would result in excessively verbose code.
|
|
220
|
+
'jest/prefer-each': 'off', // We prefer the traditional for loop.
|
|
221
|
+
'jest/require-top-level-describe': 'off', // This is not a good pattern. There is nothing wrong with having multiple top level describe blocks or tests.
|
|
222
|
+
'jest/max-expects': 'off', // It's good to limit the number of expects in a test, but this rule is too strict.
|
|
223
|
+
'jest/valid-title': 'off', // Prevents using "<function-name>.name" as a test name
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Logger
|
|
2
|
+
|
|
3
|
+
> Configurable BE-only logger.
|
|
4
|
+
|
|
5
|
+
Backend-only logger for Node.js packages based on Winston logger.
|
|
6
|
+
|
|
7
|
+
## Getting started
|
|
8
|
+
|
|
9
|
+
1. Install `zod` which is a peer dependency of this module. Zod is used for validating the logger configuration.
|
|
10
|
+
2. Use `createLogger` function to create a logger instance.
|
|
11
|
+
|
|
12
|
+
## Configuration
|
|
13
|
+
|
|
14
|
+
Logger configuration allows specifying log format, styling and level.
|
|
15
|
+
|
|
16
|
+
### `enabled`
|
|
17
|
+
|
|
18
|
+
Enables or disables logging. Options:
|
|
19
|
+
|
|
20
|
+
- `true` - Enables logging.
|
|
21
|
+
- `false` - Disables logging.
|
|
22
|
+
|
|
23
|
+
### `format`
|
|
24
|
+
|
|
25
|
+
- `json` - Specifies JSON log format. This is suitable when running in production and streaming logs to other services.
|
|
26
|
+
- `pretty` - Logs are formatted in a human-friendly "pretty" way. Ideal, when running the service locally and in
|
|
27
|
+
development.
|
|
28
|
+
|
|
29
|
+
### `colorize`
|
|
30
|
+
|
|
31
|
+
Enables or disables colors in the log output. Options:
|
|
32
|
+
|
|
33
|
+
- `true` - Enables colors in the log output. The output has special color setting characters that are parseable by CLI.
|
|
34
|
+
Recommended when running locally and in development.
|
|
35
|
+
- `false` - Disables colors in the log output. Recommended for production.
|
|
36
|
+
|
|
37
|
+
### `minLevel`
|
|
38
|
+
|
|
39
|
+
Defines the minimum level of logs. Logs with smaller level (severity) will be silenced. Options:
|
|
40
|
+
|
|
41
|
+
- `debug` - Enables all logs.
|
|
42
|
+
- `info` - Enables logs with level `info`, `warn` and `error`.
|
|
43
|
+
- `warn` - Enables logs with level `warn` and `error`.
|
|
44
|
+
- `error` - Enables logs with level `error`.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import winston from 'winston';
|
|
2
|
+
import { consoleFormat } from 'winston-console-format';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
|
|
5
|
+
export const logFormatSchema = z.union([z.literal('json'), z.literal('pretty')]);
|
|
6
|
+
|
|
7
|
+
export type LogType = z.infer<typeof logFormatSchema>;
|
|
8
|
+
|
|
9
|
+
export const logLevelSchema = z.union([z.literal('debug'), z.literal('info'), z.literal('warn'), z.literal('error')]);
|
|
10
|
+
|
|
11
|
+
export type LogLevel = z.infer<typeof logLevelSchema>;
|
|
12
|
+
|
|
13
|
+
export const logConfigSchema = z.object({
|
|
14
|
+
colorize: z.boolean(),
|
|
15
|
+
enabled: z.boolean(),
|
|
16
|
+
format: logFormatSchema,
|
|
17
|
+
minLevel: logLevelSchema,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export type LogConfig = z.infer<typeof logConfigSchema>;
|
|
21
|
+
|
|
22
|
+
const createConsoleTransport = (config: LogConfig) => {
|
|
23
|
+
const { colorize, enabled, format } = config;
|
|
24
|
+
|
|
25
|
+
if (!enabled) {
|
|
26
|
+
return new winston.transports.Console({ silent: true });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
switch (format) {
|
|
30
|
+
case 'json': {
|
|
31
|
+
return new winston.transports.Console({ format: winston.format.json() });
|
|
32
|
+
}
|
|
33
|
+
case 'pretty': {
|
|
34
|
+
const formats = [
|
|
35
|
+
colorize ? winston.format.colorize({ all: true }) : null,
|
|
36
|
+
winston.format.padLevels(),
|
|
37
|
+
consoleFormat({
|
|
38
|
+
showMeta: true,
|
|
39
|
+
metaStrip: [],
|
|
40
|
+
inspectOptions: {
|
|
41
|
+
depth: Number.POSITIVE_INFINITY,
|
|
42
|
+
colors: colorize,
|
|
43
|
+
maxArrayLength: Number.POSITIVE_INFINITY,
|
|
44
|
+
breakLength: 120,
|
|
45
|
+
compact: Number.POSITIVE_INFINITY,
|
|
46
|
+
},
|
|
47
|
+
}),
|
|
48
|
+
].filter(Boolean) as winston.Logform.Format[];
|
|
49
|
+
|
|
50
|
+
return new winston.transports.Console({
|
|
51
|
+
format: winston.format.combine(...formats),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const createBaseLogger = (config: LogConfig) => {
|
|
58
|
+
const { enabled, minLevel } = config;
|
|
59
|
+
|
|
60
|
+
return winston.createLogger({
|
|
61
|
+
level: minLevel,
|
|
62
|
+
// This format is recommended by the "winston-console-format" package.
|
|
63
|
+
format: winston.format.combine(
|
|
64
|
+
winston.format.timestamp(),
|
|
65
|
+
winston.format.ms(),
|
|
66
|
+
winston.format.errors({ stack: true }),
|
|
67
|
+
winston.format.splat(),
|
|
68
|
+
winston.format.json()
|
|
69
|
+
),
|
|
70
|
+
silent: !enabled,
|
|
71
|
+
exitOnError: false,
|
|
72
|
+
transports: [createConsoleTransport(config)],
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export type LogContext = Record<string, any>;
|
|
77
|
+
|
|
78
|
+
export interface Logger {
|
|
79
|
+
debug: (message: string, context?: LogContext) => void;
|
|
80
|
+
info: (message: string, context?: LogContext) => void;
|
|
81
|
+
warn: (message: string, context?: LogContext) => void;
|
|
82
|
+
error: ((message: string, context?: LogContext) => void) &
|
|
83
|
+
((message: string, error: Error, context?: LogContext) => void);
|
|
84
|
+
child: (options: { name: string }) => Logger;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Winston by default merges content of `context` among the rest of the fields for the JSON format.
|
|
88
|
+
// That's causing an override of fields `name` and `message` if they are present.
|
|
89
|
+
const wrapper = (logger: Logger): Logger => {
|
|
90
|
+
return {
|
|
91
|
+
debug: (message, context) => logger.debug(message, context ? { context } : undefined),
|
|
92
|
+
info: (message, context) => logger.info(message, context ? { context } : undefined),
|
|
93
|
+
warn: (message, context) => logger.warn(message, context ? { context } : undefined),
|
|
94
|
+
// We need to handle both overloads of the `error` function
|
|
95
|
+
error: (message, errorOrContext, context) => {
|
|
96
|
+
// eslint-disable-next-line lodash/prefer-lodash-typecheck
|
|
97
|
+
if (errorOrContext instanceof Error) {
|
|
98
|
+
logger.error(message, errorOrContext, context ? { context } : undefined);
|
|
99
|
+
} else {
|
|
100
|
+
logger.error(message, errorOrContext ? { context: errorOrContext } : undefined);
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
child: (options) => wrapper(logger.child(options)),
|
|
104
|
+
} as Logger;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export const createLogger = (config: LogConfig) => {
|
|
108
|
+
// Ensure that the configuration is valid according to the schema.
|
|
109
|
+
return wrapper(createBaseLogger(logConfigSchema.parse(config)));
|
|
110
|
+
};
|