@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.
- package/README.md +23 -17
- package/dist/cli.js +64 -13
- 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
|
-
|
|
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`
|
|
30
|
-
|
|
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.
|
|
36
|
-
|
|
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": "
|
|
48
|
-
"name": "
|
|
49
|
-
"description": "
|
|
50
|
+
"slug": "kart-rush",
|
|
51
|
+
"name": "Kart Rush",
|
|
52
|
+
"description": "Race friends around twisty tracks.",
|
|
50
53
|
"dir": ".",
|
|
51
|
-
"genre": "
|
|
54
|
+
"genre": "Racing",
|
|
52
55
|
"icon": "store/icon.png",
|
|
53
56
|
"thumbnails": ["store/thumb-1.png", "store/thumb-2.png"],
|
|
54
|
-
"badges": [{ "id": "first-
|
|
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
|
-
|
|
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>
|
|
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": "
|
|
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
|
|
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 ??
|
|
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
|
|
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
|
|
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
|
|
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
|
-
"
|
|
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) {
|