mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-16 06:18:42 +02:00
This reverts commit 92b8b58588.
Accidental merge on my part - the code is in decent shape, but isn't meant to go in for 3.0.
This commit is contained in:
@@ -1,23 +0,0 @@
|
||||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
</td></tr>
|
||||
<tr><td>
|
||||
|
||||
[getStdin()](./bitburner.ns.getstdin.md)
|
||||
|
||||
|
||||
</td><td>
|
||||
|
||||
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.
|
||||
|
||||
|
||||
</td></tr>
|
||||
<tr><td>
|
||||
|
||||
|
||||
@@ -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 {
|
||||
<>
|
||||
<span>{item.program}</span> - <span>{cost}</span> - <span>{item.description}</span>
|
||||
</>,
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -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;
|
||||
|
||||
@@ -664,7 +664,6 @@ export const RamCosts: RamCostTree<NSFull> = {
|
||||
tprintRaw: 0,
|
||||
printRaw: 0,
|
||||
dynamicImport: 0,
|
||||
getStdin: 0,
|
||||
|
||||
formulas: {
|
||||
mockServer: 0,
|
||||
|
||||
@@ -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<NSFull> = {
|
||||
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<NSFull> = {
|
||||
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<NSFull> = {
|
||||
//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(),
|
||||
|
||||
@@ -468,7 +468,7 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
|
||||
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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<CompletedProgramName, Program> = {
|
||||
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<CompletedProgramName, Program> = {
|
||||
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<CompletedProgramName, Program> = {
|
||||
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<CompletedProgramName, Program> = {
|
||||
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<CompletedProgramName, Program> = {
|
||||
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<CompletedProgramName, Program> = {
|
||||
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<CompletedProgramName, Program> = {
|
||||
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<CompletedProgramName, Program> = {
|
||||
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<CompletedProgramName, Program> = {
|
||||
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<CompletedProgramName, Program> = {
|
||||
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<CompletedProgramName, Program> = {
|
||||
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, Program> = {
|
||||
[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<CompletedProgramName, Program> = {
|
||||
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);
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
15
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
15
src/ScriptEditor/NetscriptDefinitions.d.ts
vendored
@@ -8991,21 +8991,6 @@ export interface NS {
|
||||
*/
|
||||
dynamicImport(path: string): Promise<any>;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<string, string[]> = {
|
||||
" ",
|
||||
],
|
||||
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<string, string[]> = {
|
||||
" ",
|
||||
" 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<string, string[]> = {
|
||||
"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]",
|
||||
" ",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<void> {
|
||||
return this.handle.nextWrite();
|
||||
}
|
||||
|
||||
peek(): unknown {
|
||||
return this.handle.peek();
|
||||
}
|
||||
|
||||
read(): unknown {
|
||||
return this.handle.read();
|
||||
}
|
||||
}
|
||||
@@ -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<void>[] = [];
|
||||
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> | 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<void> {
|
||||
while (stdio.stdout && !stdio.stdout?.isClosed) {
|
||||
await stdio.stdout.nextWrite();
|
||||
}
|
||||
}
|
||||
@@ -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<IOStream> | 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();
|
||||
}
|
||||
}
|
||||
@@ -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("<br/>", "\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);
|
||||
}
|
||||
@@ -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<string, (args: (string | number | boolean)[], server: BaseServer) => 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<void> {
|
||||
async runContract(contractPath: ContractFilePath): Promise<void> {
|
||||
// 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<void> {
|
||||
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 {
|
||||
|
||||
@@ -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"]');
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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.`);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}
|
||||
</LongListItem>,
|
||||
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(<SegmentGrid colSize={colSize}>{segmentElements}</SegmentGrid>, stdIO);
|
||||
Terminal.printRaw(<SegmentGrid colSize={colSize}>{segmentElements}</SegmentGrid>);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)");
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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.`);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<number, RunningScript> | null {
|
||||
const result = new Map<number, RunningScript>();
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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))}`);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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<string[]> {
|
||||
// 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<string[]> {
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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}\\[(?<code>.*?)m", "ug");
|
||||
const ANSI_ESCAPE = new RegExp("\u{001b}\\[(?<code>.*?)m", "ug");
|
||||
|
||||
const useStyles = makeStyles()((theme: Theme) => ({
|
||||
success: {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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": [
|
||||
{
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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`,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user