From 0ef48e85f02b80c8a0d57825de17f3703f57451d Mon Sep 17 00:00:00 2001 From: sophie Date: Fri, 2 Aug 2024 04:25:05 +0300 Subject: [PATCH] first commit --- .gitignore | 175 +++++++++++++++++++++++++++++++ README.md | 13 +++ bun.lockb | Bin 0 -> 14344 bytes package.json | 19 ++++ src/index.ts | 106 +++++++++++++++++++ src/plugins/dev.ts | 98 +++++++++++++++++ src/plugins/markdown-compiler.ts | 24 +++++ src/plugins/markdown-metadata.ts | 31 ++++++ src/plugins/ts-compiler.ts | 58 ++++++++++ src/plugins/variables.ts | 29 +++++ tsconfig.json | 27 +++++ 11 files changed, 580 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100755 bun.lockb create mode 100644 package.json create mode 100644 src/index.ts create mode 100644 src/plugins/dev.ts create mode 100644 src/plugins/markdown-compiler.ts create mode 100644 src/plugins/markdown-metadata.ts create mode 100644 src/plugins/ts-compiler.ts create mode 100644 src/plugins/variables.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9b1ee42 --- /dev/null +++ b/.gitignore @@ -0,0 +1,175 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# 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/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# 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 variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# 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 + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..e9e30fc --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# (S)ophie's (S)erver (S)ite (G)enerator + +## Usage +`bun add git+https://git.sad.ovh/sophie/sssg` + + +## design goals: +1. rewrite and generate custom HTML/TS/JS/whatever.. +2. allow for variables and other important features in buildsystem->html +3. plugins, this ties together the top two +4. HMR and HTML/CSS reloading (in the Dev plugin) +5. Rewriteable file renaming +6. Every plugin runs on the same set of files, and can rename and reinject multiple times diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..5a20e55e44958c407f418170038fab02287f789e GIT binary patch literal 14344 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 AF8}}l literal 0 HcmV?d00001 diff --git a/package.json b/package.json new file mode 100644 index 0000000..9833157 --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "sssg", + "module": "index.ts", + "type": "module", + "main": "src/index.ts", + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "dependencies": { + "@types/mime-types": "^2.1.4", + "esbuild": "^0.23.0", + "marked": "^13.0.3", + "mime-types": "^2.1.35", + "preact": "^10.23.1" + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..7bed27e --- /dev/null +++ b/src/index.ts @@ -0,0 +1,106 @@ +export abstract class Plugin { + abstract name: string; + abstract rewriteTriggers: string[]; + renameTo?: string; + abstract longLasting: boolean; + abstract build?(): void; + + abstract rewriteFile( + file: string, + filePath: string + ): Promise; +} + +import * as fs from "fs"; +import * as path from "path"; + +export default class SSSG { + plugins: Plugin[] = []; + outputFolder!: string; + inputFolder!: string; + constructor({ + outputFolder, + inputFolder, + }: { + outputFolder: string; + inputFolder: string; + }) { + this.inputFolder = inputFolder; + this.outputFolder = outputFolder; + } + + async run({ plugins }: { plugins: Plugin[] }) { + this.plugins = plugins; + if (!fs.existsSync(this.outputFolder)) fs.mkdirSync(this.outputFolder); + } + + async build() { + console.log("build triggered") + for(const plugin of this.plugins) { + if(plugin.build) { + plugin.build(); + } + } + try { + fs.rmSync(this.outputFolder, {recursive: true}); + } catch {} + + const sourceFiles = fs.readdirSync(this.inputFolder, { + recursive: true, + withFileTypes: true, + }); + const globalPlugins = this.plugins.filter((z) => + z.rewriteTriggers.includes("*") + ); + + for await (const file of sourceFiles) { + if (!file.isFile()) continue; + + const type = file.name.split(".").at(-1); + if (!type) continue; + + const shortname = file.name.slice( + 0, + file.name.length - (type.length + 1) + ); + const availablePlugins = this.plugins.filter((z) => + z.rewriteTriggers.includes(type) + ); + console.log(availablePlugins) + + if (availablePlugins.length == 0) { + const oldPath = path.join(file.parentPath, file.name); + fs.cpSync( + oldPath, + oldPath.replace(this.inputFolder, this.outputFolder) + ); + } + + for await (const plugin of availablePlugins) { + const oldPath = path.join(file.parentPath, file.name); + const newPath = path + .join( + file.parentPath, + shortname + + "." + + (plugin.rewriteTriggers.includes("*") ? type : plugin.renameTo) + ) + .replace(this.inputFolder, this.outputFolder); + let data = fs.readFileSync(oldPath).toString("utf8"); + + for await (const globalPlugin of globalPlugins) { + const rewritten = await globalPlugin.rewriteFile(data, oldPath); + if (!rewritten) continue; + data = rewritten; + } + + let rewrite = await plugin.rewriteFile(data, oldPath); + + if (!rewrite) continue; + + fs.mkdirSync(path.dirname(newPath), { recursive: true }); + fs.writeFileSync(newPath, rewrite); + } + } + } +} diff --git a/src/plugins/dev.ts b/src/plugins/dev.ts new file mode 100644 index 0000000..f027aba --- /dev/null +++ b/src/plugins/dev.ts @@ -0,0 +1,98 @@ +import type { Server, ServerWebSocket } from "bun"; +import SSSG, { Plugin } from ".."; +import * as fs from "fs"; +import mime from "mime-types"; + +const script = `let reconnectTimeout;function connect(){console.log("[--dev] connecting to dev server");let ws=new WebSocket("ws://localhost:8080");ws.addEventListener("message",message=>{if(message.data=="refresh"){location.reload()}});ws.addEventListener("open",()=>{console.log("[--dev] connected")});ws.addEventListener("close",()=>{console.log("[--dev] socket closed, restarting in 1s");clearTimeout(reconnectTimeout);reconnectTimeout=setTimeout(()=>{connect()},1e3)})}window.addEventListener("load",()=>connect());`; + +export default class DevPlugin extends Plugin { + build: undefined; + + name = "dev"; + rewriteTriggers = []; + renameTo = undefined; + longLasting = true; + server!: Server; + allConnections: ServerWebSocket[] = []; + + constructor(sssg: SSSG) { + super(); + if (!process.argv.includes("--dev")) return; + + fs.watch( + sssg.inputFolder, + { + recursive: true, + }, + async (e, f) => { + console.log("[dev] Noticed update in " + f + ", of type " + e + "."); + this.allConnections.forEach((z) => z.send("refresh")); + await sssg.build(); + } + ); + + this.server = Bun.serve({ + fetch(req, server) { + const success = server.upgrade(req); + if (success) { + return undefined; + } + + const url = new URL(req.url); + + let cleanedPath = url.pathname; + if (cleanedPath == "/") cleanedPath = "/index.html"; + if (cleanedPath.endsWith("/")) cleanedPath = cleanedPath.slice(0, -1); + + let fsPath = sssg.outputFolder + cleanedPath; + + if (fsPath.match(/\.\.\//g) !== null) { + return undefined; + } + let rawFile; + try { + rawFile = fs.readFileSync(fsPath); + } catch { + return new Response("404 Not Found", { + status: 404, + }); + } + const type = fsPath.split(".").at(-1); + if (!type) return; + if (type == "html") { + + rawFile = rawFile.toString().replace( + "", + `` + ); + } + return new Response(rawFile, { + headers: { + "Cache-Control": "no-cache, no-store, must-revalidate", + "Pragma": "no-cache", + "Expires": "0", + "Content-Type": (mime.lookup(type) || "application/octet-stream") + "; charset=utf-8", + }, + }); + }, + websocket: { + open: (ws) => { + ws.data = Math.random(); + + this.allConnections.push(ws); + }, + message(ws, message) {}, + close: (ws) => { + this.allConnections = this.allConnections.filter( + (z) => z.data != ws.data + ); + }, + }, + port: 8080, + }); + } + + async rewriteFile(file: string, filePath: string) { + return undefined; + } +} diff --git a/src/plugins/markdown-compiler.ts b/src/plugins/markdown-compiler.ts new file mode 100644 index 0000000..63bce9d --- /dev/null +++ b/src/plugins/markdown-compiler.ts @@ -0,0 +1,24 @@ +import { Plugin } from ".."; +import { marked } from "marked"; +import { parseMetadata } from "./markdown-metadata"; + +export default class MarkdownCompiler extends Plugin { + build: undefined; + + name = "markdown-compiler"; + rewriteTriggers = ["md"] + renameTo = "html" + longLasting = false; + + async rewriteFile(file: string, filePath: string) { + let text = file; + const metadata = parseMetadata(text); + if(metadata) { + let textSplit = text.split('\n'); + textSplit.splice(0, Object.keys(metadata).length); + textSplit.unshift(metadata.title) + text = textSplit.join("\n"); + } + return await marked.parse(text); + } +} \ No newline at end of file diff --git a/src/plugins/markdown-metadata.ts b/src/plugins/markdown-metadata.ts new file mode 100644 index 0000000..655246f --- /dev/null +++ b/src/plugins/markdown-metadata.ts @@ -0,0 +1,31 @@ +import { Plugin } from ".."; + +export function parseMetadata(file: string) { + if (!/^=+$/gm.test(file)) return; + const splitfile = file.split("\n"); + let properties: Record | undefined; + for (let i = 0; i < splitfile.length; i++) { + if (!properties) properties = {}; + const line = splitfile[i]; + if (/^=+$/gm.test(line)) break; + const parts = line.split("="); + if (parts.length !== 2) break; + properties[parts[0].trim()] = parts[1].trim(); + } + return properties; +} + +export default class MarkdownMetadataGenerator extends Plugin { + build: undefined; + + name = "markdown-metadata"; + rewriteTriggers = ["md"]; + renameTo = "json"; + longLasting = false; + + async rewriteFile(file: string, filePath: string) { + const metadata = parseMetadata(file); + if (!metadata) return; + return JSON.stringify(metadata); + } +} diff --git a/src/plugins/ts-compiler.ts b/src/plugins/ts-compiler.ts new file mode 100644 index 0000000..fa62fdf --- /dev/null +++ b/src/plugins/ts-compiler.ts @@ -0,0 +1,58 @@ +import { Plugin } from ".."; +import * as fs from "fs"; +import * as esbuild from "esbuild"; + +export default class TSCompiler extends Plugin { + build: undefined; + + name = "ts-compiler"; + rewriteTriggers = ["ts", "tsx", "jsx"]; + renameTo = "js"; + longLasting = false; + + minify = false; + constructor() { + super(); + if (process.argv.includes("--prod")) { + this.minify = true; + } + } + async rewriteFile(file: string, filePath: string) { + let result; + try { + result = await esbuild.build({ + stdin: { + contents: file, + resolveDir: filePath.split("/")?.slice(0, -1).join("/"), + sourcefile: filePath.split("/").at(-1), + loader: filePath.split("/").at(-1)?.split(".").at(-1) as + | "ts" + | "tsx" + | "jsx", + }, + jsxFragment: "Fragment", + jsxFactory: "h", + jsxImportSource: "preact", + jsx: "transform", + write: false, + bundle: true, + outdir: "out", + minify: this.minify, + }); + } catch (e) { + console.error(e); + console.log("Errored!"); + return; + } + + if (result.errors.length != 0) { + console.log("TS compiler errored."); + result.errors.forEach((element) => { + console.error(element); + }); + } else { + const output = result.outputFiles[0].contents; + return new TextDecoder().decode(output); + } + } +} diff --git a/src/plugins/variables.ts b/src/plugins/variables.ts new file mode 100644 index 0000000..f3f8334 --- /dev/null +++ b/src/plugins/variables.ts @@ -0,0 +1,29 @@ +import { Plugin } from ".."; + +export default class Variables extends Plugin { + + name = "variables"; + rewriteTriggers = ["html", "*"]; + renameTo = undefined; + longLasting = false; + + varBuild!: () => Record; + variables!: Record; + + constructor(varBuild: () => Record) { + super(); + this.varBuild = varBuild; + } + + build() { + this.variables = this.varBuild(); + } + + async rewriteFile(file: string, filePath: string): Promise { + let prevfile = file; + for (const a of Object.entries(this.variables)) { + prevfile = prevfile.replaceAll(a[0], a[1]); + } + return prevfile; + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..238655f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +}