@onceworlds/cli 0.13.0 → 0.14.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/dist/cli.js +228 -83
- package/dist/emulator.js +1066 -151
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
5
5
|
import { createHash as createHash5 } from "node:crypto";
|
|
6
|
-
import { existsSync as existsSync12, watch } from "node:fs";
|
|
7
|
-
import { mkdir as mkdir4, readdir as readdir4, readFile as readFile9, stat as stat2, writeFile as writeFile4 } from "node:fs/promises";
|
|
6
|
+
import { createReadStream, existsSync as existsSync12, watch } from "node:fs";
|
|
7
|
+
import { mkdir as mkdir4, open, readdir as readdir4, readFile as readFile9, stat as stat2, writeFile as writeFile4 } from "node:fs/promises";
|
|
8
8
|
import { basename as basename6, dirname as dirname10, join as join17, resolve as resolve12, sep as sep5 } from "node:path";
|
|
9
9
|
import { parseArgs } from "node:util";
|
|
10
10
|
|
|
@@ -861,10 +861,10 @@ function pageScripts(html) {
|
|
|
861
861
|
comment = html.indexOf("<!--", at);
|
|
862
862
|
continue;
|
|
863
863
|
}
|
|
864
|
-
const
|
|
865
|
-
const close =
|
|
864
|
+
const open2 = html.indexOf(">", script);
|
|
865
|
+
const close = open2 === -1 ? -1 : find(closing2, open2);
|
|
866
866
|
if (close === -1) return scripts;
|
|
867
|
-
scripts.push({ tag: html.slice(script,
|
|
867
|
+
scripts.push({ tag: html.slice(script, open2 + 1), body: html.slice(open2 + 1, close), at: open2 + 1 });
|
|
868
868
|
at = close + 8;
|
|
869
869
|
if (comment !== -1 && comment < at) comment = html.indexOf("<!--", at);
|
|
870
870
|
}
|
|
@@ -1610,17 +1610,17 @@ function keyName(key) {
|
|
|
1610
1610
|
return key.length === 1 ? key.toUpperCase() : key.charAt(0).toUpperCase() + key.slice(1);
|
|
1611
1611
|
}
|
|
1612
1612
|
var PRESET_KEYS = { wasd: ["W", "A", "S", "D"], ijkl: ["I", "J", "K", "L"], arrows: ["Arrows"] };
|
|
1613
|
-
function objectAt(text2,
|
|
1613
|
+
function objectAt(text2, open2) {
|
|
1614
1614
|
let depth = 0;
|
|
1615
1615
|
let quote = "";
|
|
1616
|
-
for (let i =
|
|
1616
|
+
for (let i = open2; i < text2.length; i++) {
|
|
1617
1617
|
const c = text2[i];
|
|
1618
1618
|
if (quote) {
|
|
1619
1619
|
if (c === "\\") i++;
|
|
1620
1620
|
else if (c === quote) quote = "";
|
|
1621
1621
|
} else if (c === '"' || c === "'" || c === "`") quote = c;
|
|
1622
1622
|
else if (c === "{") depth++;
|
|
1623
|
-
else if (c === "}" && --depth === 0) return text2.slice(
|
|
1623
|
+
else if (c === "}" && --depth === 0) return text2.slice(open2, i + 1);
|
|
1624
1624
|
}
|
|
1625
1625
|
return null;
|
|
1626
1626
|
}
|
|
@@ -2324,9 +2324,9 @@ var SCREENS2 = [
|
|
|
2324
2324
|
];
|
|
2325
2325
|
var DEFAULT_FONT = "sora";
|
|
2326
2326
|
var lineAt = (text2, index) => text2.slice(0, index).split("\n").length;
|
|
2327
|
-
function objectAt2(file, code,
|
|
2328
|
-
const end = closing(code,
|
|
2329
|
-
return end < 0 ? null : { text: file.text.slice(
|
|
2327
|
+
function objectAt2(file, code, open2) {
|
|
2328
|
+
const end = closing(code, open2);
|
|
2329
|
+
return end < 0 ? null : { text: file.text.slice(open2, end + 1), code: code.slice(open2, end + 1), from: open2 };
|
|
2330
2330
|
}
|
|
2331
2331
|
function constObject(files, name) {
|
|
2332
2332
|
const re = new RegExp(`(?:const|let|var)\\s+${name.replace(/\$/g, "\\$")}\\s*=\\s*\\{`);
|
|
@@ -2414,8 +2414,8 @@ function designFindings(input) {
|
|
|
2414
2414
|
if (f && !flow) flow = { file, code, at: f.index, shape: f[1] };
|
|
2415
2415
|
for (const m of code.matchAll(/(?<![\w$.])UI\s*\(\s*(\{)?/g)) {
|
|
2416
2416
|
if (ui) break;
|
|
2417
|
-
const
|
|
2418
|
-
ui = { file, code, at: m.index ?? 0, options: m[1] ? objectAt2(file, code,
|
|
2417
|
+
const open2 = (m.index ?? 0) + m[0].length - 1;
|
|
2418
|
+
ui = { file, code, at: m.index ?? 0, options: m[1] ? objectAt2(file, code, open2) : null };
|
|
2419
2419
|
}
|
|
2420
2420
|
}
|
|
2421
2421
|
if (!flow || !ui) return found;
|
|
@@ -2490,10 +2490,10 @@ function designFindings(input) {
|
|
|
2490
2490
|
const start = (m.index ?? 0) + m[0].length;
|
|
2491
2491
|
const name = file.text.slice(start, file.text.indexOf(m[1], start));
|
|
2492
2492
|
if (name === "flow.countdown") continue;
|
|
2493
|
-
const
|
|
2493
|
+
const open2 = (m.index ?? 0) + m[0].length - m[1].length - 1;
|
|
2494
2494
|
const end = closing(code, code.indexOf("(", m.index ?? 0));
|
|
2495
2495
|
if (end < 0) continue;
|
|
2496
|
-
const span = file.text.slice(
|
|
2496
|
+
const span = file.text.slice(open2, end);
|
|
2497
2497
|
const countdown = /phase\s*[!=]==?\s*['"]countdown['"]|['"]countdown['"]\s*===?\s*[\w$.?]*phase|\bflow\??\.countdown\b/.test(span);
|
|
2498
2498
|
const digits = /Math\s*\.\s*ceil\s*\(|['"`]GO!?['"`]/.test(span);
|
|
2499
2499
|
if (countdown && digits) {
|
|
@@ -2634,10 +2634,10 @@ function blank(text2) {
|
|
|
2634
2634
|
}
|
|
2635
2635
|
return out.join("");
|
|
2636
2636
|
}
|
|
2637
|
-
function closing(code,
|
|
2637
|
+
function closing(code, open2) {
|
|
2638
2638
|
const pairs = { "(": ")", "[": "]", "{": "}" };
|
|
2639
2639
|
const stack = [];
|
|
2640
|
-
for (let i =
|
|
2640
|
+
for (let i = open2; i < code.length; i++) {
|
|
2641
2641
|
const c = code[i];
|
|
2642
2642
|
if (pairs[c]) stack.push(pairs[c]);
|
|
2643
2643
|
else if (c === ")" || c === "]" || c === "}") {
|
|
@@ -2717,30 +2717,30 @@ function functionBody(code, from, to, name) {
|
|
|
2717
2717
|
function regions(text2, code) {
|
|
2718
2718
|
const out = [];
|
|
2719
2719
|
for (const m of code.matchAll(/\b(defineSystem|defineBotSystem|script|Flow\s*\.\s*(?:rounds|turns|coop|dropIn))\s*\(\s*\{/g)) {
|
|
2720
|
-
const
|
|
2721
|
-
const end = closing(code,
|
|
2720
|
+
const open2 = (m.index ?? 0) + m[0].length - 1;
|
|
2721
|
+
const end = closing(code, open2);
|
|
2722
2722
|
if (end < 0) continue;
|
|
2723
2723
|
const which = m[1].replace(/\s/g, "");
|
|
2724
|
-
const name = stringProp(text2, code,
|
|
2724
|
+
const name = stringProp(text2, code, open2, end, "name") ?? which;
|
|
2725
2725
|
if (which === "defineSystem" || which === "defineBotSystem") {
|
|
2726
|
-
const stage = stringProp(text2, code,
|
|
2727
|
-
const host = which === "defineBotSystem" || stringProp(text2, code,
|
|
2726
|
+
const stage = stringProp(text2, code, open2, end, "stage") ?? (which === "defineBotSystem" ? "fixed" : "update");
|
|
2727
|
+
const host = which === "defineBotSystem" || stringProp(text2, code, open2, end, "authority") === "host";
|
|
2728
2728
|
for (const fn of which === "defineBotSystem" ? ["think", "act"] : ["run"]) {
|
|
2729
|
-
const body = functionBody(code,
|
|
2730
|
-
if (body) out.push({ from: body[0], to: body[1], kind: "system", name, stage, host, defFrom:
|
|
2729
|
+
const body = functionBody(code, open2, end, fn);
|
|
2730
|
+
if (body) out.push({ from: body[0], to: body[1], kind: "system", name, stage, host, defFrom: open2, defTo: end });
|
|
2731
2731
|
}
|
|
2732
2732
|
} else if (which === "script") {
|
|
2733
2733
|
for (const hook of ["fixed", "update", "ready"]) {
|
|
2734
|
-
const body = functionBody(code,
|
|
2735
|
-
if (body) out.push({ from: body[0], to: body[1], kind: "script", name: hook, stage: hook === "fixed" ? "fixed" : "update", host: stringProp(text2, code,
|
|
2734
|
+
const body = functionBody(code, open2, end, hook);
|
|
2735
|
+
if (body) out.push({ from: body[0], to: body[1], kind: "script", name: hook, stage: hook === "fixed" ? "fixed" : "update", host: stringProp(text2, code, open2, end, "authority") === "host", defFrom: open2, defTo: end });
|
|
2736
2736
|
}
|
|
2737
2737
|
} else {
|
|
2738
2738
|
for (const rule of ["setup", "populate", "spawn", "isOver", "rank", "end", "begin", "bot"]) {
|
|
2739
2739
|
const re = new RegExp(`(?<![\\w$.])${rule}\\s*(\\(|:)`, "g");
|
|
2740
|
-
re.lastIndex =
|
|
2740
|
+
re.lastIndex = open2;
|
|
2741
2741
|
for (let r = re.exec(code); r && r.index < end; r = re.exec(code)) {
|
|
2742
|
-
const body = functionBody(code, Math.max(
|
|
2743
|
-
if (body) out.push({ from: body[0], to: body[1], kind: "rules", name: rule, stage: "fixed", host: rule === "populate" || rule === "isOver" || rule === "rank" || rule === "end", defFrom:
|
|
2742
|
+
const body = functionBody(code, Math.max(open2, enclosing(code, r.index)), end, rule);
|
|
2743
|
+
if (body) out.push({ from: body[0], to: body[1], kind: "rules", name: rule, stage: "fixed", host: rule === "populate" || rule === "isOver" || rule === "rank" || rule === "end", defFrom: open2, defTo: end });
|
|
2744
2744
|
}
|
|
2745
2745
|
}
|
|
2746
2746
|
}
|
|
@@ -2787,10 +2787,10 @@ function lintGame(input) {
|
|
|
2787
2787
|
for (const file of scripts) {
|
|
2788
2788
|
const code = blank(file.text);
|
|
2789
2789
|
for (const m of code.matchAll(/(?:const|let|var)\s+([\w$]+)\s*=\s*defineComponent\s*\(/g)) {
|
|
2790
|
-
const
|
|
2791
|
-
const end = closing(code,
|
|
2790
|
+
const open2 = code.indexOf("(", m.index ?? 0);
|
|
2791
|
+
const end = closing(code, open2);
|
|
2792
2792
|
if (end < 0) continue;
|
|
2793
|
-
if (/\bowner\s*:\s*['"]host['"]/.test(file.text.slice(
|
|
2793
|
+
if (/\bowner\s*:\s*['"]host['"]/.test(file.text.slice(open2, end))) hostComponents.add(m[1]);
|
|
2794
2794
|
}
|
|
2795
2795
|
}
|
|
2796
2796
|
for (const file of scripts) {
|
|
@@ -2847,16 +2847,16 @@ function lintGame(input) {
|
|
|
2847
2847
|
}
|
|
2848
2848
|
}
|
|
2849
2849
|
for (const m of code.matchAll(/\bwhile\s*\(\s*(?:true|1)\s*\)|\bfor\s*\(\s*;\s*;\s*\)/g)) {
|
|
2850
|
-
const
|
|
2851
|
-
const end =
|
|
2852
|
-
const body = end < 0 ? "" : code.slice(
|
|
2850
|
+
const open2 = code.indexOf("{", m.index ?? 0);
|
|
2851
|
+
const end = open2 < 0 ? -1 : closing(code, open2);
|
|
2852
|
+
const body = end < 0 ? "" : code.slice(open2, end);
|
|
2853
2853
|
if (!/\b(break|return|throw)\b/.test(body)) add(m.index ?? 0, "warn", "unbounded-spawn", "A loop with no break or return: it hangs the page. Give it an end, or do the work a step at a time in a system.");
|
|
2854
2854
|
}
|
|
2855
2855
|
const actionsFound = [];
|
|
2856
2856
|
for (const m of code.matchAll(/(?<![\w$])([\w$]+)\s*:\s*Input\s*\.\s*(button|axis2d|pointer)\s*\(\s*(\{)?/g)) {
|
|
2857
|
-
const
|
|
2858
|
-
const end = m[3] ? closing(code,
|
|
2859
|
-
const options = m[3] && end > 0 ? code.slice(
|
|
2857
|
+
const open2 = (m.index ?? 0) + m[0].length - 1;
|
|
2858
|
+
const end = m[3] ? closing(code, open2) : open2;
|
|
2859
|
+
const options = m[3] && end > 0 ? code.slice(open2, end) : "";
|
|
2860
2860
|
const touch = m[2] === "pointer" || (m[2] === "button" ? /(?<![\w$])touch\s*:/.test(options) : /(?<![\w$])stick\s*:\s*(?!false)/.test(options));
|
|
2861
2861
|
actionsFound.push({ name: m[1], kind: m[2], touch, at: m.index ?? 0 });
|
|
2862
2862
|
}
|
|
@@ -5387,6 +5387,106 @@ import { tmpdir as tmpdir2 } from "node:os";
|
|
|
5387
5387
|
import { dirname as dirname6, join as join8, resolve as resolve4 } from "node:path";
|
|
5388
5388
|
import { fileURLToPath as fileURLToPath2, pathToFileURL as pathToFileURL2 } from "node:url";
|
|
5389
5389
|
|
|
5390
|
+
// ../protocol/src/about.ts
|
|
5391
|
+
var ABOUT_LIMITS = {
|
|
5392
|
+
sourceLength: 2e4,
|
|
5393
|
+
blocks: 300,
|
|
5394
|
+
images: 40,
|
|
5395
|
+
videos: 6,
|
|
5396
|
+
youtube: 6,
|
|
5397
|
+
buttons: 12,
|
|
5398
|
+
links: 60,
|
|
5399
|
+
headings: 40,
|
|
5400
|
+
/** Pictures and videos uploaded for one game's About. */
|
|
5401
|
+
files: 40,
|
|
5402
|
+
fileNameLength: 40,
|
|
5403
|
+
headingLength: 100,
|
|
5404
|
+
captionLength: 200,
|
|
5405
|
+
titleLength: 80,
|
|
5406
|
+
buttonLabelLength: 40,
|
|
5407
|
+
urlLength: 500,
|
|
5408
|
+
columnsMin: 2,
|
|
5409
|
+
columnsMax: 3,
|
|
5410
|
+
galleryMin: 2,
|
|
5411
|
+
galleryMax: 12,
|
|
5412
|
+
listItems: 40,
|
|
5413
|
+
tableRows: 30,
|
|
5414
|
+
tableColumns: 5,
|
|
5415
|
+
imageMinSide: 32,
|
|
5416
|
+
imageMaxSide: 4096
|
|
5417
|
+
};
|
|
5418
|
+
var BLOCK_COMMANDS = /* @__PURE__ */ new Set(["section", "subsection", "image", "video", "youtube", "button", "divider", "begin", "end", "item", "column", "head", "row"]);
|
|
5419
|
+
var INLINE_WRAPS = /* @__PURE__ */ new Set(["b", "i", "spoiler"]);
|
|
5420
|
+
var KNOWN = [...BLOCK_COMMANDS, ...INLINE_WRAPS, "link"];
|
|
5421
|
+
var ABOUT_COMMANDS = [
|
|
5422
|
+
{ group: "Text", syntax: "A blank line", does: "Starts a new paragraph." },
|
|
5423
|
+
{ group: "Text", syntax: "\\section{Title}", does: "A heading.", tool: { label: "Heading", insert: "\\section{$Title$}\n" } },
|
|
5424
|
+
{ group: "Text", syntax: "\\subsection{Title}", does: "A smaller heading." },
|
|
5425
|
+
{ group: "Text", syntax: "\\b{bold} \\i{italic}", does: "Bold and italic.", tool: { label: "Bold", insert: "\\b{$text$}" } },
|
|
5426
|
+
{ group: "Text", syntax: "\\link{text}{https://...}", does: "A link. Also /games/name or /users/name.", tool: { label: "Link", insert: "\\link{$text$}{https://}" } },
|
|
5427
|
+
{ group: "Text", syntax: "\\begin{itemize} \\item One \\end{itemize}", does: "A list. enumerate numbers it.", tool: { label: "List", insert: "\\begin{itemize}\n\\item $One$\n\\item Two\n\\end{itemize}\n" } },
|
|
5428
|
+
{ group: "Text", syntax: "\\begin{quote}[Who] ... \\end{quote}", does: "A quote. Who said it is optional." },
|
|
5429
|
+
{ group: "Text", syntax: "\\spoiler{text}", does: "Hidden until hovered or focused." },
|
|
5430
|
+
{ group: "Text", syntax: "\\\\", does: "A line break." },
|
|
5431
|
+
{ group: "Text", syntax: "% note", does: "A comment, at the start of a line: not shown." },
|
|
5432
|
+
{ group: "Text", syntax: "\\{ \\} \\% \\[ \\]", does: "The character itself." },
|
|
5433
|
+
{ group: "Media", syntax: "\\image{name}{caption}", does: "A picture you uploaded, at its own size up to the page\u2019s width. The caption is optional.", tool: { label: "Image", insert: "\\image{$name$}\n" } },
|
|
5434
|
+
{ group: "Media", syntax: "\\video{name}{caption}", does: "A video you uploaded. It plays when pressed.", tool: { label: "Video", insert: "\\video{$name$}\n" } },
|
|
5435
|
+
{ group: "Media", syntax: "\\video[loop]{name}", does: "Plays muted on a loop while in view, like a GIF." },
|
|
5436
|
+
{ group: "Media", syntax: "\\youtube{address}", does: "A YouTube video. \\youtube[name]{address} shows your picture until it\u2019s played.", tool: { label: "YouTube", insert: "\\youtube{$https://youtu.be/$}\n" } },
|
|
5437
|
+
{ group: "Media", syntax: "\\begin{gallery} \\image{a} \\image{b} \\end{gallery}", does: "Pictures side by side." },
|
|
5438
|
+
{ group: "Layout", syntax: "\\button{Label}{https://...}", does: "A button. Buttons in a row sit together.", tool: { label: "Button", insert: "\\button{$Label$}{https://}\n" } },
|
|
5439
|
+
{ group: "Layout", syntax: "\\button{Play now}{play}", does: "Plays this game." },
|
|
5440
|
+
{ group: "Layout", syntax: "\\begin{columns} \\column ... \\column ... \\end{columns}", does: "2 or 3 columns, stacked on phones." },
|
|
5441
|
+
{ group: "Layout", syntax: "\\begin{table} \\head{A}{B} \\row{1}{2} \\end{table}", does: "A table, up to 5 columns." },
|
|
5442
|
+
{ group: "Layout", syntax: "\\begin{fold}{Title} ... \\end{fold}", does: "Closed until opened." },
|
|
5443
|
+
{ group: "Layout", syntax: "\\divider", does: "A line across." }
|
|
5444
|
+
];
|
|
5445
|
+
var ABOUT_EXAMPLE = `Hold the castle until dawn. Up to eight friends, one wall, and a night that gets longer every time.
|
|
5446
|
+
|
|
5447
|
+
\\image{castle}
|
|
5448
|
+
|
|
5449
|
+
\\section{Features}
|
|
5450
|
+
|
|
5451
|
+
\\begin{itemize}
|
|
5452
|
+
\\item \\b{Build between nights.} Walls, traps and towers, rebuilt however you like.
|
|
5453
|
+
\\item \\b{Twelve bosses.} Each has a weak spot, and none of them tell you where.
|
|
5454
|
+
\\item \\b{Raid your friends.} Your castle is a level they can attack.
|
|
5455
|
+
\\end{itemize}
|
|
5456
|
+
|
|
5457
|
+
\\video[loop]{night-raid}
|
|
5458
|
+
|
|
5459
|
+
\\section{Credits}
|
|
5460
|
+
|
|
5461
|
+
Made by two people. Music by a very kind friend.
|
|
5462
|
+
|
|
5463
|
+
\\button{Join the Discord}{https://discord.gg/example}
|
|
5464
|
+
`;
|
|
5465
|
+
function aboutGuide() {
|
|
5466
|
+
const groups = ["Text", "Media", "Layout"];
|
|
5467
|
+
const L = ABOUT_LIMITS;
|
|
5468
|
+
return [
|
|
5469
|
+
"# About (OnceTeX)",
|
|
5470
|
+
"",
|
|
5471
|
+
"A game page has a short description and, above How to play, an About section the creator writes in OnceTeX, a small LaTeX-like language.",
|
|
5472
|
+
"Write it in the dashboard: the game's About tab, with a live preview. There is no HTML or CSS: the commands below are all there is. Every game's text looks the same, so the pictures do the designing.",
|
|
5473
|
+
"",
|
|
5474
|
+
...groups.flatMap((group) => [`## ${group}`, "", ...ABOUT_COMMANDS.filter((c) => c.group === group).map((c) => `- \`${c.syntax}\`: ${c.does}`), ""]),
|
|
5475
|
+
"## Rules",
|
|
5476
|
+
"",
|
|
5477
|
+
`- Pictures and videos are the game's own uploads, up to ${L.files}, used by name (\`castle.png\` is \`castle\`). Outside pictures can't be shown.`,
|
|
5478
|
+
`- Limits: ${L.sourceLength.toLocaleString("en")} characters, ${L.images} pictures, ${L.videos} videos, ${L.youtube} YouTube videos, ${L.buttons} buttons, ${L.links} links, ${L.headings} headings.`,
|
|
5479
|
+
"- Links go in `\\link` and `\\button` only: an address, an email or a handle in plain text is refused.",
|
|
5480
|
+
"- Columns hold text, media, buttons, lists and quotes.",
|
|
5481
|
+
"",
|
|
5482
|
+
"## Example",
|
|
5483
|
+
"",
|
|
5484
|
+
"```latex",
|
|
5485
|
+
ABOUT_EXAMPLE.trimEnd(),
|
|
5486
|
+
"```"
|
|
5487
|
+
].join("\n");
|
|
5488
|
+
}
|
|
5489
|
+
|
|
5390
5490
|
// ../protocol/src/listing.ts
|
|
5391
5491
|
function badgeIdMaxLength() {
|
|
5392
5492
|
let n = 1;
|
|
@@ -5444,7 +5544,13 @@ function listingTopics() {
|
|
|
5444
5544
|
"The creator can also add badges on the dashboard."
|
|
5445
5545
|
].join("\n")
|
|
5446
5546
|
};
|
|
5447
|
-
|
|
5547
|
+
const about = {
|
|
5548
|
+
name: "about",
|
|
5549
|
+
title: "About",
|
|
5550
|
+
summary: "The long section of a game page, with pictures, videos, links and buttons: written in OnceTeX in the dashboard.",
|
|
5551
|
+
text: aboutGuide()
|
|
5552
|
+
};
|
|
5553
|
+
return [genres, badges, about];
|
|
5448
5554
|
}
|
|
5449
5555
|
|
|
5450
5556
|
// src/lookup.ts
|
|
@@ -7539,8 +7645,8 @@ var LocalRoom = class {
|
|
|
7539
7645
|
}
|
|
7540
7646
|
hostOpen(ws, me, msg, config) {
|
|
7541
7647
|
if (!this.isHost(me.id)) return this.error(ws, "not_host", "Only the host can open or close the server");
|
|
7542
|
-
const
|
|
7543
|
-
if (config.pool && !
|
|
7648
|
+
const open2 = Boolean(msg.open);
|
|
7649
|
+
if (config.pool && !open2 && this.mrec.phase === "lobby") {
|
|
7544
7650
|
if (!this.takeHostAction(me.id, "host")) return this.error(ws, "slow_down", "Too many changes at once.");
|
|
7545
7651
|
const start = closingStart(this.lobbyView(config));
|
|
7546
7652
|
if ("code" in start) return this.error(ws, start.code, start.message);
|
|
@@ -7550,11 +7656,11 @@ var LocalRoom = class {
|
|
|
7550
7656
|
this.stepMatch();
|
|
7551
7657
|
return;
|
|
7552
7658
|
}
|
|
7553
|
-
if (config.pool &&
|
|
7554
|
-
if (config.open ===
|
|
7659
|
+
if (config.pool && open2 && this.mrec.byOpen && this.mrec.phase !== "lobby") return this.hostEnd(ws, me, config);
|
|
7660
|
+
if (config.open === open2) return;
|
|
7555
7661
|
if (!this.takeHostAction(me.id, "host")) return this.error(ws, "slow_down", "Too many changes at once.");
|
|
7556
|
-
config.open =
|
|
7557
|
-
this.broadcast({ t: "open", open });
|
|
7662
|
+
config.open = open2;
|
|
7663
|
+
this.broadcast({ t: "open", open: open2 });
|
|
7558
7664
|
this.hub.changed();
|
|
7559
7665
|
}
|
|
7560
7666
|
hostEnd(ws, me, config) {
|
|
@@ -9538,16 +9644,16 @@ function fromScript(text2, addFont) {
|
|
|
9538
9644
|
const taken = /* @__PURE__ */ new Set();
|
|
9539
9645
|
const lists = [];
|
|
9540
9646
|
for (const m of code.matchAll(/(?<![\w$])([\w$]*fonts)\s*(?::|=(?![=>]))\s*\[/gi)) {
|
|
9541
|
-
const
|
|
9542
|
-
const end = closing(code,
|
|
9647
|
+
const open2 = m.index + m[0].length - 1;
|
|
9648
|
+
const end = closing(code, open2);
|
|
9543
9649
|
if (end < 0) continue;
|
|
9544
|
-
lists.push([
|
|
9545
|
-
for (let i =
|
|
9650
|
+
lists.push([open2, end]);
|
|
9651
|
+
for (let i = open2 + 1; i < end; i++) {
|
|
9546
9652
|
const c = code[i];
|
|
9547
9653
|
if (c !== "'" && c !== '"' && c !== "`") continue;
|
|
9548
9654
|
const literal = literalAt(text2, code, i);
|
|
9549
9655
|
if (!literal) break;
|
|
9550
|
-
const before = code.slice(
|
|
9656
|
+
const before = code.slice(open2, i).trimEnd();
|
|
9551
9657
|
if ((before.endsWith("[") || before.endsWith(",")) && !files.has(i)) add(i, literal.value, void 0, literal.dynamic);
|
|
9552
9658
|
i = literal.end;
|
|
9553
9659
|
}
|
|
@@ -12287,15 +12393,41 @@ function statementEnd(code, from) {
|
|
|
12287
12393
|
return Math.min(code.length, from + 2e3);
|
|
12288
12394
|
}
|
|
12289
12395
|
|
|
12396
|
+
// ../protocol/src/deploy-files.ts
|
|
12397
|
+
var MB = 1024 * 1024;
|
|
12398
|
+
var GB = 1024 * MB;
|
|
12399
|
+
var DEPLOY_FREE_LIMITS = {
|
|
12400
|
+
deployFileBytes: 500 * MB,
|
|
12401
|
+
deployTotalBytes: GB,
|
|
12402
|
+
deployBytesPerOwnerPerDay: 10 * GB
|
|
12403
|
+
};
|
|
12404
|
+
var DEPLOY_REQUEST_BYTES = 95 * MB;
|
|
12405
|
+
var DEPLOY_PART_BYTES = 50 * MB;
|
|
12406
|
+
var NOT_A_WEB_GAME = [
|
|
12407
|
+
{ pattern: "(^|/)project\\.godot$", text: "This is a Godot project, not its web export. In Godot: Project > Export > Add > Web, with Thread Support off, and name the file index.html." },
|
|
12408
|
+
{ pattern: "\\.(rpy|rpyc|rpa)$", text: "This is a Ren'Py game, not its web build. In the Ren'Py launcher: Web, then Build Web Application." },
|
|
12409
|
+
{ pattern: "(^|/)(ProjectSettings|Assets)/|(^|/)UnityPlayer\\.dll$", text: "This is a Unity project, not a web build. Build for WebGL with Compression Format: Disabled." },
|
|
12410
|
+
{ pattern: "\\.py$", text: "Python games need a web export, such as pygbag." },
|
|
12411
|
+
{ pattern: "\\.(exe|apk|aab|ipa|dmg|msi|deb|rpm|appimage|x86_64)$|\\.app/", text: "This is a program to install. Games here run in the browser: export a web version with an index.html." }
|
|
12412
|
+
];
|
|
12413
|
+
function notAWebGame(paths) {
|
|
12414
|
+
for (const { pattern: pattern2, text: text2 } of NOT_A_WEB_GAME) {
|
|
12415
|
+
const test = new RegExp(pattern2, "i");
|
|
12416
|
+
if (paths.some((path) => test.test(path))) return text2;
|
|
12417
|
+
}
|
|
12418
|
+
return null;
|
|
12419
|
+
}
|
|
12420
|
+
|
|
12290
12421
|
// ../protocol/src/billing.ts
|
|
12291
12422
|
var PRO_LIMITS = {
|
|
12292
12423
|
gamesPerOwner: 250,
|
|
12293
12424
|
gamesCreatedPerCreatorPerDay: 20,
|
|
12294
12425
|
deployFiles: 1e4,
|
|
12295
|
-
|
|
12426
|
+
deployFileBytes: 2 * 1024 * 1024 * 1024,
|
|
12427
|
+
deployTotalBytes: 5 * 1024 * 1024 * 1024,
|
|
12296
12428
|
deploysPerOwnerPerDay: 1e3,
|
|
12297
12429
|
draftsPerOwnerPerDay: 5e3,
|
|
12298
|
-
deployBytesPerOwnerPerDay:
|
|
12430
|
+
deployBytesPerOwnerPerDay: 100 * 1024 * 1024 * 1024,
|
|
12299
12431
|
deployEntriesPerOwnerPerDay: 5e5,
|
|
12300
12432
|
deploymentsKeptPerGame: 50,
|
|
12301
12433
|
mediaUploadsPerDay: 1e3,
|
|
@@ -12315,8 +12447,9 @@ var MARKET_FREE_LIMITS = {
|
|
|
12315
12447
|
itemFiles: 50,
|
|
12316
12448
|
itemUploadBytesPerDay: 10 * 1024 * 1024 * 1024
|
|
12317
12449
|
};
|
|
12450
|
+
var FREE_OUTSIDE_LIMITS = { ...MARKET_FREE_LIMITS, ...DEPLOY_FREE_LIMITS };
|
|
12318
12451
|
var FREE_LIMITS = Object.fromEntries(
|
|
12319
|
-
Object.keys(PRO_LIMITS).map((key) => [key,
|
|
12452
|
+
Object.keys(PRO_LIMITS).map((key) => [key, FREE_OUTSIDE_LIMITS[key] ?? LIMITS[key]])
|
|
12320
12453
|
);
|
|
12321
12454
|
|
|
12322
12455
|
// ../protocol/src/config.ts
|
|
@@ -12413,29 +12546,8 @@ function checkProjectConfig(config) {
|
|
|
12413
12546
|
return out;
|
|
12414
12547
|
}
|
|
12415
12548
|
|
|
12416
|
-
// ../protocol/src/deploy-files.ts
|
|
12417
|
-
var LARGE_FILE_EXTENSIONS = ["wasm", "pck", "data", "zip"];
|
|
12418
|
-
var DEPLOY_LARGE_FILE_BYTES = 95 * 1024 * 1024;
|
|
12419
|
-
function deployFileLimit(path) {
|
|
12420
|
-
return LARGE_FILE_EXTENSIONS.includes(extensionOf(path)) ? DEPLOY_LARGE_FILE_BYTES : LIMITS.deployFileBytes;
|
|
12421
|
-
}
|
|
12422
|
-
var NOT_A_WEB_GAME = [
|
|
12423
|
-
{ pattern: "(^|/)project\\.godot$", text: "This is a Godot project, not its web export. In Godot: Project > Export > Add > Web, with Thread Support off, and name the file index.html." },
|
|
12424
|
-
{ pattern: "\\.(rpy|rpyc|rpa)$", text: "This is a Ren'Py game, not its web build. In the Ren'Py launcher: Web, then Build Web Application." },
|
|
12425
|
-
{ pattern: "(^|/)(ProjectSettings|Assets)/|(^|/)UnityPlayer\\.dll$", text: "This is a Unity project, not a web build. Build for WebGL with Compression Format: Disabled." },
|
|
12426
|
-
{ pattern: "\\.py$", text: "Python games need a web export, such as pygbag." },
|
|
12427
|
-
{ pattern: "\\.(exe|apk|aab|ipa|dmg|msi|deb|rpm|appimage|x86_64)$|\\.app/", text: "This is a program to install. Games here run in the browser: export a web version with an index.html." }
|
|
12428
|
-
];
|
|
12429
|
-
function notAWebGame(paths) {
|
|
12430
|
-
for (const { pattern: pattern2, text: text2 } of NOT_A_WEB_GAME) {
|
|
12431
|
-
const test = new RegExp(pattern2, "i");
|
|
12432
|
-
if (paths.some((path) => test.test(path))) return text2;
|
|
12433
|
-
}
|
|
12434
|
-
return null;
|
|
12435
|
-
}
|
|
12436
|
-
|
|
12437
12549
|
// src/index.ts
|
|
12438
|
-
var CLI_VERSION = "0.
|
|
12550
|
+
var CLI_VERSION = "0.14.0";
|
|
12439
12551
|
var PRODUCTION_API = "https://onceworlds.com";
|
|
12440
12552
|
var LOCAL_API = "http://localhost:8787";
|
|
12441
12553
|
var USAGE = `Usage:
|
|
@@ -12678,8 +12790,9 @@ async function deploy(projectDir, api2, tokenArg, quiet = false, dev2 = false) {
|
|
|
12678
12790
|
console.log(` \u2191 uploading ${uploads.length} changed file(s), ${formatBytes(uploads.reduce((s, f) => s + f.size, 0))}`);
|
|
12679
12791
|
}
|
|
12680
12792
|
await runPool(uploads, 6, async (file) => {
|
|
12681
|
-
const
|
|
12682
|
-
|
|
12793
|
+
const url = `${api2}/api/deploy/${start.deploymentId}/blobs/${file.hash}`;
|
|
12794
|
+
if (file.size > DEPLOY_REQUEST_BYTES) return sendInParts(url, start.uploadToken, file);
|
|
12795
|
+
await send("PUT", url, start.uploadToken, await readFile9(file.abs));
|
|
12683
12796
|
});
|
|
12684
12797
|
const result = await send("POST", `${api2}/api/deploy/${start.deploymentId}/finalize`, start.uploadToken, {});
|
|
12685
12798
|
if (!quiet) {
|
|
@@ -13077,6 +13190,11 @@ async function loadStore(projectDir, config) {
|
|
|
13077
13190
|
if (guide.help) store.help = guide.help;
|
|
13078
13191
|
return { store, images: [...images.values()] };
|
|
13079
13192
|
}
|
|
13193
|
+
async function hashFile(abs) {
|
|
13194
|
+
const hash2 = createHash5("sha256");
|
|
13195
|
+
for await (const chunk2 of createReadStream(abs)) hash2.update(chunk2);
|
|
13196
|
+
return hash2.digest("hex");
|
|
13197
|
+
}
|
|
13080
13198
|
async function collectFiles(root) {
|
|
13081
13199
|
const files = [];
|
|
13082
13200
|
const skipped = [];
|
|
@@ -13099,11 +13217,11 @@ async function collectFiles(root) {
|
|
|
13099
13217
|
if (!QUIET_EXTENSIONS.has(extensionOf(path))) skipped.push(path);
|
|
13100
13218
|
continue;
|
|
13101
13219
|
}
|
|
13102
|
-
const
|
|
13103
|
-
if (
|
|
13104
|
-
throw new CliError(`${path} is ${formatBytes(
|
|
13220
|
+
const { size } = await stat2(abs);
|
|
13221
|
+
if (size > PRO_LIMITS.deployFileBytes) {
|
|
13222
|
+
throw new CliError(`${path} is ${formatBytes(size)}; a file can be at most ${formatBytes(PRO_LIMITS.deployFileBytes)}.`);
|
|
13105
13223
|
}
|
|
13106
|
-
files.push({ path, abs, hash:
|
|
13224
|
+
files.push({ path, abs, hash: await hashFile(abs), size });
|
|
13107
13225
|
}
|
|
13108
13226
|
}
|
|
13109
13227
|
}
|
|
@@ -13350,7 +13468,33 @@ function mcpHost(api2) {
|
|
|
13350
13468
|
token: async () => (await readCredential(api2))?.token ?? null
|
|
13351
13469
|
};
|
|
13352
13470
|
}
|
|
13353
|
-
async function
|
|
13471
|
+
async function sendInParts(url, token, file) {
|
|
13472
|
+
const { uploadId, partBytes, parts } = await send("POST", `${url}/parts`, token, void 0);
|
|
13473
|
+
const etags = new Array(parts);
|
|
13474
|
+
const handle = await open(file.abs);
|
|
13475
|
+
try {
|
|
13476
|
+
await runPool(Array.from({ length: parts }, (_, i) => i), 3, async (i) => {
|
|
13477
|
+
const data = Buffer.alloc(Math.min(partBytes, file.size - i * partBytes));
|
|
13478
|
+
await handle.read(data, 0, data.byteLength, i * partBytes);
|
|
13479
|
+
const stored = await retrying(() => send("PUT", `${url}/parts/${i + 1}`, token, data, { "X-Upload-Id": uploadId }));
|
|
13480
|
+
etags[i] = stored.etag;
|
|
13481
|
+
});
|
|
13482
|
+
} finally {
|
|
13483
|
+
await handle.close();
|
|
13484
|
+
}
|
|
13485
|
+
await retrying(() => send("POST", `${url}/complete`, token, { uploadId, etags }));
|
|
13486
|
+
}
|
|
13487
|
+
async function retrying(request) {
|
|
13488
|
+
for (let attempt = 1; ; attempt++) {
|
|
13489
|
+
try {
|
|
13490
|
+
return await request();
|
|
13491
|
+
} catch (err) {
|
|
13492
|
+
if (attempt === 3) throw err;
|
|
13493
|
+
await new Promise((done) => setTimeout(done, attempt * 2e3));
|
|
13494
|
+
}
|
|
13495
|
+
}
|
|
13496
|
+
}
|
|
13497
|
+
async function send(method, url, token, body, headers = {}) {
|
|
13354
13498
|
const binary = body instanceof Uint8Array;
|
|
13355
13499
|
let res;
|
|
13356
13500
|
try {
|
|
@@ -13358,7 +13502,8 @@ async function send(method, url, token, body) {
|
|
|
13358
13502
|
method,
|
|
13359
13503
|
headers: {
|
|
13360
13504
|
Authorization: `Bearer ${token}`,
|
|
13361
|
-
...body === void 0 ? {} : { "Content-Type": binary ? "application/octet-stream" : "application/json" }
|
|
13505
|
+
...body === void 0 ? {} : { "Content-Type": binary ? "application/octet-stream" : "application/json" },
|
|
13506
|
+
...headers
|
|
13362
13507
|
},
|
|
13363
13508
|
body: body === void 0 ? void 0 : binary ? body : JSON.stringify(body)
|
|
13364
13509
|
});
|