From 8f4313b1807770defe7f568f1f4440d9d09415d6 Mon Sep 17 00:00:00 2001 From: David Walker Date: Sun, 22 Feb 2026 11:28:10 -0800 Subject: [PATCH] Revert "PIPE: Add pipe support for passing data into and out of terminal commands (#2395)" (#2524) This reverts commit 92b8b58588b548bdc00dd9d4285f86cc34c28024. Accidental merge on my part - the code is in decent shape, but isn't meant to go in for 3.0. --- markdown/bitburner.ns.getstdin.md | 23 - markdown/bitburner.ns.md | 17 - src/DarkWeb/DarkWeb.tsx | 25 +- src/Documentation/doc/en/index.md | 5 +- .../terminal_pipes_and_redirects.md | 60 --- src/Documentation/pages.ts | 8 +- src/Netscript/RamCostGenerator.ts | 1 - src/NetscriptFunctions.ts | 27 +- src/NetscriptFunctions/Singularity.ts | 2 +- src/Programs/Program.ts | 5 +- src/Programs/Programs.ts | 143 +++-- src/Script/RunningScript.ts | 20 +- src/ScriptEditor/NetscriptDefinitions.d.ts | 15 - src/Terminal/HelpText.ts | 32 +- src/Terminal/Parser.ts | 4 - src/Terminal/StdIO/IOStream.ts | 55 -- src/Terminal/StdIO/RedirectIO.ts | 227 -------- src/Terminal/StdIO/StdIO.ts | 81 --- src/Terminal/StdIO/utils.tsx | 67 --- src/Terminal/Terminal.ts | 242 +++------ src/Terminal/commands/alias.ts | 12 +- src/Terminal/commands/analyze.ts | 8 +- src/Terminal/commands/backdoor.ts | 14 +- src/Terminal/commands/buy.ts | 19 +- src/Terminal/commands/cat.ts | 148 +----- src/Terminal/commands/cd.ts | 10 +- src/Terminal/commands/check.ts | 13 +- src/Terminal/commands/connect.ts | 8 +- src/Terminal/commands/cp.ts | 20 +- src/Terminal/commands/echo.ts | 7 - src/Terminal/commands/expr.ts | 10 +- src/Terminal/commands/free.ts | 10 +- src/Terminal/commands/grep.ts | 170 +++--- src/Terminal/commands/grow.ts | 13 +- src/Terminal/commands/hack.ts | 14 +- src/Terminal/commands/help.ts | 12 +- src/Terminal/commands/history.ts | 8 +- src/Terminal/commands/hostname.ts | 7 +- src/Terminal/commands/ipaddr.ts | 7 +- src/Terminal/commands/kill.ts | 22 +- src/Terminal/commands/killall.ts | 5 +- src/Terminal/commands/ls.tsx | 10 +- src/Terminal/commands/lscpu.ts | 5 +- src/Terminal/commands/mem.ts | 19 +- src/Terminal/commands/ps.ts | 7 +- src/Terminal/commands/rm.ts | 21 +- src/Terminal/commands/run.ts | 19 +- src/Terminal/commands/runProgram.ts | 11 +- src/Terminal/commands/runScript.ts | 31 +- src/Terminal/commands/scan.ts | 7 +- src/Terminal/commands/scananalyze.ts | 18 +- src/Terminal/commands/scp.ts | 23 +- src/Terminal/commands/sudov.ts | 9 +- src/Terminal/commands/tail.ts | 56 +- src/Terminal/commands/top.ts | 9 +- src/Terminal/commands/weaken.ts | 13 +- src/Terminal/commands/wget.ts | 56 +- src/Terminal/getTabCompletionPossibilities.ts | 22 +- src/Terminal/ui/TerminalInput.tsx | 4 +- src/ui/React/ANSIITypography.tsx | 2 +- src/utils/APIBreaks/APIBreak.ts | 11 +- test/jest/Netscript/RunScript.test.ts | 13 +- test/jest/Save.test.ts | 12 - test/jest/Terminal/Pipes.test.ts | 494 ------------------ test/jest/Terminal/RedirectIO.test.ts | 207 -------- test/jest/Terminal/cat.test.ts | 142 ----- test/jest/Terminal/grep.test.ts | 85 --- test/jest/__snapshots__/Save.test.ts.snap | 6 - 68 files changed, 479 insertions(+), 2429 deletions(-) delete mode 100644 markdown/bitburner.ns.getstdin.md delete mode 100644 src/Documentation/doc/en/programming/terminal_pipes_and_redirects.md delete mode 100644 src/Terminal/StdIO/IOStream.ts delete mode 100644 src/Terminal/StdIO/RedirectIO.ts delete mode 100644 src/Terminal/StdIO/StdIO.ts delete mode 100644 src/Terminal/StdIO/utils.tsx delete mode 100644 src/Terminal/commands/echo.ts delete mode 100644 test/jest/Terminal/Pipes.test.ts delete mode 100644 test/jest/Terminal/RedirectIO.test.ts delete mode 100644 test/jest/Terminal/cat.test.ts delete mode 100644 test/jest/Terminal/grep.test.ts diff --git a/markdown/bitburner.ns.getstdin.md b/markdown/bitburner.ns.getstdin.md deleted file mode 100644 index b02eead1c..000000000 --- a/markdown/bitburner.ns.getstdin.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [bitburner](./bitburner.md) > [NS](./bitburner.ns.md) > [getStdin](./bitburner.ns.getstdin.md) - -## NS.getStdin() method - -Retrieves the NetscriptPort handle used to get input piped to the script. Examples: - -If a script was run with data piped into it via the terminal: `echo input1 | run myScript.js` - -then `ns.getStdin().read()` inside `myScript.js` would return `"input1"`. - -If more data is added later (for example, if one script's terminal is piped to another script), then the script can read that data from `ns.getStdin()` as well. `await ns.getStdin().nextPortWrite()` can be used to wait until new data is available to read. - -**Signature:** - -```typescript -getStdin(): NetscriptPort | null; -``` -**Returns:** - -[NetscriptPort](./bitburner.netscriptport.md) \| null - diff --git a/markdown/bitburner.ns.md b/markdown/bitburner.ns.md index 9cb2627fb..43c291e42 100644 --- a/markdown/bitburner.ns.md +++ b/markdown/bitburner.ns.md @@ -1051,23 +1051,6 @@ Get the used RAM on a server. Share power has a multiplicative effect on rep/second while doing work for a faction. Share power increases incrementally for every thread of share running on your server network, but at a sharply decreasing rate. - - - -[getStdin()](./bitburner.ns.getstdin.md) - - - - -Retrieves the NetscriptPort handle used to get input piped to the script. Examples: - -If a script was run with data piped into it via the terminal: `echo input1 | run myScript.js` - -then `ns.getStdin().read()` inside `myScript.js` would return `"input1"`. - -If more data is added later (for example, if one script's terminal is piped to another script), then the script can read that data from `ns.getStdin()` as well. `await ns.getStdin().nextPortWrite()` can be used to wait until new data is available to read. - - diff --git a/src/DarkWeb/DarkWeb.tsx b/src/DarkWeb/DarkWeb.tsx index 815f6bdf2..c213fbb9c 100644 --- a/src/DarkWeb/DarkWeb.tsx +++ b/src/DarkWeb/DarkWeb.tsx @@ -7,7 +7,6 @@ import { SpecialServers } from "../Server/data/SpecialServers"; import { Money } from "../ui/React/Money"; import { DarkWebItem } from "./DarkWebItem"; import { isCreateProgramWork } from "../Work/CreateProgramWork"; -import type { StdIO } from "../Terminal/StdIO/StdIO"; import { CompletedProgramName } from "@enums"; import { getDarkscapeNavigator } from "../DarkNet/effects/effects"; @@ -15,7 +14,7 @@ import { getDarkscapeNavigator } from "../DarkNet/effects/effects"; export function checkIfConnectedToDarkweb(): void { const server = Player.getCurrentServer(); if (server !== null && SpecialServers.DarkWeb == server.hostname) { - Terminal.printAndBypassPipes( + Terminal.print( "You are now connected to the dark web. From the dark web you can purchase illegal items. " + "Use the 'buy -l' command to display a list of all the items you can buy. Use 'buy [item-name]' " + "to purchase an item. Use 'buy -a' to purchase all unowned items. You can use the 'buy' command anywhere, " + @@ -24,7 +23,7 @@ export function checkIfConnectedToDarkweb(): void { } } -export function listAllDarkwebItems(stdIO: StdIO): void { +export function listAllDarkwebItems(): void { for (const key of Object.keys(DarkWebItems) as (keyof typeof DarkWebItems)[]) { const item = DarkWebItems[key]; @@ -38,12 +37,11 @@ export function listAllDarkwebItems(stdIO: StdIO): void { <> {item.program} - {cost} - {item.description} , - stdIO, ); } } -export function buyDarkwebItem(itemName: string, stdIO: StdIO): void { +export function buyDarkwebItem(itemName: string): void { itemName = itemName.toLowerCase(); // find the program that matches, if any @@ -58,19 +56,19 @@ export function buyDarkwebItem(itemName: string, stdIO: StdIO): void { // return if invalid if (item === null) { - Terminal.error("Unrecognized item: " + itemName, stdIO); + Terminal.error("Unrecognized item: " + itemName); return; } // return if the player already has it. if (Player.hasProgram(item.program)) { - Terminal.print("You already have the " + item.program + " program", stdIO); + Terminal.print("You already have the " + item.program + " program"); return; } // return if the player doesn't have enough money if (Player.money < item.price) { - Terminal.error("Not enough money to purchase " + item.program, stdIO); + Terminal.error("Not enough money to purchase " + item.program); return; } @@ -85,7 +83,6 @@ export function buyDarkwebItem(itemName: string, stdIO: StdIO): void { Terminal.print( "You have purchased the " + item.program + " program. The new program can be found on your home computer.", - stdIO, ); if (item.program === CompletedProgramName.darkscape) { @@ -93,7 +90,7 @@ export function buyDarkwebItem(itemName: string, stdIO: StdIO): void { } } -export function buyAllDarkwebItems(stdIO: StdIO): void { +export function buyAllDarkwebItems(): void { const itemsToBuy: DarkWebItem[] = []; for (const key of Object.keys(DarkWebItems) as (keyof typeof DarkWebItems)[]) { @@ -101,21 +98,21 @@ export function buyAllDarkwebItems(stdIO: StdIO): void { if (!Player.hasProgram(item.program)) { itemsToBuy.push(item); if (item.price > Player.money) { - Terminal.error("Need " + formatMoney(item.price - Player.money) + " more to purchase " + item.program, stdIO); + Terminal.error("Need " + formatMoney(item.price - Player.money) + " more to purchase " + item.program); return; } else { - buyDarkwebItem(item.program, stdIO); + buyDarkwebItem(item.program); } } } if (itemsToBuy.length === 0) { - Terminal.print("All available programs have been purchased already.", stdIO); + Terminal.print("All available programs have been purchased already."); return; } if (itemsToBuy.length > 0) { - Terminal.print("All programs have been purchased.", stdIO); + Terminal.print("All programs have been purchased."); return; } } diff --git a/src/Documentation/doc/en/index.md b/src/Documentation/doc/en/index.md index 7da8c94c4..0994d34d9 100644 --- a/src/Documentation/doc/en/index.md +++ b/src/Documentation/doc/en/index.md @@ -28,9 +28,6 @@ ## Advanced Mechanics - [Hacking algorithms](programming/hackingalgorithms.md) -- [IPvGO](programming/go_algorithms.md) -- [Darkweb Network](programming/darknet.md) -- [Terminal Pipes and Redirects](programming/terminal_pipes_and_redirects.md) - [List of factions and their requirements](advanced/faction_list.md) - [Offline scripts and bonus time](advanced/offlineandbonustime.md) - [BitNodes](advanced/bitnodes.md) @@ -45,6 +42,8 @@ - [Sleeves](advanced/sleeves.md) - [Grafting](advanced/grafting.md) - [Stanek's Gift](advanced/stanek.md) +- [IPvGO](programming/go_algorithms.md) +- [Darkweb Network](programming/darknet.md) ## Resources diff --git a/src/Documentation/doc/en/programming/terminal_pipes_and_redirects.md b/src/Documentation/doc/en/programming/terminal_pipes_and_redirects.md deleted file mode 100644 index 0d12924ec..000000000 --- a/src/Documentation/doc/en/programming/terminal_pipes_and_redirects.md +++ /dev/null @@ -1,60 +0,0 @@ -# Terminal Pipes and Redirects - WIP - -The output of commands and scripts, that normally would be logged to the terminal, can instead be redirected and sent to another location. - -For example, `echo` logs whatever input it is given. - -``` -[home /]> echo test123 -test123 -``` - -However, its output can instead be sent to a file using the output redirect `>` : - -``` -[home /]> echo test123 >> newFile.txt -``` - -After this, `newFile.txt` will be created (if it didn't exist) and will contain `test123` - -### Accessing stdin via script - -```js -/** @param {NS} ns */ -async function read(ns) { - const stdin = ns.getStdin(); - if (stdin.empty()) { - await stdin.nextWrite(); - } - return stdin.read(); -} -``` - -### Creating your own command line utilities - -`cut.js` using `read()` from the snippet above - -```js -/** @param {NS} ns */ -export async function main(ns) { - if (!ns.getStdin()) { - ns.tprint("ERROR: No piped input given"); - return; - } - - // The '-c' flag expects a range of characters like 2-4 - // Other flags, such as '-b' bytes and '-d' delimeter, are left as an excercise for the reader - const flags = ns.flags([["c", "0"]]); - const charCountRange = flags.c.split("-"); - const startCharCount = Number(charCountRange[0]?.trim()); - const endCharCount = Number(charCountRange[1]?.trim() ?? startCharCount); - - let data = await read(ns); - while (data != null) { - // slice the characters from the input data to specified range, and print them (aka send to stdout) - // tprintf is used to avoid printing the script's filename and line number before the message - ns.tprintf("%s", data.slice(startCharCount - 1, endCharCount)); - data = await read(ns); - } -} -``` diff --git a/src/Documentation/pages.ts b/src/Documentation/pages.ts index e6bf61c88..1772241a0 100644 --- a/src/Documentation/pages.ts +++ b/src/Documentation/pages.ts @@ -65,8 +65,7 @@ import file62 from "./doc/en/programming/go_algorithms.md?raw"; import file63 from "./doc/en/programming/hackingalgorithms.md?raw"; import file64 from "./doc/en/programming/learn.md?raw"; import file65 from "./doc/en/programming/remote_api.md?raw"; -import file66 from "./doc/en/programming/terminal_pipes_and_redirects.md?raw"; -import file67 from "./doc/en/programming/typescript_react.md?raw"; +import file66 from "./doc/en/programming/typescript_react.md?raw"; import nsDoc_bitburner__valueof_md from "../../markdown/bitburner._valueof.md?raw"; import nsDoc_bitburner_activefragment_highestcharge_md from "../../markdown/bitburner.activefragment.highestcharge.md?raw"; @@ -1048,7 +1047,6 @@ import nsDoc_bitburner_ns_getserverrequiredhackinglevel_md from "../../markdown/ import nsDoc_bitburner_ns_getserversecuritylevel_md from "../../markdown/bitburner.ns.getserversecuritylevel.md?raw"; import nsDoc_bitburner_ns_getserverusedram_md from "../../markdown/bitburner.ns.getserverusedram.md?raw"; import nsDoc_bitburner_ns_getsharepower_md from "../../markdown/bitburner.ns.getsharepower.md?raw"; -import nsDoc_bitburner_ns_getstdin_md from "../../markdown/bitburner.ns.getstdin.md?raw"; import nsDoc_bitburner_ns_gettotalscriptexpgain_md from "../../markdown/bitburner.ns.gettotalscriptexpgain.md?raw"; import nsDoc_bitburner_ns_gettotalscriptincome_md from "../../markdown/bitburner.ns.gettotalscriptincome.md?raw"; import nsDoc_bitburner_ns_getweakentime_md from "../../markdown/bitburner.ns.getweakentime.md?raw"; @@ -1658,8 +1656,7 @@ AllPages["en/programming/go_algorithms.md"] = file62; AllPages["en/programming/hackingalgorithms.md"] = file63; AllPages["en/programming/learn.md"] = file64; AllPages["en/programming/remote_api.md"] = file65; -AllPages["en/programming/terminal_pipes_and_redirects.md"] = file66; -AllPages["en/programming/typescript_react.md"] = file67; +AllPages["en/programming/typescript_react.md"] = file66; AllPages["nsDoc/bitburner._valueof.md"] = nsDoc_bitburner__valueof_md; AllPages["nsDoc/bitburner.activefragment.highestcharge.md"] = nsDoc_bitburner_activefragment_highestcharge_md; @@ -2641,7 +2638,6 @@ AllPages["nsDoc/bitburner.ns.getserverrequiredhackinglevel.md"] = nsDoc_bitburne AllPages["nsDoc/bitburner.ns.getserversecuritylevel.md"] = nsDoc_bitburner_ns_getserversecuritylevel_md; AllPages["nsDoc/bitburner.ns.getserverusedram.md"] = nsDoc_bitburner_ns_getserverusedram_md; AllPages["nsDoc/bitburner.ns.getsharepower.md"] = nsDoc_bitburner_ns_getsharepower_md; -AllPages["nsDoc/bitburner.ns.getstdin.md"] = nsDoc_bitburner_ns_getstdin_md; AllPages["nsDoc/bitburner.ns.gettotalscriptexpgain.md"] = nsDoc_bitburner_ns_gettotalscriptexpgain_md; AllPages["nsDoc/bitburner.ns.gettotalscriptincome.md"] = nsDoc_bitburner_ns_gettotalscriptincome_md; AllPages["nsDoc/bitburner.ns.getweakentime.md"] = nsDoc_bitburner_ns_getweakentime_md; diff --git a/src/Netscript/RamCostGenerator.ts b/src/Netscript/RamCostGenerator.ts index 03db7d7b9..cb3441d5f 100644 --- a/src/Netscript/RamCostGenerator.ts +++ b/src/Netscript/RamCostGenerator.ts @@ -664,7 +664,6 @@ export const RamCosts: RamCostTree = { tprintRaw: 0, printRaw: 0, dynamicImport: 0, - getStdin: 0, formulas: { mockServer: 0, diff --git a/src/NetscriptFunctions.ts b/src/NetscriptFunctions.ts index c0e344235..19bc82d5c 100644 --- a/src/NetscriptFunctions.ts +++ b/src/NetscriptFunctions.ts @@ -112,7 +112,6 @@ import { NetscriptFormat } from "./NetscriptFunctions/Format"; import { checkDarknetServer } from "./DarkNet/effects/offlineServerHandling"; import { DarknetServer } from "./Server/DarknetServer"; import { FragmentTypeEnum } from "./CotMG/FragmentType"; -import { PortHandle } from "./NetscriptPort"; import { exampleDarknetServerData, ResponseCodeEnum } from "./DarkNet/Enums"; import { renderToStaticMarkup } from "react-dom/server"; import { Literatures } from "./Literature/Literatures"; @@ -428,49 +427,47 @@ export const ns: InternalAPI = { throw helpers.errorMessage(ctx, "Takes at least 1 argument."); } const str = helpers.argsToString(args); - const stdOut = ctx.workerScript.scriptRef.terminalStdOut; if (str.startsWith("ERROR") || str.startsWith("FAIL")) { Terminal.error(`${ctx.workerScript.name}: ${str}`); return; } if (str.startsWith("SUCCESS")) { - Terminal.success(`${ctx.workerScript.name}: ${str}`, stdOut); + Terminal.success(`${ctx.workerScript.name}: ${str}`); return; } if (str.startsWith("WARN")) { - Terminal.warn(`${ctx.workerScript.name}: ${str}`, stdOut); + Terminal.warn(`${ctx.workerScript.name}: ${str}`); return; } if (str.startsWith("INFO")) { - Terminal.info(`${ctx.workerScript.name}: ${str}`, stdOut); + Terminal.info(`${ctx.workerScript.name}: ${str}`); return; } - Terminal.print(`${ctx.workerScript.name}: ${str}`, stdOut); + Terminal.print(`${ctx.workerScript.name}: ${str}`); }, tprintf: (ctx) => (_format, ...args) => { const format = helpers.string(ctx, "format", _format); const str = vsprintf(format, args); - const stdOut = ctx.workerScript.scriptRef.terminalStdOut; if (str.startsWith("ERROR") || str.startsWith("FAIL")) { Terminal.error(`${str}`); return; } if (str.startsWith("SUCCESS")) { - Terminal.success(`${str}`, stdOut); + Terminal.success(`${str}`); return; } if (str.startsWith("WARN")) { - Terminal.warn(`${str}`, stdOut); + Terminal.warn(`${str}`); return; } if (str.startsWith("INFO")) { - Terminal.info(`${str}`, stdOut); + Terminal.info(`${str}`); return; } - Terminal.print(`${str}`, stdOut); + Terminal.print(`${str}`); }, clearLog: (ctx) => () => { ctx.workerScript.scriptRef.clearLog(); @@ -1510,8 +1507,8 @@ export const ns: InternalAPI = { const name = helpers.string(ctx, "name", _name); return getRamCost(name.split("."), true); }, - tprintRaw: (ctx) => (value) => { - Terminal.printRaw(wrapUserNode(value), ctx.workerScript.scriptRef.terminalStdOut); + tprintRaw: () => (value) => { + Terminal.printRaw(wrapUserNode(value)); }, printRaw: (ctx) => (value) => { ctx.workerScript.print(wrapUserNode(value)); @@ -1527,10 +1524,6 @@ export const ns: InternalAPI = { //Script **must** be a script at this point return compile(script as Script, server.scripts); }, - getStdin: (ctx) => () => { - const stdinHandle = ctx.workerScript.scriptRef.stdin?.handle; - return stdinHandle ? new PortHandle(stdinHandle.n) : null; - }, flags: Flags, heart: { break: () => () => Player.karma }, ...NetscriptExtra(), diff --git a/src/NetscriptFunctions/Singularity.ts b/src/NetscriptFunctions/Singularity.ts index 493c3e47b..9b5731b1d 100644 --- a/src/NetscriptFunctions/Singularity.ts +++ b/src/NetscriptFunctions/Singularity.ts @@ -468,7 +468,7 @@ export function NetscriptSingularity(): InternalAPI { helpers.checkSingularityAccess(ctx); const filename = helpers.string(ctx, "filename", _filename); const server = Player.getCurrentServer(); - cat([filename], server, ctx.workerScript.scriptRef.terminalStdOut); + cat([filename], server); }, connect: (ctx) => (_host?) => { helpers.checkSingularityAccess(ctx); diff --git a/src/Programs/Program.ts b/src/Programs/Program.ts index e6ea2a00d..14cc53826 100644 --- a/src/Programs/Program.ts +++ b/src/Programs/Program.ts @@ -1,7 +1,6 @@ import type { CompletedProgramName } from "@enums"; import { ProgramFilePath, asProgramFilePath } from "../Paths/ProgramFilePath"; import { BaseServer } from "../Server/BaseServer"; -import type { StdIO } from "../Terminal/StdIO/StdIO"; export interface IProgramCreate { level: number; @@ -12,14 +11,14 @@ export interface IProgramCreate { interface ProgramConstructorParams { name: CompletedProgramName; create: IProgramCreate | null; - run: (args: string[], server: BaseServer, stdIO: StdIO) => void; + run: (args: string[], server: BaseServer) => void; nsMethod?: string; } export class Program { name: ProgramFilePath & CompletedProgramName; create: IProgramCreate | null; - run: (args: string[], server: BaseServer, stdIO: StdIO) => void; + run: (args: string[], server: BaseServer) => void; nsMethod?: string; constructor({ name, create, run, nsMethod }: ProgramConstructorParams) { diff --git a/src/Programs/Programs.ts b/src/Programs/Programs.ts index 259f764f5..289728386 100644 --- a/src/Programs/Programs.ts +++ b/src/Programs/Programs.ts @@ -16,7 +16,6 @@ import { Page } from "../ui/Router"; import { knowAboutBitverse } from "../BitNode/BitNodeUtils"; import { handleStormSeed } from "../DarkNet/effects/webstorm"; import { clampNumber } from "../utils/helpers/clampNumber"; -import type { StdIO } from "../Terminal/StdIO/StdIO"; function requireHackingLevel(lvl: number) { return function () { @@ -34,7 +33,7 @@ function bitFlumeRequirements() { }; } -function warnIfNonArgProgramIsRunWithArgs(name: CompletedProgramName, args: string[], stdIO: StdIO): void { +function warnIfNonArgProgramIsRunWithArgs(name: CompletedProgramName, args: string[]): void { if (args.length === 0) { return; } @@ -42,7 +41,6 @@ function warnIfNonArgProgramIsRunWithArgs(name: CompletedProgramName, args: stri `You are running ${name} with arguments, but ${name} does not accept arguments. These arguments will be ignored. ` + `${name} only affects the server ('${Player.currentServer}') that you are connecting via the terminal. ` + "If you want to pass the target's hostname as an argument, you have to use the respective NS API.", - stdIO, ); } @@ -56,25 +54,25 @@ export const Programs: Record = { req: requireHackingLevel(1), time: CONSTANTS.MillisecondsPerFiveMinutes, }, - run: (args: string[], server: BaseServer, stdIO: StdIO): void => { - warnIfNonArgProgramIsRunWithArgs(CompletedProgramName.nuke, args, stdIO); + run: (args: string[], server: BaseServer): void => { + warnIfNonArgProgramIsRunWithArgs(CompletedProgramName.nuke, args); if (!(server instanceof Server)) { - Terminal.error("Cannot nuke this kind of server.", stdIO); + Terminal.error("Cannot nuke this kind of server."); return; } if (server.hasAdminRights) { - Terminal.print("You already have root access to this computer. There is no reason to run NUKE.exe", stdIO); - Terminal.print("You can now run scripts on this server.", stdIO); + Terminal.print("You already have root access to this computer. There is no reason to run NUKE.exe"); + Terminal.print("You can now run scripts on this server."); return; } if (server.openPortCount >= server.numOpenPortsRequired) { server.hasAdminRights = true; - Terminal.print("NUKE successful! Gained root access to " + server.hostname, stdIO); - Terminal.print("You can now run scripts on this server.", stdIO); + Terminal.print("NUKE successful! Gained root access to " + server.hostname); + Terminal.print("You can now run scripts on this server."); return; } - Terminal.print("NUKE unsuccessful. Not enough ports have been opened", stdIO); + Terminal.print("NUKE unsuccessful. Not enough ports have been opened"); }, }), [CompletedProgramName.bruteSsh]: new Program({ @@ -86,19 +84,19 @@ export const Programs: Record = { req: requireHackingLevel(50), time: CONSTANTS.MillisecondsPerFiveMinutes * 2, }, - run: (args: string[], server: BaseServer, stdIO: StdIO): void => { - warnIfNonArgProgramIsRunWithArgs(CompletedProgramName.bruteSsh, args, stdIO); + run: (args: string[], server: BaseServer): void => { + warnIfNonArgProgramIsRunWithArgs(CompletedProgramName.bruteSsh, args); if (!(server instanceof Server)) { - Terminal.error("Cannot run BruteSSH.exe on this kind of server.", stdIO); + Terminal.error("Cannot run BruteSSH.exe on this kind of server."); return; } if (server.sshPortOpen) { - Terminal.print("SSH Port (22) is already open!", stdIO); + Terminal.print("SSH Port (22) is already open!"); return; } server.sshPortOpen = true; - Terminal.print("Opened SSH Port(22)!", stdIO); + Terminal.print("Opened SSH Port(22)!"); server.openPortCount++; }, }), @@ -111,19 +109,19 @@ export const Programs: Record = { req: requireHackingLevel(100), time: CONSTANTS.MillisecondsPerHalfHour, }, - run: (args: string[], server: BaseServer, stdIO: StdIO): void => { - warnIfNonArgProgramIsRunWithArgs(CompletedProgramName.ftpCrack, args, stdIO); + run: (args: string[], server: BaseServer): void => { + warnIfNonArgProgramIsRunWithArgs(CompletedProgramName.ftpCrack, args); if (!(server instanceof Server)) { - Terminal.error("Cannot run FTPCrack.exe on this kind of server.", stdIO); + Terminal.error("Cannot run FTPCrack.exe on this kind of server."); return; } if (server.ftpPortOpen) { - Terminal.print("FTP Port (21) is already open!", stdIO); + Terminal.print("FTP Port (21) is already open!"); return; } server.ftpPortOpen = true; - Terminal.print("Opened FTP Port (21)!", stdIO); + Terminal.print("Opened FTP Port (21)!"); server.openPortCount++; }, }), @@ -136,19 +134,19 @@ export const Programs: Record = { req: requireHackingLevel(250), time: CONSTANTS.MillisecondsPer2Hours, }, - run: (args: string[], server: BaseServer, stdIO: StdIO): void => { - warnIfNonArgProgramIsRunWithArgs(CompletedProgramName.relaySmtp, args, stdIO); + run: (args: string[], server: BaseServer): void => { + warnIfNonArgProgramIsRunWithArgs(CompletedProgramName.relaySmtp, args); if (!(server instanceof Server)) { - Terminal.error("Cannot run relaySMTP.exe on this kind of server.", stdIO); + Terminal.error("Cannot run relaySMTP.exe on this kind of server."); return; } if (server.smtpPortOpen) { - Terminal.print("SMTP Port (25) is already open!", stdIO); + Terminal.print("SMTP Port (25) is already open!"); return; } server.smtpPortOpen = true; - Terminal.print("Opened SMTP Port (25)!", stdIO); + Terminal.print("Opened SMTP Port (25)!"); server.openPortCount++; }, }), @@ -161,19 +159,19 @@ export const Programs: Record = { req: requireHackingLevel(500), time: CONSTANTS.MillisecondsPer4Hours, }, - run: (args: string[], server: BaseServer, stdIO: StdIO): void => { - warnIfNonArgProgramIsRunWithArgs(CompletedProgramName.httpWorm, args, stdIO); + run: (args: string[], server: BaseServer): void => { + warnIfNonArgProgramIsRunWithArgs(CompletedProgramName.httpWorm, args); if (!(server instanceof Server)) { - Terminal.error("Cannot run HTTPWorm.exe on this kind of server.", stdIO); + Terminal.error("Cannot run HTTPWorm.exe on this kind of server."); return; } if (server.httpPortOpen) { - Terminal.print("HTTP Port (80) is already open!", stdIO); + Terminal.print("HTTP Port (80) is already open!"); return; } server.httpPortOpen = true; - Terminal.print("Opened HTTP Port (80)!", stdIO); + Terminal.print("Opened HTTP Port (80)!"); server.openPortCount++; }, }), @@ -186,19 +184,19 @@ export const Programs: Record = { req: requireHackingLevel(750), time: CONSTANTS.MillisecondsPer8Hours, }, - run: (args: string[], server: BaseServer, stdIO: StdIO): void => { - warnIfNonArgProgramIsRunWithArgs(CompletedProgramName.sqlInject, args, stdIO); + run: (args: string[], server: BaseServer): void => { + warnIfNonArgProgramIsRunWithArgs(CompletedProgramName.sqlInject, args); if (!(server instanceof Server)) { - Terminal.error("Cannot run SQLInject.exe on this kind of server.", stdIO); + Terminal.error("Cannot run SQLInject.exe on this kind of server."); return; } if (server.sqlPortOpen) { - Terminal.print("SQL Port (1433) is already open!", stdIO); + Terminal.print("SQL Port (1433) is already open!"); return; } server.sqlPortOpen = true; - Terminal.print("Opened SQL Port (1433)!", stdIO); + Terminal.print("Opened SQL Port (1433)!"); server.openPortCount++; }, }), @@ -210,9 +208,9 @@ export const Programs: Record = { req: requireHackingLevel(75), time: CONSTANTS.MillisecondsPerQuarterHour, }, - run: (__, ___, stdIO: StdIO): void => { - Terminal.print("This executable cannot be run.", stdIO); - Terminal.print("DeepscanV1.exe lets you run 'scan-analyze' with a depth up to 5.", stdIO); + run: (): void => { + Terminal.print("This executable cannot be run."); + Terminal.print("DeepscanV1.exe lets you run 'scan-analyze' with a depth up to 5."); }, }), [CompletedProgramName.deepScan2]: new Program({ @@ -223,9 +221,9 @@ export const Programs: Record = { req: requireHackingLevel(400), time: CONSTANTS.MillisecondsPer2Hours, }, - run: (__, ___, stdIO: StdIO): void => { - Terminal.print("This executable cannot be run.", stdIO); - Terminal.print("DeepscanV2.exe lets you run 'scan-analyze' with a depth up to 10.", stdIO); + run: (): void => { + Terminal.print("This executable cannot be run."); + Terminal.print("DeepscanV2.exe lets you run 'scan-analyze' with a depth up to 10."); }, }), [CompletedProgramName.serverProfiler]: new Program({ @@ -237,47 +235,44 @@ export const Programs: Record = { req: requireHackingLevel(75), time: CONSTANTS.MillisecondsPerHalfHour, }, - run: (args: string[], __, stdIO: StdIO): void => { + run: (args: string[]): void => { if (args.length !== 1) { - Terminal.error("Must pass a server hostname or IP as an argument for ServerProfiler.exe", stdIO); + Terminal.error("Must pass a server hostname or IP as an argument for ServerProfiler.exe"); return; } const targetServer = GetServer(args[0]); if (targetServer == null) { - Terminal.error("Invalid server IP/hostname", stdIO); + Terminal.error("Invalid server IP/hostname"); return; } if (!(targetServer instanceof Server)) { - Terminal.error(`ServerProfiler.exe can only be run on normal servers.`, stdIO); + Terminal.error(`ServerProfiler.exe can only be run on normal servers.`); return; } - Terminal.print(targetServer.hostname + ":", stdIO); - Terminal.print("Server base security level: " + targetServer.baseDifficulty, stdIO); - Terminal.print("Server current security level: " + targetServer.hackDifficulty, stdIO); - Terminal.print("Server growth rate: " + targetServer.serverGrowth, stdIO); + Terminal.print(targetServer.hostname + ":"); + Terminal.print("Server base security level: " + targetServer.baseDifficulty); + Terminal.print("Server current security level: " + targetServer.hackDifficulty); + Terminal.print("Server growth rate: " + targetServer.serverGrowth); Terminal.print( `Netscript hack() execution time: ${convertTimeMsToTimeElapsedString( calculateHackingTime(targetServer, Player) * 1000, true, )}`, - stdIO, ); Terminal.print( `Netscript grow() execution time: ${convertTimeMsToTimeElapsedString( calculateGrowTime(targetServer, Player) * 1000, true, )}`, - stdIO, ); Terminal.print( `Netscript weaken() execution time: ${convertTimeMsToTimeElapsedString( calculateWeakenTime(targetServer, Player) * 1000, true, )}`, - stdIO, ); }, }), @@ -289,10 +284,10 @@ export const Programs: Record = { req: requireHackingLevel(25), time: CONSTANTS.MillisecondsPerQuarterHour, }, - run: (__, ___, stdIO: StdIO): void => { - Terminal.print("This executable cannot be run.", stdIO); - Terminal.print("AutoLink.exe lets you automatically connect to other servers when using 'scan-analyze'.", stdIO); - Terminal.print("When using scan-analyze, click on a server's hostname to connect to it.", stdIO); + run: (): void => { + Terminal.print("This executable cannot be run."); + Terminal.print("AutoLink.exe lets you automatically connect to other servers when using 'scan-analyze'."); + Terminal.print("When using scan-analyze, click on a server's hostname to connect to it."); }, }), [CompletedProgramName.formulas]: new Program({ @@ -303,9 +298,9 @@ export const Programs: Record = { req: requireHackingLevel(1000), time: CONSTANTS.MillisecondsPer4Hours, }, - run: (__, ___, stdIO: StdIO): void => { - Terminal.print("This executable cannot be run.", stdIO); - Terminal.print("Formulas.exe lets you use the formulas API.", stdIO); + run: (): void => { + Terminal.print("This executable cannot be run."); + Terminal.print("Formulas.exe lets you use the formulas API."); }, }), [CompletedProgramName.bitFlume]: new Program({ @@ -329,45 +324,43 @@ export const Programs: Record = { [CompletedProgramName.flight]: new Program({ name: CompletedProgramName.flight, create: null, - run: (__, ___, stdIO: StdIO): void => { + run: (): void => { const numAugReq = currentNodeMults.DaedalusAugsRequirement; const fulfilled = Player.augmentations.length >= numAugReq && Player.money >= 1e11 && Player.skills.hacking >= 2500; if (!fulfilled) { if (Player.augmentations.length >= numAugReq) { - Terminal.print(`[x] Augmentations: ${Player.augmentations.length} / ${numAugReq}`, stdIO); + Terminal.print(`[x] Augmentations: ${Player.augmentations.length} / ${numAugReq}`); } else { - Terminal.print(`[ ] Augmentations: ${Player.augmentations.length} / ${numAugReq}`, stdIO); + Terminal.print(`[ ] Augmentations: ${Player.augmentations.length} / ${numAugReq}`); } if (Player.money >= 1e11) { - Terminal.print(`[x] Money: ${formatMoney(Player.money)} / ${formatMoney(1e11)}`, stdIO); + Terminal.print(`[x] Money: ${formatMoney(Player.money)} / ${formatMoney(1e11)}`); } else { - Terminal.print(`[ ] Money: ${formatMoney(Player.money)} / ${formatMoney(1e11)}`, stdIO); + Terminal.print(`[ ] Money: ${formatMoney(Player.money)} / ${formatMoney(1e11)}`); } if (Player.skills.hacking >= 2500) { - Terminal.print(`[x] Hacking skill: ${Player.skills.hacking} / 2500`, stdIO); + Terminal.print(`[x] Hacking skill: ${Player.skills.hacking} / 2500`); } else { - Terminal.print(`[ ] Hacking skill: ${Player.skills.hacking} / 2500`, stdIO); + Terminal.print(`[ ] Hacking skill: ${Player.skills.hacking} / 2500`); } return; } - Terminal.print("We will contact you.", stdIO); - Terminal.print(`-- ${FactionName.Daedalus} --`, stdIO); + Terminal.print("We will contact you."); + Terminal.print(`-- ${FactionName.Daedalus} --`); }, }), [CompletedProgramName.darkscape]: new Program({ name: CompletedProgramName.darkscape, create: null, - run: (__, ___, stdIO: StdIO): void => { - Terminal.print("This program gives access to the dark net.", stdIO); + run: (): void => { + Terminal.print("This program gives access to the dark net."); Terminal.print( "The dark net is an unstable, constantly shifting network of servers that are only connected to the normal network through the darkweb server.", - stdIO, ); Terminal.print( "This network can be accessed using the `ns.dnet` api functions, or the DarkNet UI on the left-hand panel.", - stdIO, ); }, }), @@ -375,8 +368,8 @@ export const Programs: Record = { name: CompletedProgramName.stormSeed, nsMethod: "dnet.unleashStormSeed", create: null, - run: (__, ___, stdIO: StdIO): void => { - Terminal.print("You can feel a storm approaching...", stdIO); + run: (): void => { + Terminal.print("You can feel a storm approaching..."); const connectedServer = Player.getCurrentServer(); handleStormSeed(connectedServer); }, diff --git a/src/Script/RunningScript.ts b/src/Script/RunningScript.ts index 4a2ba48b8..34b736127 100644 --- a/src/Script/RunningScript.ts +++ b/src/Script/RunningScript.ts @@ -19,10 +19,6 @@ import { ScriptKey, scriptKey } from "../utils/helpers/scriptKey"; import type { LogBoxProperties } from "../ui/React/LogBoxManager"; -import { StdIO } from "../Terminal/StdIO/StdIO"; -import { IOStream } from "../Terminal/StdIO/IOStream"; -import { getTerminalStdIO } from "../Terminal/StdIO/RedirectIO"; - export class RunningScript { // Script arguments args: ScriptArg[] = []; @@ -74,17 +70,9 @@ export class RunningScript { // Cached key for ByArgs lookups. Will be overwritten by a correct ScriptKey in fromJSON or constructor scriptKey = "" as ScriptKey; - stdin: IOStream | null = null; - // Access to properties of the tail window. Can be used to get/set size, position, etc. tailProps = null as LogBoxProperties | null; - // Configuration for piping the script's tail output - tailStdOut: StdIO | null = null; - - // Configuration for piping the script's terminal output - terminalStdOut: StdIO = getTerminalStdIO(null); - // The title, as shown in the script's log box. Defaults to the name + args, // but can be changed by the user. If it is set to a React element (only by the user), // that will not be persisted, and will be restored to default on load. @@ -123,16 +111,14 @@ export class RunningScript { this.logs.push(logEntry); this.logUpd = true; - - this.tailStdOut?.write?.(logEntry); } - displayLog(stdIO: StdIO): void { + displayLog(): void { for (const log of this.logs) { if (typeof log === "string") { - Terminal.print(log, stdIO); + Terminal.print(log); } else { - Terminal.printRaw(log, stdIO); + Terminal.printRaw(log); } } } diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts index 96482f4ee..92bb582e7 100644 --- a/src/ScriptEditor/NetscriptDefinitions.d.ts +++ b/src/ScriptEditor/NetscriptDefinitions.d.ts @@ -8991,21 +8991,6 @@ export interface NS { */ dynamicImport(path: string): Promise; - /** - * Retrieves the NetscriptPort handle used to get input piped to the script. - * Examples: - * - * If a script was run with data piped into it via the terminal: - * `echo input1 | run myScript.js` - * - * then `ns.getStdin().read()` inside `myScript.js` would return `"input1"`. - * - * If more data is added later (for example, if one script's terminal is piped to another script), - * then the script can read that data from `ns.getStdin()` as well. - * `await ns.getStdin().nextPortWrite()` can be used to wait until new data is available to read. - */ - getStdin(): NetscriptPort | null; - enums: NSEnums; } diff --git a/src/Terminal/HelpText.ts b/src/Terminal/HelpText.ts index 0c366b4ef..64239cc27 100644 --- a/src/Terminal/HelpText.ts +++ b/src/Terminal/HelpText.ts @@ -5,7 +5,7 @@ export const TerminalHelpText: string[] = [ " analyze Get information about the current machine ", " backdoor Install a backdoor on the current machine ", " buy [-l/-a/program] Purchase a program through the Dark Web", - " cat [file]... Display a .msg, .lit, or text file, or concatenate multiple together", + " cat [file] Display a .msg, .lit, or text file", " cd [dir] Change to a new directory", " changelog Display changelog", " check [script] [args...] Print a script's logs to Terminal", @@ -14,7 +14,6 @@ export const TerminalHelpText: string[] = [ " connect [hostname] Connects to a remote server", " cp [src] [dest] Copy a file", " download [script/text file] Downloads scripts or text files to your computer", - " echo [string] Print the specified string to the terminal.", " expr [math expression] Evaluate a mathematical expression", " free Check the machine's memory (RAM) usage", " grep [opts]... pattern [file]... Search for PATTERN (string/regular expression) in each FILE and print results to terminal", @@ -141,7 +140,7 @@ export const HelpTexts: Record = { " ", ], cat: [ - "Usage: cat [file name]...", + "Usage: cat [file name]", " ", "Display message (.msg), literature (.lit), script (.js, .jsx, .ts, .tsx), or text (.txt, .json, .css) files. Examples:", " ", @@ -151,24 +150,6 @@ export const HelpTexts: Record = { " ", " cat servers.txt", " ", - "Can be used to concatenate multiple files and/or piped input together. Examples:", - " ", - " cat j1.msg foo.lit", - " ", - " cat servers.txt scripts/hack.js logs.txt", - " ", - "If a hyphen (-) is provided as an argument, cat will read from stdin at that location.", - "If not provided, any stdin will be placed at the end or the concatenated output.", - " ", - "This example pipes 'some text' in between the contents of file1.txt and file2.txt, and writes the result to the terminal:", - " ", - " echo some text | cat file1.txt - file2.txt", - " ", - "The output of cat can be redirected (as can all commands that log text).", - "For example, this duplicates the contents of file1.js into file3.js:", - " ", - " cat file1.js > file3.js", - " ", ], cd: [ "Usage: cd [dir]", @@ -234,15 +215,6 @@ export const HelpTexts: Record = { "Download all text files: download *.txt", " ", ], - echo: [ - "Usage: echo [string]", - " ", - "Print the specified string to the terminal. This command is mostly useful for piping", - " ", - "Example: echo 'Text To Store In File' > newFile.txt", - " ", - "Example: echo 'Text To Search In' | grep To", - ], expr: [ "Usage: expr [mathematical expression]", " ", diff --git a/src/Terminal/Parser.ts b/src/Terminal/Parser.ts index 27d687eee..27774c2de 100644 --- a/src/Terminal/Parser.ts +++ b/src/Terminal/Parser.ts @@ -1,15 +1,11 @@ import { trimQuotes } from "../utils/helpers/string"; import { substituteAliases } from "../Alias"; -import { Terminal } from "../Terminal"; // Helper function to parse individual arguments into number/boolean/string as appropriate function parseArg(arg: string): string | number | boolean { if (arg === "true") return true; if (arg === "false") return false; const argAsNumber = Number(arg); if (!isNaN(argAsNumber)) return argAsNumber; - if (arg === "$!") { - return Terminal.pidOfLastScriptRun ?? -1; - } return trimQuotes(arg); } diff --git a/src/Terminal/StdIO/IOStream.ts b/src/Terminal/StdIO/IOStream.ts deleted file mode 100644 index 7edcd2a2e..000000000 --- a/src/Terminal/StdIO/IOStream.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { NetscriptPort } from "@nsdefs"; -import { PortHandle } from "../../NetscriptPort"; -import { getNextStdinHandle } from "./utils"; - -export class IOStream implements NetscriptPort { - isClosed: boolean = false; - - handle: PortHandle = getNextStdinHandle(); - - close(): void { - this.write(null); - } - - write(value: any): unknown { - if (this.isClosed) { - return; - } - if (value === null) { - this.isClosed = true; - } - return this.handle.write(value); - } - - tryWrite(value: any): boolean { - if (this.isClosed) { - return false; - } - this.write(value); - return true; - } - - clear(): void { - this.handle.clear(); - } - - empty(): boolean { - return this.handle.empty(); - } - - full(): boolean { - return this.handle.full(); - } - - nextWrite(): Promise { - return this.handle.nextWrite(); - } - - peek(): unknown { - return this.handle.peek(); - } - - read(): unknown { - return this.handle.read(); - } -} diff --git a/src/Terminal/StdIO/RedirectIO.ts b/src/Terminal/StdIO/RedirectIO.ts deleted file mode 100644 index cdc386215..000000000 --- a/src/Terminal/StdIO/RedirectIO.ts +++ /dev/null @@ -1,227 +0,0 @@ -import { parseCommand } from "../Parser"; -import { IOStream } from "./IOStream"; -import { StdIO } from "./StdIO"; -import { Terminal } from "../../Terminal"; -import { hasTextExtension } from "../../Paths/TextFilePath"; -import { hasScriptExtension, resolveScriptFilePath } from "../../Paths/ScriptFilePath"; -import { Player } from "@player"; -import { Settings } from "../../Settings/Settings"; -import { Args, isPipeSymbol, PipeSymbols, stringify } from "./utils"; -import { sleep } from "../../utils/Utility"; - -export async function parseRedirectedCommands(commandString: string) { - const parsed = parseCommand(commandString); - const commandSets = findCommandsSplitByRedirects(parsed); - if (commandSets.length <= 1) { - return Terminal.executeCommand(commandString, getTerminalStdIO(null)); - } - - const stdIOChain = buildStdIOChain(commandSets.length); - const openPipes: Promise[] = []; - let longRunningCommandUsed = false; - for (let i = 0; i < commandSets.length; i++) { - const commandSet = commandSets[i]; - const stdIO = stdIOChain[i]; - handleCommand(stdIO, commandSet); - longRunningCommandUsed ||= isLongRunningCommand(commandSet); - openPipes.push(longRunningCommandUsed ? sleep(0) : waitUntilClosed(stdIO)); - } - - // Allow the IO chain to pass data through its async iterators - await Promise.all(openPipes); - return true; -} - -export function handleCommand(stdIO: StdIO, commandStrings: Args[]) { - const pipeSymbol = isPipeSymbol(commandStrings[0]) ? `${commandStrings[0]}` : null; - const command = `${pipeSymbol ? commandStrings[1] : commandStrings[0]}`; - const args = pipeSymbol ? commandStrings.slice(2) : commandStrings.slice(1); - - if (!command) { - return handleIoError(stdIO, `Invalid command string: no command found after output redirect ${pipeSymbol}.`); - } - - // Pipe to file - if (command && (hasTextExtension(command) || (hasScriptExtension(command) && pipeSymbol !== PipeSymbols.Pipe))) { - return handlePipeToFile(command, pipeSymbol, stdIO); - } - - // > and >> are invalid pipes for commands that are not piping to files - if (pipeSymbol === PipeSymbols.OutputRedirection || pipeSymbol === PipeSymbols.AppendOutputRedirection) { - return handleIoError( - stdIO, - `Invalid pipe symbol '${pipeSymbol}' for command: ${command}. > and >> can only be used to pipe into files.`, - ); - } - const commandArgs = args.map((arg) => (`${arg}`.includes(" ") ? `"${arg}"` : `${arg}`)); - const commandString = [command, ...commandArgs].join(" "); - - Terminal.executeCommand(commandString, stdIO); -} - -export function buildStdIOChain(length: number, initialStdIO: StdIO | null = null): StdIO[] { - const stdIOs: StdIO[] = []; - let priorStdIO = initialStdIO; - - for (let i = 0; i < length; i++) { - const newStdIO = new StdIO(priorStdIO?.stdout ?? null); - stdIOs.push(newStdIO); - priorStdIO = newStdIO; - } - stdIOs[stdIOs.length - 1].stdout = null; // Last StdIO writes to terminal - - return stdIOs; -} - -export function findCommandsSplitByRedirects(commands: Args[]) { - const result: Args[][] = []; - let currentCommand: Args[] = []; - for (const token of commands) { - if (isPipeSymbol(token)) { - result.push(currentCommand); - currentCommand = [token]; - } else { - currentCommand.push(token); - } - } - result.push(currentCommand); - - for (const [index, commandGroup] of result.entries()) { - if (index !== 1 && commandGroup[0] === PipeSymbols.InputRedirection) { - handleIoError( - getTerminalStdIO(), - `Error in pipe command: Invalid pipe command. Only the first command in a pipe chain can have input redirection '<'.`, - ); - return []; - } - } - - // If the second command starts with an input redirection, convert it to a simple pipe. - if (result[1]?.[0] === PipeSymbols.InputRedirection) { - const inputRedirectCommand = result.splice(1, 1)[0]; - result.unshift(["cat", ...inputRedirectCommand.slice(1)]); - result[1].unshift(PipeSymbols.Pipe); - } - - return result; -} - -export function getTerminalStdIO(stdin: IOStream | null = null) { - return new StdIO(stdin, null); -} - -function handlePipeToFile(fileName: string, pipeType: string | null, stdIO: StdIO) { - if (!pipeType) { - return handleIoError(stdIO, `Invalid command string: no pipe symbol found for piping to file ${fileName}.`); - } - if (pipeType !== PipeSymbols.OutputRedirection && pipeType !== PipeSymbols.AppendOutputRedirection) { - return handleIoError( - stdIO, - `Invalid pipe symbol '${pipeType}' for piping to file ${fileName}. Only > and >> are allowed.`, - ); - } - - // No output from writing to files - stdIO.stdout?.close(); - - if (hasTextExtension(fileName)) { - writeToTextFile(fileName, pipeType, stdIO); - } else if (hasScriptExtension(fileName)) { - writeToScriptFile(fileName, pipeType, stdIO); - } else { - return handleIoError(stdIO, `Invalid file extension for piping to file: ${fileName}`); - } -} - -function writeToTextFile(filename: string, pipeType: string, stdIO: StdIO) { - const filePath = Terminal.getFilepath(filename); - if (!filePath || !hasTextExtension(filePath)) { - return handleIoError(stdIO, `Invalid file path provided: ${filename}`); - } - if (!Terminal.getFile(filePath)) { - Player.getCurrentServer().writeToTextFile(filePath, ""); - } - - const file = Terminal.getTextFile(filePath); - const overwrite = pipeType === PipeSymbols.OutputRedirection; - - if (!file) { - return handleIoError(stdIO, `Failed to create text file for piping output: ${filePath}`); - } - - if (file?.content && overwrite) { - file.content = ""; - } - - void callOnRead(stdIO, (data: unknown) => { - const currentFile = Terminal.getTextFile(filePath); - if (!currentFile) { - return; - } - const output = stringify(data); - currentFile.content = concatenateFileContents(currentFile.content, output); - }); -} - -function writeToScriptFile(filename: string, pipeType: string, stdIO: StdIO): void { - const scriptPath = Terminal.getFilepath(filename); - if (!scriptPath || !hasScriptExtension(scriptPath)) { - return handleIoError(stdIO, `Invalid file path provided: ${filename}`); - } - const overwrite = pipeType === PipeSymbols.OutputRedirection; - - void callOnRead(stdIO, (data: unknown) => { - if (!Terminal.getScript(scriptPath)) { - Player.getCurrentServer().writeToScriptFile(scriptPath, ""); - } - const file = Terminal.getScript(scriptPath); - if (!file) { - return handleIoError(stdIO, `Failed to create script file for piping output: ${scriptPath}`); - } - if (file?.content && overwrite) { - return handleIoError( - stdIO, - `Overwriting non-empty script files is forbidden. Attempted to overwrite ${scriptPath}`, - ); - } - const output = stringify(data); - file.content = concatenateFileContents(file.content, output); - }); -} - -export async function callOnRead(stdIO: StdIO, callback: (data: unknown, stdIO: StdIO) => Promise | void) { - for await (const data of stdIO.read()) { - const streamIsCleared = stdIO.stdin?.deref()?.isClosed && stdIO.stdin?.deref()?.empty(); - if (data === null || streamIsCleared) { - return; - } - await callback(data, stdIO); - } -} - -function handleIoError(stdIO: StdIO, error: string) { - Terminal.error(error, stdIO); -} - -function isLongRunningCommand(commandSet: Args[]) { - const pipeSymbol = isPipeSymbol(commandSet[0]) ? `${commandSet[0]}` : null; - const command = `${pipeSymbol ? commandSet[1] : commandSet[0]}`; - return ["wget", "tail", "run"].includes(command) || !!resolveScriptFilePath(command); -} - -function concatenateFileContents(content: string, newContent: string): string { - const concatenatedContent = content + (content ? "\n" : "") + newContent; - const splitLines = concatenatedContent.split("\n"); - if (splitLines.length > Settings.MaxTerminalCapacity * 5) { - const truncatedFileContent = splitLines.slice(-Settings.MaxTerminalCapacity * 5).join("\n"); - return `(File truncated at ${Settings.MaxTerminalCapacity * 5} lines)\n${truncatedFileContent}`; - } - - return concatenatedContent; -} - -async function waitUntilClosed(stdio: StdIO): Promise { - while (stdio.stdout && !stdio.stdout?.isClosed) { - await stdio.stdout.nextWrite(); - } -} diff --git a/src/Terminal/StdIO/StdIO.ts b/src/Terminal/StdIO/StdIO.ts deleted file mode 100644 index 04a3684df..000000000 --- a/src/Terminal/StdIO/StdIO.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { IOStream } from "./IOStream"; -import { Terminal } from "../../Terminal"; -import { Output, RawOutput, Link } from "../OutputTypes"; -import { stringify } from "./utils"; - -let remaining = 0; -const registerStdIOInstance = (stdIO: StdIO) => { - const id = `StdIO-${Math.random().toString(16).slice(2)}`; - StdIORegistry.register(stdIO, id); - remaining++; - console.debug(`Created StdIO instance ${id}. Instances remaining: ${remaining}`); -}; -const StdIORegistry = new FinalizationRegistry((name: string) => { - remaining--; - console.debug(`StdIO instance ${name} has been garbage collected. Remaining instances: ${remaining}`); -}); - -export class StdIO { - stdin: WeakRef | null = null; - - stdout: IOStream | null; - - constructor(stdin: IOStream | null, stdout: IOStream | null = new IOStream()) { - if (stdin) { - this.stdin = new WeakRef(stdin); - } - this.stdout = stdout; - registerStdIOInstance(this); - } - - // Async iterator to read from stdin - async *[Symbol.asyncIterator]() { - const stdin = this.stdin?.deref(); - if (!stdin || (stdin.isClosed && stdin.empty())) { - return; - } - while (!stdin.isClosed || !stdin.empty()) { - if (stdin.empty() && !stdin.isClosed) { - await stdin.nextWrite(); - } - yield stdin.read(); - } - } - - // Read from stdin via the async iterator - read() { - return this[Symbol.asyncIterator](); - } - - getAllCurrentStdin(includeNewlines = true): string { - const stdin = this.stdin?.deref(); - if (!stdin) { - return ""; - } - const inputs: string[] = []; - while (!stdin.empty()) { - const input = stdin.read(); - if (input === null) { - break; - } - inputs.push(stringify(input)); - } - return inputs.map((i) => `${i}${includeNewlines ? "\n" : ""}`).join(""); - } - - write(data: unknown): unknown { - if (this.stdout) { - return this.stdout.write(stringify(data, true)); - } - // If there is no stdout, write to the terminal - if (data instanceof Output || data instanceof Link || data instanceof RawOutput) { - return Terminal.terminalOutput(data); - } - Terminal.printAndBypassPipes(stringify(data)); - } - - close(): void { - this.stdout?.close(); - this.stdin?.deref()?.close(); - } -} diff --git a/src/Terminal/StdIO/utils.tsx b/src/Terminal/StdIO/utils.tsx deleted file mode 100644 index 1ccd18eb7..000000000 --- a/src/Terminal/StdIO/utils.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import React, { isValidElement } from "react"; -import { renderToStaticMarkup } from "react-dom/server"; -import { Link, Output, RawOutput } from "../OutputTypes"; -import { ANSI_ESCAPE } from "../../ui/React/ANSIITypography"; -import { PortHandle, PortNumber } from "../../NetscriptPort"; -import { parseCommand } from "../Parser"; - -export type Args = string | number | boolean; - -export const PipeSymbols = { - Pipe: "|", - OutputRedirection: ">", - AppendOutputRedirection: ">>", - InputRedirection: "<", -} as const; - -export function isPipeSymbol(symbol: string | number | boolean): boolean { - return Object.keys(PipeSymbols).some((key) => PipeSymbols[key as keyof typeof PipeSymbols] === symbol); -} - -export function stringify(s: unknown, stripAnsiEscape = false): string { - if (s == null) { - return ""; - } else if (s instanceof Output) { - return clean(s.text, stripAnsiEscape); - } else if (s instanceof Link) { - return `${s.dashes} ${s.hostname}`; - } else if (s instanceof RawOutput) { - // TODO: test - return stringifyReactElement(s.raw); - } else if (isValidElement(s)) { - return stringifyReactElement(s); - } else if (s instanceof HTMLElement) { - return s.innerText; - } else if (typeof s === "string" || typeof s === "number" || typeof s === "boolean") { - return clean(s.toString(), stripAnsiEscape); - } else { - return clean(JSON.stringify(s), stripAnsiEscape); - } -} - -export function stringifyReactElement(element: React.ReactNode): string { - const markup = renderToStaticMarkup(<>{element}); - const div = document.createElement("div"); - div.innerHTML = markup.replaceAll(">", "> ").replaceAll("
", "\n"); - return (div.innerText ?? div.textContent ?? "").trim(); -} - -export function getCommandAfterLastPipe(commandString: string): string { - const parsedCommands = parseCommand(commandString); - const lastPipeIndex = parsedCommands.findLastIndex(isPipeSymbol); - if (lastPipeIndex === -1) { - return commandString; - } - - return parsedCommands.slice(lastPipeIndex + 1).join(" "); -} - -function clean(str: string, stripAnsiEscape: boolean) { - return stripAnsiEscape ? str.replaceAll(ANSI_ESCAPE, "") : str; -} - -let nextStdinPort = -1e7; -export function getNextStdinHandle(): PortHandle { - // port numbers for pipes are negative numbers to avoid collisions with standard player ns ports - return new PortHandle(nextStdinPort-- as PortNumber); -} diff --git a/src/Terminal/Terminal.ts b/src/Terminal/Terminal.ts index 121b4903d..91a5765e7 100644 --- a/src/Terminal/Terminal.ts +++ b/src/Terminal/Terminal.ts @@ -44,7 +44,6 @@ import { check } from "./commands/check"; import { connect } from "./commands/connect"; import { cp } from "./commands/cp"; import { download } from "./commands/download"; -import { echo } from "./commands/echo"; import { expr } from "./commands/expr"; import { free } from "./commands/free"; import { grep } from "./commands/grep"; @@ -87,15 +86,10 @@ import { hasTextExtension } from "../Paths/TextFilePath"; import { ContractFilePath } from "../Paths/ContractFilePath"; import { ServerConstants } from "../Server/data/Constants"; import { isIPAddress } from "../Types/strings"; -import { StdIO } from "./StdIO/StdIO"; -import { getTerminalStdIO, parseRedirectedCommands } from "./StdIO/RedirectIO"; import { getRewardFromCache } from "../DarkNet/effects/cacheFiles"; import { DarknetServer } from "../Server/DarknetServer"; -export const TerminalCommands: Record< - string, - (args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO) => void -> = { +export const TerminalCommands: Record void> = { "scan-analyze": scananalyze, alias: alias, analyze: analyze, @@ -110,7 +104,6 @@ export const TerminalCommands: Record< connect: connect, cp: cp, download: download, - echo: echo, expr: expr, free: free, grep: grep, @@ -146,7 +139,6 @@ export const TerminalCommands: Record< export class Terminal { // Flags to determine whether the player is currently running a hack or an analyze action: TTimer | null = null; - actionStdIO: StdIO | null = null; commandHistory: string[] = []; commandHistoryIndex = 0; @@ -161,16 +153,13 @@ export class Terminal { // Path of current directory currDir = "" as Directory; - // PID of the script run as part of the last executed command, if any - pidOfLastScriptRun: number | null = null; - process(cycles: number): void { if (this.action === null) return; this.action.timeLeft -= (CONSTANTS.MilliPerCycle * cycles) / 1000; if (this.action.timeLeft < 0.01) this.finishAction(false); } - terminalOutput(item: Output | Link | RawOutput): void { + append(item: Output | Link | RawOutput): void { this.outputHistory.push(item); if (this.outputHistory.length > Settings.MaxTerminalCapacity) { this.outputHistory.splice(0, this.outputHistory.length - Settings.MaxTerminalCapacity); @@ -178,36 +167,31 @@ export class Terminal { TerminalEvents.emit(); } - print(s: string, stdIO: StdIO = getTerminalStdIO(null)): void { - stdIO.write(s); + print(s: string): void { + this.append(new Output(s, "primary")); } - printRaw(node: React.ReactNode, stdIO: StdIO = getTerminalStdIO(null)): void { - stdIO.write(new RawOutput(node)); + printRaw(node: React.ReactNode): void { + this.append(new RawOutput(node)); } - printAndBypassPipes(s: string): void { - this.terminalOutput(new Output(s, "primary")); + error(s: string): void { + this.append(new Output(s, "error")); } - error(s: string, stdIO: StdIO | null = null): void { - stdIO?.close(); - this.terminalOutput(new Output(s, "error")); + success(s: string): void { + this.append(new Output(s, "success")); } - success(s: string, stdIO: StdIO = getTerminalStdIO(null)): void { - stdIO.write(new Output(s, "success")); + info(s: string): void { + this.append(new Output(s, "info")); } - info(s: string, stdIO: StdIO = getTerminalStdIO(null)): void { - stdIO.write(new Output(s, "info")); + warn(s: string): void { + this.append(new Output(s, "warn")); } - warn(s: string, stdIO: StdIO = getTerminalStdIO(null)): void { - stdIO.write(new Output(s, "warn")); - } - - startHack(stdIO: StdIO): void { + startHack(): void { // Hacking through Terminal should be faster than hacking through a script const server = Player.getCurrentServer(); if (server instanceof HacknetServer) { @@ -215,49 +199,48 @@ export class Terminal { return; } if (!(server instanceof Server)) throw new Error("server should be normal server"); - this.startAction(calculateHackingTime(server, Player) / 4, "h", stdIO, server); + this.startAction(calculateHackingTime(server, Player) / 4, "h", server); } - startGrow(stdIO: StdIO): void { + startGrow(): void { const server = Player.getCurrentServer(); if (server instanceof HacknetServer) { this.error("Cannot grow this kind of server"); return; } if (!(server instanceof Server)) throw new Error("server should be normal server"); - this.startAction(calculateGrowTime(server, Player) / 16, "g", stdIO, server); + this.startAction(calculateGrowTime(server, Player) / 16, "g", server); } - startWeaken(stdIO: StdIO): void { + startWeaken(): void { const server = Player.getCurrentServer(); if (server instanceof HacknetServer) { this.error("Cannot weaken this kind of server"); return; } if (!(server instanceof Server)) throw new Error("server should be normal server"); - this.startAction(calculateWeakenTime(server, Player) / 16, "w", stdIO, server); + this.startAction(calculateWeakenTime(server, Player) / 16, "w", server); } - startBackdoor(stdIO: StdIO): void { + startBackdoor(): void { // Backdoor should take the same amount of time as hack const server = Player.getCurrentServer(); if (server instanceof HacknetServer) { - this.error("Cannot backdoor this kind of server", stdIO); + this.error("Cannot backdoor this kind of server"); return; } if (!(server instanceof Server || server instanceof DarknetServer)) throw new Error("server should be normal server"); - this.startAction(calculateHackingTime(server, Player) / 4, "b", stdIO, server); + this.startAction(calculateHackingTime(server, Player) / 4, "b", server); } - startAnalyze(stdIO: StdIO): void { - this.print("Analyzing system...", stdIO); + startAnalyze(): void { + this.print("Analyzing system..."); const server = Player.getCurrentServer(); - this.startAction(1, "a", stdIO, server); + this.startAction(1, "a", server); } - startAction(n: number, action: "h" | "b" | "a" | "g" | "w" | "c", stdIO: StdIO, server?: BaseServer): void { + startAction(n: number, action: "h" | "b" | "a" | "g" | "w" | "c", server?: BaseServer): void { this.action = new TTimer(n, action, server); - this.actionStdIO = stdIO; } // Complete the hack/analyze command @@ -269,9 +252,6 @@ export class Terminal { return; } if (!(server instanceof Server)) throw new Error("server should be normal server"); - if (!this.actionStdIO) { - throw new Error("Missing stdIO for hack action"); - } // Calculate whether hack was successful const hackChance = calculateHackingChance(server, Player); @@ -323,36 +303,25 @@ export class Terminal { `Hack successful on '${server.hostname}'! Gained ${formatMoney(moneyGained, true)} and ${formatExp( expGainedOnSuccess, )} hacking exp`, - this.actionStdIO, ); this.print( `Security increased on '${server.hostname}' from ${formatSecurity(oldSec)} to ${formatSecurity(newSec)}`, - this.actionStdIO, ); } else { // Failure Player.gainHackingExp(expGainedOnFailure); - this.print( - `Failed to hack '${server.hostname}'. Gained ${formatExp(expGainedOnFailure)} hacking exp`, - this.actionStdIO, - ); + this.print(`Failed to hack '${server.hostname}'. Gained ${formatExp(expGainedOnFailure)} hacking exp`); } - this.actionStdIO.close(); - this.actionStdIO = null; } finishGrow(server: BaseServer, cancelled = false): void { if (cancelled) return; if (server instanceof HacknetServer) { - this.error("Cannot grow this kind of server", this.actionStdIO); + this.error("Cannot grow this kind of server"); return; } if (!(server instanceof Server)) throw new Error("server should be normal server"); - if (!this.actionStdIO) { - throw new Error("Missing stdIO for grow action"); - } - const expGain = calculateHackingExpGain(server, Player); const oldSec = server.hackDifficulty; const growth = processSingleServerGrowth(server, 25, server.cpuCores); @@ -363,27 +332,20 @@ export class Terminal { `Available money on '${server.hostname}' grown by ${formatPercent(growth - 1, 6)}. Gained ${formatExp( expGain, )} hacking exp.`, - this.actionStdIO, ); this.print( `Security increased on '${server.hostname}' from ${formatSecurity(oldSec)} to ${formatSecurity(newSec)}`, - this.actionStdIO, ); - this.actionStdIO.close(); - this.actionStdIO = null; } finishWeaken(server: BaseServer, cancelled = false): void { if (cancelled) return; if (server instanceof HacknetServer) { - this.error("Cannot weaken this kind of server", this.actionStdIO); + this.error("Cannot weaken this kind of server"); return; } if (!(server instanceof Server)) throw new Error("server should be normal server"); - if (!this.actionStdIO) { - throw new Error("Missing stdIO for weaken action"); - } const expGain = calculateHackingExpGain(server, Player); const oldSec = server.hackDifficulty; const weakenAmt = getWeakenEffect(1, server.cpuCores); @@ -396,24 +358,17 @@ export class Terminal { oldSec, )} to ${formatSecurity(newSec)} (min: ${formatSecurity(server.minDifficulty)})` + ` and Gained ${formatExp(expGain)} hacking exp.`, - this.actionStdIO, ); - this.actionStdIO.close(); - this.actionStdIO = null; } finishBackdoor(server: BaseServer, cancelled = false): void { if (!cancelled) { if (server instanceof HacknetServer) { - this.error("Cannot hack this kind of server", this.actionStdIO); + this.error("Cannot hack this kind of server"); return; } if (!(server instanceof Server || server instanceof DarknetServer)) throw new Error("server should be normal server"); - if (!this.actionStdIO) { - throw new Error("Missing stdIO for backdoor action"); - } - server.backdoorInstalled = true; if (SpecialServers.WorldDaemon === server.hostname) { if (Player.bitNodeN == null) { @@ -426,65 +381,51 @@ export class Terminal { Engine.Counters.checkFactionInvitations = 0; Engine.checkCounters(); - this.print(`Backdoor on '${server.hostname}' successful!`, this.actionStdIO); - this.actionStdIO.close(); - this.actionStdIO = null; + this.print(`Backdoor on '${server.hostname}' successful!`); } } finishAnalyze(currServ: BaseServer, cancelled = false): void { if (!cancelled) { - if (!this.actionStdIO) { - throw new Error("Missing stdIO for analyze action"); - } const isHacknet = currServ instanceof HacknetServer; - this.print(currServ.hostname + ": ", this.actionStdIO); + this.print(currServ.hostname + ": "); const org = currServ.organizationName; - this.print("Organization name: " + (!isHacknet ? org : "player"), this.actionStdIO); + this.print("Organization name: " + (!isHacknet ? org : "player")); const hasAdminRights = (!isHacknet && currServ.hasAdminRights) || isHacknet; - this.print("Root Access: " + (hasAdminRights ? "YES" : "NO"), this.actionStdIO); + this.print("Root Access: " + (hasAdminRights ? "YES" : "NO")); const canRunScripts = hasAdminRights && currServ.maxRam > 0; - this.print("Can run scripts on this host: " + (canRunScripts ? "YES" : "NO"), this.actionStdIO); - this.print("RAM: " + formatRam(currServ.maxRam), this.actionStdIO); + this.print("Can run scripts on this host: " + (canRunScripts ? "YES" : "NO")); + this.print("RAM: " + formatRam(currServ.maxRam)); if (currServ instanceof DarknetServer && currServ.blockedRam) { - this.print("RAM blocked by owner: " + formatRam(currServ.blockedRam), this.actionStdIO); - this.print("Stasis link: " + (currServ.hasStasisLink ? "YES" : "NO"), this.actionStdIO); - this.print("Backdoor: " + (currServ.backdoorInstalled ? "YES" : "NO"), this.actionStdIO); + this.print("RAM blocked by owner: " + formatRam(currServ.blockedRam)); + this.print("Stasis link: " + (currServ.hasStasisLink ? "YES" : "NO")); + this.print("Backdoor: " + (currServ.backdoorInstalled ? "YES" : "NO")); } if (currServ instanceof Server) { - this.print("Backdoor: " + (currServ.backdoorInstalled ? "YES" : "NO"), this.actionStdIO); + this.print("Backdoor: " + (currServ.backdoorInstalled ? "YES" : "NO")); const hackingSkill = currServ.requiredHackingSkill; - this.print( - "Required hacking skill for hack() and backdoor: " + (!isHacknet ? hackingSkill : "N/A"), - this.actionStdIO, - ); + this.print("Required hacking skill for hack() and backdoor: " + (!isHacknet ? hackingSkill : "N/A")); const security = currServ.hackDifficulty; - this.print("Server security level: " + (!isHacknet ? formatSecurity(security) : "N/A"), this.actionStdIO); + this.print("Server security level: " + (!isHacknet ? formatSecurity(security) : "N/A")); const hackingChance = calculateHackingChance(currServ, Player); - this.print("Chance to hack: " + (!isHacknet ? formatPercent(hackingChance) : "N/A"), this.actionStdIO); + this.print("Chance to hack: " + (!isHacknet ? formatPercent(hackingChance) : "N/A")); const hackingTime = calculateHackingTime(currServ, Player) * 1000; - this.print( - "Time to hack: " + (!isHacknet ? convertTimeMsToTimeElapsedString(hackingTime, true) : "N/A"), - this.actionStdIO, - ); + this.print("Time to hack: " + (!isHacknet ? convertTimeMsToTimeElapsedString(hackingTime, true) : "N/A")); } this.print( `Total money available on server: ${ currServ instanceof Server ? formatMoney(currServ.moneyAvailable, true) : "N/A" }`, - this.actionStdIO, ); if (currServ instanceof Server) { const numPort = currServ.numOpenPortsRequired; - this.print("Required number of open ports for NUKE: " + (!isHacknet ? numPort : "N/A"), this.actionStdIO); - this.print("SSH port: " + (currServ.sshPortOpen ? "Open" : "Closed"), this.actionStdIO); - this.print("FTP port: " + (currServ.ftpPortOpen ? "Open" : "Closed"), this.actionStdIO); - this.print("SMTP port: " + (currServ.smtpPortOpen ? "Open" : "Closed"), this.actionStdIO); - this.print("HTTP port: " + (currServ.httpPortOpen ? "Open" : "Closed"), this.actionStdIO); - this.print("SQL port: " + (currServ.sqlPortOpen ? "Open" : "Closed"), this.actionStdIO); + this.print("Required number of open ports for NUKE: " + (!isHacknet ? numPort : "N/A")); + this.print("SSH port: " + (currServ.sshPortOpen ? "Open" : "Closed")); + this.print("FTP port: " + (currServ.ftpPortOpen ? "Open" : "Closed")); + this.print("SMTP port: " + (currServ.smtpPortOpen ? "Open" : "Closed")); + this.print("HTTP port: " + (currServ.httpPortOpen ? "Open" : "Closed")); + this.print("SQL port: " + (currServ.sqlPortOpen ? "Open" : "Closed")); } - this.actionStdIO.close(); - this.actionStdIO = null; } } @@ -495,11 +436,8 @@ export class Terminal { } if (!this.action.server) throw new Error("Missing action target server"); - if (!this.actionStdIO) { - throw new Error("Missing stdIO for action"); - } - this.print(this.getProgressText(), this.actionStdIO); + this.print(this.getProgressText()); if (this.action.action === "h") { this.finishHack(this.action.server, cancelled); } else if (this.action.action === "g") { @@ -521,11 +459,9 @@ export class Terminal { } if (cancelled) { - this.print("Cancelled", this.actionStdIO); + this.print("Cancelled"); } this.action = null; - this.actionStdIO.close(); - this.actionStdIO = null; TerminalEvents.emit(); } @@ -588,16 +524,16 @@ export class Terminal { TerminalEvents.emit(); } - async runContract(contractPath: ContractFilePath, stdIO: StdIO): Promise { + async runContract(contractPath: ContractFilePath): Promise { // There's already an opened contract if (this.contractOpen) { - return this.error("There's already a Coding Contract in Progress", stdIO); + return this.error("There's already a Coding Contract in Progress"); } const server = Player.getCurrentServer(); const contract = server.getContract(contractPath); if (!contract) { - return this.error("No such contract", stdIO); + return this.error("No such contract"); } this.contractOpen = true; @@ -609,14 +545,14 @@ export class Terminal { // Check if the contract still exists by the time the promise is fulfilled if (postPromptServer?.getContract(contractPath) == null) { this.contractOpen = false; - return this.error("Contract no longer exists (Was it solved by a script?)", stdIO); + return this.error("Contract no longer exists (Was it solved by a script?)"); } switch (promptResult.result) { case CodingContractResult.Success: if (contract.reward !== null) { const reward = Player.gainCodingContractReward(contract.reward, contract.getDifficulty()); - this.print(`Contract SUCCESS - ${reward}`, stdIO); + this.print(`Contract SUCCESS - ${reward}`); } server.removeContract(contract); break; @@ -625,24 +561,23 @@ export class Terminal { `Contract FAILED - ${ promptResult.message ?? `The answer is not in the right format for contract '${contract.type}'` }`, - stdIO, ); break; case CodingContractResult.Failure: ++contract.tries; if (contract.tries >= contract.getMaxNumTries()) { - this.error("Contract FAILED - Contract is now self-destructing", stdIO); + this.error("Contract FAILED - Contract is now self-destructing"); const solution = contract.getAnswer(); if (solution !== null) { - this.error(`Coding Contract solution was: ${solution}`, stdIO); + this.error(`Coding Contract solution was: ${solution}`); } server.removeContract(contract); } else { - this.error(`Contract FAILED - ${contract.getMaxNumTries() - contract.tries} tries remaining`, stdIO); + this.error(`Contract FAILED - ${contract.getMaxNumTries() - contract.tries} tries remaining`); } break; case CodingContractResult.Cancelled: - this.print("Contract cancelled", stdIO); + this.print("Contract cancelled"); break; default: { const __: never = promptResult.result; @@ -651,7 +586,7 @@ export class Terminal { this.contractOpen = false; } - executeScanAnalyzeCommand(depth = 1, all = false, stdIO: StdIO): void { + executeScanAnalyzeCommand(depth = 1, all = false): void { interface Node { hostname: string; children: Node[]; @@ -683,13 +618,13 @@ export class Terminal { const root = makeNode(); - const printOutput = (node: Node, stdIO: StdIO, prefix = [" "], last = true) => { + const printOutput = (node: Node, prefix = [" "], last = true) => { const titlePrefix = prefix.slice(0, prefix.length - 1).join("") + (last ? "┗ " : "┣ "); const infoPrefix = prefix.join("") + (node.children.length > 0 ? "┃ " : " "); if (Player.hasProgram(CompletedProgramName.autoLink)) { - this.printRaw(new Link(titlePrefix, node.hostname), stdIO); + this.append(new Link(titlePrefix, node.hostname)); } else { - this.print(titlePrefix + node.hostname + "\n", stdIO); + this.print(titlePrefix + node.hostname + "\n"); } const server = GetServer(node.hostname); @@ -698,24 +633,18 @@ export class Terminal { if (server instanceof Server) { this.print( `${infoPrefix}Root Access: ${hasRoot}, Required hacking skill: ${server.requiredHackingSkill}` + "\n", - stdIO, ); - this.print(`${infoPrefix}Number of open ports required to NUKE: ${server.numOpenPortsRequired}` + "\n", stdIO); + this.print(`${infoPrefix}Number of open ports required to NUKE: ${server.numOpenPortsRequired}` + "\n"); } else { - this.print(`${infoPrefix}Root Access: ${hasRoot}` + "\n", stdIO); + this.print(`${infoPrefix}Root Access: ${hasRoot}` + "\n"); } - this.print(`${infoPrefix}RAM: ${formatRam(server.maxRam)}` + "\n", stdIO); + this.print(`${infoPrefix}RAM: ${formatRam(server.maxRam)}` + "\n"); node.children.forEach((n, i) => - printOutput( - n, - stdIO, - [...prefix, i === node.children.length - 1 ? " " : "┃ "], - i === node.children.length - 1, - ), + printOutput(n, [...prefix, i === node.children.length - 1 ? " " : "┃ "], i === node.children.length - 1), ); }; - printOutput(root, stdIO); + printOutput(root); } connectToServer(hostname: string, singularity = false): void { @@ -729,14 +658,14 @@ export class Terminal { server.isConnectedTo = true; this.setcwd(root); if (!singularity) { - this.printAndBypassPipes("Connected to " + `${isIPAddress(hostname) ? server.ip : server.hostname}`); + this.print("Connected to " + `${isIPAddress(hostname) ? server.ip : server.hostname}`); if (Player.getCurrentServer().hostname === "darkweb") { checkIfConnectedToDarkweb(); // Posts a 'help' message if connecting to dark web } } } - async executeCommands(commands: string): Promise { + executeCommands(commands: string): void { // Handle Terminal History - multiple commands should be saved as one if (this.commandHistory[this.commandHistory.length - 1] != commands) { this.commandHistory.push(commands); @@ -747,9 +676,7 @@ export class Terminal { } this.commandHistoryIndex = this.commandHistory.length; const allCommands = parseCommands(commands); - for (const command of allCommands) { - await parseRedirectedCommands(command); - } + for (const command of allCommands) this.executeCommand(command); } clear(): void { @@ -763,9 +690,8 @@ export class Terminal { this.clear(); } - executeCommand(command: string, stdIO: StdIO): void { - if (this.action !== null) - return this.error(`Cannot execute command (${command}) while an action is in progress`, stdIO); + executeCommand(command: string): void { + if (this.action !== null) return this.error(`Cannot execute command (${command}) while an action is in progress`); const commandArray = parseCommand(command); if (!commandArray.length) return; @@ -918,9 +844,9 @@ export class Terminal { /* Command parser */ const commandName = commandArray[0]; - if (typeof commandName !== "string") return this.error(`${commandName} is not a valid command.`, stdIO); + if (typeof commandName !== "string") return this.error(`${commandName} is not a valid command.`); // run by path command - if (isBasicFilePath(commandName)) return run(commandArray, currentServer, stdIO); + if (isBasicFilePath(commandName)) return run(commandArray, currentServer); // Aside from the run-by-path command, we don't need the first entry once we've stored it in commandName. commandArray.shift(); @@ -929,18 +855,10 @@ export class Terminal { if (!f) { const similarCommands = findSimilarCommands(commandName); const didYouMeanString = similarCommands.length ? ` Did you mean: ${similarCommands.join(" or ")}?` : ""; - return this.error(`Command ${commandName} not found.${didYouMeanString}`, stdIO); + return this.error(`Command ${commandName} not found.${didYouMeanString}`); } - f(commandArray, currentServer, stdIO); - - if (commandName.toLowerCase() !== "run") { - this.pidOfLastScriptRun = null; - } - - if (!this.action && !["wget", "run", "cat", "grep", "tail"].includes(commandName.toLowerCase())) { - stdIO.close(); - } + f(commandArray, currentServer); } getProgressText(): string { diff --git a/src/Terminal/commands/alias.ts b/src/Terminal/commands/alias.ts index 40cf0628e..74ba60861 100644 --- a/src/Terminal/commands/alias.ts +++ b/src/Terminal/commands/alias.ts @@ -1,30 +1,28 @@ import { Terminal } from "../../Terminal"; import { parseAliasDeclaration, printAliases } from "../../Alias"; -import { BaseServer } from "../../Server/BaseServer"; -import { StdIO } from "../StdIO/StdIO"; -export function alias(args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { +export function alias(args: (string | number | boolean)[]): void { if (args.length === 0) { printAliases(); return; } if (args[0] === "--all") { - Terminal.error(`--all is reserved for removal`, stdIO); + Terminal.error(`--all is reserved for removal`); return; } if (args.length === 1) { if (parseAliasDeclaration(args[0] + "")) { - Terminal.printAndBypassPipes(`Set alias ${args[0]}`); + Terminal.print(`Set alias ${args[0]}`); return; } } if (args.length === 2) { if (args[0] === "-g") { if (parseAliasDeclaration(args[1] + "", true)) { - Terminal.printAndBypassPipes(`Set global alias ${args[1]}`); + Terminal.print(`Set global alias ${args[1]}`); return; } } } - Terminal.error('Incorrect usage of alias command. Usage: alias [-g] [aliasname="value"]', stdIO); + Terminal.error('Incorrect usage of alias command. Usage: alias [-g] [aliasname="value"]'); } diff --git a/src/Terminal/commands/analyze.ts b/src/Terminal/commands/analyze.ts index efdae4d2a..674b1db76 100644 --- a/src/Terminal/commands/analyze.ts +++ b/src/Terminal/commands/analyze.ts @@ -1,11 +1,9 @@ import { Terminal } from "../../Terminal"; -import { StdIO } from "../StdIO/StdIO"; -import { BaseServer } from "../../Server/BaseServer"; -export function analyze(args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { +export function analyze(args: (string | number | boolean)[]): void { if (args.length !== 0) { - Terminal.error("Incorrect usage of analyze command. Usage: analyze", stdIO); + Terminal.error("Incorrect usage of analyze command. Usage: analyze"); return; } - Terminal.startAnalyze(stdIO); + Terminal.startAnalyze(); } diff --git a/src/Terminal/commands/backdoor.ts b/src/Terminal/commands/backdoor.ts index 83af232d2..5edf59200 100644 --- a/src/Terminal/commands/backdoor.ts +++ b/src/Terminal/commands/backdoor.ts @@ -2,34 +2,31 @@ import { Terminal } from "../../Terminal"; import { Player } from "@player"; import { BaseServer } from "../../Server/BaseServer"; import { Server } from "../../Server/Server"; -import { StdIO } from "../StdIO/StdIO"; import { DarknetServer } from "../../Server/DarknetServer"; -export function backdoor(args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { +export function backdoor(args: (string | number | boolean)[], server: BaseServer): void { if (args.length !== 0) { - Terminal.error("Incorrect usage of backdoor command. Usage: backdoor", stdIO); + Terminal.error("Incorrect usage of backdoor command. Usage: backdoor"); return; } if (!(server instanceof Server) && !(server instanceof DarknetServer)) { - Terminal.error("Can only install a backdoor on normal servers", stdIO); + Terminal.error("Can only install a backdoor on normal servers"); return; } if (server.purchasedByPlayer) { Terminal.error( "Cannot install a backdoor on your own machines! You are currently connected to your home PC or one of your cloud servers.", - stdIO, ); return; } if (!server.hasAdminRights) { - Terminal.error("You do not have admin rights for this machine!", stdIO); + Terminal.error("You do not have admin rights for this machine!"); return; } if (server.requiredHackingSkill && server.requiredHackingSkill > Player.skills.hacking) { Terminal.error( "Your hacking skill is not high enough to install a backdoor on this machine. Try analyzing the machine to determine the required hacking skill.", - stdIO, ); return; } @@ -37,9 +34,8 @@ export function backdoor(args: (string | number | boolean)[], server: BaseServer if (server.backdoorInstalled) { Terminal.warn( `You have already installed a backdoor on this server. You can check the "Backdoor" status via the "analyze" command.`, - stdIO, ); } - Terminal.startBackdoor(stdIO); + Terminal.startBackdoor(); } diff --git a/src/Terminal/commands/buy.ts b/src/Terminal/commands/buy.ts index d6bb35b7a..d8682cb0f 100644 --- a/src/Terminal/commands/buy.ts +++ b/src/Terminal/commands/buy.ts @@ -1,26 +1,23 @@ import { Terminal } from "../../Terminal"; import { Player } from "@player"; import { listAllDarkwebItems, buyAllDarkwebItems, buyDarkwebItem } from "../../DarkWeb/DarkWeb"; -import { StdIO } from "../StdIO/StdIO"; -import { BaseServer } from "../../Server/BaseServer"; -export function buy(args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { +export function buy(args: (string | number | boolean)[]): void { if (!Player.hasTorRouter()) { Terminal.error( `You need to be able to connect to the Dark Web to use the "buy" command. (Maybe there's a TOR router you can buy somewhere)`, - stdIO, ); return; } if (args.length != 1) { - Terminal.print("Incorrect number of arguments. Usage: ", stdIO); - Terminal.print("buy -l", stdIO); - Terminal.print("buy -a", stdIO); - Terminal.print("buy [item name]", stdIO); + Terminal.print("Incorrect number of arguments. Usage: "); + Terminal.print("buy -l"); + Terminal.print("buy -a"); + Terminal.print("buy [item name]"); return; } const arg = args[0] + ""; - if (arg == "-l" || arg == "-1" || arg == "--list") listAllDarkwebItems(stdIO); - else if (arg == "-a" || arg == "--all") buyAllDarkwebItems(stdIO); - else buyDarkwebItem(arg, stdIO); + if (arg == "-l" || arg == "-1" || arg == "--list") listAllDarkwebItems(); + else if (arg == "-a" || arg == "--all") buyAllDarkwebItems(); + else buyDarkwebItem(arg); } diff --git a/src/Terminal/commands/cat.ts b/src/Terminal/commands/cat.ts index 657560bc5..ce4c53ca6 100644 --- a/src/Terminal/commands/cat.ts +++ b/src/Terminal/commands/cat.ts @@ -1,144 +1,36 @@ import { Terminal } from "../../Terminal"; import { BaseServer } from "../../Server/BaseServer"; -import { Messages, showMessage } from "../../Message/MessageHelpers"; +import { showMessage } from "../../Message/MessageHelpers"; +import { showLiterature } from "../../Literature/LiteratureHelpers"; +import { dialogBoxCreate } from "../../ui/React/DialogBox"; import { hasScriptExtension } from "../../Paths/ScriptFilePath"; import { hasTextExtension } from "../../Paths/TextFilePath"; import { isMember } from "../../utils/EnumHelper"; -import { StdIO } from "../StdIO/StdIO"; -import { Literatures } from "../../Literature/Literatures"; -import { LiteratureName, MessageFilename } from "@enums"; -import { callOnRead } from "../StdIO/RedirectIO"; -import { stringify } from "../StdIO/utils"; -import { showLiterature } from "../../Literature/LiteratureHelpers"; -import { dialogBoxCreate } from "../../ui/React/DialogBox"; -export function cat(args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { - const initialStdIn = stdIO.getAllCurrentStdin(false); - const stdin = stdIO.stdin?.deref(); - const stdinIsClosed = !stdin || (stdin.isClosed && stdin.empty()); - const hasStdOut = !!stdIO.stdout; +export function cat(args: (string | number | boolean)[], server: BaseServer): void { + if (args.length !== 1) return Terminal.error("Incorrect usage of cat command. Usage: cat [file]"); - if (args.length === 0 && initialStdIn.length === 0 && stdinIsClosed) { - return Terminal.error( - `Incorrect use of cat command: No files specified, and no stdin provided. Try "cat [filename]"`, - stdIO, - ); - } - if (!validateFilenames(args, server, stdIO)) { - return; - } - - // If only a single file is being catted, and no stdin/stdout redirects are being used, show the file dialog - if (args.length === 1 && args[0] !== "-" && !initialStdIn.length && stdinIsClosed && !hasStdOut) { - return showFileContentDialog(String(args[0]), server, stdIO); - } - - const output = concatenateFileContents(args, server, initialStdIn); - - stdIO.write(output); - - if (stdinIsClosed) { - stdIO.close(); - } else { - void callOnRead(stdIO, (data: unknown, stdInOut) => { - stdInOut.write(stringify(data)); - }); - } -} - -export function concatenateFileContents( - filenames: (string | number | boolean)[], - server: BaseServer, - initialStdin: string, -): string { - let result = ""; - for (const arg of filenames) { - const filename = String(arg); - if (filename === "-") { - result += initialStdin; - } else { - result += getFileContents(filename, server); - } - } - if (!filenames.find((arg) => arg === "-")) { - // If stdin location is not specified, append it to the end by default - result += initialStdin; - } - return result; -} - -export function getFileContents(filename: string, server: BaseServer): string { - const path = Terminal.getFilepath(filename); - if (!path) return ""; + const relative_filename = args[0] + ""; + const path = Terminal.getFilepath(relative_filename); + if (!path) return Terminal.error(`Invalid filename: ${relative_filename}`); if (hasScriptExtension(path) || hasTextExtension(path)) { const file = server.getContentFile(path); - if (!file) return ""; - return file.content ?? ""; - } - if (isMember("MessageFilename", path) && server.messages.includes(path)) { - return stringify(Messages[path as MessageFilename].msg) + "\n"; - } - if (isMember("LiteratureName", path) && server.messages.includes(path)) { - const lit = Literatures[path as LiteratureName]; - return `${lit.title}\n\n${stringify(lit.text)}\n`; - } - return ""; -} - -function showFileContentDialog(filename: string, server: BaseServer, stdIO: StdIO) { - const path = Terminal.getFilepath(filename); - if (!path) return Terminal.error(`Invalid filename: ${filename}`, stdIO); - - if (hasScriptExtension(path) || hasTextExtension(path)) { - const file = server.getContentFile(path); - if (!file) return Terminal.error(`No file at path ${path}`, stdIO); + if (!file) return Terminal.error(`No file at path ${path}`); return dialogBoxCreate(`${file.filename}\n\n${file.content}`); } - if (isMember("MessageFilename", path) && server.messages.includes(path)) { - return showMessage(path); + if (!path.endsWith(".msg") && !path.endsWith(".lit")) { + return Terminal.error( + "Invalid file extension. Filename must end with .msg, .lit, a script extension (.js, .jsx, .ts, .tsx) or a text extension (.txt, .json, .css)", + ); } - if (isMember("LiteratureName", path) && server.messages.includes(path)) { - return showLiterature(path); - } -} -export function validateFilenames(filenames: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): boolean { - for (const filename of filenames) { - if (filename === "-") continue; - if (typeof filename !== "string") { - Terminal.error(`Invalid filename: ${filename}`, stdIO); - return false; - } - const path = Terminal.getFilepath(filename); - if (!path) { - Terminal.error(`Invalid filename: ${filename}`, stdIO); - return false; - } - - if (hasScriptExtension(path) || hasTextExtension(path)) { - const file = server.getContentFile(path); - if (!file) { - Terminal.error(`No file at path ${path}`, stdIO); - return false; - } - } else if (path.endsWith(".msg")) { - if (!isMember("MessageFilename", path) || !server.messages.includes(path)) { - Terminal.error(`No file at path ${path}`, stdIO); - return false; - } - } else if (path.endsWith(".lit")) { - if (!isMember("LiteratureName", path) || !server.messages.includes(path)) { - Terminal.error(`No file at path ${path}`, stdIO); - return false; - } - } else { - Terminal.error( - "Invalid file extension. Filename must end with .msg, .lit, a script extension (.js, .jsx, .ts, .tsx) or a text extension (.txt, .json, .css)", - stdIO, - ); - return false; - } + // Message + if (isMember("MessageFilename", path)) { + if (server.messages.includes(path)) return showMessage(path); } - return true; + if (isMember("LiteratureName", path)) { + if (server.messages.includes(path)) return showLiterature(path); + } + Terminal.error(`No file at path ${path}`); } diff --git a/src/Terminal/commands/cd.ts b/src/Terminal/commands/cd.ts index 466f9fb00..3dd182452 100644 --- a/src/Terminal/commands/cd.ts +++ b/src/Terminal/commands/cd.ts @@ -1,16 +1,14 @@ import { Terminal } from "../../Terminal"; import { BaseServer } from "../../Server/BaseServer"; import { directoryExistsOnServer, resolveDirectory } from "../../Paths/Directory"; -import { StdIO } from "../StdIO/StdIO"; -export function cd(args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { - if (args.length > 1) return Terminal.error("Incorrect number of arguments. Usage: cd [dir]", stdIO); +export function cd(args: (string | number | boolean)[], server: BaseServer): void { + if (args.length > 1) return Terminal.error("Incorrect number of arguments. Usage: cd [dir]"); // If no arg was provided, just use "/". const userInput = String(args[0] ?? "/"); const targetDir = resolveDirectory(userInput, Terminal.currDir); // Explicitly checking null due to root being "" - if (targetDir === null) return Terminal.error(`Could not resolve directory ${userInput}`, stdIO); - if (!directoryExistsOnServer(targetDir, server)) - return Terminal.error(`Directory ${targetDir} does not exist.`, stdIO); + if (targetDir === null) return Terminal.error(`Could not resolve directory ${userInput}`); + if (!directoryExistsOnServer(targetDir, server)) return Terminal.error(`Directory ${targetDir} does not exist.`); Terminal.setcwd(targetDir); } diff --git a/src/Terminal/commands/check.ts b/src/Terminal/commands/check.ts index 75d7df4be..e79858249 100644 --- a/src/Terminal/commands/check.ts +++ b/src/Terminal/commands/check.ts @@ -2,29 +2,28 @@ import { Terminal } from "../../Terminal"; import { BaseServer } from "../../Server/BaseServer"; import { findRunningScripts } from "../../Script/ScriptHelpers"; import { hasScriptExtension, validScriptExtensions } from "../../Paths/ScriptFilePath"; -import { StdIO } from "../StdIO/StdIO"; -export function check(args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { +export function check(args: (string | number | boolean)[], server: BaseServer): void { if (args.length < 1) { - Terminal.error(`Incorrect number of arguments. Usage: check [script] [arg1] [arg2]...`, stdIO); + Terminal.error(`Incorrect number of arguments. Usage: check [script] [arg1] [arg2]...`); } else { const scriptName = Terminal.getFilepath(args[0] + ""); - if (!scriptName) return Terminal.error(`Invalid filename: ${args[0]}`, stdIO); + if (!scriptName) return Terminal.error(`Invalid filename: ${args[0]}`); // Can only tail script files if (!hasScriptExtension(scriptName)) { - return Terminal.error(`check: File extension must be one of ${validScriptExtensions.join(", ")})`, stdIO); + return Terminal.error(`check: File extension must be one of ${validScriptExtensions.join(", ")})`); } // Check that the script is running on this machine const runningScripts = findRunningScripts(scriptName, args.slice(1), server); if (runningScripts === null) { - Terminal.error(`No script named ${scriptName} is running on the server`, stdIO); + Terminal.error(`No script named ${scriptName} is running on the server`); return; } const next = runningScripts.values().next(); if (!next.done) { - next.value.displayLog(stdIO); + next.value.displayLog(); } } } diff --git a/src/Terminal/commands/connect.ts b/src/Terminal/commands/connect.ts index bb576620c..e561171bc 100644 --- a/src/Terminal/commands/connect.ts +++ b/src/Terminal/commands/connect.ts @@ -3,12 +3,11 @@ import { BaseServer } from "../../Server/BaseServer"; import { getServerOnNetwork } from "../../Server/ServerHelpers"; import { GetServer } from "../../Server/AllServers"; import { exceptionAlert } from "../../utils/helpers/exceptionAlert"; -import { StdIO } from "../StdIO/StdIO"; -export function connect(args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { +export function connect(args: (string | number | boolean)[], server: BaseServer): void { // Disconnect from current server in Terminal and connect to new one if (args.length !== 1) { - Terminal.error("Incorrect usage of connect command. Usage: connect [hostname]", stdIO); + Terminal.error("Incorrect usage of connect command. Usage: connect [hostname]"); return; } @@ -16,7 +15,7 @@ export function connect(args: (string | number | boolean)[], server: BaseServer, const target = GetServer(hostname); if (target === null) { - Terminal.error(`Invalid hostname: '${hostname}'`, stdIO); + Terminal.error(`Invalid hostname: '${hostname}'`); return; } @@ -48,6 +47,5 @@ export function connect(args: (string | number | boolean)[], server: BaseServer, Terminal.error( `Cannot directly connect to ${hostname}. Make sure the server is backdoored or adjacent to your current server`, - stdIO, ); } diff --git a/src/Terminal/commands/cp.ts b/src/Terminal/commands/cp.ts index 7394e182d..ade51e545 100644 --- a/src/Terminal/commands/cp.ts +++ b/src/Terminal/commands/cp.ts @@ -3,20 +3,19 @@ import { BaseServer } from "../../Server/BaseServer"; import { combinePath, getFilenameOnly } from "../../Paths/FilePath"; import { hasTextExtension } from "../../Paths/TextFilePath"; import { hasScriptExtension } from "../../Paths/ScriptFilePath"; -import { StdIO } from "../StdIO/StdIO"; -export function cp(args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { +export function cp(args: (string | number | boolean)[], server: BaseServer): void { if (args.length !== 2) { - return Terminal.error("Incorrect usage of cp command. Usage: cp [source filename] [destination]", stdIO); + return Terminal.error("Incorrect usage of cp command. Usage: cp [source filename] [destination]"); } // Find the source file const sourceFilePath = Terminal.getFilepath(String(args[0])); - if (!sourceFilePath) return Terminal.error(`Invalid source filename ${args[0]}`, stdIO); + if (!sourceFilePath) return Terminal.error(`Invalid source filename ${args[0]}`); if (!hasTextExtension(sourceFilePath) && !hasScriptExtension(sourceFilePath)) { - return Terminal.error("cp: Can only be performed on script and text files", stdIO); + return Terminal.error("cp: Can only be performed on script and text files"); } const source = server.getContentFile(sourceFilePath); - if (!source) return Terminal.error(`File not found: ${sourceFilePath}`, stdIO); + if (!source) return Terminal.error(`File not found: ${sourceFilePath}`); // Determine the destination file path. const destinationInput = String(args[1]); @@ -24,15 +23,14 @@ export function cp(args: (string | number | boolean)[], server: BaseServer, stdI let destFilePath = Terminal.getFilepath(destinationInput); if (!destFilePath) { const destDirectory = Terminal.getDirectory(destinationInput); - if (!destDirectory) - return Terminal.error(`Could not resolve ${destinationInput} as a FilePath or Directory`, stdIO); + if (!destDirectory) return Terminal.error(`Could not resolve ${destinationInput} as a FilePath or Directory`); destFilePath = combinePath(destDirectory, getFilenameOnly(sourceFilePath)); } if (!hasTextExtension(destFilePath) && !hasScriptExtension(destFilePath)) { - return Terminal.error(`cp: Can only copy to script and text files (${destFilePath} is invalid destination)`, stdIO); + return Terminal.error(`cp: Can only copy to script and text files (${destFilePath} is invalid destination)`); } const result = server.writeToContentFile(destFilePath, source.content); - Terminal.print(`File ${sourceFilePath} copied to ${destFilePath}`, stdIO); - if (result.overwritten) Terminal.warn(`${destFilePath} was overwritten.`, stdIO); + Terminal.print(`File ${sourceFilePath} copied to ${destFilePath}`); + if (result.overwritten) Terminal.warn(`${destFilePath} was overwritten.`); } diff --git a/src/Terminal/commands/echo.ts b/src/Terminal/commands/echo.ts deleted file mode 100644 index cdbfc4df2..000000000 --- a/src/Terminal/commands/echo.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { StdIO } from "../StdIO/StdIO"; -import { BaseServer } from "../../Server/BaseServer"; - -export function echo(args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { - stdIO.write(args.join(" ")); - stdIO.close(); -} diff --git a/src/Terminal/commands/expr.ts b/src/Terminal/commands/expr.ts index 24f2dea42..f38bb5115 100644 --- a/src/Terminal/commands/expr.ts +++ b/src/Terminal/commands/expr.ts @@ -1,10 +1,8 @@ import { Terminal } from "../../Terminal"; -import { StdIO } from "../StdIO/StdIO"; -import { BaseServer } from "../../Server/BaseServer"; -export function expr(args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { +export function expr(args: (string | number | boolean)[]): void { if (args.length === 0) { - Terminal.error("Incorrect usage of expr command. Usage: expr [math expression]", stdIO); + Terminal.error("Incorrect usage of expr command. Usage: expr [math expression]"); return; } const expr = args.join(""); @@ -15,8 +13,8 @@ export function expr(args: (string | number | boolean)[], server: BaseServer, st try { result = String(eval?.(sanitizedExpr)); } catch (e) { - Terminal.error(`Could not evaluate expression: ${sanitizedExpr}. Error: ${e}.`, stdIO); + Terminal.error(`Could not evaluate expression: ${sanitizedExpr}. Error: ${e}.`); return; } - Terminal.print(result, stdIO); + Terminal.print(result); } diff --git a/src/Terminal/commands/free.ts b/src/Terminal/commands/free.ts index 982dddbc8..968f1da85 100644 --- a/src/Terminal/commands/free.ts +++ b/src/Terminal/commands/free.ts @@ -1,11 +1,10 @@ import { Terminal } from "../../Terminal"; import { BaseServer } from "../../Server/BaseServer"; import { formatPercent, formatRam } from "../../ui/formatNumber"; -import { StdIO } from "../StdIO/StdIO"; -export function free(args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { +export function free(args: (string | number | boolean)[], server: BaseServer): void { if (args.length !== 0) { - Terminal.error("Incorrect usage of free command. Usage: free", stdIO); + Terminal.error("Incorrect usage of free command. Usage: free"); return; } const ram = formatRam(server.maxRam); @@ -14,10 +13,9 @@ export function free(args: (string | number | boolean)[], server: BaseServer, st const maxLength = Math.max(ram.length, Math.max(used.length, avail.length)); const usedPercent = formatPercent(server.ramUsed / server.maxRam); - Terminal.print(`Total: ${" ".repeat(maxLength - ram.length)}${ram}`, stdIO); + Terminal.print(`Total: ${" ".repeat(maxLength - ram.length)}${ram}`); Terminal.print( `Used: ${" ".repeat(maxLength - used.length)}${used}` + (server.maxRam > 0 ? ` (${usedPercent})` : ""), - stdIO, ); - Terminal.print(`Available: ${" ".repeat(maxLength - avail.length)}${avail}`, stdIO); + Terminal.print(`Available: ${" ".repeat(maxLength - avail.length)}${avail}`); } diff --git a/src/Terminal/commands/grep.ts b/src/Terminal/commands/grep.ts index ad7825201..9c4598ee1 100644 --- a/src/Terminal/commands/grep.ts +++ b/src/Terminal/commands/grep.ts @@ -1,15 +1,13 @@ import { Terminal } from "../../Terminal"; import { BaseServer } from "../../Server/BaseServer"; import { hasTextExtension } from "../../Paths/TextFilePath"; -import { ContentFilePath, allContentFiles } from "../../Paths/ContentFile"; +import { ContentFile, ContentFilePath, allContentFiles } from "../../Paths/ContentFile"; import { Settings } from "../../Settings/Settings"; import { help } from "../commands/help"; import { Output } from "../OutputTypes"; import { pluralize } from "../../utils/I18nUtils"; -import { StdIO } from "../StdIO/StdIO"; -import { callOnRead } from "../StdIO/RedirectIO"; -import { stringify } from "../StdIO/utils"; -import { getFileContents, validateFilenames } from "./cat"; + +type LineParser = (options: Options, filename: string, line: string, i: number) => ParsedLine; const RED: string = "\x1b[31m"; const DEFAULT: string = "\x1b[0m"; @@ -21,7 +19,8 @@ const WHITE: string = "\x1b[37m"; const ERR = { noArgs: "grep argument error. Usage: grep [OPTION]... PATTERN [FILE]... [-O] [OUTPUT FILE] [-m -B/A/C] [NUM]", - noSearchArg: `grep argument error: At least one FILE argument must be passed, or pass -*/--search-all to search all files on server, or pipe input into grep e.g. "echo test | grep t"`, + noSearchArg: + "grep argument error: At least one FILE argument must be passed, or pass -*/--search-all to search all files on server", badArgs: (args: string[]) => "grep argument error: Invalid argument(s): " + args.join(", "), badParameter: (option: string, arg: string) => `grep argument error: Incorrect ${option} argument "${arg}". Must be a number. OPTIONS with additional parameters (-O, -m, -B/A/C) must be separated from other options`, @@ -31,7 +30,6 @@ const ERR = { `grep file output failed: Invalid output file "${path}". Output file path must be a valid text file. (.txt, .json, .css)`, truncated: () => `\n${YELLOW}Terminal output truncated to ${Settings.MaxTerminalCapacity} lines (Max terminal capacity)`, - tooManyInputs: () => `grep argument error. Cannot use both redirected input and terminal search simultaneously.`, } as const; type ArgStrings = { @@ -291,7 +289,7 @@ class Results { return this; } - getVerboseInfo(files: DataToSearch[], pattern: string | RegExp, options: Options): string { + getVerboseInfo(files: ContentFile[], pattern: string | RegExp, options: Options): string { if (!options.isVerbose) return ""; const totalLines = this.results.length; const matchCount = Math.abs((options.isInvertMatch ? totalLines : 0) - this.numMatches); @@ -313,12 +311,28 @@ class Results { } } -function getServerFiles(server: BaseServer) { +function getServerFiles(server: BaseServer): [ContentFile[], string[]] { const files = []; for (const tuple of allContentFiles(server)) { files.push(tuple[1]); } - return files.map((file) => ({ filename: file.filename, content: file.content })); + return [files, []]; +} + +function getArgFiles(args: string[]): [ContentFile[], string[]] { + const notFiles = []; + const files = []; + + for (const arg of args) { + const file = hasTextExtension(arg) ? Terminal.getTextFile(arg) : Terminal.getScript(arg); + if (!file) { + notFiles.push(arg); + } else { + files.push(file); + } + } + + return [files, notFiles]; } function parseLine(pattern: string | RegExp, options: Options, filename: string, line: string, i: number): ParsedLine { @@ -335,32 +349,49 @@ function parseLine(pattern: string | RegExp, options: Options, filename: string, return { lines, filename, isMatched, isPrint: false, isFileSep: false }; } +function parseFile(lineParser: LineParser, options: Options, file: ContentFile, i: number): ParsedLine[] { + const parseLineFn = lineParser.bind(null, options, file.filename); + const editedContent: ParsedLine[] = file.content.split("\n").map(parseLineFn); + + const hasMatch = editedContent.some((line) => line.isMatched); + + const isPrintFileSep = options.hasContextFlag && hasMatch && i !== 0; + + const fileSeparator: ParsedLine = { + lines: { prettyLine: `${CYAN}--${DEFAULT}`, rawLine: "--" }, + isPrint: true, + isMatched: false, + isFileSep: true, + filename: "", + }; + return isPrintFileSep ? [fileSeparator, ...editedContent] : editedContent; +} + function writeToTerminal( prettyResult: string[], options: Options, results: Results, - files: DataToSearch[], + files: ContentFile[], pattern: string | RegExp, - stdIO: StdIO, ): void { const printResult = prettyResult.slice(0, Math.min(prettyResult.length, Settings.MaxTerminalCapacity)); // limit printing to terminal const verboseInfo = results.getVerboseInfo(files, pattern, options); const truncateInfo = prettyResult.length !== printResult.length ? ERR.truncated() : ""; - if (results.areEdited) stdIO.write(printResult.join("\n") + truncateInfo); - if (options.isVerbose) stdIO.write(verboseInfo); + if (results.areEdited) Terminal.print(printResult.join("\n") + truncateInfo); + if (options.isVerbose) Terminal.print(verboseInfo); } -function checkOutFile(outFileStr: string, options: Options, server: BaseServer, stdIO: StdIO): ContentFilePath | null { +function checkOutFile(outFileStr: string, options: Options, server: BaseServer): ContentFilePath | null { if (!outFileStr) { return null; } const outFilePath = Terminal.getFilepath(outFileStr); if (!outFilePath || !hasTextExtension(outFilePath)) { - Terminal.error(ERR.badOutFile(outFileStr), stdIO); + Terminal.error(ERR.badOutFile(outFileStr)); return null; } if (!options.isOverWrite && server.textFiles.has(outFilePath)) { - Terminal.error(ERR.outFileExists(outFileStr), stdIO); + Terminal.error(ERR.outFileExists(outFileStr)); return null; } return outFilePath; @@ -370,105 +401,44 @@ function grabTerminal(): string[] { return Terminal.outputHistory.map((line) => (line as Output).text ?? ""); } -export function grep(args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { - const stdin = stdIO.stdin?.deref(); - const noStdinProvided = !stdin || (stdin.isClosed && stdin.empty()); - if (!args.length && noStdinProvided) return Terminal.error(ERR.noArgs, stdIO); +export function grep(args: (string | number | boolean)[], server: BaseServer): void { + if (!args.length) return Terminal.error(ERR.noArgs); const [otherArgs, options, params] = new Args(args).splitOptsAndArgs(); - if (options.isHelp) return help(["grep"], server, stdIO); + if (options.isHelp) return help(["grep"]); options.hasContextFlag = !!params.context || !!params.preContext || !!params.postContext; const nContext = Math.max(Number(params.preContext), Number(params.context), Number(params.postContext)); const nLimit = Number(params.maxMatches); if (options.hasContextFlag && (!nContext || isNaN(Number(params.context)))) - return Terminal.error(ERR.badParameter("context", params.context), stdIO); + return Terminal.error(ERR.badParameter("context", params.context)); if (params.maxMatches && (!nLimit || isNaN(Number(params.maxMatches)))) - return Terminal.error(ERR.badParameter("limit", params.maxMatches), stdIO); + return Terminal.error(ERR.badParameter("limit", params.maxMatches)); - const stdinContent = stdIO.getAllCurrentStdin(); + const [files, notFiles] = options.isSearchAll ? getServerFiles(server) : getArgFiles(otherArgs.slice(1)); - if (!options.isPipeIn && !options.isSearchAll && !otherArgs.length && noStdinProvided) - return Terminal.error(ERR.noSearchArg, stdIO); - if (options.isPipeIn && stdinContent.length) return Terminal.error(ERR.tooManyInputs(), stdIO); + if (notFiles.length) return Terminal.error(ERR.badArgs(notFiles)); + if (!options.isPipeIn && !options.isSearchAll && !files.length) return Terminal.error(ERR.noSearchArg); - options.isMultiFile = otherArgs.length > 1; - const outFilePath = checkOutFile(params.outfile, options, server, stdIO); + options.isMultiFile = files.length > 1; + const outFilePath = checkOutFile(params.outfile, options, server); if (params.outfile && !outFilePath) return; // associated errors are printed in checkOutFile - if (!validateFilenames(otherArgs.slice(1), server, stdIO)) return; - const fileContent: DataToSearch[] = options.isSearchAll - ? getServerFiles(server) - : getDataToGrep(otherArgs.slice(1), server, stdinContent); - try { - applyFilters(options, params, otherArgs, fileContent, server, stdIO); + const pattern = options.isRegExpr ? new RegExp(otherArgs[0], "g") : otherArgs[0]; + const lineParser = parseLine.bind(null, pattern); + const termParser = lineParser.bind(null, options, "Terminal"); + const fileParser = parseFile.bind(null, lineParser, options); + const contentToMatch = options.isPipeIn ? grabTerminal().map(termParser) : files.flatMap(fileParser); + const results = new Results(contentToMatch, options, params); + const [rawResult, prettyResult] = results.capMatches(nLimit).addContext(nContext).splitAndFilter(); + + if (options.isPipeIn) files.length = 0; + if (!options.isQuiet) writeToTerminal(prettyResult, options, results, files, pattern); + if (params.outfile && outFilePath) server.writeToContentFile(outFilePath, rawResult.join("\n")); } catch (error) { console.error(error); - Terminal.error(`grep processing error: ${error}`, stdIO); + Terminal.error(`grep processing error: ${error}`); } - - void callOnRead(stdIO, (data: unknown, stdInOut: StdIO) => { - const content = { filename: "stdin", content: stringify(data) }; - applyFilters(options, params, otherArgs, [content], server, stdInOut); - }); } - -function applyFilters( - options: Options, - params: Parameters, - otherArgs: string[], - fileContent: DataToSearch[], - server: BaseServer, - stdIO: StdIO, -) { - const nContext = Math.max(Number(params.preContext), Number(params.context), Number(params.postContext)); - const nLimit = Number(params.maxMatches); - const outFilePath = checkOutFile(params.outfile, options, server, stdIO); - const pattern = options.isRegExpr ? new RegExp(otherArgs[0], "g") : otherArgs[0]; - const contentToMatch = getContentToMatch(pattern, options, fileContent); - const results = new Results(contentToMatch, options, params); - const [rawResult, prettyResult] = results.capMatches(nLimit).addContext(nContext).splitAndFilter(); - - if (!options.isQuiet) writeToTerminal(prettyResult, options, results, fileContent, pattern, stdIO); - if (params.outfile && outFilePath) server.writeToContentFile(outFilePath, rawResult.join("\n")); -} - -function getContentToMatch(pattern: RegExp | string, options: Options, fileContent: DataToSearch[]): ParsedLine[] { - if (options.isPipeIn) { - return grabTerminal().map((terminalOutput, i) => parseLine(pattern, options, "Terminal", terminalOutput, i)); - } - - return fileContent.flatMap((fileContent) => - fileContent.content.split("\n").map((line, i) => parseLine(pattern, options, fileContent.filename, line, i)), - ); -} - -function getDataToGrep(filenames: string[], server: BaseServer, stdinContent: string): DataToSearch[] { - const dataToGrep: DataToSearch[] = []; - const stdinToGrep = { - filename: "stdin", - content: stdinContent, - }; - for (const filename of filenames) { - if (filename === "-") { - dataToGrep.push(stdinToGrep); - } else { - dataToGrep.push({ - filename, - content: getFileContents(filename, server), - }); - } - } - // If stdin location is not explicitly specified, append it to the end - if (!filenames.includes("-") && stdinContent.length) { - dataToGrep.push(stdinToGrep); - } - return dataToGrep; -} - -type DataToSearch = { - filename: string; - content: string; -}; diff --git a/src/Terminal/commands/grow.ts b/src/Terminal/commands/grow.ts index 0d1eedd1a..280457315 100644 --- a/src/Terminal/commands/grow.ts +++ b/src/Terminal/commands/grow.ts @@ -1,13 +1,12 @@ import { Terminal } from "../../Terminal"; import { BaseServer } from "../../Server/BaseServer"; -import { StdIO } from "../StdIO/StdIO"; -export function grow(args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { - if (args.length !== 0) return Terminal.error("Incorrect usage of grow command. Usage: grow", stdIO); +export function grow(args: (string | number | boolean)[], server: BaseServer): void { + if (args.length !== 0) return Terminal.error("Incorrect usage of grow command. Usage: grow"); - if (server.purchasedByPlayer) return Terminal.error("Cannot grow your own machines!", stdIO); - if (!server.hasAdminRights) return Terminal.error("You do not have admin rights for this machine!", stdIO); + if (server.purchasedByPlayer) return Terminal.error("Cannot grow your own machines!"); + if (!server.hasAdminRights) return Terminal.error("You do not have admin rights for this machine!"); // Grow does not require meeting the hacking level, but undefined requiredHackingSkill indicates the wrong type of server. - if (server.requiredHackingSkill === undefined) return Terminal.error("Cannot grow this server.", stdIO); - Terminal.startGrow(stdIO); + if (server.requiredHackingSkill === undefined) return Terminal.error("Cannot grow this server."); + Terminal.startGrow(); } diff --git a/src/Terminal/commands/hack.ts b/src/Terminal/commands/hack.ts index 5dd870b13..46df900a5 100644 --- a/src/Terminal/commands/hack.ts +++ b/src/Terminal/commands/hack.ts @@ -1,19 +1,17 @@ import { Terminal } from "../../Terminal"; import { Player } from "@player"; import { BaseServer } from "../../Server/BaseServer"; -import { StdIO } from "../StdIO/StdIO"; -export function hack(args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { - if (args.length !== 0) return Terminal.error("Incorrect usage of hack command. Usage: hack", stdIO); - if (server.purchasedByPlayer) return Terminal.error("Cannot hack your own machines!", stdIO); - if (!server.hasAdminRights) return Terminal.error("You do not have admin rights for this machine!", stdIO); +export function hack(args: (string | number | boolean)[], server: BaseServer): void { + if (args.length !== 0) return Terminal.error("Incorrect usage of hack command. Usage: hack"); + if (server.purchasedByPlayer) return Terminal.error("Cannot hack your own machines!"); + if (!server.hasAdminRights) return Terminal.error("You do not have admin rights for this machine!"); // Acts as a functional check that the server is hackable. Hacknet servers should already be filtered out anyway by purchasedByPlayer - if (server.requiredHackingSkill === undefined) return Terminal.error("Cannot hack this server.", stdIO); + if (server.requiredHackingSkill === undefined) return Terminal.error("Cannot hack this server."); if (server.requiredHackingSkill > Player.skills.hacking) { return Terminal.error( "Your hacking skill is not high enough to hack this machine. Try analyzing the machine to determine the required hacking skill", - stdIO, ); } - Terminal.startHack(stdIO); + Terminal.startHack(); } diff --git a/src/Terminal/commands/help.ts b/src/Terminal/commands/help.ts index 992e2c583..6bdf2a0d5 100644 --- a/src/Terminal/commands/help.ts +++ b/src/Terminal/commands/help.ts @@ -1,22 +1,20 @@ import { Terminal } from "../../Terminal"; import { TerminalHelpText, HelpTexts } from "../HelpText"; -import { BaseServer } from "../../Server/BaseServer"; -import { StdIO } from "../StdIO/StdIO"; -export function help(args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { +export function help(args: (string | number | boolean)[]): void { if (args.length !== 0 && args.length !== 1) { - Terminal.error("Incorrect usage of help command. Usage: help", stdIO); + Terminal.error("Incorrect usage of help command. Usage: help"); return; } if (args.length === 0) { - TerminalHelpText.forEach((line) => Terminal.print(line, stdIO)); + TerminalHelpText.forEach((line) => Terminal.print(line)); } else { const cmd = args[0] + ""; const txt = HelpTexts[cmd]; if (txt == null) { - Terminal.error("No help topics match '" + cmd + "'", stdIO); + Terminal.error("No help topics match '" + cmd + "'"); return; } - txt.forEach((t) => Terminal.print(t, stdIO)); + txt.forEach((t) => Terminal.print(t)); } } diff --git a/src/Terminal/commands/history.ts b/src/Terminal/commands/history.ts index 8a0827560..c7a9064fe 100644 --- a/src/Terminal/commands/history.ts +++ b/src/Terminal/commands/history.ts @@ -1,12 +1,10 @@ import { Terminal } from "../../Terminal"; import { Player } from "@player"; -import { BaseServer } from "../../Server/BaseServer"; -import { StdIO } from "../StdIO/StdIO"; -export function history(args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { +export function history(args: (string | number | boolean)[]): void { if (args.length === 0) { Terminal.commandHistory.forEach((command, index) => { - Terminal.print(`${index.toString().padStart(2)} ${command}`, stdIO); + Terminal.print(`${index.toString().padStart(2)} ${command}`); }); return; } @@ -16,6 +14,6 @@ export function history(args: (string | number | boolean)[], server: BaseServer, Terminal.commandHistory = []; Terminal.commandHistoryIndex = 1; } else { - Terminal.error("Incorrect usage of history command. usage: history [-c]", stdIO); + Terminal.error("Incorrect usage of history command. usage: history [-c]"); } } diff --git a/src/Terminal/commands/hostname.ts b/src/Terminal/commands/hostname.ts index 6374f5cf8..767763808 100644 --- a/src/Terminal/commands/hostname.ts +++ b/src/Terminal/commands/hostname.ts @@ -1,11 +1,10 @@ import { Terminal } from "../../Terminal"; import { BaseServer } from "../../Server/BaseServer"; -import { StdIO } from "../StdIO/StdIO"; -export function hostname(args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { +export function hostname(args: (string | number | boolean)[], server: BaseServer): void { if (args.length !== 0) { - Terminal.error("Incorrect usage of hostname command. Usage: hostname", stdIO); + Terminal.error("Incorrect usage of hostname command. Usage: hostname"); return; } - Terminal.print(server.hostname, stdIO); + Terminal.print(server.hostname); } diff --git a/src/Terminal/commands/ipaddr.ts b/src/Terminal/commands/ipaddr.ts index d3a7b471a..abe9edb1d 100644 --- a/src/Terminal/commands/ipaddr.ts +++ b/src/Terminal/commands/ipaddr.ts @@ -1,11 +1,10 @@ import { Terminal } from "../../Terminal"; import { BaseServer } from "../../Server/BaseServer"; -import { StdIO } from "../StdIO/StdIO"; -export function ipaddr(args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { +export function ipaddr(args: (string | number | boolean)[], server: BaseServer): void { if (args.length !== 0) { - Terminal.error("Incorrect usage of hostname command. Usage: ipaddr", stdIO); + Terminal.error("Incorrect usage of hostname command. Usage: ipaddr"); return; } - Terminal.print(server.ip, stdIO); + Terminal.print(server.ip); } diff --git a/src/Terminal/commands/kill.ts b/src/Terminal/commands/kill.ts index 44bf52d82..6639f14f9 100644 --- a/src/Terminal/commands/kill.ts +++ b/src/Terminal/commands/kill.ts @@ -4,12 +4,11 @@ import { killWorkerScriptByPid } from "../../Netscript/killWorkerScript"; import { hasScriptExtension } from "../../Paths/ScriptFilePath"; import type { BaseServer } from "../../Server/BaseServer"; -import { StdIO } from "../StdIO/StdIO"; -export function kill(args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { +export function kill(args: (string | number | boolean)[], server: BaseServer): void { try { if (args.length < 1 || typeof args[0] === "boolean") { - Terminal.error("Incorrect usage of kill command. Usage: kill [pid] or kill [scriptname] [arg1] [arg2]...", stdIO); + Terminal.error("Incorrect usage of kill command. Usage: kill [pid] or kill [scriptname] [arg1] [arg2]..."); return; } @@ -18,36 +17,35 @@ export function kill(args: (string | number | boolean)[], server: BaseServer, st const pid = args[0]; const res = killWorkerScriptByPid(pid); if (res) { - Terminal.print(`Killing script with PID ${pid}`, stdIO); + Terminal.print(`Killing script with PID ${pid}`); } else { - Terminal.error(`Failed to kill script with PID ${pid}. No such script is running`, stdIO); + Terminal.error(`Failed to kill script with PID ${pid}. No such script is running`); } return; } const path = Terminal.getFilepath(args[0]); - if (!path) return Terminal.error(`Invalid filename: ${args[0]}`, stdIO); - if (!hasScriptExtension(path)) - return Terminal.error(`Invalid file extension. Kill can only be used on scripts.`, stdIO); + if (!path) return Terminal.error(`Invalid filename: ${args[0]}`); + if (!hasScriptExtension(path)) return Terminal.error(`Invalid file extension. Kill can only be used on scripts.`); const runningScripts = findRunningScripts(path, args.slice(1), server); if (runningScripts === null) { - Terminal.error("No such script is running. Nothing to kill", stdIO); + Terminal.error("No such script is running. Nothing to kill"); return; } let killed = 0; for (const pid of runningScripts.keys()) { killed++; if (killed < 5) { - Terminal.print(`Killing ${path} with pid ${pid}`, stdIO); + Terminal.print(`Killing ${path} with pid ${pid}`); } killWorkerScriptByPid(pid); } if (killed >= 5) { - Terminal.print(`... killed ${killed} instances total`, stdIO); + Terminal.print(`... killed ${killed} instances total`); } } catch (error) { console.error(error); - Terminal.error(String(error), stdIO); + Terminal.error(String(error)); } } diff --git a/src/Terminal/commands/killall.ts b/src/Terminal/commands/killall.ts index 6a8a0cf28..1ce2a69d0 100644 --- a/src/Terminal/commands/killall.ts +++ b/src/Terminal/commands/killall.ts @@ -1,10 +1,9 @@ import { Terminal } from "../../Terminal"; import { BaseServer } from "../../Server/BaseServer"; import { killWorkerScriptByPid } from "../../Netscript/killWorkerScript"; -import { StdIO } from "../StdIO/StdIO"; -export function killall(_args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { - Terminal.print("Killing all running scripts", stdIO); +export function killall(_args: (string | number | boolean)[], server: BaseServer): void { + Terminal.print("Killing all running scripts"); for (const byPid of server.runningScriptMap.values()) { for (const runningScript of byPid.values()) { killWorkerScriptByPid(runningScript.pid); diff --git a/src/Terminal/commands/ls.tsx b/src/Terminal/commands/ls.tsx index b5101c006..8cfbada89 100644 --- a/src/Terminal/commands/ls.tsx +++ b/src/Terminal/commands/ls.tsx @@ -26,11 +26,10 @@ import { import { isMember } from "../../utils/EnumHelper"; import { Settings } from "../../Settings/Settings"; import { formatBytes, formatRam } from "../../ui/formatNumber"; -import { StdIO } from "../StdIO/StdIO"; import { DarknetServer } from "../../Server/DarknetServer"; import type { CacheFilePath } from "../../Paths/CacheFilePath"; -export function ls(args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { +export function ls(args: (string | number | boolean)[], server: BaseServer): void { enum FileType { Folder, Message, @@ -77,7 +76,7 @@ export function ls(args: (string | number | boolean)[], server: BaseServer, stdI const numArgs = args.length; function incorrectUsage(): void { - Terminal.error("Incorrect usage of ls command. Usage: ls [dir] [-l] [-h] [-g, --grep pattern]", stdIO); + Terminal.error("Incorrect usage of ls command. Usage: ls [dir] [-l] [-h] [-g, --grep pattern]"); } if (numArgs > 5) { @@ -263,7 +262,7 @@ export function ls(args: (string | number | boolean)[], server: BaseServer, stdI })(); function onClick(): void { if (!server.isConnectedTo) { - return Terminal.error(`File is not on this server, connect to ${server.hostname} and try again`, stdIO); + return Terminal.error(`File is not on this server, connect to ${server.hostname} and try again`); } // Message and lit files are always in root, no need to combine path with base directory if (isMember("MessageFilename", props.path)) { @@ -328,7 +327,6 @@ export function ls(args: (string | number | boolean)[], server: BaseServer, stdI > {nameElement} , - stdIO, ); } } else { @@ -337,7 +335,7 @@ export function ls(args: (string | number | boolean)[], server: BaseServer, stdI return React.cloneElement(nameElement, { key: segmentPath.toString() }); }); const colSize = Math.ceil(Math.max(...segments.map((segment) => segment.length)) * 0.7) + "em"; - Terminal.printRaw({segmentElements}, stdIO); + Terminal.printRaw({segmentElements}); } } diff --git a/src/Terminal/commands/lscpu.ts b/src/Terminal/commands/lscpu.ts index 17fc6c862..096793249 100644 --- a/src/Terminal/commands/lscpu.ts +++ b/src/Terminal/commands/lscpu.ts @@ -1,7 +1,6 @@ import { Terminal } from "../../Terminal"; import { BaseServer } from "../../Server/BaseServer"; -import { StdIO } from "../StdIO/StdIO"; -export function lscpu(_args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { - Terminal.print(server.cpuCores + " Core(s)", stdIO); +export function lscpu(_args: (string | number | boolean)[], server: BaseServer): void { + Terminal.print(server.cpuCores + " Core(s)"); } diff --git a/src/Terminal/commands/mem.ts b/src/Terminal/commands/mem.ts index 43e63f2e2..df56e6768 100644 --- a/src/Terminal/commands/mem.ts +++ b/src/Terminal/commands/mem.ts @@ -2,12 +2,11 @@ import { Terminal } from "../../Terminal"; import { formatRam } from "../../ui/formatNumber"; import { Settings } from "../../Settings/Settings"; import { BaseServer } from "../../Server/BaseServer"; -import { StdIO } from "../StdIO/StdIO"; -export function mem(args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { +export function mem(args: (string | number | boolean)[], server: BaseServer): void { try { if (args.length !== 1 && args.length !== 3) { - Terminal.error("Incorrect usage of mem command. usage: mem [scriptname] [-t] [number threads]", stdIO); + Terminal.error("Incorrect usage of mem command. usage: mem [scriptname] [-t] [number threads]"); return; } @@ -16,36 +15,36 @@ export function mem(args: (string | number | boolean)[], server: BaseServer, std if (args.length === 3 && args[1] === "-t") { numThreads = Math.round(parseInt(args[2] + "")); if (isNaN(numThreads) || numThreads < 1) { - Terminal.error("Invalid number of threads specified. Number of threads must be greater than 1", stdIO); + Terminal.error("Invalid number of threads specified. Number of threads must be greater than 1"); return; } } const script = Terminal.getScript(scriptName); if (script == null) { - Terminal.error("mem failed. No such script exists!", stdIO); + Terminal.error("mem failed. No such script exists!"); return; } const singleRamUsage = script.getRamUsage(server.scripts); - if (!singleRamUsage) return Terminal.error(`Could not calculate ram usage for ${scriptName}`, stdIO); + if (!singleRamUsage) return Terminal.error(`Could not calculate ram usage for ${scriptName}`); const ramUsage = singleRamUsage * numThreads; - Terminal.print(`This script requires ${formatRam(ramUsage)} of RAM to run for ${numThreads} thread(s)`, stdIO); + Terminal.print(`This script requires ${formatRam(ramUsage)} of RAM to run for ${numThreads} thread(s)`); const verboseEntries = script.ramUsageEntries.sort((a, b) => b.cost - a.cost) ?? []; const padding = Settings.UseIEC60027_2 ? 9 : 8; for (const entry of verboseEntries) { - Terminal.print(`${formatRam(entry.cost * numThreads).padStart(padding)} | ${entry.name} (${entry.type})`, stdIO); + Terminal.print(`${formatRam(entry.cost * numThreads).padStart(padding)} | ${entry.name} (${entry.type})`); } if (ramUsage > 0 && verboseEntries.length === 0) { // Let's warn the user that he might need to save his script again to generate the detailed entries - Terminal.warn("You might have to open & save this script to see the detailed RAM usage information.", stdIO); + Terminal.warn("You might have to open & save this script to see the detailed RAM usage information."); } } catch (error) { console.error(error); - Terminal.error(String(error), stdIO); + Terminal.error(String(error)); } } diff --git a/src/Terminal/commands/ps.ts b/src/Terminal/commands/ps.ts index c94cb80ec..260621918 100644 --- a/src/Terminal/commands/ps.ts +++ b/src/Terminal/commands/ps.ts @@ -2,9 +2,8 @@ import { Terminal } from "../../Terminal"; import { BaseServer } from "../../Server/BaseServer"; import { matchScriptPathUnanchored } from "../../utils/helpers/scriptKey"; import libarg from "arg"; -import { StdIO } from "../StdIO/StdIO"; -export function ps(args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { +export function ps(args: (string | number | boolean)[], server: BaseServer): void { let flags: { "--grep": string; }; @@ -19,7 +18,7 @@ export function ps(args: (string | number | boolean)[], server: BaseServer, stdI ); } catch (e) { // catch passing only -g / --grep with no string to use as the search - Terminal.error("Incorrect usage of ps command. Usage: ps [-g, --grep pattern]", stdIO); + Terminal.error("Incorrect usage of ps command. Usage: ps [-g, --grep pattern]"); return; } let pattern = flags["--grep"]; @@ -31,7 +30,7 @@ export function ps(args: (string | number | boolean)[], server: BaseServer, stdI if (!re.test(k)) continue; for (const rsObj of byPid.values()) { const res = `(PID - ${rsObj.pid}) ${rsObj.filename} ${rsObj.args.join(" ")}`; - Terminal.print(res, stdIO); + Terminal.print(res); } } } diff --git a/src/Terminal/commands/rm.ts b/src/Terminal/commands/rm.ts index 6c450dbd1..db4f59090 100644 --- a/src/Terminal/commands/rm.ts +++ b/src/Terminal/commands/rm.ts @@ -5,9 +5,8 @@ import { getAllDirectories, type Directory } from "../../Paths/Directory"; import type { ProgramFilePath } from "../../Paths/ProgramFilePath"; import type { IReturnStatus } from "../../types"; import type { FilePath } from "../../Paths/FilePath"; -import { StdIO } from "../StdIO/StdIO"; -export function rm(args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { +export function rm(args: (string | number | boolean)[], server: BaseServer): void { const errors = { arg: (reason: string) => `Incorrect usage of rm command. ${reason}. Usage: rm [OPTION]... [FILE]...`, dirsProvided: (name: string) => @@ -20,7 +19,7 @@ export function rm(args: (string | number | boolean)[], server: BaseServer, stdI "You are trying to delete all files within the root directory. If this is intentional, use the --no-preserve-root flag", } as const; - if (args.length === 0) return Terminal.error(errors["arg"]("No arguments provided"), stdIO); + if (args.length === 0) return Terminal.error(errors["arg"]("No arguments provided")); const recursive = args.includes("-r") || args.includes("-R") || args.includes("--recursive") || args.includes("-rf"); const force = args.includes("-f") || args.includes("--force") || args.includes("-rf"); @@ -34,8 +33,8 @@ export function rm(args: (string | number | boolean)[], server: BaseServer, stdI typeof arg === "string" && (!arg.startsWith("-") || (index - 1 >= 0 && array[index - 1] === "--")); const targets = args.filter(isTargetString); - if (targets.length === 0) return Terminal.error(errors["arg"]("No targets provided"), stdIO); - if (!ignoreSpecialRoot && targets.includes("/")) return Terminal.error(errors["rootDeletion"](), stdIO); + if (targets.length === 0) return Terminal.error(errors["arg"]("No targets provided")); + if (!ignoreSpecialRoot && targets.includes("/")) return Terminal.error(errors["rootDeletion"]()); const directories: Directory[] = []; const files: FilePath[] = []; @@ -63,7 +62,7 @@ export function rm(args: (string | number | boolean)[], server: BaseServer, stdI const fileExists = file !== null && allFiles.has(file); - if (fileDir === null) return Terminal.error(errors.invalidFile(target), stdIO); + if (fileDir === null) return Terminal.error(errors.invalidFile(target)); const dirExists = allDirs.has(fileDir); if (file === null || dirExists) { // If file === null, it means we specified a trailing-slash directory/, @@ -83,11 +82,11 @@ export function rm(args: (string | number | boolean)[], server: BaseServer, stdI continue; } else { // Only exists as a directory (maybe). - return Terminal.error(errors.dirsProvided(target), stdIO); + return Terminal.error(errors.dirsProvided(target)); } } if (!dirExists && !force) { - return Terminal.error(errors.noSuchDir(target), stdIO); + return Terminal.error(errors.noSuchDir(target)); } // If we pass -f and pass a non-existing directory, we will add it // here and then it will match no files, producing no errors. This @@ -97,7 +96,7 @@ export function rm(args: (string | number | boolean)[], server: BaseServer, stdI } if (!force && !allFiles.has(file)) { // With -f, we ignore file-not-found and try to delete everything at the end. - return Terminal.error(errors.noSuchFile(target), stdIO); + return Terminal.error(errors.noSuchFile(target)); } files.push(file); } @@ -121,9 +120,9 @@ export function rm(args: (string | number | boolean)[], server: BaseServer, stdI for (const report of reports) { if (report.result.res) { - Terminal.success(`Deleted: ${report.target}`, stdIO); + Terminal.success(`Deleted: ${report.target}`); } else { - Terminal.error(errors.deleteFailed(report.target, report.result.msg), stdIO); + Terminal.error(errors.deleteFailed(report.target, report.result.msg)); } } }; diff --git a/src/Terminal/commands/run.ts b/src/Terminal/commands/run.ts index 73dd2e583..f8c9169fe 100644 --- a/src/Terminal/commands/run.ts +++ b/src/Terminal/commands/run.ts @@ -5,33 +5,30 @@ import { runProgram } from "./runProgram"; import { hasScriptExtension } from "../../Paths/ScriptFilePath"; import { hasContractExtension } from "../../Paths/ContractFilePath"; import { hasProgramExtension } from "../../Paths/ProgramFilePath"; -import { StdIO } from "../StdIO/StdIO"; import { hasCacheExtension } from "../../Paths/CacheFilePath"; -export function run(args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { +export function run(args: (string | number | boolean)[], server: BaseServer): void { // Run a program or a script const arg = args.shift(); if (!arg) return Terminal.error( "Usage: run [program/script] [-t num_threads] [--tail] [--ram-override ram_in_GBs] [--temporary] [args...]", - stdIO, ); const path = Terminal.getFilepath(String(arg)); - if (!path) return Terminal.error(`${arg} is not a valid filepath.`, stdIO); + if (!path) return Terminal.error(`${arg} is not a valid filepath.`); if (hasScriptExtension(path)) { - runScript(path, args, server, stdIO); - return; + return runScript(path, args, server); } else if (hasContractExtension(path)) { - Terminal.runContract(path, stdIO).catch((error) => { + Terminal.runContract(path).catch((error) => { console.error(error); - Terminal.error(`Cannot run contract ${path} on ${server.hostname}. Error: ${error}.`, stdIO); + Terminal.error(`Cannot run contract ${path} on ${server.hostname}. Error: ${error}.`); }); return; } else if (hasProgramExtension(path)) { - return runProgram(path, args, server, stdIO); + return runProgram(path, args, server); } else if (hasCacheExtension(path)) { - return Terminal.startAction(4, "c", stdIO, server); + return Terminal.startAction(4, "c", server); } - Terminal.error(`Invalid file extension. Only .js, .jsx, .ts, .tsx, .cct, and .exe files can be run.`, stdIO); + Terminal.error(`Invalid file extension. Only .js, .jsx, .ts, .tsx, .cct, and .exe files can be run.`); } diff --git a/src/Terminal/commands/runProgram.ts b/src/Terminal/commands/runProgram.ts index bcca7b9d4..140e8d8fc 100644 --- a/src/Terminal/commands/runProgram.ts +++ b/src/Terminal/commands/runProgram.ts @@ -4,14 +4,8 @@ import { BaseServer } from "../../Server/BaseServer"; import { Programs } from "../../Programs/Programs"; import { ProgramFilePath } from "../../Paths/ProgramFilePath"; import { getRecordKeys } from "../../Types/Record"; -import { StdIO } from "../StdIO/StdIO"; -export function runProgram( - path: ProgramFilePath, - args: (string | number | boolean)[], - server: BaseServer, - stdIO: StdIO, -): void { +export function runProgram(path: ProgramFilePath, args: (string | number | boolean)[], server: BaseServer): void { // Check if you have the program on your computer. If you do, execute it, otherwise // display an error message const programLowered = path.toLowerCase(); @@ -22,9 +16,8 @@ export function runProgram( if (!realProgramName || (!Player.hasProgram(realProgramName) && !programPresentOnServer)) { Terminal.error( `No such (js, jsx, ts, tsx, script, cct, or exe) file! (Only finished programs that exist on your home computer or scripts on ${server.hostname} can be run)`, - stdIO, ); return; } - Programs[realProgramName].run(args.map(String), server, stdIO); + Programs[realProgramName].run(args.map(String), server); } diff --git a/src/Terminal/commands/runScript.ts b/src/Terminal/commands/runScript.ts index 55d805e2a..7b6f37e14 100644 --- a/src/Terminal/commands/runScript.ts +++ b/src/Terminal/commands/runScript.ts @@ -10,15 +10,12 @@ import { sendDeprecationNotice } from "./common/deprecation"; import { roundToTwo } from "../../utils/helpers/roundToTwo"; import { RamCostConstants } from "../../Netscript/RamCostGenerator"; import { pluralize } from "../../utils/I18nUtils"; -import { RunningScript } from "../../Script/RunningScript"; -import { StdIO } from "../StdIO/StdIO"; export function runScript( scriptPath: ScriptFilePath, commandArgs: (string | number | boolean)[], server: BaseServer, - stdIO: StdIO, -): RunningScript | undefined { +): void { if (isLegacyScript(scriptPath)) { sendDeprecationNotice(); return; @@ -38,20 +35,18 @@ export function runScript( argv: commandArgs, }); } catch (error) { - Terminal.error(`Invalid arguments. ${error}.`, stdIO); + Terminal.error(`Invalid arguments. ${error}.`); return; } const tailFlag = flags["--tail"] === true; const numThreads = parseFloat(flags["-t"] ?? 1); const ramOverride = flags["--ram-override"] != null ? roundToTwo(parseFloat(flags["--ram-override"])) : undefined; if (!isPositiveInteger(numThreads)) { - Terminal.error("Invalid number of threads specified. Number of threads must be an integer greater than 0", stdIO); - return; + return Terminal.error("Invalid number of threads specified. Number of threads must be an integer greater than 0"); } if (ramOverride != null && (isNaN(ramOverride) || ramOverride < RamCostConstants.Base)) { Terminal.error( `Invalid ram override specified. Ram override must be a number greater than ${RamCostConstants.Base}`, - stdIO, ); return; } @@ -67,7 +62,7 @@ export function runScript( args, ); if (!result.success) { - Terminal.error(result.message, stdIO); + Terminal.error(result.message); return; } @@ -77,11 +72,11 @@ export function runScript( const success = startWorkerScript(runningScript, server); if (!success) { - Terminal.error(`Failed to start script`, stdIO); + Terminal.error(`Failed to start script`); return; } - Terminal.printAndBypassPipes( + Terminal.print( `Running script with ${pluralize(numThreads, "thread")}, pid ${runningScript.pid} and args: ${JSON.stringify( args, )}.`, @@ -89,17 +84,5 @@ export function runScript( if (tailFlag) { LogBoxEvents.emit(runningScript); } - - Terminal.pidOfLastScriptRun = runningScript.pid; - - // Bind stdio to script - runningScript.stdin = stdIO.stdin?.deref() ?? null; - runningScript.terminalStdOut = stdIO; - - // scripts interacting with terminal pipes are temporary, to avoid orphaned or partial pipelines on start - if (runningScript.stdin || stdIO.stdout) { - runningScript.temporary = true; - } - - return runningScript; + return; } diff --git a/src/Terminal/commands/scan.ts b/src/Terminal/commands/scan.ts index 45477a4b2..7a4b257b1 100644 --- a/src/Terminal/commands/scan.ts +++ b/src/Terminal/commands/scan.ts @@ -2,11 +2,10 @@ import { Terminal } from "../../Terminal"; import { BaseServer } from "../../Server/BaseServer"; import { getServerOnNetwork } from "../../Server/ServerHelpers"; import { IPAddress } from "../../Types/strings"; -import { StdIO } from "../StdIO/StdIO"; -export function scan(args: (string | number | boolean)[], currServ: BaseServer, stdIO: StdIO): void { +export function scan(args: (string | number | boolean)[], currServ: BaseServer): void { if (args.length !== 0) { - Terminal.error("Incorrect usage of scan command. Usage: scan", stdIO); + Terminal.error("Incorrect usage of scan command. Usage: scan"); return; } // Displays available network connections using TCP @@ -33,6 +32,6 @@ export function scan(args: (string | number | boolean)[], currServ: BaseServer, entry += server.ip; entry += " ".repeat(maxIP - server.ip.length + 1); entry += server.hasRoot; - Terminal.print(entry, stdIO); + Terminal.print(entry); } } diff --git a/src/Terminal/commands/scananalyze.ts b/src/Terminal/commands/scananalyze.ts index 375a8c7ad..cb126eba9 100644 --- a/src/Terminal/commands/scananalyze.ts +++ b/src/Terminal/commands/scananalyze.ts @@ -1,16 +1,14 @@ import { Player } from "@player"; import { CompletedProgramName } from "@enums"; import { Terminal } from "../../Terminal"; -import { StdIO } from "../StdIO/StdIO"; -import { BaseServer } from "../../Server/BaseServer"; -export function scananalyze(args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { +export function scananalyze(args: (string | number | boolean)[]): void { if (args.length === 0) { - Terminal.executeScanAnalyzeCommand(1, false, stdIO); + Terminal.executeScanAnalyzeCommand(); } else { // # of args must be 2 or 3 if (args.length > 2) { - Terminal.error("Incorrect usage of scan-analyze command. usage: scan-analyze [depth]", stdIO); + Terminal.error("Incorrect usage of scan-analyze command. usage: scan-analyze [depth]"); return; } let all = false; @@ -21,19 +19,19 @@ export function scananalyze(args: (string | number | boolean)[], server: BaseSer const depth = parseInt(args[0] + ""); if (isNaN(depth) || depth < 0) { - return Terminal.error("Incorrect usage of scan-analyze command. depth argument must be positive numeric", stdIO); + return Terminal.error("Incorrect usage of scan-analyze command. depth argument must be positive numeric"); } if ( depth > 3 && !Player.hasProgram(CompletedProgramName.deepScan1) && !Player.hasProgram(CompletedProgramName.deepScan2) ) { - return Terminal.error("You cannot scan-analyze with that high of a depth. Maximum depth is 3", stdIO); + return Terminal.error("You cannot scan-analyze with that high of a depth. Maximum depth is 3"); } else if (depth > 5 && !Player.hasProgram(CompletedProgramName.deepScan2)) { - return Terminal.error("You cannot scan-analyze with that high of a depth. Maximum depth is 5", stdIO); + return Terminal.error("You cannot scan-analyze with that high of a depth. Maximum depth is 5"); } else if (depth > 10) { - return Terminal.error("You cannot scan-analyze with that high of a depth. Maximum depth is 10", stdIO); + return Terminal.error("You cannot scan-analyze with that high of a depth. Maximum depth is 10"); } - Terminal.executeScanAnalyzeCommand(depth, all, stdIO); + Terminal.executeScanAnalyzeCommand(depth, all); } } diff --git a/src/Terminal/commands/scp.ts b/src/Terminal/commands/scp.ts index 23425411f..f8e75144c 100644 --- a/src/Terminal/commands/scp.ts +++ b/src/Terminal/commands/scp.ts @@ -6,17 +6,16 @@ import { hasTextExtension } from "../../Paths/TextFilePath"; import { isMember } from "../../utils/EnumHelper"; import { LiteratureName } from "@enums"; import { ContentFile } from "../../Paths/ContentFile"; -import { StdIO } from "../StdIO/StdIO"; -export function scp(args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { +export function scp(args: (string | number | boolean)[], server: BaseServer): void { if (args.length < 2) { - return Terminal.error("Incorrect usage of scp command. Usage: scp [source filename] [destination hostname]", stdIO); + return Terminal.error("Incorrect usage of scp command. Usage: scp [source filename] [destination hostname]"); } // Validate destination server const destHostname = String(args.pop()); const destServer = GetReachableServer(destHostname); - if (!destServer) return Terminal.error(`Invalid destination server: ${destHostname}`, stdIO); + if (!destServer) return Terminal.error(`Invalid destination server: ${destHostname}`); // Validate filepaths const filenames = args.map(String); @@ -25,11 +24,11 @@ export function scp(args: (string | number | boolean)[], server: BaseServer, std // File validation loop, handle all errors before copying any files for (const filename of filenames) { const path = Terminal.getFilepath(filename); - if (!path) return Terminal.error(`Invalid file path: ${filename}`, stdIO); + if (!path) return Terminal.error(`Invalid file path: ${filename}`); // Validate .lit files if (path.endsWith(".lit")) { if (!isMember("LiteratureName", path) || !server.messages.includes(path)) { - return Terminal.error(`scp failed: ${path} does not exist on server ${server.hostname}`, stdIO); + return Terminal.error(`scp failed: ${path} does not exist on server ${server.hostname}`); } files.push(path); continue; @@ -38,12 +37,10 @@ export function scp(args: (string | number | boolean)[], server: BaseServer, std if (!hasScriptExtension(path) && !hasTextExtension(path)) { return Terminal.error( `scp failed: ${path} has invalid extension. scp only works for scripts (.js, .jsx, .ts, .tsx), text files (.txt, .json, .css), and literature files (.lit)`, - stdIO, ); } const sourceContentFile = server.getContentFile(path); - if (!sourceContentFile) - return Terminal.error(`scp failed: ${path} does not exist on server ${server.hostname}`, stdIO); + if (!sourceContentFile) return Terminal.error(`scp failed: ${path} does not exist on server ${server.hostname}`); files.push(sourceContentFile); } @@ -52,18 +49,18 @@ export function scp(args: (string | number | boolean)[], server: BaseServer, std // Lit files, entire "file" is just the name if (isMember("LiteratureName", file)) { if (destServer.messages.includes(file)) { - Terminal.print(`${file} was already on ${destHostname}, file skipped`, stdIO); + Terminal.print(`${file} was already on ${destHostname}, file skipped`); continue; } destServer.messages.push(file); - Terminal.print(`${file} copied to ${destHostname}`, stdIO); + Terminal.print(`${file} copied to ${destHostname}`); continue; } // Content files (script and txt) const { filename, content } = file; const { overwritten } = destServer.writeToContentFile(filename, content); - if (overwritten) Terminal.warn(`${filename} already existed on ${destHostname} and was overwritten`, stdIO); - else Terminal.print(`${filename} copied to ${destHostname}`, stdIO); + if (overwritten) Terminal.warn(`${filename} already existed on ${destHostname} and was overwritten`); + else Terminal.print(`${filename} copied to ${destHostname}`); } } diff --git a/src/Terminal/commands/sudov.ts b/src/Terminal/commands/sudov.ts index 6390d3b26..9fac032c9 100644 --- a/src/Terminal/commands/sudov.ts +++ b/src/Terminal/commands/sudov.ts @@ -1,16 +1,15 @@ import { Terminal } from "../../Terminal"; import { BaseServer } from "../../Server/BaseServer"; -import { StdIO } from "../StdIO/StdIO"; -export function sudov(args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { +export function sudov(args: (string | number | boolean)[], server: BaseServer): void { if (args.length !== 0) { - Terminal.error("Incorrect number of arguments. Usage: sudov", stdIO); + Terminal.error("Incorrect number of arguments. Usage: sudov"); return; } if (server.hasAdminRights) { - Terminal.print("You have ROOT access to this machine", stdIO); + Terminal.print("You have ROOT access to this machine"); } else { - Terminal.print("You do NOT have root access to this machine", stdIO); + Terminal.print("You do NOT have root access to this machine"); } } diff --git a/src/Terminal/commands/tail.ts b/src/Terminal/commands/tail.ts index d708954dc..9c68a54ec 100644 --- a/src/Terminal/commands/tail.ts +++ b/src/Terminal/commands/tail.ts @@ -1,14 +1,10 @@ -import { escapeRegExp } from "lodash"; import { Terminal } from "../../Terminal"; import { BaseServer } from "../../Server/BaseServer"; import { findRunningScripts, findRunningScriptByPid } from "../../Script/ScriptHelpers"; import { LogBoxEvents } from "../../ui/React/LogBoxManager"; -import { hasScriptExtension, ScriptFilePath } from "../../Paths/ScriptFilePath"; -import { RunningScript } from "../../Script/RunningScript"; -import { matchScriptPathExact } from "../../utils/helpers/scriptKey"; -import { StdIO } from "../StdIO/StdIO"; +import { hasScriptExtension } from "../../Paths/ScriptFilePath"; -export function tail(commandArray: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { +export function tail(commandArray: (string | number | boolean)[], server: BaseServer): void { try { if (commandArray.length < 1) { Terminal.error("Incorrect number of arguments. Usage: tail [pid] or tail [scriptname] [arg1] [arg2]..."); @@ -18,32 +14,18 @@ export function tail(commandArray: (string | number | boolean)[], server: BaseSe if (!path) return Terminal.error(`Invalid filename: ${rawName}`); if (!hasScriptExtension(path)) return Terminal.error(`Invalid file extension. Tail can only be used on scripts.`); - // Only select from name match if there is no ambiguity and no argument filter specified - const scriptsMatchingName = commandArray.length === 1 ? findRunningScriptsByFilename(path, server) : null; - const scriptMatchingName = scriptsMatchingName?.size === 1 ? scriptsMatchingName.values().next() : null; - - // Check for exact matches with specified arguments const candidates = findRunningScripts(path, args, server); - if (candidates === null && (scriptsMatchingName?.size ?? 0) > 1) { - Terminal.error( - `Multiple scripts named ${path} are running on the server. ` + - `Specify arguments to pick which script to tail.`, - ); - return; - } - // if there's no candidate then we just don't know. - if (candidates === null && scriptMatchingName === null) { + if (candidates === null) { Terminal.error(`No script named ${path} with args ${JSON.stringify(args)} is running on the server`); return; } - // Just use the first one (if there are multiple with the same // arguments, they can't be distinguished except by pid). - const next = scriptMatchingName ?? candidates?.values().next(); - if (next && !next.done) { - handleTail(next.value, stdIO); + const next = candidates.values().next(); + if (!next.done) { + LogBoxEvents.emit(next.value); } } else if (typeof commandArray[0] === "number") { const runningScript = findRunningScriptByPid(commandArray[0]); @@ -51,34 +33,10 @@ export function tail(commandArray: (string | number | boolean)[], server: BaseSe Terminal.error(`No script with PID ${commandArray[0]} is running`); return; } - handleTail(runningScript, stdIO); + LogBoxEvents.emit(runningScript); } } catch (error) { console.error(error); Terminal.error(String(error)); } } - -function handleTail(script: RunningScript, stdIO: StdIO): void { - if (!stdIO.stdout) { - return LogBoxEvents.emit(script); - } - - script.tailStdOut = stdIO; - script.logs.forEach((log) => { - script.tailStdOut?.write?.(log); - }); -} - -function findRunningScriptsByFilename(path: ScriptFilePath, server: BaseServer): Map | null { - const result = new Map(); - const pattern = matchScriptPathExact(escapeRegExp(path)); - for (const [key, runningScriptMap] of server.runningScriptMap.entries()) { - if (pattern.test(key)) { - for (const [pid, runningScript] of runningScriptMap.entries()) { - result.set(pid, runningScript); - } - } - } - return result.size > 0 ? result : null; -} diff --git a/src/Terminal/commands/top.ts b/src/Terminal/commands/top.ts index cc22906cc..7c6a0263a 100644 --- a/src/Terminal/commands/top.ts +++ b/src/Terminal/commands/top.ts @@ -1,11 +1,10 @@ import { Terminal } from "../../Terminal"; import { BaseServer } from "../../Server/BaseServer"; import { formatRam } from "../../ui/formatNumber"; -import { StdIO } from "../StdIO/StdIO"; -export function top(args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { +export function top(args: (string | number | boolean)[], server: BaseServer): void { if (args.length !== 0) { - Terminal.error("Incorrect usage of top command. Usage: top", stdIO); + Terminal.error("Incorrect usage of top command. Usage: top"); return; } @@ -25,7 +24,7 @@ export function top(args: (string | number | boolean)[], server: BaseServer, std const headers = `${scriptTxt}${spacesAfterScriptTxt}${pidTxt}${spacesAfterPidTxt}${threadsTxt}${spacesAfterThreadsTxt}${ramTxt}`; - Terminal.print(headers, stdIO); + Terminal.print(headers); const currRunningScripts = server.runningScriptMap; // Iterate through scripts on current server @@ -49,7 +48,7 @@ export function top(args: (string | number | boolean)[], server: BaseServer, std const entry = [script.filename, spacesScript, script.pid, spacesPid, script.threads, spacesThread, ramUsage].join( "", ); - Terminal.print(entry, stdIO); + Terminal.print(entry); } } } diff --git a/src/Terminal/commands/weaken.ts b/src/Terminal/commands/weaken.ts index 0b0ad45ed..4de82a491 100644 --- a/src/Terminal/commands/weaken.ts +++ b/src/Terminal/commands/weaken.ts @@ -1,13 +1,12 @@ import { Terminal } from "../../Terminal"; import { BaseServer } from "../../Server/BaseServer"; -import { StdIO } from "../StdIO/StdIO"; -export function weaken(args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { - if (args.length !== 0) return Terminal.error("Incorrect usage of weaken command. Usage: weaken", stdIO); +export function weaken(args: (string | number | boolean)[], server: BaseServer): void { + if (args.length !== 0) return Terminal.error("Incorrect usage of weaken command. Usage: weaken"); - if (server.purchasedByPlayer) return Terminal.error("Cannot weaken your own machines!", stdIO); - if (!server.hasAdminRights) return Terminal.error("You do not have admin rights for this machine!", stdIO); + if (server.purchasedByPlayer) return Terminal.error("Cannot weaken your own machines!"); + if (!server.hasAdminRights) return Terminal.error("You do not have admin rights for this machine!"); // Weaken does not require meeting the hacking level, but undefined requiredHackingSkill indicates the wrong type of server. - if (server.requiredHackingSkill === undefined) return Terminal.error("Cannot weaken this server.", stdIO); - Terminal.startWeaken(stdIO); + if (server.requiredHackingSkill === undefined) return Terminal.error("Cannot weaken this server."); + Terminal.startWeaken(); } diff --git a/src/Terminal/commands/wget.ts b/src/Terminal/commands/wget.ts index 5c02330cf..a7482b654 100644 --- a/src/Terminal/commands/wget.ts +++ b/src/Terminal/commands/wget.ts @@ -2,56 +2,34 @@ import { Terminal } from "../../Terminal"; import { BaseServer } from "../../Server/BaseServer"; import { hasScriptExtension } from "../../Paths/ScriptFilePath"; import { hasTextExtension } from "../../Paths/TextFilePath"; -import { StdIO } from "../StdIO/StdIO"; -// TODO-FICO: unit tests -export function wget(args: (string | number | boolean)[], server: BaseServer, stdIO: StdIO): void { - if (args.length === 2 && stdIO.stdout) { - Terminal.error( - "Incorrect use of wget command. Either specify a destination file or redirect the output with a pipe, not both.", - ); - return; - } - const [source, fileName] = args; - - const argCountIsValid = (args.length === 1 && stdIO.stdout) || args.length === 2; - const arg1IsValid = typeof source === "string"; - const arg2IsValid = (args.length === 1 && stdIO.stdout) || typeof fileName === "string"; - if (!argCountIsValid || !arg1IsValid || !arg2IsValid) { - Terminal.error("Incorrect usage of wget command. Usage: wget [url] [target file]", stdIO); - return; - } - const target = Terminal.getFilepath(`${fileName}`); - if (args.length === 2 && (!target || (!hasScriptExtension(target) && !hasTextExtension(target)))) { - Terminal.error(`wget failed: Invalid target file. Target file must be a script file or a text file.`, stdIO); +export function wget(args: (string | number | boolean)[], server: BaseServer): void { + if (args.length !== 2 || typeof args[0] !== "string" || typeof args[1] !== "string") { + Terminal.error("Incorrect usage of wget command. Usage: wget [url] [target file]"); return; } - fetch(source) + const target = Terminal.getFilepath(args[1]); + if (!target || (!hasScriptExtension(target) && !hasTextExtension(target))) { + Terminal.error(`wget failed: Invalid target file. Target file must be a script file or a text file.`); + return; + } + + fetch(args[0]) .then(async (response) => { if (response.status !== 200) { - Terminal.error(`wget failed. HTTP code: ${response.status}.`, stdIO); + Terminal.error(`wget failed. HTTP code: ${response.status}.`); return; } - const content = await response.text(); - if (stdIO.stdout) { - Terminal.printAndBypassPipes(`wget successfully retrieved content`); - stdIO.write(content); - stdIO.close(); - return; - } - - if (target && (hasScriptExtension(target) || hasTextExtension(target))) { - const writeResult = server.writeToContentFile(target, content); - if (writeResult.overwritten) { - Terminal.print(`wget successfully retrieved content and overwrote ${target}`); - } else { - Terminal.print(`wget successfully retrieved content to new file ${target}`); - } + const writeResult = server.writeToContentFile(target, await response.text()); + if (writeResult.overwritten) { + Terminal.print(`wget successfully retrieved content and overwrote ${target}`); + } else { + Terminal.print(`wget successfully retrieved content to new file ${target}`); } }) .catch((reason) => { // Check the comment in wget of src\NetscriptFunctions.ts to see why we use Object.getOwnPropertyNames. - Terminal.error(`wget failed: ${JSON.stringify(reason, Object.getOwnPropertyNames(reason))}`, stdIO); + Terminal.error(`wget failed: ${JSON.stringify(reason, Object.getOwnPropertyNames(reason))}`); }); } diff --git a/src/Terminal/getTabCompletionPossibilities.ts b/src/Terminal/getTabCompletionPossibilities.ts index 279785299..c5dbcf065 100644 --- a/src/Terminal/getTabCompletionPossibilities.ts +++ b/src/Terminal/getTabCompletionPossibilities.ts @@ -16,19 +16,13 @@ import { Terminal } from "../Terminal"; import { parseUnknownError } from "../utils/ErrorHelper"; import { DarknetServer } from "../Server/DarknetServer"; import { CompletedProgramName } from "@enums"; -import { getCommandAfterLastPipe } from "./StdIO/utils"; /** Suggest all completion possibilities for the last argument in the last command being typed * @param terminalText The current full text entered in the terminal * @param baseDir The current working directory. * @returns Array of possible string replacements for the current text being autocompleted. */ -export async function getTabCompletionPossibilities(fullTerminalText: string, baseDir = root): Promise { - // Get the text in the terminal after the most recent pipe character - const terminalText = getCommandAfterLastPipe(fullTerminalText); - // True if there is a pipe in the terminal text - const isInPipe = fullTerminalText !== terminalText; - +export async function getTabCompletionPossibilities(terminalText: string, baseDir = root): Promise { // Get the current command text const currentText = /[^ ]*$/.exec(terminalText)?.[0] ?? ""; // Remove the current text from the commands string @@ -81,10 +75,9 @@ export async function getTabCompletionPossibilities(fullTerminalText: string, ba function addGeneric({ iterable, usePathing, ignoreCurrent }: AddAllGenericOptions) { const requiredStart = usePathing ? pathingRequiredMatch : requiredMatch; for (const member of iterable) { - const itemToAdd = usePathing ? relativeDir + member.substring(baseDir.length) : member; - if ((ignoreCurrent && member.length <= requiredStart.length) || possibilities.includes(itemToAdd)) continue; + if (ignoreCurrent && member.length <= requiredStart.length) continue; if (member.toLowerCase().startsWith(requiredStart)) { - possibilities.push(itemToAdd); + possibilities.push(usePathing ? relativeDir + member.substring(baseDir.length) : member); } } } @@ -170,6 +163,7 @@ export async function getTabCompletionPossibilities(fullTerminalText: string, ba addCodingContracts(); } } + switch (commandArray[0]) { case "buy": addDarkwebItems(); @@ -273,14 +267,6 @@ export async function getTabCompletionPossibilities(fullTerminalText: string, ba if (options) { addGeneric({ iterable: options, usePathing: false }); } - } else { - // Add script names if you are in a command - scripts can be run by name - addScripts(); - - // Include text files if the command is part of a pipe - if (isInPipe) { - addTextFiles(); - } } return possibilities; } diff --git a/src/Terminal/ui/TerminalInput.tsx b/src/Terminal/ui/TerminalInput.tsx index d92436bdd..d4db28522 100644 --- a/src/Terminal/ui/TerminalInput.tsx +++ b/src/Terminal/ui/TerminalInput.tsx @@ -231,11 +231,11 @@ export function TerminalInput(): React.ReactElement { if (event.key === KEY.ENTER) { event.preventDefault(); const command = searchResults.length ? searchResults[searchResultsIndex] : value; - Terminal.printAndBypassPipes(`[${Player.getCurrentServer().hostname} /${Terminal.cwd()}]> ${command}`); + Terminal.print(`[${Player.getCurrentServer().hostname} /${Terminal.cwd()}]> ${command}`); if (command) { + Terminal.executeCommands(command); saveValue(""); resetSearch(); - await Terminal.executeCommands(command); } return; } diff --git a/src/ui/React/ANSIITypography.tsx b/src/ui/React/ANSIITypography.tsx index 73419bfe3..6955f365a 100644 --- a/src/ui/React/ANSIITypography.tsx +++ b/src/ui/React/ANSIITypography.tsx @@ -7,7 +7,7 @@ import { Settings } from "../../Settings/Settings"; // This particular eslint-disable is correct. // In this super specific weird case we in fact do want a regex on an ANSII character. // eslint-disable-next-line no-control-regex -export const ANSI_ESCAPE = new RegExp("\u{001b}\\[(?.*?)m", "ug"); +const ANSI_ESCAPE = new RegExp("\u{001b}\\[(?.*?)m", "ug"); const useStyles = makeStyles()((theme: Theme) => ({ success: { diff --git a/src/utils/APIBreaks/APIBreak.ts b/src/utils/APIBreaks/APIBreak.ts index dfd64e349..d152dbb6a 100644 --- a/src/utils/APIBreaks/APIBreak.ts +++ b/src/utils/APIBreaks/APIBreak.ts @@ -8,7 +8,6 @@ import { resolveTextFilePath } from "../../Paths/TextFilePath"; import { dialogBoxCreate as dialogBoxCreateOriginal } from "../../ui/React/DialogBox"; import { Terminal } from "../../Terminal"; import { pluralize } from "../I18nUtils"; -import { getTerminalStdIO } from "../../Terminal/StdIO/RedirectIO"; // Temporary until fixing alerts manager to store alerts outside of react scope const dialogBoxCreate = (text: string) => @@ -172,12 +171,8 @@ export function showAPIBreaks(version: string, { additionalText, apiBreakingChan textFileName, `API BREAK INFO FOR ${version}\n\n${details.map((detail) => detail.text).join("\n\n\n\n")}`, ); - const stdIO = getTerminalStdIO(); - Terminal.warn(`AN API BREAK FROM VERSION ${version} MAY HAVE AFFECTED SOME OF YOUR SCRIPTS.`, stdIO); - Terminal.warn( - `INFORMATION ABOUT THIS POTENTIAL IMPACT HAS BEEN LOGGED IN ${textFileName} ON YOUR HOME COMPUTER.`, - stdIO, - ); + Terminal.warn(`AN API BREAK FROM VERSION ${version} MAY HAVE AFFECTED SOME OF YOUR SCRIPTS.`); + Terminal.warn(`INFORMATION ABOUT THIS POTENTIAL IMPACT HAS BEEN LOGGED IN ${textFileName} ON YOUR HOME COMPUTER.`); dialogBoxCreate( `SOME OF YOUR SCRIPTS HAVE POTENTIALLY BEEN IMPACTED BY AN API BREAK, DUE TO CHANGES IN VERSION ${version}\n\n` + "The following dialog boxes will provide details of the potential impact to your scripts.\n" + @@ -202,9 +197,7 @@ export function showAPIBreaks(version: string, { additionalText, apiBreakingChan (detail.apiBreakInfo.brokenAPIs.length > 0 ? `\n\nWe found ${pluralize(detail.totalDetectedLines, "affected line")}.` : ""), - stdIO, ); ++warningIndex; } - stdIO.close(); } diff --git a/test/jest/Netscript/RunScript.test.ts b/test/jest/Netscript/RunScript.test.ts index c787afefc..715674d3c 100644 --- a/test/jest/Netscript/RunScript.test.ts +++ b/test/jest/Netscript/RunScript.test.ts @@ -14,7 +14,6 @@ import { WorkerScript } from "../../../src/Netscript/WorkerScript"; import { NetscriptFunctions } from "../../../src/NetscriptFunctions"; import type { PositiveInteger } from "../../../src/types"; import { ErrorState } from "../../../src/ErrorHandling/ErrorState"; -import { getTerminalStdIO } from "../../../src/Terminal/StdIO/RedirectIO"; fixDoImportIssue(); @@ -37,7 +36,7 @@ async function expectErrorWhenRunningScript( for (const script of scripts) { Player.getHomeComputer().writeToScriptFile(script.filePath, script.code); } - runScript(testScriptPath, [], Player.getHomeComputer(), getTerminalStdIO()); + runScript(testScriptPath, [], Player.getHomeComputer()); const workerScript = workerScripts.get(1); if (!workerScript) { throw new Error(`Invalid worker script`); @@ -146,7 +145,7 @@ describe("runScript and runScriptFromScript", () => { ns.print(server.hostname); }`, ); - runScript(testScriptPath, [], Player.getHomeComputer(), getTerminalStdIO()); + runScript(testScriptPath, [], Player.getHomeComputer()); const workerScript = workerScripts.get(1); if (!workerScript) { throw new Error(`Invalid worker script`); @@ -161,7 +160,7 @@ describe("runScript and runScriptFromScript", () => { }); describe("Failure", () => { test("Script does not exist", () => { - runScript(testScriptPath, [], Player.getHomeComputer(), getTerminalStdIO()); + runScript(testScriptPath, [], Player.getHomeComputer()); expect((Terminal.outputHistory[1] as { text: string }).text).toContain( `Script ${testScriptPath} does not exist on home`, ); @@ -173,7 +172,7 @@ describe("runScript and runScriptFromScript", () => { `export async function main(ns) { }`, ); - runScript(testScriptPath, [], server, getTerminalStdIO()); + runScript(testScriptPath, [], server); expect((Terminal.outputHistory[1] as { text: string }).text).toContain( `You do not have root access on ${server.hostname}`, ); @@ -185,7 +184,7 @@ describe("runScript and runScriptFromScript", () => { { }`, ); - runScript(testScriptPath, [], Player.getHomeComputer(), getTerminalStdIO()); + runScript(testScriptPath, [], Player.getHomeComputer()); expect((Terminal.outputHistory[1] as { text: string }).text).toContain( `Cannot calculate RAM usage of ${testScriptPath}`, ); @@ -197,7 +196,7 @@ describe("runScript and runScriptFromScript", () => { ns.ramOverride(1024); }`, ); - runScript(testScriptPath, [], Player.getHomeComputer(), getTerminalStdIO()); + runScript(testScriptPath, [], Player.getHomeComputer()); expect((Terminal.outputHistory[1] as { text: string }).text).toContain("This script requires 1.02TB of RAM"); }); test("Thrown error in main function", async () => { diff --git a/test/jest/Save.test.ts b/test/jest/Save.test.ts index a98c68186..638f8e403 100644 --- a/test/jest/Save.test.ts +++ b/test/jest/Save.test.ts @@ -59,12 +59,6 @@ function loadStandardServers() { "ramUsage": 1.6, "server": "home", "scriptKey": "script.js*[]", - "stdin": null, - "tailStdOut": null, - "terminalStdOut": { - "stdin": null, - "stdout": null - }, "temporary": true, "dependencies": [ { @@ -91,12 +85,6 @@ function loadStandardServers() { "ramUsage": 1.6, "server": "home", "scriptKey": "script.js*[]", - "stdin": null, - "tailStdOut": null, - "terminalStdOut": { - "stdin": null, - "stdout": null - }, "title": "Awesome Script", "dependencies": [ { diff --git a/test/jest/Terminal/Pipes.test.ts b/test/jest/Terminal/Pipes.test.ts deleted file mode 100644 index 591c25d27..000000000 --- a/test/jest/Terminal/Pipes.test.ts +++ /dev/null @@ -1,494 +0,0 @@ -import { Terminal } from "../../../src/Terminal"; -import { GetServer, prestigeAllServers } from "../../../src/Server/AllServers"; -import { Player } from "@player"; -import { type TextFilePath } from "../../../src/Paths/TextFilePath"; -import { type ScriptFilePath } from "../../../src/Paths/ScriptFilePath"; -import { LiteratureName, MessageFilename } from "@enums"; -import { fixDoImportIssue, initGameEnvironment } from "../Utilities"; -import { runScript } from "../../../src/Terminal/commands/runScript"; -import { getTerminalStdIO } from "../../../src/Terminal/StdIO/RedirectIO"; - -const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); - -fixDoImportIssue(); -initGameEnvironment(); - -describe("Terminal Pipes", () => { - beforeEach(() => { - prestigeAllServers(); - Player.init(); - Terminal.outputHistory = []; - GetServer(Player.currentServer)?.textFiles.clear(); - GetServer(Player.currentServer)?.scripts.clear(); - }); - - describe("piping to files", () => { - it("should handle piping to a file", async () => { - const fileName = "output.txt"; - const command = `echo 'Hello World' > ${fileName}`; - await Terminal.executeCommands(command); - - const server = GetServer(Player.currentServer); - const fileContent = server?.textFiles?.get(fileName as TextFilePath)?.text; - - expect(JSON.stringify(Terminal.outputHistory)).toBe("[]"); - expect(fileContent).toBe("Hello World"); - }); - - it("should reject invalid text filenames", async () => { - const invalidFileName = 'a".txt'; - const command = `echo 'Hello World' > ${invalidFileName}`; - await Terminal.executeCommands(command); - - const mostRecentOutput = Terminal.outputHistory[Terminal.outputHistory.length - 1]; - expect(mostRecentOutput?.text).toBe(`Invalid file path provided: ${invalidFileName}`); - }); - - it("should reject invalid script filenames", async () => { - const invalidFileName = 'a".js'; - const command = `echo 'Hello World' > ${invalidFileName}`; - await Terminal.executeCommands(command); - - const mostRecentOutput = Terminal.outputHistory[Terminal.outputHistory.length - 1]; - expect(mostRecentOutput?.text).toBe(`Invalid file path provided: ${invalidFileName}`); - }); - - it("should append to a file when using >> operator", async () => { - const fileName = "output.txt"; - const commandString = `echo first line >> ${fileName}; echo second line >> ${fileName}`; - - await Terminal.executeCommands(commandString); - - const server = GetServer(Player.currentServer); - const fileContent = server?.textFiles?.get(fileName as TextFilePath)?.text; - - expect(JSON.stringify(Terminal.outputHistory)).toBe("[]"); - expect(fileContent).toBe("first line\nsecond line"); - }); - - it("should overwrite a file when using > operator", async () => { - const fileName = "output.txt"; - const commandString = `echo first line > ${fileName}; echo second line > ${fileName}`; - - await Terminal.executeCommands(commandString); - - const server = GetServer(Player.currentServer); - const fileContent = server?.textFiles?.get(fileName as TextFilePath)?.text; - expect(fileContent).toBe("second line"); - }); - - it("should only overwrite file contents once per > pipe", async () => { - // Add file to server with content - const outputFileName = "scriptOutput9.txt" as TextFilePath; - const startingData = "startingData"; - const commandString = `echo ${startingData} > ${outputFileName}`; - await Terminal.executeCommands(commandString); - - const scriptName = "testScript.js" as ScriptFilePath; - const scriptContent = `export async function main(ns) { ns.tprint(ns.args); await ns.sleep(100); ns.tprint(ns.args); }`; - - // Add script to server - await Terminal.executeCommands(`echo '${scriptContent}' > ${scriptName}`); - - // Pass arguments to script via pipe - const command = `run ${scriptName} test1 > ${outputFileName}`; - await Terminal.executeCommands(command); - await sleep(200); - - const server = GetServer(Player.currentServer); - const fileContent = server?.textFiles?.get(outputFileName)?.text; - - expect(Terminal.outputHistory.length).toBe(1); - expect(fileContent).toContain(`${scriptName}: ["test1"]\n${scriptName}: ["test1"]`); - expect(fileContent).not.toContain(startingData); - }); - - it("should only overwrite file contents once per > pipe when arguments are piped in", async () => { - // Add file to server with content - const outputFileName = "scriptOutput8.txt" as TextFilePath; - const startingData = "startingData"; - const commandString = `echo ${startingData} > ${outputFileName}`; - await Terminal.executeCommands(commandString); - - const scriptName = "testScript.js" as ScriptFilePath; - const scriptContent = `export async function main(ns) { ns.tprint(ns.getStdin().read()); await ns.sleep(100); ns.tprint(ns.getStdin().read()); }`; - - // Add script to server - await Terminal.executeCommands(`echo '${scriptContent}' > ${scriptName}`); - - // Pass arguments to script via pipe - const command = `echo test1 test2 | ${scriptName} > ${outputFileName}`; - await Terminal.executeCommands(command); - await sleep(200); - - const server = GetServer(Player.currentServer); - const fileContent = server?.textFiles?.get(outputFileName)?.text; - - expect(Terminal.outputHistory.length).toBe(1); - expect(fileContent).toContain(`${scriptName}: test1 test2\n${scriptName}: null`); - expect(fileContent).not.toContain(startingData); - }); - - it("should not permit overwriting a script file with content", async () => { - const fileName = "output.js"; - const commandString = `echo 'Hello World' > ${fileName}; echo 'Malicious Content' > ${fileName}`; - - await Terminal.executeCommands(commandString); - - const server = GetServer(Player.currentServer); - const fileContent = server?.scripts?.get(fileName as ScriptFilePath)?.content; - - expect(fileContent).toContain("Hello World"); - }); - }); - - describe("piping multiple inputs", () => { - it("should handle multiple commands with distinct pipes", async () => { - const fileName1 = "output.txt"; - const fileName2 = "output2.txt"; - const commandString = `echo test > ${fileName1}; echo test2 > ${fileName2}`; - await Terminal.executeCommands(commandString); - - expect(JSON.stringify(Terminal.outputHistory)).toBe("[]"); - - const server = GetServer(Player.currentServer); - const fileContent1 = server?.textFiles?.get(fileName1 as TextFilePath)?.text; - expect(fileContent1).toBe("test"); - - const fileContent2 = server?.textFiles?.get(fileName2 as TextFilePath)?.text; - expect(fileContent2).toBe("test2"); - }); - - it("passes all piped inputs to the output command", async () => { - await Terminal.executeCommands("echo 1337 > file1.txt"); - const command = "cat file1.txt > file2.txt"; - await Terminal.executeCommands(command); - await sleep(100); - - const server = GetServer(Player.currentServer); - const fileContent = server?.textFiles?.get("file2.txt" as TextFilePath)?.text; - expect(fileContent).toBe("1337"); - }); - }); - - describe("cat and echo with pipes", () => { - it("should pipe cat file contents to specified output", async () => { - const fileName = "test4.txt"; - const fileContent = "This is a test file."; - await Terminal.executeCommands(`echo '${fileContent}' > ${fileName}`); - await Terminal.executeCommands(`cat '${fileName}' | cat`); - - const server = GetServer(Player.currentServer); - const newFileContent = server?.textFiles?.get(fileName as TextFilePath)?.text; - expect(newFileContent).toBe(fileContent); - - const lastOutput = Terminal.outputHistory[Terminal.outputHistory.length - 1]; - expect(lastOutput.text).toContain(fileContent); - }); - - it("should pipe cat .lit file contents to specified output", async () => { - const fileName = "test.txt"; - const server = GetServer(Player.currentServer); - server?.messages.push(LiteratureName.HackersStartingHandbook); - - await Terminal.executeCommands(`cat ${LiteratureName.HackersStartingHandbook} > ${fileName}`); - await Terminal.executeCommands(`cat ${fileName} | cat `); - - const newFileContent = server?.textFiles?.get(fileName as TextFilePath)?.text; - expect(newFileContent).toContain("hacking is the most profitable way to earn money and progress"); - - const lastOutput = Terminal.outputHistory[Terminal.outputHistory.length - 1]; - expect(lastOutput.text).toContain("hacking is the most profitable way to earn money and progress"); - }); - - it("should pipe cat message file contents to specified output", async () => { - const fileName = "test3.txt"; - const server = GetServer(Player.currentServer); - server?.messages.push(MessageFilename.TruthGazer); - - await Terminal.executeCommands(`cat ${MessageFilename.TruthGazer} > ${fileName}`); - await Terminal.executeCommands(`cat ${fileName} | cat `); - - const newFileContent = server?.textFiles?.get(fileName as TextFilePath)?.text; - expect(newFileContent).toContain("__ESCAP3__"); - - const lastOutput = Terminal.outputHistory[Terminal.outputHistory.length - 1]; - expect(lastOutput.text).toContain("__ESCAP3__"); - }); - }); - - describe("piping to and from scripts", () => { - it("should handle piping to a script file, and passing arguments into a script to run", async () => { - const scriptName = "testScript2.js" as ScriptFilePath; - const scriptContent = `export function main(ns) { ns.tprint('Input received: ', ns.getStdin().peek()); }`; - - // Add script to server - await Terminal.executeCommands(`echo "${scriptContent}" > ${scriptName}`); - - const content = GetServer(Player.currentServer)?.scripts.get(scriptName)?.content; - expect(content).toBe(scriptContent); - - // Pass arguments to script via pipe - const command = `echo 'data' | run ${scriptName}`; - await Terminal.executeCommands(command); - await sleep(100); - - expect(Terminal.outputHistory[0]?.text).toContain(`Running script with 1 thread`); - expect(Terminal.outputHistory[1]?.text).toEqual(`${scriptName}: Input received: data`); - }); - - it("should piping content out of a script", async () => { - const outputFileName = "scriptOutput4.txt" as TextFilePath; - const scriptName = "testScript.js" as ScriptFilePath; - const scriptContent = `export async function main(ns) { ns.tprint('Input received: ', ns.getStdin().peek()); }`; - - // Add script to server - await Terminal.executeCommands(`echo "${scriptContent}" > ${scriptName}`); - - const content = GetServer(Player.currentServer)?.scripts.get(scriptName)?.content; - expect(content).toBe(scriptContent); - - // Pass arguments to script via pipe - const command = `echo 'data' | ${scriptName} > ${outputFileName}`; - await Terminal.executeCommands(command); - await sleep(200); - - const server = GetServer(Player.currentServer); - const fileContent = server?.textFiles?.get(outputFileName)?.text; - - expect(Terminal.outputHistory.length).toBe(1); - expect(fileContent).toContain(`Input received: data`); - }); - - it("should pipe content out of a script when the run command is used", async () => { - const outputFileName = "scriptOutput3.txt" as TextFilePath; - const scriptName = "testScript.js" as ScriptFilePath; - const scriptContent = `export function main(ns) { ns.tprint('Args received: ', ns.args); }`; - - // Add script to server - await Terminal.executeCommands(`echo "${scriptContent}" > ${scriptName}`); - - const content = GetServer(Player.currentServer)?.scripts.get(scriptName)?.content; - expect(content).toBe(scriptContent); - - // Pass arguments to script via pipe - const command = `run ${scriptName} test1 arguments > ${outputFileName}`; - await Terminal.executeCommands(command); - await sleep(200); - - const server = GetServer(Player.currentServer); - const fileContent = server?.textFiles?.get(outputFileName)?.text; - - expect(Terminal.outputHistory.length).toBe(1); - expect(fileContent).toContain(`Args received: ["test1","arguments"]`); - }); - - it("should correctly pipe each script's async output to its specified location", async () => { - // Add file to server with content - const outputFileName = "scriptOutput.txt" as TextFilePath; - const outputFileName2 = "scriptOutput2.txt" as TextFilePath; - - const scriptName = "testScript.js" as ScriptFilePath; - const scriptContent = `export async function main(ns) { ns.tprint(ns.args); await ns.sleep(100); ns.tprint(ns.args); }`; - - // Add script to server - await Terminal.executeCommands(`echo '${scriptContent}' > ${scriptName}`); - - // Pass arguments to script via pipe - const command = `run ${scriptName} test1 test2 > ${outputFileName}; run ${scriptName} test3 test4 > ${outputFileName2}`; - await Terminal.executeCommands(command); - await sleep(300); - - const server = GetServer(Player.currentServer); - const fileContent = server?.textFiles?.get(outputFileName)?.text; - const fileContent2 = server?.textFiles?.get(outputFileName2)?.text; - - expect(Terminal.outputHistory.length).toBe(2); - expect(fileContent).toContain(`${scriptName}: ["test1","test2"]\n${scriptName}: ["test1","test2"]`); - expect(fileContent2).toContain(`${scriptName}: ["test3","test4"]\n${scriptName}: ["test3","test4"]`); - }); - - it("should correctly pipe a script's async output to a specified destination script", async () => { - // Add file to server with content - const outputFileName = "scriptOutput.txt" as TextFilePath; - - const scriptName = "testScript.js" as ScriptFilePath; - const scriptName2 = "testScript2.js" as ScriptFilePath; - const scriptContent = `export async function main(ns) { ns.tprint(ns.getStdin().peek()); await ns.sleep(80); ns.tprint(ns.getStdin().peek()); }`; - const scriptContent2 = `export async function main(ns) { ns.tprint(ns.getStdin().read()); await ns.sleep(200); ns.tprint(ns.getStdin().read()); ns.tprint(ns.getStdin().read()); }`; - - // Add script to server - await Terminal.executeCommands( - `echo '${scriptContent}' > ${scriptName}; echo '${scriptContent2}' > ${scriptName2}`, - ); - - // Pass arguments to script via pipe - const command = `echo 1 | ${scriptName} | ${scriptName2} > ${outputFileName}`; - await Terminal.executeCommands(command); - await sleep(300); - - const server = GetServer(Player.currentServer); - const fileContent = server?.textFiles?.get(outputFileName)?.text; - - expect(Terminal.outputHistory.length).toBe(2); - expect(fileContent).toContain(`${scriptName2}: ${scriptName}: 1\n${scriptName2}: NULL PORT DATA`); - }); - - it("should correctly pipe each script's async output to its specified destination script", async () => { - // Add file to server with content - const outputFileName = "scriptOutput.txt" as TextFilePath; - const outputFileName2 = "scriptOutput2.txt" as TextFilePath; - - const scriptName = "testScript.js" as ScriptFilePath; - const scriptName2 = "testScript2.js" as ScriptFilePath; - const scriptName3 = "testScript3.js" as ScriptFilePath; - const scriptName4 = "testScript4.js" as ScriptFilePath; - const scriptContent = `export async function main(ns) { ns.tprint(ns.getStdin().peek()); await ns.sleep(80); ns.tprint(ns.getStdin().peek()); }`; - const scriptContent2 = `export async function main(ns) { ns.tprint(ns.getStdin().read()); await ns.sleep(200); ns.tprint(ns.getStdin().read()); ns.tprint(ns.getStdin().read()); }`; - - // Add script to server - await Terminal.executeCommands( - `echo '${scriptContent}' > ${scriptName}; echo '${scriptContent2}' > ${scriptName2}`, - ); - await Terminal.executeCommands(`cat ${scriptName} > ${scriptName3}; cat ${scriptName2} > ${scriptName4};`); - - // Pass arguments to script via pipe - const command = `echo 1 | ${scriptName} | ${scriptName2} > ${outputFileName}; echo 2 | ${scriptName3} | ${scriptName4} > ${outputFileName2}`; - await Terminal.executeCommands(command); - await sleep(300); - - const server = GetServer(Player.currentServer); - const fileContent = server?.textFiles?.get(outputFileName)?.text; - const fileContent2 = server?.textFiles?.get(outputFileName2)?.text; - - expect(Terminal.outputHistory.length).toBe(4); - expect(fileContent).toContain(`${scriptName2}: ${scriptName}: 1\n${scriptName2}: NULL PORT DATA`); - expect(fileContent2).toContain(`${scriptName4}: ${scriptName3}: 2\n${scriptName4}: NULL PORT DATA`); - }); - }); - - describe("input redirection", () => { - it("should use file contents as input stream if input redirection < is used", async () => { - const fileContent = "File input data"; - const fileName = "inputFile.txt"; - await Terminal.executeCommands(`echo '${fileContent}' > ${fileName}`); - - const fileContentOnServer = GetServer(Player.currentServer)?.textFiles?.get(fileName as TextFilePath)?.text; - expect(fileContentOnServer).toBe(fileContent); - - const commandString = `cat < ${fileName} | cat `; - await Terminal.executeCommands(commandString); - - const lastOutput = Terminal.outputHistory[Terminal.outputHistory.length - 1]; - expect(lastOutput?.text).toBe(fileContent); - }); - - it("should return an error if input redirection file does not exist", async () => { - const fileName = "nonExistentFile.txt"; - const commandString = `cat < ${fileName}`; - await Terminal.executeCommands(commandString); - - const lastOutput = Terminal.outputHistory[Terminal.outputHistory.length - 2]; - expect(lastOutput?.text).toBe(`No file at path ${fileName}`); - }); - - it("should return an error if the input redirection is not the first pipe in the chain", async () => { - await Terminal.executeCommands(`echo 'Some data' | cat < inputFile.txt`); - - const error = Terminal.outputHistory[0]; - expect(error?.text).toBe( - `Error in pipe command: Invalid pipe command. Only the first command in a pipe chain can have input redirection '<'.`, - ); - }); - }); - - it("should handle piping content to cat", async () => { - const testContent = "This is a test."; - const commandString = `echo "${testContent}" | cat`; - await Terminal.executeCommands(commandString); - await sleep(50); - - expect(Terminal.outputHistory.length).toBe(1); - expect(Terminal.outputHistory[0].text).toContain(testContent); - }); - - it("should replace $! with the PID of the last script run", async () => { - const scriptName = "testScript.js" as ScriptFilePath; - const scriptContent = `export async function main(ns) { ns.print('Script is running'); await ns.sleep(100); }`; - const server = GetServer(Player.currentServer); - - // Add script to server - await Terminal.executeCommands(`echo "${scriptContent}" > ${scriptName}`); - await sleep(50); - - // Run the script to set PipeState.pidOfLastScriptRun - const runningScript = runScript(scriptName, [], server, getTerminalStdIO()); - const expectedPid = runningScript?.pid; - await sleep(200); - - const command = `echo $! > pidOutput.txt`; - await Terminal.executeCommands(command); - await sleep(50); - const fileContent = server?.textFiles?.get("pidOutput.txt" as TextFilePath)?.text; - - expect(Number(fileContent)).toBe(expectedPid); - }); - - it("should replace $! with -1 if the prior command was not a run", async () => { - const scriptName = "testScript.js" as ScriptFilePath; - const scriptContent = `export async function main(ns) { ns.print("Script is running"); await ns.sleep(100); }`; - const server = GetServer(Player.currentServer); - - // Add script to server - await Terminal.executeCommands(`echo '${scriptContent}' > ${scriptName}`); - await sleep(50); - - // Run the script to set PipeState.pidOfLastScriptRun - await Terminal.executeCommands(`run ${scriptName}`); - await sleep(200); - - await Terminal.executeCommands(`echo "Not a run command"`); - - const command = `echo $! > pidOutput.txt`; - await Terminal.executeCommands(command); - const fileContent = server?.textFiles?.get("pidOutput.txt" as TextFilePath)?.text; - - expect(Number(fileContent)).toBe(-1); - }); - - it("should pipe the tail output of scripts to stdout when specified with $!", async () => { - const scriptContent = `export async function main(ns) {ns.print('foo');await ns.sleep(50);ns.print('test2');}`; - const scriptName = "testScript.jsx" as ScriptFilePath; - const tailOutputFileName = "tailOutput.txt" as TextFilePath; - - // Add script to server - await Terminal.executeCommands(`echo "${scriptContent}" > ${scriptName}`); - - const fileContent = GetServer(Player.currentServer)?.scripts?.get(scriptName)?.content; - expect(fileContent).toBe(scriptContent); - - await Terminal.executeCommands(`run ${scriptName}; tail $! > ${tailOutputFileName}`); - await sleep(200); - - const outputFileContent = GetServer(Player.currentServer)?.textFiles?.get(tailOutputFileName)?.text; - expect(outputFileContent).toContain("foo\nsleep: Sleeping for 0.050 seconds.\ntest2"); - }); - - it("should pipe the tail output of scripts to stdout", async () => { - const scriptContent = `export async function main(ns) {ns.print('foo');await ns.sleep(50);ns.print('test2');}`; - const scriptName = "testScript.jsx" as ScriptFilePath; - const tailOutputFileName = "tailOutput.txt" as TextFilePath; - - // Add script to server - await Terminal.executeCommands(`echo "${scriptContent}" > ${scriptName}`); - - const fileContent = GetServer(Player.currentServer)?.scripts?.get(scriptName)?.content; - expect(fileContent).toBe(scriptContent); - - await Terminal.executeCommands(`run ${scriptName}; tail ${scriptName} > ${tailOutputFileName}`); - await sleep(200); - - const outputFileContent = GetServer(Player.currentServer)?.textFiles?.get(tailOutputFileName)?.text; - expect(outputFileContent).toContain("foo\nsleep: Sleeping for 0.050 seconds.\ntest2"); - }); -}); diff --git a/test/jest/Terminal/RedirectIO.test.ts b/test/jest/Terminal/RedirectIO.test.ts deleted file mode 100644 index cab52c56a..000000000 --- a/test/jest/Terminal/RedirectIO.test.ts +++ /dev/null @@ -1,207 +0,0 @@ -import { IOStream } from "../../../src/Terminal/StdIO/IOStream"; -import { - findCommandsSplitByRedirects, - getTerminalStdIO, - handleCommand, - parseRedirectedCommands, -} from "../../../src/Terminal/StdIO/RedirectIO"; -import { Terminal } from "../../../src/Terminal"; -import { fixDoImportIssue, initGameEnvironment } from "../Utilities"; -import { GetServer, prestigeAllServers } from "../../../src/Server/AllServers"; -import { Player } from "@player"; -import { StdIO } from "../../../src/Terminal/StdIO/StdIO"; -import { TextFilePath } from "../../../src/Paths/TextFilePath"; -import { ScriptFilePath } from "../../../src/Paths/ScriptFilePath"; -import { Output } from "../../../src/Terminal/OutputTypes"; -import { ANSI_ESCAPE } from "../../../src/ui/React/ANSIITypography"; - -export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); - -fixDoImportIssue(); -initGameEnvironment(); - -describe("RedirectIOTests", () => { - beforeEach(() => { - prestigeAllServers(); - Player.init(); - Terminal.outputHistory = []; - GetServer(Player.currentServer)?.textFiles.clear(); - GetServer(Player.currentServer)?.scripts.clear(); - }); - - it("should redirect output to the terminal correctly from a terminal StdIO", async () => { - const data = "Hello, Terminal!"; - const terminalIO = getTerminalStdIO(null); - terminalIO.write(data); - await sleep(50); - - expect(Terminal.outputHistory.length).toBe(1); - expect(Terminal.outputHistory[0].text).toContain(data); - }); - - it("findCommandsSplitByRedirects should split commands by pipes", () => { - const commandString = "echo Hello > file.txt >> anotherFile.txt | echo World"; - const parsedCommands = commandString.split(" "); - const result = findCommandsSplitByRedirects(parsedCommands); - - expect(result[0]).toEqual(["echo", "Hello"]); - expect(result[1]).toEqual([">", "file.txt"]); - expect(result[2]).toEqual([">>", "anotherFile.txt"]); - expect(result[3]).toEqual(["|", "echo", "World"]); - expect(result.length).toBe(4); - }); - - describe("handleCommand", () => { - it("should handle echo command passing its args to stdout", async () => { - const commandString = "echo Hello, World"; - const stdIO = new StdIO(null); - handleCommand(stdIO, commandString.split(" ")); - await sleep(50); - - expect(stdIO.stdout.empty()).toBe(false); - const output = stdIO.stdout.read(); - expect(output).toBe("Hello, World"); - }); - - it("should handle writing stdin contents to files", async () => { - const filename = "output.txt"; - const commandString = `> ${filename}`; - const stdin = new IOStream(); - const stdIO = new StdIO(stdin); - void handleCommand(stdIO, commandString.split(" ")); - stdin.write("File content line 1"); - stdin.write("File content line 2"); - - await sleep(50); - const server = GetServer(Player.currentServer); - const file = server?.textFiles.get(filename as TextFilePath); - expect(file).toBeDefined(); - expect(file?.content).toBe("File content line 1\nFile content line 2"); - }); - }); - - describe("parseRedirectedCommands", () => { - it("should append echo output redirected to a file", async () => { - const filename = "appendOutput.txt"; - const commandString = `echo First Line >> ${filename} | echo Second Line >> ${filename}`; - - await parseRedirectedCommands(commandString); - - const server = GetServer(Player.currentServer); - const file = server?.textFiles.get(filename as TextFilePath); - expect(file).toBeDefined(); - expect(file?.content).toBe("First Line\nSecond Line"); - }); - - it("should prevent overwriting non-empty script files", async () => { - const filename = "scriptOutput.js"; - const commandString = `echo Hello > ${filename} | echo World > ${filename}`; - - await parseRedirectedCommands(commandString); - - const server = GetServer(Player.currentServer); - const file = server?.scripts.get(filename as ScriptFilePath); - expect(file).toBeDefined(); - expect(file?.content).toBe("Hello"); - }); - }); - - describe("stdout from scripts", () => { - it("should redirect tprint output from a running script to a file", async () => { - const scriptName = "testScript.js"; - const filename = "scriptLog.txt"; - const scriptContent = `export function main(ns) { ns.tprint('Logging to file' ); }`; - await Terminal.executeCommands(`echo "${scriptContent}" > ${scriptName}`); - - const currentScripts = GetServer(Player.currentServer)?.scripts; - const script = currentScripts?.get(scriptName as ScriptFilePath); - expect(script?.content).toBe(scriptContent); - - await Terminal.executeCommands(`run ${scriptName} >> ${filename}`); - await sleep(50); - - const server = GetServer(Player.currentServer); - const file = server?.textFiles.get(filename as TextFilePath); - expect(file?.content).toBe("testScript.js: Logging to file"); - }); - }); - - describe("stdin to scripts", () => { - it("should provide stdin input to a running script", async () => { - const scriptName = "inputScript.js"; - const scriptContent = `export async function main(ns) { - const stdIn = await ns.getStdin(); - if (stdIn?.empty()) { - ns.tprint('No input received yet'); - await stdIn.nextWrite(); - } - const input = stdIn?.read(); - ns.tprint('Received input: ' + input); - }`; - await Terminal.executeCommands(`echo "${scriptContent}" > ${scriptName}`); - - const inputData = "Hello from stdin!"; - await Terminal.executeCommands(`echo "${inputData}" | run ${scriptName}`); - await sleep(50); - - console.log(Terminal.outputHistory); - const outputLog: Output[] = Terminal.outputHistory.filter(isOutput); - const outputText: Output = outputLog.find((entry: Output) => entry.text?.includes("Received input:")); - expect(outputText?.text).toEqual(`${scriptName}: Received input: ${inputData}`); - }); - - it("should provide stdin input from a script to a running script", async () => { - const scriptName = "inputScript.js"; - const scriptContent = `export async function main(ns) { - const stdIn = await ns.getStdin(); - if (stdIn?.empty()) { - ns.tprint('No input received yet'); - await stdIn.nextWrite(); - } - const input = stdIn?.read(); - ns.tprint('Received input: ' + input); - }`; - await Terminal.executeCommands(`echo "${scriptContent}" > ${scriptName}`); - - const inputData = "Hello from stdin!"; - await Terminal.executeCommands(`echo "${inputData}" | ${scriptName} | run ${scriptName}`); - await sleep(50); - - console.log(Terminal.outputHistory); - const outputLog: Output[] = Terminal.outputHistory.filter(isOutput); - const outputText: Output = outputLog.find((entry: Output) => entry.text?.includes("Received input:")); - expect(outputText?.text).toEqual(`${scriptName}: Received input: ${scriptName}: Received input: ${inputData}`); - }); - }); - - describe("cat and grep with redirected IO", () => { - it("should be able to read files to the terminal", async () => { - const filename = "appendOutput.txt"; - const setupCommandString = `echo First Line >> ${filename} | echo Second Line >> ${filename}`; - - await parseRedirectedCommands(setupCommandString); - - await parseRedirectedCommands(`echo 1 | cat ${filename}`); - expect(Terminal.outputHistory.length).toBe(1); - expect(Terminal.outputHistory[0].text).toBe("First Line\nSecond Line1"); - }); - - it("should be able to grep files read by cat", async () => { - const filename = "appendOutput.txt"; - const setupCommandString = `echo First Line >> ${filename} | echo Second Line >> ${filename}`; - - await parseRedirectedCommands(setupCommandString); - - await parseRedirectedCommands(`cat ${filename} | grep Second`); - - expect(Terminal.outputHistory.length).toBe(1); - const log = Terminal.outputHistory[0]; - if (!isOutput(log)) throw new Error("Expected output to be of type Output"); - expect(log.text.replaceAll(ANSI_ESCAPE, "")).toBe("Second Line"); - }); - }); -}); - -function isOutput(entry: unknown): entry is Output { - return !!entry && typeof entry === "object" && entry instanceof Output; -} diff --git a/test/jest/Terminal/cat.test.ts b/test/jest/Terminal/cat.test.ts deleted file mode 100644 index af3a55930..000000000 --- a/test/jest/Terminal/cat.test.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { cat } from "../../../src/Terminal/commands/cat"; -import { GetServerOrThrow, prestigeAllServers } from "../../../src/Server/AllServers"; -import { Player } from "@player"; -import { Terminal } from "../../../src/Terminal"; -import { StdIO } from "../../../src/Terminal/StdIO/StdIO"; -import { IOStream } from "../../../src/Terminal/StdIO/IOStream"; -import { TextFile } from "../../../src/TextFile"; -import { TextFilePath } from "../../../src/Paths/TextFilePath"; -import { LiteratureName, MessageFilename } from "@enums"; -import { Literatures } from "../../../src/Literature/Literatures"; -import { stringifyReactElement } from "../../../src/Terminal/StdIO/utils"; -import { Messages } from "../../../src/Message/MessageHelpers"; - -const fileName = "example.txt" as TextFilePath; -const fileName2 = "example2.txt" as TextFilePath; -const fileContent1 = "This is an example text file."; -const fileContent2 = "This is another example text file."; - -describe("cat command", () => { - beforeEach(() => { - prestigeAllServers(); - Player.init(); - Terminal.outputHistory = []; - const server = GetServerOrThrow(Player.currentServer); - server.textFiles.clear(); - server.scripts.clear(); - server.messages.length = 0; //Remove .lit and .msg files - server.messages.push(LiteratureName.HackersStartingHandbook); - server.messages.push(MessageFilename.Jumper0); - const file = new TextFile(fileName, fileContent1); - server.textFiles.set(fileName, file); - const file2 = new TextFile(fileName2, fileContent2); - server.textFiles.set(fileName2, file2); - }); - - it("should retrieve file contents and pass to stdout", () => { - const server = GetServerOrThrow(Player.currentServer); - - const stdOut = new IOStream(); - const stdIO = new StdIO(null, stdOut); - - cat([fileName, fileName2], server, stdIO); - const output = stdOut.read(); - - expect(output).toBe(`${fileContent1}${fileContent2}`); - }); - - it("should read from stdin when '-' is provided as an argument", () => { - const server = GetServerOrThrow(Player.currentServer); - const stdinStuff = "\nInput from stdin line 1"; - - const stdIn = new IOStream(); - stdIn.write(stdinStuff); - const stdOut = new IOStream(); - const stdIO = new StdIO(stdIn, stdOut); - - cat([fileName, "-", fileName2], server, stdIO); - const output = stdOut.read(); - - expect(output).toBe(`${fileContent1}${stdinStuff}${fileContent2}`); - }); - - it("should read from stdin and concat it last when '-' is not provided as an argument", () => { - const server = GetServerOrThrow(Player.currentServer); - - const stdIn = new IOStream(); - stdIn.write("Input from stdin line 1"); - const stdOut = new IOStream(); - const stdIO = new StdIO(stdIn, stdOut); - - cat([fileName, fileName2], server, stdIO); - const output = stdOut.read(); - - expect(output).toBe(`${fileContent1}${fileContent2}Input from stdin line 1`); - }); - - it("should be able to read .lit files", () => { - const server = GetServerOrThrow(Player.currentServer); - - const stdOut = new IOStream(); - const stdIO = new StdIO(null, stdOut); - - cat([`${LiteratureName.HackersStartingHandbook}`], server, stdIO); - const output = stdOut.read(); - - const bodyText = stringifyReactElement(Literatures[LiteratureName.HackersStartingHandbook].text); - const expectedOutput = `${Literatures[LiteratureName.HackersStartingHandbook].title}\n\n${bodyText}\n`; - - expect(output).toBe(expectedOutput); - expect(output).toContain("When starting out, hacking is the most profitable way to earn money and progress."); - }); - - it("should be able to read msg files", () => { - const server = GetServerOrThrow(Player.currentServer); - - const stdOut = new IOStream(); - const stdIO = new StdIO(null, stdOut); - - cat([`${MessageFilename.Jumper0}`], server, stdIO); - const output = stdOut.read(); - - const text = Messages[MessageFilename.Jumper0].msg + "\n"; - - expect(output).toBe(text); - }); - - it("should be able to concatenate lit and msg files", () => { - const server = GetServerOrThrow(Player.currentServer); - - const stdOut = new IOStream(); - const stdIO = new StdIO(null, stdOut); - - cat([`${LiteratureName.HackersStartingHandbook}`, `${MessageFilename.Jumper0}`], server, stdIO); - const output = stdOut.read(); - - const bodyText = stringifyReactElement(Literatures[LiteratureName.HackersStartingHandbook].text); - const expectedLitOutput = `${Literatures[LiteratureName.HackersStartingHandbook].title}\n\n${bodyText}\n`; - const expectedMsgOutput = Messages[MessageFilename.Jumper0].msg + "\n"; - const expectedOutput = `${expectedLitOutput}${expectedMsgOutput}`; - - expect(output).toBe(expectedOutput); - }); - - it("should be able to concatenate lit and msg files with stdin", () => { - const server = GetServerOrThrow(Player.currentServer); - - const stdIn = new IOStream(); - stdIn.write("Input from stdin line 1"); - const stdOut = new IOStream(); - const stdIO = new StdIO(stdIn, stdOut); - - cat([`${LiteratureName.HackersStartingHandbook}`, "-", `${MessageFilename.Jumper0}`], server, stdIO); - const output = stdOut.read(); - - const bodyText = stringifyReactElement(Literatures[LiteratureName.HackersStartingHandbook].text); - const expectedLitOutput = `${Literatures[LiteratureName.HackersStartingHandbook].title}\n\n${bodyText}\n`; - const expectedMsgOutput = Messages[MessageFilename.Jumper0].msg + "\n"; - const expectedOutput = `${expectedLitOutput}Input from stdin line 1${expectedMsgOutput}`; - - expect(output).toBe(expectedOutput); - }); -}); diff --git a/test/jest/Terminal/grep.test.ts b/test/jest/Terminal/grep.test.ts deleted file mode 100644 index 9c2774557..000000000 --- a/test/jest/Terminal/grep.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { GetServerOrThrow, prestigeAllServers } from "../../../src/Server/AllServers"; -import { Player } from "@player"; -import { Terminal } from "../../../src/Terminal"; -import { StdIO } from "../../../src/Terminal/StdIO/StdIO"; -import { IOStream } from "../../../src/Terminal/StdIO/IOStream"; -import { TextFile } from "../../../src/TextFile"; -import { TextFilePath } from "../../../src/Paths/TextFilePath"; -import { grep } from "../../../src/Terminal/commands/grep"; -import { ScriptFilePath } from "../../../src/Paths/ScriptFilePath"; -import { Script } from "../../../src/Script/Script"; -import { stringify } from "../../../src/Terminal/StdIO/utils"; - -const fileName = "example.txt" as TextFilePath; -const fileName2 = "example2.txt" as TextFilePath; -const fileContent1 = "This is an example text file.\nThis is line 2 of file 1"; -const fileContent2 = "This is another example text file.\nThis is line 2 of file 2"; - -describe("grep command", () => { - beforeEach(() => { - prestigeAllServers(); - Player.init(); - Terminal.outputHistory = []; - const server = GetServerOrThrow(Player.currentServer); - server.textFiles.clear(); - server.scripts.clear(); - const file = new TextFile(fileName, fileContent1); - server.textFiles.set(fileName, file); - const file2 = new TextFile(fileName2, fileContent2); - server.textFiles.set(fileName2, file2); - }); - - it("should retrieve lines matching the pattern from the specified text file", () => { - const server = GetServerOrThrow(Player.currentServer); - - const stdOut = new IOStream(); - const stdIO = new StdIO(null, stdOut); - - grep(["line 2", fileName], server, stdIO); - const output = stdOut.read(); - - expect(Terminal.outputHistory).toEqual([]); - expect(output).toBe(`example.txt:This is line 2 of file 1`); - }); - - it("should retrieve lines matching the pattern from the specified script file", () => { - const server = GetServerOrThrow(Player.currentServer); - const scriptFileName = "script.js" as ScriptFilePath; - const scriptContent = "console.log('Hello World');\n// This is line 2 of the script"; - const scriptFile = new Script(scriptFileName, scriptContent, server.hostname); - server.scripts.set(scriptFileName, scriptFile); - const stdOut = new IOStream(); - const stdIO = new StdIO(null, stdOut); - - grep(["line 2", scriptFileName], server, stdIO); - const output = stdOut.read(); - - expect(Terminal.outputHistory).toEqual([]); - expect(output).toBe(`script.js:// This is line 2 of the script`); - }); - - it("should retrieve lines matching the pattern from stdin", () => { - const server = GetServerOrThrow(Player.currentServer); - - const stdIn = new IOStream(); - stdIn.write("First line from stdin\nThis is line 2 from stdin\nThird line from stdin"); - stdIn.close(); - const stdOut = new IOStream(); - const stdIO = new StdIO(stdIn, stdOut); - - grep(["line 2"], server, stdIO); - const output = stdOut.read(); - - expect(Terminal.outputHistory).toEqual([]); - expect(output).toBe(`This is line 2 from stdin`); - }); - - it("should grep input piped from cat", async () => { - await Terminal.executeCommands(`cat ${fileName} ${fileName2} | grep "line 2"`); - const lastOutput = Terminal.outputHistory[Terminal.outputHistory.length - 1]; - // Output from cat will not have filenames, and will not add additional newlines between file contents - expect(stringify(lastOutput.text, true)).toBe( - `This is line 2 of file 1This is another example text file.\nThis is line 2 of file 2`, - ); - }); -}); diff --git a/test/jest/__snapshots__/Save.test.ts.snap b/test/jest/__snapshots__/Save.test.ts.snap index 29afc0a0d..6d0cd5375 100644 --- a/test/jest/__snapshots__/Save.test.ts.snap +++ b/test/jest/__snapshots__/Save.test.ts.snap @@ -82,12 +82,6 @@ exports[`load/saveAllServers 1`] = ` "ramUsage": 1.6, "server": "home", "scriptKey": "script.js*[]", - "stdin": null, - "tailStdOut": null, - "terminalStdOut": { - "stdin": null, - "stdout": null - }, "title": "Awesome Script", "threads": 1, "temporary": false