minecraft-data 3.97.0 → 3.98.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 (41) hide show
  1. package/data.js +27 -0
  2. package/doc/history.md +4 -0
  3. package/index.d.ts +72 -63
  4. package/minecraft-data/.github/helper-bot/handleMcpcGeneratedArtifacts.js +109 -0
  5. package/minecraft-data/.github/helper-bot/index.js +77 -34
  6. package/minecraft-data/.github/helper-bot/package.json +11 -0
  7. package/minecraft-data/.github/workflows/handle-mcpc-generator.yml +36 -0
  8. package/minecraft-data/.github/workflows/update-helper.yml +3 -2
  9. package/minecraft-data/README.md +1 -1
  10. package/minecraft-data/data/bedrock/1.21.100/protocol.json +2 -2
  11. package/minecraft-data/data/dataPaths.json +28 -1
  12. package/minecraft-data/data/pc/1.21.6/proto.yml +3494 -0
  13. package/minecraft-data/data/pc/1.21.8/attributes.json +247 -0
  14. package/minecraft-data/data/pc/1.21.8/biomes.json +652 -0
  15. package/minecraft-data/data/pc/1.21.8/blockCollisionShapes.json +142541 -0
  16. package/minecraft-data/data/pc/1.21.8/blocks.json +41358 -0
  17. package/minecraft-data/data/pc/1.21.8/effects.json +236 -0
  18. package/minecraft-data/data/pc/1.21.8/enchantments.json +959 -0
  19. package/minecraft-data/data/pc/1.21.8/entities.json +4176 -0
  20. package/minecraft-data/data/pc/1.21.8/foods.json +402 -0
  21. package/minecraft-data/data/pc/1.21.8/instruments.json +94 -0
  22. package/minecraft-data/data/pc/1.21.8/items.json +9284 -0
  23. package/minecraft-data/data/pc/1.21.8/language.json +7394 -0
  24. package/minecraft-data/data/pc/1.21.8/materials.json +206 -0
  25. package/minecraft-data/data/pc/1.21.8/particles.json +458 -0
  26. package/minecraft-data/data/pc/1.21.8/protocol.json +10249 -0
  27. package/minecraft-data/data/pc/1.21.8/recipes.json +30473 -0
  28. package/minecraft-data/data/pc/1.21.8/sounds.json +6914 -0
  29. package/minecraft-data/data/pc/1.21.8/tints.json +465 -0
  30. package/minecraft-data/data/pc/1.21.8/version.json +6 -0
  31. package/minecraft-data/data/pc/common/protocolVersions.json +40 -0
  32. package/minecraft-data/data/pc/common/versions.json +6 -2
  33. package/minecraft-data/data/pc/latest/proto.yml +1 -1
  34. package/minecraft-data/doc/history.md +16 -0
  35. package/minecraft-data/schemas/blockMappings_schema.json +24 -34
  36. package/minecraft-data/schemas/commands_schema.json +13 -7
  37. package/minecraft-data/schemas/tints_schema.json +37 -128
  38. package/minecraft-data/tools/js/extractPcEntityMetadata.js +160 -102
  39. package/minecraft-data/tools/js/test/audit_versions.js +48 -0
  40. package/minecraft-data/tools/js/test/test.js +6 -0
  41. package/package.json +1 -1
@@ -2,161 +2,70 @@
2
2
  "title": "tints",
3
3
  "type": "object",
4
4
  "additionalProperties": false,
5
- "properties": {
6
- "grass": {
7
- "type": "object",
8
- "additionalProperties": false,
9
- "properties": {
10
- "data": {
11
- "type": "array",
12
- "minItem": 1,
13
- "uniqueItems": true,
14
- "items": {
15
- "type": "object",
16
- "additionalProperties": false,
17
- "properties": {
18
- "keys": {
19
- "type": "array",
20
- "minItem": 1,
21
- "uniqueItems": true,
22
- "items": {
23
- "type": "string"
24
- }
25
- },
26
- "color": {
27
- "type": "integer"
28
- }
29
- }
30
- }
31
- },
32
- "default": {
33
- "type": "integer"
34
- }
35
- }
36
- },
37
- "foliage": {
5
+ "definitions": {
6
+ "StringKeyTintEntry": {
38
7
  "type": "object",
39
8
  "additionalProperties": false,
40
9
  "properties": {
41
- "data": {
10
+ "keys": {
42
11
  "type": "array",
43
- "minItem": 1,
12
+ "minItems": 1,
44
13
  "uniqueItems": true,
45
- "items": {
46
- "type": "object",
47
- "additionalProperties": false,
48
- "properties": {
49
- "keys": {
50
- "type": "array",
51
- "minItem": 1,
52
- "uniqueItems": true,
53
- "items": {
54
- "type": "string"
55
- }
56
- },
57
- "color": {
58
- "type": "integer"
59
- }
60
- }
61
- }
14
+ "items": { "type": "string" }
62
15
  },
63
- "default": {
64
- "type": "integer"
65
- }
66
- }
16
+ "color": { "type": "integer" }
17
+ },
18
+ "required": ["keys", "color"]
67
19
  },
68
- "water": {
20
+ "IntegerKeyTintEntry": {
69
21
  "type": "object",
70
22
  "additionalProperties": false,
71
23
  "properties": {
72
- "data": {
24
+ "keys": {
73
25
  "type": "array",
74
- "minItem": 1,
26
+ "minItems": 1,
75
27
  "uniqueItems": true,
76
- "items": {
77
- "type": "object",
78
- "additionalProperties": false,
79
- "properties": {
80
- "keys": {
81
- "type": "array",
82
- "minItem": 1,
83
- "uniqueItems": true,
84
- "items": {
85
- "type": "string"
86
- }
87
- },
88
- "color": {
89
- "type": "integer"
90
- }
91
- }
92
- }
28
+ "items": { "type": "integer" }
93
29
  },
94
- "default": {
95
- "type": "integer"
96
- }
97
- }
30
+ "color": { "type": "integer" }
31
+ },
32
+ "required": ["keys", "color"]
98
33
  },
99
- "redstone": {
34
+ "StringKeyTintGroup": {
100
35
  "type": "object",
101
36
  "additionalProperties": false,
102
37
  "properties": {
103
38
  "data": {
104
39
  "type": "array",
105
- "minItem": 1,
40
+ "minItems": 1,
106
41
  "uniqueItems": true,
107
- "items": {
108
- "type": "object",
109
- "additionalProperties": false,
110
- "properties": {
111
- "keys": {
112
- "type": "array",
113
- "minItem": 1,
114
- "uniqueItems": true,
115
- "items": {
116
- "type": "integer"
117
- }
118
- },
119
- "color": {
120
- "type": "integer"
121
- }
122
- }
123
- }
42
+ "items": { "$ref": "#/definitions/StringKeyTintEntry" }
124
43
  },
125
- "default": {
126
- "type": "integer"
127
- }
128
- }
44
+ "default": { "type": "integer" }
45
+ },
46
+ "required": ["data"]
129
47
  },
130
- "constant": {
48
+ "IntegerKeyTintGroup": {
131
49
  "type": "object",
132
50
  "additionalProperties": false,
133
51
  "properties": {
134
52
  "data": {
135
53
  "type": "array",
136
- "minItem": 1,
54
+ "minItems": 1,
137
55
  "uniqueItems": true,
138
- "items": {
139
- "type": "object",
140
- "additionalProperties": false,
141
- "properties": {
142
- "keys": {
143
- "type": "array",
144
- "minItem": 1,
145
- "uniqueItems": true,
146
- "items": {
147
- "type": "string"
148
- }
149
- },
150
- "color": {
151
- "type": "integer"
152
- }
153
- }
154
- }
56
+ "items": { "$ref": "#/definitions/IntegerKeyTintEntry" }
155
57
  },
156
- "default": {
157
- "type": "integer"
158
- }
159
- }
58
+ "default": { "type": "integer" }
59
+ },
60
+ "required": ["data"]
160
61
  }
161
- }
162
- }
62
+ },
63
+ "properties": {
64
+ "grass": { "$ref": "#/definitions/StringKeyTintGroup" },
65
+ "foliage": { "$ref": "#/definitions/StringKeyTintGroup" },
66
+ "water": { "$ref": "#/definitions/StringKeyTintGroup" },
67
+ "redstone": { "$ref": "#/definitions/IntegerKeyTintGroup" },
68
+ "constant": { "$ref": "#/definitions/StringKeyTintGroup" }
69
+ },
70
+ "required": ["grass", "foliage", "water", "redstone", "constant"]
71
+ }
@@ -1,18 +1,13 @@
1
+ #!/usr/bin/env node
1
2
  const fs = require('fs')
2
3
  const cp = require('child_process')
3
4
  const { globSync } = require('glob')
4
- const version = process.argv[2]
5
- const mcdataVersion = process.argv[3] || process.argv[2]
6
- if (!version) {
7
- console.log('Usage: node extractEntityMetadata.js <codeVersion> [mcdataVersion]')
8
- process.exit(1)
9
- }
10
-
11
- if (!fs.existsSync(version)) {
12
- cp.execSync(`git clone -b client${version} https://github.com/extremeheat/extracted_minecraft_data.git ${version} --depth 1`, { stdio: 'inherit' })
13
- }
14
5
 
15
- // decompiler may either put static defs inline (static varname = value) or keep decl and def in seperate blocks (static varname; static{varname = val, ..}) so normalize it
6
+ /**
7
+ * Normalize decompiler output into an array of "statements"/lines.
8
+ * @param {string} raw
9
+ * @returns {string[]}
10
+ */
16
11
  function prepLines (raw) {
17
12
  const lines = raw.replaceAll(' {\n', ' {;\n').split(';')
18
13
  for (let i = 0; i < lines.length; i++) {
@@ -31,8 +26,13 @@ function prepLines (raw) {
31
26
  return lines
32
27
  }
33
28
 
34
- function getEntityTypes () {
35
- const entityTypes = fs.readFileSync(`./${version}/client/net/minecraft/world/entity/EntityType.java`, 'utf8')
29
+ /**
30
+ * Read entity types from the client EntityType.java and return mappings.
31
+ * @param {string} versionDir
32
+ * @returns {[Record<string,string>, Record<string,string>]}
33
+ */
34
+ function getEntityTypes (versionDir) {
35
+ const entityTypes = fs.readFileSync(`${versionDir}/client/net/minecraft/world/entity/EntityType.java`, 'utf8')
36
36
  const entityTypesLines = prepLines(entityTypes)
37
37
  const classNameTo = {}
38
38
  const nameToClass = {}
@@ -51,9 +51,14 @@ function getEntityTypes () {
51
51
  return [classNameTo, nameToClass]
52
52
  }
53
53
 
54
- function getEntityMetadataSerializers () {
54
+ /**
55
+ * Read EntityDataSerializers.java and return serializer names in order.
56
+ * @param {string} versionDir
57
+ * @returns {string[]}
58
+ */
59
+ function getEntityMetadataSerializers (versionDir) {
55
60
  const output = []
56
- const entityTypes = fs.readFileSync(`./${version}/client/net/minecraft/network/syncher/EntityDataSerializers.java`, 'utf8')
61
+ const entityTypes = fs.readFileSync(`${versionDir}/client/net/minecraft/network/syncher/EntityDataSerializers.java`, 'utf8')
57
62
  const entityTypesLines = prepLines(entityTypes)
58
63
  for (const line of entityTypesLines) {
59
64
  if (line.includes('registerSerializer')) {
@@ -69,107 +74,160 @@ function getEntityMetadataSerializers () {
69
74
  return output.map(e => e.toLowerCase())
70
75
  }
71
76
 
72
- const serializers = getEntityMetadataSerializers()
73
- const [classNameToRegistryName, entityNameToClass] = getEntityTypes()
74
-
75
- const allEntityFiles = globSync(`${version}/**/entity/**/*.java`)
76
- const allEntityFileCodes = Object.fromEntries(allEntityFiles.map(file => [
77
- file.split(/\\|\//g).pop().replace('.java', ''),
78
- fs.readFileSync(file, 'utf8')
79
- ]))
80
-
81
- const entityPreTree = []
82
- const metadatas = {}
83
-
84
- for (const file in allEntityFileCodes) {
85
- const code = allEntityFileCodes[file]
86
- const lines = prepLines(code)
87
- let lastClass
88
- for (const line of lines) {
89
- let lineWithoutGenerics = line.replace(/<.*>/g, '')
90
- const bloatMods = ['abstract', 'static']
91
- for (const mod of bloatMods) lineWithoutGenerics = lineWithoutGenerics.replace(` ${mod} `, ' ')
92
-
93
- if (lineWithoutGenerics.includes('public class ')) {
94
- const className = lineWithoutGenerics.split('public class ')[1]?.split(' ')[0]
95
- lastClass = className
96
- const extend = lineWithoutGenerics.split('extends ')[1]?.split(' ')[0]
97
- if (className === file) {
98
- if (extend) entityPreTree.push([extend, file])
99
- else entityPreTree.push([file])
100
- } else if (extend === file) {
101
- lastClass = `${file}.${className}`
102
- entityPreTree.push([file, lastClass])
77
+ /**
78
+ * Main function to extract and optionally update mcdata files.
79
+ * @param {string} version - directory name for extracted src (e.g. "1.20")
80
+ * @param {string} [mcdataVersion] - mcdata version to update (defaults to version)
81
+ * @param {{write?:boolean, cloneIfMissing?:boolean}} [opts]
82
+ * @returns {{tree:object, flat:object, serializers:string[], classNameToRegistryName:object, entityNameToClass:object, metadatas:object}}
83
+ */
84
+ function extractPcEntityMetadata (version, mcdataVersion = version, opts = {}) {
85
+ if (!version) {
86
+ throw new Error('Usage: extractPcEntityMetadata(version, [mcdataVersion])')
87
+ }
88
+ const write = opts.write !== false
89
+ const cloneIfMissing = opts.cloneIfMissing !== false
90
+
91
+ if (!fs.existsSync(version)) {
92
+ if (!cloneIfMissing) {
93
+ throw new Error(`Version directory "${version}" does not exist`)
94
+ }
95
+ cp.execSync(`git clone -b client${version} https://github.com/extremeheat/extracted_minecraft_data.git ${version} --depth 1`, { stdio: 'inherit' })
96
+ }
97
+
98
+ const serializers = getEntityMetadataSerializers(version)
99
+ const [classNameToRegistryName, entityNameToClass] = getEntityTypes(version)
100
+
101
+ const allEntityFiles = globSync(`${version}/**/entity/**/*.java`)
102
+ const allEntityFileCodes = Object.fromEntries(allEntityFiles.map(file => [
103
+ file.split(/\\|\//g).pop().replace('.java', ''),
104
+ fs.readFileSync(file, 'utf8')
105
+ ]))
106
+
107
+ const entityPreTree = []
108
+ const metadatas = {}
109
+
110
+ for (const file in allEntityFileCodes) {
111
+ const code = allEntityFileCodes[file]
112
+ const lines = prepLines(code)
113
+ let lastClass
114
+ for (const line of lines) {
115
+ let lineWithoutGenerics = line.replace(/<.*>/g, '')
116
+ const bloatMods = ['abstract', 'static']
117
+ for (const mod of bloatMods) lineWithoutGenerics = lineWithoutGenerics.replace(` ${mod} `, ' ')
118
+
119
+ if (lineWithoutGenerics.includes('public class ')) {
120
+ const className = lineWithoutGenerics.split('public class ')[1]?.split(' ')[0]
121
+ lastClass = className
122
+ const extend = lineWithoutGenerics.split('extends ')[1]?.split(' ')[0]
123
+ if (className === file) {
124
+ if (extend) entityPreTree.push([extend, file])
125
+ else entityPreTree.push([file])
126
+ } else if (extend === file) {
127
+ lastClass = `${file}.${className}`
128
+ entityPreTree.push([file, lastClass])
129
+ }
130
+ }
131
+
132
+ if (line.match(/SynchedEntityData\..*defineId\(/)) {
133
+ // from: private static final EntityDataAccessor<Sniffer.State> DATA_STATE = SynchedEntityData.defineId(Sniffer.class, EntityDataSerializers.SNIFFER_STATE);
134
+ // extract: DATA_STATE, SNIFFER_STATE
135
+ const r = line.match(/> ([A-Z_0-9]+) = .*EntityDataSerializers.([A-Z_0-9]+)/s)
136
+ if (r) {
137
+ const [, data, serializer] = r
138
+ ; (metadatas[lastClass] ??= []).push([data, serializer])
139
+ } else {
140
+ throw new Error('Failed to parse line: ' + line)
141
+ }
103
142
  }
104
143
  }
144
+ }
105
145
 
106
- if (line.match(/SynchedEntityData\..*defineId\(/)) {
107
- // from: private static final EntityDataAccessor<Sniffer.State> DATA_STATE = SynchedEntityData.defineId(Sniffer.class, EntityDataSerializers.SNIFFER_STATE);
108
- // extract: DATA_STATE, SNIFFER_STATE
109
- const r = line.match(/> ([A-Z_0-9]+) = .*EntityDataSerializers.([A-Z_0-9]+)/s)
110
- if (r) {
111
- const [, data, serializer] = r
112
- ; (metadatas[lastClass] ??= []).push([data, serializer])
113
- } else {
114
- throw new Error('Failed to parse line: ' + line)
146
+ const tree = {}
147
+ const flat = {}
148
+ const handledNames = []
149
+ function build (ele, root = tree, arr = []) {
150
+ const name = classNameToRegistryName[ele]
151
+ const metadata = metadatas[ele]
152
+ root[ele] = { children: {}, name, metadata }
153
+ if (name) flat[name] = arr.concat(metadata ?? [])
154
+ handledNames.push(ele)
155
+ for (const [key, val] of entityPreTree) {
156
+ if (key === ele) {
157
+ build(val, root[key].children, arr.concat(metadata ?? []))
115
158
  }
116
159
  }
117
160
  }
118
- }
161
+ build('Entity')
162
+
163
+ for (const key in classNameToRegistryName) {
164
+ if (!handledNames.includes(key)) throw new Error('Unhandled entity ' + key)
165
+ }
166
+
167
+ function updateMcDataEntitiesJSON () {
168
+ const presentMcDataPath = `../../data/pc/${mcdataVersion}/entities.json`
169
+ const presentMcData = require(presentMcDataPath)
170
+ for (const entry of presentMcData) {
171
+ const cls = entityNameToClass[entry.name]
172
+ const regName = classNameToRegistryName[cls]
173
+ entry.metadataKeys = (flat[regName] || []).map(e => e[0].replace('DATA_', '').replace('_ID', '').replace('ID_', '').toLowerCase())
174
+ }
175
+ fs.writeFileSync(presentMcDataPath, JSON.stringify(presentMcData, null, 2))
176
+ }
119
177
 
120
- const tree = {}
121
- const flat = {}
122
- const handledNames = []
123
- function build (ele, root = tree, arr = []) {
124
- const name = classNameToRegistryName[ele]
125
- const metadata = metadatas[ele]
126
- root[ele] = { children: {}, name, metadata }
127
- if (name) flat[name] = arr.concat(metadata ?? [])
128
- handledNames.push(ele)
129
- for (const [key, val] of entityPreTree) {
130
- if (key === ele) {
131
- build(val, root[key].children, arr.concat(metadata ?? []))
178
+ function updateMcDataProtocolJSON () {
179
+ const presentMcDataPath = `../../data/pc/${mcdataVersion}/protocol.json`
180
+ const mcdata = require(presentMcDataPath)
181
+ const mapper = { type: 'varint', mappings: {} }
182
+ for (let i = 0; i < serializers.length; i++) {
183
+ mapper.mappings[i] = serializers[i].toLowerCase()
132
184
  }
185
+ mcdata.types.entityMetadata[1].type[1] = [
186
+ { name: 'key', type: 'u8' },
187
+ { name: 'type', type: ['mapper', mapper] },
188
+ { name: 'value', type: ['entityMetadataItem', { compareTo: 'type' }] }
189
+ ]
190
+ fs.writeFileSync(presentMcDataPath, JSON.stringify(mcdata, null, 2))
191
+ }
192
+
193
+ if (write) {
194
+ updateMcDataEntitiesJSON()
195
+ updateMcDataProtocolJSON()
196
+ }
197
+
198
+ return {
199
+ tree,
200
+ flat,
201
+ serializers,
202
+ classNameToRegistryName,
203
+ entityNameToClass,
204
+ metadatas,
205
+ entityPreTree
133
206
  }
134
207
  }
135
- build('Entity')
136
208
 
137
- for (const key in classNameToRegistryName) {
138
- if (!handledNames.includes(key)) throw new Error('Unhandled entity ' + key)
209
+ // Expose functions for use as a library
210
+ module.exports = {
211
+ extractPcEntityMetadata,
212
+ prepLines,
213
+ getEntityTypes,
214
+ getEntityMetadataSerializers
139
215
  }
140
216
 
141
- function updateMcDataEntitiesJSON () {
142
- const presentMcDataPath = `../../data/pc/${mcdataVersion}/entities.json`
143
- const presentMcData = require(presentMcDataPath)
144
- for (const entry of presentMcData) {
145
- entry.metadataKeys = flat[classNameToRegistryName[entityNameToClass[entry.name]]].map(e => e[0].replace('DATA_', '').replace('_ID', '').replace('ID_', '').toLowerCase())
217
+ // If run directly from the CLI, call the function with process.argv
218
+ if (require.main === module) {
219
+ const version = process.argv[2]
220
+ const mcdataVersion = process.argv[3] || process.argv[2]
221
+ if (!version) {
222
+ console.log('Usage: node extractEntityMetadata.js <codeVersion> [mcdataVersion]')
223
+ process.exit(1)
146
224
  }
147
- fs.writeFileSync(presentMcDataPath, JSON.stringify(presentMcData, null, 2))
148
- }
149
225
 
150
- function updateMcDataProtocolJSON () {
151
- const presentMcDataPath = `../../data/pc/${mcdataVersion}/protocol.json`
152
- const mcdata = require(presentMcDataPath)
153
- const mapper = { type: 'varint', mappings: {} }
154
- for (let i = 0; i < serializers.length; i++) {
155
- mapper.mappings[i] = serializers[i].toLowerCase()
226
+ try {
227
+ extractPcEntityMetadata(version, mcdataVersion, { write: true, cloneIfMissing: true })
228
+ // finished successfully
229
+ } catch (err) {
230
+ console.error('Error:', err)
231
+ process.exit(1)
156
232
  }
157
- mcdata.types.entityMetadata[1].type[1] = [
158
- { name: 'key', type: 'u8' },
159
- { name: 'type', type: ['mapper', mapper] },
160
- { name: 'value', type: ['entityMetadataItem', { compareTo: 'type' }] }
161
- ]
162
- // One time code to remap integers to strings in protocol.json for the entityMetadataItem type
163
- // const metadataItems = mcdata.types.entityMetadataItem[1].fields
164
- // const next = {}
165
- // for (const key in metadataItems) {
166
- // next[serializers[key]] = metadataItems[key]
167
- // }
168
- // mcdata.types.entityMetadataItem[1].fields = next
169
- fs.writeFileSync(presentMcDataPath, JSON.stringify(mcdata, null, 2))
170
233
  }
171
-
172
- // fs.writeFileSync(`./entityTree.json`, JSON.stringify(tree, null, 2))
173
- // fs.writeFileSync(`./entityFlat.json`, JSON.stringify(flat, null, 2))
174
- updateMcDataEntitiesJSON()
175
- updateMcDataProtocolJSON()
@@ -0,0 +1,48 @@
1
+ /* eslint-env mocha */
2
+
3
+ const assert = require('assert')
4
+ const path = require('path')
5
+ const fs = require('fs')
6
+
7
+ describe('dataPaths.json consistency with versions.json', function () {
8
+ const dataPathsFile = path.join(__dirname, '../../../data/dataPaths.json')
9
+ const dataPaths = require(dataPathsFile)
10
+
11
+ const editions = ['pc', 'bedrock']
12
+
13
+ editions.forEach(function (edition) {
14
+ describe(`${edition} versions consistency`, function () {
15
+ const versionsFile = path.join(
16
+ __dirname,
17
+ `../../../data/${edition}/common/versions.json`
18
+ )
19
+ assert.ok(
20
+ fs.existsSync(versionsFile),
21
+ `Missing versions.json for ${edition}`
22
+ )
23
+
24
+ const versionsList = require(versionsFile)
25
+ const manifestVersions = Object.keys(dataPaths[edition] || {})
26
+
27
+ it('all manifest versions are present in versions.json', function () {
28
+ const missing = manifestVersions.filter(
29
+ (v) => !versionsList.includes(v)
30
+ )
31
+ assert.deepStrictEqual(
32
+ missing,
33
+ [],
34
+ `The following ${edition} versions are in dataPaths.json but not in versions.json: ${missing.join(', ')}`
35
+ )
36
+ })
37
+
38
+ it('no extra versions exist in versions.json compared to manifest', function () {
39
+ const extra = versionsList.filter((v) => !manifestVersions.includes(v))
40
+ assert.deepStrictEqual(
41
+ extra,
42
+ [],
43
+ `The following ${edition} versions are in versions.json but not in dataPaths.json: ${extra.join(', ')}`
44
+ )
45
+ })
46
+ })
47
+ })
48
+ })
@@ -24,6 +24,12 @@ require('./version_iterator')(function (p, versionString) {
24
24
  }
25
25
  if (instance) {
26
26
  it(dataName + '.json is valid', function () {
27
+ // Skip tints schema validation for PC 1.21.4, as it doesn't meet the
28
+ // maxItems: 1 check for the constant tints.
29
+ if (dataName === 'tints' && versionString === 'pc 1.21.4') {
30
+ this.skip()
31
+ }
32
+
27
33
  if (dataName === 'protocol') {
28
34
  const validator = new Validator()
29
35
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minecraft-data",
3
- "version": "3.97.0",
3
+ "version": "3.98.0",
4
4
  "description": "Provide easy access to minecraft data in node.js",
5
5
  "main": "index.js",
6
6
  "tonicExampleFilename": "example.js",