patchdrill 0.1.0
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/.patchdrill.yml +33 -0
- package/CHANGELOG.md +150 -0
- package/CONTRIBUTING.md +59 -0
- package/LICENSE +21 -0
- package/README.md +601 -0
- package/SECURITY.md +28 -0
- package/action.yml +338 -0
- package/dist/baseline.d.ts +9 -0
- package/dist/baseline.js +38 -0
- package/dist/baseline.js.map +1 -0
- package/dist/cli.d.ts +19 -0
- package/dist/cli.js +662 -0
- package/dist/cli.js.map +1 -0
- package/dist/codeowners.d.ts +14 -0
- package/dist/codeowners.js +104 -0
- package/dist/codeowners.js.map +1 -0
- package/dist/command-plan.d.ts +3 -0
- package/dist/command-plan.js +26 -0
- package/dist/command-plan.js.map +1 -0
- package/dist/demo.d.ts +5 -0
- package/dist/demo.js +525 -0
- package/dist/demo.js.map +1 -0
- package/dist/dependency.d.ts +4 -0
- package/dist/dependency.js +1424 -0
- package/dist/dependency.js.map +1 -0
- package/dist/doctor.d.ts +26 -0
- package/dist/doctor.js +183 -0
- package/dist/doctor.js.map +1 -0
- package/dist/evidence.d.ts +64 -0
- package/dist/evidence.js +352 -0
- package/dist/evidence.js.map +1 -0
- package/dist/git.d.ts +16 -0
- package/dist/git.js +349 -0
- package/dist/git.js.map +1 -0
- package/dist/i18n-catalog.d.ts +8 -0
- package/dist/i18n-catalog.js +446 -0
- package/dist/i18n-catalog.js.map +1 -0
- package/dist/i18n.d.ts +20 -0
- package/dist/i18n.js +67 -0
- package/dist/i18n.js.map +1 -0
- package/dist/init.d.ts +13 -0
- package/dist/init.js +312 -0
- package/dist/init.js.map +1 -0
- package/dist/markdown-links.d.ts +18 -0
- package/dist/markdown-links.js +180 -0
- package/dist/markdown-links.js.map +1 -0
- package/dist/package-scripts.d.ts +3 -0
- package/dist/package-scripts.js +55 -0
- package/dist/package-scripts.js.map +1 -0
- package/dist/planner.d.ts +8 -0
- package/dist/planner.js +2351 -0
- package/dist/planner.js.map +1 -0
- package/dist/policy.d.ts +12 -0
- package/dist/policy.js +255 -0
- package/dist/policy.js.map +1 -0
- package/dist/project.d.ts +2 -0
- package/dist/project.js +1085 -0
- package/dist/project.js.map +1 -0
- package/dist/release-readiness.d.ts +25 -0
- package/dist/release-readiness.js +426 -0
- package/dist/release-readiness.js.map +1 -0
- package/dist/report-annotations.d.ts +3 -0
- package/dist/report-annotations.js +28 -0
- package/dist/report-annotations.js.map +1 -0
- package/dist/report-contract.d.ts +2 -0
- package/dist/report-contract.js +82 -0
- package/dist/report-contract.js.map +1 -0
- package/dist/report-html.d.ts +7 -0
- package/dist/report-html.js +706 -0
- package/dist/report-html.js.map +1 -0
- package/dist/report-sarif.d.ts +2 -0
- package/dist/report-sarif.js +90 -0
- package/dist/report-sarif.js.map +1 -0
- package/dist/report.d.ts +14 -0
- package/dist/report.js +310 -0
- package/dist/report.js.map +1 -0
- package/dist/risk.d.ts +19 -0
- package/dist/risk.js +1226 -0
- package/dist/risk.js.map +1 -0
- package/dist/runner.d.ts +8 -0
- package/dist/runner.js +113 -0
- package/dist/runner.js.map +1 -0
- package/dist/scan.d.ts +2 -0
- package/dist/scan.js +195 -0
- package/dist/scan.js.map +1 -0
- package/dist/schema.d.ts +12 -0
- package/dist/schema.js +30 -0
- package/dist/schema.js.map +1 -0
- package/dist/stack-coverage.d.ts +8 -0
- package/dist/stack-coverage.js +94 -0
- package/dist/stack-coverage.js.map +1 -0
- package/dist/types.d.ts +206 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/verification.d.ts +11 -0
- package/dist/verification.js +108 -0
- package/dist/verification.js.map +1 -0
- package/docs/ANNOTATIONS.md +34 -0
- package/docs/ARCHITECTURE.md +79 -0
- package/docs/BASELINES.md +32 -0
- package/docs/CASE_STUDIES.md +106 -0
- package/docs/CODEOWNERS.md +23 -0
- package/docs/DASHBOARD.md +87 -0
- package/docs/EVIDENCE.md +55 -0
- package/docs/LAUNCH_PLAYBOOK.md +103 -0
- package/docs/MONOREPOS.md +74 -0
- package/docs/POLICY.md +98 -0
- package/docs/PROOF_PACKS.md +57 -0
- package/docs/PR_COMMENTS.md +56 -0
- package/docs/RELEASE.md +35 -0
- package/docs/ROADMAP.md +152 -0
- package/docs/RULE_CATALOG.md +90 -0
- package/docs/SARIF.md +74 -0
- package/docs/SCHEMAS.md +49 -0
- package/docs/SECURITY_POSTURE.md +32 -0
- package/docs/STACK_COVERAGE.md +20 -0
- package/docs/assets/patchdrill-demo.svg +21 -0
- package/docs/media/patchdrill-dashboard.png +0 -0
- package/docs/media/patchdrill-demo.gif +0 -0
- package/examples/case-studies/README.md +20 -0
- package/examples/demo/README.md +21 -0
- package/examples/demo/patchdrill-demo-summary.md +35 -0
- package/examples/demo/patchdrill-demo.html +623 -0
- package/examples/demo/patchdrill-demo.json +355 -0
- package/examples/demo/patchdrill-demo.md +120 -0
- package/examples/demo/patchdrill-demo.sarif +195 -0
- package/examples/report.md +128 -0
- package/examples/risky-agent-pr/README.md +15 -0
- package/examples/risky-agent-pr/patchdrill-demo-summary.md +41 -0
- package/examples/risky-agent-pr/patchdrill-demo.html +681 -0
- package/examples/risky-agent-pr/patchdrill-demo.json +483 -0
- package/examples/risky-agent-pr/patchdrill-demo.md +140 -0
- package/examples/risky-agent-pr/patchdrill-demo.sarif +398 -0
- package/fixtures/stacks/README.md +4 -0
- package/fixtures/stacks/android-gradle/fixture.json +33 -0
- package/fixtures/stacks/aspnet-core-service/fixture.json +36 -0
- package/fixtures/stacks/bazel-workspace/fixture.json +30 -0
- package/fixtures/stacks/buck2-workspace/fixture.json +30 -0
- package/fixtures/stacks/cargo-workspace/fixture.json +48 -0
- package/fixtures/stacks/django-app/fixture.json +25 -0
- package/fixtures/stacks/docker-compose/fixture.json +17 -0
- package/fixtures/stacks/dockerfile-service/fixture.json +17 -0
- package/fixtures/stacks/dotnet-service/fixture.json +36 -0
- package/fixtures/stacks/dotnet-solution-filter/fixture.json +62 -0
- package/fixtures/stacks/fastapi-app/fixture.json +29 -0
- package/fixtures/stacks/go-workspace/fixture.json +48 -0
- package/fixtures/stacks/java-gradle/fixture.json +29 -0
- package/fixtures/stacks/java-maven/fixture.json +32 -0
- package/fixtures/stacks/kubernetes-helm/fixture.json +25 -0
- package/fixtures/stacks/kubernetes-kustomize/fixture.json +21 -0
- package/fixtures/stacks/nested-go-workspace/fixture.json +51 -0
- package/fixtures/stacks/nextjs-app/fixture.json +34 -0
- package/fixtures/stacks/node-turbo-workspace/fixture.json +39 -0
- package/fixtures/stacks/pants-python/fixture.json +33 -0
- package/fixtures/stacks/php-composer/fixture.json +31 -0
- package/fixtures/stacks/python-service/fixture.json +21 -0
- package/fixtures/stacks/rails-app/fixture.json +25 -0
- package/fixtures/stacks/spring-boot-gradle/fixture.json +29 -0
- package/fixtures/stacks/spring-boot-maven/fixture.json +43 -0
- package/fixtures/stacks/swift-package/fixture.json +21 -0
- package/fixtures/stacks/terraform-module/fixture.json +17 -0
- package/fixtures/stacks/uv-python-service/fixture.json +47 -0
- package/fixtures/stacks/xcode-app/fixture.json +72 -0
- package/package.json +80 -0
- package/schemas/patchdrill-doctor.schema.json +171 -0
- package/schemas/patchdrill-evidence.schema.json +239 -0
- package/schemas/patchdrill-policy.schema.json +170 -0
- package/schemas/patchdrill-release-check.schema.json +78 -0
- package/schemas/patchdrill-report.schema.json +647 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,662 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { createDemoReport, demoScenarioNames, isDemoScenario } from "./demo.js";
|
|
6
|
+
import { formatEvidenceVerification, renderEvidenceManifest, verifyEvidenceManifest } from "./evidence.js";
|
|
7
|
+
import { gitRoot } from "./git.js";
|
|
8
|
+
import { isLocale, LOCALES, resolveLocale, t } from "./i18n.js";
|
|
9
|
+
import { isPolicyPackName, policyPackNames, writeGitHubWorkflow, writeOnboardingGuide, writePolicyFile } from "./init.js";
|
|
10
|
+
import { inspectDoctor, renderDoctor } from "./doctor.js";
|
|
11
|
+
import { checkReleaseReadiness, createReleaseReadinessReport, renderReleaseReadiness, summarizeReleaseReadiness } from "./release-readiness.js";
|
|
12
|
+
import { reportContractFailures } from "./report-contract.js";
|
|
13
|
+
import { renderGitHubAnnotations, renderHtml, renderMarkdown, renderSarif, renderSummaryMarkdown, shouldFail, verificationEvidencePhrase } from "./report.js";
|
|
14
|
+
import { isSchemaName, readSchema, schemaNames } from "./schema.js";
|
|
15
|
+
import { scan } from "./scan.js";
|
|
16
|
+
import { verificationSummary } from "./verification.js";
|
|
17
|
+
const severities = ["info", "low", "medium", "high", "critical"];
|
|
18
|
+
async function main() {
|
|
19
|
+
const parsed = parseArgs(process.argv.slice(2));
|
|
20
|
+
const command = parsed.command;
|
|
21
|
+
if (flagBoolean(parsed, "help") || command === "help") {
|
|
22
|
+
printHelp();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (flagBoolean(parsed, "version")) {
|
|
26
|
+
console.log(readVersion());
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (command === "scan") {
|
|
30
|
+
await scanCommand(parsed);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (command === "dashboard") {
|
|
34
|
+
dashboardCommand(parsed);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (command === "demo") {
|
|
38
|
+
demoCommand(parsed);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (command === "doctor") {
|
|
42
|
+
doctorCommand(parsed);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (command === "evidence") {
|
|
46
|
+
evidenceCommand(parsed);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (command === "init") {
|
|
50
|
+
initCommand(parsed);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (command === "explain") {
|
|
54
|
+
explainCommand();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (command === "schema") {
|
|
58
|
+
schemaCommand(parsed);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (command === "release-check") {
|
|
62
|
+
releaseCheckCommand(parsed);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (command === "verify") {
|
|
66
|
+
verifyCommand(parsed);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
throw new Error(`Unknown command "${command}". Run patchdrill --help.`);
|
|
70
|
+
}
|
|
71
|
+
export async function scanCommand(parsed) {
|
|
72
|
+
const cliFailOnValue = flagString(parsed, "fail-on");
|
|
73
|
+
const cliMaxRiskValue = flagString(parsed, "max-risk");
|
|
74
|
+
const cliMaxRiskDeltaValue = flagString(parsed, "max-risk-delta");
|
|
75
|
+
const cliMaxOutputCharsValue = flagString(parsed, "max-output-chars");
|
|
76
|
+
const cliCommandTimeoutMsValue = flagString(parsed, "command-timeout-ms");
|
|
77
|
+
const base = flagString(parsed, "base");
|
|
78
|
+
const head = flagString(parsed, "head");
|
|
79
|
+
const configPath = flagString(parsed, "config");
|
|
80
|
+
const baselinePath = flagString(parsed, "baseline");
|
|
81
|
+
const evidencePath = flagString(parsed, "evidence");
|
|
82
|
+
const summaryMarkdownPath = flagString(parsed, "summary-markdown");
|
|
83
|
+
const markdownPath = flagString(parsed, "markdown");
|
|
84
|
+
const jsonPath = flagString(parsed, "json");
|
|
85
|
+
const sarifPath = flagString(parsed, "sarif");
|
|
86
|
+
const htmlPath = flagString(parsed, "html");
|
|
87
|
+
const run = flagBoolean(parsed, "run");
|
|
88
|
+
const runOptional = flagBoolean(parsed, "run-optional");
|
|
89
|
+
const cliFailOn = cliFailOnValue ? readSeverity(cliFailOnValue, "critical") : undefined;
|
|
90
|
+
const cliMaxRisk = cliMaxRiskValue ? readMaxRisk(cliMaxRiskValue) : undefined;
|
|
91
|
+
const cliMaxRiskDelta = cliMaxRiskDeltaValue ? readMaxRiskDelta(cliMaxRiskDeltaValue) : undefined;
|
|
92
|
+
const cliMaxOutputChars = cliMaxOutputCharsValue ? readPositiveInteger(cliMaxOutputCharsValue, "max output chars") : undefined;
|
|
93
|
+
const cliCommandTimeoutMs = cliCommandTimeoutMsValue ? readPositiveInteger(cliCommandTimeoutMsValue, "command timeout ms") : undefined;
|
|
94
|
+
if (runOptional && !run) {
|
|
95
|
+
throw new Error("--run-optional requires --run.");
|
|
96
|
+
}
|
|
97
|
+
if (evidencePath && !jsonPath) {
|
|
98
|
+
throw new Error("--evidence requires --json so the evidence manifest can verify the JSON report contract.");
|
|
99
|
+
}
|
|
100
|
+
if (cliMaxRiskDelta !== undefined && !baselinePath) {
|
|
101
|
+
throw new Error("--max-risk-delta requires --baseline so the risk delta gate has a previous report to compare against.");
|
|
102
|
+
}
|
|
103
|
+
const locale = readLocale(parsed, true);
|
|
104
|
+
const report = await scan({
|
|
105
|
+
cwd: process.cwd(),
|
|
106
|
+
...(base ? { base } : {}),
|
|
107
|
+
...(head ? { head } : {}),
|
|
108
|
+
...(configPath ? { configPath } : {}),
|
|
109
|
+
...(baselinePath ? { baselinePath } : {}),
|
|
110
|
+
...(evidencePath ? { evidencePath } : {}),
|
|
111
|
+
locale,
|
|
112
|
+
run,
|
|
113
|
+
...(runOptional ? { runOptional: true } : {}),
|
|
114
|
+
...(cliFailOn ? { failOn: cliFailOn } : {}),
|
|
115
|
+
...(summaryMarkdownPath ? { summaryMarkdownPath } : {}),
|
|
116
|
+
...(markdownPath ? { markdownPath } : {}),
|
|
117
|
+
...(jsonPath ? { jsonPath } : {}),
|
|
118
|
+
...(sarifPath ? { sarifPath } : {}),
|
|
119
|
+
...(htmlPath ? { htmlPath } : {}),
|
|
120
|
+
...(cliMaxOutputChars !== undefined ? { maxOutputChars: cliMaxOutputChars } : {}),
|
|
121
|
+
...(cliCommandTimeoutMs !== undefined ? { commandTimeoutMs: cliCommandTimeoutMs } : {})
|
|
122
|
+
});
|
|
123
|
+
const gateOptions = {
|
|
124
|
+
failOn: cliFailOn ?? report.policy?.failOn ?? "critical",
|
|
125
|
+
maxRisk: cliMaxRisk ?? report.policy?.maxRisk ?? 69,
|
|
126
|
+
...(cliMaxRiskDelta !== undefined ? { maxRiskDelta: cliMaxRiskDelta } : {})
|
|
127
|
+
};
|
|
128
|
+
if (!flagBoolean(parsed, "quiet")) {
|
|
129
|
+
console.log(renderConsoleSummary(report, gateOptions, locale));
|
|
130
|
+
if (!markdownPath) {
|
|
131
|
+
console.log("");
|
|
132
|
+
console.log(renderMarkdown(report, locale));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (flagBoolean(parsed, "github-annotations")) {
|
|
136
|
+
const annotations = renderGitHubAnnotations(report, locale).trimEnd();
|
|
137
|
+
if (annotations)
|
|
138
|
+
console.log(annotations);
|
|
139
|
+
}
|
|
140
|
+
if (shouldFail(report, gateOptions)) {
|
|
141
|
+
process.exitCode = 1;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
export function dashboardCommand(parsed) {
|
|
145
|
+
const jsonPaths = flagStrings(parsed, "json");
|
|
146
|
+
if (jsonPaths.length === 0) {
|
|
147
|
+
throw new Error("dashboard requires --json <report.json>.");
|
|
148
|
+
}
|
|
149
|
+
const output = flagString(parsed, "output") ?? "patchdrill-dashboard.html";
|
|
150
|
+
const locale = readLocale(parsed);
|
|
151
|
+
const reports = jsonPaths.map((path) => readSavedReport(path).report);
|
|
152
|
+
const report = reports[reports.length - 1];
|
|
153
|
+
if (!report)
|
|
154
|
+
throw new Error("dashboard requires at least one JSON report.");
|
|
155
|
+
const resolved = resolve(process.cwd(), output);
|
|
156
|
+
mkdirSync(dirname(resolved), { recursive: true });
|
|
157
|
+
writeFileSync(resolved, renderHtml(report, { ...(reports.length > 1 ? { history: reports } : {}), locale }), "utf8");
|
|
158
|
+
console.log(`Wrote ${output}`);
|
|
159
|
+
}
|
|
160
|
+
export function demoCommand(parsed) {
|
|
161
|
+
const scenario = readDemoScenario(flagString(parsed, "scenario"));
|
|
162
|
+
const report = createDemoReport(scenario);
|
|
163
|
+
const output = flagString(parsed, "output");
|
|
164
|
+
const locale = readLocale(parsed);
|
|
165
|
+
if (!output) {
|
|
166
|
+
console.log(renderMarkdown(report, locale).trimEnd());
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const outputDir = resolve(process.cwd(), output);
|
|
170
|
+
mkdirSync(outputDir, { recursive: true });
|
|
171
|
+
const files = {
|
|
172
|
+
summaryMarkdown: join(outputDir, "patchdrill-demo-summary.md"),
|
|
173
|
+
markdown: join(outputDir, "patchdrill-demo.md"),
|
|
174
|
+
json: join(outputDir, "patchdrill-demo.json"),
|
|
175
|
+
sarif: join(outputDir, "patchdrill-demo.sarif"),
|
|
176
|
+
html: join(outputDir, "patchdrill-demo.html")
|
|
177
|
+
};
|
|
178
|
+
writeFileSync(files.summaryMarkdown, renderSummaryMarkdown(report, locale), "utf8");
|
|
179
|
+
writeFileSync(files.markdown, renderMarkdown(report, locale), "utf8");
|
|
180
|
+
writeFileSync(files.json, `${JSON.stringify(report, null, 2)}\n`, "utf8");
|
|
181
|
+
writeFileSync(files.sarif, renderSarif(report), "utf8");
|
|
182
|
+
writeFileSync(files.html, renderHtml(report, { locale }), "utf8");
|
|
183
|
+
console.log(`Wrote demo artifacts to ${output}`);
|
|
184
|
+
console.log(`- ${files.summaryMarkdown}`);
|
|
185
|
+
console.log(`- ${files.markdown}`);
|
|
186
|
+
console.log(`- ${files.json}`);
|
|
187
|
+
console.log(`- ${files.sarif}`);
|
|
188
|
+
console.log(`- ${files.html}`);
|
|
189
|
+
}
|
|
190
|
+
export function doctorCommand(parsed = { command: "doctor", flags: {}, positionals: [] }) {
|
|
191
|
+
const root = gitRoot(process.cwd());
|
|
192
|
+
const report = inspectDoctor(root);
|
|
193
|
+
if (readOutputFormat(parsed) === "json") {
|
|
194
|
+
console.log(JSON.stringify(report, null, 2));
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
console.log(renderDoctor(report).trimEnd());
|
|
198
|
+
}
|
|
199
|
+
export function evidenceCommand(parsed) {
|
|
200
|
+
const reportPath = flagString(parsed, "json");
|
|
201
|
+
const evidencePath = flagString(parsed, "evidence") ?? flagString(parsed, "output");
|
|
202
|
+
if (!reportPath) {
|
|
203
|
+
throw new Error("evidence requires --json <patchdrill-report.json>.");
|
|
204
|
+
}
|
|
205
|
+
if (!evidencePath) {
|
|
206
|
+
throw new Error("evidence requires --evidence <patchdrill-evidence.json>.");
|
|
207
|
+
}
|
|
208
|
+
const { report, contents: reportJson } = readSavedReport(reportPath);
|
|
209
|
+
const artifacts = [
|
|
210
|
+
...optionalEvidenceArtifact("summary-markdown", flagString(parsed, "summary-markdown")),
|
|
211
|
+
...optionalEvidenceArtifact("markdown", flagString(parsed, "markdown")),
|
|
212
|
+
{ kind: "json", path: reportPath, contents: reportJson },
|
|
213
|
+
...optionalEvidenceArtifact("sarif", flagString(parsed, "sarif")),
|
|
214
|
+
...optionalEvidenceArtifact("html", flagString(parsed, "html"))
|
|
215
|
+
];
|
|
216
|
+
const resolved = resolve(process.cwd(), evidencePath);
|
|
217
|
+
mkdirSync(dirname(resolved), { recursive: true });
|
|
218
|
+
writeFileSync(resolved, renderEvidenceManifest(report, artifacts, report.root || process.cwd(), reportJson), "utf8");
|
|
219
|
+
console.log(`Wrote ${evidencePath}`);
|
|
220
|
+
}
|
|
221
|
+
function readSavedReport(path) {
|
|
222
|
+
const contents = readFileSync(path, "utf8");
|
|
223
|
+
let parsed;
|
|
224
|
+
try {
|
|
225
|
+
parsed = JSON.parse(contents);
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
229
|
+
throw new Error(`Could not parse JSON report at ${path}: ${detail}`, { cause: error });
|
|
230
|
+
}
|
|
231
|
+
if (!isRecord(parsed)) {
|
|
232
|
+
throw new Error(`JSON report at ${path} must be an object; got ${describeJsonValue(parsed)}.`);
|
|
233
|
+
}
|
|
234
|
+
const failures = reportContractFailures(parsed);
|
|
235
|
+
if (failures.length > 0) {
|
|
236
|
+
throw new Error(`JSON report contract failed for ${path}: ${failures.join("; ")}`);
|
|
237
|
+
}
|
|
238
|
+
return { report: parsed, contents };
|
|
239
|
+
}
|
|
240
|
+
function isRecord(value) {
|
|
241
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
242
|
+
}
|
|
243
|
+
function describeJsonValue(value) {
|
|
244
|
+
if (Array.isArray(value))
|
|
245
|
+
return "array";
|
|
246
|
+
if (value === null)
|
|
247
|
+
return "null";
|
|
248
|
+
return typeof value;
|
|
249
|
+
}
|
|
250
|
+
function initCommand(parsed) {
|
|
251
|
+
const root = gitRoot(process.cwd());
|
|
252
|
+
const policyPack = readPolicyPack(flagString(parsed, "policy-pack"));
|
|
253
|
+
const force = flagBoolean(parsed, "force");
|
|
254
|
+
const policyRequested = flagBoolean(parsed, "policy") || parsed.flags["policy-pack"] !== undefined;
|
|
255
|
+
const path = writeGitHubWorkflow(root, force);
|
|
256
|
+
console.log(`Created ${path}`);
|
|
257
|
+
if (policyRequested) {
|
|
258
|
+
const policyPath = writePolicyFile(root, force, policyPack);
|
|
259
|
+
console.log(`Created ${policyPath}`);
|
|
260
|
+
}
|
|
261
|
+
const guidePath = writeOnboardingGuide(root, force, { policyPack, policyCreated: policyRequested });
|
|
262
|
+
console.log(`Created ${guidePath}`);
|
|
263
|
+
}
|
|
264
|
+
export function explainCommand() {
|
|
265
|
+
console.log(renderExplainText());
|
|
266
|
+
}
|
|
267
|
+
export function renderExplainText() {
|
|
268
|
+
return `PatchDrill is the deterministic proof layer between code review and CI.
|
|
269
|
+
|
|
270
|
+
PatchDrill is not an AI PR reviewer.
|
|
271
|
+
|
|
272
|
+
AI reviewers answer: "Does this diff look right?"
|
|
273
|
+
PatchDrill answers: "What deterministic proof should exist before merge?"
|
|
274
|
+
|
|
275
|
+
What PatchDrill does:
|
|
276
|
+
1. Reads changed files and added lines from git.
|
|
277
|
+
2. Detects repository ecosystems, workspaces, owners, dependencies, package scripts, and workflow trust boundaries.
|
|
278
|
+
3. Infers required and optional verification commands from the patch.
|
|
279
|
+
4. Scores risk with human-readable findings where every score increase maps to a report row.
|
|
280
|
+
5. Emits a Proof Pack: Markdown, JSON, SARIF, static HTML, PR-comment summaries, and verifiable evidence manifests.
|
|
281
|
+
|
|
282
|
+
What makes it different:
|
|
283
|
+
- No model call is required; the same diff produces the same plan and findings.
|
|
284
|
+
- scan does not mutate the repository or run commands unless --run is set.
|
|
285
|
+
- --run executes inferred required checks; --run-optional explicitly opts into optional checks.
|
|
286
|
+
- Proof Pack artifacts are meant for CI gates, bots, auditors, reviewers, and model-assisted review.
|
|
287
|
+
- You can run PatchDrill before handing the report to a human or a frontier model.
|
|
288
|
+
|
|
289
|
+
Try it without a repository:
|
|
290
|
+
patchdrill demo --scenario risky-agent-pr --output patchdrill-risky-demo
|
|
291
|
+
|
|
292
|
+
Typical CI/local use:
|
|
293
|
+
patchdrill scan --base origin/main --run --evidence patchdrill-evidence.json --summary-markdown patchdrill-summary.md --markdown patchdrill-report.md --json patchdrill-report.json --sarif patchdrill.sarif --html patchdrill-dashboard.html --fail-on high --max-risk 69`;
|
|
294
|
+
}
|
|
295
|
+
function schemaCommand(parsed) {
|
|
296
|
+
const requested = parsed.positionals[0];
|
|
297
|
+
if (flagBoolean(parsed, "list") || requested === undefined) {
|
|
298
|
+
console.log(schemaNames.join("\n"));
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (!isSchemaName(requested)) {
|
|
302
|
+
throw new Error(`Unknown schema "${requested}". Expected one of ${schemaNames.join(", ")}.`);
|
|
303
|
+
}
|
|
304
|
+
const schema = readSchema(requested);
|
|
305
|
+
const output = flagString(parsed, "output");
|
|
306
|
+
if (output) {
|
|
307
|
+
writeFileSync(output, schema, "utf8");
|
|
308
|
+
console.log(`Wrote ${output}`);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
console.log(schema.trimEnd());
|
|
312
|
+
}
|
|
313
|
+
function verifyCommand(parsed) {
|
|
314
|
+
const evidencePath = flagString(parsed, "evidence") ?? parsed.positionals[0];
|
|
315
|
+
if (!evidencePath) {
|
|
316
|
+
throw new Error("verify requires --evidence <patchdrill-evidence.json>.");
|
|
317
|
+
}
|
|
318
|
+
const result = verifyEvidenceManifest(evidencePath, process.cwd());
|
|
319
|
+
if (!flagBoolean(parsed, "quiet")) {
|
|
320
|
+
console.log(formatEvidenceVerification(result));
|
|
321
|
+
}
|
|
322
|
+
if (!result.ok) {
|
|
323
|
+
process.exitCode = 1;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
export function releaseCheckCommand(parsed = { command: "release-check", flags: {}, positionals: [] }) {
|
|
327
|
+
const root = gitRoot(process.cwd());
|
|
328
|
+
const checks = checkReleaseReadiness(root);
|
|
329
|
+
const summary = summarizeReleaseReadiness(checks);
|
|
330
|
+
if (readOutputFormat(parsed) === "json") {
|
|
331
|
+
console.log(JSON.stringify(createReleaseReadinessReport(checks), null, 2));
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
console.log(renderReleaseReadiness(checks).trimEnd());
|
|
335
|
+
}
|
|
336
|
+
if (!summary.ok) {
|
|
337
|
+
process.exitCode = 1;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
export function renderConsoleSummary(report, gateOptions, locale = "en") {
|
|
341
|
+
const tr = (text) => t(locale, text);
|
|
342
|
+
const required = report.commandPlan.filter((command) => command.required);
|
|
343
|
+
const optional = report.commandPlan.filter((command) => !command.required);
|
|
344
|
+
const verification = verificationSummary(report);
|
|
345
|
+
const gateStatus = shouldFail(report, gateOptions) ? "FAIL" : "PASS";
|
|
346
|
+
const lines = [
|
|
347
|
+
`${tr("PatchDrill Gate")} ${tr(gateStatus)} - ${tr("assessment")} ${tr(report.summary.status.toUpperCase())}, ${tr("risk")} ${report.summary.riskScore}/100, ${tr("confidence")} ${report.summary.confidenceScore}/100`,
|
|
348
|
+
`${tr("Gate policy")}: ${tr("fail-on")} ${gateOptions.failOn}, ${tr("max-risk")} ${gateOptions.maxRisk}${gateOptions.maxRiskDelta !== undefined ? `, ${tr("max-risk-delta")} ${gateOptions.maxRiskDelta}` : ""}`,
|
|
349
|
+
`${tr("Changed files")}: ${report.summary.changedFileCount}, +${report.summary.additions}/-${report.summary.deletions}`,
|
|
350
|
+
`${tr("Required commands")}: ${required.length}, ${tr("optional commands")}: ${optional.length}`,
|
|
351
|
+
`${tr("Verification evidence")}: ${verificationEvidencePhrase(verification, locale)}`,
|
|
352
|
+
`${tr("Added lines inspected")}: ${report.addedLines}`
|
|
353
|
+
];
|
|
354
|
+
if (report.findings.length > 0) {
|
|
355
|
+
lines.push(`${tr("Top findings")}:`);
|
|
356
|
+
for (const finding of report.findings.slice(0, 5)) {
|
|
357
|
+
lines.push(`- [${tr(finding.severity)}] ${tr(finding.title)}${finding.file ? ` (${finding.file})` : ""}`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (verification.missingRequired > 0) {
|
|
361
|
+
lines.push(tr("Run with --run to execute required verification commands. Add --run-optional to include optional checks."));
|
|
362
|
+
}
|
|
363
|
+
return lines.join("\n");
|
|
364
|
+
}
|
|
365
|
+
export function parseArgs(args) {
|
|
366
|
+
const flags = {};
|
|
367
|
+
const positionals = [];
|
|
368
|
+
let command = "scan";
|
|
369
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
370
|
+
const arg = args[index];
|
|
371
|
+
if (!arg)
|
|
372
|
+
continue;
|
|
373
|
+
if (arg.startsWith("--")) {
|
|
374
|
+
const [rawKey, inlineValue] = arg.slice(2).split("=", 2);
|
|
375
|
+
const key = rawKey ?? "";
|
|
376
|
+
if (!key)
|
|
377
|
+
continue;
|
|
378
|
+
if (!isKnownFlag(key)) {
|
|
379
|
+
throw new Error(`Unknown flag "--${key}". Run patchdrill --help.`);
|
|
380
|
+
}
|
|
381
|
+
if (inlineValue !== undefined) {
|
|
382
|
+
addFlag(flags, key, isBooleanFlag(key) ? readBooleanFlag(inlineValue, key) : inlineValue);
|
|
383
|
+
}
|
|
384
|
+
else if (takesValue(key)) {
|
|
385
|
+
const next = args[index + 1];
|
|
386
|
+
if (next === undefined || next.startsWith("-")) {
|
|
387
|
+
throw new Error(`Flag "--${key}" requires a value. Run patchdrill --help.`);
|
|
388
|
+
}
|
|
389
|
+
addFlag(flags, key, next);
|
|
390
|
+
index += 1;
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
const next = args[index + 1];
|
|
394
|
+
if (next && !next.startsWith("-") && isBooleanFlag(key) && isBooleanLiteral(next)) {
|
|
395
|
+
addFlag(flags, key, readBooleanFlag(next, key));
|
|
396
|
+
index += 1;
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
addFlag(flags, key, true);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
if (!arg.startsWith("-") &&
|
|
405
|
+
command === "scan" &&
|
|
406
|
+
["scan", "dashboard", "demo", "doctor", "evidence", "init", "explain", "release-check", "schema", "verify", "help"].includes(arg)) {
|
|
407
|
+
command = arg;
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
positionals.push(arg);
|
|
411
|
+
}
|
|
412
|
+
return { command, flags, positionals };
|
|
413
|
+
}
|
|
414
|
+
function addFlag(flags, key, value) {
|
|
415
|
+
const existing = flags[key];
|
|
416
|
+
if (existing === undefined) {
|
|
417
|
+
flags[key] = value;
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
const next = typeof value === "string" ? value : String(value);
|
|
421
|
+
if (Array.isArray(existing)) {
|
|
422
|
+
existing.push(next);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
flags[key] = [typeof existing === "string" ? existing : String(existing), next];
|
|
426
|
+
}
|
|
427
|
+
function flagString(parsed, key) {
|
|
428
|
+
const value = parsed.flags[key];
|
|
429
|
+
if (typeof value === "string")
|
|
430
|
+
return value;
|
|
431
|
+
if (Array.isArray(value))
|
|
432
|
+
return value[value.length - 1];
|
|
433
|
+
return undefined;
|
|
434
|
+
}
|
|
435
|
+
function flagStrings(parsed, key) {
|
|
436
|
+
const value = parsed.flags[key];
|
|
437
|
+
if (typeof value === "string")
|
|
438
|
+
return [value];
|
|
439
|
+
if (Array.isArray(value))
|
|
440
|
+
return value;
|
|
441
|
+
return [];
|
|
442
|
+
}
|
|
443
|
+
function flagBoolean(parsed, key) {
|
|
444
|
+
const value = parsed.flags[key];
|
|
445
|
+
if (value === undefined)
|
|
446
|
+
return false;
|
|
447
|
+
if (typeof value === "boolean")
|
|
448
|
+
return value;
|
|
449
|
+
if (typeof value === "string")
|
|
450
|
+
return readBooleanFlag(value, key);
|
|
451
|
+
const last = value.at(-1);
|
|
452
|
+
return last === undefined ? false : readBooleanFlag(last, key);
|
|
453
|
+
}
|
|
454
|
+
function takesValue(flag) {
|
|
455
|
+
return [
|
|
456
|
+
"base",
|
|
457
|
+
"head",
|
|
458
|
+
"config",
|
|
459
|
+
"baseline",
|
|
460
|
+
"evidence",
|
|
461
|
+
"summary-markdown",
|
|
462
|
+
"markdown",
|
|
463
|
+
"json",
|
|
464
|
+
"sarif",
|
|
465
|
+
"html",
|
|
466
|
+
"fail-on",
|
|
467
|
+
"max-risk",
|
|
468
|
+
"max-risk-delta",
|
|
469
|
+
"max-output-chars",
|
|
470
|
+
"command-timeout-ms",
|
|
471
|
+
"policy-pack",
|
|
472
|
+
"scenario",
|
|
473
|
+
"format",
|
|
474
|
+
"output",
|
|
475
|
+
"locale"
|
|
476
|
+
].includes(flag);
|
|
477
|
+
}
|
|
478
|
+
function isBooleanFlag(flag) {
|
|
479
|
+
return ["help", "version", "quiet", "run", "run-optional", "github-annotations", "force", "policy", "list"].includes(flag);
|
|
480
|
+
}
|
|
481
|
+
function isKnownFlag(flag) {
|
|
482
|
+
return takesValue(flag) || isBooleanFlag(flag);
|
|
483
|
+
}
|
|
484
|
+
function isBooleanLiteral(value) {
|
|
485
|
+
return /^(true|false|1|0|yes|no|on|off)$/i.test(value);
|
|
486
|
+
}
|
|
487
|
+
function readBooleanFlag(value, flag) {
|
|
488
|
+
if (/^(true|1|yes|on)$/i.test(value))
|
|
489
|
+
return true;
|
|
490
|
+
if (/^(false|0|no|off)$/i.test(value))
|
|
491
|
+
return false;
|
|
492
|
+
throw new Error(`Invalid boolean value "${value}" for --${flag}. Expected true or false.`);
|
|
493
|
+
}
|
|
494
|
+
function readSeverity(value, fallback) {
|
|
495
|
+
if (typeof value !== "string")
|
|
496
|
+
return fallback;
|
|
497
|
+
if (!severities.includes(value)) {
|
|
498
|
+
throw new Error(`Invalid severity "${value}". Expected one of ${severities.join(", ")}.`);
|
|
499
|
+
}
|
|
500
|
+
return value;
|
|
501
|
+
}
|
|
502
|
+
function readMaxRisk(value) {
|
|
503
|
+
if (!/^\d+$/.test(value)) {
|
|
504
|
+
throw new Error(`Invalid max risk "${value}". Expected an integer from 0 to 100.`);
|
|
505
|
+
}
|
|
506
|
+
const parsed = Number.parseInt(value, 10);
|
|
507
|
+
if (!Number.isFinite(parsed) || parsed < 0 || parsed > 100) {
|
|
508
|
+
throw new Error(`Invalid max risk "${value}". Expected an integer from 0 to 100.`);
|
|
509
|
+
}
|
|
510
|
+
return parsed;
|
|
511
|
+
}
|
|
512
|
+
function readMaxRiskDelta(value) {
|
|
513
|
+
if (!/^\d+$/.test(value)) {
|
|
514
|
+
throw new Error(`Invalid max risk delta "${value}". Expected an integer from 0 to 100.`);
|
|
515
|
+
}
|
|
516
|
+
const parsed = Number.parseInt(value, 10);
|
|
517
|
+
if (!Number.isFinite(parsed) || parsed < 0 || parsed > 100) {
|
|
518
|
+
throw new Error(`Invalid max risk delta "${value}". Expected an integer from 0 to 100.`);
|
|
519
|
+
}
|
|
520
|
+
return parsed;
|
|
521
|
+
}
|
|
522
|
+
function readPositiveInteger(value, label) {
|
|
523
|
+
if (!/^\d+$/.test(value)) {
|
|
524
|
+
throw new Error(`Invalid ${label} "${value}". Expected a positive integer.`);
|
|
525
|
+
}
|
|
526
|
+
const parsed = Number.parseInt(value, 10);
|
|
527
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
528
|
+
throw new Error(`Invalid ${label} "${value}". Expected a positive integer.`);
|
|
529
|
+
}
|
|
530
|
+
return parsed;
|
|
531
|
+
}
|
|
532
|
+
function readLocale(parsed, detectEnv = false) {
|
|
533
|
+
const explicit = flagString(parsed, "locale");
|
|
534
|
+
if (explicit !== undefined && !isLocale(explicit)) {
|
|
535
|
+
throw new Error(`Invalid locale "${explicit}". Expected one of ${LOCALES.join(", ")}.`);
|
|
536
|
+
}
|
|
537
|
+
// Only `scan` auto-detects the system locale (it analyzes the user's own repo).
|
|
538
|
+
// `demo`/`dashboard` render fixed or saved artifacts and stay English unless an
|
|
539
|
+
// explicit --locale is given, so sample output and fixtures are deterministic.
|
|
540
|
+
return resolveLocale(detectEnv ? { explicit, env: process.env } : { explicit });
|
|
541
|
+
}
|
|
542
|
+
function readPolicyPack(value) {
|
|
543
|
+
if (value === undefined || value === false)
|
|
544
|
+
return "default";
|
|
545
|
+
if (typeof value !== "string") {
|
|
546
|
+
throw new Error(`Invalid policy pack. Expected one of ${policyPackNames.join(", ")}.`);
|
|
547
|
+
}
|
|
548
|
+
if (!isPolicyPackName(value)) {
|
|
549
|
+
throw new Error(`Invalid policy pack "${value}". Expected one of ${policyPackNames.join(", ")}.`);
|
|
550
|
+
}
|
|
551
|
+
return value;
|
|
552
|
+
}
|
|
553
|
+
function readOutputFormat(parsed) {
|
|
554
|
+
const format = flagString(parsed, "format") ?? "text";
|
|
555
|
+
if (format !== "text" && format !== "json") {
|
|
556
|
+
throw new Error(`Invalid output format "${format}". Expected text or json.`);
|
|
557
|
+
}
|
|
558
|
+
return format;
|
|
559
|
+
}
|
|
560
|
+
function optionalEvidenceArtifact(kind, path) {
|
|
561
|
+
return path ? [{ kind, path, contents: readFileSync(path, "utf8") }] : [];
|
|
562
|
+
}
|
|
563
|
+
function readDemoScenario(value) {
|
|
564
|
+
if (value === undefined)
|
|
565
|
+
return "review-ready";
|
|
566
|
+
if (!isDemoScenario(value)) {
|
|
567
|
+
throw new Error(`Invalid demo scenario "${value}". Expected one of ${demoScenarioNames.join(", ")}.`);
|
|
568
|
+
}
|
|
569
|
+
return value;
|
|
570
|
+
}
|
|
571
|
+
function readVersion() {
|
|
572
|
+
const packagePath = join(new URL("..", import.meta.url).pathname, "package.json");
|
|
573
|
+
if (!existsSync(packagePath))
|
|
574
|
+
return "0.1.0";
|
|
575
|
+
try {
|
|
576
|
+
const parsed = JSON.parse(readFileSync(packagePath, "utf8"));
|
|
577
|
+
return parsed.version ?? "0.1.0";
|
|
578
|
+
}
|
|
579
|
+
catch {
|
|
580
|
+
return "0.1.0";
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
function printHelp() {
|
|
584
|
+
console.log(`PatchDrill - deterministic proof layer for AI-era patches
|
|
585
|
+
|
|
586
|
+
Usage:
|
|
587
|
+
patchdrill scan [options]
|
|
588
|
+
patchdrill dashboard --json <report.json> [--json <report.json>...] [--output <dashboard.html>]
|
|
589
|
+
patchdrill demo [--scenario <name>] [--output <directory>]
|
|
590
|
+
patchdrill doctor [--format text|json]
|
|
591
|
+
patchdrill evidence --json <report.json> --evidence <evidence.json> [artifact options]
|
|
592
|
+
patchdrill init [--force] [--policy] [--policy-pack <name>]
|
|
593
|
+
patchdrill explain
|
|
594
|
+
patchdrill release-check [--format text|json]
|
|
595
|
+
patchdrill schema [policy|report|evidence|doctor|release-check] [--output <path>]
|
|
596
|
+
patchdrill verify --evidence <patchdrill-evidence.json>
|
|
597
|
+
|
|
598
|
+
First run:
|
|
599
|
+
patchdrill explain
|
|
600
|
+
patchdrill demo --scenario risky-agent-pr --output patchdrill-risky-demo
|
|
601
|
+
patchdrill doctor
|
|
602
|
+
patchdrill scan --base origin/main
|
|
603
|
+
|
|
604
|
+
Options:
|
|
605
|
+
--base <ref> Compare against a base ref, for example origin/main
|
|
606
|
+
--head <ref> Head ref when using --base, default HEAD
|
|
607
|
+
--config <path> Read policy from .patchdrill.yml/json or a specific path
|
|
608
|
+
--baseline <path> Compare against a previous PatchDrill JSON report
|
|
609
|
+
--evidence <path> Write a Proof Pack evidence manifest during scan/evidence, or select one for verify. scan --evidence requires --json
|
|
610
|
+
--run Execute required inferred verification commands
|
|
611
|
+
--run-optional With --run, also execute optional verification commands
|
|
612
|
+
--github-annotations
|
|
613
|
+
Emit GitHub Actions log annotations for findings
|
|
614
|
+
--summary-markdown <path>
|
|
615
|
+
Write a compact Markdown summary for PR comments or step summaries
|
|
616
|
+
--markdown <path> Write a Markdown report
|
|
617
|
+
--json <path> Write a JSON report
|
|
618
|
+
--sarif <path> Write a SARIF report for GitHub code scanning
|
|
619
|
+
--html <path> Write a self-contained static HTML dashboard
|
|
620
|
+
--fail-on <level> Fail when findings meet severity: info, low, medium, high, critical
|
|
621
|
+
--max-risk <score> Fail when risk score is above 0-100 threshold, default 69
|
|
622
|
+
--max-risk-delta <score>
|
|
623
|
+
Fail when baseline risk increase is above this threshold
|
|
624
|
+
--max-output-chars <n>
|
|
625
|
+
Keep the last n characters of each command output stream, default 20000
|
|
626
|
+
--command-timeout-ms <n>
|
|
627
|
+
Stop each verification command after n milliseconds
|
|
628
|
+
--quiet Only use exit code, no console report
|
|
629
|
+
--policy Create .patchdrill.yml when used with init
|
|
630
|
+
--policy-pack <name>
|
|
631
|
+
Starter policy pack for init --policy: ${policyPackNames.join(", ")}
|
|
632
|
+
--scenario <name> Demo scenario: ${demoScenarioNames.join(", ")}
|
|
633
|
+
--format <format> Output format for doctor and release-check: text, json
|
|
634
|
+
--locale <lang> Language for human-facing reports: ${LOCALES.join(", ")} (default: system locale, else en)
|
|
635
|
+
--list List schemas when used with schema
|
|
636
|
+
--output <path> Write a schema/dashboard file or demo artifact directory
|
|
637
|
+
--version Print version
|
|
638
|
+
--help Print help
|
|
639
|
+
|
|
640
|
+
Boolean flags accept explicit values: --run=false, --quiet=true, --github-annotations=off.
|
|
641
|
+
`);
|
|
642
|
+
}
|
|
643
|
+
if (isCliEntrypoint()) {
|
|
644
|
+
main().catch((error) => {
|
|
645
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
646
|
+
console.error(`patchdrill: ${message}`);
|
|
647
|
+
process.exitCode = 1;
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
function isCliEntrypoint() {
|
|
651
|
+
const entry = process.argv[1];
|
|
652
|
+
if (!entry)
|
|
653
|
+
return false;
|
|
654
|
+
const modulePath = fileURLToPath(import.meta.url);
|
|
655
|
+
try {
|
|
656
|
+
return realpathSync(entry) === realpathSync(modulePath);
|
|
657
|
+
}
|
|
658
|
+
catch {
|
|
659
|
+
return resolve(entry) === modulePath;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
//# sourceMappingURL=cli.js.map
|