@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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli/main.js
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ApiguardError,
|
|
4
|
+
CliUsageError,
|
|
5
|
+
ExitCode,
|
|
6
|
+
SpecValidationError,
|
|
7
|
+
TOOL_VERSION,
|
|
8
|
+
analyze,
|
|
9
|
+
discoverAndLoadConfig,
|
|
10
|
+
errorMessage,
|
|
11
|
+
formatIssues,
|
|
12
|
+
loadSpec,
|
|
13
|
+
mergeWithFlags,
|
|
14
|
+
validateSemantics
|
|
15
|
+
} from "../chunk-RQVNHPHC.js";
|
|
16
|
+
|
|
17
|
+
// src/cli/run.ts
|
|
18
|
+
import { Command } from "commander";
|
|
19
|
+
|
|
20
|
+
// src/cli/exit-codes.ts
|
|
21
|
+
function isCommanderError(err) {
|
|
22
|
+
return err instanceof Error && typeof err.name === "string" && err.name === "CommanderError";
|
|
23
|
+
}
|
|
24
|
+
function exitCodeFor(err) {
|
|
25
|
+
if (isCommanderError(err)) {
|
|
26
|
+
if (err.code === "commander.helpDisplayed" || err.code === "commander.version") {
|
|
27
|
+
return ExitCode.Ok;
|
|
28
|
+
}
|
|
29
|
+
return err.exitCode > 0 ? err.exitCode : ExitCode.Usage;
|
|
30
|
+
}
|
|
31
|
+
if (err instanceof ApiguardError) return err.exitCode;
|
|
32
|
+
return ExitCode.Internal;
|
|
33
|
+
}
|
|
34
|
+
function renderError(err, verbose = false) {
|
|
35
|
+
if (isCommanderError(err)) {
|
|
36
|
+
if (err.code === "commander.helpDisplayed" || err.code === "commander.version") return "";
|
|
37
|
+
return `error: ${err.message}`;
|
|
38
|
+
}
|
|
39
|
+
if (err instanceof ApiguardError) {
|
|
40
|
+
const hint = err.exitCode === ExitCode.Internal ? " (please report this bug)" : "";
|
|
41
|
+
return `error: ${err.message}${hint}${verbose && err.stack ? "\n" + err.stack : ""}`;
|
|
42
|
+
}
|
|
43
|
+
return `error: unexpected internal error: ${errorMessage(err)} (please report this bug)`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/cli/diff-command.ts
|
|
47
|
+
import fs from "fs";
|
|
48
|
+
|
|
49
|
+
// src/reporters/terminal.ts
|
|
50
|
+
var MARKER = { error: "ERROR", warning: "WARN", info: "INFO" };
|
|
51
|
+
var ANSI = {
|
|
52
|
+
red: (s) => `\x1B[31m${s}\x1B[0m`,
|
|
53
|
+
yellow: (s) => `\x1B[33m${s}\x1B[0m`,
|
|
54
|
+
cyan: (s) => `\x1B[36m${s}\x1B[0m`,
|
|
55
|
+
bold: (s) => `\x1B[1m${s}\x1B[0m`,
|
|
56
|
+
green: (s) => `\x1B[32m${s}\x1B[0m`
|
|
57
|
+
};
|
|
58
|
+
function markerFor(sev, color) {
|
|
59
|
+
const marker = MARKER[sev].padEnd(5);
|
|
60
|
+
if (!color) return marker;
|
|
61
|
+
if (sev === "error") return ANSI.red(marker);
|
|
62
|
+
if (sev === "warning") return ANSI.yellow(marker);
|
|
63
|
+
return ANSI.cyan(marker);
|
|
64
|
+
}
|
|
65
|
+
function groupKey(change) {
|
|
66
|
+
const loc = change.location;
|
|
67
|
+
if (loc.method) return `${loc.method} ${loc.path}`;
|
|
68
|
+
if (loc.path && loc.path !== "/") return loc.path;
|
|
69
|
+
if (loc.context === "webhook") return `webhook ${loc.componentName ?? ""}`.trim();
|
|
70
|
+
if (loc.componentName) return `component ${loc.componentName}`;
|
|
71
|
+
return "API";
|
|
72
|
+
}
|
|
73
|
+
function renderTerminal(report, options) {
|
|
74
|
+
const lines = [];
|
|
75
|
+
const breakingCount = report.summary.bySeverity.error;
|
|
76
|
+
const infoCount = report.summary.bySeverity.info + report.summary.bySeverity.warning;
|
|
77
|
+
const renderable = report.changes.filter((c) => {
|
|
78
|
+
if (c.severity === "error") return true;
|
|
79
|
+
return options.includeNonBreaking;
|
|
80
|
+
});
|
|
81
|
+
if (!options.quiet) {
|
|
82
|
+
if (renderable.length > 0) {
|
|
83
|
+
const groups = /* @__PURE__ */ new Map();
|
|
84
|
+
for (const c of renderable) {
|
|
85
|
+
const key = groupKey(c);
|
|
86
|
+
const arr = groups.get(key);
|
|
87
|
+
if (arr) arr.push(c);
|
|
88
|
+
else groups.set(key, [c]);
|
|
89
|
+
}
|
|
90
|
+
for (const [key, changes] of groups) {
|
|
91
|
+
lines.push(key);
|
|
92
|
+
for (const c of changes) {
|
|
93
|
+
const marker = markerFor(c.severity, options.color);
|
|
94
|
+
lines.push(` ${marker} ${c.message}`);
|
|
95
|
+
lines.push(` Suggestion: ${c.suggestion}`);
|
|
96
|
+
}
|
|
97
|
+
lines.push("");
|
|
98
|
+
}
|
|
99
|
+
while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
100
|
+
}
|
|
101
|
+
if (options.showIgnored && report.ignored && report.ignored.length > 0) {
|
|
102
|
+
if (lines.length > 0) lines.push("");
|
|
103
|
+
lines.push("Ignored changes:");
|
|
104
|
+
for (const c of report.ignored) {
|
|
105
|
+
const marker = markerFor(c.severity, options.color);
|
|
106
|
+
lines.push(` ${marker} [ignored] ${c.message}`);
|
|
107
|
+
lines.push(` Suggestion: ${c.suggestion}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (breakingCount > 0) {
|
|
112
|
+
const verdict = "\u274C Breaking API changes found";
|
|
113
|
+
lines.push(options.color ? ANSI.red(ANSI.bold(verdict)) : verdict);
|
|
114
|
+
} else {
|
|
115
|
+
const suffix = infoCount > 0 ? ` (${infoCount} informational change${infoCount === 1 ? "" : "s"})` : "";
|
|
116
|
+
const verdict = `\u2713 No breaking API changes found${suffix}`;
|
|
117
|
+
lines.push(options.color ? ANSI.green(verdict) : verdict);
|
|
118
|
+
}
|
|
119
|
+
return lines;
|
|
120
|
+
}
|
|
121
|
+
function suggestLine(report) {
|
|
122
|
+
const bump = report.semver.bump;
|
|
123
|
+
if (bump === "none") return null;
|
|
124
|
+
const label = bump === "major" ? "MAJOR" : bump === "minor" ? "MINOR" : "PATCH";
|
|
125
|
+
return `Suggested version bump: ${label}`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// src/reporters/json.ts
|
|
129
|
+
function renderJson(report) {
|
|
130
|
+
return JSON.stringify(report, null, 2);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// src/cli/diff-command.ts
|
|
134
|
+
function resolveFormat(opts) {
|
|
135
|
+
if (opts.json) return "json";
|
|
136
|
+
if (opts.markdown) return "markdown";
|
|
137
|
+
if (opts.format !== void 0) {
|
|
138
|
+
if (opts.format === "terminal" || opts.format === "json" || opts.format === "markdown") {
|
|
139
|
+
return opts.format;
|
|
140
|
+
}
|
|
141
|
+
throw new CliUsageError(`invalid --format "${opts.format}" (expected terminal|json|markdown)`);
|
|
142
|
+
}
|
|
143
|
+
const envFormat = process.env.APIGUARD_FORMAT;
|
|
144
|
+
if (envFormat === "json" || envFormat === "markdown" || envFormat === "terminal")
|
|
145
|
+
return envFormat;
|
|
146
|
+
return "terminal";
|
|
147
|
+
}
|
|
148
|
+
function resolveFailOn(opts) {
|
|
149
|
+
const raw = opts.strict ? "warning" : opts.failOn ?? process.env.APIGUARD_FAIL_ON ?? "error";
|
|
150
|
+
if (raw === "error" || raw === "warning" || raw === "never") return raw;
|
|
151
|
+
throw new CliUsageError(`invalid --fail-on "${raw}" (expected error|warning|never)`);
|
|
152
|
+
}
|
|
153
|
+
function colorEnabled(opts, configColor) {
|
|
154
|
+
if (opts.color === false) return false;
|
|
155
|
+
if (process.env.APIGUARD_NO_COLOR !== void 0 || process.env.NO_COLOR !== void 0)
|
|
156
|
+
return false;
|
|
157
|
+
if (configColor === "always") return true;
|
|
158
|
+
if (configColor === "never") return false;
|
|
159
|
+
return process.stdout.isTTY === true;
|
|
160
|
+
}
|
|
161
|
+
async function runDiff(oldSpec, newSpec, opts, io) {
|
|
162
|
+
if (opts.verbose) io.stderr(`verbose: loading baseline ${oldSpec} and current ${newSpec}`);
|
|
163
|
+
const loaded = discoverAndLoadConfig({ explicit: opts.config, env: io.env.APIGUARD_CONFIG });
|
|
164
|
+
for (const w of loaded.warnings) io.stderr(`warning: ${w}`);
|
|
165
|
+
const config = mergeWithFlags(loaded.config, {
|
|
166
|
+
format: resolveFormat(opts),
|
|
167
|
+
failOn: resolveFailOn(opts),
|
|
168
|
+
includeNonBreaking: opts.strict || opts.includeNonBreaking,
|
|
169
|
+
suggestVersion: opts.suggestVersion,
|
|
170
|
+
noColor: !colorEnabled(opts, loaded.config.output.color)
|
|
171
|
+
});
|
|
172
|
+
const { report } = await analyze(oldSpec, newSpec, {
|
|
173
|
+
config,
|
|
174
|
+
showIgnored: Boolean(opts.showIgnored) || loaded.config.ignore.showIgnored === true
|
|
175
|
+
});
|
|
176
|
+
let body;
|
|
177
|
+
const wantBump = !opts.quiet && (config.versioning.suggest || opts.suggestVersion === true);
|
|
178
|
+
if (opts.quiet) {
|
|
179
|
+
body = report.summary.bySeverity.error > 0 ? "\u274C Breaking API changes found" : "\u2713 No breaking API changes found";
|
|
180
|
+
} else if (config.output.format === "json") {
|
|
181
|
+
body = renderJson(report);
|
|
182
|
+
} else if (config.output.format === "markdown") {
|
|
183
|
+
const { renderMarkdown } = await import("../markdown-J2EC3VFS.js");
|
|
184
|
+
body = renderMarkdown(report);
|
|
185
|
+
} else {
|
|
186
|
+
const lines = renderTerminal(report, {
|
|
187
|
+
includeNonBreaking: config.output.includeNonBreaking,
|
|
188
|
+
showIgnored: Boolean(opts.showIgnored) || loaded.config.ignore.showIgnored === true,
|
|
189
|
+
color: config.output.color === "always" || config.output.color === "auto" && process.stdout.isTTY === true,
|
|
190
|
+
quiet: Boolean(opts.quiet)
|
|
191
|
+
});
|
|
192
|
+
body = lines.join("\n");
|
|
193
|
+
const bump = wantBump ? suggestLine(report) : null;
|
|
194
|
+
if (bump) body += "\n\n" + bump;
|
|
195
|
+
}
|
|
196
|
+
io.stdout(body);
|
|
197
|
+
if (opts.output) {
|
|
198
|
+
fs.writeFileSync(opts.output, body + "\n", "utf8");
|
|
199
|
+
if (!opts.quiet) io.stderr(`report written to ${opts.output}`);
|
|
200
|
+
}
|
|
201
|
+
if (config.failOn === "never") return ExitCode.Ok;
|
|
202
|
+
const threshold = config.failOn === "error" ? "error" : "warning";
|
|
203
|
+
const hits = threshold === "error" ? report.summary.bySeverity.error > 0 : report.summary.bySeverity.error + report.summary.bySeverity.warning > 0;
|
|
204
|
+
return hits ? ExitCode.BreakingChanges : ExitCode.Ok;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// src/cli/validate-command.ts
|
|
208
|
+
async function runValidate(spec, opts, io) {
|
|
209
|
+
if (opts.verbose) io.stderr(`verbose: loading ${spec}`);
|
|
210
|
+
const loaded = await loadSpec(spec);
|
|
211
|
+
const result = validateSemantics(loaded.document, spec);
|
|
212
|
+
if (result.issues.length > 0) {
|
|
213
|
+
for (const line of formatIssues(result.issues)) io.stdout(line);
|
|
214
|
+
}
|
|
215
|
+
const structuralErrors = result.issues.some((i) => i.level === "error");
|
|
216
|
+
if (structuralErrors) {
|
|
217
|
+
const first = result.issues.find((i) => i.level === "error");
|
|
218
|
+
throw new SpecValidationError(`${spec}: semantic validation failed: ${first?.message ?? ""}`);
|
|
219
|
+
}
|
|
220
|
+
const warnings = result.issues.filter((i) => i.level === "warning");
|
|
221
|
+
if (opts.strict && warnings.length > 0) {
|
|
222
|
+
io.stdout(`\u2717 ${warnings.length} semantic warning(s)`);
|
|
223
|
+
return { exitCode: 1 };
|
|
224
|
+
}
|
|
225
|
+
io.stdout(`\u2713 ${spec} is valid${warnings.length > 0 ? ` (${warnings.length} warning(s))` : ""}`);
|
|
226
|
+
return { exitCode: 0 };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// src/cli/init-command.ts
|
|
230
|
+
import fs2 from "fs";
|
|
231
|
+
import path from "path";
|
|
232
|
+
|
|
233
|
+
// src/core/config/template.ts
|
|
234
|
+
var CONFIG_TEMPLATE = `# API Guard configuration
|
|
235
|
+
# Docs: https://github.com/apiguard/apiguard/blob/main/docs/configuration.md
|
|
236
|
+
schemaVersion: 1 # required; other versions are rejected with exit 2
|
|
237
|
+
rules: # optional; values: error|warning|info|off; key = ruleId, or
|
|
238
|
+
removed-path: error # ruleId@context-class to override one context (see docs/breaking-changes.md)
|
|
239
|
+
removed-method: error
|
|
240
|
+
property-removed@request-body: warning
|
|
241
|
+
property-removed@response-body: error
|
|
242
|
+
required-property-added: error
|
|
243
|
+
property-type-changed: error
|
|
244
|
+
enum-value-removed: error
|
|
245
|
+
ignore: # optional; globs via micromatch; applied after classification
|
|
246
|
+
paths: [] # matches path templates, all methods, e.g. ["/internal/**", "/metrics"]
|
|
247
|
+
operations: [] # exact "METHOD /path" entries, e.g. ["GET /health", "POST /users"]
|
|
248
|
+
schemas: [] # component names / pointer prefixes, e.g. ["InternalModel", "components/schemas/V1*"]
|
|
249
|
+
changes: [] # ruleId globs, e.g. ["operation-id-changed", "server-*"]
|
|
250
|
+
show-ignored: false # if true: records appear flagged, excluded from counts+exit
|
|
251
|
+
output:
|
|
252
|
+
format: terminal # terminal|json|markdown
|
|
253
|
+
include-non-breaking: false
|
|
254
|
+
color: auto # auto|always|never
|
|
255
|
+
fail-on: error # error|warning|never
|
|
256
|
+
versioning:
|
|
257
|
+
suggest: false # print bump suggestion after every diff (same as --suggest-version)
|
|
258
|
+
loader:
|
|
259
|
+
allow-remote-refs: false
|
|
260
|
+
github: # read only by the GitHub Action, ignored by the CLI
|
|
261
|
+
comment: true
|
|
262
|
+
check-run: true
|
|
263
|
+
update-existing-comment: true
|
|
264
|
+
`;
|
|
265
|
+
|
|
266
|
+
// src/cli/init-command.ts
|
|
267
|
+
var EXAMPLE_BASELINE = `openapi: 3.0.3
|
|
268
|
+
info:
|
|
269
|
+
title: Sample API
|
|
270
|
+
version: 1.0.0
|
|
271
|
+
paths:
|
|
272
|
+
/users:
|
|
273
|
+
get:
|
|
274
|
+
operationId: listUsers
|
|
275
|
+
responses:
|
|
276
|
+
'200':
|
|
277
|
+
description: OK
|
|
278
|
+
content:
|
|
279
|
+
application/json:
|
|
280
|
+
schema:
|
|
281
|
+
type: object
|
|
282
|
+
properties:
|
|
283
|
+
id:
|
|
284
|
+
type: string
|
|
285
|
+
name:
|
|
286
|
+
type: string
|
|
287
|
+
`;
|
|
288
|
+
var EXAMPLE_CURRENT = `openapi: 3.0.3
|
|
289
|
+
info:
|
|
290
|
+
title: Sample API
|
|
291
|
+
version: 1.1.0
|
|
292
|
+
paths:
|
|
293
|
+
/users:
|
|
294
|
+
get:
|
|
295
|
+
operationId: listUsers
|
|
296
|
+
responses:
|
|
297
|
+
'200':
|
|
298
|
+
description: OK
|
|
299
|
+
content:
|
|
300
|
+
application/json:
|
|
301
|
+
schema:
|
|
302
|
+
type: object
|
|
303
|
+
properties:
|
|
304
|
+
id:
|
|
305
|
+
type: string
|
|
306
|
+
name:
|
|
307
|
+
type: string
|
|
308
|
+
email:
|
|
309
|
+
type: string
|
|
310
|
+
post:
|
|
311
|
+
operationId: createUser
|
|
312
|
+
requestBody:
|
|
313
|
+
required: true
|
|
314
|
+
content:
|
|
315
|
+
application/json:
|
|
316
|
+
schema:
|
|
317
|
+
type: object
|
|
318
|
+
required: [email]
|
|
319
|
+
properties:
|
|
320
|
+
email:
|
|
321
|
+
type: string
|
|
322
|
+
responses:
|
|
323
|
+
'201':
|
|
324
|
+
description: Created
|
|
325
|
+
`;
|
|
326
|
+
function filesFor(_dir, withExamples) {
|
|
327
|
+
const files = [{ relPath: "apiguard.yaml", content: CONFIG_TEMPLATE }];
|
|
328
|
+
if (withExamples) {
|
|
329
|
+
files.push({ relPath: path.join("examples", "baseline.yaml"), content: EXAMPLE_BASELINE });
|
|
330
|
+
files.push({ relPath: path.join("examples", "current.yaml"), content: EXAMPLE_CURRENT });
|
|
331
|
+
}
|
|
332
|
+
return files;
|
|
333
|
+
}
|
|
334
|
+
async function runInit(dir, opts, io) {
|
|
335
|
+
const target = path.resolve(dir);
|
|
336
|
+
const planned = filesFor(target, opts.examples === true);
|
|
337
|
+
const conflicts = planned.filter((f) => fs2.existsSync(path.join(target, f.relPath)));
|
|
338
|
+
if (conflicts.length > 0 && !opts.force) {
|
|
339
|
+
const lines = conflicts.map(
|
|
340
|
+
(f) => ` would write ${f.relPath} (${f.content.split("\n").length} lines)`
|
|
341
|
+
);
|
|
342
|
+
throw new CliUsageError(
|
|
343
|
+
`refusing to overwrite existing file(s):
|
|
344
|
+
${lines.join("\n")}
|
|
345
|
+
use --force to overwrite`
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
for (const f of planned) {
|
|
349
|
+
const abs = path.join(target, f.relPath);
|
|
350
|
+
fs2.mkdirSync(path.dirname(abs), { recursive: true });
|
|
351
|
+
fs2.writeFileSync(abs, f.content, "utf8");
|
|
352
|
+
io.stdout(`wrote ${f.relPath}`);
|
|
353
|
+
}
|
|
354
|
+
io.stdout('next: run "apiguard diff examples/baseline.yaml examples/current.yaml"');
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// src/cli/run.ts
|
|
358
|
+
var defaultIo = {
|
|
359
|
+
stdout: (s) => process.stdout.write(s + "\n"),
|
|
360
|
+
stderr: (s) => process.stderr.write(s + "\n"),
|
|
361
|
+
argv: process.argv,
|
|
362
|
+
env: process.env
|
|
363
|
+
};
|
|
364
|
+
function buildProgram(io) {
|
|
365
|
+
const program = new Command();
|
|
366
|
+
program.exitOverride();
|
|
367
|
+
program.configureOutput({
|
|
368
|
+
writeOut: (s) => io.stdout(s.trimEnd()),
|
|
369
|
+
writeErr: (s) => io.stderr(s.trimEnd())
|
|
370
|
+
});
|
|
371
|
+
program.name("apiguard").version(TOOL_VERSION).description("Detect breaking changes between OpenAPI specifications");
|
|
372
|
+
const diff = program.command("diff").description("Diff two OpenAPI specs and report breaking changes").argument("<old-spec>", "baseline spec path").argument("<new-spec>", "current spec path").option("--format <f>", "terminal|json|markdown (aliases: --json, --markdown)").option("--json", "shorthand for --format json").option("--markdown", "shorthand for --format markdown").option("--config <path>", "explicit config file (missing file: exit 2)").option("--fail-on <level>", "error|warning|never", "error").option("--strict", "shorthand for --fail-on warning AND --include-non-breaking").option("--quiet", "print only the one-line verdict").option("--suggest-version", "print suggested version bump after the report").option("--include-non-breaking", "also render warning+info changes").option("--output <path>", "also write the full report to a file").option("--no-color", "disable ANSI color (auto-detected for non-TTY)").option("--show-ignored", "render changes suppressed by ignore rules, tagged ignored").option("--verbose", "log parse/config steps to stderr").action(async (oldSpec, newSpec, opts) => {
|
|
373
|
+
const code = await runDiff(oldSpec, newSpec, opts, io);
|
|
374
|
+
if (code !== ExitCode.Ok) {
|
|
375
|
+
process.exitCode = code;
|
|
376
|
+
program.__apiguardExitCode = code;
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
void diff;
|
|
380
|
+
program.command("validate").description("Load and validate a spec").argument("<spec>", "spec path").option("--config <path>", "explicit config file").option("--strict", "exit 1 when semantic warnings exist").option("--verbose", "log parse steps to stderr").action(async (spec, opts) => {
|
|
381
|
+
const outcome = await runValidate(spec, opts, io);
|
|
382
|
+
if (outcome.exitCode !== ExitCode.Ok) {
|
|
383
|
+
program.__apiguardExitCode = outcome.exitCode;
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
program.command("init").description("Scaffold an apiguard.yaml config template").argument("[dir]", "target directory (default: cwd)", ".").option("--examples", "also write two sample specs for a first diff run").option("--force", "overwrite existing files").action(async (dir, opts) => {
|
|
387
|
+
await runInit(dir, opts, io);
|
|
388
|
+
});
|
|
389
|
+
return program;
|
|
390
|
+
}
|
|
391
|
+
async function run(io = defaultIo) {
|
|
392
|
+
const program = buildProgram(io);
|
|
393
|
+
try {
|
|
394
|
+
await program.parseAsync(io.argv, { from: "node" });
|
|
395
|
+
const code = program.__apiguardExitCode;
|
|
396
|
+
return code ?? ExitCode.Ok;
|
|
397
|
+
} catch (err) {
|
|
398
|
+
io.stderr(renderError(err, io.env.APIGUARD_VERBOSE === "1"));
|
|
399
|
+
return exitCodeFor(err);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// src/cli/main.ts
|
|
404
|
+
run().then((code) => {
|
|
405
|
+
process.exitCode = code;
|
|
406
|
+
});
|