@donkeylabs/cli 2.0.21 → 2.0.22

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.0.21",
3
+ "version": "2.0.22",
4
4
  "type": "module",
5
5
  "description": "CLI for @donkeylabs/server - project scaffolding and code generation",
6
6
  "main": "./src/index.ts",
@@ -30,6 +30,7 @@
30
30
  "picocolors": "^1.1.1",
31
31
  "prompts": "^2.4.2",
32
32
  "sharp": "^0.33.0",
33
+ "typescript": "^5.0.0",
33
34
  "zod": "^3.23.0"
34
35
  },
35
36
  "devDependencies": {
@@ -7,6 +7,7 @@ import { Kysely, Migrator, FileMigrationProvider } from "kysely";
7
7
  import { BunSqliteDialect } from "kysely-bun-sqlite";
8
8
  import { Database } from "bun:sqlite";
9
9
  import { generate, KyselyBunSqliteDialect } from "kysely-codegen";
10
+ import * as ts from "typescript";
10
11
 
11
12
  interface DonkeylabsConfig {
12
13
  plugins: string[];
@@ -69,55 +70,144 @@ async function extractHandlerNames(pluginPath: string): Promise<string[]> {
69
70
  async function extractMiddlewareNames(pluginPath: string): Promise<string[]> {
70
71
  try {
71
72
  const content = await readFile(pluginPath, "utf-8");
73
+ const astNames = extractMiddlewareNamesFromAst(content, pluginPath);
74
+ if (astNames.length > 0) {
75
+ return astNames;
76
+ }
77
+
78
+ return extractMiddlewareNamesRegexFallback(content);
79
+ } catch {
80
+ return [];
81
+ }
82
+ }
72
83
 
73
- const names = new Set<string>();
84
+ function extractMiddlewareNamesFromAst(content: string, fileName: string): string[] {
85
+ const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
86
+
87
+ const names = new Set<string>();
88
+
89
+ const addFromObjectLiteral = (obj: ts.ObjectLiteralExpression) => {
90
+ for (const prop of obj.properties) {
91
+ if (!ts.isPropertyAssignment(prop) && !ts.isMethodDeclaration(prop)) continue;
92
+ const propName = getStaticPropertyName(prop.name);
93
+ if (propName && isSafeIdentifier(propName)) {
94
+ names.add(propName);
95
+ }
96
+ }
97
+ };
74
98
 
75
- // Look for middleware definitions: `name: createMiddleware(...)`
76
- // Supports generic config: `name: createMiddleware<Config>(...)`
77
- for (const match of content.matchAll(/(\w+)\s*:\s*createMiddleware\s*(?:<[^>]+>)?\s*\(/g)) {
78
- if (match[1]) names.add(match[1]);
99
+ const collectFromMiddlewareInitializer = (initializer: ts.Expression) => {
100
+ if (ts.isObjectLiteralExpression(initializer)) {
101
+ addFromObjectLiteral(initializer);
102
+ return;
79
103
  }
80
104
 
81
- // Look for direct middleware objects: `middleware: { ... }`
82
- for (const match of content.matchAll(/middleware\s*:\s*\{/g)) {
83
- const openBracePos = (match.index ?? 0) + match[0].length - 1;
84
- const block = extractBalancedBlock(content, openBracePos, "{", "}");
85
- for (const key of extractTopLevelObjectKeys(block)) {
86
- names.add(key);
105
+ if (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)) {
106
+ const body = initializer.body;
107
+
108
+ if (ts.isObjectLiteralExpression(body)) {
109
+ addFromObjectLiteral(body);
110
+ return;
111
+ }
112
+
113
+ if (ts.isParenthesizedExpression(body) && ts.isObjectLiteralExpression(body.expression)) {
114
+ addFromObjectLiteral(body.expression);
115
+ return;
116
+ }
117
+
118
+ if (ts.isBlock(body)) {
119
+ for (const stmt of body.statements) {
120
+ if (!ts.isReturnStatement(stmt) || !stmt.expression) continue;
121
+
122
+ if (ts.isObjectLiteralExpression(stmt.expression)) {
123
+ addFromObjectLiteral(stmt.expression);
124
+ return;
125
+ }
126
+
127
+ if (
128
+ ts.isParenthesizedExpression(stmt.expression) &&
129
+ ts.isObjectLiteralExpression(stmt.expression.expression)
130
+ ) {
131
+ addFromObjectLiteral(stmt.expression.expression);
132
+ return;
133
+ }
134
+ }
87
135
  }
88
136
  }
137
+ };
89
138
 
90
- // Look for middleware factory returning object literal directly:
91
- // `middleware: (ctx, service) => ({ ... })`
92
- for (const match of content.matchAll(/middleware\s*:\s*\([^)]*\)\s*=>\s*\(\s*\{/g)) {
93
- const openBracePos = (match.index ?? 0) + match[0].length - 1;
94
- const block = extractBalancedBlock(content, openBracePos, "{", "}");
95
- for (const key of extractTopLevelObjectKeys(block)) {
96
- names.add(key);
139
+ const visit = (node: ts.Node) => {
140
+ if (ts.isPropertyAssignment(node)) {
141
+ const propName = getStaticPropertyName(node.name);
142
+ if (propName === "middleware") {
143
+ collectFromMiddlewareInitializer(node.initializer);
97
144
  }
98
145
  }
146
+ ts.forEachChild(node, visit);
147
+ };
99
148
 
100
- // Look for middleware factory with block body:
101
- // `middleware: (...) => { return { ... } }`
102
- for (const match of content.matchAll(/middleware\s*:\s*\([^)]*\)\s*=>\s*\{/g)) {
103
- const openBracePos = (match.index ?? 0) + match[0].length - 1;
104
- const fnBody = extractBalancedBlock(content, openBracePos, "{", "}");
105
- if (!fnBody) continue;
149
+ visit(sourceFile);
150
+ return [...names];
151
+ }
106
152
 
107
- const returnMatch = fnBody.match(/return\s*\{/);
108
- if (!returnMatch || returnMatch.index === undefined) continue;
153
+ function getStaticPropertyName(name: ts.PropertyName): string | null {
154
+ if (ts.isIdentifier(name)) return name.text;
155
+ if (ts.isStringLiteral(name)) return name.text;
156
+ if (ts.isNoSubstitutionTemplateLiteral(name)) return name.text;
157
+ if (ts.isNumericLiteral(name)) return name.text;
158
+ return null;
159
+ }
109
160
 
110
- const returnObjStart = returnMatch.index + returnMatch[0].length - 1;
111
- const returnObj = extractBalancedBlock(fnBody, returnObjStart, "{", "}");
112
- for (const key of extractTopLevelObjectKeys(returnObj)) {
113
- names.add(key);
114
- }
161
+ function isSafeIdentifier(name: string): boolean {
162
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
163
+ }
164
+
165
+ function extractMiddlewareNamesRegexFallback(content: string): string[] {
166
+ const names = new Set<string>();
167
+
168
+ // Look for middleware definitions: `name: createMiddleware(...)`
169
+ // Supports generic config: `name: createMiddleware<Config>(...)`
170
+ for (const match of content.matchAll(/(\w+)\s*:\s*createMiddleware\s*(?:<[^>]+>)?\s*\(/g)) {
171
+ if (match[1]) names.add(match[1]);
172
+ }
173
+
174
+ // Look for direct middleware objects: `middleware: { ... }`
175
+ for (const match of content.matchAll(/middleware\s*:\s*\{/g)) {
176
+ const openBracePos = (match.index ?? 0) + match[0].length - 1;
177
+ const block = extractBalancedBlock(content, openBracePos, "{", "}");
178
+ for (const key of extractTopLevelObjectKeys(block)) {
179
+ names.add(key);
115
180
  }
181
+ }
116
182
 
117
- return [...names];
118
- } catch {
119
- return [];
183
+ // Look for middleware factory returning object literal directly:
184
+ // `middleware: (ctx, service) => ({ ... })`
185
+ for (const match of content.matchAll(/middleware\s*:\s*\([^)]*\)\s*=>\s*\(\s*\{/g)) {
186
+ const openBracePos = (match.index ?? 0) + match[0].length - 1;
187
+ const block = extractBalancedBlock(content, openBracePos, "{", "}");
188
+ for (const key of extractTopLevelObjectKeys(block)) {
189
+ names.add(key);
190
+ }
191
+ }
192
+
193
+ // Look for middleware factory with block body:
194
+ // `middleware: (...) => { return { ... } }`
195
+ for (const match of content.matchAll(/middleware\s*:\s*\([^)]*\)\s*=>\s*\{/g)) {
196
+ const openBracePos = (match.index ?? 0) + match[0].length - 1;
197
+ const fnBody = extractBalancedBlock(content, openBracePos, "{", "}");
198
+ if (!fnBody) continue;
199
+
200
+ const returnMatch = fnBody.match(/return\s*\{/);
201
+ if (!returnMatch || returnMatch.index === undefined) continue;
202
+
203
+ const returnObjStart = returnMatch.index + returnMatch[0].length - 1;
204
+ const returnObj = extractBalancedBlock(fnBody, returnObjStart, "{", "}");
205
+ for (const key of extractTopLevelObjectKeys(returnObj)) {
206
+ names.add(key);
207
+ }
120
208
  }
209
+
210
+ return [...names];
121
211
  }
122
212
 
123
213
  function extractTopLevelObjectKeys(objectBlock: string): string[] {