@subhamxcod/apiguard 1.0.3 → 1.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/dist/cli/main.js CHANGED
@@ -9,10 +9,14 @@ import {
9
9
  discoverAndLoadConfig,
10
10
  errorMessage,
11
11
  formatIssues,
12
+ generateGatewayShim,
12
13
  loadSpec,
13
14
  mergeWithFlags,
15
+ renderChangelog,
16
+ renderHtml,
17
+ renderSarif,
14
18
  validateSemantics
15
- } from "../chunk-RQVNHPHC.js";
19
+ } from "../chunk-EJQ5XOY6.js";
16
20
 
17
21
  // src/cli/run.ts
18
22
  import { Command } from "commander";
@@ -45,6 +49,7 @@ function renderError(err, verbose = false) {
45
49
 
46
50
  // src/cli/diff-command.ts
47
51
  import fs from "fs";
52
+ import { exec } from "child_process";
48
53
 
49
54
  // src/reporters/terminal.ts
50
55
  var MARKER = { error: "ERROR", warning: "WARN", info: "INFO" };
@@ -134,14 +139,18 @@ function renderJson(report) {
134
139
  function resolveFormat(opts) {
135
140
  if (opts.json) return "json";
136
141
  if (opts.markdown) return "markdown";
142
+ if (opts.html) return "html";
143
+ if (opts.sarif) return "sarif";
137
144
  if (opts.format !== void 0) {
138
- if (opts.format === "terminal" || opts.format === "json" || opts.format === "markdown") {
145
+ if (opts.format === "terminal" || opts.format === "json" || opts.format === "markdown" || opts.format === "html" || opts.format === "sarif") {
139
146
  return opts.format;
140
147
  }
141
- throw new CliUsageError(`invalid --format "${opts.format}" (expected terminal|json|markdown)`);
148
+ throw new CliUsageError(
149
+ `invalid --format "${opts.format}" (expected terminal|json|markdown|html|sarif)`
150
+ );
142
151
  }
143
152
  const envFormat = process.env.APIGUARD_FORMAT;
144
- if (envFormat === "json" || envFormat === "markdown" || envFormat === "terminal")
153
+ if (envFormat === "json" || envFormat === "markdown" || envFormat === "terminal" || envFormat === "html" || envFormat === "sarif")
145
154
  return envFormat;
146
155
  return "terminal";
147
156
  }
@@ -159,6 +168,17 @@ function colorEnabled(opts, configColor) {
159
168
  return process.stdout.isTTY === true;
160
169
  }
161
170
  async function runDiff(oldSpec, newSpec, opts, io) {
171
+ if (opts.gitBase) {
172
+ if (!newSpec && oldSpec) {
173
+ newSpec = oldSpec;
174
+ oldSpec = `git:${opts.gitBase}:${newSpec}`;
175
+ } else if (newSpec && !oldSpec) {
176
+ oldSpec = `git:${opts.gitBase}:${newSpec}`;
177
+ }
178
+ }
179
+ if (!oldSpec || !newSpec) {
180
+ throw new CliUsageError("missing required spec arguments (or use --git-base <ref>)");
181
+ }
162
182
  if (opts.verbose) io.stderr(`verbose: loading baseline ${oldSpec} and current ${newSpec}`);
163
183
  const loaded = discoverAndLoadConfig({ explicit: opts.config, env: io.env.APIGUARD_CONFIG });
164
184
  for (const w of loaded.warnings) io.stderr(`warning: ${w}`);
@@ -167,7 +187,8 @@ async function runDiff(oldSpec, newSpec, opts, io) {
167
187
  failOn: resolveFailOn(opts),
168
188
  includeNonBreaking: opts.strict || opts.includeNonBreaking,
169
189
  suggestVersion: opts.suggestVersion,
170
- noColor: !colorEnabled(opts, loaded.config.output.color)
190
+ noColor: !colorEnabled(opts, loaded.config.output.color),
191
+ profile: opts.profile
171
192
  });
172
193
  const { report } = await analyze(oldSpec, newSpec, {
173
194
  config,
@@ -182,6 +203,10 @@ async function runDiff(oldSpec, newSpec, opts, io) {
182
203
  } else if (config.output.format === "markdown") {
183
204
  const { renderMarkdown } = await import("../markdown-J2EC3VFS.js");
184
205
  body = renderMarkdown(report);
206
+ } else if (config.output.format === "html") {
207
+ body = renderHtml(report);
208
+ } else if (config.output.format === "sarif") {
209
+ body = renderSarif(report);
185
210
  } else {
186
211
  const lines = renderTerminal(report, {
187
212
  includeNonBreaking: config.output.includeNonBreaking,
@@ -194,9 +219,17 @@ async function runDiff(oldSpec, newSpec, opts, io) {
194
219
  if (bump) body += "\n\n" + bump;
195
220
  }
196
221
  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}`);
222
+ const outputPath = opts.output ?? (opts.open ? "apiguard-report.html" : void 0);
223
+ if (outputPath) {
224
+ fs.writeFileSync(outputPath, body + "\n", "utf8");
225
+ if (!opts.quiet) io.stderr(`report written to ${outputPath}`);
226
+ if (opts.open && (config.output.format === "html" || outputPath.endsWith(".html"))) {
227
+ const openCmd = process.platform === "win32" ? `start "" "${outputPath}"` : process.platform === "darwin" ? `open "${outputPath}"` : `xdg-open "${outputPath}"`;
228
+ try {
229
+ exec(openCmd);
230
+ } catch {
231
+ }
232
+ }
200
233
  }
201
234
  if (config.failOn === "never") return ExitCode.Ok;
202
235
  const threshold = config.failOn === "error" ? "error" : "warning";
@@ -354,6 +387,65 @@ use --force to overwrite`
354
387
  io.stdout('next: run "apiguard diff examples/baseline.yaml examples/current.yaml"');
355
388
  }
356
389
 
390
+ // src/cli/changelog-command.ts
391
+ import fs3 from "fs";
392
+ async function runChangelog(oldSpec, newSpec, opts, io) {
393
+ if (opts.gitBase) {
394
+ if (!newSpec && oldSpec) {
395
+ newSpec = oldSpec;
396
+ oldSpec = `git:${opts.gitBase}:${newSpec}`;
397
+ }
398
+ }
399
+ if (!oldSpec || !newSpec) {
400
+ throw new CliUsageError("missing required spec arguments (or use --git-base <ref>)");
401
+ }
402
+ const loaded = discoverAndLoadConfig({ explicit: opts.config, env: io.env.APIGUARD_CONFIG });
403
+ const { report } = await analyze(oldSpec, newSpec, { config: loaded.config });
404
+ const body = opts.json ? JSON.stringify(
405
+ { semver: report.semver, summary: report.summary, changes: report.changes },
406
+ null,
407
+ 2
408
+ ) : renderChangelog(report);
409
+ io.stdout(body);
410
+ if (opts.output) {
411
+ fs3.writeFileSync(opts.output, body + "\n", "utf8");
412
+ io.stderr(`changelog written to ${opts.output}`);
413
+ }
414
+ return ExitCode.Ok;
415
+ }
416
+
417
+ // src/cli/codegen-command.ts
418
+ import fs4 from "fs";
419
+ async function runCodegenShim(oldSpec, newSpec, opts, io) {
420
+ if (opts.gitBase) {
421
+ if (!newSpec && oldSpec) {
422
+ newSpec = oldSpec;
423
+ oldSpec = `git:${opts.gitBase}:${newSpec}`;
424
+ }
425
+ }
426
+ if (!oldSpec || !newSpec) {
427
+ throw new CliUsageError("missing required spec arguments (or use --git-base <ref>)");
428
+ }
429
+ const rawTarget = opts.target ?? "express";
430
+ if (rawTarget !== "express" && rawTarget !== "cloudflare-worker" && rawTarget !== "envoy-lua") {
431
+ throw new CliUsageError(
432
+ `invalid --target "${rawTarget}" (expected express|cloudflare-worker|envoy-lua)`
433
+ );
434
+ }
435
+ const target = rawTarget;
436
+ const loaded = discoverAndLoadConfig({ explicit: opts.config, env: io.env.APIGUARD_CONFIG });
437
+ const { report } = await analyze(oldSpec, newSpec, { config: loaded.config });
438
+ const result = generateGatewayShim(report, target);
439
+ io.stdout(result.code);
440
+ if (opts.output) {
441
+ fs4.writeFileSync(opts.output, result.code, "utf8");
442
+ io.stderr(
443
+ `compatibility shim written to ${opts.output} (${result.handledChanges} breaking change(s) addressed)`
444
+ );
445
+ }
446
+ return ExitCode.Ok;
447
+ }
448
+
357
449
  // src/cli/run.ts
358
450
  var defaultIo = {
359
451
  stdout: (s) => process.stdout.write(s + "\n"),
@@ -369,7 +461,10 @@ function buildProgram(io) {
369
461
  writeErr: (s) => io.stderr(s.trimEnd())
370
462
  });
371
463
  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) => {
464
+ 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(
465
+ "--format <f>",
466
+ "terminal|json|markdown|html|sarif (aliases: --json, --markdown, --html, --sarif)"
467
+ ).option("--json", "shorthand for --format json").option("--markdown", "shorthand for --format markdown").option("--html", "shorthand for --format html").option("--sarif", "shorthand for --format sarif").option("--open", "open interactive HTML report in browser").option("--git-base <ref>", "resolve baseline against git branch or commit").option("--profile <p>", "client persona profile (default|strict-sdk|loose-web)").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
468
  const code = await runDiff(oldSpec, newSpec, opts, io);
374
469
  if (code !== ExitCode.Ok) {
375
470
  process.exitCode = code;
@@ -377,6 +472,20 @@ function buildProgram(io) {
377
472
  }
378
473
  });
379
474
  void diff;
475
+ program.command("changelog").description("Generate a Keep-a-Changelog formatted release summary from spec diff").argument("<old-spec>", "baseline spec path").argument("<new-spec>", "current spec path").option("--config <path>", "explicit config file").option("--output <path>", "write changelog to file").option("--git-base <ref>", "resolve baseline against git branch or commit").option("--json", "output changelog as JSON").action(async (oldSpec, newSpec, opts) => {
476
+ const code = await runChangelog(oldSpec, newSpec, opts, io);
477
+ if (code !== ExitCode.Ok) {
478
+ process.exitCode = code;
479
+ program.__apiguardExitCode = code;
480
+ }
481
+ });
482
+ program.command("codegen-shim").description("Generate backward-compatibility gateway adapter for breaking changes").argument("<old-spec>", "baseline spec path").argument("<new-spec>", "current spec path").option("--target <t>", "express|cloudflare-worker|envoy-lua", "express").option("--config <path>", "explicit config file").option("--output <path>", "write generated shim code to file").option("--git-base <ref>", "resolve baseline against git branch or commit").action(async (oldSpec, newSpec, opts) => {
483
+ const code = await runCodegenShim(oldSpec, newSpec, opts, io);
484
+ if (code !== ExitCode.Ok) {
485
+ process.exitCode = code;
486
+ program.__apiguardExitCode = code;
487
+ }
488
+ });
380
489
  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
490
  const outcome = await runValidate(spec, opts, io);
382
491
  if (outcome.exitCode !== ExitCode.Ok) {