opencroc 0.3.1 → 0.5.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/README.en.md CHANGED
@@ -260,7 +260,7 @@ export default defineConfig({
260
260
 
261
261
  | Layer | Supported | Planned |
262
262
  |---|---|---|
263
- | **ORM** | Sequelize, TypeORM, Prisma | Drizzle |
263
+ | **ORM** | Sequelize, TypeORM, Prisma, Drizzle | |
264
264
  | **Framework** | Express | NestJS, Fastify, Koa |
265
265
  | **Test Runner** | Playwright | — |
266
266
  | **LLM** | OpenAI, ZhiPu (GLM), Ollama (local) | Anthropic |
@@ -293,9 +293,9 @@ export default defineConfig({
293
293
  - [x] VS Code extension scaffold
294
294
  - [x] Plugin system
295
295
  - [x] HTML / JSON / Markdown report generation
296
- - [ ] NestJS controller parser
296
+ - [x] NestJS controller parser
297
297
  - [ ] Visual dashboard (opencroc.com)
298
- - [ ] Drizzle ORM adapter
298
+ - [x] Drizzle ORM adapter
299
299
 
300
300
  ## Documentation
301
301
 
package/README.ja.md CHANGED
@@ -260,7 +260,7 @@ export default defineConfig({
260
260
 
261
261
  | レイヤー | 対応済み | 予定 |
262
262
  |---|---|---|
263
- | **ORM** | Sequelize, TypeORM, Prisma | Drizzle |
263
+ | **ORM** | Sequelize, TypeORM, Prisma, Drizzle | |
264
264
  | **Framework** | Express | NestJS, Fastify, Koa |
265
265
  | **Test Runner** | Playwright | — |
266
266
  | **LLM** | OpenAI, ZhiPu (GLM), Ollama (local) | Anthropic |
@@ -293,9 +293,9 @@ export default defineConfig({
293
293
  - [x] VS Code extension scaffold
294
294
  - [x] Plugin system
295
295
  - [x] HTML / JSON / Markdown report generation
296
- - [ ] NestJS controller parser
296
+ - [x] NestJS controller parser
297
297
  - [ ] Visual dashboard (opencroc.com)
298
- - [ ] Drizzle ORM adapter
298
+ - [x] Drizzle ORM adapter
299
299
 
300
300
  ## ドキュメント
301
301
 
package/README.md CHANGED
@@ -260,7 +260,7 @@ export default defineConfig({
260
260
 
261
261
  | Layer | Supported | Planned |
262
262
  |---|---|---|
263
- | **ORM** | Sequelize, TypeORM, Prisma | Drizzle |
263
+ | **ORM** | Sequelize, TypeORM, Prisma, Drizzle | |
264
264
  | **Framework** | Express | NestJS, Fastify, Koa |
265
265
  | **Test Runner** | Playwright | — |
266
266
  | **LLM** | OpenAI, ZhiPu (GLM), Ollama (local) | Anthropic |
@@ -293,9 +293,9 @@ export default defineConfig({
293
293
  - [x] VS Code extension scaffold
294
294
  - [x] Plugin system
295
295
  - [x] HTML / JSON / Markdown report generation
296
- - [ ] NestJS controller parser
296
+ - [x] NestJS controller parser
297
297
  - [ ] Visual dashboard (opencroc.com)
298
- - [ ] Drizzle ORM adapter
298
+ - [x] Drizzle ORM adapter
299
299
 
300
300
  ## Documentation
301
301
 
package/README.zh-CN.md CHANGED
@@ -260,7 +260,7 @@ export default defineConfig({
260
260
 
261
261
  | 层级 | 已支持 | 规划中 |
262
262
  |---|---|---|
263
- | **ORM** | Sequelize, TypeORM, Prisma | Drizzle |
263
+ | **ORM** | Sequelize, TypeORM, Prisma, Drizzle | |
264
264
  | **Framework** | Express | NestJS, Fastify, Koa |
265
265
  | **Test Runner** | Playwright | — |
266
266
  | **LLM** | OpenAI, ZhiPu (GLM), Ollama (local) | Anthropic |
@@ -293,9 +293,9 @@ export default defineConfig({
293
293
  - [x] VS Code extension scaffold
294
294
  - [x] Plugin system
295
295
  - [x] HTML / JSON / Markdown report generation
296
- - [ ] NestJS controller parser
296
+ - [x] NestJS controller parser
297
297
  - [ ] Visual dashboard (opencroc.com)
298
- - [ ] Drizzle ORM adapter
298
+ - [x] Drizzle ORM adapter
299
299
 
300
300
  ## 文档
301
301
 
package/dist/cli/index.js CHANGED
@@ -339,7 +339,8 @@ import * as fs2 from "fs";
339
339
  import * as path3 from "path";
340
340
  import {
341
341
  Project as Project2,
342
- SyntaxKind as SyntaxKind2
342
+ SyntaxKind as SyntaxKind2,
343
+ Node
343
344
  } from "ts-morph";
344
345
  function parseControllerFile(filePath) {
345
346
  const absolutePath = path3.resolve(filePath);
@@ -350,6 +351,7 @@ function parseControllerFile(filePath) {
350
351
  const endpoints = [];
351
352
  endpoints.push(...extractRouterCalls(sourceFile));
352
353
  endpoints.push(...extractBaseCrudRoutes(sourceFile));
354
+ endpoints.push(...extractNestControllerRoutes(sourceFile));
353
355
  return deduplicateEndpoints(endpoints);
354
356
  } catch {
355
357
  return [];
@@ -442,6 +444,49 @@ function extractBaseCrudRoutes(sourceFile) {
442
444
  }
443
445
  return endpoints;
444
446
  }
447
+ function extractNestControllerRoutes(sourceFile) {
448
+ const endpoints = [];
449
+ for (const cls of sourceFile.getClasses()) {
450
+ const controllerDecorator = cls.getDecorators().find((d) => d.getName().toLowerCase() === "controller");
451
+ if (!controllerDecorator) continue;
452
+ const controllerBasePath = normalizeRoutePath(extractDecoratorPath(controllerDecorator, sourceFile) ?? "");
453
+ for (const methodDecl of cls.getMethods()) {
454
+ const requestMapping = extractRequestMapping(methodDecl, sourceFile);
455
+ if (requestMapping) {
456
+ const fullPath2 = joinRoutePath(controllerBasePath, normalizeRoutePath(requestMapping.path));
457
+ endpoints.push({
458
+ method: requestMapping.method,
459
+ path: fullPath2,
460
+ pathParams: extractPathParams(fullPath2),
461
+ queryParams: [],
462
+ bodyFields: [],
463
+ responseFields: [],
464
+ relatedTables: [],
465
+ description: extractMethodDescription(methodDecl)
466
+ });
467
+ continue;
468
+ }
469
+ const httpDecorator = methodDecl.getDecorators().find((d) => NEST_HTTP_DECORATORS.has(d.getName().toLowerCase()));
470
+ if (!httpDecorator) continue;
471
+ const methodName = httpDecorator.getName().toLowerCase();
472
+ const method = METHOD_MAP[methodName];
473
+ if (!method) continue;
474
+ const methodPath = normalizeRoutePath(extractDecoratorPath(httpDecorator, sourceFile) ?? "");
475
+ const fullPath = joinRoutePath(controllerBasePath, methodPath);
476
+ endpoints.push({
477
+ method,
478
+ path: fullPath,
479
+ pathParams: extractPathParams(fullPath),
480
+ queryParams: [],
481
+ bodyFields: [],
482
+ responseFields: [],
483
+ relatedTables: [],
484
+ description: extractMethodDescription(methodDecl)
485
+ });
486
+ }
487
+ }
488
+ return endpoints;
489
+ }
445
490
  function resolveRoutePath(node, sourceFile) {
446
491
  const kind = node.getKind();
447
492
  if (kind === SyntaxKind2.StringLiteral) return node.getText().slice(1, -1);
@@ -453,6 +498,60 @@ function resolveRoutePath(node, sourceFile) {
453
498
  }
454
499
  return null;
455
500
  }
501
+ function extractDecoratorPath(decorator, sourceFile) {
502
+ const args = decorator.getArguments();
503
+ if (args.length === 0) return "";
504
+ const firstArg = args[0];
505
+ if (firstArg.getKind() === SyntaxKind2.ObjectLiteralExpression) {
506
+ return extractPathFromObjectLiteral(firstArg, sourceFile);
507
+ }
508
+ return resolveRoutePath(firstArg, sourceFile);
509
+ }
510
+ function extractPathFromObjectLiteral(node, sourceFile) {
511
+ const pathProp = node.getProperty("path");
512
+ if (!pathProp || !Node.isPropertyAssignment(pathProp)) return null;
513
+ const initializer = pathProp.getInitializer();
514
+ if (!initializer) return null;
515
+ return resolveRoutePath(initializer, sourceFile);
516
+ }
517
+ function extractRequestMapping(methodDecl, sourceFile) {
518
+ const decorator = methodDecl.getDecorators().find((d) => d.getName().toLowerCase() === "requestmapping");
519
+ if (!decorator) return null;
520
+ const args = decorator.getArguments();
521
+ if (args.length === 0) return null;
522
+ const firstArg = args[0];
523
+ if (firstArg.getKind() !== SyntaxKind2.ObjectLiteralExpression) return null;
524
+ const obj = firstArg;
525
+ const methodProp = obj.getProperty("method");
526
+ let method = "GET";
527
+ if (methodProp && Node.isPropertyAssignment(methodProp)) {
528
+ const init = methodProp.getInitializer();
529
+ const methodText = init?.getText() || "";
530
+ const normalized = methodText.replace(/['"`]/g, "").split(".").pop()?.toUpperCase();
531
+ if (normalized && ["GET", "POST", "PUT", "DELETE", "PATCH"].includes(normalized)) {
532
+ method = normalized;
533
+ }
534
+ }
535
+ const pathValue = extractPathFromObjectLiteral(obj, sourceFile) ?? "";
536
+ return { method, path: pathValue };
537
+ }
538
+ function normalizeRoutePath(routePath) {
539
+ const cleaned = routePath.trim();
540
+ if (!cleaned || cleaned === "/") return "";
541
+ return `/${cleaned.replace(/^\/+|\/+$/g, "")}`;
542
+ }
543
+ function joinRoutePath(basePath, childPath) {
544
+ const joined = `${basePath}${childPath}`.replace(/\/+/g, "/");
545
+ return joined || "/";
546
+ }
547
+ function extractMethodDescription(methodDecl) {
548
+ const docs = methodDecl.getJsDocs();
549
+ if (docs.length > 0) {
550
+ const desc = docs[0].getDescription().trim();
551
+ if (desc) return desc;
552
+ }
553
+ return "";
554
+ }
456
555
  function resolveTemplateLiteral(node, sourceFile) {
457
556
  let result = node.getText().slice(1, -1);
458
557
  result = result.replace(/\$\{([^}]+)\}/g, (_match, expr) => {
@@ -516,7 +615,7 @@ function deduplicateEndpoints(endpoints) {
516
615
  }
517
616
  return Array.from(seen.values());
518
617
  }
519
- var HTTP_METHODS, METHOD_MAP;
618
+ var HTTP_METHODS, METHOD_MAP, NEST_HTTP_DECORATORS;
520
619
  var init_controller_parser = __esm({
521
620
  "src/parsers/controller-parser.ts"() {
522
621
  "use strict";
@@ -529,6 +628,7 @@ var init_controller_parser = __esm({
529
628
  delete: "DELETE",
530
629
  patch: "PATCH"
531
630
  };
631
+ NEST_HTTP_DECORATORS = /* @__PURE__ */ new Set(["get", "post", "put", "delete", "patch"]);
532
632
  }
533
633
  });
534
634
 
@@ -2031,7 +2131,7 @@ var init_report = __esm({
2031
2131
  init_esm_shims();
2032
2132
  import { Command } from "commander";
2033
2133
  var program = new Command();
2034
- program.name("opencroc").description("AI-native E2E testing framework").version("0.3.1");
2134
+ program.name("opencroc").description("AI-native E2E testing framework").version("0.5.0");
2035
2135
  program.command("init").description("Initialize OpenCroc in the current project").option("-y, --yes", "Skip prompts and use defaults").action(async (opts) => {
2036
2136
  const { initProject: initProject2 } = await Promise.resolve().then(() => (init_init(), init_exports));
2037
2137
  await initProject2(opts);