@couch-kit/cli 0.2.5 → 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 +221 -2
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1887,6 +1887,9 @@ var init_esm = __esm(() => {
1887
1887
  } = import__.default);
1888
1888
  });
1889
1889
 
1890
+ // ../core/src/middleware.ts
1891
+ var init_middleware = () => {};
1892
+
1890
1893
  // ../core/src/types.ts
1891
1894
  var init_types = () => {};
1892
1895
 
@@ -1931,11 +1934,35 @@ var init_constants = __esm(() => {
1931
1934
  DEFAULT_DISCONNECT_TIMEOUT = 5 * 60 * 1000;
1932
1935
  });
1933
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
+
1934
1960
  // ../core/src/index.ts
1935
1961
  var init_src = __esm(() => {
1936
1962
  init_types();
1937
1963
  init_protocol();
1938
1964
  init_constants();
1965
+ init_middleware();
1939
1966
  });
1940
1967
 
1941
1968
  // src/commands/bundle.ts
@@ -2094,6 +2121,18 @@ __export(exports_init, {
2094
2121
  });
2095
2122
  import fs2 from "node:fs";
2096
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
+ }
2097
2136
  var initCommand;
2098
2137
  var init_init = __esm(() => {
2099
2138
  init_esm();
@@ -2145,6 +2184,8 @@ var init_init = __esm(() => {
2145
2184
  noEmit: true,
2146
2185
  jsx: "react-jsx",
2147
2186
  strict: true,
2187
+ noUncheckedIndexedAccess: true,
2188
+ exactOptionalPropertyTypes: true,
2148
2189
  noUnusedLocals: true,
2149
2190
  noUnusedParameters: true,
2150
2191
  noFallthroughCasesInSwitch: true
@@ -2228,12 +2269,53 @@ export default App
2228
2269
  fs2.writeFileSync(path2.join(targetDir, "src/index.css"), `
2229
2270
  body { margin: 0; background: #111; color: #fff; }
2230
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();
2231
2313
  console.log(`Done! Created new project in ${name}`);
2232
2314
  console.log(`
2233
2315
  Next steps:
2234
2316
  cd ${name}
2235
- bun install
2236
- bun run dev
2317
+ ${pm} install
2318
+ ${pm} run dev
2237
2319
  `);
2238
2320
  } catch (e) {
2239
2321
  console.error(`Init failed: ${toErrorMessage(e)}`);
@@ -2242,6 +2324,133 @@ Next steps:
2242
2324
  });
2243
2325
  });
2244
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
+
2245
2454
  // src/index.ts
2246
2455
  init_esm();
2247
2456
  import { createRequire as createRequire2 } from "node:module";
@@ -2263,6 +2472,16 @@ program2.command("init").description("Scaffolds a new web controller project").a
2263
2472
  const { initCommand: initCommand2 } = await Promise.resolve().then(() => (init_init(), exports_init));
2264
2473
  await initCommand2.parseAsync(["init", name], { from: "user" });
2265
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
+ });
2266
2485
  function reconstructArgs(options) {
2267
2486
  const args = [];
2268
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.5",
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.6.0",
39
+ "@couch-kit/core": "0.8.0",
40
40
  "commander": "^12.0.0"
41
41
  },
42
42
  "devDependencies": {