@onceworlds/cli 0.2.0 → 0.2.1

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.
Files changed (3) hide show
  1. package/README.md +23 -17
  2. package/dist/cli.js +64 -13
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -6,12 +6,17 @@ players) with no server code.
6
6
 
7
7
  ## Deploy from GitHub Actions
8
8
 
9
- Add `.github/workflows/deploy.yml` to your game's repo:
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/dashboard/new,
11
+ which adds `onceworlds.json` and `.github/workflows/onceworlds.yml` for you (that
12
+ workflow also builds with npm, pnpm, yarn or bun when there's a build script). By
13
+ hand, the minimum is:
10
14
 
11
15
  ```yaml
12
16
  name: Deploy to Onceworlds
13
17
  on:
14
18
  push:
19
+ branches: ['**']
15
20
  permissions:
16
21
  contents: read
17
22
  id-token: write # proves which repo is deploying; no API keys needed
@@ -26,17 +31,15 @@ jobs:
26
31
  - run: npx --yes @onceworlds/cli@latest deploy
27
32
  ```
28
33
 
29
- Pushes to `main` go live at `onceworlds.com/play/<slug>`; other branches get a
30
- preview URL in the job log. Sign in at https://onceworlds.com with the GitHub
34
+ Pushes to the production branch (`main` unless you change it in the dashboard or
35
+ `onceworlds.json`) go live at `onceworlds.com/play/<slug>`; other branches get a
36
+ preview URL in the job log. Log in to https://onceworlds.com with the GitHub
31
37
  account that owns the repo (or any member of its organization) before the first
32
- deploy.
38
+ deploy. If a deploy fails, the dashboard shows why.
33
39
 
34
40
  New games start private: only the repo's owner (or its organization's members)
35
- can play them. When it's ready to share, open the game in the dashboard and click
36
- "Make public" in the Settings tab.
37
-
38
- You can also skip the setup: import the repo at https://onceworlds.com/dashboard/new,
39
- which adds `onceworlds.json` and this workflow for you.
41
+ can play them. To share one, open it in the dashboard and pick Unlisted (anyone
42
+ with the link) or Public (also listed on Onceworlds).
40
43
 
41
44
  ## Project file
42
45
 
@@ -44,19 +47,20 @@ which adds `onceworlds.json` and this workflow for you.
44
47
 
45
48
  ```json
46
49
  {
47
- "slug": "star-party",
48
- "name": "Star Party",
49
- "description": "Grab stars with everyone online.",
50
+ "slug": "kart-rush",
51
+ "name": "Kart Rush",
52
+ "description": "Race friends around twisty tracks.",
50
53
  "dir": ".",
51
- "genre": "Party",
54
+ "genre": "Racing",
52
55
  "icon": "store/icon.png",
53
56
  "thumbnails": ["store/thumb-1.png", "store/thumb-2.png"],
54
- "badges": [{ "id": "first-star", "name": "First Star", "description": "Grab a star." }]
57
+ "badges": [{ "id": "first-win", "name": "Top Step", "description": "Win a race." }]
55
58
  }
56
59
  ```
57
60
 
58
61
  `dir` is the folder to publish (for example `dist` after a build); it must contain
59
- `index.html`. `productionBranch` (default `main`) picks the branch that goes live.
62
+ `index.html`. `productionBranch` (default `main`) picks the branch that goes live;
63
+ the dashboard can change it too, and the file wins again only when this value changes.
60
64
 
61
65
  The rest fills in the game page: `genre`, a square `icon` (at least 128×128),
62
66
  16:9 `thumbnails` (at least 480 wide; the first is the cover) and `badges` (`id`,
@@ -66,8 +70,10 @@ these fields only when they change, so edits made in the dashboard last until th
66
70
 
67
71
  ## Commands
68
72
 
69
- - `onceworlds deploy [dir]` uploads only the files that changed and publishes the game.
70
- - `onceworlds dev [dir]` deploys to a local Onceworlds platform and redeploys on every save.
73
+ - `onceworlds deploy [dir]` uploads only the files that changed and publishes the game
74
+ (the workflow runs it). It lists every file it skipped because Onceworlds can't serve it.
75
+ - `onceworlds dev [dir]` deploys to a local Onceworlds platform (for platform development)
76
+ and redeploys on every save.
71
77
 
72
78
  Games talk to the platform through the injected `window.onceworlds` SDK. The
73
79
  starter template's `AGENTS.md` documents it for AI coding tools.
package/dist/cli.js CHANGED
@@ -39,7 +39,15 @@ var LIMITS = {
39
39
  envValueBytes: 4 * 1024,
40
40
  proxyRequestBytes: 1024 * 1024,
41
41
  proxyResponseBytes: 10 * 1024 * 1024,
42
+ proxyMaxOutputTokens: 8192,
42
43
  proxyCallsPerDay: 2e3,
44
+ proxyCallsPerDayMax: 1e5,
45
+ proxyCallsPerPlayerPerDay: 100,
46
+ proxyCallsPerNetworkPerDay: 1e3,
47
+ proxyMediaCallsPerDay: 100,
48
+ proxyMediaCallsPerDayMax: 1e4,
49
+ proxyMediaCallsPerPlayerPerDay: 20,
50
+ proxyMediaCallsPerNetworkPerDay: 25,
43
51
  errorReportsPerPage: 10,
44
52
  gameNameLength: 60,
45
53
  gameDescriptionLength: 1e3,
@@ -52,19 +60,49 @@ var LIMITS = {
52
60
  leaderboardsPerGame: 25,
53
61
  leaderboardTop: 100,
54
62
  gamesPerOwner: 50,
63
+ /** New games per creator in any rolling 24-hour period. Redeploys do not count. */
64
+ gamesCreatedPerCreatorPerDay: 3,
55
65
  deploysPerOwnerPerDay: 100,
66
+ /** Finished versions retained per game, including its live version. */
67
+ deploymentsKeptPerGame: 10,
56
68
  deployBytesPerOwnerPerDay: 2 * 1024 * 1024 * 1024,
57
69
  bioLength: 500,
58
70
  friendsMax: 200,
59
71
  friendRequestsMax: 100,
60
72
  passwordMinLength: 8,
61
- passwordMaxLength: 72
73
+ passwordMaxLength: 72,
74
+ /** Account creation attempts, with or without email, per network (IP, or IPv6 /64) in 24 hours. */
75
+ signupsPerNetworkPerDay: 100,
76
+ /** New guest players per network per day (each one can hold saves). */
77
+ guestsPerNetworkPerDay: 300,
78
+ /** Visits a game gets per network (IP, or IPv6 /64) per day: a script can't pump the count. */
79
+ visitsPerNetworkPerDay: 10,
80
+ /** Seconds of visible play before a play counts as a visit. */
81
+ visitSeconds: 10,
82
+ /** Distinct errors kept per deploy of a game (more are counted by nobody). */
83
+ errorsPerDeployment: 100,
84
+ /** On-screen controls: buttons beside the thumbstick, and their labels. */
85
+ controlButtons: 4,
86
+ controlLabelLength: 12,
87
+ /** Friends in one party, the leader included. */
88
+ partySize: 6,
89
+ partyChatHistory: 50,
90
+ /** Messages outside games: saved chats between friends. */
91
+ messageLength: 500,
92
+ messagesPerPage: 50,
93
+ groupChatMembers: 20,
94
+ groupChatNameLength: 40,
95
+ conversationsListed: 50,
96
+ notificationsListed: 50,
97
+ /** Friends one game invite can go to. */
98
+ invitesPerRequest: 10
62
99
  };
63
100
  var CONTENT_TYPES = {
64
101
  html: "text/html; charset=utf-8",
65
102
  htm: "text/html; charset=utf-8",
66
103
  js: "text/javascript; charset=utf-8",
67
104
  mjs: "text/javascript; charset=utf-8",
105
+ cjs: "text/javascript; charset=utf-8",
68
106
  css: "text/css; charset=utf-8",
69
107
  json: "application/json; charset=utf-8",
70
108
  map: "application/json; charset=utf-8",
@@ -90,6 +128,7 @@ var CONTENT_TYPES = {
90
128
  opus: "audio/ogg",
91
129
  wav: "audio/wav",
92
130
  m4a: "audio/mp4",
131
+ aac: "audio/aac",
93
132
  flac: "audio/flac",
94
133
  mp4: "video/mp4",
95
134
  webm: "video/webm",
@@ -100,6 +139,11 @@ var CONTENT_TYPES = {
100
139
  mtl: "text/plain; charset=utf-8",
101
140
  fbx: "application/octet-stream",
102
141
  wasm: "application/wasm",
142
+ // Shaders, loaded as text.
143
+ glsl: "text/plain; charset=utf-8",
144
+ vert: "text/plain; charset=utf-8",
145
+ frag: "text/plain; charset=utf-8",
146
+ wgsl: "text/plain; charset=utf-8",
103
147
  pck: "application/octet-stream",
104
148
  data: "application/octet-stream",
105
149
  woff: "font/woff",
@@ -132,6 +176,7 @@ var RESERVED_SLUGS = /* @__PURE__ */ new Set(["www", "api", "play", "admin", "st
132
176
  function isValidSlug(slug) {
133
177
  return SLUG_RE.test(slug) && !slug.includes("--") && !RESERVED_SLUGS.has(slug);
134
178
  }
179
+ var DEFAULT_PRODUCTION_BRANCH = "main";
135
180
  var BADGE_KEY_RE = /^[a-z0-9](?:[a-z0-9-]{0,38}[a-z0-9])?$/;
136
181
  var GENRES = [
137
182
  "Action",
@@ -217,15 +262,18 @@ var USAGE = `Usage:
217
262
 
218
263
  Options:
219
264
  --api <url> Platform URL (default: $ONCEWORLDS_API, else the ones above)
220
- --token <t> Deploy token (default: $ONCEWORLDS_TOKEN, GitHub Actions OIDC, or "dev" on localhost)
265
+ --token <t> Local platform's deploy token (default: "dev")
266
+
267
+ Deploys to onceworlds.com run from GitHub Actions: the repo's Onceworlds
268
+ workflow runs this on every push, and GitHub vouches for the repo.
221
269
 
222
270
  The project directory needs an onceworlds.json:
223
- { "slug": "star-party", "name": "Star Party", "dir": "." }
271
+ { "slug": "my-game", "name": "My Game", "dir": "." }
224
272
  `;
225
273
  var CliError = class extends Error {
226
274
  };
227
275
  var QUIET_SKIPS = /* @__PURE__ */ new Set(["node_modules", "onceworlds.json", "package.json", "package-lock.json", "tsconfig.json", "jsconfig.json", "LICENSE"]);
228
- var QUIET_EXTENSIONS = /* @__PURE__ */ new Set(["md", "ts", "tsx", "jsx", "lock", "log", "yml", "yaml", "sh"]);
276
+ var QUIET_EXTENSIONS = /* @__PURE__ */ new Set(["md", "ts", "tsx", "jsx", "lock", "lockb", "log", "yml", "yaml", "sh"]);
229
277
  async function main() {
230
278
  const { values, positionals } = parseArgs({
231
279
  allowPositionals: true,
@@ -251,7 +299,11 @@ ${USAGE}`);
251
299
  async function deploy(projectDir, api, tokenArg, quiet = false) {
252
300
  const config = await loadConfig(projectDir);
253
301
  const root = resolve(projectDir, config.dir ?? ".");
254
- const files = await collectFiles(root, quiet);
302
+ const { files, skipped } = await collectFiles(root);
303
+ if (skipped.length > 0) {
304
+ console.warn(` ! ${skipped.length} unsupported file${skipped.length === 1 ? "" : "s"} not deployed:`);
305
+ for (const path of skipped) console.warn(` ${path}`);
306
+ }
255
307
  if (!files.some((f) => f.path === "index.html")) {
256
308
  throw new CliError(`No index.html in ${root}. Set "dir" in onceworlds.json to the folder that contains it.`);
257
309
  }
@@ -265,7 +317,7 @@ async function deploy(projectDir, api, tokenArg, quiet = false) {
265
317
  slug: config.slug,
266
318
  name: config.name,
267
319
  description: config.description ?? "",
268
- productionBranch: config.productionBranch ?? "main",
320
+ productionBranch: config.productionBranch ?? DEFAULT_PRODUCTION_BRANCH,
269
321
  store
270
322
  },
271
323
  files: files.map(({ path, hash, size }) => ({ path, hash, size })),
@@ -282,8 +334,9 @@ async function deploy(projectDir, api, tokenArg, quiet = false) {
282
334
  });
283
335
  const result = await send("POST", `${api}/api/deploy/${start.deploymentId}/finalize`, start.uploadToken, {});
284
336
  if (!quiet) {
337
+ const branch = result.productionBranch ?? start.productionBranch;
285
338
  if (result.production) console.log(`\u2713 Live: ${result.playUrl}`);
286
- else console.log(`\u2713 Preview (this branch isn't ${config.productionBranch ?? "main"}): ${result.previewUrl}`);
339
+ else console.log(`\u2713 Preview${branch ? ` (only ${branch} goes live)` : ""}: ${result.previewUrl}`);
287
340
  for (const note of result.notes ?? []) console.log(` ${note}`);
288
341
  }
289
342
  return result;
@@ -296,7 +349,7 @@ async function dev(projectDir, api, tokenArg) {
296
349
  Play (reloads on every change): ${first.playUrl}?watch
297
350
  Watching ${root}. Press Ctrl+C to stop.
298
351
  `);
299
- const projectSignature = async () => signature(await collectFiles(root, true)) + await readFile(join(projectDir, "onceworlds.json"), "utf8").catch(() => "");
352
+ const projectSignature = async () => signature((await collectFiles(root)).files) + await readFile(join(projectDir, "onceworlds.json"), "utf8").catch(() => "");
300
353
  let lastSignature = await projectSignature();
301
354
  let timer;
302
355
  let running = false;
@@ -409,7 +462,7 @@ async function loadStore(projectDir, config) {
409
462
  }
410
463
  return { store: { genre, icon, thumbnails, badges }, images: [...images.values()] };
411
464
  }
412
- async function collectFiles(root, quiet) {
465
+ async function collectFiles(root) {
413
466
  const files = [];
414
467
  const skipped = [];
415
468
  async function walk(dir, prefix) {
@@ -439,9 +492,8 @@ async function collectFiles(root, quiet) {
439
492
  }
440
493
  }
441
494
  await walk(root, "");
442
- if (skipped.length > 0 && !quiet) console.warn(` ! skipped unsupported files: ${skipped.join(", ")}`);
443
495
  if (files.length > LIMITS.deployFiles) throw new CliError(`Too many files (${files.length}); the limit is ${LIMITS.deployFiles}.`);
444
- return files;
496
+ return { files, skipped };
445
497
  }
446
498
  function signature(files) {
447
499
  return files.map((f) => `${f.path}:${f.hash}`).join("\n");
@@ -458,7 +510,6 @@ function gitInfo(cwd) {
458
510
  }
459
511
  async function getToken(api, tokenArg) {
460
512
  if (tokenArg) return tokenArg;
461
- if (process.env.ONCEWORLDS_TOKEN) return process.env.ONCEWORLDS_TOKEN;
462
513
  const requestUrl = process.env.ACTIONS_ID_TOKEN_REQUEST_URL;
463
514
  const requestToken = process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
464
515
  if (requestUrl && requestToken) {
@@ -471,7 +522,7 @@ async function getToken(api, tokenArg) {
471
522
  const host = new URL(api).hostname;
472
523
  if (host === "localhost" || host === "127.0.0.1" || host === "[::1]" || host.endsWith(".localhost")) return "dev";
473
524
  throw new CliError(
474
- "No deploy credentials. In GitHub Actions, add `permissions: id-token: write` to the workflow. Elsewhere, set ONCEWORLDS_TOKEN."
525
+ "Deploys to Onceworlds run from GitHub Actions: push to GitHub and the repo's Onceworlds workflow deploys (it needs `permissions: id-token: write`)."
475
526
  );
476
527
  }
477
528
  async function send(method, url, token, body) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onceworlds/cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Deploy games to Onceworlds.",
5
5
  "homepage": "https://onceworlds.com",
6
6
  "type": "module",