@visulima/vis 1.0.0-alpha.11 → 1.0.0-alpha.13

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.
Files changed (116) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/LICENSE.md +559 -186
  3. package/README.md +18 -0
  4. package/dist/bin.js +1 -9
  5. package/dist/config/index.d.ts +477 -556
  6. package/dist/config/index.js +1 -2
  7. package/dist/generate/index.js +1 -3
  8. package/dist/packem_chunks/applyDefaults.js +2 -336
  9. package/dist/packem_chunks/bin.js +234 -9552
  10. package/dist/packem_chunks/doctor-probe.js +2 -112
  11. package/dist/packem_chunks/fix.js +11 -234
  12. package/dist/packem_chunks/handler.js +1 -99
  13. package/dist/packem_chunks/handler10.js +2 -53
  14. package/dist/packem_chunks/handler11.js +1 -32
  15. package/dist/packem_chunks/handler12.js +5 -100
  16. package/dist/packem_chunks/handler13.js +1 -25
  17. package/dist/packem_chunks/handler14.js +18 -916
  18. package/dist/packem_chunks/handler15.js +15 -201
  19. package/dist/packem_chunks/handler16.js +1 -124
  20. package/dist/packem_chunks/handler17.js +1 -13
  21. package/dist/packem_chunks/handler18.js +1 -106
  22. package/dist/packem_chunks/handler19.js +1 -19
  23. package/dist/packem_chunks/handler2.js +2 -75
  24. package/dist/packem_chunks/handler20.js +5 -29
  25. package/dist/packem_chunks/handler21.js +1 -222
  26. package/dist/packem_chunks/handler22.js +1 -237
  27. package/dist/packem_chunks/handler23.js +5 -101
  28. package/dist/packem_chunks/handler24.js +1 -110
  29. package/dist/packem_chunks/handler25.js +3 -402
  30. package/dist/packem_chunks/handler26.js +1 -13
  31. package/dist/packem_chunks/handler27.js +1 -63
  32. package/dist/packem_chunks/handler28.js +7 -34
  33. package/dist/packem_chunks/handler29.js +21 -456
  34. package/dist/packem_chunks/handler3.js +4 -95
  35. package/dist/packem_chunks/handler30.js +3 -170
  36. package/dist/packem_chunks/handler31.js +1 -530
  37. package/dist/packem_chunks/handler32.js +2 -214
  38. package/dist/packem_chunks/handler33.js +25 -119
  39. package/dist/packem_chunks/handler34.js +2 -630
  40. package/dist/packem_chunks/handler35.js +3 -283
  41. package/dist/packem_chunks/handler36.js +22 -542
  42. package/dist/packem_chunks/handler37.js +410 -744
  43. package/dist/packem_chunks/handler38.js +22 -989
  44. package/dist/packem_chunks/handler39.js +22 -574
  45. package/dist/packem_chunks/handler4.js +2 -90
  46. package/dist/packem_chunks/handler40.js +22 -1685
  47. package/dist/packem_chunks/handler41.js +6 -1088
  48. package/dist/packem_chunks/handler42.js +5 -797
  49. package/dist/packem_chunks/handler43.js +10 -2658
  50. package/dist/packem_chunks/handler44.js +51 -3784
  51. package/dist/packem_chunks/handler45.js +25 -2574
  52. package/dist/packem_chunks/handler46.js +3 -3769
  53. package/dist/packem_chunks/handler47.js +21 -1485
  54. package/dist/packem_chunks/handler48.js +42 -0
  55. package/dist/packem_chunks/handler5.js +8 -174
  56. package/dist/packem_chunks/handler6.js +1 -95
  57. package/dist/packem_chunks/handler7.js +1 -115
  58. package/dist/packem_chunks/handler8.js +1 -12
  59. package/dist/packem_chunks/handler9.js +1 -29
  60. package/dist/packem_chunks/heal-accept.js +10 -522
  61. package/dist/packem_chunks/heal.js +14 -673
  62. package/dist/packem_chunks/index.js +7 -873
  63. package/dist/packem_chunks/loader.js +1 -23
  64. package/dist/packem_chunks/tar.js +3 -0
  65. package/dist/packem_shared/ai-analysis-hm8d2W7z.js +67 -0
  66. package/dist/packem_shared/ai-cache-DoiF80AR.js +1 -0
  67. package/dist/packem_shared/ai-fix-nn4zOE95.js +43 -0
  68. package/dist/packem_shared/cache-directory-CwHlJhgx.js +1 -0
  69. package/dist/packem_shared/dependency-scan-COr5n63B.js +2 -0
  70. package/dist/packem_shared/docker-D6OGr5_S.js +2 -0
  71. package/dist/packem_shared/failure-log-iUVLf6ts.js +2 -0
  72. package/dist/packem_shared/flakiness-D9wf0t56.js +1 -0
  73. package/dist/packem_shared/giget-CcEy_Elm.js +2 -0
  74. package/dist/packem_shared/index-DH-5hsrC.js +1 -0
  75. package/dist/packem_shared/otel-DxDUPJJH.js +6 -0
  76. package/dist/packem_shared/otelPlugin-CQq6poq8.js +1 -0
  77. package/dist/packem_shared/registry-CkubDdiY.js +2 -0
  78. package/dist/packem_shared/run-summary-utils-BfBvjzhY.js +1 -0
  79. package/dist/packem_shared/runtime-check-BXZ43CBW.js +1 -0
  80. package/dist/packem_shared/selectors-BylODRiM.js +3 -0
  81. package/dist/packem_shared/symbols-CQmER5MT.js +1 -0
  82. package/dist/packem_shared/toolchain-BgBOUHII.js +5 -0
  83. package/dist/packem_shared/typosquats-CcZl99B1.js +1 -0
  84. package/dist/packem_shared/use-measured-height-DjYgUOKk.js +1 -0
  85. package/dist/packem_shared/utils-DrNg0XTR.js +1 -0
  86. package/dist/packem_shared/verify-Baj5mFJ7.js +1 -0
  87. package/dist/packem_shared/vis-update-app-D1jl0UZZ.js +1 -0
  88. package/dist/packem_shared/xxh3-DrAUNq4n.js +1 -0
  89. package/index.js +556 -727
  90. package/package.json +19 -29
  91. package/schemas/project.schema.json +739 -297
  92. package/schemas/vis-config.schema.json +3365 -384
  93. package/templates/buildkite-ci/template.yml +20 -20
  94. package/dist/packem_shared/VisUpdateApp-D-Yz_wvg.js +0 -1316
  95. package/dist/packem_shared/_commonjsHelpers-BqLXS_qQ.js +0 -5
  96. package/dist/packem_shared/ai-analysis-CHeB1joD.js +0 -367
  97. package/dist/packem_shared/ai-cache-Be_jexe4.js +0 -142
  98. package/dist/packem_shared/ai-fix-B9iQVcD2.js +0 -379
  99. package/dist/packem_shared/cache-directory-2qvs4goY.js +0 -98
  100. package/dist/packem_shared/catalog-BJTtyi-O.js +0 -1371
  101. package/dist/packem_shared/dependency-scan-A0KSklpG.js +0 -188
  102. package/dist/packem_shared/docker-2iZzc280.js +0 -181
  103. package/dist/packem_shared/failure-log-Cz3Z4SKL.js +0 -100
  104. package/dist/packem_shared/flakiness-goTxXuCX.js +0 -180
  105. package/dist/packem_shared/otel-DCvqCTz_.js +0 -158
  106. package/dist/packem_shared/otelPlugin-DFaLDvJf.js +0 -3
  107. package/dist/packem_shared/registry-CbqXI0rc.js +0 -272
  108. package/dist/packem_shared/run-summary-utils-PVMl4aIh.js +0 -130
  109. package/dist/packem_shared/runtime-check-Cobi3p6l.js +0 -127
  110. package/dist/packem_shared/selectors-SM69TfqC.js +0 -194
  111. package/dist/packem_shared/symbols-Ta7g2nU-.js +0 -14
  112. package/dist/packem_shared/toolchain-BdZd9eBi.js +0 -975
  113. package/dist/packem_shared/typosquats-C-bCh3PX.js +0 -1210
  114. package/dist/packem_shared/use-measured-height-CNP0vT4M.js +0 -20
  115. package/dist/packem_shared/utils-CthVdBPS.js +0 -40
  116. package/dist/packem_shared/xxh3-Ck8mXNg1.js +0 -239
@@ -1,1491 +1,27 @@
1
- import { createRequire as __cjs_createRequire } from "node:module";
1
+ var Ee=Object.defineProperty;var f=(e,t)=>Ee(e,"name",{value:t,configurable:!0});import{createRequire as Ae}from"node:module";import{isAccessibleSync as se,isFsCaseSensitive as je}from"@visulima/fs";import{isAbsolute as Fe,join as X,relative as D,basename as ve}from"@visulima/path";import{execa as ye}from"execa";import{o as ke}from"../packem_shared/index-DH-5hsrC.js";import{jsx as b,jsxs as C}from"react/jsx-runtime";import{Spinner as Ge,Text as $,Box as A,render as _e}from"@visulima/tui";import{C as be,D as B,T as Te}from"../packem_shared/symbols-CQmER5MT.js";import{dim as I,green as De,yellow as ne,cyan as K,red as Y}from"@visulima/colorize";const Me=Ae(import.meta.url),F=typeof globalThis<"u"&&typeof globalThis.process<"u"?globalThis.process:process,ee=f(e=>{if(typeof F<"u"&&F.versions&&F.versions.node){const[t,r]=F.versions.node.split(".").map(Number);if(t>22||t===22&&r>=3||t===20&&r>=16)return F.getBuiltinModule(e)}return Me(e)},"__cjs_getBuiltinModule"),{readFileSync:Oe,writeFileSync:Pe,unlinkSync:Re}=ee("node:fs"),{randomBytes:Ne}=ee("node:crypto"),{availableParallelism:ae}=ee("node:os");var He=Object.defineProperty,Le=f((e,t)=>He(e,"name",{value:t,configurable:!0}),"s$4");class O extends Error{static{f(this,"StagedError")}static{Le(this,"StagedError")}constructor(t,r){super(t,r),this.name=this.constructor.name}}var Be=Object.defineProperty,Ue=f((e,t)=>Be(e,"name",{value:t,configurable:!0}),"o$6");class qe extends O{static{f(this,"ApplyEmptyCommitError")}static{Ue(this,"ApplyEmptyCommitError")}}var Ve=Object.defineProperty,ze=f((e,t)=>Ve(e,"name",{value:t,configurable:!0}),"o$5");class T extends O{static{f(this,"ConfigError")}static{ze(this,"ConfigError")}}var We=Object.defineProperty,Ke=f((e,t)=>We(e,"name",{value:t,configurable:!0}),"t$1");class ie extends O{static{f(this,"GetBackupStashError")}static{Ke(this,"GetBackupStashError")}}var Ye=Object.defineProperty,Je=f((e,t)=>Ye(e,"name",{value:t,configurable:!0}),"s$3");class _ extends O{static{f(this,"GitError")}static{Je(this,"GitError")}stderr;constructor(t,r,n){super(t,n),this.stderr=r}}var Qe=Object.defineProperty,Xe=f((e,t)=>Qe(e,"name",{value:t,configurable:!0}),"e");class oe extends O{static{f(this,"RestoreOriginalStateError")}static{Xe(this,"RestoreOriginalStateError")}}var Ze=Object.defineProperty,et=f((e,t)=>Ze(e,"name",{value:t,configurable:!0}),"o$2");class M extends O{static{f(this,"TaskError")}static{et(this,"TaskError")}commandTitle;constructor(t,r,n){super(r,n),this.commandTitle=t}}var tt=Object.defineProperty,q=f((e,t)=>tt(e,"name",{value:t,configurable:!0}),"i");const rt=q(async e=>{if(e.config!==void 0)return e.config;throw new T(`No staged config provided. Add \`staged\` to your vis.config.ts:
2
2
 
3
- const __cjs_require = __cjs_createRequire(import.meta.url);
3
+ import { defineConfig } from "@visulima/vis/config";
4
4
 
5
- const __cjs_getProcess = typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" ? globalThis.process : process;
6
-
7
- const __cjs_getBuiltinModule = (module) => {
8
- // Check if we're in Node.js and version supports getBuiltinModule
9
- if (typeof __cjs_getProcess !== "undefined" && __cjs_getProcess.versions && __cjs_getProcess.versions.node) {
10
- const [major, minor] = __cjs_getProcess.versions.node.split(".").map(Number);
11
- // Node.js 20.16.0+ and 22.3.0+
12
- if (major > 22 || (major === 22 && minor >= 3) || (major === 20 && minor >= 16)) {
13
- return __cjs_getProcess.getBuiltinModule(module);
14
- }
15
- }
16
- // Fallback to createRequire
17
- return __cjs_require(module);
18
- };
19
-
20
- import { isAccessibleSync, isFsCaseSensitive } from '@visulima/fs';
21
- const {
22
- readFileSync,
23
- writeFileSync,
24
- unlinkSync
25
- } = __cjs_getBuiltinModule("node:fs");
26
- import { isAbsolute, join, relative, basename } from '@visulima/path';
27
- import { execa } from 'execa';
28
- import zeptomatch from 'zeptomatch';
29
- import { jsx, jsxs } from 'react/jsx-runtime';
30
- import { render, Box, Text, Spinner } from '@visulima/tui';
31
- import { C as CROSS, D as DASH, T as TICK } from '../packem_shared/symbols-Ta7g2nU-.js';
32
- import { yellow, dim, cyan, red, green } from '@visulima/colorize';
33
- const {
34
- availableParallelism
35
- } = __cjs_getBuiltinModule("node:os");
36
-
37
- class StagedError extends Error {
38
- constructor(message, options) {
39
- super(message, options);
40
- this.name = this.constructor.name;
41
- }
42
- }
43
-
44
- class ApplyEmptyCommitError extends StagedError {
45
- }
46
-
47
- class ConfigError extends StagedError {
48
- }
49
-
50
- class GetBackupStashError extends StagedError {
51
- }
52
-
53
- class GitError extends StagedError {
54
- stderr;
55
- constructor(message, stderr, options) {
56
- super(message, options);
57
- this.stderr = stderr;
58
- }
59
- }
60
-
61
- class RestoreOriginalStateError extends StagedError {
62
- }
63
-
64
- class TaskError extends StagedError {
65
- commandTitle;
66
- constructor(commandTitle, message, options) {
67
- super(message, options);
68
- this.commandTitle = commandTitle;
69
- }
70
- }
71
-
72
- const resolveConfig = async (options) => {
73
- if (options.config !== void 0) {
74
- return options.config;
75
- }
76
- throw new ConfigError(
77
- 'No staged config provided. Add `staged` to your vis.config.ts:\n\n import { defineConfig } from "@visulima/vis/config";\n\n export default defineConfig({\n staged: { "*.ts": "eslint --fix" },\n });\n\nComing from lint-staged or nano-staged? Run `vis migrate lint-staged` (or `vis migrate nano-staged`) to move the config in and remove the legacy files.'
78
- );
79
- };
80
- const validateConfig = (config) => {
81
- if (typeof config !== "object" || config === null) {
82
- throw new ConfigError("Staged config must be an object mapping glob patterns to tasks.");
83
- }
84
- const entries = Object.entries(config);
85
- if (entries.length === 0) {
86
- throw new ConfigError("Staged config is empty — at least one glob pattern is required.");
87
- }
88
- for (const [pattern, value] of entries) {
89
- if (!pattern || pattern.trim() === "") {
90
- throw new ConfigError("Staged config keys must be non-empty glob patterns.");
91
- }
92
- validateTask(pattern, value);
93
- }
94
- return config;
95
- };
96
- const validateTask = (pattern, value) => {
97
- if (typeof value === "string") {
98
- if (value.trim() === "") {
99
- throw new ConfigError(`Task for "${pattern}" is an empty string.`);
100
- }
101
- return;
102
- }
103
- if (Array.isArray(value)) {
104
- if (value.length === 0) {
105
- throw new ConfigError(`Task array for "${pattern}" is empty.`);
106
- }
107
- for (const item of value) {
108
- validateTask(pattern, item);
109
- }
110
- return;
111
- }
112
- if (typeof value === "function") {
113
- return;
114
- }
115
- if (isCustomTask$1(value)) {
116
- return;
117
- }
118
- throw new ConfigError(`Invalid task for "${pattern}" — expected string, string[], function, or { title, task } object.`);
119
- };
120
- const isCustomTask$1 = (value) => typeof value === "object" && value !== null && typeof value.title === "string" && typeof value.task === "function";
121
-
122
- const MAX_STDERR_PREVIEW = 2048;
123
- const git = async (args, options) => {
124
- const result = await execa("git", [...args], {
125
- cwd: options.cwd,
126
- env: options.env ? { ...process.env, ...options.env } : void 0,
127
- input: options.input,
128
- reject: false,
129
- stderr: "pipe",
130
- stdin: options.input === void 0 ? "ignore" : "pipe",
131
- stdout: "pipe"
5
+ export default defineConfig({
6
+ staged: { "*.ts": "eslint --fix" },
132
7
  });
133
- const exitCode = typeof result.exitCode === "number" ? result.exitCode : 1;
134
- if (exitCode !== 0 && !options.lenient) {
135
- const stderr = typeof result.stderr === "string" ? result.stderr : "";
136
- const preview = stderr.length > MAX_STDERR_PREVIEW ? `${stderr.slice(0, MAX_STDERR_PREVIEW)}…` : stderr;
137
- throw new GitError(`git ${args.join(" ")} failed with exit code ${exitCode}: ${preview.trim()}`, stderr);
138
- }
139
- return {
140
- exitCode,
141
- stderr: typeof result.stderr === "string" ? result.stderr : "",
142
- stdout: typeof result.stdout === "string" ? result.stdout : ""
143
- };
144
- };
145
- const gitOut = async (args, options) => {
146
- const { stdout } = await git(args, options);
147
- return stdout.trim();
148
- };
149
- const isGitRepo = async (cwd) => {
150
- const result = await git(["rev-parse", "--is-inside-work-tree"], { cwd, lenient: true });
151
- return result.exitCode === 0 && result.stdout.trim() === "true";
152
- };
153
- const getGitDirectory = async (cwd) => gitOut(["rev-parse", "--absolute-git-dir"], { cwd });
154
- const getWorkTree = async (cwd) => gitOut(["rev-parse", "--show-toplevel"], { cwd });
155
- const writeIndexTree = async (cwd) => gitOut(["write-tree"], { cwd });
156
- const headTreeSha = async (cwd) => {
157
- const result = await git(["rev-parse", "HEAD^{tree}"], { cwd, lenient: true });
158
- return result.exitCode === 0 ? result.stdout.trim() : "";
159
- };
160
- const MIN_GIT_VERSION = { major: 2, minor: 32 };
161
- const parseGitVersion = (output) => {
162
- const match = /git version (\d+)\.(\d+)/.exec(output);
163
- if (!match) {
164
- return null;
165
- }
166
- const major = Number.parseInt(match[1] ?? "", 10);
167
- const minor = Number.parseInt(match[2] ?? "", 10);
168
- if (Number.isNaN(major) || Number.isNaN(minor)) {
169
- return null;
170
- }
171
- return { major, minor };
172
- };
173
- const assertGitVersion = async (cwd) => {
174
- const version = parseGitVersion(await gitOut(["--version"], { cwd }));
175
- if (version === null) {
176
- return;
177
- }
178
- const tooOld = version.major < MIN_GIT_VERSION.major || version.major === MIN_GIT_VERSION.major && version.minor < MIN_GIT_VERSION.minor;
179
- if (tooOld) {
180
- throw new GitError(`Git ${MIN_GIT_VERSION.major}.${MIN_GIT_VERSION.minor} or newer is required; found ${version.major}.${version.minor}.`);
181
- }
182
- };
183
-
184
- const DEFAULT_DIFF_FILTER = "ACMR";
185
- const PATH_BATCH_SIZE = 500;
186
- const getIntentToAddPaths = async (cwd) => {
187
- const { stdout } = await git(["diff-files", "--raw", "-z"], { cwd });
188
- const tokens = stdout.split("\0").filter((token) => token.length > 0);
189
- const paths = [];
190
- for (let i = 0; i < tokens.length; i += 1) {
191
- const header = tokens[i];
192
- if (!header?.startsWith(":")) {
193
- continue;
194
- }
195
- const parts = header.slice(1).split(" ");
196
- const dstSha = parts[3];
197
- const status = parts[4];
198
- const path = tokens[i + 1];
199
- i += 1;
200
- if (status === "A" && dstSha !== void 0 && /^0+$/.test(dstSha) && path !== void 0) {
201
- paths.push(path);
202
- }
203
- }
204
- return paths;
205
- };
206
- const getUntrackedFiles = async (cwd) => {
207
- const { stdout } = await git(["ls-files", "--others", "--exclude-standard", "-z"], { cwd });
208
- return stdout.split("\0").filter((path) => path.length > 0);
209
- };
210
- const removeFromIndex = async (paths, options) => {
211
- if (paths.length === 0) {
212
- return;
213
- }
214
- const pathspec = `${paths.join("\0")}\0`;
215
- await git(["rm", "--cached", "--quiet", "--pathspec-from-file=-", "--pathspec-file-nul", "--"], { cwd: options.cwd, input: pathspec });
216
- };
217
- const getFiles = async (options) => {
218
- const filter = options.diffFilter ?? DEFAULT_DIFF_FILTER;
219
- const args = options.diff === void 0 ? ["diff", "--name-only", "-z", `--diff-filter=${filter}`, "--staged"] : ["diff", "--name-only", "-z", `--diff-filter=${filter}`, ...options.diff.split(/\s+/).filter(Boolean)];
220
- const { stdout } = await git(args, { cwd: options.cwd });
221
- const relativePaths = stdout.split("\0").filter((path) => path.length > 0);
222
- if (relativePaths.length === 0) {
223
- return [];
224
- }
225
- const worktree = options.workTree ?? await getWorkTree(options.cwd);
226
- return relativePaths.map((p) => isAbsolute(p) ? p : join(worktree, p));
227
- };
228
- const capturePatch = async (paths, options) => {
229
- if (paths.length === 0) {
230
- return null;
231
- }
232
- const fragments = [];
233
- for (let start = 0; start < paths.length; start += PATH_BATCH_SIZE) {
234
- const batch = paths.slice(start, start + PATH_BATCH_SIZE);
235
- const { stdout } = await git(
236
- [
237
- "diff",
238
- "--binary",
239
- "--unified=0",
240
- "--no-color",
241
- "--no-ext-diff",
242
- "--src-prefix=a/",
243
- "--dst-prefix=b/",
244
- "--patch",
245
- "--submodule=short",
246
- "--",
247
- ...batch
248
- ],
249
- { cwd: options.cwd }
250
- );
251
- if (stdout.length > 0) {
252
- fragments.push(stdout);
253
- }
254
- }
255
- if (fragments.length === 0) {
256
- return null;
257
- }
258
- const combined = fragments.join("");
259
- const normalized = combined.endsWith("\n") ? combined : `${combined}
260
- `;
261
- return Buffer.from(normalized, "utf8");
262
- };
263
- const getPartiallyStagedFiles = async (cwd) => {
264
- const { stdout } = await git(["status", "--porcelain=v1", "-z"], { cwd });
265
- const records = stdout.split("\0");
266
- const partial = [];
267
- for (let i = 0; i < records.length; i += 1) {
268
- const record = records[i];
269
- if (record === void 0 || record.length < 4) {
270
- continue;
271
- }
272
- const indexState = record.charAt(0);
273
- const worktreeState = record.charAt(1);
274
- const path = record.slice(3);
275
- const consumesNextRecord = indexState === "R" || indexState === "C" || worktreeState === "R" || worktreeState === "C";
276
- if (indexState !== " " && indexState !== "?" && worktreeState !== " " && worktreeState !== "?") {
277
- partial.push(path);
278
- }
279
- if (consumesNextRecord) {
280
- i += 1;
281
- }
282
- }
283
- return partial;
284
- };
285
- const checkoutPaths = async (paths, options) => {
286
- if (paths.length === 0) {
287
- return;
288
- }
289
- const pathspec = `${paths.join("\0")}\0`;
290
- await git(["checkout", "--force", "--pathspec-from-file=-", "--pathspec-file-nul", "--"], { cwd: options.cwd, input: pathspec });
291
- };
292
- const updateIndexAgain = async (paths, options) => {
293
- await git(["update-index", "--again"], { cwd: options.cwd, lenient: true });
294
- if (paths.length === 0) {
295
- return;
296
- }
297
- const pathspec = `${paths.join("\0")}\0`;
298
- await git(["add", "-u", "--pathspec-from-file=-", "--pathspec-file-nul", "--"], { cwd: options.cwd, input: pathspec });
299
- };
300
8
 
301
- const STASH_MESSAGE_PREFIX = "vis_staged_automatic_backup";
302
- const buildMessage = () => `${STASH_MESSAGE_PREFIX}-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
303
- const createBackupStash = async (cwd) => {
304
- const sha = await gitOut(["stash", "create"], { cwd });
305
- if (sha.length === 0) {
306
- return null;
307
- }
308
- await git(["stash", "store", "-m", buildMessage(), sha], { cwd });
309
- return sha;
310
- };
311
- const createHideAllStash = async (cwd) => {
312
- const message = buildMessage();
313
- const { exitCode, stdout } = await git(["stash", "push", "--keep-index", "--include-untracked", "--quiet", "-m", message], { cwd, lenient: true });
314
- if (exitCode !== 0) {
315
- return null;
316
- }
317
- const note = stdout.trim();
318
- if (note.length > 0 && /no local changes/i.test(note)) {
319
- return null;
320
- }
321
- return gitOut(["rev-parse", "stash@{0}"], { cwd });
322
- };
323
- const findStashRefBySha = async (cwd, sha) => {
324
- const { exitCode, stdout } = await git(["reflog", "--format=%H %gd", "refs/stash"], { cwd, lenient: true });
325
- if (exitCode !== 0) {
326
- return null;
327
- }
328
- for (const line of stdout.split(/\r?\n/)) {
329
- const [entrySha, ref] = line.split(/\s+/, 2);
330
- if (entrySha === sha && ref !== void 0) {
331
- return ref;
332
- }
333
- }
334
- return null;
335
- };
336
- const dropBackupStash = async (cwd, sha) => {
337
- if (sha === null) {
338
- return;
339
- }
340
- const reference = await findStashRefBySha(cwd, sha);
341
- if (reference === null) {
342
- return;
343
- }
344
- await git(["stash", "drop", "--quiet", reference], { cwd });
345
- };
346
- const applyBackupStash = async (cwd, sha) => {
347
- if (sha === null) {
348
- throw new GetBackupStashError("Backup stash was not found — can't revert working tree.");
349
- }
350
- const reference = await findStashRefBySha(cwd, sha);
351
- if (reference === null) {
352
- throw new GetBackupStashError(`Backup stash ${sha} is no longer reachable — can't revert working tree.`);
353
- }
354
- await git(["reset", "--hard", "HEAD"], { cwd });
355
- await git(["stash", "apply", "--index", "--quiet", reference], { cwd });
356
- };
357
- const popHideAllStash = async (cwd, sha) => {
358
- if (sha === null) {
359
- return;
360
- }
361
- const reference = await findStashRefBySha(cwd, sha);
362
- if (reference === null) {
363
- return;
364
- }
365
- await git(["stash", "pop", "--quiet", reference], { cwd });
366
- };
367
-
368
- class GitWorkflow {
369
- stagedFiles = [];
370
- partiallyStaged = [];
371
- workTree = "";
372
- gitDir = "";
373
- /** Index tree sha captured at the end of `prepare()`, before tasks run. */
374
- preTaskIndexTree = "";
375
- /** Index tree sha captured at the end of `applyModifications()`, after tasks' in-place edits are staged. */
376
- postTaskIndexTree = "";
377
- /** HEAD tree sha captured in `prepare()` — used to detect empty-after-revert commits. */
378
- headTree = "";
379
- /** Becomes true after a successful `revert()` so the caller can skip the unstaged-patch re-apply that would duplicate the restored deltas. */
380
- revertApplied = false;
381
- /** Emitted by `prepare()` when we skip the backup stash but still have partially-staged files — the caller can surface this to the user. */
382
- warnings = [];
383
- cwd;
384
- options;
385
- patch = null;
386
- backupStashSha = null;
387
- merge = [];
388
- shouldStash;
389
- shouldHidePartial;
390
- shouldHideUnstaged;
391
- shouldHideAll;
392
- /** Sha of the hide-all push stash (if `--hide-all` is set), tracked separately from the create+store backup. */
393
- hideAllStashSha = null;
394
- /** Paths that were `git add -N`'d (intent-to-add) — removed from the index before stashing, restored after. */
395
- intentToAddPaths = [];
396
- /** Untracked files observed at the end of `prepare()`. Diffed post-run to detect task-created files for `--autoStage`. */
397
- preTaskUntracked = /* @__PURE__ */ new Set();
398
- constructor(options) {
399
- this.cwd = options.cwd ?? process.cwd();
400
- this.options = options;
401
- this.shouldStash = options.stash !== false && options.diff === void 0;
402
- this.shouldHidePartial = options.hidePartiallyStaged !== false;
403
- this.shouldHideUnstaged = options.hideUnstaged === true;
404
- this.shouldHideAll = options.hideAll === true;
405
- }
406
- async prepare() {
407
- if (!await isGitRepo(this.cwd)) {
408
- throw new GitError(`Not a git repository: ${this.cwd}`);
409
- }
410
- await assertGitVersion(this.cwd);
411
- this.workTree = await getWorkTree(this.cwd);
412
- this.gitDir = await getGitDirectory(this.cwd);
413
- this.stagedFiles = await getFiles({ cwd: this.cwd, diff: this.options.diff, diffFilter: this.options.diffFilter, workTree: this.workTree });
414
- this.partiallyStaged = this.stagedFiles.length === 0 ? [] : await getPartiallyStagedFiles(this.cwd);
415
- this.snapshotMergeState();
416
- this.intentToAddPaths = await getIntentToAddPaths(this.workTree);
417
- if (this.intentToAddPaths.length > 0) {
418
- await removeFromIndex(this.intentToAddPaths, { cwd: this.workTree });
419
- }
420
- if (this.shouldStash) {
421
- this.backupStashSha = await createBackupStash(this.workTree);
422
- } else if (this.partiallyStaged.length > 0) {
423
- this.warnings.push(
424
- "Running with --no-stash on partially-staged files — unstaged deltas will be captured to a patch, but if re-applying the patch fails after tasks run the changes cannot be recovered."
425
- );
426
- }
427
- if (this.shouldHideAll) {
428
- this.hideAllStashSha = await createHideAllStash(this.workTree);
429
- } else {
430
- await this.hideUnstagedChanges();
431
- }
432
- this.preTaskIndexTree = this.stagedFiles.length === 0 ? "" : await writeIndexTree(this.workTree);
433
- this.postTaskIndexTree = this.preTaskIndexTree;
434
- this.headTree = await headTreeSha(this.workTree);
435
- this.preTaskUntracked = new Set(await getUntrackedFiles(this.workTree));
436
- }
437
- /**
438
- * Refreshes the index against the working tree, so task edits
439
- * (including deletions of already-tracked files) are re-staged.
440
- * Uses `git update-index --again` for parity with lint-staged v17,
441
- * which behaves correctly when the original commit used a pathspec.
442
- */
443
- async applyModifications({ autoStage = false } = {}) {
444
- if (this.stagedFiles.length === 0) {
445
- return;
446
- }
447
- const worktreeRelative = this.stagedFiles.map((path) => relative(this.workTree, path));
448
- await updateIndexAgain(worktreeRelative, { cwd: this.workTree });
449
- if (autoStage) {
450
- const postUntracked = await getUntrackedFiles(this.workTree);
451
- const newFiles = postUntracked.filter((path) => !this.preTaskUntracked.has(path));
452
- if (newFiles.length > 0) {
453
- const pathspec = `${newFiles.join("\0")}\0`;
454
- await git(["add", "--pathspec-from-file=-", "--pathspec-file-nul", "--"], { cwd: this.workTree, input: pathspec });
455
- }
456
- }
457
- if (this.intentToAddPaths.length > 0) {
458
- try {
459
- await git(["add", "--intent-to-add", "--", ...this.intentToAddPaths], { cwd: this.workTree });
460
- } catch {
461
- }
462
- }
463
- this.postTaskIndexTree = await writeIndexTree(this.workTree);
464
- }
465
- /** True when tasks modified at least one staged file's content (index tree changed). */
466
- indexTreeChanged() {
467
- return this.preTaskIndexTree.length > 0 && this.postTaskIndexTree.length > 0 && this.preTaskIndexTree !== this.postTaskIndexTree;
468
- }
469
- /** True when the post-task index tree matches HEAD — i.e. tasks reverted every staged change. */
470
- postTaskIndexMatchesHead() {
471
- return this.postTaskIndexTree.length > 0 && this.headTree.length > 0 && this.postTaskIndexTree === this.headTree;
472
- }
473
- /**
474
- * Re-applies the hidden unstaged patch. Falls back to 3-way on conflict.
475
- * Skips silently when `revert()` already restored the working tree from the backup stash,
476
- * since the stash already carries those deltas and re-applying would duplicate them.
477
- * When `--hide-all` is active this is also a no-op — the `popHideAllStash()` call in
478
- * `cleanup()` handles restore for that mode.
479
- */
480
- async restoreUnstagedChanges() {
481
- if (this.revertApplied || this.patch === null || this.shouldHideAll) {
482
- return;
483
- }
484
- const applyArguments = ["apply", "--whitespace=nowarn", "--recount", "--unidiff-zero"];
485
- let firstError;
486
- try {
487
- await git(applyArguments, { cwd: this.workTree, input: this.patch });
488
- return;
489
- } catch (error) {
490
- firstError = error instanceof GitError ? error.stderr : String(error);
491
- }
492
- try {
493
- await git([...applyArguments, "--3way"], { cwd: this.workTree, input: this.patch });
494
- } catch (error) {
495
- const detail = error instanceof GitError && error.stderr ? error.stderr : String(error);
496
- throw new RestoreOriginalStateError(
497
- `Failed to re-apply unstaged changes after running tasks. Original changes remain in the backup stash — recover with \`git stash list\` and \`git stash apply\`.
498
- First attempt: ${firstError ?? "(no stderr)"}
499
- Second attempt: ${detail}`,
500
- { cause: error }
501
- );
502
- }
503
- }
504
- /** Restores index + working tree from the backup stash and drops it. */
505
- async revert() {
506
- if (this.backupStashSha === null) {
507
- return;
508
- }
509
- try {
510
- await applyBackupStash(this.workTree, this.backupStashSha);
511
- } catch (error) {
512
- throw new RestoreOriginalStateError("Revert failed while restoring the backup stash. Use `git stash list` to recover manually.", {
513
- cause: error
514
- });
515
- }
516
- await dropBackupStash(this.workTree, this.backupStashSha);
517
- if (this.intentToAddPaths.length > 0) {
518
- try {
519
- await git(["add", "--intent-to-add", "--", ...this.intentToAddPaths], { cwd: this.workTree });
520
- } catch {
521
- }
522
- }
523
- this.revertApplied = true;
524
- }
525
- /**
526
- * On success, drops the backup stash and pops the hide-all stash (if any).
527
- * On failure without --revert, leaves the backup stash in place so the
528
- * user can recover manually.
529
- */
530
- async cleanup(success) {
531
- this.restoreMergeState();
532
- if (this.hideAllStashSha !== null) {
533
- try {
534
- await popHideAllStash(this.workTree, this.hideAllStashSha);
535
- } catch {
536
- }
537
- }
538
- if (success && this.backupStashSha !== null && !this.revertApplied) {
539
- await dropBackupStash(this.workTree, this.backupStashSha);
540
- }
541
- }
542
- /**
543
- * Returns a user-facing hint that points at the backup stash when
544
- * tasks failed and we kept it around (no --revert).
545
- */
546
- recoveryHint() {
547
- if (this.backupStashSha === null) {
548
- return null;
549
- }
550
- return `Backup stash is preserved (sha ${this.backupStashSha.slice(0, 7)}) — restore with: git stash apply --index ${this.backupStashSha}`;
551
- }
552
- async hideUnstagedChanges() {
553
- const stagedRelative = new Set(this.stagedFiles.map((path) => relative(this.workTree, path)));
554
- const candidatesRelative = this.shouldHideUnstaged ? [...stagedRelative] : this.shouldHidePartial ? this.partiallyStaged.filter((path) => stagedRelative.has(path)) : [];
555
- if (candidatesRelative.length === 0) {
556
- return;
557
- }
558
- this.patch = await capturePatch(candidatesRelative, { cwd: this.workTree });
559
- if (this.patch === null) {
560
- return;
561
- }
562
- await checkoutPaths(candidatesRelative, { cwd: this.workTree });
563
- }
564
- snapshotMergeState() {
565
- if (this.gitDir.length === 0) {
566
- return;
567
- }
568
- this.merge = ["MERGE_HEAD", "MERGE_MODE", "MERGE_MSG"].map((name) => {
569
- const target = join(this.gitDir, name);
570
- if (isAccessibleSync(target)) {
571
- return { body: readFileSync(target), existed: true, name };
572
- }
573
- return { body: null, existed: false, name };
574
- });
575
- }
576
- restoreMergeState() {
577
- if (this.gitDir.length === 0 || this.merge.length === 0) {
578
- return;
579
- }
580
- for (const entry of this.merge) {
581
- const target = join(this.gitDir, entry.name);
582
- try {
583
- if (entry.existed && entry.body !== null) {
584
- writeFileSync(target, entry.body);
585
- } else if (isAccessibleSync(target)) {
586
- unlinkSync(target);
587
- }
588
- } catch {
589
- }
590
- }
591
- }
592
- }
593
-
594
- const isPathStyle = (pattern) => pattern.includes("/");
595
- const normalizeForMatch = (value, caseInsensitive) => caseInsensitive ? value.toLowerCase() : value;
596
- const matchFiles = (pattern, files, cwd, options = {}) => {
597
- const pathStyle = isPathStyle(pattern);
598
- const caseInsensitive = options.caseInsensitive === true;
599
- const effectivePattern = normalizeForMatch(pattern, caseInsensitive);
600
- const matched = [];
601
- for (const absolute of files) {
602
- const candidate = pathStyle ? relative(cwd, absolute) : basename(absolute);
603
- if (zeptomatch(effectivePattern, normalizeForMatch(candidate, caseInsensitive))) {
604
- matched.push(absolute);
605
- }
606
- }
607
- return matched;
608
- };
609
- const applyIgnore = (files, ignore, cwd, options = {}) => {
610
- if (!ignore || ignore.length === 0) {
611
- return [...files];
612
- }
613
- const caseInsensitive = options.caseInsensitive === true;
614
- return files.filter((absolute) => {
615
- for (const pattern of ignore) {
616
- const candidate = isPathStyle(pattern) ? relative(cwd, absolute) : basename(absolute);
617
- const effectivePattern = normalizeForMatch(pattern, caseInsensitive);
618
- if (zeptomatch(effectivePattern, normalizeForMatch(candidate, caseInsensitive))) {
619
- return false;
620
- }
621
- }
622
- return true;
9
+ Coming from lint-staged or nano-staged? Run \`vis migrate lint-staged\` (or \`vis migrate nano-staged\`) to move the config in and remove the legacy files.`)},"resolveConfig"),ce=q(e=>{if(typeof e!="object"||e===null)throw new T("Staged config must be an object mapping glob patterns to tasks.");const t=Object.entries(e);if(t.length===0)throw new T("Staged config is empty — at least one glob pattern is required.");for(const[r,n]of t){if(!r||r.trim()==="")throw new T("Staged config keys must be non-empty glob patterns.");Se(r,n)}return e},"validateConfig"),Se=q((e,t)=>{if(typeof t=="string"){if(t.trim()==="")throw new T(`Task for "${e}" is an empty string.`);return}if(Array.isArray(t)){if(t.length===0)throw new T(`Task array for "${e}" is empty.`);for(const r of t)Se(e,r);return}if(typeof t!="function"&&!st(t))throw new T(`Invalid task for "${e}" — expected string, string[], function, or { title, task } object.`)},"validateTask"),st=q(e=>typeof e=="object"&&e!==null&&typeof e.title=="string"&&typeof e.task=="function","isCustomTask");var nt=Object.defineProperty,x=f((e,t)=>nt(e,"name",{value:t,configurable:!0}),"n$1");const de=2048,g=x(async(e,t)=>{const r=await ye("git",[...e],{cwd:t.cwd,env:t.env?{...process.env,...t.env}:void 0,input:t.input,reject:!1,stderr:"pipe",stdin:t.input===void 0?"ignore":"pipe",stdout:"pipe"}),n=typeof r.exitCode=="number"?r.exitCode:1;if(n!==0&&!t.lenient){const s=typeof r.stderr=="string"?r.stderr:"",i=s.length>de?`${s.slice(0,de)}…`:s;throw new _(`git ${e.join(" ")} failed with exit code ${n}: ${i.trim()}`,s)}return{exitCode:n,stderr:typeof r.stderr=="string"?r.stderr:"",stdout:typeof r.stdout=="string"?r.stdout:""}},"git"),R=x(async(e,t)=>{const{stdout:r}=await g(e,t);return r.trim()},"gitOut"),at=x(async e=>{const t=await g(["rev-parse","--is-inside-work-tree"],{cwd:e,lenient:!0});return t.exitCode===0&&t.stdout.trim()==="true"},"isGitRepo"),it=x(async e=>R(["rev-parse","--absolute-git-dir"],{cwd:e}),"getGitDirectory"),$e=x(async e=>R(["rev-parse","--show-toplevel"],{cwd:e}),"getWorkTree"),le=x(async e=>R(["write-tree"],{cwd:e}),"writeIndexTree"),ot=x(async e=>{const t=await g(["rev-parse","HEAD^{tree}"],{cwd:e,lenient:!0});return t.exitCode===0?t.stdout.trim():""},"headTreeSha"),N={major:2,minor:32},ct=x(e=>{const t=/git version (\d+)\.(\d+)/.exec(e);if(!t)return null;const r=Number.parseInt(t[1]??"",10),n=Number.parseInt(t[2]??"",10);return Number.isNaN(r)||Number.isNaN(n)?null:{major:r,minor:n}},"parseGitVersion"),dt=x(async e=>{const t=ct(await R(["--version"],{cwd:e}));if(t!==null&&(t.major<N.major||t.major===N.major&&t.minor<N.minor))throw new _(`Git ${N.major}.${N.minor} or newer is required; found ${t.major}.${t.minor}.`)},"assertGitVersion");var lt=Object.defineProperty,E=f((e,t)=>lt(e,"name",{value:t,configurable:!0}),"a$2");const ut="ACMR",ue=500,ft=E(async e=>{const{stdout:t}=await g(["diff-files","--raw","-z"],{cwd:e}),r=t.split("\0").filter(s=>s.length>0),n=[];for(let s=0;s<r.length;s+=1){const i=r[s];if(!i?.startsWith(":"))continue;const o=i.slice(1).split(" "),a=o[3],c=o[4],l=r[s+1];s+=1,c==="A"&&a!==void 0&&/^0+$/.test(a)&&l!==void 0&&n.push(l)}return n},"getIntentToAddPaths"),fe=E(async e=>{const{stdout:t}=await g(["ls-files","--others","--exclude-standard","-z"],{cwd:e});return t.split("\0").filter(r=>r.length>0)},"getUntrackedFiles"),ht=E(async(e,t)=>{if(e.length===0)return;const r=`${e.join("\0")}\0`;await g(["rm","--cached","--quiet","--pathspec-from-file=-","--pathspec-file-nul","--"],{cwd:t.cwd,input:r})},"removeFromIndex"),pt=E(async e=>{const t=e.diffFilter??ut,r=e.diff===void 0?["diff","--name-only","-z",`--diff-filter=${t}`,"--staged"]:["diff","--name-only","-z",`--diff-filter=${t}`,...e.diff.split(/\s+/).filter(Boolean)],{stdout:n}=await g(r,{cwd:e.cwd}),s=n.split("\0").filter(o=>o.length>0);if(s.length===0)return[];const i=e.workTree??await $e(e.cwd);return s.map(o=>Fe(o)?o:X(i,o))},"getFiles"),gt=E(async(e,t)=>{if(e.length===0)return null;const r=[];for(let i=0;i<e.length;i+=ue){const o=e.slice(i,i+ue),{stdout:a}=await g(["diff","--binary","--unified=0","--no-color","--no-ext-diff","--src-prefix=a/","--dst-prefix=b/","--patch","--submodule=short","--",...o],{cwd:t.cwd});a.length>0&&r.push(a)}if(r.length===0)return null;const n=r.join(""),s=n.endsWith(`
10
+ `)?n:`${n}
11
+ `;return Buffer.from(s,"utf8")},"capturePatch"),mt=E(async e=>{const{stdout:t}=await g(["status","--porcelain=v1","-z"],{cwd:e}),r=t.split("\0"),n=[];for(let s=0;s<r.length;s+=1){const i=r[s];if(i===void 0||i.length<4)continue;const o=i.charAt(0),a=i.charAt(1),c=i.slice(3),l=o==="R"||o==="C"||a==="R"||a==="C";o!==" "&&o!=="?"&&a!==" "&&a!=="?"&&n.push(c),l&&(s+=1)}return n},"getPartiallyStagedFiles"),wt=E(async(e,t)=>{if(e.length===0)return;const r=`${e.join("\0")}\0`;await g(["checkout","--force","--pathspec-from-file=-","--pathspec-file-nul","--"],{cwd:t.cwd,input:r})},"checkoutPaths"),vt=E(async(e,t)=>{if(await g(["update-index","--again"],{cwd:t.cwd,lenient:!0}),e.length===0)return;const r=`${e.join("\0")}\0`;await g(["add","-u","--pathspec-from-file=-","--pathspec-file-nul","--"],{cwd:t.cwd,input:r})},"updateIndexAgain");var yt=Object.defineProperty,P=f((e,t)=>yt(e,"name",{value:t,configurable:!0}),"r$1");const kt="vis_staged_automatic_backup",xe=P(()=>`${kt}-${process.pid}-${Date.now()}-${Ne(3).toString("hex")}`,"buildMessage"),bt=P(async e=>{const t=await R(["stash","create"],{cwd:e});return t.length===0?null:(await g(["stash","store","-m",xe(),t],{cwd:e}),t)},"createBackupStash"),Tt=P(async e=>{const t=xe(),{exitCode:r,stdout:n}=await g(["stash","push","--keep-index","--include-untracked","--quiet","-m",t],{cwd:e,lenient:!0});if(r!==0)return null;const s=n.trim();return s.length>0&&/no local changes/i.test(s)?null:R(["rev-parse","stash@{0}"],{cwd:e})},"createHideAllStash"),te=P(async(e,t)=>{const{exitCode:r,stdout:n}=await g(["reflog","--format=%H %gd","refs/stash"],{cwd:e,lenient:!0});if(r!==0)return null;for(const s of n.split(/\r?\n/)){const[i,o]=s.split(/\s+/,2);if(i===t&&o!==void 0)return o}return null},"findStashRefBySha"),he=P(async(e,t)=>{if(t===null)return;const r=await te(e,t);r!==null&&await g(["stash","drop","--quiet",r],{cwd:e})},"dropBackupStash"),St=P(async(e,t)=>{if(t===null)throw new ie("Backup stash was not found — can't revert working tree.");const r=await te(e,t);if(r===null)throw new ie(`Backup stash ${t} is no longer reachable — can't revert working tree.`);await g(["reset","--hard","HEAD"],{cwd:e}),await g(["stash","apply","--index","--quiet",r],{cwd:e})},"applyBackupStash"),$t=P(async(e,t)=>{if(t===null)return;const r=await te(e,t);r!==null&&await g(["stash","pop","--quiet",r],{cwd:e})},"popHideAllStash");var xt=Object.defineProperty,Ct=f((e,t)=>xt(e,"name",{value:t,configurable:!0}),"l");class It{static{f(this,"GitWorkflow")}static{Ct(this,"GitWorkflow")}stagedFiles=[];partiallyStaged=[];workTree="";gitDir="";preTaskIndexTree="";postTaskIndexTree="";headTree="";revertApplied=!1;warnings=[];cwd;options;patch=null;backupStashSha=null;merge=[];shouldStash;shouldHidePartial;shouldHideUnstaged;shouldHideAll;hideAllStashSha=null;intentToAddPaths=[];preTaskUntracked=new Set;constructor(t){this.cwd=t.cwd??process.cwd(),this.options=t,this.shouldStash=t.stash!==!1&&t.diff===void 0,this.shouldHidePartial=t.hidePartiallyStaged!==!1,this.shouldHideUnstaged=t.hideUnstaged===!0,this.shouldHideAll=t.hideAll===!0}async prepare(){if(!await at(this.cwd))throw new _(`Not a git repository: ${this.cwd}`);await dt(this.cwd),this.workTree=await $e(this.cwd),this.gitDir=await it(this.cwd),this.stagedFiles=await pt({cwd:this.cwd,diff:this.options.diff,diffFilter:this.options.diffFilter,workTree:this.workTree}),this.partiallyStaged=this.stagedFiles.length===0?[]:await mt(this.cwd),this.snapshotMergeState(),this.intentToAddPaths=await ft(this.workTree),this.intentToAddPaths.length>0&&await ht(this.intentToAddPaths,{cwd:this.workTree}),this.shouldStash?this.backupStashSha=await bt(this.workTree):this.partiallyStaged.length>0&&this.warnings.push("Running with --no-stash on partially-staged files — unstaged deltas will be captured to a patch, but if re-applying the patch fails after tasks run the changes cannot be recovered."),this.shouldHideAll?this.hideAllStashSha=await Tt(this.workTree):await this.hideUnstagedChanges(),this.preTaskIndexTree=this.stagedFiles.length===0?"":await le(this.workTree),this.postTaskIndexTree=this.preTaskIndexTree,this.headTree=await ot(this.workTree),this.preTaskUntracked=new Set(await fe(this.workTree))}async applyModifications({autoStage:t=!1}={}){if(this.stagedFiles.length===0)return;const r=this.stagedFiles.map(n=>D(this.workTree,n));if(await vt(r,{cwd:this.workTree}),t){const n=(await fe(this.workTree)).filter(s=>!this.preTaskUntracked.has(s));if(n.length>0){const s=`${n.join("\0")}\0`;await g(["add","--pathspec-from-file=-","--pathspec-file-nul","--"],{cwd:this.workTree,input:s})}}if(this.intentToAddPaths.length>0)try{await g(["add","--intent-to-add","--",...this.intentToAddPaths],{cwd:this.workTree})}catch{}this.postTaskIndexTree=await le(this.workTree)}indexTreeChanged(){return this.preTaskIndexTree.length>0&&this.postTaskIndexTree.length>0&&this.preTaskIndexTree!==this.postTaskIndexTree}postTaskIndexMatchesHead(){return this.postTaskIndexTree.length>0&&this.headTree.length>0&&this.postTaskIndexTree===this.headTree}async restoreUnstagedChanges(){if(this.revertApplied||this.patch===null||this.shouldHideAll)return;const t=["apply","--whitespace=nowarn","--recount","--unidiff-zero"];let r;try{await g(t,{cwd:this.workTree,input:this.patch});return}catch(n){r=n instanceof _?n.stderr:String(n)}try{await g([...t,"--3way"],{cwd:this.workTree,input:this.patch})}catch(n){const s=n instanceof _&&n.stderr?n.stderr:String(n);throw new oe(`Failed to re-apply unstaged changes after running tasks. Original changes remain in the backup stash — recover with \`git stash list\` and \`git stash apply\`.
12
+ First attempt: ${r??"(no stderr)"}
13
+ Second attempt: ${s}`,{cause:n})}}async revert(){if(this.backupStashSha!==null){try{await St(this.workTree,this.backupStashSha)}catch(t){throw new oe("Revert failed while restoring the backup stash. Use `git stash list` to recover manually.",{cause:t})}if(await he(this.workTree,this.backupStashSha),this.intentToAddPaths.length>0)try{await g(["add","--intent-to-add","--",...this.intentToAddPaths],{cwd:this.workTree})}catch{}this.revertApplied=!0}}async cleanup(t){if(this.restoreMergeState(),this.hideAllStashSha!==null)try{await $t(this.workTree,this.hideAllStashSha)}catch{}t&&this.backupStashSha!==null&&!this.revertApplied&&await he(this.workTree,this.backupStashSha)}recoveryHint(){return this.backupStashSha===null?null:`Backup stash is preserved (sha ${this.backupStashSha.slice(0,7)}) — restore with: git stash apply --index ${this.backupStashSha}`}async hideUnstagedChanges(){const t=new Set(this.stagedFiles.map(n=>D(this.workTree,n))),r=this.shouldHideUnstaged?[...t]:this.shouldHidePartial?this.partiallyStaged.filter(n=>t.has(n)):[];r.length!==0&&(this.patch=await gt(r,{cwd:this.workTree}),this.patch!==null&&await wt(r,{cwd:this.workTree}))}snapshotMergeState(){this.gitDir.length!==0&&(this.merge=["MERGE_HEAD","MERGE_MODE","MERGE_MSG"].map(t=>{const r=X(this.gitDir,t);return se(r)?{body:Oe(r),existed:!0,name:t}:{body:null,existed:!1,name:t}}))}restoreMergeState(){if(!(this.gitDir.length===0||this.merge.length===0))for(const t of this.merge){const r=X(this.gitDir,t.name);try{t.existed&&t.body!==null?Pe(r,t.body):se(r)&&Re(r)}catch{}}}}var Et=Object.defineProperty,V=f((e,t)=>Et(e,"name",{value:t,configurable:!0}),"r");const Ce=V(e=>e.includes("/"),"isPathStyle"),U=V((e,t)=>t?e.toLowerCase():e,"normalizeForMatch"),At=V((e,t,r,n={})=>{const s=Ce(e),i=n.caseInsensitive===!0,o=U(e,i),a=[];for(const c of t){const l=s?D(r,c):ve(c);ke(o,U(l,i))&&a.push(c)}return a},"matchFiles"),Mt=V((e,t,r,n={})=>{if(!t||t.length===0)return[...e];const s=n.caseInsensitive===!0;return e.filter(i=>{for(const o of t){const a=Ce(o)?D(r,i):ve(i),c=U(o,s);if(ke(c,U(a,s)))return!1}return!0})},"applyIgnore");var jt=Object.defineProperty,H=f((e,t)=>jt(e,"name",{value:t,configurable:!0}),"g");const Ot=H(e=>{switch(e){case"failed":return"red";case"running":return"cyan";case"skipped":return"yellow";case"success":return"green";default:return"gray"}},"colorForStatus"),pe=H(e=>{if(e==="running")return b(Ge,{type:"dots"});const t=e==="failed"?be:e==="skipped"?B:e==="success"?Te:B;return b($,{color:Ot(e),children:t})},"iconForStatus"),ge=H(({state:e,tick:t,verbose:r})=>C(A,{flexDirection:"column",children:[[...e.patterns.values()].map(n=>C(A,{flexDirection:"column",children:[C(A,{children:[pe(n.status),C($,{children:[" ",n.title]})]}),[...n.commands.values()].map(s=>C(A,{flexDirection:"column",marginLeft:2,children:[C(A,{children:[pe(s.status),C($,{children:[" ",s.title," "]}),s.status!=="pending"&&s.status!=="running"?C($,{color:"gray",children:["(",s.durationMs,"ms)"]}):null]}),r&&s.output?b(A,{flexDirection:"column",marginLeft:2,children:s.output.split(/\r?\n/).slice(0,20).map((i,o)=>b($,{color:"gray",children:i},`${s.id}-line-${o}`))}):null,s.status==="failed"&&s.error?b(A,{marginLeft:2,children:b($,{color:"red",children:s.error.message})}):null]},s.id))]},n.id)),e.infoMessages.map((n,s)=>b($,{color:"gray",children:n},`info-${s}`)),e.warnMessages.map((n,s)=>b($,{color:"yellow",children:n},`warn-${s}`)),e.errorMessages.map(({message:n},s)=>b($,{color:"red",children:n},`err-${s}`))]}),"App"),Pt=H((e={})=>{const t=e.verbose===!0,r={errorMessages:[],infoMessages:[],patterns:new Map,started:!1,warnMessages:[]};let n=0;const s=_e(b(ge,{state:r,tick:n,verbose:t}),{exitOnCtrlC:!1,stdout:process.stderr}),i=H(()=>{n+=1,s.rerender(b(ge,{state:r,tick:n,verbose:t}))},"refresh");return{commandEnd({commandId:o,durationMs:a,error:c,output:l,patternId:u,status:d}){const p=r.patterns.get(u)?.commands.get(o);p&&(p.status=d,p.durationMs=a,p.output=l,p.error=c,i())},commandStart({commandId:o,patternId:a}){const c=r.patterns.get(a)?.commands.get(o);c&&(c.status="running",i())},error({error:o,message:a}){r.errorMessages.push({error:o,message:a}),i()},info({message:o}){r.infoMessages.push(o),i()},patternEnd({patternId:o,status:a}){const c=r.patterns.get(o);c&&(c.status=a,i())},patternStart({patternId:o}){const a=r.patterns.get(o);a&&(a.status="running",i())},start({patterns:o}){r.started=!0;for(const a of o){const c=new Map;for(const l of a.commands)c.set(l.id,{durationMs:0,id:l.id,status:"pending",title:l.title});r.patterns.set(a.id,{commands:c,id:a.id,status:"pending",title:a.title})}i()},async stop(){s.unmount(),await s.waitUntilExit()},warn({message:o}){r.warnMessages.push(o),i()}}},"createInkRenderer");var Rt=Object.defineProperty,J=f((e,t)=>Rt(e,"name",{value:t,configurable:!0}),"a$1");const me=J((e={})=>{const{quiet:t=!1,verbose:r=!1}=e,n=new Map,s=new Map,i=J(a=>{t||process.stderr.write(`${a}
14
+ `)},"print"),o=J(a=>{switch(a){case"failed":return Y(be);case"running":return K(">");case"skipped":return ne(B);case"success":return De(Te);default:return I(B)}},"iconFor");return{commandEnd({commandId:a,durationMs:c,error:l,output:u,status:d}){const p=s.get(a)??a,v=I(`(${c}ms)`);if(i(` ${o(d)} ${p} ${v}`),d==="failed"&&l&&i(I(l.message)),(d==="failed"||r)&&u&&u.trim().length>0)for(const h of u.split(/\r?\n/))i(` ${I(h)}`)},commandStart({commandId:a}){if(!r)return;const c=s.get(a)??a;i(` ${I("…")} ${c}`)},error({error:a,message:c}){t?process.stderr.write(`${Y(c)}
15
+ `):i(Y(c)),a?.stack&&(r||!t)&&process.stderr.write(`${I(a.stack)}
16
+ `)},info({message:a}){i(I(a))},patternEnd({patternId:a,status:c}){const l=n.get(a)??a;i(`${o(c)} ${l}`)},patternStart({patternId:a}){const c=n.get(a)??a;i(`${K(">")} ${c}`)},start({patterns:a}){if(a.length===0){i(I("No staged files matched any pattern."));return}const c=new Set(a.flatMap(l=>l.files)).size;i(`${K(">")} Running staged tasks on ${c} file${c===1?"":"s"} across ${a.length} pattern${a.length===1?"":"s"}`);for(const l of a){n.set(l.id,l.title);for(const u of l.commands)s.set(u.id,u.title)}},stop(){},warn({message:a}){i(ne(a))}}},"createPlainRenderer");var Ft=Object.defineProperty,Nt=f((e,t)=>Ft(e,"name",{value:t,configurable:!0}),"t");const Gt=Nt(async e=>{const{env:t}=process;if(e.debug===!0||e.quiet===!0||t.NODE_ENV==="test"||t.TERM==="dumb"||t.CI!==void 0||!process.stderr.isTTY)return me({quiet:e.quiet,verbose:e.verbose});try{return Pt({verbose:e.verbose})}catch{return me({quiet:e.quiet,verbose:e.verbose})}},"pickRenderer");var _t=Object.defineProperty,G=f((e,t)=>_t(e,"name",{value:t,configurable:!0}),"a");const Q=G(e=>typeof e=="object"&&e!==null&&typeof e.title=="string"&&typeof e.task=="function","isCustomTask"),Dt=G(async e=>{let t=0,r=0;const n=G(()=>(t+=1,`pattern-${t}`),"nextPatternId"),s=G(()=>(r+=1,`cmd-${r}`),"nextCommandId"),i=G(async(a,c,l)=>{if(typeof a=="string"){l.push({command:a,files:c,id:s(),source:"string",title:a});return}if(Array.isArray(a)){for(const u of a)await i(u,c,l);return}if(typeof a=="function"){const u=await a([...c]);if(typeof u=="string"){l.push({command:u,files:c,id:s(),source:"function",title:u});return}if(Array.isArray(u)){for(const d of u)if(typeof d=="string")l.push({command:d,files:c,id:s(),source:"function",title:d});else if(Q(d))l.push({files:c,id:s(),run:d.task,source:"custom",title:d.title});else throw new T("Task function returned an array with an unsupported entry — expected strings or { title, task }.");return}if(Q(u)){l.push({files:c,id:s(),run:u.task,source:"custom",title:u.title});return}throw new T("Task function returned an unsupported value — expected string, string[], or { title, task }.")}if(Q(a)){l.push({files:c,id:s(),run:a.task,source:"custom",title:a.title});return}throw new T("Unsupported task value — expected string, string[], function, or { title, task }.")},"expandTask"),o=[];for(const[a,c]of Object.entries(e.config)){const l=At(a,e.files,e.cwd,{caseInsensitive:e.caseInsensitive===!0});if(l.length===0)continue;const u=e.relative?l.map(p=>D(e.cwd,p)):l,d=[];await i(c,u,d),d.length!==0&&o.push({commands:d,files:u,id:n(),pattern:a,title:`${a} — ${l.length} file${l.length===1?"":"s"}`})}return o},"buildTaskGraph");var Ht=Object.defineProperty,z=f((e,t)=>Ht(e,"name",{value:t,configurable:!0}),"d$1");const Lt=z(e=>{const t=[];let r="",n=!1,s=!1;for(let i=0;i<e.length;i+=1){const o=e[i];if(o===void 0)break;if(o==="\\"&&!n&&i+1<e.length){const a=e[i+1];if(a!==void 0){s&&a!=='"'&&a!=="\\"&&(r+=o),r+=a,i+=1;continue}}if(o==='"'&&!n){s=!s;continue}if(o==="'"&&!s){n=!n;continue}if(!n&&!s&&/\s/.test(o)){r.length>0&&(t.push(r),r="");continue}r+=o}if(n||s)throw new T(`Unterminated ${n?"single":"double"} quote in command: ${e}`);return r.length>0&&t.push(r),t},"parseCommandString"),Ie=process.platform==="win32"?28e3:131072,Bt=z((e,t,r)=>{const n=[];let s=[],i=t;const o=r<=0?Ie:r;for(const a of e){const c=Buffer.byteLength(a)+1;s.length>0&&i+c>o&&(n.push(s),s=[],i=t),s.push(a),i+=c}return s.length>0&&n.push(s),n},"chunkFiles"),Ut=z(async(e,t,r)=>{const n=Lt(e);if(n.length===0)throw new M(e,"Empty command for staged task.");const[s,...i]=n;if(s===void 0)throw new M(e,"Empty command for staged task.");const o=Buffer.byteLength(s)+i.reduce((u,d)=>u+Buffer.byteLength(d)+1,0),a=Bt(t,o,r.maxArgLength??Ie),c=Date.now(),l=[];for(const u of a){if(r.signal?.aborted===!0)throw new M(e,"Task aborted by earlier failure.");const d=await ye(s,[...i,...u],{cancelSignal:r.signal,cwd:r.cwd,env:qt(r.env),killSignal:r.killSignal??"SIGTERM",reject:!1,stderr:"pipe",stdout:"pipe"}),p=typeof d.stdout=="string"?d.stdout:"",v=typeof d.stderr=="string"?d.stderr:"",h=[p,v].filter(m=>m.length>0).join(`
17
+ `);if(h.length>0&&l.push(h),d.isCanceled||d.isTerminated||typeof d.exitCode!="number"){const m=d.isCanceled?"Task aborted by earlier failure.":d.isTerminated?`Task killed by signal ${d.signal??"(unknown)"}.`:h.trim()||"Task exited without a numeric status code.";throw new M(e,m)}if(d.exitCode!==0)throw new M(e,h.trim()||`Exit code ${d.exitCode} from ${s}`)}return{durationMs:Date.now()-c,output:l.join(`
18
+ `)}},"execCommand"),qt=z(e=>{const t={...process.env};return process.stderr.isTTY&&t.FORCE_COLOR===void 0&&t.NO_COLOR===void 0&&(t.FORCE_COLOR="1"),e?{...t,...e}:t},"buildTaskEnv");var Vt=Object.defineProperty,j=f((e,t)=>Vt(e,"name",{value:t,configurable:!0}),"o");const zt=j(async(e,t,r)=>{const n=Kt(r.concurrent,e.length),s=[],i=new AbortController;let o=!1,a=0;const c=j(()=>{o||(o=!0,r.continueOnError||i.abort())},"cancel");r.externalSignal&&(r.externalSignal.aborted?c():r.externalSignal.addEventListener("abort",()=>{c()},{once:!0}));const l=j(h=>{for(const m of h.commands)t.commandEnd({commandId:m.id,durationMs:0,patternId:h.id,status:"skipped"})},"emitSkippedCommands"),u=j(async h=>{if(o){l(h),t.patternEnd({patternId:h.id,status:"skipped"});return}t.patternStart({patternId:h.id});let m="success";for(const y of h.commands){if(o){t.commandEnd({commandId:y.id,durationMs:0,patternId:h.id,status:"skipped"}),m=m==="success"?"skipped":m;continue}t.commandStart({commandId:y.id,patternId:h.id});const k=await Wt(y,r,i.signal),S=k.status==="failed"&&i.signal.aborted?{...k,status:"skipped"}:k;if(t.commandEnd({commandId:y.id,durationMs:S.durationMs,error:S.error,output:S.output,patternId:h.id,status:S.status}),S.status==="failed"){s.push(y.title),m="failed",c();break}if(S.status==="skipped"){m=m==="success"?"skipped":m;break}}t.patternEnd({patternId:h.id,status:m})},"runOne"),d=j(async()=>{for(;a<e.length;){const h=e[a];a+=1,h&&await u(h)}},"pickNext"),p=[];for(let h=0;h<Math.min(n,e.length);h+=1)p.push(d());await Promise.all(p);const v=r.externalSignal?.aborted===!0;return{failedCommands:s,success:s.length===0&&!v}},"runTasks"),Wt=j(async(e,t,r)=>{const n=Date.now();try{if(e.source==="custom"&&e.run)return await e.run([...e.files]),{durationMs:Date.now()-n,status:"success"};if(e.command){const s=await Ut(e.command,e.files,{cwd:t.cwd,killSignal:t.killSignal,maxArgLength:t.maxArgLength,signal:r});return{durationMs:s.durationMs,output:t.verbose?s.output:void 0,status:"success"}}return{durationMs:Date.now()-n,error:new M(e.title,"Command has no invocation target."),status:"failed"}}catch(s){const i=s instanceof Error?s:new Error(String(s));return{durationMs:Date.now()-n,error:i,output:i instanceof M?i.message:void 0,status:"failed"}}},"runCommand"),Kt=j((e,t)=>{if(e===!1)return 1;if(e===!0){const n=Math.max(1,typeof ae=="function"?ae():4);return Math.min(Math.max(1,t),n)}const r=Math.floor(e);return r>0?r:1},"concurrencyLimit");var Yt=Object.defineProperty,Z=f((e,t)=>Yt(e,"name",{value:t,configurable:!0}),"u");const Jt=!0,Qt=Z(e=>{try{return!je(e)}catch{return!1}},"detectCaseInsensitive"),Xt=Z(async(e={})=>{const t=e.cwd??process.cwd(),r=await Gt(e),n=await rt(e);typeof n!="function"&&ce(n);const s=new It({...e,cwd:t}),i=new AbortController;let o=!1;const a=Z(d=>{if(o){process.removeListener("SIGINT",a),process.removeListener("SIGTERM",a),process.kill(process.pid,d);return}o=!0,r.warn({message:`Received ${d} — cancelling staged tasks and restoring state. Press Ctrl+C again to abort.`}),i.abort()},"onInterrupt");process.on("SIGINT",a),process.on("SIGTERM",a);let c={failedCommands:[],ranTasks:!1,success:!0},l=!1,u=!1;try{await s.prepare(),l=!0;for(const k of s.warnings)r.warn({message:k});if(s.stagedFiles.length===0)return e.allowEmpty!==!0&&r.info({message:"No staged files found."}),u=!0,{failedCommands:[],ranTasks:!1,success:!0};const d=Qt(t),p=Mt(s.stagedFiles,e.ignore,t,{caseInsensitive:d});if(p.length===0&&s.stagedFiles.length>0)return r.info({message:"Every staged file was excluded by the `ignore` list."}),u=!0,{failedCommands:[],ranTasks:!1,success:!0};const v=typeof n=="function"?ce(await n([...p])):n,h=await Dt({caseInsensitive:d,config:v,cwd:t,files:p,relative:e.relative});if(r.start({patterns:h}),h.length===0)return r.info({message:"No staged files matched any pattern."}),u=!0,{failedCommands:[],ranTasks:!1,success:!0};const{failedCommands:m,success:y}=await zt(h,r,{concurrent:e.concurrent??Jt,continueOnError:e.continueOnError===!0,cwd:t,externalSignal:i.signal,killSignal:e.killSignal,maxArgLength:e.maxArgLength,verbose:e.verbose});if(c={failedCommands:m,ranTasks:!0,success:y},y){if(e.diff===void 0&&(await s.applyModifications({autoStage:e.autoStage===!0}),e.failOnChanges===!0&&s.indexTreeChanged()&&(r.warn({message:"Tasks modified staged content — failing because --fail-on-changes is set."}),c={failedCommands:[...m],ranTasks:!0,success:!1}),e.allowEmpty!==!0&&s.postTaskIndexMatchesHead()))throw new qe("All staged changes were reverted by tasks. Re-stage changes or rerun with --allow-empty.")}else if(e.revert===!0)r.info({message:"Reverting working tree from backup stash."}),await s.revert();else{const k=s.recoveryHint();k&&r.warn({message:k})}return await s.restoreUnstagedChanges(),u=c.success,c}catch(d){const p=d instanceof Error?d.message:String(d),v=d instanceof Error?d:new Error(p);if(r.error({error:v,message:p}),d instanceof O)return{failedCommands:c.failedCommands,ranTasks:c.ranTasks,success:!1};throw d}finally{if(process.removeListener("SIGINT",a),process.removeListener("SIGTERM",a),l)try{await s.cleanup(u)}catch(d){r.error({error:d,message:"Cleanup failed."})}await r.stop()}},"runStaged");var Zt=Object.defineProperty,er=f((e,t)=>Zt(e,"name",{value:t,configurable:!0}),"n");const tr="VIS_STAGED_CONCURRENT",we=er(e=>{const t=e.trim();if(t==="true"||t==="")return!0;if(t==="false")return!1;const r=Number(t);return Number.isNaN(r)?!0:r},"parseConcurrent");var rr=Object.defineProperty,L=f((e,t)=>rr(e,"name",{value:t,configurable:!0}),"d");const sr=L((e,t)=>{const r={};t!==void 0&&(r.config=t);const n=L(w=>e[w]===void 0?void 0:!!e[w],"readBool"),s=L(w=>{const W=e[w];return typeof W=="string"&&W.length>0?W:void 0},"readString"),i=n("allow-empty");i!==void 0&&(r.allowEmpty=i);const o=n("auto-stage");o!==void 0&&(r.autoStage=o);const a=n("continue-on-error");a!==void 0&&(r.continueOnError=a);const c=n("debug");c!==void 0&&(r.debug=c);const l=n("fail-on-changes");l!==void 0&&(r.failOnChanges=l);const u=n("hide-partially-staged");u!==void 0&&(r.hidePartiallyStaged=u);const d=n("hide-unstaged");d!==void 0&&(r.hideUnstaged=d);const p=n("quiet");p!==void 0&&(r.quiet=p);const v=n("relative");v!==void 0&&(r.relative=v);const h=n("revert");h!==void 0&&(r.revert=h);const m=n("stash");m!==void 0&&(r.stash=m);const y=n("verbose");y!==void 0&&(r.verbose=y);const k=s("cwd");k!==void 0&&(r.cwd=k);const S=s("diff");S!==void 0&&(r.diff=S);const re=s("diff-filter");if(re!==void 0&&(r.diffFilter=re),n("force-kill")===!0&&(r.killSignal="SIGKILL"),e.concurrent===void 0){const w=process.env[tr];w!==void 0&&(r.concurrent=we(w.trim()))}else{const{concurrent:w}=e;r.concurrent=we(typeof w=="string"?w:typeof w=="number"||typeof w=="boolean"?String(w):"")}return r},"buildRunOptions"),pr=L(async({options:e,visConfig:t})=>{const r=(t??{}).staged;if(!r)throw new Error(`No "staged" config found in vis.config.ts. Add one:
19
+
20
+ // vis.config.ts
21
+ import { defineConfig } from "@visulima/vis/config";
22
+
23
+ export default defineConfig({
24
+ staged: { '*': 'vis check --fix' },
623
25
  });
624
- };
625
-
626
- const colorForStatus = (status) => {
627
- switch (status) {
628
- case "failed": {
629
- return "red";
630
- }
631
- case "running": {
632
- return "cyan";
633
- }
634
- case "skipped": {
635
- return "yellow";
636
- }
637
- case "success": {
638
- return "green";
639
- }
640
- default: {
641
- return "gray";
642
- }
643
- }
644
- };
645
- const iconForStatus = (status) => {
646
- if (status === "running") {
647
- return jsx(Spinner, { type: "dots" });
648
- }
649
- const glyph = status === "failed" ? CROSS : status === "skipped" ? DASH : status === "success" ? TICK : DASH;
650
- return jsx(Text, { color: colorForStatus(status), children: glyph });
651
- };
652
- const App = ({ state, tick: _tick, verbose }) => jsxs(Box, { flexDirection: "column", children: [
653
- [...state.patterns.values()].map((pattern) => jsxs(Box, { flexDirection: "column", children: [
654
- jsxs(Box, { children: [
655
- iconForStatus(pattern.status),
656
- jsxs(Text, { children: [
657
- " ",
658
- pattern.title
659
- ] })
660
- ] }),
661
- [...pattern.commands.values()].map((command) => jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [
662
- jsxs(Box, { children: [
663
- iconForStatus(command.status),
664
- jsxs(Text, { children: [
665
- " ",
666
- command.title,
667
- " "
668
- ] }),
669
- command.status !== "pending" && command.status !== "running" ? jsxs(Text, { color: "gray", children: [
670
- "(",
671
- command.durationMs,
672
- "ms)"
673
- ] }) : null
674
- ] }),
675
- verbose && command.output ? jsx(Box, { flexDirection: "column", marginLeft: 2, children: command.output.split(/\r?\n/).slice(0, 20).map((line, index) => jsx(Text, { color: "gray", children: line }, `${command.id}-line-${index}`)) }) : null,
676
- command.status === "failed" && command.error ? jsx(Box, { marginLeft: 2, children: jsx(Text, { color: "red", children: command.error.message }) }) : null
677
- ] }, command.id))
678
- ] }, pattern.id)),
679
- state.infoMessages.map((message, index) => jsx(Text, { color: "gray", children: message }, `info-${index}`)),
680
- state.warnMessages.map((message, index) => jsx(Text, { color: "yellow", children: message }, `warn-${index}`)),
681
- state.errorMessages.map(({ message }, index) => jsx(Text, { color: "red", children: message }, `err-${index}`))
682
- ] });
683
- const createInkRenderer = (options = {}) => {
684
- const verbose = options.verbose === true;
685
- const state = {
686
- errorMessages: [],
687
- infoMessages: [],
688
- patterns: /* @__PURE__ */ new Map(),
689
- started: false,
690
- warnMessages: []
691
- };
692
- let tick = 0;
693
- const instance = render(jsx(App, { state, tick, verbose }), { exitOnCtrlC: false, stdout: process.stderr });
694
- const refresh = () => {
695
- tick += 1;
696
- instance.rerender(jsx(App, { state, tick, verbose }));
697
- };
698
- return {
699
- commandEnd({ commandId, durationMs, error, output, patternId, status }) {
700
- const command = state.patterns.get(patternId)?.commands.get(commandId);
701
- if (command) {
702
- command.status = status;
703
- command.durationMs = durationMs;
704
- command.output = output;
705
- command.error = error;
706
- refresh();
707
- }
708
- },
709
- commandStart({ commandId, patternId }) {
710
- const command = state.patterns.get(patternId)?.commands.get(commandId);
711
- if (command) {
712
- command.status = "running";
713
- refresh();
714
- }
715
- },
716
- error({ error, message }) {
717
- state.errorMessages.push({ error, message });
718
- refresh();
719
- },
720
- info({ message }) {
721
- state.infoMessages.push(message);
722
- refresh();
723
- },
724
- patternEnd({ patternId, status }) {
725
- const pattern = state.patterns.get(patternId);
726
- if (pattern) {
727
- pattern.status = status;
728
- refresh();
729
- }
730
- },
731
- patternStart({ patternId }) {
732
- const pattern = state.patterns.get(patternId);
733
- if (pattern) {
734
- pattern.status = "running";
735
- refresh();
736
- }
737
- },
738
- start({ patterns }) {
739
- state.started = true;
740
- for (const pattern of patterns) {
741
- const commandMap = /* @__PURE__ */ new Map();
742
- for (const command of pattern.commands) {
743
- commandMap.set(command.id, {
744
- durationMs: 0,
745
- id: command.id,
746
- status: "pending",
747
- title: command.title
748
- });
749
- }
750
- state.patterns.set(pattern.id, {
751
- commands: commandMap,
752
- id: pattern.id,
753
- status: "pending",
754
- title: pattern.title
755
- });
756
- }
757
- refresh();
758
- },
759
- async stop() {
760
- instance.unmount();
761
- await instance.waitUntilExit();
762
- },
763
- warn({ message }) {
764
- state.warnMessages.push(message);
765
- refresh();
766
- }
767
- };
768
- };
769
-
770
- const createPlainRenderer = (options = {}) => {
771
- const { quiet = false, verbose = false } = options;
772
- const patternTitles = /* @__PURE__ */ new Map();
773
- const commandTitles = /* @__PURE__ */ new Map();
774
- const print = (message) => {
775
- if (!quiet) {
776
- process.stderr.write(`${message}
777
- `);
778
- }
779
- };
780
- const iconFor = (status) => {
781
- switch (status) {
782
- case "failed": {
783
- return red(CROSS);
784
- }
785
- case "running": {
786
- return cyan(">");
787
- }
788
- case "skipped": {
789
- return yellow(DASH);
790
- }
791
- case "success": {
792
- return green(TICK);
793
- }
794
- default: {
795
- return dim(DASH);
796
- }
797
- }
798
- };
799
- return {
800
- commandEnd({ commandId, durationMs, error, output, status }) {
801
- const title = commandTitles.get(commandId) ?? commandId;
802
- const duration = dim(`(${durationMs}ms)`);
803
- print(` ${iconFor(status)} ${title} ${duration}`);
804
- if (status === "failed" && error) {
805
- print(dim(error.message));
806
- }
807
- if ((status === "failed" || verbose) && output && output.trim().length > 0) {
808
- for (const line of output.split(/\r?\n/)) {
809
- print(` ${dim(line)}`);
810
- }
811
- }
812
- },
813
- commandStart({ commandId }) {
814
- if (!verbose) {
815
- return;
816
- }
817
- const title = commandTitles.get(commandId) ?? commandId;
818
- print(` ${dim("…")} ${title}`);
819
- },
820
- error({ error, message }) {
821
- if (quiet) {
822
- process.stderr.write(`${red(message)}
823
- `);
824
- } else {
825
- print(red(message));
826
- }
827
- if (error?.stack && (verbose || !quiet)) {
828
- process.stderr.write(`${dim(error.stack)}
829
- `);
830
- }
831
- },
832
- info({ message }) {
833
- print(dim(message));
834
- },
835
- patternEnd({ patternId, status }) {
836
- const title = patternTitles.get(patternId) ?? patternId;
837
- print(`${iconFor(status)} ${title}`);
838
- },
839
- patternStart({ patternId }) {
840
- const title = patternTitles.get(patternId) ?? patternId;
841
- print(`${cyan(">")} ${title}`);
842
- },
843
- start({ patterns }) {
844
- if (patterns.length === 0) {
845
- print(dim("No staged files matched any pattern."));
846
- return;
847
- }
848
- const fileCount = new Set(patterns.flatMap((p) => p.files)).size;
849
- print(
850
- `${cyan(">")} Running staged tasks on ${fileCount} file${fileCount === 1 ? "" : "s"} across ${patterns.length} pattern${patterns.length === 1 ? "" : "s"}`
851
- );
852
- for (const pattern of patterns) {
853
- patternTitles.set(pattern.id, pattern.title);
854
- for (const cmd of pattern.commands) {
855
- commandTitles.set(cmd.id, cmd.title);
856
- }
857
- }
858
- },
859
- stop() {
860
- },
861
- warn({ message }) {
862
- print(yellow(message));
863
- }
864
- };
865
- };
866
-
867
- const pickRenderer = async (options) => {
868
- const { env } = process;
869
- const forcePlain = options.debug === true || options.quiet === true || env["NODE_ENV"] === "test" || env["TERM"] === "dumb" || env["CI"] !== void 0 || !process.stderr.isTTY;
870
- if (forcePlain) {
871
- return createPlainRenderer({ quiet: options.quiet, verbose: options.verbose });
872
- }
873
- try {
874
- return createInkRenderer({ verbose: options.verbose });
875
- } catch {
876
- return createPlainRenderer({ quiet: options.quiet, verbose: options.verbose });
877
- }
878
- };
879
-
880
- const isCustomTask = (value) => typeof value === "object" && value !== null && typeof value.title === "string" && typeof value.task === "function";
881
- const buildTaskGraph = async (options) => {
882
- let patternCounter = 0;
883
- let commandCounter = 0;
884
- const nextPatternId = () => {
885
- patternCounter += 1;
886
- return `pattern-${patternCounter}`;
887
- };
888
- const nextCommandId = () => {
889
- commandCounter += 1;
890
- return `cmd-${commandCounter}`;
891
- };
892
- const expandTask = async (value, files, commands) => {
893
- if (typeof value === "string") {
894
- commands.push({
895
- command: value,
896
- files,
897
- id: nextCommandId(),
898
- source: "string",
899
- title: value
900
- });
901
- return;
902
- }
903
- if (Array.isArray(value)) {
904
- for (const item of value) {
905
- await expandTask(item, files, commands);
906
- }
907
- return;
908
- }
909
- if (typeof value === "function") {
910
- const generated = await value([...files]);
911
- if (typeof generated === "string") {
912
- commands.push({
913
- command: generated,
914
- files,
915
- id: nextCommandId(),
916
- source: "function",
917
- title: generated
918
- });
919
- return;
920
- }
921
- if (Array.isArray(generated)) {
922
- for (const item of generated) {
923
- if (typeof item === "string") {
924
- commands.push({
925
- command: item,
926
- files,
927
- id: nextCommandId(),
928
- source: "function",
929
- title: item
930
- });
931
- } else if (isCustomTask(item)) {
932
- commands.push({
933
- files,
934
- id: nextCommandId(),
935
- run: item.task,
936
- source: "custom",
937
- title: item.title
938
- });
939
- } else {
940
- throw new ConfigError("Task function returned an array with an unsupported entry — expected strings or { title, task }.");
941
- }
942
- }
943
- return;
944
- }
945
- if (isCustomTask(generated)) {
946
- commands.push({
947
- files,
948
- id: nextCommandId(),
949
- run: generated.task,
950
- source: "custom",
951
- title: generated.title
952
- });
953
- return;
954
- }
955
- throw new ConfigError("Task function returned an unsupported value — expected string, string[], or { title, task }.");
956
- }
957
- if (isCustomTask(value)) {
958
- commands.push({
959
- files,
960
- id: nextCommandId(),
961
- run: value.task,
962
- source: "custom",
963
- title: value.title
964
- });
965
- return;
966
- }
967
- throw new ConfigError("Unsupported task value — expected string, string[], function, or { title, task }.");
968
- };
969
- const patterns = [];
970
- for (const [pattern, value] of Object.entries(options.config)) {
971
- const matched = matchFiles(pattern, options.files, options.cwd, { caseInsensitive: options.caseInsensitive === true });
972
- if (matched.length === 0) {
973
- continue;
974
- }
975
- const view = options.relative ? matched.map((file) => relative(options.cwd, file)) : matched;
976
- const commands = [];
977
- await expandTask(value, view, commands);
978
- if (commands.length === 0) {
979
- continue;
980
- }
981
- patterns.push({
982
- commands,
983
- files: view,
984
- id: nextPatternId(),
985
- pattern,
986
- title: `${pattern} — ${matched.length} file${matched.length === 1 ? "" : "s"}`
987
- });
988
- }
989
- return patterns;
990
- };
991
-
992
- const parseCommandString = (input) => {
993
- const tokens = [];
994
- let buffer = "";
995
- let inSingle = false;
996
- let inDouble = false;
997
- for (let i = 0; i < input.length; i += 1) {
998
- const character = input[i];
999
- if (character === void 0) {
1000
- break;
1001
- }
1002
- if (character === "\\" && !inSingle && i + 1 < input.length) {
1003
- const next = input[i + 1];
1004
- if (next !== void 0) {
1005
- if (inDouble && next !== '"' && next !== "\\") {
1006
- buffer += character;
1007
- buffer += next;
1008
- } else {
1009
- buffer += next;
1010
- }
1011
- i += 1;
1012
- continue;
1013
- }
1014
- }
1015
- if (character === '"' && !inSingle) {
1016
- inDouble = !inDouble;
1017
- continue;
1018
- }
1019
- if (character === "'" && !inDouble) {
1020
- inSingle = !inSingle;
1021
- continue;
1022
- }
1023
- if (!inSingle && !inDouble && /\s/.test(character)) {
1024
- if (buffer.length > 0) {
1025
- tokens.push(buffer);
1026
- buffer = "";
1027
- }
1028
- continue;
1029
- }
1030
- buffer += character;
1031
- }
1032
- if (inSingle || inDouble) {
1033
- throw new ConfigError(`Unterminated ${inSingle ? "single" : "double"} quote in command: ${input}`);
1034
- }
1035
- if (buffer.length > 0) {
1036
- tokens.push(buffer);
1037
- }
1038
- return tokens;
1039
- };
1040
- const DEFAULT_MAX_ARG_LENGTH = process.platform === "win32" ? 28e3 : 131072;
1041
- const chunkFiles = (files, fixedPrefixLength, maxArgLength) => {
1042
- const chunks = [];
1043
- let current = [];
1044
- let currentLength = fixedPrefixLength;
1045
- const limit = maxArgLength <= 0 ? DEFAULT_MAX_ARG_LENGTH : maxArgLength;
1046
- for (const file of files) {
1047
- const fileLength = Buffer.byteLength(file) + 1;
1048
- if (current.length > 0 && currentLength + fileLength > limit) {
1049
- chunks.push(current);
1050
- current = [];
1051
- currentLength = fixedPrefixLength;
1052
- }
1053
- current.push(file);
1054
- currentLength += fileLength;
1055
- }
1056
- if (current.length > 0) {
1057
- chunks.push(current);
1058
- }
1059
- return chunks;
1060
- };
1061
- const execCommand = async (command, files, options) => {
1062
- const argv = parseCommandString(command);
1063
- if (argv.length === 0) {
1064
- throw new TaskError(command, `Empty command for staged task.`);
1065
- }
1066
- const [program, ...baseArgs] = argv;
1067
- if (program === void 0) {
1068
- throw new TaskError(command, `Empty command for staged task.`);
1069
- }
1070
- const prefixLength = Buffer.byteLength(program) + baseArgs.reduce((total, argument) => total + Buffer.byteLength(argument) + 1, 0);
1071
- const chunks = chunkFiles(files, prefixLength, options.maxArgLength ?? DEFAULT_MAX_ARG_LENGTH);
1072
- const started = Date.now();
1073
- const outputs = [];
1074
- for (const chunk of chunks) {
1075
- if (options.signal?.aborted === true) {
1076
- throw new TaskError(command, "Task aborted by earlier failure.");
1077
- }
1078
- const result = await execa(program, [...baseArgs, ...chunk], {
1079
- cancelSignal: options.signal,
1080
- cwd: options.cwd,
1081
- // Pipe stdout/stderr so we can capture task output for the renderer, but forward `FORCE_COLOR=1`
1082
- // unless the user has already set it — tools like eslint / prettier check isTTY, which is false
1083
- // when piped, and drop ANSI styling without this hint. Matches lint-staged / nano-staged behavior.
1084
- env: buildTaskEnv(options.env),
1085
- // Signal delivered to the child when `cancelSignal` aborts. Defaults to SIGTERM for graceful
1086
- // shutdown; callers can set SIGKILL via `killSignal` for fast-fail runs.
1087
- killSignal: options.killSignal ?? "SIGTERM",
1088
- reject: false,
1089
- stderr: "pipe",
1090
- stdout: "pipe"
1091
- });
1092
- const stdout = typeof result.stdout === "string" ? result.stdout : "";
1093
- const stderr = typeof result.stderr === "string" ? result.stderr : "";
1094
- const merged = [stdout, stderr].filter((s) => s.length > 0).join("\n");
1095
- if (merged.length > 0) {
1096
- outputs.push(merged);
1097
- }
1098
- if (result.isCanceled || result.isTerminated || typeof result.exitCode !== "number") {
1099
- const reason = result.isCanceled ? "Task aborted by earlier failure." : result.isTerminated ? `Task killed by signal ${result.signal ?? "(unknown)"}.` : merged.trim() || `Task exited without a numeric status code.`;
1100
- throw new TaskError(command, reason);
1101
- }
1102
- if (result.exitCode !== 0) {
1103
- throw new TaskError(command, merged.trim() || `Exit code ${result.exitCode} from ${program}`);
1104
- }
1105
- }
1106
- return { durationMs: Date.now() - started, output: outputs.join("\n") };
1107
- };
1108
- const buildTaskEnv = (overrides) => {
1109
- const base = { ...process.env };
1110
- if (process.stderr.isTTY && base["FORCE_COLOR"] === void 0 && base["NO_COLOR"] === void 0) {
1111
- base["FORCE_COLOR"] = "1";
1112
- }
1113
- return overrides ? { ...base, ...overrides } : base;
1114
- };
1115
-
1116
- const runTasks = async (patterns, renderer, options) => {
1117
- const limit = concurrencyLimit(options.concurrent, patterns.length);
1118
- const failedCommands = [];
1119
- const abortController = new AbortController();
1120
- let cancelled = false;
1121
- let index = 0;
1122
- const cancel = () => {
1123
- if (cancelled) {
1124
- return;
1125
- }
1126
- cancelled = true;
1127
- if (!options.continueOnError) {
1128
- abortController.abort();
1129
- }
1130
- };
1131
- if (options.externalSignal) {
1132
- if (options.externalSignal.aborted) {
1133
- cancel();
1134
- } else {
1135
- options.externalSignal.addEventListener(
1136
- "abort",
1137
- () => {
1138
- cancel();
1139
- },
1140
- { once: true }
1141
- );
1142
- }
1143
- }
1144
- const emitSkippedCommands = (pattern) => {
1145
- for (const command of pattern.commands) {
1146
- renderer.commandEnd({
1147
- commandId: command.id,
1148
- durationMs: 0,
1149
- patternId: pattern.id,
1150
- status: "skipped"
1151
- });
1152
- }
1153
- };
1154
- const runOne = async (pattern) => {
1155
- if (cancelled) {
1156
- emitSkippedCommands(pattern);
1157
- renderer.patternEnd({ patternId: pattern.id, status: "skipped" });
1158
- return;
1159
- }
1160
- renderer.patternStart({ patternId: pattern.id });
1161
- let patternStatus = "success";
1162
- for (const command of pattern.commands) {
1163
- if (cancelled) {
1164
- renderer.commandEnd({
1165
- commandId: command.id,
1166
- durationMs: 0,
1167
- patternId: pattern.id,
1168
- status: "skipped"
1169
- });
1170
- patternStatus = patternStatus === "success" ? "skipped" : patternStatus;
1171
- continue;
1172
- }
1173
- renderer.commandStart({ commandId: command.id, patternId: pattern.id });
1174
- const outcome = await runCommand(command, options, abortController.signal);
1175
- const classified = outcome.status === "failed" && abortController.signal.aborted ? { ...outcome, status: "skipped" } : outcome;
1176
- renderer.commandEnd({
1177
- commandId: command.id,
1178
- durationMs: classified.durationMs,
1179
- error: classified.error,
1180
- output: classified.output,
1181
- patternId: pattern.id,
1182
- status: classified.status
1183
- });
1184
- if (classified.status === "failed") {
1185
- failedCommands.push(command.title);
1186
- patternStatus = "failed";
1187
- cancel();
1188
- break;
1189
- }
1190
- if (classified.status === "skipped") {
1191
- patternStatus = patternStatus === "success" ? "skipped" : patternStatus;
1192
- break;
1193
- }
1194
- }
1195
- renderer.patternEnd({ patternId: pattern.id, status: patternStatus });
1196
- };
1197
- const pickNext = async () => {
1198
- while (index < patterns.length) {
1199
- const slot = patterns[index];
1200
- index += 1;
1201
- if (slot) {
1202
- await runOne(slot);
1203
- }
1204
- }
1205
- };
1206
- const workers = [];
1207
- for (let i = 0; i < Math.min(limit, patterns.length); i += 1) {
1208
- workers.push(pickNext());
1209
- }
1210
- await Promise.all(workers);
1211
- const externallyCancelled = options.externalSignal?.aborted === true;
1212
- return { failedCommands, success: failedCommands.length === 0 && !externallyCancelled };
1213
- };
1214
- const runCommand = async (command, options, signal) => {
1215
- const started = Date.now();
1216
- try {
1217
- if (command.source === "custom" && command.run) {
1218
- await command.run([...command.files]);
1219
- return { durationMs: Date.now() - started, status: "success" };
1220
- }
1221
- if (command.command) {
1222
- const result = await execCommand(command.command, command.files, {
1223
- cwd: options.cwd,
1224
- killSignal: options.killSignal,
1225
- maxArgLength: options.maxArgLength,
1226
- signal
1227
- });
1228
- return { durationMs: result.durationMs, output: options.verbose ? result.output : void 0, status: "success" };
1229
- }
1230
- return {
1231
- durationMs: Date.now() - started,
1232
- error: new TaskError(command.title, `Command has no invocation target.`),
1233
- status: "failed"
1234
- };
1235
- } catch (error_) {
1236
- const error = error_ instanceof Error ? error_ : new Error(String(error_));
1237
- return {
1238
- durationMs: Date.now() - started,
1239
- error,
1240
- output: error instanceof TaskError ? error.message : void 0,
1241
- status: "failed"
1242
- };
1243
- }
1244
- };
1245
- const concurrencyLimit = (concurrent, patternCount) => {
1246
- if (concurrent === false) {
1247
- return 1;
1248
- }
1249
- if (concurrent === true) {
1250
- const reasonable = Math.max(1, typeof availableParallelism === "function" ? availableParallelism() : 4);
1251
- return Math.min(Math.max(1, patternCount), reasonable);
1252
- }
1253
- const value = Math.floor(concurrent);
1254
- return value > 0 ? value : 1;
1255
- };
1256
-
1257
- const DEFAULT_CONCURRENT = true;
1258
- const detectCaseInsensitive = (cwd) => {
1259
- try {
1260
- return !isFsCaseSensitive(cwd);
1261
- } catch {
1262
- return false;
1263
- }
1264
- };
1265
- const runStaged = async (options = {}) => {
1266
- const cwd = options.cwd ?? process.cwd();
1267
- const renderer = await pickRenderer(options);
1268
- const resolvedConfig = await resolveConfig(options);
1269
- if (typeof resolvedConfig !== "function") {
1270
- validateConfig(resolvedConfig);
1271
- }
1272
- const workflow = new GitWorkflow({ ...options, cwd });
1273
- const externalAborter = new AbortController();
1274
- let interrupted = false;
1275
- const onInterrupt = (signal) => {
1276
- if (interrupted) {
1277
- process.removeListener("SIGINT", onInterrupt);
1278
- process.removeListener("SIGTERM", onInterrupt);
1279
- process.kill(process.pid, signal);
1280
- return;
1281
- }
1282
- interrupted = true;
1283
- renderer.warn({ message: `Received ${signal} — cancelling staged tasks and restoring state. Press Ctrl+C again to abort.` });
1284
- externalAborter.abort();
1285
- };
1286
- process.on("SIGINT", onInterrupt);
1287
- process.on("SIGTERM", onInterrupt);
1288
- let runResult = { failedCommands: [], ranTasks: false, success: true };
1289
- let workflowPrepared = false;
1290
- let finalSuccess = false;
1291
- try {
1292
- await workflow.prepare();
1293
- workflowPrepared = true;
1294
- for (const message of workflow.warnings) {
1295
- renderer.warn({ message });
1296
- }
1297
- if (workflow.stagedFiles.length === 0) {
1298
- if (options.allowEmpty !== true) {
1299
- renderer.info({ message: "No staged files found." });
1300
- }
1301
- finalSuccess = true;
1302
- return { failedCommands: [], ranTasks: false, success: true };
1303
- }
1304
- const caseInsensitive = detectCaseInsensitive(cwd);
1305
- const candidateFiles = applyIgnore(workflow.stagedFiles, options.ignore, cwd, { caseInsensitive });
1306
- if (candidateFiles.length === 0 && workflow.stagedFiles.length > 0) {
1307
- renderer.info({ message: "Every staged file was excluded by the `ignore` list." });
1308
- finalSuccess = true;
1309
- return { failedCommands: [], ranTasks: false, success: true };
1310
- }
1311
- const staticConfig = typeof resolvedConfig === "function" ? validateConfig(await resolvedConfig([...candidateFiles])) : resolvedConfig;
1312
- const patterns = await buildTaskGraph({
1313
- caseInsensitive,
1314
- config: staticConfig,
1315
- cwd,
1316
- files: candidateFiles,
1317
- relative: options.relative
1318
- });
1319
- renderer.start({ patterns });
1320
- if (patterns.length === 0) {
1321
- renderer.info({ message: "No staged files matched any pattern." });
1322
- finalSuccess = true;
1323
- return { failedCommands: [], ranTasks: false, success: true };
1324
- }
1325
- const { failedCommands, success } = await runTasks(patterns, renderer, {
1326
- concurrent: options.concurrent ?? DEFAULT_CONCURRENT,
1327
- continueOnError: options.continueOnError === true,
1328
- cwd,
1329
- externalSignal: externalAborter.signal,
1330
- killSignal: options.killSignal,
1331
- maxArgLength: options.maxArgLength,
1332
- verbose: options.verbose
1333
- });
1334
- runResult = { failedCommands, ranTasks: true, success };
1335
- if (success) {
1336
- if (options.diff === void 0) {
1337
- await workflow.applyModifications({ autoStage: options.autoStage === true });
1338
- if (options.failOnChanges === true && workflow.indexTreeChanged()) {
1339
- renderer.warn({ message: "Tasks modified staged content — failing because --fail-on-changes is set." });
1340
- runResult = { failedCommands: [...failedCommands], ranTasks: true, success: false };
1341
- }
1342
- if (options.allowEmpty !== true && workflow.postTaskIndexMatchesHead()) {
1343
- throw new ApplyEmptyCommitError("All staged changes were reverted by tasks. Re-stage changes or rerun with --allow-empty.");
1344
- }
1345
- }
1346
- } else if (options.revert === true) {
1347
- renderer.info({ message: "Reverting working tree from backup stash." });
1348
- await workflow.revert();
1349
- } else {
1350
- const hint = workflow.recoveryHint();
1351
- if (hint) {
1352
- renderer.warn({ message: hint });
1353
- }
1354
- }
1355
- await workflow.restoreUnstagedChanges();
1356
- finalSuccess = runResult.success;
1357
- return runResult;
1358
- } catch (error_) {
1359
- const message = error_ instanceof Error ? error_.message : String(error_);
1360
- const error = error_ instanceof Error ? error_ : new Error(message);
1361
- renderer.error({ error, message });
1362
- if (error_ instanceof StagedError) {
1363
- return { failedCommands: runResult.failedCommands, ranTasks: runResult.ranTasks, success: false };
1364
- }
1365
- throw error_;
1366
- } finally {
1367
- process.removeListener("SIGINT", onInterrupt);
1368
- process.removeListener("SIGTERM", onInterrupt);
1369
- if (workflowPrepared) {
1370
- try {
1371
- await workflow.cleanup(finalSuccess);
1372
- } catch (error) {
1373
- renderer.error({ error, message: "Cleanup failed." });
1374
- }
1375
- }
1376
- await renderer.stop();
1377
- }
1378
- };
1379
-
1380
- const CONCURRENT_ENV_VAR = "VIS_STAGED_CONCURRENT";
1381
- const parseConcurrent = (value) => {
1382
- const trimmed = value.trim();
1383
- if (trimmed === "true" || trimmed === "") {
1384
- return true;
1385
- }
1386
- if (trimmed === "false") {
1387
- return false;
1388
- }
1389
- const parsed = Number(trimmed);
1390
- return Number.isNaN(parsed) ? true : parsed;
1391
- };
1392
-
1393
- const buildRunOptions = (raw, stagedConfig) => {
1394
- const options = {};
1395
- if (stagedConfig !== void 0) {
1396
- options.config = stagedConfig;
1397
- }
1398
- const readBool = (key) => raw[key] === void 0 ? void 0 : Boolean(raw[key]);
1399
- const readString = (key) => {
1400
- const value = raw[key];
1401
- return typeof value === "string" && value.length > 0 ? value : void 0;
1402
- };
1403
- const allowEmpty = readBool("allow-empty");
1404
- if (allowEmpty !== void 0) {
1405
- options.allowEmpty = allowEmpty;
1406
- }
1407
- const autoStage = readBool("auto-stage");
1408
- if (autoStage !== void 0) {
1409
- options.autoStage = autoStage;
1410
- }
1411
- const continueOnError = readBool("continue-on-error");
1412
- if (continueOnError !== void 0) {
1413
- options.continueOnError = continueOnError;
1414
- }
1415
- const debug = readBool("debug");
1416
- if (debug !== void 0) {
1417
- options.debug = debug;
1418
- }
1419
- const failOnChanges = readBool("fail-on-changes");
1420
- if (failOnChanges !== void 0) {
1421
- options.failOnChanges = failOnChanges;
1422
- }
1423
- const hidePartiallyStaged = readBool("hide-partially-staged");
1424
- if (hidePartiallyStaged !== void 0) {
1425
- options.hidePartiallyStaged = hidePartiallyStaged;
1426
- }
1427
- const hideUnstaged = readBool("hide-unstaged");
1428
- if (hideUnstaged !== void 0) {
1429
- options.hideUnstaged = hideUnstaged;
1430
- }
1431
- const quiet = readBool("quiet");
1432
- if (quiet !== void 0) {
1433
- options.quiet = quiet;
1434
- }
1435
- const relative = readBool("relative");
1436
- if (relative !== void 0) {
1437
- options.relative = relative;
1438
- }
1439
- const revert = readBool("revert");
1440
- if (revert !== void 0) {
1441
- options.revert = revert;
1442
- }
1443
- const stash = readBool("stash");
1444
- if (stash !== void 0) {
1445
- options.stash = stash;
1446
- }
1447
- const verbose = readBool("verbose");
1448
- if (verbose !== void 0) {
1449
- options.verbose = verbose;
1450
- }
1451
- const cwd = readString("cwd");
1452
- if (cwd !== void 0) {
1453
- options.cwd = cwd;
1454
- }
1455
- const diff = readString("diff");
1456
- if (diff !== void 0) {
1457
- options.diff = diff;
1458
- }
1459
- const diffFilter = readString("diff-filter");
1460
- if (diffFilter !== void 0) {
1461
- options.diffFilter = diffFilter;
1462
- }
1463
- const forceKill = readBool("force-kill");
1464
- if (forceKill === true) {
1465
- options.killSignal = "SIGKILL";
1466
- }
1467
- if (raw["concurrent"] === void 0) {
1468
- const envValue = process.env[CONCURRENT_ENV_VAR];
1469
- if (envValue !== void 0) {
1470
- options.concurrent = parseConcurrent(envValue.trim());
1471
- }
1472
- } else {
1473
- options.concurrent = parseConcurrent(String(raw["concurrent"]));
1474
- }
1475
- return options;
1476
- };
1477
- const execute = async ({ options, visConfig }) => {
1478
- const config = visConfig ?? {};
1479
- const stagedConfig = config["staged"];
1480
- if (!stagedConfig) {
1481
- throw new Error(
1482
- "No \"staged\" config found in vis.config.ts. Add one:\n\n // vis.config.ts\n import { defineConfig } from \"@visulima/vis/config\";\n\n export default defineConfig({\n staged: { '*': 'vis check --fix' },\n });\n\nMigrating from lint-staged or nano-staged? Run `vis migrate lint-staged` (or `vis migrate nano-staged`) to move the config in and remove the legacy files."
1483
- );
1484
- }
1485
- const result = await runStaged(buildRunOptions(options, stagedConfig));
1486
- if (!result.success) {
1487
- process.exitCode = 1;
1488
- }
1489
- };
1490
26
 
1491
- export { buildRunOptions, execute as default };
27
+ Migrating from lint-staged or nano-staged? Run \`vis migrate lint-staged\` (or \`vis migrate nano-staged\`) to move the config in and remove the legacy files.`);(await Xt(sr(e,r))).success||(process.exitCode=1)},"execute");export{sr as buildRunOptions,pr as default};