@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.
- package/LICENSE +21 -0
- package/README.md +162 -0
- package/README.ru.md +162 -0
- package/amxmodx/base/include/amxconst.inc +594 -0
- package/amxmodx/base/include/amxmisc.inc +902 -0
- package/amxmodx/base/include/amxmodx.inc +3447 -0
- package/amxmodx/base/include/amxmodx_version.inc +20 -0
- package/amxmodx/base/include/cellarray.inc +525 -0
- package/amxmodx/base/include/cellstack.inc +166 -0
- package/amxmodx/base/include/celltrie.inc +412 -0
- package/amxmodx/base/include/core.inc +202 -0
- package/amxmodx/base/include/csstats.inc +298 -0
- package/amxmodx/base/include/csstats_const.inc +29 -0
- package/amxmodx/base/include/cstrike.inc +1287 -0
- package/amxmodx/base/include/cstrike_const.inc +521 -0
- package/amxmodx/base/include/csx.inc +259 -0
- package/amxmodx/base/include/cvars.inc +547 -0
- package/amxmodx/base/include/datapack.inc +161 -0
- package/amxmodx/base/include/dbi.inc +154 -0
- package/amxmodx/base/include/dodconst.inc +152 -0
- package/amxmodx/base/include/dodfun.inc +152 -0
- package/amxmodx/base/include/dodstats.inc +69 -0
- package/amxmodx/base/include/dodx.inc +161 -0
- package/amxmodx/base/include/engine.inc +1343 -0
- package/amxmodx/base/include/engine_const.inc +310 -0
- package/amxmodx/base/include/engine_stocks.inc +248 -0
- package/amxmodx/base/include/esf.inc +70 -0
- package/amxmodx/base/include/esf_const.inc +74 -0
- package/amxmodx/base/include/fakemeta.inc +1127 -0
- package/amxmodx/base/include/fakemeta_const.inc +793 -0
- package/amxmodx/base/include/fakemeta_stocks.inc +395 -0
- package/amxmodx/base/include/fakemeta_util.inc +882 -0
- package/amxmodx/base/include/file.inc +666 -0
- package/amxmodx/base/include/float.inc +422 -0
- package/amxmodx/base/include/fun.inc +333 -0
- package/amxmodx/base/include/gameconfig.inc +93 -0
- package/amxmodx/base/include/geoip.inc +264 -0
- package/amxmodx/base/include/ham_const.inc +4352 -0
- package/amxmodx/base/include/hamsandwich.inc +439 -0
- package/amxmodx/base/include/hlsdk_const.inc +808 -0
- package/amxmodx/base/include/json.inc +777 -0
- package/amxmodx/base/include/lang.inc +119 -0
- package/amxmodx/base/include/message_const.inc +1130 -0
- package/amxmodx/base/include/message_stocks.inc +161 -0
- package/amxmodx/base/include/messages.inc +604 -0
- package/amxmodx/base/include/newmenus.inc +387 -0
- package/amxmodx/base/include/ns.inc +754 -0
- package/amxmodx/base/include/ns_const.inc +196 -0
- package/amxmodx/base/include/nvault.inc +154 -0
- package/amxmodx/base/include/regex.inc +348 -0
- package/amxmodx/base/include/sockets.inc +183 -0
- package/amxmodx/base/include/sorting.inc +107 -0
- package/amxmodx/base/include/sqlx.inc +582 -0
- package/amxmodx/base/include/string.inc +786 -0
- package/amxmodx/base/include/string_const.inc +156 -0
- package/amxmodx/base/include/string_stocks.inc +325 -0
- package/amxmodx/base/include/textparse_ini.inc +214 -0
- package/amxmodx/base/include/textparse_smc.inc +261 -0
- package/amxmodx/base/include/tfcconst.inc +84 -0
- package/amxmodx/base/include/tfcstats.inc +62 -0
- package/amxmodx/base/include/tfcx.inc +148 -0
- package/amxmodx/base/include/time.inc +118 -0
- package/amxmodx/base/include/tsconst.inc +154 -0
- package/amxmodx/base/include/tsfun.inc +198 -0
- package/amxmodx/base/include/tsstats.inc +65 -0
- package/amxmodx/base/include/tsx.inc +88 -0
- package/amxmodx/base/include/vault.inc +60 -0
- package/amxmodx/base/include/vector.inc +131 -0
- package/amxmodx/base/include/xs.inc +1570 -0
- package/as/amxts.d.ts +153 -0
- package/as/constants.ts +4730 -0
- package/as/effects.ts +1449 -0
- package/as/entities.ts +5482 -0
- package/as/events.ts +3054 -0
- package/as/facade.ts +4639 -0
- package/as/flags.ts +299 -0
- package/as/fs.ts +287 -0
- package/as/hooks.ts +18536 -0
- package/as/kit.ts +25 -0
- package/as/lib/check.ts +86 -0
- package/as/modules/http.ts +184 -0
- package/as/natives.ts +11845 -0
- package/as/os.ts +30 -0
- package/as/promise.ts +1392 -0
- package/as/promise.types.d.ts +82 -0
- package/as/remote.ts +288 -0
- package/as/tsconfig.json +22 -0
- package/as/vector.ts +65 -0
- package/bin/amxts.mjs +10 -0
- package/includes/menu_core.inc +355 -0
- package/includes/order.txt +54 -0
- package/includes/sources.json +27 -0
- package/includes/universal_config.inc +355 -0
- package/lint/oxlint-plugin.mjs +77 -0
- package/node_modules/assemblyscript/LICENSE +201 -0
- package/node_modules/assemblyscript/NOTICE +93 -0
- package/node_modules/assemblyscript/package.json +63 -0
- package/node_modules/assemblyscript/std/README.md +6 -0
- package/node_modules/assemblyscript/std/assembly/array.ts +526 -0
- package/node_modules/assemblyscript/std/assembly/arraybuffer.ts +77 -0
- package/node_modules/assemblyscript/std/assembly/atomics.ts +127 -0
- package/node_modules/assemblyscript/std/assembly/bindings/asyncify.ts +16 -0
- package/node_modules/assemblyscript/std/assembly/bindings/dom.ts +291 -0
- package/node_modules/assemblyscript/std/assembly/bindings/node.ts +6 -0
- package/node_modules/assemblyscript/std/assembly/builtins.ts +2631 -0
- package/node_modules/assemblyscript/std/assembly/compat.ts +2 -0
- package/node_modules/assemblyscript/std/assembly/console.ts +42 -0
- package/node_modules/assemblyscript/std/assembly/crypto.ts +9 -0
- package/node_modules/assemblyscript/std/assembly/dataview.ts +181 -0
- package/node_modules/assemblyscript/std/assembly/date.ts +373 -0
- package/node_modules/assemblyscript/std/assembly/diagnostics.ts +11 -0
- package/node_modules/assemblyscript/std/assembly/error.ts +44 -0
- package/node_modules/assemblyscript/std/assembly/function.ts +38 -0
- package/node_modules/assemblyscript/std/assembly/index.d.ts +2822 -0
- package/node_modules/assemblyscript/std/assembly/iterator.ts +35 -0
- package/node_modules/assemblyscript/std/assembly/map.ts +260 -0
- package/node_modules/assemblyscript/std/assembly/math.ts +3289 -0
- package/node_modules/assemblyscript/std/assembly/memory.ts +123 -0
- package/node_modules/assemblyscript/std/assembly/number.ts +388 -0
- package/node_modules/assemblyscript/std/assembly/object.ts +36 -0
- package/node_modules/assemblyscript/std/assembly/performance.ts +9 -0
- package/node_modules/assemblyscript/std/assembly/polyfills.ts +27 -0
- package/node_modules/assemblyscript/std/assembly/process.ts +50 -0
- package/node_modules/assemblyscript/std/assembly/reference.ts +48 -0
- package/node_modules/assemblyscript/std/assembly/regexp.ts +12 -0
- package/node_modules/assemblyscript/std/assembly/rt/README.md +83 -0
- package/node_modules/assemblyscript/std/assembly/rt/common.ts +81 -0
- package/node_modules/assemblyscript/std/assembly/rt/index-incremental.ts +2 -0
- package/node_modules/assemblyscript/std/assembly/rt/index-minimal.ts +2 -0
- package/node_modules/assemblyscript/std/assembly/rt/index-stub.ts +1 -0
- package/node_modules/assemblyscript/std/assembly/rt/index.d.ts +37 -0
- package/node_modules/assemblyscript/std/assembly/rt/itcms.ts +419 -0
- package/node_modules/assemblyscript/std/assembly/rt/rtrace.ts +15 -0
- package/node_modules/assemblyscript/std/assembly/rt/stub.ts +133 -0
- package/node_modules/assemblyscript/std/assembly/rt/tcms.ts +254 -0
- package/node_modules/assemblyscript/std/assembly/rt/tlsf.ts +592 -0
- package/node_modules/assemblyscript/std/assembly/rt.ts +87 -0
- package/node_modules/assemblyscript/std/assembly/set.ts +220 -0
- package/node_modules/assemblyscript/std/assembly/shared/feature.ts +65 -0
- package/node_modules/assemblyscript/std/assembly/shared/runtime.ts +11 -0
- package/node_modules/assemblyscript/std/assembly/shared/target.ts +11 -0
- package/node_modules/assemblyscript/std/assembly/shared/tsconfig.json +11 -0
- package/node_modules/assemblyscript/std/assembly/shared/typeinfo.ts +72 -0
- package/node_modules/assemblyscript/std/assembly/staticarray.ts +401 -0
- package/node_modules/assemblyscript/std/assembly/string.ts +847 -0
- package/node_modules/assemblyscript/std/assembly/symbol.ts +114 -0
- package/node_modules/assemblyscript/std/assembly/table.ts +16 -0
- package/node_modules/assemblyscript/std/assembly/tsconfig.json +6 -0
- package/node_modules/assemblyscript/std/assembly/typedarray.ts +1945 -0
- package/node_modules/assemblyscript/std/assembly/uri.ts +17 -0
- package/node_modules/assemblyscript/std/assembly/util/bytes.ts +107 -0
- package/node_modules/assemblyscript/std/assembly/util/casemap.ts +497 -0
- package/node_modules/assemblyscript/std/assembly/util/error.ts +54 -0
- package/node_modules/assemblyscript/std/assembly/util/hash.ts +117 -0
- package/node_modules/assemblyscript/std/assembly/util/math.ts +1922 -0
- package/node_modules/assemblyscript/std/assembly/util/memory.ts +290 -0
- package/node_modules/assemblyscript/std/assembly/util/number.ts +873 -0
- package/node_modules/assemblyscript/std/assembly/util/sort.ts +313 -0
- package/node_modules/assemblyscript/std/assembly/util/string.ts +1202 -0
- package/node_modules/assemblyscript/std/assembly/util/uri.ts +275 -0
- package/node_modules/assemblyscript/std/assembly/vector.ts +4 -0
- package/node_modules/assemblyscript/std/assembly.json +16 -0
- package/node_modules/assemblyscript/std/portable/index.d.ts +462 -0
- package/node_modules/assemblyscript/std/portable/index.js +416 -0
- package/node_modules/assemblyscript/std/portable.json +11 -0
- package/node_modules/assemblyscript/std/types/assembly/index.d.ts +1 -0
- package/node_modules/assemblyscript/std/types/assembly/package.json +3 -0
- package/node_modules/assemblyscript/std/types/portable/index.d.ts +1 -0
- package/node_modules/assemblyscript/std/types/portable/package.json +3 -0
- package/node_modules/assemblyscript/tsconfig-base.json +13 -0
- package/package.json +112 -0
- package/runtime/deps/assemblyscript/LICENSE +201 -0
- package/runtime/deps/assemblyscript/NOTICE +93 -0
- package/runtime/deps/assemblyscript/bin/asc.js +35 -0
- package/runtime/deps/assemblyscript/dist/asc.js +25035 -0
- package/runtime/deps/assemblyscript/dist/assemblyscript.js +337 -0
- package/runtime/deps/assemblyscript/node_modules/binaryen/LICENSE +201 -0
- package/runtime/deps/assemblyscript/node_modules/binaryen/index.js +28905 -0
- package/runtime/deps/assemblyscript/node_modules/binaryen/package.json +50 -0
- package/runtime/deps/assemblyscript/node_modules/long/LICENSE +202 -0
- package/runtime/deps/assemblyscript/node_modules/long/index.js +1581 -0
- package/runtime/deps/assemblyscript/node_modules/long/package.json +58 -0
- package/runtime/deps/assemblyscript/package.json +114 -0
- package/runtime/host/amxts.inc +85 -0
- package/runtime/licenses/AMXX-LICENSE.txt +34 -0
- package/runtime/licenses/AssemblyScript-LICENSE +201 -0
- package/runtime/licenses/AssemblyScript-NOTICE +93 -0
- package/runtime/licenses/Binaryen-LICENSE +201 -0
- package/runtime/licenses/Bun-LICENSE.md +84 -0
- package/runtime/licenses/GPL-3.0.txt +674 -0
- package/runtime/licenses/LLVM-LICENSE.TXT +279 -0
- package/runtime/licenses/README.md +25 -0
- package/runtime/licenses/WAMR-LICENSE +219 -0
- package/runtime/natives.txt +1095 -0
- package/runtime/patches/README.md +59 -0
- package/runtime/patches/assemblyscript-0.28.20-amxts.patch +14174 -0
- package/runtime/patches/wamr-2.4.5-amxts.patch +364 -0
- package/scripts/apply-docs.ts +508 -0
- package/scripts/asc.ts +27 -0
- package/scripts/auto-imports.ts +258 -0
- package/scripts/build-wasm.ts +595 -0
- package/scripts/check.ts +93 -0
- package/scripts/compile-cache.ts +179 -0
- package/scripts/compile.ts +401 -0
- package/scripts/docker-server.ts +73 -0
- package/scripts/docs/as/amxts.ts +210 -0
- package/scripts/docs/as/effects.ts +1684 -0
- package/scripts/docs/as/facade.ts +3302 -0
- package/scripts/docs/as/fs.ts +173 -0
- package/scripts/docs/as/lib/check.ts +68 -0
- package/scripts/docs/as/modules/http.ts +66 -0
- package/scripts/docs/as/os.ts +16 -0
- package/scripts/docs/as/promise.ts +132 -0
- package/scripts/docs/as/promise.types.ts +63 -0
- package/scripts/docs/as/vector.ts +48 -0
- package/scripts/docs/entities.ts +2338 -0
- package/scripts/docs/events.ts +245 -0
- package/scripts/docs/game.ts +497 -0
- package/scripts/docs/messages.ts +148 -0
- package/scripts/file-contents.ts +74 -0
- package/scripts/includes.ts +134 -0
- package/scripts/player-fields.ts +866 -0
- package/scripts/plugin-cache.ts +92 -0
- package/scripts/plugin-natives.ts +1157 -0
- package/scripts/prebuilt.ts +268 -0
- package/scripts/prepare.ts +134 -0
- package/scripts/project.ts +973 -0
- package/scripts/run.ts +28 -0
- package/scripts/shared-modules.ts +906 -0
- package/scripts/system.ts +33 -0
- package/scripts/tracked-fs.ts +115 -0
- package/scripts/typed-configs.ts +788 -0
- package/scripts/ui.ts +77 -0
- package/src/cli-api.mjs +175 -0
- package/src/parser/include-parser.ts +506 -0
- package/src/system.mjs +155 -0
- package/src/testing/compile-cache.ts +31 -0
- package/src/testing/compile.ts +222 -0
- package/src/testing/coroutines.ts +191 -0
- package/src/testing/index.ts +39 -0
- package/src/testing/kits.ts +55 -0
- package/src/testing/memory.ts +125 -0
- package/src/testing/natives.ts +925 -0
- package/src/testing/server.ts +1874 -0
- package/src/testing/setup.ts +63 -0
- package/src/testing/tables.ts +139 -0
- 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
|
+
}
|