@sovovs/bycli 2.1.29 → 2.1.30

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.
@@ -26,6 +26,21 @@ const PAGE_SIZE = 20;
26
26
  const MAX_LIMIT = 200;
27
27
  const MAX_PAGES = 30;
28
28
 
29
+ // 掘金对**非 0 的时间窗**(search_type=1/2/3)会间歇性返回空首页:同一 query
30
+ // 连打 6 次,2-5 次拿到 `data: []` 而 err_no 仍是 0/success,只有 cursor 里的
31
+ // 实例标识不同——像是部分后端分片对时间窗查询答不出来。实测 period=week 命中
32
+ // 率只有 1/6 ~ 2/6,但 4 次以内总能拿到数据。
33
+ //
34
+ // 真实无结果(如 query="鿃鿄鿅鿆")连打 8 次稳定是 0,所以重试只会救回抖动,
35
+ // 不会把空态变成假数据。只重试**首页且一行都没拿到**的情形:翻页途中的空页
36
+ // 本来就是正常的终止信号。
37
+ const EMPTY_RETRY_ATTEMPTS = 4;
38
+ const EMPTY_RETRY_DELAY_MS = 400;
39
+
40
+ function delay(ms) {
41
+ return new Promise((resolve) => { setTimeout(resolve, ms); });
42
+ }
43
+
29
44
  const TYPES = {
30
45
  all: 0,
31
46
  article: 2,
@@ -308,9 +323,18 @@ cli({
308
323
 
309
324
  while (rows.length < limit && pages < MAX_PAGES) {
310
325
  const url = buildUrl({ query, idType, sortType, period, cursor });
311
- const payload = await fetchPage(url);
326
+ let payload = await fetchPage(url);
312
327
  pages += 1;
313
328
 
329
+ // 首页空 → 可能是上面说的分片抖动,有界重试;真实空态重试后仍是空。
330
+ if (rows.length === 0 && payload.data.length === 0) {
331
+ for (let attempt = 1; attempt < EMPTY_RETRY_ATTEMPTS; attempt += 1) {
332
+ await delay(EMPTY_RETRY_DELAY_MS);
333
+ payload = await fetchPage(url);
334
+ if (payload.data.length > 0) break;
335
+ }
336
+ }
337
+
314
338
  for (const entry of payload.data) {
315
339
  const normalized = normalizeEntry(entry);
316
340
  if (!normalized) continue;
@@ -357,6 +381,7 @@ export const __test__ = {
357
381
  PAGE_SIZE,
358
382
  MAX_LIMIT,
359
383
  MAX_PAGES,
384
+ EMPTY_RETRY_ATTEMPTS,
360
385
  requireQuery,
361
386
  requireChoice,
362
387
  requireLimit,
@@ -10,6 +10,26 @@ function normalizeRows(data) {
10
10
  return [data];
11
11
  return [{ value: data }];
12
12
  }
13
+ /**
14
+ * Render one cell for the flat text formats (table / plain / md / csv).
15
+ *
16
+ * Adapters occasionally expose a structured column (e.g. juejin/search's
17
+ * per-kind `extra`), which `String(v)` would turn into "[object Object]".
18
+ * JSON is lossless and still one line, so it degrades gracefully.
19
+ */
20
+ function formatCell(v) {
21
+ if (v === null || v === undefined)
22
+ return '';
23
+ if (typeof v === 'object') {
24
+ try {
25
+ return JSON.stringify(v);
26
+ }
27
+ catch {
28
+ return String(v);
29
+ }
30
+ }
31
+ return String(v);
32
+ }
13
33
  function resolveColumns(rows, opts) {
14
34
  return opts.columns ?? Object.keys(rows[0] ?? {});
15
35
  }
@@ -63,8 +83,7 @@ function renderTable(data, opts) {
63
83
  });
64
84
  for (const row of rows) {
65
85
  table.push(columns.map(c => {
66
- const v = row[c];
67
- return v === null || v === undefined ? '' : String(v);
86
+ return formatCell(row[c]);
68
87
  }));
69
88
  }
70
89
  console.log();
@@ -101,7 +120,9 @@ function renderPlain(data, opts) {
101
120
  }
102
121
  }
103
122
  rows.forEach((row, index) => {
104
- const entries = Object.entries(row).filter(([, value]) => value !== undefined && value !== null && String(value) !== '');
123
+ const entries = Object.entries(row)
124
+ .map(([key, value]) => [key, formatCell(value)])
125
+ .filter(([, value]) => value !== '');
105
126
  entries.forEach(([key, value]) => {
106
127
  console.log(`${key}: ${value}`);
107
128
  });
@@ -127,7 +148,7 @@ function renderMarkdown(data, opts) {
127
148
  console.log('| ' + columns.join(' | ') + ' |');
128
149
  console.log('| ' + columns.map(() => '---').join(' | ') + ' |');
129
150
  for (const row of rows) {
130
- console.log('| ' + columns.map(c => String(row[c] ?? '')).join(' | ') + ' |');
151
+ console.log('| ' + columns.map(c => formatCell(row[c])).join(' | ') + ' |');
131
152
  }
132
153
  }
133
154
  function renderCsv(data, opts) {
@@ -138,7 +159,7 @@ function renderCsv(data, opts) {
138
159
  console.log(columns.join(','));
139
160
  for (const row of rows) {
140
161
  console.log(columns.map(c => {
141
- const v = String(row[c] ?? '');
162
+ const v = formatCell(row[c]);
142
163
  return v.includes(',') || v.includes('"') || v.includes('\n') || v.includes('\r')
143
164
  ? `"${v.replace(/"/g, '""')}"` : v;
144
165
  }).join(','));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sovovs/bycli",
3
- "version": "2.1.29",
3
+ "version": "2.1.30",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },