Ditch bun
This commit is contained in:
parent
1ee5a42a59
commit
f40ff817ef
1
.gitignore
vendored
1
.gitignore
vendored
@ -106,4 +106,5 @@ dist
|
||||
assets/css/
|
||||
assets/js/
|
||||
assets/webp/
|
||||
assets/images/webp/
|
||||
config/
|
39
Dockerfile
39
Dockerfile
@ -1,36 +1,21 @@
|
||||
FROM oven/bun:alpine AS baseimg
|
||||
FROM node:23-alpine AS base
|
||||
FROM base AS build-env
|
||||
|
||||
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 ./package*json ./
|
||||
RUN npm ci
|
||||
COPY . .
|
||||
RUN npm run build && \
|
||||
npm exec tsc && \
|
||||
npm ci --only=production --omit=dev
|
||||
|
||||
RUN bun run build.ts && \
|
||||
chown -R bun .
|
||||
FROM base AS deploy
|
||||
|
||||
WORKDIR /srv/bitch
|
||||
|
||||
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
|
||||
RUN apk add --no-cache docker-cli
|
||||
COPY --from=build-env /build .
|
||||
|
||||
EXPOSE 8080
|
||||
CMD [ "bun", "run", "index.ts"]
|
||||
CMD [ "node", "--experimental-strip-types", "src/index.ts"]
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 116 B After Width: | Height: | Size: 116 B |
171
build.ts
171
build.ts
@ -1,171 +0,0 @@
|
||||
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;
|
||||
}
|
||||
})();
|
||||
}
|
||||
})();
|
@ -10,4 +10,4 @@ services:
|
||||
ports:
|
||||
- 8080:8080
|
||||
volumes:
|
||||
- ./config:/usr/src/bitch/config
|
||||
- ./config:/srv/bitch/config
|
1654
package-lock.json
generated
Normal file
1654
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
19
package.json
19
package.json
@ -1,21 +1,18 @@
|
||||
{
|
||||
"name": "jakehurwitzisabitch",
|
||||
"module": "index.ts",
|
||||
"module": "src/index.ts",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"ejs": "3.1.9",
|
||||
"express": "4.18.2"
|
||||
"ejs": "3.1.10",
|
||||
"express": "4.21.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": {
|
||||
"forking-build-shit": "0.0.2",
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "npx build-shit",
|
||||
"watch": "npx build-shit --watch"
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import type { Express } from "express";
|
||||
import express, { application } from 'express';
|
||||
import Path from 'path';
|
||||
import fsp from 'fs/promises';
|
||||
|
||||
function notStupidParseInt(v: string | undefined): number {
|
||||
return v === undefined ? NaN : parseInt(v);
|
||||
@ -13,9 +14,9 @@ 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'));
|
||||
app.use('/assets', express.static('assets', { maxAge: '12m' }));
|
||||
|
||||
let visitCount = (await Bun.file(dataPath).json())['visits'] as number || 0;
|
||||
let visitCount = JSON.parse((await fsp.readFile(dataPath)).toString())['visits'] as number || 0;
|
||||
|
||||
app.get('/healthcheck', (req, res) => {
|
||||
res.send('Healthy');
|
||||
@ -41,7 +42,7 @@ app.get('/', (req, res) => {
|
||||
const sendPixel = (_: express.Request, res: express.Response) => {
|
||||
res.set('Cache-Control', 'private, max-age=31557600'); // one year
|
||||
visitCount++;
|
||||
res.sendFile(Path.join(import.meta.dir, 'assets', 'webp', 'cache-me.webp'));
|
||||
res.sendFile(Path.join(import.meta.dirname, 'assets', 'images', 'webp', 'cache-me.webp'));
|
||||
}
|
||||
|
||||
app.get('/cache-me.webp', sendPixel);
|
||||
@ -55,7 +56,7 @@ app.all('*', (req, res) => {
|
||||
const webserver = app.listen(port, () => console.log(`jakehurwitzisabitch.com running on port ${port}`));
|
||||
|
||||
async function saveVisits() {
|
||||
await Bun.write(dataPath, JSON.stringify({
|
||||
await fsp.writeFile(dataPath, JSON.stringify({
|
||||
visits: visitCount
|
||||
}));
|
||||
}
|
@ -13,7 +13,7 @@ body {
|
||||
}
|
||||
|
||||
body {
|
||||
background: url('/assets/webp/bitch.webp');
|
||||
background: url('/assets/images/webp/bitch.webp');
|
||||
}
|
||||
|
||||
#w {
|
||||
|
@ -1,22 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["ESNext"],
|
||||
"module": "NodeNext",
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
}
|
||||
}
|
||||
"moduleResolution": "node16",
|
||||
"noEmit": true,
|
||||
"allowImportingTsExtensions": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["**/*.spec.ts"]
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user