diff --git a/gitnexus/src/core/ingestion/route-extractors/nest.ts b/gitnexus/src/core/ingestion/route-extractors/nest.ts index 1220b5a66..1f052bb25 100644 --- a/gitnexus/src/core/ingestion/route-extractors/nest.ts +++ b/gitnexus/src/core/ingestion/route-extractors/nest.ts @@ -44,6 +44,8 @@ import type Parser from 'tree-sitter'; import type { ExtractedDecoratorRoute } from '../workers/parse-worker.js'; import { plainString, propertyName } from './data-route-table.js'; +import { isDev } from '../utils/env.js'; +import { logger } from '../../logger.js'; /** * NestJS method decorators β†’ HTTP verb. A Map rather than an object literal @@ -129,7 +131,10 @@ function decoratorName(decorator: Parser.SyntaxNode): string | null { * `:id(d+)`, and `@Get('/v\u0069ews')` came out as `/vews`. Both are paths the * app never serves, i.e. the wrong-URL outcome the paragraph above forbids. */ -function decoratorLiteralPaths(decorator: Parser.SyntaxNode): readonly string[] | null { +function decoratorLiteralPaths( + decorator: Parser.SyntaxNode, + filePath: string, +): readonly string[] | null { const call = decorator.namedChild(0); // A bare `@Injectable` with no call, or `@Get()` with no argument β€” legal, // and both mean "no path segment of my own". @@ -141,7 +146,42 @@ function decoratorLiteralPaths(decorator: Parser.SyntaxNode): readonly string[] // Reading it as a route would mint a URL the app never serves, which is the // invented fact this module refuses; an unreadable shape drops instead. if (first.type === 'object' && decoratorName(decorator) !== 'Controller') return null; - return literalPaths(first); + return literalPaths(first, filePath); +} + +/** + * How much of a refused options object to quote in the dev line. Enough to + * recognise the shape, bounded because an options object is arbitrary source. + */ +const REFUSED_OBJECT_LOG_LIMIT = 160; + +/** + * Decline an options object whose `path` is not provable, and say so. + * + * Gated on `isDev` exactly as the routes phase gates its own registry line + * (`pipeline-phases/routes.ts`), and emitted at `info` rather than `debug` + * because the logger's base level IS `info`: an `isDev`-gated `logger.debug` + * would be gated twice and stay silent in the very dev run it exists for. + * + * Names the FILE, not just the shape β€” a controller dropped without a path to + * look at is only marginally louder than one dropped in silence, and this + * refusal costs every route on the class. The line number is deliberately + * absent: `lineOffset` (a Vue SFC `