Skip to content

Detect API spec files (OpenAPI, GraphQL, protobuf) #7

@mvoutov

Description

@mvoutov

What

Detect API specification files in the repo. When a project has OpenAPI specs, GraphQL schemas, or protobuf definitions, report them in the scan results.

Why

API specs define the contract surface of the application. Knowing "this project has an OpenAPI spec with 15 endpoints" or "GraphQL schema with 8 types" helps Claude write accurate skills about the API layer.

How

Edit src/lib/scanner.js. Add a detectAPISpecs(repoPath) function:

Type Detection Signal
OpenAPI/Swagger Files named openapi.yaml, openapi.json, swagger.yaml, swagger.json at root or in api/, docs/
GraphQL Files matching *.graphql, schema.graphql, *.gql, or codegen.yml/codegen.ts in dependencies
Protocol Buffers Files matching *.proto anywhere in the repo (use a shallow walk, max depth 4)
tRPC trpc in package.json dependencies + files matching *router* or *trpc*
AsyncAPI asyncapi.yaml or asyncapi.json

Implementation steps

  1. Add detectAPISpecs(repoPath) to src/lib/scanner.js:

    function detectAPISpecs(repoPath) {
      const detected = [];
    
      // OpenAPI / Swagger
      const openapiNames = ['openapi.yaml', 'openapi.json', 'swagger.yaml', 'swagger.json'];
      const openapiDirs = ['', 'api', 'docs'];
      for (const dir of openapiDirs) {
        for (const name of openapiNames) {
          if (existsSync(join(repoPath, dir, name))) {
            if (!detected.includes('openapi')) detected.push('openapi');
          }
        }
      }
    
      // GraphQL — check for .graphql files
      // Use a simple recursive check (depth 3)
      if (hasFileWithExtension(repoPath, '.graphql', 3) ||
          hasFileWithExtension(repoPath, '.gql', 3)) {
        detected.push('graphql');
      }
    
      // Protobuf
      if (hasFileWithExtension(repoPath, '.proto', 4)) {
        detected.push('protobuf');
      }
    
      // AsyncAPI
      if (existsSync(join(repoPath, 'asyncapi.yaml')) ||
          existsSync(join(repoPath, 'asyncapi.json'))) {
        detected.push('asyncapi');
      }
    
      return detected;
    }
    
    // Helper: check if any file with given extension exists (shallow walk)
    function hasFileWithExtension(rootPath, ext, maxDepth, depth = 0) {
      if (depth >= maxDepth) return false;
      try {
        const entries = readdirSync(rootPath);
        for (const entry of entries) {
          if (entry.startsWith('.') || entry === 'node_modules') continue;
          if (entry.endsWith(ext)) return true;
          const full = join(rootPath, entry);
          if (isDir(full) && hasFileWithExtension(full, ext, maxDepth, depth + 1)) return true;
        }
      } catch {}
      return false;
    }
  2. Call from scanRepo(): result.apiSpecs = detectAPISpecs(repoPath);

  3. Display in src/commands/scan.js:

    if (result.apiSpecs && result.apiSpecs.length > 0) {
      console.log(pc.cyan('  API specs: ') + result.apiSpecs.join(', '));
    }
  4. Add tests in tests/scanner.test.js:

    describe('API spec detection', () => {
      it('detects OpenAPI spec', () => {
        const dir = createFixture('api-openapi', {
          'openapi.yaml': 'openapi: "3.0.0"',
        });
        const scan = scanRepo(dir);
        expect(scan.apiSpecs).toContain('openapi');
      });
    
      it('detects GraphQL schema', () => {
        const dir = createFixture('api-graphql', {
          'src/schema.graphql': 'type Query { hello: String }',
        });
        const scan = scanRepo(dir);
        expect(scan.apiSpecs).toContain('graphql');
      });
    
      it('detects protobuf', () => {
        const dir = createFixture('api-proto', {
          'proto/service.proto': 'syntax = "proto3";',
        });
        const scan = scanRepo(dir);
        expect(scan.apiSpecs).toContain('protobuf');
      });
    });

Files to change

  • src/lib/scanner.js — add detectAPISpecs() + helper, call from scanRepo()
  • src/commands/scan.js — display in output
  • tests/scanner.test.js — add tests

Acceptance criteria

  • npm test passes
  • OpenAPI, GraphQL, and protobuf detected correctly
  • aspens scan displays detected API specs

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions