samengine 1.9.1 → 1.10.1

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.
Files changed (89) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +168 -0
  3. package/dist/config/buildconfig.d.ts +146 -0
  4. package/dist/config/buildconfig.js +115 -0
  5. package/dist/config/index.d.ts +9 -0
  6. package/dist/config/index.js +1 -0
  7. package/dist/core.d.ts +17 -0
  8. package/dist/core.js +24 -0
  9. package/dist/html.d.ts +29 -0
  10. package/dist/html.js +20 -0
  11. package/dist/input.d.ts +51 -0
  12. package/dist/input.js +44 -3
  13. package/dist/keys.d.ts +6 -0
  14. package/dist/keys.js +6 -2
  15. package/dist/logger.d.ts +8 -0
  16. package/dist/logger.js +8 -1
  17. package/dist/nonbrowser/getversion.d.ts +13 -0
  18. package/dist/nonbrowser/getversion.js +35 -0
  19. package/dist/nonbrowser/ghresolver.d.ts +1 -0
  20. package/dist/nonbrowser/ghresolver.js +7 -0
  21. package/dist/nonbrowser/index.d.ts +9 -0
  22. package/dist/nonbrowser/index.js +9 -0
  23. package/dist/nonbrowser/internal/buildhelper.d.ts +42 -0
  24. package/dist/nonbrowser/internal/buildhelper.js +144 -0
  25. package/dist/nonbrowser/internal/cli/argparser.d.ts +18 -0
  26. package/dist/nonbrowser/internal/cli/argparser.js +36 -0
  27. package/dist/nonbrowser/internal/cli/main.d.ts +13 -0
  28. package/dist/nonbrowser/internal/cli/main.js +265 -0
  29. package/dist/nonbrowser/internal/config.d.ts +9 -0
  30. package/dist/nonbrowser/internal/config.js +40 -0
  31. package/dist/nonbrowser/internal/exporthtml.d.ts +37 -0
  32. package/dist/nonbrowser/internal/exporthtml.js +622 -0
  33. package/dist/nonbrowser/utils.d.ts +8 -0
  34. package/dist/nonbrowser/utils.js +18 -0
  35. package/dist/physics/collision.d.ts +33 -0
  36. package/dist/physics/collision.js +27 -0
  37. package/dist/physics/physicsEngine.d.ts +18 -0
  38. package/dist/physics/physicsEngine.js +18 -0
  39. package/dist/physics/physicsObject.d.ts +20 -0
  40. package/dist/physics/physicsObject.js +20 -0
  41. package/dist/renderer.d.ts +78 -0
  42. package/dist/renderer.js +72 -9
  43. package/dist/samegui/index.d.ts +29 -0
  44. package/dist/samegui/index.js +26 -0
  45. package/dist/save.d.ts +12 -0
  46. package/dist/save.js +10 -0
  47. package/dist/sound/audioplayer.d.ts +39 -0
  48. package/dist/sound/audioplayer.js +39 -5
  49. package/dist/storage/index.d.ts +40 -2
  50. package/dist/storage/index.js +34 -3
  51. package/dist/text/index.d.ts +14 -0
  52. package/dist/text/index.js +58 -0
  53. package/dist/texture.d.ts +100 -0
  54. package/dist/texture.js +75 -41
  55. package/dist/types/button.d.ts +25 -0
  56. package/dist/types/button.js +22 -0
  57. package/dist/types/circle.d.ts +26 -0
  58. package/dist/types/circle.js +21 -7
  59. package/dist/types/color.d.ts +17 -0
  60. package/dist/types/color.js +11 -1
  61. package/dist/types/index.d.ts +1 -1
  62. package/dist/types/index.js +1 -1
  63. package/dist/types/rectangle.d.ts +29 -0
  64. package/dist/types/rectangle.js +23 -7
  65. package/dist/types/triangle.d.ts +23 -0
  66. package/dist/types/triangle.js +20 -6
  67. package/dist/types/vector2d.d.ts +42 -0
  68. package/dist/types/vector2d.js +39 -11
  69. package/dist/types/vector3d.d.ts +38 -0
  70. package/dist/types/vector3d.js +35 -11
  71. package/dist/utils/index.d.ts +11 -4
  72. package/dist/utils/index.js +11 -4
  73. package/dist/utils/logger/index.d.ts +24 -0
  74. package/dist/utils/logger/index.js +44 -0
  75. package/dist/utils/math.d.ts +18 -0
  76. package/dist/utils/math.js +18 -4
  77. package/package.json +29 -11
  78. package/dist/utils/csv/index.d.ts +0 -3
  79. package/dist/utils/csv/index.js +0 -2
  80. package/dist/utils/csv/parser.d.ts +0 -25
  81. package/dist/utils/csv/parser.js +0 -212
  82. package/dist/utils/csv/stringifier.d.ts +0 -30
  83. package/dist/utils/csv/stringifier.js +0 -130
  84. package/dist/utils/csv/types.d.ts +0 -63
  85. package/dist/utils/csv/types.js +0 -1
  86. package/dist/utils/jsonc-parser.d.ts +0 -4
  87. package/dist/utils/jsonc-parser.js +0 -166
  88. package/dist/utils/markdown.d.ts +0 -41
  89. package/dist/utils/markdown.js +0 -699
@@ -0,0 +1,265 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI entry point for `samengine-build`.
4
+ *
5
+ * High-level flow:
6
+ * 1. Parse CLI arguments.
7
+ * 2. Optionally create a new project.
8
+ * 3. Load `samengine.config.ts`.
9
+ * 4. Bundle the game with esbuild.
10
+ * 5. Generate HTML and handle resources.
11
+ * 6. In development mode, start a local server and watch for changes.
12
+ */
13
+ import { build as esbuild } from "esbuild";
14
+ import { createServer } from "http";
15
+ import { readFile, writeFile, mkdir, rm } from "fs/promises";
16
+ import { watch, watchFile } from "fs";
17
+ import path from "path";
18
+ import { WebSocketServer } from "ws";
19
+ import { copyFolder, flog, getContentType, scanResourcesAsDataURIs, filterResourcesByUsage } from "./../buildhelper.js";
20
+ import { GetDefaultHTML, GetSingleFileHTML, getVersion } from "../exporthtml.js";
21
+ import { loadUserConfig } from "./../config.js";
22
+ import { compressHTML } from "../../index.js";
23
+ import { parseArgs } from "./argparser.js";
24
+ // ================= HELP ============
25
+ /**
26
+ * Function to print Help
27
+ */
28
+ function showHelp() {
29
+ console.log(`
30
+ ███████╗ █████╗ ███╗ ███╗███████╗███╗ ██╗ ██████╗ ██╗███╗ ██╗███████╗
31
+ ██╔════╝██╔══██╗████╗ ████║██╔════╝████╗ ██║██╔════╝ ██║████╗ ██║██╔════╝
32
+ ███████╗███████║██╔████╔██║█████╗ ██╔██╗ ██║██║ ███╗██║██╔██╗ ██║█████╗
33
+ ╚════██║██╔══██║██║╚██╔╝██║██╔══╝ ██║╚██╗██║██║ ██║██║██║╚██╗██║██╔══╝
34
+ ███████║██║ ██║██║ ╚═╝ ██║███████╗██║ ╚████║╚██████╔╝██║██║ ╚████║███████╗
35
+ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝╚═╝ ╚═══╝╚══════╝
36
+
37
+ CLI Tool for samengine
38
+
39
+ Usage:
40
+ -r, --release build the project in Release Mode
41
+ create create a new project
42
+ help Show this menu
43
+
44
+ More: samfile
45
+ use with @shadowdara/samtool
46
+ Install: npm i @shadowdara/samtool
47
+ `);
48
+ }
49
+ // ================= BUILD =================
50
+ /**
51
+ * Creates a build runner for one config object and one build mode.
52
+ *
53
+ * The returned `build` function owns the esbuild call, generated HTML, resource
54
+ * copying or embedding, release minification, and metadata comments. Dev builds
55
+ * keep source maps; release builds clean the output directory first.
56
+ */
57
+ function createBuilder(config, isRelease) {
58
+ // Ensure that the Directories are created
59
+ mkdir("resources", { recursive: true });
60
+ mkdir("game", { recursive: true });
61
+ async function build() {
62
+ try {
63
+ flog("🔄 Building project...");
64
+ if (isRelease)
65
+ await rm(`./${config.outdir}`, { recursive: true, force: true });
66
+ await esbuild({
67
+ entryPoints: [`./game/${config.entryname}`],
68
+ outdir: `./${config.outdir}`,
69
+ bundle: true,
70
+ platform: "browser",
71
+ minify: isRelease,
72
+ sourcemap: !isRelease,
73
+ define: { "import.meta.env.DEV": JSON.stringify(!isRelease) },
74
+ });
75
+ if (isRelease && config.releaseMode.singlefile || !isRelease && config.devMode.singlefile) {
76
+ // Single-file export
77
+ const bundledJsPath = path.join(".", config.outdir, `${config.entryname.replace(/\.[^.]*$/, "")}.js`);
78
+ const bundledJsContent = await readFile(bundledJsPath, "utf-8");
79
+ // Scan resources and convert to data URIs
80
+ let resourcesMap = await scanResourcesAsDataURIs("./resources");
81
+ // Filter resources by usage in the bundled code
82
+ resourcesMap = filterResourcesByUsage(bundledJsContent, resourcesMap);
83
+ let html = GetSingleFileHTML(config, bundledJsContent, resourcesMap);
84
+ if (isRelease)
85
+ html = await compressHTML(html);
86
+ // Add comment at the beginning after minification
87
+ const htmlComment = `<!-- Game made with samengine v${getVersion()} - https://github.com/Shadowdara/samengine ${config.gameauthor} (Game Author) -->\n`;
88
+ html = htmlComment + html;
89
+ await writeFile(`./${config.outdir}/index.html`, html);
90
+ // Delete the JS File
91
+ await rm(`./${config.outdir}/main.js`, { recursive: true, force: true });
92
+ flog("✅ Single-file export created!");
93
+ }
94
+ else {
95
+ // Multi-file export (original behavior)
96
+ let html = GetDefaultHTML(config, isRelease);
97
+ if (isRelease)
98
+ html = await compressHTML(html);
99
+ // Add HTML comment at the beginning after minification
100
+ const htmlComment = `<!-- Game made with samengine v${getVersion()} - https://www.npmjs.com/samengine ${config.gameauthor} (Game Author) -->\n`;
101
+ html = htmlComment + html;
102
+ await writeFile(`./${config.outdir}/index.html`, html);
103
+ // Add JS comment at the beginning of JS files
104
+ const jsComment = `// Game made with samengine v${getVersion()} - https://www.npmjs.com/samengine by ${config.gameauthor} (Game Author)\n`;
105
+ const jsPath = path.join(".", config.outdir, `${config.entryname.replace(/\.[^.]*$/, "")}.js`);
106
+ let jsContent = await readFile(jsPath, "utf-8");
107
+ jsContent = jsComment + jsContent;
108
+ await writeFile(jsPath, jsContent);
109
+ await copyFolder("./resources", `./${config.outdir}/resources`);
110
+ flog("✅ Build finished!");
111
+ }
112
+ }
113
+ catch (error) {
114
+ flog(`❌ Build failed: ${error instanceof Error ? error.message : String(error)}`);
115
+ }
116
+ }
117
+ return { build };
118
+ }
119
+ // ================= SERVER & RELOAD =================
120
+ /**
121
+ * Starts the local development server for the configured output directory.
122
+ *
123
+ * Static files are served from `config.outdir`. A WebSocket server is attached
124
+ * to the same HTTP server so future rebuilds can notify connected browsers.
125
+ */
126
+ function createDevServer(config) {
127
+ const sockets = new Set();
128
+ const server = createServer(async (req, res) => {
129
+ const url = req.url || "/";
130
+ const filePath = path.join(process.cwd(), `${config.outdir}`, url === "/" ? "index.html" : url);
131
+ try {
132
+ const file = await readFile(filePath);
133
+ res.writeHead(200, { "Content-Type": getContentType(filePath) });
134
+ res.end(file);
135
+ }
136
+ catch {
137
+ res.writeHead(404);
138
+ res.end("Not Found");
139
+ }
140
+ });
141
+ const wss = new WebSocketServer({ server });
142
+ wss.on("connection", (ws) => {
143
+ sockets.add(ws);
144
+ ws.on("close", () => sockets.delete(ws));
145
+ });
146
+ function startListening(port) {
147
+ server.listen(port);
148
+ server.on("listening", () => {
149
+ flog(`🚀 Dev Server running on http://localhost:${port}`);
150
+ });
151
+ server.on("error", (err) => {
152
+ if (err.code === "EADDRINUSE") {
153
+ flog(`⚠️ Port ${port} is already in use, trying ${port + 1}...`);
154
+ startListening(port + 1);
155
+ }
156
+ else {
157
+ throw err;
158
+ }
159
+ });
160
+ }
161
+ startListening(config.dev_server_port);
162
+ function reloadClients() {
163
+ flog("🔄 Browser reload...");
164
+ sockets.forEach((ws) => ws.send("reload"));
165
+ }
166
+ function stop() {
167
+ flog("🛑 Stopping dev server...");
168
+ sockets.forEach(ws => ws.close());
169
+ wss.close();
170
+ server.close();
171
+ }
172
+ return { reloadClients, stop };
173
+ }
174
+ // ================= WATCHER =================
175
+ /**
176
+ * Watches the `resources` and `game` folders recursively.
177
+ *
178
+ * The watcher delegates rebuild scheduling to `onChange`. The main function
179
+ * serializes rebuilds so multiple file-system events do not run overlapping
180
+ * builds.
181
+ */
182
+ async function startWatcher(onChange) {
183
+ await mkdir("resources", { recursive: true });
184
+ await mkdir("game", { recursive: true });
185
+ ["resources", "game"].forEach((dir) => {
186
+ watch(dir, { recursive: true }, async () => {
187
+ flog(`📁 Change noticed in ${dir}`);
188
+ await onChange();
189
+ });
190
+ });
191
+ flog("👀 Watcher active...");
192
+ }
193
+ // ================= CLI APP =================
194
+ /**
195
+ * Runs the CLI application.
196
+ *
197
+ * Development mode starts with one build, then creates a server and watchers.
198
+ * Config changes trigger a full restart because output folder, port, menu, or
199
+ * other generated HTML settings may have changed.
200
+ */
201
+ async function main() {
202
+ const args = parseArgs();
203
+ if (args.newProject) {
204
+ console.log(`to create a new project run:
205
+
206
+ npx create-samengine-project
207
+
208
+ and then select your template!`);
209
+ process.exit(0);
210
+ }
211
+ if (args.help) {
212
+ showHelp();
213
+ process.exit(0);
214
+ }
215
+ const config = await loadUserConfig();
216
+ let builder = createBuilder(config, args.release);
217
+ let isBuilding = false;
218
+ let pendingRestart = false;
219
+ async function restart() {
220
+ if (isBuilding) {
221
+ pendingRestart = true;
222
+ return;
223
+ }
224
+ isBuilding = true;
225
+ do {
226
+ pendingRestart = false;
227
+ try {
228
+ // Load the New Config
229
+ const newConfig = await loadUserConfig();
230
+ // Dev Server Stoppen
231
+ devServer?.stop();
232
+ // Create new Dev Server
233
+ devServer = createDevServer(newConfig);
234
+ // New Builder (use the new Config)
235
+ builder = createBuilder(newConfig, args.release);
236
+ await builder.build();
237
+ devServer?.reloadClients();
238
+ }
239
+ catch (error) {
240
+ flog(`❌ Rebuild failed: ${error instanceof Error ? error.message : String(error)}`);
241
+ }
242
+ } while (pendingRestart);
243
+ isBuilding = false;
244
+ }
245
+ try {
246
+ await builder.build();
247
+ }
248
+ catch (error) {
249
+ flog(`❌ Initial build failed: ${error instanceof Error ? error.message : String(error)}`);
250
+ }
251
+ let devServer = null;
252
+ // Only start the Dev Server in Release Mode
253
+ if (!args.release) {
254
+ devServer = createDevServer(config);
255
+ // Watch the config separately because changing it can require a server restart.
256
+ watchFile("samengine.config.ts", { interval: 300 }, async () => {
257
+ flog("⚙️ Config file changed → full restart");
258
+ await restart();
259
+ });
260
+ await startWatcher(restart);
261
+ }
262
+ // Dev or Release Mode
263
+ flog(`Build finished! Mode: ${args.release ? "Release" : "Dev"}`);
264
+ }
265
+ main();
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Loads `samengine.config.ts` from the current project directory.
3
+ *
4
+ * Node cannot import TypeScript config files directly, so the file is bundled to
5
+ * `.samengine/config.mjs` with esbuild first. The generated file is then loaded
6
+ * through a dynamic ESM import. A timestamp query parameter bypasses Node's
7
+ * import cache, which lets the watch mode see changed config values immediately.
8
+ */
9
+ export declare function loadUserConfig(): Promise<any>;
@@ -0,0 +1,40 @@
1
+ import { build } from "esbuild";
2
+ import path from "path";
3
+ import { pathToFileURL } from "url";
4
+ import fs from "fs/promises";
5
+ /**
6
+ * Loads `samengine.config.ts` from the current project directory.
7
+ *
8
+ * Node cannot import TypeScript config files directly, so the file is bundled to
9
+ * `.samengine/config.mjs` with esbuild first. The generated file is then loaded
10
+ * through a dynamic ESM import. A timestamp query parameter bypasses Node's
11
+ * import cache, which lets the watch mode see changed config values immediately.
12
+ */
13
+ export async function loadUserConfig() {
14
+ const root = process.cwd();
15
+ const configPath = path.resolve(root, "samengine.config.ts");
16
+ const outDir = path.resolve(root, ".samengine");
17
+ const outfile = path.join(outDir, "config.mjs");
18
+ try {
19
+ // ensure folder exists
20
+ await fs.mkdir(outDir, { recursive: true });
21
+ // 🔥 bundle TS → JS
22
+ await build({
23
+ entryPoints: [configPath],
24
+ outfile,
25
+ bundle: true,
26
+ platform: "node",
27
+ format: "esm",
28
+ });
29
+ // 🔥 import compiled file
30
+ const mod = await import(pathToFileURL(outfile).href + `?t=${Date.now()}`);
31
+ const config = typeof mod.default === "function"
32
+ ? await mod.default()
33
+ : mod.default;
34
+ return config;
35
+ }
36
+ catch (e) {
37
+ console.error(e);
38
+ throw new Error("❌ Could not load samengine.config.ts: " + configPath);
39
+ }
40
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * HTML generation for samengine-build.
3
+ *
4
+ * This module creates the complete `index.html` written into the output folder.
5
+ * There are two build shapes:
6
+ *
7
+ * - `GetDefaultHTML` creates a normal multi-file page that imports the bundled
8
+ * game JavaScript after the player clicks the start button.
9
+ * - `GetSingleFileHTML` embeds the bundled JavaScript and optional resource
10
+ * Data URIs directly into one HTML document.
11
+ */
12
+ import type { buildconfig } from "../../config/buildconfig.js";
13
+ /**
14
+ * Function to get the samengine Version
15
+ *
16
+ * @deprecated WARN Function should not be used outside samengine. Use getPackageVersion instead!
17
+ */
18
+ export declare function getVersion(): string;
19
+ /**
20
+ * Creates a complete single-file HTML document.
21
+ *
22
+ * `bundledJsContent` is the already bundled game code from esbuild.
23
+ * `resourcesMap` contains optional Data URIs that are exposed through
24
+ * `window.__resources`, `window.__getResource`, and `window.__loadResource`.
25
+ *
26
+ * The bundled game is wrapped in `window.__initializeGame` so it does not run
27
+ * until the player clicks the start button. This allows the generated page to
28
+ * show the start screen, unlock audio, and remove temporary UI first.
29
+ */
30
+ export declare function GetSingleFileHTML(config: buildconfig, bundledJsContent: string, resourcesMap?: Record<string, string>): string;
31
+ /**
32
+ * Creates the normal multi-file HTML document.
33
+ *
34
+ * This page contains the generated start screen and optional menu UI. The game
35
+ * bundle is loaded with a dynamic import only after the start button is clicked.
36
+ */
37
+ export declare function GetDefaultHTML(config: buildconfig, releasemode: boolean): string;