@cobbled-creators/cobblemon-schemas 1.7.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.
Files changed (85) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +68 -0
  3. package/bin/cobblemon-schemas.mjs +51 -0
  4. package/dist/bundle.json +1 -0
  5. package/dist/validators.mjs +3 -0
  6. package/package.json +66 -0
  7. package/schemas/_shared/common.schema.json +89 -0
  8. package/schemas/_shared/defs/collections.schema.json +32 -0
  9. package/schemas/_shared/defs/drops.schema.json +89 -0
  10. package/schemas/_shared/defs/expressions.schema.json +82 -0
  11. package/schemas/_shared/defs/identifiers.schema.json +24 -0
  12. package/schemas/_shared/defs/npc.schema.json +76 -0
  13. package/schemas/_shared/defs/pokemon.schema.json +41 -0
  14. package/schemas/_shared/defs/ranges.schema.json +70 -0
  15. package/schemas/_shared/defs/ride.schema.json +77 -0
  16. package/schemas/_shared/defs/spawning.schema.json +183 -0
  17. package/schemas/_shared/defs/vectors.schema.json +44 -0
  18. package/schemas/action_effects/schema.json +123 -0
  19. package/schemas/bedrock_pokemon_resolvers/schema.json +184 -0
  20. package/schemas/bedrock_posers/schema.json +367 -0
  21. package/schemas/behaviours/schema.json +145 -0
  22. package/schemas/berries/schema.json +178 -0
  23. package/schemas/cosmetic_items/schema.json +55 -0
  24. package/schemas/dex_additions/schema.json +22 -0
  25. package/schemas/dex_entries/schema.json +83 -0
  26. package/schemas/dex_entry_additions/schema.json +63 -0
  27. package/schemas/dexes/schema.json +89 -0
  28. package/schemas/dialogues/schema.json +260 -0
  29. package/schemas/fossils/schema.json +22 -0
  30. package/schemas/global_species_features/schema.json +6 -0
  31. package/schemas/index.json +244 -0
  32. package/schemas/marks/schema.json +49 -0
  33. package/schemas/mechanics/aprijuices.schema.json +25 -0
  34. package/schemas/mechanics/berries.schema.json +35 -0
  35. package/schemas/mechanics/potions.schema.json +23 -0
  36. package/schemas/mechanics/remedies.schema.json +31 -0
  37. package/schemas/mechanics/slowpoke_tails.schema.json +17 -0
  38. package/schemas/natural_materials/schema.json +40 -0
  39. package/schemas/npc_presets/schema.json +6 -0
  40. package/schemas/npcs/schema.json +443 -0
  41. package/schemas/pokemon_interactions/schema.json +169 -0
  42. package/schemas/pokerods/schema.json +19 -0
  43. package/schemas/ride_settings/bird.schema.json +48 -0
  44. package/schemas/ride_settings/boat.schema.json +45 -0
  45. package/schemas/ride_settings/burst.schema.json +11 -0
  46. package/schemas/ride_settings/dolphin.schema.json +45 -0
  47. package/schemas/ride_settings/glider.schema.json +18 -0
  48. package/schemas/ride_settings/helicopter.schema.json +21 -0
  49. package/schemas/ride_settings/horse.schema.json +36 -0
  50. package/schemas/ride_settings/hover.schema.json +33 -0
  51. package/schemas/ride_settings/jet.schema.json +42 -0
  52. package/schemas/ride_settings/minekart.schema.json +33 -0
  53. package/schemas/ride_settings/rocket.schema.json +36 -0
  54. package/schemas/ride_settings/submarine.schema.json +33 -0
  55. package/schemas/ride_settings/vehicle.schema.json +45 -0
  56. package/schemas/seasonings/schema.json +124 -0
  57. package/schemas/spawn_bait_effects/schema.json +42 -0
  58. package/schemas/spawn_detail_presets/schema.json +79 -0
  59. package/schemas/spawn_pool_world/schema.json +455 -0
  60. package/schemas/spawn_rules/schema.json +217 -0
  61. package/schemas/species/schema.json +1043 -0
  62. package/schemas/species_additions/schema.json +22 -0
  63. package/schemas/species_feature_assignments/schema.json +25 -0
  64. package/schemas/species_features/schema.json +139 -0
  65. package/schemas/starters/schema.json +28 -0
  66. package/schemas/unlockable_pc_box_wallpapers/schema.json +29 -0
  67. package/src/errors/format.mjs +46 -0
  68. package/src/errors/pointer.mjs +30 -0
  69. package/src/errors/simplify.mjs +58 -0
  70. package/src/errors/type-branches.mjs +65 -0
  71. package/src/errors/values.mjs +7 -0
  72. package/src/index.d.ts +74 -0
  73. package/src/index.mjs +6 -0
  74. package/src/json/line-index.mjs +21 -0
  75. package/src/json/parse-json.mjs +28 -0
  76. package/src/json/syntax-errors.mjs +56 -0
  77. package/src/node/load-engine.mjs +30 -0
  78. package/src/node/load-schemas.mjs +35 -0
  79. package/src/node/package-info.mjs +10 -0
  80. package/src/node/read-pack.mjs +18 -0
  81. package/src/node.d.ts +13 -0
  82. package/src/node.mjs +5 -0
  83. package/src/schema-engine.mjs +43 -0
  84. package/src/schema-index.mjs +54 -0
  85. package/src/validate-pack.mjs +28 -0
@@ -0,0 +1,35 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import Ajv2020 from 'ajv/dist/2020.js';
4
+ import { packageRoot, schemaRoot } from './package-info.mjs';
5
+
6
+ const AJV_OPTIONS = { allErrors: true, strict: false, allowUnionTypes: true, verbose: true };
7
+
8
+ export function loadSchemas(root = schemaRoot, ajvOptions = {}) {
9
+ const ajv = new Ajv2020({ ...AJV_OPTIONS, ...ajvOptions });
10
+ for (const file of schemaFiles(root)) {
11
+ const schema = JSON.parse(fs.readFileSync(file, 'utf8'));
12
+ ajv.addSchema(schema, schema.$id ?? path.relative(packageRoot, file));
13
+ }
14
+ return { ajv, entries: readIndex(root) };
15
+ }
16
+
17
+ function readIndex(root) {
18
+ const index = JSON.parse(fs.readFileSync(path.join(root, 'index.json'), 'utf8'));
19
+ return index.schemas
20
+ .filter((entry) => !entry.path.startsWith('schemas/_shared/'))
21
+ .map((entry) => ({ path: entry.path, id: entry.$id, ...(entry.dataPath ? { dataPath: entry.dataPath } : {}) }));
22
+ }
23
+
24
+ function* schemaFiles(dir) {
25
+ const children = fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
26
+ for (const child of children) {
27
+ const childPath = path.join(dir, child.name);
28
+ if (child.isDirectory()) yield* schemaFiles(childPath);
29
+ else if (isSchemaFile(child.name)) yield childPath;
30
+ }
31
+ }
32
+
33
+ function isSchemaFile(name) {
34
+ return name === 'schema.json' || name.endsWith('.schema.json');
35
+ }
@@ -0,0 +1,10 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ export const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
6
+ export const schemaRoot = path.join(packageRoot, 'schemas');
7
+ export const distRoot = path.join(packageRoot, 'dist');
8
+
9
+ export const packageJson = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
10
+ export const cobblemon = packageJson.cobblemon;
@@ -0,0 +1,18 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ export function readPack(packRoot) {
5
+ const files = new Map();
6
+ for (const relativePath of jsonFiles(packRoot, '')) {
7
+ files.set(relativePath, fs.readFileSync(path.join(packRoot, relativePath)));
8
+ }
9
+ return files;
10
+ }
11
+
12
+ function* jsonFiles(packRoot, relativeDir) {
13
+ for (const child of fs.readdirSync(path.join(packRoot, relativeDir), { withFileTypes: true })) {
14
+ const relativePath = relativeDir ? `${relativeDir}/${child.name}` : child.name;
15
+ if (child.isDirectory()) yield* jsonFiles(packRoot, relativePath);
16
+ else if (child.isFile() && child.name.endsWith('.json')) yield relativePath;
17
+ }
18
+ }
package/src/node.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import type { CobblemonTarget, SchemaEngine, SchemaEntry } from './index.js';
2
+
3
+ export * from './index.js';
4
+
5
+ export const packageRoot: string;
6
+ export const schemaRoot: string;
7
+ export const cobblemon: CobblemonTarget;
8
+
9
+ export function loadSchemas(root?: string, ajvOptions?: Record<string, unknown>): { ajv: import('ajv/dist/2020.js').default; entries: SchemaEntry[] };
10
+
11
+ export function loadEngine(options?: { live?: boolean }): Promise<SchemaEngine>;
12
+
13
+ export function readPack(dir: string): Map<string, Uint8Array>;
package/src/node.mjs ADDED
@@ -0,0 +1,5 @@
1
+ export * from './index.mjs';
2
+ export { packageRoot, schemaRoot, cobblemon } from './node/package-info.mjs';
3
+ export { loadSchemas } from './node/load-schemas.mjs';
4
+ export { loadEngine } from './node/load-engine.mjs';
5
+ export { readPack } from './node/read-pack.mjs';
@@ -0,0 +1,43 @@
1
+ import { formatAjvError } from './errors/format.mjs';
2
+ import { decodePointer } from './errors/pointer.mjs';
3
+ import { simplifyAjvErrors } from './errors/simplify.mjs';
4
+ import { SchemaIndex } from './schema-index.mjs';
5
+
6
+ export class SchemaEngine {
7
+ #index;
8
+ #validators;
9
+
10
+ constructor({ entries, validators, cobblemon = null }) {
11
+ this.cobblemon = cobblemon;
12
+ this.#index = new SchemaIndex(entries);
13
+ this.#validators = new Map(Object.entries(validators));
14
+ }
15
+
16
+ static fromBundle(bundle, compiled) {
17
+ const validators = {};
18
+ for (const entry of bundle.entries) {
19
+ if (entry.validator) validators[entry.path] = compiled[entry.validator];
20
+ }
21
+ return new SchemaEngine({ entries: bundle.entries, validators, cobblemon: bundle.cobblemon });
22
+ }
23
+
24
+ resolve(packPath) {
25
+ return this.#index.resolve(packPath);
26
+ }
27
+
28
+ validate(schemaPath, value) {
29
+ const validator = this.#validators.get(schemaPath);
30
+ if (!validator || validator(value)) return [];
31
+ return simplifyAjvErrors(validator.errors ?? [], value).map((error) => ({
32
+ pointer: decodePointer(error.instancePath),
33
+ keyword: error.keyword,
34
+ params: error.params ?? {},
35
+ message: formatAjvError(error),
36
+ }));
37
+ }
38
+
39
+ check(packPath, value) {
40
+ const schema = this.resolve(packPath) ?? null;
41
+ return { schema, errors: schema ? this.validate(schema, value) : [] };
42
+ }
43
+ }
@@ -0,0 +1,54 @@
1
+ const SHARED_SCHEMAS = 'schemas/_shared/';
2
+ const POKEMON_RESOLVER_FILE = /^assets\/[^/]+\/bedrock\/pokemon\/resolvers\//;
3
+ const POSER_FILE = /^assets\/[^/]+\/bedrock\/(pokemon|npcs|fossils|block_entities|generic|poke_balls)\/posers\//;
4
+ const DATA_FILE = /^data\/[^/]+\/(.+\.json)$/;
5
+ const PER_FILE_SCHEMA = /^schemas\/(mechanics|ride_settings)\/([^.]+)\.schema\.json$/;
6
+ const FOLDER_SCHEMA = /^schemas\/([^/]+)\/schema\.json$/;
7
+
8
+ export class SchemaIndex {
9
+ #byDataPath = new Map();
10
+ #byFolderAndFile = new Map();
11
+ #byFolder = new Map();
12
+
13
+ constructor(entries) {
14
+ for (const entry of entries) {
15
+ if (!entry.path.startsWith(SHARED_SCHEMAS)) this.#add(entry);
16
+ }
17
+ }
18
+
19
+ resolve(packPath) {
20
+ if (POKEMON_RESOLVER_FILE.test(packPath)) return 'schemas/bedrock_pokemon_resolvers/schema.json';
21
+ if (POSER_FILE.test(packPath)) return 'schemas/bedrock_posers/schema.json';
22
+
23
+ const dataPath = packPath.match(DATA_FILE)?.[1];
24
+ if (!dataPath) return undefined;
25
+ return this.#byDataPath.get(dataPath) ?? this.#resolveByFolder(dataPath);
26
+ }
27
+
28
+ #resolveByFolder(dataPath) {
29
+ const slash = dataPath.indexOf('/');
30
+ if (slash === -1) return undefined;
31
+ const folder = dataPath.slice(0, slash);
32
+ const fileName = dataPath.slice(dataPath.lastIndexOf('/') + 1);
33
+ return this.#byFolderAndFile.get(folder)?.get(fileName) ?? this.#byFolder.get(folder);
34
+ }
35
+
36
+ #add(entry) {
37
+ if (entry.dataPath) {
38
+ this.#byDataPath.set(entry.dataPath, entry.path);
39
+ return;
40
+ }
41
+
42
+ const perFile = entry.path.match(PER_FILE_SCHEMA);
43
+ if (perFile) {
44
+ const [, folder, name] = perFile;
45
+ const byFile = this.#byFolderAndFile.get(folder) ?? new Map();
46
+ byFile.set(`${name}.json`, entry.path);
47
+ this.#byFolderAndFile.set(folder, byFile);
48
+ return;
49
+ }
50
+
51
+ const folder = entry.path.match(FOLDER_SCHEMA)?.[1];
52
+ if (folder && !folder.startsWith('bedrock_')) this.#byFolder.set(folder, entry.path);
53
+ }
54
+ }
@@ -0,0 +1,28 @@
1
+ import { parseJson } from './json/parse-json.mjs';
2
+
3
+ export function validatePack(files, engine) {
4
+ const results = [];
5
+ for (const [file, content] of files) {
6
+ const schema = engine.resolve(file);
7
+ if (schema) results.push({ file, schema, errors: validateFile(content, schema, engine) });
8
+ }
9
+ return results.sort((a, b) => a.file.localeCompare(b.file));
10
+ }
11
+
12
+ function validateFile(content, schema, engine) {
13
+ const parsed = parseJson(content);
14
+ const syntaxErrors = parsed.syntaxErrors.map((error) => ({ kind: 'syntax', pointer: [], ...error }));
15
+ if (parsed.value === undefined) return syntaxErrors;
16
+
17
+ const schemaErrors = engine.validate(schema, parsed.value).map((error) => ({
18
+ kind: 'schema',
19
+ ...error,
20
+ ...parsed.locate(pointerToHighlight(error)),
21
+ }));
22
+ return [...syntaxErrors, ...schemaErrors];
23
+ }
24
+
25
+ function pointerToHighlight(error) {
26
+ const unknownProperty = error.keyword === 'additionalProperties' ? error.params.additionalProperty : undefined;
27
+ return unknownProperty === undefined ? error.pointer : [...error.pointer, unknownProperty];
28
+ }