@couch-kit/cli 0.2.6 → 0.3.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.
Files changed (2) hide show
  1. package/dist/index.js +217 -2
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1934,6 +1934,29 @@ var init_constants = __esm(() => {
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();
@@ -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
- bun install
2240
- bun run dev
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.2.6",
3
+ "version": "0.3.0",
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.7.0",
39
+ "@couch-kit/core": "0.8.0",
40
40
  "commander": "^12.0.0"
41
41
  },
42
42
  "devDependencies": {