@lexho111/plainblog 0.7.2 → 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,135 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import crypto from "crypto";
4
+ import { Worker } from "worker_threads";
5
+ import { fileURLToPath } from "url";
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+ // Resolve package root from modules/csscompiler/
10
+ const packageRoot = path.resolve(__dirname, "../../");
11
+
12
+ // css sources
13
+ // in cascadining order
14
+ // 1. src/styles.css // the base
15
+ const defaultBaseStylePath = path.join(packageRoot, "src", "styles.css");
16
+
17
+ const defaultOutputDir = path.join(packageRoot, "public");
18
+ const defaultOutputPath = path.join(defaultOutputDir, "styles.min.css");
19
+ const defaultHashPath = path.join(packageRoot, "styles.hash");
20
+
21
+ function getFileContent(filePath) {
22
+ if (!filePath) return "";
23
+ if (Array.isArray(filePath)) {
24
+ return filePath.map((f) => getFileContent(f)).join("\n");
25
+ }
26
+ const fullPath = path.isAbsolute(filePath)
27
+ ? filePath
28
+ : path.join(packageRoot, filePath);
29
+ if (fs.existsSync(fullPath)) {
30
+ return fs.readFileSync(fullPath, "utf8");
31
+ }
32
+ console.warn(`[csscompiler] Warning: Stylesheet not found: ${fullPath}`);
33
+ return "";
34
+ }
35
+
36
+ function computeHash(content) {
37
+ return crypto.createHash("md5").update(content).digest("hex");
38
+ }
39
+
40
+ const locks = new Map();
41
+
42
+ export function checkSourceCSS(options = {}) {
43
+ const outputPath = options.outputPath || defaultOutputPath;
44
+
45
+ if (!locks.has(outputPath)) {
46
+ locks.set(outputPath, Promise.resolve());
47
+ }
48
+
49
+ const nextPromise = locks.get(outputPath).then(
50
+ () => doCheckSourceCSS(options),
51
+ () => doCheckSourceCSS(options),
52
+ );
53
+ locks.set(outputPath, nextPromise);
54
+
55
+ return nextPromise;
56
+ }
57
+
58
+ function doCheckSourceCSS({
59
+ baseStylePath = defaultBaseStylePath,
60
+ stylesheetPath = null,
61
+ style = "",
62
+ outputDir = defaultOutputDir,
63
+ outputPath = defaultOutputPath,
64
+ hashPath = defaultHashPath,
65
+ ...options
66
+ } = {}) {
67
+ return new Promise((resolve, reject) => {
68
+ // something changed?
69
+ const baseContent = getFileContent(baseStylePath);
70
+ const extraContent = getFileContent(stylesheetPath);
71
+ const inlineContent = options.inlineContent || style || "";
72
+
73
+ const combinedContent = JSON.stringify({
74
+ baseContent,
75
+ extraContent,
76
+ inlineContent,
77
+ });
78
+ const currentHash = computeHash(combinedContent);
79
+
80
+ let previousHash = "";
81
+ if (fs.existsSync(hashPath)) {
82
+ previousHash = fs.readFileSync(hashPath, "utf8");
83
+ }
84
+
85
+ if (currentHash === previousHash && fs.existsSync(outputPath)) {
86
+ // no --> keep old "public/styles.min.css"
87
+ //console.log("No changes detected.");
88
+ resolve(false);
89
+ } else {
90
+ // yes -> something changed
91
+ // run worker 'compile"
92
+ console.log("style assets have changed. recompiling...");
93
+
94
+ if (!fs.existsSync(outputDir)) {
95
+ fs.mkdirSync(outputDir, { recursive: true });
96
+ }
97
+
98
+ const worker = new Worker(
99
+ path.join(__dirname, "compile-style-worker.js"),
100
+ {
101
+ workerData: {
102
+ baseContent,
103
+ extraContent,
104
+ inlineContent,
105
+ outputPath,
106
+ ...options,
107
+ },
108
+ },
109
+ );
110
+
111
+ worker.on("message", (msg) => {
112
+ if (msg === "success") {
113
+ fs.writeFileSync(hashPath, currentHash);
114
+ console.log("compilation complete: public/styles.min.css created");
115
+ resolve(true);
116
+ } else if (msg && msg.error) {
117
+ console.error("Worker compilation failed:", msg.error);
118
+ reject(new Error(msg.error));
119
+ }
120
+ });
121
+
122
+ worker.on("error", (err) => {
123
+ console.error("Worker error:", err);
124
+ reject(err);
125
+ });
126
+ worker.on("exit", (code) => {
127
+ if (code !== 0) {
128
+ const error = new Error(`Worker stopped with exit code ${code}`);
129
+ console.error(error);
130
+ reject(error);
131
+ }
132
+ });
133
+ }
134
+ });
135
+ }
@@ -0,0 +1,34 @@
1
+ import { parentPort, workerData } from "worker_threads";
2
+ import { compileScripts } from "./jscompiler.js";
3
+ import { measure_perf } from "../../debug-loader.js";
4
+
5
+ async function run() {
6
+ if (!parentPort) {
7
+ return;
8
+ }
9
+ const { type, fileData } = workerData;
10
+ try {
11
+ const result = await measure_perf("compilerWorker", async () => {
12
+ if (type === "scripts") {
13
+ return await compileScripts(fileData);
14
+ } else {
15
+ throw new Error(`Unknown compilation type: ${type}`);
16
+ }
17
+ });
18
+ parentPort.postMessage({ status: "success", result });
19
+ } catch (error) {
20
+ parentPort.postMessage({ status: "error", error: error.message });
21
+ }
22
+ }
23
+
24
+ run();
25
+
26
+ process.on("uncaughtException", (err) => {
27
+ console.error("KRITISCHER FEHLER:", err.stack);
28
+ // Logge den Fehler in eine Datei oder einen Dienst
29
+ process.exit(1); // Kontrollierter Neustart durch PM2
30
+ });
31
+
32
+ process.on("unhandledRejection", (reason, promise) => {
33
+ console.error("UNBEHANDELTES PROMISE:", reason);
34
+ });
@@ -34,7 +34,9 @@ async function runBabelAndUglify(js) {
34
34
  presetEnv = presetModule.default || presetModule;
35
35
  } catch (error) {
36
36
  console.error("Build scripts failed. Missing dependencies.");
37
- console.error("Please run: npm install");
37
+ console.error(
38
+ "Please run: npm install @babel/core uglify-js @babel/preset-env",
39
+ );
38
40
  throw new Error("Missing optional dependencies");
39
41
  }
40
42
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lexho111/plainblog",
3
- "version": "0.7.2",
3
+ "version": "0.7.4",
4
4
  "description": "A tool for creating and serving a minimalist, single-page blog.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -12,7 +12,8 @@
12
12
  "test2": "node --experimental-vm-modules node_modules/jest/bin/jest.js --runInBand",
13
13
  "coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage & start coverage/lcov-report/index.html",
14
14
  "view-coverage": "start coverage/lcov-report/index.html",
15
- "lint": "eslint ."
15
+ "lint": "eslint .",
16
+ "tsc": "npx tsc"
16
17
  },
17
18
  "keywords": [
18
19
  "blog",
@@ -26,8 +27,6 @@
26
27
  "@babel/preset-env": "^7.28.6",
27
28
  "@eslint/js": "^9.39.2",
28
29
  "@types/node": "^25.0.3",
29
- "autoprefixer": "^10.4.23",
30
- "cssnano": "^7.1.2",
31
30
  "debug": "^4.4.3",
32
31
  "dependency-cruiser": "^17.3.7",
33
32
  "eslint": "^9.39.2",
@@ -37,7 +36,6 @@
37
36
  "jest": "^29.7.0",
38
37
  "pg": "^8.16.3",
39
38
  "pg-hstore": "^2.3.4",
40
- "postcss": "^8.5.6",
41
39
  "sequelize": "^6.37.7",
42
40
  "sqlite3": "^5.0.2",
43
41
  "typescript": "^5.9.3",
@@ -46,7 +44,6 @@
46
44
  },
47
45
  "dependencies": {
48
46
  "async-lock": "^1.4.1",
49
- "better-sqlite3": "^12.6.2",
50
- "child_process": "^1.0.2"
47
+ "better-sqlite3": "^12.6.2"
51
48
  }
52
49
  }
@@ -1,2 +1,8 @@
1
- :root{--black:#111;--clearwhite:#fefefe;--white:#eee;--darkgray:rgba(54,54,54,.5);--text-primary:var(--black);--text-secondary:var(--clearwhite);--max-width:600px}body,html{margin:0;overflow-x:hidden}html{font-size:clamp(.85rem,1vw + .5rem,1.1rem)}body{background:var(--white_darker);color:var(--text-primary);font-family:Arial}nav{align-items:center;background:#ebebeb;box-sizing:border-box;display:flex;margin-top:10px;max-width:var(--max-width);padding:5px}#header{max-width:var(--max-width)}button,form input,form textarea{box-sizing:border-box;padding:5px 10px}button{padding:10px;width:-moz-fit-content;width:fit-content}img{max-width:100%}input,textarea{border:1px solid #ababab}input,input:focus,textarea,textarea:focus{box-shadow:6px 6px 1px 1px rgba(50,50,50,.2)}input:focus,textarea:focus{background-color:var(--clearwhite);border:2px solid #3b40c1;outline:none}.form_element{display:block;font-family:monospace;font-size:103%;margin-bottom:10px;padding:4px}.wide{width:100%}.password{width:250px}.new_title{height:35px;padding:10px}.new_content{height:300px;padding:10px;resize:none}#createNew{max-width:var(--max-width);width:100%}#search{border:1px solid var(--text-primary);box-shadow:none;font-size:1rem;margin-left:auto;padding:.4rem 1rem}hr{margin:40px 0}.articles,hr{max-width:var(--max-width)}.articles{border:0 solid #000;display:grid;gap:.25rem;grid-template-columns:1fr}.articles article{border:2px solid #a9a9a9;border-radius:4px;margin-bottom:10px;min-width:0;overflow-wrap:break-word;padding:.4rem}.articles article h2{color:#353535;margin-bottom:5px}.articles article .datetime{color:#757575;margin:0}.articles article p{margin-bottom:0;margin-top:10px}article a,article a:visited,h1{color:#696969}h2{border:0 solid #000;margin-top:0}nav a{color:#3b40c1;font-size:20px;text-decoration:underline}nav a:visited{color:#3b40c1;text-decoration-color:#3b40c1}.loginform{margin-left:25px}#wrapper{margin-left:0;max-width:1200px;padding:0 20px}#wrapper,.buttons{box-sizing:border-box;width:100%}.buttons{align-items:center;border:0 solid #000;display:flex;gap:5px;height:25px;list-style:none;margin:0 0 16px;padding:15px 15px 15px 0}.btn{border:none;border:1px solid var(--text-primary);border-radius:0;box-shadow:3px 2px 2px var(--darkgray);color:var(--text-primary);cursor:pointer;font-size:1rem;font-weight:500;padding:.4rem 1rem;text-decoration:none;width:-moz-fit-content;width:fit-content}.btn:hover{background-color:#fff;border:2px solid var(--black);color:#000}.light{background:var(--clearwhite);color:var(--black)}.light:hover{background:var(--black);color:var(--clearwhite)}.login{font-weight:600}.hide-image{display:none}.edit{background-color:blue}.delete,.edit{color:var(--clearwhite)}.delete{background-color:red}@media screen and (min-width:1000px){#wrapper,body{font-size:.75rem;margin:0 auto;max-width:1400px;padding:0 40px;width:90%}.articles article p,.form_element{font-size:105%}}
2
- /* source-hash: 322c19466bcee8ad0e0b817e72b3cc495e1ebbf84398d7c346ab1fc8ec65f0b9 */
1
+ :root{--black:#111;--clearwhite:#fefefe;--white:#eeeeee;--darkgray:rgba(54, 54, 54, 0.5);--text-primary:var(--black);--text-secondary:var(--clearwhite);--max-width:600px}html,
2
+ body{margin:0;overflow-x:hidden}html{font-size:clamp(0.85rem, 1vw + 0.5rem, 1.1rem)}body{color:var(--text-primary);background:var(--white_darker);font-family:Arial}nav{margin-top:10px;display:flex;align-items:center;max-width:var(--max-width);background:hsl(0deg 0% 92.27%);padding:5px;box-sizing:border-box}nav,
3
+ #header h1{}#header{max-width:var(--max-width)}form input,
4
+ form textarea,
5
+ button{padding:5px 10px;box-sizing:border-box}button{width:fit-content;padding:10px}img{max-width:100%}input,
6
+ textarea{box-shadow:6px 6px 1px 1px rgb(50 50 50 / 0.2);border:1px solid #ababab}input:focus,
7
+ textarea:focus{outline:none;border:2px solid #3b40c1;box-shadow:6px 6px 1px 1px rgb(50 50 50 / 0.2);background-color:var(--clearwhite)}.form_element{display:block;margin-bottom:10px;padding:4px;font-family:monospace;font-size:103%}.wide{width:100%}.password{width:250px}.new_title{padding:10px;height:35px}.new_content{height:300px;resize:none;padding:10px}#createNew{width:100%;max-width:var(--max-width)}#search{margin-left:auto;font-size:1rem;padding:0.4rem 1rem;border:1px solid var(--text-primary);box-shadow:none}hr{max-width:var(--max-width);margin-left:0;margin:40px 0}.articles{border:0 solid #000;display:grid;gap:0.25rem;grid-template-columns:1fr;max-width:var(--max-width)}.articles article{border:0 solid #ccc;border-radius:4px;min-width:0;overflow-wrap:break-word;padding:0.4rem;margin-bottom:10px;border:2px solid darkgray}.articles article h2{color:rgb(53, 53, 53);margin-bottom:5px}.articles article .datetime{margin:0;color:rgb(117, 117, 117)}.articles article p{margin-top:10px;margin-bottom:0}article a{color:rgb(105, 105, 105)}article a:visited{color:rgb(105, 105, 105)}h1{color:#696969}h2{margin-top:0;border:0px solid black}nav a{color:#3b40c1;font-size:20px;text-decoration:underline}nav a:visited{color:#3b40c1;text-decoration-color:#3b40c1}.loginform{margin-left:25px}#wrapper{max-width:1200px;padding:0 20px;width:100%;margin-left:0;box-sizing:border-box}.buttons{box-sizing:border-box;display:flex;align-items:center;gap:5px;list-style:none;margin:0;margin-bottom:16px;padding:15px;padding-left:0px;border:0px solid black;width:100%;height:25px}.box{}.btn{border:none;color:var(--text-primary);font-size:1rem;padding:0.4rem 1rem;font-weight:500;text-decoration:none;cursor:pointer;border-radius:0px;width:fit-content;border:1px solid var(--text-primary);box-shadow:3px 2px 2px var(--darkgray)}.btn:hover{color:black;background-color:white;border:2px solid var(--black)}.light{color:var(--black);background:var(--clearwhite)}.light:hover{color:var(--clearwhite);background:var(--black)}.login{font-weight:600}.hide-image{display:none}.edit{color:var(--clearwhite);background-color:blue}.delete{color:var(--clearwhite);background-color:red}@media screen and (min-width:1000px){#wrapper,
8
+ body{width:90%;max-width:1400px;padding:0 40px;margin:0 auto;font-size:0.75rem}.form_element{font-size:105%}.articles article p{font-size:105%}}
package/router.js CHANGED
@@ -9,112 +9,108 @@ const debug = createDebug("plainblog:api");
9
9
  const staticCache = new Map();
10
10
 
11
11
  // API routes
12
- export async function login(req, res, cb, cb2) {
12
+ export function login(req, res, cb, cb2) {
13
13
  // workaround for angular frontend
14
14
  if (
15
15
  (req.url === "/api/login" || req.url === "/login") &&
16
16
  req.method === "POST"
17
17
  ) {
18
18
  debug(`${req.method} ${req.url}`);
19
- await cb(req, res);
20
- return;
19
+ return cb(req, res);
21
20
  }
22
21
  if (req.method === "GET" && req.url === "/login") {
23
22
  debug(`${req.method} ${req.url}`);
24
23
  res.writeHead(200, { "Content-Type": "text/html" });
25
- await cb2(req, res);
26
- return;
24
+ return cb2(req, res);
27
25
  }
28
26
  }
29
27
 
30
- export async function logout(req, res, cb) {
28
+ export function logout(req, res, cb) {
31
29
  if (req.method === "POST") {
32
30
  if (req.url === "/logout" || req.url === "/api/logout") {
33
31
  debug(`${req.method} ${req.url}`);
34
- await cb(req, res);
35
- return;
32
+ return cb(req, res);
36
33
  }
37
34
  }
38
35
  }
39
36
 
40
- export async function api(req, res, cb) {
37
+ export function api(req, res, cb) {
41
38
  if (req.url.startsWith("/api")) {
42
39
  debug(`${req.method} ${req.url}`);
43
- await cb(req, res);
44
- return;
40
+ return cb(req, res);
45
41
  }
46
42
  }
47
43
 
48
- export async function new1(req, res, cb) {
44
+ export function new1(req, res, cb) {
49
45
  if (req.method === "POST" && req.url === "/new") {
50
46
  const time_start = performance.now();
51
47
  debug(`${req.method} ${req.url}`);
52
- await cb(req, res);
53
- const time_end = performance.now();
54
- const duration = time_end - time_start;
55
- debug_perf('"/new" took ' + duration + "ms", duration);
56
- return;
48
+ return cb(req, res).then(() => {
49
+ const time_end = performance.now();
50
+ const duration = time_end - time_start;
51
+ debug_perf('"/new"', duration);
52
+ });
57
53
  }
58
54
  }
59
55
 
60
- export async function handleLogin(req, res, cb) {
56
+ export function handleLogin(req, res, cb) {
61
57
  if (req.method === "POST") {
62
58
  debug(`${req.method} ${req.url}`);
63
- await cb(req, res);
64
- return;
59
+ return cb(req, res);
65
60
  }
66
61
  }
67
62
 
68
- export async function getPages(req, res, cb, publicDir) {
63
+ export function getPages(req, res, cb, publicDir) {
69
64
  if (req.method === "GET") {
70
- const getRoot_start = performance.now();
71
- if (req.url === "/") {
72
- debug(`${req.method} ${req.url}`);
73
- await cb(req, res);
74
- const getRoot_end = performance.now();
75
- const duration = getRoot_end - getRoot_start;
76
- debug_perf('GET "/" took ' + duration + "ms", duration);
77
- return;
78
- }
79
- // Try to serve static files from public folder
80
- // Normalize path to prevent directory traversal attacks
81
- const safePath = path.normalize(req.url).replace(/^(\.\.[\/\\])+/, "");
82
- const filePath = path.join(
83
- publicDir,
84
- safePath === "/" ? "index.html" : safePath,
85
- );
86
-
87
- //debug("%s", filePath);
88
- try {
89
- let data;
90
- if (staticCache.has(filePath)) {
91
- data = staticCache.get(filePath);
92
- } else {
93
- data = await readFile(filePath);
94
- staticCache.set(filePath, data);
65
+ return (async () => {
66
+ const getRoot_start = performance.now();
67
+ if (req.url === "/") {
68
+ debug(`${req.method} ${req.url}`);
69
+ await cb(req, res);
70
+ const getRoot_end = performance.now();
71
+ const duration = getRoot_end - getRoot_start;
72
+ debug_perf('GET "/"', duration);
73
+ return;
95
74
  }
75
+ // Try to serve static files from public folder
76
+ // Normalize path to prevent directory traversal attacks
77
+ const safePath = path.normalize(req.url).replace(/^(\.\.[\/\\])+/, "");
78
+ const filePath = path.join(
79
+ publicDir,
80
+ safePath === "/" ? "index.html" : safePath,
81
+ );
96
82
 
97
- // Manual MIME type detection (simplified)
98
- const ext = path.extname(filePath);
99
- const mimeTypes = {
100
- ".html": "text/html",
101
- ".css": "text/css",
102
- ".js": "text/javascript",
103
- };
83
+ //debug("%s", filePath);
84
+ try {
85
+ let data;
86
+ if (staticCache.has(filePath)) {
87
+ data = staticCache.get(filePath);
88
+ } else {
89
+ data = await readFile(filePath);
90
+ staticCache.set(filePath, data);
91
+ }
92
+
93
+ // Manual MIME type detection (simplified)
94
+ const ext = path.extname(filePath);
95
+ const mimeTypes = {
96
+ ".html": "text/html",
97
+ ".css": "text/css",
98
+ ".js": "text/javascript",
99
+ };
104
100
 
105
- res.writeHead(200, {
106
- "Content-Type": mimeTypes[ext] || "application/octet-stream",
107
- });
108
- res.end(data);
109
- } catch (err) {
110
- if (err) {
111
- if (!res.headersSent) {
112
- res.writeHead(404);
113
- return res.end("File Not Found");
101
+ res.writeHead(200, {
102
+ "Content-Type": mimeTypes[ext] || "application/octet-stream",
103
+ });
104
+ res.end(data);
105
+ } catch (err) {
106
+ if (err) {
107
+ if (!res.headersSent) {
108
+ res.writeHead(404);
109
+ return res.end("File Not Found");
110
+ }
114
111
  }
115
112
  }
116
- }
117
- return;
113
+ })();
118
114
  }
119
115
  }
120
116
  // ---------------------------------------------