@amxts/core 0.2.3 → 0.2.4

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@amxts/core",
3
3
  "type": "module",
4
- "version": "0.2.3",
4
+ "version": "0.2.4",
5
5
  "description": "Counter-Strike 1.6 plugins in TypeScript: compiled ahead of time and run inside AMX Mod X, next to Pawn plugins.",
6
6
  "author": {
7
7
  "name": "kukson777",
@@ -104,8 +104,8 @@
104
104
  "assemblyscript": "0.28.20"
105
105
  },
106
106
  "optionalDependencies": {
107
- "@amxts/wamrc-linux-x64": "^0.2.3",
108
- "@amxts/wamrc-win32-x64": "^0.2.3"
107
+ "@amxts/wamrc-linux-x64": "^0.2.4",
108
+ "@amxts/wamrc-win32-x64": "^0.2.4"
109
109
  },
110
110
  "devDependencies": {
111
111
  "@amxts/config-core": "^0.1.2",
@@ -45,7 +45,7 @@ import { serverIncludes } from './includes';
45
45
  import { pluginCache } from './plugin-cache';
46
46
  import { includeName } from './plugin-natives';
47
47
  import { fromRegistry, prebuiltOf, prebuiltSurface } from './prebuilt';
48
- import { CORE_DIR, CORE_PLUGINS, loadProject, modulesInUse, pluginList, projectPlugins, sourcesFor } from './project';
48
+ import { CORE_DIR, CORE_PLUGINS, loadProject, modulesInUse, pluginList, projectPlugins, sourcesFor, staleCopies } from './project';
49
49
  import { sharedModulesBuild } from './shared-modules';
50
50
  import { describeSystem, serverFolder, serverSystem, SYSTEM_NAME, WAMRC_PACKAGE, wamrcPath } from './system';
51
51
  import { c, live, log, progress, since } from './ui';
@@ -162,6 +162,10 @@ function ownPlugins(): Target[] {
162
162
  return projectPlugins(project).map(source => ({ source, name: basename(source).replace(/\.ts$/, '') }));
163
163
  }
164
164
 
165
+ for (const copy of staleCopies(project)) {
166
+ log.warn(`${shown(copy)} is a copy of amxts's own API from an older version, not a plugin: delete it (npx amxts upgrade does)`);
167
+ }
168
+
165
169
  for (const plugin of ownPlugins()) {
166
170
  if (project.modules.some(pkg => pkg.short === plugin.name)) {
167
171
  fail(`${shown(plugin.source)}: a module in amxts.config.ts is called ${plugin.name} too`, 'Rename the plugin.');
@@ -235,7 +235,7 @@ function mismatch(manifest: PrebuiltManifest, pkg: ModulePackage, sources: Sourc
235
235
  }
236
236
  }
237
237
  const own = resolve(sources.project.pluginsDir) !== resolve(CORE_PLUGINS);
238
- const shadow = own ? manifest.places.find(place => existsSync(join(sources.project.pluginsDir, place))) : undefined;
238
+ const shadow = own ? manifest.places.find(place => sources.ownFile(place)) : undefined;
239
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
240
  return null;
241
241
  }
@@ -53,6 +53,7 @@ import { createRequire } from 'node:module';
53
53
  import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';
54
54
  import { fileURLToPath } from 'node:url';
55
55
  import ts from 'typescript';
56
+ import { serverFolder } from '../src/system.mjs';
56
57
  import { coreImports, importedName, importTable, withAutoImports } from './auto-imports';
57
58
  import { includeDirs } from './includes';
58
59
  import { existsSync, readdirSync, readFileSync, statSync } from './tracked-fs';
@@ -667,9 +668,57 @@ export function coreEntryOf(place: string): string {
667
668
  export function projectPlugins(project: Project): string[] {
668
669
  const core = resolve(project.pluginsDir) === resolve(CORE_PLUGINS);
669
670
  if (core && resolve(project.dir) !== resolve(CORE_DIR)) return [];
671
+ const stale = new Set(staleCopies(project));
670
672
  return readdirSync(project.pluginsDir)
671
673
  .filter(f => f.endsWith('.ts') && !f.endsWith('.d.ts') && !(core && NOT_PLUGINS.has(f)))
672
- .map(f => join(project.pluginsDir, f));
674
+ .map(f => join(project.pluginsDir, f))
675
+ .filter(f => !stale.has(f));
676
+ }
677
+
678
+ /** A server's addons/amxts: the module's files, written over on every start. */
679
+ export const SERVER_FOLDER = /[\\/]addons[\\/]amxts$/i;
680
+
681
+ /** The server's addons/amxts that AMXTS_SERVER names, or '' when it names none. */
682
+ export function serverDir(): string {
683
+ try {
684
+ const dir = serverFolder(process.env.AMXTS_SERVER ?? '');
685
+ return dir && resolve(dir);
686
+ } catch {
687
+ return '';
688
+ }
689
+ }
690
+
691
+ /**
692
+ * Copies of the core's own API at the top of the project's plugins folder,
693
+ * left there by an older amxts: a file named as one of the core's as/ (or
694
+ * the editor's imports.d.ts) whose first line is the core's - a generator's
695
+ * header or that file's own first line. An author's file of the same name
696
+ * starts otherwise and stays a plugin. A server's addons/amxts/plugins holds
697
+ * these files on purpose: the module writes them there for the plugins
698
+ * compiled on the server.
699
+ */
700
+ export function staleCopies(project: Project): string[] {
701
+ const dir = resolve(project.pluginsDir);
702
+ const parent = dirname(dir);
703
+ if (!existsSync(dir) || dir === resolve(CORE_PLUGINS) || SERVER_FOLDER.test(parent) || parent === serverDir()) return [];
704
+ coreFirstLines ??= new Map([...readdirSync(CORE_PLUGINS).filter(f => f.endsWith('.ts')).map(f => [f, firstLine(join(CORE_PLUGINS, f))] as const), ['imports.d.ts', '// GENERATED by ']]);
705
+ const core = coreFirstLines;
706
+ return readdirSync(dir)
707
+ .filter((f) => {
708
+ const header = core.get(f);
709
+ if (header === undefined) return false;
710
+ const line = firstLine(join(dir, f));
711
+ return line.startsWith('// GENERATED by ') || line === header;
712
+ })
713
+ .sort()
714
+ .map(f => join(dir, f));
715
+ }
716
+
717
+ /** The first line of each file at the top of the core's as/, read once. */
718
+ let coreFirstLines: Map<string, string> | undefined;
719
+
720
+ function firstLine(path: string): string {
721
+ return readFileSync(path, 'utf8').split('\n', 1)[0].trimEnd();
673
722
  }
674
723
 
675
724
  /** The modules that run on the server - each in its owner plugin: every one but a library. */
@@ -747,7 +796,12 @@ export class Sources {
747
796
  /** A plugin that imports a module package the config does not list. */
748
797
  readonly problems: string[] = [];
749
798
 
750
- constructor(readonly root: string, readonly project: Project) {}
799
+ /** Copies of the core's API an older amxts left in the plugins folder: the core's files are under them. */
800
+ private readonly stale: Set<string>;
801
+
802
+ constructor(readonly root: string, readonly project: Project) {
803
+ this.stale = new Set(staleCopies(project));
804
+ }
751
805
 
752
806
  /**
753
807
  * Whether a file is a plugin's code, which auto-imports reach: the
@@ -792,14 +846,20 @@ export class Sources {
792
846
  return this.project.modules.find(pkg => path === pkg.module || path === pkg.natives || ofPackage(pkg, path)) ?? null;
793
847
  }
794
848
 
849
+ /** The project's own file at this place under its plugins folder, or null: an old copy of the core's API is not one. */
850
+ ownFile(place: string): string | null {
851
+ const own = join(this.project.pluginsDir, place);
852
+ return existsSync(own) && !this.stale.has(own) ? own : null;
853
+ }
854
+
795
855
  /** The file on disk at this place in the tree, or null. */
796
856
  real(path: string): string | null {
797
857
  const rel = this.rel(path);
798
858
  if (!inside(this.root, resolve(path))) return existsSync(path) ? resolve(path) : null;
799
859
 
800
860
  if (this.project.pluginsDir !== resolve(this.root)) {
801
- const own = join(this.project.pluginsDir, rel);
802
- if (existsSync(own)) return own;
861
+ const own = this.ownFile(rel);
862
+ if (own) return own;
803
863
  }
804
864
  const core = resolve(this.root, rel);
805
865
  if (existsSync(core)) return core;
@@ -29,13 +29,13 @@ import type { Kind } from './upgrade-names';
29
29
  // functions take one object, the menu's context, and an item is one object
30
30
  // with its title (scripts/upgrade-menus.ts). What is rewritten no longer
31
31
  // matches, so a second run changes nothing.
32
- import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
32
+ import { existsSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
33
33
  import { basename, dirname, join, relative, resolve } from 'node:path';
34
34
  import { fileURLToPath } from 'node:url';
35
35
  import ts from 'typescript';
36
36
  import { MESSAGE_NAMES } from './client-messages';
37
- import { CORE_ENTRIES, CORE_PLUGINS, loadProject } from './project';
38
- import { apiFiles, serverFolder } from './system';
37
+ import { CORE_ENTRIES, CORE_PLUGINS, loadProject, SERVER_FOLDER, serverDir, staleCopies } from './project';
38
+ import { apiFiles } from './system';
39
39
  import { c, log } from './ui';
40
40
  import { upgradeMenus } from './upgrade-menus';
41
41
  import { COMMON, EVENTS, FLAG_NAMES, GAME_EVENT_FIELDS, HIDDEN, HIDDEN_EVENTS, JOIN_OPTIONS, RENAMED, SERVER_EVENT_CLASSES, SERVER_EVENT_FIELDS, SERVER_EVENTS, SERVER_EVENTS_BY_HAND, SERVER_FIELD_CLASSES, SERVER_GAME_EVENTS } from './upgrade-names';
@@ -775,9 +775,6 @@ export function upgradeFlags(file: string, text: string): { text: string; change
775
775
  /** Folders that are not the project's code: what is installed, built or generated. */
776
776
  const SKIP = new Set(['node_modules', 'dist', '.amxts', '.git']);
777
777
 
778
- /** A server's addons/amxts: the module's files, written over on every start. */
779
- const SERVER_FOLDER = /[\\/]addons[\\/]amxts$/i;
780
-
781
778
  /** The project's TypeScript files: its plugins, its tests and fixtures, a module's sources. */
782
779
  function codeFiles(dir: string, skip: Set<string>): string[] {
783
780
  return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
@@ -787,16 +784,6 @@ function codeFiles(dir: string, skip: Set<string>): string[] {
787
784
  });
788
785
  }
789
786
 
790
- /** The server's addons/amxts that AMXTS_SERVER names, or '' when it names none. */
791
- function serverDir(): string {
792
- try {
793
- const dir = serverFolder(process.env.AMXTS_SERVER ?? '');
794
- return dir && resolve(dir);
795
- } catch {
796
- return '';
797
- }
798
- }
799
-
800
787
  /**
801
788
  * The project's code to upgrade. The module's own files - the API a server
802
789
  * writes beside its plugins, at the core's version it runs - are not, in the
@@ -810,14 +797,20 @@ function projectFiles(dir: string, pluginsDir: string, outDir: string): string[]
810
797
  return codeFiles(dir, new Set([outDir, serverDir()])).filter(file => !theirs.has(basename(file)) || dirname(file) === pluginsDir);
811
798
  }
812
799
 
813
- /** Rewrites the project in `dir`: every change, in the order of the files, and what is left to do by hand. `write: false` only lists them. */
814
- export function upgradeProject(dir: string, { write = true } = {}): { changes: Change[]; left: Left[] } {
800
+ /**
801
+ * Rewrites the project in `dir`: every change, in the order of the files, and
802
+ * what is left to do by hand. Then removes the copies of the core's API an
803
+ * older amxts left in the plugins folder - after the rewrite, so nothing
804
+ * still imports them by `~/`. `write: false` only lists them.
805
+ */
806
+ export function upgradeProject(dir: string, { write = true } = {}): { changes: Change[]; left: Left[]; removed: string[] } {
815
807
  const project = loadProject(dir);
816
808
  const ownFolder = resolve(project.pluginsDir) !== resolve(CORE_PLUGINS);
817
- const renames = renamesFor(project.modules, place => ownFolder && existsSync(join(project.pluginsDir, place)));
809
+ const stale = staleCopies(project);
810
+ const renames = renamesFor(project.modules, place => ownFolder && existsSync(join(project.pluginsDir, place)) && !stale.includes(join(project.pluginsDir, place)));
818
811
  const changes: Change[] = [];
819
812
  const left: Left[] = [];
820
- for (const file of projectFiles(project.dir, resolve(project.pluginsDir), project.outDir)) {
813
+ for (const file of projectFiles(project.dir, resolve(project.pluginsDir), project.outDir).filter(file => !stale.includes(file))) {
821
814
  const text = readFileSync(file, 'utf8');
822
815
  const name = relative(project.dir, file).replace(/\\/g, '/');
823
816
  const http = dropHttpImports(name, text);
@@ -835,7 +828,10 @@ export function upgradeProject(dir: string, { write = true } = {}): { changes: C
835
828
  if (write) writeFileSync(file, flags.text);
836
829
  changes.push(...http.changes, ...imports.changes, ...handlers.changes, ...menus.changes, ...names.changes, ...events.changes, ...messages.changes, ...flags.changes);
837
830
  }
838
- return { changes, left };
831
+ if (write) {
832
+ for (const file of stale) rmSync(file);
833
+ }
834
+ return { changes, left, removed: stale.map(file => basename(file)) };
839
835
  }
840
836
 
841
837
  // Run as a task (scripts/run.ts names it in argv) or by itself.
@@ -844,14 +840,16 @@ if (import.meta.main || resolve(process.argv[1] ?? '') === fileURLToPath(import.
844
840
  // amxts command as JSON, which prints what is left to do in its summary.
845
841
  const args = process.argv.slice(2);
846
842
  const report = args.includes('--report') ? args[args.indexOf('--report') + 1] : undefined;
847
- const { changes, left } = upgradeProject(process.cwd(), { write: !args.includes('--dry-run') });
843
+ const write = !args.includes('--dry-run');
844
+ const { changes, left, removed } = upgradeProject(process.cwd(), { write });
845
+ if (removed.length) log.success(`${write ? 'removed' : 'would remove'} amxts's old API copies from plugins/: ${removed.join(', ')}`);
848
846
  for (const change of changes) console.log(` ${c.dim(`${change.file}:${change.line}`)} ${change.from} ${c.dim('→')} ${change.to}`);
849
847
  if (report) {
850
848
  writeFileSync(report, JSON.stringify({ changes, left }));
851
849
  } else {
852
850
  const files = new Set(changes.map(change => change.file)).size;
853
851
  if (changes.length) log.success(`upgraded ${changes.length} place(s) in ${files} file(s)`);
854
- else if (!left.length) log.success('nothing to upgrade: the code already uses this core\'s API');
852
+ else if (!left.length && !removed.length) log.success('nothing to upgrade: the code already uses this core\'s API');
855
853
  for (const each of left) log.warn(`${each.file}:${each.line} ${each.why}`);
856
854
  }
857
855
  }