Skip to content

Commit 8eb06c6

Browse files
author
searchcode bot
committed
Regenerate from the customer API contract
Generated from contracts/customer-api.json in the private source repository. Do not edit these files by hand; they are overwritten on the next contract change.
1 parent cc5e771 commit 8eb06c6

3 files changed

Lines changed: 47 additions & 2 deletions

File tree

.github/workflows/publish.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
# GENERATED. Publishes @searchcode/cli when a version tag is pushed.
2+
#
3+
# A tag only reaches this repository from the private source repository, and it only pushes one
4+
# after the live smoke test has passed against the production API. That smoke test is the gate
5+
# that proves these clients match the API they describe; the tests below are the offline,
6+
# structural half. See scripts/smoke-public-clients.mjs in the source repository.
27
name: publish
38

49
on:

src/index.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,21 @@ function commandHelp(command) {
7979

8080
/** Render a response as readable text; --json prints the raw payload instead. */
8181
function render(payload) {
82-
const rows = payload.results ?? payload.rows ?? payload.domains ?? payload.items;
82+
// The API names its row array after the thing it returns: "sites" for a technology query,
83+
// "domains" for the domain index, "results" for a search, and so on. scripts/smoke-public-
84+
// clients.mjs asserts against the live API that every key a command can return is listed here,
85+
// because an unlisted key silently degrades this command to raw JSON.
86+
const rows = payload.sites ?? payload.domains ?? payload.results ?? payload.rows
87+
?? payload.products ?? payload.shops ?? payload.hits ?? payload.items;
8388
if (!Array.isArray(rows)) return JSON.stringify(payload, null, 2);
8489
if (rows.length === 0) return 'No results.';
8590
const out = [];
86-
if (typeof payload.total === 'number') out.push(`${payload.total} total`, '');
91+
// Likewise the count: "total_sites" for a technology query, "total" for the domain index.
92+
const total = payload.total_sites ?? payload.total ?? payload.sites;
93+
if (typeof total === 'number') {
94+
const approx = payload.total_is_exact === false ? 'about ' : '';
95+
out.push(`${approx}${total.toLocaleString('en-US')} total`, '');
96+
}
8797
for (const row of rows) {
8898
if (typeof row === 'string') { out.push(row); continue; }
8999
const primary = row.domain ?? row.name ?? row.host ?? row.title ?? '';

test/commands.test.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import { test } from 'node:test';
55
import assert from 'node:assert/strict';
6+
import { readFile } from 'node:fs/promises';
67
import { CUSTOMER_API_ROUTES } from '@searchcode/core';
78
import { COMMANDS } from '../src/commands.js';
89

@@ -56,3 +57,32 @@ test('every parameter carries a description', () => {
5657
}
5758
}
5859
});
60+
61+
// The renderer picks the row array by name, and the API names it after what it returns:
62+
// "sites" from a technology query, "domains" from the domain index, "results" from a search.
63+
// These payloads are the real gateway shapes. If the renderer stops recognising one, output
64+
// silently degrades to raw JSON — which is exactly the regression these pin down. The live
65+
// smoke test (scripts/smoke-public-clients.mjs) checks the same thing against production.
66+
const GATEWAY_SHAPES = [
67+
['technology query', { technology: 'React', total_sites: 1234567, sites: [{ domain: 'a.com', rank: 1 }], offset: 0 }],
68+
['domain index', { total: 42, total_is_exact: true, domains: [{ domain: 'b.com' }], limit: 10 }],
69+
['facet count', { kind: 'tech', signal: 'React', sites: 1234567 }],
70+
['search results', { results: [{ domain: 'c.com', blob_hash: 'x' }], next_cursor: null }],
71+
];
72+
73+
for (const [name, payload] of GATEWAY_SHAPES) {
74+
test(`the renderer reads the ${name} response shape`, async () => {
75+
const source = await readFile(new URL('../src/index.js', import.meta.url), 'utf8');
76+
const match = source.match(/const rows = ([^;]+);/);
77+
assert.ok(match, 'the renderer no longer looks up rows the way this test expects');
78+
const known = [...match[1].matchAll(/payload\.([a-z_]+)/g)].map((m) => m[1]);
79+
const arrayKey = Object.keys(payload).find((k) => Array.isArray(payload[k]));
80+
if (arrayKey) {
81+
assert.ok(
82+
known.includes(arrayKey),
83+
`the renderer would print raw JSON for a ${name}: it returns "${arrayKey}", ` +
84+
`and the renderer only knows ${known.join(', ')}`,
85+
);
86+
}
87+
});
88+
}

0 commit comments

Comments
 (0)