samengine-build 1.6.3

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.
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # Samengine Build
2
+
3
+ The build and export Tool for samengine.
4
+
5
+ Check samengine on [Github](https://github.com/ShadowDara/samengine) or [NPM](https://www.npmjs.com/package/samengine).
@@ -0,0 +1,9 @@
1
+ export interface buildconfig {
2
+ htmlhead: string;
3
+ title: string;
4
+ version: string;
5
+ show_fullscreen_button: boolean;
6
+ entryname: string;
7
+ outdir: string;
8
+ }
9
+ export declare function new_buildconfig(): buildconfig;
@@ -0,0 +1,11 @@
1
+ // Generate the HTML File
2
+ export function new_buildconfig() {
3
+ return {
4
+ htmlhead: "",
5
+ title: "My new Game",
6
+ version: "0.0.0",
7
+ show_fullscreen_button: true,
8
+ entryname: "main",
9
+ outdir: "dist",
10
+ };
11
+ }
@@ -0,0 +1,6 @@
1
+ export declare const flog: (...args: any[]) => void;
2
+ export declare function copyFolder(src: string, dest: string): Promise<void>;
3
+ export declare function scanResourcesAsDataURIs(resourceDir: string): Promise<Record<string, string>>;
4
+ export declare function getContentType(path: string): string;
5
+ export declare function getMimeType(filename: string): string;
6
+ export declare function filterResourcesByUsage(bundledJsContent: string, resourcesMap: Record<string, string>): Record<string, string>;
@@ -0,0 +1,108 @@
1
+ import { readdirSync, mkdirSync, promises as fsPromises, existsSync } from "fs";
2
+ import { join } from "path";
3
+ export const flog = (...args) => {
4
+ const now = new Date();
5
+ const time = `[${now.getHours().toString().padStart(2, "0")}:` +
6
+ `${now.getMinutes().toString().padStart(2, "0")}:` +
7
+ `${now.getSeconds().toString().padStart(2, "0")}.` +
8
+ `${now.getMilliseconds().toString().padStart(3, "0")}]`;
9
+ console.log(time, ...args);
10
+ };
11
+ export async function copyFolder(src, dest) {
12
+ // Zielordner erstellen
13
+ mkdirSync(dest, { recursive: true });
14
+ const entries = readdirSync(src, { withFileTypes: true });
15
+ for (const entry of entries) {
16
+ const srcPath = join(src, entry.name);
17
+ const destPath = join(dest, entry.name);
18
+ if (entry.isDirectory()) {
19
+ await copyFolder(srcPath, destPath);
20
+ }
21
+ else if (entry.isFile()) {
22
+ // Datei mit Node.js schreiben
23
+ const data = await fsPromises.readFile(srcPath);
24
+ await fsPromises.writeFile(destPath, data);
25
+ }
26
+ }
27
+ }
28
+ // Scan resources folder and convert to data URIs (base64)
29
+ export async function scanResourcesAsDataURIs(resourceDir) {
30
+ const resourceMap = {};
31
+ if (!existsSync(resourceDir)) {
32
+ return resourceMap;
33
+ }
34
+ async function scanDir(dir, basePath = "") {
35
+ const entries = readdirSync(dir, { withFileTypes: true });
36
+ for (const entry of entries) {
37
+ const fullPath = join(dir, entry.name);
38
+ const resourcePath = basePath ? `${basePath}/${entry.name}` : entry.name;
39
+ if (entry.isDirectory()) {
40
+ await scanDir(fullPath, resourcePath);
41
+ }
42
+ else if (entry.isFile()) {
43
+ const fileData = await fsPromises.readFile(fullPath);
44
+ const base64Data = fileData.toString("base64");
45
+ const mimeType = getMimeType(entry.name);
46
+ resourceMap[resourcePath] = `data:${mimeType};base64,${base64Data}`;
47
+ }
48
+ }
49
+ }
50
+ await scanDir(resourceDir);
51
+ return resourceMap;
52
+ }
53
+ // === Helper ===
54
+ export function getContentType(path) {
55
+ if (path.endsWith(".js"))
56
+ return "application/javascript";
57
+ if (path.endsWith(".ts"))
58
+ return "application/typescript";
59
+ if (path.endsWith(".html"))
60
+ return "text/html";
61
+ if (path.endsWith(".css"))
62
+ return "text/css";
63
+ if (path.endsWith(".png"))
64
+ return "image/png";
65
+ return "text/plain";
66
+ }
67
+ export function getMimeType(filename) {
68
+ const ext = filename.toLowerCase().split(".").pop() || "";
69
+ const mimeTypes = {
70
+ "png": "image/png",
71
+ "jpg": "image/jpeg",
72
+ "jpeg": "image/jpeg",
73
+ "gif": "image/gif",
74
+ "svg": "image/svg+xml",
75
+ "webp": "image/webp",
76
+ "mp3": "audio/mpeg",
77
+ "wav": "audio/wav",
78
+ "ogg": "audio/ogg",
79
+ "m4a": "audio/mp4",
80
+ "json": "application/json",
81
+ "txt": "text/plain",
82
+ "css": "text/css",
83
+ };
84
+ return mimeTypes[ext] || "application/octet-stream";
85
+ }
86
+ // Filter resources by checking if they are actually used in the bundled code
87
+ export function filterResourcesByUsage(bundledJsContent, resourcesMap) {
88
+ const filteredResources = {};
89
+ const unusedResources = [];
90
+ for (const resourcePath in resourcesMap) {
91
+ // Extract just the filename (last part after /)
92
+ const filename = resourcePath.split("/").pop() || resourcePath;
93
+ // Check if the resource is referenced in the bundled code
94
+ // Look for string literals containing the resource path or filename
95
+ if (bundledJsContent.includes(filename) || bundledJsContent.includes(resourcePath)) {
96
+ filteredResources[resourcePath] = resourcesMap[resourcePath];
97
+ }
98
+ else {
99
+ unusedResources.push(resourcePath);
100
+ }
101
+ }
102
+ // Log unused resources
103
+ if (unusedResources.length > 0) {
104
+ flog(`⚠️ ${unusedResources.length} unused resource(s) excluded from single-file build:`);
105
+ unusedResources.forEach(res => flog(` - ${res}`));
106
+ }
107
+ return filteredResources;
108
+ }
@@ -0,0 +1,8 @@
1
+ export interface CLIArgs {
2
+ release: boolean;
3
+ singlefile: boolean;
4
+ port: number;
5
+ newProject: string | null;
6
+ empty: boolean;
7
+ }
8
+ export declare function parseArgs(): CLIArgs;
@@ -0,0 +1,36 @@
1
+ // Arguments for the CLI Tool
2
+ // Function to parse the Args for the CLI Tools
3
+ export function parseArgs() {
4
+ const args = process.argv.slice(2);
5
+ const options = { release: false, singlefile: false, port: 3000, newProject: null, empty: false };
6
+ for (let i = 0; i < args.length; i++) {
7
+ const arg = args[i];
8
+ switch (arg) {
9
+ case "--single-file":
10
+ options.singlefile = true;
11
+ break;
12
+ case "--release":
13
+ case "-r":
14
+ options.release = true;
15
+ break;
16
+ case "--port":
17
+ case "-p":
18
+ options.port = Number(args[++i]);
19
+ break;
20
+ case "--new":
21
+ case "-n":
22
+ options.newProject = args[++i];
23
+ break;
24
+ case "--new-empty":
25
+ options.empty = true;
26
+ break;
27
+ case "-h":
28
+ case "--help":
29
+ console.log("CLI Tools for samengine\nUsage:\n -r, --release\n -p <port>\n -n <project>\n --new-empty\n --single-file to generate the Export into one file");
30
+ process.exit(0);
31
+ default:
32
+ console.warn(`⚠️ Unknown Argument: ${arg}`);
33
+ }
34
+ }
35
+ return options;
36
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,160 @@
1
+ #!/usr/bin/env node
2
+ // Build Cli Tools for samengine
3
+ import { build as esbuild } from "esbuild";
4
+ import { createServer } from "http";
5
+ import { readFile, writeFile, mkdir, rm } from "fs/promises";
6
+ import { watch } from "fs";
7
+ import path from "path";
8
+ import { WebSocketServer } from "ws";
9
+ import { createProject } from "./new.js";
10
+ import { copyFolder, flog, getContentType, scanResourcesAsDataURIs, filterResourcesByUsage } from "../buildhelper.js";
11
+ import { GetDefaultHTML, GetSingleFileHTML } from "../exporthtml.js";
12
+ import { loadUserConfig } from "./config.js";
13
+ import { compressHTML } from "../utils/utils.js";
14
+ import { parseArgs } from "./argparser.js";
15
+ // ================= BUILD =================
16
+ function createBuilder(config, isRelease, isSingleFile = false) {
17
+ // Ensure that the Directories are created
18
+ mkdir("resources", { recursive: true });
19
+ mkdir("game", { recursive: true });
20
+ async function build() {
21
+ try {
22
+ flog("🔄 Building project...");
23
+ if (isRelease)
24
+ await rm("./dist", { recursive: true, force: true });
25
+ await esbuild({
26
+ entryPoints: [`./game/${config.entryname}`],
27
+ outdir: `./${config.outdir}`,
28
+ bundle: true,
29
+ platform: "browser",
30
+ minify: isRelease,
31
+ sourcemap: !isRelease,
32
+ define: { "import.meta.env.DEV": JSON.stringify(!isRelease) },
33
+ });
34
+ if (isSingleFile) {
35
+ // Single-file export
36
+ const bundledJsPath = path.join(".", config.outdir, `${config.entryname.replace(/\.[^.]*$/, "")}.js`);
37
+ const bundledJsContent = await readFile(bundledJsPath, "utf-8");
38
+ // Scan resources and convert to data URIs
39
+ let resourcesMap = await scanResourcesAsDataURIs("./resources");
40
+ // Filter resources by usage in the bundled code
41
+ resourcesMap = filterResourcesByUsage(bundledJsContent, resourcesMap);
42
+ let html = GetSingleFileHTML(config, bundledJsContent, resourcesMap);
43
+ if (isRelease)
44
+ html = await compressHTML(html);
45
+ // Add comment at the beginning after minification
46
+ const htmlComment = `<!-- Game made with samengine v${config.version} - https://github.com/Shadowdara/samengine -->\n`;
47
+ html = htmlComment + html;
48
+ await writeFile("./dist/index.html", html);
49
+ // Delete the JS File
50
+ await rm("./dist/main.js", { recursive: true, force: true });
51
+ flog("✅ Single-file export created!");
52
+ }
53
+ else {
54
+ // Multi-file export (original behavior)
55
+ let html = GetDefaultHTML(config);
56
+ if (isRelease)
57
+ html = await compressHTML(html);
58
+ // Add HTML comment at the beginning after minification
59
+ const htmlComment = `<!-- Game made with samengine v${config.version} - https://www.npmjs.com/@shadowdara/samengine -->\n`;
60
+ html = htmlComment + html;
61
+ await writeFile("./dist/index.html", html);
62
+ // Add JS comment at the beginning of JS files
63
+ const jsComment = `// Game made with samengine v${config.version} - https://www.npmjs.com/@shadowdara/samengine\n`;
64
+ const jsPath = path.join(".", config.outdir, `${config.entryname.replace(/\.[^.]*$/, "")}.js`);
65
+ let jsContent = await readFile(jsPath, "utf-8");
66
+ jsContent = jsComment + jsContent;
67
+ await writeFile(jsPath, jsContent);
68
+ await copyFolder("./resources", "./dist/resources");
69
+ flog("✅ Build finished!");
70
+ }
71
+ }
72
+ catch (error) {
73
+ flog(`❌ Build failed: ${error instanceof Error ? error.message : String(error)}`);
74
+ }
75
+ }
76
+ return { build };
77
+ }
78
+ // ================= SERVER & RELOAD =================
79
+ function createDevServer(port) {
80
+ const sockets = new Set(); // <-- ws WebSocket, nicht DOM
81
+ const server = createServer(async (req, res) => {
82
+ const url = req.url || "/";
83
+ const filePath = path.join(process.cwd(), "dist", url === "/" ? "index.html" : url);
84
+ try {
85
+ const file = await readFile(filePath);
86
+ res.writeHead(200, { "Content-Type": getContentType(filePath) });
87
+ res.end(file);
88
+ }
89
+ catch {
90
+ res.writeHead(404);
91
+ res.end("Not Found");
92
+ }
93
+ });
94
+ const wss = new WebSocketServer({ server });
95
+ wss.on("connection", (ws) => {
96
+ sockets.add(ws);
97
+ ws.on("close", () => sockets.delete(ws));
98
+ });
99
+ server.listen(port, () => flog(`🚀 Dev Server running on http://localhost:${port}`));
100
+ function reloadClients() {
101
+ flog("🔄 Browser reload...");
102
+ sockets.forEach((ws) => ws.send("reload"));
103
+ }
104
+ return { reloadClients };
105
+ }
106
+ // ================= WATCHER =================
107
+ async function startWatcher(onChange) {
108
+ await mkdir("resources", { recursive: true });
109
+ await mkdir("game", { recursive: true });
110
+ ["resources", "game"].forEach((dir) => {
111
+ watch(dir, { recursive: true }, async () => {
112
+ flog(`📁 Change noticed in ${dir}`);
113
+ await onChange();
114
+ });
115
+ });
116
+ flog("👀 Watcher active...");
117
+ }
118
+ // ================= CLI APP =================
119
+ async function main() {
120
+ const args = parseArgs();
121
+ if (args.newProject) {
122
+ await createProject(args.newProject, args.empty);
123
+ process.exit(0);
124
+ }
125
+ const config = await loadUserConfig();
126
+ const builder = createBuilder(config, args.release, args.singlefile);
127
+ let isBuilding = false;
128
+ let pendingRestart = false;
129
+ async function restart() {
130
+ if (isBuilding) {
131
+ pendingRestart = true;
132
+ return;
133
+ }
134
+ isBuilding = true;
135
+ do {
136
+ pendingRestart = false;
137
+ try {
138
+ await builder.build();
139
+ devServer?.reloadClients();
140
+ }
141
+ catch (error) {
142
+ flog(`❌ Rebuild failed: ${error instanceof Error ? error.message : String(error)}`);
143
+ }
144
+ } while (pendingRestart);
145
+ isBuilding = false;
146
+ }
147
+ try {
148
+ await builder.build();
149
+ }
150
+ catch (error) {
151
+ flog(`❌ Initial build failed: ${error instanceof Error ? error.message : String(error)}`);
152
+ }
153
+ let devServer = null;
154
+ if (!args.release) {
155
+ devServer = createDevServer(args.port);
156
+ await startWatcher(restart);
157
+ }
158
+ flog(`Build finished! Mode: ${args.release ? "Release" : "Dev"}`);
159
+ }
160
+ main();
@@ -0,0 +1 @@
1
+ export declare function loadUserConfig(): Promise<any>;
@@ -0,0 +1,32 @@
1
+ import { build } from "esbuild";
2
+ import path from "path";
3
+ import { pathToFileURL } from "url";
4
+ import fs from "fs/promises";
5
+ export async function loadUserConfig() {
6
+ const root = process.cwd();
7
+ const configPath = path.resolve(root, "samengine.config.ts");
8
+ const outDir = path.resolve(root, ".samengine");
9
+ const outfile = path.join(outDir, "config.mjs");
10
+ try {
11
+ // ensure folder exists
12
+ await fs.mkdir(outDir, { recursive: true });
13
+ // 🔥 bundle TS → JS
14
+ await build({
15
+ entryPoints: [configPath],
16
+ outfile,
17
+ bundle: true,
18
+ platform: "node",
19
+ format: "esm",
20
+ });
21
+ // 🔥 import compiled file
22
+ const mod = await import(pathToFileURL(outfile).href + `?t=${Date.now()}`);
23
+ const config = typeof mod.default === "function"
24
+ ? await mod.default()
25
+ : mod.default;
26
+ return config;
27
+ }
28
+ catch (e) {
29
+ console.error(e);
30
+ throw new Error("❌ Could not load webgameengine.config.ts: " + configPath);
31
+ }
32
+ }
@@ -0,0 +1 @@
1
+ export declare function createProject(name: string, empty: boolean): Promise<void>;
@@ -0,0 +1,198 @@
1
+ import path from "path";
2
+ import { writeFile, mkdir } from "fs/promises";
3
+ import chalk from 'chalk';
4
+ import { flog } from "../buildhelper.js";
5
+ // ================= NEW PROJECT =================
6
+ export async function createProject(name, empty) {
7
+ flog(`📦 Erstelle neues Projekt: ${name}`);
8
+ // Create Dirs
9
+ await mkdir("game", { recursive: true });
10
+ await mkdir("resources", { recursive: true });
11
+ await mkdir("dist", { recursive: true });
12
+ let content = `
13
+ // A empty Project with the Web Framework
14
+
15
+ import { createCanvas, enableFullscreen, setupFullscreenButton } from "samengine";
16
+ import { setupInput, resetInput, getMouse } from "samengine";
17
+ import { startEngine } from "samengine";
18
+
19
+ const { canvas, ctx, applyScaling } = createCanvas({fullscreen: true, scaling: "fit", virtualWidth: window.innerWidth, virtualHeight: window.innerHeight});
20
+ setupInput(canvas);
21
+
22
+ enableFullscreen(canvas);
23
+ setupFullscreenButton(canvas);
24
+
25
+ async function gameStart() {
26
+ // Code which runs at the Game Start
27
+ }
28
+
29
+ function gameLoop(dt: number) {
30
+ // Code which runs every Frame
31
+
32
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
33
+
34
+ const mouse = getMouse();
35
+
36
+ applyScaling();
37
+
38
+ resetInput();
39
+ }
40
+
41
+ // Because start Game is Async
42
+ startEngine(() => { gameStart().then(() => {/* ready */}) }, gameLoop);
43
+ `;
44
+ if (empty) {
45
+ content = `
46
+ // A mini Snake Clone with my Webframework
47
+
48
+ import { createCanvas } from "samengine";
49
+ import { setupInput, isKeyJustPressed, resetInput } from "samengine";
50
+ import { startEngine } from "samengine";
51
+ import { renderText } from "samengine";
52
+ import { Vector2d } from "samengine/types";
53
+ import { dlog } from "samengine";
54
+ import { Key } from "samengine";
55
+
56
+ const { canvas, ctx } = createCanvas({fullscreen: true, scaling: "fit", virtualWidth: window.innerWidth, virtualHeight: window.innerHeight});
57
+ setupInput(canvas);
58
+
59
+ let snake: Vector2d[] = [{ x: 10, y: 10 }];
60
+ let dir: Vector2d = { x: 1, y: 0 };
61
+ let food: Vector2d = { x: 15, y: 10 };
62
+ let gridSize = 20;
63
+ let lastMove = 0;
64
+ let speed = 0.2; // seconds per cell
65
+ let start = false;
66
+
67
+ async function gameStart() {
68
+ dlog("Snake gestartet");
69
+ }
70
+
71
+ function gameLoop(dt: number) {
72
+ if (isKeyJustPressed(Key.Escape)) {
73
+ start = !start
74
+ }
75
+
76
+ if (!start) {
77
+ ctx.fillStyle = "white";
78
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
79
+
80
+ renderText(ctx, "Snake", 10, 10, "black", "24px Arial");
81
+ renderText(ctx, "Press ESC to start or Pause the Game!", 10, 50, "black", "24px Arial");
82
+
83
+ // return;
84
+ } else {
85
+
86
+ // Input
87
+ if (isKeyJustPressed(Key.ArrowUp) && dir.y === 0) dir = { x: 0, y: -1 };
88
+ if (isKeyJustPressed(Key.ArrowDown) && dir.y === 0) dir = { x: 0, y: 1 };
89
+ if (isKeyJustPressed(Key.ArrowLeft) && dir.x === 0) dir = { x: -1, y: 0 };
90
+ if (isKeyJustPressed(Key.ArrowRight) && dir.x === 0) dir = { x: 1, y: 0 };
91
+
92
+ lastMove += dt;
93
+
94
+ if (lastMove >= speed) {
95
+ lastMove = 0;
96
+ const head = { x: snake[0].x + dir.x, y: snake[0].y + dir.y };
97
+
98
+ // Kollision mit Walls
99
+ if (head.x < 0 || head.y < 0 || head.x >= canvas.width / gridSize || head.y >= canvas.height / gridSize) {
100
+ snake = [{ x: 10, y: 10 }];
101
+ dir = { x: 1, y: 0 };
102
+ dlog("Game Over");
103
+ return;
104
+ }
105
+
106
+ // Kollision mit sich selbst
107
+ if (snake.some(s => s.x === head.x && s.y === head.y)) {
108
+ snake = [{ x: 10, y: 10 }];
109
+ dir = { x: 1, y: 0 };
110
+ dlog(\`Game Over! Highscore: \${snake.length - 1}\`);
111
+ return;
112
+ }
113
+
114
+ snake.unshift(head);
115
+
116
+ // Food Check
117
+ if (head.x === food.x && head.y === food.y) {
118
+ food = { x: Math.floor(Math.random() * (canvas.width / gridSize)), y: Math.floor(Math.random() * (canvas.height / gridSize)) };
119
+ } else {
120
+ snake.pop();
121
+ }
122
+ }
123
+
124
+ // Zeichnen
125
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
126
+
127
+ ctx.fillStyle = "black";
128
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
129
+
130
+ ctx.fillStyle = "green";
131
+ snake.forEach(s => ctx.fillRect(s.x * gridSize, s.y * gridSize, gridSize, gridSize));
132
+
133
+ ctx.fillStyle = "red";
134
+ ctx.fillRect(food.x * gridSize, food.y * gridSize, gridSize, gridSize);
135
+
136
+ renderText(ctx, "Score: " + (snake.length - 1), 10, 10, "yellow", "24px Arial");
137
+
138
+ }
139
+
140
+ // Reset Input
141
+ resetInput();
142
+ }
143
+
144
+ // Because start Game is Async
145
+ startEngine(() => { gameStart().then(() => {/* ready */}) }, gameLoop);
146
+ `;
147
+ }
148
+ // Basic game entry
149
+ await writeFile(path.join("game", "main.ts"), content);
150
+ // Config
151
+ await writeFile(path.join("samengine.config.ts"), `
152
+ // Project File for the Game
153
+
154
+ import type { buildconfig } from "samengine-build";
155
+ import { new_buildconfig } from "samengine-build";
156
+
157
+ export default function defineConfig(): buildconfig {
158
+ let config: buildconfig = new_buildconfig();
159
+ return config;
160
+ }
161
+ `);
162
+ // package.json
163
+ // await writeFile(
164
+ // path.join(base, "package.json"),
165
+ // JSON.stringify(
166
+ // {
167
+ // name,
168
+ // version: "1.0.0",
169
+ // type: "module",
170
+ // scripts: {
171
+ // dev: "mycli",
172
+ // build: "mycli --release",
173
+ // },
174
+ // },
175
+ // null,
176
+ // 2
177
+ // )
178
+ // );
179
+ flog("✅ Projekt created!");
180
+ // flog(`👉 cd ${name}`);
181
+ // flog(`👉 npm install`);
182
+ // flog(`👉 npm run dev`);
183
+ console.log(`Add to your package.json file:
184
+
185
+ "scripts": {
186
+ "dev": "npx samengine-build",
187
+ "build": "npx samengine-build --release"
188
+ },
189
+
190
+ Run "npm run dev" to start the Dev Server and play a little Snake Clone
191
+
192
+ Add ".samengine/config.mjs" to your gitignore file if you are using Git.
193
+
194
+ ${chalk.red("Dont ignore the complete Folder!")}
195
+ ${chalk.red("Some Tools by samengine are saving important configs in this Folder!")}
196
+ `);
197
+ process.exit(0);
198
+ }
@@ -0,0 +1,3 @@
1
+ import type { buildconfig } from "./buildconfig.js";
2
+ export declare function GetSingleFileHTML(config: buildconfig, bundledJsContent: string, resourcesMap?: Record<string, string>): string;
3
+ export declare function GetDefaultHTML(config: buildconfig): string;
@@ -0,0 +1,279 @@
1
+ // Function to create the Export HTML for the Build
2
+ // Function to create the Export HTML for the Build
3
+ import { version } from "samengine/build";
4
+ export function GetSingleFileHTML(config, bundledJsContent, resourcesMap = {}) {
5
+ let frameworkVersion = version();
6
+ let fullscreenbutton = "";
7
+ let fullscreenBtn = "";
8
+ if (config.show_fullscreen_button) {
9
+ fullscreenbutton = `#fullscreenBtn {
10
+ position: fixed;
11
+ top: 10px;
12
+ right: 10px;
13
+
14
+ padding: 10px 15px;
15
+ font-size: 16px;
16
+
17
+ background: rgba(0, 0, 0, 0.6);
18
+ color: white;
19
+ border: none;
20
+ border-radius: 6px;
21
+
22
+ cursor: pointer;
23
+ z-index: 1000;
24
+ }
25
+
26
+ #fullscreenBtn:hover {
27
+ background: rgba(0, 0, 0, 0.8);
28
+ }`;
29
+ fullscreenBtn = `<!-- Button to make it fullscreen -->
30
+ <button id="fullscreenBtn">⛶ Fullscreen</button>`;
31
+ }
32
+ // Create a resource loader function that will be embedded in the HTML
33
+ const resourceLoaderScript = `window.__resources = ${JSON.stringify(resourcesMap)};
34
+ window.__getResource = function(path) {
35
+ return window.__resources[path] || null;
36
+ };
37
+ window.__loadResource = function(path) {
38
+ const resource = window.__getResource(path);
39
+ if (!resource) {
40
+ console.warn('Resource not found:', path);
41
+ return null;
42
+ }
43
+ return resource;
44
+ };
45
+ window.__samengine__ = {
46
+ version: "${frameworkVersion}"
47
+ };`;
48
+ // Wrap bundled JS in a function to prevent auto-execution
49
+ const wrappedGameCode = `function __initializeGame() {
50
+ ${bundledJsContent.split('\n').map(line => ' ' + line).join('\n')}
51
+ }`;
52
+ const defaulthtml = `<!-- HTML Web Game made with samengine by Shadowdara -->
53
+ <!-- DO NOT REMOVE THIS NOTE ! -->
54
+ <!DOCTYPE html>
55
+ <html>
56
+ <head>
57
+ <meta charset="UTF-8" />
58
+ <title>${config.title}</title>
59
+ <!-- Für Mobile Viewports -->
60
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
61
+ <style>
62
+ * {
63
+ margin: 0;
64
+ padding: 0;
65
+ box-sizing: border-box;
66
+ }
67
+ body {
68
+ overflow: hidden; /* 🔥 verhindert Scrollbars */
69
+ }
70
+ body {
71
+ margin: 0;
72
+ background: #0f172a;
73
+ color: white;
74
+ font-family: sans-serif;
75
+ display: flex;
76
+ justify-content: center;
77
+ align-items: center;
78
+ height: 100vh;
79
+ }
80
+
81
+ #startscreen {
82
+ text-align: center;
83
+ }
84
+
85
+ h1 {
86
+ font-size: 3rem;
87
+ margin-bottom: 0.5rem;
88
+ }
89
+
90
+ h2 {
91
+ font-weight: normal;
92
+ opacity: 0.7;
93
+ }
94
+
95
+ .startbutton {
96
+ margin-top: 2rem;
97
+ padding: 1rem 2rem;
98
+ font-size: 1.2rem;
99
+ background: #22c55e;
100
+ border: none;
101
+ border-radius: 8px;
102
+ cursor: pointer;
103
+ }
104
+
105
+ .startbutton:hover {
106
+ background: #16a34a;
107
+ }
108
+ ${fullscreenbutton}
109
+ </style>
110
+ </style>
111
+ </head>
112
+ <body>
113
+ <div id="startscreen">
114
+ <h2>made with samengine</h2>
115
+ <h1>${config.title}</h1>
116
+ <p>${config.version}</p>
117
+
118
+ <button class="startbutton" id="startBtn">Start</button>
119
+ </div>
120
+
121
+ <script>
122
+ ${resourceLoaderScript}
123
+ </script>
124
+ <script>
125
+ ${wrappedGameCode}
126
+ </script>
127
+ <script type="module">
128
+ const btn = document.getElementById("startBtn");
129
+
130
+ btn.addEventListener("click", async () => {
131
+ // 🔊 Audio freischalten
132
+ const AudioContext = window.AudioContext || window.webkitAudioContext;
133
+
134
+ const ctx = new AudioContext();
135
+ await ctx.resume();
136
+
137
+ // 👉 HIER rein!
138
+ window.__audioCtx = ctx;
139
+
140
+ // Startscreen entfernen
141
+ document.getElementById("startscreen").remove();
142
+
143
+ // Initialize the game
144
+ window.__initializeGame();
145
+ });
146
+ </script>
147
+ ${fullscreenBtn}
148
+ </body>
149
+ </html>
150
+ `;
151
+ return defaulthtml;
152
+ }
153
+ export function GetDefaultHTML(config) {
154
+ let frameworkVersion = version();
155
+ let fullscreenbutton = "";
156
+ let fullscreenBtn = "";
157
+ if (config.show_fullscreen_button) {
158
+ fullscreenbutton = `#fullscreenBtn {
159
+ position: fixed;
160
+ top: 10px;
161
+ right: 10px;
162
+
163
+ padding: 10px 15px;
164
+ font-size: 16px;
165
+
166
+ background: rgba(0, 0, 0, 0.6);
167
+ color: white;
168
+ border: none;
169
+ border-radius: 6px;
170
+
171
+ cursor: pointer;
172
+ z-index: 1000;
173
+ }
174
+
175
+ #fullscreenBtn:hover {
176
+ background: rgba(0, 0, 0, 0.8);
177
+ }`;
178
+ fullscreenBtn = `<!-- Button to make it fullscreen -->
179
+ <button id="fullscreenBtn">⛶ Fullscreen</button>`;
180
+ }
181
+ const defaulthtml = `<!-- HTML Web Game made with samengine by Shadowdara -->
182
+ <!-- DO NOT REMOVE THIS NOTE ! -->
183
+ <!DOCTYPE html>
184
+ <html>
185
+ <head>
186
+ <meta charset="UTF-8" />
187
+ <title>${config.title}</title>
188
+ <!-- Für Mobile Viewports -->
189
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
190
+ <style>
191
+ * {
192
+ margin: 0;
193
+ padding: 0;
194
+ box-sizing: border-box;
195
+ }
196
+ body {
197
+ overflow: hidden; /* 🔥 verhindert Scrollbars */
198
+ }
199
+ body {
200
+ margin: 0;
201
+ background: #0f172a;
202
+ color: white;
203
+ font-family: sans-serif;
204
+ display: flex;
205
+ justify-content: center;
206
+ align-items: center;
207
+ height: 100vh;
208
+ }
209
+
210
+ #startscreen {
211
+ text-align: center;
212
+ }
213
+
214
+ h1 {
215
+ font-size: 3rem;
216
+ margin-bottom: 0.5rem;
217
+ }
218
+
219
+ h2 {
220
+ font-weight: normal;
221
+ opacity: 0.7;
222
+ }
223
+
224
+ .startbutton {
225
+ margin-top: 2rem;
226
+ padding: 1rem 2rem;
227
+ font-size: 1.2rem;
228
+ background: #22c55e;
229
+ border: none;
230
+ border-radius: 8px;
231
+ cursor: pointer;
232
+ }
233
+
234
+ .startbutton:hover {
235
+ background: #16a34a;
236
+ }
237
+ ${fullscreenbutton}
238
+ </style>
239
+ </style>
240
+ </head>
241
+ <body>
242
+ <div id="startscreen">
243
+ <h2>made with samengine</h2>
244
+ <h1>${config.title}</h1>
245
+ <p>${config.version}</p>
246
+
247
+ <button class="startbutton" id="startBtn">Start</button>
248
+ </div>
249
+
250
+ <script type="module">
251
+ const btn = document.getElementById("startBtn");
252
+
253
+ btn.addEventListener("click", async () => {
254
+ // 🔊 Audio freischalten
255
+ const AudioContext = window.AudioContext || window.webkitAudioContext;
256
+
257
+ const ctx = new AudioContext();
258
+ await ctx.resume();
259
+
260
+ // 👉 HIER rein!
261
+ window.__audioCtx = ctx;
262
+
263
+ // Startscreen entfernen
264
+ document.getElementById("startscreen").remove();
265
+
266
+ // Game laden
267
+ import("./${config.entryname}.js");
268
+ });
269
+
270
+ window.__samengine__ = {
271
+ version: "${frameworkVersion}"
272
+ };
273
+ </script>
274
+ ${fullscreenBtn}
275
+ </body>
276
+ </html>
277
+ `;
278
+ return defaulthtml;
279
+ }
@@ -0,0 +1,2 @@
1
+ export type { buildconfig } from "./buildconfig.js";
2
+ export { new_buildconfig } from "./buildconfig.js";
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { new_buildconfig } from "./buildconfig.js";
@@ -0,0 +1 @@
1
+ export { compressHTML } from "./utils.js";
@@ -0,0 +1,2 @@
1
+ // Build Utils
2
+ export { compressHTML } from "./utils.js";
@@ -0,0 +1 @@
1
+ export declare function compressHTML(html: string): Promise<string>;
@@ -0,0 +1,12 @@
1
+ import { minify } from "html-minifier-terser";
2
+ // Function to compress HTML
3
+ export async function compressHTML(html) {
4
+ return await minify(html, {
5
+ collapseWhitespace: true,
6
+ removeComments: true,
7
+ removeRedundantAttributes: true,
8
+ removeEmptyAttributes: true,
9
+ minifyCSS: true,
10
+ minifyJS: true,
11
+ });
12
+ }
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "samengine-build",
3
+ "version": "1.6.3",
4
+ "description": "The build and export Tool for samengine.",
5
+ "sideEffects": false,
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "pack": "node ../scripts/clean.js && npm run build && npm pack && git push --follow-tags && node ../scripts/checkversion.js"
12
+ },
13
+ "exports": {
14
+ ".": "./dist/index.js",
15
+ "./utils": "./dist/utils/index.js"
16
+ },
17
+ "bin": {
18
+ "samengine-build": "dist/cli/cli.js"
19
+ },
20
+ "author": "Shadowdara",
21
+ "type": "module",
22
+ "devDependencies": {
23
+ "@types/html-minifier-terser": "^7.0.2",
24
+ "@types/node": "^25.5.2",
25
+ "@types/ws": "^8.18.1",
26
+ "typescript": "^6.0.2"
27
+ },
28
+ "dependencies": {
29
+ "chalk": "^5.6.2",
30
+ "esbuild": "^0.28.0",
31
+ "html-minifier-terser": "^7.2.0",
32
+ "mime": "^4.1.0",
33
+ "samengine": "^1.6.3",
34
+ "ws": "^8.20.0"
35
+ },
36
+ "license": "MIT",
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "https://github.com/shadowdara/samengine"
40
+ },
41
+ "bugs": {
42
+ "url": "https://github.com/shadowdara/samengine/issues"
43
+ }
44
+ }