@brianbuie/node-kit 0.12.5 → 0.14.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 +124 -44
- package/dist/index.d.mts +105 -21
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +127 -26
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
- package/src/Cache.ts +2 -2
- package/src/Dir.test.ts +32 -8
- package/src/Dir.ts +116 -19
- package/src/Fetcher.ts +4 -4
- package/src/File.test.ts +3 -3
- package/src/Log.ts +22 -3
- package/src/TypeWriter.ts +1 -1
- package/src/index.ts +1 -1
- package/src/snapshot.ts +4 -4
- package/src/timeout.ts +1 -1
- package/tsconfig.json +1 -0
- package/prettier.config.ts +0 -6
package/src/Dir.ts
CHANGED
|
@@ -3,52 +3,82 @@ import * as path from 'node:path';
|
|
|
3
3
|
import sanitizeFilename from 'sanitize-filename';
|
|
4
4
|
import { File } from './File.ts';
|
|
5
5
|
|
|
6
|
+
export type DirOptions = {
|
|
7
|
+
temp?: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
6
10
|
/**
|
|
7
11
|
* Reference to a specific directory with methods to create and list files.
|
|
8
|
-
*
|
|
9
|
-
*
|
|
12
|
+
* @param inputPath
|
|
13
|
+
* The path of the directory, created on file system the first time `.path` is read or any methods are used
|
|
14
|
+
* @param options
|
|
15
|
+
* include `{ temp: true }` to enable the `.clear()` method
|
|
10
16
|
*/
|
|
11
17
|
export class Dir {
|
|
12
18
|
#inputPath;
|
|
13
19
|
#resolved?: string;
|
|
20
|
+
isTemp;
|
|
14
21
|
|
|
15
22
|
/**
|
|
16
23
|
* @param path can be relative to workspace or absolute
|
|
17
24
|
*/
|
|
18
|
-
constructor(inputPath =
|
|
25
|
+
constructor(inputPath: string, options: DirOptions = {}) {
|
|
19
26
|
this.#inputPath = inputPath;
|
|
27
|
+
this.isTemp = Boolean(options.temp);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The path of the directory, which might not exist yet.
|
|
32
|
+
*/
|
|
33
|
+
get pathUnsafe() {
|
|
34
|
+
return this.#resolved || path.resolve(this.#inputPath);
|
|
20
35
|
}
|
|
21
36
|
|
|
22
37
|
/**
|
|
23
38
|
* The path of this Dir instance. Created on file system the first time this property is read/used.
|
|
39
|
+
* Safe to use the directory immediately, without calling mkdir separately.
|
|
24
40
|
*/
|
|
25
41
|
get path() {
|
|
42
|
+
// avoids calling mkdir every time path is read
|
|
26
43
|
if (!this.#resolved) {
|
|
27
|
-
this.#resolved =
|
|
44
|
+
this.#resolved = this.pathUnsafe;
|
|
28
45
|
fs.mkdirSync(this.#resolved, { recursive: true });
|
|
29
46
|
}
|
|
30
47
|
return this.#resolved;
|
|
31
48
|
}
|
|
32
49
|
|
|
50
|
+
/**
|
|
51
|
+
* The last segment in the path. Doesn't read this.path, to avoid creating directory on file system before it's needed.
|
|
52
|
+
* @example
|
|
53
|
+
* const example = new Dir('/path/to/folder');
|
|
54
|
+
* console.log(example.name); // "folder"
|
|
55
|
+
*/
|
|
56
|
+
get name() {
|
|
57
|
+
return this.pathUnsafe.split(path.sep).at(-1)!;
|
|
58
|
+
}
|
|
59
|
+
|
|
33
60
|
/**
|
|
34
61
|
* Create a new Dir inside the current Dir
|
|
35
|
-
* @param subPath
|
|
62
|
+
* @param subPath
|
|
63
|
+
* joined with parent Dir's path to make new Dir
|
|
64
|
+
* @param options
|
|
65
|
+
* include `{ temp: true }` to enable the `.clear()` method. If current Dir is temporary, child directories will also be temporary.
|
|
36
66
|
* @example
|
|
37
67
|
* const folder = new Dir('example');
|
|
38
68
|
* // folder.path = '/path/to/cwd/example'
|
|
39
69
|
* const child = folder.dir('path/to/dir');
|
|
40
70
|
* // child.path = '/path/to/cwd/example/path/to/dir'
|
|
41
71
|
*/
|
|
42
|
-
dir(subPath: string) {
|
|
43
|
-
return new Dir(path.join(this.path, subPath));
|
|
72
|
+
dir(subPath: string, options: DirOptions = { temp: this.isTemp }) {
|
|
73
|
+
return new (this.constructor as typeof Dir)(path.join(this.path, subPath), options) as this;
|
|
44
74
|
}
|
|
45
75
|
|
|
46
76
|
/**
|
|
47
|
-
* Creates a new
|
|
77
|
+
* Creates a new temp directory inside current Dir
|
|
48
78
|
* @param subPath joined with parent Dir's path to make new TempDir
|
|
49
79
|
*/
|
|
50
80
|
tempDir(subPath: string) {
|
|
51
|
-
return
|
|
81
|
+
return this.dir(subPath, { temp: true });
|
|
52
82
|
}
|
|
53
83
|
|
|
54
84
|
sanitize(filename: string) {
|
|
@@ -67,28 +97,95 @@ export class Dir {
|
|
|
67
97
|
return path.resolve(this.path, this.sanitize(base));
|
|
68
98
|
}
|
|
69
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Create a new file in this directory
|
|
102
|
+
*/
|
|
70
103
|
file(base: string) {
|
|
71
104
|
return new File(this.filepath(base));
|
|
72
105
|
}
|
|
73
106
|
|
|
107
|
+
/**
|
|
108
|
+
* All files and subdirectories in in this directory, returned as Dir and File instances
|
|
109
|
+
*/
|
|
110
|
+
get contents(): (Dir | File)[] {
|
|
111
|
+
return fs
|
|
112
|
+
.readdirSync(this.path)
|
|
113
|
+
.map(name => (fs.statSync(path.join(this.path, name)).isDirectory() ? this.dir(name) : this.file(name)));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* All subdirectories in this directory
|
|
118
|
+
*/
|
|
119
|
+
get dirs() {
|
|
120
|
+
return this.contents.filter(f => f instanceof Dir);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* All files in this directory
|
|
125
|
+
*/
|
|
74
126
|
get files() {
|
|
75
|
-
return
|
|
127
|
+
return this.contents.filter(f => f instanceof File);
|
|
76
128
|
}
|
|
77
|
-
}
|
|
78
129
|
|
|
79
|
-
/**
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
130
|
+
/**
|
|
131
|
+
* All files with MIME type that includes "video"
|
|
132
|
+
*/
|
|
133
|
+
get videos() {
|
|
134
|
+
return this.files.filter(f => f.type?.includes('video'));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* All files with MIME type that includes "image"
|
|
139
|
+
*/
|
|
140
|
+
get images() {
|
|
141
|
+
return this.files.filter(f => f.type?.includes('image'));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* All files with ext ".json"
|
|
146
|
+
* @example
|
|
147
|
+
* // Directory of json files with the same shape
|
|
148
|
+
* const dataFiles = dataDir.jsonFiles.map(f => f.json<ExampleType>());
|
|
149
|
+
* // dataFiles: FileTypeJson<ExampleType>[]
|
|
150
|
+
*/
|
|
151
|
+
get jsonFiles() {
|
|
152
|
+
return this.files.filter(f => f.ext === '.json');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* All files with ext ".ndjson"
|
|
157
|
+
* @example
|
|
158
|
+
* // Directory of ndjson files with the same shape
|
|
159
|
+
* const dataFiles = dataDir.ndjsonFiles.map(f => f.ndjson<ExampleType>());
|
|
160
|
+
* // dataFiles: FileTypeNdjson<ExampleType>[]
|
|
161
|
+
*/
|
|
162
|
+
get ndjsonFiles() {
|
|
163
|
+
return this.files.filter(f => f.ext === '.ndjson');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* All files with ext ".csv"
|
|
168
|
+
* @example
|
|
169
|
+
* // Directory of csv files with the same shape
|
|
170
|
+
* const dataFiles = dataDir.csvFile.map(f => f.csv<ExampleType>());
|
|
171
|
+
* // dataFiles: FileTypeCsv<ExampleType>[]
|
|
172
|
+
*/
|
|
173
|
+
get csvFiles() {
|
|
174
|
+
return this.files.filter(f => f.ext === '.csv');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* All files with ext ".txt"
|
|
179
|
+
*/
|
|
180
|
+
get textFiles() {
|
|
181
|
+
return this.files.filter(f => f.ext === '.txt');
|
|
86
182
|
}
|
|
87
183
|
|
|
88
184
|
/**
|
|
89
|
-
*
|
|
185
|
+
* Deletes the contents of the directory. Only allowed if created with `temp` option set to `true` (or created with `dir.tempDir` method).
|
|
90
186
|
*/
|
|
91
187
|
clear() {
|
|
188
|
+
if (!this.isTemp) throw new Error('Dir is not temporary');
|
|
92
189
|
fs.rmSync(this.path, { recursive: true, force: true });
|
|
93
190
|
fs.mkdirSync(this.path, { recursive: true });
|
|
94
191
|
}
|
package/src/Fetcher.ts
CHANGED
|
@@ -43,7 +43,7 @@ export class Fetcher {
|
|
|
43
43
|
Object.entries(mergedOptions.query || {}).forEach(([key, val]) => {
|
|
44
44
|
if (val === undefined) return;
|
|
45
45
|
if (Array.isArray(val)) {
|
|
46
|
-
val.forEach(
|
|
46
|
+
val.forEach(v => {
|
|
47
47
|
params.push([key, `${v}`]);
|
|
48
48
|
});
|
|
49
49
|
} else {
|
|
@@ -98,15 +98,15 @@ export class Fetcher {
|
|
|
98
98
|
attempt++;
|
|
99
99
|
const [req] = this.buildRequest(route, opts);
|
|
100
100
|
const res = await fetch(req)
|
|
101
|
-
.then(
|
|
101
|
+
.then(r => {
|
|
102
102
|
if (!r.ok) throw new Error(r.statusText);
|
|
103
103
|
return r;
|
|
104
104
|
})
|
|
105
|
-
.catch(async
|
|
105
|
+
.catch(async error => {
|
|
106
106
|
if (attempt < maxAttempts) {
|
|
107
107
|
const wait = attempt * 3000;
|
|
108
108
|
console.warn(`${req.method} ${req.url} (attempt ${attempt} of ${maxAttempts})`, error);
|
|
109
|
-
await new Promise(
|
|
109
|
+
await new Promise(resolve => setTimeout(resolve, wait));
|
|
110
110
|
} else {
|
|
111
111
|
throw new Error(error);
|
|
112
112
|
}
|
package/src/File.test.ts
CHANGED
|
@@ -17,7 +17,7 @@ const thing = {
|
|
|
17
17
|
describe('File', () => {
|
|
18
18
|
it('Handles request body as stream input', async () => {
|
|
19
19
|
const img = testDir.file('image.jpg');
|
|
20
|
-
await fetch('https://testingbot.com/free-online-tools/random-avatar/300').then(
|
|
20
|
+
await fetch('https://testingbot.com/free-online-tools/random-avatar/300').then(res => {
|
|
21
21
|
if (!res.body) throw new Error('No response body');
|
|
22
22
|
return img.write(res.body);
|
|
23
23
|
});
|
|
@@ -74,7 +74,7 @@ describe('FileTypeNdjson', () => {
|
|
|
74
74
|
assert(file.lines().length === 2);
|
|
75
75
|
file.append(thing);
|
|
76
76
|
assert(file.lines().length === 3);
|
|
77
|
-
file.lines().forEach(
|
|
77
|
+
file.lines().forEach(line => {
|
|
78
78
|
assert.deepStrictEqual(line, thing);
|
|
79
79
|
});
|
|
80
80
|
});
|
|
@@ -93,7 +93,7 @@ describe('FileTypeCsv', () => {
|
|
|
93
93
|
const things = [thing, thing, thing];
|
|
94
94
|
const file = await testDir.file('csv-data').csv(things);
|
|
95
95
|
const parsed = await file.read();
|
|
96
|
-
parsed.forEach(
|
|
96
|
+
parsed.forEach(row => {
|
|
97
97
|
assert.deepEqual(row, thing);
|
|
98
98
|
});
|
|
99
99
|
});
|
package/src/Log.ts
CHANGED
|
@@ -4,6 +4,7 @@ import chalk, { type ChalkInstance } from 'chalk';
|
|
|
4
4
|
import { snapshot } from './snapshot.ts';
|
|
5
5
|
import { Format } from './Format.ts';
|
|
6
6
|
|
|
7
|
+
// https://docs.cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#logseverity
|
|
7
8
|
type Severity = 'DEFAULT' | 'DEBUG' | 'INFO' | 'NOTICE' | 'WARNING' | 'ERROR' | 'CRITICAL' | 'ALERT' | 'EMERGENCY';
|
|
8
9
|
|
|
9
10
|
type Options = {
|
|
@@ -82,25 +83,43 @@ export class Log {
|
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
/**
|
|
85
|
-
*
|
|
86
|
+
* Events that require action or attention immediately
|
|
87
|
+
*/
|
|
88
|
+
static alert(...input: unknown[]) {
|
|
89
|
+
return this.#log({ severity: 'ALERT', color: chalk.bgRed }, ...input);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Events that cause problems
|
|
86
94
|
*/
|
|
87
95
|
static error(...input: unknown[]) {
|
|
88
|
-
|
|
89
|
-
throw new Error(message);
|
|
96
|
+
return this.#log({ severity: 'ERROR', color: chalk.red }, ...input);
|
|
90
97
|
}
|
|
91
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Events that might cause problems
|
|
101
|
+
*/
|
|
92
102
|
static warn(...input: unknown[]) {
|
|
93
103
|
return this.#log({ severity: 'WARNING', color: chalk.yellow }, ...input);
|
|
94
104
|
}
|
|
95
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Normal but significant events, such as start up, shut down, or a configuration change
|
|
108
|
+
*/
|
|
96
109
|
static notice(...input: unknown[]) {
|
|
97
110
|
return this.#log({ severity: 'NOTICE', color: chalk.cyan }, ...input);
|
|
98
111
|
}
|
|
99
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Routine information, such as ongoing status or performance
|
|
115
|
+
*/
|
|
100
116
|
static info(...input: unknown[]) {
|
|
101
117
|
return this.#log({ severity: 'INFO', color: chalk.white }, ...input);
|
|
102
118
|
}
|
|
103
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Debug or trace information
|
|
122
|
+
*/
|
|
104
123
|
static debug(...input: unknown[]) {
|
|
105
124
|
return this.#log({ severity: 'DEBUG', color: chalk.gray }, ...input);
|
|
106
125
|
}
|
package/src/TypeWriter.ts
CHANGED
|
@@ -25,7 +25,7 @@ export class TypeWriter {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
async addMember(name: string, _samples: any[]) {
|
|
28
|
-
const samples = _samples.map(
|
|
28
|
+
const samples = _samples.map(s => (typeof s === 'string' ? s : JSON.stringify(s)));
|
|
29
29
|
await this.input.addSource({ name, samples });
|
|
30
30
|
}
|
|
31
31
|
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { Dir,
|
|
1
|
+
export { Dir, type DirOptions, temp, cwd } from './Dir.ts';
|
|
2
2
|
export { Cache } from './Cache.ts';
|
|
3
3
|
export { Fetcher, type Route, type Query, type FetchOptions } from './Fetcher.ts';
|
|
4
4
|
export { File, FileType, FileTypeJson, FileTypeNdjson, FileTypeCsv } from './File.ts';
|
package/src/snapshot.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { isObjectLike } from 'lodash-es';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Allows special objects (Error, Headers, Set) to be included in JSON.stringify output
|
|
5
|
-
*
|
|
4
|
+
* Allows special objects (Error, Headers, Set) to be included in JSON.stringify output.
|
|
5
|
+
* Functions are removed
|
|
6
6
|
*/
|
|
7
7
|
export function snapshot(i: unknown, max = 50, depth = 0): any {
|
|
8
8
|
if (Array.isArray(i)) {
|
|
9
9
|
if (depth === max) return [];
|
|
10
|
-
return i.map(
|
|
10
|
+
return i.map(c => snapshot(c, max, depth + 1));
|
|
11
11
|
}
|
|
12
12
|
if (typeof i === 'function') return undefined;
|
|
13
13
|
if (!isObjectLike(i)) return i;
|
|
@@ -32,7 +32,7 @@ export function snapshot(i: unknown, max = 50, depth = 0): any {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
// Get Non-enumberable, own properties
|
|
35
|
-
Object.getOwnPropertyNames(obj).forEach(
|
|
35
|
+
Object.getOwnPropertyNames(obj).forEach(key => {
|
|
36
36
|
output[key] = snapshot(obj[key], max, depth + 1);
|
|
37
37
|
});
|
|
38
38
|
|
package/src/timeout.ts
CHANGED
package/tsconfig.json
CHANGED