@slimlib/smart-mock 1.0.1 → 1.0.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.
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
"ReplacerFunction"
|
|
7
7
|
],
|
|
8
8
|
"sources": [
|
|
9
|
-
"
|
|
9
|
+
"index.js"
|
|
10
10
|
],
|
|
11
11
|
"sourcesContent": [
|
|
12
12
|
null
|
|
13
13
|
],
|
|
14
|
-
"mappings": ";
|
|
15
|
-
}
|
|
14
|
+
"mappings": ";yBAmBwBA,0BAA0BA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"
|
|
15
|
+
}
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* @enum {number}
|
|
3
|
-
*/
|
|
4
|
-
export const MockDataSource = /** @type {const} */ ({
|
|
1
|
+
export const MockDataSource = ({
|
|
5
2
|
root: 0,
|
|
6
3
|
get: 1,
|
|
7
4
|
call: 2,
|
|
@@ -13,58 +10,15 @@ export const MockDataSource = /** @type {const} */ ({
|
|
|
13
10
|
construct: 8,
|
|
14
11
|
});
|
|
15
12
|
|
|
16
|
-
/**
|
|
17
|
-
* @typedef {object} MockData
|
|
18
|
-
* @property {number} useCount
|
|
19
|
-
* @property {string | symbol} name
|
|
20
|
-
* @property {MockData} [parent]
|
|
21
|
-
* @property {number} source
|
|
22
|
-
* @property {unknown | unknown[]} [options]
|
|
23
|
-
* @property {{[key: string | symbol]: MockData}} [mocks]
|
|
24
|
-
* @property {MockData[]} [sideEffects]
|
|
25
|
-
* @property {string} [instanceName]
|
|
26
|
-
* @property {boolean} generated
|
|
27
|
-
* @property {Function} [target]
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* @typedef {(v: unknown) => unknown} ReplacerFunction
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* @template T
|
|
36
|
-
* @typedef {new (...args: unknown[]) => T} Constructor
|
|
37
|
-
*/
|
|
38
|
-
|
|
39
13
|
const mock = Symbol();
|
|
40
14
|
const unwrap = Symbol();
|
|
41
15
|
|
|
42
|
-
|
|
43
|
-
* @template T
|
|
44
|
-
* @typedef {object} Unwrappable
|
|
45
|
-
* @property {T} [unwrap]
|
|
46
|
-
* @property {MockData} [mock]
|
|
47
|
-
*/
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* @template T
|
|
51
|
-
* @param {T} value
|
|
52
|
-
* @returns {T}
|
|
53
|
-
*/
|
|
54
|
-
const unwrapValue = value => (value != null && /** @type {any} */ (value)[unwrap]) || value;
|
|
16
|
+
const unwrapValue = value => (value != null && (value)[unwrap]) || value;
|
|
55
17
|
|
|
56
|
-
|
|
57
|
-
* @template T
|
|
58
|
-
* @param {T} value
|
|
59
|
-
* @returns {MockData | undefined}
|
|
60
|
-
*/
|
|
61
|
-
const getMockData = value => (value != null && /** @type {any} */ (value)[mock]) || undefined;
|
|
18
|
+
const getMockData = value => (value != null && (value)[mock]) || undefined;
|
|
62
19
|
|
|
63
|
-
/**
|
|
64
|
-
* @returns {{createMock: <T extends object>(object: T, name: string) => T, generateGlobals: () => string, generate: (object: unknown) => string | null | undefined | RegExp | boolean}}
|
|
65
|
-
*/
|
|
66
20
|
export default function createRecordingMockFactory() {
|
|
67
|
-
|
|
21
|
+
|
|
68
22
|
const mockDatas = [];
|
|
69
23
|
|
|
70
24
|
let counter = 0;
|
|
@@ -75,12 +29,6 @@ export default function createRecordingMockFactory() {
|
|
|
75
29
|
generate,
|
|
76
30
|
};
|
|
77
31
|
|
|
78
|
-
/**
|
|
79
|
-
* @template {object} T
|
|
80
|
-
* @param {T} object
|
|
81
|
-
* @param {string} name
|
|
82
|
-
* @returns {T}
|
|
83
|
-
*/
|
|
84
32
|
function createMock(object, name) {
|
|
85
33
|
return createInternalMock(object, {
|
|
86
34
|
name,
|
|
@@ -90,35 +38,30 @@ export default function createRecordingMockFactory() {
|
|
|
90
38
|
});
|
|
91
39
|
}
|
|
92
40
|
|
|
93
|
-
/**
|
|
94
|
-
* @returns {string}
|
|
95
|
-
*/
|
|
96
41
|
function generateGlobals() {
|
|
97
|
-
|
|
42
|
+
|
|
98
43
|
const strings = [];
|
|
99
44
|
for (const mockData of mockDatas) {
|
|
100
45
|
if (mockData.generated) continue;
|
|
101
46
|
if (!mockData.instanceName && mockData.source !== MockDataSource.root) {
|
|
102
47
|
mockData.instanceName = getNextInstanceName();
|
|
103
48
|
}
|
|
104
|
-
const identifier = mockData?.instanceName ??
|
|
49
|
+
const identifier = mockData?.instanceName ?? (mockData?.name);
|
|
105
50
|
if (mockData.source !== MockDataSource.root) {
|
|
106
|
-
strings.push(`const ${identifier} = ${getAccessor(mockData,
|
|
51
|
+
strings.push(`const ${identifier} = ${getAccessor(mockData, (mockData.parent))}`);
|
|
107
52
|
}
|
|
108
53
|
for (const effect of mockData.sideEffects || []) {
|
|
109
|
-
switch (
|
|
54
|
+
switch ( (effect.source)) {
|
|
110
55
|
case MockDataSource.set:
|
|
111
56
|
strings.push(
|
|
112
57
|
identifier +
|
|
113
58
|
'.' +
|
|
114
|
-
|
|
59
|
+
(effect.name) +
|
|
115
60
|
' = ' +
|
|
116
|
-
stringify(effect.options,
|
|
61
|
+
stringify(effect.options, (replacer))
|
|
117
62
|
);
|
|
118
63
|
break;
|
|
119
|
-
|
|
120
|
-
// strings.push('Object.defineProperty(' + identifier + ', "' + (effect.name as string) + '", ' + stringify(effect.options, replacer as ReplacerFunction) + ')');
|
|
121
|
-
// break;
|
|
64
|
+
|
|
122
65
|
case MockDataSource.deleteProperty:
|
|
123
66
|
strings.push(`delete ${identifier}["${String(effect.name)}"]`);
|
|
124
67
|
break;
|
|
@@ -127,7 +70,7 @@ export default function createRecordingMockFactory() {
|
|
|
127
70
|
'Object.setPrototypeOf(' +
|
|
128
71
|
identifier +
|
|
129
72
|
', ' +
|
|
130
|
-
stringify(effect.options,
|
|
73
|
+
stringify(effect.options, (replacer)) +
|
|
131
74
|
')'
|
|
132
75
|
);
|
|
133
76
|
break;
|
|
@@ -137,7 +80,7 @@ export default function createRecordingMockFactory() {
|
|
|
137
80
|
case MockDataSource.call:
|
|
138
81
|
strings.push(
|
|
139
82
|
identifier +
|
|
140
|
-
getParameters(
|
|
83
|
+
getParameters( (effect.options), (replacer))
|
|
141
84
|
);
|
|
142
85
|
break;
|
|
143
86
|
}
|
|
@@ -145,83 +88,60 @@ export default function createRecordingMockFactory() {
|
|
|
145
88
|
}
|
|
146
89
|
return strings.join('\n');
|
|
147
90
|
|
|
148
|
-
/**
|
|
149
|
-
* @param {MockData} mockData
|
|
150
|
-
* @param {MockData} parent
|
|
151
|
-
* @returns {string}
|
|
152
|
-
*/
|
|
153
91
|
function getAccessor(mockData, parent) {
|
|
154
|
-
const parentName = parent?.instanceName ??
|
|
155
|
-
switch (
|
|
92
|
+
const parentName = parent?.instanceName ?? (parent?.name);
|
|
93
|
+
switch ( (mockData.source)) {
|
|
156
94
|
case MockDataSource.call:
|
|
157
95
|
return (
|
|
158
|
-
parentName + getParameters(
|
|
96
|
+
parentName + getParameters( (mockData.options), (replacer))
|
|
159
97
|
);
|
|
160
98
|
case MockDataSource.get:
|
|
161
99
|
return `${parentName}.${String(mockData.name)}`;
|
|
162
100
|
case MockDataSource.construct: {
|
|
163
|
-
const newTarget = stringify(mockData.target,
|
|
101
|
+
const newTarget = stringify(mockData.target, (replacer));
|
|
164
102
|
return parentName !== newTarget
|
|
165
103
|
? 'Reflect.construct(' +
|
|
166
104
|
parentName +
|
|
167
105
|
',' +
|
|
168
|
-
stringify(mockData.options,
|
|
106
|
+
stringify(mockData.options, (replacer)) +
|
|
169
107
|
',' +
|
|
170
108
|
newTarget +
|
|
171
109
|
')'
|
|
172
110
|
: 'new ' +
|
|
173
111
|
parentName +
|
|
174
|
-
getParameters(
|
|
112
|
+
getParameters( (mockData.options), (replacer));
|
|
175
113
|
}
|
|
176
114
|
}
|
|
177
115
|
return '';
|
|
178
116
|
}
|
|
179
117
|
}
|
|
180
118
|
|
|
181
|
-
/**
|
|
182
|
-
* @param {unknown} object
|
|
183
|
-
* @returns {string | null | undefined | RegExp | boolean}
|
|
184
|
-
*/
|
|
185
119
|
function generate(object) {
|
|
186
|
-
stringify(object,
|
|
187
|
-
return stringify(object,
|
|
120
|
+
stringify(object, (bumpReplacer));
|
|
121
|
+
return stringify(object, (replacer));
|
|
188
122
|
}
|
|
189
123
|
|
|
190
|
-
/**
|
|
191
|
-
* @param {object & { [key: symbol]: MockData }} value
|
|
192
|
-
* @returns {unknown}
|
|
193
|
-
*/
|
|
194
124
|
function bumpReplacer(value) {
|
|
195
125
|
const mockData = getMockData(value);
|
|
196
126
|
if (mockData) {
|
|
197
127
|
++mockData.useCount;
|
|
198
|
-
return getCode(mockData,
|
|
128
|
+
return getCode(mockData, (bumpReplacer), true);
|
|
199
129
|
}
|
|
200
130
|
return value;
|
|
201
131
|
}
|
|
202
132
|
|
|
203
|
-
/**
|
|
204
|
-
* @param {object & { [key: symbol]: MockData }} value
|
|
205
|
-
* @returns {unknown}
|
|
206
|
-
*/
|
|
207
133
|
function replacer(value) {
|
|
208
134
|
const mockData = getMockData(value);
|
|
209
135
|
if (mockData) {
|
|
210
|
-
return getCode(mockData,
|
|
136
|
+
return getCode(mockData, (replacer), true);
|
|
211
137
|
}
|
|
212
138
|
return value;
|
|
213
139
|
}
|
|
214
140
|
|
|
215
|
-
/**
|
|
216
|
-
* @param {MockData} value
|
|
217
|
-
* @param {ReplacerFunction} replacer
|
|
218
|
-
* @param {boolean} bumpCount
|
|
219
|
-
* @returns {string}
|
|
220
|
-
*/
|
|
221
141
|
function getCode(value, replacer, bumpCount) {
|
|
222
142
|
if (bumpCount && value.useCount > 1) {
|
|
223
143
|
if (value.source === MockDataSource.root) {
|
|
224
|
-
return
|
|
144
|
+
return (value.name);
|
|
225
145
|
}
|
|
226
146
|
if (!value.instanceName) {
|
|
227
147
|
value.instanceName = getNextInstanceName();
|
|
@@ -229,58 +149,42 @@ export default function createRecordingMockFactory() {
|
|
|
229
149
|
return value.instanceName;
|
|
230
150
|
}
|
|
231
151
|
value.generated = true;
|
|
232
|
-
switch (
|
|
152
|
+
switch ( (value.source)) {
|
|
233
153
|
case MockDataSource.call:
|
|
234
|
-
return getPrevCode(value) + getParameters(
|
|
154
|
+
return getPrevCode(value) + getParameters( (value.options), replacer);
|
|
235
155
|
case MockDataSource.get:
|
|
236
156
|
return `${getPrevCode(value)}.${String(value.name)}`;
|
|
237
157
|
case MockDataSource.root:
|
|
238
|
-
return
|
|
158
|
+
return (value.name);
|
|
239
159
|
case MockDataSource.construct: {
|
|
240
160
|
const prevCode = getPrevCode(value);
|
|
241
|
-
const newTarget = stringify(value.target,
|
|
161
|
+
const newTarget = stringify(value.target, (replacer));
|
|
242
162
|
return prevCode !== newTarget
|
|
243
163
|
? 'Reflect.construct(' +
|
|
244
164
|
prevCode +
|
|
245
165
|
',' +
|
|
246
|
-
stringify(value.options,
|
|
166
|
+
stringify(value.options, (replacer)) +
|
|
247
167
|
',' +
|
|
248
168
|
newTarget +
|
|
249
169
|
')'
|
|
250
170
|
: 'new ' +
|
|
251
171
|
prevCode +
|
|
252
|
-
getParameters(
|
|
172
|
+
getParameters( (value.options), (replacer));
|
|
253
173
|
}
|
|
254
174
|
}
|
|
255
175
|
return '';
|
|
256
176
|
|
|
257
|
-
/**
|
|
258
|
-
* @param {MockData} mockData
|
|
259
|
-
* @returns {string}
|
|
260
|
-
*/
|
|
261
177
|
function getPrevCode(mockData) {
|
|
262
178
|
return mockData.parent ? getCode(mockData.parent, replacer, bumpCount) : '';
|
|
263
179
|
}
|
|
264
180
|
}
|
|
265
181
|
|
|
266
|
-
/**
|
|
267
|
-
* @template {object} T
|
|
268
|
-
* @param {T} target
|
|
269
|
-
* @param {MockData} mockData
|
|
270
|
-
* @returns {T}
|
|
271
|
-
*/
|
|
272
182
|
function createInternalMock(target, mockData) {
|
|
273
183
|
mockDatas.push(mockData);
|
|
274
|
-
|
|
275
|
-
return
|
|
184
|
+
(target)[mock] = mockData;
|
|
185
|
+
return (
|
|
276
186
|
new Proxy(target, {
|
|
277
|
-
|
|
278
|
-
* @param {T} target
|
|
279
|
-
* @param {string | symbol} p
|
|
280
|
-
* @param {unknown} value
|
|
281
|
-
* @param {unknown} receiver
|
|
282
|
-
* @returns {boolean}
|
|
283
|
-
*/
|
|
187
|
+
|
|
284
188
|
set(target, p, value, receiver) {
|
|
285
189
|
const realValue = unwrapValue(value);
|
|
286
190
|
if (!mockData.sideEffects) {
|
|
@@ -298,11 +202,7 @@ export default function createRecordingMockFactory() {
|
|
|
298
202
|
Reflect.set(target, p, realValue, receiver);
|
|
299
203
|
return true;
|
|
300
204
|
},
|
|
301
|
-
|
|
302
|
-
* @param {object} target
|
|
303
|
-
* @param {string | symbol} p
|
|
304
|
-
* @returns {unknown}
|
|
305
|
-
*/
|
|
205
|
+
|
|
306
206
|
get(target, p) {
|
|
307
207
|
if (p === unwrap) return target;
|
|
308
208
|
if (p === mock) return mockData;
|
|
@@ -313,8 +213,8 @@ export default function createRecordingMockFactory() {
|
|
|
313
213
|
if (!mockData.mocks) {
|
|
314
214
|
mockData.mocks = Object.create(null);
|
|
315
215
|
}
|
|
316
|
-
if (!(
|
|
317
|
-
|
|
216
|
+
if (!( (mockData.mocks)[p])) {
|
|
217
|
+
(mockData.mocks)[p] = createInternalMock(value, {
|
|
318
218
|
useCount: 0,
|
|
319
219
|
name: p,
|
|
320
220
|
parent: mockData,
|
|
@@ -322,62 +222,37 @@ export default function createRecordingMockFactory() {
|
|
|
322
222
|
generated: false,
|
|
323
223
|
});
|
|
324
224
|
}
|
|
325
|
-
const result =
|
|
225
|
+
const result = (mockData.mocks)[p];
|
|
326
226
|
++mockData.useCount;
|
|
327
227
|
return result;
|
|
328
228
|
},
|
|
329
|
-
|
|
330
|
-
* @param {Constructor<T>} target
|
|
331
|
-
* @param {unknown[]} argArray
|
|
332
|
-
* @param {Function} newTarget
|
|
333
|
-
* @returns {T}
|
|
334
|
-
*/
|
|
229
|
+
|
|
335
230
|
construct(target, argArray, newTarget) {
|
|
336
231
|
const realTarget = unwrapValue(newTarget);
|
|
337
232
|
const realArguments = unwrapValue(argArray);
|
|
338
233
|
++mockData.useCount;
|
|
339
234
|
const result = Reflect.construct(
|
|
340
235
|
target,
|
|
341
|
-
|
|
342
|
-
|
|
236
|
+
(realArguments),
|
|
237
|
+
(realTarget)
|
|
343
238
|
);
|
|
344
239
|
return createInternalMock(result, {
|
|
345
240
|
useCount: 0,
|
|
346
241
|
name: '',
|
|
347
242
|
options: realArguments,
|
|
348
|
-
target:
|
|
243
|
+
target: (realTarget),
|
|
349
244
|
parent: mockData,
|
|
350
245
|
source: MockDataSource.construct,
|
|
351
246
|
generated: false,
|
|
352
247
|
});
|
|
353
248
|
},
|
|
354
|
-
|
|
355
|
-
* @param {T} target
|
|
356
|
-
* @param {string | symbol} property
|
|
357
|
-
* @param {PropertyDescriptor} attributes
|
|
358
|
-
* @returns {boolean}
|
|
359
|
-
*/
|
|
249
|
+
|
|
360
250
|
defineProperty(target, property, attributes) {
|
|
361
251
|
const realValue = unwrapValue(attributes);
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
// }
|
|
365
|
-
// mockData.sideEffects.push({
|
|
366
|
-
// useCount: 0,
|
|
367
|
-
// name: property,
|
|
368
|
-
// options: realValue,
|
|
369
|
-
// parent: mockData,
|
|
370
|
-
// source: MockDataSource.defineProperty,
|
|
371
|
-
// generated: false
|
|
372
|
-
// });
|
|
373
|
-
// ++mockData.useCount;
|
|
374
|
-
return Reflect.defineProperty(target, property, /** @type {PropertyDescriptor} */ (realValue));
|
|
252
|
+
|
|
253
|
+
return Reflect.defineProperty(target, property, (realValue));
|
|
375
254
|
},
|
|
376
|
-
|
|
377
|
-
* @param {object} target
|
|
378
|
-
* @param {string | symbol} p
|
|
379
|
-
* @returns {boolean}
|
|
380
|
-
*/
|
|
255
|
+
|
|
381
256
|
deleteProperty(target, p) {
|
|
382
257
|
if (!mockData.sideEffects) {
|
|
383
258
|
mockData.sideEffects = [];
|
|
@@ -394,11 +269,7 @@ export default function createRecordingMockFactory() {
|
|
|
394
269
|
const result = Reflect.deleteProperty(target, p);
|
|
395
270
|
return result;
|
|
396
271
|
},
|
|
397
|
-
|
|
398
|
-
* @param {T} target
|
|
399
|
-
* @param {object | null} v
|
|
400
|
-
* @returns {boolean}
|
|
401
|
-
*/
|
|
272
|
+
|
|
402
273
|
setPrototypeOf(target, v) {
|
|
403
274
|
const realValue = unwrapValue(v);
|
|
404
275
|
if (!mockData.sideEffects) {
|
|
@@ -415,10 +286,7 @@ export default function createRecordingMockFactory() {
|
|
|
415
286
|
++mockData.useCount;
|
|
416
287
|
return Reflect.setPrototypeOf(target, realValue);
|
|
417
288
|
},
|
|
418
|
-
|
|
419
|
-
* @param {T} target
|
|
420
|
-
* @returns {boolean}
|
|
421
|
-
*/
|
|
289
|
+
|
|
422
290
|
preventExtensions(target) {
|
|
423
291
|
if (!mockData.sideEffects) {
|
|
424
292
|
mockData.sideEffects = [];
|
|
@@ -434,17 +302,12 @@ export default function createRecordingMockFactory() {
|
|
|
434
302
|
++mockData.useCount;
|
|
435
303
|
return Reflect.preventExtensions(target);
|
|
436
304
|
},
|
|
437
|
-
|
|
438
|
-
* @param {Function} target
|
|
439
|
-
* @param {unknown} thisArg
|
|
440
|
-
* @param {unknown[]} argumentsList
|
|
441
|
-
* @returns {unknown}
|
|
442
|
-
*/
|
|
305
|
+
|
|
443
306
|
apply(target, thisArg, argumentsList) {
|
|
444
307
|
const realThis = unwrapValue(thisArg);
|
|
445
308
|
const realArguments = unwrapValue(argumentsList);
|
|
446
309
|
++mockData.useCount;
|
|
447
|
-
const result = Reflect.apply(target, realThis,
|
|
310
|
+
const result = Reflect.apply(target, realThis, (realArguments));
|
|
448
311
|
if (result === null || (typeof result !== 'object' && typeof result !== 'function')) {
|
|
449
312
|
if (!mockData.sideEffects) {
|
|
450
313
|
mockData.sideEffects = [];
|
|
@@ -473,29 +336,15 @@ export default function createRecordingMockFactory() {
|
|
|
473
336
|
);
|
|
474
337
|
}
|
|
475
338
|
|
|
476
|
-
/**
|
|
477
|
-
* @returns {string}
|
|
478
|
-
*/
|
|
479
339
|
function getNextInstanceName() {
|
|
480
340
|
return `tmp_${counter++}`;
|
|
481
341
|
}
|
|
482
342
|
}
|
|
483
343
|
|
|
484
|
-
/**
|
|
485
|
-
* @param {unknown[]} options
|
|
486
|
-
* @param {ReplacerFunction} replacer
|
|
487
|
-
* @returns {string}
|
|
488
|
-
*/
|
|
489
344
|
function getParameters(options, replacer) {
|
|
490
345
|
return `(${options.length ? options.map(value => stringify(value, replacer)).join(',') : ''})`;
|
|
491
346
|
}
|
|
492
347
|
|
|
493
|
-
/**
|
|
494
|
-
* stringify like functionality, recursively walks through objects and converts them to strings but leaves some basic values intact
|
|
495
|
-
* @param {unknown} value
|
|
496
|
-
* @param {ReplacerFunction} replacer
|
|
497
|
-
* @returns {string | null | undefined | RegExp | boolean}
|
|
498
|
-
*/
|
|
499
348
|
function stringify(value, replacer) {
|
|
500
349
|
const original = value;
|
|
501
350
|
value = replacer(value);
|
package/package.json
CHANGED
|
@@ -1,22 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"name": "@slimlib/smart-mock",
|
|
5
5
|
"description": "One more proxy based mock",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Konstantin Shutkin",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
-
"types": "./
|
|
11
|
-
"default": "./
|
|
10
|
+
"types": "./index.d.ts",
|
|
11
|
+
"default": "./index.js"
|
|
12
12
|
},
|
|
13
13
|
"./package.json": "./package.json"
|
|
14
14
|
},
|
|
15
|
-
"types": "./
|
|
16
|
-
"files": [
|
|
17
|
-
"src",
|
|
18
|
-
"types"
|
|
19
|
-
],
|
|
15
|
+
"types": "./index.d.ts",
|
|
20
16
|
"engines": {
|
|
21
17
|
"node": ">=20"
|
|
22
18
|
},
|
|
File without changes
|