initial commit
This commit is contained in:
commit
ff8fd8eec4
6
.dockerignore
Normal file
6
.dockerignore
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
docker-cache/
|
||||||
|
node_modules/
|
||||||
|
assets/css/
|
||||||
|
assets/js/
|
||||||
|
assets/webp/
|
||||||
|
config/
|
109
.gitignore
vendored
Normal file
109
.gitignore
vendored
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# TypeScript v1 declaration files
|
||||||
|
typings/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
.env.test
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and *not* Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
assets/css/
|
||||||
|
assets/js/
|
||||||
|
assets/webp/
|
||||||
|
config/
|
36
Dockerfile
Normal file
36
Dockerfile
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
FROM oven/bun:alpine AS baseimg
|
||||||
|
|
||||||
|
FROM baseimg AS dependencies
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
COPY ./package*json ./
|
||||||
|
COPY ./bun.lockb ./
|
||||||
|
RUN bun install --production --no-progress && \
|
||||||
|
chown -R bun .
|
||||||
|
|
||||||
|
|
||||||
|
FROM dependencies as build-env
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
RUN apk add --no-cache libwebp libwebp-tools
|
||||||
|
|
||||||
|
RUN bun install --no-progress
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN bun run build.ts && \
|
||||||
|
chown -R bun .
|
||||||
|
|
||||||
|
|
||||||
|
FROM baseimg as deploy
|
||||||
|
WORKDIR /usr/src/bitch
|
||||||
|
HEALTHCHECK --timeout=3s \
|
||||||
|
CMD curl --fail http://localhost:8080/healthcheck || exit 1
|
||||||
|
RUN apk add --no-cache curl
|
||||||
|
COPY --from=dependencies /build .
|
||||||
|
COPY --from=build-env /build/assets ./assets
|
||||||
|
COPY . .
|
||||||
|
USER bun
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
CMD [ "bun", "run", "index.ts"]
|
15
README.md
Normal file
15
README.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# jakehurwitzisabitch
|
||||||
|
|
||||||
|
To install dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun install
|
||||||
|
```
|
||||||
|
|
||||||
|
To run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
This project was created using `bun init` in bun v1.0.23. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
|
BIN
assets/images/bitch.jpg
Normal file
BIN
assets/images/bitch.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
BIN
assets/images/cache-me.png
Normal file
BIN
assets/images/cache-me.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 116 B |
171
build.ts
Normal file
171
build.ts
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import child_process from 'child_process';
|
||||||
|
import uglifyjs from "uglify-js";
|
||||||
|
import * as sass from 'sass';
|
||||||
|
import * as csso from 'csso';
|
||||||
|
|
||||||
|
const spawn = child_process.spawn;
|
||||||
|
const fsp = fs.promises;
|
||||||
|
const STYLESDIR = 'styles';
|
||||||
|
const SCRIPTSDIR = 'scripts';
|
||||||
|
const IMAGESDIR = path.join('assets', 'images');
|
||||||
|
const STYLEOUTDIR = process.env.STYLEOUTDIR || path.join(import.meta.dir, 'assets', 'css');
|
||||||
|
const SCRIPTSOUTDIR = process.env.SCRIPTSOUTDIR || path.join(import.meta.dir, 'assets', 'js');
|
||||||
|
const IMAGESOUTDIR = process.env.IMAGESOUTDIR || path.join(import.meta.dir, 'assets', 'webp');
|
||||||
|
const STYLEOUTFILE = process.env.STYLEOUTFILE || 'styles.css';
|
||||||
|
const SQUASH = new RegExp('^[0-9]+-');
|
||||||
|
|
||||||
|
async function emptyDir(dir: string) {
|
||||||
|
await Promise.all((await fsp.readdir(dir, { withFileTypes: true })).map(f => path.join(dir, f.name)).map(p => fsp.rm(p, {
|
||||||
|
recursive: true,
|
||||||
|
force: true
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function mkdir(dir: string | string[]) {
|
||||||
|
if (typeof dir === 'string') {
|
||||||
|
await fsp.mkdir(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await Promise.all(dir.map(mkdir));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process styles
|
||||||
|
async function styles() {
|
||||||
|
await mkdir([STYLEOUTDIR, STYLESDIR]);
|
||||||
|
await emptyDir(STYLEOUTDIR);
|
||||||
|
let styles: string[] = [];
|
||||||
|
let files = await fsp.readdir(STYLESDIR);
|
||||||
|
await Promise.all(files.map(f => new Promise(async (res, reject) => {
|
||||||
|
let p = path.join(STYLESDIR, f);
|
||||||
|
console.log(`Processing style ${p}`);
|
||||||
|
let style = sass.compile(p).css;
|
||||||
|
if (f.charAt(0) !== '_') {
|
||||||
|
if (SQUASH.test(f)) {
|
||||||
|
styles.push(style);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let o = path.join(STYLEOUTDIR, f.substring(0, f.lastIndexOf('.')) + '.css');
|
||||||
|
await fsp.writeFile(o, csso.minify(style).css);
|
||||||
|
console.log(`Wrote ${o}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res(0);
|
||||||
|
})));
|
||||||
|
let out = csso.minify(styles.join('\n')).css;
|
||||||
|
let outpath = path.join(STYLEOUTDIR, STYLEOUTFILE);
|
||||||
|
await fsp.writeFile(outpath, out);
|
||||||
|
console.log(`Wrote ${outpath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process scripts
|
||||||
|
async function scripts() {
|
||||||
|
await mkdir([SCRIPTSOUTDIR, SCRIPTSDIR]);
|
||||||
|
await emptyDir(SCRIPTSOUTDIR);
|
||||||
|
let files = await fsp.readdir(SCRIPTSDIR);
|
||||||
|
await Promise.all(files.map(f => new Promise(async (res, reject) => {
|
||||||
|
let p = path.join(SCRIPTSDIR, f);
|
||||||
|
let o = path.join(SCRIPTSOUTDIR, f);
|
||||||
|
console.log(`Processing script ${p}`);
|
||||||
|
try {
|
||||||
|
await fsp.writeFile(o, uglifyjs.minify((await fsp.readFile(p)).toString()).code);
|
||||||
|
console.log(`Wrote ${o}`);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
console.log(`error writing ${o}: ${ex}`);
|
||||||
|
}
|
||||||
|
res(0);
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process images
|
||||||
|
async function images(dir = '') {
|
||||||
|
let p = path.join(IMAGESDIR, dir);
|
||||||
|
await mkdir(p);
|
||||||
|
if (dir.length === 0) {
|
||||||
|
await mkdir(IMAGESOUTDIR)
|
||||||
|
await emptyDir(IMAGESOUTDIR);
|
||||||
|
}
|
||||||
|
let files = await fsp.readdir(p, {
|
||||||
|
withFileTypes: true
|
||||||
|
});
|
||||||
|
if (files.length) {
|
||||||
|
await Promise.all(files.map(f => new Promise(async (res, reject) => {
|
||||||
|
if (f.isFile()) {
|
||||||
|
let outDir = path.join(IMAGESOUTDIR, dir);
|
||||||
|
let infile = path.join(p, f.name);
|
||||||
|
let outfile = path.join(outDir, f.name.substring(0, f.name.lastIndexOf('.')) + '.webp');
|
||||||
|
await mkdir(outDir);
|
||||||
|
console.log(`Processing image ${infile}`)
|
||||||
|
let process = spawn('cwebp', ['-mt', '-q', '50', infile, '-o', outfile]);
|
||||||
|
let timeout = setTimeout(() => {
|
||||||
|
reject('Timed out');
|
||||||
|
process.kill();
|
||||||
|
}, 30000);
|
||||||
|
process.on('exit', async (code) => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
if (code === 0) {
|
||||||
|
console.log(`Wrote ${outfile}`);
|
||||||
|
res(null);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
reject(code);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (f.isDirectory()) {
|
||||||
|
images(path.join(dir, f.name)).then(res).catch(reject);
|
||||||
|
}
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAbortError(err: unknown): boolean {
|
||||||
|
return typeof err === 'object' && err !== null && 'name' in err && err.name === 'AbortError';
|
||||||
|
}
|
||||||
|
|
||||||
|
(async function () {
|
||||||
|
await Promise.all([styles(), scripts(), images()]);
|
||||||
|
if (process.argv.indexOf('--watch') >= 0) {
|
||||||
|
console.log('watching for changes...');
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const watcher = fsp.watch(STYLESDIR);
|
||||||
|
for await (const _ of watcher)
|
||||||
|
await styles();
|
||||||
|
} catch (err) {
|
||||||
|
if (isAbortError(err))
|
||||||
|
return;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const watcher = fsp.watch(SCRIPTSDIR);
|
||||||
|
for await (const _ of watcher)
|
||||||
|
await scripts();
|
||||||
|
} catch (err) {
|
||||||
|
if (isAbortError(err))
|
||||||
|
return;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const watcher = fsp.watch(IMAGESDIR, {
|
||||||
|
recursive: true // no Linux ☹️
|
||||||
|
});
|
||||||
|
for await (const _ of watcher)
|
||||||
|
await images();
|
||||||
|
} catch (err) {
|
||||||
|
if (isAbortError(err))
|
||||||
|
return;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
})();
|
13
docker-compose.yml
Normal file
13
docker-compose.yml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
version: '2'
|
||||||
|
|
||||||
|
services:
|
||||||
|
bitch:
|
||||||
|
container_name: bitch
|
||||||
|
build:
|
||||||
|
context: ./
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
restart: "no"
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
volumes:
|
||||||
|
- ./config:/usr/src/bitch/config
|
58
index.ts
Normal file
58
index.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import type { Express } from "express";
|
||||||
|
import express, { application } from 'express';
|
||||||
|
import Path from 'path';
|
||||||
|
|
||||||
|
function notStupidParseInt(v: string | undefined): number {
|
||||||
|
return v === undefined ? NaN : parseInt(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
const app: Express = express();
|
||||||
|
const port: number = notStupidParseInt(process.env.PORT) || 8080;
|
||||||
|
const dataPath: string = process.env.CONFIG || Path.join('config', 'config.json');
|
||||||
|
|
||||||
|
app.set('trust proxy', 1);
|
||||||
|
app.set('view engine', 'ejs');
|
||||||
|
app.set('view options', { outputFunctionName: 'echo' });
|
||||||
|
app.use('/assets', express.static('assets'));
|
||||||
|
|
||||||
|
let visitCount = (await Bun.file(dataPath).json())['visits'] as number || 0;
|
||||||
|
|
||||||
|
app.get('/', (_, res) => {
|
||||||
|
res.render('index',
|
||||||
|
{
|
||||||
|
visitCount
|
||||||
|
},
|
||||||
|
function (err, html) {
|
||||||
|
if (!err) {
|
||||||
|
res.send(html);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.error(err);
|
||||||
|
res.status(500).send('Something went wrong. Please try again later.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/cache-me.webp', (_, res) => {
|
||||||
|
res.set('Cache-Control', 'public, max-age=31557600'); // one year
|
||||||
|
visitCount++;
|
||||||
|
res.sendFile(Path.join(import.meta.dir, 'assets', 'webp', 'cache-me.webp'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const webserver = app.listen(port, () => console.log(`jakehurwitzisabitch.com running on port ${port}`));
|
||||||
|
|
||||||
|
async function saveVisits() {
|
||||||
|
await Bun.write(dataPath, JSON.stringify({
|
||||||
|
visits: visitCount
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let inverval = setInterval(saveVisits, 1800000);
|
||||||
|
|
||||||
|
process.on('SIGTERM', async () => {
|
||||||
|
clearInterval(inverval);
|
||||||
|
console.log('writing visit count...');
|
||||||
|
await saveVisits();
|
||||||
|
console.log('done. shutting down...');
|
||||||
|
webserver.close();
|
||||||
|
});
|
21
package.json
Normal file
21
package.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "jakehurwitzisabitch",
|
||||||
|
"module": "index.ts",
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"ejs": "3.1.9",
|
||||||
|
"express": "4.18.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest",
|
||||||
|
"@types/express": "4.17.21",
|
||||||
|
"@types/csso": "5.0.4",
|
||||||
|
"@types/uglify-js": "3.17.4",
|
||||||
|
"csso": "5.0.5",
|
||||||
|
"sass": "1.66.1",
|
||||||
|
"uglify-js": "3.17.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5.0.0"
|
||||||
|
}
|
||||||
|
}
|
48
styles/00-reset.css
Normal file
48
styles/00-reset.css
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/* http://meyerweb.com/eric/tools/css/reset/
|
||||||
|
v2.0 | 20110126
|
||||||
|
License: none (public domain)
|
||||||
|
*/
|
||||||
|
|
||||||
|
html, body, div, span, applet, object, iframe,
|
||||||
|
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||||
|
a, abbr, acronym, address, big, cite, code,
|
||||||
|
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||||
|
small, strike, strong, sub, sup, tt, var,
|
||||||
|
b, u, i, center,
|
||||||
|
dl, dt, dd, ol, ul, li,
|
||||||
|
fieldset, form, label, legend,
|
||||||
|
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||||
|
article, aside, canvas, details, embed,
|
||||||
|
figure, figcaption, footer, header, hgroup,
|
||||||
|
menu, nav, output, ruby, section, summary,
|
||||||
|
time, mark, audio, video {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
font-size: 100%;
|
||||||
|
font: inherit;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
/* HTML5 display-role reset for older browsers */
|
||||||
|
article, aside, details, figcaption, figure,
|
||||||
|
footer, header, hgroup, menu, nav, section {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
ol, ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
blockquote, q {
|
||||||
|
quotes: none;
|
||||||
|
}
|
||||||
|
blockquote:before, blockquote:after,
|
||||||
|
q:before, q:after {
|
||||||
|
content: '';
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
}
|
55
styles/01-poetry-n-style.scss
Normal file
55
styles/01-poetry-n-style.scss
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
html,
|
||||||
|
body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font: normal 14px 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: url('/assets/webp/bitch.webp');
|
||||||
|
}
|
||||||
|
|
||||||
|
#w {
|
||||||
|
max-width: 620px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 125px 0 50px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0 0 100px;
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 79px;
|
||||||
|
text-align: center;
|
||||||
|
color: #FFFC00;
|
||||||
|
text-shadow: 0 0 5px #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
display: inline-block;
|
||||||
|
padding: .2em .5em;
|
||||||
|
color: #00F900;
|
||||||
|
line-height: 1.5em;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
background: #000;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#pixel {
|
||||||
|
background: url('/cache-me.webp');
|
||||||
|
position: fixed;
|
||||||
|
left: -99999999999999px;
|
||||||
|
top: -20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 640px) {
|
||||||
|
body {
|
||||||
|
background-size: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#w h1 {
|
||||||
|
font-size: 40px;
|
||||||
|
}
|
||||||
|
}
|
22
tsconfig.json
Normal file
22
tsconfig.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["ESNext"],
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"allowJs": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"forceConsistentCasingInFileNames": true
|
||||||
|
}
|
||||||
|
}
|
21
views/index.ejs
Normal file
21
views/index.ejs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>
|
||||||
|
JAKE HURWITZ IS A BITCH
|
||||||
|
</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="stylesheet" href="/assets/css/styles.css?v1">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="w">
|
||||||
|
<h1>Welcome to <strong>Jake Hurwitz is a bitch</strong> .com</h1>
|
||||||
|
<p><%= visitCount %> People have seen this site and agreed Jake Hurwitz is a bitch</p>
|
||||||
|
<div id="pixel"><a href="https://youcantkillgod.com/">YOU CAN'T KILL GOD</a></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
Loading…
x
Reference in New Issue
Block a user