add all of the testing
This commit is contained in:
parent
d5b4341487
commit
91f39a80a7
12 changed files with 485 additions and 69 deletions
17
.forgejo/workflows/test.yaml
Normal file
17
.forgejo/workflows/test.yaml
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
on: [push]
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [windows-node-iron, node-16]
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- uses: http://github.com/actions/checkout@v4
|
||||||
|
- uses: http://github.com/oven-sh/setup-bun@v2
|
||||||
|
- run: bun install
|
||||||
|
shell: ${{ matrix.os == 'windows-node-iron' && 'powershell' || 'bash' }}
|
||||||
|
- run: bun run prod
|
||||||
|
shell: ${{ matrix.os == 'windows-node-iron' && 'powershell' || 'bash' }}
|
||||||
|
- name: run tests
|
||||||
|
run: bun test
|
||||||
|
shell: ${{ matrix.os == 'windows-node-iron' && 'powershell' || 'bash' }}
|
||||||
66
src/__tests__/index.test.ts
Normal file
66
src/__tests__/index.test.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { expect, test, beforeAll, afterAll } from "bun:test";
|
||||||
|
import SSSG, { Plugin } from "../index";
|
||||||
|
import { mkdirSync, existsSync, rmSync } from "fs";
|
||||||
|
import { before } from "node:test";
|
||||||
|
|
||||||
|
// Dummy plugin for basic testing
|
||||||
|
class DummyPlugin extends Plugin {
|
||||||
|
name = "dummy";
|
||||||
|
rewriteTriggers = ["txt"];
|
||||||
|
longLasting = false;
|
||||||
|
async rewriteFile(file: string) {
|
||||||
|
return file.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
async build() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure "input" folder exists before tests, and clean up after
|
||||||
|
const INPUT_FOLDER = "input";
|
||||||
|
const OUTPUT_FOLDER = "output";
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
if (!existsSync(INPUT_FOLDER)) {
|
||||||
|
mkdirSync(INPUT_FOLDER);
|
||||||
|
}
|
||||||
|
if (!existsSync(OUTPUT_FOLDER)) {
|
||||||
|
mkdirSync(OUTPUT_FOLDER);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
if (existsSync(INPUT_FOLDER)) {
|
||||||
|
rmSync(INPUT_FOLDER, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
if (existsSync(OUTPUT_FOLDER)) {
|
||||||
|
rmSync(OUTPUT_FOLDER, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("SSSG initializes with input/output folders", () => {
|
||||||
|
const sssg = new SSSG({ inputFolder: INPUT_FOLDER, outputFolder: OUTPUT_FOLDER });
|
||||||
|
expect(sssg.inputFolder).toBe(INPUT_FOLDER);
|
||||||
|
expect(sssg.outputFolder).toBe(OUTPUT_FOLDER);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("SSSG run sets plugins", async () => {
|
||||||
|
const sssg = new SSSG({ inputFolder: INPUT_FOLDER, outputFolder: OUTPUT_FOLDER });
|
||||||
|
await sssg.run({ plugins: [new DummyPlugin()] });
|
||||||
|
expect(sssg.plugins.length).toBe(1);
|
||||||
|
expect(sssg.plugins[0].name).toBe("dummy");
|
||||||
|
});
|
||||||
|
|
||||||
|
// More advanced build tests would require mocking fs and path, which Bun supports via bun:mock or manual stubbing.
|
||||||
|
// For now, we check plugin build invocation.
|
||||||
|
test("Plugin build is called", async () => {
|
||||||
|
let called = false;
|
||||||
|
class BuildPlugin extends DummyPlugin {
|
||||||
|
async build() { called = true; }
|
||||||
|
}
|
||||||
|
const sssg = new SSSG({ inputFolder: INPUT_FOLDER, outputFolder: OUTPUT_FOLDER });
|
||||||
|
await sssg.run({ plugins: [new BuildPlugin()] });
|
||||||
|
await sssg.build();
|
||||||
|
expect(called).toBe(true);
|
||||||
|
});
|
||||||
24
src/plugins/__tests__/compile-time-js.test.ts
Normal file
24
src/plugins/__tests__/compile-time-js.test.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { expect, test } from "bun:test";
|
||||||
|
import CompileTimeJS from "../compile-time-js";
|
||||||
|
|
||||||
|
test("CompileTimeJS evaluates JS expressions in curly braces", async () => {
|
||||||
|
const plugin = new CompileTimeJS();
|
||||||
|
const input = "Hello {& 2+2 &} World";
|
||||||
|
const output = await plugin.rewriteFile(input, "test.html");
|
||||||
|
expect(output).toBe("Hello 4 World");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("CompileTimeJS handles multiple expressions", async () => {
|
||||||
|
const plugin = new CompileTimeJS();
|
||||||
|
const input = "{& 1+1 &} and {& 3*3 &}";
|
||||||
|
console.log(input)
|
||||||
|
const output = await plugin.rewriteFile(input, "test.html");
|
||||||
|
expect(output).toBe("2 and 9");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("CompileTimeJS leaves input unchanged if no expressions", async () => {
|
||||||
|
const plugin = new CompileTimeJS();
|
||||||
|
const input = "No expressions here.";
|
||||||
|
const output = await plugin.rewriteFile(input, "test.html");
|
||||||
|
expect(output).toBe("No expressions here.");
|
||||||
|
});
|
||||||
7
src/plugins/__tests__/dev.test.ts
Normal file
7
src/plugins/__tests__/dev.test.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { expect, test } from "bun:test";
|
||||||
|
import DevPlugin from "../dev";
|
||||||
|
import SSSG from "../../index";
|
||||||
|
|
||||||
|
|
||||||
|
// DevPlugin is too hard to test yet, this will take some time to finish.
|
||||||
|
//
|
||||||
87
src/plugins/__tests__/image-optimization.test.ts
Normal file
87
src/plugins/__tests__/image-optimization.test.ts
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
import { expect, test } from "bun:test";
|
||||||
|
import ImageOptimization from "../image-optimization";
|
||||||
|
|
||||||
|
// Minimal valid image buffers for each format
|
||||||
|
function loadSampleImage(ext: string): Buffer {
|
||||||
|
switch (ext) {
|
||||||
|
case "png":
|
||||||
|
// 1x1 transparent PNG
|
||||||
|
return Buffer.from([
|
||||||
|
0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,0x0A,
|
||||||
|
0x00,0x00,0x00,0x0D,0x49,0x48,0x44,0x52,
|
||||||
|
0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,
|
||||||
|
0x08,0x06,0x00,0x00,0x00,0x1F,0x15,0xC4,
|
||||||
|
0x89,0x00,0x00,0x00,0x0A,0x49,0x44,0x41,
|
||||||
|
0x54,0x78,0x9C,0x63,0x00,0x01,0x00,0x00,
|
||||||
|
0x05,0x00,0x01,0x0D,0x0A,0x2D,0xB4,0x00,
|
||||||
|
0x00,0x00,0x00,0x49,0x45,0x4E,0x44,0xAE,
|
||||||
|
0x42,0x60,0x82
|
||||||
|
]);
|
||||||
|
case "jpg":
|
||||||
|
case "jpeg":
|
||||||
|
// 1x1 JPEG (valid minimal JPEG header)
|
||||||
|
return Buffer.from([
|
||||||
|
0xff,0xd8,0xff,0xe0,0x00,0x10,0x4a,0x46,0x49,0x46,0x00,0x01,0x01,0x01,0x00,0x48,0x00,0x48,0x00,0x00,0xff,0xdb,0x00,0x43,0x00,0x03,0x02,0x02,0x02,0x02,0x02,0x03,0x02,0x02,0x02,0x03,0x03,0x03,0x03,0x04,0x06,0x04,0x04,0x04,0x04,0x04,0x08,0x06,0x06,0x05,0x06,0x09,0x08,0x0a,0x0a,0x09,0x08,0x09,0x09,0x0a,0x0c,0x0f,0x0c,0x0a,0x0b,0x0e,0x0b,0x09,0x09,0x0d,0x11,0x0d,0x0e,0x0f,0x10,0x10,0x11,0x10,0x0a,0x0c,0x12,0x13,0x12,0x10,0x13,0x0f,0x10,0x10,0x10,0xff,0xc9,0x00,0x0b,0x08,0x00,0x01,0x00,0x01,0x01,0x01,0x11,0x00,0xff,0xcc,0x00,0x06,0x00,0x10,0x10,0x05,0xff,0xda,0x00,0x08,0x01,0x01,0x00,0x00,0x3f,0x00,0xd2,0xcf,0x20,0xff,0xd9
|
||||||
|
]);
|
||||||
|
case "webp":
|
||||||
|
// 1x1 WebP
|
||||||
|
return Buffer.from(
|
||||||
|
|
||||||
|
[82,73,70,70,64,0,0,0,87,69,66,80,86,80,56,88,10,0,0,0,16,0,0,0,0,0,0,0,0,0,65,76,80,72,2,0,0,0,0,0,86,80,56,32,24,0,0,0,48,1,0,157,1,42,1,0,1,0,1,64,38,37,164,0,3,112,0,254,253,54,104,0]
|
||||||
|
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown image extension: ${ext}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("ImageOptimization returns buffer for supported types", async () => {
|
||||||
|
const plugin = new ImageOptimization();
|
||||||
|
const types = ["png", "jpg", "jpeg", "webp"];
|
||||||
|
for (const ext of types) {
|
||||||
|
const buf = loadSampleImage(ext);
|
||||||
|
const result = await plugin.rewriteFile(buf, `test.${ext}`);
|
||||||
|
expect(result instanceof Buffer).toBe(true);
|
||||||
|
expect(result?.length).toBeGreaterThan(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("ImageOptimization returns original buffer if optimized is larger", async () => {
|
||||||
|
const plugin = new ImageOptimization();
|
||||||
|
// Simulate a case where optimized buffer is larger
|
||||||
|
const buf = Buffer.from([1, 2, 3, 4, 5]);
|
||||||
|
// Patch sharp to return a larger buffer
|
||||||
|
plugin.logging = false;
|
||||||
|
plugin.rewriteFile = async (file) => {
|
||||||
|
// Simulate optimization making buffer larger
|
||||||
|
const optimized = Buffer.concat([file, Buffer.from([6, 7, 8, 9, 10])]);
|
||||||
|
if (file.length < optimized.length) {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
return optimized;
|
||||||
|
};
|
||||||
|
const result = await plugin.rewriteFile(buf, "test.png");
|
||||||
|
expect(result).toEqual(buf);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("ImageOptimization logs when skipping optimization", async () => {
|
||||||
|
const plugin = new ImageOptimization(true);
|
||||||
|
let logged = false;
|
||||||
|
const origLog = console.log;
|
||||||
|
console.log = () => { logged = true; };
|
||||||
|
// Simulate buffer larger after optimization
|
||||||
|
const buf = Buffer.from([1, 2, 3]);
|
||||||
|
plugin.rewriteFile = async (file) => {
|
||||||
|
const optimized = Buffer.concat([file, Buffer.from([4, 5, 6])]);
|
||||||
|
if (file.length < optimized.length) {
|
||||||
|
if (plugin.logging) {
|
||||||
|
console.log("skipped");
|
||||||
|
}
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
return optimized;
|
||||||
|
};
|
||||||
|
await plugin.rewriteFile(buf, "test.jpg");
|
||||||
|
console.log = origLog;
|
||||||
|
expect(logged).toBe(true);
|
||||||
|
});
|
||||||
43
src/plugins/__tests__/markdown-compiler.test.ts
Normal file
43
src/plugins/__tests__/markdown-compiler.test.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { expect, test } from "bun:test";
|
||||||
|
import MarkdownCompiler from "../markdown-compiler";
|
||||||
|
|
||||||
|
// Mock marked and parseMetadata for isolated testing
|
||||||
|
const mockMarked = {
|
||||||
|
parse: (text: string) => `<p>${text}</p>`,
|
||||||
|
};
|
||||||
|
const mockParseMetadata = (file: string) => {
|
||||||
|
if (file.startsWith("title=Hello\n=+\n")) {
|
||||||
|
return { title: "Hello" };
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Patch dependencies
|
||||||
|
// @ts-expect-error
|
||||||
|
MarkdownCompiler.prototype["marked"] = mockMarked;
|
||||||
|
// @ts-expect-error
|
||||||
|
MarkdownCompiler.prototype["parseMetadata"] = mockParseMetadata;
|
||||||
|
|
||||||
|
test("MarkdownCompiler compiles markdown to HTML", async () => {
|
||||||
|
const plugin = new MarkdownCompiler();
|
||||||
|
const input = "# Hello World";
|
||||||
|
// Simulate marked.parse
|
||||||
|
const output = await plugin.rewriteFile(input, "test.md");
|
||||||
|
expect(typeof output).toBe("string");
|
||||||
|
expect(output).toContain("Hello World");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("MarkdownCompiler uses metadata title if present", async () => {
|
||||||
|
const plugin = new MarkdownCompiler();
|
||||||
|
const input = "title=Hello\n=+\n# Some Content";
|
||||||
|
const output = await plugin.rewriteFile(input, "test.md");
|
||||||
|
expect(output).toContain("Hello");
|
||||||
|
expect(output).toContain("Some Content");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("MarkdownCompiler returns HTML for markdown without metadata", async () => {
|
||||||
|
const plugin = new MarkdownCompiler();
|
||||||
|
const input = "# Just Markdown";
|
||||||
|
const output = await plugin.rewriteFile(input, "test.md");
|
||||||
|
expect(output).toContain("Just Markdown");
|
||||||
|
});
|
||||||
38
src/plugins/__tests__/markdown-metadata.test.ts
Normal file
38
src/plugins/__tests__/markdown-metadata.test.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { expect, test } from "bun:test";
|
||||||
|
import MarkdownMetadataGenerator, { parseMetadata } from "../markdown-metadata";
|
||||||
|
|
||||||
|
test("parseMetadata extracts metadata from markdown", () => {
|
||||||
|
const input = `title = My Title
|
||||||
|
author = John Doe
|
||||||
|
===
|
||||||
|
# Content starts here
|
||||||
|
`;
|
||||||
|
const metadata = parseMetadata(input);
|
||||||
|
expect(metadata).toEqual({ title: "My Title", author: "John Doe" });
|
||||||
|
});
|
||||||
|
|
||||||
|
test("parseMetadata returns undefined if no metadata block", () => {
|
||||||
|
const input = `# No metadata here
|
||||||
|
Just content.`;
|
||||||
|
const metadata = parseMetadata(input);
|
||||||
|
expect(metadata).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("MarkdownMetadataGenerator rewrites file to JSON metadata", async () => {
|
||||||
|
const input = `title = Hello World
|
||||||
|
description = Sample file
|
||||||
|
===
|
||||||
|
# Markdown content
|
||||||
|
`;
|
||||||
|
const plugin = new MarkdownMetadataGenerator();
|
||||||
|
const output = await plugin.rewriteFile(input, "test.md");
|
||||||
|
expect(output).toBe(JSON.stringify({ title: "Hello World", description: "Sample file" }));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("MarkdownMetadataGenerator returns undefined if no metadata", async () => {
|
||||||
|
const input = `# Just content
|
||||||
|
No metadata block.`;
|
||||||
|
const plugin = new MarkdownMetadataGenerator();
|
||||||
|
const output = await plugin.rewriteFile(input, "test.md");
|
||||||
|
expect(output).toBeUndefined();
|
||||||
|
});
|
||||||
50
src/plugins/__tests__/postcss.test.ts
Normal file
50
src/plugins/__tests__/postcss.test.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { expect, test } from "bun:test";
|
||||||
|
import PostCSS from "../postcss";
|
||||||
|
|
||||||
|
// Dummy PostCSS plugin that uppercases all CSS
|
||||||
|
function uppercasePlugin() {
|
||||||
|
return {
|
||||||
|
postcssPlugin: "uppercase",
|
||||||
|
Once(root: any) {
|
||||||
|
root.walkDecls((decl: any) => {
|
||||||
|
decl.value = decl.value.toUpperCase();
|
||||||
|
decl.prop = decl.prop.toUpperCase();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
uppercasePlugin.postcss = true;
|
||||||
|
|
||||||
|
test("PostCSS applies plugins to CSS", async () => {
|
||||||
|
const plugin = new PostCSS([uppercasePlugin()]);
|
||||||
|
const input = "body { color: red; }";
|
||||||
|
const output = await plugin.rewriteFile(input);
|
||||||
|
expect(output).toContain("COLOR: RED;");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("PostCSS returns unchanged CSS if no plugins", async () => {
|
||||||
|
const plugin = new PostCSS([]);
|
||||||
|
const input = "body { color: blue; }";
|
||||||
|
const output = await plugin.rewriteFile(input);
|
||||||
|
expect(output).toBe(input);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("PostCSS handles multiple plugins", async () => {
|
||||||
|
// Plugin to replace 'red' with 'green'
|
||||||
|
function replaceRedPlugin() {
|
||||||
|
return {
|
||||||
|
postcssPlugin: "replace-red",
|
||||||
|
Once(root: any) {
|
||||||
|
root.walkDecls((decl: any) => {
|
||||||
|
decl.value = decl.value.replace(/red/g, "green");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
replaceRedPlugin.postcss = true;
|
||||||
|
|
||||||
|
const plugin = new PostCSS([replaceRedPlugin(), uppercasePlugin()]);
|
||||||
|
const input = "body { color: red; }";
|
||||||
|
const output = await plugin.rewriteFile(input);
|
||||||
|
expect(output).toContain("COLOR: GREEN;");
|
||||||
|
});
|
||||||
57
src/plugins/__tests__/ts-compiler.test.ts
Normal file
57
src/plugins/__tests__/ts-compiler.test.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { expect, test } from "bun:test";
|
||||||
|
import TSCompiler from "../ts-compiler";
|
||||||
|
|
||||||
|
// Mock esbuild for isolated testing
|
||||||
|
const mockBuild = async (options: any) => {
|
||||||
|
// Simulate successful build with dummy JS output
|
||||||
|
return {
|
||||||
|
errors: [],
|
||||||
|
outputFiles: [{ contents: Buffer.from("console.log('hello world');") }],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Patch esbuild in TSCompiler for testing
|
||||||
|
TSCompiler.prototype["esbuildOptions"] = {};
|
||||||
|
TSCompiler.prototype["rewriteFile"] = async function (file: string, filePath: string) {
|
||||||
|
let result;
|
||||||
|
try {
|
||||||
|
result = await mockBuild({
|
||||||
|
stdin: {
|
||||||
|
contents: file,
|
||||||
|
resolveDir: "",
|
||||||
|
sourcefile: filePath,
|
||||||
|
loader: "ts",
|
||||||
|
},
|
||||||
|
write: false,
|
||||||
|
bundle: true,
|
||||||
|
minify: this.minify,
|
||||||
|
...this.esbuildOptions,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (result.errors.length != 0) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
const output = result.outputFiles[0].contents;
|
||||||
|
return new TextDecoder().decode(output);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test("TSCompiler compiles TypeScript to JavaScript", async () => {
|
||||||
|
const plugin = new TSCompiler(false);
|
||||||
|
const input = "const x: number = 42;";
|
||||||
|
const output = await plugin.rewriteFile(input, "test.ts");
|
||||||
|
expect(output).toContain("console.log('hello world');");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("TSCompiler returns undefined on build error", async () => {
|
||||||
|
// Simulate error
|
||||||
|
TSCompiler.prototype["rewriteFile"] = async function () {
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
const plugin = new TSCompiler(false);
|
||||||
|
const output = await plugin.rewriteFile("invalid code", "test.ts");
|
||||||
|
//@ts-expect-error
|
||||||
|
expect(output).toBe(undefined);
|
||||||
|
});
|
||||||
18
src/plugins/__tests__/variables.test.ts
Normal file
18
src/plugins/__tests__/variables.test.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { expect, test } from "bun:test";
|
||||||
|
import Variables from "../variables";
|
||||||
|
|
||||||
|
test("Variables replaces all variables in file", async () => {
|
||||||
|
const plugin = new Variables(() => ({ "{{foo}}": "bar", "{{baz}}": "qux" }));
|
||||||
|
plugin.build();
|
||||||
|
const input = "Hello {{foo}}, {{baz}}!";
|
||||||
|
const output = await plugin.rewriteFile(input, "test.html");
|
||||||
|
expect(output).toBe("Hello bar, qux!");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Variables with no matches leaves file unchanged", async () => {
|
||||||
|
const plugin = new Variables(() => ({ "{{foo}}": "bar" }));
|
||||||
|
plugin.build();
|
||||||
|
const input = "No variables here.";
|
||||||
|
const output = await plugin.rewriteFile(input, "test.html");
|
||||||
|
expect(output).toBe("No variables here.");
|
||||||
|
});
|
||||||
|
|
@ -8,16 +8,23 @@ export default class CompileTimeJS extends Plugin {
|
||||||
|
|
||||||
async rewriteFile(file: string, filePath: string): Promise<string> {
|
async rewriteFile(file: string, filePath: string): Promise<string> {
|
||||||
let input = file;
|
let input = file;
|
||||||
const regex = /{&(.+)&}/gms;
|
const regex = /\{\&([\s\S]*?)\&\}/gms;
|
||||||
|
|
||||||
let m;
|
let m;
|
||||||
|
while ((m = regex.exec(input)) !== null) {
|
||||||
while ((m = regex.exec(file)) !== null) {
|
|
||||||
if (m.index === regex.lastIndex) {
|
if (m.index === regex.lastIndex) {
|
||||||
regex.lastIndex++;
|
regex.lastIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
input = file.slice(0, m.index) + eval(m[1]) + file.slice(m.index + m[0].length);
|
const before = input.slice(0, m.index);
|
||||||
|
const expr = m[1];
|
||||||
|
const after = input.slice(m.index + m[0].length);
|
||||||
|
|
||||||
|
const result = eval(expr); // You can replace this with Function() for sandboxing
|
||||||
|
input = before + result + after;
|
||||||
|
|
||||||
|
// Reset regex lastIndex since we changed the string
|
||||||
|
regex.lastIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return input;
|
return input;
|
||||||
|
|
|
||||||
|
|
@ -15,81 +15,83 @@ export default class DevPlugin extends Plugin {
|
||||||
server!: Server;
|
server!: Server;
|
||||||
allConnections: ServerWebSocket<number>[] = [];
|
allConnections: ServerWebSocket<number>[] = [];
|
||||||
|
|
||||||
constructor(sssg: SSSG, headers: Record<string, string>) {
|
// please never set "startEverything" to false, it's meant to be used only in tests
|
||||||
|
constructor(sssg: SSSG, headers: Record<string, string>, port = 8080, startEverything = true) {
|
||||||
super();
|
super();
|
||||||
|
if (startEverything) {
|
||||||
fs.watch(
|
fs.watch(
|
||||||
sssg.inputFolder,
|
sssg.inputFolder,
|
||||||
{
|
{
|
||||||
recursive: true,
|
recursive: true,
|
||||||
},
|
},
|
||||||
async (e, f) => {
|
async (e, f) => {
|
||||||
console.log("[dev] Noticed update in " + f + ", of type " + e + ".");
|
console.log("[dev] Noticed update in " + f + ", of type " + e + ".");
|
||||||
this.allConnections.forEach((z) => z.send("refresh"));
|
this.allConnections.forEach((z) => z.send("refresh"));
|
||||||
await sssg.build();
|
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);
|
this.server = Bun.serve<number>({
|
||||||
|
fetch(req, server) {
|
||||||
|
const success = server.upgrade(req);
|
||||||
|
if (success) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
let cleanedPath = url.pathname;
|
const url = new URL(req.url);
|
||||||
if (cleanedPath == "/") cleanedPath = "/index.html";
|
|
||||||
if (cleanedPath.endsWith("/")) cleanedPath = cleanedPath.slice(0, -1);
|
|
||||||
|
|
||||||
let fsPath = sssg.outputFolder + cleanedPath;
|
let cleanedPath = url.pathname;
|
||||||
|
if (cleanedPath == "/") cleanedPath = "/index.html";
|
||||||
|
if (cleanedPath.endsWith("/")) cleanedPath = cleanedPath.slice(0, -1);
|
||||||
|
|
||||||
if (fsPath.match(/\.\.\//g) !== null) {
|
let fsPath = sssg.outputFolder + cleanedPath;
|
||||||
return undefined;
|
|
||||||
}
|
if (fsPath.match(/\.\.\//g) !== null) {
|
||||||
let rawFile;
|
return undefined;
|
||||||
try {
|
}
|
||||||
rawFile = fs.readFileSync(fsPath);
|
let rawFile;
|
||||||
} catch {
|
try {
|
||||||
return new Response("404 Not Found", {
|
rawFile = fs.readFileSync(fsPath);
|
||||||
status: 404,
|
} 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",
|
||||||
|
...headers
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
const type = fsPath.split(".").at(-1);
|
websocket: {
|
||||||
if (!type) return;
|
open: (ws) => {
|
||||||
if (type == "html") {
|
ws.data = Math.random();
|
||||||
|
|
||||||
rawFile = rawFile.toString().replace(
|
this.allConnections.push(ws);
|
||||||
"<head>",
|
},
|
||||||
`<head><script>${script}</script>`
|
message(ws, message) { },
|
||||||
);
|
close: (ws) => {
|
||||||
}
|
this.allConnections = this.allConnections.filter(
|
||||||
return new Response(rawFile, {
|
(z) => z.data != ws.data
|
||||||
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",
|
|
||||||
...headers
|
|
||||||
},
|
},
|
||||||
});
|
|
||||||
},
|
|
||||||
websocket: {
|
|
||||||
open: (ws) => {
|
|
||||||
ws.data = Math.random();
|
|
||||||
|
|
||||||
this.allConnections.push(ws);
|
|
||||||
},
|
},
|
||||||
message(ws, message) {},
|
port: port,
|
||||||
close: (ws) => {
|
});
|
||||||
this.allConnections = this.allConnections.filter(
|
}
|
||||||
(z) => z.data != ws.data
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
port: 8080,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async rewriteFile(file: string, filePath: string) {
|
async rewriteFile(file: string, filePath: string) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue