specshield 3.4.8 → 3.4.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specshield",
3
- "version": "3.4.8",
3
+ "version": "3.4.9",
4
4
  "description": "Contract compatibility testing for APIs — catch breaking OpenAPI changes before they reach your consumers, with can-i-deploy deploy gating and GitHub PR checks. (a.k.a. bidirectional contract testing.)",
5
5
  "main": "src/cli.js",
6
6
  "homepage": "https://specshield.io",
@@ -12,6 +12,7 @@ const { classifyChanges, filterBySeverity } = require('../core/classifyChanges')
12
12
  const { formatHuman, formatJson } = require('../core/outputFormatter');
13
13
  const { loadConfig } = require('../core/configLoader');
14
14
  const { resolveExitCode } = require('../core/exitCode');
15
+ const { resolveRemoteUrl } = require('../core/remoteUrl');
15
16
  const { recordCompareAndMaybeRender } = require('../core/conversionPrompt');
16
17
  const logger = require('../utils/logger');
17
18
  const exitAfterFlush = require('../utils/exitAfterFlush');
@@ -168,7 +169,7 @@ async function runRemoteComparison(base, target, options, spinner) {
168
169
  const baseRaw = await loadSpec(base);
169
170
  const targetRaw = await loadSpec(target);
170
171
 
171
- const url = options.remoteUrl || HOSTED_API_URL + '/compare';
172
+ const url = resolveRemoteUrl(options.remoteUrl, HOSTED_API_URL);
172
173
  const timeout = parseInt(options.timeout, 10) || 10000;
173
174
 
174
175
  if (spinner) spinner.text = `Sending to hosted API...`;
@@ -186,7 +187,7 @@ async function runRemoteComparison(base, target, options, spinner) {
186
187
  { baseSpec: baseRaw, targetSpec: targetRaw },
187
188
  { timeout, headers }
188
189
  );
189
- return response.data;
190
+ return normalizeRemoteResult(response.data, url);
190
191
  } catch (err) {
191
192
  const msg = err.response
192
193
  ? `Remote API error ${err.response.status}: ${JSON.stringify(err.response.data)}`
@@ -195,6 +196,30 @@ async function runRemoteComparison(base, target, options, spinner) {
195
196
  }
196
197
  }
197
198
 
199
+ /**
200
+ * A remote response is untrusted input. Without this, a proxy error page or a
201
+ * redirect to the marketing site arrives as a 200 with an HTML body, and the
202
+ * first `.filter` downstream throws an unreadable TypeError at the user.
203
+ */
204
+ function normalizeRemoteResult(data, url) {
205
+ if (!data || typeof data !== 'object' || !Array.isArray(data.breakingChanges)) {
206
+ const gotHtml = typeof data === 'string' && /<html|<!doctype/i.test(data);
207
+ throw new Error(
208
+ `Unexpected response from ${url}` +
209
+ (gotHtml
210
+ ? ' — that URL returned a web page, not the compare API. Pass the server base URL, e.g. --remote-url https://specshield.io'
211
+ : ' — the response was not a comparison result.')
212
+ );
213
+ }
214
+ // Downstream steps assume all four arrays exist.
215
+ return {
216
+ ...data,
217
+ additions: data.additions || [],
218
+ modifications: data.modifications || [],
219
+ warnings: data.warnings || [],
220
+ };
221
+ }
222
+
198
223
  function applyIgnoreList(result, ignoreList) {
199
224
  if (!ignoreList || ignoreList.length === 0) return result;
200
225
 
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Resolve the hosted compare endpoint from a base URL.
5
+ *
6
+ * `--remote-url` is documented as "Override the hosted API base URL", but the
7
+ * value used to be passed to axios verbatim — so `--remote-url https://host`
8
+ * POSTed to the site root instead of /compare. The root answers with a web page
9
+ * or a 405, and the CLI then died on `Cannot read properties of undefined
10
+ * (reading 'filter')` while trying to read breakingChanges off HTML.
11
+ *
12
+ * A URL that already ends in /compare is returned unchanged, so anyone who
13
+ * worked around the old behaviour by passing the full endpoint keeps working.
14
+ */
15
+ function resolveRemoteUrl(remoteUrl, hostedDefault) {
16
+ const base = String(remoteUrl || hostedDefault).trim().replace(/\/+$/, '');
17
+ return /\/compare$/i.test(base) ? base : base + '/compare';
18
+ }
19
+
20
+ module.exports = { resolveRemoteUrl };