5etools-utils 0.1.24 → 0.2.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/lib/TestJson.js +107 -18
- package/lib/TestJsonWorker.js +57 -0
- package/lib/WorkerList.js +47 -0
- package/package.json +8 -7
package/lib/TestJson.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
|
+
import * as os from "os";
|
|
1
2
|
import * as path from "path";
|
|
3
|
+
import * as url from "url";
|
|
4
|
+
import {Worker} from "worker_threads";
|
|
2
5
|
|
|
3
6
|
import Ajv2020 from "ajv/dist/2020.js";
|
|
4
|
-
import addFormats from "ajv-formats"
|
|
7
|
+
import addFormats from "ajv-formats";
|
|
5
8
|
import * as jsonSourceMap from "json-source-map";
|
|
6
9
|
|
|
7
10
|
import * as uf from "./UtilFs.js";
|
|
8
11
|
import * as um from "./UtilMisc.js";
|
|
12
|
+
import {WorkerList, Deferred} from "./WorkerList.js";
|
|
13
|
+
|
|
14
|
+
const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
|
|
9
15
|
|
|
10
16
|
const _IS_SORT_RESULTS = !process.env.VET_TEST_JSON_RESULTS_UNSORTED;
|
|
11
17
|
const _IS_TRIM_RESULTS = !process.env.VET_TEST_JSON_RESULTS_UNTRIMMED;
|
|
@@ -65,8 +71,10 @@ class JsonTester {
|
|
|
65
71
|
|
|
66
72
|
_getFileErrors ({filePath}) { return `${filePath}\n${JSON.stringify(this._ajv.errors, null, 2)}`; }
|
|
67
73
|
|
|
68
|
-
|
|
69
|
-
|
|
74
|
+
_getErrors ({filePath, data}) {
|
|
75
|
+
const out = {errors: [], errorsFull: []};
|
|
76
|
+
|
|
77
|
+
if (!process.env.CI) out.errorsFull.push(this._getFileErrors({filePath}));
|
|
70
78
|
|
|
71
79
|
// Sort the deepest errors to the bottom, as these are the ones we're most likely to be the ones we care about
|
|
72
80
|
// manually checking.
|
|
@@ -91,7 +99,9 @@ class JsonTester {
|
|
|
91
99
|
|
|
92
100
|
const error = this._getFileErrors({filePath});
|
|
93
101
|
um.error(this._tagLog, error);
|
|
94
|
-
errors.push(error);
|
|
102
|
+
out.errors.push(error);
|
|
103
|
+
|
|
104
|
+
return out;
|
|
95
105
|
}
|
|
96
106
|
|
|
97
107
|
_doLoadSchema () {
|
|
@@ -111,18 +121,26 @@ class JsonTester {
|
|
|
111
121
|
this._isSchemaLoaded = true;
|
|
112
122
|
}
|
|
113
123
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
um.info(this._tagLog, `\tValidating "${filePath}"...`);
|
|
124
|
+
getFileErrors ({filePath} = {}) {
|
|
125
|
+
this._doLoadSchema();
|
|
117
126
|
|
|
118
|
-
|
|
119
|
-
this._addImplicits(data);
|
|
120
|
-
const valid = this._ajv.validate(this._fnGetSchemaId(filePath), data);
|
|
127
|
+
um.info(this._tagLog, `\tValidating "${filePath}"...`);
|
|
121
128
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
129
|
+
const data = uf.readJSON(filePath);
|
|
130
|
+
this._addImplicits(data);
|
|
131
|
+
|
|
132
|
+
const isValid = this._ajv.validate(this._fnGetSchemaId(filePath), data);
|
|
133
|
+
if (isValid) return {};
|
|
134
|
+
|
|
135
|
+
return this._getErrors({filePath, data});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
_doRunOnDir ({isFailFast, dir, errors, errorsFull, ...opts} = {}) {
|
|
139
|
+
for (const filePath of uf.listJsonFiles(dir, opts)) {
|
|
140
|
+
const {errors: errorsFile = [], errorsFull: errorsFullFile = []} = this.getFileErrors({filePath});
|
|
141
|
+
errors.push(...errorsFile);
|
|
142
|
+
errorsFull.push(...errorsFullFile);
|
|
143
|
+
if (isFailFast && (errorsFile.length || errorsFullFile.length)) break;
|
|
126
144
|
}
|
|
127
145
|
}
|
|
128
146
|
|
|
@@ -134,8 +152,6 @@ class JsonTester {
|
|
|
134
152
|
getErrors (dir, opts = {}) {
|
|
135
153
|
um.info(this._tagLog, `Validating JSON against schema`);
|
|
136
154
|
|
|
137
|
-
this._doLoadSchema();
|
|
138
|
-
|
|
139
155
|
const errors = [];
|
|
140
156
|
const errorsFull = [];
|
|
141
157
|
|
|
@@ -147,8 +163,6 @@ class JsonTester {
|
|
|
147
163
|
getErrorsOnDirs ({isFailFast = false} = {}) {
|
|
148
164
|
um.info(this._tagLog, `Validating JSON against schema`);
|
|
149
165
|
|
|
150
|
-
this._doLoadSchema();
|
|
151
|
-
|
|
152
166
|
const errors = [];
|
|
153
167
|
const errorsFull = [];
|
|
154
168
|
|
|
@@ -159,6 +173,81 @@ class JsonTester {
|
|
|
159
173
|
|
|
160
174
|
return {errors, errorsFull};
|
|
161
175
|
}
|
|
176
|
+
|
|
177
|
+
async pGetErrorsOnDirsWorkers ({isFailFast = false} = {}) {
|
|
178
|
+
um.info(this._tagLog, `Validating JSON against schema`);
|
|
179
|
+
|
|
180
|
+
const cntWorkers = Math.max(1, os.cpus().length - 1);
|
|
181
|
+
|
|
182
|
+
const errors = [];
|
|
183
|
+
const errorsFull = [];
|
|
184
|
+
|
|
185
|
+
const fileQueue = [];
|
|
186
|
+
|
|
187
|
+
uf.runOnDirs((dir) => {
|
|
188
|
+
fileQueue.push(...uf.listJsonFiles(dir));
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const workerList = new WorkerList();
|
|
192
|
+
|
|
193
|
+
const workers = [...new Array(cntWorkers)]
|
|
194
|
+
.map(() => {
|
|
195
|
+
// Relative `Worker` paths do not function in packages, so give an exact path
|
|
196
|
+
const worker = new Worker(path.join(__dirname, "TestJsonWorker.js"));
|
|
197
|
+
|
|
198
|
+
worker.on("message", (msg) => {
|
|
199
|
+
switch (msg.type) {
|
|
200
|
+
case "ready":
|
|
201
|
+
case "done": {
|
|
202
|
+
if (msg.payload.isError) {
|
|
203
|
+
errors.push(...msg.payload.errors);
|
|
204
|
+
errorsFull.push(...msg.payload.errorsFull);
|
|
205
|
+
|
|
206
|
+
if (isFailFast) workers.forEach(worker => worker.postMessage({type: "cancel"}));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (worker.dIsActive) worker.dIsActive.resolve();
|
|
210
|
+
workerList.add(worker);
|
|
211
|
+
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
worker.on("error", e => console.error(e));
|
|
218
|
+
|
|
219
|
+
worker.postMessage({
|
|
220
|
+
type: "init",
|
|
221
|
+
payload: {
|
|
222
|
+
dirSchema: this._dirSchema,
|
|
223
|
+
tagLog: this._tagLog,
|
|
224
|
+
strFnGetSchemaId: this._fnGetSchemaId.toString(),
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
return worker;
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
while (fileQueue.length) {
|
|
232
|
+
if (isFailFast && errors.length) break;
|
|
233
|
+
|
|
234
|
+
const file = fileQueue.shift();
|
|
235
|
+
const worker = await workerList.get();
|
|
236
|
+
|
|
237
|
+
worker.dIsActive = new Deferred();
|
|
238
|
+
worker.postMessage({
|
|
239
|
+
type: "work",
|
|
240
|
+
payload: {
|
|
241
|
+
file,
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
await Promise.all(workers.map(it => it.dIsActive.promise));
|
|
247
|
+
await Promise.all(workers.map(it => it.terminate()));
|
|
248
|
+
|
|
249
|
+
return {errors, errorsFull};
|
|
250
|
+
}
|
|
162
251
|
}
|
|
163
252
|
|
|
164
253
|
export {JsonTester, LOG_TAG};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import {isMainThread, parentPort} from "worker_threads";
|
|
2
|
+
import {JsonTester} from "./TestJson.js";
|
|
3
|
+
|
|
4
|
+
if (isMainThread) throw new Error(`Worker must not be started in main thread!`);
|
|
5
|
+
|
|
6
|
+
let jsonTester;
|
|
7
|
+
let isCancelled = false;
|
|
8
|
+
|
|
9
|
+
parentPort
|
|
10
|
+
.on("message", msg => {
|
|
11
|
+
switch (msg.type) {
|
|
12
|
+
case "init": {
|
|
13
|
+
const {dirSchema, tagLog, strFnGetSchemaId} = msg.payload;
|
|
14
|
+
|
|
15
|
+
jsonTester = new JsonTester({
|
|
16
|
+
dirSchema,
|
|
17
|
+
tagLog,
|
|
18
|
+
fnGetSchemaId: eval(strFnGetSchemaId), // eslint-disable-line no-eval
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
parentPort.postMessage({
|
|
22
|
+
type: "ready",
|
|
23
|
+
payload: {},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
case "cancel": {
|
|
30
|
+
isCancelled = true;
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
case "work": {
|
|
35
|
+
if (isCancelled) {
|
|
36
|
+
parentPort.postMessage({
|
|
37
|
+
type: "done",
|
|
38
|
+
payload: {},
|
|
39
|
+
});
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const {errors = [], errorsFull = []} = jsonTester.getFileErrors({filePath: msg.payload.file});
|
|
44
|
+
|
|
45
|
+
parentPort.postMessage({
|
|
46
|
+
type: "done",
|
|
47
|
+
payload: {
|
|
48
|
+
isError: !!(errors.length || errorsFull.length),
|
|
49
|
+
errors,
|
|
50
|
+
errorsFull,
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
class Deferred {
|
|
2
|
+
constructor () {
|
|
3
|
+
this._resolve = null;
|
|
4
|
+
this._reject = null;
|
|
5
|
+
this._promise = new Promise((resolve, reject) => {
|
|
6
|
+
this._resolve = resolve;
|
|
7
|
+
this._reject = reject;
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
get resolve () { return this._resolve; }
|
|
12
|
+
get reject () { return this._reject; }
|
|
13
|
+
get promise () { return this._promise; }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class WorkerList {
|
|
17
|
+
/**
|
|
18
|
+
* @param {Array} workers
|
|
19
|
+
*/
|
|
20
|
+
constructor ({workers = null} = {}) {
|
|
21
|
+
this._workers = workers || [];
|
|
22
|
+
this._deferredQueue = [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
add (worker) {
|
|
26
|
+
this._workers.push(worker);
|
|
27
|
+
|
|
28
|
+
while (this._deferredQueue.length && this._workers.length) {
|
|
29
|
+
const d = this._deferredQueue.shift();
|
|
30
|
+
d.resolve(this._workers.shift());
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get () {
|
|
35
|
+
if (this._workers.length) {
|
|
36
|
+
return Promise.resolve(this._workers.shift());
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const d = new Deferred();
|
|
40
|
+
this._deferredQueue.push(d);
|
|
41
|
+
return d.promise;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get cntAvailableWorkers () { return this._workers.length; }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export {Deferred, WorkerList};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "5etools-utils",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Shared utilities for the 5etools ecosystem.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/Api.js",
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
"lib": "./lib"
|
|
12
12
|
},
|
|
13
13
|
"scripts": {
|
|
14
|
-
"test": "npm run test:js",
|
|
15
|
-
"test:js": "
|
|
16
|
-
"lint:js": "eslint lib --fix"
|
|
14
|
+
"test": "npm run lint:js && npm run test:js",
|
|
15
|
+
"test:js": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
16
|
+
"lint:js": "eslint lib test --fix"
|
|
17
17
|
},
|
|
18
18
|
"repository": {
|
|
19
19
|
"type": "git",
|
|
@@ -27,10 +27,11 @@
|
|
|
27
27
|
"homepage": "https://github.com/TheGiddyLimit/5etools-utils#readme",
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"ajv": "^8.11.2",
|
|
30
|
-
"
|
|
31
|
-
"
|
|
30
|
+
"ajv-formats": "^2.1.1",
|
|
31
|
+
"json-source-map": "^0.6.1"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"eslint": "^8.11.0"
|
|
34
|
+
"eslint": "^8.11.0",
|
|
35
|
+
"jest": "^29.3.1"
|
|
35
36
|
}
|
|
36
37
|
}
|