@alint-js/cli 0.0.29 → 0.0.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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as executeCli } from "../cli-BjDMMvUi.mjs";
2
+ import { t as executeCli } from "../cli-fZkZ4dza.mjs";
3
3
  import process from "node:process";
4
4
  //#region src/bin/index.ts
5
5
  executeCli(process.argv, {
@@ -3,18 +3,20 @@ import { inspect } from "node:util";
3
3
  import { cac } from "cac";
4
4
  import { appendFile, mkdir, readFile, readdir, rm, stat } from "node:fs/promises";
5
5
  import { getGlobalSetupConfigPath, getProjectSetupConfigPath, getStatsDir, installStaticPlugins, loadAlintConfig, loadSetupConfig, mergeSetupConfigs, writeSetupConfig } from "@alint-js/config";
6
- import { AlintRunError, hasDiscoveryFilePatterns, matchesDiscoveryFile, normalizeConfig, resolveConfigForDirectory, resolveConfigForFile, runAlint } from "@alint-js/core";
6
+ import { AlintRunCancelledError, AlintRunError, hasDiscoveryFilePatterns, matchesDiscoveryFile, normalizeConfig, resolveConfigForDirectory, resolveConfigForFile, runAlint } from "@alint-js/core";
7
7
  import { isAbsolute, join, relative, resolve } from "pathe";
8
8
  import { getBorderCharacters, table } from "table";
9
9
  import { errorMessageFrom } from "@moeru/std/error";
10
- import c, { createColors } from "tinyrainbow";
10
+ import { createColors } from "tinyrainbow";
11
11
  import cliSpinners from "cli-spinners";
12
12
  import { relative as relative$1 } from "node:path";
13
+ import fastStringTruncatedWidth from "fast-string-truncated-width";
14
+ import fastStringWidth from "fast-string-width";
13
15
  import Gitignore from "gitignore-fs";
14
16
  import { Minimatch, minimatch } from "minimatch";
15
17
  import { errorMessageFrom as errorMessageFrom$1 } from "@moeru/std";
16
18
  //#region package.json
17
- var version = "0.0.29";
19
+ var version = "0.0.30";
18
20
  //#endregion
19
21
  //#region src/cli/commands/command.ts
20
22
  function defineCommand(node) {
@@ -190,7 +192,6 @@ function shouldSkipOptionValue(arg) {
190
192
  "--cache-location",
191
193
  "--config",
192
194
  "--cwd",
193
- "--file-concurrency",
194
195
  "--format",
195
196
  "--model",
196
197
  "--provider-endpoint",
@@ -545,7 +546,7 @@ function formatJson(result) {
545
546
  }
546
547
  //#endregion
547
548
  //#region src/cli/reporters/stylish.ts
548
- const colors$2 = createColors({ force: true });
549
+ const colors$3 = createColors({ force: true });
549
550
  function formatStylish(input, options = {}) {
550
551
  const diagnostics = Array.isArray(input) ? input : input.diagnostics;
551
552
  const result = Array.isArray(input) ? void 0 : input;
@@ -590,12 +591,12 @@ function createStyle(color) {
590
591
  warning: identity
591
592
  };
592
593
  return {
593
- error: colors$2.red,
594
- file: colors$2.underline,
595
- location: colors$2.dim,
596
- ruleId: colors$2.dim,
597
- summaryToken: colors$2.cyan,
598
- warning: colors$2.yellow
594
+ error: colors$3.red,
595
+ file: colors$3.underline,
596
+ location: colors$3.dim,
597
+ ruleId: colors$3.dim,
598
+ summaryToken: colors$3.cyan,
599
+ warning: colors$3.yellow
599
600
  };
600
601
  }
601
602
  function formatDiagnosticCount(total, cached, label) {
@@ -640,20 +641,19 @@ function formatDiagnostics(format, result, options = {}) {
640
641
  function createPlainProgressReporter(options) {
641
642
  const writeLine = (line) => options.write(`${line}\n`);
642
643
  return {
643
- onRuleStart: (payload) => {
644
- const target = payload.path.target.name ? `${payload.path.target.kind} ${payload.path.target.name}` : payload.path.target.kind;
645
- writeLine(`scan ${payload.path.file.path} > ${target} > ${payload.path.rule.id}`);
644
+ onJobStart: (payload) => {
645
+ const target = payload.job.target.name ? `${payload.job.target.kind} ${payload.job.target.name}` : payload.job.target.kind;
646
+ writeLine(`scan ${payload.job.inputPath} > ${target} > ${payload.job.ruleId}`);
646
647
  },
647
648
  onRunEnd: (payload) => {
648
649
  const warnCount = countDiagnostics$1(payload.diagnostics, "warn");
649
650
  const errorCount = countDiagnostics$1(payload.diagnostics, "error");
650
- const state = payload.errored > 0 ? "failed" : "finished";
651
- const cached = payload.cached > 0 ? `, ${payload.cached} cached` : "";
652
- const errored = payload.errored > 0 ? `, ${payload.errored} errored` : "";
653
- writeLine(`alint ${state}: ${warnCount} warn, ${errorCount} error, ${payload.usage.totalTokens} tokens${cached}${errored}`);
651
+ const state = payload.execution.failed > 0 ? "failed" : "finished";
652
+ const { cached, cancelled, completed, failed, skipped } = payload.execution;
653
+ writeLine(`alint ${state}: ${warnCount} warn, ${errorCount} error, ${payload.usage.totalTokens} tokens, ${completed} completed, ${cached} cached, ${failed} failed, ${cancelled} cancelled, ${skipped} skipped`);
654
654
  },
655
655
  onRunStart: (payload) => {
656
- writeLine(`alint started: ${payload.filesTotal} files, ${payload.rulesTotal} rules, ${payload.planned} planned executions`);
656
+ writeLine(`alint started: ${payload.jobsTotal} queued jobs`);
657
657
  }
658
658
  };
659
659
  }
@@ -662,217 +662,150 @@ function countDiagnostics$1(diagnostics, severity) {
662
662
  }
663
663
  //#endregion
664
664
  //#region src/cli/reporters/progress/summary.ts
665
- const colors$1 = createColors({ force: true });
665
+ const colors$2 = createColors({ force: true });
666
+ const graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
666
667
  function createSummaryProgressReporter(options) {
667
668
  const now = Date.now;
668
- const state = {
669
- cached: 0,
670
- completed: 0,
671
- diagnostics: [],
672
- errored: 0,
673
- files: /* @__PURE__ */ new Map(),
674
- planned: 0,
675
- spinnerIndex: 0,
676
- totalTokens: 0
677
- };
669
+ const state = createInitialState();
678
670
  return {
679
671
  getRows: () => createRows(state, options, now()),
680
- onDiagnostic: (payload) => {
681
- state.diagnostics = payload.diagnostics;
672
+ onDiagnostic: ({ diagnostic }) => {
673
+ state.diagnostics.push(diagnostic);
682
674
  },
683
- onFileEnd: (payload) => {
684
- const file = getFileState(state, payload.file);
685
- file.endedAt = payload.endedAt ?? now();
686
- file.rule = void 0;
687
- file.target = void 0;
688
- },
689
- onFileStart: (payload) => {
690
- const file = getFileState(state, payload.file);
691
- file.startedAt = payload.startedAt ?? now();
692
- file.endedAt = void 0;
675
+ onJobEnd: (payload) => {
676
+ const current = state.jobs.get(payload.job.id);
677
+ state.jobs.set(payload.job.id, {
678
+ job: payload.job,
679
+ startedAt: payload.startedAt ?? current?.startedAt,
680
+ state: payload.state
681
+ });
682
+ transition(state.execution, "running", payload.state);
693
683
  },
694
- onRuleEnd: (payload) => {
695
- const file = getFileState(state, payload.path.file);
696
- if (payload.cache === "hit") {
697
- state.cached += 1;
698
- file.cached += 1;
699
- }
700
- if (payload.state === "completed") {
701
- state.completed += 1;
702
- file.completed += 1;
703
- }
704
- if (payload.state === "errored") {
705
- state.errored += 1;
706
- file.errored += 1;
707
- }
708
- if (file.rule?.id === payload.path.rule.id) file.rule = void 0;
684
+ onJobQueued: ({ job }) => {
685
+ state.jobs.set(job.id, {
686
+ job,
687
+ state: "queued"
688
+ });
689
+ state.execution.queued += 1;
709
690
  },
710
- onRuleStart: (payload) => {
711
- const file = getFileState(state, payload.path.file);
712
- file.startedAt ??= payload.startedAt ?? now();
713
- file.rule = {
714
- id: payload.path.rule.id,
691
+ onJobStart: (payload) => {
692
+ state.jobs.set(payload.job.id, {
693
+ job: payload.job,
715
694
  startedAt: payload.startedAt ?? now(),
716
- target: formatTarget(payload)
717
- };
718
- file.target = file.rule.target;
695
+ state: "running"
696
+ });
697
+ transition(state.execution, "queued", "running");
719
698
  },
720
699
  onRunEnd: (payload) => {
721
- state.cached = payload.cached;
722
- state.completed = payload.completed;
723
- state.diagnostics = payload.diagnostics;
724
- state.endedAt = payload.endedAt ?? now();
725
- state.errored = payload.errored;
726
- state.planned = payload.planned;
727
- state.runStartedAt = payload.startedAt ?? state.runStartedAt;
700
+ state.diagnostics = [...payload.diagnostics];
701
+ state.execution = { ...payload.execution };
728
702
  state.totalTokens = payload.usage.totalTokens;
729
703
  },
730
- onRunStart: (payload) => {
731
- state.cached = 0;
732
- state.completed = 0;
704
+ onRunStart: ({ jobsTotal }) => {
733
705
  state.diagnostics = [];
734
- state.endedAt = void 0;
735
- state.errored = 0;
736
- state.files = /* @__PURE__ */ new Map();
737
- state.planned = payload.planned;
738
- state.runStartedAt = payload.startedAt ?? now();
706
+ state.execution = createCounts(jobsTotal);
707
+ state.jobs.clear();
739
708
  state.spinnerIndex = 0;
740
709
  state.totalTokens = 0;
741
- for (const file of payload.files ?? []) state.files.set(file.path, createFileState(file));
742
- },
743
- onTargetEnd: (payload) => {
744
- const file = getFileState(state, payload.path.file);
745
- const target = formatTarget(payload);
746
- if (file.target === target) file.target = void 0;
747
710
  },
748
- onTargetStart: (payload) => {
749
- const file = getFileState(state, payload.path.file);
750
- file.startedAt ??= payload.startedAt ?? now();
751
- file.target = formatTarget(payload);
752
- },
753
- onUsage: (payload) => {
754
- state.totalTokens = payload.total.totalTokens;
711
+ onUsage: ({ record }) => {
712
+ if (record.totalTokens != null && Number.isFinite(record.totalTokens)) state.totalTokens += record.totalTokens;
755
713
  },
756
714
  tick: () => {
757
715
  state.spinnerIndex = (state.spinnerIndex + 1) % Math.max(options.spinnerFrames.length, 1);
758
716
  }
759
717
  };
760
718
  }
719
+ function completeGraphemeBoundary(input, maximumIndex) {
720
+ let boundary = 0;
721
+ for (const segment of graphemeSegmenter.segment(input)) {
722
+ const end = segment.index + segment.segment.length;
723
+ if (end > maximumIndex) break;
724
+ boundary = end;
725
+ }
726
+ return boundary;
727
+ }
761
728
  function countDiagnostics(diagnostics, severity) {
762
729
  return diagnostics.filter((diagnostic) => diagnostic.severity === severity).length;
763
730
  }
764
- function countQueuedFiles(state) {
765
- return [...state.files.values()].filter((file) => file.startedAt === void 0 && file.endedAt === void 0 && (file.file.planned ?? 0) > 0).length;
766
- }
767
- function createFileState(file) {
731
+ function createCounts(planned = 0) {
768
732
  return {
769
733
  cached: 0,
734
+ cancelled: 0,
770
735
  completed: 0,
771
- errored: 0,
772
- file
736
+ failed: 0,
737
+ planned,
738
+ queued: 0,
739
+ running: 0,
740
+ skipped: 0
741
+ };
742
+ }
743
+ function createInitialState() {
744
+ return {
745
+ diagnostics: [],
746
+ execution: createCounts(),
747
+ jobs: /* @__PURE__ */ new Map(),
748
+ spinnerIndex: 0,
749
+ totalTokens: 0
773
750
  };
774
751
  }
775
752
  function createRows(state, options, now) {
776
- const rows = getActiveFiles(state).flatMap((file) => formatFileRows(file, state, options, now));
777
- const queued = countQueuedFiles(state);
753
+ if (options.rows !== void 0 && options.rows <= 0) return [];
778
754
  const warnCount = countDiagnostics(state.diagnostics, "warn");
779
755
  const errorCount = countDiagnostics(state.diagnostics, "error");
780
- const footer = formatFooter(state, warnCount, errorCount, queued, options, now);
781
- if (queued > 0) rows.push(formatQueuedRow(queued, options));
782
- if (rows.length === 0) rows.push(formatIdleRow(state, options));
783
- rows.push("", footer);
784
- return options.color ? rows.map((row) => styleRow(row, state, warnCount, errorCount, options)) : rows;
785
- }
786
- function escapeRegExp(value) {
787
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
788
- }
789
- function estimateTotal(elapsedMs, completed, planned) {
790
- if (completed <= 0 || planned <= 0) return;
791
- return elapsedMs * planned / completed;
756
+ const runningJobs = [...state.jobs.values()].filter((job) => job.state === "running").sort((left, right) => left.job.index - right.job.index);
757
+ const footerRows = 1;
758
+ const separatorRows = options.rows === void 0 || options.rows >= 2 ? 1 : 0;
759
+ const contentBudget = options.rows === void 0 ? Number.POSITIVE_INFINITY : Math.max(options.rows - footerRows - separatorRows, 0);
760
+ const jobBudget = runningJobs.length > contentBudget ? Math.max(contentBudget - 1, 0) : contentBudget;
761
+ const visible = runningJobs.slice(0, jobBudget).map((job) => formatJobRow(job, state, options, now));
762
+ const hiddenJobs = runningJobs.length - visible.length;
763
+ if (hiddenJobs > 0 && contentBudget > 0) visible.push(fitRow(` └─ … ${hiddenJobs} more running jobs hidden`, options.columns));
764
+ if (separatorRows === 1) visible.push("");
765
+ visible.push(formatFooter(state, warnCount, errorCount, options));
766
+ return options.color ? visible.map((row) => styleRow(row, state, warnCount, errorCount, options)) : visible;
792
767
  }
793
768
  function fitRow(row, columns) {
794
769
  if (columns <= 0) return "";
795
- if (row.length <= columns) return row;
796
- if (columns === 1) return "…";
797
- return `${row.slice(0, columns - 1)}…`;
770
+ if (fastStringWidth(row) <= columns) return row;
771
+ let limit = columns;
772
+ while (limit > 0) {
773
+ const result = fastStringTruncatedWidth(row, {
774
+ ellipsis: "…",
775
+ limit
776
+ });
777
+ const index = completeGraphemeBoundary(row, result.index);
778
+ const visible = row.slice(0, index);
779
+ const fitted = `${visible}${visible.includes("\x1B") ? "\x1B[0m" : ""}${result.ellipsed ? "…" : ""}`;
780
+ const overflow = fastStringWidth(fitted) - columns;
781
+ if (overflow <= 0) return fitted;
782
+ limit -= overflow;
783
+ }
784
+ return "";
798
785
  }
799
786
  function formatDuration(ms) {
800
- if (ms === void 0 || !Number.isFinite(ms)) return "?";
801
787
  return `${(Math.max(ms, 0) / 1e3).toFixed(1)}s`;
802
788
  }
803
- function formatEstimatedDuration(ms) {
804
- return `~${formatDuration(ms)}`;
805
- }
806
- function formatFilePath(filePath, cwd) {
807
- if (!cwd) return filePath;
808
- return relative$1(cwd, filePath) || filePath;
809
- }
810
- function formatFileRows(file, state, options, now) {
811
- const firstRow = formatFileSummaryRow(file, state, options);
812
- if (!file.rule) return [firstRow];
813
- const elapsed = now - file.rule.startedAt;
814
- const done = file.completed + file.cached + file.errored;
815
- const estimated = estimateTotal(file.startedAt === void 0 ? elapsed : now - file.startedAt, done, file.file.planned ?? 0);
816
- return [firstRow, fitRow(` ${file.rule.target} > ${file.rule.id} (${formatDuration(elapsed)}, ${formatEstimatedDuration(estimated)})`, options.columns)];
817
- }
818
- function formatFileSummaryRow(file, state, options) {
819
- const prefix = `${options.spinnerFrames[state.spinnerIndex] ?? ""} ${formatFilePath(file.file.path, options.cwd)}`;
820
- const counter = `${file.completed}/${file.cached}/${file.errored}/${file.file.planned ?? 0}`;
821
- return fitRow(`${prefix}${" ".repeat(Math.max(1, options.columns - prefix.length - counter.length))}${counter}`, options.columns);
822
- }
823
- function formatFooter(state, warnCount, errorCount, queued, options, now) {
824
- const endedAt = state.endedAt ?? now;
825
- const elapsed = state.runStartedAt === void 0 ? void 0 : endedAt - state.runStartedAt;
826
- const completed = state.completed + state.cached + state.errored;
827
- const estimated = elapsed === void 0 ? void 0 : estimateTotal(elapsed, completed, state.planned);
828
- const estimatedTokens = completed > 0 && state.planned > 0 ? Math.ceil(state.totalTokens * state.planned / completed).toLocaleString("en-US") : "?";
829
- return fitRow([
830
- `${formatDuration(elapsed)} -> ${formatEstimatedDuration(estimated)}`,
831
- `${state.totalTokens.toLocaleString("en-US")} tokens -> ~${estimatedTokens} tokens`,
832
- `${queued} queued / ${state.cached} cached / ${warnCount} warn / ${errorCount} error`
833
- ].join(" | "), options.columns);
834
- }
835
- function formatIdleRow(state, options) {
836
- const prefix = `${options.spinnerFrames[state.spinnerIndex] ?? ""} alint`;
837
- const counter = `${state.completed}/${state.cached}/${state.errored}/${state.planned}`;
838
- return fitRow(`${prefix}${" ".repeat(Math.max(1, options.columns - prefix.length - counter.length))}${counter}`, options.columns);
839
- }
840
- function formatQueuedRow(queued, options) {
841
- return fitRow(` ${queued} ${queued === 1 ? "file" : "files"} queued`, options.columns);
842
- }
843
- function formatTarget(payload) {
844
- return payload.path.target.name ? `${payload.path.target.kind} ${payload.path.target.name}` : payload.path.target.kind;
845
- }
846
- function getActiveFiles(state) {
847
- return [...state.files.values()].filter((file) => file.startedAt !== void 0 && file.endedAt === void 0).sort((left, right) => left.file.index - right.file.index);
848
- }
849
- function getFileState(state, file) {
850
- const existingFile = state.files.get(file.path);
851
- if (existingFile) {
852
- existingFile.file = {
853
- ...existingFile.file,
854
- ...file,
855
- planned: file.planned ?? existingFile.file.planned
856
- };
857
- return existingFile;
858
- }
859
- const nextFile = createFileState({
860
- ...file,
861
- planned: file.planned ?? (state.files.size === 0 ? state.planned : void 0)
862
- });
863
- state.files.set(file.path, nextFile);
864
- return nextFile;
789
+ function formatFooter(state, warnCount, errorCount, options) {
790
+ const { cached, failed, queued, running } = state.execution;
791
+ return fitRow(`${running} running / ${queued} queued / ${cached} cached / ${warnCount} warn / ${errorCount} error / ${failed} failed / ${state.totalTokens} tokens`, options.columns);
865
792
  }
866
- function replaceFirst(row, search, replacement) {
867
- if (search.length === 0) return row;
868
- return row.replace(new RegExp(escapeRegExp(search)), replacement);
793
+ function formatJobRow(jobState, state, options, now) {
794
+ const spinner = options.spinnerFrames[state.spinnerIndex] ?? "";
795
+ const inputPath = options.cwd ? relative$1(options.cwd, jobState.job.inputPath) || "." : jobState.job.inputPath;
796
+ const target = jobState.job.target.name ? `${jobState.job.target.kind} ${jobState.job.target.name}` : jobState.job.target.kind;
797
+ const startedAt = jobState.startedAt ?? now;
798
+ return fitRow(`${spinner} ${inputPath} > ${target} > ${jobState.job.ruleId} (${formatDuration(now - startedAt)})`, options.columns);
869
799
  }
870
800
  function styleRow(row, state, warnCount, errorCount, options) {
871
801
  let styledRow = row;
872
802
  const spinner = options.spinnerFrames[state.spinnerIndex] ?? "";
873
- if (spinner) styledRow = replaceFirst(styledRow, spinner, colors$1.cyan(spinner));
874
- styledRow = styledRow.replace(/\|/g, colors$1.gray("|")).replace(`${warnCount} warn`, colors$1.yellow(`${warnCount} warn`)).replace(`${errorCount} error`, (errorCount > 0 ? colors$1.red : colors$1.gray)(`${errorCount} error`)).replace(/(\d+\/\d+\/\d+\/\d+)/, (match) => state.errored > 0 ? colors$1.red(match) : colors$1.gray(match)).replace(/(\d[\d,]* tokens)/g, (match) => colors$1.cyan(match));
875
- return styledRow;
803
+ if (spinner) styledRow = styledRow.replace(spinner, colors$2.cyan(spinner));
804
+ return styledRow.replace(/\//g, colors$2.gray("/")).replace(`${warnCount} warn`, colors$2.yellow(`${warnCount} warn`)).replace(`${errorCount} error`, (errorCount > 0 ? colors$2.red : colors$2.gray)(`${errorCount} error`)).replace(`${state.execution.failed} failed`, (state.execution.failed > 0 ? colors$2.red : colors$2.gray)(`${state.execution.failed} failed`));
805
+ }
806
+ function transition(counts, from, to) {
807
+ counts[from] -= 1;
808
+ counts[to] += 1;
876
809
  }
877
810
  //#endregion
878
811
  //#region src/cli/reporters/progress/tty.ts
@@ -936,6 +869,7 @@ function createCliProgressReporter(options) {
936
869
  color: options.color,
937
870
  columns: options.columns,
938
871
  cwd: options.cwd,
872
+ rows: options.rows,
939
873
  spinnerFrames: cliSpinners.dots.frames
940
874
  });
941
875
  const renderer = createTtyProgressRenderer({
@@ -961,20 +895,16 @@ function createRenderingProgressReporter(summary, renderer) {
961
895
  summary.onDiagnostic?.(payload);
962
896
  renderer.render();
963
897
  },
964
- onFileEnd: (payload) => {
965
- summary.onFileEnd?.(payload);
966
- renderer.render();
967
- },
968
- onFileStart: (payload) => {
969
- summary.onFileStart?.(payload);
898
+ onJobEnd: (payload) => {
899
+ summary.onJobEnd?.(payload);
970
900
  renderer.render();
971
901
  },
972
- onRuleEnd: (payload) => {
973
- summary.onRuleEnd?.(payload);
902
+ onJobQueued: (payload) => {
903
+ summary.onJobQueued?.(payload);
974
904
  renderer.render();
975
905
  },
976
- onRuleStart: (payload) => {
977
- summary.onRuleStart?.(payload);
906
+ onJobStart: (payload) => {
907
+ summary.onJobStart?.(payload);
978
908
  renderer.render();
979
909
  },
980
910
  onRunEnd: (payload) => {
@@ -985,14 +915,6 @@ function createRenderingProgressReporter(summary, renderer) {
985
915
  summary.onRunStart?.(payload);
986
916
  renderer.start();
987
917
  },
988
- onTargetEnd: (payload) => {
989
- summary.onTargetEnd?.(payload);
990
- renderer.render();
991
- },
992
- onTargetStart: (payload) => {
993
- summary.onTargetStart?.(payload);
994
- renderer.render();
995
- },
996
918
  onUsage: (payload) => {
997
919
  summary.onUsage?.(payload);
998
920
  renderer.render();
@@ -1174,18 +1096,20 @@ async function walkFiles(root, options) {
1174
1096
  }
1175
1097
  //#endregion
1176
1098
  //#region src/cli/commands/lint/errors.ts
1177
- function formatRunError(error, color) {
1178
- return `${color ? c.red("error") : "error"} ${formatRunErrorContext(error)}\n Rule running failed due to ${error.failure?.message ?? error.message}\n`;
1099
+ const colors$1 = createColors({ force: true });
1100
+ function formatCancelledError(error, color) {
1101
+ return `${color ? colors$1.red("error") : "error"} ${error.message}\n`;
1179
1102
  }
1180
- function formatRunErrorContext(error) {
1181
- const failure = error.failure;
1182
- if (!failure) return "alint run failed";
1183
- const target = failure.target ? failure.target.name ? `${failure.target.kind} ${failure.target.name}` : failure.target.kind : void 0;
1103
+ function formatRunError(error, color) {
1184
1104
  return [
1185
- failure.filePath,
1186
- target,
1187
- failure.ruleId
1188
- ].filter(Boolean).join(" > ");
1105
+ `${color ? colors$1.red("error") : "error"} ${error.message}`,
1106
+ ...error.failures.map(formatFailure),
1107
+ ""
1108
+ ].join("\n");
1109
+ }
1110
+ function formatFailure(failure) {
1111
+ const target = failure.job.target.name ? `${failure.job.target.kind} ${failure.job.target.name}` : failure.job.target.kind;
1112
+ return ` [${failure.kind}] ${failure.job.inputPath} > ${target} > ${failure.job.ruleId}: ${failure.message}`;
1189
1113
  }
1190
1114
  //#endregion
1191
1115
  //#region src/cli/commands/lint/runner.ts
@@ -1198,14 +1122,12 @@ function resolveConfigRunner(config) {
1198
1122
  function resolveRunnerConfig(setupConfig, config, options) {
1199
1123
  const cache = resolveRunnerCacheConfig(setupConfig.runner?.cache, config.runner?.cache, options);
1200
1124
  const stats = resolveRunnerStatsConfig(setupConfig.runner?.stats, config.runner?.stats, options);
1201
- const fileConcurrency = parsePositiveIntegerOption(options.fileConcurrency, "--file-concurrency");
1202
1125
  const ruleConcurrency = parsePositiveIntegerOption(options.ruleConcurrency, "--rule-concurrency");
1203
1126
  const timeoutMs = parsePositiveIntegerOption(options.timeoutMs, "--timeout-ms");
1204
1127
  const runner = {
1205
1128
  ...setupConfig.runner ?? {},
1206
1129
  ...config.runner ?? {},
1207
1130
  cache,
1208
- fileConcurrency: fileConcurrency ?? config.runner?.fileConcurrency ?? setupConfig.runner?.fileConcurrency,
1209
1131
  ruleConcurrency: ruleConcurrency ?? config.runner?.ruleConcurrency ?? setupConfig.runner?.ruleConcurrency,
1210
1132
  stats,
1211
1133
  timeoutMs: timeoutMs ?? config.runner?.timeoutMs ?? setupConfig.runner?.timeoutMs
@@ -1450,8 +1372,9 @@ function toStatsUsageRecord(record) {
1450
1372
  function createStatsCollector() {
1451
1373
  const counts = {
1452
1374
  cached: 0,
1375
+ cancelled: 0,
1453
1376
  completed: 0,
1454
- errored: 0,
1377
+ failed: 0,
1455
1378
  planned: 0
1456
1379
  };
1457
1380
  let startedAt;
@@ -1461,10 +1384,11 @@ function createStatsCollector() {
1461
1384
  durationMs: () => startedAt !== void 0 && endedAt !== void 0 ? endedAt - startedAt : void 0,
1462
1385
  reporter: {
1463
1386
  onRunEnd: (payload) => {
1464
- counts.cached = payload.cached;
1465
- counts.completed = payload.completed;
1466
- counts.errored = payload.errored;
1467
- counts.planned = payload.planned;
1387
+ counts.cached = payload.execution.cached;
1388
+ counts.cancelled = payload.execution.cancelled;
1389
+ counts.completed = payload.execution.completed;
1390
+ counts.failed = payload.execution.failed;
1391
+ counts.planned = payload.execution.planned;
1468
1392
  endedAt = payload.endedAt;
1469
1393
  },
1470
1394
  onRunStart: (payload) => {
@@ -1476,46 +1400,50 @@ function createStatsCollector() {
1476
1400
  function mergeProgressReporters(base, extra) {
1477
1401
  if (!base) return extra;
1478
1402
  if (!extra) return base;
1403
+ const baseState = {
1404
+ disabled: false,
1405
+ reporter: base
1406
+ };
1407
+ const extraState = {
1408
+ disabled: false,
1409
+ reporter: extra
1410
+ };
1411
+ let firstFailure;
1412
+ const deliver = (state, callback) => {
1413
+ if (state.disabled) return;
1414
+ try {
1415
+ callback(state.reporter);
1416
+ } catch (error) {
1417
+ state.disabled = true;
1418
+ firstFailure ??= normalizeReporterError(error);
1419
+ }
1420
+ };
1421
+ const deliverBoth = (callback) => {
1422
+ deliver(baseState, callback);
1423
+ deliver(extraState, callback);
1424
+ };
1479
1425
  return {
1480
1426
  onDiagnostic: (payload) => {
1481
- base.onDiagnostic?.(payload);
1482
- extra.onDiagnostic?.(payload);
1427
+ deliverBoth((reporter) => reporter.onDiagnostic?.(payload));
1483
1428
  },
1484
- onFileEnd: (payload) => {
1485
- base.onFileEnd?.(payload);
1486
- extra.onFileEnd?.(payload);
1429
+ onJobEnd: (payload) => {
1430
+ deliverBoth((reporter) => reporter.onJobEnd?.(payload));
1487
1431
  },
1488
- onFileStart: (payload) => {
1489
- base.onFileStart?.(payload);
1490
- extra.onFileStart?.(payload);
1432
+ onJobQueued: (payload) => {
1433
+ deliverBoth((reporter) => reporter.onJobQueued?.(payload));
1491
1434
  },
1492
- onRuleEnd: (payload) => {
1493
- base.onRuleEnd?.(payload);
1494
- extra.onRuleEnd?.(payload);
1495
- },
1496
- onRuleStart: (payload) => {
1497
- base.onRuleStart?.(payload);
1498
- extra.onRuleStart?.(payload);
1435
+ onJobStart: (payload) => {
1436
+ deliverBoth((reporter) => reporter.onJobStart?.(payload));
1499
1437
  },
1500
1438
  onRunEnd: (payload) => {
1501
- base.onRunEnd?.(payload);
1502
- extra.onRunEnd?.(payload);
1439
+ deliverBoth((reporter) => reporter.onRunEnd?.(payload));
1440
+ if (firstFailure) throw firstFailure;
1503
1441
  },
1504
1442
  onRunStart: (payload) => {
1505
- base.onRunStart?.(payload);
1506
- extra.onRunStart?.(payload);
1507
- },
1508
- onTargetEnd: (payload) => {
1509
- base.onTargetEnd?.(payload);
1510
- extra.onTargetEnd?.(payload);
1511
- },
1512
- onTargetStart: (payload) => {
1513
- base.onTargetStart?.(payload);
1514
- extra.onTargetStart?.(payload);
1443
+ deliverBoth((reporter) => reporter.onRunStart?.(payload));
1515
1444
  },
1516
1445
  onUsage: (payload) => {
1517
- base.onUsage?.(payload);
1518
- extra.onUsage?.(payload);
1446
+ deliverBoth((reporter) => reporter.onUsage?.(payload));
1519
1447
  }
1520
1448
  };
1521
1449
  }
@@ -1537,11 +1465,21 @@ async function writeRunStats(target, collector, result, cwd) {
1537
1465
  await store.record(createRunStat({
1538
1466
  cwd,
1539
1467
  durationMs: collector.durationMs(),
1540
- ruleCounts: collector.counts,
1468
+ ruleCounts: {
1469
+ cached: result.execution.cached,
1470
+ cancelled: result.execution.cancelled,
1471
+ completed: result.execution.completed,
1472
+ failed: result.execution.failed,
1473
+ planned: result.execution.planned
1474
+ },
1541
1475
  usage: result.usage
1542
1476
  }));
1543
1477
  } catch {}
1544
1478
  }
1479
+ function normalizeReporterError(error) {
1480
+ if (error instanceof Error) return error;
1481
+ return new Error(error != null ? String(error) : "Unknown progress reporter error.");
1482
+ }
1545
1483
  //#endregion
1546
1484
  //#region src/cli/commands/lint/index.ts
1547
1485
  const lint = defineCommand({
@@ -1592,11 +1530,15 @@ async function runLintCommand(files, options, io, interceptConsoleOutput) {
1592
1530
  columns: io.stderr.columns ?? 80,
1593
1531
  cwd: io.cwd,
1594
1532
  isTty: io.stderr.isTTY === true,
1533
+ rows: io.stderr.rows,
1595
1534
  write: (chunk) => io.stderr.write(chunk)
1596
1535
  }) : void 0;
1597
1536
  const restoreProgressConsole = progress ? interceptConsoleOutput({ write: progress.write }) : void 0;
1598
1537
  const statsTarget = resolveStatsWrite(runner?.stats, io.env);
1599
1538
  const statsCollector = statsTarget ? createStatsCollector() : void 0;
1539
+ const persistStats = async (runResult) => {
1540
+ if (statsTarget && statsCollector) await writeRunStats(statsTarget, statsCollector, runResult, io.cwd);
1541
+ };
1600
1542
  let result;
1601
1543
  try {
1602
1544
  result = await runAlint({
@@ -1614,14 +1556,20 @@ async function runLintCommand(files, options, io, interceptConsoleOutput) {
1614
1556
  restoreProgressConsole?.();
1615
1557
  progress?.dispose();
1616
1558
  if (error instanceof AlintRunError) {
1559
+ await persistStats(error.result);
1617
1560
  io.stderr.write(formatRunError(error, io.stderr.isTTY === true));
1618
1561
  return 2;
1619
1562
  }
1563
+ if (error instanceof AlintRunCancelledError) {
1564
+ await persistStats(error.result);
1565
+ io.stderr.write(formatCancelledError(error, io.stderr.isTTY === true));
1566
+ return 2;
1567
+ }
1620
1568
  throw error;
1621
1569
  }
1622
1570
  restoreProgressConsole?.();
1623
1571
  progress?.dispose();
1624
- if (statsTarget && statsCollector) await writeRunStats(statsTarget, statsCollector, result, io.cwd);
1572
+ await persistStats(result);
1625
1573
  io.stdout.write(formatDiagnostics(options.format, result, { color: io.stdout.isTTY === true }));
1626
1574
  return result.diagnostics.some((diagnostic) => diagnostic.severity === "error") ? 1 : 0;
1627
1575
  }
@@ -2292,7 +2240,7 @@ async function executeCli(argv, io) {
2292
2240
  pendingResult = result;
2293
2241
  return result;
2294
2242
  };
2295
- cli.option("--no-cache", "Disable cache for this run").option("--cache-location <path>", "Path to the alint cache file or directory").option("-c, --config <path>", "Path to alint config file").option("--file-concurrency <count>", "Number of files to lint concurrently").option("--format <format>", "Reporter format", { default: "stylish" }).option("--model <model>", "Force a model override").option("-l, --lang <language>", "Ask model-backed rules to write diagnostics in this language").option("--progress", "Show run progress").option("--rule-concurrency <count>", "Number of rules to run concurrently within a file").option("--no-stats", "Do not record run stats for this run").option("--timeout-ms <ms>", "Rule execution timeout in milliseconds").version(version).help();
2243
+ cli.option("--no-cache", "Disable cache for this run").option("--cache-location <path>", "Path to the alint cache file or directory").option("-c, --config <path>", "Path to alint config file").option("--format <format>", "Reporter format", { default: "stylish" }).option("--model <model>", "Force a model override").option("-l, --lang <language>", "Ask model-backed rules to write diagnostics in this language").option("--progress", "Show run progress").option("--rule-concurrency <count>", "Maximum rule executions across the entire run").option("--no-stats", "Do not record run stats for this run").option("--timeout-ms <ms>", "Rule execution timeout in milliseconds").version(version).help();
2296
2244
  registerCommandTree(cli, commandTree, {
2297
2245
  globalOptions,
2298
2246
  interceptConsoleOutput,
package/dist/index.d.mts CHANGED
@@ -14,6 +14,7 @@ interface CliIo {
14
14
  interface CliWritable {
15
15
  columns?: number;
16
16
  isTTY?: boolean;
17
+ rows?: number;
17
18
  write: (chunk: string) => unknown;
18
19
  }
19
20
  //#endregion
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-BjDMMvUi.mjs";
1
+ import { i as formatJson, n as formatDiagnostics, r as formatStylish, t as executeCli } from "./cli-fZkZ4dza.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.0.29",
4
+ "version": "0.0.30",
5
5
  "exports": {
6
6
  ".": {
7
7
  "types": "./dist/index.d.mts",
@@ -17,16 +17,18 @@
17
17
  ],
18
18
  "dependencies": {
19
19
  "@clack/prompts": "^1.7.0",
20
- "@moeru/std": "^0.1.0-beta.18",
20
+ "@moeru/std": "^0.1.0-beta.20",
21
21
  "cac": "^7.0.0",
22
22
  "cli-spinners": "^3.4.0",
23
+ "fast-string-truncated-width": "^3.0.3",
24
+ "fast-string-width": "^3.0.2",
23
25
  "gitignore-fs": "^2.2.3",
24
26
  "minimatch": "^10.2.5",
25
27
  "pathe": "^2.0.3",
26
28
  "table": "^6.9.0",
27
29
  "tinyrainbow": "^3.1.0",
28
- "@alint-js/config": "0.0.29",
29
- "@alint-js/core": "0.0.29"
30
+ "@alint-js/core": "0.0.30",
31
+ "@alint-js/config": "0.0.30"
30
32
  },
31
33
  "devDependencies": {
32
34
  "@pnpm/find-workspace-dir": "^1000.1.5",