@donkeylabs/cli 2.1.0 → 2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@donkeylabs/cli",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "type": "module",
5
5
  "description": "CLI for @donkeylabs/server - project scaffolding and code generation",
6
6
  "main": "./src/index.ts",
@@ -18,7 +18,8 @@ export async function generateClientCommand(
18
18
  // Extract routes from server
19
19
  const entryPath = config.entry || "./src/index.ts";
20
20
  console.log(pc.dim(`Extracting routes from ${entryPath}...`));
21
- const routes = await extractRoutesFromServer(entryPath);
21
+ const serverOutput = await extractRoutesFromServer(entryPath);
22
+ const routes = serverOutput.routes;
22
23
 
23
24
  if (routes.length === 0) {
24
25
  console.warn(pc.yellow("No routes found - generating empty client"));
@@ -36,15 +36,26 @@ export interface RouteInfo {
36
36
  eventsSource?: Record<string, string>;
37
37
  }
38
38
 
39
+ export interface ProcessInfo {
40
+ name: string;
41
+ events?: Record<string, string>;
42
+ commands?: Record<string, string>;
43
+ }
44
+
45
+ export interface ServerOutput {
46
+ routes: RouteInfo[];
47
+ processes: ProcessInfo[];
48
+ }
49
+
39
50
  /**
40
51
  * Run the server entry file with DONKEYLABS_GENERATE=1 to get typed route metadata
41
52
  */
42
- export async function extractRoutesFromServer(entryPath: string): Promise<RouteInfo[]> {
53
+ export async function extractRoutesFromServer(entryPath: string): Promise<ServerOutput> {
43
54
  const fullPath = join(process.cwd(), entryPath);
44
55
 
45
56
  if (!existsSync(fullPath)) {
46
57
  console.warn(pc.yellow(`Entry file not found: ${entryPath}`));
47
- return [];
58
+ return { routes: [], processes: [] };
48
59
  }
49
60
 
50
61
  const TIMEOUT_MS = 10000; // 10 second timeout
@@ -66,7 +77,7 @@ export async function extractRoutesFromServer(entryPath: string): Promise<RouteI
66
77
  child.kill("SIGTERM");
67
78
  console.warn(pc.yellow(`Route extraction timed out after ${TIMEOUT_MS / 1000}s`));
68
79
  console.warn(pc.dim("Make sure routes are registered with server.use() before any blocking operations"));
69
- resolve([]);
80
+ resolve({ routes: [], processes: [] });
70
81
  }, TIMEOUT_MS);
71
82
 
72
83
  child.stdout?.on("data", (data) => {
@@ -84,7 +95,7 @@ export async function extractRoutesFromServer(entryPath: string): Promise<RouteI
84
95
  if (code !== 0) {
85
96
  console.warn(pc.yellow(`Failed to extract routes from server (exit code ${code})`));
86
97
  if (stderr) console.warn(pc.dim(stderr));
87
- resolve([]);
98
+ resolve({ routes: [], processes: [] });
88
99
  return;
89
100
  }
90
101
 
@@ -105,17 +116,25 @@ export async function extractRoutesFromServer(entryPath: string): Promise<RouteI
105
116
  eventsSource: r.eventsType,
106
117
  };
107
118
  });
108
- resolve(routes);
119
+
120
+ // Parse process definitions
121
+ const processes: ProcessInfo[] = (result.processes || []).map((p: any) => ({
122
+ name: p.name,
123
+ events: p.events,
124
+ commands: p.commands,
125
+ }));
126
+
127
+ resolve({ routes, processes });
109
128
  } catch (e) {
110
129
  console.warn(pc.yellow("Failed to parse route data from server"));
111
- resolve([]);
130
+ resolve({ routes: [], processes: [] });
112
131
  }
113
132
  });
114
133
 
115
134
  child.on("error", (err) => {
116
135
  clearTimeout(timeout);
117
136
  console.warn(pc.yellow(`Failed to run entry file: ${err.message}`));
118
- resolve([]);
137
+ resolve({ routes: [], processes: [] });
119
138
  });
120
139
  });
121
140
  }
@@ -7,7 +7,7 @@ import { BunSqliteDialect } from "kysely-bun-sqlite";
7
7
  import { Database } from "bun:sqlite";
8
8
  import { generate, KyselyBunSqliteDialect } from "kysely-codegen";
9
9
  import * as ts from "typescript";
10
- import { loadConfig, extractRoutesFromServer, type DonkeylabsConfig, type RouteInfo } from "./generate-utils";
10
+ import { loadConfig, extractRoutesFromServer, type DonkeylabsConfig, type RouteInfo, type ProcessInfo } from "./generate-utils";
11
11
 
12
12
  async function getPluginExportName(pluginPath: string): Promise<string | null> {
13
13
  try {
@@ -918,9 +918,11 @@ export async function generateCommand(_args: string[]): Promise<void> {
918
918
  );
919
919
  }
920
920
 
921
- // Extract routes by running the server with DONKEYLABS_GENERATE=1
921
+ // Extract routes and processes by running the server with DONKEYLABS_GENERATE=1
922
922
  const entryPath = config.entry || "./src/index.ts";
923
- const serverRoutes = await extractRoutesFromServer(entryPath);
923
+ const serverOutput = await extractRoutesFromServer(entryPath);
924
+ const serverRoutes = serverOutput.routes;
925
+ const serverProcesses = serverOutput.processes;
924
926
 
925
927
  // Find custom service definitions
926
928
  const services = await findServiceDefinitions(entryPath);
@@ -940,13 +942,22 @@ export async function generateCommand(_args: string[]): Promise<void> {
940
942
  );
941
943
  }
942
944
 
945
+ // Log process definitions found
946
+ if (serverProcesses.length > 0) {
947
+ console.log(
948
+ pc.green("Found processes:"),
949
+ serverProcesses.map((p) => pc.dim(p.name)).join(", ")
950
+ );
951
+ }
952
+
943
953
  // Generate all files
944
954
  await generateRegistry(plugins, outPath);
945
955
  await generateContext(plugins, services, outPath);
946
956
  await generateRouteTypes(fileRoutes, outPath);
947
957
  await generateEventTypes(serverEvents, outPath);
958
+ await generateProcessTypes(serverProcesses, outPath);
948
959
 
949
- const generated = ["registry", "context", "routes", "events"];
960
+ const generated = ["registry", "context", "routes", "events", "processes"];
950
961
 
951
962
  // Determine client output path
952
963
  const clientOutput = config.client?.output || join(outPath, "client.ts");
@@ -1454,3 +1465,99 @@ ${eventRegistryEntries.join("\n")}
1454
1465
 
1455
1466
  await writeFile(join(outPath, "events.ts"), content);
1456
1467
  }
1468
+
1469
+ /**
1470
+ * Generate processes.ts with typed events, commands, and ProcessRegistry augmentation.
1471
+ */
1472
+ async function generateProcessTypes(processes: ProcessInfo[], outPath: string): Promise<void> {
1473
+ if (processes.length === 0) {
1474
+ const content = `// Auto-generated by donkeylabs generate
1475
+ // Processes - import as: import { type ProcessEventMap, type ProcessCommandMap } from ".@donkeylabs/server/processes";
1476
+
1477
+ /** Map of all process event names to their data types */
1478
+ export interface ProcessEventMap {}
1479
+
1480
+ /** Map of all process command names to their data types */
1481
+ export interface ProcessCommandMap {}
1482
+
1483
+ // Augment ProcessRegistry for typed send() (empty when no processes defined)
1484
+ declare module "@donkeylabs/server" {
1485
+ interface ProcessRegistry {}
1486
+ }
1487
+ `;
1488
+ await writeFile(join(outPath, "processes.ts"), content);
1489
+ return;
1490
+ }
1491
+
1492
+ const namespaceBlocks: string[] = [];
1493
+ const eventMapEntries: string[] = [];
1494
+ const commandMapEntries: string[] = [];
1495
+ const registryEntries: string[] = [];
1496
+
1497
+ for (const proc of processes) {
1498
+ const pascalName = toPascalCase(proc.name);
1499
+
1500
+ const eventDecls: string[] = [];
1501
+ const commandDecls: string[] = [];
1502
+
1503
+ // Generate event types
1504
+ if (proc.events) {
1505
+ for (const [eventName, tsType] of Object.entries(proc.events)) {
1506
+ const pascalEvent = toPascalCase(eventName);
1507
+ eventDecls.push(` export type ${pascalEvent} = ${tsType};`);
1508
+ eventMapEntries.push(` "${proc.name}.${eventName}": ${pascalName}.Events.${pascalEvent};`);
1509
+ }
1510
+ }
1511
+
1512
+ // Generate command types
1513
+ if (proc.commands) {
1514
+ for (const [cmdName, tsType] of Object.entries(proc.commands)) {
1515
+ const pascalCmd = toPascalCase(cmdName);
1516
+ commandDecls.push(` export type ${pascalCmd} = ${tsType};`);
1517
+ commandMapEntries.push(` "${proc.name}.${cmdName}": ${pascalName}.Commands.${pascalCmd};`);
1518
+ }
1519
+ }
1520
+
1521
+ // Build namespace
1522
+ const eventsNs = eventDecls.length > 0
1523
+ ? ` export namespace Events {\n${eventDecls.join("\n")}\n }`
1524
+ : ` export namespace Events {}`;
1525
+ const commandsNs = commandDecls.length > 0
1526
+ ? ` export namespace Commands {\n${commandDecls.join("\n")}\n }`
1527
+ : ` export namespace Commands {}`;
1528
+
1529
+ namespaceBlocks.push(`export namespace ${pascalName} {\n${eventsNs}\n${commandsNs}\n}`);
1530
+
1531
+ // Registry entry
1532
+ registryEntries.push(` "${proc.name}": {
1533
+ events: ${pascalName}.Events;
1534
+ commands: ${pascalName}.Commands;
1535
+ };`);
1536
+ }
1537
+
1538
+ const content = `// Auto-generated by donkeylabs generate
1539
+ // Processes - import as: import { type ProcessEventMap, type ProcessCommandMap } from ".@donkeylabs/server/processes";
1540
+
1541
+ // Process type namespaces
1542
+ ${namespaceBlocks.join("\n\n")}
1543
+
1544
+ /** Map of all process event names to their data types */
1545
+ export interface ProcessEventMap {
1546
+ ${eventMapEntries.join("\n")}
1547
+ }
1548
+
1549
+ /** Map of all process command names to their data types */
1550
+ export interface ProcessCommandMap {
1551
+ ${commandMapEntries.join("\n")}
1552
+ }
1553
+
1554
+ // Augment ProcessRegistry for typed send()
1555
+ declare module "@donkeylabs/server" {
1556
+ interface ProcessRegistry {
1557
+ ${registryEntries.join("\n")}
1558
+ }
1559
+ }
1560
+ `;
1561
+
1562
+ await writeFile(join(outPath, "processes.ts"), content);
1563
+ }