first commit

This commit is contained in:
Soph :3 2024-08-02 04:25:05 +03:00
commit 0ef48e85f0
Signed by: sophie
GPG key ID: EDA5D222A0C270F2
11 changed files with 580 additions and 0 deletions

106
src/index.ts Normal file
View file

@ -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<string | undefined | null | void>;
}
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);
}
}
}
}

98
src/plugins/dev.ts Normal file
View file

@ -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<number>[] = [];
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<number>({
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(
"<head>",
`<head><script>${script}</script>`
);
}
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;
}
}

View file

@ -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);
}
}

View file

@ -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<string, string> | 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);
}
}

View file

@ -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);
}
}
}

29
src/plugins/variables.ts Normal file
View file

@ -0,0 +1,29 @@
import { Plugin } from "..";
export default class Variables extends Plugin {
name = "variables";
rewriteTriggers = ["html", "*"];
renameTo = undefined;
longLasting = false;
varBuild!: () => Record<string, string>;
variables!: Record<string, string>;
constructor(varBuild: () => Record<string, string>) {
super();
this.varBuild = varBuild;
}
build() {
this.variables = this.varBuild();
}
async rewriteFile(file: string, filePath: string): Promise<string> {
let prevfile = file;
for (const a of Object.entries(this.variables)) {
prevfile = prevfile.replaceAll(a[0], a[1]);
}
return prevfile;
}
}