@amxts/core 0.1.0 → 0.2.1
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/README.md +21 -25
- package/README.ru.md +22 -24
- package/as/amxts.d.ts +63 -3
- package/as/constants.ts +684 -116
- package/as/entities.ts +1905 -1364
- package/as/events.ts +1696 -907
- package/as/facade.ts +1240 -220
- package/as/fetch.ts +1519 -0
- package/as/flags.ts +159 -145
- package/as/hlds.ts +1071 -0
- package/as/hooks.ts +5226 -5379
- package/as/kit.ts +15 -5
- package/as/lib/check.ts +1 -1
- package/as/natives.ts +2658 -2499
- package/as/remote.ts +39 -15
- package/as/tsconfig.json +7 -2
- package/as/vector.ts +12 -9
- package/includes/order.txt +22 -29
- package/includes/sources.json +0 -8
- package/node_modules/assemblyscript/std/assembly/index.d.ts +20 -11
- package/package.json +29 -7
- package/runtime/deps/assemblyscript/dist/asc.js +86 -16
- package/runtime/deps/assemblyscript/dist/assemblyscript.js +29 -29
- package/runtime/licenses/MPL-2.0.txt +373 -0
- package/runtime/licenses/README.md +5 -1
- package/runtime/licenses/curl-COPYING +22 -0
- package/runtime/licenses/libssh2-COPYING +43 -0
- package/runtime/licenses/mbedTLS-LICENSE +553 -0
- package/runtime/natives.txt +108 -105
- package/runtime/patches/assemblyscript-0.28.20-amxts.patch +752 -296
- package/runtime/patches/wamr-2.4.5-amxts.patch +148 -0
- package/scripts/apply-docs.ts +2 -2
- package/scripts/auto-imports.ts +2 -2
- package/scripts/build-identity.ts +115 -0
- package/scripts/build-wasm.ts +101 -50
- package/scripts/check.ts +17 -3
- package/scripts/client-messages.ts +246 -0
- package/scripts/compile-cache.ts +4 -2
- package/scripts/compile-pool.ts +128 -0
- package/scripts/compile-worker.ts +23 -0
- package/scripts/compile.ts +156 -59
- package/scripts/docker-server.ts +13 -3
- package/scripts/docs/as/amxts.ts +86 -0
- package/scripts/docs/as/facade.ts +688 -188
- package/scripts/docs/as/fetch.ts +805 -0
- package/scripts/docs/entities.ts +43 -144
- package/scripts/docs/events.ts +17 -28
- package/scripts/docs/game.ts +68 -22
- package/scripts/docs/messages.ts +330 -16
- package/scripts/editor-api.ts +101 -0
- package/scripts/hlds-events.ts +370 -0
- package/scripts/includes.ts +12 -10
- package/scripts/player-fields.ts +16 -15
- package/scripts/prebuilt.ts +65 -23
- package/scripts/prepare.ts +42 -28
- package/scripts/project.ts +120 -20
- package/scripts/reapi-events.ts +98 -0
- package/scripts/shared-modules.ts +85 -23
- package/scripts/source-map.ts +242 -0
- package/scripts/system.ts +1 -1
- package/scripts/typed-commands.ts +263 -0
- package/scripts/typed-configs.ts +23 -12
- package/scripts/ui.ts +3 -1
- package/scripts/upgrade-menus.ts +274 -0
- package/scripts/upgrade-names.ts +384 -0
- package/scripts/upgrade.ts +857 -0
- package/src/cli-api.mjs +37 -4
- package/src/system.mjs +74 -3
- package/src/testing/compile.ts +8 -5
- package/src/testing/index.ts +2 -1
- package/src/testing/natives.ts +199 -5
- package/src/testing/network.ts +531 -0
- package/src/testing/server.ts +572 -48
- package/src/testing/setup.ts +1 -1
- package/src/testing/tables.ts +25 -0
- package/as/modules/http.ts +0 -184
- package/scripts/docs/as/modules/http.ts +0 -66
package/scripts/prebuilt.ts
CHANGED
|
@@ -5,6 +5,13 @@
|
|
|
5
5
|
// would compile the same thing; when it would not, the build says why and
|
|
6
6
|
// compiles it.
|
|
7
7
|
//
|
|
8
|
+
// The manifest also carries the module's surface - the proxy a plugin that
|
|
9
|
+
// uses the module reads in its place, and the owner's dispatcher
|
|
10
|
+
// (scripts/shared-modules.ts) - which every compile of a plugin that uses
|
|
11
|
+
// it would otherwise make by analysing the module with asc. It is taken on
|
|
12
|
+
// the same terms as the .aot, but for what only the machine code depends on:
|
|
13
|
+
// the system and the forwards' declarations.
|
|
14
|
+
//
|
|
8
15
|
// bun scripts/prebuilt.ts in a project: compile every module it lists
|
|
9
16
|
// into the module's prebuilt/, for every
|
|
10
17
|
// system (scripts/publish.ts runs it on the
|
|
@@ -34,15 +41,18 @@
|
|
|
34
41
|
// no change to the code.
|
|
35
42
|
import type { NativesBeside, PluginNative } from './plugin-natives';
|
|
36
43
|
import type { ModulePackage, Options, Sources } from './project';
|
|
44
|
+
import type { ModuleSurface } from './shared-modules';
|
|
37
45
|
import type { System } from './system';
|
|
38
46
|
import { createHash } from 'node:crypto';
|
|
39
47
|
import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
|
|
40
48
|
import { join, relative, resolve, sep } from 'node:path';
|
|
41
49
|
import ts from 'typescript';
|
|
50
|
+
import { coreVersion } from './build-identity';
|
|
42
51
|
import { compileToMachineCode, compileToWasm } from './compile';
|
|
43
52
|
import { codeFiles } from './compile-cache';
|
|
44
53
|
import { includeForward, nativesBeside } from './plugin-natives';
|
|
45
|
-
import { CORE_DIR, CORE_PLUGINS, loadProject, optionsOf, sourcesFor } from './project';
|
|
54
|
+
import { CORE_DIR, CORE_PLUGINS, loadProject, optionsOf, shared, sourcesFor } from './project';
|
|
55
|
+
import { moduleSurface } from './shared-modules';
|
|
46
56
|
import { SYSTEM_NAME, SYSTEMS, wamrcPath } from './system';
|
|
47
57
|
import { c, log, since } from './ui';
|
|
48
58
|
|
|
@@ -76,6 +86,8 @@ export interface PrebuiltManifest {
|
|
|
76
86
|
natives: PluginNative[];
|
|
77
87
|
beside: NativesBeside;
|
|
78
88
|
systems: Partial<Record<System, { file: string; size: number; sha256: string }>>;
|
|
89
|
+
/** What a plugin that uses the module compiles against (scripts/shared-modules.ts); missing in a manifest made without it. */
|
|
90
|
+
surface?: ModuleSurface;
|
|
79
91
|
}
|
|
80
92
|
|
|
81
93
|
/** What a build does with a module's prebuilt .aot: takes it, or compiles the module and says why. */
|
|
@@ -142,7 +154,7 @@ function coreHash(): string {
|
|
|
142
154
|
/** The core or a module package the project has, by name, as a prebuilt module is checked against it. */
|
|
143
155
|
function installed(name: string, sources: Sources): { version: string; dir: string; hash: () => string } | null {
|
|
144
156
|
if (name === CORE_NAME) {
|
|
145
|
-
return { version:
|
|
157
|
+
return { version: coreVersion(), dir: CORE_DIR, hash: coreHash };
|
|
146
158
|
}
|
|
147
159
|
const pkg = sources.project.packages.find(each => each.name === name);
|
|
148
160
|
return pkg ? { version: pkg.version, dir: pkg.dir, hash: () => packageHash(pkg) } : null;
|
|
@@ -155,49 +167,77 @@ function declarationOf(name: string): string | null {
|
|
|
155
167
|
}
|
|
156
168
|
|
|
157
169
|
/**
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
* as any plugin is.
|
|
170
|
+
* A module package's manifest, or why it cannot be used. Null for a module
|
|
171
|
+
* that has none - a folder on this machine, or a package without prebuilt/ -
|
|
172
|
+
* which is compiled as any plugin is.
|
|
162
173
|
*/
|
|
163
|
-
|
|
174
|
+
function manifestOf(pkg: ModulePackage): PrebuiltManifest | { why: string } | null {
|
|
164
175
|
const file = join(pkg.dir, PREBUILT_DIR, MANIFEST);
|
|
165
176
|
if (!fromRegistry(pkg.dir) || !existsSync(file)) return null;
|
|
166
177
|
const named = `${pkg.short} ${pkg.version}`;
|
|
167
|
-
let manifest: PrebuiltManifest;
|
|
168
178
|
try {
|
|
169
|
-
manifest = JSON.parse(readFileSync(file, 'utf8'));
|
|
179
|
+
const manifest = JSON.parse(readFileSync(file, 'utf8')) as PrebuiltManifest;
|
|
180
|
+
return manifest.format === FORMAT ? manifest : { why: `${named} comes compiled for another build of amxts` };
|
|
170
181
|
} catch {
|
|
171
182
|
return { why: `${named}: its ${PREBUILT_DIR}/${MANIFEST} cannot be read` };
|
|
172
183
|
}
|
|
173
|
-
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* The prebuilt .aot of a module from npm for `system`, when it is what the
|
|
188
|
+
* project would compile; else why not. Null for a module that has none.
|
|
189
|
+
*/
|
|
190
|
+
export function prebuiltOf(pkg: ModulePackage, system: System, sources: Sources): PrebuiltUse | null {
|
|
191
|
+
const manifest = manifestOf(pkg);
|
|
192
|
+
if (!manifest || 'why' in manifest) return manifest;
|
|
193
|
+
const named = `${pkg.short} ${pkg.version}`;
|
|
174
194
|
const aot = manifest.systems[system];
|
|
175
195
|
if (!aot) return { why: `${named} comes compiled for ${Object.keys(manifest.systems).map(each => SYSTEM_NAME[each as System]).join(' and ')}, not ${SYSTEM_NAME[system]}` };
|
|
196
|
+
const why = mismatch(manifest, pkg, sources);
|
|
197
|
+
if (why) return { why };
|
|
198
|
+
for (const [name, declared] of Object.entries(manifest.forwards)) {
|
|
199
|
+
if (declarationOf(name) !== declared) return { why: `the includes declare the forward ${name} otherwise than when ${named} was built` };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const bytes = new Uint8Array(readFileSync(join(pkg.dir, aot.file)));
|
|
203
|
+
if (sha256(bytes) !== aot.sha256) return { why: `${named}: ${aot.file} is not the file its manifest lists` };
|
|
204
|
+
return { aot: bytes, natives: manifest.natives, beside: manifest.beside };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* The surface a module from npm came with (scripts/shared-modules.ts), when
|
|
209
|
+
* it is what the project would make; else why not. Null for a module that
|
|
210
|
+
* has none.
|
|
211
|
+
*/
|
|
212
|
+
export function prebuiltSurface(pkg: ModulePackage, sources: Sources): { surface: ModuleSurface } | { why: string } | null {
|
|
213
|
+
const manifest = manifestOf(pkg);
|
|
214
|
+
if (!manifest || 'why' in manifest) return manifest;
|
|
215
|
+
if (!manifest.surface) return { why: `${pkg.short} ${pkg.version} comes without its analysis` };
|
|
216
|
+
const why = mismatch(manifest, pkg, sources);
|
|
217
|
+
return why ? { why } : { surface: manifest.surface };
|
|
218
|
+
}
|
|
176
219
|
|
|
220
|
+
/** Why the project would not compile a module as its manifest says it was: another core or package, other options, a file in the way; null when it would. */
|
|
221
|
+
function mismatch(manifest: PrebuiltManifest, pkg: ModulePackage, sources: Sources): string | null {
|
|
222
|
+
const named = `${pkg.short} ${pkg.version}`;
|
|
177
223
|
for (const [name, built] of Object.entries(manifest.from)) {
|
|
178
224
|
const have = installed(name, sources);
|
|
179
|
-
if (!have) return
|
|
180
|
-
if (have.version !== built.version) return
|
|
225
|
+
if (!have) return `${named} was built with ${name}, which the project does not have`;
|
|
226
|
+
if (have.version !== built.version) return `${named} was built for ${name} ${built.version}, the project has ${have.version}`;
|
|
181
227
|
if (!fromRegistry(have.dir) && have.hash() !== built.hash) {
|
|
182
|
-
return
|
|
228
|
+
return `${named} was built from another ${name} ${built.version} than the one in ${posix(relative(sources.project.dir, have.dir)) || '.'}`;
|
|
183
229
|
}
|
|
184
230
|
}
|
|
185
231
|
for (const [name, options] of Object.entries(manifest.options)) {
|
|
186
232
|
const module = sources.project.packages.find(each => each.name === name)!;
|
|
187
233
|
if (JSON.stringify(optionsOf(sources.project, module.definition)) !== JSON.stringify(options)) {
|
|
188
|
-
return
|
|
234
|
+
return `${named} was built with ${name === pkg.name ? 'its' : `${module.short}'s`} default options, and amxts.config.ts sets ${module.definition.configKey}`;
|
|
189
235
|
}
|
|
190
236
|
}
|
|
191
|
-
for (const [name, declared] of Object.entries(manifest.forwards)) {
|
|
192
|
-
if (declarationOf(name) !== declared) return { why: `the includes declare the forward ${name} otherwise than when ${named} was built` };
|
|
193
|
-
}
|
|
194
237
|
const own = resolve(sources.project.pluginsDir) !== resolve(CORE_PLUGINS);
|
|
195
238
|
const shadow = own ? manifest.places.find(place => existsSync(join(sources.project.pluginsDir, place))) : undefined;
|
|
196
|
-
if (shadow) return
|
|
197
|
-
|
|
198
|
-
const bytes = new Uint8Array(readFileSync(join(pkg.dir, aot.file)));
|
|
199
|
-
if (sha256(bytes) !== aot.sha256) return { why: `${named}: ${aot.file} is not the file its manifest lists` };
|
|
200
|
-
return { aot: bytes, natives: manifest.natives, beside: manifest.beside };
|
|
239
|
+
if (shadow) return `${posix(relative(sources.project.dir, join(sources.project.pluginsDir, shadow)))} takes the place of a file ${named} was built from`;
|
|
240
|
+
return null;
|
|
201
241
|
}
|
|
202
242
|
|
|
203
243
|
/** The `new Forward<...>("name")` calls of a package whose arguments are not all text: their names. */
|
|
@@ -255,6 +295,7 @@ async function prebuild(pkg: ModulePackage, sources: Sources): Promise<string> {
|
|
|
255
295
|
natives,
|
|
256
296
|
beside: nativesBeside(natives),
|
|
257
297
|
systems,
|
|
298
|
+
surface: await moduleSurface(CORE_PLUGINS, pkg.short),
|
|
258
299
|
};
|
|
259
300
|
writeFileSync(join(dir, MANIFEST), `${JSON.stringify(manifest, null, '\t')}\n`);
|
|
260
301
|
const sizes = SYSTEMS.map(system => `${SYSTEM_NAME[system]} ${(systems[system]!.size / 1024).toFixed(0)} KB`).join(', ');
|
|
@@ -264,5 +305,6 @@ async function prebuild(pkg: ModulePackage, sources: Sources): Promise<string> {
|
|
|
264
305
|
if (import.meta.main) {
|
|
265
306
|
const project = loadProject(process.cwd());
|
|
266
307
|
if (project.problems.length) throw new Error(project.problems.join('\n'));
|
|
267
|
-
|
|
308
|
+
// A library has no owner to compile: it is compiled into the plugins that import it.
|
|
309
|
+
for (const pkg of shared(project.modules)) log.success(await prebuild(pkg, sourcesFor(CORE_PLUGINS)));
|
|
268
310
|
}
|
package/scripts/prepare.ts
CHANGED
|
@@ -5,19 +5,22 @@
|
|
|
5
5
|
// the line it prints only under --debug)
|
|
6
6
|
//
|
|
7
7
|
// The project's own tsconfig.json extends it: { "extends": "./.amxts/tsconfig.json" }.
|
|
8
|
-
// It says what the build says - `~/` is the project's plugins
|
|
9
|
-
//
|
|
10
|
-
// file - and it takes in amxts.config.ts and every module the config
|
|
11
|
-
// that the options each module declares (ModuleOptions) type the
|
|
12
|
-
// Beside it, imports.d.ts makes what plugins use without an import -
|
|
13
|
-
// facade's API, the modules' namespaces - globals for the editor
|
|
14
|
-
// (scripts/auto-imports.ts)
|
|
8
|
+
// It says what the build says - `~/` is the project's plugins folder,
|
|
9
|
+
// "@amxts/core" and its entries are the core's API, a module package is its
|
|
10
|
+
// module file - and it takes in amxts.config.ts and every module the config
|
|
11
|
+
// lists, so that the options each module declares (ModuleOptions) type the
|
|
12
|
+
// config. Beside it, imports.d.ts makes what plugins use without an import -
|
|
13
|
+
// the facade's API, the modules' namespaces - globals for the editor
|
|
14
|
+
// (scripts/auto-imports.ts), and api/ holds the API with its tooltips in the
|
|
15
|
+
// language AMXTS_DOCS_LANG picks, when that is not English
|
|
16
|
+
// (scripts/editor-api.ts): the installed packages are never written to.
|
|
15
17
|
import { existsSync, mkdirSync, realpathSync, writeFileSync } from 'node:fs';
|
|
16
18
|
import { createRequire } from 'node:module';
|
|
17
19
|
import { dirname, isAbsolute, join, relative } from 'node:path';
|
|
18
|
-
import {
|
|
20
|
+
import { applySources, docsLang, moduleSources, registerFilter } from './apply-docs';
|
|
19
21
|
import { importsDeclaration } from './auto-imports';
|
|
20
|
-
import {
|
|
22
|
+
import { editorApi } from './editor-api';
|
|
23
|
+
import { CONFIG_FILE, CORE_DIR, CORE_ENTRIES, CORE_PLUGINS, loadProject, shared } from './project';
|
|
21
24
|
import { c, debug, log } from './ui';
|
|
22
25
|
|
|
23
26
|
function fail(message: string): void {
|
|
@@ -32,19 +35,18 @@ if (project.problems.length) {
|
|
|
32
35
|
process.exit(1);
|
|
33
36
|
}
|
|
34
37
|
|
|
35
|
-
|
|
36
|
-
// picks (AMXTS_DOCS_LANG), as the generated API has them. A core it cannot
|
|
37
|
-
// write to keeps English: the editor still has words, only not these.
|
|
38
|
-
let docs = '';
|
|
39
|
-
try {
|
|
40
|
-
docs = await applyProject(project.dir, docsLang());
|
|
41
|
-
} catch (error) {
|
|
42
|
-
fail(`docs: ${(error as Error).message}`);
|
|
43
|
-
}
|
|
38
|
+
const lang = docsLang();
|
|
44
39
|
|
|
45
40
|
// A module's own folder, without a project config, has its own tsconfig.json
|
|
46
|
-
// (extending the core's as/tsconfig.json): nothing to write there
|
|
47
|
-
|
|
41
|
+
// (extending the core's as/tsconfig.json): nothing to write there but its
|
|
42
|
+
// own words, in place - its repository's filter stores them in English.
|
|
43
|
+
const own = project.config ? null : project.packages.find(pkg => pkg.dir === project.dir);
|
|
44
|
+
if (own) {
|
|
45
|
+
registerFilter(own.dir);
|
|
46
|
+
const changed = await applySources(moduleSources(own), lang);
|
|
47
|
+
if (!process.argv.includes('--quiet') || debug()) log.step(c.dim(`docs: ${lang}, ${changed.length ? `${changed.length} file(s) rewritten` : 'up to date'}`));
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
48
50
|
|
|
49
51
|
const out = join(project.dir, '.amxts');
|
|
50
52
|
/** A path as the generated config writes it: from .amxts/, with forward slashes. */
|
|
@@ -74,19 +76,28 @@ function installed(name: string, dir: string) {
|
|
|
74
76
|
}
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
// `~/` is not a package. The core and the
|
|
78
|
-
// that is not (a module in modules/, a core
|
|
79
|
-
// its file, as the build finds it.
|
|
79
|
+
// `~/` is the project's plugins folder, not a package. The core and the
|
|
80
|
+
// modules are, once installed; one that is not (a module in modules/, a core
|
|
81
|
+
// run from elsewhere) is mapped to its file, as the build finds it. In a
|
|
82
|
+
// language other than English they are all mapped to the copy with its
|
|
83
|
+
// words, which the build never reads.
|
|
80
84
|
const paths: Record<string, string[]> = {
|
|
81
|
-
'~/*': [
|
|
85
|
+
'~/*': [`${from(project.pluginsDir)}/*`],
|
|
82
86
|
};
|
|
83
87
|
if (!installed('@amxts/core', CORE_DIR)) {
|
|
84
|
-
paths[
|
|
85
|
-
paths['@amxts/core/*'] = [`${from(CORE_PLUGINS)}/*`];
|
|
88
|
+
for (const [name, file] of Object.entries(CORE_ENTRIES)) paths[name] = [from(join(CORE_PLUGINS, file))];
|
|
86
89
|
}
|
|
87
90
|
for (const pkg of project.modules) {
|
|
88
91
|
if (!installed(pkg.name, pkg.dir)) paths[pkg.name] = [from(pkg.module)];
|
|
89
92
|
}
|
|
93
|
+
let api: Awaited<ReturnType<typeof editorApi>> = null;
|
|
94
|
+
try {
|
|
95
|
+
api = await editorApi(project, lang, join(out, 'api'));
|
|
96
|
+
} catch (error) {
|
|
97
|
+
fail(`docs: ${(error as Error).message}`);
|
|
98
|
+
}
|
|
99
|
+
for (const [name, file] of Object.entries(api?.paths ?? {})) paths[name] = [from(file)];
|
|
100
|
+
const docs = lang === 'en' ? '' : `docs: ${lang}${api?.written ? ', .amxts/api written' : ''}`;
|
|
90
101
|
|
|
91
102
|
const tsconfig = {
|
|
92
103
|
extends: from(assembly),
|
|
@@ -104,12 +115,15 @@ const tsconfig = {
|
|
|
104
115
|
// plugin declares is its own, never a global another plugin's name
|
|
105
116
|
// collides with.
|
|
106
117
|
moduleDetection: 'force',
|
|
118
|
+
// `catch (error)` reads `error.message` as the compiler takes it: the
|
|
119
|
+
// base config's strict mode would make the caught value `unknown`.
|
|
120
|
+
useUnknownInCatchVariables: false,
|
|
107
121
|
paths,
|
|
108
122
|
},
|
|
109
123
|
include: [
|
|
110
124
|
`${from(project.pluginsDir)}/**/*.ts`,
|
|
111
125
|
from(join(project.dir, CONFIG_FILE)),
|
|
112
|
-
`${from(CORE_PLUGINS)}/*.d.ts`,
|
|
126
|
+
`${from(api?.core ?? CORE_PLUGINS)}/*.d.ts`,
|
|
113
127
|
'./modules.d.ts',
|
|
114
128
|
'./imports.d.ts',
|
|
115
129
|
],
|
|
@@ -120,7 +134,7 @@ writeFileSync(join(out, 'tsconfig.json'), `${JSON.stringify(tsconfig, null, '\t'
|
|
|
120
134
|
writeFileSync(join(out, 'modules.d.ts'), [
|
|
121
135
|
'// GENERATED by amxts prepare: the modules amxts.config.ts lists, for the',
|
|
122
136
|
'// options each one adds to ModuleOptions.',
|
|
123
|
-
...project.modules.map(pkg => `import "${pkg.name}";`),
|
|
137
|
+
...shared(project.modules).map(pkg => `import "${pkg.name}";`),
|
|
124
138
|
'',
|
|
125
139
|
'export {};',
|
|
126
140
|
'',
|
package/scripts/project.ts
CHANGED
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
//
|
|
12
12
|
// "amxts": { "module": "src/index.ts", "natives": "src/natives.ts", "include": "include/menu_core.inc" }
|
|
13
13
|
//
|
|
14
|
+
// - or, for a library, `"amxts": { "module": "src/index.ts", "library": true }`:
|
|
15
|
+
// code compiled into each plugin that imports it, as an npm library is, with
|
|
16
|
+
// no instance on the server, no owner, no proxy and no defineModule.
|
|
17
|
+
//
|
|
14
18
|
// and in its module file, with the global defineModule:
|
|
15
19
|
//
|
|
16
20
|
// export default defineModule<MenuCoreOptions>({ // src/index.ts
|
|
@@ -52,6 +56,7 @@ import ts from 'typescript';
|
|
|
52
56
|
import { coreImports, importedName, importTable, withAutoImports } from './auto-imports';
|
|
53
57
|
import { includeDirs } from './includes';
|
|
54
58
|
import { existsSync, readdirSync, readFileSync, statSync } from './tracked-fs';
|
|
59
|
+
import { typedCommands } from './typed-commands';
|
|
55
60
|
import { typedConfigs } from './typed-configs';
|
|
56
61
|
|
|
57
62
|
/** The core's folder: the package this file ships in. */
|
|
@@ -123,6 +128,12 @@ export interface ModulePackage {
|
|
|
123
128
|
dir: string;
|
|
124
129
|
/** The module file: the API plugins import. */
|
|
125
130
|
module: string;
|
|
131
|
+
/**
|
|
132
|
+
* `"library": true`: compiled into each plugin that imports it, like an
|
|
133
|
+
* npm library - no instance on the server, so no owner, no proxy, no
|
|
134
|
+
* defineModule, natives, include or test kit.
|
|
135
|
+
*/
|
|
136
|
+
library: boolean;
|
|
126
137
|
/**
|
|
127
138
|
* Its Pawn natives: the plugin that owns the module - its one instance on
|
|
128
139
|
* the server. Without one, the build generates an owner that only runs it.
|
|
@@ -375,7 +386,16 @@ export function readPackage(dir: string): ModulePackage | null {
|
|
|
375
386
|
const include = typeof amxts.include === 'string' ? resolve(dir, amxts.include) : null;
|
|
376
387
|
|
|
377
388
|
const short = String(json.name).replace(/^@[^/]+\//, '');
|
|
378
|
-
const
|
|
389
|
+
const library = amxts.library === true;
|
|
390
|
+
const owned = ['natives', 'include', 'contract', 'testing'].filter(key => amxts[key] !== undefined);
|
|
391
|
+
if (library && owned.length) {
|
|
392
|
+
throw new Error(`${json.name}: a library is compiled into each plugin that imports it and runs no plugin of its own - it has no ${owned.map(key => `"${key}"`).join(', ')} (package.json, "amxts")`);
|
|
393
|
+
}
|
|
394
|
+
const read = readDefinition(module);
|
|
395
|
+
if (library && read) {
|
|
396
|
+
throw new Error(`${json.name}: a library has no defineModule - no options, no instance on the server; plugins import what it exports. Leave "library": true out to make it a module`);
|
|
397
|
+
}
|
|
398
|
+
const definition = read ?? { name: null, configKey: null, requires: [], defaults: {}, imports: [], optionsType: null, hasSetup: false };
|
|
379
399
|
if (definition.name && definition.name !== short) {
|
|
380
400
|
throw new Error(`${json.name}: defineModule's meta.name is "${definition.name}" - it is the package's name without the scope, "${short}"`);
|
|
381
401
|
}
|
|
@@ -387,7 +407,7 @@ export function readPackage(dir: string): ModulePackage | null {
|
|
|
387
407
|
if (include && amxts.contract === true && !existsSync(include)) throw new Error(`${json.name}: its contract ${amxts.include} is not there (package.json, "amxts".include)`);
|
|
388
408
|
const testing = typeof amxts.testing === 'string' ? resolve(dir, amxts.testing) : null;
|
|
389
409
|
if (testing && !existsSync(testing)) throw new Error(`${json.name}: its test kit ${amxts.testing} is not there (package.json, "amxts".testing)`);
|
|
390
|
-
return { name: json.name, short, dir: resolve(dir), module, natives, include, contract: amxts.contract === true, testing, version: String(json.version ?? '0.0.0'), description: String(json.description ?? ''), definition };
|
|
410
|
+
return { name: json.name, short, dir: resolve(dir), module, library, natives, include, contract: amxts.contract === true, testing, version: String(json.version ?? '0.0.0'), description: String(json.description ?? ''), definition };
|
|
391
411
|
}
|
|
392
412
|
|
|
393
413
|
/**
|
|
@@ -614,7 +634,30 @@ export function optionsOf(project: Project, definition: ModuleDefinition): Optio
|
|
|
614
634
|
* Everything in the core's as/ that is not a plugin: the facade a plugin
|
|
615
635
|
* imports, the kit a module imports and the generated native layer under them.
|
|
616
636
|
*/
|
|
617
|
-
export const NOT_PLUGINS = new Set(['facade.ts', 'kit.ts', 'promise.ts', 'vector.ts', 'effects.ts', 'fs.ts', 'os.ts', 'natives.ts', 'remote.ts', 'constants.ts', 'events.ts', 'entities.ts', 'flags.ts', 'hooks.ts']);
|
|
637
|
+
export const NOT_PLUGINS = new Set(['facade.ts', 'kit.ts', 'promise.ts', 'vector.ts', 'effects.ts', 'fetch.ts', 'fs.ts', 'os.ts', 'natives.ts', 'remote.ts', 'constants.ts', 'events.ts', 'entities.ts', 'flags.ts', 'hooks.ts', 'hlds.ts']);
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* The core's API a plugin imports, by the package's name, and its file in
|
|
641
|
+
* the core's as/ - its place in the tree. package.json's "exports" gives
|
|
642
|
+
* the same files to the editor.
|
|
643
|
+
*/
|
|
644
|
+
export const CORE_ENTRIES: Record<string, string> = {
|
|
645
|
+
'@amxts/core': 'facade.ts',
|
|
646
|
+
'@amxts/core/natives': 'natives.ts',
|
|
647
|
+
'@amxts/core/constants': 'constants.ts',
|
|
648
|
+
'@amxts/core/fs': 'fs.ts',
|
|
649
|
+
'@amxts/core/os': 'os.ts',
|
|
650
|
+
'@amxts/core/kit': 'kit.ts',
|
|
651
|
+
'@amxts/core/check': 'lib/check.ts',
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
/** Every place of the core's as/ in the tree: what `~/` must not reach from a plugin. */
|
|
655
|
+
const CORE_PLACES = new Set([...NOT_PLUGINS, ...Object.values(CORE_ENTRIES), 'amxts.d.ts']);
|
|
656
|
+
|
|
657
|
+
/** How a plugin names the core's file at a place: its entry, or the facade, which exports the rest. */
|
|
658
|
+
export function coreEntryOf(place: string): string {
|
|
659
|
+
return Object.entries(CORE_ENTRIES).find(([, file]) => file === place)?.[0] ?? '@amxts/core';
|
|
660
|
+
}
|
|
618
661
|
|
|
619
662
|
/**
|
|
620
663
|
* The project's own plugins: the .ts files at the top of its plugins folder.
|
|
@@ -629,8 +672,13 @@ export function projectPlugins(project: Project): string[] {
|
|
|
629
672
|
.map(f => join(project.pluginsDir, f));
|
|
630
673
|
}
|
|
631
674
|
|
|
675
|
+
/** The modules that run on the server - each in its owner plugin: every one but a library. */
|
|
676
|
+
export function shared(modules: ModulePackage[]): ModulePackage[] {
|
|
677
|
+
return modules.filter(pkg => !pkg.library);
|
|
678
|
+
}
|
|
679
|
+
|
|
632
680
|
/** plugins.ini: the modules' owners in load order, then the project's plugins. */
|
|
633
|
-
export function pluginList(project: Project, plugins: string[], modules = project.modules): string[] {
|
|
681
|
+
export function pluginList(project: Project, plugins: string[], modules = shared(project.modules)): string[] {
|
|
634
682
|
const owners = modules.map(pkg => `${pkg.short}.aot`);
|
|
635
683
|
return [...owners, ...plugins.filter(plugin => !owners.includes(plugin))];
|
|
636
684
|
}
|
|
@@ -641,11 +689,12 @@ export function pluginList(project: Project, plugins: string[], modules = projec
|
|
|
641
689
|
* the files they import - with what those modules import and require, and
|
|
642
690
|
* the ones `pawn` keeps for Pawn plugins, whose use no build can see.
|
|
643
691
|
* Without amxts.config.ts every module is in use: a module's own
|
|
644
|
-
* repository, testing itself.
|
|
692
|
+
* repository, testing itself. A library is never one: it is compiled into
|
|
693
|
+
* the plugins, and what it imports is reached through them.
|
|
645
694
|
*/
|
|
646
695
|
export function modulesInUse(sources: Sources, plugins: string[]): ModulePackage[] {
|
|
647
696
|
const { project } = sources;
|
|
648
|
-
if (!project.config) return project.modules;
|
|
697
|
+
if (!project.config) return shared(project.modules);
|
|
649
698
|
const used = new Set(project.config.pawn ?? []);
|
|
650
699
|
const reached = new Set<string>();
|
|
651
700
|
const reach = (source: string) => sources.reach(join(sources.root, sources.entry(source)), reached);
|
|
@@ -653,7 +702,7 @@ export function modulesInUse(sources: Sources, plugins: string[]): ModulePackage
|
|
|
653
702
|
// A module in use brings what its owner imports and what it requires.
|
|
654
703
|
for (let before = -1; used.size !== before;) {
|
|
655
704
|
before = used.size;
|
|
656
|
-
for (const pkg of project.modules) {
|
|
705
|
+
for (const pkg of shared(project.modules)) {
|
|
657
706
|
const imported = reached.has(join(sources.root, 'modules', `${pkg.short}.ts`));
|
|
658
707
|
if (!imported && !used.has(pkg.name)) continue;
|
|
659
708
|
reach(sources.ownerSource(pkg));
|
|
@@ -661,7 +710,7 @@ export function modulesInUse(sources: Sources, plugins: string[]): ModulePackage
|
|
|
661
710
|
for (const required of pkg.definition.requires) used.add(required);
|
|
662
711
|
}
|
|
663
712
|
}
|
|
664
|
-
return project.modules.filter(pkg => used.has(pkg.name));
|
|
713
|
+
return shared(project.modules).filter(pkg => used.has(pkg.name));
|
|
665
714
|
}
|
|
666
715
|
|
|
667
716
|
// ---------------------------------------------------------------- the tree
|
|
@@ -772,7 +821,7 @@ export class Sources {
|
|
|
772
821
|
generated(path: string): string | null {
|
|
773
822
|
const owner = this.rel(path).match(/^([^/]+)\.ts$/);
|
|
774
823
|
const pkg = owner ? this.moduleNamed(owner[1]) : null;
|
|
775
|
-
if (!pkg || pkg.natives || this.real(path)) return null;
|
|
824
|
+
if (!pkg || pkg.natives || pkg.library || this.real(path)) return null;
|
|
776
825
|
return [
|
|
777
826
|
`// GENERATED by scripts/project.ts: the plugin that runs ${pkg.name} - its one instance on the server.`,
|
|
778
827
|
'import { plugin } from "~/facade";',
|
|
@@ -811,7 +860,7 @@ export class Sources {
|
|
|
811
860
|
return this.rel(this.place(real));
|
|
812
861
|
}
|
|
813
862
|
|
|
814
|
-
/** Each file's typed configs, made once a compile (scripts/typed-configs.ts). */
|
|
863
|
+
/** Each file's typed configs and commands, made once a compile (scripts/typed-configs.ts, scripts/typed-commands.ts). */
|
|
815
864
|
private typed = new Map<string, { text: string; out: string }>();
|
|
816
865
|
|
|
817
866
|
/**
|
|
@@ -827,14 +876,14 @@ export class Sources {
|
|
|
827
876
|
if (known && known.text === text) return known.out;
|
|
828
877
|
|
|
829
878
|
const display = posix(relative(this.project.dir, this.real(at) ?? at));
|
|
830
|
-
const
|
|
879
|
+
const reader = (from: string, spec: string) => {
|
|
831
880
|
const file = this.resolveImport(from, spec);
|
|
832
881
|
const found = file ? this.readPlain(file) : null;
|
|
833
882
|
return file && found !== null ? { path: file, text: found } : null;
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
883
|
+
};
|
|
884
|
+
const configs = typedConfigs(at, display, text, reader);
|
|
885
|
+
const made = typedCommands(at, display, configs.text, reader);
|
|
886
|
+
for (const problem of [...configs.problems, ...made.problems]) this.problem(problem);
|
|
838
887
|
this.typed.set(at, { text, out: made.text });
|
|
839
888
|
return made.text;
|
|
840
889
|
}
|
|
@@ -843,8 +892,21 @@ export class Sources {
|
|
|
843
892
|
private readPlain(path: string): string | null {
|
|
844
893
|
const real = this.real(path);
|
|
845
894
|
if (!real) return this.generated(path);
|
|
895
|
+
const raw = readFileSync(real, 'utf8');
|
|
896
|
+
const at = resolve(path);
|
|
897
|
+
const known = this.plain.get(at);
|
|
898
|
+
if (known?.raw === raw) return known.out;
|
|
899
|
+
const out = this.plainOf(path, real, raw);
|
|
900
|
+
this.plain.set(at, { raw, out });
|
|
901
|
+
return out;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
/** Each file's plain text, made once a compile: a build reaches the facade from every plugin. */
|
|
905
|
+
private plain = new Map<string, { raw: string; out: string }>();
|
|
906
|
+
|
|
907
|
+
private plainOf(path: string, real: string, raw: string): string {
|
|
846
908
|
// A module's ModuleOptions augmentation may be in any of its files (types.ts).
|
|
847
|
-
const text = withoutOptionsAugmentation(this.rewrite(real, this.withImports(real,
|
|
909
|
+
const text = withoutOptionsAugmentation(this.rewrite(real, this.withImports(real, raw)));
|
|
848
910
|
const pkg = this.project.modules.find(each => each.module === real);
|
|
849
911
|
if (pkg) return moduleSource(real, text, optionsOf(this.project, pkg.definition));
|
|
850
912
|
if (/^modules\/[^/]+\.ts$/.test(this.rel(path)) && text.includes('defineModule')) {
|
|
@@ -854,10 +916,49 @@ export class Sources {
|
|
|
854
916
|
return text;
|
|
855
917
|
}
|
|
856
918
|
|
|
919
|
+
private problem(text: string) {
|
|
920
|
+
if (!this.problems.includes(text)) this.problems.push(text);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/**
|
|
924
|
+
* Whether a file on disk is the core's as/: the hood, which names its
|
|
925
|
+
* places by `~/`. On a server the plugins folder holds them, by place.
|
|
926
|
+
*/
|
|
927
|
+
private isCore(real: string): boolean {
|
|
928
|
+
if (inside(CORE_PLUGINS, real)) return true;
|
|
929
|
+
const own = resolve(this.project.pluginsDir) !== resolve(CORE_PLUGINS) && inside(this.project.pluginsDir, real);
|
|
930
|
+
return !own && !this.packageOf(real) && CORE_PLACES.has(this.rel(this.place(real)));
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
/**
|
|
934
|
+
* `~/` in a plugin or a module is the project's own files: the core's API
|
|
935
|
+
* goes by `@amxts/core/<entry>` and a module package by its name. What
|
|
936
|
+
* to write instead, or null for a file of the project's own.
|
|
937
|
+
*/
|
|
938
|
+
private notOwn(real: string, spec: string): string | null {
|
|
939
|
+
const file = this.resolveImport(this.place(real), spec);
|
|
940
|
+
const target = file ? this.real(file) : null;
|
|
941
|
+
if (!target) return file && this.generated(file) ? '' : null;
|
|
942
|
+
const pkg = this.packageOf(target);
|
|
943
|
+
if (pkg) return pkg.module === target ? pkg.name : `${pkg.name}/${posix(relative(pkg.dir, target)).replace(/\.ts$/, '')}`;
|
|
944
|
+
return this.isCore(target) ? coreEntryOf(this.rel(file!)) : null;
|
|
945
|
+
}
|
|
946
|
+
|
|
857
947
|
/** A specifier as the tree has it: a package by its place, a relative import inside a package likewise. */
|
|
858
948
|
private specifier(real: string, spec: string): string {
|
|
859
|
-
|
|
860
|
-
if (spec
|
|
949
|
+
const display = () => posix(relative(this.project.dir, real));
|
|
950
|
+
if (spec === '@amxts/core' || spec.startsWith('@amxts/core/')) {
|
|
951
|
+
const place = CORE_ENTRIES[spec];
|
|
952
|
+
if (place) return `~/${place.replace(/\.ts$/, '')}`;
|
|
953
|
+
this.problem(`${display()}: imports ${spec}, which @amxts/core does not export - its API is ${Object.keys(CORE_ENTRIES).join(', ')}`);
|
|
954
|
+
return spec;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
if (spec.startsWith('~/')) {
|
|
958
|
+
const instead = this.isCore(real) ? null : this.notOwn(real, spec);
|
|
959
|
+
if (instead !== null) this.problem(`${display()}: ${spec} is not a file of the project - \`~/\` is the plugins folder; import ${instead || 'the module by its package name'} (npx amxts upgrade rewrites these imports)`);
|
|
960
|
+
return spec;
|
|
961
|
+
}
|
|
861
962
|
|
|
862
963
|
if (spec.startsWith('.')) {
|
|
863
964
|
const pkg = this.packageOf(real);
|
|
@@ -873,7 +974,6 @@ export class Sources {
|
|
|
873
974
|
return `~/modules/${pkg.short}/${posix(relative(pkg.dir, target))}`;
|
|
874
975
|
}
|
|
875
976
|
|
|
876
|
-
if (spec.startsWith('~/')) return spec;
|
|
877
977
|
const name = spec.match(/^(@[^/]+\/[^/]+|[^@/][^/]*)/)?.[1];
|
|
878
978
|
const active = this.project.modules.find(pkg => pkg.name === name);
|
|
879
979
|
if (active) return spec === active.name ? `~/modules/${active.short}` : `~/modules/${active.short}/${spec.slice(name!.length + 1)}`;
|
|
@@ -885,7 +985,7 @@ export class Sources {
|
|
|
885
985
|
}
|
|
886
986
|
|
|
887
987
|
rewrite(real: string, text: string): string {
|
|
888
|
-
if (!text.includes('@') && !(this.packageOf(real) && /["']\.\.?\//.test(text))) return text;
|
|
988
|
+
if (!text.includes('@') && !text.includes('~/') && !(this.packageOf(real) && /["']\.\.?\//.test(text))) return text;
|
|
889
989
|
return text.replace(IMPORT, (whole, head: string, quote: string, spec: string) => {
|
|
890
990
|
const next = this.specifier(real, spec);
|
|
891
991
|
return next === spec ? whole : `${head}${quote}${next}${quote}`;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// The game events reapi alone delivers - ReGameDLL's and ReHLDS's own
|
|
2
|
+
// functions - and the build's refusal of a listener for one nothing else
|
|
3
|
+
// hears, in a project for plain HLDS.
|
|
4
|
+
//
|
|
5
|
+
// A server without reapi hears every other game event through Ham Sandwich
|
|
6
|
+
// (as/hooks.ts picks it once, by hasModule), and many of these through
|
|
7
|
+
// another stock hook (scripts/hlds-events.ts); a listener for one of the rest
|
|
8
|
+
// is only a line in its console. A project whose amxts.config.ts says
|
|
9
|
+
// `target: "hlds"` has said there is no reapi, so the build stops at such a
|
|
10
|
+
// listener instead, with the file and the line.
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
// @ts-ignore - shipped as JavaScript, with types beside it we do not need here
|
|
13
|
+
import * as asc from '../runtime/deps/assemblyscript/dist/assemblyscript.js';
|
|
14
|
+
import { HEARD } from './hlds-events';
|
|
15
|
+
import { CORE_PLUGINS } from './project';
|
|
16
|
+
import { readFileSync } from './tracked-fs';
|
|
17
|
+
|
|
18
|
+
let cached: { text: string; events: Set<string> } | null = null;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The events of as/hooks.ts's GameEventMap reapi alone delivers: an event
|
|
22
|
+
* class with no Ham Sandwich function behind it (`private static readonly
|
|
23
|
+
* ham`) is a hookchain's alone.
|
|
24
|
+
*/
|
|
25
|
+
export function reapiEvents(hooksText = readFileSync(join(CORE_PLUGINS, 'hooks.ts'), 'utf8')): Set<string> {
|
|
26
|
+
if (cached?.text === hooksText) return cached.events;
|
|
27
|
+
|
|
28
|
+
const reapiClasses = new Set<string>();
|
|
29
|
+
for (const [, name, body] of hooksText.matchAll(/^export class (\w+) extends HookEvent \{\n([\s\S]*?)\n\}\n/gm)) {
|
|
30
|
+
if (!body.includes('private static readonly ham:')) reapiClasses.add(name);
|
|
31
|
+
}
|
|
32
|
+
const map = hooksText.slice(hooksText.indexOf('export interface GameEventMap'));
|
|
33
|
+
const events = new Set<string>();
|
|
34
|
+
for (const [, event, name] of map.slice(0, map.indexOf('\n}\n')).matchAll(/^\t(\w+): (\w+);/gm)) {
|
|
35
|
+
if (reapiClasses.has(name)) events.add(event);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
cached = { text: hooksText, events };
|
|
39
|
+
return events;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** The events reapi alone delivers that plain HLDS does not hear at all. */
|
|
43
|
+
export function unheardEvents(hooksText?: string): Set<string> {
|
|
44
|
+
return new Set([...reapiEvents(hooksText)].filter(event => !Object.hasOwn(HEARD, event)));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The event a `game.addEventListener("name", ...)` call listens for; undefined for any other call. */
|
|
48
|
+
function gameEventOf(call: any): string | undefined {
|
|
49
|
+
const callee = call.expression;
|
|
50
|
+
if (callee.kind !== asc.NodeKind.PropertyAccess || callee.property.text !== 'addEventListener') return undefined;
|
|
51
|
+
if (callee.expression.kind !== asc.NodeKind.Identifier || callee.expression.text !== 'game') return undefined;
|
|
52
|
+
const type = call.args[0];
|
|
53
|
+
return type?.kind === asc.NodeKind.Literal && type.literalKind === asc.LiteralKind.String ? type.value : undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Calls `visit` on every node under `node`. */
|
|
57
|
+
function walk(node: any, visit: (node: any) => void) {
|
|
58
|
+
if (Array.isArray(node)) {
|
|
59
|
+
for (const each of node) walk(each, visit);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (!node || typeof node !== 'object' || typeof node.kind !== 'number') return;
|
|
63
|
+
visit(node);
|
|
64
|
+
for (const key in node) {
|
|
65
|
+
if (key !== 'range') walk(node[key], visit);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** The listeners in a compile's sources for an event plain HLDS does not hear, each where it is written. */
|
|
70
|
+
export function reapiListeners(sources: any[], events = unheardEvents()): string[] {
|
|
71
|
+
const found: string[] = [];
|
|
72
|
+
for (const source of sources) {
|
|
73
|
+
if (source.normalizedPath.startsWith('~lib/') || !source.text.includes('addEventListener')) continue;
|
|
74
|
+
walk(source.statements, (node) => {
|
|
75
|
+
if (node.kind !== asc.NodeKind.Call) return;
|
|
76
|
+
const event = gameEventOf(node);
|
|
77
|
+
if (event === undefined || !events.has(event)) return;
|
|
78
|
+
const line = source.text.slice(0, node.range.start).split('\n').length;
|
|
79
|
+
found.push(`${source.normalizedPath}:${line}: "${event}" needs ReAPI, and amxts.config.ts's target is "hlds" - nothing on plain HLDS hears it. Listen for another event, or set target: "rehlds"`);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return found;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The transform that stops a plugin for plain HLDS at a listener nothing there could call. */
|
|
86
|
+
function hldsTransform() {
|
|
87
|
+
return class {
|
|
88
|
+
afterParse(parser: any): void {
|
|
89
|
+
const problems = reapiListeners(parser.sources);
|
|
90
|
+
if (problems.length) throw new Error(problems.join('\n'));
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** What a project's target adds to a compile: for plain HLDS, the refusal of the events it does not hear. */
|
|
96
|
+
export function targetTransforms(target: string | undefined) {
|
|
97
|
+
return target === 'hlds' ? [hldsTransform()] : [];
|
|
98
|
+
}
|