forked from tools/josh
Christen "josh" and add version number
Oh and make persistence a bit more manageable.
This commit is contained in:
parent
f5713fdf31
commit
11523b5046
21
Gruntfile.js
21
Gruntfile.js
|
@ -17,6 +17,18 @@ module.exports = grunt => {
|
||||||
files: [{expand: true, cwd: "src/main/", src: "**/*.css", dest: "build/"}]
|
files: [{expand: true, cwd: "src/main/", src: "**/*.css", dest: "build/"}]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
replace: {
|
||||||
|
default: {
|
||||||
|
src: "./build/bundle.js",
|
||||||
|
replacements: [
|
||||||
|
{
|
||||||
|
from: "%%VERSION_NUMBER%%",
|
||||||
|
to: "<%= pkg.version %>"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
overwrite: true
|
||||||
|
}
|
||||||
|
},
|
||||||
webpack: {
|
webpack: {
|
||||||
options: {
|
options: {
|
||||||
entry: "./src/main/js/Main.ts",
|
entry: "./src/main/js/Main.ts",
|
||||||
|
@ -49,6 +61,7 @@ module.exports = grunt => {
|
||||||
|
|
||||||
grunt.loadNpmTasks("grunt-contrib-clean");
|
grunt.loadNpmTasks("grunt-contrib-clean");
|
||||||
grunt.loadNpmTasks("grunt-contrib-copy");
|
grunt.loadNpmTasks("grunt-contrib-copy");
|
||||||
|
grunt.loadNpmTasks("grunt-text-replace");
|
||||||
grunt.loadNpmTasks("grunt-webpack");
|
grunt.loadNpmTasks("grunt-webpack");
|
||||||
|
|
||||||
grunt.registerTask("dev", [
|
grunt.registerTask("dev", [
|
||||||
|
@ -59,7 +72,9 @@ module.exports = grunt => {
|
||||||
"copy:html",
|
"copy:html",
|
||||||
"copy:css",
|
"copy:css",
|
||||||
// Compile
|
// Compile
|
||||||
"webpack:dev"
|
"webpack:dev",
|
||||||
|
// Post
|
||||||
|
"replace"
|
||||||
]);
|
]);
|
||||||
grunt.registerTask("deploy", [
|
grunt.registerTask("deploy", [
|
||||||
// Pre
|
// Pre
|
||||||
|
@ -69,7 +84,9 @@ module.exports = grunt => {
|
||||||
"copy:html",
|
"copy:html",
|
||||||
"copy:css",
|
"copy:css",
|
||||||
// Compile JS
|
// Compile JS
|
||||||
"webpack:deploy"
|
"webpack:deploy",
|
||||||
|
// Post
|
||||||
|
"replace"
|
||||||
]);
|
]);
|
||||||
|
|
||||||
grunt.registerTask("default", ["dev"]);
|
grunt.registerTask("default", ["dev"]);
|
||||||
|
|
Binary file not shown.
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "fwdekker.com",
|
"name": "fwdekker.com",
|
||||||
"version": "1.0.0",
|
"version": "1.6.4",
|
||||||
"description": "The source code of [my personal website](https://fwdekker.com/).",
|
"description": "The source code of [my personal website](https://fwdekker.com/).",
|
||||||
"author": "Felix W. Dekker",
|
"author": "Felix W. Dekker",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -26,6 +26,7 @@
|
||||||
"grunt-cli": "^1.3.2",
|
"grunt-cli": "^1.3.2",
|
||||||
"grunt-contrib-clean": "^2.0.0",
|
"grunt-contrib-clean": "^2.0.0",
|
||||||
"grunt-contrib-copy": "^1.0.0",
|
"grunt-contrib-copy": "^1.0.0",
|
||||||
|
"grunt-text-replace": "^0.4.0",
|
||||||
"grunt-webpack": "^3.1.3",
|
"grunt-webpack": "^3.1.3",
|
||||||
"mocha": "^6.2.2",
|
"mocha": "^6.2.2",
|
||||||
"ts-loader": "^6.2.1",
|
"ts-loader": "^6.2.1",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as Cookies from "js-cookie";
|
|
||||||
import "./Extensions"
|
import "./Extensions"
|
||||||
import {Environment} from "./Environment";
|
import {Environment} from "./Environment";
|
||||||
import {Directory, File, FileSystem, Path} from "./FileSystem"
|
import {Directory, File, FileSystem, Path} from "./FileSystem"
|
||||||
|
import {Persistence} from "./Persistence";
|
||||||
import {IllegalArgumentError, IllegalStateError} from "./Shared";
|
import {IllegalArgumentError, IllegalStateError} from "./Shared";
|
||||||
import {InputArgs} from "./Shell";
|
import {InputArgs} from "./Shell";
|
||||||
import {EscapeCharacters} from "./Terminal";
|
import {EscapeCharacters} from "./Terminal";
|
||||||
|
@ -229,8 +229,7 @@ export class Commands {
|
||||||
*/
|
*/
|
||||||
execute(input: InputArgs): string {
|
execute(input: InputArgs): string {
|
||||||
if (input.command === "factory-reset") {
|
if (input.command === "factory-reset") {
|
||||||
Cookies.remove("files");
|
Persistence.reset();
|
||||||
Cookies.remove("env");
|
|
||||||
location.reload();
|
location.reload();
|
||||||
throw new Error("Goodbye");
|
throw new Error("Goodbye");
|
||||||
}
|
}
|
||||||
|
@ -369,7 +368,7 @@ export class Commands {
|
||||||
<b>List of commands</b>
|
<b>List of commands</b>
|
||||||
${commandEntries.join("\n")}
|
${commandEntries.join("\n")}
|
||||||
|
|
||||||
Write "help [COMMAND]" or click a command in the list above for more information on a command.\\\
|
Write "help [COMMAND]" or click a command in the list above for more information.\\\
|
||||||
`.trimMultiLines();
|
`.trimMultiLines();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -476,12 +475,9 @@ export class Commands {
|
||||||
if (userName === "")
|
if (userName === "")
|
||||||
throw new IllegalStateError("Cannot execute `poweroff` while not logged in.");
|
throw new IllegalStateError("Cannot execute `poweroff` while not logged in.");
|
||||||
|
|
||||||
Cookies.set("poweroff", "true", {
|
Persistence.setPoweroff(true);
|
||||||
"expires": new Date(new Date().setSeconds(new Date().getSeconds() + 30)),
|
|
||||||
"path": "/"
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(() => location.reload(), 2000);
|
setTimeout(() => location.reload(), 2000);
|
||||||
|
|
||||||
return `Shutdown NOW!
|
return `Shutdown NOW!
|
||||||
|
|
||||||
*** FINAL System shutdown message from ${userName}@fwdekker.com ***
|
*** FINAL System shutdown message from ${userName}@fwdekker.com ***
|
||||||
|
|
|
@ -29,5 +29,5 @@ addOnLoad(() => {
|
||||||
|
|
||||||
// @ts-ignore: Ugly hack to execute it anyway
|
// @ts-ignore: Ugly hack to execute it anyway
|
||||||
if (window.terminal.shell.environment.get("user") !== "")
|
if (window.terminal.shell.environment.get("user") !== "")
|
||||||
window.terminal.processInput("ls");
|
window.execute("ls");
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
import * as Cookies from "js-cookie";
|
||||||
|
import {Environment} from "./Environment";
|
||||||
|
import {Directory, FileSystem, Node, Path} from "./FileSystem";
|
||||||
|
import {UserList} from "./UserList";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages persistence of state.
|
||||||
|
*/
|
||||||
|
export class Persistence {
|
||||||
|
/**
|
||||||
|
* Deserializes a file system from persistent storage, or returns the default file system if the deserialization
|
||||||
|
* failed.
|
||||||
|
*/
|
||||||
|
static getFileSystem(): FileSystem {
|
||||||
|
const filesString = Cookies.get("files");
|
||||||
|
|
||||||
|
let files: Directory | undefined = undefined;
|
||||||
|
if (filesString !== undefined) {
|
||||||
|
try {
|
||||||
|
const parsedFiles = Node.deserialize(filesString);
|
||||||
|
if (parsedFiles instanceof Directory)
|
||||||
|
files = parsedFiles;
|
||||||
|
else
|
||||||
|
console.warn("`files` cookie contains non-directory.");
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("Failed to deserialize `files` cookie.", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FileSystem(files);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persists the given file system.
|
||||||
|
*
|
||||||
|
* @param fileSystem the file system to persist
|
||||||
|
*/
|
||||||
|
static setFileSystem(fileSystem: FileSystem) {
|
||||||
|
Cookies.set("files", fileSystem.root, {
|
||||||
|
"expires": new Date(new Date().setFullYear(new Date().getFullYear() + 25)),
|
||||||
|
"path": "/"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserializes an environment from persistent storage, or returns the default environment if the deserialization
|
||||||
|
* failed.
|
||||||
|
*
|
||||||
|
* @param fileSystem the file system used to validate the `cwd` environment variable
|
||||||
|
* @param userList the list of users used to validate the `user` environment variable
|
||||||
|
*/
|
||||||
|
static getEnvironment(fileSystem: FileSystem, userList: UserList): Environment {
|
||||||
|
const environmentString = Cookies.get("env") ?? "{}";
|
||||||
|
|
||||||
|
let environment: Environment;
|
||||||
|
try {
|
||||||
|
environment = new Environment(["cwd", "home", "user"], JSON.parse(environmentString));
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("Failed to set environment from cookie.");
|
||||||
|
environment = new Environment(["cwd", "home", "user"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check user in environment
|
||||||
|
if (!environment.has("user")) {
|
||||||
|
environment.set("user", "felix");
|
||||||
|
} else if (environment.get("user") !== "" && !userList.has(environment.get("user"))) {
|
||||||
|
console.warn(`Invalid user '${environment.get("user")}' in environment.`);
|
||||||
|
environment.set("user", "felix");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set home directory
|
||||||
|
environment.set("home", userList.get(environment.get("user"))?.home ?? "/");
|
||||||
|
|
||||||
|
// Check cwd in environment
|
||||||
|
if (!environment.has("cwd")) {
|
||||||
|
environment.set("cwd", environment.get("home"));
|
||||||
|
} else if (!fileSystem.has(new Path(environment.get("cwd")))) {
|
||||||
|
console.warn(`Invalid cwd '${environment.get("cwd")}' in environment.`);
|
||||||
|
environment.set("cwd", environment.get("home"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persists the given environment.
|
||||||
|
*
|
||||||
|
* @param environment the environment to persist
|
||||||
|
*/
|
||||||
|
static setEnvironment(environment: Environment) {
|
||||||
|
Cookies.set("env", environment.variables, {"path": "/"});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persists the "power off" setting.
|
||||||
|
*
|
||||||
|
* @param value the value to persist for the "power off" setting
|
||||||
|
*/
|
||||||
|
static setPoweroff(value: boolean) {
|
||||||
|
Cookies.set("poweroff", `${value}`, {
|
||||||
|
"expires": new Date(new Date().setSeconds(new Date().getSeconds() + 30)),
|
||||||
|
"path": "/"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all persistent storage.
|
||||||
|
*/
|
||||||
|
static reset() {
|
||||||
|
Cookies.remove("files");
|
||||||
|
Cookies.remove("env");
|
||||||
|
Cookies.remove("poweroff");
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
import * as Cookies from "js-cookie";
|
|
||||||
import {Commands} from "./Commands";
|
import {Commands} from "./Commands";
|
||||||
import {Environment} from "./Environment";
|
import {Environment} from "./Environment";
|
||||||
import {Directory, File, FileSystem, Node, Path} from "./FileSystem";
|
import {Directory, File, FileSystem, Path} from "./FileSystem";
|
||||||
import {InputParser} from "./InputParser";
|
import {InputParser} from "./InputParser";
|
||||||
|
import {Persistence} from "./Persistence";
|
||||||
import {asciiHeaderHtml, IllegalStateError, stripHtmlTags} from "./Shared";
|
import {asciiHeaderHtml, IllegalStateError, stripHtmlTags} from "./Shared";
|
||||||
import {EscapeCharacters, InputHistory} from "./Terminal";
|
import {EscapeCharacters, InputHistory} from "./Terminal";
|
||||||
import {UserList} from "./UserList";
|
import {UserList} from "./UserList";
|
||||||
|
@ -48,8 +48,8 @@ export class Shell {
|
||||||
constructor(inputHistory: InputHistory) {
|
constructor(inputHistory: InputHistory) {
|
||||||
this.inputHistory = inputHistory;
|
this.inputHistory = inputHistory;
|
||||||
this.userList = new UserList();
|
this.userList = new UserList();
|
||||||
this.fileSystem = Shell.loadFileSystem();
|
this.fileSystem = Persistence.getFileSystem();
|
||||||
this.environment = Shell.loadEnvironment(this.fileSystem, this.userList);
|
this.environment = Persistence.getEnvironment(this.fileSystem, this.userList);
|
||||||
this.commands = new Commands(this.environment, this.userList, this.fileSystem);
|
this.commands = new Commands(this.environment, this.userList, this.fileSystem);
|
||||||
|
|
||||||
this.saveState();
|
this.saveState();
|
||||||
|
@ -71,6 +71,7 @@ export class Shell {
|
||||||
</span>
|
</span>
|
||||||
Type "<a href="#" onclick="execute('help');">help</a>" for help.
|
Type "<a href="#" onclick="execute('help');">help</a>" for help.
|
||||||
|
|
||||||
|
Welcome to josh v%%VERSION_NUMBER%%, the javascript online shell.
|
||||||
`.trimLines();
|
`.trimLines();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,74 +206,13 @@ export class Shell {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves the shell's state in cookies.
|
* Persists the shell's state.
|
||||||
|
*
|
||||||
|
* @see Persistence
|
||||||
*/
|
*/
|
||||||
private saveState() {
|
private saveState() {
|
||||||
Cookies.set("files", this.fileSystem.root, {
|
Persistence.setFileSystem(this.fileSystem);
|
||||||
"expires": new Date(new Date().setFullYear(new Date().getFullYear() + 25)),
|
Persistence.setEnvironment(this.environment);
|
||||||
"path": "/"
|
|
||||||
});
|
|
||||||
Cookies.set("env", this.environment.variables, {"path": "/"});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the file system loaded from a cookie, or the default file system if no cookie is present or the cookie
|
|
||||||
* is invalid.
|
|
||||||
*/
|
|
||||||
private static loadFileSystem(): FileSystem {
|
|
||||||
let files: Directory | undefined = undefined;
|
|
||||||
const filesString = Cookies.get("files");
|
|
||||||
if (filesString !== undefined) {
|
|
||||||
try {
|
|
||||||
const parsedFiles = Node.deserialize(filesString);
|
|
||||||
if (parsedFiles instanceof Directory)
|
|
||||||
files = parsedFiles;
|
|
||||||
else
|
|
||||||
console.warn("`files` cookie contains non-directory.");
|
|
||||||
} catch (error) {
|
|
||||||
console.warn("Failed to deserialize `files` cookie.", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new FileSystem(files);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the environment loaded from a cookie, or the default environment if no cookie is present or the cookie
|
|
||||||
* is invalid.
|
|
||||||
*
|
|
||||||
* @param fileSystem the file system used to validate the `cwd` environment variable
|
|
||||||
* @param userList the list of users used to validate the `user` environment variable
|
|
||||||
*/
|
|
||||||
private static loadEnvironment(fileSystem: FileSystem, userList: UserList): Environment {
|
|
||||||
const environmentString = Cookies.get("env") ?? "{}";
|
|
||||||
let environment: Environment;
|
|
||||||
try {
|
|
||||||
environment = new Environment(["cwd", "home", "user"], JSON.parse(environmentString));
|
|
||||||
} catch (error) {
|
|
||||||
console.warn("Failed to set environment from cookie.");
|
|
||||||
environment = new Environment(["cwd", "home", "user"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check user in environment
|
|
||||||
if (!environment.has("user")) {
|
|
||||||
environment.set("user", "felix");
|
|
||||||
} else if (environment.get("user") !== "" && !userList.has(environment.get("user"))) {
|
|
||||||
console.warn(`Invalid user '${environment.get("user")}' in environment.`);
|
|
||||||
environment.set("user", "felix");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set home directory
|
|
||||||
environment.set("home", userList.get(environment.get("user"))?.home ?? "/");
|
|
||||||
|
|
||||||
// Check cwd in environment
|
|
||||||
if (!environment.has("cwd")) {
|
|
||||||
environment.set("cwd", environment.get("home"));
|
|
||||||
} else if (!fileSystem.has(new Path(environment.get("cwd")))) {
|
|
||||||
console.warn(`Invalid cwd '${environment.get("cwd")}' in environment.`);
|
|
||||||
environment.set("cwd", environment.get("home"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return environment;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue