@couch-kit/cli 0.2.6 → 0.3.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/dist/index.js +219 -4
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1928,12 +1928,35 @@ function toErrorMessage(error) {
|
|
|
1928
1928
|
return error.message;
|
|
1929
1929
|
return String(error);
|
|
1930
1930
|
}
|
|
1931
|
-
var MAX_FRAME_SIZE, DEFAULT_DISCONNECT_TIMEOUT;
|
|
1931
|
+
var DEFAULT_HTTP_PORT = 8080, DEFAULT_WS_PORT_OFFSET = 2, DEFAULT_WS_PATH = "/ws", MAX_FRAME_SIZE, DEFAULT_DISCONNECT_TIMEOUT;
|
|
1932
1932
|
var init_constants = __esm(() => {
|
|
1933
1933
|
MAX_FRAME_SIZE = 1024 * 1024;
|
|
1934
1934
|
DEFAULT_DISCONNECT_TIMEOUT = 5 * 60 * 1000;
|
|
1935
1935
|
});
|
|
1936
1936
|
|
|
1937
|
+
// ../core/src/replay.ts
|
|
1938
|
+
function replayActions(recording, reducer) {
|
|
1939
|
+
const snapshots = [];
|
|
1940
|
+
let currentState = recording.initialState;
|
|
1941
|
+
for (let i = 0;i < recording.actions.length; i++) {
|
|
1942
|
+
const { action, timestamp } = recording.actions[i];
|
|
1943
|
+
currentState = reducer(currentState, action);
|
|
1944
|
+
snapshots.push({
|
|
1945
|
+
state: currentState,
|
|
1946
|
+
action,
|
|
1947
|
+
timestamp,
|
|
1948
|
+
index: i
|
|
1949
|
+
});
|
|
1950
|
+
}
|
|
1951
|
+
const endTimestamp = recording.endTimestamp ?? (recording.actions.length > 0 ? recording.actions[recording.actions.length - 1].timestamp : recording.startTimestamp);
|
|
1952
|
+
return {
|
|
1953
|
+
finalState: currentState,
|
|
1954
|
+
snapshots,
|
|
1955
|
+
duration: endTimestamp - recording.startTimestamp,
|
|
1956
|
+
actionCount: recording.actions.length
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1937
1960
|
// ../core/src/index.ts
|
|
1938
1961
|
var init_src = __esm(() => {
|
|
1939
1962
|
init_types();
|
|
@@ -2035,7 +2058,7 @@ var simulateCommand;
|
|
|
2035
2058
|
var init_simulate = __esm(() => {
|
|
2036
2059
|
init_esm();
|
|
2037
2060
|
init_src();
|
|
2038
|
-
simulateCommand = new Command("simulate").description("Spawns headless bots to simulate players").option("-n, --count <number>", "Number of bots", "4").option("-u, --url <url>", "WebSocket URL of host",
|
|
2061
|
+
simulateCommand = new Command("simulate").description("Spawns headless bots to simulate players").option("-n, --count <number>", "Number of bots", "4").option("-u, --url <url>", "WebSocket URL of host", `ws://localhost:${DEFAULT_HTTP_PORT + DEFAULT_WS_PORT_OFFSET}${DEFAULT_WS_PATH}`).option("-i, --interval <ms>", "Action interval in ms", "1000").action(async (options) => {
|
|
2039
2062
|
const count = parseInt(options.count);
|
|
2040
2063
|
const url = options.url;
|
|
2041
2064
|
const interval = parseInt(options.interval);
|
|
@@ -2098,6 +2121,18 @@ __export(exports_init, {
|
|
|
2098
2121
|
});
|
|
2099
2122
|
import fs2 from "node:fs";
|
|
2100
2123
|
import path2 from "node:path";
|
|
2124
|
+
function detectPackageManager2() {
|
|
2125
|
+
if (typeof Bun !== "undefined")
|
|
2126
|
+
return "bun";
|
|
2127
|
+
const userAgent = process.env.npm_config_user_agent ?? "";
|
|
2128
|
+
if (userAgent.includes("yarn"))
|
|
2129
|
+
return "yarn";
|
|
2130
|
+
if (userAgent.includes("pnpm"))
|
|
2131
|
+
return "pnpm";
|
|
2132
|
+
if (userAgent.includes("bun"))
|
|
2133
|
+
return "bun";
|
|
2134
|
+
return "npm";
|
|
2135
|
+
}
|
|
2101
2136
|
var initCommand;
|
|
2102
2137
|
var init_init = __esm(() => {
|
|
2103
2138
|
init_esm();
|
|
@@ -2149,6 +2184,8 @@ var init_init = __esm(() => {
|
|
|
2149
2184
|
noEmit: true,
|
|
2150
2185
|
jsx: "react-jsx",
|
|
2151
2186
|
strict: true,
|
|
2187
|
+
noUncheckedIndexedAccess: true,
|
|
2188
|
+
exactOptionalPropertyTypes: true,
|
|
2152
2189
|
noUnusedLocals: true,
|
|
2153
2190
|
noUnusedParameters: true,
|
|
2154
2191
|
noFallthroughCasesInSwitch: true
|
|
@@ -2232,12 +2269,53 @@ export default App
|
|
|
2232
2269
|
fs2.writeFileSync(path2.join(targetDir, "src/index.css"), `
|
|
2233
2270
|
body { margin: 0; background: #111; color: #fff; }
|
|
2234
2271
|
`);
|
|
2272
|
+
const reducerContent = `import type { IAction, IGameState, GameReducer } from "@couch-kit/core";
|
|
2273
|
+
|
|
2274
|
+
export interface GameState extends IGameState {
|
|
2275
|
+
score: number;
|
|
2276
|
+
round: number;
|
|
2277
|
+
}
|
|
2278
|
+
|
|
2279
|
+
export interface GameAction extends IAction {
|
|
2280
|
+
type: "SCORE" | "NEXT_ROUND" | "RESET";
|
|
2281
|
+
payload?: { points?: number };
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
export const gameReducer: GameReducer<GameState, GameAction> = (state, action) => {
|
|
2285
|
+
switch (action.type) {
|
|
2286
|
+
case "SCORE":
|
|
2287
|
+
return { ...state, score: state.score + (action.payload?.points ?? 1) };
|
|
2288
|
+
case "NEXT_ROUND":
|
|
2289
|
+
return { ...state, round: state.round + 1 };
|
|
2290
|
+
case "RESET":
|
|
2291
|
+
return { ...state, score: 0, round: 1 };
|
|
2292
|
+
default:
|
|
2293
|
+
return state;
|
|
2294
|
+
}
|
|
2295
|
+
};
|
|
2296
|
+
|
|
2297
|
+
export const initialState: GameState = {
|
|
2298
|
+
status: "lobby",
|
|
2299
|
+
players: {},
|
|
2300
|
+
score: 0,
|
|
2301
|
+
round: 1,
|
|
2302
|
+
};
|
|
2303
|
+
`;
|
|
2304
|
+
fs2.writeFileSync(path2.join(targetDir, "src/reducer.ts"), reducerContent);
|
|
2305
|
+
const gitignoreContent = `node_modules/
|
|
2306
|
+
dist/
|
|
2307
|
+
lib/
|
|
2308
|
+
.DS_Store
|
|
2309
|
+
*.local
|
|
2310
|
+
`;
|
|
2311
|
+
fs2.writeFileSync(path2.join(targetDir, ".gitignore"), gitignoreContent);
|
|
2312
|
+
const pm = detectPackageManager2();
|
|
2235
2313
|
console.log(`Done! Created new project in ${name}`);
|
|
2236
2314
|
console.log(`
|
|
2237
2315
|
Next steps:
|
|
2238
2316
|
cd ${name}
|
|
2239
|
-
|
|
2240
|
-
|
|
2317
|
+
${pm} install
|
|
2318
|
+
${pm} run dev
|
|
2241
2319
|
`);
|
|
2242
2320
|
} catch (e) {
|
|
2243
2321
|
console.error(`Init failed: ${toErrorMessage(e)}`);
|
|
@@ -2246,6 +2324,133 @@ Next steps:
|
|
|
2246
2324
|
});
|
|
2247
2325
|
});
|
|
2248
2326
|
|
|
2327
|
+
// src/commands/replay.ts
|
|
2328
|
+
var exports_replay = {};
|
|
2329
|
+
__export(exports_replay, {
|
|
2330
|
+
replay: () => replay2
|
|
2331
|
+
});
|
|
2332
|
+
import { resolve } from "node:path";
|
|
2333
|
+
var replay2;
|
|
2334
|
+
var init_replay = __esm(() => {
|
|
2335
|
+
init_esm();
|
|
2336
|
+
init_src();
|
|
2337
|
+
replay2 = new Command("replay").description("Replay a recorded game session against a reducer").argument("<recording>", "Path to recording JSON file").argument("<reducer>", "Path to reducer module (must export default or named 'reducer')").option("--snapshots", "Output intermediate state snapshots", false).option("--json", "Output as formatted JSON", false).action(async (recordingPath, reducerPath, options) => {
|
|
2338
|
+
try {
|
|
2339
|
+
const resolvedRecordingPath = resolve(recordingPath);
|
|
2340
|
+
const recordingFile = Bun.file(resolvedRecordingPath);
|
|
2341
|
+
const exists = await recordingFile.exists();
|
|
2342
|
+
if (!exists) {
|
|
2343
|
+
console.error(`Error: Recording file not found: ${recordingPath}`);
|
|
2344
|
+
process.exit(1);
|
|
2345
|
+
}
|
|
2346
|
+
const recording = await recordingFile.json();
|
|
2347
|
+
if (!recording.initialState || !Array.isArray(recording.actions)) {
|
|
2348
|
+
console.error("Error: Invalid recording format. Expected { initialState, actions[] }");
|
|
2349
|
+
process.exit(1);
|
|
2350
|
+
}
|
|
2351
|
+
const resolvedReducerPath = resolve(reducerPath);
|
|
2352
|
+
const reducerModule = await import(resolvedReducerPath);
|
|
2353
|
+
const reducer = reducerModule.default ?? reducerModule.reducer;
|
|
2354
|
+
if (typeof reducer !== "function") {
|
|
2355
|
+
console.error("Error: Reducer module must export a default function or named 'reducer' export");
|
|
2356
|
+
process.exit(1);
|
|
2357
|
+
}
|
|
2358
|
+
const result = replayActions(recording, reducer);
|
|
2359
|
+
if (options.json) {
|
|
2360
|
+
const output = options.snapshots ? result : {
|
|
2361
|
+
finalState: result.finalState,
|
|
2362
|
+
duration: result.duration,
|
|
2363
|
+
actionCount: result.actionCount
|
|
2364
|
+
};
|
|
2365
|
+
console.log(JSON.stringify(output, null, 2));
|
|
2366
|
+
} else {
|
|
2367
|
+
console.log(`Replayed ${result.actionCount} actions in ${result.duration}ms`);
|
|
2368
|
+
console.log(`
|
|
2369
|
+
Final state:`);
|
|
2370
|
+
console.log(JSON.stringify(result.finalState, null, 2));
|
|
2371
|
+
if (options.snapshots) {
|
|
2372
|
+
console.log(`
|
|
2373
|
+
Snapshots (${result.snapshots.length}):`);
|
|
2374
|
+
for (const snapshot of result.snapshots) {
|
|
2375
|
+
console.log(`
|
|
2376
|
+
[${snapshot.index}] ${JSON.stringify(snapshot.action)} @ ${snapshot.timestamp}`);
|
|
2377
|
+
console.log(` State: ${JSON.stringify(snapshot.state)}`);
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
} catch (error) {
|
|
2382
|
+
console.error(`Error: ${toErrorMessage(error)}`);
|
|
2383
|
+
process.exit(1);
|
|
2384
|
+
}
|
|
2385
|
+
});
|
|
2386
|
+
});
|
|
2387
|
+
|
|
2388
|
+
// src/commands/dev.ts
|
|
2389
|
+
var exports_dev = {};
|
|
2390
|
+
__export(exports_dev, {
|
|
2391
|
+
dev: () => dev
|
|
2392
|
+
});
|
|
2393
|
+
import { resolve as resolve2 } from "node:path";
|
|
2394
|
+
import { networkInterfaces } from "node:os";
|
|
2395
|
+
function getLanIp() {
|
|
2396
|
+
const interfaces = networkInterfaces();
|
|
2397
|
+
for (const name of Object.keys(interfaces)) {
|
|
2398
|
+
const nets = interfaces[name];
|
|
2399
|
+
if (!nets)
|
|
2400
|
+
continue;
|
|
2401
|
+
for (const net of nets) {
|
|
2402
|
+
if (net.family === "IPv4" && !net.internal) {
|
|
2403
|
+
return net.address;
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
return null;
|
|
2408
|
+
}
|
|
2409
|
+
var dev;
|
|
2410
|
+
var init_dev = __esm(() => {
|
|
2411
|
+
init_esm();
|
|
2412
|
+
init_src();
|
|
2413
|
+
dev = new Command("dev").description("Start development server with LAN access for mobile testing").option("-p, --port <port>", "Port number", "5173").option("--host", "Expose to LAN", true).option("--open", "Open browser automatically", false).action(async (options) => {
|
|
2414
|
+
try {
|
|
2415
|
+
const port = parseInt(options.port, 10);
|
|
2416
|
+
const cwd = process.cwd();
|
|
2417
|
+
let vite;
|
|
2418
|
+
try {
|
|
2419
|
+
const vitePkg = "vite";
|
|
2420
|
+
vite = await import(vitePkg);
|
|
2421
|
+
} catch {
|
|
2422
|
+
console.error("Error: Vite is not installed. Run: bun add -d vite");
|
|
2423
|
+
process.exit(1);
|
|
2424
|
+
}
|
|
2425
|
+
const lanIp = getLanIp();
|
|
2426
|
+
console.log(`Starting Couch Kit dev server...
|
|
2427
|
+
`);
|
|
2428
|
+
const server = await vite.createServer({
|
|
2429
|
+
root: cwd,
|
|
2430
|
+
server: {
|
|
2431
|
+
port,
|
|
2432
|
+
host: options.host ? "0.0.0.0" : "localhost",
|
|
2433
|
+
open: options.open
|
|
2434
|
+
},
|
|
2435
|
+
configFile: resolve2(cwd, "vite.config.ts")
|
|
2436
|
+
});
|
|
2437
|
+
await server.listen();
|
|
2438
|
+
console.log(` Local: http://localhost:${port}/`);
|
|
2439
|
+
if (lanIp) {
|
|
2440
|
+
console.log(` LAN: http://${lanIp}:${port}/`);
|
|
2441
|
+
console.log(`
|
|
2442
|
+
Scan QR or open LAN URL on your mobile device`);
|
|
2443
|
+
}
|
|
2444
|
+
console.log(`
|
|
2445
|
+
Press Ctrl+C to stop
|
|
2446
|
+
`);
|
|
2447
|
+
} catch (error) {
|
|
2448
|
+
console.error(`Error: ${toErrorMessage(error)}`);
|
|
2449
|
+
process.exit(1);
|
|
2450
|
+
}
|
|
2451
|
+
});
|
|
2452
|
+
});
|
|
2453
|
+
|
|
2249
2454
|
// src/index.ts
|
|
2250
2455
|
init_esm();
|
|
2251
2456
|
import { createRequire as createRequire2 } from "node:module";
|
|
@@ -2267,6 +2472,16 @@ program2.command("init").description("Scaffolds a new web controller project").a
|
|
|
2267
2472
|
const { initCommand: initCommand2 } = await Promise.resolve().then(() => (init_init(), exports_init));
|
|
2268
2473
|
await initCommand2.parseAsync(["init", name], { from: "user" });
|
|
2269
2474
|
});
|
|
2475
|
+
program2.command("replay").description("Replay a recorded game session against a reducer").argument("<recording>", "Path to recording JSON file").argument("<reducer>", "Path to reducer module").option("--snapshots", "Output intermediate state snapshots").option("--json", "Output as formatted JSON").action(async (recording, reducer, options) => {
|
|
2476
|
+
const { replay: replay3 } = await Promise.resolve().then(() => (init_replay(), exports_replay));
|
|
2477
|
+
await replay3.parseAsync(["replay", recording, reducer, ...reconstructArgs(options)], { from: "user" });
|
|
2478
|
+
});
|
|
2479
|
+
program2.command("dev").description("Start development server with LAN access").option("-p, --port <port>", "Port number", "5173").option("--host", "Expose to LAN").option("--open", "Open browser automatically").action(async (options) => {
|
|
2480
|
+
const { dev: dev2 } = await Promise.resolve().then(() => (init_dev(), exports_dev));
|
|
2481
|
+
await dev2.parseAsync(["dev", ...reconstructArgs(options)], {
|
|
2482
|
+
from: "user"
|
|
2483
|
+
});
|
|
2484
|
+
});
|
|
2270
2485
|
function reconstructArgs(options) {
|
|
2271
2486
|
const args = [];
|
|
2272
2487
|
for (const [key, value] of Object.entries(options)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@couch-kit/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"provenance": true
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"clean": "rm -rf dist"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@couch-kit/core": "0.
|
|
39
|
+
"@couch-kit/core": "0.8.1",
|
|
40
40
|
"commander": "^12.0.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|