@amxts/core 0.1.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 (247) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +162 -0
  3. package/README.ru.md +162 -0
  4. package/amxmodx/base/include/amxconst.inc +594 -0
  5. package/amxmodx/base/include/amxmisc.inc +902 -0
  6. package/amxmodx/base/include/amxmodx.inc +3447 -0
  7. package/amxmodx/base/include/amxmodx_version.inc +20 -0
  8. package/amxmodx/base/include/cellarray.inc +525 -0
  9. package/amxmodx/base/include/cellstack.inc +166 -0
  10. package/amxmodx/base/include/celltrie.inc +412 -0
  11. package/amxmodx/base/include/core.inc +202 -0
  12. package/amxmodx/base/include/csstats.inc +298 -0
  13. package/amxmodx/base/include/csstats_const.inc +29 -0
  14. package/amxmodx/base/include/cstrike.inc +1287 -0
  15. package/amxmodx/base/include/cstrike_const.inc +521 -0
  16. package/amxmodx/base/include/csx.inc +259 -0
  17. package/amxmodx/base/include/cvars.inc +547 -0
  18. package/amxmodx/base/include/datapack.inc +161 -0
  19. package/amxmodx/base/include/dbi.inc +154 -0
  20. package/amxmodx/base/include/dodconst.inc +152 -0
  21. package/amxmodx/base/include/dodfun.inc +152 -0
  22. package/amxmodx/base/include/dodstats.inc +69 -0
  23. package/amxmodx/base/include/dodx.inc +161 -0
  24. package/amxmodx/base/include/engine.inc +1343 -0
  25. package/amxmodx/base/include/engine_const.inc +310 -0
  26. package/amxmodx/base/include/engine_stocks.inc +248 -0
  27. package/amxmodx/base/include/esf.inc +70 -0
  28. package/amxmodx/base/include/esf_const.inc +74 -0
  29. package/amxmodx/base/include/fakemeta.inc +1127 -0
  30. package/amxmodx/base/include/fakemeta_const.inc +793 -0
  31. package/amxmodx/base/include/fakemeta_stocks.inc +395 -0
  32. package/amxmodx/base/include/fakemeta_util.inc +882 -0
  33. package/amxmodx/base/include/file.inc +666 -0
  34. package/amxmodx/base/include/float.inc +422 -0
  35. package/amxmodx/base/include/fun.inc +333 -0
  36. package/amxmodx/base/include/gameconfig.inc +93 -0
  37. package/amxmodx/base/include/geoip.inc +264 -0
  38. package/amxmodx/base/include/ham_const.inc +4352 -0
  39. package/amxmodx/base/include/hamsandwich.inc +439 -0
  40. package/amxmodx/base/include/hlsdk_const.inc +808 -0
  41. package/amxmodx/base/include/json.inc +777 -0
  42. package/amxmodx/base/include/lang.inc +119 -0
  43. package/amxmodx/base/include/message_const.inc +1130 -0
  44. package/amxmodx/base/include/message_stocks.inc +161 -0
  45. package/amxmodx/base/include/messages.inc +604 -0
  46. package/amxmodx/base/include/newmenus.inc +387 -0
  47. package/amxmodx/base/include/ns.inc +754 -0
  48. package/amxmodx/base/include/ns_const.inc +196 -0
  49. package/amxmodx/base/include/nvault.inc +154 -0
  50. package/amxmodx/base/include/regex.inc +348 -0
  51. package/amxmodx/base/include/sockets.inc +183 -0
  52. package/amxmodx/base/include/sorting.inc +107 -0
  53. package/amxmodx/base/include/sqlx.inc +582 -0
  54. package/amxmodx/base/include/string.inc +786 -0
  55. package/amxmodx/base/include/string_const.inc +156 -0
  56. package/amxmodx/base/include/string_stocks.inc +325 -0
  57. package/amxmodx/base/include/textparse_ini.inc +214 -0
  58. package/amxmodx/base/include/textparse_smc.inc +261 -0
  59. package/amxmodx/base/include/tfcconst.inc +84 -0
  60. package/amxmodx/base/include/tfcstats.inc +62 -0
  61. package/amxmodx/base/include/tfcx.inc +148 -0
  62. package/amxmodx/base/include/time.inc +118 -0
  63. package/amxmodx/base/include/tsconst.inc +154 -0
  64. package/amxmodx/base/include/tsfun.inc +198 -0
  65. package/amxmodx/base/include/tsstats.inc +65 -0
  66. package/amxmodx/base/include/tsx.inc +88 -0
  67. package/amxmodx/base/include/vault.inc +60 -0
  68. package/amxmodx/base/include/vector.inc +131 -0
  69. package/amxmodx/base/include/xs.inc +1570 -0
  70. package/as/amxts.d.ts +153 -0
  71. package/as/constants.ts +4730 -0
  72. package/as/effects.ts +1449 -0
  73. package/as/entities.ts +5482 -0
  74. package/as/events.ts +3054 -0
  75. package/as/facade.ts +4639 -0
  76. package/as/flags.ts +299 -0
  77. package/as/fs.ts +287 -0
  78. package/as/hooks.ts +18536 -0
  79. package/as/kit.ts +25 -0
  80. package/as/lib/check.ts +86 -0
  81. package/as/modules/http.ts +184 -0
  82. package/as/natives.ts +11845 -0
  83. package/as/os.ts +30 -0
  84. package/as/promise.ts +1392 -0
  85. package/as/promise.types.d.ts +82 -0
  86. package/as/remote.ts +288 -0
  87. package/as/tsconfig.json +22 -0
  88. package/as/vector.ts +65 -0
  89. package/bin/amxts.mjs +10 -0
  90. package/includes/menu_core.inc +355 -0
  91. package/includes/order.txt +54 -0
  92. package/includes/sources.json +27 -0
  93. package/includes/universal_config.inc +355 -0
  94. package/lint/oxlint-plugin.mjs +77 -0
  95. package/node_modules/assemblyscript/LICENSE +201 -0
  96. package/node_modules/assemblyscript/NOTICE +93 -0
  97. package/node_modules/assemblyscript/package.json +63 -0
  98. package/node_modules/assemblyscript/std/README.md +6 -0
  99. package/node_modules/assemblyscript/std/assembly/array.ts +526 -0
  100. package/node_modules/assemblyscript/std/assembly/arraybuffer.ts +77 -0
  101. package/node_modules/assemblyscript/std/assembly/atomics.ts +127 -0
  102. package/node_modules/assemblyscript/std/assembly/bindings/asyncify.ts +16 -0
  103. package/node_modules/assemblyscript/std/assembly/bindings/dom.ts +291 -0
  104. package/node_modules/assemblyscript/std/assembly/bindings/node.ts +6 -0
  105. package/node_modules/assemblyscript/std/assembly/builtins.ts +2631 -0
  106. package/node_modules/assemblyscript/std/assembly/compat.ts +2 -0
  107. package/node_modules/assemblyscript/std/assembly/console.ts +42 -0
  108. package/node_modules/assemblyscript/std/assembly/crypto.ts +9 -0
  109. package/node_modules/assemblyscript/std/assembly/dataview.ts +181 -0
  110. package/node_modules/assemblyscript/std/assembly/date.ts +373 -0
  111. package/node_modules/assemblyscript/std/assembly/diagnostics.ts +11 -0
  112. package/node_modules/assemblyscript/std/assembly/error.ts +44 -0
  113. package/node_modules/assemblyscript/std/assembly/function.ts +38 -0
  114. package/node_modules/assemblyscript/std/assembly/index.d.ts +2822 -0
  115. package/node_modules/assemblyscript/std/assembly/iterator.ts +35 -0
  116. package/node_modules/assemblyscript/std/assembly/map.ts +260 -0
  117. package/node_modules/assemblyscript/std/assembly/math.ts +3289 -0
  118. package/node_modules/assemblyscript/std/assembly/memory.ts +123 -0
  119. package/node_modules/assemblyscript/std/assembly/number.ts +388 -0
  120. package/node_modules/assemblyscript/std/assembly/object.ts +36 -0
  121. package/node_modules/assemblyscript/std/assembly/performance.ts +9 -0
  122. package/node_modules/assemblyscript/std/assembly/polyfills.ts +27 -0
  123. package/node_modules/assemblyscript/std/assembly/process.ts +50 -0
  124. package/node_modules/assemblyscript/std/assembly/reference.ts +48 -0
  125. package/node_modules/assemblyscript/std/assembly/regexp.ts +12 -0
  126. package/node_modules/assemblyscript/std/assembly/rt/README.md +83 -0
  127. package/node_modules/assemblyscript/std/assembly/rt/common.ts +81 -0
  128. package/node_modules/assemblyscript/std/assembly/rt/index-incremental.ts +2 -0
  129. package/node_modules/assemblyscript/std/assembly/rt/index-minimal.ts +2 -0
  130. package/node_modules/assemblyscript/std/assembly/rt/index-stub.ts +1 -0
  131. package/node_modules/assemblyscript/std/assembly/rt/index.d.ts +37 -0
  132. package/node_modules/assemblyscript/std/assembly/rt/itcms.ts +419 -0
  133. package/node_modules/assemblyscript/std/assembly/rt/rtrace.ts +15 -0
  134. package/node_modules/assemblyscript/std/assembly/rt/stub.ts +133 -0
  135. package/node_modules/assemblyscript/std/assembly/rt/tcms.ts +254 -0
  136. package/node_modules/assemblyscript/std/assembly/rt/tlsf.ts +592 -0
  137. package/node_modules/assemblyscript/std/assembly/rt.ts +87 -0
  138. package/node_modules/assemblyscript/std/assembly/set.ts +220 -0
  139. package/node_modules/assemblyscript/std/assembly/shared/feature.ts +65 -0
  140. package/node_modules/assemblyscript/std/assembly/shared/runtime.ts +11 -0
  141. package/node_modules/assemblyscript/std/assembly/shared/target.ts +11 -0
  142. package/node_modules/assemblyscript/std/assembly/shared/tsconfig.json +11 -0
  143. package/node_modules/assemblyscript/std/assembly/shared/typeinfo.ts +72 -0
  144. package/node_modules/assemblyscript/std/assembly/staticarray.ts +401 -0
  145. package/node_modules/assemblyscript/std/assembly/string.ts +847 -0
  146. package/node_modules/assemblyscript/std/assembly/symbol.ts +114 -0
  147. package/node_modules/assemblyscript/std/assembly/table.ts +16 -0
  148. package/node_modules/assemblyscript/std/assembly/tsconfig.json +6 -0
  149. package/node_modules/assemblyscript/std/assembly/typedarray.ts +1945 -0
  150. package/node_modules/assemblyscript/std/assembly/uri.ts +17 -0
  151. package/node_modules/assemblyscript/std/assembly/util/bytes.ts +107 -0
  152. package/node_modules/assemblyscript/std/assembly/util/casemap.ts +497 -0
  153. package/node_modules/assemblyscript/std/assembly/util/error.ts +54 -0
  154. package/node_modules/assemblyscript/std/assembly/util/hash.ts +117 -0
  155. package/node_modules/assemblyscript/std/assembly/util/math.ts +1922 -0
  156. package/node_modules/assemblyscript/std/assembly/util/memory.ts +290 -0
  157. package/node_modules/assemblyscript/std/assembly/util/number.ts +873 -0
  158. package/node_modules/assemblyscript/std/assembly/util/sort.ts +313 -0
  159. package/node_modules/assemblyscript/std/assembly/util/string.ts +1202 -0
  160. package/node_modules/assemblyscript/std/assembly/util/uri.ts +275 -0
  161. package/node_modules/assemblyscript/std/assembly/vector.ts +4 -0
  162. package/node_modules/assemblyscript/std/assembly.json +16 -0
  163. package/node_modules/assemblyscript/std/portable/index.d.ts +462 -0
  164. package/node_modules/assemblyscript/std/portable/index.js +416 -0
  165. package/node_modules/assemblyscript/std/portable.json +11 -0
  166. package/node_modules/assemblyscript/std/types/assembly/index.d.ts +1 -0
  167. package/node_modules/assemblyscript/std/types/assembly/package.json +3 -0
  168. package/node_modules/assemblyscript/std/types/portable/index.d.ts +1 -0
  169. package/node_modules/assemblyscript/std/types/portable/package.json +3 -0
  170. package/node_modules/assemblyscript/tsconfig-base.json +13 -0
  171. package/package.json +112 -0
  172. package/runtime/deps/assemblyscript/LICENSE +201 -0
  173. package/runtime/deps/assemblyscript/NOTICE +93 -0
  174. package/runtime/deps/assemblyscript/bin/asc.js +35 -0
  175. package/runtime/deps/assemblyscript/dist/asc.js +25035 -0
  176. package/runtime/deps/assemblyscript/dist/assemblyscript.js +337 -0
  177. package/runtime/deps/assemblyscript/node_modules/binaryen/LICENSE +201 -0
  178. package/runtime/deps/assemblyscript/node_modules/binaryen/index.js +28905 -0
  179. package/runtime/deps/assemblyscript/node_modules/binaryen/package.json +50 -0
  180. package/runtime/deps/assemblyscript/node_modules/long/LICENSE +202 -0
  181. package/runtime/deps/assemblyscript/node_modules/long/index.js +1581 -0
  182. package/runtime/deps/assemblyscript/node_modules/long/package.json +58 -0
  183. package/runtime/deps/assemblyscript/package.json +114 -0
  184. package/runtime/host/amxts.inc +85 -0
  185. package/runtime/licenses/AMXX-LICENSE.txt +34 -0
  186. package/runtime/licenses/AssemblyScript-LICENSE +201 -0
  187. package/runtime/licenses/AssemblyScript-NOTICE +93 -0
  188. package/runtime/licenses/Binaryen-LICENSE +201 -0
  189. package/runtime/licenses/Bun-LICENSE.md +84 -0
  190. package/runtime/licenses/GPL-3.0.txt +674 -0
  191. package/runtime/licenses/LLVM-LICENSE.TXT +279 -0
  192. package/runtime/licenses/README.md +25 -0
  193. package/runtime/licenses/WAMR-LICENSE +219 -0
  194. package/runtime/natives.txt +1095 -0
  195. package/runtime/patches/README.md +59 -0
  196. package/runtime/patches/assemblyscript-0.28.20-amxts.patch +14174 -0
  197. package/runtime/patches/wamr-2.4.5-amxts.patch +364 -0
  198. package/scripts/apply-docs.ts +508 -0
  199. package/scripts/asc.ts +27 -0
  200. package/scripts/auto-imports.ts +258 -0
  201. package/scripts/build-wasm.ts +595 -0
  202. package/scripts/check.ts +93 -0
  203. package/scripts/compile-cache.ts +179 -0
  204. package/scripts/compile.ts +401 -0
  205. package/scripts/docker-server.ts +73 -0
  206. package/scripts/docs/as/amxts.ts +210 -0
  207. package/scripts/docs/as/effects.ts +1684 -0
  208. package/scripts/docs/as/facade.ts +3302 -0
  209. package/scripts/docs/as/fs.ts +173 -0
  210. package/scripts/docs/as/lib/check.ts +68 -0
  211. package/scripts/docs/as/modules/http.ts +66 -0
  212. package/scripts/docs/as/os.ts +16 -0
  213. package/scripts/docs/as/promise.ts +132 -0
  214. package/scripts/docs/as/promise.types.ts +63 -0
  215. package/scripts/docs/as/vector.ts +48 -0
  216. package/scripts/docs/entities.ts +2338 -0
  217. package/scripts/docs/events.ts +245 -0
  218. package/scripts/docs/game.ts +497 -0
  219. package/scripts/docs/messages.ts +148 -0
  220. package/scripts/file-contents.ts +74 -0
  221. package/scripts/includes.ts +134 -0
  222. package/scripts/player-fields.ts +866 -0
  223. package/scripts/plugin-cache.ts +92 -0
  224. package/scripts/plugin-natives.ts +1157 -0
  225. package/scripts/prebuilt.ts +268 -0
  226. package/scripts/prepare.ts +134 -0
  227. package/scripts/project.ts +973 -0
  228. package/scripts/run.ts +28 -0
  229. package/scripts/shared-modules.ts +906 -0
  230. package/scripts/system.ts +33 -0
  231. package/scripts/tracked-fs.ts +115 -0
  232. package/scripts/typed-configs.ts +788 -0
  233. package/scripts/ui.ts +77 -0
  234. package/src/cli-api.mjs +175 -0
  235. package/src/parser/include-parser.ts +506 -0
  236. package/src/system.mjs +155 -0
  237. package/src/testing/compile-cache.ts +31 -0
  238. package/src/testing/compile.ts +222 -0
  239. package/src/testing/coroutines.ts +191 -0
  240. package/src/testing/index.ts +39 -0
  241. package/src/testing/kits.ts +55 -0
  242. package/src/testing/memory.ts +125 -0
  243. package/src/testing/natives.ts +925 -0
  244. package/src/testing/server.ts +1874 -0
  245. package/src/testing/setup.ts +63 -0
  246. package/src/testing/tables.ts +139 -0
  247. package/src/types.ts +51 -0
@@ -0,0 +1,595 @@
1
+ import type { Plugin } from './compile';
2
+ import type { PluginNative } from './plugin-natives';
3
+ import type { ModulePackage } from './project';
4
+ import { createSocket } from 'node:dgram';
5
+ // Builds a project's plugins into .aot files the server can load, and
6
+ // optionally copies them there. The project is the current folder: its
7
+ // amxts.config.ts names the modules it uses and where its plugins are
8
+ // (scripts/project.ts). In this repository that is as/ and dist-wasm/.
9
+ //
10
+ // bun run plugins build into dist-wasm/ (npx amxts build)
11
+ // bun run plugins --deploy build, copy to the server, write its plugins.ini
12
+ // and reload the running server over rcon
13
+ // bun scripts/build-wasm.ts --deploy --watch that on every save (npx amxts dev)
14
+ // bun scripts/build-wasm.ts --watch only the build, on every save, for a
15
+ // server that reads dist/ (npx amxts build --watch)
16
+ // --watch --docker --os linux that, for the Docker server that mounts the project,
17
+ // with its console's amxts lines (npx amxts dev --docker)
18
+ // --os windows|linux the server's system, when AMXTS_SERVER does not say
19
+ //
20
+ // Every module the plugins use is built too, as its owner plugin - <name>.aot,
21
+ // the module's one instance on the server; one from npm that comes compiled
22
+ // is taken as it came (scripts/prebuilt.ts) - and plugins.ini lists the modules
23
+ // first, each after what it requires, then the project's plugins. A module the
24
+ // config lists and no plugin uses is left out, unless `pawn` keeps it for Pawn
25
+ // plugins (modulesInUse).
26
+ //
27
+ // `dev` is --deploy --watch: saving a .ts is not enough on its own, because
28
+ // the server loads a .aot and only asc and wamrc can produce one. A save
29
+ // rebuilds the plugins that import the saved file, copies them over and sends
30
+ // the server RELOAD_COMMAND, whose reply is printed.
31
+ //
32
+ // Two compilers, in order: asc turns AssemblyScript into wasm, wamrc turns
33
+ // wasm into i386 machine code - in the object format of the server's system,
34
+ // Windows or Linux, whichever this machine is (scripts/system.ts). wamrc is given runtime/natives.txt so that a
35
+ // call into a host native compiles to a direct call — 2 ns instead of 28. Both
36
+ // must come from the same WAMR release as the module, or the loader reports
37
+ // "unknown binary version" (CONTRIBUTING.md builds both from one checkout).
38
+ import { copyFileSync, cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, utimesSync, watch, writeFileSync } from 'node:fs';
39
+ import { basename, dirname, join, relative, resolve } from 'node:path';
40
+ import { includePath } from './compile';
41
+ import { followConsoles, projectContainers, runCommand } from './docker-server';
42
+ import { FileContents, sourceIn, sourcesIn } from './file-contents';
43
+ import { serverIncludes } from './includes';
44
+ import { pluginCache } from './plugin-cache';
45
+ import { includeName } from './plugin-natives';
46
+ import { fromRegistry, prebuiltOf } from './prebuilt';
47
+ import { CORE_DIR, CORE_PLUGINS, loadProject, modulesInUse, pluginList, projectPlugins, sourcesFor } from './project';
48
+ import { sharedModulesBuild } from './shared-modules';
49
+ import { describeSystem, serverSystem, SYSTEM_NAME, WAMRC_PACKAGE, wamrcPath } from './system';
50
+ import { c, log, progress, since } from './ui';
51
+
52
+ function fail(message: string, hint?: string): void {
53
+ log.error(message.trim());
54
+ if (hint) log.hint(hint);
55
+ }
56
+
57
+ /** A path as the report shows it: from the project's folder, with forward slashes. */
58
+ function shown(path: string): string {
59
+ return (relative(process.cwd(), path) || '.').replace(/\\/g, '/');
60
+ }
61
+
62
+ const project = loadProject(process.cwd());
63
+ if (project.problems.length) {
64
+ fail(project.problems.join('\n'));
65
+ process.exit(1);
66
+ }
67
+ const sourceDir = project.pluginsDir;
68
+ const outDir = project.outDir;
69
+ // Both of these differ between this repository and the kit a plugin author
70
+ // gets: there wamrc sits beside the plugins and the signature table is a file
71
+ // shipped with it, because neither WAMR nor the includes are there to build.
72
+ const wamrc = wamrcPath();
73
+
74
+ // The patched AssemblyScript, not the one npm installs: only ours parses a
75
+ // union of string literal types, which is how an event name is completed in an
76
+ // editor and misspelled names are refused. See runtime/patches.
77
+ const asc_ = process.env.AMXTS_ASC ?? join(CORE_DIR, 'runtime/deps/assemblyscript/bin/asc.js');
78
+ const signatures = process.env.AMXTS_NATIVES ?? join(CORE_DIR, 'runtime/natives.txt');
79
+ // Where --deploy copies the built plugins: the amxts folder inside a server's
80
+ // addons. It is per-machine, so it comes from the environment rather than from
81
+ // this file, and .env keeps it out of everyone else's way.
82
+ const serverDir = process.env.AMXTS_SERVER ?? '';
83
+
84
+ // The module's server command that starts every plugin over from disk.
85
+ const RELOAD_COMMAND = 'amxts_reload';
86
+ // The server's UDP port: 27015 unless AMXTS_PORT says otherwise.
87
+ const RCON_PORT = Number(process.env.AMXTS_PORT ?? 27015);
88
+ // Where rcon_password is set: cstrike/server.cfg, two folders above addons/amxts.
89
+ const serverCfg = join(serverDir, '..', '..', 'server.cfg');
90
+
91
+ // The system of the server the plugins are for: what the .aot is written as.
92
+ let target: ReturnType<typeof serverSystem>;
93
+ try {
94
+ target = serverSystem();
95
+ } catch (error) {
96
+ fail((error as Error).message);
97
+ process.exit(1);
98
+ }
99
+
100
+ const deploy = process.argv.includes('--deploy');
101
+ const keepWatching = process.argv.includes('--watch');
102
+ // A development loop compiles quickly, not fully optimised (scripts/compile.ts).
103
+ const quick = keepWatching;
104
+ // For the Docker server: it reads dist/ where it is, so only the build.
105
+ const forDocker = process.argv.includes('--docker');
106
+
107
+ if (forDocker && deploy) {
108
+ fail('--docker builds into dist/, which the container reads: there is nothing to deploy.');
109
+ process.exit(1);
110
+ }
111
+
112
+ if (forDocker && target.system !== 'linux') {
113
+ fail(`The Docker server is Linux, and the build is for ${describeSystem(target)}.`, 'Build with --os linux.');
114
+ process.exit(1);
115
+ }
116
+
117
+ if (deploy && !serverDir) {
118
+ fail('AMXTS_SERVER is not set: where should the plugins go?', [
119
+ 'Point it at the amxts folder inside your server, in .env beside package.json:',
120
+ ' AMXTS_SERVER=D:/hlds/cstrike/addons/amxts',
121
+ ].join('\n'));
122
+ process.exit(1);
123
+ }
124
+
125
+ if (!existsSync(asc_)) {
126
+ fail(`The patched AssemblyScript is missing: ${asc_}`, 'Clone AssemblyScript into runtime/deps and apply the patch - CONTRIBUTING.md.');
127
+ process.exit(1);
128
+ }
129
+
130
+ if (!existsSync(wamrc)) {
131
+ fail(`wamrc is missing: ${wamrc}`, `It comes with @amxts/core as ${WAMRC_PACKAGE}, an optional dependency: install the project's dependencies again, optional ones included. A checkout of the core builds it - CONTRIBUTING.md.`);
132
+ process.exit(1);
133
+ }
134
+
135
+ if (!existsSync(signatures)) {
136
+ fail(`${signatures} is missing`, 'Run bun run generate in the core.');
137
+ process.exit(1);
138
+ }
139
+
140
+ mkdirSync(outDir, { recursive: true });
141
+
142
+ /** A plugin to build: where its source is, and the name of its .aot. */
143
+ interface Target {
144
+ source: string;
145
+ name: string;
146
+ /** The module package it owns. */
147
+ pkg?: ModulePackage;
148
+ }
149
+
150
+ /** The project's own plugins: the .ts files at the top of its plugins folder. */
151
+ function ownPlugins(): Target[] {
152
+ return projectPlugins(project).map(source => ({ source, name: basename(source).replace(/\.ts$/, '') }));
153
+ }
154
+
155
+ for (const plugin of ownPlugins()) {
156
+ if (project.modules.some(pkg => pkg.short === plugin.name)) {
157
+ fail(`${shown(plugin.source)}: a module in amxts.config.ts is called ${plugin.name} too`, 'Rename the plugin.');
158
+ process.exit(1);
159
+ }
160
+ }
161
+
162
+ /** The owners of the modules the plugins use, in load order. */
163
+ function moduleOwners(): Target[] {
164
+ const sources = sourcesFor(CORE_PLUGINS);
165
+ return modulesInUse(sources, ownPlugins().map(plugin => plugin.source)).map(pkg => ({ source: sources.ownerSource(pkg), name: pkg.short, pkg }));
166
+ }
167
+
168
+ /** The modules' owners, in load order, then the project's plugins. */
169
+ function allPlugins(): Target[] {
170
+ return [...moduleOwners(), ...ownPlugins()];
171
+ }
172
+
173
+ /** The plugins a change to these files has to rebuild: every one importing them. */
174
+ function affected(files: string[]): Target[] {
175
+ const sources = sourcesFor(CORE_PLUGINS);
176
+ const changed = files.map(file => resolve(file));
177
+ return allPlugins().filter((plugin) => {
178
+ const reached = [...sources.reach(join(CORE_PLUGINS, sources.entry(plugin.source)))].map(place => sources.real(place) ?? place);
179
+ return changed.some(file => reached.includes(file));
180
+ });
181
+ }
182
+
183
+ /**
184
+ * asc's errors, one per block, as `as/file.ts:line:col - message` over the
185
+ * line it points at. Anything else - wamrc's - is passed through as it came.
186
+ */
187
+ function readable(problem: string): string {
188
+ const errors = [...problem.matchAll(/ERROR (\w+): (.*)\r?\n[ \t]*:\r?\n([\s\S]*?)\r?\n[ \t]*└─ in (.+?)\((\d+),(\d+)\)/g)];
189
+ if (errors.length === 0) return problem.trim();
190
+
191
+ const sources = sourcesFor(CORE_PLUGINS);
192
+ // A file reached through `~/` is named `~lib/~/myplugin/bits.ts`; a module's
193
+ // is the file in its package.
194
+ const where = (file: string) => {
195
+ const place = join(CORE_PLUGINS, file.replace(/^~lib\/~\//, ''));
196
+ return relative(process.cwd(), sources.real(place) ?? place).replace(/\\/g, '/');
197
+ };
198
+ return errors
199
+ .map(([, code, message, snippet, file, line, col]) => `${where(file)}:${line}:${col} - ${code} ${message}\n${snippet}`)
200
+ .join('\n\n');
201
+ }
202
+
203
+ /** The include each built plugin wrote, by .aot: a contract's keeps its own name. */
204
+ const includes = new Map<string, string>();
205
+
206
+ /**
207
+ * What the build keeps of the plugins it compiled (scripts/plugin-cache.ts).
208
+ * A deploy copies the plugins' includes, and amxts.inc, where the server's
209
+ * includes are: they are the build's own, not what it reads - the ones it
210
+ * writes under its plugins' and modules' names, and any an earlier build
211
+ * wrote. Named before the first build as after it, or a dev started again
212
+ * would compile everything again.
213
+ */
214
+ const cache = pluginCache(project.dir, [
215
+ 'amxts',
216
+ ...[...project.modules.map(pkg => pkg.short), ...ownPlugins().map(plugin => plugin.name)].map(includeName),
217
+ ...project.modules.flatMap(pkg => (pkg.include ? [basename(pkg.include).replace(/\.inc$/, '')] : [])),
218
+ ...readdirSync(outDir).filter(file => file.endsWith('.inc')).map(file => file.replace(/\.inc$/, '')),
219
+ ]);
220
+
221
+ /**
222
+ * Builds these plugins: a module that comes compiled for this project is
223
+ * put in place as it came (scripts/prebuilt.ts), one an earlier build kept,
224
+ * from the same files, likewise; the rest are compiled. The first build of a
225
+ * run says when it has a module to compile. Returns the .aot names, or null
226
+ * after printing the error.
227
+ */
228
+ async function compile(plugins: Target[], first: boolean): Promise<string[] | null> {
229
+ const sources = sourcesFor(CORE_PLUGINS);
230
+ const planned = plugins.map((each) => {
231
+ const plugin: Plugin = {
232
+ source: each.source,
233
+ output: join(outDir, `${each.name}.aot`),
234
+ // `~/` is the core's as/, with the project's plugins and the
235
+ // modules over it (scripts/project.ts).
236
+ root: CORE_PLUGINS,
237
+ wamrc,
238
+ signatures,
239
+ system: target.system,
240
+ quick,
241
+ };
242
+ const prebuilt = each.pkg ? prebuiltOf(each.pkg, target.system, sources) : null;
243
+ if (prebuilt && 'why' in prebuilt) log.info(`${prebuilt.why} - compiling it here`);
244
+ if (prebuilt && 'aot' in prebuilt) return { ...each, plugin, how: 'prebuilt', natives: cache.take(plugin, prebuilt) };
245
+ return { ...each, plugin, how: 'unchanged', natives: cache.reuse(plugin) };
246
+ });
247
+ const fresh = planned.filter(each => !each.natives);
248
+ if (first && fresh.some(each => each.pkg)) {
249
+ log.info('The first build compiles the modules to machine code: it takes about a minute.');
250
+ }
251
+ // What comes prebuilt or unchanged is taken quietly: only what is compiled shows.
252
+
253
+ for (const [i, each] of fresh.entries()) {
254
+ const natives: PluginNative[] = [];
255
+ const started = performance.now();
256
+ const step = progress(`compiling ${c.bold(each.name)} ${c.dim(`${i + 1}/${fresh.length}`)}`);
257
+ const problem = await cache.compile(each.plugin, natives).catch((error) => {
258
+ step.end();
259
+ throw error;
260
+ });
261
+ step.end(problem ? undefined : `${c.bold(each.name)} ${c.dim(`· ${since(started)}`)}`);
262
+ if (problem) {
263
+ fail(`${c.bold(each.name)} does not compile:\n${readable(problem)}\n`, keepWatching ? 'The server keeps the last good build.' : undefined);
264
+ return null;
265
+ }
266
+ each.natives = natives;
267
+ }
268
+
269
+ for (const each of planned) {
270
+ includes.set(`${each.name}.aot`, includePath(each.plugin.output, each.natives!));
271
+ if (each.pkg) writePackageInclude(each.pkg, each.plugin.output);
272
+ }
273
+ return planned.map(each => `${each.name}.aot`);
274
+ }
275
+
276
+ /**
277
+ * Built in a module's own folder, a module with natives gets its include
278
+ * written into the package - include/<name>.inc, which the author commits and
279
+ * Pawn plugins compile against. Not with `"contract": true`: that include is
280
+ * the original's, and the build has checked the natives against it instead.
281
+ */
282
+ function writePackageInclude(pkg: ModulePackage, output: string) {
283
+ if (pkg.dir !== project.dir || !pkg.natives || pkg.contract) return;
284
+ const generated = includePath(output);
285
+ if (!existsSync(generated)) return;
286
+ const target = pkg.include ?? join(pkg.dir, 'include', basename(generated));
287
+ const text = readFileSync(generated, 'utf8');
288
+ if (existsSync(target) && readFileSync(target, 'utf8').replace(/\r\n/g, '\n') === text.replace(/\r\n/g, '\n')) return;
289
+ mkdirSync(dirname(target), { recursive: true });
290
+ writeFileSync(target, text);
291
+ log.info(`wrote ${relative(project.dir, target).replace(/\\/g, '/')} from ${relative(project.dir, pkg.natives).replace(/\\/g, '/')}`);
292
+ }
293
+
294
+ /**
295
+ * Sends one connectionless packet to the local server and collects what comes
296
+ * back until it goes quiet. Null when nothing does: no server on that port.
297
+ */
298
+ function ask(message: string, wait: number): Promise<string | null> {
299
+ return new Promise((done) => {
300
+ const socket = createSocket('udp4');
301
+ let reply: string | null = null;
302
+ let finished = false;
303
+
304
+ const finish = () => {
305
+ if (finished) return;
306
+ finished = true;
307
+ clearTimeout(timer);
308
+ socket.close();
309
+ done(reply);
310
+ };
311
+ let timer = setTimeout(finish, wait);
312
+
313
+ // Every packet starts with four 0xFF, and an rcon reply with an `l`
314
+ // after them; a long reply comes in several.
315
+ socket.on('message', (packet) => {
316
+ reply = (reply ?? '') + packet.subarray(4).toString('latin1').replace(/^l/, '').replace(/\0+$/, '');
317
+ clearTimeout(timer);
318
+ timer = setTimeout(finish, 200);
319
+ });
320
+ // Windows answers a closed port with an error rather than silence.
321
+ socket.on('error', finish);
322
+ socket.send(Buffer.concat([Buffer.from([255, 255, 255, 255]), Buffer.from(`${message}\n`, 'latin1')]), RCON_PORT, '127.0.0.1');
323
+ });
324
+ }
325
+
326
+ /**
327
+ * Copies the built plugins to the server, then reloads it. Returns the
328
+ * report's line - what happened to `names` - with what the server answered
329
+ * under it.
330
+ *
331
+ * The module also reloads by itself when a plugin's .aot is newer than the
332
+ * one it loaded. Reloading twice would run every plugin_init twice, so when
333
+ * rcon can reload, the copies keep the old file's time and only rcon does.
334
+ * When it cannot, they get a new time and the module's watcher does it.
335
+ */
336
+ async function deployAndReload(built: string[], names: string): Promise<string> {
337
+ if (!existsSync(serverDir)) return `${names} not deployed: there is no ${shown(serverDir)}`;
338
+
339
+ // Without amxts.config.ts the list is the server's, not the build's: a
340
+ // deploy names what it has just built and has never seen named, and leaves
341
+ // every other line alone - a commented-out plugin stays commented out, and
342
+ // an order somebody chose stays theirs. With the config the order is the
343
+ // build's (serverList).
344
+ const listPath = join(serverDir, 'plugins.ini');
345
+ const existing = existsSync(listPath) ? readFileSync(listPath, 'utf8') : '';
346
+ const named = new Set(
347
+ existing.split('\n').map(line => line.replace(/^[\s;]+/, '').trim()).filter(Boolean),
348
+ );
349
+ const added = built.filter(file => !named.has(file));
350
+
351
+ const password = existsSync(serverCfg)
352
+ ? readFileSync(serverCfg, 'utf8').match(/^\s*rcon_password\s+"?([^"\r\n]*)"?/m)?.[1]?.trim() ?? ''
353
+ : '';
354
+ const challenge = (await ask('challenge rcon', 1000))?.match(/challenge rcon (\d+)/)?.[1];
355
+ const byRcon = Boolean(challenge && password);
356
+
357
+ for (const file of built) {
358
+ const target = join(serverDir, 'plugins', file);
359
+ const before = existsSync(target) ? statSync(target) : null;
360
+ copyFileSync(join(outDir, file), target);
361
+ if (before && byRcon) utimesSync(target, before.atime, before.mtime);
362
+ }
363
+
364
+ // A plugin that exports natives has an include for Pawn plugins beside it;
365
+ // it goes where amxxpc on the server looks, when the server has one.
366
+ const pawnIncludes = join(serverDir, '..', 'amxmodx', 'scripting', 'include');
367
+ for (const file of built) {
368
+ const include = includes.get(file) ?? includePath(join(outDir, file));
369
+ if (existsSync(include) && existsSync(pawnIncludes)) copyFileSync(include, join(pawnIncludes, basename(include)));
370
+ }
371
+ // The module's own natives for Pawn: the fields plugins add to Player.
372
+ if (existsSync(pawnIncludes)) copyFileSync(join(CORE_DIR, 'runtime/host/amxts.inc'), join(pawnIncludes, 'amxts.inc'));
373
+
374
+ // A folder beside the plugins is a library - `~/lib/thing`, `~/myplugin/config`.
375
+ // It is compiled into the .aot already, so nothing here needs it; a .ts
376
+ // plugin written on the server does, and it was silently compiling against
377
+ // whatever copy happened to be there.
378
+ const ownFolder = resolve(sourceDir) !== resolve(CORE_PLUGINS) || resolve(project.dir) === resolve(CORE_DIR);
379
+ for (const entry of ownFolder ? readdirSync(sourceDir, { withFileTypes: true }) : []) {
380
+ if (entry.isDirectory()) {
381
+ cpSync(join(sourceDir, entry.name), join(serverDir, 'plugins', entry.name), { recursive: true });
382
+ }
383
+ }
384
+
385
+ if (project.config) {
386
+ writeFileSync(listPath, serverList(existing, readFileSync(join(outDir, 'plugins.ini'), 'utf8')));
387
+ } else if (existing.length === 0) {
388
+ writeFileSync(listPath, `${built.join('\n')}\n`);
389
+ } else if (added.length > 0) {
390
+ writeFileSync(listPath, `${existing.replace(/\n*$/, '\n') + added.join('\n')}\n`);
391
+ }
392
+
393
+ // A reload re-reads plugins.ini, so a new plugin loads with it (seen on the
394
+ // server: api-async loaded that way).
395
+ for (const file of added) log.info(`new plugin ${c.bold(file.replace(/\.aot$/, ''))}: added to plugins.ini`);
396
+
397
+ if (!challenge) return `deployed ${names} - the server is not running`;
398
+
399
+ if (!password) {
400
+ log.warn(`no rcon_password in ${serverCfg}: add a line rcon_password "something" and restart the server (until then the module reloads on its own)`);
401
+ return `deployed ${names} - the module reloads them on its own`;
402
+ }
403
+
404
+ const reply = await ask(`rcon ${challenge} "${password}" ${RELOAD_COMMAND}`, 5000);
405
+
406
+ if (reply === null || /bad rcon_password/i.test(reply)) {
407
+ // Nothing reloaded them, so let the module's watcher see the new files.
408
+ const now = new Date();
409
+ for (const file of built) utimesSync(join(serverDir, 'plugins', file), now, now);
410
+ return `deployed ${names} - rcon ${reply === null ? 'got no answer' : 'refused the password'}, the module reloads them on its own`;
411
+ }
412
+
413
+ const lines = reply.split('\n').map(line => line.trimEnd()).filter(Boolean);
414
+ return [`deployed, the server reloaded ${names}`, ...lines.map(line => c.dim(` ${line}`))].join('\n');
415
+ }
416
+
417
+ /**
418
+ * The server's plugins.ini with the build's order: the modules, each after
419
+ * what it requires, then the project's plugins. A line the server commented
420
+ * out stays commented out, and a plugin the build does not know stays, after.
421
+ */
422
+ function serverList(existing: string, generated: string): string {
423
+ const lines = existing.split(/\r?\n/).filter(line => line.trim());
424
+ const nameOf = (line: string) => line.replace(/^[\s;]+/, '').trim();
425
+ const order = generated.split('\n').filter(line => line && !line.startsWith(';'));
426
+ const out = order.map(name => (lines.some(line => nameOf(line) === name && line.trim().startsWith(';')) ? `;${name}` : name));
427
+ for (const line of lines) {
428
+ if (!order.includes(nameOf(line)) && !line.trim().startsWith('; GENERATED')) out.push(line);
429
+ }
430
+ return `${out.join('\n')}\n`;
431
+ }
432
+
433
+ /** The modules plugins.ini lists; null before the first build. */
434
+ let listed: string[] | null = null;
435
+
436
+ /**
437
+ * plugins.ini: the modules the plugins use, then the plugins. A module the
438
+ * config lists that no plugin uses has no .aot in the build folder; one a
439
+ * save stops using is said (the header says it of the first build).
440
+ */
441
+ function writeList(owners: Target[]) {
442
+ const names = owners.map(owner => owner.name);
443
+ for (const pkg of project.modules.filter(each => !names.includes(each.short))) {
444
+ if (listed?.includes(pkg.short)) log.info(`module "${pkg.short}" is no longer used by any plugin - left out`);
445
+ rmSync(join(outDir, `${pkg.short}.aot`), { force: true });
446
+ }
447
+ listed = names;
448
+ const list = pluginList(project, ownPlugins().map(plugin => `${plugin.name}.aot`), owners.map(owner => owner.pkg!));
449
+ writeFileSync(join(outDir, 'plugins.ini'), `; GENERATED by amxts build: the modules in load order, then the plugins.\n${list.join('\n')}\n`);
450
+ }
451
+
452
+ /**
453
+ * Builds, deploys when asked, and prints one line about it - after `why`,
454
+ * the files a rebuild is for. A module a save brings into use is built with
455
+ * the plugin that uses it.
456
+ */
457
+ async function run(plugins: Target[], verb: string, why = ''): Promise<boolean> {
458
+ const started = performance.now();
459
+ const owners = moduleOwners();
460
+ const fresh = owners.filter(owner => !listed?.includes(owner.name) && !plugins.some(plugin => plugin.name === owner.name));
461
+ const built = await compile([...fresh, ...plugins], listed === null);
462
+ if (!built) return false;
463
+ writeList(owners);
464
+
465
+ const names = c.bold(built.map(file => file.replace(/\.aot$/, '')).join(', '));
466
+ const [first, ...rest] = (deploy ? await deployAndReload(built, names) : `${verb} ${names} into ${shown(outDir)}`).split('\n');
467
+ const time = keepWatching ? `${c.dim(new Date().toLocaleTimeString())} ` : '';
468
+ log.success([`${time}${why}${first} ${c.dim(`(${since(started)})`)}`, ...rest].join('\n'));
469
+ return true;
470
+ }
471
+
472
+ /**
473
+ * The server the plugins are for: its folder, its system, ReHLDS or HLDS (as
474
+ * its includes - or `target` without them - say) and, for a deploy, whether
475
+ * it runs.
476
+ */
477
+ async function serverLine(): Promise<string> {
478
+ const system = SYSTEM_NAME[target.system];
479
+ if (forDocker) return `the Docker one that mounts this project · ${system}`;
480
+ if (!serverDir) return `${c.dim('none - AMXTS_SERVER is not set')} · the plugins are for ${system}`;
481
+ const folder = serverDir.replace(/\\/g, '/');
482
+ if (!existsSync(serverDir)) return `${folder} ${c.yellow('is not there')} · the plugins are for ${system}`;
483
+ const includes = serverIncludes();
484
+ const rehlds = includes ? existsSync(join(includes, 'reapi.inc')) : project.config?.target !== 'hlds';
485
+ const running = deploy ? ` · ${(await ask('challenge rcon', 1000)) === null ? 'not running' : 'running'}` : '';
486
+ return `${folder} · ${system} · ${rehlds ? 'ReHLDS' : 'HLDS'}${running}`;
487
+ }
488
+
489
+ /**
490
+ * What a build works with, before it starts: the core, the modules - each
491
+ * with what brings it, or that no plugin uses it - the server and the
492
+ * plugins. A module or a core from a folder on this machine is "local".
493
+ */
494
+ async function header(): Promise<void> {
495
+ const used = moduleOwners().map(owner => owner.pkg!);
496
+ const listedFirst = project.config?.modules ?? [];
497
+ const order = (pkg: ModulePackage) => (listedFirst.includes(pkg.name) ? listedFirst.indexOf(pkg.name) : listedFirst.length);
498
+ const module = (pkg: ModulePackage) => {
499
+ const notes = [
500
+ !fromRegistry(pkg.dir) && 'local',
501
+ !used.includes(pkg) && 'no plugin uses it',
502
+ ].filter(Boolean);
503
+ return `${pkg.short} ${pkg.version}${notes.length ? c.dim(` (${notes.join(', ')})`) : ''}`;
504
+ };
505
+ const core = JSON.parse(readFileSync(join(CORE_DIR, 'package.json'), 'utf8'));
506
+ const plugins = ownPlugins().length;
507
+ const rows = [
508
+ ['Core', `@amxts/core ${core.version}${fromRegistry(CORE_DIR) ? '' : c.dim(' (local)')}`],
509
+ ['Modules', [...project.modules].sort((a, b) => order(a) - order(b)).map(module).join(c.dim(' · ')) || c.dim('none')],
510
+ ['Server', await serverLine()],
511
+ ['Plugins', `${plugins || 'none'} in ${shown(sourceDir)}/`],
512
+ ];
513
+ console.log(['', ...rows.map(([label, value]) => ` ${c.dim(label.padEnd(8))} ${value}`), ''].join('\n'));
514
+ }
515
+
516
+ await header();
517
+
518
+ // The containers are looked for before the first build, so that their answer
519
+ // to it - the reload - is shown too.
520
+ if (forDocker) {
521
+ const containers = projectContainers(project.dir);
522
+ if (containers === null) {
523
+ log.warn('docker does not answer - is it running? The plugins are built all the same.');
524
+ } else if (containers.length === 0) {
525
+ log.warn(`no running container mounts ${shown(project.dir)} at /project - start the server with:`);
526
+ log.hint(runCommand(project.dir));
527
+ } else {
528
+ log.step(`the server's console: ${containers.map(name => c.cyan(name)).join(', ')}`);
529
+ followConsoles(containers);
530
+ }
531
+ }
532
+
533
+ // What --watch watches: the plugins folder, and every module package the
534
+ // project uses - a module being written beside the project rebuilds its
535
+ // importers too - but not what the build and the editor config write. A save
536
+ // is compared with the sources as the first build reads them
537
+ // (scripts/file-contents.ts).
538
+ const watched = [...new Set([sourceDir, ...project.modules.map(pkg => pkg.dir)])];
539
+ const written = [outDir, join(project.dir, '.amxts')];
540
+ const watchedSources = () => watched.flatMap(folder => sourcesIn(folder, written));
541
+ const contents = new FileContents(keepWatching ? watchedSources() : []);
542
+
543
+ const ok = await run(ownPlugins(), 'built');
544
+
545
+ if (!keepWatching) process.exit(ok ? 0 : 1);
546
+
547
+ log.step(`watching ${c.cyan(shown(sourceDir))} - save a plugin and it ${deploy ? 'goes to the server' : `is built into ${shown(outDir)} again`} ${c.dim('(Ctrl+C stops)')}`);
548
+
549
+ // An editor writes a file more than once when saving, so one save means
550
+ // several events. They are collected for a quarter of a second, and a save
551
+ // that lands during a build waits for it and goes next. Only a file whose
552
+ // contents changed rebuilds.
553
+ const heard = new Set<string>();
554
+ let pending: ReturnType<typeof setTimeout> | null = null;
555
+ let building = false;
556
+
557
+ async function flush(): Promise<void> {
558
+ if (building || heard.size === 0) return;
559
+ building = true;
560
+
561
+ const files = contents.changed([...heard]);
562
+ heard.clear();
563
+ const plugins = affected(files);
564
+ if (plugins.length > 0) await run(plugins, 'rebuilt', `${files.map(shown).join(', ')} changed: `).catch(error => fail(String(error)));
565
+
566
+ building = false;
567
+ void flush();
568
+ }
569
+
570
+ for (const folder of watched) {
571
+ watch(folder, { recursive: true }, (_event, file) => {
572
+ const path = file && sourceIn(folder, file, written);
573
+ if (!path) return;
574
+
575
+ heard.add(path);
576
+ if (pending) clearTimeout(pending);
577
+ pending = setTimeout(() => {
578
+ pending = null;
579
+ void flush();
580
+ }, 250);
581
+ });
582
+ }
583
+
584
+ // A save made while the first build ran is built now.
585
+ for (const file of watchedSources()) heard.add(file);
586
+ void flush();
587
+
588
+ // A start that took every plugin from the cache has compiled nothing yet, and
589
+ // a plugin's compile begins with the modules it uses analysed
590
+ // (scripts/shared-modules.ts): that is done now, while nobody waits, rather
591
+ // than on the first save.
592
+ if (cache.counts.misses === 0) {
593
+ const sources = sourcesFor(CORE_PLUGINS);
594
+ for (const plugin of ownPlugins()) await sharedModulesBuild(CORE_PLUGINS, sources.entry(plugin.source)).catch(() => {});
595
+ }