@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 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/create?new=github,
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/create?new=github.
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: 'card' }) (see AGENTS.md, "Matches").`
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}/create?new=github, import the repo, and every push publishes your game.`);
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}/create?new=github: \`.github/workflows/onceworlds.yml\` deploys every push.
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, msg.open === true);
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 all = scripts.map((s) => s.code).join("\n");
12199
- const own = /\bstartsAt\b/.exec(all) && /countdown|count_down|\b3\s*,\s*2\s*,\s*1\b/i.test(all);
12200
- 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. Say ownCountdown: true in rooms.join to draw yours alone, or drop yours.`);
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 !== "string" || !isValidSlug(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.11.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/create)
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 on onceworlds.com/create
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
- throw new CliError(`No index.html in ${root}. Set "dir" in onceworlds.json to the folder that contains it.`);
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 on ${api2}/create)
12878
- To publish from a repo: push it to GitHub and import it at ${api2}/create?new=github (its workflow deploys every push).
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 > LIMITS.deployFileBytes) {
13031
- throw new CliError(`${path} is ${formatBytes(data.byteLength)}; files can be at most ${formatBytes(LIMITS.deployFileBytes)}.`);
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
  }