@alint-js/cli 0.1.4 → 0.1.5
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/README.md +8 -4
- package/dist/bin/index.mjs +1 -1
- package/dist/{cli-DUyZBCTT.mjs → cli-B0jEUKYS.mjs} +395 -72
- package/dist/index.mjs +1 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -184,6 +184,8 @@ alint config inspect src/index.ts
|
|
|
184
184
|
alint config providers list
|
|
185
185
|
alint config providers show openrouter
|
|
186
186
|
alint config models list
|
|
187
|
+
alint config models list --with-speed
|
|
188
|
+
alint config models list --with-speed --with-speed-concurrency openrouter=10 --with-speed-concurrency ollama=1
|
|
187
189
|
alint config models show ollama/qwen
|
|
188
190
|
alint config models probe --endpoint http://localhost:11434/v1
|
|
189
191
|
alint config models rm qwen --provider ollama
|
|
@@ -194,6 +196,8 @@ When a model ID exists under multiple providers, qualify it as `<provider>/<mode
|
|
|
194
196
|
|
|
195
197
|
`models rm` removes one exact configured model. `models prune` probes provider model endpoints and destructively removes configured IDs that are no longer reported. Interactive prune asks for confirmation; scripts must pass `-N --yes`.
|
|
196
198
|
|
|
199
|
+
`models list --with-speed` sends live streamed requests to every configured model. It reports median repeat and non-repeat latency, non-repeat output throughput, and successful attempts. Each model receives one repeat warm-up, three measured repeat requests, and three non-repeat requests. Model jobs run concurrently within independent provider limits: OpenRouter defaults to 20, while CLIProxyAPI and other providers default to 2. Repeat `--with-speed-concurrency <provider-id>=<limit>` to override one or more configured providers. Requests within one model remain serial so its repeat and non-repeat cache measurements do not overlap. An unavailable model is shown as `errored` without preventing the remaining models from running. On a TTY, spinners and a refreshable table show active models, phases, and samples. Each request uses the configured runner timeout, or 60 seconds when none is configured. This command consumes provider tokens and may incur charges.
|
|
200
|
+
|
|
197
201
|
Save machine-readable output and inspect it later without rerunning model calls:
|
|
198
202
|
|
|
199
203
|
```bash
|
|
@@ -339,7 +343,7 @@ Reference the directory through the same static `plugins` table:
|
|
|
339
343
|
```toml
|
|
340
344
|
[[config.group]]
|
|
341
345
|
files = [ "src/**/*.py" ]
|
|
342
|
-
language = "
|
|
346
|
+
language = "plaintext"
|
|
343
347
|
|
|
344
348
|
[config.group.plugins]
|
|
345
349
|
arch = "./rules/architecture"
|
|
@@ -364,7 +368,7 @@ Rule severities follow the familiar lint convention:
|
|
|
364
368
|
- `"warn"` or `1` reports a warning.
|
|
365
369
|
- `"error"` or `2` reports an error.
|
|
366
370
|
|
|
367
|
-
Flat configs can analyze non-JavaScript files by selecting `
|
|
371
|
+
Flat configs can analyze non-JavaScript files by selecting `plaintext`. Language ids follow [VS Code's language identifiers](https://code.visualstudio.com/docs/languages/identifiers), so a plugin that registers a language should use the same id:
|
|
368
372
|
|
|
369
373
|
```ts
|
|
370
374
|
import docsPlugin from '@your-alint-config/docs-rules'
|
|
@@ -374,7 +378,7 @@ import { defineConfig } from '@alint-js/cli'
|
|
|
374
378
|
export default defineConfig([
|
|
375
379
|
{
|
|
376
380
|
files: ['docs/**/*.md', '**/*.txt'],
|
|
377
|
-
language: '
|
|
381
|
+
language: 'plaintext',
|
|
378
382
|
plugins: {
|
|
379
383
|
docs: docsPlugin,
|
|
380
384
|
},
|
|
@@ -522,7 +526,7 @@ defineRule({
|
|
|
522
526
|
| [`@alint-js/agent-pi`](https://github.com/moeru-ai/alint/tree/main/packages/agent-pi) | Pi-backed `AgentAdapter`. |
|
|
523
527
|
| [`@alint-js/plugin-example`](https://github.com/moeru-ai/alint/tree/main/packages/plugin-example) | Example TypeScript/JavaScript model-backed rules. |
|
|
524
528
|
| [`@alint-js/plugin-example-agent`](https://github.com/moeru-ai/alint/tree/main/packages/plugin-example-agent) | Example plugin for framework-agnostic agentic rules. |
|
|
525
|
-
| [`@alint-js/plugin-example-go`](https://github.com/moeru-ai/alint/tree/main/packages/plugin-example-go) | Example semantic Go review plugin using `
|
|
529
|
+
| [`@alint-js/plugin-example-go`](https://github.com/moeru-ai/alint/tree/main/packages/plugin-example-go) | Example semantic Go review plugin using `plaintext`. |
|
|
526
530
|
|
|
527
531
|
## Documentation Automation
|
|
528
532
|
|
package/dist/bin/index.mjs
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import process from "node:process";
|
|
2
2
|
import { inspect } from "node:util";
|
|
3
3
|
import { cac } from "cac";
|
|
4
|
-
import { appendFile, mkdir, readFile, readdir, rm, stat } from "node:fs/promises";
|
|
4
|
+
import { appendFile, mkdir, opendir, readFile, readdir, rm, stat } from "node:fs/promises";
|
|
5
5
|
import { addProviderModels, getGlobalSetupConfigPath, getProjectSetupConfigPath, getStatsDir, installStaticPlugins, loadAlintConfig, loadSetupConfig, mergeSetupConfigs, pruneProviderModels, removeProviderModels, replaceSetupProvider, setProviderEndpoint, setProviderHeader, unsetProviderHeader, writeSetupConfig } from "@alint-js/config";
|
|
6
|
-
import { AlintRunCancelledError, AlintRunError, hasDiscoveryFilePatterns, matchesDiscoveryFile, normalizeConfig, resolveConfigForDirectory, resolveConfigForFile, runAlint } from "@alint-js/core";
|
|
6
|
+
import { AlintRunCancelledError, AlintRunError, benchmarkModels, hasDiscoveryFilePatterns, matchesDiscoveryFile, normalizeConfig, resolveConfigForDirectory, resolveConfigForFile, runAlint } from "@alint-js/core";
|
|
7
7
|
import { isAbsolute, join, relative, resolve } from "pathe";
|
|
8
|
-
import { getBorderCharacters, table } from "table";
|
|
9
8
|
import { errorMessageFrom } from "@moeru/std/error";
|
|
9
|
+
import { getBorderCharacters, table } from "table";
|
|
10
10
|
import { createColors } from "tinyrainbow";
|
|
11
11
|
import cliSpinners from "cli-spinners";
|
|
12
|
+
import fastStringWidth from "fast-string-width";
|
|
12
13
|
import { relative as relative$1 } from "node:path";
|
|
13
14
|
import fastStringTruncatedWidth from "fast-string-truncated-width";
|
|
14
|
-
import fastStringWidth from "fast-string-width";
|
|
15
15
|
import Gitignore from "gitignore-fs";
|
|
16
16
|
import { Minimatch, minimatch } from "minimatch";
|
|
17
17
|
import { errorMessageFrom as errorMessageFrom$1 } from "@moeru/std";
|
|
18
18
|
//#region package.json
|
|
19
|
-
var version = "0.1.
|
|
19
|
+
var version = "0.1.5";
|
|
20
20
|
//#endregion
|
|
21
21
|
//#region src/cli/output.ts
|
|
22
22
|
function escapeLineValue(value) {
|
|
@@ -218,6 +218,7 @@ function shouldSkipOptionValue(arg) {
|
|
|
218
218
|
"--rule-concurrency",
|
|
219
219
|
"--since",
|
|
220
220
|
"--timeout-ms",
|
|
221
|
+
"--with-speed-concurrency",
|
|
221
222
|
"-c",
|
|
222
223
|
"-f",
|
|
223
224
|
"-l"
|
|
@@ -246,7 +247,43 @@ async function runConfigInspectCommand(path, configPath, io) {
|
|
|
246
247
|
return 0;
|
|
247
248
|
}
|
|
248
249
|
//#endregion
|
|
250
|
+
//#region src/cli/reporters/progress/bar.ts
|
|
251
|
+
const minBarWidth = 4;
|
|
252
|
+
const maxBarWidth = 16;
|
|
253
|
+
const animationThreshold = .4;
|
|
254
|
+
function formatMiniBar(options) {
|
|
255
|
+
if (options.planned <= 0 || options.width < minBarWidth) return "";
|
|
256
|
+
const width = Math.min(Math.max(Math.floor(options.width), minBarWidth), maxBarWidth);
|
|
257
|
+
const ratio = Math.min(Math.max(options.completed / options.planned, 0), 1);
|
|
258
|
+
const completeCells = Math.min(Math.floor(ratio * width), width);
|
|
259
|
+
if (width - completeCells === 0) return `[${"█".repeat(width)}]`;
|
|
260
|
+
if (ratio < animationThreshold) return formatStaticBar(completeCells, width);
|
|
261
|
+
const frontierIndex = completeCells;
|
|
262
|
+
const phase = positiveModulo(options.tick, frontierIndex + 2);
|
|
263
|
+
if (phase === frontierIndex + 1) return formatStaticBar(completeCells, width);
|
|
264
|
+
return `[${Array.from({ length: width }, (_, index) => {
|
|
265
|
+
if (phase === 0) {
|
|
266
|
+
if (index === 0) return "▓";
|
|
267
|
+
if (index <= frontierIndex) return "█";
|
|
268
|
+
return "░";
|
|
269
|
+
}
|
|
270
|
+
if (index < phase) return "█";
|
|
271
|
+
if (index === phase) return "▓";
|
|
272
|
+
if (index === phase + 1 && index <= frontierIndex) return "░";
|
|
273
|
+
if (index <= frontierIndex) return "█";
|
|
274
|
+
return "░";
|
|
275
|
+
}).join("")}]`;
|
|
276
|
+
}
|
|
277
|
+
function formatStaticBar(completeCells, width) {
|
|
278
|
+
return `[${"█".repeat(completeCells)}${"░".repeat(width - completeCells)}]`;
|
|
279
|
+
}
|
|
280
|
+
function positiveModulo(value, divisor) {
|
|
281
|
+
return (Math.floor(value) % divisor + divisor) % divisor;
|
|
282
|
+
}
|
|
283
|
+
//#endregion
|
|
249
284
|
//#region src/cli/provider-registry.ts
|
|
285
|
+
const colors$4 = createColors({ force: true });
|
|
286
|
+
const defaultBenchmarkConcurrency = 2;
|
|
250
287
|
function formatDuplicateModelIdentity(identity) {
|
|
251
288
|
return [
|
|
252
289
|
`model "${escapeLineValue(identity)}" is configured more than once.`,
|
|
@@ -256,6 +293,7 @@ function formatDuplicateModelIdentity(identity) {
|
|
|
256
293
|
}
|
|
257
294
|
const providerSetupSources = [
|
|
258
295
|
{
|
|
296
|
+
benchmarkConcurrency: 2,
|
|
259
297
|
defaultEndpoint: "http://127.0.0.1:8317/v1",
|
|
260
298
|
defaultProviderId: "CLIProxyAPI",
|
|
261
299
|
label: "CLIProxyAPI",
|
|
@@ -263,11 +301,21 @@ const providerSetupSources = [
|
|
|
263
301
|
value: "cliProxyApi"
|
|
264
302
|
},
|
|
265
303
|
{
|
|
304
|
+
benchmarkConcurrency: 2,
|
|
266
305
|
label: "Custom OpenAI-compatible provider",
|
|
267
306
|
probeModels: true,
|
|
268
307
|
value: "custom"
|
|
269
308
|
},
|
|
270
309
|
{
|
|
310
|
+
benchmarkConcurrency: 20,
|
|
311
|
+
defaultEndpoint: "https://openrouter.ai/api/v1",
|
|
312
|
+
defaultProviderId: "openrouter",
|
|
313
|
+
label: "OpenRouter",
|
|
314
|
+
probeModels: true,
|
|
315
|
+
value: "openrouter"
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
benchmarkConcurrency: 2,
|
|
271
319
|
defaultEndpoint: "https://api.cerebras.ai/v1",
|
|
272
320
|
defaultProviderId: "cerebras",
|
|
273
321
|
label: "Cerebras",
|
|
@@ -275,6 +323,7 @@ const providerSetupSources = [
|
|
|
275
323
|
value: "cerebras"
|
|
276
324
|
},
|
|
277
325
|
{
|
|
326
|
+
benchmarkConcurrency: 2,
|
|
278
327
|
defaultEndpoint: "https://api.groq.com/openai/v1",
|
|
279
328
|
defaultProviderId: "groq",
|
|
280
329
|
label: "Groq",
|
|
@@ -282,12 +331,14 @@ const providerSetupSources = [
|
|
|
282
331
|
value: "groq"
|
|
283
332
|
},
|
|
284
333
|
{
|
|
334
|
+
benchmarkConcurrency: 2,
|
|
285
335
|
defaultEndpoint: "http://localhost:11434/v1",
|
|
286
336
|
label: "Ollama",
|
|
287
337
|
probeModels: true,
|
|
288
338
|
value: "ollama"
|
|
289
339
|
},
|
|
290
340
|
{
|
|
341
|
+
benchmarkConcurrency: 2,
|
|
291
342
|
label: "Manual model entry",
|
|
292
343
|
probeModels: false,
|
|
293
344
|
value: "manual"
|
|
@@ -353,6 +404,88 @@ function formatAmbiguousModels(request, candidates) {
|
|
|
353
404
|
...choices.map((choice) => ` ${escapeLineValue(choice)}`)
|
|
354
405
|
].join("\n")}\n`;
|
|
355
406
|
}
|
|
407
|
+
function formatModelBenchmarkList(config, benchmarks, options = {}) {
|
|
408
|
+
const benchmarksByIdentity = /* @__PURE__ */ new Map();
|
|
409
|
+
for (const benchmark of benchmarks) {
|
|
410
|
+
const identity = `${benchmark.providerId}/${benchmark.modelId}`;
|
|
411
|
+
const matches = benchmarksByIdentity.get(identity) ?? [];
|
|
412
|
+
matches.push(benchmark);
|
|
413
|
+
benchmarksByIdentity.set(identity, matches);
|
|
414
|
+
}
|
|
415
|
+
return formatBenchmarkTable(flattenModels(config).map(({ model, provider }) => {
|
|
416
|
+
const benchmark = benchmarksByIdentity.get(`${provider.id}/${model.id}`)?.shift();
|
|
417
|
+
return benchmark === void 0 ? [
|
|
418
|
+
model.id,
|
|
419
|
+
provider.id,
|
|
420
|
+
model.name ?? model.id,
|
|
421
|
+
"n/a",
|
|
422
|
+
"n/a",
|
|
423
|
+
"n/a",
|
|
424
|
+
"0/0"
|
|
425
|
+
] : benchmarkRow(model.id, model.name ?? model.id, provider.id, benchmark, options.color === true);
|
|
426
|
+
}));
|
|
427
|
+
}
|
|
428
|
+
function formatModelBenchmarkProgressFrame(config, progress, options) {
|
|
429
|
+
const columns = {
|
|
430
|
+
3: {
|
|
431
|
+
truncate: 10,
|
|
432
|
+
width: 10
|
|
433
|
+
},
|
|
434
|
+
4: {
|
|
435
|
+
truncate: 12,
|
|
436
|
+
width: 12
|
|
437
|
+
},
|
|
438
|
+
5: {
|
|
439
|
+
truncate: 16,
|
|
440
|
+
width: 16
|
|
441
|
+
},
|
|
442
|
+
6: {
|
|
443
|
+
truncate: 8,
|
|
444
|
+
width: 8
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
const models = flattenModels(config);
|
|
448
|
+
const modelCapacity = options.maxRows === void 0 ? models.length : Math.max(options.maxRows - 2, 1);
|
|
449
|
+
const activeByModelIndex = new Map(progress.active.map((current) => [current.modelIndex, current]));
|
|
450
|
+
const completed = progress.results.filter((result) => result !== void 0).length;
|
|
451
|
+
const activeIndex = progress.active[0]?.modelIndex ?? completed;
|
|
452
|
+
const start = Math.min(Math.max(activeIndex - Math.floor(modelCapacity / 2), 0), Math.max(models.length - modelCapacity, 0));
|
|
453
|
+
const end = Math.min(start + modelCapacity, models.length);
|
|
454
|
+
const visibleModels = models.slice(start, end);
|
|
455
|
+
const bodyRows = formatBenchmarkTable(visibleModels.map(({ model, provider }, visibleIndex) => {
|
|
456
|
+
const modelIndex = start + visibleIndex;
|
|
457
|
+
const benchmark = progress.results[modelIndex];
|
|
458
|
+
if (benchmark !== void 0) return benchmarkRow(model.id, model.name ?? model.id, provider.id, benchmark, options.color === true);
|
|
459
|
+
const current = activeByModelIndex.get(modelIndex);
|
|
460
|
+
if (current !== void 0) return activeBenchmarkRow(model.id, model.name ?? model.id, provider.id, current, options);
|
|
461
|
+
return [
|
|
462
|
+
model.id,
|
|
463
|
+
provider.id,
|
|
464
|
+
model.name ?? model.id,
|
|
465
|
+
"pending",
|
|
466
|
+
"pending",
|
|
467
|
+
"pending",
|
|
468
|
+
"0/0"
|
|
469
|
+
];
|
|
470
|
+
}), columns).trimEnd().split("\n");
|
|
471
|
+
const range = models.length > visibleModels.length ? ` · showing models ${start + 1}-${end} of ${models.length}` : "";
|
|
472
|
+
const bar = formatMiniBar({
|
|
473
|
+
completed,
|
|
474
|
+
planned: progress.modelsTotal,
|
|
475
|
+
tick: options.tick,
|
|
476
|
+
width: 12
|
|
477
|
+
});
|
|
478
|
+
const running = progress.active.length === 0 ? "" : ` · ${progress.active.length} running`;
|
|
479
|
+
const speedColumn = bodyRows[0]?.indexOf("repeat") ?? -1;
|
|
480
|
+
if (speedColumn < 0) throw new Error("Benchmark progress table is missing its speed columns.");
|
|
481
|
+
return { rows: [...bodyRows.map((row) => ({
|
|
482
|
+
prefix: row.slice(0, speedColumn),
|
|
483
|
+
suffix: row.slice(speedColumn)
|
|
484
|
+
})), {
|
|
485
|
+
prefix: "",
|
|
486
|
+
suffix: `${completed}/${progress.modelsTotal} models complete ${bar}${running}${range}`
|
|
487
|
+
}] };
|
|
488
|
+
}
|
|
356
489
|
function formatModelList(config) {
|
|
357
490
|
return formatTable([[
|
|
358
491
|
"id",
|
|
@@ -426,10 +559,65 @@ async function probeModels(endpoint, headers = {}) {
|
|
|
426
559
|
if (!isRecord$1(body) || !Array.isArray(body.data) || !body.data.every(isModelRecord)) throw new TypeError("Expected OpenAI-compatible models response with data array.");
|
|
427
560
|
return body.data.map((model) => model.id);
|
|
428
561
|
}
|
|
562
|
+
function resolveProviderBenchmarkConcurrency(config, overrides = []) {
|
|
563
|
+
const concurrency = Object.fromEntries(config.providers.map((provider) => [provider.id, findProviderSetupSourceByEndpoint(provider.endpoint)?.benchmarkConcurrency ?? defaultBenchmarkConcurrency]));
|
|
564
|
+
for (const override of overrides) {
|
|
565
|
+
const separatorIndex = override.indexOf("=");
|
|
566
|
+
if (separatorIndex <= 0 || separatorIndex === override.length - 1) throw new Error("Invalid benchmark concurrency override. Expected <provider-id>=<limit>.");
|
|
567
|
+
const providerId = override.slice(0, separatorIndex);
|
|
568
|
+
const value = override.slice(separatorIndex + 1);
|
|
569
|
+
const limit = Number(value);
|
|
570
|
+
if (!(providerId in concurrency)) throw new Error(`Unknown provider "${escapeLineValue(providerId)}" in benchmark concurrency override.`);
|
|
571
|
+
if (!/^\d+$/u.test(value) || !Number.isSafeInteger(limit) || limit < 1) throw new Error(`Benchmark concurrency for provider "${escapeLineValue(providerId)}" must be a positive integer.`);
|
|
572
|
+
concurrency[providerId] = limit;
|
|
573
|
+
}
|
|
574
|
+
return concurrency;
|
|
575
|
+
}
|
|
576
|
+
function activeBenchmarkRow(modelId, modelName, providerId, current, options) {
|
|
577
|
+
const frame = options.color === true ? colors$4.cyan(options.frame) : options.frame;
|
|
578
|
+
const active = current.warmup ? "warm-up" : `${frame} ${current.sample}/${current.samples}`;
|
|
579
|
+
return [
|
|
580
|
+
modelId,
|
|
581
|
+
providerId,
|
|
582
|
+
modelName,
|
|
583
|
+
current.phase === "repeat" ? active : "done",
|
|
584
|
+
current.phase === "non-repeat" ? active : "pending",
|
|
585
|
+
current.phase === "non-repeat" ? "measuring" : "pending",
|
|
586
|
+
`${current.success.completed}/${current.success.attempted}`
|
|
587
|
+
];
|
|
588
|
+
}
|
|
589
|
+
function benchmarkRow(modelId, modelName, providerId, benchmark, color) {
|
|
590
|
+
const errored = color ? colors$4.red("errored") : "errored";
|
|
591
|
+
return [
|
|
592
|
+
modelId,
|
|
593
|
+
providerId,
|
|
594
|
+
modelName,
|
|
595
|
+
benchmark.error === void 0 ? formatDuration$1(benchmark.repeatMs) : errored,
|
|
596
|
+
benchmark.error === void 0 ? formatDuration$1(benchmark.nonRepeatMs) : errored,
|
|
597
|
+
benchmark.error === void 0 ? formatThroughput(benchmark.throughput) : errored,
|
|
598
|
+
`${benchmark.success.completed}/${benchmark.success.attempted}`
|
|
599
|
+
];
|
|
600
|
+
}
|
|
429
601
|
function canonicalIdentity(candidate) {
|
|
430
602
|
return `${candidate.provider.id}/${candidate.model.id}`;
|
|
431
603
|
}
|
|
432
|
-
function
|
|
604
|
+
function formatBenchmarkTable(rows, columns) {
|
|
605
|
+
return formatTable([[
|
|
606
|
+
"id",
|
|
607
|
+
"provider",
|
|
608
|
+
"name",
|
|
609
|
+
"repeat",
|
|
610
|
+
"non-repeat",
|
|
611
|
+
"throughput",
|
|
612
|
+
"success"
|
|
613
|
+
], ...rows], columns);
|
|
614
|
+
}
|
|
615
|
+
function formatDuration$1(durationMs) {
|
|
616
|
+
if (durationMs === void 0) return "n/a";
|
|
617
|
+
if (durationMs < 1e3) return `${Math.round(durationMs)}ms`;
|
|
618
|
+
return `${stripTrailingZeros((durationMs / 1e3).toFixed(2))}s`;
|
|
619
|
+
}
|
|
620
|
+
function formatTable(rows, columns) {
|
|
433
621
|
if (rows.length <= 1) return "";
|
|
434
622
|
return table(rows, {
|
|
435
623
|
border: getBorderCharacters("void"),
|
|
@@ -437,9 +625,13 @@ function formatTable(rows) {
|
|
|
437
625
|
paddingLeft: 0,
|
|
438
626
|
paddingRight: 2
|
|
439
627
|
},
|
|
628
|
+
columns,
|
|
440
629
|
drawHorizontalLine: () => false
|
|
441
630
|
});
|
|
442
631
|
}
|
|
632
|
+
function formatThroughput(throughput) {
|
|
633
|
+
return throughput === void 0 ? "n/a" : `${throughput.toFixed(1)} tok/s`;
|
|
634
|
+
}
|
|
443
635
|
function isModelRecord(value) {
|
|
444
636
|
return isRecord$1(value) && typeof value.id === "string" && value.id.length > 0;
|
|
445
637
|
}
|
|
@@ -449,6 +641,9 @@ function isRecord$1(value) {
|
|
|
449
641
|
function normalizeProviderIdBase(value) {
|
|
450
642
|
return value.replace(/[^a-z0-9]+/gi, "-").replace(/^-|-$/g, "").toLowerCase() || "provider";
|
|
451
643
|
}
|
|
644
|
+
function stripTrailingZeros(value) {
|
|
645
|
+
return String(Number(value));
|
|
646
|
+
}
|
|
452
647
|
//#endregion
|
|
453
648
|
//#region src/cli/commands/config/setup-config.ts
|
|
454
649
|
function formatUnknownProvider(providerId, scope) {
|
|
@@ -471,16 +666,163 @@ async function loadScopedSetupConfig(io, local) {
|
|
|
471
666
|
};
|
|
472
667
|
}
|
|
473
668
|
//#endregion
|
|
669
|
+
//#region src/cli/commands/config/models/benchmark-progress.ts
|
|
670
|
+
/** Keeps the benchmark preview live without mixing transient frames into the final stdout table. */
|
|
671
|
+
function createModelBenchmarkProgressDisplay(options) {
|
|
672
|
+
const frames = cliSpinners.dots.frames;
|
|
673
|
+
let frameIndex = 0;
|
|
674
|
+
let latest;
|
|
675
|
+
let previous;
|
|
676
|
+
let interval;
|
|
677
|
+
const render = () => {
|
|
678
|
+
if (latest === void 0) return;
|
|
679
|
+
const next = formatModelBenchmarkProgressFrame(options.config, latest, {
|
|
680
|
+
color: options.color,
|
|
681
|
+
frame: frames[frameIndex % frames.length] ?? "",
|
|
682
|
+
maxRows: options.output.rows,
|
|
683
|
+
tick: 0
|
|
684
|
+
});
|
|
685
|
+
if (previous === void 0 || !sameLayout(previous, next)) {
|
|
686
|
+
clearFrame(options.output, previous?.rows.length ?? 0);
|
|
687
|
+
options.output.write(`${next.rows.map((row) => `${row.prefix}${row.suffix}`).join("\n")}\r`);
|
|
688
|
+
previous = next;
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
for (const [rowIndex, row] of next.rows.entries()) {
|
|
692
|
+
const previousRow = previous.rows[rowIndex];
|
|
693
|
+
if (previousRow !== void 0 && previousRow.suffix !== row.suffix) patchSuffix(options.output, rowIndex, next.rows.length, fastStringWidth(row.prefix), row.suffix);
|
|
694
|
+
}
|
|
695
|
+
previous = next;
|
|
696
|
+
};
|
|
697
|
+
const start = () => {
|
|
698
|
+
if (interval === void 0) {
|
|
699
|
+
interval = globalThis.setInterval(() => {
|
|
700
|
+
frameIndex += 1;
|
|
701
|
+
render();
|
|
702
|
+
}, cliSpinners.dots.interval);
|
|
703
|
+
interval.unref();
|
|
704
|
+
}
|
|
705
|
+
render();
|
|
706
|
+
};
|
|
707
|
+
/**
|
|
708
|
+
* Applies one core benchmark progress snapshot to the transient TTY table.
|
|
709
|
+
*
|
|
710
|
+
* Triggering workflow:
|
|
711
|
+
*
|
|
712
|
+
* `benchmarkModels` `onProgress`
|
|
713
|
+
* -> `config models list --with-speed`
|
|
714
|
+
* -> `ModelBenchmarkProgressDisplay.update`
|
|
715
|
+
*
|
|
716
|
+
* Upstream:
|
|
717
|
+
* - `benchmarkModels` `onProgress` callback registered by `runListModelsCommand`
|
|
718
|
+
*
|
|
719
|
+
* Downstream:
|
|
720
|
+
* - incremental benchmark table renderer `start` or `render`
|
|
721
|
+
*/
|
|
722
|
+
const update = (progress) => {
|
|
723
|
+
latest = progress;
|
|
724
|
+
if (interval === void 0) {
|
|
725
|
+
start();
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
render();
|
|
729
|
+
};
|
|
730
|
+
return {
|
|
731
|
+
finish: () => {
|
|
732
|
+
if (interval !== void 0) {
|
|
733
|
+
globalThis.clearInterval(interval);
|
|
734
|
+
interval = void 0;
|
|
735
|
+
}
|
|
736
|
+
clearFrame(options.output, previous?.rows.length ?? 0);
|
|
737
|
+
previous = void 0;
|
|
738
|
+
},
|
|
739
|
+
update
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
function clearFrame(output, rowCount) {
|
|
743
|
+
if (rowCount === 0) return;
|
|
744
|
+
let sequence = "\r\x1B[K";
|
|
745
|
+
for (let row = 1; row < rowCount; row += 1) sequence += "\r\x1B[1A\x1B[K";
|
|
746
|
+
output.write(sequence);
|
|
747
|
+
}
|
|
748
|
+
function patchSuffix(output, rowIndex, rowCount, column, suffix) {
|
|
749
|
+
const rowsUp = rowCount - rowIndex - 1;
|
|
750
|
+
const moveUp = rowsUp === 0 ? "" : `\x1B[${rowsUp}A`;
|
|
751
|
+
const moveDown = rowsUp === 0 ? "" : `\x1B[${rowsUp}B`;
|
|
752
|
+
const moveRight = column === 0 ? "" : `\x1B[${column}C`;
|
|
753
|
+
output.write(`\r${moveUp}${moveRight}\x1B[K${suffix}\r${moveDown}`);
|
|
754
|
+
}
|
|
755
|
+
function sameLayout(previous, next) {
|
|
756
|
+
return previous.rows.length === next.rows.length && previous.rows.every((row, index) => row.prefix === next.rows[index]?.prefix);
|
|
757
|
+
}
|
|
758
|
+
//#endregion
|
|
474
759
|
//#region src/cli/commands/config/models/ls.ts
|
|
475
760
|
const ls$1 = defineCommand({
|
|
476
|
-
|
|
477
|
-
context.io.stdout.write(formatModelList(await loadMergedSetupConfig(context.io)));
|
|
478
|
-
return 0;
|
|
479
|
-
},
|
|
761
|
+
action: runListModelsCommand,
|
|
480
762
|
alias: ["ls"],
|
|
481
763
|
description: "List configured models",
|
|
482
|
-
name: "list"
|
|
764
|
+
name: "list",
|
|
765
|
+
options: [{
|
|
766
|
+
description: "Benchmark configured models with live requests",
|
|
767
|
+
flags: "--with-speed"
|
|
768
|
+
}, {
|
|
769
|
+
description: "Override one provider benchmark concurrency (repeatable)",
|
|
770
|
+
flags: "--with-speed-concurrency <provider-id=limit>"
|
|
771
|
+
}]
|
|
483
772
|
});
|
|
773
|
+
/**
|
|
774
|
+
* Lists configured models and optionally benchmarks each model with live streamed requests.
|
|
775
|
+
*
|
|
776
|
+
* Triggering workflow:
|
|
777
|
+
*
|
|
778
|
+
* {@link defineCommand}
|
|
779
|
+
* -> `config models list`
|
|
780
|
+
* -> {@link runListModelsCommand}
|
|
781
|
+
*
|
|
782
|
+
* Upstream:
|
|
783
|
+
* - `config models list` command dispatch
|
|
784
|
+
*
|
|
785
|
+
* Downstream:
|
|
786
|
+
* - {@link loadMergedSetupConfig}
|
|
787
|
+
* - {@link benchmarkModels}
|
|
788
|
+
* - {@link createModelBenchmarkProgressDisplay}
|
|
789
|
+
* - `context.io.stdout.write`
|
|
790
|
+
*/
|
|
791
|
+
async function runListModelsCommand(context, options) {
|
|
792
|
+
const config = await loadMergedSetupConfig(context.io);
|
|
793
|
+
const concurrencyOverrides = options.withSpeedConcurrency === void 0 ? [] : [options.withSpeedConcurrency].flat();
|
|
794
|
+
if (options.withSpeed !== true) {
|
|
795
|
+
if (concurrencyOverrides.length > 0) {
|
|
796
|
+
context.io.stderr.write("--with-speed-concurrency requires --with-speed.\n");
|
|
797
|
+
return 2;
|
|
798
|
+
}
|
|
799
|
+
context.io.stdout.write(formatModelList(config));
|
|
800
|
+
return 0;
|
|
801
|
+
}
|
|
802
|
+
let providerConcurrency;
|
|
803
|
+
try {
|
|
804
|
+
providerConcurrency = resolveProviderBenchmarkConcurrency(config, concurrencyOverrides);
|
|
805
|
+
} catch (error) {
|
|
806
|
+
context.io.stderr.write(`${errorMessageFrom(error) ?? "Invalid benchmark concurrency override."}\n`);
|
|
807
|
+
return 2;
|
|
808
|
+
}
|
|
809
|
+
const progress = context.io.stderr.isTTY === true ? createModelBenchmarkProgressDisplay({
|
|
810
|
+
color: true,
|
|
811
|
+
config,
|
|
812
|
+
output: context.io.stderr
|
|
813
|
+
}) : void 0;
|
|
814
|
+
let benchmarks;
|
|
815
|
+
try {
|
|
816
|
+
benchmarks = await benchmarkModels(config, {
|
|
817
|
+
onProgress: (snapshot) => progress?.update(snapshot),
|
|
818
|
+
providerConcurrency
|
|
819
|
+
});
|
|
820
|
+
} finally {
|
|
821
|
+
progress?.finish();
|
|
822
|
+
}
|
|
823
|
+
context.io.stdout.write(formatModelBenchmarkList(config, benchmarks, { color: context.io.stdout.isTTY === true }));
|
|
824
|
+
return 0;
|
|
825
|
+
}
|
|
484
826
|
//#endregion
|
|
485
827
|
//#region src/cli/commands/config/probe.ts
|
|
486
828
|
function providerHeadersFromOptions(options) {
|
|
@@ -1794,40 +2136,6 @@ function countDiagnostics(diagnostics, severity) {
|
|
|
1794
2136
|
return diagnostics.filter((diagnostic) => diagnostic.severity === severity).length;
|
|
1795
2137
|
}
|
|
1796
2138
|
//#endregion
|
|
1797
|
-
//#region src/cli/reporters/progress/bar.ts
|
|
1798
|
-
const minBarWidth = 4;
|
|
1799
|
-
const maxBarWidth = 16;
|
|
1800
|
-
const animationThreshold = .4;
|
|
1801
|
-
function formatMiniBar(options) {
|
|
1802
|
-
if (options.planned <= 0 || options.width < minBarWidth) return "";
|
|
1803
|
-
const width = Math.min(Math.max(Math.floor(options.width), minBarWidth), maxBarWidth);
|
|
1804
|
-
const ratio = Math.min(Math.max(options.completed / options.planned, 0), 1);
|
|
1805
|
-
const completeCells = Math.min(Math.floor(ratio * width), width);
|
|
1806
|
-
if (width - completeCells === 0) return `[${"█".repeat(width)}]`;
|
|
1807
|
-
if (ratio < animationThreshold) return formatStaticBar(completeCells, width);
|
|
1808
|
-
const frontierIndex = completeCells;
|
|
1809
|
-
const phase = positiveModulo(options.tick, frontierIndex + 2);
|
|
1810
|
-
if (phase === frontierIndex + 1) return formatStaticBar(completeCells, width);
|
|
1811
|
-
return `[${Array.from({ length: width }, (_, index) => {
|
|
1812
|
-
if (phase === 0) {
|
|
1813
|
-
if (index === 0) return "▓";
|
|
1814
|
-
if (index <= frontierIndex) return "█";
|
|
1815
|
-
return "░";
|
|
1816
|
-
}
|
|
1817
|
-
if (index < phase) return "█";
|
|
1818
|
-
if (index === phase) return "▓";
|
|
1819
|
-
if (index === phase + 1 && index <= frontierIndex) return "░";
|
|
1820
|
-
if (index <= frontierIndex) return "█";
|
|
1821
|
-
return "░";
|
|
1822
|
-
}).join("")}]`;
|
|
1823
|
-
}
|
|
1824
|
-
function formatStaticBar(completeCells, width) {
|
|
1825
|
-
return `[${"█".repeat(completeCells)}${"░".repeat(width - completeCells)}]`;
|
|
1826
|
-
}
|
|
1827
|
-
function positiveModulo(value, divisor) {
|
|
1828
|
-
return (Math.floor(value) % divisor + divisor) % divisor;
|
|
1829
|
-
}
|
|
1830
|
-
//#endregion
|
|
1831
2139
|
//#region src/cli/reporters/progress/summary.ts
|
|
1832
2140
|
const colors$2 = createColors({ force: true });
|
|
1833
2141
|
const graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
@@ -1896,6 +2204,7 @@ function createSummaryProgressReporter(options) {
|
|
|
1896
2204
|
state: "running"
|
|
1897
2205
|
});
|
|
1898
2206
|
},
|
|
2207
|
+
onPlanningEnd: ({ progress }) => updateProgress(state, progress),
|
|
1899
2208
|
onPrepareStart: ({ startedAt }) => {
|
|
1900
2209
|
state.activeJobs.clear();
|
|
1901
2210
|
state.errorCount = 0;
|
|
@@ -1995,11 +2304,12 @@ function createInitialState() {
|
|
|
1995
2304
|
function createProgress() {
|
|
1996
2305
|
return {
|
|
1997
2306
|
execution: createCounts(),
|
|
2307
|
+
filesPlanned: 0,
|
|
1998
2308
|
filesTotal: 0,
|
|
1999
|
-
final: false,
|
|
2000
2309
|
jobsCompleted: 0,
|
|
2001
2310
|
jobsStarted: 0,
|
|
2002
|
-
jobsTotal: 0
|
|
2311
|
+
jobsTotal: 0,
|
|
2312
|
+
planningComplete: false
|
|
2003
2313
|
};
|
|
2004
2314
|
}
|
|
2005
2315
|
function createRows(state, options, now) {
|
|
@@ -2098,10 +2408,10 @@ function formatDuration(ms) {
|
|
|
2098
2408
|
function formatFooters(state, options, now) {
|
|
2099
2409
|
const terminal = terminalCount(state.execution);
|
|
2100
2410
|
const elapsed = state.runStartedAt === void 0 ? 0 : now - state.runStartedAt;
|
|
2101
|
-
const progressBar = state.progress.
|
|
2102
|
-
const eta = state.progress.
|
|
2103
|
-
const projectedTokens = state.progress.
|
|
2104
|
-
const discovering = state.progress.
|
|
2411
|
+
const progressBar = state.progress.planningComplete ? formatMiniBarSegment(terminal, state.execution.planned, state.animationTick, options) : "";
|
|
2412
|
+
const eta = state.progress.planningComplete ? estimateEta(elapsed, terminal, state.execution.planned) : "eta ?";
|
|
2413
|
+
const projectedTokens = state.progress.planningComplete && terminal > 0 && state.execution.planned > 0 ? Math.ceil((state.totalTokens + state.cachedTokens) * state.execution.planned / terminal) : void 0;
|
|
2414
|
+
const discovering = state.progress.planningComplete ? "" : " jobs (discovering)";
|
|
2105
2415
|
return [
|
|
2106
2416
|
fitRow(`${terminal}/${state.execution.planned}${discovering}${progressBar} ${formatDuration(elapsed)} -> ${eta === "eta ?" ? "~?" : eta.replace("eta ", "")}`, options.columns),
|
|
2107
2417
|
fitRow(`${state.execution.running} concurrency / ${state.execution.queued} queued / ${state.execution.cached} cached / ${state.execution.failed} failed`, options.columns),
|
|
@@ -2289,6 +2599,7 @@ function createRenderingProgressReporter(summary, renderer) {
|
|
|
2289
2599
|
//#endregion
|
|
2290
2600
|
//#region src/cli/commands/lint/discovery.ts
|
|
2291
2601
|
const minimatchOptions = { dot: true };
|
|
2602
|
+
const directoryBufferSize = 256;
|
|
2292
2603
|
var NoFilesFoundError = class extends Error {
|
|
2293
2604
|
globInputPaths;
|
|
2294
2605
|
pattern;
|
|
@@ -2376,15 +2687,16 @@ async function resolveInputFiles(options) {
|
|
|
2376
2687
|
})) continue;
|
|
2377
2688
|
directories.push(input);
|
|
2378
2689
|
if (!hasFilePatterns) continue;
|
|
2379
|
-
const directoryFiles =
|
|
2690
|
+
const directoryFiles = [];
|
|
2691
|
+
for await (const file of walkFiles(path, {
|
|
2380
2692
|
cwd: options.cwd,
|
|
2381
2693
|
gitignore: options.gitignore,
|
|
2382
2694
|
ignoredPatterns
|
|
2383
|
-
}))
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
if (hasFilePatterns && matchesDiscoveryFile(relativePath, options.config, { cwd: options.cwd })) candidates.push(relativePath);
|
|
2695
|
+
})) {
|
|
2696
|
+
const relativePath = normalizeRelativePath(options.cwd, file);
|
|
2697
|
+
if (hasFilePatterns && matchesDiscoveryFile(relativePath, options.config, { cwd: options.cwd })) directoryFiles.push(relativePath);
|
|
2387
2698
|
}
|
|
2699
|
+
for (const file of directoryFiles.sort()) candidates.push(file);
|
|
2388
2700
|
continue;
|
|
2389
2701
|
}
|
|
2390
2702
|
if (options.globInputPaths && isGlobPattern(input)) {
|
|
@@ -2397,7 +2709,7 @@ async function resolveInputFiles(options) {
|
|
|
2397
2709
|
pattern: input
|
|
2398
2710
|
});
|
|
2399
2711
|
if (matches.length === 0 && options.errorOnUnmatchedPattern) throw new NoFilesFoundError(input, { globInputPaths: options.globInputPaths });
|
|
2400
|
-
candidates.push(
|
|
2712
|
+
for (const match of matches) candidates.push(match);
|
|
2401
2713
|
continue;
|
|
2402
2714
|
}
|
|
2403
2715
|
if (options.errorOnUnmatchedPattern) throw new NoFilesFoundError(input, { globInputPaths: options.globInputPaths });
|
|
@@ -2412,20 +2724,19 @@ async function searchGlob(options) {
|
|
|
2412
2724
|
const rootStats = await statPath(root);
|
|
2413
2725
|
const pattern = normalizeGlobPattern(options.cwd, options.pattern);
|
|
2414
2726
|
if (!rootStats?.isDirectory()) return [];
|
|
2415
|
-
const
|
|
2727
|
+
const candidates = [];
|
|
2728
|
+
for await (const file of walkFiles(root, {
|
|
2416
2729
|
cwd: options.cwd,
|
|
2417
2730
|
gitignore: options.gitignore,
|
|
2418
2731
|
ignoredPatterns: options.ignoredPatterns
|
|
2419
|
-
}))
|
|
2420
|
-
const candidates = [];
|
|
2421
|
-
for (const file of files) {
|
|
2732
|
+
})) {
|
|
2422
2733
|
const relativePath = normalizeRelativePath(options.cwd, file);
|
|
2423
2734
|
if (matchesGlob(relativePath, pattern) && shouldIncludeGlobCandidate(relativePath, options.config, {
|
|
2424
2735
|
cwd: options.cwd,
|
|
2425
2736
|
hasFilePatterns: options.hasFilePatterns
|
|
2426
2737
|
})) candidates.push(relativePath);
|
|
2427
2738
|
}
|
|
2428
|
-
return candidates;
|
|
2739
|
+
return candidates.sort();
|
|
2429
2740
|
}
|
|
2430
2741
|
function shouldFilterGitignoredFiles(config) {
|
|
2431
2742
|
return normalizeConfig(config).some((item) => item.ignore?.gitignore === true);
|
|
@@ -2446,18 +2757,27 @@ async function statPath(path) {
|
|
|
2446
2757
|
throw error;
|
|
2447
2758
|
}
|
|
2448
2759
|
}
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2760
|
+
/**
|
|
2761
|
+
* Streams directory entries so unmatched trees never accumulate into recursive child arrays.
|
|
2762
|
+
* The consumer owns result ordering because filesystem iteration order is not stable.
|
|
2763
|
+
*/
|
|
2764
|
+
async function* walkFiles(root, options) {
|
|
2765
|
+
const pendingDirectories = [root];
|
|
2766
|
+
while (pendingDirectories.length > 0) {
|
|
2767
|
+
const currentDirectory = pendingDirectories.pop();
|
|
2768
|
+
if (currentDirectory === void 0) continue;
|
|
2769
|
+
const childDirectories = [];
|
|
2770
|
+
const directory = await opendir(currentDirectory, { bufferSize: directoryBufferSize });
|
|
2771
|
+
for await (const entry of directory) {
|
|
2772
|
+
const path = resolve(currentDirectory, entry.name);
|
|
2773
|
+
if (entry.isDirectory()) {
|
|
2774
|
+
if (!await shouldPruneDirectory(path, options)) childDirectories.push(path);
|
|
2775
|
+
continue;
|
|
2776
|
+
}
|
|
2777
|
+
if (entry.isFile()) yield path;
|
|
2457
2778
|
}
|
|
2458
|
-
|
|
2779
|
+
for (const path of childDirectories.reverse()) pendingDirectories.push(path);
|
|
2459
2780
|
}
|
|
2460
|
-
return files;
|
|
2461
2781
|
}
|
|
2462
2782
|
//#endregion
|
|
2463
2783
|
//#region src/cli/commands/lint/errors.ts
|
|
@@ -3007,6 +3327,9 @@ function mergeProgressReporters(base, extra) {
|
|
|
3007
3327
|
onJobStart: (payload) => {
|
|
3008
3328
|
deliverBoth((reporter) => reporter.onJobStart?.(payload));
|
|
3009
3329
|
},
|
|
3330
|
+
onPlanningEnd: (payload) => {
|
|
3331
|
+
deliverBoth((reporter) => reporter.onPlanningEnd?.(payload));
|
|
3332
|
+
},
|
|
3010
3333
|
onPrepareEnd: (payload) => {
|
|
3011
3334
|
deliverBoth((reporter) => reporter.onPrepareEnd?.(payload));
|
|
3012
3335
|
},
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as formatJson, n as formatDiagnostics, r as formatStylish, t as executeCli } from "./cli-
|
|
1
|
+
import { i as formatJson, n as formatDiagnostics, r as formatStylish, t as executeCli } from "./cli-B0jEUKYS.mjs";
|
|
2
2
|
import { ignorePatternsAIAgents, ignorePatternsBuildOutputs, ignorePatternsCaches, ignorePatternsCommon, ignorePatternsEslintDefaults, ignorePatternsGenerated } from "@alint-js/config";
|
|
3
3
|
import { defineConfig } from "@alint-js/core";
|
|
4
4
|
export { defineConfig, executeCli, formatDiagnostics, formatJson, formatStylish, ignorePatternsAIAgents, ignorePatternsBuildOutputs, ignorePatternsCaches, ignorePatternsCommon, ignorePatternsEslintDefaults, ignorePatternsGenerated };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alint-js/cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.5",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
7
7
|
"types": "./dist/index.d.mts",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"pathe": "^2.0.3",
|
|
28
28
|
"table": "^6.9.0",
|
|
29
29
|
"tinyrainbow": "^3.1.0",
|
|
30
|
-
"@alint-js/config": "0.1.
|
|
31
|
-
"@alint-js/core": "0.1.
|
|
30
|
+
"@alint-js/config": "0.1.5",
|
|
31
|
+
"@alint-js/core": "0.1.5"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@pnpm/find-workspace-dir": "^1000.1.5",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"alint": "node ./dist/bin/index.mjs",
|
|
41
|
+
"bench": "vitest bench --config vitest.config.ts",
|
|
41
42
|
"build": "tsdown",
|
|
42
43
|
"build:binary": "tsdown --config tsdown.binary.config.ts --config-loader unrun",
|
|
43
44
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|