@genex-ai/cli-demo 0.7.0 → 0.10.0
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 +13 -12
- package/dist/index.js +410 -154
- package/package.json +10 -11
- package/templates/README.md +8 -7
- package/templates/skills/genex-ai-model/SKILL.md +5 -5
- package/templates/skills/genex-ai-sfx/SKILL.md +1 -1
- package/templates/skills/genex-ai-skybox/SKILL.md +2 -2
- package/templates/skills/genex-ai-texture/SKILL.md +1 -1
- package/templates/skills/genex-getting-started/SKILL.md +10 -6
- package/templates/skills/genex-threejs-embed-auth/SKILL.md +174 -0
- package/templates/skills/genex-threejs-multiplayer/SKILL.md +193 -100
- package/templates/skills/genex-threejs-multiplayer/references/genre-recipes.md +115 -0
- package/templates/skills/genex-threejs-multiplayer/references/realtime-patterns.md +138 -123
- package/templates/skills/genex-threejs-precipitation-surfaces/SKILL.md +59 -0
- package/templates/skills/genex-threejs-precipitation-surfaces/references/precipitation-surfaces.md +181 -0
- package/templates/skills/genex-threejs-procedural-architecture/SKILL.md +2 -1
- package/templates/skills/genex-threejs-procedural-architecture/references/architecture-systems.md +1 -1
- package/templates/skills/genex-threejs-procedural-fields/SKILL.md +0 -5
- package/templates/skills/genex-threejs-procedural-geometry/SKILL.md +0 -5
- package/templates/skills/genex-threejs-procedural-materials/SKILL.md +2 -17
- package/templates/skills/genex-threejs-procedural-vegetation/SKILL.md +11 -1
- package/templates/skills/genex-threejs-procedural-vfx/SKILL.md +3 -1
- package/templates/skills/genex-threejs-shadow-systems/SKILL.md +0 -5
- package/templates/skills/genex-threejs-skill-router/SKILL.md +10 -5
- package/templates/skills/genex-threejs-skill-router/references/routing-map.md +9 -6
- package/templates/skills/genex-threejs-spectral-ocean/SKILL.md +11 -1
- package/templates/skills/genex-threejs-temporal-surfaces/SKILL.md +3 -1
- package/templates/skills/genex-threejs-water-optics/SKILL.md +14 -2
package/dist/index.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { readFileSync } from "fs";
|
|
5
|
-
import
|
|
5
|
+
import path12 from "path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7
7
|
|
|
8
8
|
// src/commands/init.ts
|
|
9
|
-
import
|
|
9
|
+
import path7 from "path";
|
|
10
10
|
|
|
11
11
|
// src/config.ts
|
|
12
12
|
import fs from "fs";
|
|
@@ -125,7 +125,7 @@ import http from "http";
|
|
|
125
125
|
import crypto from "crypto";
|
|
126
126
|
import readline from "readline";
|
|
127
127
|
import { spawn } from "child_process";
|
|
128
|
-
import { URL } from "url";
|
|
128
|
+
import { URL as URL2 } from "url";
|
|
129
129
|
var SUCCESS_HTML = `<!doctype html><html><head><meta charset="utf-8"><title>Genex</title>
|
|
130
130
|
<style>body{font-family:system-ui,sans-serif;background:#0b0b0f;color:#eaeaea;display:grid;place-items:center;height:100vh;margin:0}
|
|
131
131
|
.card{text-align:center;padding:2rem 3rem;border:1px solid #26262e;border-radius:14px;background:#13131a}
|
|
@@ -216,7 +216,7 @@ async function authorize(authBaseUrl, options) {
|
|
|
216
216
|
});
|
|
217
217
|
}
|
|
218
218
|
function buildAuthUrl(authBaseUrl, redirectUri, state) {
|
|
219
|
-
const url = new
|
|
219
|
+
const url = new URL2(`${authBaseUrl}/cli/auth`);
|
|
220
220
|
url.searchParams.set("redirect_uri", redirectUri);
|
|
221
221
|
url.searchParams.set("state", state);
|
|
222
222
|
return url.toString();
|
|
@@ -225,7 +225,7 @@ async function handleCallback(req, res, expectedState, log) {
|
|
|
225
225
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
226
226
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
227
227
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
228
|
-
const reqUrl = new
|
|
228
|
+
const reqUrl = new URL2(req.url ?? "/", "http://127.0.0.1");
|
|
229
229
|
if (req.method === "OPTIONS") {
|
|
230
230
|
res.writeHead(204);
|
|
231
231
|
res.end();
|
|
@@ -438,7 +438,10 @@ async function createDraftProject(opts) {
|
|
|
438
438
|
apiUrl,
|
|
439
439
|
colyseusUrl,
|
|
440
440
|
playUrl: project.playUrl ?? void 0,
|
|
441
|
-
status: "draft"
|
|
441
|
+
status: "draft",
|
|
442
|
+
// Derived client-side from the auth/web origin the CLI just authorized
|
|
443
|
+
// against (respects --auth-url overrides) — no API contract change.
|
|
444
|
+
dashboardOrigins: [new URL(dashboardUrl).origin]
|
|
442
445
|
};
|
|
443
446
|
}
|
|
444
447
|
log.warn("Couldn't create a project with a unique name. Try `--name <unique>`.");
|
|
@@ -639,6 +642,70 @@ async function writeProject(meta, cwd = process.cwd()) {
|
|
|
639
642
|
return { path: file };
|
|
640
643
|
}
|
|
641
644
|
|
|
645
|
+
// src/lib/game-config.ts
|
|
646
|
+
import fs6 from "fs/promises";
|
|
647
|
+
import path6 from "path";
|
|
648
|
+
var DEFAULT_DASHBOARD_ORIGIN = new URL(DEFAULT_AUTH_URL).origin;
|
|
649
|
+
function renderGenexConfig() {
|
|
650
|
+
return `// src/genex.config.ts \u2014 written by \`genex init\`. DO NOT hardcode URLs here.
|
|
651
|
+
//
|
|
652
|
+
// Values come from Vite env:
|
|
653
|
+
// .env -> VITE_GENEX_SLUG (this game's identity; committed)
|
|
654
|
+
// .env.development.local -> local-stack URL overrides (dev mode ONLY \u2014 a
|
|
655
|
+
// production build always uses the defaults below;
|
|
656
|
+
// gitignored via *.local, never committed)
|
|
657
|
+
export const GENEX = {
|
|
658
|
+
slug: import.meta.env.VITE_GENEX_SLUG as string,
|
|
659
|
+
apiUrl: (import.meta.env.VITE_GENEX_API_URL as string | undefined) ?? "${DEFAULT_API_URL}",
|
|
660
|
+
colyseusUrl: (import.meta.env.VITE_GENEX_COLYSEUS_URL as string | undefined) ?? "${DEFAULT_COLYSEUS_URL}",
|
|
661
|
+
dashboardOrigins: ((import.meta.env.VITE_GENEX_DASHBOARD_ORIGINS as string | undefined) ?? "${DEFAULT_DASHBOARD_ORIGIN}").split(","),
|
|
662
|
+
} as const;
|
|
663
|
+
`;
|
|
664
|
+
}
|
|
665
|
+
function renderSlugEnv(slug) {
|
|
666
|
+
return `# Genex game identity \u2014 committed; environment-independent.
|
|
667
|
+
VITE_GENEX_SLUG=${slug}
|
|
668
|
+
`;
|
|
669
|
+
}
|
|
670
|
+
function renderDevOverrides(meta) {
|
|
671
|
+
const dashboardOrigins = meta.dashboardOrigins?.join(",") ?? DEFAULT_DASHBOARD_ORIGIN;
|
|
672
|
+
const overrides = [];
|
|
673
|
+
if (meta.apiUrl !== DEFAULT_API_URL) overrides.push(`VITE_GENEX_API_URL=${meta.apiUrl}`);
|
|
674
|
+
if (meta.colyseusUrl !== DEFAULT_COLYSEUS_URL) {
|
|
675
|
+
overrides.push(`VITE_GENEX_COLYSEUS_URL=${meta.colyseusUrl}`);
|
|
676
|
+
}
|
|
677
|
+
if (dashboardOrigins !== DEFAULT_DASHBOARD_ORIGIN) {
|
|
678
|
+
overrides.push(`VITE_GENEX_DASHBOARD_ORIGINS=${dashboardOrigins}`);
|
|
679
|
+
}
|
|
680
|
+
if (overrides.length === 0) return null;
|
|
681
|
+
return `# Local-stack overrides \u2014 DEV MODE ONLY (vite build ignores this file).
|
|
682
|
+
# Written by \`genex init\` because it ran against a non-default stack.
|
|
683
|
+
${overrides.join("\n")}
|
|
684
|
+
`;
|
|
685
|
+
}
|
|
686
|
+
async function writeIfAbsent(file, content, log) {
|
|
687
|
+
try {
|
|
688
|
+
await fs6.writeFile(file, content, { flag: "wx" });
|
|
689
|
+
log.dim(` wrote ${c.cyan(file)}`);
|
|
690
|
+
return true;
|
|
691
|
+
} catch (err) {
|
|
692
|
+
if (err.code === "EEXIST") {
|
|
693
|
+
log.dim(` kept existing ${c.cyan(file)}`);
|
|
694
|
+
return false;
|
|
695
|
+
}
|
|
696
|
+
throw err;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
async function writeGameConfigFiles(meta, log, cwd = process.cwd()) {
|
|
700
|
+
await fs6.mkdir(path6.join(cwd, "src"), { recursive: true });
|
|
701
|
+
await writeIfAbsent(path6.join(cwd, "src", "genex.config.ts"), renderGenexConfig(), log);
|
|
702
|
+
await writeIfAbsent(path6.join(cwd, ".env"), renderSlugEnv(meta.slug), log);
|
|
703
|
+
const overrides = renderDevOverrides(meta);
|
|
704
|
+
if (overrides) {
|
|
705
|
+
await writeIfAbsent(path6.join(cwd, ".env.development.local"), overrides, log);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
642
709
|
// src/utils/logger.ts
|
|
643
710
|
function createLogger(opts = {}) {
|
|
644
711
|
const out = (s) => {
|
|
@@ -671,8 +738,8 @@ async function runInit(opts) {
|
|
|
671
738
|
let totalNew = 0;
|
|
672
739
|
let totalUpdated = 0;
|
|
673
740
|
for (const t of targets) {
|
|
674
|
-
const src = t.full ? templatesDir :
|
|
675
|
-
const dest = t.full ? t.baseDir :
|
|
741
|
+
const src = t.full ? templatesDir : path7.join(templatesDir, "skills");
|
|
742
|
+
const dest = t.full ? t.baseDir : path7.join(t.baseDir, "skills");
|
|
676
743
|
const { copied, updated } = await copyTemplates(src, dest, { force: opts.force });
|
|
677
744
|
const added = copied.length - updated.length;
|
|
678
745
|
totalNew += added;
|
|
@@ -716,7 +783,7 @@ async function runInit(opts) {
|
|
|
716
783
|
}
|
|
717
784
|
const apiUrl = getApiUrl(opts.apiUrl);
|
|
718
785
|
const colyseusUrl = getColyseusUrl(opts.colyseusUrl);
|
|
719
|
-
const projectName = opts.name?.trim() ||
|
|
786
|
+
const projectName = opts.name?.trim() || path7.basename(process.cwd());
|
|
720
787
|
const meta = await createDraftProject({
|
|
721
788
|
apiUrl,
|
|
722
789
|
token,
|
|
@@ -729,16 +796,22 @@ async function runInit(opts) {
|
|
|
729
796
|
if (meta) {
|
|
730
797
|
const { path: metaPath } = await writeProject(meta);
|
|
731
798
|
log.dim(` saved ${c.cyan(metaPath)}`);
|
|
799
|
+
await writeGameConfigFiles(meta, log);
|
|
732
800
|
}
|
|
733
801
|
log.plain("");
|
|
734
802
|
log.success("All set. \u{1F680}");
|
|
735
803
|
}
|
|
736
804
|
|
|
805
|
+
// src/commands/publish.ts
|
|
806
|
+
import fs8 from "fs/promises";
|
|
807
|
+
import path9 from "path";
|
|
808
|
+
|
|
737
809
|
// src/lib/deploy.ts
|
|
738
810
|
import { spawn as spawn4 } from "child_process";
|
|
739
|
-
import
|
|
811
|
+
import crypto3 from "crypto";
|
|
812
|
+
import fs7 from "fs/promises";
|
|
740
813
|
import os2 from "os";
|
|
741
|
-
import
|
|
814
|
+
import path8 from "path";
|
|
742
815
|
function run(cmd, args, env) {
|
|
743
816
|
return new Promise((resolve) => {
|
|
744
817
|
let child;
|
|
@@ -756,7 +829,8 @@ function run(cmd, args, env) {
|
|
|
756
829
|
child.on("close", (code2) => resolve({ code: code2 ?? -1, out, err }));
|
|
757
830
|
});
|
|
758
831
|
}
|
|
759
|
-
|
|
832
|
+
var EXCLUDE_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".genex", "dist"]);
|
|
833
|
+
async function deployGame(ctx, opts, log) {
|
|
760
834
|
const cwd = process.cwd();
|
|
761
835
|
if (!opts.noBuild && await hasBuildScript(cwd)) {
|
|
762
836
|
log.step("Building the production bundle\u2026");
|
|
@@ -769,25 +843,164 @@ async function deployGame(sshUrl, opts, log) {
|
|
|
769
843
|
}
|
|
770
844
|
log.success("Built.");
|
|
771
845
|
}
|
|
772
|
-
const distDir =
|
|
846
|
+
const distDir = path8.join(cwd, "dist");
|
|
773
847
|
const siteDir = await isDir2(distDir) ? distDir : cwd;
|
|
774
|
-
|
|
775
|
-
|
|
848
|
+
const rel = path8.relative(cwd, siteDir) || ".";
|
|
849
|
+
if (siteDir === cwd) await writeGitignore(cwd, log);
|
|
850
|
+
const files = await collectFiles(siteDir);
|
|
851
|
+
if (files.some((f) => /(^|\/)genex_key(\.pub)?$/.test(f.relPath))) {
|
|
852
|
+
log.error(`Refusing to deploy: ${KEY_NAME} is in ${rel}. Add it to .gitignore and retry.`);
|
|
853
|
+
return false;
|
|
854
|
+
}
|
|
855
|
+
if (files.length === 0) {
|
|
856
|
+
log.warn("Nothing to deploy \u2014 the build produced no files.");
|
|
857
|
+
return false;
|
|
858
|
+
}
|
|
859
|
+
if (!files.some((f) => f.relPath === "index.html")) {
|
|
860
|
+
log.error(`No index.html in ${rel} \u2014 a game needs one to serve. Nothing deployed.`);
|
|
861
|
+
return false;
|
|
862
|
+
}
|
|
863
|
+
const commit = contentCommit(files);
|
|
864
|
+
log.step("Requesting an upload token\u2026");
|
|
865
|
+
const grant = await getUploadToken(ctx, commit, log);
|
|
866
|
+
if (!grant) return false;
|
|
867
|
+
const totalBytes = files.reduce((n, f) => n + f.bytes.length, 0);
|
|
868
|
+
const mb = (n) => (n / 1048576).toFixed(n < 1048576 ? 2 : 1);
|
|
869
|
+
if (files.length > grant.limits.maxFiles) {
|
|
870
|
+
log.error(`Too many files (${files.length} > ${grant.limits.maxFiles}). Trim the build and retry.`);
|
|
871
|
+
return false;
|
|
872
|
+
}
|
|
873
|
+
const oversized = files.find((f) => f.bytes.length > grant.limits.maxFileBytes);
|
|
874
|
+
if (oversized) {
|
|
875
|
+
log.error(`${oversized.relPath} is ${mb(oversized.bytes.length)} MB \u2014 over the ${mb(grant.limits.maxFileBytes)} MB per-file limit.`);
|
|
876
|
+
return false;
|
|
877
|
+
}
|
|
878
|
+
if (totalBytes > grant.limits.maxTotalBytes) {
|
|
879
|
+
log.error(`Build is ${mb(totalBytes)} MB \u2014 over the ${mb(grant.limits.maxTotalBytes)} MB limit.`);
|
|
880
|
+
return false;
|
|
881
|
+
}
|
|
882
|
+
log.step(`Uploading ${files.length} files\u2026`);
|
|
883
|
+
if (!await uploadAll(grant.uploadUrl, grant.token, files, log)) {
|
|
884
|
+
log.error("Couldn't upload your game \u2014 please try again.");
|
|
885
|
+
return false;
|
|
886
|
+
}
|
|
887
|
+
const keyPath = path8.resolve(cwd, KEY_NAME);
|
|
888
|
+
if (!await fileExists(keyPath)) {
|
|
889
|
+
log.error(`No deploy key (${KEY_NAME}) here \u2014 run \`genex init\` in this folder first.`);
|
|
890
|
+
return false;
|
|
776
891
|
}
|
|
777
|
-
|
|
892
|
+
if (!await pushSource(cwd, ctx.sshUrl, keyPath, log)) return false;
|
|
893
|
+
log.step("Publishing\u2026");
|
|
894
|
+
if (!await callPublish(ctx, commit, log)) return false;
|
|
895
|
+
const index = files.find((f) => f.relPath === "index.html");
|
|
896
|
+
await waitUntilLive(grant.playUrl, fingerprintOf(index.bytes.toString("utf8")), opts.liveTimeoutMs ?? 2e4, log);
|
|
897
|
+
return true;
|
|
898
|
+
}
|
|
899
|
+
async function hasBuildScript(cwd) {
|
|
778
900
|
try {
|
|
779
|
-
await
|
|
901
|
+
const pkg = JSON.parse(await fs7.readFile(path8.join(cwd, "package.json"), "utf8"));
|
|
902
|
+
return Boolean(pkg.scripts?.build);
|
|
780
903
|
} catch {
|
|
781
|
-
|
|
904
|
+
return false;
|
|
782
905
|
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
const
|
|
786
|
-
|
|
787
|
-
|
|
906
|
+
}
|
|
907
|
+
async function collectFiles(root) {
|
|
908
|
+
const out = [];
|
|
909
|
+
const walk2 = async (dir, prefix) => {
|
|
910
|
+
for (const e of await fs7.readdir(dir, { withFileTypes: true })) {
|
|
911
|
+
const relPath = prefix ? `${prefix}/${e.name}` : e.name;
|
|
912
|
+
if (e.isDirectory()) {
|
|
913
|
+
if (!EXCLUDE_DIRS.has(e.name)) await walk2(path8.join(dir, e.name), relPath);
|
|
914
|
+
} else if (e.isFile() && e.name !== KEY_NAME && e.name !== `${KEY_NAME}.pub`) {
|
|
915
|
+
out.push({ relPath, bytes: await fs7.readFile(path8.join(dir, e.name)) });
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
};
|
|
919
|
+
await walk2(root, "");
|
|
920
|
+
return out;
|
|
921
|
+
}
|
|
922
|
+
function contentCommit(files) {
|
|
923
|
+
const h = crypto3.createHash("sha1");
|
|
924
|
+
for (const f of [...files].sort((a, b) => a.relPath < b.relPath ? -1 : a.relPath > b.relPath ? 1 : 0)) {
|
|
925
|
+
h.update(f.relPath, "utf8");
|
|
926
|
+
h.update("\0");
|
|
927
|
+
h.update(f.bytes);
|
|
928
|
+
}
|
|
929
|
+
return h.digest("hex");
|
|
930
|
+
}
|
|
931
|
+
async function getUploadToken(ctx, commit, log) {
|
|
932
|
+
let res;
|
|
933
|
+
try {
|
|
934
|
+
res = await fetch(`${ctx.apiUrl}/api/games/${ctx.projectId}/upload-token`, {
|
|
935
|
+
method: "POST",
|
|
936
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${ctx.token}` },
|
|
937
|
+
body: JSON.stringify({ commit })
|
|
938
|
+
});
|
|
939
|
+
} catch (err) {
|
|
940
|
+
log.error(`Couldn't reach the API at ${ctx.apiUrl}: ${String(err)}`);
|
|
941
|
+
return null;
|
|
942
|
+
}
|
|
943
|
+
if (!res.ok) {
|
|
944
|
+
log.error(`Couldn't get an upload token (HTTP ${res.status}).`);
|
|
945
|
+
return null;
|
|
946
|
+
}
|
|
947
|
+
return await res.json();
|
|
948
|
+
}
|
|
949
|
+
async function uploadAll(uploadUrl, uploadToken, files, log) {
|
|
950
|
+
const base = uploadUrl.replace(/\/+$/, "");
|
|
951
|
+
let failed = false;
|
|
952
|
+
let next = 0;
|
|
953
|
+
const worker = async () => {
|
|
954
|
+
while (!failed) {
|
|
955
|
+
const i = next++;
|
|
956
|
+
if (i >= files.length) return;
|
|
957
|
+
const f = files[i];
|
|
958
|
+
const encoded = f.relPath.split("/").map(encodeURIComponent).join("/");
|
|
959
|
+
let res = null;
|
|
960
|
+
try {
|
|
961
|
+
res = await fetch(`${base}/${encoded}`, {
|
|
962
|
+
method: "PUT",
|
|
963
|
+
headers: { Authorization: `Bearer ${uploadToken}` },
|
|
964
|
+
body: f.bytes
|
|
965
|
+
});
|
|
966
|
+
} catch {
|
|
967
|
+
res = null;
|
|
968
|
+
}
|
|
969
|
+
if (!res || !res.ok) {
|
|
970
|
+
failed = true;
|
|
971
|
+
log.warn(`Upload failed for ${f.relPath}${res ? ` (HTTP ${res.status})` : " (network error)"}.`);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
};
|
|
975
|
+
await Promise.all(Array.from({ length: Math.min(8, files.length) }, () => worker()));
|
|
976
|
+
return !failed;
|
|
977
|
+
}
|
|
978
|
+
async function callPublish(ctx, commit, log) {
|
|
979
|
+
let res;
|
|
980
|
+
try {
|
|
981
|
+
res = await fetch(`${ctx.apiUrl}/api/games/${ctx.projectId}/publish`, {
|
|
982
|
+
method: "POST",
|
|
983
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${ctx.token}` },
|
|
984
|
+
body: JSON.stringify({ commit })
|
|
985
|
+
});
|
|
986
|
+
} catch (err) {
|
|
987
|
+
log.error(`Couldn't reach the API at ${ctx.apiUrl}: ${String(err)}`);
|
|
988
|
+
return false;
|
|
989
|
+
}
|
|
990
|
+
if (!res.ok) {
|
|
991
|
+
const detail = await res.text().catch(() => "");
|
|
992
|
+
log.error(`Publish failed (HTTP ${res.status})${detail ? `: ${detail}` : ""}.`);
|
|
788
993
|
return false;
|
|
789
994
|
}
|
|
790
|
-
|
|
995
|
+
return true;
|
|
996
|
+
}
|
|
997
|
+
async function pushSource(cwd, sshUrl, keyPath, log) {
|
|
998
|
+
const EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
999
|
+
const failed = () => {
|
|
1000
|
+
log.error("Couldn't save your game's source \u2014 please try again.");
|
|
1001
|
+
return false;
|
|
1002
|
+
};
|
|
1003
|
+
const gitDir = await fs7.mkdtemp(path8.join(os2.tmpdir(), "genex-source-"));
|
|
791
1004
|
const base = { GIT_DIR: gitDir };
|
|
792
1005
|
const ident = {
|
|
793
1006
|
GIT_AUTHOR_NAME: "genex",
|
|
@@ -795,121 +1008,65 @@ async function deployGame(sshUrl, opts, log) {
|
|
|
795
1008
|
GIT_COMMITTER_NAME: "genex",
|
|
796
1009
|
GIT_COMMITTER_EMAIL: "agent@genex.local"
|
|
797
1010
|
};
|
|
798
|
-
const EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
799
1011
|
try {
|
|
800
|
-
if ((await run("git", ["init", "-q"], base)).code !== 0)
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
}
|
|
804
|
-
await fs6.writeFile(
|
|
805
|
-
path7.join(gitDir, "info", "exclude"),
|
|
1012
|
+
if ((await run("git", ["init", "-q"], base)).code !== 0) return failed();
|
|
1013
|
+
await fs7.writeFile(
|
|
1014
|
+
path8.join(gitDir, "info", "exclude"),
|
|
806
1015
|
["node_modules/", "dist/", ".git/", KEY_NAME, `${KEY_NAME}.pub`, ".genex/", ""].join("\n")
|
|
807
1016
|
);
|
|
808
|
-
const
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
if (!tracked.out.trim()) return null;
|
|
814
|
-
const tree = (await run("git", ["write-tree"], env)).out.trim();
|
|
815
|
-
return tree || null;
|
|
816
|
-
};
|
|
817
|
-
const commitTree = async (tree, message) => (await run("git", ["commit-tree", tree, "-m", message], { ...base, ...ident })).out.trim();
|
|
818
|
-
let mainTree;
|
|
819
|
-
try {
|
|
820
|
-
mainTree = await stage(siteDir, "index-main");
|
|
821
|
-
} catch {
|
|
822
|
-
log.error(`Refusing to deploy: ${KEY_NAME} is staged. Add it to .gitignore and retry.`);
|
|
1017
|
+
const env = { ...base, GIT_WORK_TREE: cwd, GIT_INDEX_FILE: path8.join(gitDir, "index-source") };
|
|
1018
|
+
await run("git", ["add", "-A"], env);
|
|
1019
|
+
const tracked = await run("git", ["ls-files"], env);
|
|
1020
|
+
if (/(^|\/)genex_key(\.pub)?$/m.test(tracked.out)) {
|
|
1021
|
+
log.error(`Refusing to publish: ${KEY_NAME} is in your project. Add it to .gitignore and retry.`);
|
|
823
1022
|
return false;
|
|
824
1023
|
}
|
|
825
|
-
|
|
826
|
-
|
|
1024
|
+
const tree = tracked.out.trim() ? (await run("git", ["write-tree"], env)).out.trim() : "";
|
|
1025
|
+
if (!tree || tree === EMPTY_TREE) {
|
|
1026
|
+
log.error("Nothing to publish \u2014 the project has no files.");
|
|
827
1027
|
return false;
|
|
828
1028
|
}
|
|
829
|
-
await run("git", ["
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
pushSource = true;
|
|
837
|
-
}
|
|
838
|
-
} catch {
|
|
839
|
-
log.dim(" (Couldn't publish source for remixing \u2014 the deploy key was in the way.)");
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
log.step("Pushing your game over SSH\u2026");
|
|
843
|
-
const refspecs = ["+refs/heads/main:main"];
|
|
844
|
-
if (pushSource) refspecs.push("+refs/heads/source:source");
|
|
845
|
-
const push = await run(
|
|
846
|
-
"git",
|
|
847
|
-
["push", "-q", ...pushSource ? ["--atomic"] : [], sshUrl, ...refspecs],
|
|
848
|
-
{
|
|
849
|
-
...base,
|
|
850
|
-
// Quote the key path: git splits GIT_SSH_COMMAND with shell-like rules, so a
|
|
851
|
-
// project folder with a space (e.g. "fly drone") otherwise breaks the `-i`
|
|
852
|
-
// argument and ssh fails with "Could not resolve hostname …".
|
|
853
|
-
GIT_SSH_COMMAND: `ssh -i "${keyPath}" -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new`
|
|
854
|
-
}
|
|
855
|
-
);
|
|
856
|
-
if (push.code === 0) {
|
|
857
|
-
log.success("Pushed.");
|
|
858
|
-
if (opts.playUrl) {
|
|
859
|
-
await waitUntilLive(opts.playUrl, await buildFingerprint(siteDir), opts.liveTimeoutMs ?? 12e4, log);
|
|
860
|
-
}
|
|
861
|
-
return true;
|
|
862
|
-
}
|
|
863
|
-
log.warn("git push failed \u2014 your live game was NOT updated.");
|
|
864
|
-
const tail = push.err.trim().split("\n").slice(-2).join(" ");
|
|
865
|
-
if (tail) log.dim(` ${tail}`);
|
|
866
|
-
return false;
|
|
867
|
-
} finally {
|
|
868
|
-
await fs6.rm(gitDir, { recursive: true, force: true }).catch(() => {
|
|
1029
|
+
const commit = (await run("git", ["commit-tree", tree, "-m", "source"], { ...base, ...ident })).out.trim();
|
|
1030
|
+
await run("git", ["update-ref", "refs/heads/main", commit], base);
|
|
1031
|
+
const push = await run("git", ["push", "-q", sshUrl, "+refs/heads/main:main"], {
|
|
1032
|
+
...base,
|
|
1033
|
+
// Quote the key path: git splits GIT_SSH_COMMAND shell-like, so a folder with a
|
|
1034
|
+
// space would otherwise break the `-i` argument.
|
|
1035
|
+
GIT_SSH_COMMAND: `ssh -i "${keyPath}" -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new`
|
|
869
1036
|
});
|
|
870
|
-
|
|
871
|
-
}
|
|
872
|
-
async function hasBuildScript(cwd) {
|
|
873
|
-
try {
|
|
874
|
-
const pkg = JSON.parse(await fs6.readFile(path7.join(cwd, "package.json"), "utf8"));
|
|
875
|
-
return Boolean(pkg.scripts?.build);
|
|
1037
|
+
return push.code === 0 ? true : failed();
|
|
876
1038
|
} catch {
|
|
877
|
-
return
|
|
1039
|
+
return failed();
|
|
1040
|
+
} finally {
|
|
1041
|
+
await fs7.rm(gitDir, { recursive: true, force: true }).catch(() => {
|
|
1042
|
+
});
|
|
878
1043
|
}
|
|
879
1044
|
}
|
|
880
1045
|
async function isDir2(p) {
|
|
881
1046
|
try {
|
|
882
|
-
return (await
|
|
1047
|
+
return (await fs7.stat(p)).isDirectory();
|
|
883
1048
|
} catch {
|
|
884
1049
|
return false;
|
|
885
1050
|
}
|
|
886
1051
|
}
|
|
887
1052
|
async function fileExists(p) {
|
|
888
1053
|
try {
|
|
889
|
-
await
|
|
1054
|
+
await fs7.access(p);
|
|
890
1055
|
return true;
|
|
891
1056
|
} catch {
|
|
892
1057
|
return false;
|
|
893
1058
|
}
|
|
894
1059
|
}
|
|
895
1060
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
896
|
-
|
|
897
|
-
let html;
|
|
898
|
-
try {
|
|
899
|
-
html = await fs6.readFile(path7.join(siteDir, "index.html"), "utf8");
|
|
900
|
-
} catch {
|
|
901
|
-
return null;
|
|
902
|
-
}
|
|
1061
|
+
function fingerprintOf(html) {
|
|
903
1062
|
const m = html.match(/[A-Za-z0-9_]+-[A-Za-z0-9_-]{8}\.(?:js|css)/);
|
|
904
1063
|
return m ? m[0] : null;
|
|
905
1064
|
}
|
|
906
1065
|
async function waitUntilLive(playUrl, fingerprint, timeoutMs, log) {
|
|
907
1066
|
if (!fingerprint) {
|
|
908
|
-
log.
|
|
909
|
-
log.dim(" (GitHub Pages rebuilds ~30\u201390s after a push.)");
|
|
1067
|
+
log.success(`Live. \u{1F310} ${playUrl}`);
|
|
910
1068
|
return;
|
|
911
1069
|
}
|
|
912
|
-
log.step("Waiting for GitHub Pages to serve the new build\u2026");
|
|
913
1070
|
const deadline = Date.now() + timeoutMs;
|
|
914
1071
|
let n = 0;
|
|
915
1072
|
while (Date.now() < deadline) {
|
|
@@ -921,25 +1078,23 @@ async function waitUntilLive(playUrl, fingerprint, timeoutMs, log) {
|
|
|
921
1078
|
}
|
|
922
1079
|
} catch {
|
|
923
1080
|
}
|
|
924
|
-
await sleep(
|
|
1081
|
+
await sleep(1500);
|
|
925
1082
|
}
|
|
926
|
-
log.
|
|
927
|
-
log.dim(
|
|
928
|
-
log.dim(" (Open it in ~30\u201360s; the new build is on its way.)");
|
|
1083
|
+
log.success(`Published. \u{1F310} ${playUrl}`);
|
|
1084
|
+
log.dim(" (If the previous build shows, hard-refresh in a few seconds.)");
|
|
929
1085
|
}
|
|
930
|
-
|
|
931
|
-
|
|
1086
|
+
|
|
1087
|
+
// src/commands/publish.ts
|
|
1088
|
+
async function detectEmbedSdkVersion(cwd = process.cwd()) {
|
|
932
1089
|
try {
|
|
933
|
-
|
|
1090
|
+
const raw = await fs8.readFile(path9.join(cwd, "package.json"), "utf8");
|
|
1091
|
+
const pkg = JSON.parse(raw);
|
|
1092
|
+
const version = pkg.dependencies?.["@genex-ai/embed-sdk"] ?? pkg.devDependencies?.["@genex-ai/embed-sdk"];
|
|
1093
|
+
return typeof version === "string" && version ? version : null;
|
|
934
1094
|
} catch {
|
|
935
|
-
return;
|
|
936
|
-
}
|
|
937
|
-
if (/(?:src|href)="\/(?!\/)/.test(html)) {
|
|
938
|
-
log.warn("Built index.html uses absolute paths \u2014 set `base: './'` in vite.config so assets load under the Pages subpath.");
|
|
1095
|
+
return null;
|
|
939
1096
|
}
|
|
940
1097
|
}
|
|
941
|
-
|
|
942
|
-
// src/commands/publish.ts
|
|
943
1098
|
async function runPublish(opts) {
|
|
944
1099
|
const log = createLogger({ quiet: opts.quiet });
|
|
945
1100
|
log.plain(c.bold("genex publish"));
|
|
@@ -957,11 +1112,18 @@ async function runPublish(opts) {
|
|
|
957
1112
|
return;
|
|
958
1113
|
}
|
|
959
1114
|
const apiUrl = getApiUrl(opts.apiUrl ?? meta.apiUrl);
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1115
|
+
if (!opts.noPush) {
|
|
1116
|
+
const ok = await deployGame(
|
|
1117
|
+
{ projectId: meta.id, sshUrl: meta.sshUrl, apiUrl, token },
|
|
1118
|
+
{ noBuild: opts.noBuild },
|
|
1119
|
+
log
|
|
1120
|
+
);
|
|
1121
|
+
if (!ok) {
|
|
1122
|
+
process.exitCode = 1;
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
963
1125
|
} else {
|
|
964
|
-
|
|
1126
|
+
log.info("Skipping build + deploy (--no-push).");
|
|
965
1127
|
}
|
|
966
1128
|
log.step("Listing it in the gallery\u2026");
|
|
967
1129
|
let res;
|
|
@@ -970,33 +1132,26 @@ async function runPublish(opts) {
|
|
|
970
1132
|
if (opts.title) body.title = opts.title;
|
|
971
1133
|
if (opts.description) body.description = opts.description;
|
|
972
1134
|
if (opts.regenerateCover) body.regenerateCover = true;
|
|
1135
|
+
const embedSdkVersion = await detectEmbedSdkVersion();
|
|
1136
|
+
if (embedSdkVersion) body.embedSdkVersion = embedSdkVersion;
|
|
973
1137
|
res = await fetch(`${apiUrl}/api/projects/${meta.id}/publish`, {
|
|
974
1138
|
method: "POST",
|
|
975
1139
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
976
1140
|
body: JSON.stringify(body)
|
|
977
1141
|
});
|
|
978
|
-
} catch
|
|
979
|
-
log.error(
|
|
1142
|
+
} catch {
|
|
1143
|
+
log.error("Couldn't reach Genex \u2014 please try again.");
|
|
980
1144
|
process.exitCode = 1;
|
|
981
1145
|
return;
|
|
982
1146
|
}
|
|
983
1147
|
if (!res.ok) {
|
|
984
|
-
log.error(
|
|
1148
|
+
log.error("Publish failed \u2014 please try again.");
|
|
985
1149
|
process.exitCode = 1;
|
|
986
1150
|
return;
|
|
987
1151
|
}
|
|
988
1152
|
log.plain("");
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
"Listed in the gallery, but the game wasn't pushed \u2014 the play URL won't work until the push succeeds."
|
|
992
|
-
);
|
|
993
|
-
log.dim(" Fix the error above and re-run `genex publish`.");
|
|
994
|
-
} else {
|
|
995
|
-
log.success("Published. \u{1F389}");
|
|
996
|
-
}
|
|
997
|
-
if (meta.playUrl) {
|
|
998
|
-
log.dim(` play: ${meta.playUrl}`);
|
|
999
|
-
}
|
|
1153
|
+
log.success("Published. \u{1F389}");
|
|
1154
|
+
if (meta.playUrl) log.dim(` play: ${meta.playUrl}`);
|
|
1000
1155
|
}
|
|
1001
1156
|
|
|
1002
1157
|
// src/commands/preview.ts
|
|
@@ -1010,26 +1165,32 @@ async function runPreview(opts) {
|
|
|
1010
1165
|
process.exitCode = 1;
|
|
1011
1166
|
return;
|
|
1012
1167
|
}
|
|
1013
|
-
const
|
|
1014
|
-
|
|
1015
|
-
|
|
1168
|
+
const token = await readUserToken(opts.envPath);
|
|
1169
|
+
if (!token) {
|
|
1170
|
+
log.error("Not authorized. Run `genex init` first to sign in.");
|
|
1171
|
+
process.exitCode = 1;
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
const apiUrl = getApiUrl(meta.apiUrl);
|
|
1175
|
+
const ok = await deployGame(
|
|
1176
|
+
{ projectId: meta.id, sshUrl: meta.sshUrl, apiUrl, token },
|
|
1177
|
+
{ noBuild: opts.noBuild },
|
|
1016
1178
|
log
|
|
1017
1179
|
);
|
|
1018
|
-
|
|
1019
|
-
if (!pushed) {
|
|
1020
|
-
log.warn("Preview not updated \u2014 see the error above.");
|
|
1180
|
+
if (!ok) {
|
|
1021
1181
|
process.exitCode = 1;
|
|
1022
1182
|
return;
|
|
1023
1183
|
}
|
|
1184
|
+
log.plain("");
|
|
1024
1185
|
log.success("Preview deployed \u2014 unlisted draft (run `genex publish` to list it).");
|
|
1025
1186
|
}
|
|
1026
1187
|
|
|
1027
1188
|
// src/commands/generate.ts
|
|
1028
|
-
import
|
|
1189
|
+
import path11 from "path";
|
|
1029
1190
|
|
|
1030
1191
|
// src/lib/assets.ts
|
|
1031
|
-
import
|
|
1032
|
-
import
|
|
1192
|
+
import fs9 from "fs/promises";
|
|
1193
|
+
import path10 from "path";
|
|
1033
1194
|
function slugify(input) {
|
|
1034
1195
|
const s = input.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 50).replace(/-+$/g, "");
|
|
1035
1196
|
return s || "asset";
|
|
@@ -1038,11 +1199,50 @@ async function downloadToFile(url, dest, headers) {
|
|
|
1038
1199
|
const res = await fetch(url, { headers });
|
|
1039
1200
|
if (!res.ok) throw new Error(`download failed (HTTP ${res.status}) for ${url}`);
|
|
1040
1201
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
1041
|
-
await
|
|
1042
|
-
await
|
|
1202
|
+
await fs9.mkdir(path10.dirname(dest), { recursive: true });
|
|
1203
|
+
await fs9.writeFile(dest, buf);
|
|
1043
1204
|
return buf.byteLength;
|
|
1044
1205
|
}
|
|
1045
1206
|
|
|
1207
|
+
// src/lib/sse.ts
|
|
1208
|
+
async function* readSSE(body) {
|
|
1209
|
+
const decoder = new TextDecoder();
|
|
1210
|
+
const reader = body.getReader();
|
|
1211
|
+
let buf = "";
|
|
1212
|
+
let event = "";
|
|
1213
|
+
let data = [];
|
|
1214
|
+
try {
|
|
1215
|
+
while (true) {
|
|
1216
|
+
const { done, value } = await reader.read();
|
|
1217
|
+
if (done) break;
|
|
1218
|
+
buf += decoder.decode(value, { stream: true });
|
|
1219
|
+
while (true) {
|
|
1220
|
+
const nl = buf.search(/[\r\n]/);
|
|
1221
|
+
if (nl === -1) break;
|
|
1222
|
+
if (buf[nl] === "\r" && nl === buf.length - 1) break;
|
|
1223
|
+
const sep = buf[nl] === "\r" && buf[nl + 1] === "\n" ? 2 : 1;
|
|
1224
|
+
const line = buf.slice(0, nl);
|
|
1225
|
+
buf = buf.slice(nl + sep);
|
|
1226
|
+
if (line === "") {
|
|
1227
|
+
if (data.length > 0) yield { event: event || "message", data: data.join("\n") };
|
|
1228
|
+
event = "";
|
|
1229
|
+
data = [];
|
|
1230
|
+
continue;
|
|
1231
|
+
}
|
|
1232
|
+
if (line.startsWith(":")) continue;
|
|
1233
|
+
const colon = line.indexOf(":");
|
|
1234
|
+
const field = colon === -1 ? line : line.slice(0, colon);
|
|
1235
|
+
let value2 = colon === -1 ? "" : line.slice(colon + 1);
|
|
1236
|
+
if (value2.startsWith(" ")) value2 = value2.slice(1);
|
|
1237
|
+
if (field === "event") event = value2;
|
|
1238
|
+
else if (field === "data") data.push(value2);
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
} finally {
|
|
1242
|
+
reader.releaseLock();
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1046
1246
|
// src/commands/generate.ts
|
|
1047
1247
|
var PUBLIC_PREFIX = "public/";
|
|
1048
1248
|
var KIND_DIR = {
|
|
@@ -1112,7 +1312,10 @@ async function runGenerate(kind, opts) {
|
|
|
1112
1312
|
return;
|
|
1113
1313
|
}
|
|
1114
1314
|
log.step("Generating\u2026 (this can take up to a minute)");
|
|
1115
|
-
const
|
|
1315
|
+
const onProgress = (p) => log.dim(` ${p}%`);
|
|
1316
|
+
const deadline = Date.now() + WAIT_TIMEOUT_MS;
|
|
1317
|
+
const streamed = await waitViaSSE(apiUrl, token, id, onProgress, deadline);
|
|
1318
|
+
const view = streamed === "unsupported" ? await poll(apiUrl, token, id, onProgress) : streamed;
|
|
1116
1319
|
if (!view) {
|
|
1117
1320
|
log.error("Timed out waiting for the generation.");
|
|
1118
1321
|
process.exitCode = 1;
|
|
@@ -1129,7 +1332,7 @@ async function runGenerate(kind, opts) {
|
|
|
1129
1332
|
for (const f of view.files ?? []) {
|
|
1130
1333
|
const rel = fileDest(kind, slug, f);
|
|
1131
1334
|
const url = `${apiUrl}/api/generations/${id}/asset/${f.role}`;
|
|
1132
|
-
const bytes = await downloadToFile(url,
|
|
1335
|
+
const bytes = await downloadToFile(url, path11.join(process.cwd(), rel), {
|
|
1133
1336
|
Authorization: `Bearer ${token}`
|
|
1134
1337
|
});
|
|
1135
1338
|
written.push(rel);
|
|
@@ -1143,8 +1346,61 @@ async function runGenerate(kind, opts) {
|
|
|
1143
1346
|
log.plain("");
|
|
1144
1347
|
printHint(kind, written, log);
|
|
1145
1348
|
}
|
|
1349
|
+
var WAIT_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
1350
|
+
var TERMINAL = /* @__PURE__ */ new Set(["completed", "failed"]);
|
|
1351
|
+
async function waitViaSSE(apiUrl, token, id, onProgress, deadline) {
|
|
1352
|
+
let connectFailures = 0;
|
|
1353
|
+
let last = -1;
|
|
1354
|
+
while (Date.now() < deadline) {
|
|
1355
|
+
const abort = new AbortController();
|
|
1356
|
+
const timer = setTimeout(() => abort.abort(), deadline - Date.now());
|
|
1357
|
+
try {
|
|
1358
|
+
let res;
|
|
1359
|
+
try {
|
|
1360
|
+
res = await fetch(`${apiUrl}/api/generations/${id}/events`, {
|
|
1361
|
+
headers: { Authorization: `Bearer ${token}`, Accept: "text/event-stream" },
|
|
1362
|
+
signal: abort.signal
|
|
1363
|
+
});
|
|
1364
|
+
} catch {
|
|
1365
|
+
if (++connectFailures >= 3) return "unsupported";
|
|
1366
|
+
await sleep2(2e3);
|
|
1367
|
+
continue;
|
|
1368
|
+
}
|
|
1369
|
+
if (res.status === 404 || res.status === 405) return "unsupported";
|
|
1370
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
1371
|
+
if (!res.ok || !res.body || !contentType.includes("text/event-stream")) {
|
|
1372
|
+
if (++connectFailures >= 3) return "unsupported";
|
|
1373
|
+
await sleep2(2e3);
|
|
1374
|
+
continue;
|
|
1375
|
+
}
|
|
1376
|
+
connectFailures = 0;
|
|
1377
|
+
try {
|
|
1378
|
+
for await (const ev of readSSE(res.body)) {
|
|
1379
|
+
if (ev.event !== "status") continue;
|
|
1380
|
+
let view;
|
|
1381
|
+
try {
|
|
1382
|
+
view = JSON.parse(ev.data);
|
|
1383
|
+
} catch {
|
|
1384
|
+
continue;
|
|
1385
|
+
}
|
|
1386
|
+
if (view.progress !== last) {
|
|
1387
|
+
last = view.progress;
|
|
1388
|
+
onProgress(view.progress);
|
|
1389
|
+
}
|
|
1390
|
+
if (TERMINAL.has(view.status)) return view;
|
|
1391
|
+
}
|
|
1392
|
+
} catch {
|
|
1393
|
+
}
|
|
1394
|
+
await sleep2(1e3);
|
|
1395
|
+
} finally {
|
|
1396
|
+
clearTimeout(timer);
|
|
1397
|
+
abort.abort();
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
return null;
|
|
1401
|
+
}
|
|
1146
1402
|
async function poll(apiUrl, token, id, onProgress) {
|
|
1147
|
-
const deadline = Date.now() +
|
|
1403
|
+
const deadline = Date.now() + WAIT_TIMEOUT_MS;
|
|
1148
1404
|
let last = -1;
|
|
1149
1405
|
while (Date.now() < deadline) {
|
|
1150
1406
|
try {
|
|
@@ -1185,9 +1441,9 @@ function printHint(kind, written, log) {
|
|
|
1185
1441
|
var GEN_KINDS = /* @__PURE__ */ new Set(["model", "skybox", "sfx", "texture"]);
|
|
1186
1442
|
function getVersion() {
|
|
1187
1443
|
try {
|
|
1188
|
-
const here =
|
|
1444
|
+
const here = path12.dirname(fileURLToPath2(import.meta.url));
|
|
1189
1445
|
const pkg = JSON.parse(
|
|
1190
|
-
readFileSync(
|
|
1446
|
+
readFileSync(path12.resolve(here, "..", "package.json"), "utf8")
|
|
1191
1447
|
);
|
|
1192
1448
|
return pkg.version ?? "0.0.0";
|
|
1193
1449
|
} catch {
|