cajonzero 0.1.1 → 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 +13 -15
- package/dist/bin/cj0.js +403 -87
- package/dist/{config-YtU8BYQM.d.ts → config-BOzAyP7m.d.ts} +1 -1
- package/dist/core.d.ts +3 -3
- package/dist/core.js +9 -39
- package/dist/index.d.ts +5 -5
- package/dist/index.js +10 -68
- package/dist/{providers-Dh-umPQ6.d.ts → providers-BX7e4pNW.d.ts} +3 -17
- package/dist/scaffold.d.ts +60 -2
- package/dist/scaffold.js +306 -79
- package/dist/serve.d.ts +2 -2
- package/dist/serve.js +10 -68
- package/dist/ui/assets/index-Bhv7LV8L.css +2 -0
- package/dist/ui/assets/index-CaAHje8X.js +4404 -0
- package/dist/ui/assets/{main-DrPj1s7t.js → main-BeABKxzY.js} +26 -26
- 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-8g0ogAbk.js +0 -4404
- package/dist/ui/assets/index-W1HlDaw6.css +0 -2
package/README.md
CHANGED
|
@@ -10,27 +10,27 @@ npm install cajonzero
|
|
|
10
10
|
npx cj0 --help
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## 本地运行(本仓库)
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
npm install
|
|
17
17
|
npm run build # 构建大厅与管理后台
|
|
18
|
-
npm run demo #
|
|
18
|
+
npm run demo # http://127.0.0.1:8787
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
管理后台地址是 `/admin/login`,演示环境的令牌为 `cajonzero-demo-admin-token`。
|
|
21
|
+
管理后台地址 `/admin/login`,演示令牌 `cajonzero-demo-admin-token`。
|
|
23
22
|
|
|
24
|
-
##
|
|
23
|
+
## 创建项目
|
|
25
24
|
|
|
26
25
|
```bash
|
|
27
|
-
cj0 create my-server
|
|
26
|
+
npx cj0 create my-server
|
|
28
27
|
cd my-server
|
|
29
28
|
npm install
|
|
30
|
-
|
|
29
|
+
npx cj0 setup # 交互配置 ENV:vars 写 wrangler.jsonc,密钥写 .dev.vars
|
|
30
|
+
npm run dev
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
## 部署
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
36
|
npx wrangler kv namespace create CAJONZERO_KV # 把返回的 id 填进 wrangler.jsonc
|
|
@@ -41,8 +41,8 @@ npm run deploy
|
|
|
41
41
|
|
|
42
42
|
## 配置地图
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
OSS/S3/CDN
|
|
44
|
+
地图文件单独上传,不随 worker 打包。把解包后的 `.boxplay` 目录上传到
|
|
45
|
+
OSS/S3/CDN(需允许 CORS),然后在 `cajonzero/maps.json` 中登记:
|
|
46
46
|
|
|
47
47
|
```json
|
|
48
48
|
{
|
|
@@ -58,17 +58,15 @@ OSS/S3/CDN,然后在 `cajonzero/maps.json` 中登记:
|
|
|
58
58
|
}
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
更新地图后请刷新 CDN 缓存,或改用带版本的路径。
|
|
61
|
+
`root` 必须以 `/` 结尾;更新地图后刷新 CDN 缓存或改用带版本的路径。
|
|
63
62
|
房间参数、地图拉取策略等配置位于 `cajonzero/config.json`。
|
|
64
63
|
|
|
65
64
|
## 常用命令
|
|
66
65
|
|
|
67
66
|
```bash
|
|
68
|
-
cj0 serve [--port <port>] [--host <host>] # 本地启动
|
|
69
67
|
cj0 create <folder> # 生成部署项目
|
|
68
|
+
cj0 setup [--config <file>] # 交互配置必需/可选的 ENV
|
|
69
|
+
cj0 serve [--port <port>] [--host <host>] # 本地启动
|
|
70
70
|
npm test # 运行测试
|
|
71
71
|
npm run ci:gates # 完整检查(类型/测试/构建)
|
|
72
72
|
```
|
|
73
|
-
|
|
74
|
-
详细说明见 [`docs/`](./docs/)。
|
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";
|
|
@@ -51,7 +53,7 @@ var SCAFFOLD_ENTRY_CONTENT = [
|
|
|
51
53
|
" };",
|
|
52
54
|
" },",
|
|
53
55
|
"});",
|
|
54
|
-
'export { Room } from "cajonzero";',
|
|
56
|
+
'export { Room } from "cajonzero/serve";',
|
|
55
57
|
""
|
|
56
58
|
].join("\n");
|
|
57
59
|
var SCAFFOLD_CONFIG_CONTENT = `${JSON.stringify(
|
|
@@ -156,82 +158,49 @@ function readme(options) {
|
|
|
156
158
|
return [
|
|
157
159
|
`# ${options.packageName}`,
|
|
158
160
|
"",
|
|
159
|
-
"
|
|
160
|
-
"
|
|
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",
|
|
161
163
|
"",
|
|
162
|
-
"
|
|
163
|
-
"worker entry that hands `cajonzero/config.json` and `cajonzero/maps.json` to the",
|
|
164
|
-
"runtime:",
|
|
165
|
-
"",
|
|
166
|
-
"```ts",
|
|
167
|
-
'import config from "../cajonzero/config.json";',
|
|
168
|
-
'import maps from "../cajonzero/maps.json";',
|
|
169
|
-
'import { serve } from "cajonzero/serve";',
|
|
170
|
-
"",
|
|
171
|
-
"export default serve({",
|
|
172
|
-
" config,",
|
|
173
|
-
" maps,",
|
|
174
|
-
" storage: (env) => /* any unstorage driver over env bindings */,",
|
|
175
|
-
" sessionSecret: (env) => env.CAJONZERO_SESSION_SECRET,",
|
|
176
|
-
" // adminToken / environment / rooms / settings \u2026",
|
|
177
|
-
"});",
|
|
178
|
-
'export { Room } from "cajonzero";',
|
|
179
|
-
"```",
|
|
180
|
-
"",
|
|
181
|
-
"## Quick start",
|
|
164
|
+
"## \u5FEB\u901F\u5F00\u59CB",
|
|
182
165
|
"",
|
|
183
166
|
"```bash",
|
|
184
|
-
"npm install
|
|
185
|
-
"
|
|
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",
|
|
186
170
|
"```",
|
|
187
171
|
"",
|
|
188
|
-
"
|
|
189
|
-
"publishing (`wrangler deploy --dry-run`).",
|
|
190
|
-
"",
|
|
191
|
-
"## Environment",
|
|
192
|
-
"",
|
|
193
|
-
"Every value the runtime consumes is passed explicitly in `src/index.ts`;",
|
|
194
|
-
"the package never guesses env var names.",
|
|
195
|
-
"",
|
|
196
|
-
"| Name | Kind | Required | Purpose |",
|
|
197
|
-
"|---|---|---|---|",
|
|
198
|
-
"| `CAJONZERO_KV` | KV binding | yes | player/account data + map-script KV |",
|
|
199
|
-
"| `ROOMS` | Durable Object binding | yes | room actors |",
|
|
200
|
-
"| `CAJONZERO_SESSION_SECRET` | secret | yes | signs session proofs and login cookies |",
|
|
201
|
-
"| `BOX_PLAY_ADMIN_TOKEN` | secret | yes | owner console and `/api/admin/*` |",
|
|
202
|
-
"| `CAJONZERO_ENV` | var | no | deployment label (`development` relaxes cookie `Secure`) |",
|
|
203
|
-
"| `CAJONZERO_PLAYER_LIMIT` | var | no | maximum players per room |",
|
|
204
|
-
"| `CAJONZERO_ZERO_PLAYER_TIMEOUT_MS` | var | no | empty-room grace window |",
|
|
205
|
-
"| `CAJONZERO_AUTOSAVE_INTERVAL_MS` | var | no | snapshot cadence |",
|
|
206
|
-
"| `CAJONZERO_DEBUG` | var | no | verbose logging |",
|
|
207
|
-
"| `CAJONZERO_TELEPORT_ACCESS` | var | no | allow non-team containers |",
|
|
208
|
-
"| `CAJONZERO_PAID_ACCESS` | var | no | restrict joins to paid containers |",
|
|
209
|
-
"| `CAJONZERO_SCRIPT_HEAP_MB` | var | no | QuickJS heap per script host |",
|
|
210
|
-
"| `CAJONZERO_SCRIPT_TIMEOUT_MS` | var | no | script invocation timeout |",
|
|
211
|
-
"| `CAJONZERO_DB_SIZE_LIMIT` | var | no | map-script storage budget |",
|
|
172
|
+
"## \u73AF\u5883\u53D8\u91CF",
|
|
212
173
|
"",
|
|
213
|
-
"
|
|
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",
|
|
214
176
|
"",
|
|
215
177
|
"```bash",
|
|
216
|
-
"npx wrangler kv namespace create CAJONZERO_KV #
|
|
178
|
+
"npx wrangler kv namespace create CAJONZERO_KV # id \u586B\u8FDB wrangler.jsonc",
|
|
217
179
|
"npx wrangler secret put CAJONZERO_SESSION_SECRET",
|
|
218
180
|
"npx wrangler secret put BOX_PLAY_ADMIN_TOKEN",
|
|
219
181
|
"```",
|
|
220
182
|
"",
|
|
221
|
-
"
|
|
222
|
-
"",
|
|
223
|
-
"
|
|
224
|
-
"
|
|
225
|
-
"
|
|
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 |",
|
|
226
199
|
"",
|
|
227
|
-
"
|
|
228
|
-
"<root>/bundle.json",
|
|
229
|
-
"<root>/worlds/<worldId>/manifest.json",
|
|
230
|
-
"<root>/worlds/<worldId>/data/project-state.json",
|
|
231
|
-
"<root>/worlds/<worldId>/data/blobs/<cid>",
|
|
232
|
-
"```",
|
|
200
|
+
"## \u5730\u56FE",
|
|
233
201
|
"",
|
|
234
|
-
"
|
|
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",
|
|
235
204
|
"",
|
|
236
205
|
"```json",
|
|
237
206
|
"{",
|
|
@@ -241,30 +210,21 @@ function readme(options) {
|
|
|
241
210
|
' "slug": "omega",',
|
|
242
211
|
' "root": "https://cdn.example.com/cajonzero/omega/",',
|
|
243
212
|
' "worldId": "project-omega",',
|
|
244
|
-
' "name": "Omega City Reloaded"
|
|
245
|
-
' "version": "1.0.4"',
|
|
213
|
+
' "name": "Omega City Reloaded"',
|
|
246
214
|
" }",
|
|
247
215
|
" ]",
|
|
248
216
|
"}",
|
|
249
217
|
"```",
|
|
250
218
|
"",
|
|
251
|
-
"
|
|
252
|
-
"- The browser loads the map files directly, so the bucket must allow CORS",
|
|
253
|
-
" (`Access-Control-Allow-Origin`) for this deployment's origin.",
|
|
254
|
-
"- Re-uploaded maps need a CDN cache refresh (or a versioned path).",
|
|
219
|
+
"`root` \u5FC5\u987B\u4EE5 `/` \u7ED3\u5C3E\uFF1B\u66F4\u65B0\u5730\u56FE\u540E\u5237\u65B0 CDN \u7F13\u5B58\u6216\u6362\u8DEF\u5F84\u3002",
|
|
255
220
|
"",
|
|
256
|
-
"##
|
|
257
|
-
"",
|
|
258
|
-
"- **Play lobby** \u2014 the worker serves the lobby at `/`.",
|
|
259
|
-
"- **Admin console** \u2014 at `/admin` (sign in with `BOX_PLAY_ADMIN_TOKEN`) for",
|
|
260
|
-
" player bans and runtime settings; its JSON API is under `/api/admin/*`.",
|
|
261
|
-
"- **Map catalog** \u2014 `GET /api/maps` returns the entries from `maps.json`.",
|
|
262
|
-
"",
|
|
263
|
-
"## Deploy",
|
|
221
|
+
"## \u90E8\u7F72",
|
|
264
222
|
"",
|
|
265
223
|
"```bash",
|
|
266
|
-
"npm run deploy
|
|
224
|
+
"npm run deploy",
|
|
267
225
|
"```",
|
|
226
|
+
"",
|
|
227
|
+
"\u5927\u5385\u5728 `/`\uFF0C\u7BA1\u7406\u540E\u53F0\u5728 `/admin`\u3002",
|
|
268
228
|
""
|
|
269
229
|
].join("\n");
|
|
270
230
|
}
|
|
@@ -306,7 +266,7 @@ function readCajonzeroPackage(manifestPath) {
|
|
|
306
266
|
return { version, wranglerRange, unstorageRange };
|
|
307
267
|
}
|
|
308
268
|
function toPackageName(folder) {
|
|
309
|
-
const base =
|
|
269
|
+
const base = path3.basename(folder);
|
|
310
270
|
const slug = base.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[-._]+/, "").replace(/[-._]+$/, "");
|
|
311
271
|
return slug.length > 0 ? slug : "cajonzero-app";
|
|
312
272
|
}
|
|
@@ -321,7 +281,7 @@ function createProject(folder, options = {}) {
|
|
|
321
281
|
throw new ScaffoldError("INVALID_TARGET", "create requires a target folder");
|
|
322
282
|
}
|
|
323
283
|
const cwd = options.cwd ?? process.cwd();
|
|
324
|
-
const target =
|
|
284
|
+
const target = path3.resolve(cwd, name);
|
|
325
285
|
if (existsSync(target) && !statSync(target).isDirectory()) {
|
|
326
286
|
throw new ScaffoldError("INVALID_TARGET", `target exists and is not a directory: ${target}`);
|
|
327
287
|
}
|
|
@@ -344,12 +304,280 @@ function createProject(folder, options = {}) {
|
|
|
344
304
|
});
|
|
345
305
|
mkdirSync(target, { recursive: true });
|
|
346
306
|
for (const file of files) {
|
|
347
|
-
const fullPath =
|
|
348
|
-
mkdirSync(
|
|
307
|
+
const fullPath = path3.join(target, file.path);
|
|
308
|
+
mkdirSync(path3.dirname(fullPath), { recursive: true });
|
|
349
309
|
writeFileSync(fullPath, file.content, "utf8");
|
|
350
310
|
}
|
|
351
311
|
return { target, files: files.map((file) => file.path) };
|
|
352
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
|
+
}
|
|
353
581
|
|
|
354
582
|
// src/bin/cj0.ts
|
|
355
583
|
var USAGE = `cj0 \u2014 serverless (Cloudflare) play-only BOX3 engine
|
|
@@ -359,6 +587,7 @@ Usage:
|
|
|
359
587
|
|
|
360
588
|
Commands:
|
|
361
589
|
create <folder> Scaffold a deployable project
|
|
590
|
+
setup [--config <file>] Configure required/optional ENV values
|
|
362
591
|
serve [--port <port>] [--host <host>] Run the cajonzero server locally (workerd)
|
|
363
592
|
|
|
364
593
|
Serve options:
|
|
@@ -408,10 +637,94 @@ function runCreate(args) {
|
|
|
408
637
|
process.stdout.write(
|
|
409
638
|
`created project in ${result.target} (${result.files.length} files)
|
|
410
639
|
${result.files.join(", ")}
|
|
411
|
-
next: cd ${folder} && npm install && npm run dev
|
|
640
|
+
next: cd ${folder} && npm install && npx cj0 setup && npm run dev
|
|
412
641
|
`
|
|
413
642
|
);
|
|
414
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
|
+
}
|
|
415
728
|
function readFlagValue(args, index, flag) {
|
|
416
729
|
const inline = args[index];
|
|
417
730
|
if (inline !== void 0 && inline.startsWith(`${flag}=`)) {
|
|
@@ -460,8 +773,8 @@ async function runServe(args) {
|
|
|
460
773
|
}
|
|
461
774
|
const assetsDir = config.assets?.directory;
|
|
462
775
|
if (typeof assetsDir === "string" && assetsDir.length > 0) {
|
|
463
|
-
const configDir =
|
|
464
|
-
mkdirSync(
|
|
776
|
+
const configDir = path3.dirname(path3.resolve(process.cwd(), flags.config));
|
|
777
|
+
mkdirSync(path3.resolve(configDir, assetsDir), { recursive: true });
|
|
465
778
|
}
|
|
466
779
|
const secrets = localDevVars();
|
|
467
780
|
const worker = await unstable_dev(entry, {
|
|
@@ -520,6 +833,9 @@ async function main(argv) {
|
|
|
520
833
|
case "create":
|
|
521
834
|
runCreate(rest);
|
|
522
835
|
return;
|
|
836
|
+
case "setup":
|
|
837
|
+
await runSetup(rest);
|
|
838
|
+
return;
|
|
523
839
|
default:
|
|
524
840
|
process.stderr.write(`cajonzero: unknown command "${command}"
|
|
525
841
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { T as TimestampedClientInput, N as NetPublic, O as NetSecret } from './providers-
|
|
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).
|