configure eslint and tsconfig

This commit is contained in:
Raymond Wang 2022-10-23 16:58:22 -07:00
parent 5b814172fb
commit 8a57631324
15 changed files with 1668 additions and 2619 deletions

View file

@ -1,27 +1,20 @@
{ {
"env": { "root": true,
"browser": true,
"commonjs": true,
"es6": true,
"jest": true,
"node": true
},
"extends": [ "extends": [
"eslint:recommended", "eslint:recommended",
"plugin:react/recommended", "plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended" "plugin:@typescript-eslint/recommended-requiring-type-checking"
], ],
"parserOptions": {
"project": "./tsconfig.json"
},
"rules": {
"@typescript-eslint/no-unsafe-argument": "warn",
"@typescript-eslint/no-unsafe-assignment": "warn",
"@typescript-eslint/unbound-method": "warn"
},
"overrides": [], "overrides": [],
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"parserOptions": { "plugins": ["@typescript-eslint"],
"ecmaVersion": "latest" "ignorePatterns": ["node_modules", "build", "webpack**", "src/public/"]
},
"plugins": ["react", "@typescript-eslint"],
"rules": {},
"settings": {
"react": {
"version": "detect"
}
},
"ignorePatterns": ["node_modules/", "dist/", "build/", "webpack.config.*"]
} }

3371
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -4,32 +4,36 @@
"description": "Website for IEEE of UC San Diego", "description": "Website for IEEE of UC San Diego",
"main": "build/index.js", "main": "build/index.js",
"scripts": { "scripts": {
"build": "webpack & tsc", "build": "webpack & tsc --build --verbose & node build/index.js generate",
"lint": "eslint \"**/*.{ts, tsx}\"" "start": "node build/index.js",
"watch": "webpack watch",
"generate": "node build/index.js generate",
"lint": "eslint ."
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/express": "^4.17.13", "@types/express": "^4.17.14",
"@types/node": "^16.11.7", "@types/mongoose": "^5.11.97",
"@types/react-dom": "^16.9.14", "@types/node": "^18.11.4",
"@types/ws": "^7.4.7", "@types/react-dom": "^18.0.6",
"express": "^4.17.1", "@types/ws": "^8.5.3",
"mongoose": "^6.1.7", "express": "^4.18.2",
"react": "^16.14.0", "mongoose": "^6.6.7",
"react-dom": "^16.14.0" "react": "^18.2.0",
"react-dom": "^18.2.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.15.8", "@babel/core": "^7.19.6",
"@babel/preset-env": "^7.15.8", "@babel/preset-env": "^7.19.4",
"@babel/preset-react": "^7.14.5", "@babel/preset-react": "^7.14.5",
"@types/react": "^16.14.20", "@types/react": "^18.0.21",
"@typescript-eslint/eslint-plugin": "^5.40.1", "@typescript-eslint/eslint-plugin": "^5.40.1",
"@typescript-eslint/parser": "^5.40.1", "@typescript-eslint/parser": "^5.40.1",
"babel-loader": "^8.2.3", "babel-loader": "^8.2.3",
"copy-webpack-plugin": "^6.4.1", "copy-webpack-plugin": "^11.0.0",
"css-loader": "^5.2.7", "css-loader": "^6.7.1",
"eslint": "^8.26.0", "eslint": "^8.26.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-config-standard-with-typescript": "^23.0.0", "eslint-config-standard-with-typescript": "^23.0.0",
@ -38,12 +42,12 @@
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-promise": "^6.1.1", "eslint-plugin-promise": "^6.1.1",
"eslint-plugin-react": "^7.31.10", "eslint-plugin-react": "^7.31.10",
"html-webpack-plugin": "^4.5.2", "html-webpack-plugin": "^5.5.0",
"license-webpack-plugin": "^2.3.21", "license-webpack-plugin": "^4.0.2",
"style-loader": "^2.0.0", "style-loader": "^3.3.1",
"ts-loader": "^8.3.0", "ts-loader": "^9.4.1",
"typescript": "^4.8.4", "typescript": "^4.8.4",
"webpack": "^5.60.0", "webpack": "^5.74.0",
"webpack-cli": "^4.10.0" "webpack-cli": "^4.10.0"
} }
} }

View file

@ -44,6 +44,7 @@ export const SOCIALS = [
message: "@ieeeucsd", message: "@ieeeucsd",
}, },
]; ];
export const EMAIL = [ export const EMAIL = [
{ {
icon: "img/email.svg", icon: "img/email.svg",
@ -51,6 +52,7 @@ export const EMAIL = [
message: "ieee@eng.ucsd.edu", message: "ieee@eng.ucsd.edu",
}, },
]; ];
export const EMAIL_WHITE = [ export const EMAIL_WHITE = [
{ {
icon: "img/email_white.svg", icon: "img/email_white.svg",

View file

@ -10,10 +10,11 @@ import Carousel from "./components/Carousel";
import Footer from "./components/Footer"; import Footer from "./components/Footer";
class Main extends React.Component { class Main extends React.Component {
constructor(props: any) { constructor() {
super(props); super({});
this.state = {}; this.state = {};
} }
public render() { public render() {
return ( return (
<> <>

View file

@ -5,6 +5,7 @@ interface DefaultSectionProps {
title: string; title: string;
paragraphs?: string[]; paragraphs?: string[];
className?: string; className?: string;
children?: React.ReactNode;
} }
export default class DefaultSection extends Component<DefaultSectionProps> { export default class DefaultSection extends Component<DefaultSectionProps> {
@ -26,7 +27,6 @@ export default class DefaultSection extends Component<DefaultSectionProps> {
<p key={n}>{n}</p> <p key={n}>{n}</p>
) )
)} )}
{/* eslint-disable-next-line react/prop-types */}
{this.props.children} {this.props.children}
</div> </div>
); );

View file

@ -4,8 +4,8 @@ import SocialCard from "./SocialCard";
import { EMAIL_WHITE, SOCIALS_WHITE } from "../Config"; import { EMAIL_WHITE, SOCIALS_WHITE } from "../Config";
export default class Footer extends Component { export default class Footer extends Component {
constructor(props: any) { constructor() {
super(props); super({});
this.state = {}; this.state = {};
} }

View file

@ -20,14 +20,18 @@ export default class TopBar extends Component<TopBarProps, TopBarState> {
showBurger: this.shouldShowBurger(), showBurger: this.shouldShowBurger(),
menuVisible: false, menuVisible: false,
}; };
this.toggleMenu.bind(this);
window.addEventListener("resize", this.checkResize.bind(this)); window.addEventListener("resize", this.checkResize.bind(this));
} }
private toggleMenu() { private toggleMenu() {
this.setState({ menuVisible: !this.state.menuVisible }); this.setState({ menuVisible: !this.state.menuVisible });
} }
private checkResize() { private checkResize() {
this.setState({ showBurger: this.shouldShowBurger() }); this.setState({ showBurger: this.shouldShowBurger() });
} }
private shouldShowBurger(): boolean { private shouldShowBurger(): boolean {
return window.innerWidth < TopBar.HAMBURGER_POINT; return window.innerWidth < TopBar.HAMBURGER_POINT;
} }
@ -48,7 +52,7 @@ export default class TopBar extends Component<TopBarProps, TopBarState> {
style={{ style={{
display: this.state.showBurger ? "initial" : "none", display: this.state.showBurger ? "initial" : "none",
}} }}
onClick={this.toggleMenu.bind(this)} onClick={this.toggleMenu}
role="menubar" role="menubar"
> >

View file

@ -8,10 +8,11 @@ import SocialCard from "./components/SocialCard";
import Footer from "./components/Footer"; import Footer from "./components/Footer";
class Main extends React.Component { class Main extends React.Component {
constructor(props: any) { constructor() {
super(props); super({});
this.state = {}; this.state = {};
} }
public render() { public render() {
return ( return (
<> <>

View file

@ -10,10 +10,11 @@ import Carousel from "./components/Carousel";
import Footer from "./components/Footer"; import Footer from "./components/Footer";
class Main extends React.Component { class Main extends React.Component {
constructor(props: any) { constructor() {
super(props); super({});
this.state = {}; this.state = {};
} }
public render() { public render() {
return ( return (
<> <>

View file

@ -9,10 +9,11 @@ import SocialCard from "./components/SocialCard";
import Footer from "./components/Footer"; import Footer from "./components/Footer";
class Main extends React.Component { class Main extends React.Component {
constructor(props: any) { constructor() {
super(props); super({});
this.state = {}; this.state = {};
} }
public render() { public render() {
return ( return (
<> <>

View file

@ -1,236 +1,247 @@
import { Schema, Document } from "mongoose"; import { Schema } from "mongoose";
import * as mongoose from "mongoose"; import * as mongoose from "mongoose";
import { promisify } from "util"; import { promisify } from "util";
import { randomBytes, pbkdf2 } from "crypto"; import { randomBytes, pbkdf2 } from "crypto";
const pbkdf = promisify(pbkdf2); const pbkdf = promisify(pbkdf2);
const User = mongoose.model( const User = mongoose.model(
"User", "User",
new Schema({ new Schema({
display: String, display: String,
email: { email: {
public: Boolean, public: Boolean,
text: String, text: String,
}, },
picture: String, picture: String,
password: { password: {
hash: Buffer, hash: Buffer,
salt: Buffer, salt: Buffer,
}, },
bio: { type: String, default: "" }, bio: { type: String, default: "" },
isOfficer: { type: Boolean, default: false }, isOfficer: { type: Boolean, default: false },
title: { type: String, default: "Member" }, title: { type: String, default: "Member" },
uuid: String, uuid: String,
}) })
); );
export interface DBUser extends Document { interface HashSalt {
display: string; hash: Buffer;
email: string; salt: Buffer;
picture: string; }
password: HashSalt;
isOfficer: boolean; export interface DBUser {
title: string; display: string;
uuid: string; email: {
} public: boolean;
interface HashSalt { text: string;
hash: Buffer; };
salt: Buffer; picture: string;
} password: HashSalt;
isOfficer: boolean;
export default class UserDatabase { title: string;
// How many iterations pbkdf2 will iterate the password hashing algorithm uuid: string;
private static PW_ITERATIONS = 100000; }
// The length of the salt that should be used with every password hash
private static SALT_LENGTH = 64; export default class UserDatabase {
// How many bytes will be used to generate a UUID of length 2 times this variable // How many iterations pbkdf2 will iterate the password hashing algorithm
private static UUID_LENGTH = 32; private static PW_ITERATIONS = 100000;
// The length of the salt that should be used with every password hash
/** private static SALT_LENGTH = 64;
* Makes a new UserDatabase // How many bytes will be used to generate a UUID of length 2 times this variable
* @param url the mongodb database url to connect to private static UUID_LENGTH = 32;
*/
constructor(url: string) { /**
mongoose.connect(url); * Makes a new UserDatabase
mongoose.set("returnOriginal", false); * @param url the mongodb database url to connect to
} */
constructor(url: string) {
// Functions to edit current users mongoose
/** .connect(url)
* Changes a user's display name on the website .then(() => {
* @param uuid the UUID of the user mongoose.set("returnOriginal", false);
* @param newName the user's new name })
*/ .catch((err) => {
public async setName(uuid: string, newName: string) { console.error(err);
await User.findOneAndUpdate({ uuid: uuid }, { name: newName }).exec(); });
} }
/**
* Gets the display name of a user with a given UUID // Functions to edit current users
* @param uuid the UUID of the user /**
* @returns the user's display name * Changes a user's display name on the website
*/ * @param uuid the UUID of the user
public async getName(uuid: string): Promise<string> { * @param newName the user's new name
return ((await User.findOne({ uuid: uuid }).lean().exec()) as DBUser) */
.display; public async setName(uuid: string, newName: string) {
} await User.findOneAndUpdate({ uuid: uuid }, { name: newName }).exec();
}
/** /**
* Updates a user's display email * Gets the display name of a user with a given UUID
* @param uuid the UUID of the user * @param uuid the UUID of the user
* @param newEmail the user's new display email * @returns the user's display name
*/ */
public async setEmail(uuid: string, newEmail: string) { public async getName(uuid: string): Promise<string> {
await User.findOneAndUpdate({ uuid: uuid }, { email: newEmail }).exec(); const user = await User.findOne({ uuid: uuid }).lean().exec();
} return user.display;
/** }
* Gets the email of a user with a given UUID
* @param uuid the UUID of the user /**
* @returns the user's email * Updates a user's display email
*/ * @param uuid the UUID of the user
public async getEmail(uuid: string): Promise<string> { * @param newEmail the user's new display email
return ((await User.findOne({ uuid: uuid }).lean().exec()) as DBUser) */
.email; public async setEmail(uuid: string, newEmail: string) {
} await User.findOneAndUpdate({ uuid: uuid }, { email: newEmail }).exec();
}
/** /**
* Updates a user's password * Gets the email of a user with a given UUID
* @param uuid the UUID of the user * @param uuid the UUID of the user
* @param newPassword the user's new password * @returns the user's email
*/ */
public async setPassword(uuid: string, newPassword: string) { public async getEmail(uuid: string): Promise<string> {
const hashsalt = await UserDatabase.hash(newPassword); const user = await User.findOne({ uuid: uuid }).lean().exec();
await User.findOneAndUpdate( return user.email.text;
{ uuid: uuid }, }
{ password: hashsalt }
).exec(); /**
} * Updates a user's password
/** * @param uuid the UUID of the user
* Gets the password of a user with a given UUID * @param newPassword the user's new password
* @param uuid the UUID of the user */
* @returns the user's password public async setPassword(uuid: string, newPassword: string) {
*/ const hashsalt = await UserDatabase.hash(newPassword);
public async getPasswordHash(uuid: string): Promise<Buffer> { await User.findOneAndUpdate(
return ((await User.findOne({ uuid: uuid }).lean().exec()) as DBUser) { uuid: uuid },
.password.hash; { password: hashsalt }
} ).exec();
}
/** /**
* Updates a member's position * Gets the password of a user with a given UUID
* @param uuid the UUID of the user * @param uuid the UUID of the user
* @param newTitle the user's new officer title/position * @returns the user's password
*/ */
public async setRank(uuid: string, newTitle: string, isOfficer: boolean) { public async getPasswordHash(uuid: string): Promise<Buffer> {
await User.findOneAndUpdate( const user = await User.findOne({ uuid: uuid }).lean().exec();
{ uuid: uuid }, return user.password.hash;
{ title: newTitle, isOfficer: isOfficer } }
).exec();
} /**
/** * Updates a member's position
* Gets the title of a user with a given UUID * @param uuid the UUID of the user
* @param uuid the UUID of the user * @param newTitle the user's new officer title/position
* @returns the user's title */
*/ public async setRank(uuid: string, newTitle: string, isOfficer: boolean) {
public async getRank(uuid: string): Promise<string> { await User.findOneAndUpdate(
return ((await User.findOne({ uuid: uuid }).lean().exec()) as DBUser) { uuid: uuid },
.title; { title: newTitle, isOfficer: isOfficer }
} ).exec();
/** }
* Gets whether of a user with a given UUID is an officer /**
* @param uuid the UUID of the user * Gets the title of a user with a given UUID
* @returns true if the user is an officer * @param uuid the UUID of the user
*/ * @returns the user's title
public async isOfficer(uuid: string): Promise<boolean> { */
return ((await User.findOne({ uuid: uuid }).lean().exec()) as DBUser) public async getRank(uuid: string): Promise<string> {
.isOfficer; const user = await User.findOne({ uuid: uuid }).lean().exec();
} return user.title;
}
/** /**
* Sets the user's new profile picture * Gets whether of a user with a given UUID is an officer
* @param uuid the UUID of the user * @param uuid the UUID of the user
* @param newPic the link to the new URL of the profile picture * @returns true if the user is an officer
*/ */
public async setPicture(uuid: string, newPic: string) { public async isOfficer(uuid: string): Promise<boolean> {
await User.findOneAndUpdate({ uuid: uuid }, { picture: newPic }).exec(); const user = await User.findOne({ uuid: uuid }).lean().exec();
} return user.isOfficer;
/** }
* Gets the profile picture of a user with a given UUID
* @param uuid the UUID of the user /**
* @returns the user's profile picture * Sets the user's new profile picture
*/ * @param uuid the UUID of the user
public async getPicture(uuid: string): Promise<string> { * @param newPic the link to the new URL of the profile picture
return ((await User.findOne({ uuid: uuid }).lean().exec()) as DBUser) */
.picture; public async setPicture(uuid: string, newPic: string) {
} await User.findOneAndUpdate({ uuid: uuid }, { picture: newPic }).exec();
}
public async getUser(uuid: string): Promise<DBUser> { /**
return User.findOne({ uuid: uuid }).exec() as Promise<DBUser>; * Gets the profile picture of a user with a given UUID
} * @param uuid the UUID of the user
* @returns the user's profile picture
// User creation and deletion functions */
/** public async getPicture(uuid: string): Promise<string> {
* Deletes a user with the given UUID const user = await User.findOne({ uuid: uuid }).lean().exec();
* @param uuid the UUID of the user return user.picture;
*/ }
public async deleteUser(uuid: string) {
await User.findOneAndDelete({ uuid: uuid }).exec(); public async getUser(uuid: string): Promise<DBUser> {
} const user = await User.findOne({ uuid: uuid }).lean().exec();
return user as DBUser;
/** }
* Makes a new user and adds it to the database
* @param display the user's display name // User creation and deletion functions
* @param email the user's display email /**
* @param picture the user's picture * Deletes a user with the given UUID
* @param password the user's plaintext password * @param uuid the UUID of the user
*/ */
public async makeNewUser( public async deleteUser(uuid: string) {
display: string, await User.findOneAndDelete({ uuid: uuid }).exec();
email: string, }
picture: string,
password: string, /**
title?: string, * Makes a new user and adds it to the database
isOfficer?: boolean * @param display the user's display name
): Promise<void> { * @param email the user's display email
const encrypted = await UserDatabase.hash(password); * @param picture the user's picture
await new User({ * @param password the user's plaintext password
display: display, */
email: email, public async makeNewUser(
picture: picture, display: string,
password: encrypted, email: string,
uuid: UserDatabase.genUUID(), picture: string,
isOfficer: !!isOfficer, password: string,
title: title ? title : "Member", title?: string,
}).save(); isOfficer?: boolean
} ): Promise<void> {
const encrypted = await UserDatabase.hash(password);
// Utility functions await new User({
/** display: display,
* Hashes a password using a salt of 64 random bytes and pbkdf2 with 100000 iterations email: email,
* @param password the password as a string that should be hashed picture: picture,
* @returns the hashed password password: encrypted,
*/ uuid: UserDatabase.genUUID(),
public static async hash(password: string): Promise<HashSalt> { isOfficer: !!isOfficer,
const salt = randomBytes(UserDatabase.SALT_LENGTH); title: title ? title : "Member",
const pass = Buffer.from(String.prototype.normalize(password)); }).save();
return { }
hash: await pbkdf(
pass, // Utility functions
salt, /**
UserDatabase.PW_ITERATIONS, * Hashes a password using a salt of 64 random bytes and pbkdf2 with 100000 iterations
512, * @param password the password as a string that should be hashed
"sha512" * @returns the hashed password
), */
salt: salt, public static async hash(password: string): Promise<HashSalt> {
}; const salt = randomBytes(UserDatabase.SALT_LENGTH);
} const pass = Buffer.from(String.prototype.normalize(password));
return {
/** hash: await pbkdf(
* Generates a UUID for a user or anything else pass,
* @returns a random hex string of length 2 * UUID_LENGTH salt,
*/ UserDatabase.PW_ITERATIONS,
public static genUUID(): string { 512,
return Array.from(randomBytes(UserDatabase.UUID_LENGTH)) "sha512"
.map((val) => val.toString(16).padStart(2, "0")) ),
.join(""); salt: salt,
} };
} }
/**
* Generates a UUID for a user or anything else
* @returns a random hex string of length 2 * UUID_LENGTH
*/
public static genUUID(): string {
return Array.from(randomBytes(UserDatabase.UUID_LENGTH))
.map((val) => val.toString(16).padStart(2, "0"))
.join("");
}
}

View file

@ -1,130 +1,134 @@
import express, { NextFunction } from "express"; import express, { NextFunction } from "express";
import { Request, Response } from "express"; import { Request, Response } from "express";
import * as path from "path"; import * as path from "path";
import * as fs from "fs"; import * as fs from "fs";
interface Website { interface Website {
[key: string]: string; [key: string]: string;
sitename: string; sitename: string;
title: string; title: string;
description: string; description: string;
jsfile: string; jsfile: string;
cssfile: string; cssfile: string;
themecolor: string; themecolor: string;
} }
const APP = express(); const APP = express();
const TEMPLATE = fs const TEMPLATE = fs
.readFileSync(path.join(__dirname, "public/template.html")) .readFileSync(path.join(__dirname, "public/template.html"))
.toString(); .toString();
const WEBSITES = [ const WEBSITES = [
{ {
sitename: "index", sitename: "index",
title: "IEEE at UCSD", title: "IEEE at UCSD",
description: "", description: "",
jsfile: "js/index.js", jsfile: "js/index.js",
cssfile: "css/styles.css", cssfile: "css/styles.css",
themecolor: "", themecolor: "",
}, },
{ {
sitename: "events", sitename: "events",
title: "IEEE at UCSD", title: "IEEE at UCSD",
description: "", description: "",
jsfile: "js/events.js", jsfile: "js/events.js",
cssfile: "css/styles.css", cssfile: "css/styles.css",
themecolor: "", themecolor: "",
}, },
{ {
sitename: "projects", sitename: "projects",
title: "IEEE at UCSD", title: "IEEE at UCSD",
description: "", description: "",
jsfile: "js/projects.js", jsfile: "js/projects.js",
cssfile: "css/styles.css", cssfile: "css/styles.css",
themecolor: "", themecolor: "",
}, },
{ {
sitename: "committees", sitename: "committees",
title: "IEEE at UCSD", title: "IEEE at UCSD",
description: "", description: "",
jsfile: "js/committees.js", jsfile: "js/committees.js",
cssfile: "css/styles.css", cssfile: "css/styles.css",
themecolor: "", themecolor: "",
}, },
{ {
sitename: "contact", sitename: "contact",
title: "IEEE at UCSD", title: "IEEE at UCSD",
description: "", description: "",
jsfile: "js/index.js", jsfile: "js/index.js",
cssfile: "css/styles.css", cssfile: "css/styles.css",
themecolor: "", themecolor: "",
}, },
] as Website[]; ] as Website[];
const PORT = 9000; const PORT = 9000;
// Make the public directory traversible to people online // Make the public directory traversible to people online
APP.use(express.static(path.join(__dirname, "public"))); APP.use(express.static(path.join(__dirname, "public")));
// Put the cookies as a variable in the request // Put the cookies as a variable in the request
APP.use((req: Request, res: Response, next: NextFunction) => { APP.use((req: Request, res: Response, next: NextFunction) => {
req.cookies = req.headers.cookie; req.cookies = req.headers.cookie;
next(); next();
}); });
// Receive json post requests and urlencoded requests // Receive json post requests and urlencoded requests
APP.use(express.json()); APP.use(express.json());
APP.use(express.urlencoded({ extended: true })); APP.use(express.urlencoded({ extended: true }));
// Send main page // Send main page
APP.get("/", (req: Request, res: Response) => { APP.get("/", (req: Request, res: Response) => {
respond(res, "index"); respond(res, "index");
}); });
APP.get("/events", (req: Request, res: Response) => {
respond(res, "events"); APP.get("/events", (req: Request, res: Response) => {
}); respond(res, "events");
APP.get("/projects", (req: Request, res: Response) => { });
respond(res, "projects");
}); APP.get("/projects", (req: Request, res: Response) => {
APP.get("/committees", (req: Request, res: Response) => { respond(res, "projects");
respond(res, "committees"); });
});
APP.get("/contact", (req: Request, res: Response) => { APP.get("/committees", (req: Request, res: Response) => {
respond(res, "contact"); respond(res, "committees");
}); });
/** APP.get("/contact", (req: Request, res: Response) => {
* Utility functions for above methods respond(res, "contact");
*/ });
function respond(res: Response, filename: string) {
res.set({ /**
"Content-Type": "text/html", * Utility functions for above methods
}); */
res.send(generatePage(filename)); function respond(res: Response, filename: string) {
} res.set({
"Content-Type": "text/html",
function generatePage(name: string): string { });
const site = WEBSITES.find((e) => e.sitename === name); res.send(generatePage(filename));
let html = TEMPLATE; }
let key;
for (key of Object.keys(site)) { function generatePage(name: string): string {
html = html.replace(new RegExp("\\$" + key.toUpperCase()), site[key]); const site = WEBSITES.find((e) => e.sitename === name);
} let html = TEMPLATE;
return html; let key;
} for (key of Object.keys(site)) {
html = html.replace(new RegExp("\\$" + key.toUpperCase()), site[key]);
function generateFilePages() { }
let site; return html;
for (site of WEBSITES) { }
const html = generatePage(site.sitename);
fs.writeFileSync( function generateFilePages() {
path.join(__dirname, "public/", `${site.sitename}.html`), let site;
html for (site of WEBSITES) {
); const html = generatePage(site.sitename);
} fs.writeFileSync(
} path.join(__dirname, "public/", `${site.sitename}.html`),
html
if (process.argv[2] === "generate") { );
generateFilePages(); }
} else { }
APP.listen(PORT, () => {
console.log(`Listening on port ${PORT}`); if (process.argv[2] === "generate") {
}); generateFilePages();
} } else {
APP.listen(PORT, () => {
console.log(`Listening on port ${PORT}`);
});
}

View file

@ -1,12 +1,11 @@
{ {
"compilerOptions": { "compilerOptions": {
"outDir": "./build/", "outDir": "./build/",
"noImplicitAny": true, "noImplicitAny": false,
"module": "commonjs", "module": "commonjs",
"target": "es6", "target": "es6",
"jsx": "react", "jsx": "react",
"esModuleInterop": true "esModuleInterop": true
}, },
"include": ["./src"], "include": ["./src/util"]
"exclude": ["./src/public"] }
}

View file

@ -1,7 +1,5 @@
const path = require("path"); const path = require("path");
const CopyPlugin = require("copy-webpack-plugin"); const CopyPlugin = require("copy-webpack-plugin");
const LicensePlugin = require("license-webpack-plugin").LicenseWebpackPlugin;
const TerserPlugin = require("terser-webpack-plugin");
const fs = require("fs"); const fs = require("fs");
module.exports = { module.exports = {
@ -10,6 +8,7 @@ module.exports = {
path: path.resolve(__dirname, "build/public"), path: path.resolve(__dirname, "build/public"),
filename: "./js/[name].js", filename: "./js/[name].js",
}, },
mode: "production",
module: { module: {
rules: [ rules: [
{ {
@ -29,44 +28,24 @@ module.exports = {
resolve: { resolve: {
extensions: [".tsx", ".ts", ".js"], extensions: [".tsx", ".ts", ".js"],
}, },
mode: "production",
plugins: [ plugins: [
new CopyPlugin({ new CopyPlugin({
patterns: [ patterns: [
{ {
from: "./src/public", from: "./src/util",
to: ".",
globOptions: {
ignore: ["**/*.tsx", "**/*.ts"],
},
}, },
], ],
}), }),
new LicensePlugin({
outputFilename: "third-party-notice.txt",
}),
], ],
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
output: {
comments: false,
},
},
extractComments: false,
}),
],
},
//devtool: "source-map"
devServer: {
static: "./build/public",
},
}; };
/**
* Load all entries from a directory
* @param {*} dir Directory to load entries from
* @returns Object with entries
*/
function loadEntries(dir) { function loadEntries(dir) {
let files = fs.readdirSync(path.join(__dirname, dir)); const files = fs.readdirSync(path.join(__dirname, dir));
let entries = {}; let entries = {};
files.forEach((file) => { files.forEach((file) => {
let name = file.match(/^(.*)\.tsx$/); let name = file.match(/^(.*)\.tsx$/);