@spyglassmc/mcdoc 0.3.1 → 0.3.2
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/binder/index.d.ts +7 -0
- package/lib/binder/index.js +260 -167
- package/lib/colorizer/index.js +2 -2
- package/lib/node/index.d.ts +18 -7
- package/lib/node/index.js +61 -28
- package/lib/parser/index.d.ts +1 -1
- package/lib/parser/index.js +162 -147
- package/lib/type/index.d.ts +39 -23
- package/lib/type/index.js +138 -56
- package/lib/uri_processors.js +5 -5
- package/package.json +3 -3
package/lib/type/index.js
CHANGED
|
@@ -1,21 +1,53 @@
|
|
|
1
|
-
import { Arrayable } from '@spyglassmc/core';
|
|
1
|
+
import { Arrayable, Dev } from '@spyglassmc/core';
|
|
2
2
|
import { localeQuote, localize } from '@spyglassmc/locales';
|
|
3
3
|
import { getRangeDelimiter } from '../node/index.js';
|
|
4
|
-
export const StaticIndexKeywords = Object.freeze([
|
|
5
|
-
|
|
4
|
+
export const StaticIndexKeywords = Object.freeze([
|
|
5
|
+
'fallback',
|
|
6
|
+
'none',
|
|
7
|
+
'unknown',
|
|
8
|
+
]);
|
|
9
|
+
export const EmptyUnion = Object.freeze({
|
|
10
|
+
kind: 'union',
|
|
11
|
+
members: [],
|
|
12
|
+
});
|
|
6
13
|
export function createEmptyUnion(attributes) {
|
|
7
14
|
return {
|
|
8
15
|
...EmptyUnion,
|
|
9
|
-
attributes,
|
|
16
|
+
// attributes,
|
|
10
17
|
};
|
|
11
18
|
}
|
|
12
|
-
export const LiteralNumberSuffixes = Object.freeze([
|
|
13
|
-
|
|
14
|
-
|
|
19
|
+
export const LiteralNumberSuffixes = Object.freeze([
|
|
20
|
+
'b',
|
|
21
|
+
's',
|
|
22
|
+
'l',
|
|
23
|
+
'f',
|
|
24
|
+
'd',
|
|
25
|
+
]);
|
|
26
|
+
export const LiteralNumberCaseInsensitiveSuffixes = Object.freeze([
|
|
27
|
+
...LiteralNumberSuffixes,
|
|
28
|
+
'B',
|
|
29
|
+
'S',
|
|
30
|
+
'L',
|
|
31
|
+
'F',
|
|
32
|
+
'D',
|
|
33
|
+
]);
|
|
34
|
+
export const NumericTypeIntKinds = Object.freeze([
|
|
35
|
+
'byte',
|
|
36
|
+
'short',
|
|
37
|
+
'int',
|
|
38
|
+
'long',
|
|
39
|
+
]);
|
|
15
40
|
export const NumericTypeFloatKinds = Object.freeze(['float', 'double']);
|
|
16
|
-
export const NumericTypeKinds = Object.freeze([
|
|
17
|
-
|
|
18
|
-
|
|
41
|
+
export const NumericTypeKinds = Object.freeze([
|
|
42
|
+
...NumericTypeIntKinds,
|
|
43
|
+
...NumericTypeFloatKinds,
|
|
44
|
+
]);
|
|
45
|
+
export const PrimitiveArrayValueKinds = Object.freeze([
|
|
46
|
+
'byte',
|
|
47
|
+
'int',
|
|
48
|
+
'long',
|
|
49
|
+
]);
|
|
50
|
+
export const PrimitiveArrayKinds = Object.freeze(PrimitiveArrayValueKinds.map((kind) => `${kind}_array`));
|
|
19
51
|
export var McdocType;
|
|
20
52
|
(function (McdocType) {
|
|
21
53
|
function toString(type) {
|
|
@@ -24,7 +56,9 @@ export var McdocType;
|
|
|
24
56
|
return '';
|
|
25
57
|
}
|
|
26
58
|
const { kind, min, max } = range;
|
|
27
|
-
return min === max
|
|
59
|
+
return min === max
|
|
60
|
+
? ` @ ${min}`
|
|
61
|
+
: ` @ ${min ?? ''}${getRangeDelimiter(kind)}${max ?? ''}`;
|
|
28
62
|
};
|
|
29
63
|
const indicesToString = (indices) => {
|
|
30
64
|
const strings = [];
|
|
@@ -35,7 +69,9 @@ export var McdocType;
|
|
|
35
69
|
else {
|
|
36
70
|
strings.push(index.kind === 'static'
|
|
37
71
|
? `[${index.value}]`
|
|
38
|
-
: `[[${index.accessor
|
|
72
|
+
: `[[${index.accessor
|
|
73
|
+
.map((v) => (typeof v === 'string' ? v : v.keyword))
|
|
74
|
+
.join('.')}]]`);
|
|
39
75
|
}
|
|
40
76
|
}
|
|
41
77
|
return `[${strings.join(', ')}]`;
|
|
@@ -47,18 +83,26 @@ export var McdocType;
|
|
|
47
83
|
case 'any':
|
|
48
84
|
case 'boolean':
|
|
49
85
|
return type.kind;
|
|
86
|
+
case 'attributed':
|
|
87
|
+
return `#[${type.attribute.name}${type.attribute.value ? '=<value ...>' : ''}] ${toString(type.child)}`;
|
|
50
88
|
case 'byte':
|
|
51
89
|
return `byte${rangeToString(type.valueRange)}`;
|
|
52
90
|
case 'byte_array':
|
|
53
91
|
return `byte${rangeToString(type.valueRange)}[]${rangeToString(type.lengthRange)}`;
|
|
92
|
+
case 'concrete':
|
|
93
|
+
return `${toString(type.child)}${type.typeArgs.length
|
|
94
|
+
? `<${type.typeArgs.map(toString).join(', ')}>`
|
|
95
|
+
: ''}`;
|
|
54
96
|
case 'dispatcher':
|
|
55
|
-
return `${type.registry ?? 'spyglass:unknown'}[${indicesToString(type.
|
|
97
|
+
return `${type.registry ?? 'spyglass:unknown'}[${indicesToString(type.parallelIndices)}]`;
|
|
56
98
|
case 'double':
|
|
57
99
|
return `double${rangeToString(type.valueRange)}`;
|
|
58
100
|
case 'enum':
|
|
59
|
-
return '<
|
|
101
|
+
return '<enum ...>';
|
|
60
102
|
case 'float':
|
|
61
103
|
return `float${rangeToString(type.valueRange)}`;
|
|
104
|
+
case 'indexed':
|
|
105
|
+
return `${toString(type.child)}${indicesToString(type.parallelIndices)}`;
|
|
62
106
|
case 'int':
|
|
63
107
|
return `int${rangeToString(type.valueRange)}`;
|
|
64
108
|
case 'int_array':
|
|
@@ -78,11 +122,19 @@ export var McdocType;
|
|
|
78
122
|
case 'string':
|
|
79
123
|
return `string${rangeToString(type.lengthRange)}`;
|
|
80
124
|
case 'struct':
|
|
81
|
-
return '<
|
|
125
|
+
return '<struct ...>';
|
|
126
|
+
case 'template':
|
|
127
|
+
return `${toString(type.child)}${type.typeParams.length
|
|
128
|
+
? `<${type.typeParams.map((v) => `?${v.path}`).join(', ')}>`
|
|
129
|
+
: ''}`;
|
|
82
130
|
case 'tuple':
|
|
83
|
-
return `[${type.items.map(v => toString(v)).join(',')}${type.items.length === 1 ? ',' : ''}]`;
|
|
131
|
+
return `[${type.items.map((v) => toString(v)).join(',')}${type.items.length === 1 ? ',' : ''}]`;
|
|
84
132
|
case 'union':
|
|
85
133
|
return `(${type.members.map(toString).join(' | ')})`;
|
|
134
|
+
case 'unsafe':
|
|
135
|
+
return 'unsafe';
|
|
136
|
+
default:
|
|
137
|
+
return Dev.assertNever(type);
|
|
86
138
|
}
|
|
87
139
|
}
|
|
88
140
|
McdocType.toString = toString;
|
|
@@ -102,17 +154,19 @@ const areRangesMatch = (s, t) => {
|
|
|
102
154
|
}
|
|
103
155
|
const { min: sMin, max: sMax } = s;
|
|
104
156
|
const { min: tMin, max: tMax } = t;
|
|
105
|
-
return (tMin === undefined || (sMin !== undefined && sMin >= tMin)) &&
|
|
106
|
-
(tMax === undefined || (sMax !== undefined && sMax <= tMax));
|
|
157
|
+
return ((tMin === undefined || (sMin !== undefined && sMin >= tMin)) &&
|
|
158
|
+
(tMax === undefined || (sMax !== undefined && sMax <= tMax)));
|
|
107
159
|
};
|
|
108
160
|
export const flattenUnionType = (union) => {
|
|
109
161
|
const set = new Set();
|
|
110
162
|
const add = (data) => {
|
|
111
163
|
for (const existingMember of set) {
|
|
112
|
-
if ((check(data, existingMember) & CheckResult.StrictlyAssignable) ===
|
|
164
|
+
if ((check(data, existingMember) & CheckResult.StrictlyAssignable) ===
|
|
165
|
+
CheckResult.StrictlyAssignable) {
|
|
113
166
|
return;
|
|
114
167
|
}
|
|
115
|
-
if ((check(existingMember, data) & CheckResult.StrictlyAssignable) ===
|
|
168
|
+
if ((check(existingMember, data) & CheckResult.StrictlyAssignable) ===
|
|
169
|
+
CheckResult.StrictlyAssignable) {
|
|
116
170
|
set.delete(existingMember);
|
|
117
171
|
}
|
|
118
172
|
}
|
|
@@ -132,17 +186,19 @@ export const flattenUnionType = (union) => {
|
|
|
132
186
|
};
|
|
133
187
|
};
|
|
134
188
|
export const unionTypes = (a, b) => {
|
|
135
|
-
if ((check(a, b) & CheckResult.StrictlyAssignable) ===
|
|
189
|
+
if ((check(a, b) & CheckResult.StrictlyAssignable) ===
|
|
190
|
+
CheckResult.StrictlyAssignable) {
|
|
136
191
|
return b;
|
|
137
192
|
}
|
|
138
|
-
if ((check(b, a) & CheckResult.StrictlyAssignable) ===
|
|
193
|
+
if ((check(b, a) & CheckResult.StrictlyAssignable) ===
|
|
194
|
+
CheckResult.StrictlyAssignable) {
|
|
139
195
|
return a;
|
|
140
196
|
}
|
|
141
197
|
const ans = {
|
|
142
198
|
kind: 'union',
|
|
143
199
|
members: [
|
|
144
|
-
...a.kind === 'union' ? a.members : [a],
|
|
145
|
-
...b.kind === 'union' ? b.members : [b],
|
|
200
|
+
...(a.kind === 'union' ? a.members : [a]),
|
|
201
|
+
...(b.kind === 'union' ? b.members : [b]),
|
|
146
202
|
],
|
|
147
203
|
};
|
|
148
204
|
return ans;
|
|
@@ -157,7 +213,7 @@ export const simplifyUnionType = (union) => {
|
|
|
157
213
|
export const simplifyListType = (list) => ({
|
|
158
214
|
kind: 'list',
|
|
159
215
|
item: simplifyType(list.item),
|
|
160
|
-
...list.lengthRange ? { lengthRange: { ...list.lengthRange } } : {},
|
|
216
|
+
...(list.lengthRange ? { lengthRange: { ...list.lengthRange } } : {}),
|
|
161
217
|
});
|
|
162
218
|
export const simplifyType = (data) => {
|
|
163
219
|
if (data.kind === 'union') {
|
|
@@ -182,10 +238,10 @@ const check = (s, t, errors = []) => {
|
|
|
182
238
|
ans = CheckResult.StrictlyAssignable;
|
|
183
239
|
}
|
|
184
240
|
else if (s.kind === 'union') {
|
|
185
|
-
ans = assignableIfTrue(s.members.every(v => check(v, t, errors)));
|
|
241
|
+
ans = assignableIfTrue(s.members.every((v) => check(v, t, errors)));
|
|
186
242
|
}
|
|
187
243
|
else if (t.kind === 'union') {
|
|
188
|
-
ans = assignableIfTrue(t.members.some(v => check(s, v)));
|
|
244
|
+
ans = assignableIfTrue(t.members.some((v) => check(s, v)));
|
|
189
245
|
}
|
|
190
246
|
else if (s.kind === 'boolean') {
|
|
191
247
|
ans = strictlyAssignableIfTrue(t.kind === 'boolean' || t.kind === 'byte');
|
|
@@ -204,16 +260,31 @@ const check = (s, t, errors = []) => {
|
|
|
204
260
|
ans = CheckResult.Nah;
|
|
205
261
|
}
|
|
206
262
|
}
|
|
207
|
-
else if (s.kind === 'byte_array' ||
|
|
208
|
-
|
|
263
|
+
else if (s.kind === 'byte_array' ||
|
|
264
|
+
s.kind === 'int_array' ||
|
|
265
|
+
s.kind === 'long_array') {
|
|
266
|
+
ans = strictlyAssignableIfTrue(t.kind === s.kind &&
|
|
267
|
+
areRangesMatch(s.lengthRange, t.lengthRange) &&
|
|
268
|
+
areRangesMatch(s.valueRange, t.valueRange));
|
|
209
269
|
}
|
|
210
270
|
else if (s.kind === 'struct' || s.kind === 'dispatcher') {
|
|
211
271
|
ans = assignableIfTrue(t.kind === 'struct' || t.kind === 'dispatcher');
|
|
212
272
|
}
|
|
213
273
|
else if (s.kind === 'enum') {
|
|
214
|
-
ans = assignableIfTrue((t.kind === 'byte' ||
|
|
215
|
-
|
|
216
|
-
|
|
274
|
+
ans = assignableIfTrue((t.kind === 'byte' ||
|
|
275
|
+
t.kind === 'float' ||
|
|
276
|
+
t.kind === 'double' ||
|
|
277
|
+
t.kind === 'int' ||
|
|
278
|
+
t.kind === 'long' ||
|
|
279
|
+
t.kind === 'short' ||
|
|
280
|
+
t.kind === 'string') &&
|
|
281
|
+
(!s.enumKind || s.enumKind === t.kind));
|
|
282
|
+
}
|
|
283
|
+
else if (s.kind === 'float' ||
|
|
284
|
+
s.kind === 'double' ||
|
|
285
|
+
s.kind === 'int' ||
|
|
286
|
+
s.kind === 'long' ||
|
|
287
|
+
s.kind === 'short') {
|
|
217
288
|
if (t.kind === s.kind) {
|
|
218
289
|
ans = strictlyAssignableIfTrue(areRangesMatch(s.valueRange, t.valueRange));
|
|
219
290
|
}
|
|
@@ -248,7 +319,7 @@ const check = (s, t, errors = []) => {
|
|
|
248
319
|
}
|
|
249
320
|
return ans;
|
|
250
321
|
};
|
|
251
|
-
export const checkAssignability = ({ source, target }) => {
|
|
322
|
+
export const checkAssignability = ({ source, target, }) => {
|
|
252
323
|
if (source === undefined || target === undefined) {
|
|
253
324
|
return { isAssignable: true };
|
|
254
325
|
}
|
|
@@ -256,17 +327,24 @@ export const checkAssignability = ({ source, target }) => {
|
|
|
256
327
|
check(source, target, errors);
|
|
257
328
|
return {
|
|
258
329
|
isAssignable: errors.length === 0,
|
|
259
|
-
...errors.length
|
|
330
|
+
...(errors.length
|
|
331
|
+
? {
|
|
332
|
+
errorMessage: errors
|
|
333
|
+
.reverse()
|
|
334
|
+
.map((m, i) => `${' '.repeat(i)}${m}`)
|
|
335
|
+
.join('\n'),
|
|
336
|
+
}
|
|
337
|
+
: {}),
|
|
260
338
|
};
|
|
261
339
|
};
|
|
262
340
|
export function resolveType(inputType, ctx, value) {
|
|
263
341
|
const type = getTangibleType(inputType, ctx, value);
|
|
264
|
-
|
|
342
|
+
const ans = (() => {
|
|
265
343
|
if (type.kind === 'union') {
|
|
266
344
|
return {
|
|
267
345
|
kind: 'union',
|
|
268
|
-
members: type.members.map(t => resolveType(t, ctx, value)),
|
|
269
|
-
attributes: type.attributes,
|
|
346
|
+
members: type.members.map((t) => resolveType(t, ctx, value)),
|
|
347
|
+
// attributes: type.attributes,
|
|
270
348
|
};
|
|
271
349
|
}
|
|
272
350
|
else {
|
|
@@ -276,9 +354,9 @@ export function resolveType(inputType, ctx, value) {
|
|
|
276
354
|
};
|
|
277
355
|
}
|
|
278
356
|
})();
|
|
279
|
-
for (const parallelIndices of type.indices ?? []) {
|
|
280
|
-
|
|
281
|
-
}
|
|
357
|
+
// for (const parallelIndices of type.indices ?? []) {
|
|
358
|
+
// ans = navigateParallelIndices(ans, parallelIndices, ctx, value)
|
|
359
|
+
// }
|
|
282
360
|
return ans;
|
|
283
361
|
}
|
|
284
362
|
function dispatchType(type, ctx) {
|
|
@@ -298,7 +376,7 @@ function getTangibleType(type, ctx, value) {
|
|
|
298
376
|
return getTangibleType(dereferencedType, ctx, value);
|
|
299
377
|
}
|
|
300
378
|
else if (type.kind === 'union') {
|
|
301
|
-
ans = mapUnion(type, t => getTangibleType(t, ctx, value));
|
|
379
|
+
ans = mapUnion(type, (t) => getTangibleType(t, ctx, value));
|
|
302
380
|
}
|
|
303
381
|
else {
|
|
304
382
|
ans = type;
|
|
@@ -312,27 +390,31 @@ function navigateParallelIndices(type, indices, ctx, value) {
|
|
|
312
390
|
else {
|
|
313
391
|
return {
|
|
314
392
|
kind: 'union',
|
|
315
|
-
members: indices.map(i => navigateIndex(type, i, ctx, value)),
|
|
316
|
-
attributes: type.attributes,
|
|
393
|
+
members: indices.map((i) => navigateIndex(type, i, ctx, value)),
|
|
394
|
+
// attributes: type.attributes,
|
|
317
395
|
};
|
|
318
396
|
}
|
|
319
397
|
}
|
|
320
398
|
function navigateIndex(type, index, ctx, value) {
|
|
321
399
|
if (type.kind === 'struct') {
|
|
322
400
|
const key = index.kind === 'static'
|
|
323
|
-
? typeof index.value === 'string'
|
|
401
|
+
? typeof index.value === 'string'
|
|
402
|
+
? index.value
|
|
403
|
+
: undefined // Special static indices have no meaning on structs.
|
|
324
404
|
: resolveDynamicIndex(index, value);
|
|
325
405
|
if (key === undefined) {
|
|
326
|
-
return createEmptyUnion(type.attributes)
|
|
406
|
+
// return createEmptyUnion(type.attributes)
|
|
407
|
+
return createEmptyUnion();
|
|
327
408
|
}
|
|
328
409
|
const flatStruct = flattenStruct(type, ctx, value);
|
|
329
410
|
return resolveType(flatStruct.fields[key], ctx, value);
|
|
330
411
|
}
|
|
331
412
|
else if (type.kind === 'union') {
|
|
332
|
-
return mapUnion(type, t => navigateIndex(t, index, ctx, value));
|
|
413
|
+
return mapUnion(type, (t) => navigateIndex(t, index, ctx, value));
|
|
333
414
|
}
|
|
334
415
|
else {
|
|
335
|
-
return createEmptyUnion(type.attributes)
|
|
416
|
+
// return createEmptyUnion(type.attributes)
|
|
417
|
+
return createEmptyUnion();
|
|
336
418
|
}
|
|
337
419
|
}
|
|
338
420
|
function resolveDynamicIndex(index, value) {
|
|
@@ -356,8 +438,8 @@ function mapUnion(type, mapper) {
|
|
|
356
438
|
const ans = {
|
|
357
439
|
kind: 'union',
|
|
358
440
|
members: type.members.map(mapper),
|
|
359
|
-
attributes: type.attributes,
|
|
360
|
-
indices: type.indices,
|
|
441
|
+
// attributes: type.attributes,
|
|
442
|
+
// indices: type.indices,
|
|
361
443
|
};
|
|
362
444
|
return ans;
|
|
363
445
|
}
|
|
@@ -365,13 +447,13 @@ function flattenStruct(type, ctx, value) {
|
|
|
365
447
|
const ans = {
|
|
366
448
|
kind: 'flat_struct',
|
|
367
449
|
fields: Object.create(null),
|
|
368
|
-
attributes: type.attributes,
|
|
369
|
-
indices: type.indices,
|
|
450
|
+
// attributes: type.attributes,
|
|
451
|
+
// indices: type.indices,
|
|
370
452
|
};
|
|
371
453
|
for (const field of type.fields) {
|
|
372
454
|
if (field.kind === 'spread') {
|
|
373
455
|
const target = resolveType(field.type, ctx, value);
|
|
374
|
-
addAttributes(ans, ...target.attributes ?? [])
|
|
456
|
+
// addAttributes(ans, ...target.attributes ?? [])
|
|
375
457
|
if (target.kind === 'struct') {
|
|
376
458
|
const flatTarget = flattenStruct(target, ctx, value);
|
|
377
459
|
for (const [key, value] of Object.entries(flatTarget)) {
|
|
@@ -392,10 +474,10 @@ function flattenStruct(type, ctx, value) {
|
|
|
392
474
|
}
|
|
393
475
|
function addAttributes(type, ...attributes) {
|
|
394
476
|
for (const attr of attributes) {
|
|
395
|
-
type.attributes ??= []
|
|
396
|
-
if (!type.attributes.some(a => a.name === attr.name)) {
|
|
397
|
-
|
|
398
|
-
}
|
|
477
|
+
// type.attributes ??= []
|
|
478
|
+
// if (!type.attributes.some(a => a.name === attr.name)) {
|
|
479
|
+
// type.attributes.push(attr)
|
|
480
|
+
// }
|
|
399
481
|
}
|
|
400
482
|
}
|
|
401
483
|
//# sourceMappingURL=index.js.map
|
package/lib/uri_processors.js
CHANGED
|
@@ -12,9 +12,7 @@ export const uriBinder = (uris, ctx) => {
|
|
|
12
12
|
if (!rel) {
|
|
13
13
|
continue;
|
|
14
14
|
}
|
|
15
|
-
rel = rel
|
|
16
|
-
.slice(0, -Extension.length)
|
|
17
|
-
.replace(/(^|\/)mod$/, '');
|
|
15
|
+
rel = rel.slice(0, -Extension.length).replace(/(^|\/)mod$/, '');
|
|
18
16
|
urisAndRels.push([uri, rel]);
|
|
19
17
|
}
|
|
20
18
|
// Now the value of `urisAndRels`:
|
|
@@ -23,8 +21,10 @@ export const uriBinder = (uris, ctx) => {
|
|
|
23
21
|
// A special check for the directory named `mcdoc`:
|
|
24
22
|
// If all files are put under this folder, we will treat that folder as the "root" instead.
|
|
25
23
|
if (urisAndRels.every(([_, rel]) => rel.startsWith(McdocRootPrefix))) {
|
|
26
|
-
urisAndRels = urisAndRels
|
|
27
|
-
|
|
24
|
+
urisAndRels = urisAndRels.map(([uri, rel]) => [
|
|
25
|
+
uri,
|
|
26
|
+
rel.slice(McdocRootPrefix.length),
|
|
27
|
+
]);
|
|
28
28
|
}
|
|
29
29
|
// Now the value of `urisAndRels`:
|
|
30
30
|
// file:///root/mcdoc/foo/mod.mcdoc -> foo
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spyglassmc/mcdoc",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"url": "https://github.com/SpyglassMC/Spyglass/issues"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@spyglassmc/core": "0.4.
|
|
29
|
-
"@spyglassmc/locales": "0.3.
|
|
28
|
+
"@spyglassmc/core": "0.4.1",
|
|
29
|
+
"@spyglassmc/locales": "0.3.1"
|
|
30
30
|
}
|
|
31
31
|
}
|