m3 skeleton

only list for now
This commit is contained in:
Dinhero21 2024-09-01 15:41:50 -03:00
parent c0d1876548
commit 350b3237ae
20 changed files with 390 additions and 55 deletions

125
package-lock.json generated
View file

@ -9,7 +9,16 @@
"version": "1.0.0", "version": "1.0.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"minecraft-protocol": "^1.47.0" "@types/text-table": "^0.2.5",
"chalk": "^5.3.0",
"commander": "^12.1.0",
"minecraft-protocol": "^1.47.0",
"strip-ansi": "^7.1.0",
"text-table": "^0.2.0",
"zod": "^3.23.8"
},
"bin": {
"m3": "dist/m3/cli.js"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.9.1", "@eslint/js": "^9.9.1",
@ -306,6 +315,16 @@
"balanced-match": "^1.0.0" "balanced-match": "^1.0.0"
} }
}, },
"node_modules/@swc/cli/node_modules/commander": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"node_modules/@swc/cli/node_modules/minimatch": { "node_modules/@swc/cli/node_modules/minimatch": {
"version": "9.0.5", "version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
@ -643,6 +662,12 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/text-table": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/@types/text-table/-/text-table-0.2.5.tgz",
"integrity": "sha512-hcZhlNvMkQG/k1vcZ6yHOl6WAYftQ2MLfTHcYRZ2xYZFD8tGVnE3qFV0lj1smQeDSR7/yY0PyuUalauf33bJeA==",
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.3.0", "version": "8.3.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.3.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.3.0.tgz",
@ -946,13 +971,15 @@
} }
}, },
"node_modules/ansi-regex": { "node_modules/ansi-regex": {
"version": "5.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
} }
}, },
"node_modules/ansi-styles": { "node_modules/ansi-styles": {
@ -1312,17 +1339,12 @@
} }
}, },
"node_modules/chalk": { "node_modules/chalk": {
"version": "4.1.2", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": { "engines": {
"node": ">=10" "node": "^12.17.0 || ^14.13 || >=16.0.0"
}, },
"funding": { "funding": {
"url": "https://github.com/chalk/chalk?sponsor=1" "url": "https://github.com/chalk/chalk?sponsor=1"
@ -1400,13 +1422,12 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/commander": { "node_modules/commander": {
"version": "8.3.0", "version": "12.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 12" "node": ">=18"
} }
}, },
"node_modules/concat-map": { "node_modules/concat-map": {
@ -1664,6 +1685,46 @@
"url": "https://opencollective.com/eslint" "url": "https://opencollective.com/eslint"
} }
}, },
"node_modules/eslint/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/eslint/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/eslint/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/espree": { "node_modules/espree": {
"version": "10.1.0", "version": "10.1.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz",
@ -3681,16 +3742,18 @@
} }
}, },
"node_modules/strip-ansi": { "node_modules/strip-ansi": {
"version": "6.0.1", "version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ansi-regex": "^5.0.1" "ansi-regex": "^6.0.1"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
} }
}, },
"node_modules/strip-eof": { "node_modules/strip-eof": {
@ -3774,7 +3837,6 @@
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/to-regex-range": { "node_modules/to-regex-range": {
@ -4045,6 +4107,15 @@
"funding": { "funding": {
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
},
"node_modules/zod": {
"version": "3.23.8",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
} }
} }
} }

View file

@ -23,7 +23,13 @@
} }
}, },
"dependencies": { "dependencies": {
"minecraft-protocol": "^1.47.0" "@types/text-table": "^0.2.5",
"chalk": "^5.3.0",
"commander": "^12.1.0",
"minecraft-protocol": "^1.47.0",
"strip-ansi": "^7.1.0",
"text-table": "^0.2.0",
"zod": "^3.23.8"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.9.1", "@eslint/js": "^9.9.1",
@ -40,5 +46,8 @@
"scripty": "^2.1.1", "scripty": "^2.1.1",
"typescript": "^5.5.4", "typescript": "^5.5.4",
"typescript-eslint": "^8.3.0" "typescript-eslint": "^8.3.0"
},
"bin": {
"m3": "./dist/m3/bin.js"
} }
} }

View file

@ -1,7 +1,6 @@
import chalk from 'chalk'; import chalk from 'chalk';
import { createClient, type ServerClient } from 'minecraft-protocol'; import { createClient, type ServerClient } from 'minecraft-protocol';
import { dirname, resolve } from 'path'; import { resolve } from 'path';
import { fileURLToPath } from 'url';
import { Worker } from 'worker_threads'; import { Worker } from 'worker_threads';
import { TARGET_OPTIONS } from '../settings.js'; import { TARGET_OPTIONS } from '../settings.js';
@ -9,13 +8,8 @@ import { Channel } from '../util/channel.js';
import { importModulesGenerator } from '../util/import-modules.js'; import { importModulesGenerator } from '../util/import-modules.js';
import { type Message } from '../worker/parent.js'; import { type Message } from '../worker/parent.js';
if (!('require' in globalThis)) { const WORKER_PATH = resolve(import.meta.dirname, '../worker');
globalThis.__filename = fileURLToPath(import.meta.url); const MODULES_DIR_PATH = resolve(import.meta.dirname, '../modules');
globalThis.__dirname = dirname(__filename);
}
const WORKER_PATH = resolve(__dirname, '../worker');
const MODULE_DIR_PATH = resolve(__dirname, '../module');
export class Instance { export class Instance {
public readonly client; public readonly client;
@ -68,7 +62,7 @@ export class Instance {
let moduleStart = NaN; let moduleStart = NaN;
for await (const module of importModulesGenerator( for await (const module of importModulesGenerator(
MODULE_DIR_PATH, MODULES_DIR_PATH,
'global.js', 'global.js',
{ {
pre(entry) { pre(entry) {

35
src/m3/README.md Normal file
View file

@ -0,0 +1,35 @@
<!--
I probably won't actually make this into its own repo
don't think that because there is a README.md file,
it's its own git repo.
-->
# MMP Module Manager
- _aka_ MMM _aka_ M3 (official-est name)
A CLI Tool to automagically manage your modules.
(no more manually `git clone`-ing dependencies!)
<!-- Upon further inspection, this idea is shit
# Specification vs Implementation
Some call it a bug, but I call it (either) a revoluionary (or stupid) idea.
Instead of just writing a _package_, what you might do in other scenarios. You write a specification.
Nothing formal, it can be just a markdown file or, if you want to get more fancy, a [d.ts type definition](https://www.typescriptlang.org/docs/handbook/declaration-files/templates/module-d-ts.html).
But then, modules, packages, libraries, whatever you want to call them (modules, in this case) are identified (imported by other modules/packages/libraries) by their specification, not their implementation.
`ddg.chat`, the semi-official (made by Dinhero Development Group, the same people who made MMP, the module loader) chat library, is then the specification that defines how a chat library should behave.
`ddg.chat`, instead of declaring how chat parsing should be implemented (by analyzing the downstream `chat_message` and `chat_command` packets), declares how a chat library should interface and expose itself to the rest of the ecosystem (there should be a default exported object that has `writeDownstream` and `writeUpstream` functions ...)
This allows you to mix and match implementations, as long as they share the same specification.
I don't really see any real-world use for this aside from person A creating a module, deprecating/archiving/abandoning it and person B creating an equivalent better module.
Things like this happen all the time in other ecosystems and are usually referred to as "spiritual successors". So my idea might not be so revolutionary after all.
-->

10
src/m3/bin.ts Executable file
View file

@ -0,0 +1,10 @@
#!/usr/bin/env node
import { resolve } from 'path';
import { importDirectory } from '../util/import.js';
import program from './program.js';
await importDirectory(resolve(import.meta.dirname, 'command'));
program.parse(process.argv);

32
src/m3/command/list.ts Normal file
View file

@ -0,0 +1,32 @@
import chalk from 'chalk';
import { readdir } from 'fs/promises';
import { resolve } from 'path';
import { createTable } from '../../util/table.js';
import { Module } from '../module.js';
import program, { modulesDir } from '../program.js';
program
.command('list')
.description('List installed modules')
.action(async () => {
const tableData: Record<string, string>[] = [];
for (const entry of await readdir(modulesDir, { withFileTypes: true })) {
if (!entry.isDirectory()) continue;
try {
const modulePath = resolve(modulesDir, entry.name);
const module = await Module.fromDir(modulePath);
tableData.push({
[chalk.bold('Name')]: module.global.name,
[chalk.bold('Dependency?')]: module.local.manual ? 'no' : 'yes',
});
} catch (error) {
console.error(error);
}
}
console.info(createTable(tableData));
});

44
src/m3/module.ts Normal file
View file

@ -0,0 +1,44 @@
import { readFile } from 'fs/promises';
import { resolve } from 'path';
import type { GlobalModuleData, LocalModuleData } from './types.js';
import { GlobalModuleDataSchema, LocalModuleDataSchema } from './types.js';
async function getLocalData(path: string): Promise<LocalModuleData> {
try {
const raw = await readFile(path, 'utf8');
const data = JSON.parse(raw) as unknown;
return LocalModuleDataSchema.parse(data);
} catch (error) {
throw new Error(`Could not load local module data: ${error}`);
}
}
async function getGlobalData(path: string): Promise<GlobalModuleData> {
try {
const raw = await readFile(path, 'utf8');
const data = JSON.parse(raw) as unknown;
return GlobalModuleDataSchema.parse(data);
} catch (error) {
throw new Error(`Could not load global module data: ${error}`);
}
}
export class Module {
public static async fromDir(path: string): Promise<Module> {
const localPath = resolve(path, 'm3.local.json');
const globalPath = resolve(path, 'm3.global.json');
return new Module(
await getLocalData(localPath),
await getGlobalData(globalPath),
);
}
constructor(
public readonly local: LocalModuleData,
public readonly global: GlobalModuleData,
) {}
}

47
src/m3/program.ts Normal file
View file

@ -0,0 +1,47 @@
import { Command } from 'commander';
import { basename, resolve } from 'path';
const program = new Command();
export default program;
program.name('m3').description('MMP Module Manager');
// #region This doesn't look like it belongs here...
// Should always be set
export let modulesDir: string;
// If cwd is a module,
// cwd will be {modulesDir}/{moduleDir}
export let moduleDir: string | undefined;
const cwd = process.cwd();
const cwdn = basename(cwd);
const parent = resolve('..');
// are we in a module?
// (parent is modules dir)
if (basename(parent) === 'modules') {
modulesDir = parent;
moduleDir = cwdn;
} else {
switch (cwdn) {
case 'modular-minecraft-proxy':
modulesDir = resolve('src/modules');
break;
case 'src':
modulesDir = resolve('modules');
break;
case 'modules':
modulesDir = resolve('.');
break;
}
}
// @ts-expect-error We are testing if modulesDir *hasn't* been set here
if (modulesDir === undefined) {
throw new Error('Could not locate modules directory');
}
// #endregion

25
src/m3/types.ts Normal file
View file

@ -0,0 +1,25 @@
// Module directory structure
// <implementation>
// ├─ [local.ts] TypeScript
// ├─ [global.ts] TypeScript
// ├─ m3.local.json JSON(LocalModuleData)
// ├─ m3.global.json JSON(GlobalModuleData)
// └─ ...
import { z } from 'zod';
export const LocalModuleDataSchema = z.object({
// manual -> required by the user
// auto -> a dependency to another module
manual: z.boolean(),
});
export type LocalModuleData = z.infer<typeof LocalModuleDataSchema>;
export const GlobalModuleDataSchema = z.object({
dependencies: z.array(z.string().url()),
// * This is different from package.json's name as it is required to be the same as the directory's
name: z.string(),
});
export type GlobalModuleData = z.infer<typeof GlobalModuleDataSchema>;

View file

@ -1,5 +0,0 @@
*
!.gitignore
# internal modules
!proxy

7
src/modules/.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
*
!.gitignore
# for whatever reason, you need to unignore
# the directories AND files within them
!internal.*
!internal.*/**

View file

@ -8,7 +8,7 @@ export default async function (instance: Instance): Promise<void> {
const downstreamQueue: RawPacket[] = []; const downstreamQueue: RawPacket[] = [];
const upstreamQueue: RawPacket[] = []; const upstreamQueue: RawPacket[] = [];
const channel = instance.createChannel<Message>('proxy'); const channel = instance.createChannel<Message>('internal.proxy');
const client = instance.client; const client = instance.client;
const server = instance.server; const server = instance.server;

View file

@ -7,8 +7,7 @@ import { type Direction as Direction, type Message } from './shared.js';
export type PacketEventMap = Record<string, (packet: Packet) => AsyncVoid>; export type PacketEventMap = Record<string, (packet: Packet) => AsyncVoid>;
// ? Should I export the channel const channel = createChannel<Message>('internal.proxy');
export const channel = createChannel<Message>('proxy');
function write(direction: Direction, packet: RawPacket): void { function write(direction: Direction, packet: RawPacket): void {
channel.write({ channel.write({

View file

@ -0,0 +1,4 @@
{
"name": "internal.proxy",
"dependencies": []
}

View file

@ -0,0 +1,3 @@
{
"manual": true
}

View file

@ -24,6 +24,8 @@ export async function* importModulesGenerator(
index: string, index: string,
callbacks?: Callbacks, callbacks?: Callbacks,
): AsyncGenerator<unknown> { ): AsyncGenerator<unknown> {
console.log('importModulesGenerator', { directory, index });
for (const entry of await readdir(directory, { withFileTypes: true })) { for (const entry of await readdir(directory, { withFileTypes: true })) {
const path = resolve(entry.path, entry.name, index); const path = resolve(entry.path, entry.name, index);

10
src/util/import.ts Normal file
View file

@ -0,0 +1,10 @@
import { readdir } from 'fs/promises';
import { resolve } from 'path';
export async function importDirectory(path: string): Promise<void> {
for (const entry of await readdir(path, { withFileTypes: true })) {
if (!entry.isFile()) continue;
await import(resolve(path, entry.name));
}
}

54
src/util/table.ts Normal file
View file

@ -0,0 +1,54 @@
import stripAnsi from 'strip-ansi';
import table from 'text-table';
class TableGenerator {
// #region Taken directly from table.Options
/** Separator to use between columns, (default: ' '). */
hsep?: string | undefined;
/** An array of alignment types for each column, default ['l','l',...]. */
align?: Array<'l' | 'r' | 'c' | '.' | null | undefined> | undefined;
/** A callback function to use when calculating the string length. */
stringLength?(str: string): number;
// #endregion
public generate(data: Record<string, string>[] | string[][]): string {
const rows: string[][] = [];
if (typeof data[0] === 'object') {
const keys = new Set<string>();
for (const row of data) {
for (const key of Object.keys(row)) {
keys.add(key);
}
}
const header = Array.from(keys);
rows.push(header);
for (const inRow of data as Record<string, string>[]) {
const outRow = header.map((key) => inRow[key] ?? '');
rows.push(outRow);
}
}
return table(rows, {
hsep: this.hsep,
align: this.align,
stringLength: this.stringLength,
});
}
}
const generator = new TableGenerator();
generator.stringLength = (string) => stripAnsi(string).length;
export function createTable(data: Record<string, string>[] | string[][]) {
return generator.generate(data);
}

View file

@ -1,15 +1,9 @@
import chalk from 'chalk'; import chalk from 'chalk';
import { dirname, resolve } from 'path'; import { resolve } from 'path';
import { fileURLToPath } from 'url';
import { importModules } from '../util/import-modules.js'; import { importModules } from '../util/import-modules.js';
if (!('require' in globalThis)) { const MODULES_DIR_PATH = resolve(import.meta.dirname, '../modules');
globalThis.__filename = fileURLToPath(import.meta.url);
globalThis.__dirname = dirname(__filename);
}
const MODULE_DIR_PATH = resolve(__dirname, '../module');
console.group('Loading modules... (local)'); console.group('Loading modules... (local)');
@ -17,7 +11,7 @@ const start = performance.now();
let moduleStart = NaN; let moduleStart = NaN;
await importModules(MODULE_DIR_PATH, 'local.js', { await importModules(MODULES_DIR_PATH, 'local.js', {
pre(entry) { pre(entry) {
const module = entry.name; const module = entry.name;
console.group(`Loading ${module}...`); console.group(`Loading ${module}...`);