mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 13:36:48 +00:00
8fb888c8a3
The ratchet only read *.service.ts for status literals and one branch shape in controllers and tools, so an envelope in a helper, impl or provider file, a branch written as result.error === 'not_found', and RPC handlers all went uncounted. It now counts the literals in every file under src/nest outside the HTTP layer, and comparisons, negations, optional chains and ternaries on a returned error in controllers, tools and RPC handlers. The baseline takes what the wider count finds.
78 lines
3.4 KiB
JavaScript
78 lines
3.4 KiB
JavaScript
#!/usr/bin/env node
|
|
/*
|
|
* lint:service-http: the result-envelope idioms may only shrink.
|
|
*
|
|
* A service refuses by throwing a DomainError (src/nest/common/domain-error.ts)
|
|
* with the status and the exact text the client sees. The global filter turns it
|
|
* into REST's `{ error }` and the MCP registry into the tool's errorResult, so no
|
|
* caller translates it. What this counts is the older way, where every caller did:
|
|
*
|
|
* - in any file under src/nest outside the HTTP and adapter layers (services,
|
|
* helpers, impl files, providers): a `status: 4xx` or `status: 5xx` literal
|
|
* (the `return { error, status }` result) and `new HttpException(` (HTTP in
|
|
* the domain layer). Controllers, guards, pipes, filters, interceptors and
|
|
* DTOs speak HTTP by design, and src/nest/common/domain-error.ts defines the
|
|
* status a refusal carries, so those are left out by name;
|
|
* - in a *.controller.ts, *.mcp.ts or *.rpc.ts: a branch on a returned error,
|
|
* `if (result.error)`, `if (!r.error)`, `'error' in result`,
|
|
* `result.error === 'not_found'`, `r?.error && ...`, `x.error ? a : b`.
|
|
*
|
|
* Each file is held to its entry in scripts/service-http-baseline.json, and a
|
|
* file without an entry holds none (scripts/lib/count-ratchet.mjs has the rules).
|
|
*
|
|
* npm run lint:service-http check against the baseline (CI)
|
|
* npm run lint:service-http -- --update lower the counts; never raises or adds an entry
|
|
*/
|
|
import { cliMain } from './lib/count-ratchet.mjs';
|
|
|
|
const STATUS_LITERAL = /\bstatus:\s*[45]\d\d\b/g;
|
|
const HTTP_EXCEPTION = /\bnew HttpException\(/g;
|
|
// A property path such as result, res.body or this.state?.last.
|
|
const PATH = String.raw`[\w$]+(?:\??\.[\w$]+)*`;
|
|
const RESULT_ERROR_BRANCH = new RegExp(
|
|
[
|
|
String.raw`['"]error['"]\s+in\s+[\w$.]+`,
|
|
String.raw`\bif\s*\(\s*!?\s*${PATH}\??\.error\s*\)`,
|
|
String.raw`${PATH}\??\.error\b\s*(?:===|!==|==|!=|&&|\|\||\?(?![.?]))`,
|
|
].join('|'),
|
|
'g',
|
|
);
|
|
|
|
/** Files that speak HTTP by design: the status literals in them are not an envelope. */
|
|
const HTTP_LAYER = /\.(?:controller|guard|pipe|filter|interceptor|dto)\.ts$/;
|
|
const DEFINES_THE_STATUS = new Set(['src/nest/common/domain-error.ts']);
|
|
/** The adapters over a service, where a branch on its returned error is the caller translating it. */
|
|
const ADAPTER = /\.(?:controller|mcp|rpc)\.ts$/;
|
|
|
|
const hits = (text, pattern) => text.match(pattern)?.length ?? 0;
|
|
|
|
/** The envelope idioms in one domain-layer file. */
|
|
export function countServiceIdioms(text) {
|
|
return hits(text, STATUS_LITERAL) + hits(text, HTTP_EXCEPTION);
|
|
}
|
|
|
|
/** The branches on a returned error in one controller, MCP or RPC file. */
|
|
export function countResultBranches(text) {
|
|
return hits(text, RESULT_ERROR_BRANCH);
|
|
}
|
|
|
|
/** What one file under src/nest holds, by the layer its name puts it in. */
|
|
export function countFile(text, file) {
|
|
let n = ADAPTER.test(file) ? countResultBranches(text) : 0;
|
|
if (!HTTP_LAYER.test(file) && !DEFINES_THE_STATUS.has(file)) n += countServiceIdioms(text);
|
|
return n;
|
|
}
|
|
|
|
export const check = {
|
|
name: 'service-http',
|
|
script: 'lint:service-http',
|
|
root: 'src/nest',
|
|
baseline: 'scripts/service-http-baseline.json',
|
|
unit: 'error-envelope idiom(s)',
|
|
advice: 'Throw a DomainError from the service (src/nest/common/domain-error.ts) and let REST and MCP answer it.',
|
|
include: (file) => file.endsWith('.ts') && !file.endsWith('.d.ts'),
|
|
count: countFile,
|
|
};
|
|
|
|
cliMain(check, import.meta.url);
|