@geoql/doctor-core 1.0.1 → 1.1.1
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/dist/index.d.ts +96 -1
- package/dist/index.js +163 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -485,5 +485,100 @@ interface ListRulesFilter {
|
|
|
485
485
|
}
|
|
486
486
|
declare function listRules(filter?: ListRulesFilter): RegisteredRule[];
|
|
487
487
|
//#endregion
|
|
488
|
-
|
|
488
|
+
//#region src/audit-filter.d.ts
|
|
489
|
+
/**
|
|
490
|
+
* Filter `report.diagnostics` to a known set of allowed rule ids, then
|
|
491
|
+
* recompute `score`, `errorCount`, `warnCount`, `infoCount`, `scoreResult`,
|
|
492
|
+
* `ruleCounts`, and `exitCode` from the filtered array.
|
|
493
|
+
*
|
|
494
|
+
* Why: the CLI layer calls `audit()` and then applies its own
|
|
495
|
+
* `allowedRuleIds` filter to drop suppressed (`off`) and preset-excluded
|
|
496
|
+
* (`info` rules not in `recommended`) diagnostics. The audit's own
|
|
497
|
+
* `score`/`counts` are computed BEFORE the CLI filter, so they still
|
|
498
|
+
* include the suppressed findings. That desync surfaces to consumers
|
|
499
|
+
* (the SaaS `--push` payload, the dashboard's counts-but-no-findings bug)
|
|
500
|
+
* and to the `score` itself (suppressed findings still penalize).
|
|
501
|
+
*
|
|
502
|
+
* Invariant enforced here:
|
|
503
|
+
* report.errorCount + report.warnCount + report.infoCount === report.diagnostics.length
|
|
504
|
+
* report.scoreResult.totalFindings === report.diagnostics.length
|
|
505
|
+
*
|
|
506
|
+
* `exitCode=2` (oxlint crash) is preserved verbatim; `exitCode=1` is
|
|
507
|
+
* recomputed from the surviving counts + the `failOn` gate.
|
|
508
|
+
*/
|
|
509
|
+
declare function filterReportByRules(report: AuditReport, allowedRuleIds: ReadonlySet<string>, failOn?: 'error' | 'warn' | 'none', oxlintStderr?: string): AuditReport;
|
|
510
|
+
//#endregion
|
|
511
|
+
//#region src/push.d.ts
|
|
512
|
+
/**
|
|
513
|
+
* The 6 fields the SaaS receives for each finding.
|
|
514
|
+
* Everything else (message, codeSnippet, recommendation, etc.) is privacy-sensitive
|
|
515
|
+
* and MUST be stripped before transmission.
|
|
516
|
+
*/
|
|
517
|
+
interface PushedFinding {
|
|
518
|
+
file: string;
|
|
519
|
+
line: number;
|
|
520
|
+
col: number;
|
|
521
|
+
ruleId: string;
|
|
522
|
+
severity: Severity;
|
|
523
|
+
category: string;
|
|
524
|
+
}
|
|
525
|
+
interface PushPayloadInput {
|
|
526
|
+
project: string;
|
|
527
|
+
score: number;
|
|
528
|
+
errorCount: number;
|
|
529
|
+
warnCount: number;
|
|
530
|
+
infoCount: number;
|
|
531
|
+
findings: PushedFinding[];
|
|
532
|
+
}
|
|
533
|
+
interface PushPayload extends PushPayloadInput {
|
|
534
|
+
commitSha?: string;
|
|
535
|
+
branch?: string;
|
|
536
|
+
prNumber?: number;
|
|
537
|
+
}
|
|
538
|
+
interface PushOptions {
|
|
539
|
+
project: string;
|
|
540
|
+
score: number;
|
|
541
|
+
errorCount: number;
|
|
542
|
+
warnCount: number;
|
|
543
|
+
infoCount: number;
|
|
544
|
+
diagnostics: Diagnostic[];
|
|
545
|
+
/** Project root used to relativize absolute finding file paths. */
|
|
546
|
+
rootDir?: string;
|
|
547
|
+
url: string;
|
|
548
|
+
apiKey: string;
|
|
549
|
+
fetchImpl?: typeof fetch;
|
|
550
|
+
/** Overrides for the request timeout (ms). Defaults to 5000. Test-only. */
|
|
551
|
+
timeoutMs?: number;
|
|
552
|
+
}
|
|
553
|
+
type PushResult = {
|
|
554
|
+
ok: true;
|
|
555
|
+
status?: number;
|
|
556
|
+
error?: undefined;
|
|
557
|
+
} | {
|
|
558
|
+
ok: false;
|
|
559
|
+
status?: number;
|
|
560
|
+
error: string;
|
|
561
|
+
};
|
|
562
|
+
/**
|
|
563
|
+
* Strip a list of Diagnostics down to the 6 allowed push fields.
|
|
564
|
+
* Privacy boundary: this function is the single point of enforcement.
|
|
565
|
+
* If you add a new privacy-sensitive field to Diagnostic, add it to the deny list here.
|
|
566
|
+
*
|
|
567
|
+
* Absolute file paths are relativized against rootDir so dashboards show
|
|
568
|
+
* `src/Foo.vue` rather than the CI runner's `/home/runner/work/<repo>/…`.
|
|
569
|
+
*/
|
|
570
|
+
declare function stripFindings(diagnostics: Diagnostic[], rootDir?: string): PushedFinding[];
|
|
571
|
+
/**
|
|
572
|
+
* Build the top-level POST body. CI fields (commitSha, branch, prNumber) are
|
|
573
|
+
* included ONLY when the matching GITHUB_* env var is set.
|
|
574
|
+
*/
|
|
575
|
+
declare function buildPushPayload(input: PushPayloadInput): PushPayload;
|
|
576
|
+
/**
|
|
577
|
+
* Strip + POST findings to a SaaS endpoint. Never throws — returns
|
|
578
|
+
* `{ ok: false, error }` on any HTTP / network failure so the audit's
|
|
579
|
+
* exit code is not affected by SaaS downtime.
|
|
580
|
+
*/
|
|
581
|
+
declare function pushFindings(opts: PushOptions): Promise<PushResult>;
|
|
582
|
+
//#endregion
|
|
583
|
+
export { type ApplyInlineDisablesOptions, type AuditConfig, type AuditReport, type AuditTimings, BUILT_IN_RECOMMENDED, type Capability, type CliOverrides, ConfigCycleError, ConfigFileNotFoundError, type ConfigSource, DOCTOR_REPORT_SCHEMA_VERSION, DeadCodeImportFailed, DeadCodeTimeoutError, type Diagnostic, type DiagnosticSource, type DirectiveLine, type DirectiveRange, type DirectiveSet, type DoctorReport, type DoctorUserConfig, type Framework, type GitScopeMode, type InitConfigFormat, type InitFileWrite, type InitOptions, type InitPlan, type InitPreset, InvalidConfigError, type ListRulesFilter, type LoadRuleDocOptions, type MonorepoKind, type MonorepoResult, OxlintOutputTooLarge, OxlintSpawnFailed, type ProjectInfo, type ProjectInfoLite, type PushOptions, type PushPayload, type PushPayloadInput, type PushResult, type PushedFinding, RULE_REGISTRY, type RawInitAnswers, type RegisteredRule, type ReporterFormat, type ReporterInput, type ReporterOptions, type ResolvedDoctorConfig, type ResolvedInitAnswers, type RuleCategory, type RuleDoc, type RuleSource, type ScoreBreakdownEntry, type ScoreConfig, type ScoreResult, type Severity, type VerboseTraceOptions, type WorkspacePackage, agentReport, applyInlineDisables, audit, buildDoctorReport, buildPushPayload, checkBuildQuality, checkDeadCode, checkDeps, checkNuxtProject, dedupeDeadCodeAgainstLint, defineConfig, detectProject, detectSummary, docsUrl, encodeAnnotation, encodeAnnotations, filterReportByRules, findMonorepoRoot, format, isNuxtLayoutFile, isNuxtPageFile, isNuxtServerFile, jsonCompactReport, jsonReport, listChangedFiles, listRules, listWorkspacePackages, loadAllRuleDocs, loadDoctorConfig, loadRuleDoc, mergeCliOverrides, normalizeInitAnswers, parseDirectives, parseExcludeList, planInit, prCommentReport, prettyReport, pushFindings, renderDetectSummary, renderVerboseTrace, runCrossFilePass, sarifReport, scoreDiagnostics, stripFindings, validateConfig };
|
|
489
584
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as listRules, c as InvalidConfigError, i as RULE_REGISTRY, l as BUILT_IN_RECOMMENDED, o as ConfigCycleError, r as validateConfig, s as ConfigFileNotFoundError, t as loadDoctorConfig } from "./load-yXDi-5ti.js";
|
|
2
|
-
import { dirname, join, relative, resolve } from "node:path";
|
|
2
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
3
|
import { performance } from "node:perf_hooks";
|
|
4
4
|
import { access, mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
@@ -3011,7 +3011,7 @@ async function listWorkspacePackages(rootDir) {
|
|
|
3011
3011
|
//#endregion
|
|
3012
3012
|
//#region src/reporters/docs-url.ts
|
|
3013
3013
|
function docsUrl(ruleId) {
|
|
3014
|
-
return `https://docs.doctor.
|
|
3014
|
+
return `https://docs.the-doctor.report/rules/${ruleId}`;
|
|
3015
3015
|
}
|
|
3016
3016
|
//#endregion
|
|
3017
3017
|
//#region src/reporters/render.ts
|
|
@@ -3374,7 +3374,7 @@ function jsonCompactReport(input) {
|
|
|
3374
3374
|
}
|
|
3375
3375
|
//#endregion
|
|
3376
3376
|
//#region src/reporters/pr-comment.ts
|
|
3377
|
-
const DOCS_BASE = "https://docs.doctor.
|
|
3377
|
+
const DOCS_BASE = "https://docs.the-doctor.report/rules/";
|
|
3378
3378
|
const MAX_PER_SECTION = 5;
|
|
3379
3379
|
function binName(toolName) {
|
|
3380
3380
|
return toolName.replace("@geoql/", "");
|
|
@@ -3474,6 +3474,165 @@ function format(input, kind = "agent", options) {
|
|
|
3474
3474
|
return agentReport(input, options);
|
|
3475
3475
|
}
|
|
3476
3476
|
//#endregion
|
|
3477
|
-
|
|
3477
|
+
//#region src/audit-filter.ts
|
|
3478
|
+
/**
|
|
3479
|
+
* Filter `report.diagnostics` to a known set of allowed rule ids, then
|
|
3480
|
+
* recompute `score`, `errorCount`, `warnCount`, `infoCount`, `scoreResult`,
|
|
3481
|
+
* `ruleCounts`, and `exitCode` from the filtered array.
|
|
3482
|
+
*
|
|
3483
|
+
* Why: the CLI layer calls `audit()` and then applies its own
|
|
3484
|
+
* `allowedRuleIds` filter to drop suppressed (`off`) and preset-excluded
|
|
3485
|
+
* (`info` rules not in `recommended`) diagnostics. The audit's own
|
|
3486
|
+
* `score`/`counts` are computed BEFORE the CLI filter, so they still
|
|
3487
|
+
* include the suppressed findings. That desync surfaces to consumers
|
|
3488
|
+
* (the SaaS `--push` payload, the dashboard's counts-but-no-findings bug)
|
|
3489
|
+
* and to the `score` itself (suppressed findings still penalize).
|
|
3490
|
+
*
|
|
3491
|
+
* Invariant enforced here:
|
|
3492
|
+
* report.errorCount + report.warnCount + report.infoCount === report.diagnostics.length
|
|
3493
|
+
* report.scoreResult.totalFindings === report.diagnostics.length
|
|
3494
|
+
*
|
|
3495
|
+
* `exitCode=2` (oxlint crash) is preserved verbatim; `exitCode=1` is
|
|
3496
|
+
* recomputed from the surviving counts + the `failOn` gate.
|
|
3497
|
+
*/
|
|
3498
|
+
function filterReportByRules(report, allowedRuleIds, failOn = "error", oxlintStderr) {
|
|
3499
|
+
const filtered = [];
|
|
3500
|
+
for (const d of report.diagnostics) if (allowedRuleIds.has(d.ruleId)) filtered.push(d);
|
|
3501
|
+
const threshold = report.scoreResult.threshold;
|
|
3502
|
+
const scoreResult = scoreDiagnostics(filtered, { threshold });
|
|
3503
|
+
const ruleCounts = {};
|
|
3504
|
+
for (const d of filtered) ruleCounts[d.ruleId] = (ruleCounts[d.ruleId] ?? 0) + 1;
|
|
3505
|
+
let exitCode = report.exitCode;
|
|
3506
|
+
if (exitCode !== 2) if (failOn === "none") exitCode = 0;
|
|
3507
|
+
else exitCode = (failOn === "warn" ? scoreResult.errorCount + scoreResult.warnCount : scoreResult.errorCount) > 0 ? 1 : 0;
|
|
3508
|
+
return {
|
|
3509
|
+
...report,
|
|
3510
|
+
diagnostics: filtered,
|
|
3511
|
+
score: scoreResult.score,
|
|
3512
|
+
errorCount: scoreResult.errorCount,
|
|
3513
|
+
warnCount: scoreResult.warnCount,
|
|
3514
|
+
infoCount: scoreResult.infoCount,
|
|
3515
|
+
scoreResult,
|
|
3516
|
+
ruleCounts,
|
|
3517
|
+
exitCode
|
|
3518
|
+
};
|
|
3519
|
+
}
|
|
3520
|
+
//#endregion
|
|
3521
|
+
//#region src/push.ts
|
|
3522
|
+
const PUSH_TIMEOUT_MS = 5e3;
|
|
3523
|
+
const categoryByRuleId = new Map(RULE_REGISTRY.map((r) => [r.id, r.category]));
|
|
3524
|
+
function lookupCategory(ruleId) {
|
|
3525
|
+
return categoryByRuleId.get(ruleId) ?? "unknown";
|
|
3526
|
+
}
|
|
3527
|
+
function toRepoRelative(file, rootDir) {
|
|
3528
|
+
if (!rootDir || !isAbsolute(file)) return file;
|
|
3529
|
+
const rel = relative(rootDir, file);
|
|
3530
|
+
if (rel.startsWith("..")) return file;
|
|
3531
|
+
return rel;
|
|
3532
|
+
}
|
|
3533
|
+
/**
|
|
3534
|
+
* Strip a list of Diagnostics down to the 6 allowed push fields.
|
|
3535
|
+
* Privacy boundary: this function is the single point of enforcement.
|
|
3536
|
+
* If you add a new privacy-sensitive field to Diagnostic, add it to the deny list here.
|
|
3537
|
+
*
|
|
3538
|
+
* Absolute file paths are relativized against rootDir so dashboards show
|
|
3539
|
+
* `src/Foo.vue` rather than the CI runner's `/home/runner/work/<repo>/…`.
|
|
3540
|
+
*/
|
|
3541
|
+
function stripFindings(diagnostics, rootDir) {
|
|
3542
|
+
const out = new Array(diagnostics.length);
|
|
3543
|
+
for (let i = 0; i < diagnostics.length; i++) {
|
|
3544
|
+
const d = diagnostics[i];
|
|
3545
|
+
out[i] = {
|
|
3546
|
+
file: toRepoRelative(d.file, rootDir),
|
|
3547
|
+
line: d.line,
|
|
3548
|
+
col: d.column,
|
|
3549
|
+
ruleId: d.ruleId,
|
|
3550
|
+
severity: d.severity,
|
|
3551
|
+
category: lookupCategory(d.ruleId)
|
|
3552
|
+
};
|
|
3553
|
+
}
|
|
3554
|
+
return out;
|
|
3555
|
+
}
|
|
3556
|
+
function readOptionalEnv(name) {
|
|
3557
|
+
const v = process.env[name];
|
|
3558
|
+
return v === void 0 || v === "" ? void 0 : v;
|
|
3559
|
+
}
|
|
3560
|
+
function readPrNumber() {
|
|
3561
|
+
const raw = readOptionalEnv("GITHUB_PR_NUMBER");
|
|
3562
|
+
if (raw === void 0) return void 0;
|
|
3563
|
+
const n = Number(raw);
|
|
3564
|
+
return Number.isFinite(n) ? n : void 0;
|
|
3565
|
+
}
|
|
3566
|
+
/**
|
|
3567
|
+
* Build the top-level POST body. CI fields (commitSha, branch, prNumber) are
|
|
3568
|
+
* included ONLY when the matching GITHUB_* env var is set.
|
|
3569
|
+
*/
|
|
3570
|
+
function buildPushPayload(input) {
|
|
3571
|
+
const payload = {
|
|
3572
|
+
project: input.project,
|
|
3573
|
+
score: input.score,
|
|
3574
|
+
errorCount: input.errorCount,
|
|
3575
|
+
warnCount: input.warnCount,
|
|
3576
|
+
infoCount: input.infoCount,
|
|
3577
|
+
findings: input.findings
|
|
3578
|
+
};
|
|
3579
|
+
const commitSha = readOptionalEnv("GITHUB_SHA");
|
|
3580
|
+
const branch = readOptionalEnv("GITHUB_REF_NAME");
|
|
3581
|
+
const prNumber = readPrNumber();
|
|
3582
|
+
if (commitSha !== void 0) payload.commitSha = commitSha;
|
|
3583
|
+
if (branch !== void 0) payload.branch = branch;
|
|
3584
|
+
if (prNumber !== void 0) payload.prNumber = prNumber;
|
|
3585
|
+
return payload;
|
|
3586
|
+
}
|
|
3587
|
+
/**
|
|
3588
|
+
* Strip + POST findings to a SaaS endpoint. Never throws — returns
|
|
3589
|
+
* `{ ok: false, error }` on any HTTP / network failure so the audit's
|
|
3590
|
+
* exit code is not affected by SaaS downtime.
|
|
3591
|
+
*/
|
|
3592
|
+
async function pushFindings(opts) {
|
|
3593
|
+
const findings = stripFindings(opts.diagnostics, opts.rootDir);
|
|
3594
|
+
const payload = buildPushPayload({
|
|
3595
|
+
project: opts.project,
|
|
3596
|
+
score: opts.score,
|
|
3597
|
+
errorCount: opts.errorCount,
|
|
3598
|
+
warnCount: opts.warnCount,
|
|
3599
|
+
infoCount: opts.infoCount,
|
|
3600
|
+
findings
|
|
3601
|
+
});
|
|
3602
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
3603
|
+
const controller = new AbortController();
|
|
3604
|
+
const timeoutMs = opts.timeoutMs ?? PUSH_TIMEOUT_MS;
|
|
3605
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
3606
|
+
timer.unref?.();
|
|
3607
|
+
try {
|
|
3608
|
+
const res = await fetchImpl(opts.url, {
|
|
3609
|
+
method: "POST",
|
|
3610
|
+
headers: {
|
|
3611
|
+
"Content-Type": "application/json",
|
|
3612
|
+
"x-api-key": opts.apiKey
|
|
3613
|
+
},
|
|
3614
|
+
body: JSON.stringify(payload),
|
|
3615
|
+
signal: controller.signal
|
|
3616
|
+
});
|
|
3617
|
+
clearTimeout(timer);
|
|
3618
|
+
if (res.ok) return {
|
|
3619
|
+
ok: true,
|
|
3620
|
+
status: res.status
|
|
3621
|
+
};
|
|
3622
|
+
return {
|
|
3623
|
+
ok: false,
|
|
3624
|
+
status: res.status,
|
|
3625
|
+
error: `HTTP ${res.status} ${res.statusText}`.trim()
|
|
3626
|
+
};
|
|
3627
|
+
} catch (err) {
|
|
3628
|
+
clearTimeout(timer);
|
|
3629
|
+
return {
|
|
3630
|
+
ok: false,
|
|
3631
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3632
|
+
};
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
//#endregion
|
|
3636
|
+
export { BUILT_IN_RECOMMENDED, ConfigCycleError, ConfigFileNotFoundError, DOCTOR_REPORT_SCHEMA_VERSION, DeadCodeImportFailed, DeadCodeTimeoutError, InvalidConfigError, OxlintOutputTooLarge, OxlintSpawnFailed, RULE_REGISTRY, agentReport, applyInlineDisables, audit, buildDoctorReport, buildPushPayload, checkBuildQuality, checkDeadCode, checkDeps, checkNuxtProject, dedupeDeadCodeAgainstLint, defineConfig, detectProject, detectSummary, docsUrl, encodeAnnotation, encodeAnnotations, filterReportByRules, findMonorepoRoot, format, isNuxtLayoutFile, isNuxtPageFile, isNuxtServerFile, jsonCompactReport, jsonReport, listChangedFiles, listRules, listWorkspacePackages, loadAllRuleDocs, loadDoctorConfig, loadRuleDoc, mergeCliOverrides, normalizeInitAnswers, parseDirectives, parseExcludeList, planInit, prCommentReport, prettyReport, pushFindings, renderDetectSummary, renderVerboseTrace, runCrossFilePass, sarifReport, scoreDiagnostics, stripFindings, validateConfig };
|
|
3478
3637
|
|
|
3479
3638
|
//# sourceMappingURL=index.js.map
|