Publish Forma Engine 0.3.0 source with documentation and CI

This commit is contained in:
emil28092005
2026-09-09 15:49:08 +03:00
commit e52bc0e33b
70 changed files with 19610 additions and 0 deletions
+73
View File
@@ -0,0 +1,73 @@
import { promises as fs } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { unpackProject, decodeData } from "../engine/archive.ts";
import { BuildManager } from "../server/builds.ts";
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const arg = (n: string) => {
const i = process.argv.indexOf(n);
return i < 0 ? undefined : process.argv[i + 1];
};
const input = path.resolve(arg("--project") || "projects/MyGame");
let manager: BuildManager | undefined;
try {
const isDir = (await fs.stat(input)).isDirectory();
const project = unpackProject(
new Uint8Array(
await fs.readFile(isDir ? path.join(input, "project.forma.json") : input),
),
);
const base = isDir ? input : path.dirname(input);
const read = async (uri: string) => {
if (uri.startsWith("data:")) return decodeData(uri);
const clean = uri.replace(/^\/+/, "").replace(/^\.\//, "");
if (!/^(assets|engine)\/[a-zA-Z0-9_.-]+$/.test(clean))
throw Error("Invalid asset path");
const safe = async (folder: string) => {
const file = await fs.realpath(path.join(folder, clean));
if (!file.startsWith((await fs.realpath(folder)) + path.sep))
throw Error("Asset escapes project");
return new Uint8Array(await fs.readFile(file));
};
if (clean.startsWith("assets/"))
try {
return await safe(base);
} catch (e: any) {
if (e.code !== "ENOENT") throw e;
}
return safe(path.join(root, "public"));
};
manager = new BuildManager(base, read);
await manager.init();
const options = {
target: arg("--target") || "linux",
name: arg("--name") || project.name,
appId: arg("--app-id") || "games.forma.mygame",
version: arg("--version") || "1.0.0",
versionCode: Number(arg("--version-code") || 1),
mode: arg("--mode") || "debug",
fullscreen: process.argv.includes("--fullscreen"),
};
const job = await manager.start(project, options, project.revision);
let shown = 0;
const stop = () => void manager!.cancel(job.id);
process.once("SIGINT", stop);
process.once("SIGTERM", stop);
for (;;) {
const j = manager.get(job.id);
for (const line of j.logs.slice(shown)) console.log(line);
shown = j.logs.length;
if (!["queued", "building"].includes(j.status)) {
if (j.status !== "succeeded") throw Error(j.error || j.status);
for (const file of j.artifacts || [])
console.log(await manager.artifact(j.id, file.name));
break;
}
await new Promise((r) => setTimeout(r, 750));
}
} catch (e) {
console.error(String(e));
process.exitCode = 1;
} finally {
await manager?.close();
}
+66
View File
@@ -0,0 +1,66 @@
import { build } from "esbuild";
import { readFile, writeFile, mkdir, cp, rm } from "node:fs/promises";
await writeFile(
"engine/script-host.ts",
"export const workerSource=" +
JSON.stringify(await readFile("engine/script-worker.js", "utf8")) +
";\n",
);
await mkdir("public/engine", { recursive: true });
await build({
entryPoints: ["engine/player-entry.ts"],
bundle: true,
format: "esm",
target: "es2022",
minify: true,
outfile: "public/engine/player.js",
logLevel: "warning",
});
await mkdir("public/studio", { recursive: true });
await build({
entryPoints: ["editor/local.tsx"],
bundle: true,
format: "esm",
target: "es2022",
minify: true,
outfile: "public/studio/editor.js",
define: { "process.env.NODE_ENV": '"production"' },
logLevel: "warning",
});
await writeFile(
"public/studio/index.html",
'<!doctype html><html lang="ru"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover"><title>Forma Engine</title><link rel="icon" href="/favicon.svg"><link rel="stylesheet" href="/studio/editor.css"></head><body><div id="root"></div><script type="module" src="/studio/editor.js"></script></body></html>',
);
console.log("Editor and standalone player built.");
const nativeFiles = [
"package.json",
"package-lock.json",
"build.mjs",
"cleanup.cjs",
"options.mjs",
"setup-android.mjs",
"desktop/main.cjs",
"desktop/protocol.cjs",
"desktop/icon.png",
"desktop/icon.ico",
"android/settings.gradle",
"android/build.gradle",
"android/gradle.properties",
"android/app/build.gradle",
"android/app/src/main/AndroidManifest.xml",
"android/app/src/main/res/drawable/forma_icon.xml",
"android/app/src/main/java/com/forma/shell/MainActivity.java",
];
await rm("public/build-targets", { recursive: true, force: true });
for (const file of nativeFiles) {
await mkdir(
"public/build-targets/" + file.split("/").slice(0, -1).join("/"),
{ recursive: true },
);
await cp("native/" + file, "public/build-targets/" + file);
}
await writeFile(
"public/build-targets/manifest.json",
JSON.stringify(nativeFiles),
);