cajonzero 0.1.0 → 0.1.2
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 +20 -22
- package/dist/bin/cj0.js +404 -92
- package/dist/{config-Che_6xEE.d.ts → config-BOzAyP7m.d.ts} +1 -3
- package/dist/core.d.ts +8 -32
- package/dist/core.js +65 -141
- package/dist/index.d.ts +5 -7
- package/dist/index.js +6270 -6341
- package/dist/{providers-CJgLgWh4.d.ts → providers-BX7e4pNW.d.ts} +110 -30
- package/dist/scaffold.d.ts +60 -2
- package/dist/scaffold.js +307 -84
- package/dist/serve.d.ts +2 -4
- package/dist/serve.js +137 -208
- package/dist/ui/assets/index-Bhv7LV8L.css +2 -0
- package/dist/ui/assets/index-CaAHje8X.js +4404 -0
- package/dist/ui/assets/{main-BwrBUsrr.js → main-BeABKxzY.js} +22 -22
- package/dist/ui/index.html +2 -2
- package/package.json +7 -6
- package/dist/bin/cj0.js.map +0 -1
- package/dist/core.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/scaffold.js.map +0 -1
- package/dist/serve.js.map +0 -1
- package/dist/ui/assets/index-BLz2Y_Tv.js +0 -4405
- package/dist/ui/assets/index-W1HlDaw6.css +0 -2
package/dist/bin/cj0.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { mkdirSync, existsSync,
|
|
3
|
-
import
|
|
2
|
+
import { mkdirSync, existsSync, readFileSync, writeFileSync, statSync, readdirSync } from 'fs';
|
|
3
|
+
import path3 from 'path';
|
|
4
|
+
import { createInterface } from 'readline/promises';
|
|
4
5
|
import { fileURLToPath } from 'url';
|
|
6
|
+
import { randomBytes } from 'crypto';
|
|
5
7
|
|
|
6
8
|
// src/scaffold/templates.ts
|
|
7
9
|
var SCAFFOLD_ENTRY_PATH = "src/index.ts";
|
|
@@ -34,7 +36,6 @@ var SCAFFOLD_ENTRY_CONTENT = [
|
|
|
34
36
|
" sessionSecret: (env) => env.CAJONZERO_SESSION_SECRET,",
|
|
35
37
|
" adminToken: (env) => env.BOX_PLAY_ADMIN_TOKEN,",
|
|
36
38
|
" environment: (env) => env.CAJONZERO_ENV,",
|
|
37
|
-
" mapSeedKv: (env) => env.CAJONZERO_KV,",
|
|
38
39
|
" rooms: (env) => env.ROOMS,",
|
|
39
40
|
" settings: (env) => {",
|
|
40
41
|
" const e = vars(env);",
|
|
@@ -52,7 +53,7 @@ var SCAFFOLD_ENTRY_CONTENT = [
|
|
|
52
53
|
" };",
|
|
53
54
|
" },",
|
|
54
55
|
"});",
|
|
55
|
-
'export { Room } from "cajonzero";',
|
|
56
|
+
'export { Room } from "cajonzero/serve";',
|
|
56
57
|
""
|
|
57
58
|
].join("\n");
|
|
58
59
|
var SCAFFOLD_CONFIG_CONTENT = `${JSON.stringify(
|
|
@@ -61,7 +62,6 @@ var SCAFFOLD_CONFIG_CONTENT = `${JSON.stringify(
|
|
|
61
62
|
admin: { path: "/admin" },
|
|
62
63
|
room: { tickRate: 64, emptyGraceMs: 48e4 },
|
|
63
64
|
maps: {
|
|
64
|
-
cacheTtlSeconds: 86400,
|
|
65
65
|
maxDownloadBytes: 33554432,
|
|
66
66
|
concurrency: 8,
|
|
67
67
|
requestTimeoutMs: 15e3
|
|
@@ -126,7 +126,7 @@ function wranglerConfig(options) {
|
|
|
126
126
|
" },",
|
|
127
127
|
' "kv_namespaces": [',
|
|
128
128
|
" {",
|
|
129
|
-
" // Player/account data + map-script KV
|
|
129
|
+
" // Player/account data + map-script KV.",
|
|
130
130
|
' "binding": "CAJONZERO_KV",',
|
|
131
131
|
" // Replace with `wrangler kv namespace create CAJONZERO_KV` output.",
|
|
132
132
|
` "id": ${JSON.stringify(SCAFFOLD_KV_PLACEHOLDER_ID)}`,
|
|
@@ -158,82 +158,49 @@ function readme(options) {
|
|
|
158
158
|
return [
|
|
159
159
|
`# ${options.packageName}`,
|
|
160
160
|
"",
|
|
161
|
-
"
|
|
162
|
-
"
|
|
161
|
+
"\u7531 cajonzero \u751F\u6210\u7684 Cloudflare Workers \u670D\u52A1\u3002\u9879\u76EE\u53EA\u6709\u914D\u7F6E\u548C\u4E00\u4E2A\u5F88\u8584\u7684 worker",
|
|
162
|
+
"\u5165\u53E3\uFF0C\u6CA1\u6709\u5F15\u64CE\u6E90\u7801\u3002",
|
|
163
163
|
"",
|
|
164
|
-
"
|
|
165
|
-
"worker entry that hands `cajonzero/config.json` and `cajonzero/maps.json` to the",
|
|
166
|
-
"runtime:",
|
|
167
|
-
"",
|
|
168
|
-
"```ts",
|
|
169
|
-
'import config from "../cajonzero/config.json";',
|
|
170
|
-
'import maps from "../cajonzero/maps.json";',
|
|
171
|
-
'import { serve } from "cajonzero/serve";',
|
|
172
|
-
"",
|
|
173
|
-
"export default serve({",
|
|
174
|
-
" config,",
|
|
175
|
-
" maps,",
|
|
176
|
-
" storage: (env) => /* any unstorage driver over env bindings */,",
|
|
177
|
-
" sessionSecret: (env) => env.CAJONZERO_SESSION_SECRET,",
|
|
178
|
-
" // adminToken / environment / mapSeedKv / rooms / settings \u2026",
|
|
179
|
-
"});",
|
|
180
|
-
'export { Room } from "cajonzero";',
|
|
181
|
-
"```",
|
|
182
|
-
"",
|
|
183
|
-
"## Quick start",
|
|
164
|
+
"## \u5FEB\u901F\u5F00\u59CB",
|
|
184
165
|
"",
|
|
185
166
|
"```bash",
|
|
186
|
-
"npm install
|
|
187
|
-
"
|
|
167
|
+
"npm install",
|
|
168
|
+
"npx cj0 setup # \u4EA4\u4E92\u5F0F\u914D\u7F6E\u5FC5\u9700/\u53EF\u9009\u7684 ENV",
|
|
169
|
+
"npm run dev # http://localhost:8787",
|
|
188
170
|
"```",
|
|
189
171
|
"",
|
|
190
|
-
"
|
|
191
|
-
"publishing (`wrangler deploy --dry-run`).",
|
|
192
|
-
"",
|
|
193
|
-
"## Environment",
|
|
194
|
-
"",
|
|
195
|
-
"Every value the runtime consumes is passed explicitly in `src/index.ts`;",
|
|
196
|
-
"the package never guesses env var names.",
|
|
197
|
-
"",
|
|
198
|
-
"| Name | Kind | Required | Purpose |",
|
|
199
|
-
"|---|---|---|---|",
|
|
200
|
-
"| `CAJONZERO_KV` | KV binding | yes | player/account data + map-seed cache |",
|
|
201
|
-
"| `ROOMS` | Durable Object binding | yes | room actors |",
|
|
202
|
-
"| `CAJONZERO_SESSION_SECRET` | secret | yes | signs session proofs and login cookies |",
|
|
203
|
-
"| `BOX_PLAY_ADMIN_TOKEN` | secret | yes | owner console and `/api/admin/*` |",
|
|
204
|
-
"| `CAJONZERO_ENV` | var | no | deployment label (`development` relaxes cookie `Secure`) |",
|
|
205
|
-
"| `CAJONZERO_PLAYER_LIMIT` | var | no | maximum players per room |",
|
|
206
|
-
"| `CAJONZERO_ZERO_PLAYER_TIMEOUT_MS` | var | no | empty-room grace window |",
|
|
207
|
-
"| `CAJONZERO_AUTOSAVE_INTERVAL_MS` | var | no | snapshot cadence |",
|
|
208
|
-
"| `CAJONZERO_DEBUG` | var | no | verbose logging |",
|
|
209
|
-
"| `CAJONZERO_TELEPORT_ACCESS` | var | no | allow non-team containers |",
|
|
210
|
-
"| `CAJONZERO_PAID_ACCESS` | var | no | restrict joins to paid containers |",
|
|
211
|
-
"| `CAJONZERO_SCRIPT_HEAP_MB` | var | no | QuickJS heap per script host |",
|
|
212
|
-
"| `CAJONZERO_SCRIPT_TIMEOUT_MS` | var | no | script invocation timeout |",
|
|
213
|
-
"| `CAJONZERO_DB_SIZE_LIMIT` | var | no | map-script storage budget |",
|
|
172
|
+
"## \u73AF\u5883\u53D8\u91CF",
|
|
214
173
|
"",
|
|
215
|
-
"
|
|
174
|
+
"`cj0 setup` \u628A\u975E\u5BC6\u94A5\u5199\u5165 `wrangler.jsonc` \u7684 `vars`\uFF0C\u628A\u5BC6\u94A5\u5199\u5165 `.dev.vars`\uFF08\u672C\u5730\uFF09\u3002",
|
|
175
|
+
"\u90E8\u7F72\u65F6\u5BC6\u94A5\u8981\u5355\u72EC\u8BBE\u7F6E\uFF1A",
|
|
216
176
|
"",
|
|
217
177
|
"```bash",
|
|
218
|
-
"npx wrangler kv namespace create CAJONZERO_KV #
|
|
178
|
+
"npx wrangler kv namespace create CAJONZERO_KV # id \u586B\u8FDB wrangler.jsonc",
|
|
219
179
|
"npx wrangler secret put CAJONZERO_SESSION_SECRET",
|
|
220
180
|
"npx wrangler secret put BOX_PLAY_ADMIN_TOKEN",
|
|
221
181
|
"```",
|
|
222
182
|
"",
|
|
223
|
-
"
|
|
224
|
-
"",
|
|
225
|
-
"
|
|
226
|
-
"
|
|
227
|
-
"
|
|
183
|
+
"| \u540D\u79F0 | \u7C7B\u578B | \u5FC5\u9700 | \u8BF4\u660E |",
|
|
184
|
+
"|---|---|---|---|",
|
|
185
|
+
"| `CAJONZERO_KV` | KV \u7ED1\u5B9A | \u662F | \u8D26\u53F7/\u73A9\u5BB6\u6570\u636E + \u5730\u56FE\u811A\u672C KV |",
|
|
186
|
+
"| `ROOMS` | Durable Object \u7ED1\u5B9A | \u662F | \u623F\u95F4\u5B9E\u4F8B |",
|
|
187
|
+
"| `CAJONZERO_SESSION_SECRET` | \u5BC6\u94A5 | \u662F | \u4F1A\u8BDD\u51ED\u8BC1\u4E0E Cookie \u7B7E\u540D |",
|
|
188
|
+
"| `BOX_PLAY_ADMIN_TOKEN` | \u5BC6\u94A5 | \u662F | \u7BA1\u7406\u540E\u53F0\u4EE4\u724C |",
|
|
189
|
+
"| `CAJONZERO_ENV` | \u53D8\u91CF | \u5426 | \u90E8\u7F72\u540D\u79F0\uFF08`development` \u65F6 Cookie \u4E0D\u52A0 Secure\uFF09 |",
|
|
190
|
+
"| `CAJONZERO_PLAYER_LIMIT` | \u53D8\u91CF | \u5426 | \u5355\u623F\u95F4\u73A9\u5BB6\u4E0A\u9650 |",
|
|
191
|
+
"| `CAJONZERO_ZERO_PLAYER_TIMEOUT_MS` | \u53D8\u91CF | \u5426 | \u7A7A\u623F\u95F4\u4FDD\u7559\u65F6\u95F4 |",
|
|
192
|
+
"| `CAJONZERO_AUTOSAVE_INTERVAL_MS` | \u53D8\u91CF | \u5426 | \u81EA\u52A8\u5B58\u6863\u95F4\u9694 |",
|
|
193
|
+
"| `CAJONZERO_DEBUG` | \u53D8\u91CF | \u5426 | \u8C03\u8BD5\u65E5\u5FD7 |",
|
|
194
|
+
"| `CAJONZERO_TELEPORT_ACCESS` | \u53D8\u91CF | \u5426 | \u5141\u8BB8\u4F20\u9001\u7C7B\u5BB9\u5668 |",
|
|
195
|
+
"| `CAJONZERO_PAID_ACCESS` | \u53D8\u91CF | \u5426 | \u4EC5\u4ED8\u8D39\u5BB9\u5668\u53EF\u52A0\u5165 |",
|
|
196
|
+
"| `CAJONZERO_SCRIPT_HEAP_MB` | \u53D8\u91CF | \u5426 | \u811A\u672C\u5806\u4E0A\u9650 |",
|
|
197
|
+
"| `CAJONZERO_SCRIPT_TIMEOUT_MS` | \u53D8\u91CF | \u5426 | \u5355\u6B21\u811A\u672C\u8D85\u65F6 |",
|
|
198
|
+
"| `CAJONZERO_DB_SIZE_LIMIT` | \u53D8\u91CF | \u5426 | \u5730\u56FE\u811A\u672C\u5B58\u50A8\u4E0A\u9650 |",
|
|
228
199
|
"",
|
|
229
|
-
"
|
|
230
|
-
"<root>/bundle.json",
|
|
231
|
-
"<root>/worlds/<worldId>/manifest.json",
|
|
232
|
-
"<root>/worlds/<worldId>/data/project-state.json",
|
|
233
|
-
"<root>/worlds/<worldId>/data/blobs/<cid>",
|
|
234
|
-
"```",
|
|
200
|
+
"## \u5730\u56FE",
|
|
235
201
|
"",
|
|
236
|
-
"
|
|
202
|
+
"\u5730\u56FE\u4E0D\u968F worker \u4E0A\u4F20\u3002\u628A\u89E3\u5305\u540E\u7684 `.boxplay` \u76EE\u5F55\u4F20\u5230 OSS/S3/CDN\uFF08\u9700\u5141\u8BB8 CORS\uFF09\uFF0C",
|
|
203
|
+
"\u518D\u5728 `cajonzero/maps.json` \u767B\u8BB0\uFF1A",
|
|
237
204
|
"",
|
|
238
205
|
"```json",
|
|
239
206
|
"{",
|
|
@@ -243,32 +210,21 @@ function readme(options) {
|
|
|
243
210
|
' "slug": "omega",',
|
|
244
211
|
' "root": "https://cdn.example.com/cajonzero/omega/",',
|
|
245
212
|
' "worldId": "project-omega",',
|
|
246
|
-
' "name": "Omega City Reloaded"
|
|
247
|
-
' "version": "1.0.4",',
|
|
248
|
-
' "etag": "v1.0.4"',
|
|
213
|
+
' "name": "Omega City Reloaded"',
|
|
249
214
|
" }",
|
|
250
215
|
" ]",
|
|
251
216
|
"}",
|
|
252
217
|
"```",
|
|
253
218
|
"",
|
|
254
|
-
"
|
|
255
|
-
"- `etag` (or `version`) is the seed-cache revision: bump it whenever you",
|
|
256
|
-
" re-upload the map, otherwise the cached world stays stale.",
|
|
257
|
-
"- The bucket may be public-read; the server fetches it, so no CORS is needed",
|
|
258
|
-
" for gameplay. Enable CORS only if the browser loads assets from it.",
|
|
219
|
+
"`root` \u5FC5\u987B\u4EE5 `/` \u7ED3\u5C3E\uFF1B\u66F4\u65B0\u5730\u56FE\u540E\u5237\u65B0 CDN \u7F13\u5B58\u6216\u6362\u8DEF\u5F84\u3002",
|
|
259
220
|
"",
|
|
260
|
-
"##
|
|
261
|
-
"",
|
|
262
|
-
"- **Play lobby** \u2014 the worker serves the lobby at `/`.",
|
|
263
|
-
"- **Admin console** \u2014 at `/admin` (sign in with `BOX_PLAY_ADMIN_TOKEN`) for",
|
|
264
|
-
" player bans and runtime settings; its JSON API is under `/api/admin/*`.",
|
|
265
|
-
"- **Map catalog** \u2014 `GET /api/maps` returns the entries from `maps.json`.",
|
|
266
|
-
"",
|
|
267
|
-
"## Deploy",
|
|
221
|
+
"## \u90E8\u7F72",
|
|
268
222
|
"",
|
|
269
223
|
"```bash",
|
|
270
|
-
"npm run deploy
|
|
224
|
+
"npm run deploy",
|
|
271
225
|
"```",
|
|
226
|
+
"",
|
|
227
|
+
"\u5927\u5385\u5728 `/`\uFF0C\u7BA1\u7406\u540E\u53F0\u5728 `/admin`\u3002",
|
|
272
228
|
""
|
|
273
229
|
].join("\n");
|
|
274
230
|
}
|
|
@@ -310,7 +266,7 @@ function readCajonzeroPackage(manifestPath) {
|
|
|
310
266
|
return { version, wranglerRange, unstorageRange };
|
|
311
267
|
}
|
|
312
268
|
function toPackageName(folder) {
|
|
313
|
-
const base =
|
|
269
|
+
const base = path3.basename(folder);
|
|
314
270
|
const slug = base.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[-._]+/, "").replace(/[-._]+$/, "");
|
|
315
271
|
return slug.length > 0 ? slug : "cajonzero-app";
|
|
316
272
|
}
|
|
@@ -325,7 +281,7 @@ function createProject(folder, options = {}) {
|
|
|
325
281
|
throw new ScaffoldError("INVALID_TARGET", "create requires a target folder");
|
|
326
282
|
}
|
|
327
283
|
const cwd = options.cwd ?? process.cwd();
|
|
328
|
-
const target =
|
|
284
|
+
const target = path3.resolve(cwd, name);
|
|
329
285
|
if (existsSync(target) && !statSync(target).isDirectory()) {
|
|
330
286
|
throw new ScaffoldError("INVALID_TARGET", `target exists and is not a directory: ${target}`);
|
|
331
287
|
}
|
|
@@ -348,12 +304,280 @@ function createProject(folder, options = {}) {
|
|
|
348
304
|
});
|
|
349
305
|
mkdirSync(target, { recursive: true });
|
|
350
306
|
for (const file of files) {
|
|
351
|
-
const fullPath =
|
|
352
|
-
mkdirSync(
|
|
307
|
+
const fullPath = path3.join(target, file.path);
|
|
308
|
+
mkdirSync(path3.dirname(fullPath), { recursive: true });
|
|
353
309
|
writeFileSync(fullPath, file.content, "utf8");
|
|
354
310
|
}
|
|
355
311
|
return { target, files: files.map((file) => file.path) };
|
|
356
312
|
}
|
|
313
|
+
var SETTING_FIELDS = [
|
|
314
|
+
{
|
|
315
|
+
name: "CAJONZERO_PLAYER_LIMIT",
|
|
316
|
+
label: "\u5355\u623F\u95F4\u73A9\u5BB6\u4E0A\u9650",
|
|
317
|
+
kind: "var",
|
|
318
|
+
required: false,
|
|
319
|
+
hint: "\u9ED8\u8BA4 100"
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
name: "CAJONZERO_ZERO_PLAYER_TIMEOUT_MS",
|
|
323
|
+
label: "\u7A7A\u623F\u95F4\u4FDD\u7559\u65F6\u95F4\uFF08\u6BEB\u79D2\uFF09",
|
|
324
|
+
kind: "var",
|
|
325
|
+
required: false,
|
|
326
|
+
hint: "\u9ED8\u8BA4 480000\uFF088 \u5206\u949F\uFF09"
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
name: "CAJONZERO_AUTOSAVE_INTERVAL_MS",
|
|
330
|
+
label: "\u81EA\u52A8\u5B58\u6863\u95F4\u9694\uFF08\u6BEB\u79D2\uFF09",
|
|
331
|
+
kind: "var",
|
|
332
|
+
required: false,
|
|
333
|
+
hint: "\u9ED8\u8BA4 300000"
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
name: "CAJONZERO_DEBUG",
|
|
337
|
+
label: "\u8C03\u8BD5\u65E5\u5FD7\uFF08true/false\uFF09",
|
|
338
|
+
kind: "var",
|
|
339
|
+
required: false,
|
|
340
|
+
hint: "\u9ED8\u8BA4 false"
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
name: "CAJONZERO_TELEPORT_ACCESS",
|
|
344
|
+
label: "\u5141\u8BB8\u4F20\u9001\u7C7B\u5BB9\u5668\uFF08true/false\uFF09",
|
|
345
|
+
kind: "var",
|
|
346
|
+
required: false,
|
|
347
|
+
hint: "\u9ED8\u8BA4 false"
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
name: "CAJONZERO_PAID_ACCESS",
|
|
351
|
+
label: "\u4EC5\u4ED8\u8D39\u5BB9\u5668\u53EF\u52A0\u5165\uFF08true/false\uFF09",
|
|
352
|
+
kind: "var",
|
|
353
|
+
required: false,
|
|
354
|
+
hint: "\u9ED8\u8BA4 false"
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
name: "CAJONZERO_SCRIPT_HEAP_MB",
|
|
358
|
+
label: "\u811A\u672C\u5806\u4E0A\u9650\uFF08MB\uFF09",
|
|
359
|
+
kind: "var",
|
|
360
|
+
required: false,
|
|
361
|
+
hint: "\u9ED8\u8BA4 256"
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
name: "CAJONZERO_SCRIPT_TIMEOUT_MS",
|
|
365
|
+
label: "\u5355\u6B21\u811A\u672C\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",
|
|
366
|
+
kind: "var",
|
|
367
|
+
required: false,
|
|
368
|
+
hint: "\u9ED8\u8BA4 10000"
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
name: "CAJONZERO_DB_SIZE_LIMIT",
|
|
372
|
+
label: "\u5730\u56FE\u811A\u672C\u5B58\u50A8\u4E0A\u9650\uFF08\u5B57\u8282\uFF09",
|
|
373
|
+
kind: "var",
|
|
374
|
+
required: false,
|
|
375
|
+
hint: "\u9ED8\u8BA4 134217728"
|
|
376
|
+
}
|
|
377
|
+
];
|
|
378
|
+
var SETUP_FIELDS = [
|
|
379
|
+
{
|
|
380
|
+
name: "CAJONZERO_SESSION_SECRET",
|
|
381
|
+
label: "\u4F1A\u8BDD\u7B7E\u540D\u5BC6\u94A5",
|
|
382
|
+
kind: "secret",
|
|
383
|
+
required: true,
|
|
384
|
+
hint: "\u7559\u7A7A\u81EA\u52A8\u751F\u6210\uFF1B\u672C\u5730\u5199\u5165 .dev.vars"
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
name: "BOX_PLAY_ADMIN_TOKEN",
|
|
388
|
+
label: "\u7BA1\u7406\u540E\u53F0\u4EE4\u724C",
|
|
389
|
+
kind: "secret",
|
|
390
|
+
required: true,
|
|
391
|
+
hint: "\u7559\u7A7A\u81EA\u52A8\u751F\u6210\uFF1B\u672C\u5730\u5199\u5165 .dev.vars"
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
name: "CAJONZERO_ENV",
|
|
395
|
+
label: "\u90E8\u7F72\u540D\u79F0",
|
|
396
|
+
kind: "var",
|
|
397
|
+
required: true,
|
|
398
|
+
default: "development",
|
|
399
|
+
hint: "development \u65F6 Cookie \u4E0D\u52A0 Secure"
|
|
400
|
+
},
|
|
401
|
+
...SETTING_FIELDS
|
|
402
|
+
];
|
|
403
|
+
function generateSecret() {
|
|
404
|
+
return randomBytes(32).toString("hex");
|
|
405
|
+
}
|
|
406
|
+
function generateAdminToken() {
|
|
407
|
+
return randomBytes(24).toString("base64url");
|
|
408
|
+
}
|
|
409
|
+
function suggestedValue(field) {
|
|
410
|
+
if (field.default !== void 0) return field.default;
|
|
411
|
+
if (field.kind === "secret") {
|
|
412
|
+
return field.name === "BOX_PLAY_ADMIN_TOKEN" ? generateAdminToken() : generateSecret();
|
|
413
|
+
}
|
|
414
|
+
return void 0;
|
|
415
|
+
}
|
|
416
|
+
function parseDevVars(text) {
|
|
417
|
+
const out = {};
|
|
418
|
+
for (const line of text.split(/\r?\n/)) {
|
|
419
|
+
const trimmed = line.trim();
|
|
420
|
+
if (trimmed.length === 0 || trimmed.startsWith("#")) continue;
|
|
421
|
+
const eq = trimmed.indexOf("=");
|
|
422
|
+
if (eq <= 0) continue;
|
|
423
|
+
const key = trimmed.slice(0, eq).trim();
|
|
424
|
+
let value = trimmed.slice(eq + 1).trim();
|
|
425
|
+
if (value.length >= 2 && (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))) {
|
|
426
|
+
value = value.slice(1, -1);
|
|
427
|
+
}
|
|
428
|
+
out[key] = value;
|
|
429
|
+
}
|
|
430
|
+
return out;
|
|
431
|
+
}
|
|
432
|
+
function encodeDevVar(value) {
|
|
433
|
+
return /[\s#'"]/.test(value) ? `"${value.replace(/"/g, '\\"')}"` : value;
|
|
434
|
+
}
|
|
435
|
+
function serializeDevVars(values) {
|
|
436
|
+
const lines = Object.entries(values).map(([key, value]) => `${key}=${encodeDevVar(value)}`);
|
|
437
|
+
return `${lines.join("\n")}
|
|
438
|
+
`;
|
|
439
|
+
}
|
|
440
|
+
function findVarsBlock(raw) {
|
|
441
|
+
const match = /"vars"\s*:\s*\{/.exec(raw);
|
|
442
|
+
if (match === null) return null;
|
|
443
|
+
const open = raw.indexOf("{", match.index);
|
|
444
|
+
let depth = 0;
|
|
445
|
+
let inString = false;
|
|
446
|
+
let escaped = false;
|
|
447
|
+
for (let i = open; i < raw.length; i += 1) {
|
|
448
|
+
const ch = raw[i];
|
|
449
|
+
if (inString) {
|
|
450
|
+
if (escaped) escaped = false;
|
|
451
|
+
else if (ch === "\\") escaped = true;
|
|
452
|
+
else if (ch === '"') inString = false;
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
if (ch === '"') inString = true;
|
|
456
|
+
else if (ch === "{") depth += 1;
|
|
457
|
+
else if (ch === "}") {
|
|
458
|
+
depth -= 1;
|
|
459
|
+
if (depth === 0) {
|
|
460
|
+
const lineStart = raw.lastIndexOf("\n", match.index) + 1;
|
|
461
|
+
const indent = /^\s*/.exec(raw.slice(lineStart, match.index))?.[0] ?? " ";
|
|
462
|
+
return { start: match.index, open, close: i, indent };
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return null;
|
|
467
|
+
}
|
|
468
|
+
function parseJsoncObject(text) {
|
|
469
|
+
let out = "";
|
|
470
|
+
let inString = false;
|
|
471
|
+
let escaped = false;
|
|
472
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
473
|
+
const ch = text[i];
|
|
474
|
+
if (inString) {
|
|
475
|
+
out += ch;
|
|
476
|
+
if (escaped) escaped = false;
|
|
477
|
+
else if (ch === "\\") escaped = true;
|
|
478
|
+
else if (ch === '"') inString = false;
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
if (ch === '"') {
|
|
482
|
+
inString = true;
|
|
483
|
+
out += ch;
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
if (ch === "/" && text[i + 1] === "/") {
|
|
487
|
+
while (i < text.length && text[i] !== "\n") i += 1;
|
|
488
|
+
out += "\n";
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
if (ch === "/" && text[i + 1] === "*") {
|
|
492
|
+
i += 2;
|
|
493
|
+
while (i < text.length && !(text[i] === "*" && text[i + 1] === "/")) i += 1;
|
|
494
|
+
i += 1;
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
out += ch;
|
|
498
|
+
}
|
|
499
|
+
out = out.replace(/,(\s*[}\]])/g, "$1");
|
|
500
|
+
try {
|
|
501
|
+
const parsed = JSON.parse(out);
|
|
502
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {};
|
|
503
|
+
} catch {
|
|
504
|
+
return {};
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
function setWranglerVars(raw, vars) {
|
|
508
|
+
const managed = new Set(SETUP_FIELDS.filter((f) => f.kind === "var").map((f) => f.name));
|
|
509
|
+
const block = findVarsBlock(raw);
|
|
510
|
+
const existing = block === null ? {} : parseJsoncObject(`{${raw.slice(block.open + 1, block.close)}}`);
|
|
511
|
+
const merged = {};
|
|
512
|
+
for (const [key, value] of Object.entries(existing)) {
|
|
513
|
+
if (!managed.has(key)) merged[key] = value;
|
|
514
|
+
}
|
|
515
|
+
for (const field of SETUP_FIELDS) {
|
|
516
|
+
if (field.kind !== "var") continue;
|
|
517
|
+
const value = vars[field.name];
|
|
518
|
+
if (value !== void 0 && value.length > 0) merged[field.name] = value;
|
|
519
|
+
}
|
|
520
|
+
const indent = block?.indent ?? " ";
|
|
521
|
+
const inner = `${indent} `;
|
|
522
|
+
const body = Object.entries(merged).map(([key, value]) => `${inner}${JSON.stringify(key)}: ${JSON.stringify(value)}`).join(",\n");
|
|
523
|
+
const bodyText = `"vars": {
|
|
524
|
+
${body}${body.length > 0 ? "\n" : ""}${indent}}`;
|
|
525
|
+
if (block !== null) {
|
|
526
|
+
return `${raw.slice(0, block.start)}${bodyText}${raw.slice(block.close + 1)}`;
|
|
527
|
+
}
|
|
528
|
+
const open = raw.indexOf("{");
|
|
529
|
+
if (open < 0) throw new Error("wrangler config is not a JSON object");
|
|
530
|
+
return `${raw.slice(0, open + 1)}
|
|
531
|
+
${indent}${bodyText},
|
|
532
|
+
${raw.slice(open + 1)}`;
|
|
533
|
+
}
|
|
534
|
+
function readSetupValues(cwd, configPath, devVarsPath) {
|
|
535
|
+
const out = {};
|
|
536
|
+
if (existsSync(devVarsPath)) {
|
|
537
|
+
Object.assign(out, parseDevVars(readFileSync(devVarsPath, "utf8")));
|
|
538
|
+
}
|
|
539
|
+
if (existsSync(configPath)) {
|
|
540
|
+
const raw = readFileSync(configPath, "utf8");
|
|
541
|
+
const block = findVarsBlock(raw);
|
|
542
|
+
if (block !== null) {
|
|
543
|
+
const parsed = parseJsoncObject(`{${raw.slice(block.open + 1, block.close)}}`);
|
|
544
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
545
|
+
if (typeof value === "string") out[key] = value;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return out;
|
|
550
|
+
}
|
|
551
|
+
function applySetup(options) {
|
|
552
|
+
const cwd = options.cwd ?? process.cwd();
|
|
553
|
+
const configPath = path3.resolve(cwd, options.configPath ?? "wrangler.jsonc");
|
|
554
|
+
const devVarsPath = path3.join(path3.dirname(configPath), ".dev.vars");
|
|
555
|
+
if (!existsSync(configPath)) {
|
|
556
|
+
throw new Error(`\u627E\u4E0D\u5230 ${path3.basename(configPath)}\uFF0C\u8BF7\u5728 cj0 create \u751F\u6210\u7684\u9879\u76EE\u76EE\u5F55\u4E2D\u8FD0\u884C`);
|
|
557
|
+
}
|
|
558
|
+
const vars = {};
|
|
559
|
+
const secrets = {};
|
|
560
|
+
for (const field of SETUP_FIELDS) {
|
|
561
|
+
const value = options.values[field.name]?.trim();
|
|
562
|
+
if (value === void 0 || value.length === 0) continue;
|
|
563
|
+
if (field.kind === "secret") secrets[field.name] = value;
|
|
564
|
+
else vars[field.name] = value;
|
|
565
|
+
}
|
|
566
|
+
const raw = readFileSync(configPath, "utf8");
|
|
567
|
+
const next = setWranglerVars(raw, vars);
|
|
568
|
+
if (next !== raw) writeFileSync(configPath, next, "utf8");
|
|
569
|
+
const existingDev = existsSync(devVarsPath) ? parseDevVars(readFileSync(devVarsPath, "utf8")) : {};
|
|
570
|
+
const managedSecrets = new Set(
|
|
571
|
+
SETUP_FIELDS.filter((f) => f.kind === "secret").map((f) => f.name)
|
|
572
|
+
);
|
|
573
|
+
const mergedDev = {};
|
|
574
|
+
for (const [key, value] of Object.entries(existingDev)) {
|
|
575
|
+
if (!managedSecrets.has(key)) mergedDev[key] = value;
|
|
576
|
+
}
|
|
577
|
+
Object.assign(mergedDev, secrets);
|
|
578
|
+
writeFileSync(devVarsPath, serializeDevVars(mergedDev), "utf8");
|
|
579
|
+
return { configPath, devVarsPath, vars, secrets };
|
|
580
|
+
}
|
|
357
581
|
|
|
358
582
|
// src/bin/cj0.ts
|
|
359
583
|
var USAGE = `cj0 \u2014 serverless (Cloudflare) play-only BOX3 engine
|
|
@@ -363,6 +587,7 @@ Usage:
|
|
|
363
587
|
|
|
364
588
|
Commands:
|
|
365
589
|
create <folder> Scaffold a deployable project
|
|
590
|
+
setup [--config <file>] Configure required/optional ENV values
|
|
366
591
|
serve [--port <port>] [--host <host>] Run the cajonzero server locally (workerd)
|
|
367
592
|
|
|
368
593
|
Serve options:
|
|
@@ -412,10 +637,94 @@ function runCreate(args) {
|
|
|
412
637
|
process.stdout.write(
|
|
413
638
|
`created project in ${result.target} (${result.files.length} files)
|
|
414
639
|
${result.files.join(", ")}
|
|
415
|
-
next: cd ${folder} && npm install && npm run dev
|
|
640
|
+
next: cd ${folder} && npm install && npx cj0 setup && npm run dev
|
|
416
641
|
`
|
|
417
642
|
);
|
|
418
643
|
}
|
|
644
|
+
function parseSetupFlags(args) {
|
|
645
|
+
let config = DEFAULT_CONFIG;
|
|
646
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
647
|
+
const arg = args[i];
|
|
648
|
+
if (arg === void 0) continue;
|
|
649
|
+
if (arg === "--config" || arg.startsWith("--config=")) {
|
|
650
|
+
config = readFlagValue(args, i, "--config");
|
|
651
|
+
if (!arg.includes("=")) i += 1;
|
|
652
|
+
} else {
|
|
653
|
+
throw new Error(`unexpected setup option "${arg}"`);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
return { config };
|
|
657
|
+
}
|
|
658
|
+
async function runSetup(args) {
|
|
659
|
+
const { config } = parseSetupFlags(args);
|
|
660
|
+
const cwd = process.cwd();
|
|
661
|
+
const configPath = path3.resolve(cwd, config);
|
|
662
|
+
const devVarsPath = path3.join(path3.dirname(configPath), ".dev.vars");
|
|
663
|
+
const current = readSetupValues(cwd, configPath, devVarsPath);
|
|
664
|
+
if (!process.stdin.isTTY) {
|
|
665
|
+
const values2 = {};
|
|
666
|
+
for (const field of SETUP_FIELDS) {
|
|
667
|
+
if (!field.required) continue;
|
|
668
|
+
const value = current[field.name] ?? suggestedValue(field) ?? "";
|
|
669
|
+
if (value.length > 0) values2[field.name] = value;
|
|
670
|
+
}
|
|
671
|
+
const result2 = applySetup({ cwd, configPath: config, values: values2 });
|
|
672
|
+
process.stdout.write(
|
|
673
|
+
`cajonzero setup\uFF08\u975E\u4EA4\u4E92\uFF09\uFF1Avars=${Object.keys(result2.vars).join(", ") || "\u65E0"} secrets=${Object.keys(result2.secrets).join(", ") || "\u65E0"}
|
|
674
|
+
`
|
|
675
|
+
);
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
const values = {};
|
|
679
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
680
|
+
try {
|
|
681
|
+
process.stdout.write("cajonzero setup \u2014 \u914D\u7F6E\u5FC5\u9700\u4E0E\u53EF\u9009\u7684 ENV\n\n");
|
|
682
|
+
for (const field of SETUP_FIELDS) {
|
|
683
|
+
if (!field.required) continue;
|
|
684
|
+
const existing = current[field.name];
|
|
685
|
+
const hint = field.kind === "secret" ? existing !== void 0 ? "\u5DF2\u914D\u7F6E\uFF0C\u56DE\u8F66\u4FDD\u7559" : "\u56DE\u8F66\u81EA\u52A8\u751F\u6210" : field.hint;
|
|
686
|
+
const label = `? ${field.label}\uFF08${field.name}\uFF09${hint !== void 0 ? ` [${hint}]` : ""}: `;
|
|
687
|
+
const answer = (await rl.question(label)).trim();
|
|
688
|
+
const value = answer.length > 0 ? answer : existing ?? suggestedValue(field) ?? "";
|
|
689
|
+
if (value.length > 0) values[field.name] = value;
|
|
690
|
+
}
|
|
691
|
+
const configureOptional = (await rl.question("? \u914D\u7F6E\u53EF\u9009\u8BBE\u7F6E\u9879\uFF1F(y/N): ")).trim().toLowerCase();
|
|
692
|
+
if (configureOptional === "y" || configureOptional === "yes") {
|
|
693
|
+
process.stdout.write(" \uFF08\u56DE\u8F66\u4FDD\u7559\u5F53\u524D\u503C\uFF0C\u8F93\u5165 - \u6E05\u9664\uFF09\n");
|
|
694
|
+
for (const field of SETUP_FIELDS) {
|
|
695
|
+
if (field.required) continue;
|
|
696
|
+
const existing = current[field.name];
|
|
697
|
+
const shown = existing ?? "";
|
|
698
|
+
const currentLabel = shown.length > 0 ? ` \u5F53\u524D ${shown}` : "";
|
|
699
|
+
const hint = field.hint !== void 0 ? ` [${field.hint}]` : "";
|
|
700
|
+
const answer = (await rl.question(`? ${field.label}\uFF08${field.name}\uFF09${hint}${currentLabel}: `)).trim();
|
|
701
|
+
if (answer === "-") continue;
|
|
702
|
+
const value = answer.length > 0 ? answer : shown;
|
|
703
|
+
if (value.length > 0) values[field.name] = value;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
} finally {
|
|
707
|
+
rl.close();
|
|
708
|
+
}
|
|
709
|
+
const result = applySetup({ cwd, configPath: config, values });
|
|
710
|
+
const varNames = Object.keys(result.vars);
|
|
711
|
+
const secretNames = Object.keys(result.secrets);
|
|
712
|
+
process.stdout.write(`
|
|
713
|
+
\u5DF2\u66F4\u65B0 ${path3.relative(cwd, result.configPath)}\uFF1A
|
|
714
|
+
`);
|
|
715
|
+
process.stdout.write(` vars: ${varNames.length > 0 ? varNames.join(", ") : "\uFF08\u65E0\uFF09"}
|
|
716
|
+
`);
|
|
717
|
+
process.stdout.write(`\u5DF2\u5199\u5165 .dev.vars\uFF1A${secretNames.length > 0 ? secretNames.join(", ") : "\uFF08\u65E0\uFF09"}
|
|
718
|
+
`);
|
|
719
|
+
process.stdout.write("\n\u672C\u5730\u5F00\u53D1\uFF1Anpm run dev\n");
|
|
720
|
+
if (secretNames.length > 0) {
|
|
721
|
+
process.stdout.write("\u90E8\u7F72\u524D\u8BBE\u7F6E Cloudflare secret\uFF1A\n");
|
|
722
|
+
for (const name of secretNames) {
|
|
723
|
+
process.stdout.write(` npx wrangler secret put ${name}
|
|
724
|
+
`);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
419
728
|
function readFlagValue(args, index, flag) {
|
|
420
729
|
const inline = args[index];
|
|
421
730
|
if (inline !== void 0 && inline.startsWith(`${flag}=`)) {
|
|
@@ -464,8 +773,8 @@ async function runServe(args) {
|
|
|
464
773
|
}
|
|
465
774
|
const assetsDir = config.assets?.directory;
|
|
466
775
|
if (typeof assetsDir === "string" && assetsDir.length > 0) {
|
|
467
|
-
const configDir =
|
|
468
|
-
mkdirSync(
|
|
776
|
+
const configDir = path3.dirname(path3.resolve(process.cwd(), flags.config));
|
|
777
|
+
mkdirSync(path3.resolve(configDir, assetsDir), { recursive: true });
|
|
469
778
|
}
|
|
470
779
|
const secrets = localDevVars();
|
|
471
780
|
const worker = await unstable_dev(entry, {
|
|
@@ -524,6 +833,9 @@ async function main(argv) {
|
|
|
524
833
|
case "create":
|
|
525
834
|
runCreate(rest);
|
|
526
835
|
return;
|
|
836
|
+
case "setup":
|
|
837
|
+
await runSetup(rest);
|
|
838
|
+
return;
|
|
527
839
|
default:
|
|
528
840
|
process.stderr.write(`cajonzero: unknown command "${command}"
|
|
529
841
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { T as TimestampedClientInput,
|
|
1
|
+
import { T as TimestampedClientInput, N as NetPublic, O as NetSecret } from './providers-BX7e4pNW.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* cajonzero — mudb protocol envelope (message-id tables + `raw` channel).
|
|
@@ -235,8 +235,6 @@ declare class NetCodec {
|
|
|
235
235
|
*/
|
|
236
236
|
/** Map delivery + seed loading policy. */
|
|
237
237
|
interface MapsConfig {
|
|
238
|
-
/** How long a resolved world seed stays in KV. `0` disables the cache. */
|
|
239
|
-
readonly cacheTtlSeconds: number;
|
|
240
238
|
/** Hard cap for a single map file fetched from the CDN (bytes). */
|
|
241
239
|
readonly maxDownloadBytes: number;
|
|
242
240
|
/** Parallel blob fetches while resolving a seed. */
|