codex-plugin-doctor 1.27.0 → 1.29.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/README.md +124 -105
- package/dist/core/doctor-config-store.d.ts +12 -0
- package/dist/core/doctor-config-store.js +61 -0
- package/dist/core/doctor-config.js +11 -51
- package/dist/core/npm-package-doctor.js +8 -0
- package/dist/core/output-contract.js +24 -0
- package/dist/core/suppression-management.d.ts +39 -0
- package/dist/core/suppression-management.js +181 -0
- package/dist/core/suppression-record.d.ts +26 -0
- package/dist/core/suppression-record.js +62 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/reporting/render-suppression-management.d.ts +13 -0
- package/dist/reporting/render-suppression-management.js +152 -0
- package/dist/run-cli.d.ts +3 -0
- package/dist/run-cli.js +543 -1
- package/package.json +64 -64
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { classifySuppressionRecord, isValidSuppressionFingerprint, validateSuppressionRecord } from "./suppression-record.js";
|
|
2
|
+
export class SuppressionManagementError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
field;
|
|
5
|
+
index;
|
|
6
|
+
indexes;
|
|
7
|
+
constructor(message, details) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "SuppressionManagementError";
|
|
10
|
+
this.code = details.code;
|
|
11
|
+
this.field = details.field;
|
|
12
|
+
this.index = details.index;
|
|
13
|
+
this.indexes = details.indexes;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function createSuppressionManagementError(message, details) {
|
|
17
|
+
return new SuppressionManagementError(message, details);
|
|
18
|
+
}
|
|
19
|
+
function isPlainObject(value) {
|
|
20
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21
|
+
}
|
|
22
|
+
function readSuppressions(config) {
|
|
23
|
+
if (!("suppressions" in config)) {
|
|
24
|
+
return {
|
|
25
|
+
suppressions: [],
|
|
26
|
+
hasSuppressions: false
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const { suppressions } = config;
|
|
30
|
+
if (!Array.isArray(suppressions)) {
|
|
31
|
+
throw createSuppressionManagementError("Doctor config suppressions must be an array when present.", {
|
|
32
|
+
code: "suppression_non_array"
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
suppressions,
|
|
37
|
+
hasSuppressions: true
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function copyConfigWithSuppressions(config, suppressions) {
|
|
41
|
+
return {
|
|
42
|
+
...config,
|
|
43
|
+
suppressions
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function buildManagedInvalidSuppression(index, invalidField) {
|
|
47
|
+
return {
|
|
48
|
+
index,
|
|
49
|
+
status: "invalid",
|
|
50
|
+
invalidField
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function buildRemovedSuppressionSummary(suppression) {
|
|
54
|
+
const classified = classifySuppressionRecord(suppression, new Date(0));
|
|
55
|
+
if (classified.status === "invalid") {
|
|
56
|
+
return {
|
|
57
|
+
invalidField: classified.field
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
fingerprint: classified.fingerprint,
|
|
62
|
+
reason: classified.reason,
|
|
63
|
+
expiresAt: classified.expiresAt
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function findDuplicateFingerprintIndex(suppressions, fingerprint) {
|
|
67
|
+
return suppressions.findIndex((suppression) => isPlainObject(suppression) &&
|
|
68
|
+
typeof suppression.fingerprint === "string" &&
|
|
69
|
+
suppression.fingerprint === fingerprint);
|
|
70
|
+
}
|
|
71
|
+
function validateManagedFingerprint(fingerprint) {
|
|
72
|
+
if (!isValidSuppressionFingerprint(fingerprint)) {
|
|
73
|
+
throw createSuppressionManagementError("Suppression fingerprint is invalid.", {
|
|
74
|
+
code: "suppression_invalid_fingerprint"
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return fingerprint;
|
|
78
|
+
}
|
|
79
|
+
function findFingerprintMatches(suppressions, fingerprint) {
|
|
80
|
+
return suppressions.flatMap((suppression, index) => isPlainObject(suppression) &&
|
|
81
|
+
typeof suppression.fingerprint === "string" &&
|
|
82
|
+
suppression.fingerprint === fingerprint
|
|
83
|
+
? [index]
|
|
84
|
+
: []);
|
|
85
|
+
}
|
|
86
|
+
export function listSuppressions(config, now = new Date()) {
|
|
87
|
+
const { suppressions, hasSuppressions } = readSuppressions(config);
|
|
88
|
+
if (!hasSuppressions) {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
return suppressions.map((suppression, index) => {
|
|
92
|
+
const classified = classifySuppressionRecord(suppression, now);
|
|
93
|
+
if (classified.status === "invalid") {
|
|
94
|
+
return buildManagedInvalidSuppression(index, classified.field);
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
index,
|
|
98
|
+
status: classified.status,
|
|
99
|
+
fingerprint: classified.fingerprint,
|
|
100
|
+
reason: classified.reason,
|
|
101
|
+
expiresAt: classified.expiresAt
|
|
102
|
+
};
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
export function addSuppression(config, record) {
|
|
106
|
+
const { suppressions } = readSuppressions(config);
|
|
107
|
+
const validation = validateSuppressionRecord(record);
|
|
108
|
+
if (!validation.valid) {
|
|
109
|
+
throw createSuppressionManagementError(`Suppression record is invalid: ${validation.field}.`, {
|
|
110
|
+
code: "suppression_invalid_record",
|
|
111
|
+
field: validation.field
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
const duplicateIndex = findDuplicateFingerprintIndex(suppressions, validation.fingerprint);
|
|
115
|
+
if (duplicateIndex >= 0) {
|
|
116
|
+
throw createSuppressionManagementError(`Suppression fingerprint already exists at index ${duplicateIndex}.`, {
|
|
117
|
+
code: "suppression_duplicate_fingerprint",
|
|
118
|
+
index: duplicateIndex
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
const suppression = {
|
|
122
|
+
fingerprint: validation.fingerprint,
|
|
123
|
+
reason: validation.reason,
|
|
124
|
+
expiresAt: validation.expiresAt
|
|
125
|
+
};
|
|
126
|
+
const nextSuppressions = [...suppressions, suppression];
|
|
127
|
+
return {
|
|
128
|
+
config: copyConfigWithSuppressions(config, nextSuppressions),
|
|
129
|
+
index: suppressions.length,
|
|
130
|
+
suppression
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
export function removeSuppressionByIndex(config, index) {
|
|
134
|
+
const { suppressions } = readSuppressions(config);
|
|
135
|
+
if (!Number.isInteger(index) ||
|
|
136
|
+
index < 0 ||
|
|
137
|
+
index >= suppressions.length) {
|
|
138
|
+
throw createSuppressionManagementError("Suppression index must be an integer within range.", {
|
|
139
|
+
code: "suppression_invalid_index",
|
|
140
|
+
index
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
const suppression = suppressions[index];
|
|
144
|
+
const nextSuppressions = [
|
|
145
|
+
...suppressions.slice(0, index),
|
|
146
|
+
...suppressions.slice(index + 1)
|
|
147
|
+
];
|
|
148
|
+
return {
|
|
149
|
+
config: copyConfigWithSuppressions(config, nextSuppressions),
|
|
150
|
+
index,
|
|
151
|
+
suppression: buildRemovedSuppressionSummary(suppression)
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
export function removeSuppressionByFingerprint(config, fingerprint) {
|
|
155
|
+
const { suppressions } = readSuppressions(config);
|
|
156
|
+
const validFingerprint = validateManagedFingerprint(fingerprint);
|
|
157
|
+
const matches = findFingerprintMatches(suppressions, validFingerprint);
|
|
158
|
+
if (matches.length === 0) {
|
|
159
|
+
throw createSuppressionManagementError("Suppression fingerprint not found.", {
|
|
160
|
+
code: "suppression_fingerprint_not_found"
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
if (matches.length > 1) {
|
|
164
|
+
throw createSuppressionManagementError(`Suppression fingerprint matches multiple suppressions at indexes: ${matches.join(", ")}.`, {
|
|
165
|
+
code: "suppression_fingerprint_ambiguous",
|
|
166
|
+
indexes: matches
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
return removeSuppressionByIndex(config, matches[0]);
|
|
170
|
+
}
|
|
171
|
+
export function pruneSuppressions(config, now = new Date()) {
|
|
172
|
+
const { suppressions } = readSuppressions(config);
|
|
173
|
+
const managedSuppressions = listSuppressions(config, now);
|
|
174
|
+
const removed = managedSuppressions.filter((suppression) => suppression.status !== "active");
|
|
175
|
+
const removedIndexes = new Set(removed.map((suppression) => suppression.index));
|
|
176
|
+
const nextSuppressions = suppressions.filter((_suppression, index) => !removedIndexes.has(index));
|
|
177
|
+
return {
|
|
178
|
+
config: copyConfigWithSuppressions(config, nextSuppressions),
|
|
179
|
+
removed
|
|
180
|
+
};
|
|
181
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type SuppressionRecordField = "record" | "fingerprint" | "reason" | "expiresAt";
|
|
2
|
+
export interface ValidSuppressionRecord {
|
|
3
|
+
valid: true;
|
|
4
|
+
fingerprint: string;
|
|
5
|
+
reason: string;
|
|
6
|
+
expiresAt: string;
|
|
7
|
+
}
|
|
8
|
+
export interface InvalidSuppressionRecord {
|
|
9
|
+
valid: false;
|
|
10
|
+
field: SuppressionRecordField;
|
|
11
|
+
}
|
|
12
|
+
type ActiveSuppressionRecord = ValidSuppressionRecord & {
|
|
13
|
+
status: "active";
|
|
14
|
+
};
|
|
15
|
+
type ExpiredSuppressionRecord = ValidSuppressionRecord & {
|
|
16
|
+
status: "expired";
|
|
17
|
+
};
|
|
18
|
+
type InvalidClassifiedSuppressionRecord = InvalidSuppressionRecord & {
|
|
19
|
+
status: "invalid";
|
|
20
|
+
};
|
|
21
|
+
export type ClassifiedSuppressionRecord = ActiveSuppressionRecord | ExpiredSuppressionRecord | InvalidClassifiedSuppressionRecord;
|
|
22
|
+
export declare function isValidSuppressionFingerprint(value: unknown): value is string;
|
|
23
|
+
export declare function validateSuppressionRecord(value: unknown): ValidSuppressionRecord | InvalidSuppressionRecord;
|
|
24
|
+
export declare function isSuppressionExpired(expiresAt: string, now: Date): boolean;
|
|
25
|
+
export declare function classifySuppressionRecord(value: unknown, now: Date): ClassifiedSuppressionRecord;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
function isPlainObject(value) {
|
|
2
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
function isRealDate(value) {
|
|
5
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
|
|
6
|
+
if (!match) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
const year = Number(match[1]);
|
|
10
|
+
const month = Number(match[2]);
|
|
11
|
+
const day = Number(match[3]);
|
|
12
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
|
13
|
+
return (date.getUTCFullYear() === year &&
|
|
14
|
+
date.getUTCMonth() === month - 1 &&
|
|
15
|
+
date.getUTCDate() === day);
|
|
16
|
+
}
|
|
17
|
+
export function isValidSuppressionFingerprint(value) {
|
|
18
|
+
return typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
|
|
19
|
+
}
|
|
20
|
+
export function validateSuppressionRecord(value) {
|
|
21
|
+
if (!isPlainObject(value)) {
|
|
22
|
+
return { valid: false, field: "record" };
|
|
23
|
+
}
|
|
24
|
+
if (!isValidSuppressionFingerprint(value.fingerprint)) {
|
|
25
|
+
return { valid: false, field: "fingerprint" };
|
|
26
|
+
}
|
|
27
|
+
if (typeof value.reason !== "string" || value.reason.trim().length === 0) {
|
|
28
|
+
return { valid: false, field: "reason" };
|
|
29
|
+
}
|
|
30
|
+
if (typeof value.expiresAt !== "string" || !isRealDate(value.expiresAt)) {
|
|
31
|
+
return { valid: false, field: "expiresAt" };
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
valid: true,
|
|
35
|
+
fingerprint: value.fingerprint,
|
|
36
|
+
reason: value.reason.trim(),
|
|
37
|
+
expiresAt: value.expiresAt
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export function isSuppressionExpired(expiresAt, now) {
|
|
41
|
+
const expirationBoundary = Date.parse(`${expiresAt}T00:00:00.000Z`) + 86_400_000;
|
|
42
|
+
return now.getTime() >= expirationBoundary;
|
|
43
|
+
}
|
|
44
|
+
export function classifySuppressionRecord(value, now) {
|
|
45
|
+
const validation = validateSuppressionRecord(value);
|
|
46
|
+
if (!validation.valid) {
|
|
47
|
+
return {
|
|
48
|
+
status: "invalid",
|
|
49
|
+
...validation
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (isSuppressionExpired(validation.expiresAt, now)) {
|
|
53
|
+
return {
|
|
54
|
+
status: "expired",
|
|
55
|
+
...validation
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
status: "active",
|
|
60
|
+
...validation
|
|
61
|
+
};
|
|
62
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { CheckOptions, CheckResult } from "./domain/types.js";
|
|
2
2
|
export { applyDoctorConfig, loadDoctorConfig, type DoctorConfig } from "./core/doctor-config.js";
|
|
3
|
+
export { readRawDoctorConfig, resolveDoctorConfigPath, writeRawDoctorConfig, type LoadedDoctorConfig, type RawDoctorConfig } from "./core/doctor-config-store.js";
|
|
4
|
+
export { classifySuppressionRecord, isValidSuppressionFingerprint, isSuppressionExpired, validateSuppressionRecord, type ClassifiedSuppressionRecord, type InvalidSuppressionRecord, type SuppressionRecordField, type ValidSuppressionRecord } from "./core/suppression-record.js";
|
|
5
|
+
export { addSuppression, listSuppressions, pruneSuppressions, removeSuppressionByFingerprint, removeSuppressionByIndex, SuppressionManagementError, type ManagedSuppressionRecord, type SuppressionManagementErrorCode, type SuppressionMutationResult, type SuppressionPruneResult } from "./core/suppression-management.js";
|
|
3
6
|
export { buildFindingFingerprint, formatFindingFingerprintLine, withFindingFingerprint, withFindingFingerprints } from "./reporting/finding-fingerprint.js";
|
|
4
7
|
export { buildSecurityAudit, renderSecurityAuditJson, renderSecurityScorecard, type SecurityAudit } from "./security/security-audit.js";
|
|
5
8
|
export { buildTrustScore, renderTrustScore, renderTrustScoreJson, type BuildTrustScoreOptions, type TrustScoreReport } from "./security/trust-score.js";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { validatePlugin } from "./core/validate-plugin.js";
|
|
2
2
|
export { applyDoctorConfig, loadDoctorConfig } from "./core/doctor-config.js";
|
|
3
|
+
export { readRawDoctorConfig, resolveDoctorConfigPath, writeRawDoctorConfig } from "./core/doctor-config-store.js";
|
|
4
|
+
export { classifySuppressionRecord, isValidSuppressionFingerprint, isSuppressionExpired, validateSuppressionRecord } from "./core/suppression-record.js";
|
|
5
|
+
export { addSuppression, listSuppressions, pruneSuppressions, removeSuppressionByFingerprint, removeSuppressionByIndex, SuppressionManagementError } from "./core/suppression-management.js";
|
|
3
6
|
export { buildFindingFingerprint, formatFindingFingerprintLine, withFindingFingerprint, withFindingFingerprints } from "./reporting/finding-fingerprint.js";
|
|
4
7
|
export { buildSecurityAudit, renderSecurityAuditJson, renderSecurityScorecard } from "./security/security-audit.js";
|
|
5
8
|
export { buildTrustScore, renderTrustScore, renderTrustScoreJson } from "./security/trust-score.js";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ManagedSuppressionRecord, SuppressionMutationResult, SuppressionPruneResult } from "../core/suppression-management.js";
|
|
2
|
+
type SuppressionMutationCommand = "suppress.add" | "suppress.remove";
|
|
3
|
+
export declare function renderSuppressionList(configPath: string, suppressions: ManagedSuppressionRecord[]): string;
|
|
4
|
+
export declare function renderSuppressionListJson(configPath: string, suppressions: ManagedSuppressionRecord[]): string;
|
|
5
|
+
export declare function renderSuppressionMutation(command: SuppressionMutationCommand, configPath: string, result: SuppressionMutationResult): string;
|
|
6
|
+
export declare function renderSuppressionMutationJson(command: SuppressionMutationCommand, configPath: string, result: SuppressionMutationResult): string;
|
|
7
|
+
export declare function renderSuppressionPrune(configPath: string, result: SuppressionPruneResult, options: {
|
|
8
|
+
applied: boolean;
|
|
9
|
+
}): string;
|
|
10
|
+
export declare function renderSuppressionPruneJson(configPath: string, result: SuppressionPruneResult, options: {
|
|
11
|
+
applied: boolean;
|
|
12
|
+
}): string;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
function isObject(value) {
|
|
2
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
function isInvalidField(value) {
|
|
5
|
+
return (value === "record" ||
|
|
6
|
+
value === "fingerprint" ||
|
|
7
|
+
value === "reason" ||
|
|
8
|
+
value === "expiresAt");
|
|
9
|
+
}
|
|
10
|
+
function isValidSuppressionSummary(value) {
|
|
11
|
+
return (isObject(value) &&
|
|
12
|
+
!("invalidField" in value) &&
|
|
13
|
+
typeof value.fingerprint === "string" &&
|
|
14
|
+
typeof value.reason === "string" &&
|
|
15
|
+
value.reason.trim().length > 0 &&
|
|
16
|
+
typeof value.expiresAt === "string");
|
|
17
|
+
}
|
|
18
|
+
function buildListEntry(record) {
|
|
19
|
+
if (record.status === "invalid" && isInvalidField(record.invalidField)) {
|
|
20
|
+
return {
|
|
21
|
+
index: record.index,
|
|
22
|
+
status: record.status,
|
|
23
|
+
invalidField: record.invalidField
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
if ((record.status === "active" || record.status === "expired") &&
|
|
27
|
+
isValidSuppressionSummary(record)) {
|
|
28
|
+
return {
|
|
29
|
+
index: record.index,
|
|
30
|
+
status: record.status,
|
|
31
|
+
fingerprint: record.fingerprint,
|
|
32
|
+
reason: record.reason,
|
|
33
|
+
expiresAt: record.expiresAt
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
index: record.index,
|
|
38
|
+
status: "invalid",
|
|
39
|
+
invalidField: "record"
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function buildMutationSummary(value) {
|
|
43
|
+
if (isObject(value) && isInvalidField(value.invalidField)) {
|
|
44
|
+
return {
|
|
45
|
+
invalidField: value.invalidField
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (isValidSuppressionSummary(value)) {
|
|
49
|
+
return {
|
|
50
|
+
fingerprint: value.fingerprint,
|
|
51
|
+
reason: value.reason,
|
|
52
|
+
expiresAt: value.expiresAt
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return { invalidField: "record" };
|
|
56
|
+
}
|
|
57
|
+
function getActionLabel(command) {
|
|
58
|
+
return command === "suppress.add" ? "Added" : "Removed";
|
|
59
|
+
}
|
|
60
|
+
export function renderSuppressionList(configPath, suppressions) {
|
|
61
|
+
const lines = [
|
|
62
|
+
`Config: ${configPath}`,
|
|
63
|
+
`Total suppressions: ${suppressions.length}`
|
|
64
|
+
];
|
|
65
|
+
if (suppressions.length === 0) {
|
|
66
|
+
lines.push("", "No suppressions.");
|
|
67
|
+
return lines.join("\n");
|
|
68
|
+
}
|
|
69
|
+
lines.push("", "Suppressions", "------------");
|
|
70
|
+
for (const suppression of suppressions) {
|
|
71
|
+
const entry = buildListEntry(suppression);
|
|
72
|
+
if (entry.status === "invalid") {
|
|
73
|
+
lines.push(`[${entry.index}] INVALID ${entry.invalidField}`);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
lines.push(`[${entry.index}] ${entry.status.toUpperCase()} ${entry.fingerprint}`);
|
|
77
|
+
lines.push(` Reason: ${entry.reason}`);
|
|
78
|
+
lines.push(` Expires: ${entry.expiresAt}`);
|
|
79
|
+
}
|
|
80
|
+
return lines.join("\n");
|
|
81
|
+
}
|
|
82
|
+
export function renderSuppressionListJson(configPath, suppressions) {
|
|
83
|
+
return JSON.stringify({
|
|
84
|
+
schemaVersion: "1.0.0",
|
|
85
|
+
kind: "doctor.suppress.list",
|
|
86
|
+
command: "suppress.list",
|
|
87
|
+
configPath,
|
|
88
|
+
suppressions: suppressions.map(buildListEntry)
|
|
89
|
+
}, null, 2);
|
|
90
|
+
}
|
|
91
|
+
export function renderSuppressionMutation(command, configPath, result) {
|
|
92
|
+
const summary = buildMutationSummary(result.suppression);
|
|
93
|
+
const lines = [
|
|
94
|
+
`Action: ${getActionLabel(command)}`,
|
|
95
|
+
`Config: ${configPath}`,
|
|
96
|
+
`Index: ${result.index}`
|
|
97
|
+
];
|
|
98
|
+
lines.push("");
|
|
99
|
+
if ("invalidField" in summary) {
|
|
100
|
+
lines.push(`Invalid field: ${summary.invalidField}`);
|
|
101
|
+
return lines.join("\n");
|
|
102
|
+
}
|
|
103
|
+
lines.push(`Fingerprint: ${summary.fingerprint}`);
|
|
104
|
+
lines.push(`Reason: ${summary.reason}`);
|
|
105
|
+
lines.push(`Expires: ${summary.expiresAt}`);
|
|
106
|
+
return lines.join("\n");
|
|
107
|
+
}
|
|
108
|
+
export function renderSuppressionMutationJson(command, configPath, result) {
|
|
109
|
+
return JSON.stringify({
|
|
110
|
+
schemaVersion: "1.0.0",
|
|
111
|
+
kind: `doctor.${command}`,
|
|
112
|
+
command,
|
|
113
|
+
configPath,
|
|
114
|
+
index: result.index,
|
|
115
|
+
suppression: buildMutationSummary(result.suppression)
|
|
116
|
+
}, null, 2);
|
|
117
|
+
}
|
|
118
|
+
export function renderSuppressionPrune(configPath, result, options) {
|
|
119
|
+
const lines = [
|
|
120
|
+
"Action: Prune",
|
|
121
|
+
`Mode: ${options.applied ? "apply" : "dry-run"}`,
|
|
122
|
+
`Config: ${configPath}`,
|
|
123
|
+
`Removed suppressions: ${result.removed.length}`
|
|
124
|
+
];
|
|
125
|
+
if (result.removed.length === 0) {
|
|
126
|
+
lines.push("", "No expired or invalid suppressions to prune.");
|
|
127
|
+
return lines.join("\n");
|
|
128
|
+
}
|
|
129
|
+
lines.push("", "Removed", "-------");
|
|
130
|
+
for (const suppression of result.removed) {
|
|
131
|
+
const entry = buildListEntry(suppression);
|
|
132
|
+
if (entry.status === "invalid") {
|
|
133
|
+
lines.push(`[${entry.index}] INVALID ${entry.invalidField}`);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
lines.push(`[${entry.index}] ${entry.status.toUpperCase()} ${entry.fingerprint}`);
|
|
137
|
+
lines.push(` Reason: ${entry.reason}`);
|
|
138
|
+
lines.push(` Expires: ${entry.expiresAt}`);
|
|
139
|
+
}
|
|
140
|
+
return lines.join("\n");
|
|
141
|
+
}
|
|
142
|
+
export function renderSuppressionPruneJson(configPath, result, options) {
|
|
143
|
+
return JSON.stringify({
|
|
144
|
+
schemaVersion: "1.0.0",
|
|
145
|
+
kind: "doctor.suppress.prune",
|
|
146
|
+
command: "suppress.prune",
|
|
147
|
+
configPath,
|
|
148
|
+
applied: options.applied,
|
|
149
|
+
removedCount: result.removed.length,
|
|
150
|
+
removed: result.removed.map(buildListEntry)
|
|
151
|
+
}, null, 2);
|
|
152
|
+
}
|
package/dist/run-cli.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { runCheck } from "./index.js";
|
|
2
|
+
import { writeRawDoctorConfig } from "./index.js";
|
|
2
3
|
export interface CliIo {
|
|
3
4
|
writeStdout(message: string): void;
|
|
4
5
|
writeStderr(message: string): void;
|
|
@@ -13,6 +14,8 @@ export interface CliTerminalContext {
|
|
|
13
14
|
export interface RunCliOptions {
|
|
14
15
|
terminalContext?: CliTerminalContext;
|
|
15
16
|
runCheckImpl?: typeof runCheck;
|
|
17
|
+
writeRawDoctorConfigImpl?: typeof writeRawDoctorConfig;
|
|
18
|
+
now?: () => Date;
|
|
16
19
|
releaseAssetUploadImpl?: (args: string[]) => Promise<void>;
|
|
17
20
|
resolveLatestVersion?: () => Promise<string>;
|
|
18
21
|
}
|