@brianbuie/node-kit 0.15.0 → 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/README.md +209 -202
- package/dist/index.d.mts +52 -525
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +187 -230
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -5
- package/src/Cache.ts +4 -3
- package/src/Dir.test.ts +11 -0
- package/src/Dir.ts +24 -23
- package/src/Fetcher.ts +2 -2
- package/src/File.ts +70 -56
- package/src/Format.ts +5 -5
- package/src/Log.test.ts +16 -6
- package/src/Log.ts +58 -98
- package/src/TypeWriter.ts +15 -7
- package/src/snapshot.ts +3 -2
- package/src/timeout.ts +1 -1
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[],
|
|
145
|
-
const csvFile = new FileTypeCsv<T>(this.path);
|
|
146
|
-
if (rows) await csvFile.write(rows
|
|
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,
|
|
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(
|
|
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
|
-
|
|
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[]
|
|
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
|
-
|
|
290
|
-
if (val.toLowerCase() === '
|
|
291
|
-
if (val.
|
|
292
|
-
if (
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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('
|
|
12
|
-
|
|
13
|
-
|
|
21
|
+
it('Logs for production', () => {
|
|
22
|
+
Log.configure({ environment: 'production' });
|
|
23
|
+
allLevels();
|
|
14
24
|
});
|
|
15
25
|
});
|
package/src/Log.ts
CHANGED
|
@@ -1,126 +1,86 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import chalk, { type ChalkInstance } from 'chalk';
|
|
4
|
-
import { snapshot } from './snapshot.ts';
|
|
5
|
-
import { Format } from './Format.ts';
|
|
1
|
+
import { merge } from 'lodash-es';
|
|
2
|
+
import { default as pino, type Logger } from 'pino';
|
|
6
3
|
|
|
7
|
-
|
|
8
|
-
type Severity = 'DEFAULT' | 'DEBUG' | 'INFO' | 'NOTICE' | 'WARNING' | 'ERROR' | 'CRITICAL' | 'ALERT' | 'EMERGENCY';
|
|
4
|
+
export type LogDetails = Record<string, unknown>;
|
|
9
5
|
|
|
10
|
-
type
|
|
11
|
-
severity: Severity;
|
|
12
|
-
color: ChalkInstance;
|
|
13
|
-
};
|
|
6
|
+
export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
|
|
14
7
|
|
|
15
|
-
type
|
|
16
|
-
|
|
17
|
-
severity: Severity;
|
|
18
|
-
stack?: string;
|
|
19
|
-
details?: unknown[];
|
|
8
|
+
export type LogOptions = pino.LoggerOptions & {
|
|
9
|
+
environment?: string;
|
|
20
10
|
};
|
|
21
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Wrapper for [pino](https://github.com/pinojs/pino)
|
|
14
|
+
* Levels: fatal, error, warn, info, debug, trace
|
|
15
|
+
* Use `LOG_LEVL=info` to limit what's printed to console
|
|
16
|
+
* Use `Log.configure` to customize the pino instance
|
|
17
|
+
*/
|
|
22
18
|
export class Log {
|
|
23
|
-
static
|
|
24
|
-
|
|
25
|
-
// replaces details.stack with current stack trace, excluding this Log.getStack call
|
|
26
|
-
Error.captureStackTrace(details, Log.getStack);
|
|
27
|
-
// remove 'Error' on first line
|
|
28
|
-
return details.stack
|
|
29
|
-
.split('\n')
|
|
30
|
-
.map(l => l.trim())
|
|
31
|
-
.filter(l => l !== 'Error');
|
|
32
|
-
}
|
|
19
|
+
static #logger?: Logger;
|
|
20
|
+
static #options: LogOptions;
|
|
33
21
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
22
|
+
static createLogger(): Logger {
|
|
23
|
+
const isProduction = this.#options?.environment === 'production' || process.env.NODE_ENV === 'production';
|
|
24
|
+
const defaultOptions: LogOptions = {
|
|
25
|
+
level: process.env.LOG_LEVEL ?? (isProduction ? 'info' : 'debug'),
|
|
26
|
+
timestamp: pino.stdTimeFunctions.isoTime,
|
|
27
|
+
formatters: {
|
|
28
|
+
level(label: string) {
|
|
29
|
+
return { level: label };
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
transport: isProduction
|
|
33
|
+
? undefined
|
|
34
|
+
: {
|
|
35
|
+
target: 'pino-pretty',
|
|
36
|
+
options: {
|
|
37
|
+
colorize: true,
|
|
38
|
+
singleLine: true,
|
|
39
|
+
ignore: 'pid,hostname',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
return pino(merge(defaultOptions, this.#options));
|
|
41
44
|
}
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
static #toConsole(entry: Entry, color: ChalkInstance) {
|
|
47
|
-
if (entry.message) console.log(color(`${Format.date('h:m:s')} [${entry.severity}] ${entry.message}`));
|
|
48
|
-
entry.details?.forEach(detail => {
|
|
49
|
-
console.log(inspect(detail, { depth: 10, breakLength: 100, compact: true, colors: true }));
|
|
50
|
-
});
|
|
46
|
+
static configure(options: LogOptions = {}): void {
|
|
47
|
+
this.#options = options;
|
|
48
|
+
this.#logger = this.createLogger();
|
|
51
49
|
}
|
|
52
50
|
|
|
53
|
-
static #
|
|
54
|
-
|
|
55
|
-
const entry: Entry = { message, severity, details };
|
|
56
|
-
// https://cloud.google.com/run/docs/container-contract#env-vars
|
|
57
|
-
const isGcloud = process.env.K_SERVICE !== undefined || process.env.CLOUD_RUN_JOB !== undefined;
|
|
58
|
-
if (isGcloud) {
|
|
59
|
-
this.#toGcloud(entry);
|
|
60
|
-
} else {
|
|
61
|
-
this.#toConsole(entry, color);
|
|
62
|
-
}
|
|
63
|
-
return entry;
|
|
51
|
+
static #getLogger(): Logger {
|
|
52
|
+
return (this.#logger ??= this.createLogger());
|
|
64
53
|
}
|
|
65
54
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
// First argument is a string, use that as the message
|
|
72
|
-
if (typeof firstArg === 'string') {
|
|
73
|
-
return { message: firstArg, details: rest };
|
|
74
|
-
}
|
|
75
|
-
// First argument is an object with a `message` property
|
|
76
|
-
// @ts-ignore
|
|
77
|
-
if (isObjectLike(firstArg) && typeof firstArg['message'] === 'string') {
|
|
78
|
-
const { message, ...firstDetails } = firstArg as { message: string };
|
|
79
|
-
return { message, details: [firstDetails, ...rest] };
|
|
55
|
+
static #write(level: LogLevel, message: string, details?: LogDetails): void {
|
|
56
|
+
if (details === undefined) {
|
|
57
|
+
Log.#getLogger()[level](message);
|
|
58
|
+
} else {
|
|
59
|
+
Log.#getLogger()[level](details, message);
|
|
80
60
|
}
|
|
81
|
-
// No message found, log all args as details
|
|
82
|
-
return { details: input };
|
|
83
61
|
}
|
|
84
62
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
*/
|
|
88
|
-
static alert(...input: unknown[]) {
|
|
89
|
-
return this.#log({ severity: 'ALERT', color: chalk.bgRed }, ...input);
|
|
63
|
+
static trace(message: string, details?: LogDetails): void {
|
|
64
|
+
Log.#write('trace', message, details);
|
|
90
65
|
}
|
|
91
66
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
*/
|
|
95
|
-
static error(...input: unknown[]) {
|
|
96
|
-
return this.#log({ severity: 'ERROR', color: chalk.red }, ...input);
|
|
67
|
+
static debug(message: string, details?: LogDetails): void {
|
|
68
|
+
Log.#write('debug', message, details);
|
|
97
69
|
}
|
|
98
70
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
*/
|
|
102
|
-
static warn(...input: unknown[]) {
|
|
103
|
-
return this.#log({ severity: 'WARNING', color: chalk.yellow }, ...input);
|
|
71
|
+
static info(message: string, details?: LogDetails): void {
|
|
72
|
+
Log.#write('info', message, details);
|
|
104
73
|
}
|
|
105
74
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
*/
|
|
109
|
-
static notice(...input: unknown[]) {
|
|
110
|
-
return this.#log({ severity: 'NOTICE', color: chalk.cyan }, ...input);
|
|
75
|
+
static warn(message: string, details?: LogDetails): void {
|
|
76
|
+
Log.#write('warn', message, details);
|
|
111
77
|
}
|
|
112
78
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
*/
|
|
116
|
-
static info(...input: unknown[]) {
|
|
117
|
-
return this.#log({ severity: 'INFO', color: chalk.white }, ...input);
|
|
79
|
+
static error(message: string, details?: LogDetails): void {
|
|
80
|
+
Log.#write('error', message, details);
|
|
118
81
|
}
|
|
119
82
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
*/
|
|
123
|
-
static debug(...input: unknown[]) {
|
|
124
|
-
return this.#log({ severity: 'DEBUG', color: chalk.gray }, ...input);
|
|
83
|
+
static fatal(message: string, details?: LogDetails): void {
|
|
84
|
+
Log.#write('fatal', message, details);
|
|
125
85
|
}
|
|
126
86
|
}
|
package/src/TypeWriter.ts
CHANGED
|
@@ -2,12 +2,20 @@ import * as fs from 'node:fs';
|
|
|
2
2
|
import { merge } from 'lodash-es';
|
|
3
3
|
import * as qt from 'quicktype-core';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Wrapper for [quicktype-core](https://github.com/glideapps/quicktype)
|
|
7
|
+
* @example
|
|
8
|
+
* const group = new TypeWriter('Group');
|
|
9
|
+
* await types.addMember('Thing', [{ a: 1 }, { a: 2, b: 1 }]);
|
|
10
|
+
* await types.toFile();
|
|
11
|
+
* // type def for `Thing` saved in `types/Group.types.ts`
|
|
12
|
+
*/
|
|
5
13
|
export class TypeWriter {
|
|
6
|
-
moduleName;
|
|
14
|
+
moduleName: string;
|
|
7
15
|
input = qt.jsonInputForTargetLanguage('typescript');
|
|
8
|
-
outDir;
|
|
9
|
-
outFile;
|
|
10
|
-
qtSettings
|
|
16
|
+
outDir: string;
|
|
17
|
+
outFile: string;
|
|
18
|
+
qtSettings: Partial<qt.Options>;
|
|
11
19
|
|
|
12
20
|
constructor(moduleName: string, settings: { outDir?: string; outFile?: string } & Partial<qt.Options> = {}) {
|
|
13
21
|
this.moduleName = moduleName;
|
|
@@ -26,12 +34,12 @@ export class TypeWriter {
|
|
|
26
34
|
this.qtSettings = merge(defaultSettings, qtSettings);
|
|
27
35
|
}
|
|
28
36
|
|
|
29
|
-
async addMember(name: string, _samples: any[]) {
|
|
37
|
+
async addMember(name: string, _samples: any[]): Promise<void> {
|
|
30
38
|
const samples = _samples.map(s => (typeof s === 'string' ? s : JSON.stringify(s)));
|
|
31
39
|
await this.input.addSource({ name, samples });
|
|
32
40
|
}
|
|
33
41
|
|
|
34
|
-
async toString() {
|
|
42
|
+
async toString(): Promise<string> {
|
|
35
43
|
const inputData = new qt.InputData();
|
|
36
44
|
inputData.addInput(this.input);
|
|
37
45
|
const result = await qt.quicktype({
|
|
@@ -41,7 +49,7 @@ export class TypeWriter {
|
|
|
41
49
|
return result.lines.join('\n');
|
|
42
50
|
}
|
|
43
51
|
|
|
44
|
-
async toFile() {
|
|
52
|
+
async toFile(): Promise<void> {
|
|
45
53
|
const result = await this.toString();
|
|
46
54
|
fs.mkdirSync(this.outDir, { recursive: true });
|
|
47
55
|
fs.writeFileSync(`${this.outDir}/${this.outFile}`, result);
|
package/src/snapshot.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { isObjectLike } from 'lodash-es';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
* Functions are removed
|
|
4
|
+
* @deprecated
|
|
5
|
+
* Allows special objects (Error, Headers, Set) to be included in JSON.stringify output. Functions are removed.
|
|
6
|
+
* ⚠️ This is bad! Only use it for debugging!
|
|
6
7
|
*/
|
|
7
8
|
export function snapshot(i: unknown, max = 50, depth = 0): any {
|
|
8
9
|
if (Array.isArray(i)) {
|
package/src/timeout.ts
CHANGED