@amxts/core 0.2.3 → 0.3.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 (71) hide show
  1. package/README.md +5 -5
  2. package/README.ru.md +5 -5
  3. package/as/amxts.d.ts +13 -3
  4. package/as/entities.ts +571 -501
  5. package/as/events.ts +3323 -557
  6. package/as/facade.ts +3079 -718
  7. package/as/fetch.ts +2 -2
  8. package/as/fs.ts +352 -59
  9. package/as/hlds.ts +121 -120
  10. package/as/hooks.ts +10585 -5177
  11. package/as/lib/check.ts +3 -2
  12. package/as/natives.ts +856 -1272
  13. package/as/promise.ts +37 -27
  14. package/as/vector.ts +94 -14
  15. package/includes/order.txt +17 -19
  16. package/node_modules/assemblyscript/std/assembly/index.d.ts +1 -1
  17. package/package.json +9 -6
  18. package/runtime/deps/assemblyscript/dist/asc.js +136 -42
  19. package/runtime/deps/assemblyscript/dist/assemblyscript.js +141 -141
  20. package/runtime/licenses/README.md +9 -7
  21. package/runtime/licenses/ReGameDLL-LICENSE +21 -0
  22. package/runtime/licenses/ReHLDS-LICENSE +21 -0
  23. package/runtime/licenses/zlib-LICENSE +22 -0
  24. package/runtime/natives.txt +618 -523
  25. package/runtime/patches/README.md +87 -1
  26. package/runtime/patches/assemblyscript-0.28.20-amxts.patch +4838 -416
  27. package/runtime/patches/wamr-2.4.5-amxts.patch +417 -5
  28. package/scripts/auto-imports.ts +1 -1
  29. package/scripts/build-identity.ts +102 -65
  30. package/scripts/build-wasm.ts +17 -13
  31. package/scripts/check.ts +4 -1
  32. package/scripts/client-messages.ts +73 -1
  33. package/scripts/compile-cache.ts +11 -8
  34. package/scripts/compile-pool.ts +6 -3
  35. package/scripts/compile-worker.ts +23 -23
  36. package/scripts/compile.ts +142 -19
  37. package/scripts/docs/as/amxts.ts +10 -4
  38. package/scripts/docs/as/facade.ts +1779 -275
  39. package/scripts/docs/as/fetch.ts +2 -2
  40. package/scripts/docs/as/fs.ts +190 -6
  41. package/scripts/docs/as/vector.ts +81 -0
  42. package/scripts/docs/entities.ts +12 -0
  43. package/scripts/docs/events.ts +49 -13
  44. package/scripts/docs/game.ts +6 -0
  45. package/scripts/docs/messages.ts +10 -1
  46. package/scripts/hlds-events.ts +11 -11
  47. package/scripts/includes.ts +3 -4
  48. package/scripts/leaf-natives.ts +84 -0
  49. package/scripts/player-fields.ts +1 -1
  50. package/scripts/plugin-natives.ts +1 -1
  51. package/scripts/prebuilt.ts +42 -11
  52. package/scripts/project.ts +63 -4
  53. package/scripts/reapi-events.ts +9 -9
  54. package/scripts/server-env.ts +145 -0
  55. package/scripts/shared-modules.ts +23 -7
  56. package/scripts/system.ts +1 -1
  57. package/scripts/tracked-fs.ts +11 -2
  58. package/scripts/typed-commands.ts +153 -15
  59. package/scripts/upgrade-calls.ts +225 -0
  60. package/scripts/upgrade-names.ts +1 -4
  61. package/scripts/upgrade.ts +47 -34
  62. package/src/cli-api.mjs +3 -2
  63. package/src/system.mjs +21 -11
  64. package/src/testing/compile.ts +31 -8
  65. package/src/testing/coroutines.ts +0 -10
  66. package/src/testing/memory.ts +89 -2
  67. package/src/testing/natives.ts +67 -30
  68. package/src/testing/server.ts +1094 -155
  69. package/src/testing/tables.ts +23 -6
  70. package/src/typecheck.mjs +110 -0
  71. package/runtime/licenses/Bun-LICENSE.md +0 -84
package/as/promise.ts CHANGED
@@ -30,6 +30,8 @@
30
30
  // (__tryDepth): the count is kept across an await and put back around a
31
31
  // spawn and a resume.
32
32
 
33
+ import { __ServerEvent } from "./facade";
34
+
33
35
  // ---------------------------------------------------------------- host
34
36
 
35
37
  // @ts-ignore: decorator
@@ -41,11 +43,9 @@
41
43
  // @ts-ignore: decorator
42
44
  @external("env", "co_wake") declare function __co_host_wake(): void;
43
45
  // @ts-ignore: decorator
44
- @external("env", "co_id") declare function __co_host_id(): i32;
45
- // @ts-ignore: decorator
46
- @external("env", "task") declare function __co_host_task(secondsBits: i32, fn: i32, id: i32, repeat: i32): i32;
46
+ @external("env", "task") declare function __co_host_task(secondsBits: i32, fn: i32, repeat: i32): i32;
47
47
  // @ts-ignore: decorator
48
- @external("env", "stop_task") declare function __co_host_stop_task(id: i32): i32;
48
+ @external("env", "stop_task") declare function __co_host_stop_task(slot: i32): i32;
49
49
  // @ts-ignore: decorator
50
50
  @external("env", "on") declare function __co_host_on(event: string, fn: i32, shape: i32): void;
51
51
 
@@ -829,10 +829,15 @@ function __co_current(): __Coroutine {
829
829
  return changetype<usize>(__spawnArgs);
830
830
  }
831
831
 
832
- /** @hidden a managed argument, held while the call lasts. */
832
+ /**
833
+ * @hidden a managed argument, held while the call lasts. A server event's
834
+ * object is read after the call: it keeps its fields (__ServerEvent).
835
+ */
833
836
  // @ts-ignore: decorator
834
837
  @global function __co_keep(value: usize): void {
835
- if (value != 0) __keep.push(changetype<Object>(value));
838
+ if (value == 0) return;
839
+ if (changetype<Object>(value) instanceof __ServerEvent) changetype<__ServerEvent>(value).__keep();
840
+ __keep.push(changetype<Object>(value));
836
841
  }
837
842
 
838
843
  /** @hidden runs an async function's body as a coroutine - see the top of this file. */
@@ -1167,9 +1172,8 @@ function __co_reportUnhandled(): void {
1167
1172
  /** A signal that aborts by itself after `ms`, with an Error named `"TimeoutError"`. */
1168
1173
  static timeout(ms: number): AbortSignal {
1169
1174
  const signal = new AbortSignal();
1170
- const id = __co_host_id();
1171
- __timeouts.set(id, signal);
1172
- __co_host_task(reinterpret<i32>(<f32>(ms / 1000.0)), __co_timeoutFired.index, id, 0);
1175
+ const slot = __co_host_task(reinterpret<i32>(<f32>(ms / 1000.0)), __co_timeoutFired.index, 0);
1176
+ if (slot >= 0) __timeouts[slot] = signal;
1173
1177
  return signal;
1174
1178
  }
1175
1179
 
@@ -1263,13 +1267,14 @@ class __CoroutineAbort extends __AbortWatch {
1263
1267
  }
1264
1268
  }
1265
1269
 
1270
+ // AbortSignal.timeout's signals, by the slot the module keeps their timers in.
1266
1271
  // @ts-ignore: decorator
1267
- @lazy const __timeouts = new Map<i32, AbortSignal>();
1272
+ @lazy const __timeouts: (AbortSignal | null)[] = [];
1268
1273
 
1269
- function __co_timeoutFired(id: i32): void {
1270
- if (!__timeouts.has(id)) return;
1271
- const signal = __timeouts.get(id);
1272
- __timeouts.delete(id);
1274
+ function __co_timeoutFired(slot: i32): void {
1275
+ const signal = unchecked(__timeouts[slot]);
1276
+ unchecked(__timeouts[slot] = null);
1277
+ if (!signal) return;
1273
1278
  const error = new Error("The operation was aborted due to timeout");
1274
1279
  error.name = "TimeoutError";
1275
1280
  signal.__abort(error);
@@ -1314,28 +1319,31 @@ function __co_timeoutFired(id: i32): void {
1314
1319
  class __Sleep extends __AbortWatch {
1315
1320
  guard: __AbortGuard | null = null;
1316
1321
 
1317
- constructor(public promise: Promise<void>, public id: i32) {
1322
+ // The slot the module keeps its timer in, -1 before it is armed.
1323
+ slot: i32 = -1;
1324
+
1325
+ constructor(public promise: Promise<void>) {
1318
1326
  super();
1319
1327
  }
1320
1328
 
1321
1329
  run(reason: Error): void {
1322
- if (!__sleeps.has(this.id)) return;
1323
- __sleeps.delete(this.id);
1324
- __co_host_stop_task(this.id);
1330
+ if (this.slot < 0 || unchecked(__sleeps[this.slot]) != this) return;
1331
+ unchecked(__sleeps[this.slot] = null);
1332
+ __co_host_stop_task(this.slot);
1325
1333
  (this.guard as __AbortGuard).release();
1326
1334
  this.promise.__reject(reason);
1327
1335
  }
1328
1336
  }
1329
1337
 
1338
+ // The sleeps that wait, by the slot the module keeps their timers in.
1330
1339
  // @ts-ignore: decorator
1331
- @lazy const __sleeps = new Map<i32, __Sleep>();
1340
+ @lazy const __sleeps: (__Sleep | null)[] = [];
1332
1341
 
1333
1342
  /** @hidden the facade's sleep(): fulfilled after `ms`, rejected if a signal aborts first. */
1334
1343
  // @ts-ignore: decorator
1335
1344
  @global export function __co_sleep(ms: f64, signal: AbortSignal | null): Promise<void> {
1336
1345
  const promise = __co_promise<void>();
1337
- const id = __co_host_id();
1338
- const sleep = new __Sleep(promise, id);
1346
+ const sleep = new __Sleep(promise);
1339
1347
  const guard = new __AbortGuard(sleep, signal);
1340
1348
  sleep.guard = guard;
1341
1349
 
@@ -1346,15 +1354,17 @@ class __Sleep extends __AbortWatch {
1346
1354
  return promise;
1347
1355
  }
1348
1356
 
1349
- __sleeps.set(id, sleep);
1350
- __co_host_task(reinterpret<i32>(<f32>(ms / 1000.0)), __co_sleepFired.index, id, 0);
1357
+ const slot = __co_host_task(reinterpret<i32>(<f32>(ms / 1000.0)), __co_sleepFired.index, 0);
1358
+ if (slot < 0) return promise;
1359
+ sleep.slot = slot;
1360
+ __sleeps[slot] = sleep;
1351
1361
  return promise;
1352
1362
  }
1353
1363
 
1354
- function __co_sleepFired(id: i32): void {
1355
- if (!__sleeps.has(id)) return;
1356
- const sleep = __sleeps.get(id);
1357
- __sleeps.delete(id);
1364
+ function __co_sleepFired(slot: i32): void {
1365
+ const sleep = unchecked(__sleeps[slot]);
1366
+ unchecked(__sleeps[slot] = null);
1367
+ if (!sleep) return;
1358
1368
  (sleep.guard as __AbortGuard).release();
1359
1369
  sleep.promise.__fulfillVoid();
1360
1370
  }
package/as/vector.ts CHANGED
@@ -8,27 +8,45 @@
8
8
 
9
9
  /** Three numbers, x y z, with the math a plugin needs on them. */
10
10
  export class Vector extends Array<number> {
11
- // x, y and z read and write their element without the index's check: a
12
- // Vector is made with room for three, which a shorter length leaves in
13
- // place, and the check was a call - most of what reading one cost.
14
- constructor(x: number = 0.0, y: number = 0.0, z: number = 0.0) {
15
- super(3);
16
- unchecked(this[0] = x);
17
- unchecked(this[1] = y);
18
- unchecked(this[2] = z);
11
+ // The three numbers live in the object itself, after the array's fields:
12
+ // a new Vector is one allocation, not an array and its buffer. Grown past
13
+ // three, they move out to a buffer of their own, as any array's do.
14
+ private room0: f64 = 0;
15
+ private room1: f64 = 0;
16
+ private room2: f64 = 0;
17
+
18
+ // x, y and z are loads and stores at the array's data: a Vector is made
19
+ // with room for three, which a shorter length leaves in place. An index,
20
+ // even unchecked, was a call - and a write a checked one, which may grow.
21
+ // The getters are inlined; a setter is small enough for a full build to
22
+ // inline it (an accessor pair takes one decorator in the editor). The
23
+ // constructor is inlined too, so a Vector that never leaves the function
24
+ // that makes it - `player.origin.x` - is made in that function's frame.
25
+ // @ts-ignore: decorator
26
+ @inline constructor(x: number = 0.0, y: number = 0.0, z: number = 0.0) {
27
+ // @ts-ignore: the compiler's own form of Array's constructor, which the editor's typings leave out
28
+ super(3, true);
29
+ // The array's buffer is the object itself, its data the room.
30
+ const self = changetype<usize>(this);
31
+ store<usize>(self, self, offsetof<ArrayBufferView>("buffer"));
32
+ store<usize>(self, self + offsetof<Vector>("room0"), offsetof<ArrayBufferView>("dataStart"));
33
+ store<i32>(self, 3 << alignof<f64>(), offsetof<ArrayBufferView>("byteLength"));
34
+ store<f64>(this.dataStart, x);
35
+ store<f64>(this.dataStart, y, 8);
36
+ store<f64>(this.dataStart, z, 16);
19
37
  }
20
38
 
21
39
  /** The x coordinate, `vector[0]`. */
22
- get x() { return unchecked(this[0]); }
23
- set x(value: number) { unchecked(this[0] = value); }
40
+ @inline get x(): number { return load<f64>(this.dataStart); }
41
+ set x(value: number) { store<f64>(this.dataStart, value); }
24
42
 
25
43
  /** The y coordinate, `vector[1]`. */
26
- get y() { return unchecked(this[1]); }
27
- set y(value: number) { unchecked(this[1] = value); }
44
+ @inline get y(): number { return load<f64>(this.dataStart, 8); }
45
+ set y(value: number) { store<f64>(this.dataStart, value, 8); }
28
46
 
29
47
  /** The z coordinate, `vector[2]`. */
30
- get z() { return unchecked(this[2]); }
31
- set z(value: number) { unchecked(this[2] = value); }
48
+ @inline get z(): number { return load<f64>(this.dataStart, 16); }
49
+ set z(value: number) { store<f64>(this.dataStart, value, 16); }
32
50
 
33
51
  /** A new vector: this one plus `other`, number by number. */
34
52
  add(other: number[]) {
@@ -65,4 +83,66 @@ export class Vector extends Array<number> {
65
83
  const size = this.magnitude();
66
84
  return size > 0 ? this.scale(1 / size) : new Vector();
67
85
  }
86
+
87
+ /**
88
+ * The direction angles look in, one unit long:
89
+ * `Vector.fromAngles(player.viewAngle).scale(500)` is a push the way the
90
+ * player looks.
91
+ *
92
+ * Pawn: `angle_vector(..., ANGLEVECTOR_FORWARD, ...)`, `velocity_by_aim`
93
+ */
94
+ static fromAngles(angles: number[]): Vector {
95
+ const pitch = angles[0] * DEGREES;
96
+ const yaw = angles[1] * DEGREES;
97
+ const across = Math.cos(pitch);
98
+ return new Vector(across * Math.cos(yaw), across * Math.sin(yaw), -Math.sin(pitch));
99
+ }
100
+
101
+ /**
102
+ * The three directions of angles, one unit long each: where they look,
103
+ * to their right and above them - `const { forward, right } =
104
+ * Vector.directions(player.viewAngle)`.
105
+ *
106
+ * Pawn: `angle_vector`, `engfunc(EngFunc_MakeVectors, ...)`
107
+ */
108
+ static directions(angles: number[]): Directions {
109
+ const pitch = angles[0] * DEGREES;
110
+ const yaw = angles[1] * DEGREES;
111
+ const roll = angles[2] * DEGREES;
112
+ const sp = Math.sin(pitch), cp = Math.cos(pitch);
113
+ const sy = Math.sin(yaw), cy = Math.cos(yaw);
114
+ const sr = Math.sin(roll), cr = Math.cos(roll);
115
+ return {
116
+ forward: new Vector(cp * cy, cp * sy, -sp),
117
+ right: new Vector(-sr * sp * cy + cr * sy, -sr * sp * sy - cr * cy, -sr * cp),
118
+ up: new Vector(cr * sp * cy + sr * sy, cr * sp * sy - sr * cy, cr * cp),
119
+ };
120
+ }
121
+
122
+ /**
123
+ * The angles that look along this vector, as a player's view holds them -
124
+ * a pitch below the horizon positive: `player.viewAngle =
125
+ * target.subtract(player.eyes).toAngles()`. `Vector.fromAngles` turns
126
+ * them back.
127
+ *
128
+ * Pawn: `vector_to_angle`, `engfunc(EngFunc_VecToAngles, ...)`
129
+ */
130
+ toAngles(): Vector {
131
+ const x = this[0], y = this[1], z = this[2];
132
+ if (x == 0 && y == 0) return new Vector(z > 0 ? -90 : 90, 0, 0);
133
+ const yaw = Math.atan2(y, x) / DEGREES;
134
+ return new Vector(-Math.atan2(z, Math.sqrt(x * x + y * y)) / DEGREES, yaw < 0 ? yaw + 360 : yaw, 0);
135
+ }
136
+ }
137
+
138
+ const DEGREES: f64 = Math.PI / 180;
139
+
140
+ /** The three directions of angles: ahead, to the right and up - `Vector.directions`. */
141
+ export interface Directions {
142
+ /** The direction they look in. */
143
+ forward: Vector;
144
+ /** To their right. */
145
+ right: Vector;
146
+ /** Above them. */
147
+ up: Vector;
68
148
  }
@@ -1,4 +1,4 @@
1
- # Which includes the host plugin pulls in, and in what order.
1
+ # Which includes the natives' image pulls in, and in what order.
2
2
  # This cannot be derived from the .inc files: an include may use reapi's
3
3
  # constants without including reapi itself. Files missing from this list are still
4
4
  # parsed for their natives — reapi.inc, for instance, already pulls in
@@ -20,28 +20,26 @@ universal_config
20
20
  menu_core
21
21
 
22
22
  # engine, fakemeta, nvault and geoip are stock AMX Mod X modules shipped with
23
- # the server (addons/amxmodx/modules/); geoip is listed in modules.ini and the
24
- # others are auto-detected from the `#pragma reqlib` amxxpc writes into the
25
- # .amxx. The module itself calls engine's get_global_int and fakemeta's
26
- # pev_valid and get_pdata_int to find the engine's edicts (runtime/src/fields.h).
27
- # resemiclip is a third-party module, present as a DLL and enabled explicitly
28
- # in modules.ini.
23
+ # the server (addons/amxmodx/modules/); the image names each module its
24
+ # includes ask for, and AMX Mod X loads it as it loads the image, as it does
25
+ # for a plugin's includes. resemiclip is a third-party module, present as a
26
+ # DLL and enabled explicitly in modules.ini.
29
27
  #
30
28
  # universal_config and menu_core are not modules: their natives come from
31
- # universal_config.amxx and menu_core.amxx, which plugins.ini loads ahead of
32
- # amxts_host.amxx. AMX Mod X resolves plugin-registered natives after every
33
- # plugin has run plugin_natives(), so the host links against them fine, and a
34
- # server without those two plugins loads it too (below).
29
+ # plugins - a Pawn plugin's register_native, or an amxts module's exported
30
+ # natives. AMX Mod X binds the natives plugins register in the image once
31
+ # every plugin has loaded, and a server without those plugins loads it too
32
+ # (below).
35
33
 
36
34
  # A line starting with `-` is a deny marker: the include is still resolved and
37
35
  # parsed (amxxpc needs the file on disk to compile an include that #includes
38
- # it), but its natives are never pulled into the host's native table. None is
36
+ # it), but its natives are never pulled into the image's native table. None is
39
37
  # needed today.
40
38
  #
41
- # A native the server does not have does not keep the host from loading: the
42
- # host's native filter lets it load without the native, and a call to it
43
- # answers 0 and says so once. So the includes whose natives a server may lack
44
- # are pulled like any other: reapi's add-ons - Reunion's natives exist only
45
- # when reapi finds Reunion, VTC's and ReChecker's only with those plugins - and
46
- # geoip, whose module turns its natives off when the GeoIP2 databases are not
47
- # there. The facade asks before it calls one (has_reunion).
39
+ # A native the server does not have does not keep the image from loading: its
40
+ # entry stays unbound, and a call to it answers 0 and says so once. So the
41
+ # includes whose natives a server may lack are pulled like any other: reapi's
42
+ # add-ons - Reunion's natives exist only when reapi finds Reunion, VTC's and
43
+ # ReChecker's only with those plugins - and geoip, whose module turns its
44
+ # natives off when the GeoIP2 databases are not there. The facade asks before
45
+ # it calls one (has_reunion).
@@ -2094,6 +2094,7 @@ declare class Array<T> {
2094
2094
  reverse(): this;
2095
2095
  /** Flattens an array of arrays. If any null entries exist in the array, they are ignored, unlike JavaScript's version of Array#flat(). */
2096
2096
  flat(): T extends unknown[] ? T : never;
2097
+ flatMap<U>(callbackfn: (value: T, index: i32, array: Array<T>) => U[]): Array<U>;
2097
2098
  toString(): string;
2098
2099
  }
2099
2100
 
@@ -2634,7 +2635,6 @@ declare const Mathf: IMath<f32>;
2634
2635
  /** Environmental abort function. */
2635
2636
  declare function abort(msg?: string | null, fileName?: string | null, lineNumber?: i32, columnNumber?: i32): never;
2636
2637
  /** Environmental tracing function. */
2637
- declare function trace(msg: string, n?: i32, a0?: f64, a1?: f64, a2?: f64, a3?: f64, a4?: f64): void;
2638
2638
  /** Environmental seeding function. */
2639
2639
  declare function seed(): f64;
2640
2640
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@amxts/core",
3
3
  "type": "module",
4
- "version": "0.2.3",
4
+ "version": "0.3.0",
5
5
  "description": "Counter-Strike 1.6 plugins in TypeScript: compiled ahead of time and run inside AMX Mod X, next to Pawn plugins.",
6
6
  "author": {
7
7
  "name": "kukson777",
@@ -63,6 +63,7 @@
63
63
  "scripts/file-contents.ts",
64
64
  "scripts/hlds-events.ts",
65
65
  "scripts/includes.ts",
66
+ "scripts/leaf-natives.ts",
66
67
  "scripts/player-fields.ts",
67
68
  "scripts/plugin-cache.ts",
68
69
  "scripts/plugin-natives.ts",
@@ -71,6 +72,7 @@
71
72
  "scripts/project.ts",
72
73
  "scripts/reapi-events.ts",
73
74
  "scripts/run.ts",
75
+ "scripts/server-env.ts",
74
76
  "scripts/shared-modules.ts",
75
77
  "scripts/source-map.ts",
76
78
  "scripts/system.ts",
@@ -79,6 +81,7 @@
79
81
  "scripts/typed-configs.ts",
80
82
  "scripts/ui.ts",
81
83
  "scripts/upgrade.ts",
84
+ "scripts/upgrade-calls.ts",
82
85
  "scripts/upgrade-menus.ts",
83
86
  "scripts/upgrade-names.ts",
84
87
  "includes/*.inc",
@@ -98,18 +101,18 @@
98
101
  "access": "public"
99
102
  },
100
103
  "dependencies": {
101
- "@amxts/cli": "^0.2.1",
104
+ "@amxts/cli": "^0.3.0",
102
105
  "bun": "^1.4.2",
103
106
  "typescript": "^5.6.0",
104
107
  "assemblyscript": "0.28.20"
105
108
  },
106
109
  "optionalDependencies": {
107
- "@amxts/wamrc-linux-x64": "^0.2.3",
108
- "@amxts/wamrc-win32-x64": "^0.2.3"
110
+ "@amxts/wamrc-linux-x64": "^0.3.0",
111
+ "@amxts/wamrc-win32-x64": "^0.3.0"
109
112
  },
110
113
  "devDependencies": {
111
- "@amxts/config-core": "^0.1.2",
112
- "@amxts/menu-core": "^0.2.1",
114
+ "@amxts/config-core": "^0.2.0",
115
+ "@amxts/menu-core": "^0.3.0",
113
116
  "@e18e/eslint-plugin": "^0.8.1",
114
117
  "@eslint-community/eslint-plugin-eslint-comments": "^4.8.1",
115
118
  "@stylistic/eslint-plugin": "^5.10.0",