@subhamxcod/apiguard 1.0.2
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/LICENSE +21 -0
- package/README.md +141 -0
- package/dist/chunk-RQVNHPHC.js +3848 -0
- package/dist/cli/main.cjs +4336 -0
- package/dist/cli/main.d.cts +1 -0
- package/dist/cli/main.d.ts +1 -0
- package/dist/cli/main.js +406 -0
- package/dist/index.cjs +336 -0
- package/dist/index.d.cts +204 -0
- package/dist/index.d.ts +204 -0
- package/dist/index.js +22 -0
- package/dist/markdown-J2EC3VFS.js +74 -0
- package/package.json +84 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/** Zod config schema per spec §14.3. Keys use the kebab-case spelling from the spec. */
|
|
2
|
+
|
|
3
|
+
/** Internal, camelCase-shaped resolved configuration consumed by the engine/CLI. */
|
|
4
|
+
interface ResolvedConfig {
|
|
5
|
+
schemaVersion: 1;
|
|
6
|
+
rules: Record<string, 'error' | 'warning' | 'info' | 'off'>;
|
|
7
|
+
ignore: {
|
|
8
|
+
paths: string[];
|
|
9
|
+
operations: string[];
|
|
10
|
+
schemas: string[];
|
|
11
|
+
changes: string[];
|
|
12
|
+
showIgnored: boolean;
|
|
13
|
+
};
|
|
14
|
+
output: {
|
|
15
|
+
format: 'terminal' | 'json' | 'markdown';
|
|
16
|
+
includeNonBreaking: boolean;
|
|
17
|
+
color: 'auto' | 'always' | 'never';
|
|
18
|
+
};
|
|
19
|
+
failOn: 'error' | 'warning' | 'never';
|
|
20
|
+
versioning: {
|
|
21
|
+
suggest: boolean;
|
|
22
|
+
};
|
|
23
|
+
loader: {
|
|
24
|
+
allowRemoteRefs: boolean;
|
|
25
|
+
};
|
|
26
|
+
github: {
|
|
27
|
+
comment: boolean;
|
|
28
|
+
checkRun: boolean;
|
|
29
|
+
updateExistingComment: boolean;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Core change taxonomy and change records per spec §4/§8. */
|
|
34
|
+
type Severity = 'error' | 'warning' | 'info';
|
|
35
|
+
type ChangeKind = 'addition' | 'removal' | 'modification' | 'relaxation' | 'documentation';
|
|
36
|
+
/** HTTP methods recognized in location.method. */
|
|
37
|
+
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS' | 'TRACE';
|
|
38
|
+
/** Where a change was found, per spec §4 taxonomy. */
|
|
39
|
+
type Context = 'api' | 'operation' | 'request-body' | 'response-body' | 'request-header' | 'response-header' | 'parameter' | 'component' | 'security' | 'callback' | 'webhook';
|
|
40
|
+
interface ChangeLocation {
|
|
41
|
+
/** URL path template, e.g. "/users/{id}"; '/' for api-level, '' for components. */
|
|
42
|
+
path: string;
|
|
43
|
+
method?: HttpMethod | string;
|
|
44
|
+
operationId?: string;
|
|
45
|
+
/** JSON Pointer into the OLD document (after deref where applicable). */
|
|
46
|
+
pointerOld: string;
|
|
47
|
+
/** JSON Pointer into the NEW document. */
|
|
48
|
+
pointerNew: string;
|
|
49
|
+
context: Context;
|
|
50
|
+
/** When media-type relevant, e.g. "application/json". */
|
|
51
|
+
mediaType?: string;
|
|
52
|
+
/** Response context status code, e.g. "200" | "default". */
|
|
53
|
+
statusCode?: string;
|
|
54
|
+
/** When context === 'component'. */
|
|
55
|
+
componentName?: string;
|
|
56
|
+
/** Component rules: number of operation usages found. */
|
|
57
|
+
usageCount?: number;
|
|
58
|
+
}
|
|
59
|
+
interface ApiChange {
|
|
60
|
+
/** stable id = sha256(...).slice(0,16) per spec §12. */
|
|
61
|
+
id: string;
|
|
62
|
+
ruleId: string;
|
|
63
|
+
severity: Severity;
|
|
64
|
+
kind: ChangeKind;
|
|
65
|
+
/** Derived: severity === 'error'. Stored per spec §8. */
|
|
66
|
+
breaking: boolean;
|
|
67
|
+
location: ChangeLocation;
|
|
68
|
+
/** Human sentence, stable template. */
|
|
69
|
+
message: string;
|
|
70
|
+
/** Required for every change. */
|
|
71
|
+
suggestion: string;
|
|
72
|
+
/** Compact raw values (never full subtrees) for the JSON reporter. */
|
|
73
|
+
oldValue?: unknown;
|
|
74
|
+
newValue?: unknown;
|
|
75
|
+
/** True only when show-ignored rendering includes a suppressed change. */
|
|
76
|
+
ignored?: boolean;
|
|
77
|
+
}
|
|
78
|
+
interface DiffSummary {
|
|
79
|
+
total: number;
|
|
80
|
+
bySeverity: Record<Severity, number>;
|
|
81
|
+
byKind: Record<ChangeKind, number>;
|
|
82
|
+
/** Count of ignored changes (when show-ignored enabled). */
|
|
83
|
+
ignored: number;
|
|
84
|
+
}
|
|
85
|
+
interface SemverAdvice {
|
|
86
|
+
bump: 'major' | 'minor' | 'patch' | 'none';
|
|
87
|
+
reason: string;
|
|
88
|
+
triggers: string[];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Per-spec metadata used by reporters, per spec §8 DiffReport sides. */
|
|
92
|
+
interface SpecMeta {
|
|
93
|
+
source: string;
|
|
94
|
+
openapiVersion: string;
|
|
95
|
+
title: string;
|
|
96
|
+
sha256: string;
|
|
97
|
+
}
|
|
98
|
+
/** Full diff report — the machine contract (§15.1). Field order matters. */
|
|
99
|
+
interface DiffReport {
|
|
100
|
+
schemaVersion: '1.0';
|
|
101
|
+
tool: {
|
|
102
|
+
name: 'apiguard';
|
|
103
|
+
version: string;
|
|
104
|
+
};
|
|
105
|
+
baseline: SpecMeta;
|
|
106
|
+
current: SpecMeta;
|
|
107
|
+
summary: DiffSummary;
|
|
108
|
+
changes: ApiChange[];
|
|
109
|
+
semver: SemverAdvice;
|
|
110
|
+
/** Present only when show-ignored is enabled. */
|
|
111
|
+
ignored?: ApiChange[];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
interface AnalyzeOptions {
|
|
115
|
+
/** Explicit config path; otherwise discovery per §14.1. */
|
|
116
|
+
configPath?: string;
|
|
117
|
+
/** Pre-resolved config (used by the Action); overrides configPath. */
|
|
118
|
+
config?: ResolvedConfig;
|
|
119
|
+
/** Show changes suppressed by ignore rules. */
|
|
120
|
+
showIgnored?: boolean;
|
|
121
|
+
}
|
|
122
|
+
interface AnalyzeResult {
|
|
123
|
+
report: DiffReport;
|
|
124
|
+
ignored: ApiChange[];
|
|
125
|
+
}
|
|
126
|
+
/** Run the full diff pipeline over two spec files. */
|
|
127
|
+
declare function analyze(baselinePath: string, currentPath: string, options?: AnalyzeOptions): Promise<AnalyzeResult>;
|
|
128
|
+
|
|
129
|
+
interface ValidationIssue {
|
|
130
|
+
/** Severity class of the issue. */
|
|
131
|
+
level: 'error' | 'warning';
|
|
132
|
+
/** e.g. "spec.yaml" — rendered as `file: message` (line when known). */
|
|
133
|
+
file: string;
|
|
134
|
+
message: string;
|
|
135
|
+
}
|
|
136
|
+
interface ValidationResult {
|
|
137
|
+
issues: ValidationIssue[];
|
|
138
|
+
/** true when no structural (level=error) issues were found. */
|
|
139
|
+
ok: boolean;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
type JsonValue = unknown;
|
|
143
|
+
/** Resolved spec bundle: parsed document plus provenance metadata. */
|
|
144
|
+
interface LoadedSpec {
|
|
145
|
+
/** Parsed document as plain JSON-compatible data. */
|
|
146
|
+
document: JsonValue;
|
|
147
|
+
/** Human-readable source label (file path or ref), used in reports. */
|
|
148
|
+
source: string;
|
|
149
|
+
/** OpenAPI version string as declared in the document (e.g. "3.0.3", "3.1.0"); '' when absent. */
|
|
150
|
+
openapiVersion: string;
|
|
151
|
+
/** Document title or '' when absent. */
|
|
152
|
+
title: string;
|
|
153
|
+
/** sha256 hex digest of the normalized document bytes. */
|
|
154
|
+
sha256: string;
|
|
155
|
+
}
|
|
156
|
+
/** Internal representation of a normalized spec used by the diff engine. */
|
|
157
|
+
interface NormalizedSpec extends LoadedSpec {
|
|
158
|
+
/** The normalized document (types as arrays, exclusiveMin/Max numeric, etc.). */
|
|
159
|
+
normalized: JsonValue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
interface LoadedConfig {
|
|
163
|
+
config: ResolvedConfig;
|
|
164
|
+
/** Path of the file the config came from ('' when defaults only). */
|
|
165
|
+
sourcePath: string;
|
|
166
|
+
/** Deprecation warnings collected during load (printed to stderr by the CLI). */
|
|
167
|
+
warnings: string[];
|
|
168
|
+
}
|
|
169
|
+
/** Load config from an explicit path (missing file → ConfigError, exit 2). */
|
|
170
|
+
declare function loadConfigFrom(explicitPath: string): LoadedConfig;
|
|
171
|
+
/** Discover config per §14.1; returns defaults when nothing is found. */
|
|
172
|
+
declare function discoverAndLoadConfig(opts?: {
|
|
173
|
+
explicit?: string;
|
|
174
|
+
env?: string;
|
|
175
|
+
cwd?: string;
|
|
176
|
+
}): LoadedConfig;
|
|
177
|
+
|
|
178
|
+
/** Semver advisor per spec §16 — pure function over the change set. */
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Decision table (§16):
|
|
182
|
+
* 1. any error → major (reason lists top ruleIds by count)
|
|
183
|
+
* 2. else any addition|removal|modification OR any warning → minor
|
|
184
|
+
* 3. else any relaxation|documentation info → patch
|
|
185
|
+
* 4. else none
|
|
186
|
+
*/
|
|
187
|
+
declare function suggestVersion(changes: ApiChange[]): SemverAdvice;
|
|
188
|
+
|
|
189
|
+
/** Public library API per spec §18. Pure; no stdout; no process.exit. */
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Load + validate a spec (§18 `validateSpec`). Throws SpecLoadError (exit-3 class) on
|
|
193
|
+
* structural problems; returns the semantic issue list otherwise.
|
|
194
|
+
*/
|
|
195
|
+
declare function validateSpec(specPath: string, options?: {
|
|
196
|
+
loader?: {
|
|
197
|
+
allowRemoteRefs?: boolean;
|
|
198
|
+
};
|
|
199
|
+
}): Promise<{
|
|
200
|
+
spec: NormalizedSpec;
|
|
201
|
+
validation: ValidationResult;
|
|
202
|
+
}>;
|
|
203
|
+
|
|
204
|
+
export { type AnalyzeOptions, type AnalyzeResult, type ApiChange, type ChangeKind, type ChangeLocation, type Context, type DiffReport, type DiffSummary, type LoadedConfig, type LoadedSpec, type NormalizedSpec, type ResolvedConfig, type SemverAdvice, type Severity, type SpecMeta, type ValidationIssue, type ValidationResult, analyze, discoverAndLoadConfig, loadConfigFrom, suggestVersion, validateSpec };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/** Zod config schema per spec §14.3. Keys use the kebab-case spelling from the spec. */
|
|
2
|
+
|
|
3
|
+
/** Internal, camelCase-shaped resolved configuration consumed by the engine/CLI. */
|
|
4
|
+
interface ResolvedConfig {
|
|
5
|
+
schemaVersion: 1;
|
|
6
|
+
rules: Record<string, 'error' | 'warning' | 'info' | 'off'>;
|
|
7
|
+
ignore: {
|
|
8
|
+
paths: string[];
|
|
9
|
+
operations: string[];
|
|
10
|
+
schemas: string[];
|
|
11
|
+
changes: string[];
|
|
12
|
+
showIgnored: boolean;
|
|
13
|
+
};
|
|
14
|
+
output: {
|
|
15
|
+
format: 'terminal' | 'json' | 'markdown';
|
|
16
|
+
includeNonBreaking: boolean;
|
|
17
|
+
color: 'auto' | 'always' | 'never';
|
|
18
|
+
};
|
|
19
|
+
failOn: 'error' | 'warning' | 'never';
|
|
20
|
+
versioning: {
|
|
21
|
+
suggest: boolean;
|
|
22
|
+
};
|
|
23
|
+
loader: {
|
|
24
|
+
allowRemoteRefs: boolean;
|
|
25
|
+
};
|
|
26
|
+
github: {
|
|
27
|
+
comment: boolean;
|
|
28
|
+
checkRun: boolean;
|
|
29
|
+
updateExistingComment: boolean;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Core change taxonomy and change records per spec §4/§8. */
|
|
34
|
+
type Severity = 'error' | 'warning' | 'info';
|
|
35
|
+
type ChangeKind = 'addition' | 'removal' | 'modification' | 'relaxation' | 'documentation';
|
|
36
|
+
/** HTTP methods recognized in location.method. */
|
|
37
|
+
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS' | 'TRACE';
|
|
38
|
+
/** Where a change was found, per spec §4 taxonomy. */
|
|
39
|
+
type Context = 'api' | 'operation' | 'request-body' | 'response-body' | 'request-header' | 'response-header' | 'parameter' | 'component' | 'security' | 'callback' | 'webhook';
|
|
40
|
+
interface ChangeLocation {
|
|
41
|
+
/** URL path template, e.g. "/users/{id}"; '/' for api-level, '' for components. */
|
|
42
|
+
path: string;
|
|
43
|
+
method?: HttpMethod | string;
|
|
44
|
+
operationId?: string;
|
|
45
|
+
/** JSON Pointer into the OLD document (after deref where applicable). */
|
|
46
|
+
pointerOld: string;
|
|
47
|
+
/** JSON Pointer into the NEW document. */
|
|
48
|
+
pointerNew: string;
|
|
49
|
+
context: Context;
|
|
50
|
+
/** When media-type relevant, e.g. "application/json". */
|
|
51
|
+
mediaType?: string;
|
|
52
|
+
/** Response context status code, e.g. "200" | "default". */
|
|
53
|
+
statusCode?: string;
|
|
54
|
+
/** When context === 'component'. */
|
|
55
|
+
componentName?: string;
|
|
56
|
+
/** Component rules: number of operation usages found. */
|
|
57
|
+
usageCount?: number;
|
|
58
|
+
}
|
|
59
|
+
interface ApiChange {
|
|
60
|
+
/** stable id = sha256(...).slice(0,16) per spec §12. */
|
|
61
|
+
id: string;
|
|
62
|
+
ruleId: string;
|
|
63
|
+
severity: Severity;
|
|
64
|
+
kind: ChangeKind;
|
|
65
|
+
/** Derived: severity === 'error'. Stored per spec §8. */
|
|
66
|
+
breaking: boolean;
|
|
67
|
+
location: ChangeLocation;
|
|
68
|
+
/** Human sentence, stable template. */
|
|
69
|
+
message: string;
|
|
70
|
+
/** Required for every change. */
|
|
71
|
+
suggestion: string;
|
|
72
|
+
/** Compact raw values (never full subtrees) for the JSON reporter. */
|
|
73
|
+
oldValue?: unknown;
|
|
74
|
+
newValue?: unknown;
|
|
75
|
+
/** True only when show-ignored rendering includes a suppressed change. */
|
|
76
|
+
ignored?: boolean;
|
|
77
|
+
}
|
|
78
|
+
interface DiffSummary {
|
|
79
|
+
total: number;
|
|
80
|
+
bySeverity: Record<Severity, number>;
|
|
81
|
+
byKind: Record<ChangeKind, number>;
|
|
82
|
+
/** Count of ignored changes (when show-ignored enabled). */
|
|
83
|
+
ignored: number;
|
|
84
|
+
}
|
|
85
|
+
interface SemverAdvice {
|
|
86
|
+
bump: 'major' | 'minor' | 'patch' | 'none';
|
|
87
|
+
reason: string;
|
|
88
|
+
triggers: string[];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Per-spec metadata used by reporters, per spec §8 DiffReport sides. */
|
|
92
|
+
interface SpecMeta {
|
|
93
|
+
source: string;
|
|
94
|
+
openapiVersion: string;
|
|
95
|
+
title: string;
|
|
96
|
+
sha256: string;
|
|
97
|
+
}
|
|
98
|
+
/** Full diff report — the machine contract (§15.1). Field order matters. */
|
|
99
|
+
interface DiffReport {
|
|
100
|
+
schemaVersion: '1.0';
|
|
101
|
+
tool: {
|
|
102
|
+
name: 'apiguard';
|
|
103
|
+
version: string;
|
|
104
|
+
};
|
|
105
|
+
baseline: SpecMeta;
|
|
106
|
+
current: SpecMeta;
|
|
107
|
+
summary: DiffSummary;
|
|
108
|
+
changes: ApiChange[];
|
|
109
|
+
semver: SemverAdvice;
|
|
110
|
+
/** Present only when show-ignored is enabled. */
|
|
111
|
+
ignored?: ApiChange[];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
interface AnalyzeOptions {
|
|
115
|
+
/** Explicit config path; otherwise discovery per §14.1. */
|
|
116
|
+
configPath?: string;
|
|
117
|
+
/** Pre-resolved config (used by the Action); overrides configPath. */
|
|
118
|
+
config?: ResolvedConfig;
|
|
119
|
+
/** Show changes suppressed by ignore rules. */
|
|
120
|
+
showIgnored?: boolean;
|
|
121
|
+
}
|
|
122
|
+
interface AnalyzeResult {
|
|
123
|
+
report: DiffReport;
|
|
124
|
+
ignored: ApiChange[];
|
|
125
|
+
}
|
|
126
|
+
/** Run the full diff pipeline over two spec files. */
|
|
127
|
+
declare function analyze(baselinePath: string, currentPath: string, options?: AnalyzeOptions): Promise<AnalyzeResult>;
|
|
128
|
+
|
|
129
|
+
interface ValidationIssue {
|
|
130
|
+
/** Severity class of the issue. */
|
|
131
|
+
level: 'error' | 'warning';
|
|
132
|
+
/** e.g. "spec.yaml" — rendered as `file: message` (line when known). */
|
|
133
|
+
file: string;
|
|
134
|
+
message: string;
|
|
135
|
+
}
|
|
136
|
+
interface ValidationResult {
|
|
137
|
+
issues: ValidationIssue[];
|
|
138
|
+
/** true when no structural (level=error) issues were found. */
|
|
139
|
+
ok: boolean;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
type JsonValue = unknown;
|
|
143
|
+
/** Resolved spec bundle: parsed document plus provenance metadata. */
|
|
144
|
+
interface LoadedSpec {
|
|
145
|
+
/** Parsed document as plain JSON-compatible data. */
|
|
146
|
+
document: JsonValue;
|
|
147
|
+
/** Human-readable source label (file path or ref), used in reports. */
|
|
148
|
+
source: string;
|
|
149
|
+
/** OpenAPI version string as declared in the document (e.g. "3.0.3", "3.1.0"); '' when absent. */
|
|
150
|
+
openapiVersion: string;
|
|
151
|
+
/** Document title or '' when absent. */
|
|
152
|
+
title: string;
|
|
153
|
+
/** sha256 hex digest of the normalized document bytes. */
|
|
154
|
+
sha256: string;
|
|
155
|
+
}
|
|
156
|
+
/** Internal representation of a normalized spec used by the diff engine. */
|
|
157
|
+
interface NormalizedSpec extends LoadedSpec {
|
|
158
|
+
/** The normalized document (types as arrays, exclusiveMin/Max numeric, etc.). */
|
|
159
|
+
normalized: JsonValue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
interface LoadedConfig {
|
|
163
|
+
config: ResolvedConfig;
|
|
164
|
+
/** Path of the file the config came from ('' when defaults only). */
|
|
165
|
+
sourcePath: string;
|
|
166
|
+
/** Deprecation warnings collected during load (printed to stderr by the CLI). */
|
|
167
|
+
warnings: string[];
|
|
168
|
+
}
|
|
169
|
+
/** Load config from an explicit path (missing file → ConfigError, exit 2). */
|
|
170
|
+
declare function loadConfigFrom(explicitPath: string): LoadedConfig;
|
|
171
|
+
/** Discover config per §14.1; returns defaults when nothing is found. */
|
|
172
|
+
declare function discoverAndLoadConfig(opts?: {
|
|
173
|
+
explicit?: string;
|
|
174
|
+
env?: string;
|
|
175
|
+
cwd?: string;
|
|
176
|
+
}): LoadedConfig;
|
|
177
|
+
|
|
178
|
+
/** Semver advisor per spec §16 — pure function over the change set. */
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Decision table (§16):
|
|
182
|
+
* 1. any error → major (reason lists top ruleIds by count)
|
|
183
|
+
* 2. else any addition|removal|modification OR any warning → minor
|
|
184
|
+
* 3. else any relaxation|documentation info → patch
|
|
185
|
+
* 4. else none
|
|
186
|
+
*/
|
|
187
|
+
declare function suggestVersion(changes: ApiChange[]): SemverAdvice;
|
|
188
|
+
|
|
189
|
+
/** Public library API per spec §18. Pure; no stdout; no process.exit. */
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Load + validate a spec (§18 `validateSpec`). Throws SpecLoadError (exit-3 class) on
|
|
193
|
+
* structural problems; returns the semantic issue list otherwise.
|
|
194
|
+
*/
|
|
195
|
+
declare function validateSpec(specPath: string, options?: {
|
|
196
|
+
loader?: {
|
|
197
|
+
allowRemoteRefs?: boolean;
|
|
198
|
+
};
|
|
199
|
+
}): Promise<{
|
|
200
|
+
spec: NormalizedSpec;
|
|
201
|
+
validation: ValidationResult;
|
|
202
|
+
}>;
|
|
203
|
+
|
|
204
|
+
export { type AnalyzeOptions, type AnalyzeResult, type ApiChange, type ChangeKind, type ChangeLocation, type Context, type DiffReport, type DiffSummary, type LoadedConfig, type LoadedSpec, type NormalizedSpec, type ResolvedConfig, type SemverAdvice, type Severity, type SpecMeta, type ValidationIssue, type ValidationResult, analyze, discoverAndLoadConfig, loadConfigFrom, suggestVersion, validateSpec };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
analyze,
|
|
3
|
+
discoverAndLoadConfig,
|
|
4
|
+
loadConfigFrom,
|
|
5
|
+
loadSpec,
|
|
6
|
+
suggestVersion,
|
|
7
|
+
validateSemantics
|
|
8
|
+
} from "./chunk-RQVNHPHC.js";
|
|
9
|
+
|
|
10
|
+
// src/index.ts
|
|
11
|
+
async function validateSpec(specPath, options) {
|
|
12
|
+
const spec = await loadSpec(specPath, { loader: options?.loader });
|
|
13
|
+
const validation = validateSemantics(spec.document, specPath);
|
|
14
|
+
return { spec, validation };
|
|
15
|
+
}
|
|
16
|
+
export {
|
|
17
|
+
analyze,
|
|
18
|
+
discoverAndLoadConfig,
|
|
19
|
+
loadConfigFrom,
|
|
20
|
+
suggestVersion,
|
|
21
|
+
validateSpec
|
|
22
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// src/reporters/markdown.ts
|
|
2
|
+
var LABEL = {
|
|
3
|
+
error: "Breaking (errors)",
|
|
4
|
+
warning: "Warnings",
|
|
5
|
+
info: "Informational"
|
|
6
|
+
};
|
|
7
|
+
function truncate(s, max = 140) {
|
|
8
|
+
return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
|
|
9
|
+
}
|
|
10
|
+
function endpointOf(change) {
|
|
11
|
+
const loc = change.location;
|
|
12
|
+
if (loc.method) return `${loc.method} ${loc.path}`;
|
|
13
|
+
if (loc.path && loc.path !== "/") return loc.path;
|
|
14
|
+
if (loc.componentName) return loc.componentName;
|
|
15
|
+
return "API";
|
|
16
|
+
}
|
|
17
|
+
function renderMarkdown(report) {
|
|
18
|
+
const lines = [];
|
|
19
|
+
const breaking = report.summary.bySeverity.error;
|
|
20
|
+
lines.push(
|
|
21
|
+
breaking > 0 ? `## \u274C API Guard: ${breaking} breaking change${breaking === 1 ? "" : "s"} found` : "## \u2705 API Guard: no breaking changes"
|
|
22
|
+
);
|
|
23
|
+
lines.push("");
|
|
24
|
+
lines.push("| Rule | Severity | Endpoint | What changed | Suggestion |");
|
|
25
|
+
lines.push("|---|---|---|---|---|");
|
|
26
|
+
for (const c of report.changes) {
|
|
27
|
+
lines.push(
|
|
28
|
+
`| \`${c.ruleId}\` | ${c.severity} | ${endpointOf(c)} | ${truncate(c.message.replace(/\|/g, "\\|"))} | ${truncate(c.suggestion.replace(/\|/g, "\\|"))} |`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
for (const sev of ["error", "warning", "info"]) {
|
|
32
|
+
const group = report.changes.filter((c) => c.severity === sev);
|
|
33
|
+
if (group.length === 0) continue;
|
|
34
|
+
lines.push("");
|
|
35
|
+
lines.push(`<details>`);
|
|
36
|
+
lines.push(`<summary>${LABEL[sev]} (${group.length})</summary>`);
|
|
37
|
+
lines.push("");
|
|
38
|
+
for (const c of group) {
|
|
39
|
+
lines.push(`#### \`${c.ruleId}\` \u2014 ${endpointOf(c)}`);
|
|
40
|
+
lines.push("");
|
|
41
|
+
lines.push(c.message);
|
|
42
|
+
lines.push("");
|
|
43
|
+
lines.push(`> **Suggestion:** ${c.suggestion}`);
|
|
44
|
+
lines.push("");
|
|
45
|
+
}
|
|
46
|
+
lines.push(`</details>`);
|
|
47
|
+
}
|
|
48
|
+
if (report.ignored && report.ignored.length > 0) {
|
|
49
|
+
lines.push("");
|
|
50
|
+
lines.push(`<details>`);
|
|
51
|
+
lines.push(`<summary>Ignored changes (${report.ignored.length})</summary>`);
|
|
52
|
+
lines.push("");
|
|
53
|
+
for (const c of report.ignored) {
|
|
54
|
+
lines.push(`- [ignored] \`${c.ruleId}\` \u2014 ${c.message}`);
|
|
55
|
+
}
|
|
56
|
+
lines.push("");
|
|
57
|
+
lines.push(`</details>`);
|
|
58
|
+
}
|
|
59
|
+
lines.push("");
|
|
60
|
+
lines.push(
|
|
61
|
+
`<sub>apiguard v${report.tool.version} \xB7 baseline ${report.baseline.sha256.slice(0, 8)} \u2192 current ${report.current.sha256.slice(0, 8)}</sub>`
|
|
62
|
+
);
|
|
63
|
+
return lines.join("\n");
|
|
64
|
+
}
|
|
65
|
+
function markdownBumpLine(report) {
|
|
66
|
+
const bump = report.semver.bump;
|
|
67
|
+
if (bump === "none") return null;
|
|
68
|
+
const label = bump === "major" ? "MAJOR" : bump === "minor" ? "MINOR" : "PATCH";
|
|
69
|
+
return `Suggested version bump: ${label}`;
|
|
70
|
+
}
|
|
71
|
+
export {
|
|
72
|
+
markdownBumpLine,
|
|
73
|
+
renderMarkdown
|
|
74
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@subhamxcod/apiguard",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Detect breaking changes between OpenAPI specifications (3.0 & 3.1) so API contracts never silently break downstream consumers.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=20"
|
|
9
|
+
},
|
|
10
|
+
"packageManager": "pnpm@12.3.4",
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"pnpm": {
|
|
15
|
+
"onlyBuiltDependencies": [
|
|
16
|
+
"esbuild"
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/subhamxaura/apiguard.git"
|
|
22
|
+
},
|
|
23
|
+
"bin": {
|
|
24
|
+
"apiguard": "./dist/cli/main.js"
|
|
25
|
+
},
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"import": "./dist/index.js",
|
|
30
|
+
"require": "./dist/index.cjs"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"README.md",
|
|
36
|
+
"LICENSE"
|
|
37
|
+
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsup",
|
|
40
|
+
"build:action": "esbuild src/github/action-entry.ts --bundle --platform=node --target=node20 --outfile=dist/index.cjs --minify",
|
|
41
|
+
"lint": "eslint .",
|
|
42
|
+
"lint:fix": "eslint . --fix",
|
|
43
|
+
"format:check": "prettier --check .",
|
|
44
|
+
"typecheck": "tsc --noEmit",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"test:e2e": "vitest run --config vitest.e2e.config.ts",
|
|
47
|
+
"coverage": "vitest run --coverage",
|
|
48
|
+
"docs:rules": "tsx scripts/generate-rules-docs.ts",
|
|
49
|
+
"bench": "tsx scripts/bench.ts",
|
|
50
|
+
"gen-fixtures": "tsx scripts/gen-fixtures.ts"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@actions/core": "^1.11.1",
|
|
54
|
+
"@actions/github": "^6.0.1",
|
|
55
|
+
"@apidevtools/swagger-parser": "^12.1.0",
|
|
56
|
+
"commander": "^14.0.2",
|
|
57
|
+
"micromatch": "^4.0.8",
|
|
58
|
+
"yaml": "^2.8.1",
|
|
59
|
+
"zod": "^4.1.13"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@eslint/js": "^10.0.1",
|
|
63
|
+
"@semantic-release/changelog": "^7.0.0",
|
|
64
|
+
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
65
|
+
"@semantic-release/exec": "^7.1.0",
|
|
66
|
+
"@semantic-release/git": "^11.0.1",
|
|
67
|
+
"@semantic-release/github": "^12.0.9",
|
|
68
|
+
"@semantic-release/npm": "^13.1.5",
|
|
69
|
+
"@semantic-release/release-notes-generator": "^14.1.1",
|
|
70
|
+
"@types/micromatch": "^4.0.10",
|
|
71
|
+
"@types/node": "^24.9.1",
|
|
72
|
+
"@vitest/coverage-v8": "^4.0.3",
|
|
73
|
+
"esbuild": "^0.27.0",
|
|
74
|
+
"eslint": "^9.39.0",
|
|
75
|
+
"eslint-config-prettier": "^10.1.8",
|
|
76
|
+
"prettier": "^3.6.2",
|
|
77
|
+
"semantic-release": "^25.0.9",
|
|
78
|
+
"tsup": "^8.5.0",
|
|
79
|
+
"tsx": "^4.20.6",
|
|
80
|
+
"typescript": "^5.9.3",
|
|
81
|
+
"typescript-eslint": "^8.46.2",
|
|
82
|
+
"vitest": "^4.0.3"
|
|
83
|
+
}
|
|
84
|
+
}
|