@nexusgame/dev 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +58 -2
- package/dist/engine-worker.js +5033 -0
- package/dist/index.js +301 -0
- package/package.json +27 -3
package/dist/index.js
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/start.ts
|
|
7
|
+
import { Miniflare } from "miniflare";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
import { readFile } from "fs/promises";
|
|
11
|
+
|
|
12
|
+
// src/constants.ts
|
|
13
|
+
var ADMIN_SECRET = "nexus-dev-secret";
|
|
14
|
+
var JWT_SECRET = "nexus-dev-jwt-secret";
|
|
15
|
+
var FIXED_ROOM_ID = "dev-room";
|
|
16
|
+
var FIXED_OWNER_ID = "dev-owner";
|
|
17
|
+
var DEFAULT_PORT = 56512;
|
|
18
|
+
|
|
19
|
+
// src/commands/start.ts
|
|
20
|
+
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
async function startCommand(options) {
|
|
22
|
+
const port = parseInt(options.port || String(DEFAULT_PORT), 10);
|
|
23
|
+
const persistDir = path.resolve(process.cwd(), ".nexus");
|
|
24
|
+
console.log("\u{1F680} \u542F\u52A8\u661F\u67A2\u6C99\u76D2\u5F15\u64CE...");
|
|
25
|
+
const scriptPath = path.join(__dirname, "engine-worker.js");
|
|
26
|
+
const contents = await readFile(scriptPath, "utf8");
|
|
27
|
+
const mf = new Miniflare({
|
|
28
|
+
modules: [{ type: "ESModule", path: "engine-worker.js", contents }],
|
|
29
|
+
port,
|
|
30
|
+
durableObjects: { GAME_DO: "GameDO" },
|
|
31
|
+
durableObjectsPersist: path.join(persistDir, "do"),
|
|
32
|
+
bindings: {
|
|
33
|
+
ADMIN_SECRET,
|
|
34
|
+
JWT_SECRET
|
|
35
|
+
},
|
|
36
|
+
compatibilityDate: "2024-02-08",
|
|
37
|
+
outboundService(req) {
|
|
38
|
+
return fetch(req.url, {
|
|
39
|
+
method: req.method,
|
|
40
|
+
headers: req.headers,
|
|
41
|
+
body: req.method === "GET" || req.method === "HEAD" ? void 0 : req.body,
|
|
42
|
+
redirect: "manual",
|
|
43
|
+
duplex: "half"
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
const url = await mf.ready;
|
|
48
|
+
console.log(`\u2705 Engine \u5DF2\u542F\u52A8: ${url}`);
|
|
49
|
+
console.log(`\u{1F4C2} \u6301\u4E45\u5316\u76EE\u5F55: ${persistDir}`);
|
|
50
|
+
console.log(`
|
|
51
|
+
\u6309 Ctrl+C \u505C\u6B62\u5F15\u64CE
|
|
52
|
+
`);
|
|
53
|
+
const shutdown = async () => {
|
|
54
|
+
console.log("\n\u{1F6D1} \u6B63\u5728\u5173\u95ED\u5F15\u64CE...");
|
|
55
|
+
await mf.dispose();
|
|
56
|
+
console.log("\u{1F44B} \u5F15\u64CE\u5DF2\u5173\u95ED");
|
|
57
|
+
process.exit(0);
|
|
58
|
+
};
|
|
59
|
+
process.on("SIGINT", shutdown);
|
|
60
|
+
process.on("SIGTERM", shutdown);
|
|
61
|
+
await new Promise(() => {
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/client.ts
|
|
66
|
+
var EngineClient = class {
|
|
67
|
+
baseUrl;
|
|
68
|
+
constructor(port = DEFAULT_PORT) {
|
|
69
|
+
this.baseUrl = `http://localhost:${port}`;
|
|
70
|
+
}
|
|
71
|
+
get headers() {
|
|
72
|
+
return {
|
|
73
|
+
"Content-Type": "application/json",
|
|
74
|
+
Authorization: `Bearer ${ADMIN_SECRET}`
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
async createRoom() {
|
|
78
|
+
const res = await fetch(`${this.baseUrl}/api/engine/create`, {
|
|
79
|
+
method: "POST",
|
|
80
|
+
headers: this.headers,
|
|
81
|
+
body: JSON.stringify({
|
|
82
|
+
roomId: FIXED_ROOM_ID,
|
|
83
|
+
ownerId: FIXED_OWNER_ID
|
|
84
|
+
})
|
|
85
|
+
});
|
|
86
|
+
return res.json();
|
|
87
|
+
}
|
|
88
|
+
async getState() {
|
|
89
|
+
const res = await fetch(
|
|
90
|
+
`${this.baseUrl}/api/monitor/room/${FIXED_ROOM_ID}`,
|
|
91
|
+
{ headers: this.headers }
|
|
92
|
+
);
|
|
93
|
+
return res.json();
|
|
94
|
+
}
|
|
95
|
+
async getPerspective(roleId) {
|
|
96
|
+
const res = await fetch(
|
|
97
|
+
`${this.baseUrl}/api/monitor/room/${FIXED_ROOM_ID}/perspective?roleId=${encodeURIComponent(roleId)}`,
|
|
98
|
+
{ headers: this.headers }
|
|
99
|
+
);
|
|
100
|
+
return res.json();
|
|
101
|
+
}
|
|
102
|
+
async submitAction(roleId, action) {
|
|
103
|
+
const res = await fetch(
|
|
104
|
+
`${this.baseUrl}/api/monitor/room/${FIXED_ROOM_ID}/action`,
|
|
105
|
+
{
|
|
106
|
+
method: "POST",
|
|
107
|
+
headers: this.headers,
|
|
108
|
+
body: JSON.stringify({ roleId, action })
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
return res.json();
|
|
112
|
+
}
|
|
113
|
+
async setGame(gameWorkerUrl, gameId, players) {
|
|
114
|
+
const res = await fetch(
|
|
115
|
+
`${this.baseUrl}/api/monitor/room/${FIXED_ROOM_ID}/set-game`,
|
|
116
|
+
{
|
|
117
|
+
method: "POST",
|
|
118
|
+
headers: this.headers,
|
|
119
|
+
body: JSON.stringify({ gameWorkerUrl, gameId, selectedPlayerCount: players })
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
return res.json();
|
|
123
|
+
}
|
|
124
|
+
async startGame() {
|
|
125
|
+
const res = await fetch(
|
|
126
|
+
`${this.baseUrl}/api/monitor/room/${FIXED_ROOM_ID}/start-game`,
|
|
127
|
+
{ method: "POST", headers: this.headers, body: "{}" }
|
|
128
|
+
);
|
|
129
|
+
return res.json();
|
|
130
|
+
}
|
|
131
|
+
async stopGame() {
|
|
132
|
+
const res = await fetch(
|
|
133
|
+
`${this.baseUrl}/api/monitor/room/${FIXED_ROOM_ID}/stop-game`,
|
|
134
|
+
{ method: "POST", headers: this.headers, body: "{}" }
|
|
135
|
+
);
|
|
136
|
+
return res.json();
|
|
137
|
+
}
|
|
138
|
+
async restartGame() {
|
|
139
|
+
const res = await fetch(
|
|
140
|
+
`${this.baseUrl}/api/monitor/room/${FIXED_ROOM_ID}/restart-game`,
|
|
141
|
+
{ method: "POST", headers: this.headers, body: "{}" }
|
|
142
|
+
);
|
|
143
|
+
return res.json();
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// src/commands/setup.ts
|
|
148
|
+
async function setupCommand(options) {
|
|
149
|
+
const port = parseInt(options.port || String(DEFAULT_PORT), 10);
|
|
150
|
+
const client = new EngineClient(port);
|
|
151
|
+
try {
|
|
152
|
+
console.log("\u{1F50D} \u68C0\u67E5\u623F\u95F4\u72B6\u6001...");
|
|
153
|
+
let roomExists = false;
|
|
154
|
+
let currentPhase = "";
|
|
155
|
+
try {
|
|
156
|
+
const stateRes = await client.getState();
|
|
157
|
+
if (stateRes?.data?.roomId) {
|
|
158
|
+
roomExists = true;
|
|
159
|
+
currentPhase = stateRes.data.phase;
|
|
160
|
+
console.log(` \u623F\u95F4\u5DF2\u5B58\u5728, phase: ${currentPhase}`);
|
|
161
|
+
}
|
|
162
|
+
} catch {
|
|
163
|
+
}
|
|
164
|
+
if (roomExists && (currentPhase === "playing" || currentPhase === "paused")) {
|
|
165
|
+
console.log("\u23F9\uFE0F \u505C\u6B62\u5F53\u524D\u6E38\u620F...");
|
|
166
|
+
const stopRes = await client.stopGame();
|
|
167
|
+
if (stopRes.error) {
|
|
168
|
+
console.error(`\u274C \u505C\u6B62\u5931\u8D25: ${stopRes.error}`);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
console.log(" \u6E38\u620F\u5DF2\u505C\u6B62");
|
|
172
|
+
}
|
|
173
|
+
if (!roomExists) {
|
|
174
|
+
console.log("\u{1F4E6} \u521B\u5EFA\u623F\u95F4...");
|
|
175
|
+
const createRes = await client.createRoom();
|
|
176
|
+
if (createRes.error) {
|
|
177
|
+
console.error(`\u274C \u521B\u5EFA\u5931\u8D25: ${createRes.error}`);
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
console.log(` \u623F\u95F4\u5DF2\u521B\u5EFA: ${createRes.roomId}`);
|
|
181
|
+
}
|
|
182
|
+
console.log(`\u{1F3AE} \u8BBE\u7F6E\u6E38\u620F (${options.workerUrl})...`);
|
|
183
|
+
const players = options.players ? parseInt(options.players, 10) : void 0;
|
|
184
|
+
const setRes = await client.setGame(options.workerUrl, void 0, players);
|
|
185
|
+
if (setRes.error) {
|
|
186
|
+
console.error(`\u274C \u8BBE\u7F6E\u6E38\u620F\u5931\u8D25: ${setRes.error}`);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
console.log(` \u6E38\u620F: ${setRes.gameId} (\u89D2\u8272: ${setRes.roleIds?.join(", ")})`);
|
|
190
|
+
console.log("\u25B6\uFE0F \u5F00\u59CB\u6E38\u620F...");
|
|
191
|
+
const startRes = await client.startGame();
|
|
192
|
+
if (startRes.error) {
|
|
193
|
+
console.error(`\u274C \u542F\u52A8\u5931\u8D25: ${startRes.error}`);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
console.log(" \u6E38\u620F\u5DF2\u5F00\u59CB!");
|
|
197
|
+
console.log("\n\u53EF\u7528\u547D\u4EE4:");
|
|
198
|
+
console.log(` npx @nexusgame/dev state \u67E5\u770B\u623F\u95F4\u72B6\u6001`);
|
|
199
|
+
console.log(` npx @nexusgame/dev perspective <roleId> \u67E5\u770B\u89D2\u8272\u89C6\u89D2`);
|
|
200
|
+
console.log(` npx @nexusgame/dev action <roleId> '<action_json>' \u6267\u884C\u884C\u52A8`);
|
|
201
|
+
} catch (e) {
|
|
202
|
+
if (e.cause?.code === "ECONNREFUSED") {
|
|
203
|
+
console.error(`\u274C \u65E0\u6CD5\u8FDE\u63A5\u5230\u5F15\u64CE (localhost:${port})\u3002\u8BF7\u5148\u8FD0\u884C: npx @nexusgame/dev start`);
|
|
204
|
+
} else {
|
|
205
|
+
console.error(`\u274C \u9519\u8BEF: ${e.message}`);
|
|
206
|
+
}
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// src/commands/state.ts
|
|
212
|
+
async function stateCommand(options) {
|
|
213
|
+
const port = parseInt(options.port || String(DEFAULT_PORT), 10);
|
|
214
|
+
const client = new EngineClient(port);
|
|
215
|
+
try {
|
|
216
|
+
const result = await client.getState();
|
|
217
|
+
console.log(JSON.stringify(result, null, 2));
|
|
218
|
+
} catch (e) {
|
|
219
|
+
if (e.cause?.code === "ECONNREFUSED") {
|
|
220
|
+
console.error(`\u274C \u65E0\u6CD5\u8FDE\u63A5\u5230\u5F15\u64CE (localhost:${port})\u3002\u8BF7\u5148\u8FD0\u884C: npx @nexusgame/dev start`);
|
|
221
|
+
} else {
|
|
222
|
+
console.error(`\u274C \u9519\u8BEF: ${e.message}`);
|
|
223
|
+
}
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/commands/perspective.ts
|
|
229
|
+
async function perspectiveCommand(roleId, options) {
|
|
230
|
+
const port = parseInt(options.port || String(DEFAULT_PORT), 10);
|
|
231
|
+
const client = new EngineClient(port);
|
|
232
|
+
try {
|
|
233
|
+
const result = await client.getPerspective(roleId);
|
|
234
|
+
console.log(JSON.stringify(result, null, 2));
|
|
235
|
+
try {
|
|
236
|
+
const stateRes = await client.getState();
|
|
237
|
+
const uiUrl = stateRes?.data?.gameConfig?.gameWorkerUrl;
|
|
238
|
+
if (uiUrl) {
|
|
239
|
+
console.log(`
|
|
240
|
+
\u{1F4FA} \u8C03\u8BD5 GUI \u9875\u9762:`);
|
|
241
|
+
const ADMIN_SECRET2 = process.env.ADMIN_SECRET || "nexus-dev-secret";
|
|
242
|
+
const FIXED_ROOM_ID2 = "dev-room";
|
|
243
|
+
const stateUrl = encodeURIComponent(`http://127.0.0.1:${port}/api/monitor/room/${FIXED_ROOM_ID2}/perspective?roleId=${roleId}`);
|
|
244
|
+
const actionUrl = encodeURIComponent(`http://127.0.0.1:${port}/api/monitor/room/${FIXED_ROOM_ID2}/action`);
|
|
245
|
+
const token = encodeURIComponent(ADMIN_SECRET2);
|
|
246
|
+
console.log(`${uiUrl}/game-ui.html?stateUrl=${stateUrl}&actionUrl=${actionUrl}&token=${token}`);
|
|
247
|
+
}
|
|
248
|
+
} catch (ignored) {
|
|
249
|
+
}
|
|
250
|
+
} catch (e) {
|
|
251
|
+
if (e.cause?.code === "ECONNREFUSED") {
|
|
252
|
+
console.error(`\u274C \u65E0\u6CD5\u8FDE\u63A5\u5230\u5F15\u64CE (localhost:${port})\u3002\u8BF7\u5148\u8FD0\u884C: npx @nexusgame/dev start`);
|
|
253
|
+
} else {
|
|
254
|
+
console.error(`\u274C \u9519\u8BEF: ${e.message}`);
|
|
255
|
+
}
|
|
256
|
+
process.exit(1);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// src/commands/action.ts
|
|
261
|
+
async function actionCommand(roleId, actionJson, options) {
|
|
262
|
+
const port = parseInt(options.port || String(DEFAULT_PORT), 10);
|
|
263
|
+
const client = new EngineClient(port);
|
|
264
|
+
let action;
|
|
265
|
+
try {
|
|
266
|
+
action = JSON.parse(actionJson);
|
|
267
|
+
} catch {
|
|
268
|
+
console.error(`\u274C \u65E0\u6548\u7684 JSON: ${actionJson}`);
|
|
269
|
+
process.exit(1);
|
|
270
|
+
}
|
|
271
|
+
if (!action.action_id) {
|
|
272
|
+
console.error(`\u274C action_json \u5FC5\u987B\u5305\u542B action_id \u5B57\u6BB5`);
|
|
273
|
+
process.exit(1);
|
|
274
|
+
}
|
|
275
|
+
try {
|
|
276
|
+
const result = await client.submitAction(roleId, action);
|
|
277
|
+
if (result.success) {
|
|
278
|
+
console.log("\u2705 \u884C\u52A8\u6210\u529F");
|
|
279
|
+
console.log(JSON.stringify(result, null, 2));
|
|
280
|
+
} else {
|
|
281
|
+
console.error(`\u274C \u884C\u52A8\u5931\u8D25: ${result.error}`);
|
|
282
|
+
}
|
|
283
|
+
} catch (e) {
|
|
284
|
+
if (e.cause?.code === "ECONNREFUSED") {
|
|
285
|
+
console.error(`\u274C \u65E0\u6CD5\u8FDE\u63A5\u5230\u5F15\u64CE (localhost:${port})\u3002\u8BF7\u5148\u8FD0\u884C: npx @nexusgame/dev start`);
|
|
286
|
+
} else {
|
|
287
|
+
console.error(`\u274C \u9519\u8BEF: ${e.message}`);
|
|
288
|
+
}
|
|
289
|
+
process.exit(1);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// src/index.ts
|
|
294
|
+
var program = new Command();
|
|
295
|
+
program.name("npgame-dev").description("\u661F\u67A2\u6C99\u76D2 \u2014 \u672C\u5730\u6E38\u620F\u5F00\u53D1\u8C03\u8BD5\u5DE5\u5177").version("0.2.0");
|
|
296
|
+
program.command("start").description("\u542F\u52A8\u672C\u5730\u5F15\u64CE (\u524D\u53F0\u8FD0\u884C, Ctrl+C \u505C\u6B62)").option("-p, --port <port>", "\u5F15\u64CE\u7AEF\u53E3", "56512").action(startCommand);
|
|
297
|
+
program.command("setup").description("\u521B\u5EFA\u623F\u95F4 + \u8BBE\u7F6E\u6E38\u620F + \u5F00\u59CB (\u4E00\u6761\u9F99)").requiredOption("-w, --worker-url <url>", "Game Worker URL (\u5982 http://localhost:8788)").option("-p, --port <port>", "\u5F15\u64CE\u7AEF\u53E3", "56512").option("-n, --players <count>", "\u6307\u5B9A\u53C2\u4E0E\u6E38\u620F\u7684\u4EBA\u6570 (\u9002\u7528\u4E8EUNO\u7B49\u53EF\u9009\u4EBA\u6570\u6E38\u620F)").action(setupCommand);
|
|
298
|
+
program.command("state").description("\u67E5\u770B\u623F\u95F4\u5168\u5C40\u72B6\u6001").option("-p, --port <port>", "\u5F15\u64CE\u7AEF\u53E3", "56512").action(stateCommand);
|
|
299
|
+
program.command("perspective").description("\u67E5\u770B\u6307\u5B9A\u89D2\u8272\u7684\u89C6\u89D2").argument("<roleId>", "\u89D2\u8272 ID").option("-p, --port <port>", "\u5F15\u64CE\u7AEF\u53E3", "56512").action(perspectiveCommand);
|
|
300
|
+
program.command("action").description("\u4EE5\u6307\u5B9A\u89D2\u8272\u6267\u884C\u884C\u52A8").argument("<roleId>", "\u89D2\u8272 ID").argument("<actionJson>", `\u884C\u52A8 JSON (\u5982 '{"action_id":"place","params":{"x":7,"y":7}}')`).option("-p, --port <port>", "\u5F15\u64CE\u7AEF\u53E3", "56512").action(actionCommand);
|
|
301
|
+
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nexusgame/dev",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Nexus Playground - local game development and debugging toolkit",
|
|
6
|
+
"bin": {
|
|
7
|
+
"npgame-dev": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"miniflare": "^4.20260302.0",
|
|
14
|
+
"commander": "^12.0.0",
|
|
15
|
+
"chalk": "^5.3.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"tsup": "^8.0.1",
|
|
19
|
+
"tsx": "^4.7.0",
|
|
20
|
+
"typescript": "^5.3.3",
|
|
21
|
+
"@types/node": "^20.11.0"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsup src/index.ts --format esm --no-splitting && cp ../../nexus-engine/dist/engine-worker.js dist/engine-worker.js",
|
|
28
|
+
"dev": "tsx src/index.ts"
|
|
29
|
+
}
|
|
6
30
|
}
|