chore: add eslint

This commit is contained in:
Dunsin 2022-09-30 06:03:03 +00:00
parent 207e4d521e
commit ed72f3edca
11 changed files with 4419 additions and 278 deletions

21
.eslintrc.json Normal file
View file

@ -0,0 +1,21 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"plugin:react/recommended",
"standard"
],
"overrides": [
],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": [
"react"
],
"rules": {
}
}

4132
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -7,7 +7,8 @@
"main": "./npx/bitburner-filesync.js", "main": "./npx/bitburner-filesync.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"start": "node src/index.js" "start": "node src/index.js",
"lint": "eslint ./src --fix"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -29,5 +30,13 @@
"convict": "^6.2.3", "convict": "^6.2.3",
"signal-js": "^3.0.1", "signal-js": "^3.0.1",
"ws": "^8.8.1" "ws": "^8.8.1"
},
"devDependencies": {
"eslint": "^8.24.0",
"eslint-config-standard": "^17.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-n": "^15.3.0",
"eslint-plugin-promise": "^6.0.1",
"eslint-plugin-react": "^7.31.8"
} }
} }

View file

@ -1,84 +1,82 @@
import convict from "convict"; import convict from 'convict'
import { existsSync } from "fs"; import { existsSync } from 'fs'
// Define a schema // Define a schema
export let config = convict({ export const config = convict({
allowedFiletypes: { allowedFiletypes: {
doc: 'Filetypes that are synchronized to the game.', doc: 'Filetypes that are synchronized to the game.',
format: 'Array', format: 'Array',
default: [".js", ".script", ".txt"] default: ['.js', '.script', '.txt']
},
allowDeletingFiles: {
doc: 'Allow deleting files in game if they get deleted off disk.',
format: 'Boolean',
default: false,
arg: 'allowDeletingFiles'
},
port: {
doc: 'The port to bind to.',
format: 'Number',
default: 12525,
env: 'BB_PORT',
arg: 'port'
},
scriptsFolder: {
doc: 'The to be synchronized folder.',
format: 'String',
default: '.',
env: 'BB_SCRIPTFOLDER',
arg: 'folder'
},
quiet: {
doc: 'Log less internal events to stdout.',
format: 'Boolean',
env: 'BB_VERBOSE',
default: false,
arg: 'quiet'
},
dry: {
doc: 'Only print the files to be synchronised.',
format: 'Boolean',
env: 'BB_DRY',
default: false,
arg: 'dry'
},
definitionFile: {
update: {
doc: 'Automatically pull the definition file from the game.',
format: 'Boolean',
env: 'BB_UPDATE_DEF',
default: false
}, },
allowDeletingFiles: { location: {
doc: 'Allow deleting files in game if they get deleted off disk.', doc: 'Location/name of where the definition file gets placed.',
format: 'Boolean', format: 'String',
default: false, env: 'BB_LOCATION_DEF',
arg: 'allowDeletingFiles', default: './NetScriptDefinitions.d.ts'
},
port: {
doc: 'The port to bind to.',
format: 'Number',
default: 12525,
env: 'BB_PORT',
arg: 'port'
},
scriptsFolder: {
doc: 'The to be synchronized folder.',
format: 'String',
default: '.',
env: 'BB_SCRIPTFOLDER',
arg: 'folder'
},
quiet: {
doc: 'Log less internal events to stdout.',
format: 'Boolean',
env: 'BB_VERBOSE',
default: false,
arg: 'quiet'
},
dry: {
doc: 'Only print the files to be synchronised.',
format: 'Boolean',
env: 'BB_DRY',
default: false,
arg: 'dry'
},
definitionFile: {
update: {
doc: 'Automatically pull the definition file from the game.',
format: 'Boolean',
env: 'BB_UPDATE_DEF',
default: false
},
location: {
doc: 'Location/name of where the definition file gets placed.',
format: 'String',
env: 'BB_LOCATION_DEF',
default: "./NetScriptDefinitions.d.ts"
}
},
pushAllOnConnection: {
doc: 'Push all files when initial connection is made.',
format: 'Boolean',
env: 'BB_CON_PUSH',
default: false,
arg: 'pushAllOnConnection'
} }
}); },
pushAllOnConnection: {
doc: 'Push all files when initial connection is made.',
format: 'Boolean',
env: 'BB_CON_PUSH',
default: false,
arg: 'pushAllOnConnection'
}
})
export function loadConfig() { export function loadConfig () {
const configFile = "filesync.json"; const configFile = 'filesync.json'
if (existsSync(configFile)) { if (existsSync(configFile)) {
try { try {
config.loadFile(configFile); config.loadFile(configFile)
} catch (e) { } catch (e) {
throw new Error(`Unable to load configuration file at ${configFile}: ${e}`); throw new Error(`Unable to load configuration file at ${configFile}: ${e}`)
}
} else if (!config.get("quiet")) {
console.log("No configuration file found.")
} }
} else if (!config.get('quiet')) {
console.log('No configuration file found.')
}
// Perform validation // Perform validation
config.validate({ allowed: 'strict' }); config.validate({ allowed: 'strict' })
} }

View file

@ -1,7 +1,7 @@
export class EventType { export class EventType {
static ConnectionMade = "ConnectionMade"; static ConnectionMade = 'ConnectionMade'
static FileChanged = "FileChanged"; static FileChanged = 'FileChanged'
static FileDeleted = "FileDeleted"; static FileDeleted = 'FileDeleted'
static MessageReceived = "MessageReceived"; static MessageReceived = 'MessageReceived'
static MessageSend = "MessageSend"; static MessageSend = 'MessageSend'
} }

View file

@ -1,35 +1,33 @@
import CheapWatch from "cheap-watch"; import CheapWatch from 'cheap-watch'
import { config } from "./config.js"; import { config } from './config.js'
import { EventType } from "./eventTypes.js"; import { EventType } from './eventTypes.js'
import { resolve } from "path"; import { resolve } from 'path'
function fileFilter(file) { function fileFilter (file) {
if (config.get("allowedFiletypes").some(extension => file.path.endsWith(extension))) if (config.get('allowedFiletypes').some(extension => file.path.endsWith(extension))) { return true }
return true; if (file.stats.isDirectory()) { return true }
if (file.stats.isDirectory()) return false
return true;
return false;
} }
export async function setupWatch(signaller) { export async function setupWatch (signaller) {
const watch = new CheapWatch({ const watch = new CheapWatch({
dir: config.get("scriptsFolder"), dir: config.get('scriptsFolder'),
filter: fileFilter, filter: fileFilter,
watch: !config.get("dry") watch: !config.get('dry')
}); })
if (!config.get("quiet")) console.log("Watching folder", resolve(config.get("scriptsFolder"))) if (!config.get('quiet')) console.log('Watching folder', resolve(config.get('scriptsFolder')))
watch.on('+', fileEvent => { if (fileEvent.stats.isFile()) signaller.emit(EventType.FileChanged, fileEvent) }); watch.on('+', fileEvent => { if (fileEvent.stats.isFile()) signaller.emit(EventType.FileChanged, fileEvent) })
watch.on('-', fileEvent => { if (fileEvent.stats.isFile()) signaller.emit(EventType.FileDeleted, fileEvent) }); watch.on('-', fileEvent => { if (fileEvent.stats.isFile()) signaller.emit(EventType.FileDeleted, fileEvent) })
// Wait 'till filewatcher is ready to go // Wait 'till filewatcher is ready to go
await watch.init(); await watch.init()
if (config.get("dry")) { if (config.get('dry')) {
console.log("Watch would've synchronised:\n", watch.paths) console.log("Watch would've synchronised:\n", watch.paths)
process.exit(); process.exit()
} }
return watch; return watch
} }

View file

@ -1,58 +1,58 @@
"use strict" 'use strict'
import { setupWatch } from "./fileWatch.js"; import { setupWatch } from './fileWatch.js'
import { config, loadConfig } from "./config.js"; import { config, loadConfig } from './config.js'
import { setupSocket } from "./networking/webSocket.js"; import { setupSocket } from './networking/webSocket.js'
import signal from "signal-js"; import signal from 'signal-js'
import { fileChangeEventToMsg, fileRemovalEventToMsg, requestFilenames, requestDefinitionFile } from "./networking/messageGenerators.js"; import { fileChangeEventToMsg, fileRemovalEventToMsg, requestFilenames, requestDefinitionFile } from './networking/messageGenerators.js'
import { EventType } from "./eventTypes.js"; import { EventType } from './eventTypes.js'
import { messageHandler } from "./networking/messageHandler.js"; import { messageHandler } from './networking/messageHandler.js'
export async function start() { export async function start () {
loadConfig(); loadConfig()
const watch = await setupWatch(signal); const watch = await setupWatch(signal)
const socket = setupSocket(signal); const socket = setupSocket(signal)
// Add a handler for received messages. // Add a handler for received messages.
signal.on(EventType.MessageReceived, msg => messageHandler(signal, msg, watch.paths)); signal.on(EventType.MessageReceived, msg => messageHandler(signal, msg, watch.paths))
// Add a handler for when a connection to a game is made. // Add a handler for when a connection to a game is made.
signal.on(EventType.ConnectionMade, () => { signal.on(EventType.ConnectionMade, () => {
console.log("Connection made!"); console.log('Connection made!')
if (config.get("definitionFile").update) { if (config.get('definitionFile').update) {
signal.emit(EventType.MessageSend, requestDefinitionFile()); signal.emit(EventType.MessageSend, requestDefinitionFile())
} }
if (config.get("pushAllOnConnection")) { if (config.get('pushAllOnConnection')) {
const extensions = config.get("allowedFiletypes"); const extensions = config.get('allowedFiletypes')
for (const path of watch.paths.keys()) { for (const path of watch.paths.keys()) {
if (extensions.some(extension => path.endsWith(extension))) if (extensions.some(extension => path.endsWith(extension))) { signal.emit(EventType.MessageSend, fileChangeEventToMsg({ path })) }
signal.emit(EventType.MessageSend, fileChangeEventToMsg({ path: path })) }
} } else {
} else { // Upload missing files to the game.
// Upload missing files to the game. signal.emit(EventType.MessageSend, requestFilenames())
signal.emit(EventType.MessageSend, requestFilenames()); }
} })
})
// Add a handler for changed files. // Add a handler for changed files.
signal.on(EventType.FileChanged, fileEvent => { signal.on(EventType.FileChanged, fileEvent => {
if (!config.get("quiet")) console.log(fileEvent.path + " changed"); if (!config.get('quiet')) console.log(fileEvent.path + ' changed')
signal.emit(EventType.MessageSend, fileChangeEventToMsg(fileEvent)) signal.emit(EventType.MessageSend, fileChangeEventToMsg(fileEvent))
}); })
// Add a handler for removed files, if allowed. // Add a handler for removed files, if allowed.
if (config.get("allowDeletingFiles")) if (config.get('allowDeletingFiles')) {
signal.on(EventType.FileDeleted, fileEvent => signal.on(EventType.FileDeleted, fileEvent =>
signal.emit(EventType.MessageSend, fileRemovalEventToMsg(fileEvent))); signal.emit(EventType.MessageSend, fileRemovalEventToMsg(fileEvent)))
}
console.log(`Server is ready, running on ${config.get("port")}!`) console.log(`Server is ready, running on ${config.get('port')}!`)
process.on('SIGINT', function () { process.on('SIGINT', function () {
console.log("Shutting down!"); console.log('Shutting down!')
watch.close(); watch.close()
socket.close(); socket.close()
process.exit(); process.exit()
}); })
} }

View file

@ -1,57 +1,54 @@
import { readFileSync } from "fs"; import { readFileSync } from 'fs'
import { config } from "../config.js"; import { config } from '../config.js'
import { join } from "path"; import { join } from 'path'
let messageCounter = 0; let messageCounter = 0
export function fileChangeEventToMsg({ path }) { export function fileChangeEventToMsg ({ path }) {
return { return {
"jsonrpc": "2.0", jsonrpc: '2.0',
"method": "pushFile", method: 'pushFile',
"params": { params: {
"server": "home", server: 'home',
"filename": addLeadingSlash(path), filename: addLeadingSlash(path),
"content": readFileSync(join(config.get("scriptsFolder"), path)).toString() content: readFileSync(join(config.get('scriptsFolder'), path)).toString()
}, },
"id": messageCounter++ id: messageCounter++
} }
} }
export function fileRemovalEventToMsg({ path }) { export function fileRemovalEventToMsg ({ path }) {
return { return {
"jsonrpc": "2.0", jsonrpc: '2.0',
"method": "deleteFile", method: 'deleteFile',
"params": { params: {
"server": "home", server: 'home',
"filename": addLeadingSlash(path), filename: addLeadingSlash(path)
}, },
"id": messageCounter++ id: messageCounter++
} }
} }
export function requestDefinitionFile() { export function requestDefinitionFile () {
return { return {
"jsonrpc": "2.0", jsonrpc: '2.0',
"method": "getDefinitionFile", method: 'getDefinitionFile',
"id": messageCounter++ id: messageCounter++
} }
} }
export function requestFilenames() { export function requestFilenames () {
return { return {
"jsonrpc": "2.0", jsonrpc: '2.0',
"method": "getFileNames", method: 'getFileNames',
"params": { params: {
"server": "home", server: 'home'
}, },
"id": messageCounter++ id: messageCounter++
} }
} }
function addLeadingSlash(path){ function addLeadingSlash (path) {
const slashes = path.match('/'); const slashes = path.match('/')
if (slashes) if (slashes) { return `/${path}` } else { return path }
return `/${path}`
else
return path
} }

View file

@ -1,41 +1,39 @@
import { messageTracker } from "./messageTracker.js"; import { messageTracker } from './messageTracker.js'
import { writeFile } from "fs"; import { writeFile } from 'fs'
import { config } from "../config.js"; import { config } from '../config.js'
import { EventType } from "../eventTypes.js"; import { EventType } from '../eventTypes.js'
import { fileChangeEventToMsg } from "./messageGenerators.js"; import { fileChangeEventToMsg } from './messageGenerators.js'
export function messageHandler(signaller, msg, paths) { export function messageHandler (signaller, msg, paths) {
let incoming; let incoming
try { incoming = JSON.parse(msg.toString()); } try { incoming = JSON.parse(msg.toString()) } catch (err) { return console.log(err) }
catch (err) { return console.log(err); } console.log(incoming)
console.log(incoming) if (incoming.id === undefined) return
if (incoming.id == undefined) return;
if (incoming.result) { if (incoming.result) {
const request = messageTracker.get(incoming.id); const request = messageTracker.get(incoming.id)
if (request?.method && if (request?.method &&
request.method == "getDefinitionFile" request.method === 'getDefinitionFile' &&
&& incoming.result) { incoming.result) {
writeFile(config.get("definitionFile").location, incoming.result, (err) => { writeFile(config.get('definitionFile').location, incoming.result, (err) => {
if (err) return console.log(err); if (err) return console.log(err)
}); })
}
if (request?.method &&
request.method == "getFileNames"
&& incoming.result) {
const gameFiles = incoming.result.map(file => removeLeadingSlash(file));
paths.forEach((stats, fileName) => {
if (!stats.isDirectory() && !gameFiles.includes(fileName))
signaller.emit(EventType.MessageSend, fileChangeEventToMsg({ path: fileName }));
})
}
} }
if (request?.method &&
request.method === 'getFileNames' &&
incoming.result) {
const gameFiles = incoming.result.map(file => removeLeadingSlash(file))
paths.forEach((stats, fileName) => {
if (!stats.isDirectory() && !gameFiles.includes(fileName)) { signaller.emit(EventType.MessageSend, fileChangeEventToMsg({ path: fileName })) }
})
}
}
} }
function removeLeadingSlash(path) { function removeLeadingSlash (path) {
const reg = /^\//; const reg = /^\//
return path.replace(reg, "") return path.replace(reg, '')
} }

View file

@ -1,19 +1,19 @@
class MessageTracker { class MessageTracker {
data = new Map() data = new Map()
#maxLength = 200 #maxLength = 200
push(msg) { push (msg) {
this.data.set(msg.id, msg); this.data.set(msg.id, msg)
if (this.data.size > this.#maxLength) { if (this.data.size > this.#maxLength) {
const [firstKey] = this.data.keys(); const [firstKey] = this.data.keys()
this.data.delete(firstKey); this.data.delete(firstKey)
}
} }
}
get(index) { get (index) {
return this.data.get(index); return this.data.get(index)
} }
} }
export const messageTracker = new MessageTracker(); export const messageTracker = new MessageTracker()

View file

@ -1,30 +1,28 @@
import { WebSocketServer } from 'ws'; import { WebSocketServer } from 'ws'
import { config } from "../config.js"; import { config } from '../config.js'
import { EventType } from "../eventTypes.js" import { EventType } from '../eventTypes.js'
import { requestDefinitionFile } from './messageGenerators.js'; // import { requestDefinitionFile } from './messageGenerators.js'
import { messageTracker } from "./messageTracker.js" import { messageTracker } from './messageTracker.js'
export function setupSocket(signaller) { export function setupSocket (signaller) {
const wss = new WebSocketServer({ port: config.get('port') })
const wss = new WebSocketServer({ port: config.get("port") }); wss.on('connection', function connection (ws) {
function sendMessage (msg) {
messageTracker.push(msg)
ws.send(JSON.stringify(msg))
}
wss.on('connection', function connection(ws) { ws.on('message', (msg) => {
signaller.emit(EventType.MessageReceived, msg)
})
function sendMessage(msg) { signaller.on(EventType.MessageSend, msg => {
messageTracker.push(msg); sendMessage(msg)
ws.send(JSON.stringify(msg)); })
}
ws.on('message', (msg) => { signaller.trigger(EventType.ConnectionMade)
signaller.emit(EventType.MessageReceived, msg); })
});
signaller.on(EventType.MessageSend, msg => { return wss
sendMessage(msg);
});
signaller.trigger(EventType.ConnectionMade);
});
return wss;
} }