5etools-utils 0.1.0 → 0.1.3

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/Api.js ADDED
@@ -0,0 +1,4 @@
1
+
2
+ export {
3
+
4
+ };
@@ -0,0 +1,121 @@
1
+ import * as path from "path";
2
+
3
+ import Ajv from "ajv";
4
+ import * as jsonSourceMap from "json-source-map";
5
+
6
+ import * as uf from "./UtilFs.js";
7
+ import * as um from "./UtilMisc.js";
8
+
9
+ const _IS_SORT_RESULTS = !process.env.VET_TEST_JSON_RESULTS_UNSORTED;
10
+ const _IS_TRIM_RESULTS = !process.env.VET_TEST_JSON_RESULTS_UNTRIMMED;
11
+
12
+ class JsonTester {
13
+ static _RE_DATE = /^\d\d\d\d-\d\d-\d\d$/;
14
+
15
+ constructor (
16
+ {
17
+ dirSchema,
18
+ tagLog = "JSON",
19
+ },
20
+ ) {
21
+ this._dirSchema = dirSchema;
22
+ this._tagLog = tagLog;
23
+
24
+ // region Set up validator
25
+ this._ajv = new Ajv({
26
+ allowUnionTypes: true,
27
+ });
28
+
29
+ this._ajv.addKeyword({
30
+ keyword: "version",
31
+ validate: false,
32
+ });
33
+
34
+ this._ajv.addFormat(
35
+ "date",
36
+ {
37
+ validate: str => JsonTester._RE_DATE.test(str),
38
+ },
39
+ );
40
+ // endregion
41
+ }
42
+
43
+ /**
44
+ * Add any implicit data to the JSON
45
+ */
46
+ _addImplicits (obj, lastKey) {
47
+ if (typeof obj !== "object") return;
48
+ if (obj == null) return;
49
+ if (obj instanceof Array) obj.forEach(it => this._addImplicits(it, lastKey));
50
+ else {
51
+ // "obj.mode" will be set if this is in a "_copy" etc. block
52
+ if (lastKey === "spellcasting" && !obj.mode) obj.type = obj.type || "spellcasting";
53
+
54
+ Object.entries(obj).forEach(([k, v]) => this._addImplicits(v, k));
55
+ }
56
+ }
57
+
58
+ _getFileErrors ({filePath}) { return `${filePath}\n${JSON.stringify(ajv.errors, null, 2)}`; }
59
+
60
+ _handleError ({filePath, errors, errorsFull, data}) {
61
+ if (!process.env.CI) errorsFull.push(this._getFileErrors({filePath}));
62
+
63
+ // Sort the deepest errors to the bottom, as these are the ones we're most likely to be the ones we care about
64
+ // manually checking.
65
+ if (_IS_SORT_RESULTS) {
66
+ this._ajv.errors.sort((a, b) => (a.instancePath.length ?? -1) - (b.instancePath.length ?? -1));
67
+ }
68
+
69
+ // If there are an excessive number of errors, it's probably a junk entry; show only the first error and let the
70
+ // user figure it out.
71
+ if (_IS_TRIM_RESULTS && this._ajv.errors.length > 5) {
72
+ console.error(`(${this._ajv.errors.length} errors found, showing (hopefully) most-relevant one)`);
73
+ this._ajv.errors = this._ajv.errors.slice(-1);
74
+ }
75
+
76
+ // Add line numbers
77
+ const sourceMap = jsonSourceMap.stringify(data, null, "\t");
78
+ this._ajv.errors.forEach(it => {
79
+ const errorPointer = sourceMap.pointers[it.instancePath];
80
+ it.lineNumberStart = errorPointer.value.line;
81
+ it.lineNumberEnd = errorPointer.valueEnd.line;
82
+ });
83
+
84
+ const error = this._getFileErrors({filePath});
85
+ um.error(this._tagLog, error);
86
+ errors.push(error);
87
+ }
88
+
89
+ getErrors () {
90
+ um.info(this._tagLog, `Validating JSON against schema`);
91
+
92
+ uf.listJsonFiles(this._dirSchema)
93
+ .forEach(filePath => {
94
+ filePath = path.normalize(filePath);
95
+ const contents = uf.readJSON(filePath);
96
+
97
+ const relativeFilePath = path.relative(this._dirSchema, filePath)
98
+ .replace(/\\/g, "/");
99
+
100
+ this._ajv.addSchema(contents, relativeFilePath);
101
+ });
102
+
103
+ const errors = [];
104
+ const errorsFull = [];
105
+
106
+ uf.runOnDirs((dir) => {
107
+ uf.listJsonFiles(dir)
108
+ .forEach(filePath => {
109
+ const data = uf.readJSON(filePath);
110
+ this._addImplicits(data);
111
+ const valid = this._ajv.validate("homebrew.json", data);
112
+
113
+ if (!valid) this._handleError({filePath, errors, errorsFull, data});
114
+ });
115
+ });
116
+
117
+ return {errors, errorsFull};
118
+ }
119
+ }
120
+
121
+ export {JsonTester};
package/lib/UtilFs.js ADDED
@@ -0,0 +1,64 @@
1
+ import * as fs from "fs";
2
+
3
+ function isDirectory (path) {
4
+ return fs.lstatSync(path).isDirectory();
5
+ }
6
+
7
+ function readJSON (path) {
8
+ try {
9
+ if (fs.existsSync(path)) {
10
+ let str = fs.readFileSync(path, "utf8");
11
+ // Strip BOM(s)
12
+ while (str.charCodeAt(0) === 0xFEFF) str = str.slice(1);
13
+ return JSON.parse(str);
14
+ } else {
15
+ const parts = path.split(/[\\/]+/g);
16
+ const dir = parts.slice(0, -1).join("/");
17
+ const originalName = parts[parts.length - 1];
18
+ const filenames = fs.readdirSync(dir, "utf8");
19
+ const filename = filenames.find(it => it.toLowerCase() === originalName.toLowerCase());
20
+ if (filename) return JSON.parse(fs.readFileSync(`${dir}/${filename}`, "utf8"));
21
+ else throw new Error(`Could not find file "${path}"`);
22
+ }
23
+ } catch (e) {
24
+ e.message += ` (Path: ${path})`;
25
+ throw e;
26
+ }
27
+ }
28
+
29
+ function listJsonFiles (dir) {
30
+ const dirContent = fs.readdirSync(dir, "utf8")
31
+ .map(file => `${dir}/${file}`);
32
+ return dirContent.reduce((acc, file) => {
33
+ if (isDirectory(file)) acc.push(...listJsonFiles(file));
34
+ else {
35
+ if (file.toLowerCase().endsWith(".json")) acc.push(file);
36
+ }
37
+ return acc;
38
+ }, []);
39
+ }
40
+
41
+ function runOnDirs (fn) {
42
+ fs.readdirSync(".", "utf8")
43
+ .filter(dir => isDirectory(dir) && !dir.startsWith(".") && !dir.startsWith("_") && dir !== "node_modules")
44
+ .forEach(dir => fn(dir));
45
+ }
46
+
47
+ function mkDirs (pathToCreate) {
48
+ pathToCreate
49
+ .split(/[\\/]/g)
50
+ .reduce((currentPath, folder) => {
51
+ currentPath += `${folder}/`;
52
+ if (!fs.existsSync(currentPath)) {
53
+ fs.mkdirSync(currentPath);
54
+ }
55
+ return currentPath;
56
+ }, "");
57
+ }
58
+
59
+ export {
60
+ readJSON,
61
+ listJsonFiles,
62
+ runOnDirs,
63
+ mkDirs,
64
+ };
@@ -0,0 +1,22 @@
1
+ function _taggedConsole (fn, tag, ...args) {
2
+ const expandedTag = tag.padStart(12, " ");
3
+ fn(`[${expandedTag}]`, ...args);
4
+ }
5
+
6
+ function warn (tag, ...args) {
7
+ _taggedConsole(console.warn, tag, ...args);
8
+ }
9
+
10
+ function info (tag, ...args) {
11
+ _taggedConsole(console.info, tag, ...args);
12
+ }
13
+
14
+ function error (tag, ...args) {
15
+ _taggedConsole(console.error, tag, ...args);
16
+ }
17
+
18
+ export {
19
+ warn,
20
+ info,
21
+ error,
22
+ };
package/package.json CHANGED
@@ -1,10 +1,19 @@
1
1
  {
2
2
  "name": "5etools-utils",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "description": "Shared utilities for the 5etools ecosystem.",
5
5
  "type": "module",
6
+ "main": "lib/Api.js",
7
+ "files": [
8
+ "lib"
9
+ ],
10
+ "directories": {
11
+ "lib": "./lib"
12
+ },
6
13
  "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
14
+ "test": "npm run test:js",
15
+ "test:js": "eslint lib",
16
+ "lint:js": "eslint lib --fix"
8
17
  },
9
18
  "repository": {
10
19
  "type": "git",
package/.editorconfig DELETED
@@ -1,7 +0,0 @@
1
- [*]
2
- charset=utf-8
3
- end_of_line=lf
4
- trim_trailing_whitespace=true
5
- insert_final_newline=true
6
- indent_style=tab
7
- indent_size=3
package/.eslintrc.js DELETED
@@ -1,184 +0,0 @@
1
- module.exports = {
2
- "extends": "eslint:recommended",
3
- "env": {
4
- "browser": true,
5
- "es6": true,
6
- "jquery": true
7
- },
8
- "parserOptions": {
9
- "ecmaVersion": "latest",
10
- "sourceType": "module"
11
- },
12
- "rules": {
13
- "accessor-pairs": "off",
14
- "arrow-spacing": ["error", {"before": true, "after": true}],
15
- "block-spacing": ["error", "always"],
16
- "brace-style": ["error", "1tbs", {"allowSingleLine": true}],
17
- "comma-dangle": ["error", {
18
- "arrays": "always-multiline",
19
- "objects": "always-multiline",
20
- "imports": "always-multiline",
21
- "exports": "always-multiline",
22
- "functions": "always-multiline"
23
- }],
24
- "comma-spacing": ["error", {"before": false, "after": true}],
25
- "comma-style": ["error", "last"],
26
- "constructor-super": "error",
27
- "curly": ["error", "multi-line"],
28
- "dot-location": ["error", "property"],
29
- "eqeqeq": ["error", "always", {"null": "ignore"}],
30
- "func-call-spacing": ["error", "never"],
31
- "generator-star-spacing": ["error", {"before": false, "after": true}],
32
- "handle-callback-err": ["error", "^(err|error)$"],
33
- "indent": [
34
- "error",
35
- "tab",
36
- {
37
- "SwitchCase": 1
38
- }
39
- ],
40
- "key-spacing": ["error", {"beforeColon": false, "afterColon": true}],
41
- "keyword-spacing": ["error", {"before": true, "after": true}],
42
- "new-cap": ["error", {"newIsCap": true, "capIsNew": false}],
43
- "new-parens": "error",
44
- "no-array-constructor": "error",
45
- "no-caller": "error",
46
- "no-class-assign": "error",
47
- "no-compare-neg-zero": "error",
48
- "no-cond-assign": "error",
49
- "no-const-assign": "error",
50
- "no-constant-condition": ["error", {"checkLoops": false}],
51
- "no-control-regex": "error",
52
- "no-debugger": "error",
53
- "no-delete-var": "error",
54
- "no-dupe-args": "error",
55
- "no-dupe-class-members": "error",
56
- "no-dupe-keys": "error",
57
- "no-duplicate-case": "error",
58
- "no-empty-character-class": "error",
59
- "no-empty-pattern": "error",
60
- "no-eval": "error",
61
- "no-ex-assign": "error",
62
- "no-extra-bind": "error",
63
- "no-extra-boolean-cast": "error",
64
- "no-extra-parens": ["error", "functions"],
65
- "no-fallthrough": "error",
66
- "no-floating-decimal": "error",
67
- "no-func-assign": "error",
68
- "no-global-assign": "error",
69
- "no-implied-eval": "error",
70
- "no-inner-declarations": ["error", "functions"],
71
- "no-invalid-regexp": "error",
72
- "no-irregular-whitespace": "error",
73
- "no-iterator": "error",
74
- "no-label-var": "error",
75
- "no-labels": ["error", {"allowLoop": true, "allowSwitch": false}],
76
- "no-lone-blocks": "error",
77
- "no-mixed-operators": ["error", {
78
- "groups": [
79
- ["==", "!=", "===", "!==", ">", ">=", "<", "<="],
80
- ["&&", "||"],
81
- ["in", "instanceof"]
82
- ],
83
- "allowSamePrecedence": true
84
- }],
85
- "no-mixed-spaces-and-tabs": "error",
86
- "no-multi-spaces": "error",
87
- "no-multi-str": "error",
88
- "no-multiple-empty-lines": ["error", {"max": 1, "maxEOF": 0}],
89
- "no-negated-in-lhs": "error",
90
- "no-new": "error",
91
- "no-new-func": "error",
92
- "no-new-object": "error",
93
- "no-new-require": "error",
94
- "no-new-symbol": "error",
95
- "no-new-wrappers": "error",
96
- "no-obj-calls": "error",
97
- "no-octal": "error",
98
- "no-octal-escape": "error",
99
- "no-path-concat": "error",
100
- "no-proto": "error",
101
- "no-redeclare": "error",
102
- "no-regex-spaces": "error",
103
- "no-return-await": "error",
104
- "no-self-assign": "error",
105
- "no-self-compare": "error",
106
- "no-sequences": "error",
107
- "no-shadow-restricted-names": "error",
108
- "no-sparse-arrays": "error",
109
- "no-template-curly-in-string": "error",
110
- "no-this-before-super": "error",
111
- "no-throw-literal": "error",
112
- "no-trailing-spaces": "error",
113
- "no-undef": "off",
114
- "no-undef-init": "error",
115
- "no-unexpected-multiline": "error",
116
- "no-unmodified-loop-condition": "error",
117
- "no-unneeded-ternary": ["error", {"defaultAssignment": false}],
118
- "no-unreachable": "error",
119
- "no-unsafe-finally": "error",
120
- "no-unsafe-negation": "error",
121
- "no-unused-expressions": ["error", {
122
- "allowShortCircuit": true,
123
- "allowTernary": true,
124
- "allowTaggedTemplates": true
125
- }],
126
- "no-unused-vars": "off",
127
- "no-use-before-define": ["error", {"functions": false, "classes": false, "variables": false}],
128
- "no-useless-call": "error",
129
- "no-useless-computed-key": "error",
130
- "no-useless-constructor": "error",
131
- "no-useless-escape": "error",
132
- "no-useless-rename": "error",
133
- "no-useless-return": "error",
134
- "no-whitespace-before-property": "error",
135
- "no-with": "error",
136
- "object-property-newline": ["error", {"allowMultiplePropertiesPerLine": true}],
137
- "one-var": ["error", {"initialized": "never"}],
138
- "operator-linebreak": ["error", "after", {
139
- "overrides": {
140
- "?": "before",
141
- ":": "before",
142
- "+": "before",
143
- "-": "before",
144
- "*": "before",
145
- "/": "before",
146
- "||": "before",
147
- "&&": "before"
148
- }
149
- }],
150
- "padded-blocks": ["error", {"blocks": "never", "switches": "never", "classes": "never"}],
151
- "prefer-promise-reject-errors": "error",
152
- "rest-spread-spacing": ["error", "never"],
153
- "semi": ["warn", "always"],
154
- "semi-spacing": ["error", {"before": false, "after": true}],
155
- "space-before-blocks": ["error", "always"],
156
- "space-before-function-paren": ["error", "always"],
157
- "space-in-parens": ["error", "never"],
158
- "space-infix-ops": "error",
159
- "space-unary-ops": ["error", {"words": true, "nonwords": false}],
160
- "spaced-comment": ["error", "always", {
161
- "line": {"markers": ["*package", "!", "/", ",", "="]},
162
- "block": {
163
- "balanced": true,
164
- "markers": ["*package", "!", ",", ":", "::", "flow-include"],
165
- "exceptions": ["*"]
166
- }
167
- }],
168
- "symbol-description": "error",
169
- "template-curly-spacing": ["error", "never"],
170
- "template-tag-spacing": ["error", "never"],
171
- "unicode-bom": ["error", "never"],
172
- "use-isnan": "error",
173
- "valid-typeof": ["error", {"requireStringLiterals": true}],
174
- "wrap-iife": ["error", "any", {"functionPrototypeMethods": true}],
175
- "yield-star-spacing": ["error", "both"],
176
- "yoda": ["error", "never"],
177
- "no-prototype-builtins": "off",
178
- "require-atomic-updates": "off",
179
- "no-console": "error",
180
- "prefer-template": "error",
181
- "quotes": ["error", "double", {"allowTemplateLiterals": true}],
182
- "no-var": "error"
183
- }
184
- };
package/HelloWorld.js DELETED
@@ -1,3 +0,0 @@
1
- export const helloWorld = () => {
2
- console.log("Hello world!");
3
- };