5etools-utils 0.10.30 → 0.11.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/BrewTester.js CHANGED
@@ -20,6 +20,7 @@ class _BrewTesterJson {
20
20
  const opts = program.opts();
21
21
 
22
22
  const jsonTester = new JsonTester({mode, tagLog: this._LOG_TAG, fnGetSchemaId: () => "homebrew.json"});
23
+ await jsonTester.pInit();
23
24
 
24
25
  let results;
25
26
  if (program.args[0]) {
@@ -32,7 +33,7 @@ class _BrewTesterJson {
32
33
  results = await jsonTester.pGetErrorsOnDirsWorkers({isFailFast: !this._IS_FAIL_SLOW});
33
34
  }
34
35
 
35
- const {errors, errorsFull} = results;
36
+ const {errors, errorsFull, isUnknownError = false} = results;
36
37
 
37
38
  if (errors.length) {
38
39
  if (!process.env.CI) {
@@ -45,6 +46,11 @@ class _BrewTesterJson {
45
46
  process.exit(1);
46
47
  }
47
48
 
49
+ if (isUnknownError) {
50
+ console.error(`Unknown error when testing! (See above logs)`);
51
+ process.exit(1);
52
+ }
53
+
48
54
  if (!errors.length) Um.info(this._LOG_TAG, `Schema test passed.`);
49
55
  }
50
56
  }
package/lib/TestJson.js CHANGED
@@ -11,6 +11,8 @@ import NP from "number-precision";
11
11
  import * as Uf from "./UtilFs.js";
12
12
  import Um from "./UtilMisc.js";
13
13
  import {WorkerList, Deferred} from "./WorkerList.js";
14
+ import {ObjectWalker} from "./ObjectWalker.js";
15
+ import {UrlUtil} from "./UrlUtil.js";
14
16
 
15
17
  const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
16
18
 
@@ -87,7 +89,11 @@ class JsonTester {
87
89
  });
88
90
  // endregion
89
91
 
90
- this._isSchemaLoaded = false;
92
+ this._pLoadSchema = null;
93
+ }
94
+
95
+ async pInit () {
96
+ await this._pDoLoadSchemas();
91
97
  }
92
98
 
93
99
  _getFileErrors ({filePath}) { return `${filePath}\n${JSON.stringify(this._ajv.errors, null, 2)}`; }
@@ -141,30 +147,55 @@ class JsonTester {
141
147
  );
142
148
  }
143
149
 
144
- _doLoadSchemas () {
145
- if (this._isSchemaLoaded) return;
150
+ async _pDoLoadSchemas () {
151
+ await (this._pLoadSchema ||= (async () => {
152
+ const remoteUrls = new Set();
146
153
 
147
- Uf.listJsonFiles(this._dirSchema)
148
- .forEach(filePath => {
149
- filePath = path.normalize(filePath);
154
+ Uf.listJsonFiles(this._dirSchema)
155
+ .forEach(filePath => {
156
+ filePath = path.normalize(filePath);
150
157
 
151
- const relativeFilePath = path.relative(this._dirSchema, filePath)
152
- .replace(/\\/g, "/");
158
+ const relativeFilePath = path.relative(this._dirSchema, filePath)
159
+ .replace(/\\/g, "/");
153
160
 
154
- this._ajv.addSchema(Uf.readJsonSync(filePath), relativeFilePath);
155
- });
161
+ const json = Uf.readJsonSync(filePath);
162
+ this._ajv.addSchema(json, relativeFilePath);
163
+
164
+ ObjectWalker.walk({
165
+ obj: json,
166
+ filePath,
167
+ primitiveHandlers: {
168
+ object: obj => {
169
+ if (!obj.$ref) return;
170
+
171
+ if (!UrlUtil.isUrl(obj.$ref, {protocols: ["http:", "https:"]})) return;
172
+
173
+ const url = new URL(obj.$ref);
174
+ url.hash = "";
175
+ remoteUrls.add(String(url));
176
+ },
177
+ },
178
+ });
179
+ });
156
180
 
157
- this._isSchemaLoaded = true;
181
+ if (!remoteUrls.size) return;
182
+
183
+ await Promise.all(
184
+ Array.from(remoteUrls).map(async remoteUrl => {
185
+ this._ajv.addSchema(await (await fetch(remoteUrl)).json(), remoteUrl);
186
+ }),
187
+ );
188
+
189
+ return true;
190
+ })());
158
191
  }
159
192
 
160
- _hasSchema (schemaId) {
161
- this._doLoadSchemas();
193
+ async _pHasSchema (schemaId) {
194
+ await this._pDoLoadSchemas();
162
195
  return !!this._ajv.schemas[schemaId];
163
196
  }
164
197
 
165
198
  getFileErrors ({filePath} = {}) {
166
- this._doLoadSchemas();
167
-
168
199
  Um.info(this._tagLog, `\tValidating "${filePath}"...`);
169
200
 
170
201
  const data = Uf.readJsonSync(filePath);
@@ -231,16 +262,19 @@ class JsonTester {
231
262
  }
232
263
 
233
264
  // region Verify that every file path maps to a valid schema
234
- fileQueue.forEach(filePath => {
235
- const schemaId = this._fnGetSchemaId(filePath);
236
- if (!schemaId) throw new Error(`Failed to get schema ID for file path "${filePath}"`);
237
- if (!this._hasSchema(schemaId)) throw new Error(`No schema loaded with schema ID "${schemaId}"`);
238
- return schemaId;
239
- });
265
+ await Promise.all(
266
+ fileQueue.map(async filePath => {
267
+ const schemaId = this._fnGetSchemaId(filePath);
268
+ if (!schemaId) throw new Error(`Failed to get schema ID for file path "${filePath}"`);
269
+ if (!(await this._pHasSchema(schemaId))) throw new Error(`No schema loaded with schema ID "${schemaId}"`);
270
+ return schemaId;
271
+ }),
272
+ );
240
273
  // endregion
241
274
 
242
275
  const workerList = new WorkerList();
243
276
 
277
+ let cntFailures = 0;
244
278
  const workers = [...new Array(cntWorkers)]
245
279
  .map(() => {
246
280
  // Relative `Worker` paths do not function in packages, so give an exact path
@@ -265,7 +299,10 @@ class JsonTester {
265
299
  }
266
300
  });
267
301
 
268
- worker.on("error", e => console.error(e));
302
+ worker.on("error", e => {
303
+ console.error(e);
304
+ cntFailures++;
305
+ });
269
306
 
270
307
  worker.postMessage({
271
308
  type: "init",
@@ -297,7 +334,7 @@ class JsonTester {
297
334
  await Promise.all(workers.map(it => it.dIsActive?.promise));
298
335
  await Promise.all(workers.map(it => it.terminate()));
299
336
 
300
- return {errors, errorsFull};
337
+ return {errors, errorsFull, isUnknownError: !!cntFailures};
301
338
  }
302
339
  }
303
340
 
@@ -7,16 +7,17 @@ let jsonTester;
7
7
  let isCancelled = false;
8
8
 
9
9
  parentPort
10
- .on("message", msg => {
10
+ .on("message", async msg => {
11
11
  switch (msg.type) {
12
12
  case "init": {
13
13
  const {dirSchema, tagLog, strFnGetSchemaId} = msg.payload;
14
14
 
15
- jsonTester = new JsonTester({
15
+ jsonTester ||= new JsonTester({
16
16
  dirSchema,
17
17
  tagLog,
18
18
  fnGetSchemaId: eval(strFnGetSchemaId), // eslint-disable-line no-eval
19
19
  });
20
+ await jsonTester.pInit();
20
21
 
21
22
  parentPort.postMessage({
22
23
  type: "ready",
package/lib/UrlUtil.js ADDED
@@ -0,0 +1,19 @@
1
+ export class UrlUtil {
2
+ /**
3
+ * @param str
4
+ * @param {?Array<string>} protocols
5
+ * @return {*|boolean}
6
+ */
7
+ static isUrl (str, {protocols = null} = {}) {
8
+ let url;
9
+
10
+ try {
11
+ url = new URL(str);
12
+ } catch (e) {
13
+ return false;
14
+ }
15
+
16
+ if (protocols == null) return true;
17
+ return protocols.includes(url.protocol);
18
+ }
19
+ }
package/lib/UtilFs.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
+ import {getCleanString} from "./UtilClean.js";
3
4
 
4
5
  function isDirectory (path) {
5
6
  return fs.lstatSync(path).isDirectory();
@@ -29,8 +30,10 @@ function readJsonSync (path, {isIncludeRaw = false} = {}) {
29
30
  }
30
31
  }
31
32
 
32
- function writeJsonSync (filePath, data) {
33
- return fs.writeFileSync(filePath, `${JSON.stringify(data, null, "\t")}\n`, "utf-8");
33
+ function writeJsonSync (filePath, data, {isClean = false} = {}) {
34
+ let str = `${JSON.stringify(data, null, "\t")}\n`;
35
+ if (isClean) str = getCleanString(str);
36
+ return fs.writeFileSync(filePath, str, "utf-8");
34
37
  }
35
38
 
36
39
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "5etools-utils",
3
- "version": "0.10.30",
3
+ "version": "0.11.0",
4
4
  "description": "Shared utilities for the 5etools ecosystem.",
5
5
  "type": "module",
6
6
  "main": "lib/Api.js",