sweet-search 2.5.7 → 2.5.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.
@@ -123,10 +123,10 @@ export function isVerboseMode() {
123
123
  const BAR_WIDTH = 30;
124
124
  const LABEL_COL = 17; // pad "Label:" to this width so every bar's [ ] aligns
125
125
  const SUB_BLOCKS = ['', '▏', '▎', '▍', '▌', '▋', '▊', '▉']; // eighth-block partial fills
126
- const CLEAR_EOL = '\x1b[K';
127
126
  const liveBars = new Map(); // label -> { current, total }; insertion order = display order
128
127
  let regionLines = 0; // bar lines currently pinned at the bottom (TTY)
129
128
  let lastLoggedPercent = {};
129
+ let deferredLogs = []; // lines held back while parallel bars run (flushed on commit)
130
130
 
131
131
  function renderBar(current, total, label) {
132
132
  const ratio = total > 0 ? Math.max(0, Math.min(1, current / total)) : 1;
@@ -140,21 +140,34 @@ function renderBar(current, total, label) {
140
140
  return `${colors.cyan}${head}[${bar}${empty}] ${pct}% (${current}/${total})${colors.reset}`;
141
141
  }
142
142
 
143
- function drawRegion() {
144
- let out = regionLines > 0 ? `\x1b[${regionLines}A\r` : '\r';
145
- for (const [label, b] of liveBars) out += renderBar(b.current, b.total, label) + CLEAR_EOL + '\n';
146
- process.stdout.write(out);
147
- regionLines = liveBars.size;
143
+ // (Re)draw the live region in place (the `log-update` pattern). Invariant: the
144
+ // cursor enters and leaves at the END of the last bar line — NO trailing newline
145
+ // so a redraw never pushes a stale copy of a bar into scrollback. Each redraw
146
+ // moves up to the first region line and erases to end-of-screen (\x1b[J) before
147
+ // rewriting. `aboveLine`, if given, scrolls one permanent line above the bars.
148
+ function regionEscape(aboveLine) {
149
+ const bars = [...liveBars].map(([l, b]) => renderBar(b.current, b.total, l));
150
+ let out = '';
151
+ if (regionLines > 1) out += `\x1b[${regionLines - 1}A`; // up to the first region line
152
+ out += '\r\x1b[J'; // col 0, erase region + everything below
153
+ if (aboveLine != null) out += aboveLine + '\n'; // permanent line above the bars
154
+ out += bars.join('\n'); // bars — no trailing newline
155
+ regionLines = bars.length;
156
+ return out;
148
157
  }
149
158
 
150
159
  export function log(message, color = 'reset') {
151
160
  if (quietMode) return;
152
161
  const line = `${colors[color]}${message}${colors.reset}`;
153
162
  if (regionLines > 0 && process.stdout.isTTY) {
154
- // Print the line above the pinned bars, then redraw the bars below it.
155
- let out = `\x1b[${regionLines}A\r${line}${CLEAR_EOL}\n`;
156
- for (const [label, b] of liveBars) out += renderBar(b.current, b.total, label) + CLEAR_EOL + '\n';
157
- process.stdout.write(out);
163
+ if (liveBars.size > 1) {
164
+ // Parallel bars live: defer the line so it can't disturb the region. Any
165
+ // mid-region print scrolls a stale bar-pair into scrollback. Flushed once
166
+ // every bar in the region finishes.
167
+ deferredLogs.push(line);
168
+ return;
169
+ }
170
+ process.stdout.write(regionEscape(line)); // single bar: line above, bar redrawn below
158
171
  } else {
159
172
  console.log(line);
160
173
  }
@@ -172,16 +185,22 @@ export function logProgress(current, total, label) {
172
185
  }
173
186
  return;
174
187
  }
175
- // Interactive TTY: update this bar in the live region and redraw.
188
+ // Interactive TTY: update this bar in the live region and redraw in place.
176
189
  liveBars.set(label, { current, total });
177
- drawRegion();
190
+ process.stdout.write(regionEscape());
178
191
  // Once every live bar is complete, commit the region (leave it on screen).
179
192
  let allDone = true;
180
193
  for (const b of liveBars.values()) if (b.current < b.total) { allDone = false; break; }
181
194
  if (allDone) {
195
+ process.stdout.write('\n'); // move below the finished bars (cursor was at their end)
182
196
  for (const k of liveBars.keys()) lastLoggedPercent[k] = 0;
183
197
  liveBars.clear();
184
198
  regionLines = 0;
199
+ // Flush lines deferred while parallel bars ran — now below the finished bars.
200
+ if (deferredLogs.length) {
201
+ for (const l of deferredLogs) console.log(l);
202
+ deferredLogs = [];
203
+ }
185
204
  }
186
205
  }
187
206
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sweet-search",
3
- "version": "2.5.7",
3
+ "version": "2.5.9",
4
4
  "description": "Sweet Search - SOTA Hybrid Code Search Engine with WASM CatBoost Query Router, Semantic/Lexical/Structural Search, and Multilingual Support",
5
5
  "type": "module",
6
6
  "main": "core/search/sweet-search.js",
@@ -163,12 +163,12 @@
163
163
  },
164
164
  "optionalDependencies": {
165
165
  "usearch": "^2.21.4",
166
- "@sweet-search/native-darwin-arm64": "2.5.7",
167
- "@sweet-search/native-darwin-x64": "2.5.7",
168
- "@sweet-search/native-linux-arm64-gnu": "2.5.7",
169
- "@sweet-search/native-linux-arm64-gnu-cuda": "2.5.7",
170
- "@sweet-search/native-linux-x64-gnu": "2.5.7",
171
- "@sweet-search/native-linux-x64-gnu-cuda": "2.5.7"
166
+ "@sweet-search/native-darwin-arm64": "2.5.9",
167
+ "@sweet-search/native-darwin-x64": "2.5.9",
168
+ "@sweet-search/native-linux-arm64-gnu": "2.5.9",
169
+ "@sweet-search/native-linux-arm64-gnu-cuda": "2.5.9",
170
+ "@sweet-search/native-linux-x64-gnu": "2.5.9",
171
+ "@sweet-search/native-linux-x64-gnu-cuda": "2.5.9"
172
172
  },
173
173
  "engines": {
174
174
  "node": ">=18.0.0"
@@ -2,33 +2,55 @@
2
2
  /**
3
3
  * postinstall — print a short "what next" message after install.
4
4
  *
5
- * Deliberately plain text: during `npm install` npm writes its own progress
6
- * spinner to the terminal CONCURRENTLY with this script, which would interrupt
7
- * any graphics/animation escape sequence mid-stream and leak its payload as
8
- * garbage text. So the rich animated banner is reserved for `sweet-search init`
9
- * and `sweet-search index` (where we own the TTY); install just prints a clean,
10
- * escape-light pointer. Best-effort; always exits 0 so it can't fail an install.
5
+ * npm pipes postinstall stdout (and swallows it for `-g`), so we write to the
6
+ * controlling terminal (/dev/tty) directly same reason the message vanished
7
+ * when we used process.stdout. It is deliberately PLAIN TEXT (no graphics /
8
+ * animation): during `npm install` npm writes its own spinner to the same
9
+ * terminal concurrently, which would corrupt a large chunked escape sequence
10
+ * (the base64 garbage we saw) a short text line is atomic and safe. The rich
11
+ * animated banner is reserved for `sweet-search init` / `index`, where we own
12
+ * the TTY. Best-effort; never throws.
11
13
  */
12
14
  import process from 'node:process';
15
+ import { openSync, writeSync, closeSync } from 'node:fs';
13
16
 
14
17
  function run() {
15
18
  const env = process.env;
16
19
  if (env.NO_BANNER || env.SWEET_SEARCH_NO_BANNER) return;
20
+
21
+ // Choose a real-terminal sink: stdout if it's already a TTY (foreground
22
+ // scripts), otherwise the controlling terminal. Windows has no /dev/tty →
23
+ // the banner shows on `init`/`index` instead.
24
+ let fd = -1;
25
+ const useStdout = !!process.stdout.isTTY;
26
+ if (!useStdout) {
27
+ if (process.platform === 'win32') return;
28
+ try { fd = openSync('/dev/tty', 'w'); } catch { return; } // no controlling terminal → skip
29
+ }
30
+
31
+ const c = (n, s) => `\x1b[${n}m${s}\x1b[0m`;
32
+ // SWEET SEARCH half-block wordmark (kept in sync with core/search/cli-decoration.js).
33
+ const L1 = '█▀▀ █ █ █ █▀▀ █▀▀ ▀█▀ █▀▀ █▀▀ ▄▀▄ █▀▄ █▀▀ █▄█';
34
+ const L2 = '▄▄█ ▀▄█▄▀ ██▄ ██▄ █ ▄▄█ ██▄ █▀█ ██▄ █▄▄ █▀█';
35
+ const msg = [
36
+ '',
37
+ ` ${c('1;38;5;213', L1)}`,
38
+ ` ${c('1;38;5;213', L2)}`,
39
+ '',
40
+ ` ${c('1', 'Get started:')}`,
41
+ ` ${c('36', 'sweet-search init')} set up the current project`,
42
+ ` ${c('36', 'sweet-search index')} build the search index`,
43
+ ` ${c('36', 'sweet-search "query"')} search your code`,
44
+ ` ${c('2', '(installed locally? prefix with')} ${c('2;36', 'npx')}${c('2', ')')}`,
45
+ '',
46
+ '',
47
+ ].join('\n');
48
+
17
49
  try {
18
- const c = (n, s) => (process.stdout.isTTY ? `\x1b[${n}m${s}\x1b[0m` : s);
19
- const lines = [
20
- '',
21
- ` ${c('1;38;5;213', 'sweet-search')} installed ${c('2', '— SOTA hybrid code search')}`,
22
- '',
23
- ` ${c('1', 'Get started:')}`,
24
- ` ${c('36', 'sweet-search init')} set up the current project`,
25
- ` ${c('36', 'sweet-search index')} build the search index`,
26
- ` ${c('36', 'sweet-search "query"')} search your code`,
27
- ` ${c('2', '(installed locally? prefix with')} ${c('2;36', 'npx')}${c('2', ', e.g. `npx sweet-search init`)')}`,
28
- '',
29
- ];
30
- process.stdout.write(lines.join('\n') + '\n');
31
- } catch { /* never break an install */ }
50
+ if (useStdout) process.stdout.write(msg);
51
+ else { writeSync(fd, msg); }
52
+ } catch { /* best-effort */ }
53
+ finally { if (fd >= 0) { try { closeSync(fd); } catch { /* noop */ } } }
32
54
  }
33
55
 
34
56
  run();