Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Full TypeScript rewrite. Best type defs #651

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f721622
phase 1: simply rename to modules -> *.ts
zardoy Nov 2, 2023
90caa81
phase 2: add typings with script & a few manual fixes, fix a few bugs
zardoy Nov 3, 2023
bb15d04
add options types, fix some missing types
zardoy Nov 3, 2023
ff4da02
fix: setblock now correctly sets initial block state when no override
zardoy Nov 4, 2023
156c82a
make commands typed, fix many bugs! improve /tp
zardoy Nov 4, 2023
ef2393f
fix tsc & use prepare for now
zardoy Nov 4, 2023
76c5626
migrate docs to jsdoc with codemod, not documented are marked as inte…
zardoy Nov 5, 2023
8a38cdb
fix: don't mutate generation options, fix storage provider is null fo…
zardoy Nov 5, 2023
8eb54af
fix tab_complete crash in post-flatenning
zardoy Nov 11, 2023
7705c79
add slash to aliases so its easier to discover them with search
zardoy Nov 9, 2023
0fc0670
allow json resolve
zardoy Jan 8, 2024
9f1fcdf
fix build
zardoy Jan 8, 2024
a6efc32
add events, fix ts build
zardoy Jan 17, 2024
0a2a798
improve many types, fix build!
zardoy Jan 17, 2024
e8df1db
try ts-standard instead
zardoy Jan 17, 2024
426969d
rename modules back to plugins
zardoy Jan 17, 2024
4fd7926
fix jsdoc newlines
zardoy Jan 17, 2024
9b5fe20
fix docs generator
zardoy Jan 17, 2024
9b2c5f6
tests should be running as before
zardoy Jan 17, 2024
184a9e6
rm old types
zardoy Jan 17, 2024
cfc3847
revert a few things...
zardoy Jan 18, 2024
4414705
partially Revert "try ts-standard instead"
zardoy Jan 18, 2024
b514e46
rm mcdata refs
zardoy Jan 18, 2024
51cceb4
revert even more things...
zardoy Jan 18, 2024
1fdc39f
finally test that it works
zardoy Jan 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,179 changes: 0 additions & 1,179 deletions docs/API.md

This file was deleted.

15 changes: 10 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,22 @@
"name": "flying-squid",
"description": "A minecraft server written in node.js",
"version": "1.8.0",
"main": "index.js",
"main": "dist/index.js",
"bin": "dist/app.js",
"author": "mhsjlw <[email protected]>",
"contributors": [
{
"name": "rom1504",
"email": "[email protected]"
}
],
"bin": "app.js",
"scripts": {
"start": "tsc && node dist/app.js",
"build": "tsc && node scripts/genTypes.mjs",
"prepare": "npm run build",
"prepublishOnly": "cp docs/README.md README.md",
"lint": "standard test/*.test.js src/**/*.js src/**/**/*.js src/*.js examples/*.js *.js",
"fix": "standard --fix test/*.test.js src/**/*.js src/**/**/*.js src/*.js examples/*.js *.js",
"lint": "ts-standard test/*.test.js src/ examples/ *.js",
"fix": "ts-standard --fix test/*.test.js src/ examples/ *.js",
"mocha_test": "mocha --reporter spec --timeout 3000 --exit",
"test": "npm run mocha_test",
"pretest": "npm run lint"
Expand Down Expand Up @@ -53,6 +56,7 @@
"range": "^0.0.3",
"readline": "^1.3.0",
"spiralloop": "^1.0.2",
"typed-emitter": "1.4.0",
"uuid-1345": "^1.0.1",
"vec3": "^0.1.6",
"yargs": "^17.0.1"
Expand All @@ -71,6 +75,7 @@
"minecraft-wrap": "^1.2.3",
"mineflayer": "^4.17.0",
"mocha": "^10.0.0",
"standard": "^17.0.0"
"standard": "^17.0.0",
"ts-standard": "^12.0.2"
}
}
7 changes: 7 additions & 0 deletions scripts/genTypes.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import fs from 'fs'

const targetFile = './dist/types.d.ts';
let types = fs.readFileSync(targetFile, 'utf8')
const plugins = fs.readdirSync('./dist/lib/plugins').filter(f => f !== 'index')
types = plugins.filter(module => module.endsWith('.d.ts')).map(module => `import "./lib/plugins/${module}"`).join('\n') + '\n' + types
fs.writeFileSync(targetFile, types, 'utf8')
21 changes: 15 additions & 6 deletions src/globals.d.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
// make process.platform also accept browser
declare namespace NodeJS {
interface Process {
platform: string;
browser?: boolean
}
interface Process {
// @ts-expect-error
platform: string
browser?: boolean
}

}
interface NodeRequire {
// webpack bundling
context: (path: string, deep: boolean, filter: RegExp) => { keys: () => string[]; (id: string): any };
// webpack bundling
context: (path: string, deep: boolean, filter: RegExp) => { keys: () => string[], (id: string): any }
}

interface ObjectConstructor {
keys: <T extends object>(obj: T) => Array<StringKeys<T>>
entries: <T extends object>(obj: T) => Array<[StringKeys<T>, T[keyof T]]>
// todo review https://stackoverflow.com/questions/57390305/trying-to-get-fromentries-type-right
fromEntries: <T extends Array<[string, any]>>(obj: T) => Record<T[number][0], T[number][1]>
assign: <T extends Record<string, any>, K extends Record<string, any>>(target: T, source: K) => asserts target is T & K
}
73 changes: 0 additions & 73 deletions src/index.js

This file was deleted.

98 changes: 98 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { createServer, Server as ProtocolServer } from 'minecraft-protocol'

import { testedVersions, latestSupportedVersion, oldestSupportedVersion } from './lib/version'
import Command from './lib/command'
import * as plugins from './lib/plugins'
import { EventEmitter } from 'events'

import { IndexedData } from 'minecraft-data'
import './types' // include Server declarations from all plugins
import './modules'

if (typeof process !== 'undefined' && !process.browser && process.platform !== 'browser' && parseInt(process.versions.node.split('.')[0]) < 18) {
console.error('[\x1b[31mCRITICAL\x1b[0m] Node.JS 18 or newer is required')
console.error('[\x1b[31mCRITICAL\x1b[0m] You can download the new version from https://nodejs.org/')
console.error(`[\x1b[31mCRITICAL\x1b[0m] Your current Node.JS version is: ${process.versions.node}`)
process.exit(1)
}

require('emit-then').register()
if (process.env.NODE_ENV === 'dev') {
require('longjohn')
}

type InputOptions = Partial<Options> & Pick<Options, 'version'>

export function createMCServer (options: InputOptions) {
const mcServer = new MCServer()
mcServer.connect({
// defaults
'max-entities': 100,
...options
})
return mcServer as unknown as Server
}

class MCServer extends EventEmitter {
pluginsReady = false
_server: ProtocolServer = null!
constructor () {
plugins.initPlugins()
super()
}

connect (options) {
const server = this as unknown as Server
const registry = require('prismarine-registry')(options.version)
if (!registry?.version) throw new Error(`Server version '${registry?.version}' is not supported, no data for version`)

const versionData = registry.version
if (versionData['>'](latestSupportedVersion)) {
throw new Error(`Server version '${registry?.version}' is not supported. Latest supported version is '${latestSupportedVersion}'.`)
} else if (versionData['<'](oldestSupportedVersion)) {
throw new Error(`Server version '${registry?.version}' is not supported. Oldest supported version is '${oldestSupportedVersion}'.`)
}

server.commands = new Command({})
server._server = createServer(options)

const promises: Array<Promise<any>> = []
for (const plugin of plugins.builtinPlugins) {
promises.push(plugin.server?.(server, options))
}
Promise.all(promises).then(() => {
server.emit('pluginsReady')
server.pluginsReady = true
})

if (options.logging === true) server.createLog()
server._server.on('error', error => {
server.emit('error', error)
})
server._server.on('listening', () => {
// @ts-expect-error dont remember the right cast
server.emit('listening', server._server.socketServer.address().port)
})
server.emit('asap')
}
}

declare global {
interface Server {
commands: Command
pluginsReady: boolean
_server: ProtocolServer
supportFeature: (feature: string) => boolean
}
}

export {
testedVersions
}

export * as Behavior from './lib/behavior'
export * as Command from './lib/command'
export { default as generations } from './lib/generations'
export * as experience from './lib/experience'
export * as UserError from './lib/user_error'
export { default as portal_detector } from './lib/portal_detector'
10 changes: 4 additions & 6 deletions src/lib/behavior.js → src/lib/behavior.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module.exports = (obj) => {
return async (eventName, data, func, cancelFunc) => {
export default (obj) => {
return async (eventName: string, data?: any, func: Function = (() => { }), cancelFunc?: Function) => {
let hiddenCancelled = false
let cancelled = false
let cancelCount = 0
Expand All @@ -13,9 +13,7 @@ module.exports = (obj) => {
defaultCancel = dC
}

let resp

func = func || (() => {})
let resp!: boolean | Promise<any>

await obj.emitThen(eventName + '_cancel', data, cancel).catch((err) => setTimeout(() => { throw err }, 0))
await obj.emitThen(eventName, data, cancelled, cancelCount).catch((err) => setTimeout(() => { throw err }, 0))
Expand All @@ -24,7 +22,7 @@ module.exports = (obj) => {
resp = func(data)
if (resp instanceof Promise) resp = await resp.catch((err) => setTimeout(() => { throw err }, 0))
if (typeof resp === 'undefined') resp = true
} else if (cancelFunc && defaultCancel) {
} else if ((cancelFunc != null) && defaultCancel) {
resp = cancelFunc(data)
if (resp instanceof Promise) resp = await resp.catch((err) => setTimeout(() => { throw err }, 0))
if (typeof resp === 'undefined') resp = false
Expand Down
53 changes: 39 additions & 14 deletions src/lib/command.js → src/lib/command.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,33 @@
const UserError = require('./user_error')
import UserError from './user_error'

type Ctx<P extends boolean> = P extends true ? {
player: Player
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i tried to not add a lot of generics, but this one here is just super useful for other plugins

} : {
player?: Player
}

type NonFalsey<T> = T extends false ? never : NonNullable<T>

interface AddParams<T, P extends boolean = false> {
base: string
aliases?: string[]
info: string
usage: string
onlyPlayer?: P
op?: boolean
parse?: (string: string, ctx: Ctx<P>) => T
action: (data: NonFalsey<T>, ctx: Ctx<P>) => any
tab?: string[]
}

class Command {
constructor (params, parent, hash) {
this.params = params
this.parent = parent
hash: any
uniqueHash: any
parentBase: any
base: any
path: string

constructor (public params, public parent?, hash?) {
this.hash = parent ? parent.hash : {}
this.uniqueHash = parent ? parent.uniqueHash : {}
this.parentBase = (this.parent && this.parent.base && this.parent.base + ' ') || ''
Expand All @@ -20,21 +44,21 @@ class Command {
return undefined
}

async use (command, ctx = {}, op = true) {
async use (command, ctx: Ctx<false> = {}, op = true) {
const resultsFound = this.find(command)
let parsedResponse
if (resultsFound) {
if (resultsFound != null) {
const wantedCommand = resultsFound[0]
const passedArgs = resultsFound[1]
if (wantedCommand.params.onlyConsole && ctx.player) return 'This command is console only'
if (wantedCommand.params.onlyPlayer && !ctx.player) throw new UserError('This command is player only')
if (wantedCommand.params.onlyConsole && (ctx.player != null)) return 'This command is console only'
if (wantedCommand.params.onlyPlayer && (ctx.player == null)) throw new UserError('This command is player only')
if (wantedCommand.params.op && !op) return 'You do not have permission to use this command'
const customArgsParser = wantedCommand.params.parse
if (customArgsParser) {
if (typeof customArgsParser === 'function') {
parsedResponse = customArgsParser(passedArgs, ctx)
if (parsedResponse === false) {
if (ctx.player) return wantedCommand.params.usage ? 'Usage: ' + wantedCommand.params.usage : 'Bad syntax'
if (ctx.player != null) return wantedCommand.params.usage ? 'Usage: ' + wantedCommand.params.usage : 'Bad syntax'
else throw new UserError(wantedCommand.params.usage ? 'Usage: ' + wantedCommand.params.usage : 'Bad syntax')
}
} else {
Expand All @@ -46,7 +70,7 @@ class Command {
else output = await wantedCommand.params.action(resultsFound[1], ctx) // just give back the passed arg
if (output) return '' + output
} else {
if (ctx.player) return 'Command not found'
if (ctx.player != null) return 'Command not found'
else throw new UserError('Command not found')
}
}
Expand All @@ -56,7 +80,7 @@ class Command {

const list = [this.base]
if (this.params.aliases && this.params.aliases.length) {
this.params.aliases.forEach(al => list.unshift(this.parentBase + al))
this.params.aliases.forEach(al => list.unshift(this.parentBase + al.replace(/^\//, '')))
}

list.forEach((command) => {
Expand All @@ -69,11 +93,11 @@ class Command {
this.uniqueHash[this.base] = this
}

add (params) {
add <T, P extends boolean>(params: AddParams<T, P>) {
return new Command(params, this)
}

space (end) {
space (end = false) {
const first = !(this.parent && this.parent.parent)
return this.params.merged || (!end && first) ? '' : ' '
}
Expand All @@ -83,9 +107,10 @@ class Command {
}

tab (command, i) {
// @ts-expect-error
if (this.find(command)[0].params.tab) return this.find(command)[0].params.tab[i]
return 'player'
}
}

module.exports = Command
export default Command
Loading
Loading