@onceworlds/cli 0.11.0 → 0.13.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 +3 -3
- package/dist/cli.js +92 -19
- package/dist/emulator.js +211 -150
- package/dist/sdk.js +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ players) with no server code.
|
|
|
7
7
|
## Deploy from GitHub Actions
|
|
8
8
|
|
|
9
9
|
Deploys run from GitHub Actions: GitHub vouches for the repo, so there are no API
|
|
10
|
-
keys. The easiest setup is to import the repo at https://onceworlds.com/
|
|
10
|
+
keys. The easiest setup is to import the repo at https://onceworlds.com/upload?new=github,
|
|
11
11
|
which adds `onceworlds.json` and `.github/workflows/onceworlds.yml` for you (that
|
|
12
12
|
workflow also builds with npm, pnpm, yarn or bun when there's a build script). To write
|
|
13
13
|
the same files into a folder before it is on GitHub, run `npx @onceworlds/cli init` there.
|
|
@@ -96,7 +96,7 @@ from the running game and commit them under `store/`. `onceworlds doctor` says w
|
|
|
96
96
|
whose idea, look and screens are never yours; it adds its own notes as `KIT.md` and its fingerprints as `.onceworlds/kit.json`,
|
|
97
97
|
so `check` can note what is still the kit's. `--template party` or `starter` starts from a complete game on the SDK, and
|
|
98
98
|
`--template <name>` from a scene project of Onceworlds Build.
|
|
99
|
-
- `onceworlds check [dir]` is what to run before a deploy, for any game (canvas, three.js, PixiJS, Phaser, the DOM, the engine):
|
|
99
|
+
- `onceworlds check [dir]` is what to run before a deploy, for any game (canvas, three.js, PixiJS, Phaser, the DOM, the engine, a Godot or Ren'Py web export):
|
|
100
100
|
doctor's findings, plus the hosts the platform's CSP won't let the game reach, WebSockets of its own, text too small for a phone,
|
|
101
101
|
a second countdown under the platform's lobby, shared state written from `Math.random()` or the device's clock, and a `DESIGN.md`
|
|
102
102
|
silent on originality and the first minute. Then it plays the game on the local platform (`serve`'s) in headless Chrome: it loads
|
|
@@ -123,7 +123,7 @@ from the running game and commit them under `store/`. `onceworlds doctor` says w
|
|
|
123
123
|
imports it (next to a `AGENTS.md` of your own it writes `ONCEWORLDS.md` and adds a pointer to yours). It never
|
|
124
124
|
overwrites a file you have: `--force` replaces only what init writes, never your own `AGENTS.md`, `CLAUDE.md` or `.gitignore`.
|
|
125
125
|
`onceworlds new` writes the same workflow and `.gitignore` into every game it makes.
|
|
126
|
-
Then commit, push, and import the repo at https://onceworlds.com/
|
|
126
|
+
Then commit, push, and import the repo at https://onceworlds.com/upload?new=github.
|
|
127
127
|
- `onceworlds deploy [dir]` uploads only the files that changed and publishes the game
|
|
128
128
|
(the workflow runs it). It lists every file it skipped because Onceworlds can't serve it. A game's tests never go up: `*.test.js`, `vitest.config.*` and the `test/` folder at the top of the game.
|
|
129
129
|
- `onceworlds serve [dir]` plays any game in your own browser on this computer, offline and with no login, on a local platform
|
package/dist/cli.js
CHANGED
|
@@ -615,6 +615,8 @@ var CONTENT_TYPES = {
|
|
|
615
615
|
wgsl: "text/plain; charset=utf-8",
|
|
616
616
|
pck: "application/octet-stream",
|
|
617
617
|
data: "application/octet-stream",
|
|
618
|
+
// Ren'Py's web build loads its game from a game.zip.
|
|
619
|
+
zip: "application/zip",
|
|
618
620
|
woff: "font/woff",
|
|
619
621
|
woff2: "font/woff2",
|
|
620
622
|
ttf: "font/ttf",
|
|
@@ -715,6 +717,25 @@ var NEW_GAME_RESERVED_SLUGS = /* @__PURE__ */ new Set([
|
|
|
715
717
|
]);
|
|
716
718
|
var NEW_GAME_RESERVED_PREFIXES = ["onceworlds", "admin", "free-"];
|
|
717
719
|
var SLUG_AUTH_WORDS = /* @__PURE__ */ new Set(["login", "logon", "signin", "signon", "signup", "verify", "verification", "password", "passwords", "billing"]);
|
|
720
|
+
var NAME_AUTH_WORDS = /* @__PURE__ */ new Set([
|
|
721
|
+
"login",
|
|
722
|
+
"logon",
|
|
723
|
+
"signin",
|
|
724
|
+
"signup",
|
|
725
|
+
"verify",
|
|
726
|
+
"verification",
|
|
727
|
+
"password",
|
|
728
|
+
"billing",
|
|
729
|
+
"account",
|
|
730
|
+
"support",
|
|
731
|
+
"helpdesk",
|
|
732
|
+
"official",
|
|
733
|
+
"staff",
|
|
734
|
+
"admin",
|
|
735
|
+
"administrator",
|
|
736
|
+
"moderator",
|
|
737
|
+
"security"
|
|
738
|
+
]);
|
|
718
739
|
var BRANDS = ["onceworlds", "roblox", "robux", "vbucks", "fortnite", "minecraft", "xbox", "playstation", "nintendo"];
|
|
719
740
|
var LOOKALIKES = {
|
|
720
741
|
"0": "o",
|
|
@@ -793,6 +814,10 @@ function hasBrand(text2) {
|
|
|
793
814
|
}
|
|
794
815
|
return false;
|
|
795
816
|
}
|
|
817
|
+
function isReservedGameName(name) {
|
|
818
|
+
if (hasBrand(name)) return true;
|
|
819
|
+
return foldedWords(name).some((words3) => NAME_AUTH_WORDS.has(words3.join("")));
|
|
820
|
+
}
|
|
796
821
|
var DEFAULT_PRODUCTION_BRANCH = "main";
|
|
797
822
|
function isValidBranchName(name) {
|
|
798
823
|
return name.length > 0 && name.length <= 200 && /^[A-Za-z0-9._/-]+$/.test(name) && !/^[-/.]|[/.]$|\.\.|\/\/|\/\.|\.lock$|^HEAD$/.test(name);
|
|
@@ -1480,9 +1505,26 @@ function checkGame(files) {
|
|
|
1480
1505
|
line: at,
|
|
1481
1506
|
level: "note",
|
|
1482
1507
|
id: "matches",
|
|
1483
|
-
message: `This joins a room but doesn't use the platform's lobby or match lifecycle. That's right for a world people drop in and out of; a game with rounds gets ready-up, a countdown, spectators and pausing from rooms.join({ lobby: '
|
|
1508
|
+
message: `This joins a room but doesn't use the platform's lobby or match lifecycle. That's right for a world people drop in and out of; a game with rounds gets ready-up, a countdown, spectators and pausing from rooms.join({ lobby: 'bar' }), or 'card' (see AGENTS.md, "Matches").`
|
|
1484
1509
|
});
|
|
1485
1510
|
}
|
|
1511
|
+
const lobbyLine = (id, level, test, message) => {
|
|
1512
|
+
const file = code.find((f) => test.test(f.text));
|
|
1513
|
+
if (file) found.push({ file: file.path, line: lineOf(file.text, file.text.search(test)), level, id, message });
|
|
1514
|
+
};
|
|
1515
|
+
lobbyLine("hide-lobby", "warn", /\.\s*hideLobby\s*\(/, "room.hideLobby does nothing now: the platform's strip or card always shows in the lobby. Take it out.");
|
|
1516
|
+
lobbyLine("own-countdown", "warn", /\bownCountdown\s*:\s*true\b/, "The 3-2-1 is the platform's. Drop ownCountdown and your own numeral, and start the match's action on matchstart, so one countdown shows.");
|
|
1517
|
+
const gameDrawn = /\blobby\s*:\s*['"]game['"]/;
|
|
1518
|
+
if (gameDrawn.test(all)) {
|
|
1519
|
+
if (/\.\s*setReady\s*\(/.test(all)) lobbyLine("game-lobby", "warn", gameDrawn, "lobby: 'game' with setReady: this game draws Ready itself, so it can't match the rest of the platform (Enter, the same words, a strip that's always there). Use lobby: 'bar' or 'card'.");
|
|
1520
|
+
} else if (/\blobby\s*:\s*['"]bar['"]/.test(all)) {
|
|
1521
|
+
const drawn = /(?:textContent|innerText|innerHTML|fillText\s*\(|label\s*:)\s*=?\s*['"`](?:<[^>]*>)*\s*(Ready|Ready up|Start|Start match|Not ready)\s*(?:<[^>]*>)*['"`]/;
|
|
1522
|
+
const htmlButton = /<button\b[^>]*>\s*(Ready|Ready up|Start match)\s*<\/button>/i;
|
|
1523
|
+
if (/\.\s*(setReady|startMatch)\s*\(/.test(all)) {
|
|
1524
|
+
const test = drawn.test(all) ? drawn : htmlButton;
|
|
1525
|
+
lobbyLine("double-ready", "warn", test, "This joins with lobby: 'bar', and draws a Ready or Start button of its own too: players see two. The strip's button and Enter are the way; take yours out.");
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1486
1528
|
if (/\bDate\s*\.\s*now\s*\(/.test(all) && !/\bonceworlds\s*\.\s*now\s*\(|\bow\s*\.\s*now\s*\(|\bmatchNow\s*\(/.test(all)) {
|
|
1487
1529
|
found.push({
|
|
1488
1530
|
file: host.path,
|
|
@@ -3185,7 +3227,7 @@ ${line3}
|
|
|
3185
3227
|
Next:
|
|
3186
3228
|
1. Check onceworlds.json: "slug" is your game's link (onceworlds.com/play/<slug>) and "name" is its title.
|
|
3187
3229
|
2. Commit these files and push the repo to GitHub.
|
|
3188
|
-
3. Open ${options.api}/
|
|
3230
|
+
3. Open ${options.api}/upload?new=github, import the repo, and every push publishes your game.`);
|
|
3189
3231
|
}
|
|
3190
3232
|
async function readPackage2(dir) {
|
|
3191
3233
|
try {
|
|
@@ -4319,7 +4361,7 @@ ${game.description ? `${game.description}
|
|
|
4319
4361
|
- \`npm test\`: the tests of rules.js (\`node --test\`, nothing to install).
|
|
4320
4362
|
- \`npx @onceworlds/cli check\`: common mistakes, before every deploy. \`shot --story\` shows its first minute.
|
|
4321
4363
|
- \`npx @onceworlds/cli deploy\` (after \`login\`) pushes a draft to play and publish at ${game.api}/create/${game.slug}. Or push the repo to GitHub
|
|
4322
|
-
and import it at ${game.api}/
|
|
4364
|
+
and import it at ${game.api}/upload?new=github: \`.github/workflows/onceworlds.yml\` deploys every push.
|
|
4323
4365
|
`
|
|
4324
4366
|
);
|
|
4325
4367
|
return files;
|
|
@@ -6513,6 +6555,7 @@ import { extname, join as join10, resolve as resolve6, sep } from "node:path";
|
|
|
6513
6555
|
// ../protocol/src/hosting.ts
|
|
6514
6556
|
var GAME_FRAME_SANDBOX = "allow-scripts allow-pointer-lock allow-forms allow-orientation-lock";
|
|
6515
6557
|
var GAME_FRAME_ALLOW = ["autoplay", "gamepad", "screen-wake-lock", "accelerometer", "gyroscope"].map((feature) => `${feature} *`).join("; ");
|
|
6558
|
+
var GAME_STORAGE_BYTES = 100 * 1024 * 1024;
|
|
6516
6559
|
var GAME_CDNS = ["https://cdn.jsdelivr.net", "https://cdnjs.cloudflare.com", "https://unpkg.com", "https://esm.sh", "https://ga.jspm.io"];
|
|
6517
6560
|
var GOOGLE_FONTS = { styles: "https://fonts.googleapis.com", files: "https://fonts.gstatic.com" };
|
|
6518
6561
|
function gameContentSecurityPolicy(options) {
|
|
@@ -6542,6 +6585,9 @@ function gameContentSecurityPolicy(options) {
|
|
|
6542
6585
|
`sandbox ${GAME_FRAME_SANDBOX}`
|
|
6543
6586
|
].join("; ");
|
|
6544
6587
|
}
|
|
6588
|
+
var BRIDGE_PAGE = "onceworlds:page";
|
|
6589
|
+
var PAGE_WATCH_SCRIPT = `(function(){var n=0,s=function(w,d){if(n++<12)try{parent.postMessage({type:${JSON.stringify(BRIDGE_PAGE)},what:w,detail:String(d).slice(0,200)},"*")}catch(e){}};s("ran",document.readyState);addEventListener("securitypolicyviolation",function(e){s("blocked",e.effectiveDirective+" "+e.blockedURI)});addEventListener("error",function(e){var t=e.target;t&&t.tagName?s("failed",t.tagName+" "+(t.src||t.href)):s("threw",e.message+" "+e.filename+":"+e.lineno)},true);addEventListener("load",function(){s("loaded",typeof onceworlds)})})();`;
|
|
6590
|
+
var PAGE_WATCH_TAG = `<script>${PAGE_WATCH_SCRIPT}</script>`;
|
|
6545
6591
|
function gameMayReach(url, kind) {
|
|
6546
6592
|
let parsed;
|
|
6547
6593
|
try {
|
|
@@ -7488,7 +7534,7 @@ var LocalRoom = class {
|
|
|
7488
7534
|
const blocker = startBlocker(lobby);
|
|
7489
7535
|
if (blocker) return this.error(ws, blocker.code, blocker.message);
|
|
7490
7536
|
const countdown = clampCountdown(msg.countdown, config.countdown ?? LIMITS.matchCountdownMs);
|
|
7491
|
-
this.mrec = this.enterStarting(this.mrec, rosterOf(lobby), Date.now(), config, countdown,
|
|
7537
|
+
this.mrec = this.enterStarting(this.mrec, rosterOf(lobby), Date.now(), config, countdown, true);
|
|
7492
7538
|
this.stepMatch();
|
|
7493
7539
|
}
|
|
7494
7540
|
hostOpen(ws, me, msg, config) {
|
|
@@ -8696,7 +8742,7 @@ async function gameSources(root) {
|
|
|
8696
8742
|
return out;
|
|
8697
8743
|
}
|
|
8698
8744
|
async function usesPlatformMatch(root) {
|
|
8699
|
-
return (await gameSources(root)).some((f) => /\b(setReady|startMatch|matchNow)\s*\(|\bmatch\s*\.\s*phase\b|\blobby\s*:\s*['"](card|bar)['"]|\bFlow\s*\.\s*(rounds|turns|coop)\s*\(|\bcreateMatch\s*\(/.test(f.text));
|
|
8745
|
+
return (await gameSources(root)).some((f) => /\b(setReady|startMatch|matchNow)\s*\(|\bmatch\s*\.\s*phase\b|\blobby\s*:\s*['"](card|bar)['"]|\bminPlayers\s*:|\bFlow\s*\.\s*(rounds|turns|coop)\s*\(|\bcreateMatch\s*\(/.test(f.text));
|
|
8700
8746
|
}
|
|
8701
8747
|
async function readProbe(p) {
|
|
8702
8748
|
try {
|
|
@@ -12195,9 +12241,10 @@ function lintAny(input) {
|
|
|
12195
12241
|
for (const { file } of scripts) {
|
|
12196
12242
|
const join18 = /\brooms\s*\.\s*join\s*\(\s*\{[^}]*\blobby\s*:\s*["'](card|bar)["'][^}]*\}/.exec(file.text);
|
|
12197
12243
|
if (!join18 || /\bownCountdown\s*:\s*true\b/.test(join18[0])) continue;
|
|
12198
|
-
const
|
|
12199
|
-
|
|
12200
|
-
|
|
12244
|
+
const own = scripts.some(
|
|
12245
|
+
({ code: text2 }) => [...text2.matchAll(/\bstartsAt\b/g)].some((m) => /fillText|textContent|innerText|innerHTML|\btext\s*\(|draw\w*\s*\(/.test(text2.slice(m.index, m.index + 240).split(/\n\s*\}/)[0]))
|
|
12246
|
+
);
|
|
12247
|
+
if (own) push(file, join18.index, "warn", "countdown", `This joins with the platform's lobby ('${join18[1]}'), which draws the 3-2-1 itself, and the game seems to draw one too (it reads match.startsAt): players see two countdowns. Drop yours.`);
|
|
12201
12248
|
}
|
|
12202
12249
|
for (const { file, code: text2 } of scripts) {
|
|
12203
12250
|
for (const m of text2.matchAll(/\b(setState|send)\s*\(/g)) {
|
|
@@ -12292,12 +12339,15 @@ function checkProjectConfig(config) {
|
|
|
12292
12339
|
return out;
|
|
12293
12340
|
}
|
|
12294
12341
|
const c = config;
|
|
12295
|
-
if (typeof c.slug
|
|
12342
|
+
if (typeof c.slug === "string" && isReservedSlug(c.slug, false)) {
|
|
12343
|
+
error("slug", `The link "${c.slug}" in onceworlds.json isn't available.`, `Pick another link, like "star-party": it is the game's address, onceworlds.com/play/<slug>.`);
|
|
12344
|
+
} else if (typeof c.slug !== "string" || !isValidSlug(c.slug)) {
|
|
12296
12345
|
error("slug", `"slug" in onceworlds.json must be 1-40 lowercase letters, digits or single dashes (got ${JSON.stringify(c.slug)}).`, `Pick a link like "star-party": it is the game's address, onceworlds.com/play/<slug>.`);
|
|
12297
12346
|
}
|
|
12298
12347
|
const name = typeof c.name === "string" ? cleanLine(c.name) : "";
|
|
12299
12348
|
if (!name) error("name", '"name" is required in onceworlds.json.', 'Give the game its title: "name": "Star Party".');
|
|
12300
12349
|
else if (name.length > LIMITS.gameNameLength) error("name", `"name" in onceworlds.json can be at most ${LIMITS.gameNameLength} characters (it has ${name.length}).`, "Shorten the title.");
|
|
12350
|
+
else if (isReservedGameName(name)) note("name", `A game can't take the name "${name}".`, "Give it a title of its own.");
|
|
12301
12351
|
if (c.description !== void 0 && typeof c.description !== "string") {
|
|
12302
12352
|
error("description", '"description" in onceworlds.json must be text.', "Write one or two sentences about the game.");
|
|
12303
12353
|
} else if (typeof c.description === "string" && c.description.trim().length > DESCRIPTION_SHOWN) {
|
|
@@ -12363,8 +12413,29 @@ function checkProjectConfig(config) {
|
|
|
12363
12413
|
return out;
|
|
12364
12414
|
}
|
|
12365
12415
|
|
|
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
|
+
|
|
12366
12437
|
// src/index.ts
|
|
12367
|
-
var CLI_VERSION = "0.
|
|
12438
|
+
var CLI_VERSION = "0.13.0";
|
|
12368
12439
|
var PRODUCTION_API = "https://onceworlds.com";
|
|
12369
12440
|
var LOCAL_API = "http://localhost:8787";
|
|
12370
12441
|
var USAGE = `Usage:
|
|
@@ -12372,7 +12443,7 @@ var USAGE = `Usage:
|
|
|
12372
12443
|
onceworlds logout Forget this computer's login
|
|
12373
12444
|
onceworlds whoami Who you're logged in as
|
|
12374
12445
|
onceworlds mcp Run as an MCP server, so Claude Code, Codex and other AI tools can build games
|
|
12375
|
-
on your account (they push drafts; only you publish, on onceworlds.com/
|
|
12446
|
+
on your account (they push drafts; only you publish, on onceworlds.com/library)
|
|
12376
12447
|
onceworlds new [dir] Start a game on the web platform and the SDK: index.html, game.js on the stack you pick
|
|
12377
12448
|
(--stack canvas, three, pixi or phaser, from a CDN; canvas by default), rules.js with tests
|
|
12378
12449
|
that run in Node, DESIGN.md to write first, the platform guide as AGENTS.md and the deploy
|
|
@@ -12384,7 +12455,7 @@ var USAGE = `Usage:
|
|
|
12384
12455
|
onceworlds init [project-dir] Set a game folder up for Onceworlds: writes onceworlds.json, the GitHub workflow
|
|
12385
12456
|
that publishes every push, and AGENTS.md and CLAUDE.md for AI coding tools
|
|
12386
12457
|
onceworlds deploy [project-dir] Upload the game (to ${PRODUCTION_API}): from GitHub Actions it goes live; after login
|
|
12387
|
-
it is a draft you play and publish
|
|
12458
|
+
it is a draft you play and publish from onceworlds.com/library
|
|
12388
12459
|
onceworlds dev [project-dir] Deploy to a local platform (${LOCAL_API}), then redeploy on every change
|
|
12389
12460
|
onceworlds serve [project-dir] Play any game in your own browser on this computer, offline, with no login: a local platform
|
|
12390
12461
|
(the play page, rooms, lobby and SDK, saves and badges in memory) at http://127.0.0.1:8788/.
|
|
@@ -12462,7 +12533,7 @@ The project directory needs an onceworlds.json:
|
|
|
12462
12533
|
var QUIET_SKIPS = /* @__PURE__ */ new Set(["node_modules", "onceworlds.json", "package.json", "package-lock.json", "tsconfig.json", "jsconfig.json", "LICENSE"]);
|
|
12463
12534
|
var TEST_FILE = /(?:\.(?:test|spec)\.[cm]?[jt]sx?|^vitest\.config\.[cm]?[jt]s)$/;
|
|
12464
12535
|
var DEV_FOLDERS = /* @__PURE__ */ new Set(["test", "tests", "__tests__", "coverage"]);
|
|
12465
|
-
var QUIET_EXTENSIONS = /* @__PURE__ */ new Set(["md", "ts", "tsx", "jsx", "lock", "lockb", "log", "yml", "yaml", "sh"]);
|
|
12536
|
+
var QUIET_EXTENSIONS = /* @__PURE__ */ new Set(["md", "ts", "tsx", "jsx", "lock", "lockb", "log", "yml", "yaml", "sh", "symbols"]);
|
|
12466
12537
|
async function main() {
|
|
12467
12538
|
const { values, positionals } = parseArgs({
|
|
12468
12539
|
allowPositionals: true,
|
|
@@ -12569,7 +12640,8 @@ async function deploy(projectDir, api2, tokenArg, quiet = false, dev2 = false) {
|
|
|
12569
12640
|
for (const path of skipped) console.warn(` ${path}`);
|
|
12570
12641
|
}
|
|
12571
12642
|
if (!files.some((f) => f.path === "index.html")) {
|
|
12572
|
-
|
|
12643
|
+
const hint = notAWebGame([...files.map((f) => f.path), ...skipped]);
|
|
12644
|
+
throw new CliError(`No index.html in ${root}. ${hint ? `${hint} Then set "dir" in onceworlds.json to the folder it makes.` : 'Set "dir" in onceworlds.json to the folder that contains it.'}`);
|
|
12573
12645
|
}
|
|
12574
12646
|
const sources = await readSources(files);
|
|
12575
12647
|
const unnamed = config.engine ? void 0 : sources.find((f) => engineImportAt(f.path, f.text) >= 0);
|
|
@@ -12675,8 +12747,9 @@ async function kitPrintOf(projectDir) {
|
|
|
12675
12747
|
}
|
|
12676
12748
|
async function readSources(files) {
|
|
12677
12749
|
const sources = [];
|
|
12750
|
+
const wasm = new Set(files.filter((f) => /\.wasm$/i.test(f.path)).map((f) => f.path.slice(0, -".wasm".length)));
|
|
12678
12751
|
for (const file of files) {
|
|
12679
|
-
if (!/\.(html?|m?js|css)$/i.test(file.path) || file.size > 15e5) continue;
|
|
12752
|
+
if (!/\.(html?|m?js|css)$/i.test(file.path) || file.size > 15e5 || wasm.has(file.path.replace(/\.m?js$/i, ""))) continue;
|
|
12680
12753
|
sources.push({ path: file.path, text: await readFile9(file.abs, "utf8") });
|
|
12681
12754
|
}
|
|
12682
12755
|
return sources;
|
|
@@ -12874,8 +12947,8 @@ Next:
|
|
|
12874
12947
|
npm test (the tests of rules.js: node --test, nothing to install)
|
|
12875
12948
|
onceworlds check (common mistakes; before every deploy)
|
|
12876
12949
|
onceworlds shot --story (its first minute, as a new player sees it)
|
|
12877
|
-
onceworlds login, then onceworlds deploy (a draft you play and publish
|
|
12878
|
-
To publish from a repo: push it to GitHub and import it at ${api2}/
|
|
12950
|
+
onceworlds login, then onceworlds deploy (a draft you play and publish from ${api2}/library)
|
|
12951
|
+
To publish from a repo: push it to GitHub and import it at ${api2}/upload?new=github (its workflow deploys every push).
|
|
12879
12952
|
Read AGENTS.md: the platform guide, for you and your AI tool. (onceworlds is npx @onceworlds/cli.)`);
|
|
12880
12953
|
return;
|
|
12881
12954
|
}
|
|
@@ -13027,8 +13100,8 @@ async function collectFiles(root) {
|
|
|
13027
13100
|
continue;
|
|
13028
13101
|
}
|
|
13029
13102
|
const data = await readFile9(abs);
|
|
13030
|
-
if (data.byteLength >
|
|
13031
|
-
throw new CliError(`${path} is ${formatBytes(data.byteLength)}; files can be at most ${formatBytes(
|
|
13103
|
+
if (data.byteLength > deployFileLimit(path)) {
|
|
13104
|
+
throw new CliError(`${path} is ${formatBytes(data.byteLength)}; files like it can be at most ${formatBytes(deployFileLimit(path))}.`);
|
|
13032
13105
|
}
|
|
13033
13106
|
files.push({ path, abs, hash: createHash5("sha256").update(data).digest("hex"), size: data.byteLength });
|
|
13034
13107
|
}
|