@comity-dev/validate 0.1.2 → 0.2.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/run.d.ts ADDED
@@ -0,0 +1,66 @@
1
+ import type { EngineResult, Finding } from "./engines/types.js";
2
+ import type { ExitCodeValue } from "./exit-codes.js";
3
+ export interface RunOptions {
4
+ /** The root directory of the repository to validate */
5
+ root: string;
6
+ /** If specified, only run the given category of validation */
7
+ only?: string | null;
8
+ /** If true, print verbose output to stdout */
9
+ verbose?: boolean;
10
+ }
11
+ export interface CategoryResult {
12
+ /** The status of the engine for this category */
13
+ status: EngineResult["status"];
14
+ /** Whether the engine was executed or skipped */
15
+ executed: boolean;
16
+ /** The exit code of the engine process, if applicable */
17
+ exitCode: number | null;
18
+ /** Whether the engine passed or failed */
19
+ passed: boolean;
20
+ /** The number of findings produced by the engine */
21
+ findings: number;
22
+ /** The tool that produced this result, if applicable */
23
+ tool?: string | undefined;
24
+ /** The reason for the engine's status, if applicable */
25
+ reason?: string | undefined;
26
+ }
27
+ export interface RunResult {
28
+ /** Whether the overall validation passed or failed */
29
+ passed: boolean;
30
+ /** The list of all findings across all categories */
31
+ violations: Finding[];
32
+ /** The results of each validation category */
33
+ categories: {
34
+ /** Schema validation results */
35
+ schema: CategoryResult;
36
+ /** Package metadata validation results */
37
+ metadata: CategoryResult;
38
+ /** Comity config validation results */
39
+ config: CategoryResult;
40
+ /** Dependency validation results */
41
+ dependencies: CategoryResult;
42
+ /** ESLint validation results */
43
+ eslint: CategoryResult;
44
+ /** Semgrep validation results */
45
+ semgrep: CategoryResult;
46
+ /** Adapter peers validation results */
47
+ adapterPeers: CategoryResult;
48
+ };
49
+ }
50
+ /**
51
+ * Run validation against a repository. Returns a normalized result.
52
+ *
53
+ * Every category reports either an executed engine with its real exit
54
+ * code, or an explicit NOT-RUN / TOOL-UNAVAILABLE / CONFIGURATION-ERROR
55
+ * state. PASS without execution is impossible.
56
+ */
57
+ export declare function runValidation(options: RunOptions): Promise<RunResult>;
58
+ /**
59
+ * Map a validation result to the documented process exit code.
60
+ *
61
+ * A required tool that could not run must never surface as PASS: when
62
+ * every executed engine passed but at least one category reports
63
+ * `TOOL-UNAVAILABLE`, the result exits `TOOL_MISSING` (3) instead of
64
+ * fabricating a passing result.
65
+ */
66
+ export declare function exitCodeForResult(result: RunResult): ExitCodeValue;
package/dist/run.js ADDED
@@ -0,0 +1,159 @@
1
+ import { dirname, resolve } from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { discoverRepository } from "./discover.js";
4
+ import { runAdapterPeers } from "./engines/adapter-peers.js";
5
+ import { runDepcruise } from "./engines/depcruise.js";
6
+ import { runEslint } from "./engines/eslint.js";
7
+ import { runSchema } from "./engines/schema.js";
8
+ import { runSemgrep } from "./engines/semgrep.js";
9
+ import { ExitCode } from "./exit-codes.js";
10
+ function categoryOf(result) {
11
+ return {
12
+ status: result.status,
13
+ executed: result.executed,
14
+ exitCode: result.exitCode,
15
+ passed: result.passed,
16
+ findings: result.findings.length,
17
+ tool: result.tool,
18
+ reason: result.reason,
19
+ };
20
+ }
21
+ function emptyCategory(reason) {
22
+ return {
23
+ status: "NOT-RUN",
24
+ executed: false,
25
+ exitCode: null,
26
+ passed: false,
27
+ findings: 0,
28
+ reason,
29
+ };
30
+ }
31
+ function findBinDir() {
32
+ const __dirname = dirname(fileURLToPath(import.meta.url));
33
+ // `__dirname` is the directory of the running module. Both the source
34
+ // `src/` and the built `dist/` are direct children of the package root,
35
+ // so the package's own `node_modules/.bin` is always exactly one level
36
+ // up from the running module.
37
+ return resolve(__dirname, "..", "node_modules", ".bin");
38
+ }
39
+ /**
40
+ * Run validation against a repository. Returns a normalized result.
41
+ *
42
+ * Every category reports either an executed engine with its real exit
43
+ * code, or an explicit NOT-RUN / TOOL-UNAVAILABLE / CONFIGURATION-ERROR
44
+ * state. PASS without execution is impossible.
45
+ */
46
+ export async function runValidation(options) {
47
+ const only = options.only ?? null;
48
+ const repo = await discoverRepository({ root: options.root });
49
+ const binDir = findBinDir();
50
+ let schemaResult;
51
+ let metaResult;
52
+ let configResult;
53
+ if (!only || only === "metadata" || only === "config" || only === "schema") {
54
+ schemaResult = runSchema(repo);
55
+ const metaFindings = schemaResult.findings.filter((f) => f.rule === "package-metadata");
56
+ const configFindings = schemaResult.findings.filter((f) => f.rule === "comity-config");
57
+ metaResult = {
58
+ ...schemaResult,
59
+ findings: metaFindings,
60
+ passed: metaFindings.length === 0,
61
+ status: metaFindings.length === 0 ? "PASS" : "FAIL",
62
+ };
63
+ configResult = {
64
+ ...schemaResult,
65
+ findings: configFindings,
66
+ passed: configFindings.length === 0,
67
+ status: configFindings.length === 0 ? "PASS" : "FAIL",
68
+ };
69
+ }
70
+ else {
71
+ schemaResult = emptyEngineResult("schema engine skipped");
72
+ metaResult = emptyEngineResult("schema engine skipped");
73
+ configResult = emptyEngineResult("schema engine skipped");
74
+ }
75
+ let depResult;
76
+ if (!only || only === "dependencies" || only === "depcruise") {
77
+ depResult = runDepcruise(repo, binDir);
78
+ }
79
+ else {
80
+ depResult = emptyEngineResult("dependencies engine skipped");
81
+ }
82
+ let eslintResult;
83
+ if (!only || only === "eslint") {
84
+ eslintResult = runEslint(repo, binDir);
85
+ }
86
+ else {
87
+ eslintResult = emptyEngineResult("eslint engine skipped");
88
+ }
89
+ let semgrepResult;
90
+ if (!only || only === "semgrep") {
91
+ semgrepResult = await runSemgrep(repo);
92
+ }
93
+ else {
94
+ semgrepResult = emptyEngineResult("semgrep engine skipped");
95
+ }
96
+ let adapterResult;
97
+ if (!only || only === "adapter-peers") {
98
+ adapterResult = runAdapterPeers(repo);
99
+ }
100
+ else {
101
+ adapterResult = emptyEngineResult("adapter-peers engine skipped");
102
+ }
103
+ const violations = [];
104
+ for (const r of [
105
+ schemaResult,
106
+ depResult,
107
+ eslintResult,
108
+ semgrepResult,
109
+ adapterResult,
110
+ ]) {
111
+ if (Array.isArray(r.findings)) {
112
+ violations.push(...r.findings);
113
+ }
114
+ }
115
+ const allExecuted = [depResult, eslintResult, semgrepResult, adapterResult];
116
+ const allPassed = allExecuted.every((r) => (r.executed ? r.passed : r.status !== "FAIL")) &&
117
+ (metaResult.executed ? metaResult.passed : metaResult.status !== "FAIL") &&
118
+ (configResult.executed
119
+ ? configResult.passed
120
+ : configResult.status !== "FAIL");
121
+ return {
122
+ passed: allPassed,
123
+ violations,
124
+ categories: {
125
+ schema: categoryOf(schemaResult),
126
+ metadata: categoryOf(metaResult),
127
+ config: categoryOf(configResult),
128
+ dependencies: categoryOf(depResult),
129
+ eslint: categoryOf(eslintResult),
130
+ semgrep: categoryOf(semgrepResult),
131
+ adapterPeers: categoryOf(adapterResult),
132
+ },
133
+ };
134
+ }
135
+ function emptyEngineResult(reason) {
136
+ return {
137
+ executed: false,
138
+ exitCode: null,
139
+ passed: false,
140
+ findings: [],
141
+ duration: 0,
142
+ status: "NOT-RUN",
143
+ reason,
144
+ };
145
+ }
146
+ /**
147
+ * Map a validation result to the documented process exit code.
148
+ *
149
+ * A required tool that could not run must never surface as PASS: when
150
+ * every executed engine passed but at least one category reports
151
+ * `TOOL-UNAVAILABLE`, the result exits `TOOL_MISSING` (3) instead of
152
+ * fabricating a passing result.
153
+ */
154
+ export function exitCodeForResult(result) {
155
+ if (!result.passed)
156
+ return ExitCode.FAIL;
157
+ const toolUnavailable = Object.values(result.categories).some((c) => c.status === "TOOL-UNAVAILABLE");
158
+ return toolUnavailable ? ExitCode.TOOL_MISSING : ExitCode.PASS;
159
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@comity-dev/validate",
3
- "version": "0.1.2",
3
+ "version": "0.2.1",
4
4
  "description": "Thin orchestrator over Comity shared validation tooling. Development tooling.",
5
5
  "type": "module",
6
6
  "private": false,
@@ -34,10 +34,10 @@
34
34
  "@typescript-eslint/parser": "^8.69.0",
35
35
  "dependency-cruiser": "^16.10.4",
36
36
  "eslint": "^9.39.5",
37
- "@comity-dev/schemas": "0.1.1",
38
- "@comity-dev/dependency-rules": "0.1.1",
39
- "@comity-dev/eslint-plugin": "0.1.2",
40
- "@comity-dev/semgrep-rules": "0.1.2"
37
+ "@comity-dev/schemas": "0.2.0",
38
+ "@comity-dev/dependency-rules": "0.2.0",
39
+ "@comity-dev/eslint-plugin": "0.2.0",
40
+ "@comity-dev/semgrep-rules": "0.2.1"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "^24.13.3",