@brianbuie/node-kit 0.15.1 → 0.16.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brianbuie/node-kit",
3
- "version": "0.15.1",
3
+ "version": "0.16.0",
4
4
  "license": "Unlicense",
5
5
  "description": "Basic tools for quick node.js projects",
6
6
  "author": "Brian Buie <brian@buie.dev>",
@@ -30,19 +30,18 @@
30
30
  "engines": {
31
31
  "node": ">=24"
32
32
  },
33
- "peerDependencies": {
34
- "@types/node": "^24.9.1"
35
- },
36
33
  "dependencies": {
37
34
  "@types/lodash-es": "^4.17.12",
38
35
  "@types/mime-types": "^3.0.1",
39
- "chalk": "^5.6.2",
36
+ "@types/node": "^24.9.1",
40
37
  "date-fns": "^4.1.0",
41
38
  "extract-domain": "^5.0.2",
42
39
  "fast-csv": "^5.0.5",
43
40
  "format-duration": "^3.0.2",
44
41
  "lodash-es": "^4.17.23",
45
42
  "mime-types": "^3.0.2",
43
+ "pino": "^10.3.1",
44
+ "pino-pretty": "^13.1.3",
46
45
  "quicktype-core": "^23.2.6",
47
46
  "sanitize-filename": "^1.6.3"
48
47
  },
package/src/Cache.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { type Duration, isAfter, add } from 'date-fns';
2
+ import { type FileTypeJson } from './File.ts';
2
3
  import { Dir } from './Dir.ts';
3
4
 
4
5
  /**
@@ -7,12 +8,12 @@ import { Dir } from './Dir.ts';
7
8
  * so stale data can still be used if needed.
8
9
  */
9
10
  export class Cache<T> {
10
- file;
11
- ttl;
11
+ file: FileTypeJson<{ savedAt: string; data: T }>;
12
+ ttl: Duration;
12
13
 
13
14
  constructor(key: string, ttl: number | Duration, initialData?: T) {
14
15
  const dir = new Dir('.cache', { temp: true });
15
- this.file = dir.file(key).json<{ savedAt: string; data: T }>();
16
+ this.file = dir.file(key).json();
16
17
  this.ttl = typeof ttl === 'number' ? { minutes: ttl } : ttl;
17
18
  if (initialData) this.write(initialData);
18
19
  }
package/src/Dir.ts CHANGED
@@ -16,9 +16,9 @@ export type DirOptions = {
16
16
  * include `{ temp: true }` to enable the `.clear()` method
17
17
  */
18
18
  export class Dir {
19
- #inputPath;
19
+ #inputPath: string;
20
20
  #resolved?: string;
21
- isTemp;
21
+ isTemp: boolean;
22
22
 
23
23
  /**
24
24
  * @param path can be relative to workspace or absolute
@@ -31,7 +31,7 @@ export class Dir {
31
31
  /**
32
32
  * The path of the directory, which might not exist yet.
33
33
  */
34
- get pathUnsafe() {
34
+ get pathUnsafe(): string {
35
35
  if (this.#resolved) return this.#resolved;
36
36
  if (this.#inputPath[0] === '~') {
37
37
  if (process.env.HOME) {
@@ -47,7 +47,7 @@ export class Dir {
47
47
  * The path of this Dir instance. Created on file system the first time this property is read/used.
48
48
  * Safe to use the directory immediately, without calling mkdir separately.
49
49
  */
50
- get path() {
50
+ get path(): string {
51
51
  // avoids calling mkdir every time path is read
52
52
  if (!this.#resolved) {
53
53
  this.#resolved = this.pathUnsafe;
@@ -62,7 +62,7 @@ export class Dir {
62
62
  * const example = new Dir('/path/to/folder');
63
63
  * console.log(example.name); // "folder"
64
64
  */
65
- get name() {
65
+ get name(): string {
66
66
  return this.pathUnsafe.split(path.sep).at(-1)!;
67
67
  }
68
68
 
@@ -78,7 +78,7 @@ export class Dir {
78
78
  * const child = folder.dir('path/to/dir');
79
79
  * // child.path = '/path/to/cwd/example/path/to/dir'
80
80
  */
81
- dir(subPath = Format.date('ymd'), options: DirOptions = { temp: this.isTemp }) {
81
+ dir(subPath = Format.date('ymd'), options: DirOptions = { temp: this.isTemp }): Dir {
82
82
  return new (this.constructor as typeof Dir)(path.join(this.path, subPath), options) as this;
83
83
  }
84
84
 
@@ -86,11 +86,11 @@ export class Dir {
86
86
  * Creates a new temp directory inside current Dir
87
87
  * @param subPath joined with parent Dir's path to make new TempDir
88
88
  */
89
- tempDir(subPath?: string) {
89
+ tempDir(subPath?: string): Dir {
90
90
  return this.dir(subPath, { temp: true });
91
91
  }
92
92
 
93
- sanitize(filename: string) {
93
+ sanitize(filename: string): string {
94
94
  const notUrl = filename.replace('https://', '').replace('www.', '');
95
95
  return sanitizeFilename(notUrl, { replacement: '_' }).slice(-200);
96
96
  }
@@ -102,14 +102,14 @@ export class Dir {
102
102
  * const filepath = folder.resolve('file.json');
103
103
  * // '/path/to/example/file.json'
104
104
  */
105
- filepath(base: string) {
105
+ filepath(base: string): string {
106
106
  return path.resolve(this.path, this.sanitize(base));
107
107
  }
108
108
 
109
109
  /**
110
110
  * Create a new file in this directory. Filename defaults to `YYYYMMDD-HHMMSS` if not provided
111
111
  */
112
- file(base = Format.date('ymd-hms')) {
112
+ file(base = Format.date('ymd-hms')): File {
113
113
  return new File(this.filepath(base));
114
114
  }
115
115
 
@@ -125,28 +125,28 @@ export class Dir {
125
125
  /**
126
126
  * All subdirectories in this directory
127
127
  */
128
- get dirs() {
128
+ get dirs(): Dir[] {
129
129
  return this.contents.filter(f => f instanceof Dir);
130
130
  }
131
131
 
132
132
  /**
133
133
  * All files in this directory
134
134
  */
135
- get files() {
135
+ get files(): File[] {
136
136
  return this.contents.filter(f => f instanceof File);
137
137
  }
138
138
 
139
139
  /**
140
140
  * All files with MIME type that includes "video"
141
141
  */
142
- get videos() {
142
+ get videos(): File[] {
143
143
  return this.files.filter(f => f.type?.includes('video'));
144
144
  }
145
145
 
146
146
  /**
147
147
  * All files with MIME type that includes "image"
148
148
  */
149
- get images() {
149
+ get images(): File[] {
150
150
  return this.files.filter(f => f.type?.includes('image'));
151
151
  }
152
152
 
@@ -157,7 +157,7 @@ export class Dir {
157
157
  * const dataFiles = dataDir.jsonFiles.map(f => f.json<ExampleType>());
158
158
  * // dataFiles: FileTypeJson<ExampleType>[]
159
159
  */
160
- get jsonFiles() {
160
+ get jsonFiles(): File[] {
161
161
  return this.files.filter(f => f.ext === '.json');
162
162
  }
163
163
 
@@ -168,7 +168,7 @@ export class Dir {
168
168
  * const dataFiles = dataDir.ndjsonFiles.map(f => f.ndjson<ExampleType>());
169
169
  * // dataFiles: FileTypeNdjson<ExampleType>[]
170
170
  */
171
- get ndjsonFiles() {
171
+ get ndjsonFiles(): File[] {
172
172
  return this.files.filter(f => f.ext === '.ndjson');
173
173
  }
174
174
 
@@ -179,21 +179,21 @@ export class Dir {
179
179
  * const dataFiles = dataDir.csvFile.map(f => f.csv<ExampleType>());
180
180
  * // dataFiles: FileTypeCsv<ExampleType>[]
181
181
  */
182
- get csvFiles() {
182
+ get csvFiles(): File[] {
183
183
  return this.files.filter(f => f.ext === '.csv');
184
184
  }
185
185
 
186
186
  /**
187
187
  * All files with ext ".txt"
188
188
  */
189
- get textFiles() {
189
+ get txtFiles(): File[] {
190
190
  return this.files.filter(f => f.ext === '.txt');
191
191
  }
192
192
 
193
193
  /**
194
194
  * Deletes the contents of the directory. Only allowed if created with `temp` option set to `true` (or created with `dir.tempDir` method).
195
195
  */
196
- clear() {
196
+ clear(): void {
197
197
  if (!this.isTemp) throw new Error('Dir is not temporary');
198
198
  fs.rmSync(this.path, { recursive: true, force: true });
199
199
  fs.mkdirSync(this.path, { recursive: true });
package/src/Fetcher.ts CHANGED
@@ -22,7 +22,7 @@ export type FetchOptions = RequestInit & {
22
22
  * Includes basic methods for requesting and parsing responses
23
23
  */
24
24
  export class Fetcher {
25
- defaultOptions;
25
+ defaultOptions: FetchOptions;
26
26
 
27
27
  constructor(opts: FetchOptions = {}) {
28
28
  this.defaultOptions = {
@@ -65,7 +65,7 @@ export class Fetcher {
65
65
  /**
66
66
  * Merges options to get headers. Useful when extending the Fetcher class to add custom auth.
67
67
  */
68
- buildHeaders(route: Route, opts: FetchOptions = {}) {
68
+ buildHeaders(route: Route, opts: FetchOptions = {}): HeadersInit & Record<string, string> {
69
69
  const { headers } = merge({}, this.defaultOptions, opts);
70
70
  return headers || {};
71
71
  }
package/src/File.ts CHANGED
@@ -10,13 +10,13 @@ import { snapshot } from './snapshot.ts';
10
10
  * Represents a file on the file system. If the file doesn't exist, it is created the first time it is written to.
11
11
  */
12
12
  export class File {
13
- path;
14
- root;
15
- dir;
16
- base;
17
- name;
18
- ext;
19
- type;
13
+ path: string;
14
+ root: string;
15
+ dir: string;
16
+ base: string;
17
+ name: string;
18
+ ext: string;
19
+ type?: string;
20
20
 
21
21
  constructor(filepath: string) {
22
22
  this.path = this.#resolve(filepath);
@@ -29,7 +29,7 @@ export class File {
29
29
  this.type = mime.lookup(ext) || undefined;
30
30
  }
31
31
 
32
- #resolve(filepath: string) {
32
+ #resolve(filepath: string): string {
33
33
  if (filepath[0] === '~') {
34
34
  if (process.env.HOME) {
35
35
  return path.join(process.env.HOME, filepath.slice(1));
@@ -40,7 +40,7 @@ export class File {
40
40
  return path.resolve(filepath);
41
41
  }
42
42
 
43
- get exists() {
43
+ get exists(): boolean {
44
44
  return fs.existsSync(this.path);
45
45
  }
46
46
 
@@ -51,35 +51,35 @@ export class File {
51
51
  /**
52
52
  * Deletes the file if it exists
53
53
  */
54
- delete() {
54
+ delete(): void {
55
55
  fs.rmSync(this.path, { force: true });
56
56
  }
57
57
 
58
58
  /**
59
59
  * @returns the contents of the file as a string, or undefined if the file doesn't exist
60
60
  */
61
- read() {
61
+ read(): string | undefined {
62
62
  return this.exists ? fs.readFileSync(this.path, 'utf8') : undefined;
63
63
  }
64
64
 
65
65
  /**
66
66
  * @returns lines as strings, removes trailing '\n'
67
67
  */
68
- lines() {
68
+ lines(): string[] {
69
69
  const contents = (this.read() || '').split('\n');
70
70
  return contents.at(-1)?.length ? contents : contents.slice(0, contents.length - 1);
71
71
  }
72
72
 
73
- get readStream() {
73
+ get readStream(): fs.ReadStream | Readable {
74
74
  return this.exists ? fs.createReadStream(this.path) : Readable.from([]);
75
75
  }
76
76
 
77
- get writeStream() {
77
+ get writeStream(): fs.WriteStream {
78
78
  fs.mkdirSync(this.dir, { recursive: true });
79
79
  return fs.createWriteStream(this.path);
80
80
  }
81
81
 
82
- write(contents: string | ReadableStream) {
82
+ write(contents: string | ReadableStream): void | Promise<void> {
83
83
  fs.mkdirSync(this.dir, { recursive: true });
84
84
  if (typeof contents === 'string') return fs.writeFileSync(this.path, contents);
85
85
  if (contents instanceof ReadableStream) return finished(Readable.from(contents).pipe(this.writeStream));
@@ -90,7 +90,7 @@ export class File {
90
90
  * creates file if it doesn't exist, appends string or array of strings as new lines.
91
91
  * File always ends with '\n', so contents don't need to be read before appending
92
92
  */
93
- append(lines: string | string[]) {
93
+ append(lines: string | string[]): void {
94
94
  if (!this.exists) this.write('');
95
95
  const contents = Array.isArray(lines) ? lines.join('\n') : lines;
96
96
  fs.appendFileSync(this.path, contents + '\n');
@@ -106,7 +106,7 @@ export class File {
106
106
  * const file = new File('./data').json<object>({ key: 'val' }); // FileTypeJson<object>
107
107
  * file.write({ something: 'else' }) // ✅ data is typed as object
108
108
  */
109
- json<T>(contents?: T) {
109
+ json<T>(contents?: T): FileTypeJson<T> {
110
110
  return new FileTypeJson<T>(this.path, contents);
111
111
  }
112
112
 
@@ -114,14 +114,14 @@ export class File {
114
114
  * @example
115
115
  * const file = new File.json('data.json', { key: 'val' }); // FileTypeJson<{ key: string; }>
116
116
  */
117
- static get json() {
117
+ static get json(): typeof FileTypeJson {
118
118
  return FileTypeJson;
119
119
  }
120
120
 
121
121
  /**
122
122
  * @returns FileTypeNdjson adaptor for current File, adds '.ndjson' extension if not present.
123
123
  */
124
- ndjson<T extends object>(lines?: T | T[]) {
124
+ ndjson<T extends object>(lines?: T | T[]): FileTypeNdjson<T> {
125
125
  return new FileTypeNdjson<T>(this.path, lines);
126
126
  }
127
127
  /**
@@ -129,7 +129,7 @@ export class File {
129
129
  * const file = new File.ndjson('log', { key: 'val' }); // FileTypeNdjson<{ key: string; }>
130
130
  * console.log(file.path) // /path/to/cwd/log.ndjson
131
131
  */
132
- static get ndjson() {
132
+ static get ndjson(): typeof FileTypeNdjson {
133
133
  return FileTypeNdjson;
134
134
  }
135
135
 
@@ -141,13 +141,13 @@ export class File {
141
141
  * await file.write({ col: 'val' }); // ✅ Writes one row
142
142
  * await file.write([{ col: 'val2' }, { col: 'val3' }]); // ✅ Writes multiple rows
143
143
  */
144
- async csv<T extends object>(rows?: T[], keys?: (keyof T)[]) {
145
- const csvFile = new FileTypeCsv<T>(this.path);
146
- if (rows) await csvFile.write(rows, keys);
144
+ async csv<T extends object>(rows?: T[], options?: FileTypeCsvOptions<T>): Promise<FileTypeCsv<T>> {
145
+ const csvFile = new FileTypeCsv<T>(this.path, options);
146
+ if (rows) await csvFile.write(rows);
147
147
  return csvFile;
148
148
  }
149
149
 
150
- static get csv() {
150
+ static get csv(): typeof FileTypeCsv {
151
151
  return FileTypeCsv;
152
152
  }
153
153
  }
@@ -156,65 +156,65 @@ export class File {
156
156
  * A generic file adaptor, extended by specific file type implementations
157
157
  */
158
158
  export class FileType {
159
- file;
159
+ file: File;
160
160
 
161
161
  constructor(filepath: string, contents?: string) {
162
162
  this.file = new File(filepath);
163
163
  if (contents) this.file.write(contents);
164
164
  }
165
165
 
166
- get path() {
166
+ get path(): string {
167
167
  return this.file.path;
168
168
  }
169
169
 
170
- get root() {
170
+ get root(): string {
171
171
  return this.file.root;
172
172
  }
173
173
 
174
- get dir() {
174
+ get dir(): string {
175
175
  return this.file.dir;
176
176
  }
177
177
 
178
- get base() {
178
+ get base(): string {
179
179
  return this.file.base;
180
180
  }
181
181
 
182
- get name() {
182
+ get name(): string {
183
183
  return this.file.name;
184
184
  }
185
185
 
186
- get ext() {
186
+ get ext(): string {
187
187
  return this.file.ext;
188
188
  }
189
189
 
190
- get type() {
190
+ get type(): string | undefined {
191
191
  return this.file.type;
192
192
  }
193
193
 
194
- get exists() {
194
+ get exists(): boolean {
195
195
  return this.file.exists;
196
196
  }
197
197
 
198
- get stats() {
198
+ get stats(): Partial<fs.Stats> {
199
199
  return this.file.stats;
200
200
  }
201
201
 
202
- delete() {
202
+ delete(): void {
203
203
  this.file.delete();
204
204
  }
205
205
 
206
- get readStream() {
206
+ get readStream(): fs.ReadStream | Readable {
207
207
  return this.file.readStream;
208
208
  }
209
209
 
210
- get writeStream() {
210
+ get writeStream(): fs.WriteStream {
211
211
  return this.file.writeStream;
212
212
  }
213
213
  }
214
214
 
215
215
  /**
216
216
  * A .json file that maintains data type when reading/writing.
217
- * > ⚠️ This is mildly unsafe, important/foreign json files should be validated at runtime!
217
+ * > ⚠️ This is mildly unsafe, json files should be validated at runtime!
218
218
  * @example
219
219
  * const file = new FileTypeJson('./data', { key: 'val' }); // FileTypeJson<{ key: string; }>
220
220
  * console.log(file.path) // '/path/to/cwd/data.json'
@@ -229,13 +229,13 @@ export class FileTypeJson<T> extends FileType {
229
229
  if (contents) this.write(contents);
230
230
  }
231
231
 
232
- read() {
232
+ read(): T | undefined {
233
233
  const contents = this.file.read();
234
234
  return contents ? (JSON.parse(contents) as T) : undefined;
235
235
  }
236
236
 
237
- write(contents: T) {
238
- this.file.write(JSON.stringify(snapshot(contents), null, 2));
237
+ write(contents: T): void {
238
+ this.file.write(JSON.stringify(contents, null, 2));
239
239
  }
240
240
  }
241
241
 
@@ -249,32 +249,45 @@ export class FileTypeNdjson<T extends object> extends FileType {
249
249
  if (lines) this.append(lines);
250
250
  }
251
251
 
252
- append(lines: T | T[]) {
253
- this.file.append(
254
- Array.isArray(lines) ? lines.map(l => JSON.stringify(snapshot(l))) : JSON.stringify(snapshot(lines)),
255
- );
252
+ append(lines: T | T[]): void {
253
+ this.file.append(Array.isArray(lines) ? lines.map(l => JSON.stringify(l)) : JSON.stringify(lines));
256
254
  }
257
255
 
258
- lines() {
256
+ lines(): T[] {
259
257
  return this.file.lines().map(l => JSON.parse(l) as T);
260
258
  }
261
259
  }
262
260
 
263
261
  type Key<T extends object> = keyof T;
264
262
 
263
+ type FileTypeCsvOptions<Row extends object> = {
264
+ parseNumbers?: boolean;
265
+ parseBooleans?: boolean;
266
+ parseNulls?: boolean;
267
+ keys?: Key<Row>[];
268
+ };
269
+
265
270
  /**
266
271
  * Comma separated values (.csv).
267
272
  * Input rows as objects, keys are used as column headers
268
273
  */
269
274
  export class FileTypeCsv<Row extends object> extends FileType {
270
- constructor(filepath: string) {
275
+ options: FileTypeCsvOptions<Row>;
276
+
277
+ constructor(filepath: string, options: FileTypeCsvOptions<Row> = {}) {
271
278
  super(filepath.endsWith('.csv') ? filepath : filepath + '.csv');
279
+ this.options = {
280
+ parseNumbers: true,
281
+ parseBooleans: true,
282
+ parseNulls: true,
283
+ ...options,
284
+ };
272
285
  }
273
286
 
274
- async write(rows: Row[], keys?: Key<Row>[]) {
287
+ async write(rows: Row[]): Promise<void> {
275
288
  const headerSet = new Set<Key<Row>>();
276
- if (keys) {
277
- for (const key of keys) headerSet.add(key);
289
+ if (this.options.keys) {
290
+ for (const key of this.options.keys) headerSet.add(key);
278
291
  } else {
279
292
  for (const row of rows) {
280
293
  for (const key in row) headerSet.add(key);
@@ -285,15 +298,16 @@ export class FileTypeCsv<Row extends object> extends FileType {
285
298
  return finished(writeToStream(this.file.writeStream, [headers, ...outRows]));
286
299
  }
287
300
 
288
- #parseVal(val: string) {
289
- if (val.toLowerCase() === 'false') return false;
290
- if (val.toLowerCase() === 'true') return true;
291
- if (val.length === 0) return null;
292
- if (/^[\.0-9]+$/.test(val)) return Number(val);
301
+ #parseVal(val: string): string | number | boolean | null {
302
+ const { parseNumbers, parseBooleans, parseNulls } = this.options;
303
+ if (parseBooleans && val.toLowerCase() === 'false') return false;
304
+ if (parseBooleans && val.toLowerCase() === 'true') return true;
305
+ if (parseNulls && (val.length === 0 || val.toLowerCase() === 'null')) return null;
306
+ if (parseNumbers && /^[\.0-9]+$/.test(val)) return Number(val);
293
307
  return val;
294
308
  }
295
309
 
296
- async read() {
310
+ async read(): Promise<Row[]> {
297
311
  return new Promise<Row[]>((resolve, reject) => {
298
312
  const parsed: Row[] = [];
299
313
  parseStream(this.file.readStream, { headers: true })
package/src/Format.ts CHANGED
@@ -20,7 +20,7 @@ export class Format {
20
20
  static date(
21
21
  formatStr: 'iso' | 'ymd' | 'ymd-hm' | 'ymd-hms' | 'h:m:s' | string = 'iso',
22
22
  d: DateArg<Date> = new Date(),
23
- ) {
23
+ ): string {
24
24
  if (formatStr === 'iso') return formatISO(d);
25
25
  if (formatStr === 'ymd') return format(d, 'yyyyMMdd');
26
26
  if (formatStr === 'ymd-hm') return format(d, 'yyyyMMdd-HHmm');
@@ -32,11 +32,11 @@ export class Format {
32
32
  /**
33
33
  * Round a number to a specific set of places
34
34
  */
35
- static round(n: number, places = 0) {
35
+ static round(n: number, places = 0): string {
36
36
  return new Intl.NumberFormat('en-US', { maximumFractionDigits: places }).format(n);
37
37
  }
38
38
 
39
- static plural(amount: number, singular: string, multiple?: string) {
39
+ static plural(amount: number, singular: string, multiple?: string): string {
40
40
  return amount === 1 ? `${amount} ${singular}` : `${amount} ${multiple || singular + 's'}`;
41
41
  }
42
42
 
@@ -47,7 +47,7 @@ export class Format {
47
47
  * @see details on 'digital' format https://github.com/ungoldman/format-duration
48
48
  * @see waiting on `Intl.DurationFormat({ style: 'digital' })` types https://github.com/microsoft/TypeScript/issues/60608
49
49
  */
50
- static ms(ms: number, style?: 'digital') {
50
+ static ms(ms: number, style?: 'digital'): string {
51
51
  if (style === 'digital') return formatDuration(ms, { leading: true });
52
52
  if (ms < 1000) return `${this.round(ms)}ms`;
53
53
  const s = ms / 1000;
@@ -60,7 +60,7 @@ export class Format {
60
60
  return `${d}d ${h % 24}h`;
61
61
  }
62
62
 
63
- static bytes(b: number) {
63
+ static bytes(b: number): string {
64
64
  const labels = ['b', 'KB', 'MB', 'GB', 'TB'];
65
65
  let factor = 0;
66
66
  while (b >= 1024 && labels[factor + 1]) {
package/src/Log.test.ts CHANGED
@@ -3,13 +3,23 @@ import assert from 'node:assert';
3
3
  import { Log } from './Log.ts';
4
4
 
5
5
  describe('Log', () => {
6
- it('Uses first argument as message when string', () => {
7
- const result = Log.prepare('test', { something: 'else' });
8
- assert(result.message === 'test');
6
+ function allLevels() {
7
+ Log.trace('trace');
8
+ Log.debug('debug');
9
+ Log.info('info');
10
+ Log.warn('warn');
11
+ try {
12
+ // Log.error('error', { err: new Error('error') });
13
+ } catch (e) {}
14
+ Log.fatal('fatal');
15
+ }
16
+
17
+ it('Logs for dev', () => {
18
+ allLevels();
9
19
  });
10
20
 
11
- it('Uses message prop when provided', () => {
12
- const result = Log.prepare({ message: 'test', something: 'else' });
13
- assert(result.message === 'test');
21
+ it('Logs for production', () => {
22
+ Log.configure({ environment: 'production' });
23
+ allLevels();
14
24
  });
15
25
  });