MISC: Remove support for running NS1 scripts (#2083)

This commit is contained in:
catloversg
2025-04-12 03:41:48 +07:00
committed by GitHub
parent 571cc8f886
commit a9900072da
24 changed files with 86 additions and 347 deletions

View File

@@ -28,7 +28,7 @@ Data in the specified text file.
RAM cost: 0 GB
This function is used to read data from a text file (.txt, .json) or script (.js, .jsx, .ts, .tsx, .script).
This function is used to read data from a text file (.txt, .json) or script (.js, .jsx, .ts, .tsx).
This function will return the data in the specified file. If the file does not exist, an empty string will be returned.

View File

@@ -30,7 +30,7 @@ True if the data was successfully retrieved from the URL, false otherwise.
RAM cost: 0 GB
Retrieves data from a URL and downloads it to a file on the specified server. The data can only be downloaded to a script (.js, .jsx, .ts, .tsx, .script) or a text file (.txt, .json). If the file already exists, it will be overwritten by this command. Note that it will not be possible to download data from many websites because they do not allow cross-origin resource sharing (CORS).
Retrieves data from a URL and downloads it to a file on the specified server. The data can only be downloaded to a script (.js, .jsx, .ts, .tsx) or a text file (.txt, .json). If the file already exists, it will be overwritten by this command. Note that it will not be possible to download data from many websites because they do not allow cross-origin resource sharing (CORS).
IMPORTANT: This is an asynchronous function that returns a Promise. The Promises resolved value will be a boolean indicating whether or not the data was successfully retrieved from the URL. Because the function is async and returns a Promise, it is recommended you use wget in NetscriptJS (Netscript 2.0).

View File

@@ -28,7 +28,7 @@ void
RAM cost: 0 GB
This function can be used to write data to a text file (.txt, .json) or a script (.js, .jsx, .ts, .tsx, .script).
This function can be used to write data to a text file (.txt, .json) or a script (.js, .jsx, .ts, .tsx).
This function will write data to that file. If the specified file does not exist, then it will be created. The third argument mode defines how the data will be written to the file. If mode is set to “w”, then the data is written in “write” mode which means that it will overwrite all existing data on the file. If mode is set to any other value then the data will be written in “append” mode which means that the data will be added at the end of the file.

13
package-lock.json generated
View File

@@ -31,7 +31,6 @@
"clsx": "^1.2.1",
"convert-source-map": "^2.0.0",
"date-fns": "^2.30.0",
"escodegen": "^2.1.0",
"jszip": "^3.10.1",
"material-ui-color": "^1.2.0",
"material-ui-popup-state": "^1.9.3",
@@ -62,7 +61,6 @@
"@types/babel__standalone": "^7.1.7",
"@types/bcryptjs": "^2.4.4",
"@types/convert-source-map": "^2.0.3",
"@types/escodegen": "^0.0.7",
"@types/file-saver": "^2.0.5",
"@types/jest": "^29.5.5",
"@types/jquery": "^3.5.22",
@@ -4915,12 +4913,6 @@
"@types/ms": "*"
}
},
"node_modules/@types/escodegen": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/@types/escodegen/-/escodegen-0.0.7.tgz",
"integrity": "sha512-46oENdSRNEJXCNrPJoC3vRolZJpfeEm7yvATkd2bCncKFG0PUEyfBCaoacfpcXH4Y5RRuqdVj3J7TI+wwn2SbQ==",
"dev": true
},
"node_modules/@types/eslint": {
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
@@ -8594,6 +8586,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
"integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
"dev": true,
"dependencies": {
"esprima": "^4.0.1",
"estraverse": "^5.2.0",
@@ -8614,6 +8607,7 @@
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"optional": true,
"engines": {
"node": ">=0.10.0"
@@ -8963,6 +8957,7 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"dev": true,
"bin": {
"esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js"
@@ -8999,6 +8994,7 @@
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"dev": true,
"engines": {
"node": ">=4.0"
}
@@ -9007,6 +9003,7 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}

View File

@@ -31,7 +31,6 @@
"clsx": "^1.2.1",
"convert-source-map": "^2.0.0",
"date-fns": "^2.30.0",
"escodegen": "^2.1.0",
"jszip": "^3.10.1",
"material-ui-color": "^1.2.0",
"material-ui-popup-state": "^1.9.3",
@@ -63,7 +62,6 @@
"@types/babel__standalone": "^7.1.7",
"@types/bcryptjs": "^2.4.4",
"@types/convert-source-map": "^2.0.3",
"@types/escodegen": "^0.0.7",
"@types/file-saver": "^2.0.5",
"@types/jest": "^29.5.5",
"@types/jquery": "^3.5.22",

View File

@@ -114,7 +114,7 @@ Check how much [RAM](ram.md) a script requires to run with "n" threads
**nano [script]**
Create/Edit a script.
The name of a script must end with a script extension (.js, .jsx, .ts, .tsx, .script). You can also create a text file with a text extension (.txt, .json).
The name of a script must end with a script extension (.js, .jsx, .ts, .tsx). You can also create a text file with a text extension (.txt, .json).
**ps**

View File

@@ -1,12 +1,12 @@
# "Netscript 2" Migration Guide
The game allows two script formats:
In previous versions, the game supported two script formats:
- `.script` (also sometimes called "Netscript 1" or "NS1") files are ran through an interpreter that is based on a version of Javascript from 2009 (ES5). These files are no longer actively supported and should be converted to the newer `.js` format.
- `.script` (also sometimes called "Netscript 1" or "NS1") files are ran through an interpreter that is based on a version of Javascript from 2009 (ES5).
- `.js` (also sometimes called "Netscript 2" or "NS2") files are native javascript that is ran directly by the web browser. Modern features of javascript that are supported by your web browser are supported in `.js` files, because they are run as native js.
Support for `.script` files will be completely removed in future version 3.0. Some basic autoconversion will be attempted at that time, but it is highly recommended to convert your remaining `.script` files to the `.js` format before that, to ensure correctness and, if needed, to familiarize yourself with the `.js` format requirements.
Support for running `.script` files was removed in version 3.0.
## Why do I have to change anything?
@@ -75,4 +75,4 @@ export async function main(ns) {
## Additional problems or edge cases
To get additional help with the migration join the [official Discord server](https://discord.gg/TFc3hKD).
To get additional help with the migration, please join the [official Discord server](https://discord.gg/TFc3hKD).

View File

@@ -39,8 +39,8 @@ Common infinite loop when translating the server purchasing script in starting g
while (i < ns.getPurchasedServerLimit()) {
if (ns.getServerMoneyAvailable("home") > ns.getPurchasedServerCost(ram)) {
var hostname = ns.purchaseServer("pserv-" + i, ram);
ns.scp("early-hack-template.script", hostname);
ns.exec("early-hack-template.script", hostname, 3);
ns.scp("early-hack-template.js", hostname);
ns.exec("early-hack-template.js", hostname, 3);
++i;
}
}

View File

@@ -6,7 +6,6 @@ import { AutoexecInput } from "./AutoexecInput";
import { OptionSwitch } from "../../ui/React/OptionSwitch";
export const SystemPage = (): React.ReactElement => {
const [execTime, setExecTime] = useState(Settings.CodeInstructionRunTime);
const [recentScriptsSize, setRecentScriptsSize] = useState(Settings.MaxRecentScriptsCapacity);
const [logSize, setLogSize] = useState(Settings.MaxLogCapacity);
const [portSize, setPortSize] = useState(Settings.MaxPortCapacity);
@@ -24,11 +23,6 @@ export const SystemPage = (): React.ReactElement => {
Settings.MaxTerminalCapacity = newValue as number;
}
function handleExecTimeChange(_event: Event | React.SyntheticEvent, newValue: number | number[]): void {
setExecTime(newValue as number);
Settings.CodeInstructionRunTime = newValue as number;
}
function handleTailIntervalChange(_event: Event | React.SyntheticEvent, newValue: number | number[]): void {
setTailRenderInterval(newValue as number);
Settings.TailRenderInterval = newValue as number;
@@ -62,20 +56,6 @@ export const SystemPage = (): React.ReactElement => {
}
/>
<br />
<OptionsSlider
label=".script exec time (ms)"
initialValue={execTime}
callback={handleExecTimeChange}
step={1}
min={5}
max={100}
tooltip={
<>
The minimum number of milliseconds it takes to execute an operation in Netscript. Setting this too low can
result in poor performance if you have many scripts running.
</>
}
/>
<OptionsSlider
label="Recently killed scripts size"
initialValue={recentScriptsSize}

View File

@@ -9,34 +9,26 @@ import { workerScripts } from "./Netscript/WorkerScripts";
import { generateNextPid } from "./Netscript/Pid";
import { CONSTANTS } from "./Constants";
import { Interpreter } from "./ThirdParty/JSInterpreter";
import { NetscriptFunctions } from "./NetscriptFunctions";
import { compile } from "./NetscriptJSEvaluator";
import { Port, PortNumber } from "./NetscriptPort";
import { RunningScript } from "./Script/RunningScript";
import { scriptCalculateOfflineProduction } from "./Script/ScriptHelpers";
import { Script } from "./Script/Script";
import { GetAllServers } from "./Server/AllServers";
import { BaseServer } from "./Server/BaseServer";
import { Settings } from "./Settings/Settings";
import { generate } from "escodegen";
import { dialogBoxCreate } from "./ui/React/DialogBox";
import { formatRam } from "./ui/formatNumber";
import { arrayToString } from "./utils/helpers/ArrayHelpers";
import { roundToTwo } from "./utils/helpers/roundToTwo";
import { parse } from "acorn";
import type * as acorn from "acorn";
import { simple as walksimple } from "acorn-walk";
import { parseCommand } from "./Terminal/Parser";
import { Terminal } from "./Terminal";
import { ScriptArg } from "@nsdefs";
import { CompleteRunOptions, getRunningScriptsByArgs } from "./Netscript/NetscriptHelpers";
import { handleUnknownError } from "./utils/ErrorHandler";
import { isLegacyScript, legacyScriptExtension, resolveScriptFilePath, ScriptFilePath } from "./Paths/ScriptFilePath";
import { root } from "./Paths/Directory";
import { isLegacyScript, resolveScriptFilePath, ScriptFilePath } from "./Paths/ScriptFilePath";
import { getErrorMessageWithStackAndCause } from "./utils/ErrorHelper";
import { exceptionAlert } from "./utils/helpers/exceptionAlert";
import { Result } from "./types";
@@ -72,207 +64,6 @@ async function startNetscript2Script(workerScript: WorkerScript): Promise<void>
await mainFunc(ns);
}
async function startNetscript1Script(workerScript: WorkerScript): Promise<void> {
const code = workerScript.code;
let errorToThrow: unknown;
//Process imports
let codeWithImports, codeLineOffset;
try {
const importProcessingRes = processNetscript1Imports(code, workerScript);
codeWithImports = importProcessingRes.code;
codeLineOffset = importProcessingRes.lineOffset;
} catch (e: unknown) {
throw `Error processing Imports in ${workerScript.name}@${workerScript.hostname}:\n\n${e}`;
}
//TODO unplanned: Make NS1 wrapping type safe instead of using BasicObject.
type BasicObject = Record<string, any>;
const wrappedNS = NetscriptFunctions(workerScript);
function wrapNS1Layer(int: Interpreter, intLayer: unknown, nsLayer = wrappedNS as BasicObject) {
for (const [name, entry] of Object.entries(nsLayer)) {
if (typeof entry === "function") {
const wrapper = async (...args: unknown[]) => {
try {
// Sent a resolver function as an extra arg. See createAsyncFunction JSInterpreter.js:3209
const callback = args.pop() as (value: unknown) => void;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call -- NS1 is deprecated.
const result = await entry(...args.map((arg) => int.pseudoToNative(arg)));
return callback(int.nativeToPseudo(result));
} catch (e: unknown) {
errorToThrow = e;
}
};
int.setProperty(intLayer, name, int.createAsyncFunction(wrapper));
} else if (Array.isArray(entry) || typeof entry !== "object") {
// args, strings on enums, etc
int.setProperty(intLayer, name, int.nativeToPseudo(entry));
} else {
// new object layer, e.g. bladeburner
int.setProperty(intLayer, name, int.nativeToPseudo({}));
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument -- NS1 is deprecated.
wrapNS1Layer(int, (intLayer as BasicObject).properties[name], nsLayer[name]);
}
}
}
let interpreter: Interpreter;
try {
interpreter = new Interpreter(codeWithImports, wrapNS1Layer, codeLineOffset);
} catch (e: unknown) {
throw `Syntax ERROR in ${workerScript.name}@${workerScript.hostname}:\n\n${String(e)}`;
}
let more = true;
while (more) {
if (errorToThrow) throw errorToThrow;
if (workerScript.env.stopFlag) return;
for (let i = 0; more && i < 3; i++) more = interpreter.step();
if (more) await new Promise((r) => setTimeout(r, Settings.CodeInstructionRunTime));
}
}
/* Since the JS Interpreter used for Netscript 1.0 only supports ES5, the keyword
'import' throws an error. However, since we want to support import functionality
we'll implement it ourselves by parsing the Nodes in the AST out.
@param code - The script's code
@returns {Object} {
code: Newly-generated code with imported functions
lineOffset: Net number of lines of code added/removed due to imported functions
Should typically be positive
}
*/
function processNetscript1Imports(code: string, workerScript: WorkerScript): { code: string; lineOffset: number } {
//allowReserved prevents 'import' from throwing error in ES5
const ast = parse(code, {
ecmaVersion: 9,
allowReserved: true,
sourceType: "module",
});
const server = workerScript.getServer();
if (server == null) {
throw new Error("Failed to find underlying Server object for script");
}
function getScript(scriptName: ScriptFilePath): Script | null {
return server.scripts.get(scriptName) ?? null;
}
let generatedCode = ""; // Generated Javascript Code
let hasImports = false;
// Walk over the tree and process ImportDeclaration nodes
walksimple(ast, {
ImportDeclaration: (node: acorn.ImportDeclaration) => {
hasImports = true;
const scriptName = resolveScriptFilePath(node.source.value as string, root, legacyScriptExtension);
if (!scriptName) throw new Error("'Import' failed due to invalid path: " + scriptName);
const script = getScript(scriptName);
if (!script) throw new Error("'Import' failed due to script not found: " + scriptName);
const scriptAst = parse(script.code, {
ecmaVersion: 9,
allowReserved: true,
sourceType: "module",
});
if (node.specifiers.length === 1 && node.specifiers[0].type === "ImportNamespaceSpecifier") {
// import * as namespace from script
const namespace = node.specifiers[0].local.name;
const fnNames: string[] = []; //Names only
const fnDeclarations: acorn.Node[] = []; //FunctionDeclaration Node objects
walksimple(scriptAst, {
FunctionDeclaration: (node: acorn.FunctionDeclaration | acorn.AnonymousFunctionDeclaration) => {
if (!node.id) {
return;
}
fnNames.push(node.id.name);
fnDeclarations.push(node);
},
});
//Now we have to generate the code that would create the namespace
generatedCode += `var ${namespace};\n(function (namespace) {\n`;
//Add the function declarations
fnDeclarations.forEach((fn) => {
generatedCode += generate(fn);
generatedCode += "\n";
});
//Add functions to namespace
fnNames.forEach((fnName) => {
generatedCode += "namespace." + fnName + " = " + fnName;
generatedCode += "\n";
});
//Finish
generatedCode += `})(${namespace} || (" + namespace + " = {}));\n`;
} else {
//import {...} from script
//Get array of all fns to import
const fnsToImport: string[] = [];
node.specifiers.forEach((e) => {
fnsToImport.push(e.local.name);
});
//Walk through script and get FunctionDeclaration code for all specified fns
const fnDeclarations: acorn.Node[] = [];
walksimple(scriptAst, {
FunctionDeclaration: (node: acorn.FunctionDeclaration | acorn.AnonymousFunctionDeclaration) => {
if (!node.id) {
return;
}
if (fnsToImport.includes(node.id.name)) {
fnDeclarations.push(node);
}
},
});
//Convert FunctionDeclarations into code
fnDeclarations.forEach((fn) => {
generatedCode += generate(fn);
generatedCode += "\n";
});
}
},
});
//If there are no imports, just return the original code
if (!hasImports) {
return { code: code, lineOffset: 0 };
}
//Remove ImportDeclarations from AST. These ImportDeclarations must be in top-level
let linesRemoved = 0;
if (ast.type !== "Program" || ast.body == null) {
throw new Error("Code could not be properly parsed");
}
for (let i = ast.body.length - 1; i >= 0; --i) {
if (ast.body[i].type === "ImportDeclaration") {
ast.body.splice(i, 1);
++linesRemoved;
}
}
//Calculated line offset
const lineOffset = (generatedCode.match(/\n/g) || []).length - linesRemoved;
//Convert the AST back into code
code = generate(ast);
//Add the imported code and re-generate in ES5 (JS Interpreter for NS1 only supports ES5);
code = generatedCode + code;
const res = {
code: code,
lineOffset: lineOffset,
};
return res;
}
/**
* Used to start a RunningScript (by creating and starting its
* corresponding WorkerScript), and add the RunningScript to the server on which
@@ -310,6 +101,10 @@ export function startWorkerScript(runningScript: RunningScript, server: BaseServ
* returns {boolean} indicating whether or not the workerScript was successfully added
*/
function createAndAddWorkerScript(runningScriptObj: RunningScript, server: BaseServer, parent?: WorkerScript): boolean {
if (isLegacyScript(runningScriptObj.filename)) {
deferredError(`Running .script files is unsupported.`);
return false;
}
const ramUsage = roundToTwo(runningScriptObj.ramUsage * runningScriptObj.threads);
const ramAvailable = server.maxRam - server.ramUsed;
// Check failure conditions before generating the workersScript and return false
@@ -344,7 +139,7 @@ Otherwise, this can also occur if you have attempted to launch a script from a t
workerScripts.set(pid, workerScript);
// Start the script's execution using the correct function for file type
(isLegacyScript(workerScript.name) ? startNetscript1Script : startNetscript2Script)(workerScript)
startNetscript2Script(workerScript)
// Once the code finishes (either resolved or rejected, doesn't matter), set its
// running status to false
.then(function () {

View File

@@ -8,7 +8,15 @@ export type ScriptFilePath = FilePath & WithScriptExtension;
export const legacyScriptExtension = ".script";
/** Valid extensions. Used for some error messaging. */
/**
* Valid extensions. Used for some error messaging.
*
* Running .script is unsupported, but we still put it in the list of valid script extensions. When we remove the
* support of NS1, we only remove the ability to run it. Except the migration docs, the official documentation (help
* text, TSDoc of NS APIs, etc.) does not mention NS1 and .script anymore. However, for the player's convenience when
* migrating from NS1 to NS2, we still let them perform other actions on their unsupported scripts (e.g., open, copy,
* move, delete).
*/
export const validScriptExtensions = [".js", ".jsx", ".ts", ".tsx", legacyScriptExtension] as const;
export type ScriptExtension = (typeof validScriptExtensions)[number];

View File

@@ -7678,7 +7678,7 @@ export interface NS {
* @remarks
* RAM cost: 0 GB
*
* This function can be used to write data to a text file (.txt, .json) or a script (.js, .jsx, .ts, .tsx, .script).
* This function can be used to write data to a text file (.txt, .json) or a script (.js, .jsx, .ts, .tsx).
*
* This function will write data to that file. If the specified file does not exist,
* then it will be created. The third argument mode defines how the data will be written to
@@ -7724,7 +7724,7 @@ export interface NS {
* @remarks
* RAM cost: 0 GB
*
* This function is used to read data from a text file (.txt, .json) or script (.js, .jsx, .ts, .tsx, .script).
* This function is used to read data from a text file (.txt, .json) or script (.js, .jsx, .ts, .tsx).
*
* This function will return the data in the specified file.
* If the file does not exist, an empty string will be returned.
@@ -8206,7 +8206,7 @@ export interface NS {
* RAM cost: 0 GB
*
* Retrieves data from a URL and downloads it to a file on the specified server.
* The data can only be downloaded to a script (.js, .jsx, .ts, .tsx, .script) or a text file (.txt, .json).
* The data can only be downloaded to a script (.js, .jsx, .ts, .tsx) or a text file (.txt, .json).
* If the file already exists, it will be overwritten by this command.
* Note that it will not be possible to download data from many websites because they
* do not allow cross-origin resource sharing (CORS).

View File

@@ -188,14 +188,13 @@ function Root(props: IProps): React.ReactElement {
// this is duplicate code with saving later.
if (ITutorial.isRunning && ITutorial.currStep === iTutorialSteps.TerminalTypeScript) {
//Make sure filename + code properly follow tutorial
if (currentScript.path !== "n00dles.script" && currentScript.path !== "n00dles.js") {
if (currentScript.path !== "n00dles.js") {
dialogBoxCreate("Don't change the script name for now.");
return;
}
const cleanCode = currentScript.code.replace(/\s/g, "");
const ns1 = "while(true){hack('n00dles');}";
const ns2 = `/**@param{NS}ns*/exportasyncfunctionmain(ns){while(true){awaitns.hack("n00dles");}}`;
if (!cleanCode.includes(ns1) && !cleanCode.includes(ns2)) {
const expectedCleanCode = `/**@param{NS}ns*/exportasyncfunctionmain(ns){while(true){awaitns.hack("n00dles");}}`;
if (!cleanCode.includes(expectedCleanCode)) {
dialogBoxCreate("Please copy and paste the code from the tutorial!");
return;
}

View File

@@ -83,8 +83,6 @@ export const Settings = {
AutoexecScript: "",
/** How often the game should autosave the player's progress, in seconds. */
AutosaveInterval: 60,
/** How many milliseconds between execution points for Netscript 1 statements. */
CodeInstructionRunTime: 25,
/** Whether to render city as list of buttons. */
DisableASCIIArt: false,
/** Whether global keyboard shortcuts should be disabled throughout the game. */

View File

@@ -54,7 +54,7 @@ const TemplatedHelpTexts: Record<string, (command: string) => string[]> = {
return [
`Usage: ${command} [file names...] | [glob]`,
` `,
`Opens up the specified file(s) in the Script Editor. Only scripts (.js, .jsx, .ts, .tsx, .script) `,
`Opens up the specified file(s) in the Script Editor. Only scripts (.js, .jsx, .ts, .tsx) `,
`or text files (.txt, .json) can be edited using the Script Editor. If a file does not exist, a new `,
`one will be created.`,
` `,
@@ -170,11 +170,11 @@ export const HelpTexts: Record<string, string[]> = {
"a space. Remember that a running script is uniquely ",
"identified both by its name and the arguments that are used to start it. So, if a script was ran with the following arguments: ",
" ",
" run foo.script 1 2 foodnstuff",
" run foo.js 1 2 foodnstuff",
" ",
"Then to run the 'check' command on this script you would have to pass the same arguments in: ",
" ",
" check foo.script 1 2 foodnstuff",
" check foo.js 1 2 foodnstuff",
" ",
],
clear: [
@@ -209,7 +209,7 @@ export const HelpTexts: Record<string, string[]> = {
" ",
"Download all scripts and text files: download *",
" ",
"Download all scripts: download *.script",
"Download all JS scripts: download *.js",
" ",
"Download all text files: download *.txt",
" ",
@@ -325,11 +325,11 @@ export const HelpTexts: Record<string, string[]> = {
"uniquely identified by both its name and the arguments that are used to start ",
"it. So, if a script was ran with the following arguments:",
" ",
" run foo.script 1 sigma-cosmetics",
" run foo.js 1 sigma-cosmetics",
" ",
"Then to kill this script the same arguments would have to be used:",
" ",
" kill foo.script 1 sigma-cosmetics",
" kill foo.js 1 sigma-cosmetics",
" ",
"If you are killing the script using its PID, then the PID argument must be numeric",
" ",
@@ -349,9 +349,9 @@ export const HelpTexts: Record<string, string[]> = {
" ",
"Examples:",
" ",
"List all files with the '.script' extension in the current directory:",
"List all files with the '.js' extension in the current directory:",
" ",
" ls -l --grep .script",
" ls -l --grep .js",
" ",
"List all files with the '.js' extension in the root directory:",
" ",
@@ -371,12 +371,12 @@ export const HelpTexts: Record<string, string[]> = {
"the amount of RAM needed to run a script with multiple threads using the '-t' flag. If the '-t' flag is specified, then ",
"an argument for the number of threads must be passed in afterwards. Examples:",
" ",
" mem foo.script",
" mem foo.js",
" ",
" mem foo.script -t 50",
" mem foo.js -t 50",
" ",
"The first example above will print the amount of RAM needed to run 'foo.script' with a single thread. The second example ",
"above will print the amount of RAM needed to run 'foo.script' with 50 threads.",
"The first example above will print the amount of RAM needed to run 'foo.js' with a single thread. The second example ",
"above will print the amount of RAM needed to run 'foo.js' with 50 threads.",
" ",
],
mv: [
@@ -390,7 +390,7 @@ export const HelpTexts: Record<string, string[]> = {
"full filepath. ",
"Examples: ",
" ",
" mv hacking-controller.script scripts/hacking-controller.script",
" mv hacking-controller.js scripts/hacking-controller.js",
" ",
" mv myScript.js myOldScript.js",
" ",
@@ -461,13 +461,13 @@ export const HelpTexts: Record<string, string[]> = {
"Usage: scp [file names...] [target server]",
" ",
"Copies the specified file(s) from the current server to the target server. ",
"This command only works for script files (.js, .jsx, .ts, .tsx, .script), text files (.txt, .json), ",
"This command only works for script files (.js, .jsx, .ts, .tsx), text files (.txt, .json), ",
"and literature files (.lit).",
"The second argument passed in must be the hostname or IP of the target server. Examples:",
" ",
" scp foo.script n00dles",
" scp foo.js n00dles",
" ",
" scp foo.script bar.script n00dles",
" scp foo.js bar.js n00dles",
" ",
],
sudov: ["Usage: sudov", " ", "Prints whether or not you have root access to the current machine", " "],
@@ -479,11 +479,11 @@ export const HelpTexts: Record<string, string[]> = {
"by a space. Remember that a running script is uniquely identified by both its name and the arguments that were used ",
"to run it. So, if a script was ran with the following arguments: ",
" ",
" run foo.script 10 50000",
" run foo.js 10 50000",
" ",
"Then in order to check its logs with 'tail' the same arguments must be used: ",
" ",
" tail foo.script 10 50000",
" tail foo.js 10 50000",
" ",
],
top: [
@@ -523,7 +523,7 @@ export const HelpTexts: Record<string, string[]> = {
"Usage: wget [url] [target file]",
" ",
"Retrieves data from a URL and downloads it to a file on the current server. The data can only ",
"be downloaded to a script (.js, .jsx, .ts, .tsx, .script) or a text file (.txt, .json).",
"be downloaded to a script (.js, .jsx, .ts, .tsx) or a text file (.txt, .json).",
"If the file already exists, it will be overwritten by this command.",
" ",
"Note that it will not be possible to download data from many websites because they do not allow ",

View File

@@ -601,7 +601,7 @@ export class Terminal {
this.setcwd(root);
if (!singularity) {
this.print("Connected to " + `${isIPAddress(hostname) ? server.ip : server.hostname}`);
if (Player.getCurrentServer().hostname == "darkweb") {
if (Player.getCurrentServer().hostname === "darkweb") {
checkIfConnectedToDarkweb(); // Posts a 'help' message if connecting to dark web
}
}
@@ -649,7 +649,7 @@ export class Terminal {
"Bad command. Please follow the tutorial or click 'Exit Tutorial' if you'd like to skip it.";
switch (ITutorial.currStep) {
case iTutorialSteps.TerminalHelp:
if (commandArray.length === 1 && commandArray[0] == "help") {
if (commandArray.length === 1 && commandArray[0] === "help") {
iTutorialNextStep();
} else {
this.error(errorMessageForBadCommand);
@@ -657,9 +657,9 @@ export class Terminal {
}
break;
case iTutorialSteps.TerminalLs:
if (commandArray.length === 1 && commandArray[0] == "ls") {
if (commandArray.length === 1 && commandArray[0] === "ls") {
iTutorialNextStep();
} else if (commandArray[0] == "1s") {
} else if (commandArray[0] === "1s") {
this.error("Command '1s' not found. Did you mean 'ls' with a lowercase L?");
return;
} else {
@@ -668,7 +668,7 @@ export class Terminal {
}
break;
case iTutorialSteps.TerminalScan:
if (commandArray.length === 1 && commandArray[0] == "scan") {
if (commandArray.length === 1 && commandArray[0] === "scan") {
iTutorialNextStep();
} else {
this.error(errorMessageForBadCommand);
@@ -676,7 +676,7 @@ export class Terminal {
}
break;
case iTutorialSteps.TerminalScanAnalyze1:
if (commandArray.length == 1 && commandArray[0] == "scan-analyze") {
if (commandArray.length === 1 && commandArray[0] === "scan-analyze") {
iTutorialNextStep();
} else {
this.error(errorMessageForBadCommand);
@@ -684,7 +684,7 @@ export class Terminal {
}
break;
case iTutorialSteps.TerminalScanAnalyze2:
if (commandArray.length == 2 && commandArray[0] == "scan-analyze" && commandArray[1] === 2) {
if (commandArray.length === 2 && commandArray[0] === "scan-analyze" && commandArray[1] === 2) {
iTutorialNextStep();
} else {
this.error(errorMessageForBadCommand);
@@ -692,10 +692,10 @@ export class Terminal {
}
break;
case iTutorialSteps.TerminalConnect:
if (commandArray.length == 2) {
if (commandArray.length === 2) {
if (
commandArray[0] == "connect" &&
(commandArray[1] == "n00dles" || commandArray[1] == n00dlesServ.hostname)
commandArray[0] === "connect" &&
(commandArray[1] === "n00dles" || commandArray[1] === n00dlesServ.hostname)
) {
iTutorialNextStep();
} else {
@@ -716,7 +716,7 @@ export class Terminal {
}
break;
case iTutorialSteps.TerminalNuke:
if (commandArray.length == 2 && commandArray[0] == "run" && commandArray[1] == "NUKE.exe") {
if (commandArray.length === 2 && commandArray[0] === "run" && commandArray[1] === "NUKE.exe") {
iTutorialNextStep();
} else {
this.error(errorMessageForBadCommand);
@@ -724,7 +724,7 @@ export class Terminal {
}
break;
case iTutorialSteps.TerminalManualHack:
if (commandArray.length == 1 && commandArray[0] == "hack") {
if (commandArray.length === 1 && commandArray[0] === "hack") {
iTutorialNextStep();
} else {
this.error(errorMessageForBadCommand);
@@ -738,7 +738,7 @@ export class Terminal {
}
break;
case iTutorialSteps.TerminalGoHome:
if (commandArray.length == 1 && commandArray[0] == "home") {
if (commandArray.length === 1 && commandArray[0] === "home") {
iTutorialNextStep();
} else {
this.error(errorMessageForBadCommand);
@@ -746,11 +746,7 @@ export class Terminal {
}
break;
case iTutorialSteps.TerminalCreateScript:
if (
commandArray.length == 2 &&
commandArray[0] == "nano" &&
(commandArray[1] == "n00dles.script" || commandArray[1] == "n00dles.js")
) {
if (commandArray.length === 2 && commandArray[0] === "nano" && commandArray[1] === "n00dles.js") {
iTutorialNextStep();
} else {
this.error(errorMessageForBadCommand);
@@ -758,7 +754,7 @@ export class Terminal {
}
break;
case iTutorialSteps.TerminalFree:
if (commandArray.length == 1 && commandArray[0] == "free") {
if (commandArray.length === 1 && commandArray[0] === "free") {
iTutorialNextStep();
} else {
this.error(errorMessageForBadCommand);
@@ -766,11 +762,7 @@ export class Terminal {
}
break;
case iTutorialSteps.TerminalRunScript:
if (
commandArray.length == 2 &&
commandArray[0] == "run" &&
(commandArray[1] == "n00dles.script" || commandArray[1] == "n00dles.js")
) {
if (commandArray.length === 2 && commandArray[0] === "run" && commandArray[1] === "n00dles.js") {
iTutorialNextStep();
} else {
this.error(errorMessageForBadCommand);
@@ -778,11 +770,7 @@ export class Terminal {
}
break;
case iTutorialSteps.ActiveScriptsToTerminal:
if (
commandArray.length == 2 &&
commandArray[0] == "tail" &&
(commandArray[1] == "n00dles.script" || commandArray[1] == "n00dles.js")
) {
if (commandArray.length === 2 && commandArray[0] === "tail" && commandArray[1] === "n00dles.js") {
iTutorialNextStep();
} else {
this.error(errorMessageForBadCommand);

View File

@@ -21,7 +21,7 @@ export function cat(args: (string | number | boolean)[], server: BaseServer): vo
}
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, .script) or a text extension (.txt, .json)",
"Invalid file extension. Filename must end with .msg, .lit, a script extension (.js, .jsx, .ts, .tsx) or a text extension (.txt, .json)",
);
}

View File

@@ -7,8 +7,8 @@ import { Page } from "../../../ui/Router";
export function sendDeprecationNotice() {
return Terminal.printRaw(
<Typography sx={{ color: Settings.theme.warning }}>
NOTICE: Accessing the Netscript API via .script files is deprecated and will be removed in a future update.{" "}
<Typography sx={{ color: Settings.theme.error }}>
Running .script files is unsupported.{" "}
<Link
style={{ cursor: "pointer" }}
color="inherit"

View File

@@ -27,5 +27,5 @@ export function run(args: (string | number | boolean)[], server: BaseServer): vo
} else if (hasProgramExtension(path)) {
return runProgram(path, args, server);
}
Terminal.error(`Invalid file extension. Only .js, .jsx, .ts, .tsx, .script, .cct, and .exe files can be run.`);
Terminal.error(`Invalid file extension. Only .js, .jsx, .ts, .tsx, .cct, and .exe files can be run.`);
}

View File

@@ -16,6 +16,10 @@ export function runScript(
commandArgs: (string | number | boolean)[],
server: BaseServer,
): void {
if (isLegacyScript(scriptPath)) {
sendDeprecationNotice();
return;
}
const runArgs = { "--tail": Boolean, "-t": Number, "--ram-override": Number };
let flags: {
_: ScriptArg[];
@@ -65,9 +69,6 @@ export function runScript(
return;
}
if (isLegacyScript(scriptPath)) {
sendDeprecationNotice();
}
Terminal.print(
`Running script with ${pluralize(numThreads, "thread")}, pid ${runningScript.pid} and args: ${JSON.stringify(
args,

View File

@@ -36,7 +36,7 @@ export function scp(args: (string | number | boolean)[], server: BaseServer): vo
// Error for invalid filetype
if (!hasScriptExtension(path) && !hasTextExtension(path)) {
return Terminal.error(
`scp failed: ${path} has invalid extension. scp only works for scripts (.js, .jsx, .ts, .tsx, .script), text files (.txt, .json), and literature files (.lit)`,
`scp failed: ${path} has invalid extension. scp only works for scripts (.js, .jsx, .ts, .tsx), text files (.txt, .json), and literature files (.lit)`,
);
}
const sourceContentFile = server.getContentFile(path);

View File

@@ -87,12 +87,12 @@ export function TerminalRoot(): React.ReactElement {
<li key={i}>
{item instanceof Output && <ANSIITypography text={item.text} color={item.color} />}
{item instanceof RawOutput && (
<Typography classes={{ root: classes.preformatted }} paragraph={false}>
<Typography component="div" classes={{ root: classes.preformatted }} paragraph={false}>
{item.raw}
</Typography>
)}
{item instanceof Link && (
<Typography classes={{ root: classes.preformatted }}>
<Typography component="div" classes={{ root: classes.preformatted }}>
{item.dashes}
<MuiLink onClick={() => Terminal.connectToServer(item.hostname)}>{item.hostname}</MuiLink>
</Typography>

View File

@@ -283,7 +283,7 @@ export function InteractiveTutorialRoot(): React.ReactElement {
},
[iTutorialSteps.TerminalHackingMechanics as number]: {
content: (
<Typography>
<Typography component="div">
You are now attempting to hack the server. Performing a hack takes time and only has a certain percentage
chance of success. This time and success chance is determined by a variety of factors, including your hacking
skill and the server's security level.
@@ -327,8 +327,7 @@ export function InteractiveTutorialRoot(): React.ReactElement {
<Typography classes={{ root: classes.textfield }}>{"[home /]> nano"}</Typography>
<Typography>
Scripts must end with a script extension (.js, .jsx, .ts, .tsx, .script). Let's make a script now by
entering
Scripts must end with a script extension (.js, .jsx, .ts, .tsx). Let's make a script now by entering
</Typography>
<Typography classes={{ root: classes.textfield }}>{`[home /]> nano ${tutorialScriptName}`}</Typography>
</>
@@ -343,7 +342,7 @@ export function InteractiveTutorialRoot(): React.ReactElement {
into the text editor: <br />
</Typography>
<Typography classes={{ root: classes.code }}>
<Typography component="div" classes={{ root: classes.code }}>
{
<CopyableText
value={`/** @param {NS} ns */

View File

@@ -40,30 +40,6 @@ global.URL.createObjectURL = function (blob) {
initGameEnvironment();
test.each([
{
name: "NS1 test /w import",
expected: ["false home 8", "Script finished running"],
scripts: [
{
name: "import.script",
code: `
export function getInfo() {
return stock.has4SData();
}
`,
},
{
name: "simple_test.script",
code: `
import { getInfo } from "import.script";
var access = getInfo();
var server = getServer();
printf("%s %s %d", access, server.hostname, server.maxRam);
`,
},
],
},
{
name: "NS2 test /w import",
expected: ["false home 8", "Script finished running"],