coderifts 1.7.0 → 1.8.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.
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env node
2
+
3
+ 'use strict';
4
+
5
+ const { program } = require('commander');
6
+ const pkg = require('../package.json');
7
+
8
+ program
9
+ .name('coderifts')
10
+ .description('Detect breaking API changes between OpenAPI specs')
11
+ .version(pkg.version, '-v, --version');
12
+
13
+ // ── diff command ──
14
+ program
15
+ .command('diff <old-spec> <new-spec>')
16
+ .description('Compare two OpenAPI specs and report breaking changes')
17
+ .option('-f, --format <format>', 'Output format: terminal (default), json, markdown', 'terminal')
18
+ .option('--ci', 'CI mode — exit with code 1 if breaking changes exceed threshold')
19
+ .option('--threshold <number>', 'Risk score threshold for CI mode (0-100)', '50')
20
+ .option('--cloud', 'Use the CodeRifts cloud API instead of local analysis')
21
+ .option('-c, --config <path>', 'Path to .coderifts.yml config file')
22
+ .option('--no-normalize', 'Skip spec normalization (raw diff mode)')
23
+ .action(async (oldSpec, newSpec, options) => {
24
+ const { diff } = require('../src/commands/diff');
25
+ await diff(oldSpec, newSpec, options);
26
+ });
27
+
28
+ // ── init command ──
29
+ program
30
+ .command('init [template]')
31
+ .description('Generate a .coderifts.yml from a policy template (startup, growth, fintech, public-api, microservices)')
32
+ .action(async (template) => {
33
+ const { init } = require('../src/commands/init');
34
+ await init(template);
35
+ });
36
+
37
+ // ── login command ──
38
+ program
39
+ .command('login')
40
+ .description('Save your API key for cloud features')
41
+ .action(async () => {
42
+ const { login } = require('../src/commands/login');
43
+ await login();
44
+ });
45
+
46
+ // ── hook command group ──
47
+ const hookCmd = program
48
+ .command('hook')
49
+ .description('Manage the CodeRifts pre-push Git hook');
50
+
51
+ hookCmd
52
+ .command('install')
53
+ .description('Install the CodeRifts pre-push hook in the current Git repo')
54
+ .action(() => {
55
+ const { install } = require('../src/commands/hook');
56
+ install();
57
+ });
58
+
59
+ hookCmd
60
+ .command('uninstall')
61
+ .description('Remove the CodeRifts pre-push hook from the current Git repo')
62
+ .action(() => {
63
+ const { uninstall } = require('../src/commands/hook');
64
+ uninstall();
65
+ });
66
+
67
+ hookCmd
68
+ .command('status')
69
+ .description('Show whether the CodeRifts pre-push hook is installed and configured')
70
+ .action(() => {
71
+ const { status } = require('../src/commands/hook');
72
+ status();
73
+ });
74
+
75
+ program.parse();
package/dist/cli.js CHANGED
@@ -3003,7 +3003,7 @@ var require_package = __commonJS({
3003
3003
  "package.json"(exports2, module2) {
3004
3004
  module2.exports = {
3005
3005
  name: "coderifts",
3006
- version: "1.7.0",
3006
+ version: "1.8.0",
3007
3007
  description: "Detect breaking API changes from the command line. Works locally or with the CodeRifts cloud API.",
3008
3008
  author: "CodeRifts <hello@coderifts.com>",
3009
3009
  license: "MIT",
@@ -3013,6 +3013,8 @@ var require_package = __commonJS({
3013
3013
  main: "dist/cli.js",
3014
3014
  files: [
3015
3015
  "dist/",
3016
+ "bin/",
3017
+ "scripts/",
3016
3018
  "README.md"
3017
3019
  ],
3018
3020
  keywords: [
@@ -3056,7 +3058,7 @@ var require_package = __commonJS({
3056
3058
  "json-schema-ref-parser": "npm:@apidevtools/json-schema-ref-parser@^11.7.3"
3057
3059
  },
3058
3060
  devDependencies: {
3059
- esbuild: "^0.27.3"
3061
+ esbuild: "^0.27.4"
3060
3062
  }
3061
3063
  };
3062
3064
  }
@@ -61464,14 +61466,438 @@ var require_proxy_from_env = __commonJS({
61464
61466
  }
61465
61467
  });
61466
61468
 
61467
- // node_modules/follow-redirects/debug.js
61469
+ // ../../node_modules/ms/index.js
61470
+ var require_ms = __commonJS({
61471
+ "../../node_modules/ms/index.js"(exports2, module2) {
61472
+ var s = 1e3;
61473
+ var m = s * 60;
61474
+ var h = m * 60;
61475
+ var d = h * 24;
61476
+ var y = d * 365.25;
61477
+ module2.exports = function(val, options) {
61478
+ options = options || {};
61479
+ var type = typeof val;
61480
+ if (type === "string" && val.length > 0) {
61481
+ return parse(val);
61482
+ } else if (type === "number" && isNaN(val) === false) {
61483
+ return options.long ? fmtLong(val) : fmtShort(val);
61484
+ }
61485
+ throw new Error(
61486
+ "val is not a non-empty string or a valid number. val=" + JSON.stringify(val)
61487
+ );
61488
+ };
61489
+ function parse(str) {
61490
+ str = String(str);
61491
+ if (str.length > 100) {
61492
+ return;
61493
+ }
61494
+ var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(
61495
+ str
61496
+ );
61497
+ if (!match) {
61498
+ return;
61499
+ }
61500
+ var n = parseFloat(match[1]);
61501
+ var type = (match[2] || "ms").toLowerCase();
61502
+ switch (type) {
61503
+ case "years":
61504
+ case "year":
61505
+ case "yrs":
61506
+ case "yr":
61507
+ case "y":
61508
+ return n * y;
61509
+ case "days":
61510
+ case "day":
61511
+ case "d":
61512
+ return n * d;
61513
+ case "hours":
61514
+ case "hour":
61515
+ case "hrs":
61516
+ case "hr":
61517
+ case "h":
61518
+ return n * h;
61519
+ case "minutes":
61520
+ case "minute":
61521
+ case "mins":
61522
+ case "min":
61523
+ case "m":
61524
+ return n * m;
61525
+ case "seconds":
61526
+ case "second":
61527
+ case "secs":
61528
+ case "sec":
61529
+ case "s":
61530
+ return n * s;
61531
+ case "milliseconds":
61532
+ case "millisecond":
61533
+ case "msecs":
61534
+ case "msec":
61535
+ case "ms":
61536
+ return n;
61537
+ default:
61538
+ return void 0;
61539
+ }
61540
+ }
61541
+ function fmtShort(ms) {
61542
+ if (ms >= d) {
61543
+ return Math.round(ms / d) + "d";
61544
+ }
61545
+ if (ms >= h) {
61546
+ return Math.round(ms / h) + "h";
61547
+ }
61548
+ if (ms >= m) {
61549
+ return Math.round(ms / m) + "m";
61550
+ }
61551
+ if (ms >= s) {
61552
+ return Math.round(ms / s) + "s";
61553
+ }
61554
+ return ms + "ms";
61555
+ }
61556
+ function fmtLong(ms) {
61557
+ return plural(ms, d, "day") || plural(ms, h, "hour") || plural(ms, m, "minute") || plural(ms, s, "second") || ms + " ms";
61558
+ }
61559
+ function plural(ms, n, name) {
61560
+ if (ms < n) {
61561
+ return;
61562
+ }
61563
+ if (ms < n * 1.5) {
61564
+ return Math.floor(ms / n) + " " + name;
61565
+ }
61566
+ return Math.ceil(ms / n) + " " + name + "s";
61567
+ }
61568
+ }
61569
+ });
61570
+
61571
+ // ../../node_modules/debug/src/debug.js
61468
61572
  var require_debug2 = __commonJS({
61573
+ "../../node_modules/debug/src/debug.js"(exports2, module2) {
61574
+ exports2 = module2.exports = createDebug.debug = createDebug["default"] = createDebug;
61575
+ exports2.coerce = coerce;
61576
+ exports2.disable = disable;
61577
+ exports2.enable = enable;
61578
+ exports2.enabled = enabled;
61579
+ exports2.humanize = require_ms();
61580
+ exports2.names = [];
61581
+ exports2.skips = [];
61582
+ exports2.formatters = {};
61583
+ var prevTime;
61584
+ function selectColor(namespace) {
61585
+ var hash = 0, i;
61586
+ for (i in namespace) {
61587
+ hash = (hash << 5) - hash + namespace.charCodeAt(i);
61588
+ hash |= 0;
61589
+ }
61590
+ return exports2.colors[Math.abs(hash) % exports2.colors.length];
61591
+ }
61592
+ function createDebug(namespace) {
61593
+ function debug() {
61594
+ if (!debug.enabled) return;
61595
+ var self2 = debug;
61596
+ var curr = +/* @__PURE__ */ new Date();
61597
+ var ms = curr - (prevTime || curr);
61598
+ self2.diff = ms;
61599
+ self2.prev = prevTime;
61600
+ self2.curr = curr;
61601
+ prevTime = curr;
61602
+ var args = new Array(arguments.length);
61603
+ for (var i = 0; i < args.length; i++) {
61604
+ args[i] = arguments[i];
61605
+ }
61606
+ args[0] = exports2.coerce(args[0]);
61607
+ if ("string" !== typeof args[0]) {
61608
+ args.unshift("%O");
61609
+ }
61610
+ var index = 0;
61611
+ args[0] = args[0].replace(/%([a-zA-Z%])/g, function(match, format) {
61612
+ if (match === "%%") return match;
61613
+ index++;
61614
+ var formatter = exports2.formatters[format];
61615
+ if ("function" === typeof formatter) {
61616
+ var val = args[index];
61617
+ match = formatter.call(self2, val);
61618
+ args.splice(index, 1);
61619
+ index--;
61620
+ }
61621
+ return match;
61622
+ });
61623
+ exports2.formatArgs.call(self2, args);
61624
+ var logFn = debug.log || exports2.log || console.log.bind(console);
61625
+ logFn.apply(self2, args);
61626
+ }
61627
+ debug.namespace = namespace;
61628
+ debug.enabled = exports2.enabled(namespace);
61629
+ debug.useColors = exports2.useColors();
61630
+ debug.color = selectColor(namespace);
61631
+ if ("function" === typeof exports2.init) {
61632
+ exports2.init(debug);
61633
+ }
61634
+ return debug;
61635
+ }
61636
+ function enable(namespaces) {
61637
+ exports2.save(namespaces);
61638
+ exports2.names = [];
61639
+ exports2.skips = [];
61640
+ var split = (typeof namespaces === "string" ? namespaces : "").split(/[\s,]+/);
61641
+ var len = split.length;
61642
+ for (var i = 0; i < len; i++) {
61643
+ if (!split[i]) continue;
61644
+ namespaces = split[i].replace(/\*/g, ".*?");
61645
+ if (namespaces[0] === "-") {
61646
+ exports2.skips.push(new RegExp("^" + namespaces.substr(1) + "$"));
61647
+ } else {
61648
+ exports2.names.push(new RegExp("^" + namespaces + "$"));
61649
+ }
61650
+ }
61651
+ }
61652
+ function disable() {
61653
+ exports2.enable("");
61654
+ }
61655
+ function enabled(name) {
61656
+ var i, len;
61657
+ for (i = 0, len = exports2.skips.length; i < len; i++) {
61658
+ if (exports2.skips[i].test(name)) {
61659
+ return false;
61660
+ }
61661
+ }
61662
+ for (i = 0, len = exports2.names.length; i < len; i++) {
61663
+ if (exports2.names[i].test(name)) {
61664
+ return true;
61665
+ }
61666
+ }
61667
+ return false;
61668
+ }
61669
+ function coerce(val) {
61670
+ if (val instanceof Error) return val.stack || val.message;
61671
+ return val;
61672
+ }
61673
+ }
61674
+ });
61675
+
61676
+ // ../../node_modules/debug/src/browser.js
61677
+ var require_browser = __commonJS({
61678
+ "../../node_modules/debug/src/browser.js"(exports2, module2) {
61679
+ exports2 = module2.exports = require_debug2();
61680
+ exports2.log = log;
61681
+ exports2.formatArgs = formatArgs;
61682
+ exports2.save = save;
61683
+ exports2.load = load;
61684
+ exports2.useColors = useColors;
61685
+ exports2.storage = "undefined" != typeof chrome && "undefined" != typeof chrome.storage ? chrome.storage.local : localstorage();
61686
+ exports2.colors = [
61687
+ "lightseagreen",
61688
+ "forestgreen",
61689
+ "goldenrod",
61690
+ "dodgerblue",
61691
+ "darkorchid",
61692
+ "crimson"
61693
+ ];
61694
+ function useColors() {
61695
+ if (typeof window !== "undefined" && window.process && window.process.type === "renderer") {
61696
+ return true;
61697
+ }
61698
+ return typeof document !== "undefined" && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance || // is firebug? http://stackoverflow.com/a/398120/376773
61699
+ typeof window !== "undefined" && window.console && (window.console.firebug || window.console.exception && window.console.table) || // is firefox >= v31?
61700
+ // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
61701
+ typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31 || // double check webkit in userAgent just in case we are in a worker
61702
+ typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/);
61703
+ }
61704
+ exports2.formatters.j = function(v) {
61705
+ try {
61706
+ return JSON.stringify(v);
61707
+ } catch (err) {
61708
+ return "[UnexpectedJSONParseError]: " + err.message;
61709
+ }
61710
+ };
61711
+ function formatArgs(args) {
61712
+ var useColors2 = this.useColors;
61713
+ args[0] = (useColors2 ? "%c" : "") + this.namespace + (useColors2 ? " %c" : " ") + args[0] + (useColors2 ? "%c " : " ") + "+" + exports2.humanize(this.diff);
61714
+ if (!useColors2) return;
61715
+ var c = "color: " + this.color;
61716
+ args.splice(1, 0, c, "color: inherit");
61717
+ var index = 0;
61718
+ var lastC = 0;
61719
+ args[0].replace(/%[a-zA-Z%]/g, function(match) {
61720
+ if ("%%" === match) return;
61721
+ index++;
61722
+ if ("%c" === match) {
61723
+ lastC = index;
61724
+ }
61725
+ });
61726
+ args.splice(lastC, 0, c);
61727
+ }
61728
+ function log() {
61729
+ return "object" === typeof console && console.log && Function.prototype.apply.call(console.log, console, arguments);
61730
+ }
61731
+ function save(namespaces) {
61732
+ try {
61733
+ if (null == namespaces) {
61734
+ exports2.storage.removeItem("debug");
61735
+ } else {
61736
+ exports2.storage.debug = namespaces;
61737
+ }
61738
+ } catch (e) {
61739
+ }
61740
+ }
61741
+ function load() {
61742
+ var r;
61743
+ try {
61744
+ r = exports2.storage.debug;
61745
+ } catch (e) {
61746
+ }
61747
+ if (!r && typeof process !== "undefined" && "env" in process) {
61748
+ r = process.env.DEBUG;
61749
+ }
61750
+ return r;
61751
+ }
61752
+ exports2.enable(load());
61753
+ function localstorage() {
61754
+ try {
61755
+ return window.localStorage;
61756
+ } catch (e) {
61757
+ }
61758
+ }
61759
+ }
61760
+ });
61761
+
61762
+ // ../../node_modules/debug/src/node.js
61763
+ var require_node2 = __commonJS({
61764
+ "../../node_modules/debug/src/node.js"(exports2, module2) {
61765
+ var tty = require("tty");
61766
+ var util = require("util");
61767
+ exports2 = module2.exports = require_debug2();
61768
+ exports2.init = init;
61769
+ exports2.log = log;
61770
+ exports2.formatArgs = formatArgs;
61771
+ exports2.save = save;
61772
+ exports2.load = load;
61773
+ exports2.useColors = useColors;
61774
+ exports2.colors = [6, 2, 3, 4, 5, 1];
61775
+ exports2.inspectOpts = Object.keys(process.env).filter(function(key) {
61776
+ return /^debug_/i.test(key);
61777
+ }).reduce(function(obj, key) {
61778
+ var prop = key.substring(6).toLowerCase().replace(/_([a-z])/g, function(_, k) {
61779
+ return k.toUpperCase();
61780
+ });
61781
+ var val = process.env[key];
61782
+ if (/^(yes|on|true|enabled)$/i.test(val)) val = true;
61783
+ else if (/^(no|off|false|disabled)$/i.test(val)) val = false;
61784
+ else if (val === "null") val = null;
61785
+ else val = Number(val);
61786
+ obj[prop] = val;
61787
+ return obj;
61788
+ }, {});
61789
+ var fd = parseInt(process.env.DEBUG_FD, 10) || 2;
61790
+ if (1 !== fd && 2 !== fd) {
61791
+ util.deprecate(function() {
61792
+ }, "except for stderr(2) and stdout(1), any other usage of DEBUG_FD is deprecated. Override debug.log if you want to use a different log function (https://git.io/debug_fd)")();
61793
+ }
61794
+ var stream = 1 === fd ? process.stdout : 2 === fd ? process.stderr : createWritableStdioStream(fd);
61795
+ function useColors() {
61796
+ return "colors" in exports2.inspectOpts ? Boolean(exports2.inspectOpts.colors) : tty.isatty(fd);
61797
+ }
61798
+ exports2.formatters.o = function(v) {
61799
+ this.inspectOpts.colors = this.useColors;
61800
+ return util.inspect(v, this.inspectOpts).split("\n").map(function(str) {
61801
+ return str.trim();
61802
+ }).join(" ");
61803
+ };
61804
+ exports2.formatters.O = function(v) {
61805
+ this.inspectOpts.colors = this.useColors;
61806
+ return util.inspect(v, this.inspectOpts);
61807
+ };
61808
+ function formatArgs(args) {
61809
+ var name = this.namespace;
61810
+ var useColors2 = this.useColors;
61811
+ if (useColors2) {
61812
+ var c = this.color;
61813
+ var prefix = " \x1B[3" + c + ";1m" + name + " \x1B[0m";
61814
+ args[0] = prefix + args[0].split("\n").join("\n" + prefix);
61815
+ args.push("\x1B[3" + c + "m+" + exports2.humanize(this.diff) + "\x1B[0m");
61816
+ } else {
61817
+ args[0] = (/* @__PURE__ */ new Date()).toUTCString() + " " + name + " " + args[0];
61818
+ }
61819
+ }
61820
+ function log() {
61821
+ return stream.write(util.format.apply(util, arguments) + "\n");
61822
+ }
61823
+ function save(namespaces) {
61824
+ if (null == namespaces) {
61825
+ delete process.env.DEBUG;
61826
+ } else {
61827
+ process.env.DEBUG = namespaces;
61828
+ }
61829
+ }
61830
+ function load() {
61831
+ return process.env.DEBUG;
61832
+ }
61833
+ function createWritableStdioStream(fd2) {
61834
+ var stream2;
61835
+ var tty_wrap = process.binding("tty_wrap");
61836
+ switch (tty_wrap.guessHandleType(fd2)) {
61837
+ case "TTY":
61838
+ stream2 = new tty.WriteStream(fd2);
61839
+ stream2._type = "tty";
61840
+ if (stream2._handle && stream2._handle.unref) {
61841
+ stream2._handle.unref();
61842
+ }
61843
+ break;
61844
+ case "FILE":
61845
+ var fs = require("fs");
61846
+ stream2 = new fs.SyncWriteStream(fd2, { autoClose: false });
61847
+ stream2._type = "fs";
61848
+ break;
61849
+ case "PIPE":
61850
+ case "TCP":
61851
+ var net = require("net");
61852
+ stream2 = new net.Socket({
61853
+ fd: fd2,
61854
+ readable: false,
61855
+ writable: true
61856
+ });
61857
+ stream2.readable = false;
61858
+ stream2.read = null;
61859
+ stream2._type = "pipe";
61860
+ if (stream2._handle && stream2._handle.unref) {
61861
+ stream2._handle.unref();
61862
+ }
61863
+ break;
61864
+ default:
61865
+ throw new Error("Implement me. Unknown stream file type!");
61866
+ }
61867
+ stream2.fd = fd2;
61868
+ stream2._isStdio = true;
61869
+ return stream2;
61870
+ }
61871
+ function init(debug) {
61872
+ debug.inspectOpts = {};
61873
+ var keys = Object.keys(exports2.inspectOpts);
61874
+ for (var i = 0; i < keys.length; i++) {
61875
+ debug.inspectOpts[keys[i]] = exports2.inspectOpts[keys[i]];
61876
+ }
61877
+ }
61878
+ exports2.enable(load());
61879
+ }
61880
+ });
61881
+
61882
+ // ../../node_modules/debug/src/index.js
61883
+ var require_src = __commonJS({
61884
+ "../../node_modules/debug/src/index.js"(exports2, module2) {
61885
+ if (typeof process !== "undefined" && process.type === "renderer") {
61886
+ module2.exports = require_browser();
61887
+ } else {
61888
+ module2.exports = require_node2();
61889
+ }
61890
+ }
61891
+ });
61892
+
61893
+ // node_modules/follow-redirects/debug.js
61894
+ var require_debug3 = __commonJS({
61469
61895
  "node_modules/follow-redirects/debug.js"(exports2, module2) {
61470
61896
  var debug;
61471
61897
  module2.exports = function() {
61472
61898
  if (!debug) {
61473
61899
  try {
61474
- debug = require("debug")("follow-redirects");
61900
+ debug = require_src()("follow-redirects");
61475
61901
  } catch (error) {
61476
61902
  }
61477
61903
  if (typeof debug !== "function") {
@@ -61493,7 +61919,7 @@ var require_follow_redirects = __commonJS({
61493
61919
  var https = require("https");
61494
61920
  var Writable = require("stream").Writable;
61495
61921
  var assert = require("assert");
61496
- var debug = require_debug2();
61922
+ var debug = require_debug3();
61497
61923
  (function detectUnsupportedEnvironment() {
61498
61924
  var looksLikeNode = typeof process !== "undefined";
61499
61925
  var looksLikeBrowser = typeof window !== "undefined" && typeof document !== "undefined";
@@ -81783,7 +82209,7 @@ var require_password = __commonJS({
81783
82209
  });
81784
82210
 
81785
82211
  // node_modules/chardet/lib/fs/node.js
81786
- var require_node2 = __commonJS({
82212
+ var require_node3 = __commonJS({
81787
82213
  "node_modules/chardet/lib/fs/node.js"(exports2, module2) {
81788
82214
  "use strict";
81789
82215
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -87285,7 +87711,7 @@ var require_lib6 = __commonJS({
87285
87711
  };
87286
87712
  Object.defineProperty(exports2, "__esModule", { value: true });
87287
87713
  exports2.detectFileSync = exports2.detectFile = exports2.analyse = exports2.detect = void 0;
87288
- var node_1 = __importDefault(require_node2());
87714
+ var node_1 = __importDefault(require_node3());
87289
87715
  var ascii_1 = __importDefault(require_ascii());
87290
87716
  var utf8_1 = __importDefault(require_utf8());
87291
87717
  var unicode = __importStar(require_unicode());
@@ -91638,56 +92064,80 @@ var require_hook = __commonJS({
91638
92064
  var HOOK_MARKER = "# CodeRifts pre-push hook";
91639
92065
  var PRE_PUSH_SCRIPT = `#!/bin/sh
91640
92066
  ${HOOK_MARKER}
91641
- # Checks API spec changes before pushing
92067
+ # Checks API spec changes before pushing.
92068
+ # The pre-push hook receives lines on stdin:
92069
+ # <local ref> <local sha> <remote ref> <remote sha>
91642
92070
 
91643
92071
  CODERIFTS_API_KEY=$(git config coderifts.apiKey)
91644
92072
  SPEC_PATH=$(git config coderifts.specPath || echo "api/openapi.yaml")
92073
+ ZERO="0000000000000000000000000000000000000000"
91645
92074
 
91646
92075
  if [ -z "$CODERIFTS_API_KEY" ]; then
91647
92076
  echo "CodeRifts: No API key configured. Run: git config coderifts.apiKey <your-key>"
91648
92077
  exit 0 # Don't block if not configured
91649
92078
  fi
91650
92079
 
91651
- # Get the base and head specs
91652
- BASE_SPEC=$(git show HEAD:$SPEC_PATH 2>/dev/null)
91653
- HEAD_SPEC=$(cat $SPEC_PATH 2>/dev/null)
91654
-
91655
- if [ -z "$BASE_SPEC" ] || [ -z "$HEAD_SPEC" ]; then
91656
- exit 0 # No spec found, skip
91657
- fi
91658
-
91659
- if [ "$BASE_SPEC" = "$HEAD_SPEC" ]; then
91660
- exit 0 # No changes, skip
91661
- fi
91662
-
91663
- echo "CodeRifts: Checking API spec changes..."
91664
-
91665
- RESULT=$(curl -s -X POST https://app.coderifts.com/api/v1/diff \\
91666
- -H "Authorization: Bearer $CODERIFTS_API_KEY" \\
91667
- -H "Content-Type: application/json" \\
91668
- -d "{\\"before\\": $(echo "$BASE_SPEC" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))'), \\"after\\": $(echo "$HEAD_SPEC" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))')}")
91669
-
91670
- DECISION=$(echo $RESULT | python3 -c "import sys,json; print(json.load(sys.stdin).get('omega_decision','ALLOW'))" 2>/dev/null)
91671
- OMEGA=$(echo $RESULT | python3 -c "import sys,json; print(json.load(sys.stdin).get('omega_api',0))" 2>/dev/null)
91672
- BREAKING=$(echo $RESULT | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d.get('breaking_changes',[])))" 2>/dev/null)
91673
-
91674
- if [ "$DECISION" = "BLOCK" ]; then
91675
- echo ""
91676
- echo "\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"
91677
- echo "\u2551 CodeRifts: PUSH BLOCKED \u2551"
91678
- echo "\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"
91679
- echo "\u2551 Risk score: $OMEGA/100"
91680
- echo "\u2551 Breaking changes: $BREAKING"
91681
- echo "\u2551 Decision: BLOCK"
91682
- echo "\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
91683
- echo ""
91684
- echo "Fix breaking changes or use --no-verify to bypass."
91685
- exit 1
91686
- fi
91687
-
91688
- if [ "$DECISION" = "REQUIRE_APPROVAL" ] || [ "$DECISION" = "WARN" ]; then
91689
- echo "CodeRifts: \u26A0 $DECISION \u2014 Risk: $OMEGA/100, Breaking: $BREAKING"
91690
- fi
92080
+ # Read stdin lines provided by git pre-push
92081
+ while read LOCAL_REF LOCAL_SHA REMOTE_REF REMOTE_SHA; do
92082
+ # Skip delete pushes
92083
+ if [ "$LOCAL_SHA" = "$ZERO" ]; then
92084
+ continue
92085
+ fi
92086
+
92087
+ # Get the spec at the local (about-to-be-pushed) commit
92088
+ HEAD_SPEC=$(git show "$LOCAL_SHA:$SPEC_PATH" 2>/dev/null)
92089
+ if [ -z "$HEAD_SPEC" ]; then
92090
+ continue # Spec doesn't exist in local commit, skip
92091
+ fi
92092
+
92093
+ # Get the spec at the remote (already-pushed) commit
92094
+ if [ "$REMOTE_SHA" = "$ZERO" ]; then
92095
+ # New branch \u2014 no remote baseline, use empty spec
92096
+ BASE_SPEC=""
92097
+ else
92098
+ BASE_SPEC=$(git show "$REMOTE_SHA:$SPEC_PATH" 2>/dev/null || echo "")
92099
+ fi
92100
+
92101
+ # If base is empty (new spec or new branch), allow
92102
+ if [ -z "$BASE_SPEC" ]; then
92103
+ echo "CodeRifts: New spec detected, allowing push."
92104
+ continue
92105
+ fi
92106
+
92107
+ # If specs are identical, nothing to check
92108
+ if [ "$BASE_SPEC" = "$HEAD_SPEC" ]; then
92109
+ continue
92110
+ fi
92111
+
92112
+ echo "CodeRifts: Checking API spec changes..."
92113
+
92114
+ RESULT=$(curl -s -X POST https://app.coderifts.com/api/v1/diff \\
92115
+ -H "Authorization: Bearer $CODERIFTS_API_KEY" \\
92116
+ -H "Content-Type: application/json" \\
92117
+ -d "{\\"before\\": $(echo "$BASE_SPEC" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))'), \\"after\\": $(echo "$HEAD_SPEC" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))')}")
92118
+
92119
+ DECISION=$(echo $RESULT | python3 -c "import sys,json; print(json.load(sys.stdin).get('omega_decision','ALLOW'))" 2>/dev/null)
92120
+ OMEGA=$(echo $RESULT | python3 -c "import sys,json; print(json.load(sys.stdin).get('omega_api',0))" 2>/dev/null)
92121
+ BREAKING=$(echo $RESULT | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d.get('breaking_changes',[])))" 2>/dev/null)
92122
+
92123
+ if [ "$DECISION" = "BLOCK" ]; then
92124
+ echo ""
92125
+ echo "========================================"
92126
+ echo " CodeRifts: PUSH BLOCKED"
92127
+ echo "========================================"
92128
+ echo " Risk score: $OMEGA/100"
92129
+ echo " Breaking changes: $BREAKING"
92130
+ echo " Decision: BLOCK"
92131
+ echo "========================================"
92132
+ echo ""
92133
+ echo "Fix breaking changes or use --no-verify to bypass."
92134
+ exit 1
92135
+ fi
92136
+
92137
+ if [ "$DECISION" = "REQUIRE_APPROVAL" ] || [ "$DECISION" = "WARN" ]; then
92138
+ echo "CodeRifts: WARNING \u2014 $DECISION \u2014 Risk: $OMEGA/100, Breaking: $BREAKING"
92139
+ fi
92140
+ done
91691
92141
 
91692
92142
  exit 0
91693
92143
  `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coderifts",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "Detect breaking API changes from the command line. Works locally or with the CodeRifts cloud API.",
5
5
  "author": "CodeRifts <hello@coderifts.com>",
6
6
  "license": "MIT",
@@ -10,6 +10,8 @@
10
10
  "main": "dist/cli.js",
11
11
  "files": [
12
12
  "dist/",
13
+ "bin/",
14
+ "scripts/",
13
15
  "README.md"
14
16
  ],
15
17
  "keywords": [
@@ -53,6 +55,6 @@
53
55
  "json-schema-ref-parser": "npm:@apidevtools/json-schema-ref-parser@^11.7.3"
54
56
  },
55
57
  "devDependencies": {
56
- "esbuild": "^0.27.3"
58
+ "esbuild": "^0.27.4"
57
59
  }
58
60
  }
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Postinstall patch: z-schema v8+ exports { default: ZSchema } instead of ZSchema directly.
4
+ * This patches @apidevtools/swagger-parser to handle both export styles.
5
+ */
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ try {
10
+ const schemaPath = require.resolve('@apidevtools/swagger-parser/lib/validators/schema.js');
11
+ let content = fs.readFileSync(schemaPath, 'utf8');
12
+
13
+ let patched = false;
14
+ if (!content.includes('_ZSchema')) {
15
+ content = content.replace(
16
+ 'const ZSchema = require("z-schema");',
17
+ 'const _ZSchema = require("z-schema"); const ZSchema = _ZSchema.default || _ZSchema;'
18
+ );
19
+ patched = true;
20
+ }
21
+ // z-schema v10+ requires ZSchema.create() instead of new ZSchema()
22
+ if (content.includes('new ZSchema(') && !content.includes('ZSchema.create')) {
23
+ content = content.replace(
24
+ /return new ZSchema\(/g,
25
+ 'return (typeof ZSchema.create === "function" ? ZSchema.create : (opts) => new ZSchema(opts))('
26
+ );
27
+ patched = true;
28
+ }
29
+ if (patched) {
30
+ fs.writeFileSync(schemaPath, content);
31
+ console.log('postinstall: patched z-schema import/constructor in swagger-parser');
32
+ }
33
+ } catch (err) {
34
+ console.log('postinstall: skipping z-schema patch:', err.message);
35
+ }
36
+
37
+ // Patch 2: json-schema-ref-parser v11+ exports { default: $RefParser } instead of $RefParser directly
38
+ // json-schema-diff uses `new RefParser()` which breaks with the new export style
39
+ try {
40
+ const derefPath = require.resolve('json-schema-diff/dist/json-schema-diff/diff-schemas/dereference-schema.js');
41
+ let content2 = fs.readFileSync(derefPath, 'utf8');
42
+
43
+ if (!content2.includes('_RefParser')) {
44
+ content2 = content2.replace(
45
+ 'const RefParser = require("json-schema-ref-parser");',
46
+ 'const _RefParser = require("json-schema-ref-parser"); const RefParser = _RefParser.default || _RefParser;'
47
+ );
48
+ fs.writeFileSync(derefPath, content2);
49
+ console.log('postinstall: patched json-schema-ref-parser import in json-schema-diff');
50
+ }
51
+ } catch (err) {
52
+ console.log('postinstall: skipping ref-parser patch:', err.message);
53
+ }