blume 1.3.0 → 1.4.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.
- package/CHANGELOG.md +59 -0
- package/dist/cli/index.js +3290 -254
- package/dist/cli/index.js.map +79 -67
- package/dist/types/core/base-path.d.ts +5 -0
- package/dist/types/core/config-input.d.ts +82 -6
- package/dist/types/core/i18n-ui.d.ts +2 -0
- package/dist/types/core/schema.d.ts +19 -2
- package/dist/types/core/sources/types.d.ts +5 -0
- package/dist/types/core/types.d.ts +4 -3
- package/docs/02-deployment.mdx +1 -1
- package/docs/configuration/ai.mdx +16 -2
- package/docs/configuration/index.mdx +26 -0
- package/docs/configuration/search.mdx +1 -3
- package/docs/content/i18n.mdx +13 -1
- package/docs/content/navigation.mdx +11 -0
- package/docs/reference/cli.mdx +4 -0
- package/docs/reference/frontmatter.mdx +33 -0
- package/docs/reference/meta.ts +1 -1
- package/docs/reference/translate.mdx +80 -0
- package/package.json +1 -1
- package/src/ai/agent-readability.ts +7 -4
- package/src/ai/ask-context.ts +3 -6
- package/src/ai/link-headers.ts +4 -3
- package/src/ai/llms.ts +4 -2
- package/src/ai/markdown.ts +34 -1
- package/src/ai/mcp/data.ts +10 -4
- package/src/ai/mcp/server.ts +74 -3
- package/src/ai/mcp/tools.ts +2 -2
- package/src/astro/generate.ts +2 -2
- package/src/astro/integration.ts +6 -2
- package/src/astro/markdown-negotiation.ts +5 -0
- package/src/astro/templates.ts +74 -21
- package/src/audit/url.ts +5 -10
- package/src/cli/commands/build.ts +145 -34
- package/src/cli/commands/translate.ts +300 -0
- package/src/cli/index.ts +2 -0
- package/src/components/Icon.astro +2 -7
- package/src/components/content/Step.astro +3 -8
- package/src/components/content/Tab.astro +20 -1
- package/src/components/layout/LanguageSwitcher.astro +2 -1
- package/src/components/layout/Logo.astro +4 -4
- package/src/components/layout/PageActions.astro +12 -7
- package/src/components/layout/Search.astro +15 -20
- package/src/components/layout/search/orama.ts +3 -1
- package/src/core/base-path.ts +9 -0
- package/src/core/config-input.ts +84 -6
- package/src/core/graph.ts +46 -2
- package/src/core/i18n-ui.ts +2 -0
- package/src/core/i18n.ts +31 -0
- package/src/core/nav-diagnostics.ts +13 -34
- package/src/core/project-graph.ts +13 -2
- package/src/core/schema.ts +174 -74
- package/src/core/sources/normalize.ts +25 -12
- package/src/core/sources/types.ts +5 -0
- package/src/core/types.ts +4 -3
- package/src/core/ui-packs/ar.ts +42 -1
- package/src/core/ui-packs/bg.ts +42 -1
- package/src/core/ui-packs/bn.ts +42 -1
- package/src/core/ui-packs/ca.ts +44 -1
- package/src/core/ui-packs/cs.ts +42 -1
- package/src/core/ui-packs/da.ts +42 -1
- package/src/core/ui-packs/de.ts +42 -1
- package/src/core/ui-packs/el.ts +44 -1
- package/src/core/ui-packs/es.ts +44 -1
- package/src/core/ui-packs/fa.ts +42 -1
- package/src/core/ui-packs/fi.ts +42 -1
- package/src/core/ui-packs/fr.ts +44 -1
- package/src/core/ui-packs/he.ts +42 -1
- package/src/core/ui-packs/hi.ts +42 -1
- package/src/core/ui-packs/hr.ts +42 -1
- package/src/core/ui-packs/hu.ts +42 -1
- package/src/core/ui-packs/id.ts +42 -1
- package/src/core/ui-packs/it.ts +44 -1
- package/src/core/ui-packs/ja.ts +44 -1
- package/src/core/ui-packs/ko.ts +44 -1
- package/src/core/ui-packs/nl.ts +42 -1
- package/src/core/ui-packs/no.ts +42 -1
- package/src/core/ui-packs/pl.ts +42 -1
- package/src/core/ui-packs/pt-br.ts +44 -1
- package/src/core/ui-packs/pt.ts +44 -1
- package/src/core/ui-packs/ro.ts +42 -1
- package/src/core/ui-packs/ru.ts +42 -1
- package/src/core/ui-packs/sk.ts +42 -1
- package/src/core/ui-packs/sr.ts +42 -1
- package/src/core/ui-packs/sv.ts +42 -1
- package/src/core/ui-packs/th.ts +44 -1
- package/src/core/ui-packs/tr.ts +42 -1
- package/src/core/ui-packs/uk.ts +42 -1
- package/src/core/ui-packs/vi.ts +44 -1
- package/src/core/ui-packs/zh-tw.ts +44 -1
- package/src/core/ui-packs/zh.ts +44 -1
- package/src/deploy/adapter-output.ts +44 -5
- package/src/deploy/cloudflare-negotiation.ts +527 -0
- package/src/deploy/redirects.ts +13 -0
- package/src/deploy/vercel-negotiation.ts +30 -13
- package/src/eval/agents.ts +1 -1
- package/src/search/documents.ts +11 -0
- package/src/search/facets.ts +33 -0
- package/src/search/orama-index.ts +48 -6
- package/src/search/popular-icon.ts +33 -0
- package/src/theme/fonts.ts +3 -1
- package/src/theme/icon-kind.ts +20 -0
- package/src/translate/agents.ts +51 -0
- package/src/translate/ledger.ts +148 -0
- package/src/translate/meta.ts +149 -0
- package/src/translate/prompts.ts +95 -0
- package/src/translate/report.ts +360 -0
- package/src/translate/run.ts +376 -0
- package/src/translate/validate.ts +171 -0
- package/src/translate/work-list.ts +0 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import { AGENTS } from "../audit/agent.ts";
|
|
2
|
+
import type { AgentKind } from "../audit/agent.ts";
|
|
3
|
+
import { countBySeverity } from "../core/diagnostics.ts";
|
|
4
|
+
import type { Diagnostic } from "../core/types.ts";
|
|
5
|
+
import type {
|
|
6
|
+
TranslateItemResult,
|
|
7
|
+
TranslateItemStatus,
|
|
8
|
+
TranslateResult,
|
|
9
|
+
} from "./run.ts";
|
|
10
|
+
import type {
|
|
11
|
+
MetaWorkEntry,
|
|
12
|
+
TranslateWorkList,
|
|
13
|
+
WorkItem,
|
|
14
|
+
WorkStatus,
|
|
15
|
+
} from "./work-list.ts";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The live progress UI, hand-rolled ANSI (no new dependencies), matching the
|
|
19
|
+
* eval report's palette. Render functions are pure; the renderer takes an
|
|
20
|
+
* injectable `write`/`now` so tests never touch a real TTY or clock. The
|
|
21
|
+
* command sends everything here to stderr via the raw `write` — never
|
|
22
|
+
* `logger.info`, which consola drops in test and CI environments.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const ESC = String.fromCodePoint(27);
|
|
26
|
+
const COLORS = {
|
|
27
|
+
bold: `${ESC}[1m`,
|
|
28
|
+
cyan: `${ESC}[36m`,
|
|
29
|
+
dim: `${ESC}[2m`,
|
|
30
|
+
green: `${ESC}[32m`,
|
|
31
|
+
red: `${ESC}[31m`,
|
|
32
|
+
reset: `${ESC}[0m`,
|
|
33
|
+
yellow: `${ESC}[33m`,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const GLYPH: Record<TranslateItemStatus, string> = {
|
|
37
|
+
failed: "✖",
|
|
38
|
+
partial: "!",
|
|
39
|
+
translated: "✔",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const STATUS_COLOR: Record<TranslateItemStatus, string> = {
|
|
43
|
+
failed: COLORS.red,
|
|
44
|
+
partial: COLORS.yellow,
|
|
45
|
+
translated: COLORS.green,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const SPINNER_FRAMES = [
|
|
49
|
+
"⠋",
|
|
50
|
+
"⠙",
|
|
51
|
+
"⠹",
|
|
52
|
+
"⠸",
|
|
53
|
+
"⠼",
|
|
54
|
+
"⠴",
|
|
55
|
+
"⠦",
|
|
56
|
+
"⠧",
|
|
57
|
+
"⠇",
|
|
58
|
+
"⠏",
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
/** How often the TTY spinner advances (one frame per interval). */
|
|
62
|
+
export const SPINNER_INTERVAL_MS = 80;
|
|
63
|
+
|
|
64
|
+
/** The clear-to-start-of-line prefix every TTY rewrite uses. */
|
|
65
|
+
const REWRITE = `\r${ESC}[K`;
|
|
66
|
+
|
|
67
|
+
const seconds = (ms: number): string => `${(ms / 1000).toFixed(1)}s`;
|
|
68
|
+
|
|
69
|
+
const money = (cost: number | undefined): string =>
|
|
70
|
+
cost === undefined ? "" : `$${cost.toFixed(2)}`;
|
|
71
|
+
|
|
72
|
+
const duration = (ms: number): string => {
|
|
73
|
+
if (ms < 60_000) {
|
|
74
|
+
return seconds(ms);
|
|
75
|
+
}
|
|
76
|
+
const minutes = Math.floor(ms / 60_000);
|
|
77
|
+
const rest = Math.round((ms % 60_000) / 1000);
|
|
78
|
+
return `${minutes}m ${rest}s`;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/** `docs/guides/install.mdx → fr`, or the batched meta call's label. */
|
|
82
|
+
export const itemLabel = (item: WorkItem): string =>
|
|
83
|
+
item.kind === "page"
|
|
84
|
+
? `${item.sourceRel} → ${item.locale}`
|
|
85
|
+
: `meta title${item.entries.length === 1 ? "" : "s"} (${item.entries.length}) → ${item.locale}`;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The in-flight spinner line a TTY rewrites in place: the oldest active
|
|
89
|
+
* item's label, how many more lanes are running, and the run's progress.
|
|
90
|
+
*/
|
|
91
|
+
export const spinnerLine = (
|
|
92
|
+
active: WorkItem[],
|
|
93
|
+
done: number,
|
|
94
|
+
total: number,
|
|
95
|
+
frame: number
|
|
96
|
+
): string => {
|
|
97
|
+
const first = active[0] as WorkItem;
|
|
98
|
+
const more = active.length > 1 ? ` (+${active.length - 1} more)` : "";
|
|
99
|
+
return ` ${COLORS.cyan}${SPINNER_FRAMES[frame % SPINNER_FRAMES.length]}${COLORS.reset} ${itemLabel(first)}${more} ${COLORS.dim}${done}/${total}${COLORS.reset}`;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/** The permanent line printed when an item finishes. */
|
|
103
|
+
export const itemEndLine = (result: TranslateItemResult): string => {
|
|
104
|
+
const color = STATUS_COLOR[result.status];
|
|
105
|
+
const glyph = `${color}${GLYPH[result.status]}${COLORS.reset}`;
|
|
106
|
+
const label = itemLabel(result.item);
|
|
107
|
+
if (result.status === "translated") {
|
|
108
|
+
const cells = [seconds(result.durationMs), money(result.costUsd)]
|
|
109
|
+
.filter((cell) => cell !== "")
|
|
110
|
+
.join(" ");
|
|
111
|
+
return ` ${glyph} ${label} ${COLORS.dim}${cells}${COLORS.reset}`;
|
|
112
|
+
}
|
|
113
|
+
const word = result.status === "partial" ? "partial" : "failed";
|
|
114
|
+
return ` ${glyph} ${label} ${color}${word}${COLORS.reset}${
|
|
115
|
+
result.detail ? `${COLORS.dim}: ${result.detail}${COLORS.reset}` : ""
|
|
116
|
+
}`;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/** The header line the command prints before the first item runs. */
|
|
120
|
+
export const translateHeaderLine = (
|
|
121
|
+
itemCount: number,
|
|
122
|
+
localeCount: number,
|
|
123
|
+
agent: AgentKind
|
|
124
|
+
): string =>
|
|
125
|
+
`${COLORS.bold}blume translate${COLORS.reset} ${itemCount} item(s) · ${localeCount} locale(s) · ${AGENTS[agent].name}`;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* `Translated 11 files into 2 locales · 1 failed · 2 adopted · 8 already up to
|
|
129
|
+
* date · 4m 12s · $0.41` (cost only when the agent reports one).
|
|
130
|
+
*/
|
|
131
|
+
export const translateSummaryLine = (
|
|
132
|
+
result: TranslateResult,
|
|
133
|
+
workList: TranslateWorkList
|
|
134
|
+
): string => {
|
|
135
|
+
const { counts } = result;
|
|
136
|
+
const parts = [
|
|
137
|
+
`Translated ${counts.translated} file${counts.translated === 1 ? "" : "s"} into ${workList.targetLocales.length} locale${workList.targetLocales.length === 1 ? "" : "s"}`,
|
|
138
|
+
counts.failed > 0 ? `${counts.failed} failed` : "",
|
|
139
|
+
counts.partial > 0 ? `${counts.partial} partial` : "",
|
|
140
|
+
workList.untracked.length > 0 ? `${workList.untracked.length} adopted` : "",
|
|
141
|
+
workList.upToDate > 0 ? `${workList.upToDate} already up to date` : "",
|
|
142
|
+
duration(result.durationMs),
|
|
143
|
+
money(result.costUsd),
|
|
144
|
+
].filter((part) => part !== "");
|
|
145
|
+
return parts.join(" · ");
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/** Dim warnings for work-list diagnostics (e.g. a factory-form meta file). */
|
|
149
|
+
export const diagnosticLines = (diagnostics: Diagnostic[]): string[] =>
|
|
150
|
+
diagnostics.map(
|
|
151
|
+
(diagnostic) =>
|
|
152
|
+
` ${COLORS.yellow}⚠${COLORS.reset} ${COLORS.dim}${diagnostic.message}${COLORS.reset}`
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
/** Every (source, locale, status) drift row in a work list, pages then meta. */
|
|
156
|
+
const driftRows = (
|
|
157
|
+
workList: TranslateWorkList
|
|
158
|
+
): { locale: string; sourceRel: string; status: WorkStatus }[] =>
|
|
159
|
+
workList.items.flatMap((item) =>
|
|
160
|
+
item.kind === "page"
|
|
161
|
+
? [
|
|
162
|
+
{
|
|
163
|
+
locale: item.locale,
|
|
164
|
+
sourceRel: item.sourceRel,
|
|
165
|
+
status: item.status,
|
|
166
|
+
},
|
|
167
|
+
]
|
|
168
|
+
: item.entries.map((entry: MetaWorkEntry) => ({
|
|
169
|
+
locale: item.locale,
|
|
170
|
+
sourceRel: entry.meta.sourceRel,
|
|
171
|
+
status: entry.status,
|
|
172
|
+
}))
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
/** One line per missing/stale pair, plus dim lines for untracked adoptions. */
|
|
176
|
+
export const checkLines = (workList: TranslateWorkList): string[] => [
|
|
177
|
+
...driftRows(workList).map(
|
|
178
|
+
(row) =>
|
|
179
|
+
` ${COLORS.red}✖${COLORS.reset} ${row.sourceRel} → ${row.locale} ${COLORS.dim}${row.status}${COLORS.reset}`
|
|
180
|
+
),
|
|
181
|
+
...workList.untracked.map(
|
|
182
|
+
(entry) =>
|
|
183
|
+
` ${COLORS.dim}⊘ ${entry.sourceRel} → ${entry.locale} untracked (adopted by the next translate run)${COLORS.reset}`
|
|
184
|
+
),
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
/** The `--check` totals: `2 missing · 1 stale · 1 untracked · 14 up to date`. */
|
|
188
|
+
export const checkSummaryLine = (workList: TranslateWorkList): string => {
|
|
189
|
+
const rows = driftRows(workList);
|
|
190
|
+
const missing = rows.filter((row) => row.status === "missing").length;
|
|
191
|
+
const stale = rows.filter((row) => row.status === "stale").length;
|
|
192
|
+
const parts = [
|
|
193
|
+
missing > 0 ? `${missing} missing` : "",
|
|
194
|
+
stale > 0 ? `${stale} stale` : "",
|
|
195
|
+
workList.untracked.length > 0
|
|
196
|
+
? `${workList.untracked.length} untracked`
|
|
197
|
+
: "",
|
|
198
|
+
`${workList.upToDate} up to date`,
|
|
199
|
+
].filter((part) => part !== "");
|
|
200
|
+
return parts.join(" · ");
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
/** Whether a work list fails the `--check` gate (untracked never does). */
|
|
204
|
+
export const hasDrift = (workList: TranslateWorkList): boolean =>
|
|
205
|
+
workList.items.length > 0;
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* The machine-readable `--check` report. The `diagnostics` + `summary` shape
|
|
209
|
+
* matches `blume validate/audit/eval --json` exactly, with the drift report
|
|
210
|
+
* under `translate`.
|
|
211
|
+
*/
|
|
212
|
+
export const checkReportJson = (workList: TranslateWorkList): string => {
|
|
213
|
+
const locales: Record<
|
|
214
|
+
string,
|
|
215
|
+
{ missing: string[]; stale: string[]; untracked: string[] }
|
|
216
|
+
> = {};
|
|
217
|
+
for (const locale of workList.targetLocales) {
|
|
218
|
+
locales[locale] = { missing: [], stale: [], untracked: [] };
|
|
219
|
+
}
|
|
220
|
+
for (const row of driftRows(workList)) {
|
|
221
|
+
locales[row.locale]?.[row.status].push(row.sourceRel);
|
|
222
|
+
}
|
|
223
|
+
for (const entry of workList.untracked) {
|
|
224
|
+
locales[entry.locale]?.untracked.push(entry.sourceRel);
|
|
225
|
+
}
|
|
226
|
+
return `${JSON.stringify(
|
|
227
|
+
{
|
|
228
|
+
diagnostics: workList.diagnostics,
|
|
229
|
+
summary: countBySeverity(workList.diagnostics),
|
|
230
|
+
translate: { locales, upToDate: workList.upToDate },
|
|
231
|
+
},
|
|
232
|
+
null,
|
|
233
|
+
2
|
|
234
|
+
)}\n`;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
/** One run result lowered to JSON-friendly, root-relative fields. */
|
|
238
|
+
const resultJson = (result: TranslateItemResult): Record<string, unknown> => ({
|
|
239
|
+
costUsd: result.costUsd,
|
|
240
|
+
detail: result.detail,
|
|
241
|
+
durationMs: result.durationMs,
|
|
242
|
+
kind: result.item.kind,
|
|
243
|
+
locale: result.item.locale,
|
|
244
|
+
status: result.status,
|
|
245
|
+
...(result.item.kind === "page"
|
|
246
|
+
? { source: result.item.sourceRel, target: result.item.targetRel }
|
|
247
|
+
: {
|
|
248
|
+
sources: result.item.entries.map((entry) => entry.meta.sourceRel),
|
|
249
|
+
}),
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
/** The machine-readable report for a translation run (`--json`). */
|
|
253
|
+
export const translateReportJson = (
|
|
254
|
+
result: TranslateResult,
|
|
255
|
+
workList: TranslateWorkList
|
|
256
|
+
): string => {
|
|
257
|
+
const diagnostics = [...workList.diagnostics, ...result.diagnostics];
|
|
258
|
+
return `${JSON.stringify(
|
|
259
|
+
{
|
|
260
|
+
diagnostics,
|
|
261
|
+
summary: countBySeverity(diagnostics),
|
|
262
|
+
translate: {
|
|
263
|
+
adopted: workList.untracked.length,
|
|
264
|
+
agent: result.agent,
|
|
265
|
+
costUsd: result.costUsd,
|
|
266
|
+
counts: result.counts,
|
|
267
|
+
durationMs: result.durationMs,
|
|
268
|
+
results: result.results.map(resultJson),
|
|
269
|
+
upToDate: workList.upToDate,
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
null,
|
|
273
|
+
2
|
|
274
|
+
)}\n`;
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
export interface ProgressRenderer {
|
|
278
|
+
onProgress: (
|
|
279
|
+
event:
|
|
280
|
+
| {
|
|
281
|
+
kind: "item-end";
|
|
282
|
+
index: number;
|
|
283
|
+
result: TranslateItemResult;
|
|
284
|
+
total: number;
|
|
285
|
+
}
|
|
286
|
+
| { kind: "item-start"; index: number; item: WorkItem; total: number }
|
|
287
|
+
) => void;
|
|
288
|
+
stop: () => void;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Live progress: on a TTY the in-flight items render as one spinner line
|
|
293
|
+
* rewritten in place (`\r\x1B[K`) — a concurrent run shows the oldest active
|
|
294
|
+
* item plus a `(+n more)` count — and each completion prints its permanent
|
|
295
|
+
* line above it; off-TTY (CI) there is no interval and only the permanent
|
|
296
|
+
* per-item lines print.
|
|
297
|
+
*/
|
|
298
|
+
export const createProgressRenderer = (options: {
|
|
299
|
+
isTTY: boolean;
|
|
300
|
+
write: (chunk: string) => void;
|
|
301
|
+
now?: () => number;
|
|
302
|
+
}): ProgressRenderer => {
|
|
303
|
+
const now = options.now ?? (() => performance.now());
|
|
304
|
+
let timer: ReturnType<typeof setInterval> | undefined;
|
|
305
|
+
const active = new Map<number, WorkItem>();
|
|
306
|
+
let done = 0;
|
|
307
|
+
let total = 0;
|
|
308
|
+
let startedAt = 0;
|
|
309
|
+
|
|
310
|
+
const paint = (): void => {
|
|
311
|
+
if (active.size > 0) {
|
|
312
|
+
const frame = Math.floor((now() - startedAt) / SPINNER_INTERVAL_MS);
|
|
313
|
+
options.write(
|
|
314
|
+
`${REWRITE}${spinnerLine([...active.values()], done, total, frame)}`
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
const clearTimer = (): void => {
|
|
319
|
+
if (timer) {
|
|
320
|
+
clearInterval(timer);
|
|
321
|
+
timer = undefined;
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
onProgress(event) {
|
|
327
|
+
({ total } = event);
|
|
328
|
+
if (event.kind === "item-start") {
|
|
329
|
+
active.set(event.index, event.item);
|
|
330
|
+
if (!options.isTTY) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (!timer) {
|
|
334
|
+
startedAt = now();
|
|
335
|
+
timer = setInterval(paint, SPINNER_INTERVAL_MS);
|
|
336
|
+
timer.unref?.();
|
|
337
|
+
}
|
|
338
|
+
paint();
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
active.delete(event.index);
|
|
342
|
+
done += 1;
|
|
343
|
+
const line = itemEndLine(event.result);
|
|
344
|
+
if (!options.isTTY) {
|
|
345
|
+
options.write(`${line}\n`);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
options.write(`${REWRITE}${line}\n`);
|
|
349
|
+
if (active.size > 0) {
|
|
350
|
+
paint();
|
|
351
|
+
} else {
|
|
352
|
+
clearTimer();
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
stop() {
|
|
356
|
+
clearTimer();
|
|
357
|
+
active.clear();
|
|
358
|
+
},
|
|
359
|
+
};
|
|
360
|
+
};
|