@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,93 @@
1
+ // Checks a module package before it is published - `npx amxts check` in the
2
+ // module's folder, as a CI step:
3
+ //
4
+ // - package.json's "amxts" field points at files that are there;
5
+ // - the module file declares itself with defineModule, meta.name its name;
6
+ // - README.md and LICENSE are there;
7
+ // - the natives compile, and include/<name>.inc is what the build writes from
8
+ // them - not stale after an edit of natives.ts. With `"contract": true`
9
+ // the include is the original's: the natives compiling means they match it.
10
+ //
11
+ // It writes nothing into the package; what it compiles goes to a temporary
12
+ // folder.
13
+ import type { PluginNative } from './plugin-natives';
14
+ import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
15
+ import { tmpdir } from 'node:os';
16
+ import { basename, join, relative } from 'node:path';
17
+ import { importedName } from './auto-imports';
18
+ import { compileToWasm } from './compile';
19
+ import { includeName, pawnInclude } from './plugin-natives';
20
+ import { CORE_PLUGINS, loadProject, readDefinition, readPackage, sourcesFor } from './project';
21
+ import { c, log } from './ui';
22
+
23
+ function fail(message: string): void {
24
+ process.stderr.write(`${message}\n`);
25
+ }
26
+
27
+ const dir = process.cwd();
28
+ const problems: string[] = [];
29
+ const passed: string[] = [];
30
+
31
+ let pkg: ReturnType<typeof readPackage> = null;
32
+ try {
33
+ pkg = readPackage(dir);
34
+ } catch (problem) {
35
+ problems.push((problem as Error).message);
36
+ }
37
+
38
+ if (!pkg && problems.length === 0) {
39
+ fail('amxts check: this folder is not a module package - package.json has no "amxts": { "module": ... }');
40
+ process.exit(1);
41
+ }
42
+
43
+ if (pkg) {
44
+ passed.push(`"amxts" points at ${[pkg.module, pkg.natives, pkg.include].filter(Boolean).map(file => relative(dir, file!).replace(/\\/g, '/')).join(', ')}`);
45
+
46
+ try {
47
+ const definition = readDefinition(pkg.module);
48
+ if (!definition) problems.push(`${relative(dir, pkg.module)}: no \`export default defineModule({ meta: { name: "${pkg.short}" }, ... })\``);
49
+ else if (!definition.name) problems.push(`${relative(dir, pkg.module)}: defineModule has no meta.name - "${pkg.short}"`);
50
+ else passed.push(`defineModule: ${definition.name}${definition.configKey ? `, options under "${definition.configKey}"` : ''}${definition.imports.length ? `, plugins use it as ${definition.imports.map(importedName).join(', ')}` : ''}`);
51
+ } catch (problem) {
52
+ problems.push((problem as Error).message);
53
+ }
54
+
55
+ for (const file of ['README.md', 'LICENSE']) {
56
+ if (!existsSync(join(dir, file))) problems.push(`${file} is missing`);
57
+ }
58
+
59
+ const project = loadProject(dir);
60
+ problems.push(...project.problems);
61
+ if (pkg.natives && project.problems.length === 0) await checkNatives(pkg);
62
+ }
63
+
64
+ async function checkNatives(module: NonNullable<typeof pkg>) {
65
+ const sources = sourcesFor(CORE_PLUGINS);
66
+ const out = mkdtempSync(join(tmpdir(), 'amxts-check-'));
67
+ try {
68
+ const natives: PluginNative[] = [];
69
+ const problem = await compileToWasm({ source: sources.ownerSource(module), root: CORE_PLUGINS }, join(out, `${module.short}.wasm`), false, natives);
70
+ if (problem) {
71
+ problems.push(`${relative(dir, module.natives!)} does not compile:\n${problem.trim()}`);
72
+ return;
73
+ }
74
+ if (module.contract) {
75
+ passed.push(`${natives.length} natives match the contract ${relative(dir, module.include!).replace(/\\/g, '/')}`);
76
+ return;
77
+ }
78
+ const include = module.include ?? join(dir, 'include', `${includeName(module.short)}.inc`);
79
+ const expected = pawnInclude(module.short, natives).replace(/\r\n/g, '\n');
80
+ const actual = existsSync(include) ? readFileSync(include, 'utf8').replace(/\r\n/g, '\n') : null;
81
+ const name = relative(dir, include).replace(/\\/g, '/');
82
+ if (actual === null) problems.push(`${name} is missing - npx amxts build writes it from ${basename(module.natives!)}`);
83
+ else if (actual !== expected) problems.push(`${name} is stale: it is not what ${basename(module.natives!)} gives - run npx amxts build and commit it`);
84
+ else passed.push(`${name} is up to date with ${natives.length} natives`);
85
+ } finally {
86
+ rmSync(out, { recursive: true, force: true });
87
+ }
88
+ }
89
+
90
+ for (const line of passed) log.success(line);
91
+ for (const line of problems) log.error(line);
92
+ if (problems.length === 0) log.success(c.bold(`${pkg!.name} is ready to publish`));
93
+ process.exit(problems.length ? 1 : 0);
@@ -0,0 +1,179 @@
1
+ // Compiles kept on disk, reused while what they read is the same.
2
+ //
3
+ // A compile is keyed by everything that is not a file it read - the
4
+ // compiler, the hood's scripts, the flags, the project and its config - and
5
+ // stored with every file read it made (scripts/tracked-fs.ts): the entry, what
6
+ // it imports, the facade and the generated API, the module packages. It is
7
+ // taken again only while each of those reads sees the same thing it saw, so a
8
+ // hit is what compiling again would give, byte for byte.
9
+ //
10
+ // One key holds one entry: a compile made again replaces the one before, so a
11
+ // cache does not grow with every edit. The testing library keeps its compiles
12
+ // in one (src/testing/compile-cache.ts), a build its plugins in another
13
+ // (scripts/build-wasm.ts).
14
+ import type { Reads } from './tracked-fs';
15
+ import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
16
+ import { dirname, join, resolve } from 'node:path';
17
+ import { fileURLToPath } from 'node:url';
18
+ import { listIncludes } from './includes';
19
+ import { currentProjectDir, loadProject } from './project';
20
+ import { hashOf, recordReads, unchanged } from './tracked-fs';
21
+
22
+ /** Bump when what an entry holds changes shape. */
23
+ const FORMAT = 1;
24
+
25
+ /** Entries made this long ago are removed; one still in use is made again. */
26
+ const UNUSED_FOR = 14 * 24 * 3600 * 1000;
27
+
28
+ const CORE = resolve(dirname(fileURLToPath(import.meta.url)), '..');
29
+
30
+ /**
31
+ * The compiler: the patched AssemblyScript (with its standard library inside
32
+ * asc.js), its Binaryen, and every script of the hood a compile runs through -
33
+ * `entries`, this file and what they import, read off their import lines.
34
+ */
35
+ export function codeIdentity(entries: string[]): string {
36
+ const parts = codeFiles(entries).map(file => `${file}:${existsSync(file) ? hashOf(readFileSync(file)) : 'missing'}`);
37
+ return hashOf(parts.join('\n'));
38
+ }
39
+
40
+ /** The files codeIdentity hashes, sorted. */
41
+ export function codeFiles(entries: string[]): string[] {
42
+ const files = new Set<string>();
43
+ const visit = (file: string) => {
44
+ if (files.has(file) || !existsSync(file)) return;
45
+ files.add(file);
46
+ if (!file.endsWith('.ts')) return;
47
+ for (const [, spec] of readFileSync(file, 'utf8').matchAll(/(?:from|import)\s*['"](\.[^'"]+)['"]/g)) {
48
+ const target = resolve(dirname(file), spec);
49
+ visit(/\.[cm]?js$|\.ts$/.test(target) ? target : `${target}.ts`);
50
+ }
51
+ };
52
+ [...entries, fileURLToPath(import.meta.url)].forEach(visit);
53
+ const deps = join(CORE, 'runtime/deps/assemblyscript');
54
+ for (const file of [
55
+ join(deps, 'dist/asc.js'),
56
+ join(deps, 'dist/assemblyscript.js'),
57
+ join(deps, 'node_modules/binaryen/package.json'),
58
+ join(CORE, 'runtime/patches/assemblyscript-0.28.20-amxts.patch'),
59
+ ]) {
60
+ files.add(file);
61
+ }
62
+ return [...files].sort();
63
+ }
64
+
65
+ /**
66
+ * The project a compile is made in: its folder, its config's text, and the
67
+ * modules it resolved to with their options - a module's owner is compiled
68
+ * with the options its project gives it. The .inc files there are to be
69
+ * found in are listed once per run, so they are part of it too - but for
70
+ * `own`, the ones a build writes itself.
71
+ */
72
+ function projectIdentity(own: string[]): string {
73
+ const dir = currentProjectDir();
74
+ const config = join(dir, 'amxts.config.ts');
75
+ return hashOf(JSON.stringify({
76
+ dir,
77
+ config: existsSync(config) ? hashOf(readFileSync(config)) : null,
78
+ project: loadProject(dir),
79
+ includes: listIncludes().filter(name => !own.includes(name)),
80
+ }));
81
+ }
82
+
83
+ // Uint8Array fields travel as base64.
84
+ function encode(_: string, value: unknown) {
85
+ return value instanceof Uint8Array ? { $bytes: Buffer.from(value).toString('base64') } : value;
86
+ }
87
+ function decode(_: string, value: any) {
88
+ return value && typeof value === 'object' && typeof value.$bytes === 'string' ? new Uint8Array(Buffer.from(value.$bytes, 'base64')) : value;
89
+ }
90
+
91
+ interface Entry<T> {
92
+ reads: Reads;
93
+ value: T;
94
+ }
95
+
96
+ export interface DiskCache {
97
+ /** Whether results are kept on disk: not without a folder. */
98
+ on: boolean;
99
+ /** How often this process took a result from the disk, and made one. */
100
+ counts: { hits: number; misses: number };
101
+ /** What an earlier run made for `parts`, while every file it read is the same; else null. */
102
+ find: <T>(parts: unknown[]) => T | null;
103
+ /** `run`'s value, made now and kept for `parts` - unless `keep` says it is not worth keeping. */
104
+ make: <T>(parts: unknown[], run: () => Promise<T>, keep?: (value: T) => boolean) => Promise<T>;
105
+ /** find(), else make(). */
106
+ cached: <T>(parts: unknown[], run: () => Promise<T>, keep?: (value: T) => boolean) => Promise<T>;
107
+ }
108
+
109
+ /**
110
+ * A cache in `dir` (none when it is null: every compile is made). `parts` is
111
+ * what a value depends on besides the files it reads: the entry, the flags. A
112
+ * value is JSON, with Uint8Arrays in it. `ownIncludes` are includes the
113
+ * compiles themselves put where includes are found - a deploy copies its
114
+ * plugins' includes to the server - which no compile depends on.
115
+ */
116
+ export function diskCache(dir: string | null, code: () => string, ownIncludes: string[] = []): DiskCache {
117
+ const counts = { hits: 0, misses: 0 };
118
+ let identity: string | null = null;
119
+ const fileOf = (parts: unknown[]) => {
120
+ identity ??= hashOf(`${FORMAT}\n${code()}`);
121
+ return join(dir!, `${hashOf(JSON.stringify([identity, projectIdentity(ownIncludes), ...parts]))}.json`);
122
+ };
123
+
124
+ let pruned = false;
125
+ const prune = () => {
126
+ if (pruned || !existsSync(dir!)) return;
127
+ pruned = true;
128
+ const now = Date.now();
129
+ for (const name of readdirSync(dir!)) {
130
+ const file = join(dir!, name);
131
+ try {
132
+ if (now - statSync(file).mtimeMs > UNUSED_FOR) rmSync(file, { force: true });
133
+ } catch {}
134
+ }
135
+ };
136
+
137
+ const find = <T>(parts: unknown[]): T | null => {
138
+ if (!dir) return null;
139
+ prune();
140
+ try {
141
+ const entry = JSON.parse(readFileSync(fileOf(parts), 'utf8'), decode) as Entry<T>;
142
+ if (!unchanged(entry.reads)) return null;
143
+ counts.hits++;
144
+ return entry.value;
145
+ } catch {
146
+ return null;
147
+ }
148
+ };
149
+
150
+ const make = async <T>(parts: unknown[], run: () => Promise<T>, keep: (value: T) => boolean = () => true): Promise<T> => {
151
+ if (!dir) return run();
152
+ counts.misses++;
153
+ const { reads, result } = await recordReads(run);
154
+ if (result.status === 'rejected') throw result.reason;
155
+ if (keep(result.value)) store(fileOf(parts), { reads, value: result.value });
156
+ return result.value;
157
+ };
158
+
159
+ return {
160
+ on: dir !== null,
161
+ counts,
162
+ find,
163
+ make,
164
+ cached: async (parts, run, keep) => find(parts) ?? make(parts, run, keep),
165
+ };
166
+ }
167
+
168
+ function store(file: string, entry: Entry<unknown>) {
169
+ // Beside it first, then into place: a run stopped halfway leaves the old
170
+ // entry or the new one, never half of one.
171
+ const partial = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
172
+ try {
173
+ mkdirSync(dirname(file), { recursive: true });
174
+ writeFileSync(partial, JSON.stringify(entry, encode));
175
+ renameSync(partial, file);
176
+ } catch {
177
+ rmSync(partial, { force: true });
178
+ }
179
+ }
@@ -0,0 +1,401 @@
1
+ import type { PluginNative } from './plugin-natives';
2
+ import type { System } from './system';
3
+ import { spawnSync } from 'node:child_process';
4
+
5
+ // Compiles one plugin: .ts in, .aot out.
6
+ //
7
+ // Both halves of the project use this one function - `bun run plugins` here,
8
+ // and amxts-compile.exe on a server - so that a plugin compiles the same way
9
+ // in both places. In particular the `~/` alias, which is ours rather than
10
+ // AssemblyScript's and lives in the readFile hook below.
11
+ import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
12
+ import { basename, dirname, relative, resolve } from 'node:path';
13
+ // The Binaryen asc itself runs on - the same copy, so the compiler a server
14
+ // gets carries one - for the one pass asc does not run: Asyncify.
15
+ // @ts-ignore - shipped as JavaScript, with types beside it we do not need here
16
+ import * as assemblyscript from '../runtime/deps/assemblyscript/dist/assemblyscript.js';
17
+ // @ts-ignore - its types are beside it, under a path tsconfig does not map
18
+ import binaryen from '../runtime/deps/assemblyscript/node_modules/binaryen/index.js';
19
+ import { ascMain } from './asc';
20
+ import { playerFieldsBuild } from './player-fields';
21
+ import { includeName, nativeContract, nativesTransform, pawnInclude } from './plugin-natives';
22
+ import { ascPath, sourcesFor } from './project';
23
+ import { sharedModulesBuild } from './shared-modules';
24
+ import { HOST_SYSTEM, TARGET_ABI } from './system';
25
+
26
+ export interface Plugin {
27
+ /** The .ts to compile. */
28
+ source: string;
29
+ /** The .aot to write. */
30
+ output: string;
31
+ /** What `~/` means: the folder plugins and their libraries live in. */
32
+ root: string;
33
+ wamrc: string;
34
+ signatures: string;
35
+ /**
36
+ * The system of the server that loads it: the .aot is written in that
37
+ * system's object format (scripts/system.ts). This machine's by default.
38
+ */
39
+ system?: System;
40
+ /**
41
+ * Compiled for a development loop (`amxts dev`): Binaryen's -O1 in place
42
+ * of asc's full optimisation, and wamrc at -O0 - a few seconds for a small
43
+ * plugin rather than ten. It does the same, a little slower.
44
+ */
45
+ quick?: boolean;
46
+ }
47
+
48
+ /** Binaryen's optimisation level: a quick build's, and a full one's - asc's own --optimize. */
49
+ const OPTIMIZE = { quick: 1, full: 3 };
50
+
51
+ /** The hood's exports every plugin carries; see compilePlugin. */
52
+ export const HOOD_EXPORTS = '__amxts_exports.ts';
53
+
54
+ /**
55
+ * The second entry of a plugin that never waits: the hood has nothing to
56
+ * export, but its globals - Promise, defineModule - are there for any
57
+ * plugin, one that imports nothing included.
58
+ */
59
+ export const BASE_EXPORTS = 'import "~/facade";\n';
60
+
61
+ /**
62
+ * What the module's coroutine scheduler calls, for a plugin that uses
63
+ * Promise or async/await (as/promise.ts, runtime/src/module.cpp). Only such a
64
+ * plugin carries them: exporting them from every plugin would compile the
65
+ * whole scheduler into plugins that never wait for anything.
66
+ */
67
+ export const ASYNC_EXPORTS = BASE_EXPORTS + [
68
+ 'export function __co_next(): i32 { return __co_next_job(); }',
69
+ 'export function __co_done(outcome: i32): void { __co_resumed(outcome); }',
70
+ 'export function __co_stack(): usize { return __co_sp(); }',
71
+ 'export function __co_stack_set(sp: usize): void { __co_set_sp(sp); }',
72
+ 'export function __co_save(id: i32, lo: usize, hi: usize): void { __co_park(id, lo, hi); }',
73
+ 'export function __co_saved(id: i32): usize { return __co_parked(id); }',
74
+ 'export function __co_restore(id: i32, lo: usize): void { __co_unpark(id, lo); }',
75
+ '',
76
+ ].join('\n');
77
+
78
+ /**
79
+ * A transform that moves the imports at the end of a file - the ones the
80
+ * build adds (scripts/auto-imports.ts), after the file's last line - to its
81
+ * top, as ES modules run an imported file before the one that imports it:
82
+ * asc compiles an imported file's top level where the import stands. The
83
+ * imports a file writes itself stay where they are.
84
+ */
85
+ export class HoistImports {
86
+ afterParse(parser: any): void {
87
+ for (const source of parser.sources) {
88
+ const statements: any[] = source.statements;
89
+ const last = statements.findLastIndex(statement => statement.kind !== assemblyscript.NodeKind.Import);
90
+ if (last < 0 || last === statements.length - 1) continue;
91
+ source.statements = [...statements.slice(last + 1), ...statements.slice(0, last + 1)];
92
+ }
93
+ }
94
+ }
95
+
96
+ /** The import a coroutine parks in - the one Asyncify unwinds from. */
97
+ export const SUSPEND_IMPORT = 'env.co_suspend';
98
+
99
+ /**
100
+ * Where compilePlugin writes the Pawn include for a plugin's natives: beside
101
+ * the .aot, named after the plugin - or, for a plugin that implements an
102
+ * include (a contract), under that include's own name, so a Pawn plugin's
103
+ * `#include` of it works whatever the module is called.
104
+ */
105
+ export function includePath(output: string, natives: PluginNative[] = []): string {
106
+ const contract = nativeContract(natives);
107
+ const name = contract ? basename(contract.file).replace(/\.inc$/, '') : includeName(basename(output).replace(/\.aot$/, ''));
108
+ return resolve(dirname(output), `${name}.inc`);
109
+ }
110
+
111
+ /** What went wrong, or null when the plugin compiled. `natives` gets the plugin's natives. */
112
+ export async function compilePlugin(plugin: Plugin, natives: PluginNative[] = []): Promise<string | null> {
113
+ const wasm = plugin.output.replace(/\.aot$/, '.wasm');
114
+ mkdirSync(dirname(plugin.output), { recursive: true });
115
+
116
+ const problem = await compileToWasm(plugin, wasm, false, natives);
117
+ if (problem) return problem;
118
+ return compileToMachineCode(plugin, wasm, natives);
119
+ }
120
+
121
+ /**
122
+ * The second half of compilePlugin: wamrc, from `wasm` to the plugin's .aot
123
+ * for its system, with the include of its `natives` beside it. One wasm
124
+ * makes the .aot of either system (scripts/prebuilt.ts makes both).
125
+ */
126
+ export function compileToMachineCode(plugin: Plugin, wasm: string, natives: PluginNative[]): string | null {
127
+ // Written beside the .aot and moved over it once it passed: a server that
128
+ // reads the build folder where it is - the Docker one - reloads a plugin
129
+ // the moment its file changes, and must never see half of one; and a
130
+ // plugin that fails keeps the last good build.
131
+ mkdirSync(dirname(plugin.output), { recursive: true });
132
+ const part = `${plugin.output}.part`;
133
+ const wamrc = (level: string[]) => spawnSync(plugin.wamrc, [
134
+ '--target=i386',
135
+ `--target-abi=${TARGET_ABI[plugin.system ?? HOST_SYSTEM]}`,
136
+ `--native-signatures=${plugin.signatures}`,
137
+ ...level,
138
+ '-o',
139
+ part,
140
+ wasm,
141
+ ], { encoding: 'utf-8' });
142
+ let compile = wamrc(plugin.quick ? ['--opt-level=0'] : []);
143
+ // At -O0 a big function keeps a big stack frame, and for a frame over
144
+ // 4 KB LLVM calls _chkstk on Windows, which the module's loader does not
145
+ // have ("resolve symbol _chkstk failed"): such a plugin - menu-core is
146
+ // one - is compiled again at wamrc's own level. The .aot names the symbol.
147
+ if (plugin.quick && compile.status === 0 && readFileSync(part).includes('_chkstk')) compile = wamrc([]);
148
+
149
+ const out = `${compile.stdout}${compile.stderr}`;
150
+
151
+ if (compile.status !== 0) {
152
+ rmSync(part, { force: true });
153
+ return out;
154
+ }
155
+
156
+ // A signature that disagrees with its import is not an error to wamrc: it
157
+ // drops that native to the generic path, 28 ns a call instead of 2, and
158
+ // says so only in this warning. That means the API and the module have
159
+ // drifted apart, which is worth refusing rather than running slowly.
160
+ if (/failed to check signature/.test(out)) {
161
+ rmSync(part, { force: true });
162
+ return `a native signature does not match its import - this plugin kit does not belong to this module\n${out}`;
163
+ }
164
+
165
+ writeInclude(plugin.output, natives);
166
+ renameSync(part, plugin.output);
167
+ return null;
168
+ }
169
+
170
+ /**
171
+ * The plugin's `export function`s, as a Pawn plugin includes them, beside
172
+ * its .aot. One that exports nothing leaves no include behind, not even a
173
+ * stale one; neither does a contract, whose include has a name of its own.
174
+ */
175
+ export function writeInclude(output: string, natives: PluginNative[]): void {
176
+ const include = includePath(output, natives);
177
+ const ownName = includePath(output);
178
+ if (include !== ownName) rmSync(ownName, { force: true });
179
+ if (natives.length) writeFileSync(include, pawnInclude(basename(output).replace(/\.aot$/, ''), natives));
180
+ else rmSync(include, { force: true });
181
+ }
182
+
183
+ /**
184
+ * The first half of compilePlugin: the plugin's .ts to `wasm`, ready for
185
+ * wamrc. Tests run the result as it is; with `names` it keeps its function
186
+ * names, so a test can see what Asyncify instrumented. With `natives`, the
187
+ * plugin's `export function`s become natives, listed there.
188
+ */
189
+ export async function compileToWasm(
190
+ plugin: Pick<Plugin, 'source' | 'root' | 'quick'>,
191
+ wasm: string,
192
+ names = false,
193
+ natives?: PluginNative[],
194
+ ): Promise<string | null> {
195
+ // A quick build leaves the optimising to one Binaryen pass afterwards.
196
+ const flags = [...(plugin.quick ? [] : ['--optimize']), ...(names ? ['--debug'] : [])];
197
+ const level = plugin.quick ? OPTIMIZE.quick : OPTIMIZE.full;
198
+ let problem = await compileWasm(plugin, wasm, BASE_EXPORTS, flags, natives);
199
+ if (problem) return problem;
200
+
201
+ // A plugin that makes a promise - an async function, fetch, sleep - has
202
+ // the host drive its jobs, so it is compiled again with the scheduler's
203
+ // exports. Only then, and only when a coroutine can park, does Asyncify
204
+ // run: a plugin without either is the same wasm it always was.
205
+ if (importsOf(readFileSync(wasm)).has('env.co_wake')) {
206
+ if (natives) natives.length = 0;
207
+ problem = await compileWasm(plugin, wasm, ASYNC_EXPORTS, flags, natives);
208
+ if (problem) return problem;
209
+ }
210
+ const binary = readFileSync(wasm);
211
+ if (importsOf(binary).has(SUSPEND_IMPORT)) writeFileSync(wasm, asyncify(binary, names, level));
212
+ else if (plugin.quick) writeFileSync(wasm, optimized(binary, level));
213
+ return null;
214
+ }
215
+
216
+ /** Binaryen's optimisation at `level`: what a quick build runs in place of asc's. */
217
+ function optimized(wasm: Uint8Array, level: number): Uint8Array {
218
+ const module = binaryen.readBinary(wasm);
219
+ try {
220
+ module.setFeatures(binaryen.Features.All);
221
+ binaryen.setOptimizeLevel(level);
222
+ binaryen.setShrinkLevel(0);
223
+ module.optimize();
224
+ return module.emitBinary();
225
+ } finally {
226
+ module.dispose();
227
+ }
228
+ }
229
+
230
+ /**
231
+ * asc, from the plugin's .ts to `wasm`, with `hoodExports` as the second entry.
232
+ * With `natives`, the plugin's `export function`s become its natives and are
233
+ * listed there; without, they stay plain wasm exports - what a test host that
234
+ * calls a fixture's functions by name wants.
235
+ */
236
+ async function compileWasm(
237
+ plugin: Pick<Plugin, 'source' | 'root'>,
238
+ wasm: string,
239
+ hoodExports: string,
240
+ flags: string[],
241
+ natives?: PluginNative[],
242
+ ): Promise<string | null> {
243
+ // --exportTable is not optional: a handler reaches the module as its index
244
+ // in the function table, and without this asc emits no table at all, so
245
+ // every index points at nothing.
246
+ // asc is given the entry relative to the root it is told to read from, so
247
+ // that a plugin in a subfolder and the alias below resolve the same way.
248
+ // A module package's plugin compiles as ~/<name>.ts, a project's plugin as
249
+ // its place under ~/ (scripts/project.ts).
250
+ const sources = sourcesFor(plugin.root);
251
+ if (sources.project.problems.length) return sources.project.problems.join('\n');
252
+ const entry = sources.entry(plugin.source);
253
+ // Player's fields that plugins add, from every file the plugin reaches (scripts/player-fields.ts).
254
+ const playerFields = playerFieldsBuild();
255
+ // The modules it reaches that another plugin owns come as proxies (scripts/shared-modules.ts).
256
+ let shared: Awaited<ReturnType<typeof sharedModulesBuild>>;
257
+ try {
258
+ shared = await sharedModulesBuild(plugin.root, entry);
259
+ } catch (problem) {
260
+ return String((problem as Error).message ?? problem);
261
+ }
262
+
263
+ const { error, stderr } = await ascMain(
264
+ [
265
+ entry,
266
+ // A second entry nobody writes: what the hood exports - the
267
+ // coroutine scheduler's functions, for a plugin that waits (only an
268
+ // entry file's exports reach the wasm).
269
+ HOOD_EXPORTS,
270
+ '--outFile',
271
+ wasm,
272
+ '--exportTable',
273
+ ...flags,
274
+ ],
275
+ {
276
+ readFile(filename: string, baseDir: string): string | null {
277
+ if (filename.replace(/\\/g, '/').endsWith(HOOD_EXPORTS)) return hoodExports;
278
+
279
+ // `~/` is the plugins folder: ascPath answers the alias.
280
+ const path = ascPath(plugin.root, filename, baseDir);
281
+ const text = sources.read(path);
282
+ return text === null ? null : shared.read(path, playerFields.read(relative('.', (sources.real(path) ?? path)), text));
283
+ },
284
+
285
+ writeFile(filename: string, contents: string | Uint8Array): void {
286
+ // The name asc passes back is the one it was given in --outFile,
287
+ // so it is already where the caller wants it.
288
+ const path = resolve(filename);
289
+ mkdirSync(dirname(path), { recursive: true });
290
+ writeFileSync(path, contents);
291
+ },
292
+
293
+ listFiles(): string[] {
294
+ return [];
295
+ },
296
+
297
+ transforms: natives
298
+ ? [HoistImports, nativesTransform(entry, natives, plugin.root), playerFields.transform, shared.transform]
299
+ : [HoistImports, playerFields.transform, shared.transform],
300
+ },
301
+ );
302
+
303
+ // An import of a module the config does not list says so first: asc's
304
+ // "not found" after it is only the consequence.
305
+ if (sources.problems.length) return [...sources.problems, stderr.toString()].join('\n').trim();
306
+
307
+ // A transform's refusal - a native whose signature cannot cross - is an
308
+ // error with nothing written to stderr.
309
+ return error ? (stderr.toString() || String(error.message ?? error)) : null;
310
+ }
311
+
312
+ /**
313
+ * Asyncify, so that a coroutine can park in co_suspend and be rewound later.
314
+ *
315
+ * What to instrument is Binaryen's own call graph from that one import: the
316
+ * async function bodies, __await, and whatever calls them directly. Indirect
317
+ * calls are left out (asyncify-ignore-indirect), and that is exact rather
318
+ * than a guess: an async function is always entered by the host - its
319
+ * prologue asks co_spawn to call it - so no `await` is ever reached through
320
+ * a function table, and a listener that is async returns to its caller at its
321
+ * first `await` like any other call. Nothing can be missing from the list,
322
+ * which is the failure that hangs rather than traps.
323
+ */
324
+ export function asyncify(wasm: Uint8Array, names = false, level = OPTIMIZE.full): Uint8Array {
325
+ const module = binaryen.readBinary(wasm);
326
+ try {
327
+ module.setFeatures(binaryen.Features.All);
328
+ binaryen.setPassArgument('asyncify-imports', SUSPEND_IMPORT);
329
+ binaryen.setPassArgument('asyncify-ignore-indirect', '1');
330
+ // asc's own --optimize levels (a quick build's lower one), so the rest
331
+ // of the module comes out as it went in.
332
+ binaryen.setOptimizeLevel(level);
333
+ binaryen.setShrinkLevel(0);
334
+ binaryen.setDebugInfo(names);
335
+ module.runPasses(['asyncify']);
336
+ module.optimize();
337
+ if (!module.validate()) throw new Error('Asyncify produced an invalid module');
338
+ return module.emitBinary();
339
+ } finally {
340
+ binaryen.setPassArgument('asyncify-imports', null);
341
+ binaryen.setPassArgument('asyncify-ignore-indirect', null);
342
+ binaryen.setDebugInfo(false);
343
+ module.dispose();
344
+ }
345
+ }
346
+
347
+ /** `module.name` of every function a wasm binary imports. */
348
+ export function importsOf(wasm: Uint8Array): Set<string> {
349
+ const names = new Set<string>();
350
+ let at = 8;
351
+
352
+ const u32 = () => {
353
+ let result = 0;
354
+ let shift = 0;
355
+ for (;;) {
356
+ const byte = wasm[at++];
357
+ result |= (byte & 0x7F) << shift;
358
+ if ((byte & 0x80) === 0) return result >>> 0;
359
+ shift += 7;
360
+ }
361
+ };
362
+ const text = () => {
363
+ const length = u32();
364
+ const value = new TextDecoder().decode(wasm.subarray(at, at + length));
365
+ at += length;
366
+ return value;
367
+ };
368
+
369
+ while (at < wasm.length) {
370
+ const id = wasm[at++];
371
+ const size = u32();
372
+ const end = at + size;
373
+ if (id !== 2) {
374
+ at = end;
375
+ continue;
376
+ }
377
+ for (let count = u32(); count > 0; count--) {
378
+ const name = `${text()}.${text()}`;
379
+ const kind = wasm[at++];
380
+ if (kind === 0) {
381
+ u32();
382
+ names.add(name);
383
+ } else if (kind === 1) {
384
+ at++;
385
+ const flags = u32();
386
+ u32();
387
+ if (flags & 1) u32();
388
+ } else if (kind === 2) {
389
+ const flags = u32();
390
+ u32();
391
+ if (flags & 1) u32();
392
+ } else if (kind === 3) {
393
+ at += 2;
394
+ } else {
395
+ break;
396
+ }
397
+ }
398
+ break;
399
+ }
400
+ return names;
401
+ }