@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.
- package/CHANGELOG.md +101 -0
- package/LICENSE.md +559 -186
- package/README.md +18 -0
- package/dist/bin.js +1 -9
- package/dist/config/index.d.ts +477 -556
- package/dist/config/index.js +1 -2
- package/dist/generate/index.js +1 -3
- package/dist/packem_chunks/applyDefaults.js +2 -336
- package/dist/packem_chunks/bin.js +234 -9552
- package/dist/packem_chunks/doctor-probe.js +2 -112
- package/dist/packem_chunks/fix.js +11 -234
- package/dist/packem_chunks/handler.js +1 -99
- package/dist/packem_chunks/handler10.js +2 -53
- package/dist/packem_chunks/handler11.js +1 -32
- package/dist/packem_chunks/handler12.js +5 -100
- package/dist/packem_chunks/handler13.js +1 -25
- package/dist/packem_chunks/handler14.js +18 -916
- package/dist/packem_chunks/handler15.js +15 -201
- package/dist/packem_chunks/handler16.js +1 -124
- package/dist/packem_chunks/handler17.js +1 -13
- package/dist/packem_chunks/handler18.js +1 -106
- package/dist/packem_chunks/handler19.js +1 -19
- package/dist/packem_chunks/handler2.js +2 -75
- package/dist/packem_chunks/handler20.js +5 -29
- package/dist/packem_chunks/handler21.js +1 -222
- package/dist/packem_chunks/handler22.js +1 -237
- package/dist/packem_chunks/handler23.js +5 -101
- package/dist/packem_chunks/handler24.js +1 -110
- package/dist/packem_chunks/handler25.js +3 -402
- package/dist/packem_chunks/handler26.js +1 -13
- package/dist/packem_chunks/handler27.js +1 -63
- package/dist/packem_chunks/handler28.js +7 -34
- package/dist/packem_chunks/handler29.js +21 -456
- package/dist/packem_chunks/handler3.js +4 -95
- package/dist/packem_chunks/handler30.js +3 -170
- package/dist/packem_chunks/handler31.js +1 -530
- package/dist/packem_chunks/handler32.js +2 -214
- package/dist/packem_chunks/handler33.js +25 -119
- package/dist/packem_chunks/handler34.js +2 -630
- package/dist/packem_chunks/handler35.js +3 -283
- package/dist/packem_chunks/handler36.js +22 -542
- package/dist/packem_chunks/handler37.js +410 -744
- package/dist/packem_chunks/handler38.js +22 -989
- package/dist/packem_chunks/handler39.js +22 -574
- package/dist/packem_chunks/handler4.js +2 -90
- package/dist/packem_chunks/handler40.js +22 -1685
- package/dist/packem_chunks/handler41.js +6 -1088
- package/dist/packem_chunks/handler42.js +5 -797
- package/dist/packem_chunks/handler43.js +10 -2658
- package/dist/packem_chunks/handler44.js +51 -3784
- package/dist/packem_chunks/handler45.js +25 -2574
- package/dist/packem_chunks/handler46.js +3 -3769
- package/dist/packem_chunks/handler47.js +21 -1485
- package/dist/packem_chunks/handler48.js +42 -0
- package/dist/packem_chunks/handler5.js +8 -174
- package/dist/packem_chunks/handler6.js +1 -95
- package/dist/packem_chunks/handler7.js +1 -115
- package/dist/packem_chunks/handler8.js +1 -12
- package/dist/packem_chunks/handler9.js +1 -29
- package/dist/packem_chunks/heal-accept.js +10 -522
- package/dist/packem_chunks/heal.js +14 -673
- package/dist/packem_chunks/index.js +7 -873
- package/dist/packem_chunks/loader.js +1 -23
- package/dist/packem_chunks/tar.js +3 -0
- package/dist/packem_shared/ai-analysis-hm8d2W7z.js +67 -0
- package/dist/packem_shared/ai-cache-DoiF80AR.js +1 -0
- package/dist/packem_shared/ai-fix-nn4zOE95.js +43 -0
- package/dist/packem_shared/cache-directory-CwHlJhgx.js +1 -0
- package/dist/packem_shared/dependency-scan-COr5n63B.js +2 -0
- package/dist/packem_shared/docker-D6OGr5_S.js +2 -0
- package/dist/packem_shared/failure-log-iUVLf6ts.js +2 -0
- package/dist/packem_shared/flakiness-D9wf0t56.js +1 -0
- package/dist/packem_shared/giget-CcEy_Elm.js +2 -0
- package/dist/packem_shared/index-DH-5hsrC.js +1 -0
- package/dist/packem_shared/otel-DxDUPJJH.js +6 -0
- package/dist/packem_shared/otelPlugin-CQq6poq8.js +1 -0
- package/dist/packem_shared/registry-CkubDdiY.js +2 -0
- package/dist/packem_shared/run-summary-utils-BfBvjzhY.js +1 -0
- package/dist/packem_shared/runtime-check-BXZ43CBW.js +1 -0
- package/dist/packem_shared/selectors-BylODRiM.js +3 -0
- package/dist/packem_shared/symbols-CQmER5MT.js +1 -0
- package/dist/packem_shared/toolchain-BgBOUHII.js +5 -0
- package/dist/packem_shared/typosquats-CcZl99B1.js +1 -0
- package/dist/packem_shared/use-measured-height-DjYgUOKk.js +1 -0
- package/dist/packem_shared/utils-DrNg0XTR.js +1 -0
- package/dist/packem_shared/verify-Baj5mFJ7.js +1 -0
- package/dist/packem_shared/vis-update-app-D1jl0UZZ.js +1 -0
- package/dist/packem_shared/xxh3-DrAUNq4n.js +1 -0
- package/index.js +556 -727
- package/package.json +19 -29
- package/schemas/project.schema.json +739 -297
- package/schemas/vis-config.schema.json +3365 -384
- package/templates/buildkite-ci/template.yml +20 -20
- package/dist/packem_shared/VisUpdateApp-D-Yz_wvg.js +0 -1316
- package/dist/packem_shared/_commonjsHelpers-BqLXS_qQ.js +0 -5
- package/dist/packem_shared/ai-analysis-CHeB1joD.js +0 -367
- package/dist/packem_shared/ai-cache-Be_jexe4.js +0 -142
- package/dist/packem_shared/ai-fix-B9iQVcD2.js +0 -379
- package/dist/packem_shared/cache-directory-2qvs4goY.js +0 -98
- package/dist/packem_shared/catalog-BJTtyi-O.js +0 -1371
- package/dist/packem_shared/dependency-scan-A0KSklpG.js +0 -188
- package/dist/packem_shared/docker-2iZzc280.js +0 -181
- package/dist/packem_shared/failure-log-Cz3Z4SKL.js +0 -100
- package/dist/packem_shared/flakiness-goTxXuCX.js +0 -180
- package/dist/packem_shared/otel-DCvqCTz_.js +0 -158
- package/dist/packem_shared/otelPlugin-DFaLDvJf.js +0 -3
- package/dist/packem_shared/registry-CbqXI0rc.js +0 -272
- package/dist/packem_shared/run-summary-utils-PVMl4aIh.js +0 -130
- package/dist/packem_shared/runtime-check-Cobi3p6l.js +0 -127
- package/dist/packem_shared/selectors-SM69TfqC.js +0 -194
- package/dist/packem_shared/symbols-Ta7g2nU-.js +0 -14
- package/dist/packem_shared/toolchain-BdZd9eBi.js +0 -975
- package/dist/packem_shared/typosquats-C-bCh3PX.js +0 -1210
- package/dist/packem_shared/use-measured-height-CNP0vT4M.js +0 -20
- package/dist/packem_shared/utils-CthVdBPS.js +0 -40
- package/dist/packem_shared/xxh3-Ck8mXNg1.js +0 -239
|
@@ -1,1685 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const [major, minor] = __cjs_getProcess.versions.node.split(".").map(Number);
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
writeFileSync,
|
|
24
|
-
unlinkSync,
|
|
25
|
-
rmSync,
|
|
26
|
-
chmodSync
|
|
27
|
-
} = __cjs_getBuiltinModule("node:fs");
|
|
28
|
-
const {
|
|
29
|
-
cwd
|
|
30
|
-
} = __cjs_getProcess;
|
|
31
|
-
const {
|
|
32
|
-
createInterface
|
|
33
|
-
} = __cjs_getBuiltinModule("node:readline");
|
|
34
|
-
import { isAccessibleSync, readFileSync, ensureDirSync } from '@visulima/fs';
|
|
35
|
-
import { join } from '@visulima/path';
|
|
36
|
-
import { $ as HOOKS, a0 as PREK_CONFIG_FILES, a1 as installHooks, a2 as PREK_TRANSLATABLE_LANGUAGES, a3 as PREK_SUPPORTED_STAGES, a4 as PREK_STAGES_WITH_GIT_ARGS, a5 as PREK_STAGE_ALIASES, a6 as DEFAULT_HOOKS_DIRECTORY, a7 as detectHuskyDirectory, a8 as migrateFromHusky } from './bin.js';
|
|
37
|
-
const {
|
|
38
|
-
spawnSync
|
|
39
|
-
} = __cjs_getBuiltinModule("node:child_process");
|
|
40
|
-
import { readTomlSync } from '@visulima/fs/toml';
|
|
41
|
-
import { parse } from 'yaml';
|
|
42
|
-
|
|
43
|
-
const HOOK_HEADER_RE = /^# ([^:\s]\S*)(?::\s+(.+))?$/;
|
|
44
|
-
const parseStageScript = (content) => {
|
|
45
|
-
const blocks = [];
|
|
46
|
-
const lines = content.split("\n");
|
|
47
|
-
let current;
|
|
48
|
-
for (const line of lines) {
|
|
49
|
-
if (line.startsWith("#!") || line.startsWith("# Generated by") || line.startsWith("# NOTE:") || line === "set -e" || line === "") {
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
const headerMatch = HOOK_HEADER_RE.exec(line);
|
|
53
|
-
if (headerMatch) {
|
|
54
|
-
if (current) {
|
|
55
|
-
blocks.push(current);
|
|
56
|
-
}
|
|
57
|
-
current = { command: "", id: headerMatch[1] ?? "", ...headerMatch[2] ? { name: headerMatch[2] } : {} };
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
if (current) {
|
|
61
|
-
current.command = current.command.length > 0 ? `${current.command}
|
|
62
|
-
${line}` : line;
|
|
63
|
-
} else {
|
|
64
|
-
current = { command: line, id: "(custom)" };
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
if (current) {
|
|
68
|
-
blocks.push(current);
|
|
69
|
-
}
|
|
70
|
-
return blocks;
|
|
71
|
-
};
|
|
72
|
-
const listHooks = (root, hooksDirectory) => {
|
|
73
|
-
const directory = join(root, hooksDirectory);
|
|
74
|
-
const stages = [];
|
|
75
|
-
const stageSet = new Set(HOOKS);
|
|
76
|
-
if (isAccessibleSync(directory)) {
|
|
77
|
-
for (const entry of readdirSync(directory)) {
|
|
78
|
-
if (entry.startsWith(".") || entry === "_") {
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
if (!stageSet.has(entry)) {
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
const stagePath = join(directory, entry);
|
|
85
|
-
if (!statSync(stagePath).isFile()) {
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
const content = readFileSync(stagePath);
|
|
89
|
-
const blocks = parseStageScript(content);
|
|
90
|
-
stages.push({ blocks, rawLineCount: content.split("\n").length, stage: entry });
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
stages.sort((a, b) => a.stage.localeCompare(b.stage));
|
|
94
|
-
return { hooksDirectory, stages };
|
|
95
|
-
};
|
|
96
|
-
const formatListResult = (result) => {
|
|
97
|
-
const lines = [];
|
|
98
|
-
if (result.stages.length === 0) {
|
|
99
|
-
lines.push(`No hooks installed in ${result.hooksDirectory}/.`);
|
|
100
|
-
return lines;
|
|
101
|
-
}
|
|
102
|
-
lines.push(`Hooks in ${result.hooksDirectory}/:`);
|
|
103
|
-
for (const entry of result.stages) {
|
|
104
|
-
lines.push("", `${entry.stage} (${entry.rawLineCount} lines)`);
|
|
105
|
-
if (entry.blocks.length === 0) {
|
|
106
|
-
lines.push(" (empty)");
|
|
107
|
-
} else {
|
|
108
|
-
for (const block of entry.blocks) {
|
|
109
|
-
const title = block.name ? `${block.id} — ${block.name}` : block.id;
|
|
110
|
-
lines.push(` - ${title}`);
|
|
111
|
-
const firstCommandLine = block.command.split("\n").find((line) => line.trim() !== "");
|
|
112
|
-
if (firstCommandLine) {
|
|
113
|
-
const display = firstCommandLine.length > 120 ? `${firstCommandLine.slice(0, 117)}...` : firstCommandLine;
|
|
114
|
-
lines.push(` ${display}`);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
return lines;
|
|
120
|
-
};
|
|
121
|
-
const runList = (hooksDirectory, logger) => {
|
|
122
|
-
const result = listHooks(cwd(), hooksDirectory);
|
|
123
|
-
for (const line of formatListResult(result)) {
|
|
124
|
-
logger.info(line);
|
|
125
|
-
}
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
const PREK_RUNNER_FILENAME = "prek-runner.mjs";
|
|
129
|
-
const TYPES_EXTENSION_MAP = {
|
|
130
|
-
css: ["css", "scss", "sass", "less"],
|
|
131
|
-
dockerfile: ["dockerfile"],
|
|
132
|
-
html: ["htm", "html"],
|
|
133
|
-
javascript: ["cjs", "js", "jsx", "mjs"],
|
|
134
|
-
json: ["json"],
|
|
135
|
-
jsx: ["jsx", "tsx"],
|
|
136
|
-
makefile: ["mk", "makefile"],
|
|
137
|
-
markdown: ["markdown", "md", "mdown", "mdx"],
|
|
138
|
-
python: ["py", "pyi", "pyw"],
|
|
139
|
-
python3: ["py", "pyi", "pyw"],
|
|
140
|
-
ruby: ["rb"],
|
|
141
|
-
rust: ["rs"],
|
|
142
|
-
shell: ["bash", "sh", "zsh"],
|
|
143
|
-
sql: ["sql"],
|
|
144
|
-
svg: ["svg"],
|
|
145
|
-
systemd: ["service", "socket", "timer"],
|
|
146
|
-
toml: ["toml"],
|
|
147
|
-
tsx: ["tsx"],
|
|
148
|
-
typescript: ["cts", "mts", "ts", "tsx"],
|
|
149
|
-
xml: ["xml"],
|
|
150
|
-
yaml: ["yaml", "yml"]
|
|
151
|
-
};
|
|
152
|
-
const METADATA_TYPE_TAGS = ["binary", "directory", "executable", "non-executable", "symlink", "text"];
|
|
153
|
-
const SHEBANG_INTERPRETER_MAP = {
|
|
154
|
-
bash: ["bash", "shell"],
|
|
155
|
-
node: ["javascript"],
|
|
156
|
-
nodejs: ["javascript"],
|
|
157
|
-
perl: ["perl"],
|
|
158
|
-
python: ["python"],
|
|
159
|
-
python3: ["python", "python3"],
|
|
160
|
-
ruby: ["ruby"],
|
|
161
|
-
sh: ["shell"],
|
|
162
|
-
zsh: ["shell", "zsh"]
|
|
163
|
-
};
|
|
164
|
-
const BUILTIN_HOOK_IDS = ["check-json", "check-merge-conflict", "end-of-file-fixer", "mixed-line-ending", "trailing-whitespace"];
|
|
165
|
-
const KNOWN_TYPE_TAGS = [...Object.keys(TYPES_EXTENSION_MAP), ...Object.values(SHEBANG_INTERPRETER_MAP).flat(), ...METADATA_TYPE_TAGS];
|
|
166
|
-
const PREK_RUNNER_LINES = [
|
|
167
|
-
"#!/usr/bin/env node",
|
|
168
|
-
"// Auto-generated by `vis hook migrate`. Do not edit by hand.",
|
|
169
|
-
"// Replicates the subset of prek / pre-commit framework semantics that a vis",
|
|
170
|
-
"// hook script needs: staged-file discovery, regex + type filters, chunked",
|
|
171
|
-
"// argv dispatch, and a handful of built-in hook implementations.",
|
|
172
|
-
"",
|
|
173
|
-
"import { spawnSync } from 'node:child_process';",
|
|
174
|
-
"import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';",
|
|
175
|
-
"import { basename, extname, join } from 'node:path';",
|
|
176
|
-
"import process from 'node:process';",
|
|
177
|
-
"",
|
|
178
|
-
"const TYPES_EXTENSION_MAP = {",
|
|
179
|
-
" css: ['css', 'scss', 'sass', 'less'],",
|
|
180
|
-
" dockerfile: ['dockerfile'],",
|
|
181
|
-
" html: ['htm', 'html'],",
|
|
182
|
-
" javascript: ['cjs', 'js', 'jsx', 'mjs'],",
|
|
183
|
-
" json: ['json'],",
|
|
184
|
-
" jsx: ['jsx', 'tsx'],",
|
|
185
|
-
" makefile: ['mk', 'makefile'],",
|
|
186
|
-
" markdown: ['markdown', 'md', 'mdown', 'mdx'],",
|
|
187
|
-
" python: ['py', 'pyi', 'pyw'],",
|
|
188
|
-
" python3: ['py', 'pyi', 'pyw'],",
|
|
189
|
-
" ruby: ['rb'],",
|
|
190
|
-
" rust: ['rs'],",
|
|
191
|
-
" shell: ['bash', 'sh', 'zsh'],",
|
|
192
|
-
" sql: ['sql'],",
|
|
193
|
-
" svg: ['svg'],",
|
|
194
|
-
" systemd: ['service', 'socket', 'timer'],",
|
|
195
|
-
" toml: ['toml'],",
|
|
196
|
-
" tsx: ['tsx'],",
|
|
197
|
-
" typescript: ['cts', 'mts', 'ts', 'tsx'],",
|
|
198
|
-
" xml: ['xml'],",
|
|
199
|
-
" yaml: ['yaml', 'yml'],",
|
|
200
|
-
"};",
|
|
201
|
-
"",
|
|
202
|
-
"const FILENAME_TYPE_MAP = {",
|
|
203
|
-
" dockerfile: 'dockerfile',",
|
|
204
|
-
" makefile: 'makefile',",
|
|
205
|
-
" 'gnumakefile': 'makefile',",
|
|
206
|
-
"};",
|
|
207
|
-
"",
|
|
208
|
-
"const SHEBANG_INTERPRETER_MAP = {",
|
|
209
|
-
" bash: ['bash', 'shell'],",
|
|
210
|
-
" node: ['javascript'],",
|
|
211
|
-
" nodejs: ['javascript'],",
|
|
212
|
-
" perl: ['perl'],",
|
|
213
|
-
" python: ['python'],",
|
|
214
|
-
" python3: ['python', 'python3'],",
|
|
215
|
-
" ruby: ['ruby'],",
|
|
216
|
-
" sh: ['shell'],",
|
|
217
|
-
" zsh: ['shell', 'zsh'],",
|
|
218
|
-
"};",
|
|
219
|
-
"",
|
|
220
|
-
"const BUILTINS = {",
|
|
221
|
-
" 'check-json': runCheckJson,",
|
|
222
|
-
" 'check-merge-conflict': runCheckMergeConflict,",
|
|
223
|
-
" 'end-of-file-fixer': runEndOfFileFixer,",
|
|
224
|
-
" 'mixed-line-ending': runMixedLineEnding,",
|
|
225
|
-
" 'trailing-whitespace': runTrailingWhitespace,",
|
|
226
|
-
"};",
|
|
227
|
-
"",
|
|
228
|
-
"const parseArgs = (argv) => {",
|
|
229
|
-
" const flags = {",
|
|
230
|
-
" allFiles: process.env.VIS_HOOK_ALL_FILES === '1',",
|
|
231
|
-
" alwaysRun: false,",
|
|
232
|
-
" builtin: null,",
|
|
233
|
-
" exclude: null,",
|
|
234
|
-
" excludeTypes: [],",
|
|
235
|
-
" files: null,",
|
|
236
|
-
" fromRef: process.env.VIS_HOOK_FROM_REF || null,",
|
|
237
|
-
" passFilenames: true,",
|
|
238
|
-
" toRef: process.env.VIS_HOOK_TO_REF || null,",
|
|
239
|
-
" types: [],",
|
|
240
|
-
" typesOr: [],",
|
|
241
|
-
" };",
|
|
242
|
-
" const rest = [];",
|
|
243
|
-
" let seenDoubleDash = false;",
|
|
244
|
-
"",
|
|
245
|
-
" for (let i = 0; i < argv.length; i += 1) {",
|
|
246
|
-
" const arg = argv[i];",
|
|
247
|
-
"",
|
|
248
|
-
" if (seenDoubleDash) {",
|
|
249
|
-
" rest.push(arg);",
|
|
250
|
-
" continue;",
|
|
251
|
-
" }",
|
|
252
|
-
"",
|
|
253
|
-
" switch (arg) {",
|
|
254
|
-
" case '--':",
|
|
255
|
-
" seenDoubleDash = true;",
|
|
256
|
-
" break;",
|
|
257
|
-
" case '--all-files':",
|
|
258
|
-
" flags.allFiles = true;",
|
|
259
|
-
" break;",
|
|
260
|
-
" case '--always-run':",
|
|
261
|
-
" flags.alwaysRun = true;",
|
|
262
|
-
" break;",
|
|
263
|
-
" case '--builtin':",
|
|
264
|
-
" i += 1;",
|
|
265
|
-
" flags.builtin = argv[i];",
|
|
266
|
-
" break;",
|
|
267
|
-
" case '--exclude':",
|
|
268
|
-
" i += 1;",
|
|
269
|
-
" flags.exclude = argv[i];",
|
|
270
|
-
" break;",
|
|
271
|
-
" case '--from-ref':",
|
|
272
|
-
" i += 1;",
|
|
273
|
-
" flags.fromRef = argv[i];",
|
|
274
|
-
" break;",
|
|
275
|
-
" case '--to-ref':",
|
|
276
|
-
" i += 1;",
|
|
277
|
-
" flags.toRef = argv[i];",
|
|
278
|
-
" break;",
|
|
279
|
-
" case '--exclude-types':",
|
|
280
|
-
" i += 1;",
|
|
281
|
-
" flags.excludeTypes = (argv[i] || '').split(',').filter(Boolean);",
|
|
282
|
-
" break;",
|
|
283
|
-
" case '--files':",
|
|
284
|
-
" i += 1;",
|
|
285
|
-
" flags.files = argv[i];",
|
|
286
|
-
" break;",
|
|
287
|
-
" case '--no-pass-filenames':",
|
|
288
|
-
" flags.passFilenames = false;",
|
|
289
|
-
" break;",
|
|
290
|
-
" case '--types':",
|
|
291
|
-
" i += 1;",
|
|
292
|
-
" flags.types = (argv[i] || '').split(',').filter(Boolean);",
|
|
293
|
-
" break;",
|
|
294
|
-
" case '--types-or':",
|
|
295
|
-
" i += 1;",
|
|
296
|
-
" flags.typesOr = (argv[i] || '').split(',').filter(Boolean);",
|
|
297
|
-
" break;",
|
|
298
|
-
" default:",
|
|
299
|
-
" process.stderr.write('prek-runner: unknown flag ' + arg + '\\n');",
|
|
300
|
-
" process.exit(2);",
|
|
301
|
-
" }",
|
|
302
|
-
" }",
|
|
303
|
-
"",
|
|
304
|
-
" return { flags, rest };",
|
|
305
|
-
"};",
|
|
306
|
-
"",
|
|
307
|
-
"const gitListFiles = (args, errorHint) => {",
|
|
308
|
-
" const result = spawnSync('git', args, { encoding: 'buffer' });",
|
|
309
|
-
" if (result.status !== 0) {",
|
|
310
|
-
" process.stderr.write('prek-runner: git ' + errorHint + ' failed\\n');",
|
|
311
|
-
" process.stderr.write(result.stderr ? result.stderr.toString() : '');",
|
|
312
|
-
" process.exit(result.status === null ? 1 : result.status);",
|
|
313
|
-
" }",
|
|
314
|
-
" const raw = result.stdout.toString('utf8');",
|
|
315
|
-
" if (raw.length === 0) { return []; }",
|
|
316
|
-
" return raw.split('\\0').filter(function (f) { return f.length > 0; });",
|
|
317
|
-
"};",
|
|
318
|
-
"",
|
|
319
|
-
"const discoverFiles = (flags) => {",
|
|
320
|
-
" if (flags.allFiles) {",
|
|
321
|
-
" return gitListFiles(['ls-files', '-z'], 'ls-files');",
|
|
322
|
-
" }",
|
|
323
|
-
" if (flags.fromRef && flags.toRef) {",
|
|
324
|
-
" return gitListFiles(",
|
|
325
|
-
" ['diff', '--name-only', '--diff-filter=ACM', '-z', flags.fromRef, flags.toRef],",
|
|
326
|
-
" 'diff --from-ref/--to-ref'",
|
|
327
|
-
" );",
|
|
328
|
-
" }",
|
|
329
|
-
" return gitListFiles(['diff', '--cached', '--name-only', '--diff-filter=ACM', '-z'], 'diff --cached');",
|
|
330
|
-
"};",
|
|
331
|
-
"",
|
|
332
|
-
"const buildRegex = (pattern) => {",
|
|
333
|
-
" try {",
|
|
334
|
-
" return new RegExp(pattern);",
|
|
335
|
-
" } catch (error) {",
|
|
336
|
-
" process.stderr.write('prek-runner: invalid regex ' + JSON.stringify(pattern) + ': ' + error.message + '\\n');",
|
|
337
|
-
" process.exit(2);",
|
|
338
|
-
" }",
|
|
339
|
-
"};",
|
|
340
|
-
"",
|
|
341
|
-
"const readShebang = (file) => {",
|
|
342
|
-
" try {",
|
|
343
|
-
" const fd = readFileSync(file, { encoding: null });",
|
|
344
|
-
" if (fd.length < 2 || fd[0] !== 0x23 || fd[1] !== 0x21) { return null; }",
|
|
345
|
-
" const nl = fd.indexOf(0x0a);",
|
|
346
|
-
" const end = nl === -1 ? Math.min(fd.length, 256) : Math.min(nl, 256);",
|
|
347
|
-
" return fd.slice(2, end).toString('utf8').trim();",
|
|
348
|
-
" } catch (error) {",
|
|
349
|
-
" return null;",
|
|
350
|
-
" }",
|
|
351
|
-
"};",
|
|
352
|
-
"",
|
|
353
|
-
"const interpreterFromShebang = (shebang) => {",
|
|
354
|
-
" if (!shebang) { return null; }",
|
|
355
|
-
" const parts = shebang.split(/\\s+/).filter(Boolean);",
|
|
356
|
-
" if (parts.length === 0) { return null; }",
|
|
357
|
-
" const first = parts[0];",
|
|
358
|
-
" let candidate = basename(first);",
|
|
359
|
-
" if (candidate === 'env' && parts.length > 1) {",
|
|
360
|
-
" candidate = basename(parts[1].split('=')[0] || parts[1]);",
|
|
361
|
-
" }",
|
|
362
|
-
" return candidate.toLowerCase();",
|
|
363
|
-
"};",
|
|
364
|
-
"",
|
|
365
|
-
"const fileMetadataTags = (file) => {",
|
|
366
|
-
" const tags = new Set();",
|
|
367
|
-
" let info;",
|
|
368
|
-
" try { info = statSync(file, { throwIfNoEntry: false }); } catch (error) { info = null; }",
|
|
369
|
-
" if (!info) { return tags; }",
|
|
370
|
-
" if (info.isSymbolicLink()) { tags.add('symlink'); }",
|
|
371
|
-
" if (info.isDirectory()) { tags.add('directory'); }",
|
|
372
|
-
" if (info.isFile()) {",
|
|
373
|
-
" if ((info.mode & 0o111) !== 0) { tags.add('executable'); } else { tags.add('non-executable'); }",
|
|
374
|
-
" }",
|
|
375
|
-
" return tags;",
|
|
376
|
-
"};",
|
|
377
|
-
"",
|
|
378
|
-
"const isBinaryFile = (file) => {",
|
|
379
|
-
" try {",
|
|
380
|
-
" const buf = readFileSync(file);",
|
|
381
|
-
" const slice = buf.subarray(0, Math.min(buf.length, 8192));",
|
|
382
|
-
" for (let i = 0; i < slice.length; i += 1) {",
|
|
383
|
-
" if (slice[i] === 0) { return true; }",
|
|
384
|
-
" }",
|
|
385
|
-
" return false;",
|
|
386
|
-
" } catch (error) {",
|
|
387
|
-
" return false;",
|
|
388
|
-
" }",
|
|
389
|
-
"};",
|
|
390
|
-
"",
|
|
391
|
-
"const typesForFile = (file) => {",
|
|
392
|
-
" const tags = new Set();",
|
|
393
|
-
" const baseName = basename(file).toLowerCase();",
|
|
394
|
-
" const ext = extname(file).slice(1).toLowerCase();",
|
|
395
|
-
"",
|
|
396
|
-
" if (baseName in FILENAME_TYPE_MAP) {",
|
|
397
|
-
" tags.add(FILENAME_TYPE_MAP[baseName]);",
|
|
398
|
-
" }",
|
|
399
|
-
"",
|
|
400
|
-
" for (const [type, extensions] of Object.entries(TYPES_EXTENSION_MAP)) {",
|
|
401
|
-
" if (extensions.includes(ext) || extensions.includes(baseName)) {",
|
|
402
|
-
" tags.add(type);",
|
|
403
|
-
" }",
|
|
404
|
-
" }",
|
|
405
|
-
"",
|
|
406
|
-
" const metaTags = fileMetadataTags(file);",
|
|
407
|
-
" for (const tag of metaTags) { tags.add(tag); }",
|
|
408
|
-
"",
|
|
409
|
-
" if (tags.size === 0 || tags.has('executable') || tags.has('shell')) {",
|
|
410
|
-
" const interpreter = interpreterFromShebang(readShebang(file));",
|
|
411
|
-
" if (interpreter) {",
|
|
412
|
-
" const interpreterTags = SHEBANG_INTERPRETER_MAP[interpreter];",
|
|
413
|
-
" if (interpreterTags) {",
|
|
414
|
-
" for (const tag of interpreterTags) { tags.add(tag); }",
|
|
415
|
-
" }",
|
|
416
|
-
" }",
|
|
417
|
-
" }",
|
|
418
|
-
"",
|
|
419
|
-
" if (!tags.has('symlink') && !tags.has('directory')) {",
|
|
420
|
-
" tags.add(isBinaryFile(file) ? 'binary' : 'text');",
|
|
421
|
-
" }",
|
|
422
|
-
"",
|
|
423
|
-
" return tags;",
|
|
424
|
-
"};",
|
|
425
|
-
"",
|
|
426
|
-
"const applyFilters = (files, flags) => {",
|
|
427
|
-
" let filtered = files;",
|
|
428
|
-
"",
|
|
429
|
-
" if (flags.files) {",
|
|
430
|
-
" const rx = buildRegex(flags.files);",
|
|
431
|
-
" filtered = filtered.filter(function (f) { return rx.test(f); });",
|
|
432
|
-
" }",
|
|
433
|
-
"",
|
|
434
|
-
" if (flags.exclude) {",
|
|
435
|
-
" const rx = buildRegex(flags.exclude);",
|
|
436
|
-
" filtered = filtered.filter(function (f) { return !rx.test(f); });",
|
|
437
|
-
" }",
|
|
438
|
-
"",
|
|
439
|
-
" if (flags.types.length > 0) {",
|
|
440
|
-
" filtered = filtered.filter(function (f) {",
|
|
441
|
-
" const tags = typesForFile(f);",
|
|
442
|
-
" return flags.types.every(function (t) { return tags.has(t); });",
|
|
443
|
-
" });",
|
|
444
|
-
" }",
|
|
445
|
-
"",
|
|
446
|
-
" if (flags.typesOr.length > 0) {",
|
|
447
|
-
" filtered = filtered.filter(function (f) {",
|
|
448
|
-
" const tags = typesForFile(f);",
|
|
449
|
-
" return flags.typesOr.some(function (t) { return tags.has(t); });",
|
|
450
|
-
" });",
|
|
451
|
-
" }",
|
|
452
|
-
"",
|
|
453
|
-
" if (flags.excludeTypes.length > 0) {",
|
|
454
|
-
" filtered = filtered.filter(function (f) {",
|
|
455
|
-
" const tags = typesForFile(f);",
|
|
456
|
-
" return !flags.excludeTypes.some(function (t) { return tags.has(t); });",
|
|
457
|
-
" });",
|
|
458
|
-
" }",
|
|
459
|
-
"",
|
|
460
|
-
" return filtered;",
|
|
461
|
-
"};",
|
|
462
|
-
"",
|
|
463
|
-
"// Conservative per-call argv budget. POSIX guarantees 4 KiB, Linux gives ~2 MiB",
|
|
464
|
-
"// in practice. 32 KiB keeps us well clear of Windows' 32767-char limit too.",
|
|
465
|
-
"const ARG_BUDGET = 32 * 1024;",
|
|
466
|
-
"",
|
|
467
|
-
"const chunkFiles = (files) => {",
|
|
468
|
-
" const chunks = [];",
|
|
469
|
-
" let current = [];",
|
|
470
|
-
" let size = 0;",
|
|
471
|
-
"",
|
|
472
|
-
" for (const file of files) {",
|
|
473
|
-
" const cost = Buffer.byteLength(file, 'utf8') + 8;",
|
|
474
|
-
"",
|
|
475
|
-
" if (size + cost > ARG_BUDGET && current.length > 0) {",
|
|
476
|
-
" chunks.push(current);",
|
|
477
|
-
" current = [];",
|
|
478
|
-
" size = 0;",
|
|
479
|
-
" }",
|
|
480
|
-
"",
|
|
481
|
-
" current.push(file);",
|
|
482
|
-
" size += cost;",
|
|
483
|
-
" }",
|
|
484
|
-
"",
|
|
485
|
-
" if (current.length > 0) {",
|
|
486
|
-
" chunks.push(current);",
|
|
487
|
-
" }",
|
|
488
|
-
"",
|
|
489
|
-
" return chunks;",
|
|
490
|
-
"};",
|
|
491
|
-
"",
|
|
492
|
-
"const runCommand = (cmd, files, passFilenames) => {",
|
|
493
|
-
" if (!cmd || cmd.length === 0) {",
|
|
494
|
-
" process.stderr.write('prek-runner: no command specified after --\\n');",
|
|
495
|
-
" return 2;",
|
|
496
|
-
" }",
|
|
497
|
-
"",
|
|
498
|
-
" const bin = cmd[0];",
|
|
499
|
-
" const baseArgs = cmd.slice(1);",
|
|
500
|
-
"",
|
|
501
|
-
" if (!passFilenames) {",
|
|
502
|
-
" const result = spawnSync(bin, baseArgs, { stdio: 'inherit' });",
|
|
503
|
-
" return result.status === null ? 1 : result.status;",
|
|
504
|
-
" }",
|
|
505
|
-
"",
|
|
506
|
-
" let rc = 0;",
|
|
507
|
-
" const chunks = files.length === 0 ? [[]] : chunkFiles(files);",
|
|
508
|
-
"",
|
|
509
|
-
" for (const chunk of chunks) {",
|
|
510
|
-
" const result = spawnSync(bin, baseArgs.concat(chunk), { stdio: 'inherit' });",
|
|
511
|
-
" rc = rc | (result.status === null ? 1 : result.status);",
|
|
512
|
-
" }",
|
|
513
|
-
"",
|
|
514
|
-
" return rc;",
|
|
515
|
-
"};",
|
|
516
|
-
"",
|
|
517
|
-
"// ─── Built-in hook implementations ──────────────────────────────────",
|
|
518
|
-
"// Each receives the already-filtered file list and returns an exit code.",
|
|
519
|
-
"",
|
|
520
|
-
"function runTrailingWhitespace(files) {",
|
|
521
|
-
" // Mirrors pre-commit/pre-commit-hooks/trailing_whitespace_fixer.py:",
|
|
522
|
-
" // strip trailing whitespace from each line, preserve original endings,",
|
|
523
|
-
" // preserve markdown hard-break trailing two-spaces on non-blank lines.",
|
|
524
|
-
" const WS = new Set([0x20, 0x09, 0x0b, 0x0c, 0x0d]); // SP, TAB, VT, FF, CR",
|
|
525
|
-
" const MD_RE = /\\.(md|markdown|mdown|mdx)$/i;",
|
|
526
|
-
" let rc = 0;",
|
|
527
|
-
"",
|
|
528
|
-
" for (const file of files) {",
|
|
529
|
-
" const isMarkdown = MD_RE.test(file);",
|
|
530
|
-
" const buf = readFileSync(file);",
|
|
531
|
-
" const out = [];",
|
|
532
|
-
" let i = 0;",
|
|
533
|
-
"",
|
|
534
|
-
" while (i <= buf.length) {",
|
|
535
|
-
" let end = i;",
|
|
536
|
-
"",
|
|
537
|
-
" while (end < buf.length && buf[end] !== 0x0a) {",
|
|
538
|
-
" end += 1;",
|
|
539
|
-
" }",
|
|
540
|
-
"",
|
|
541
|
-
" const hadLf = end < buf.length && buf[end] === 0x0a;",
|
|
542
|
-
" let contentEnd = end;",
|
|
543
|
-
" let hadCr = false;",
|
|
544
|
-
"",
|
|
545
|
-
" if (hadLf && end > i && buf[end - 1] === 0x0d) {",
|
|
546
|
-
" hadCr = true;",
|
|
547
|
-
" contentEnd = end - 1;",
|
|
548
|
-
" }",
|
|
549
|
-
"",
|
|
550
|
-
" const content = buf.subarray(i, contentEnd);",
|
|
551
|
-
" let stripEnd = content.length;",
|
|
552
|
-
"",
|
|
553
|
-
" while (stripEnd > 0 && WS.has(content[stripEnd - 1])) {",
|
|
554
|
-
" stripEnd -= 1;",
|
|
555
|
-
" }",
|
|
556
|
-
"",
|
|
557
|
-
" const nonWhitespace = content.some(function (b) { return !WS.has(b); });",
|
|
558
|
-
"",
|
|
559
|
-
" if (isMarkdown && content.length >= 2 && content[content.length - 1] === 0x20 && content[content.length - 2] === 0x20 && nonWhitespace) {",
|
|
560
|
-
" stripEnd = Math.min(stripEnd + 2, content.length);",
|
|
561
|
-
" }",
|
|
562
|
-
"",
|
|
563
|
-
" out.push(content.subarray(0, stripEnd));",
|
|
564
|
-
"",
|
|
565
|
-
" if (hadCr) {",
|
|
566
|
-
" out.push(Buffer.from([0x0d]));",
|
|
567
|
-
" }",
|
|
568
|
-
"",
|
|
569
|
-
" if (hadLf) {",
|
|
570
|
-
" out.push(Buffer.from([0x0a]));",
|
|
571
|
-
" }",
|
|
572
|
-
"",
|
|
573
|
-
" if (!hadLf) {",
|
|
574
|
-
" break;",
|
|
575
|
-
" }",
|
|
576
|
-
"",
|
|
577
|
-
" i = end + 1;",
|
|
578
|
-
" }",
|
|
579
|
-
"",
|
|
580
|
-
" const next = Buffer.concat(out);",
|
|
581
|
-
"",
|
|
582
|
-
" if (!next.equals(buf)) {",
|
|
583
|
-
" writeFileSync(file, next);",
|
|
584
|
-
" process.stdout.write('Fixing ' + file + '\\n');",
|
|
585
|
-
" rc = 1;",
|
|
586
|
-
" }",
|
|
587
|
-
" }",
|
|
588
|
-
"",
|
|
589
|
-
" return rc;",
|
|
590
|
-
"}",
|
|
591
|
-
"",
|
|
592
|
-
"function runEndOfFileFixer(files) {",
|
|
593
|
-
" // Mirrors pre-commit/pre-commit-hooks/end_of_file_fixer.py: collapse",
|
|
594
|
-
" // trailing \\n / \\r\\n / \\r runs to a single newline; add a newline if",
|
|
595
|
-
" // missing; leave empty files alone.",
|
|
596
|
-
" let rc = 0;",
|
|
597
|
-
"",
|
|
598
|
-
" for (const file of files) {",
|
|
599
|
-
" const buf = readFileSync(file);",
|
|
600
|
-
"",
|
|
601
|
-
" if (buf.length === 0) {",
|
|
602
|
-
" continue;",
|
|
603
|
-
" }",
|
|
604
|
-
"",
|
|
605
|
-
" let end = buf.length;",
|
|
606
|
-
" const last = buf[end - 1];",
|
|
607
|
-
"",
|
|
608
|
-
" if (last !== 0x0a && last !== 0x0d) {",
|
|
609
|
-
" writeFileSync(file, Buffer.concat([buf, Buffer.from([0x0a])]));",
|
|
610
|
-
" process.stdout.write('Fixing ' + file + '\\n');",
|
|
611
|
-
" rc = 1;",
|
|
612
|
-
" continue;",
|
|
613
|
-
" }",
|
|
614
|
-
"",
|
|
615
|
-
" while (end > 0 && (buf[end - 1] === 0x0a || buf[end - 1] === 0x0d)) {",
|
|
616
|
-
" end -= 1;",
|
|
617
|
-
" }",
|
|
618
|
-
"",
|
|
619
|
-
" if (end === 0) {",
|
|
620
|
-
" writeFileSync(file, Buffer.alloc(0));",
|
|
621
|
-
" process.stdout.write('Fixing ' + file + '\\n');",
|
|
622
|
-
" rc = 1;",
|
|
623
|
-
" continue;",
|
|
624
|
-
" }",
|
|
625
|
-
"",
|
|
626
|
-
" const trailing = buf.subarray(end);",
|
|
627
|
-
" let keep;",
|
|
628
|
-
"",
|
|
629
|
-
" if (trailing[0] === 0x0d && trailing[1] === 0x0a) {",
|
|
630
|
-
" keep = Buffer.from([0x0d, 0x0a]);",
|
|
631
|
-
" } else if (trailing[0] === 0x0d) {",
|
|
632
|
-
" keep = Buffer.from([0x0d]);",
|
|
633
|
-
" } else {",
|
|
634
|
-
" keep = Buffer.from([0x0a]);",
|
|
635
|
-
" }",
|
|
636
|
-
"",
|
|
637
|
-
" if (trailing.equals(keep)) {",
|
|
638
|
-
" continue;",
|
|
639
|
-
" }",
|
|
640
|
-
"",
|
|
641
|
-
" writeFileSync(file, Buffer.concat([buf.subarray(0, end), keep]));",
|
|
642
|
-
" process.stdout.write('Fixing ' + file + '\\n');",
|
|
643
|
-
" rc = 1;",
|
|
644
|
-
" }",
|
|
645
|
-
"",
|
|
646
|
-
" return rc;",
|
|
647
|
-
"}",
|
|
648
|
-
"",
|
|
649
|
-
"function isInMerge() {",
|
|
650
|
-
" // Mirrors pre-commit/pre-commit-hooks/check_merge_conflict.py:is_in_merge.",
|
|
651
|
-
" const gitDirResult = spawnSync('git', ['rev-parse', '--git-dir'], { encoding: 'utf8' });",
|
|
652
|
-
" if (gitDirResult.status !== 0) { return false; }",
|
|
653
|
-
" const gitDir = gitDirResult.stdout.trim();",
|
|
654
|
-
" if (!existsSync(join(gitDir, 'MERGE_MSG'))) { return false; }",
|
|
655
|
-
" return existsSync(join(gitDir, 'MERGE_HEAD'))",
|
|
656
|
-
" || existsSync(join(gitDir, 'rebase-apply'))",
|
|
657
|
-
" || existsSync(join(gitDir, 'rebase-merge'));",
|
|
658
|
-
"}",
|
|
659
|
-
"",
|
|
660
|
-
"function runCheckMergeConflict(files, args) {",
|
|
661
|
-
" // Mirrors pre-commit/pre-commit-hooks/check_merge_conflict.py: only",
|
|
662
|
-
" // scans for conflict markers when git is mid-merge/rebase, unless the",
|
|
663
|
-
" // caller passes --assume-in-merge. Skipping the guard means every",
|
|
664
|
-
" // legit `<<<<<<<` in docs would fail the hook.",
|
|
665
|
-
" const assumeInMerge = Array.isArray(args) && args.includes('--assume-in-merge');",
|
|
666
|
-
" if (!assumeInMerge && !isInMerge()) { return 0; }",
|
|
667
|
-
"",
|
|
668
|
-
" const PATTERNS = ['<<<<<<< ', '======= ', '=======\\r\\n', '=======\\n', '>>>>>>> '];",
|
|
669
|
-
" let rc = 0;",
|
|
670
|
-
"",
|
|
671
|
-
" for (const file of files) {",
|
|
672
|
-
" const content = readFileSync(file, 'utf8');",
|
|
673
|
-
" const lines = content.split('\\n');",
|
|
674
|
-
"",
|
|
675
|
-
" for (let i = 0; i < lines.length; i += 1) {",
|
|
676
|
-
" const line = lines[i] + (i < lines.length - 1 ? '\\n' : '');",
|
|
677
|
-
"",
|
|
678
|
-
" for (const pattern of PATTERNS) {",
|
|
679
|
-
" if (line.startsWith(pattern)) {",
|
|
680
|
-
" process.stdout.write(file + ':' + (i + 1) + ': Merge conflict string ' + JSON.stringify(pattern.trim()) + ' found\\n');",
|
|
681
|
-
" rc = 1;",
|
|
682
|
-
" }",
|
|
683
|
-
" }",
|
|
684
|
-
" }",
|
|
685
|
-
" }",
|
|
686
|
-
"",
|
|
687
|
-
" return rc;",
|
|
688
|
-
"}",
|
|
689
|
-
"",
|
|
690
|
-
"function runCheckJson(files) {",
|
|
691
|
-
" // Mirrors pre-commit/pre-commit-hooks/check_json.py: parse each file",
|
|
692
|
-
" // and additionally reject duplicate keys.",
|
|
693
|
-
" let rc = 0;",
|
|
694
|
-
"",
|
|
695
|
-
" for (const file of files) {",
|
|
696
|
-
" const content = readFileSync(file, 'utf8');",
|
|
697
|
-
"",
|
|
698
|
-
" try {",
|
|
699
|
-
" JSON.parse(content);",
|
|
700
|
-
" detectDuplicateJsonKeys(content, file);",
|
|
701
|
-
" } catch (error) {",
|
|
702
|
-
" process.stdout.write(file + ': Failed to json decode (' + error.message + ')\\n');",
|
|
703
|
-
" rc = 1;",
|
|
704
|
-
" }",
|
|
705
|
-
" }",
|
|
706
|
-
"",
|
|
707
|
-
" return rc;",
|
|
708
|
-
"}",
|
|
709
|
-
"",
|
|
710
|
-
"function detectDuplicateJsonKeys(source, file) {",
|
|
711
|
-
" // Minimal tokeniser that walks the already-valid JSON source and throws",
|
|
712
|
-
" // with a message compatible with the Python hook when a duplicate key",
|
|
713
|
-
" // appears at any object level.",
|
|
714
|
-
" let i = 0;",
|
|
715
|
-
" const length = source.length;",
|
|
716
|
-
"",
|
|
717
|
-
" const skipWs = function () {",
|
|
718
|
-
" while (i < length && /\\s/.test(source[i])) {",
|
|
719
|
-
" i += 1;",
|
|
720
|
-
" }",
|
|
721
|
-
" };",
|
|
722
|
-
"",
|
|
723
|
-
" const parseString = function () {",
|
|
724
|
-
` if (source[i] !== '"') {`,
|
|
725
|
-
" throw new Error('expected string at ' + i);",
|
|
726
|
-
" }",
|
|
727
|
-
" i += 1;",
|
|
728
|
-
" let start = i;",
|
|
729
|
-
` while (i < length && source[i] !== '"') {`,
|
|
730
|
-
" if (source[i] === '\\\\') {",
|
|
731
|
-
" i += 2;",
|
|
732
|
-
" } else {",
|
|
733
|
-
" i += 1;",
|
|
734
|
-
" }",
|
|
735
|
-
" }",
|
|
736
|
-
" const raw = source.slice(start, i);",
|
|
737
|
-
" i += 1;",
|
|
738
|
-
` return JSON.parse('"' + raw + '"');`,
|
|
739
|
-
" };",
|
|
740
|
-
"",
|
|
741
|
-
" const parseValue = function () {",
|
|
742
|
-
" skipWs();",
|
|
743
|
-
" const ch = source[i];",
|
|
744
|
-
" if (ch === '{') { parseObject(); }",
|
|
745
|
-
" else if (ch === '[') { parseArray(); }",
|
|
746
|
-
` else if (ch === '"') { parseString(); }`,
|
|
747
|
-
" else {",
|
|
748
|
-
" while (i < length && ',}]'.indexOf(source[i]) === -1 && !/\\s/.test(source[i])) {",
|
|
749
|
-
" i += 1;",
|
|
750
|
-
" }",
|
|
751
|
-
" }",
|
|
752
|
-
" };",
|
|
753
|
-
"",
|
|
754
|
-
" const parseArray = function () {",
|
|
755
|
-
" i += 1;",
|
|
756
|
-
" skipWs();",
|
|
757
|
-
" if (source[i] === ']') { i += 1; return; }",
|
|
758
|
-
" while (i < length) {",
|
|
759
|
-
" parseValue();",
|
|
760
|
-
" skipWs();",
|
|
761
|
-
" if (source[i] === ',') { i += 1; skipWs(); }",
|
|
762
|
-
" else if (source[i] === ']') { i += 1; return; }",
|
|
763
|
-
" }",
|
|
764
|
-
" };",
|
|
765
|
-
"",
|
|
766
|
-
" const parseObject = function () {",
|
|
767
|
-
" i += 1;",
|
|
768
|
-
" skipWs();",
|
|
769
|
-
" const seen = new Set();",
|
|
770
|
-
" if (source[i] === '}') { i += 1; return; }",
|
|
771
|
-
" while (i < length) {",
|
|
772
|
-
" skipWs();",
|
|
773
|
-
" const key = parseString();",
|
|
774
|
-
" if (seen.has(key)) {",
|
|
775
|
-
" throw new Error('Duplicate key: ' + key);",
|
|
776
|
-
" }",
|
|
777
|
-
" seen.add(key);",
|
|
778
|
-
" skipWs();",
|
|
779
|
-
" if (source[i] !== ':') {",
|
|
780
|
-
" throw new Error('expected colon at ' + i);",
|
|
781
|
-
" }",
|
|
782
|
-
" i += 1;",
|
|
783
|
-
" parseValue();",
|
|
784
|
-
" skipWs();",
|
|
785
|
-
" if (source[i] === ',') { i += 1; skipWs(); }",
|
|
786
|
-
" else if (source[i] === '}') { i += 1; return; }",
|
|
787
|
-
" }",
|
|
788
|
-
" };",
|
|
789
|
-
"",
|
|
790
|
-
" skipWs();",
|
|
791
|
-
" parseValue();",
|
|
792
|
-
"}",
|
|
793
|
-
"",
|
|
794
|
-
"function runMixedLineEnding(files, args) {",
|
|
795
|
-
" // Mirrors pre-commit/pre-commit-hooks/mixed_line_ending.py.",
|
|
796
|
-
" const ENDING = { cr: Buffer.from([0x0d]), crlf: Buffer.from([0x0d, 0x0a]), lf: Buffer.from([0x0a]) };",
|
|
797
|
-
" let fixArg = 'auto';",
|
|
798
|
-
"",
|
|
799
|
-
" for (let idx = 0; idx < args.length; idx += 1) {",
|
|
800
|
-
" const a = args[idx];",
|
|
801
|
-
" if (a === '-f' || a === '--fix') {",
|
|
802
|
-
" idx += 1;",
|
|
803
|
-
" fixArg = args[idx];",
|
|
804
|
-
" } else if (a.indexOf('--fix=') === 0) {",
|
|
805
|
-
" fixArg = a.slice('--fix='.length);",
|
|
806
|
-
" }",
|
|
807
|
-
" }",
|
|
808
|
-
"",
|
|
809
|
-
" let rc = 0;",
|
|
810
|
-
"",
|
|
811
|
-
" for (const file of files) {",
|
|
812
|
-
" const buf = readFileSync(file);",
|
|
813
|
-
" const counts = { cr: 0, crlf: 0, lf: 0 };",
|
|
814
|
-
" const lines = [];",
|
|
815
|
-
" let start = 0;",
|
|
816
|
-
"",
|
|
817
|
-
" for (let i = 0; i < buf.length; i += 1) {",
|
|
818
|
-
" const b = buf[i];",
|
|
819
|
-
"",
|
|
820
|
-
" if (b === 0x0d && buf[i + 1] === 0x0a) {",
|
|
821
|
-
" lines.push({ content: buf.subarray(start, i), ending: 'crlf' });",
|
|
822
|
-
" counts.crlf += 1;",
|
|
823
|
-
" i += 1;",
|
|
824
|
-
" start = i + 1;",
|
|
825
|
-
" } else if (b === 0x0d) {",
|
|
826
|
-
" lines.push({ content: buf.subarray(start, i), ending: 'cr' });",
|
|
827
|
-
" counts.cr += 1;",
|
|
828
|
-
" start = i + 1;",
|
|
829
|
-
" } else if (b === 0x0a) {",
|
|
830
|
-
" lines.push({ content: buf.subarray(start, i), ending: 'lf' });",
|
|
831
|
-
" counts.lf += 1;",
|
|
832
|
-
" start = i + 1;",
|
|
833
|
-
" }",
|
|
834
|
-
" }",
|
|
835
|
-
"",
|
|
836
|
-
" if (start < buf.length) {",
|
|
837
|
-
" lines.push({ content: buf.subarray(start), ending: null });",
|
|
838
|
-
" }",
|
|
839
|
-
"",
|
|
840
|
-
" const distinct = Object.values(counts).filter(function (c) { return c > 0; }).length;",
|
|
841
|
-
" const mixed = distinct > 1;",
|
|
842
|
-
"",
|
|
843
|
-
" if (fixArg === 'no') {",
|
|
844
|
-
" if (mixed) {",
|
|
845
|
-
" process.stdout.write(file + ': mixed line endings\\n');",
|
|
846
|
-
" rc = 1;",
|
|
847
|
-
" }",
|
|
848
|
-
" continue;",
|
|
849
|
-
" }",
|
|
850
|
-
"",
|
|
851
|
-
" let target;",
|
|
852
|
-
"",
|
|
853
|
-
" if (fixArg === 'auto') {",
|
|
854
|
-
" if (!mixed) { continue; }",
|
|
855
|
-
" let max = -1;",
|
|
856
|
-
" for (const key of ['cr', 'crlf', 'lf']) {",
|
|
857
|
-
" if (counts[key] >= max) {",
|
|
858
|
-
" max = counts[key];",
|
|
859
|
-
" target = key;",
|
|
860
|
-
" }",
|
|
861
|
-
" }",
|
|
862
|
-
" } else if (!(fixArg in ENDING)) {",
|
|
863
|
-
" process.stderr.write('prek-runner: invalid --fix value ' + fixArg + '\\n');",
|
|
864
|
-
" return 2;",
|
|
865
|
-
" } else {",
|
|
866
|
-
" target = fixArg;",
|
|
867
|
-
" const other = Object.entries(counts).some(function (entry) { return entry[0] !== target && entry[1] > 0; });",
|
|
868
|
-
" if (!other) { continue; }",
|
|
869
|
-
" }",
|
|
870
|
-
"",
|
|
871
|
-
" const ending = ENDING[target];",
|
|
872
|
-
" const chunks = [];",
|
|
873
|
-
"",
|
|
874
|
-
" for (const line of lines) {",
|
|
875
|
-
" chunks.push(line.content);",
|
|
876
|
-
" if (line.ending !== null) {",
|
|
877
|
-
" chunks.push(ending);",
|
|
878
|
-
" }",
|
|
879
|
-
" }",
|
|
880
|
-
"",
|
|
881
|
-
" writeFileSync(file, Buffer.concat(chunks));",
|
|
882
|
-
" process.stdout.write(file + ': fixed mixed line endings\\n');",
|
|
883
|
-
" rc = 1;",
|
|
884
|
-
" }",
|
|
885
|
-
"",
|
|
886
|
-
" return rc;",
|
|
887
|
-
"}",
|
|
888
|
-
"",
|
|
889
|
-
"// ─── Entry point ────────────────────────────────────────────────────",
|
|
890
|
-
"",
|
|
891
|
-
"const parsed = parseArgs(process.argv.slice(2));",
|
|
892
|
-
"const candidateFiles = discoverFiles(parsed.flags);",
|
|
893
|
-
"const filtered = applyFilters(candidateFiles, parsed.flags);",
|
|
894
|
-
"",
|
|
895
|
-
"if (filtered.length === 0 && !parsed.flags.alwaysRun) {",
|
|
896
|
-
" process.exit(0);",
|
|
897
|
-
"}",
|
|
898
|
-
"",
|
|
899
|
-
"let code;",
|
|
900
|
-
"",
|
|
901
|
-
"if (parsed.flags.builtin) {",
|
|
902
|
-
" const impl = BUILTINS[parsed.flags.builtin];",
|
|
903
|
-
" if (!impl) {",
|
|
904
|
-
" process.stderr.write('prek-runner: unknown builtin ' + parsed.flags.builtin + '\\n');",
|
|
905
|
-
" process.exit(2);",
|
|
906
|
-
" }",
|
|
907
|
-
" code = impl(filtered, parsed.rest);",
|
|
908
|
-
"} else {",
|
|
909
|
-
" code = runCommand(parsed.rest, filtered, parsed.flags.passFilenames);",
|
|
910
|
-
"}",
|
|
911
|
-
"",
|
|
912
|
-
"process.exit(code);",
|
|
913
|
-
""
|
|
914
|
-
];
|
|
915
|
-
const PREK_RUNNER_SOURCE = PREK_RUNNER_LINES.join("\n");
|
|
916
|
-
|
|
917
|
-
const REMOTE_HOOK_BUILTIN_MAP = /* @__PURE__ */ new Map([
|
|
918
|
-
["pre-commit/pre-commit-hooks#check-json", "check-json"],
|
|
919
|
-
["pre-commit/pre-commit-hooks#check-merge-conflict", "check-merge-conflict"],
|
|
920
|
-
["pre-commit/pre-commit-hooks#end-of-file-fixer", "end-of-file-fixer"],
|
|
921
|
-
["pre-commit/pre-commit-hooks#mixed-line-ending", "mixed-line-ending"],
|
|
922
|
-
["pre-commit/pre-commit-hooks#trailing-whitespace", "trailing-whitespace"]
|
|
923
|
-
]);
|
|
924
|
-
const PIP_PIN_RE = /[<>=!~]=/;
|
|
925
|
-
const GITHUB_REPO_RE = /github\.com[/:]([^/\s]+\/[^/\s.]+)/i;
|
|
926
|
-
const RUNNER_INVOCATION_HEAD = `node "$(dirname "$0")/.builtins/${PREK_RUNNER_FILENAME}"`;
|
|
927
|
-
const AUTO_GENERATED_HEADER = "# Generated by `vis hook migrate` from prek";
|
|
928
|
-
const shellQuote = (value) => `'${value.replaceAll("'", String.raw`'\''`)}'`;
|
|
929
|
-
const detectPrekConfig = (root) => {
|
|
930
|
-
for (const file of PREK_CONFIG_FILES) {
|
|
931
|
-
if (isAccessibleSync(join(root, file))) {
|
|
932
|
-
return file;
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
return void 0;
|
|
936
|
-
};
|
|
937
|
-
const mapPrekStage = (stage) => PREK_STAGE_ALIASES[stage] ?? stage;
|
|
938
|
-
const normalizeRepoKey = (url) => {
|
|
939
|
-
const match = GITHUB_REPO_RE.exec(url);
|
|
940
|
-
return match?.[1] ?? url;
|
|
941
|
-
};
|
|
942
|
-
const parseAdditionalDep = (spec) => {
|
|
943
|
-
if (PIP_PIN_RE.test(spec)) {
|
|
944
|
-
return void 0;
|
|
945
|
-
}
|
|
946
|
-
if (spec.startsWith("@")) {
|
|
947
|
-
const at2 = spec.indexOf("@", 1);
|
|
948
|
-
if (at2 === -1) {
|
|
949
|
-
return { name: spec, version: "latest" };
|
|
950
|
-
}
|
|
951
|
-
const version2 = spec.slice(at2 + 1).trim();
|
|
952
|
-
return { name: spec.slice(0, at2), version: version2 || "latest" };
|
|
953
|
-
}
|
|
954
|
-
const at = spec.indexOf("@");
|
|
955
|
-
if (at === -1) {
|
|
956
|
-
return { name: spec, version: "latest" };
|
|
957
|
-
}
|
|
958
|
-
const version = spec.slice(at + 1).trim();
|
|
959
|
-
return { name: spec.slice(0, at), version: version || "latest" };
|
|
960
|
-
};
|
|
961
|
-
const KNOWN_TAG_SET = new Set(KNOWN_TYPE_TAGS);
|
|
962
|
-
const unknownTypes = (entry) => {
|
|
963
|
-
const unknown = [];
|
|
964
|
-
for (const list of [entry.types, entry.types_or, entry.exclude_types]) {
|
|
965
|
-
for (const type of list ?? []) {
|
|
966
|
-
if (!KNOWN_TAG_SET.has(type)) {
|
|
967
|
-
unknown.push(type);
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
return unknown;
|
|
972
|
-
};
|
|
973
|
-
const resolveStages = (entry, defaultStages) => {
|
|
974
|
-
const raw = entry.stages && entry.stages.length > 0 ? entry.stages : defaultStages ?? ["pre-commit"];
|
|
975
|
-
return raw.map((stage) => mapPrekStage(stage));
|
|
976
|
-
};
|
|
977
|
-
const buildRunnerFilterFlags = (entry) => {
|
|
978
|
-
const flags = [];
|
|
979
|
-
if (entry.files) {
|
|
980
|
-
flags.push("--files", shellQuote(entry.files));
|
|
981
|
-
}
|
|
982
|
-
if (entry.exclude) {
|
|
983
|
-
flags.push("--exclude", shellQuote(entry.exclude));
|
|
984
|
-
}
|
|
985
|
-
if (entry.types && entry.types.length > 0) {
|
|
986
|
-
flags.push("--types", shellQuote(entry.types.join(",")));
|
|
987
|
-
}
|
|
988
|
-
if (entry.types_or && entry.types_or.length > 0) {
|
|
989
|
-
flags.push("--types-or", shellQuote(entry.types_or.join(",")));
|
|
990
|
-
}
|
|
991
|
-
if (entry.exclude_types && entry.exclude_types.length > 0) {
|
|
992
|
-
flags.push("--exclude-types", shellQuote(entry.exclude_types.join(",")));
|
|
993
|
-
}
|
|
994
|
-
if (entry.always_run) {
|
|
995
|
-
flags.push("--always-run");
|
|
996
|
-
}
|
|
997
|
-
if (entry.pass_filenames === false) {
|
|
998
|
-
flags.push("--no-pass-filenames");
|
|
999
|
-
}
|
|
1000
|
-
return flags;
|
|
1001
|
-
};
|
|
1002
|
-
const buildRunnerInvocation = (entry, builtin) => {
|
|
1003
|
-
const parts = [RUNNER_INVOCATION_HEAD, ...buildRunnerFilterFlags(entry)];
|
|
1004
|
-
if (builtin) {
|
|
1005
|
-
parts.push("--builtin", builtin);
|
|
1006
|
-
if (Array.isArray(entry.args) && entry.args.length > 0) {
|
|
1007
|
-
parts.push("--", ...entry.args.map((arg) => shellQuote(arg)));
|
|
1008
|
-
}
|
|
1009
|
-
return parts.join(" ");
|
|
1010
|
-
}
|
|
1011
|
-
parts.push("--", entry.entry ?? "");
|
|
1012
|
-
if (Array.isArray(entry.args)) {
|
|
1013
|
-
for (const arg of entry.args) {
|
|
1014
|
-
parts.push(shellQuote(arg));
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
return parts.join(" ");
|
|
1018
|
-
};
|
|
1019
|
-
const buildHookCommand = (entry, stage, builtin) => {
|
|
1020
|
-
if (entry.language === "fail") {
|
|
1021
|
-
const message = entry.entry ?? entry.name ?? entry.id ?? "hook failed";
|
|
1022
|
-
return `echo ${shellQuote(message)}; exit 1`;
|
|
1023
|
-
}
|
|
1024
|
-
if (builtin) {
|
|
1025
|
-
return buildRunnerInvocation(entry, builtin);
|
|
1026
|
-
}
|
|
1027
|
-
if (PREK_STAGES_WITH_GIT_ARGS.has(stage)) {
|
|
1028
|
-
const parts = [];
|
|
1029
|
-
if (entry.entry) {
|
|
1030
|
-
parts.push(entry.entry);
|
|
1031
|
-
}
|
|
1032
|
-
if (Array.isArray(entry.args)) {
|
|
1033
|
-
for (const arg of entry.args) {
|
|
1034
|
-
parts.push(shellQuote(arg));
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
if ((entry.pass_filenames ?? true) && !entry.always_run) {
|
|
1038
|
-
parts.push('"$@"');
|
|
1039
|
-
}
|
|
1040
|
-
return parts.join(" ");
|
|
1041
|
-
}
|
|
1042
|
-
return buildRunnerInvocation(entry);
|
|
1043
|
-
};
|
|
1044
|
-
const collectAdditionalDeps = (entry, hookId, additionalDeps, manualSteps) => {
|
|
1045
|
-
if (!Array.isArray(entry.additional_dependencies)) {
|
|
1046
|
-
return;
|
|
1047
|
-
}
|
|
1048
|
-
for (const spec of entry.additional_dependencies) {
|
|
1049
|
-
const parsed = parseAdditionalDep(spec);
|
|
1050
|
-
if (!parsed) {
|
|
1051
|
-
manualSteps.push(`"${hookId}": additional_dependency "${spec}" uses a pip-style pin and cannot be added to package.json — install manually.`);
|
|
1052
|
-
continue;
|
|
1053
|
-
}
|
|
1054
|
-
additionalDeps.push({ hookId, name: parsed.name, raw: spec, version: parsed.version });
|
|
1055
|
-
}
|
|
1056
|
-
};
|
|
1057
|
-
const convertPrekConfig = (config) => {
|
|
1058
|
-
const stageBlocks = /* @__PURE__ */ new Map();
|
|
1059
|
-
const skippedHooks = [];
|
|
1060
|
-
const droppedFilters = [];
|
|
1061
|
-
const manualSteps = [];
|
|
1062
|
-
const additionalDeps = [];
|
|
1063
|
-
let usesRunner = false;
|
|
1064
|
-
if (config.files || config.exclude) {
|
|
1065
|
-
droppedFilters.push("top-level files/exclude filter dropped — apply it per hook if needed");
|
|
1066
|
-
}
|
|
1067
|
-
for (const repo of config.repos ?? []) {
|
|
1068
|
-
const repoName = repo.repo ?? "<unknown>";
|
|
1069
|
-
const isLocal = repoName === "local";
|
|
1070
|
-
const repoKey = isLocal ? void 0 : normalizeRepoKey(repoName);
|
|
1071
|
-
for (const hook of repo.hooks ?? []) {
|
|
1072
|
-
const hookId = hook.id ?? "<unknown>";
|
|
1073
|
-
let builtin;
|
|
1074
|
-
if (isLocal) {
|
|
1075
|
-
const language = hook.language ?? "system";
|
|
1076
|
-
if (!PREK_TRANSLATABLE_LANGUAGES.has(language)) {
|
|
1077
|
-
skippedHooks.push({
|
|
1078
|
-
hookId,
|
|
1079
|
-
reason: `language "${language}" needs an isolated toolchain — run via prek or reimplement as a system command`,
|
|
1080
|
-
repo: repoName
|
|
1081
|
-
});
|
|
1082
|
-
continue;
|
|
1083
|
-
}
|
|
1084
|
-
if (language !== "fail" && !hook.entry) {
|
|
1085
|
-
skippedHooks.push({ hookId, reason: "missing `entry`", repo: repoName });
|
|
1086
|
-
continue;
|
|
1087
|
-
}
|
|
1088
|
-
} else {
|
|
1089
|
-
if (repoKey) {
|
|
1090
|
-
builtin = REMOTE_HOOK_BUILTIN_MAP.get(`${repoKey}#${hookId}`);
|
|
1091
|
-
}
|
|
1092
|
-
if (!builtin) {
|
|
1093
|
-
skippedHooks.push({
|
|
1094
|
-
hookId,
|
|
1095
|
-
reason: `remote repo "${repoName}"@${repo.rev ?? "?"} has no bundled equivalent — run via prek or replace with a system command`,
|
|
1096
|
-
repo: repoName
|
|
1097
|
-
});
|
|
1098
|
-
continue;
|
|
1099
|
-
}
|
|
1100
|
-
}
|
|
1101
|
-
collectAdditionalDeps(hook, hookId, additionalDeps, manualSteps);
|
|
1102
|
-
const unknownTypeNames = unknownTypes(hook);
|
|
1103
|
-
if (unknownTypeNames.length > 0) {
|
|
1104
|
-
droppedFilters.push(`hook "${hookId}": unsupported types ${unknownTypeNames.join(", ")} — those entries are ignored by the runner`);
|
|
1105
|
-
}
|
|
1106
|
-
const stages = resolveStages(hook, config.default_stages);
|
|
1107
|
-
for (const stage of stages) {
|
|
1108
|
-
if (stage === "manual") {
|
|
1109
|
-
continue;
|
|
1110
|
-
}
|
|
1111
|
-
if (!PREK_SUPPORTED_STAGES.has(stage)) {
|
|
1112
|
-
skippedHooks.push({ hookId, reason: `unsupported stage "${stage}"`, repo: repoName });
|
|
1113
|
-
continue;
|
|
1114
|
-
}
|
|
1115
|
-
let command = buildHookCommand(hook, stage, builtin);
|
|
1116
|
-
if (command.startsWith(RUNNER_INVOCATION_HEAD)) {
|
|
1117
|
-
usesRunner = true;
|
|
1118
|
-
}
|
|
1119
|
-
if (hook.verbose) {
|
|
1120
|
-
command = `(set -x; ${command})`;
|
|
1121
|
-
}
|
|
1122
|
-
const header = `# ${hookId}${hook.name ? `: ${hook.name}` : ""}`;
|
|
1123
|
-
const block = `${header}
|
|
1124
|
-
${command}`;
|
|
1125
|
-
const existing = stageBlocks.get(stage);
|
|
1126
|
-
if (existing) {
|
|
1127
|
-
existing.push(block);
|
|
1128
|
-
} else {
|
|
1129
|
-
stageBlocks.set(stage, [block]);
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
|
-
const scripts = /* @__PURE__ */ new Map();
|
|
1135
|
-
for (const [stage, blocks] of stageBlocks) {
|
|
1136
|
-
const bodyLines = ["#!/usr/bin/env sh", AUTO_GENERATED_HEADER];
|
|
1137
|
-
if (config.fail_fast) {
|
|
1138
|
-
bodyLines.push("set -e");
|
|
1139
|
-
}
|
|
1140
|
-
bodyLines.push("", blocks.join("\n\n"), "");
|
|
1141
|
-
scripts.set(stage, bodyLines.join("\n"));
|
|
1142
|
-
}
|
|
1143
|
-
return {
|
|
1144
|
-
additionalDeps,
|
|
1145
|
-
droppedFilters,
|
|
1146
|
-
manualSteps,
|
|
1147
|
-
scripts,
|
|
1148
|
-
skippedHooks,
|
|
1149
|
-
usesRunner
|
|
1150
|
-
};
|
|
1151
|
-
};
|
|
1152
|
-
const parsePrekConfig = (content) => {
|
|
1153
|
-
const parsed = parse(content);
|
|
1154
|
-
if (parsed && typeof parsed === "object") {
|
|
1155
|
-
return parsed;
|
|
1156
|
-
}
|
|
1157
|
-
return void 0;
|
|
1158
|
-
};
|
|
1159
|
-
const loadPrekConfig = (configPath) => {
|
|
1160
|
-
if (configPath.endsWith(".toml")) {
|
|
1161
|
-
const parsed = readTomlSync(configPath);
|
|
1162
|
-
if (parsed && typeof parsed === "object") {
|
|
1163
|
-
return parsed;
|
|
1164
|
-
}
|
|
1165
|
-
return void 0;
|
|
1166
|
-
}
|
|
1167
|
-
const content = readFileSync(configPath);
|
|
1168
|
-
return parsePrekConfig(content);
|
|
1169
|
-
};
|
|
1170
|
-
const mergeAdditionalDependencies = (root, deps) => {
|
|
1171
|
-
const packageJsonPath = join(root, "package.json");
|
|
1172
|
-
const added = [];
|
|
1173
|
-
const skipped = [];
|
|
1174
|
-
if (!isAccessibleSync(packageJsonPath) || deps.length === 0) {
|
|
1175
|
-
return { added, skipped };
|
|
1176
|
-
}
|
|
1177
|
-
const content = readFileSync(packageJsonPath);
|
|
1178
|
-
const pkg = JSON.parse(content);
|
|
1179
|
-
const devDeps = pkg["devDependencies"] ?? {};
|
|
1180
|
-
const runtimeDeps = pkg["dependencies"] ?? {};
|
|
1181
|
-
for (const dep of deps) {
|
|
1182
|
-
if (dep.name in devDeps || dep.name in runtimeDeps) {
|
|
1183
|
-
skipped.push(dep.name);
|
|
1184
|
-
continue;
|
|
1185
|
-
}
|
|
1186
|
-
devDeps[dep.name] = dep.version;
|
|
1187
|
-
added.push(dep.name);
|
|
1188
|
-
}
|
|
1189
|
-
if (added.length === 0) {
|
|
1190
|
-
return { added, skipped };
|
|
1191
|
-
}
|
|
1192
|
-
pkg["devDependencies"] = devDeps;
|
|
1193
|
-
const indentMatch = /^(\s+)"/m.exec(content);
|
|
1194
|
-
const indent = indentMatch ? indentMatch[1] : " ";
|
|
1195
|
-
writeFileSync(packageJsonPath, `${JSON.stringify(pkg, void 0, indent)}
|
|
1196
|
-
`, "utf8");
|
|
1197
|
-
return { added, skipped };
|
|
1198
|
-
};
|
|
1199
|
-
const writeRunnerAssets = (root, hooksDirectory) => {
|
|
1200
|
-
const builtinsDirectory = join(root, hooksDirectory, ".builtins");
|
|
1201
|
-
ensureDirSync(builtinsDirectory);
|
|
1202
|
-
writeFileSync(join(builtinsDirectory, PREK_RUNNER_FILENAME), PREK_RUNNER_SOURCE, { mode: 493 });
|
|
1203
|
-
writeFileSync(
|
|
1204
|
-
join(builtinsDirectory, "README.md"),
|
|
1205
|
-
[
|
|
1206
|
-
"# Vis prek runner",
|
|
1207
|
-
"",
|
|
1208
|
-
"Auto-generated by `vis hook migrate` from a prek/pre-commit config.",
|
|
1209
|
-
"This directory is owned by the migrator — do not edit by hand.",
|
|
1210
|
-
"",
|
|
1211
|
-
`Supported built-in hooks: ${BUILTIN_HOOK_IDS.join(", ")}`,
|
|
1212
|
-
""
|
|
1213
|
-
].join("\n"),
|
|
1214
|
-
"utf8"
|
|
1215
|
-
);
|
|
1216
|
-
};
|
|
1217
|
-
const detachPrek = (root, logger) => {
|
|
1218
|
-
const prekCheck = spawnSync("prek", ["--version"], { cwd: root, encoding: "utf8" });
|
|
1219
|
-
if (prekCheck.status === 0) {
|
|
1220
|
-
const uninstall = spawnSync("prek", ["uninstall"], { cwd: root, encoding: "utf8" });
|
|
1221
|
-
if (uninstall.status === 0) {
|
|
1222
|
-
logger.info("Detached prek via `prek uninstall`.");
|
|
1223
|
-
} else {
|
|
1224
|
-
logger.info("`prek uninstall` did not exit cleanly — continuing. You may need to run it manually.");
|
|
1225
|
-
}
|
|
1226
|
-
} else {
|
|
1227
|
-
logger.info("prek binary not found on PATH — skipping `prek uninstall`. Run it manually if prek is installed elsewhere.");
|
|
1228
|
-
}
|
|
1229
|
-
};
|
|
1230
|
-
const migrateFromPrek = (root, hooksDirectory, logger, options = {}) => {
|
|
1231
|
-
const configFile = detectPrekConfig(root);
|
|
1232
|
-
const dryRun = options.dryRun === true;
|
|
1233
|
-
if (!configFile) {
|
|
1234
|
-
return { isError: true, message: "No prek configuration found (.pre-commit-config.yaml, .pre-commit-config.yml, or prek.toml)" };
|
|
1235
|
-
}
|
|
1236
|
-
logger.info(`Found prek config at ${configFile}`);
|
|
1237
|
-
const configPath = join(root, configFile);
|
|
1238
|
-
const rawContent = readFileSync(configPath);
|
|
1239
|
-
const config = loadPrekConfig(configPath);
|
|
1240
|
-
if (!config) {
|
|
1241
|
-
return { isError: true, message: `Could not parse ${configFile}` };
|
|
1242
|
-
}
|
|
1243
|
-
const { additionalDeps, droppedFilters, manualSteps, scripts, skippedHooks, usesRunner } = convertPrekConfig(config);
|
|
1244
|
-
if (scripts.size === 0 && skippedHooks.length === 0) {
|
|
1245
|
-
return { isError: true, message: `${configFile} has no hooks to migrate` };
|
|
1246
|
-
}
|
|
1247
|
-
if (!dryRun) {
|
|
1248
|
-
const existingPath = spawnSync("git", ["config", "--local", "core.hooksPath"], { cwd: root, encoding: "utf8" });
|
|
1249
|
-
if (existingPath.status === 0) {
|
|
1250
|
-
const current = existingPath.stdout?.toString().trim();
|
|
1251
|
-
if (current && (current.includes(".prek") || current.includes("prek-hooks"))) {
|
|
1252
|
-
spawnSync("git", ["config", "--local", "--unset", "core.hooksPath"], { cwd: root });
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
const installResult = installHooks(hooksDirectory);
|
|
1256
|
-
if (installResult.isError) {
|
|
1257
|
-
return installResult;
|
|
1258
|
-
}
|
|
1259
|
-
if (installResult.message) {
|
|
1260
|
-
logger.info(installResult.message);
|
|
1261
|
-
}
|
|
1262
|
-
}
|
|
1263
|
-
const targetDirectory = join(root, hooksDirectory);
|
|
1264
|
-
if (!dryRun) {
|
|
1265
|
-
ensureDirSync(targetDirectory);
|
|
1266
|
-
}
|
|
1267
|
-
if (usesRunner) {
|
|
1268
|
-
if (dryRun) {
|
|
1269
|
-
logger.info(` (would write) ${hooksDirectory}/.builtins/${PREK_RUNNER_FILENAME}`);
|
|
1270
|
-
} else {
|
|
1271
|
-
writeRunnerAssets(root, hooksDirectory);
|
|
1272
|
-
logger.info(` Wrote ${hooksDirectory}/.builtins/${PREK_RUNNER_FILENAME}`);
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
let migratedCount = 0;
|
|
1276
|
-
for (const [stage, body] of scripts) {
|
|
1277
|
-
if (dryRun) {
|
|
1278
|
-
logger.info(` (would write) ${hooksDirectory}/${stage} (${body.split("\n").length} lines)`);
|
|
1279
|
-
} else {
|
|
1280
|
-
writeFileSync(join(targetDirectory, stage), body, { mode: 493 });
|
|
1281
|
-
logger.info(` Wrote ${hooksDirectory}/${stage}`);
|
|
1282
|
-
}
|
|
1283
|
-
migratedCount += 1;
|
|
1284
|
-
}
|
|
1285
|
-
const { added, skipped: skippedDeps } = dryRun ? {
|
|
1286
|
-
added: additionalDeps.map((d) => d.name),
|
|
1287
|
-
skipped: []
|
|
1288
|
-
} : mergeAdditionalDependencies(root, additionalDeps);
|
|
1289
|
-
if (added.length > 0) {
|
|
1290
|
-
const verb = dryRun ? "would add" : "Added";
|
|
1291
|
-
logger.info(`${verb} ${added.length} package${added.length === 1 ? "" : "s"} to devDependencies: ${added.join(", ")}`);
|
|
1292
|
-
if (!dryRun) {
|
|
1293
|
-
logger.info("Run your package manager's install (e.g. `pnpm install`) to pick up the new devDependencies.");
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
if (skippedDeps.length > 0) {
|
|
1297
|
-
logger.info(`Skipped ${skippedDeps.length} already-declared package${skippedDeps.length === 1 ? "" : "s"}: ${skippedDeps.join(", ")}`);
|
|
1298
|
-
}
|
|
1299
|
-
if (!dryRun) {
|
|
1300
|
-
detachPrek(root, logger);
|
|
1301
|
-
}
|
|
1302
|
-
const backupPath = `${configPath}.bak`;
|
|
1303
|
-
if (dryRun) {
|
|
1304
|
-
logger.info(` (would remove) ${configFile} and back it up to ${configFile}.bak`);
|
|
1305
|
-
} else {
|
|
1306
|
-
if (!isAccessibleSync(backupPath)) {
|
|
1307
|
-
writeFileSync(backupPath, rawContent, "utf8");
|
|
1308
|
-
}
|
|
1309
|
-
unlinkSync(configPath);
|
|
1310
|
-
logger.info(`Removed ${configFile} (backup at ${configFile}.bak)`);
|
|
1311
|
-
}
|
|
1312
|
-
if (skippedHooks.length > 0) {
|
|
1313
|
-
logger.warn(`Skipped ${skippedHooks.length} hook${skippedHooks.length === 1 ? "" : "s"} that cannot run without prek:`);
|
|
1314
|
-
for (const skipped of skippedHooks) {
|
|
1315
|
-
logger.warn(` - ${skipped.repo}::${skipped.hookId} — ${skipped.reason}`);
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
|
-
if (droppedFilters.length > 0) {
|
|
1319
|
-
logger.warn("Partial filter translations:");
|
|
1320
|
-
for (const note of droppedFilters) {
|
|
1321
|
-
logger.warn(` - ${note}`);
|
|
1322
|
-
}
|
|
1323
|
-
}
|
|
1324
|
-
if (manualSteps.length > 0) {
|
|
1325
|
-
logger.warn("Manual follow-up required:");
|
|
1326
|
-
for (const step of manualSteps) {
|
|
1327
|
-
logger.warn(` - ${step}`);
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
const verbose = dryRun ? "would migrate" : "Migration complete:";
|
|
1331
|
-
return {
|
|
1332
|
-
isError: false,
|
|
1333
|
-
message: `${verbose} ${migratedCount} stage script${migratedCount === 1 ? "" : "s"} ${dryRun ? "into" : "written to"} ${hooksDirectory}/`
|
|
1334
|
-
};
|
|
1335
|
-
};
|
|
1336
|
-
|
|
1337
|
-
const DEFAULT_STAGE = "pre-commit";
|
|
1338
|
-
const runHookStage = (root, hooksDirectory, options, logger) => {
|
|
1339
|
-
const stage = options.stage ?? DEFAULT_STAGE;
|
|
1340
|
-
const scriptPath = join(root, hooksDirectory, stage);
|
|
1341
|
-
if (!isAccessibleSync(scriptPath)) {
|
|
1342
|
-
throw new Error(`No script found at ${hooksDirectory}/${stage}. Install or migrate hooks first.`);
|
|
1343
|
-
}
|
|
1344
|
-
if (options.lastCommit && (options.fromRef || options.toRef)) {
|
|
1345
|
-
throw new Error("--last-commit cannot be combined with --from-ref or --to-ref");
|
|
1346
|
-
}
|
|
1347
|
-
const fromRef = options.lastCommit ? "HEAD~1" : options.fromRef;
|
|
1348
|
-
const toRef = options.lastCommit ? "HEAD" : options.toRef;
|
|
1349
|
-
if (fromRef && !toRef) {
|
|
1350
|
-
throw new Error("--from-ref requires --to-ref");
|
|
1351
|
-
}
|
|
1352
|
-
if (toRef && !fromRef) {
|
|
1353
|
-
throw new Error("--to-ref requires --from-ref");
|
|
1354
|
-
}
|
|
1355
|
-
const env = { ...process.env };
|
|
1356
|
-
if (options.allFiles) {
|
|
1357
|
-
env["VIS_HOOK_ALL_FILES"] = "1";
|
|
1358
|
-
}
|
|
1359
|
-
if (fromRef) {
|
|
1360
|
-
env["VIS_HOOK_FROM_REF"] = fromRef;
|
|
1361
|
-
}
|
|
1362
|
-
if (toRef) {
|
|
1363
|
-
env["VIS_HOOK_TO_REF"] = toRef;
|
|
1364
|
-
}
|
|
1365
|
-
logger.info(`Running ${hooksDirectory}/${stage}${options.allFiles ? " (--all-files)" : ""}${fromRef ? ` (${fromRef}..${toRef})` : ""}`);
|
|
1366
|
-
const result = spawnSync("sh", ["-e", scriptPath], { cwd: root, env, stdio: "inherit" });
|
|
1367
|
-
if (result.error) {
|
|
1368
|
-
throw result.error;
|
|
1369
|
-
}
|
|
1370
|
-
return result.status ?? 1;
|
|
1371
|
-
};
|
|
1372
|
-
const runRun = (hooksDirectory, options, logger) => {
|
|
1373
|
-
const code = runHookStage(cwd(), hooksDirectory, options, logger);
|
|
1374
|
-
if (code !== 0) {
|
|
1375
|
-
throw new Error(`Hook stage exited with code ${code}`);
|
|
1376
|
-
}
|
|
1377
|
-
};
|
|
1378
|
-
|
|
1379
|
-
const uninstallHooks = (directory = DEFAULT_HOOKS_DIRECTORY) => {
|
|
1380
|
-
const checkResult = spawnSync("git", ["config", "--local", "core.hooksPath"]);
|
|
1381
|
-
if (checkResult.status !== 0) {
|
|
1382
|
-
return { isError: false, message: "No custom hooks path configured" };
|
|
1383
|
-
}
|
|
1384
|
-
const { status, stderr } = spawnSync("git", ["config", "--local", "--unset", "core.hooksPath"]);
|
|
1385
|
-
if (status === void 0 || status === null) {
|
|
1386
|
-
return { isError: true, message: "git command not found" };
|
|
1387
|
-
}
|
|
1388
|
-
if (status && status !== 5) {
|
|
1389
|
-
return { isError: true, message: String(stderr) };
|
|
1390
|
-
}
|
|
1391
|
-
const internalDirectory = join(directory, "_");
|
|
1392
|
-
if (isAccessibleSync(internalDirectory)) {
|
|
1393
|
-
rmSync(internalDirectory, { force: true, recursive: true });
|
|
1394
|
-
}
|
|
1395
|
-
return { isError: false, message: "" };
|
|
1396
|
-
};
|
|
1397
|
-
|
|
1398
|
-
const STAGE_SET = new Set(HOOKS);
|
|
1399
|
-
const runSyntaxCheck = (scriptPath) => {
|
|
1400
|
-
const result = spawnSync("sh", ["-n", scriptPath], { encoding: "utf8" });
|
|
1401
|
-
if (result.status === null) {
|
|
1402
|
-
return `failed to run "sh -n" (${result.error?.message ?? "unknown error"})`;
|
|
1403
|
-
}
|
|
1404
|
-
if (result.status !== 0) {
|
|
1405
|
-
return result.stderr.trim() || `sh -n exited with ${result.status}`;
|
|
1406
|
-
}
|
|
1407
|
-
return void 0;
|
|
1408
|
-
};
|
|
1409
|
-
const runNodeCheck = (scriptPath) => {
|
|
1410
|
-
const result = spawnSync("node", ["--check", scriptPath], { encoding: "utf8" });
|
|
1411
|
-
if (result.status === null) {
|
|
1412
|
-
return `failed to run "node --check" (${result.error?.message ?? "unknown error"})`;
|
|
1413
|
-
}
|
|
1414
|
-
if (result.status !== 0) {
|
|
1415
|
-
return result.stderr.trim() || `node --check exited with ${result.status}`;
|
|
1416
|
-
}
|
|
1417
|
-
return void 0;
|
|
1418
|
-
};
|
|
1419
|
-
const validateHooks = (root, hooksDirectory) => {
|
|
1420
|
-
const issues = [];
|
|
1421
|
-
const directory = join(root, hooksDirectory);
|
|
1422
|
-
const configResult = spawnSync("git", ["config", "--local", "core.hooksPath"], { cwd: root, encoding: "utf8" });
|
|
1423
|
-
if (configResult.status === 0) {
|
|
1424
|
-
const current = configResult.stdout.trim();
|
|
1425
|
-
const expected = `${hooksDirectory}/_`;
|
|
1426
|
-
if (current && current !== expected) {
|
|
1427
|
-
issues.push({ kind: "warning", message: `core.hooksPath is "${current}" — expected "${expected}". Re-run \`vis hook install\` to fix.` });
|
|
1428
|
-
}
|
|
1429
|
-
} else {
|
|
1430
|
-
issues.push({ kind: "warning", message: "core.hooksPath is not set — run `vis hook install`." });
|
|
1431
|
-
}
|
|
1432
|
-
if (!isAccessibleSync(join(directory, "_"))) {
|
|
1433
|
-
issues.push({ kind: "error", message: `Dispatcher directory ${hooksDirectory}/_ is missing. Run \`vis hook install\`.` });
|
|
1434
|
-
}
|
|
1435
|
-
if (!isAccessibleSync(directory)) {
|
|
1436
|
-
issues.push({ kind: "error", message: `Hooks directory ${hooksDirectory}/ is missing.` });
|
|
1437
|
-
return { issues, ok: false };
|
|
1438
|
-
}
|
|
1439
|
-
let referencesRunner = false;
|
|
1440
|
-
for (const entry of readdirSync(directory)) {
|
|
1441
|
-
if (entry.startsWith(".") || entry === "_") {
|
|
1442
|
-
continue;
|
|
1443
|
-
}
|
|
1444
|
-
if (!STAGE_SET.has(entry)) {
|
|
1445
|
-
issues.push({ kind: "warning", message: `Unknown hook "${entry}" — not a standard git hook.`, path: join(hooksDirectory, entry) });
|
|
1446
|
-
continue;
|
|
1447
|
-
}
|
|
1448
|
-
const stagePath = join(directory, entry);
|
|
1449
|
-
if (!statSync(stagePath).isFile()) {
|
|
1450
|
-
continue;
|
|
1451
|
-
}
|
|
1452
|
-
const mode = statSync(stagePath).mode & 511;
|
|
1453
|
-
if ((mode & 64) === 0) {
|
|
1454
|
-
issues.push({ kind: "warning", message: `Script is not owner-executable (mode ${mode.toString(8)}).`, path: join(hooksDirectory, entry) });
|
|
1455
|
-
}
|
|
1456
|
-
const syntaxError = runSyntaxCheck(stagePath);
|
|
1457
|
-
if (syntaxError) {
|
|
1458
|
-
issues.push({ kind: "error", message: `Shell syntax error: ${syntaxError}`, path: join(hooksDirectory, entry) });
|
|
1459
|
-
}
|
|
1460
|
-
const content = readFileSync(stagePath);
|
|
1461
|
-
if (content.includes(`/.builtins/${PREK_RUNNER_FILENAME}`)) {
|
|
1462
|
-
referencesRunner = true;
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
if (referencesRunner) {
|
|
1466
|
-
const runnerPath = join(directory, ".builtins", PREK_RUNNER_FILENAME);
|
|
1467
|
-
if (isAccessibleSync(runnerPath)) {
|
|
1468
|
-
const runnerError = runNodeCheck(runnerPath);
|
|
1469
|
-
if (runnerError) {
|
|
1470
|
-
issues.push({
|
|
1471
|
-
kind: "error",
|
|
1472
|
-
message: `prek-runner.mjs has a syntax error: ${runnerError}`,
|
|
1473
|
-
path: join(hooksDirectory, ".builtins", PREK_RUNNER_FILENAME)
|
|
1474
|
-
});
|
|
1475
|
-
}
|
|
1476
|
-
} else {
|
|
1477
|
-
issues.push({
|
|
1478
|
-
kind: "error",
|
|
1479
|
-
message: `Hook scripts reference ${hooksDirectory}/.builtins/${PREK_RUNNER_FILENAME} but the file is missing. Re-run \`vis hook migrate\`.`
|
|
1480
|
-
});
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
return { issues, ok: !issues.some((issue) => issue.kind === "error") };
|
|
1484
|
-
};
|
|
1485
|
-
const formatValidationResult = (result, hooksDirectory) => {
|
|
1486
|
-
if (result.issues.length === 0) {
|
|
1487
|
-
return [`Hook directory ${hooksDirectory}/ looks good.`];
|
|
1488
|
-
}
|
|
1489
|
-
const lines = [];
|
|
1490
|
-
for (const issue of result.issues) {
|
|
1491
|
-
const prefix = issue.kind === "error" ? "ERROR" : "WARN ";
|
|
1492
|
-
const pathSuffix = issue.path ? ` (${issue.path})` : "";
|
|
1493
|
-
lines.push(`${prefix} ${issue.message}${pathSuffix}`);
|
|
1494
|
-
}
|
|
1495
|
-
lines.push("", result.ok ? "No errors — warnings only." : `${result.issues.filter((i) => i.kind === "error").length} error(s).`);
|
|
1496
|
-
return lines;
|
|
1497
|
-
};
|
|
1498
|
-
const runValidate = (hooksDirectory, logger) => {
|
|
1499
|
-
const result = validateHooks(cwd(), hooksDirectory);
|
|
1500
|
-
const lines = formatValidationResult(result, hooksDirectory);
|
|
1501
|
-
for (const line of lines) {
|
|
1502
|
-
if (line.startsWith("ERROR") || line.startsWith("WARN")) {
|
|
1503
|
-
logger.warn(line);
|
|
1504
|
-
} else {
|
|
1505
|
-
logger.info(line);
|
|
1506
|
-
}
|
|
1507
|
-
}
|
|
1508
|
-
if (!result.ok) {
|
|
1509
|
-
throw new Error("Hook validation failed");
|
|
1510
|
-
}
|
|
1511
|
-
};
|
|
1512
|
-
|
|
1513
|
-
const resolveHooksDirectory = (options) => options.hooksDir ?? DEFAULT_HOOKS_DIRECTORY;
|
|
1514
|
-
const confirmPrompt = (question) => new Promise((resolve) => {
|
|
1515
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1516
|
-
rl.question(`${question} (y/N) `, (answer) => {
|
|
1517
|
-
rl.close();
|
|
1518
|
-
const trimmed = answer.trim().toLowerCase();
|
|
1519
|
-
resolve(trimmed === "y" || trimmed === "yes");
|
|
1520
|
-
});
|
|
1521
|
-
});
|
|
1522
|
-
const executeInstall = async (hooksDirectory, logger) => {
|
|
1523
|
-
const root = cwd();
|
|
1524
|
-
const huskyDirectory = detectHuskyDirectory(root);
|
|
1525
|
-
const prekConfig = detectPrekConfig(root);
|
|
1526
|
-
if (huskyDirectory && prekConfig) {
|
|
1527
|
-
throw new Error(`Found both husky (${huskyDirectory}/) and prek (${prekConfig}). Remove or migrate one before running \`vis hook install\`.`);
|
|
1528
|
-
}
|
|
1529
|
-
if (huskyDirectory) {
|
|
1530
|
-
logger.info(`Existing husky installation found at ${huskyDirectory}/`);
|
|
1531
|
-
const shouldMigrate = await confirmPrompt("Would you like to migrate your husky hooks to vis?");
|
|
1532
|
-
if (shouldMigrate) {
|
|
1533
|
-
const migrateResult = migrateFromHusky(root, hooksDirectory, logger);
|
|
1534
|
-
if (migrateResult.isError) {
|
|
1535
|
-
throw new Error(migrateResult.message);
|
|
1536
|
-
}
|
|
1537
|
-
if (migrateResult.message) {
|
|
1538
|
-
logger.info(migrateResult.message);
|
|
1539
|
-
}
|
|
1540
|
-
return;
|
|
1541
|
-
}
|
|
1542
|
-
logger.info("Aborting install. Remove husky first or run 'vis hook migrate' to migrate.");
|
|
1543
|
-
return;
|
|
1544
|
-
}
|
|
1545
|
-
if (prekConfig) {
|
|
1546
|
-
logger.info(`Existing prek configuration found at ${prekConfig}`);
|
|
1547
|
-
const shouldMigrate = await confirmPrompt("Would you like to migrate your prek hooks to vis?");
|
|
1548
|
-
if (shouldMigrate) {
|
|
1549
|
-
const migrateResult = migrateFromPrek(root, hooksDirectory, logger);
|
|
1550
|
-
if (migrateResult.isError) {
|
|
1551
|
-
throw new Error(migrateResult.message);
|
|
1552
|
-
}
|
|
1553
|
-
if (migrateResult.message) {
|
|
1554
|
-
logger.info(migrateResult.message);
|
|
1555
|
-
}
|
|
1556
|
-
return;
|
|
1557
|
-
}
|
|
1558
|
-
logger.info("Aborting install. Remove the prek config first or run 'vis hook migrate' to migrate.");
|
|
1559
|
-
return;
|
|
1560
|
-
}
|
|
1561
|
-
logger.info(`Installing git hooks in ${hooksDirectory}/...`);
|
|
1562
|
-
const result = installHooks(hooksDirectory);
|
|
1563
|
-
if (result.message) {
|
|
1564
|
-
if (result.isError) {
|
|
1565
|
-
throw new Error(result.message);
|
|
1566
|
-
}
|
|
1567
|
-
logger.info(result.message);
|
|
1568
|
-
return;
|
|
1569
|
-
}
|
|
1570
|
-
if (!isAccessibleSync(join(root, hooksDirectory, "pre-commit"))) {
|
|
1571
|
-
writeFileSync(join(root, hooksDirectory, "pre-commit"), "#!/usr/bin/env sh\n", { mode: 493 });
|
|
1572
|
-
}
|
|
1573
|
-
logger.info("Git hooks installed successfully.");
|
|
1574
|
-
};
|
|
1575
|
-
const executeMigrate = (hooksDirectory, dryRun, logger) => {
|
|
1576
|
-
const root = cwd();
|
|
1577
|
-
const huskyDirectory = detectHuskyDirectory(root);
|
|
1578
|
-
const prekConfig = detectPrekConfig(root);
|
|
1579
|
-
if (huskyDirectory && prekConfig) {
|
|
1580
|
-
throw new Error(`Found both husky (${huskyDirectory}/) and prek (${prekConfig}). Migrate one at a time — rename or remove one before retrying.`);
|
|
1581
|
-
}
|
|
1582
|
-
if (!huskyDirectory && !prekConfig) {
|
|
1583
|
-
throw new Error("No husky (.husky/) or prek (.pre-commit-config.yaml / prek.toml) configuration found to migrate.");
|
|
1584
|
-
}
|
|
1585
|
-
if (dryRun) {
|
|
1586
|
-
logger.info("(dry-run) no files will be written");
|
|
1587
|
-
}
|
|
1588
|
-
const result = huskyDirectory ? migrateFromHusky(root, hooksDirectory, logger, { dryRun }) : migrateFromPrek(root, hooksDirectory, logger, { dryRun });
|
|
1589
|
-
if (result.isError) {
|
|
1590
|
-
throw new Error(result.message);
|
|
1591
|
-
}
|
|
1592
|
-
if (result.message) {
|
|
1593
|
-
logger.info(result.message);
|
|
1594
|
-
}
|
|
1595
|
-
};
|
|
1596
|
-
const SECRETS_HOOK_MARKER = "# vis:secrets-hook";
|
|
1597
|
-
const SECRETS_HOOK_SCRIPT = `#!/usr/bin/env sh
|
|
1598
|
-
${SECRETS_HOOK_MARKER}
|
|
1599
|
-
# Scan staged files for secrets before each commit. Remove this block or the whole file to disable.
|
|
1600
|
-
pnpm exec vis secrets --staged --quiet || exit 1
|
|
1601
|
-
`;
|
|
1602
|
-
const executeAdd = (what, hooksDirectory, logger) => {
|
|
1603
|
-
if (what !== "secrets") {
|
|
1604
|
-
throw new Error(`Unknown hook add target "${String(what)}". Currently supported: "secrets".`);
|
|
1605
|
-
}
|
|
1606
|
-
const root = cwd();
|
|
1607
|
-
const hookPath = join(root, hooksDirectory, "pre-commit");
|
|
1608
|
-
if (!isAccessibleSync(join(root, hooksDirectory))) {
|
|
1609
|
-
throw new Error(`Hooks directory ${hooksDirectory}/ does not exist. Run \`vis hook install\` first.`);
|
|
1610
|
-
}
|
|
1611
|
-
if (isAccessibleSync(hookPath)) {
|
|
1612
|
-
const existing = readFileSync(hookPath);
|
|
1613
|
-
if (existing.includes(SECRETS_HOOK_MARKER)) {
|
|
1614
|
-
logger.info(`Secrets hook already present in ${hookPath}.`);
|
|
1615
|
-
return;
|
|
1616
|
-
}
|
|
1617
|
-
if (/\bvis secrets\b/.test(existing)) {
|
|
1618
|
-
logger.warn(`Found a \`vis secrets\` invocation in ${hookPath} without the managed marker — leaving it untouched.`);
|
|
1619
|
-
return;
|
|
1620
|
-
}
|
|
1621
|
-
const appended = `${existing.trimEnd()}
|
|
1622
|
-
|
|
1623
|
-
${SECRETS_HOOK_MARKER}
|
|
1624
|
-
pnpm exec vis secrets --staged --quiet || exit 1
|
|
1625
|
-
`;
|
|
1626
|
-
writeFileSync(hookPath, appended);
|
|
1627
|
-
chmodSync(hookPath, 493);
|
|
1628
|
-
logger.info(`Appended secrets scan to ${hookPath}.`);
|
|
1629
|
-
return;
|
|
1630
|
-
}
|
|
1631
|
-
writeFileSync(hookPath, SECRETS_HOOK_SCRIPT, { mode: 493 });
|
|
1632
|
-
logger.info(`Created ${hookPath} with a secrets-scan pre-commit check.`);
|
|
1633
|
-
};
|
|
1634
|
-
const executeUninstall = (hooksDirectory, logger) => {
|
|
1635
|
-
logger.info("Removing git hooks...");
|
|
1636
|
-
const result = uninstallHooks(hooksDirectory);
|
|
1637
|
-
if (result.message) {
|
|
1638
|
-
if (result.isError) {
|
|
1639
|
-
throw new Error(result.message);
|
|
1640
|
-
}
|
|
1641
|
-
logger.info(result.message);
|
|
1642
|
-
return;
|
|
1643
|
-
}
|
|
1644
|
-
logger.info("Git hooks removed successfully.");
|
|
1645
|
-
};
|
|
1646
|
-
const hookInstallImpl = async ({ logger, options }) => {
|
|
1647
|
-
await executeInstall(resolveHooksDirectory(options), logger);
|
|
1648
|
-
};
|
|
1649
|
-
const hookUninstallImpl = ({ logger, options }) => {
|
|
1650
|
-
executeUninstall(resolveHooksDirectory(options), logger);
|
|
1651
|
-
};
|
|
1652
|
-
const hookMigrateImpl = ({ logger, options }) => {
|
|
1653
|
-
executeMigrate(resolveHooksDirectory(options), Boolean(options.dryRun), logger);
|
|
1654
|
-
};
|
|
1655
|
-
const hookListImpl = ({ logger, options }) => {
|
|
1656
|
-
runList(resolveHooksDirectory(options), logger);
|
|
1657
|
-
};
|
|
1658
|
-
const hookValidateImpl = ({ logger, options }) => {
|
|
1659
|
-
runValidate(resolveHooksDirectory(options), logger);
|
|
1660
|
-
};
|
|
1661
|
-
const hookRunImpl = ({ argument, logger, options }) => {
|
|
1662
|
-
runRun(
|
|
1663
|
-
resolveHooksDirectory(options),
|
|
1664
|
-
{
|
|
1665
|
-
allFiles: Boolean(options.allFiles),
|
|
1666
|
-
fromRef: options.fromRef,
|
|
1667
|
-
lastCommit: Boolean(options.lastCommit),
|
|
1668
|
-
stage: argument[0],
|
|
1669
|
-
toRef: options.toRef
|
|
1670
|
-
},
|
|
1671
|
-
logger
|
|
1672
|
-
);
|
|
1673
|
-
};
|
|
1674
|
-
const hookAddImpl = ({ argument, logger, options }) => {
|
|
1675
|
-
executeAdd(argument[0], resolveHooksDirectory(options), logger);
|
|
1676
|
-
};
|
|
1677
|
-
const hookInstallExecute = hookInstallImpl;
|
|
1678
|
-
const hookUninstallExecute = hookUninstallImpl;
|
|
1679
|
-
const hookMigrateExecute = hookMigrateImpl;
|
|
1680
|
-
const hookListExecute = hookListImpl;
|
|
1681
|
-
const hookValidateExecute = hookValidateImpl;
|
|
1682
|
-
const hookRunExecute = hookRunImpl;
|
|
1683
|
-
const hookAddExecute = hookAddImpl;
|
|
1684
|
-
|
|
1685
|
-
export { hookAddExecute, hookInstallExecute, hookListExecute, hookMigrateExecute, hookRunExecute, hookUninstallExecute, hookValidateExecute };
|
|
1
|
+
var le=Object.defineProperty;var E=(r,e)=>le(r,"name",{value:e,configurable:!0});import{createRequire as de}from"node:module";import{getManifestData as he}from"@socketsecurity/registry";import{isAccessibleSync as X,readFileSync as z,writeFileSync as _,readJsonSync as Q,glob as ue}from"@visulima/fs";import{join as P,resolve as ge}from"@visulima/path";import{Box as p,Text as c,ScrollView as ye,ScrollBar as ke,useApp as ve,useWindowSize as we,useInput as be,render as $e}from"@visulima/tui";import{w as ee,av as xe,an as Se,aA as Ce,aB as W,A as Ne,p as h,h as Oe,s as je}from"./bin.js";import Ee from"module-replacements/manifests/micro-utilities.json"with{type:"json"};import Te from"module-replacements/manifests/native.json"with{type:"json"};import Pe from"module-replacements/manifests/preferred.json"with{type:"json"};import Ae,{useSyncExternalStore as De,useRef as Ie,useState as H,useCallback as Re}from"react";import{readYamlSync as Fe}from"@visulima/fs/yaml";import{jsx as o,jsxs as d,Fragment as q}from"react/jsx-runtime";const pe=de(import.meta.url),F=typeof globalThis<"u"&&typeof globalThis.process<"u"?globalThis.process:process,fe=E(r=>{if(typeof F<"u"&&F.versions&&F.versions.node){const[e,t]=F.versions.node.split(".").map(Number);if(e>22||e===22&&t>=3||e===20&&t>=16)return F.getBuiltinModule(r)}return pe(r)},"__cjs_getBuiltinModule"),{writeFileSync:me}=fe("node:fs");var _e=Object.defineProperty,T=E((r,e)=>_e(r,"name",{value:e,configurable:!0}),"d$1");const Me=["dependencies","devDependencies","peerDependencies","peerDependenciesMeta","optionalDependencies","bundleDependencies"],ze=["overrides","pnpm","resolutions"],Le=["engines","files"],Je=T(r=>{const e=ee.coerce(r)?.major;return e!==void 0&&e>=10},"isPnpmV10Plus"),Ge=T(r=>{const e=P(r,"pnpm-workspace.yaml");if(!X(e))return{overrides:{},source:"pnpm-workspace.yaml"};try{return{overrides:Fe(e)?.overrides??{},source:"pnpm-workspace.yaml"}}catch{return{overrides:{},source:"pnpm-workspace.yaml"}}},"readPnpmWorkspaceOverrides"),qe=T((r,e)=>{let t={};return e==="deno"?{overrides:{},source:"package.json"}:(e==="pnpm"?t=r.pnpm?.overrides??{}:e==="yarn"||e==="bun"?t=r.resolutions??{}:t=r.overrides??{},{overrides:t,source:"package.json"})},"readPkgJsonOverrides"),Be=T((r,e,t)=>t.name==="pnpm"&&Je(t.version)?Ge(r):qe(e,t.name),"readOverrides"),Y=T((r,e)=>{const t=e;for(const a of ze){const i=r.indexOf(a);if(i!==-1&&a!==t)return t==="overrides"?i:i+1}let n=-1;for(const a of Me){const i=r.indexOf(a);i>n&&(n=i)}if(n!==-1)return n+1;for(const a of Le){const i=r.indexOf(a);if(i!==-1)return i}return r.length},"findInsertIndex"),Ue=T((r,e)=>{const t=P(r,"pnpm-workspace.yaml");if(!X(t))throw new Error(`pnpm-workspace.yaml not found at ${t}. Cannot write overrides for pnpm v10+.`);let n=z(t);const a=`overrides:
|
|
2
|
+
${Object.entries(e).map(([i,l])=>` '${i}': '${l}'`).join(`
|
|
3
|
+
`)}
|
|
4
|
+
`;n=/^overrides:\s*$/m.test(n)||/^overrides:\s*\n/m.test(n)?n.replace(/^overrides:\s*\n(?:(?:[ \t].*)?\n)*/m,a):`${n.trimEnd()}
|
|
5
|
+
|
|
6
|
+
${a}`,_(t,n)},"writePnpmWorkspaceOverrides"),Ve=T((r,e,t,n,a)=>{const i=z(r),l=xe(r,i,{useEditorconfig:a});if(n==="pnpm"){const m=e.pnpm??{};if(m.overrides=t,e.pnpm)e.pnpm=m,_(r,`${JSON.stringify(e,null,l)}
|
|
7
|
+
`);else{const y=Object.keys(e),g=Y(y,"pnpm"),u=Object.entries(e);u.splice(g,0,["pnpm",m]),_(r,`${JSON.stringify(Object.fromEntries(u),null,l)}
|
|
8
|
+
`)}}else{const m=n==="yarn"||n==="bun"?"resolutions":"overrides";if(e[m])e[m]=t,_(r,`${JSON.stringify(e,null,l)}
|
|
9
|
+
`);else{const y=Object.keys(e),g=Y(y,m),u=Object.entries(e);u.splice(g,0,[m,t]),_(r,`${JSON.stringify(Object.fromEntries(u),null,l)}
|
|
10
|
+
`)}}},"writePkgJsonOverrides"),Xe=T((r,e,t,n,a)=>{if(n.name==="deno")return{added:[],updated:[]};const i=z(e),l=JSON.parse(i),{overrides:m,source:y}=Be(r,l,n),g=[],u=[],O=new Set;if(n.name==="npm")for(const k of["dependencies","devDependencies"]){const b=l[k];if(b)for(const v of Object.keys(b))O.add(v)}for(const k of t){const b=m[k.original];if(typeof b=="object")continue;let v=k.spec;n.name==="npm"&&O.has(k.original)&&(v=`$${k.original}`),b!==v&&(b?u.push(k.original):g.push(k.original),m[k.original]=v)}if(g.length===0&&u.length===0)return{added:g,updated:u};const $=Object.fromEntries(Object.entries(m).sort(([k],[b])=>k.localeCompare(b)));return y==="pnpm-workspace.yaml"?Ue(r,$):Ve(e,l,$,n.name,a),{added:g,updated:u}},"applyOverrides"),We=T((r,e)=>{const t={bun:["bun.lock"],deno:["deno.lock"],npm:["npm-shrinkwrap.json","package-lock.json"],pnpm:["pnpm-lock.yaml"],yarn:["yarn.lock"]};for(const n of t[e]??[]){const a=P(r,n);try{return z(a)}catch{continue}}return""},"readLockfileText"),He=T((r,e,t)=>{if(!r)return!1;const n=e.replaceAll(/[.*+?^${}()|[\]\\]/g,String.raw`\$&`);switch(t){case"bun":return r.includes(`"${e}":`)||new RegExp(String.raw`(^|\s|[",])${n}@`,"m").test(r);case"deno":return r.includes(`"${e}"`)||r.includes(`"npm:${e}@`)||r.includes(`"jsr:${e}@`);case"npm":return r.includes(`"${e}":`)||r.includes(`"node_modules/${e}":`);case"pnpm":return new RegExp(String.raw`(^|\s|['"/])${n}(@|['"]?:)`,"m").test(r);case"yarn":return new RegExp(String.raw`(^|\s|[",])${n}@`,"m").test(r);default:return!1}},"lockfileContainsPackage");var Ye=Object.defineProperty,M=E((r,e)=>Ye(r,"name",{value:e,configurable:!0}),"s");const Ke=M((r,e,t)=>{let n=r;if(e!=="all"&&(n=n.filter(a=>a.category===e)),t){const a=t.toLowerCase();n=n.filter(i=>i.packageName.toLowerCase().includes(a))}return n},"filterEntries");class Ze{static{E(this,"h")}static{M(this,"OptimizeStore")}#e;#t=new Set;constructor(e){this.#e={applyProgress:null,checkedEntries:new Set,entries:e,error:null,filterActive:!1,filterText:"",filterType:"all",focusedPanel:"list",phase:"browsing",selectedIndex:0}}getSnapshot=M(()=>this.#e,"getSnapshot");subscribe=M(e=>(this.#t.add(e),()=>{this.#t.delete(e)}),"subscribe");getFilteredEntries=M(()=>Ke(this.#e.entries,this.#e.filterType,this.#e.filterText),"getFilteredEntries");#r(){this.#e={...this.#e};for(const e of this.#t)e()}select(e){const t=this.getFilteredEntries();this.#e.selectedIndex=t.length===0?-1:Math.max(0,Math.min(e,t.length-1)),this.#r()}toggleCheck(e){const t=new Set(this.#e.checkedEntries);t.has(e)?t.delete(e):t.add(e),this.#e.checkedEntries=t,this.#r()}toggleAll(){const e=this.getFilteredEntries(),t=new Set(this.#e.checkedEntries);if(e.every(n=>t.has(n.packageName)))for(const n of e)t.delete(n.packageName);else for(const n of e)t.add(n.packageName);this.#e.checkedEntries=t,this.#r()}setFilter(e){this.#e.filterType=e,this.#e.selectedIndex=0,this.#r()}setFilterText(e){this.#e.filterText=e,this.#e.selectedIndex=0,this.#r()}setFilterActive(e){this.#e.filterActive=e,this.#r()}setFocusedPanel(e){this.#e.focusedPanel=e,this.#r()}setPhase(e){this.#e.phase=e,this.#r()}setProgress(e,t){this.#e.applyProgress={current:e,total:t},this.#r()}setError(e){this.#e.error=e,this.#e.phase="error",this.#r()}getCheckedEntries(){return this.#e.entries.filter(e=>this.#e.checkedEntries.has(e.packageName))}}const re={"micro-utility":"gray",native:"green",preferred:"yellow",socket:"cyan"},Qe={"micro-utility":"MICRO",native:"NATIVE",preferred:"PREF",socket:"SOCKET"},er={"micro-utility":"Trivial utility package that can be replaced with inline code.",native:"Polyfill for a native JS/Node.js API. Use the built-in instead.",preferred:"A lighter or faster alternative package exists.",socket:"Security-hardened replacement from Socket.dev's @socketregistry."};var rr=Object.defineProperty,tr=E((r,e)=>rr(r,"name",{value:e,configurable:!0}),"d");const nr=tr(({entry:r,focused:e,scrollRef:t})=>{const n=e?"white":"gray";if(!r)return o(p,{alignItems:"center",borderColor:"gray",borderStyle:"single",flexDirection:"column",flexGrow:1,justifyContent:"center",children:o(c,{dimColor:!0,children:"No entry selected"})});const a=re[r.category]??"gray";return d(p,{borderColor:n,borderStyle:"single",flexDirection:"column",flexGrow:1,children:[o(p,{flexShrink:0,paddingTop:1,paddingX:2,children:o(c,{bold:!0,color:"white",children:r.packageName})}),d(ye,{flexGrow:1,flexShrink:1,paddingX:2,ref:t,scrollbar:!0,scrollbarColor:"gray",children:[o(c,{}),d(p,{children:[o(p,{width:14,children:o(c,{dimColor:!0,children:"Category:"})}),o(c,{bold:!0,color:a,children:r.category})]}),d(p,{children:[o(p,{width:14,children:o(c,{dimColor:!0,children:"Replace with:"})}),o(c,{children:r.replacement})]}),r.overrideSpec&&d(p,{children:[o(p,{width:14,children:o(c,{dimColor:!0,children:"Override:"})}),o(c,{color:"cyan",children:r.overrideSpec})]}),d(p,{children:[o(p,{width:14,children:o(c,{dimColor:!0,children:"Codemod:"})}),o(c,{color:r.hasCodemod?"green":"gray",children:r.hasCodemod?"available ⚙":"not available"})]}),d(p,{flexDirection:"column",marginTop:1,children:[o(c,{dimColor:!0,children:"── "}),o(c,{bold:!0,color:"white",children:"DESCRIPTION"}),o(p,{marginTop:1,paddingLeft:2,children:o(c,{children:er[r.category]??""})})]}),r.category==="native"&&d(p,{flexDirection:"column",marginTop:1,children:[o(c,{dimColor:!0,children:"── "}),o(c,{bold:!0,color:"green",children:"ACTION"}),o(p,{flexDirection:"column",marginTop:1,paddingLeft:2,children:r.hasCodemod?d(q,{children:[d(c,{color:"green",children:["✓"," ","Codemod will rewrite imports to use native API."]}),o(c,{dimColor:!0,children:" The package can then be removed from dependencies."})]}):d(q,{children:[d(c,{color:"yellow",children:["ℹ"," ","No automated codemod available."]}),o(c,{dimColor:!0,children:" Manual migration required — replace usage with native equivalent."})]})})]}),r.category==="socket"&&d(p,{flexDirection:"column",marginTop:1,children:[o(c,{dimColor:!0,children:"── "}),o(c,{bold:!0,color:"cyan",children:"ACTION"}),d(p,{flexDirection:"column",marginTop:1,paddingLeft:2,children:[d(c,{color:"cyan",children:["✓"," ","Override will redirect resolution to the hardened package."]}),o(c,{dimColor:!0,children:" No source code changes needed — drop-in replacement."})]})]}),(r.category==="preferred"||r.category==="micro-utility")&&d(p,{flexDirection:"column",marginTop:1,children:[o(c,{dimColor:!0,children:"── "}),o(c,{bold:!0,color:"yellow",children:"ACTION"}),o(p,{flexDirection:"column",marginTop:1,paddingLeft:2,children:r.hasCodemod?d(q,{children:[d(c,{color:"green",children:["✓"," ","Codemod will rewrite imports to the recommended alternative."]}),o(c,{dimColor:!0,children:" The original package can then be removed from dependencies."})]}):d(q,{children:[d(c,{color:"yellow",children:["ℹ"," ","Manual migration required."]}),r.docUrl?o(c,{dimColor:!0,children:" Open the migration guide below for the recommended alternative and steps."}):o(c,{dimColor:!0,children:" Consult the package's docs or the e18e module-replacements guide for an alternative."})]})})]}),d(p,{flexDirection:"column",marginTop:1,children:[o(c,{dimColor:!0,children:"── "}),o(c,{bold:!0,color:"white",children:"LINKS"}),d(p,{flexDirection:"column",marginTop:1,paddingLeft:2,children:[d(c,{color:"cyan",underline:!0,children:["https://npmx.dev/",r.packageName]}),r.docUrl&&o(c,{color:"cyan",underline:!0,children:r.docUrl})]})]})]})]})},"OptimizeDetailPanel");var or=Object.defineProperty,te=E((r,e)=>or(r,"name",{value:e,configurable:!0}),"b$1");const ir=[{key:"all",label:"All",shortcut:"1"},{key:"native",label:"Native",shortcut:"2"},{key:"preferred",label:"Preferred",shortcut:"3"},{key:"micro-utility",label:"Micro",shortcut:"4"},{key:"socket",label:"Socket",shortcut:"5"}],cr=te(({checked:r,entry:e,isSelected:t})=>{const n=re[e.category]??"white",a=Qe[e.category]??e.category,i=r?"☑":"☐",l=e.hasCodemod?"⚙":" ";return d(p,{flexShrink:0,height:1,children:[o(c,{children:t?">":" "}),d(c,{color:r?"white":"gray",children:[" ",i," "]}),o(c,{bold:!0,color:n,children:`[${a}]`.padEnd(9)}),d(c,{children:[" ",l," "]}),o(p,{flexGrow:1,children:o(c,{bold:t,inverse:t,wrap:"truncate",children:e.packageName})}),o(c,{dimColor:!0,children:" → "}),o(c,{wrap:"truncate",children:e.replacement})]})},"EntryRow"),ar=te(({checkedEntries:r,entries:e,filterActive:t,filterText:n,filterType:a,focused:i,isDryRun:l,scrollOffset:m,selectedIndex:y,totalEntries:g,viewportHeight:u})=>{const O=i?"white":"gray";let $=0,k=0,b=0,v=0;for(const s of e)switch(s.category){case"micro-utility":{b++;break}case"native":{$++;break}case"preferred":{k++;break}case"socket":{v++;break}}const f=[];$>0&&f.push(`${$} native`),k>0&&f.push(`${k} preferred`),b>0&&f.push(`${b} micro`),v>0&&f.push(`${v} socket`);const x=f.length>0?` (${f.join(", ")})`:"",C=r.size,A=[];for(const[s,S]of e.entries())A.push(o(cr,{checked:r.has(S.packageName),entry:S,isSelected:s===y},S.packageName));const D=e.length,R=D>u&&u>0;return d(p,{borderColor:O,borderStyle:"single",flexDirection:"column",flexGrow:1,children:[d(p,{flexShrink:0,gap:1,paddingX:1,children:[o(c,{bold:!0,inverse:!0,children:" VIS OPTIMIZE "}),d(c,{wrap:"truncate",children:[g," ","optimizations",x]}),!l&&C>0&&d(c,{dimColor:!0,children:[" ","—",C," ","selected"]})]}),o(p,{flexShrink:0,gap:1,paddingX:1,paddingY:1,children:ir.map(s=>{const S=a===s.key;return d(p,{children:[o(c,{dimColor:!S,children:"["}),o(c,{bold:S,color:S?"cyan":"gray",children:s.shortcut}),o(c,{dimColor:!S,children:"]"}),d(c,{color:S?"white":"gray",children:[" ",s.label]})]},s.key)})}),t&&d(p,{flexShrink:0,paddingX:1,children:[o(c,{bold:!0,color:"white",children:"/ "}),o(c,{children:n}),o(c,{inverse:!0,children:" "})]}),d(p,{flexDirection:"row",flexGrow:1,overflow:"hidden",children:[o(p,{flexDirection:"column",flexGrow:1,overflow:"hidden",paddingLeft:1,children:o(p,{flexDirection:"column",marginTop:-m,children:A})}),R&&o(p,{flexShrink:0,marginLeft:1,marginRight:1,children:o(ke,{contentHeight:D,placement:"inset",scrollOffset:m,style:"block",viewportHeight:u})})]})]})},"OptimizeListPanel");var sr=Object.defineProperty,lr=E((r,e)=>sr(r,"name",{value:e,configurable:!0}),"I$1");const dr=100,K=10,Z={1:"all",2:"native",3:"preferred",4:"micro-utility",5:"socket"},pr=lr(({isDryRun:r,store:e})=>{const{exit:t}=ve(),{columns:n,rows:a}=we(),i=De(e.subscribe,e.getSnapshot),l=Ie(null),[m,y]=H(0),[g,u]=H(!1),O=e.getFilteredEntries(),$=O[i.selectedIndex]??null,k=n>=dr,b=Math.max(0,a-5),v=Re(f=>{t(f)},[t]);return be((f,x)=>{if(g){f==="y"||f==="Y"?v():u(!1);return}if(i.filterActive){x.escape?(e.setFilterActive(!1),e.setFilterText("")):x.return?e.setFilterActive(!1):x.backspace||x.delete?e.setFilterText(i.filterText.slice(0,-1)):f&&!x.ctrl&&!x.meta&&e.setFilterText(i.filterText+f);return}if(f==="q"){!r&&i.checkedEntries.size>0?u(!0):v();return}if(f==="/"){e.setFilterActive(!0);return}if(Z[f]){e.setFilter(Z[f]);return}if(x.tab){e.setFocusedPanel(i.focusedPanel==="list"?"detail":"list");return}if(i.focusedPanel==="list")if(x.upArrow||f==="k"){const C=Math.max(0,i.selectedIndex-1);e.select(C),C<m&&y(C)}else if(x.downArrow||f==="j"){const C=Math.min(O.length-1,i.selectedIndex+1);e.select(C),C>=m+b&&y(C-b+1)}else f===" "?$&&e.toggleCheck($.packageName):f==="a"?e.toggleAll():x.return&&!r&&i.checkedEntries.size>0&&v(e.getCheckedEntries());else i.focusedPanel==="detail"&&(x.upArrow||f==="k"?l.current?.scrollBy(-1):(x.downArrow||f==="j")&&l.current?.scrollBy(1))},{isActive:i.phase==="browsing"}),a<K?o(p,{alignItems:"center",justifyContent:"center",children:d(c,{color:"yellow",children:["Terminal too small. Resize to at least",K," ","rows."]})}):d(p,{flexDirection:"column",height:a,width:n,children:[d(p,{flexDirection:k?"row":"column",flexGrow:1,children:[o(p,{flexBasis:k?"50%":void 0,flexGrow:1,children:o(ar,{checkedEntries:i.checkedEntries,entries:O,filterActive:i.filterActive,filterText:i.filterText,filterType:i.filterType,focused:i.focusedPanel==="list",isDryRun:r,scrollOffset:m,selectedIndex:i.selectedIndex,totalEntries:i.entries.length,viewportHeight:b})}),o(p,{flexBasis:k?"50%":void 0,flexGrow:1,children:o(nr,{entry:$,focused:i.focusedPanel==="detail",scrollRef:l})})]}),o(p,{flexShrink:0,paddingX:1,children:d(c,{dimColor:!0,children:[r?"Preview mode":"space:toggle a:all enter:apply"," ","| tab:switch panel /: filter q:quit |","⚙","=codemod available"]})}),o(Se,{autoExitSeconds:3,onCancel:E(()=>{u(!1)},"onCancel"),visible:g})]})},"VisOptimizeApp");var fr=Object.defineProperty,N=E((r,e)=>fr(r,"name",{value:e,configurable:!0}),"p");const V=N((r,e)=>{const t=new Set;try{const n=Q(r),a=e?[n.dependencies,n.optionalDependencies]:[n.dependencies,n.devDependencies,n.peerDependencies,n.optionalDependencies];for(const i of a)if(i)for(const l of Object.keys(i))t.add(l)}catch{}return t},"collectDepsFromPkgJson"),ne=N(r=>{const e=Ce(r);if(e)return W(r,e);const t=P(r,"package.json");if(!X(t))return[];try{const n=Q(t),a=Array.isArray(n.workspaces)?n.workspaces:n.workspaces?.packages;return a?W(r,a):[]}catch{return[]}},"discoverWorkspacePackages"),mr="https://github.com/es-tooling/module-replacements/blob/main/docs/modules",hr=N(r=>`${mr}/${r}.md`,"buildE18eDocUrl"),ur=N(r=>{if(r.type==="simple"&&r.replacement)return r.replacement;if(r.type==="native"){const e=r.nodeVersion?` (Node ${r.nodeVersion}+)`:"";return r.replacement?`${r.replacement}${e}`:`Native API${e}`}return r.type==="documented"&&r.docPath?"see migration guide":""},"e18eReplacementHint"),oe=N(r=>{const e=[],t=N((n,a)=>{const i=n.moduleReplacements;if(Array.isArray(i))for(const l of i)r.has(l.moduleName)&&e.push({category:a,docUrl:l.type==="documented"&&l.docPath?hr(l.docPath):void 0,hasCodemod:!1,overrideSpec:void 0,packageName:l.moduleName,replacement:ur(l)})},"scanManifest");return t(Te,"native"),t(Pe,"preferred"),t(Ee,"micro-utility"),e},"buildE18eEntries"),ie=N((r,e,t,n)=>{const a=he("npm")??[],i=[];for(const[,l]of a){if(l.deprecated)continue;const m=r.has(l.package),y=e?He(e,l.package,t.name):!1;if(!m&&!y)continue;const g=ee.coerce(l.version)?.major;if(g===void 0)continue;const u=n?`npm:${l.name}@${l.version}`:`npm:${l.name}@^${String(g)}`;i.push({category:"socket",hasCodemod:!1,overrideSpec:u,packageName:l.package,replacement:l.name})}return i},"buildSocketEntries");let B;const ce=N(async()=>{if(B)return B;try{return B=(await import("module-replacements-codemods")).codemods,B}catch{return{}}},"loadCodemods"),ae=N(async r=>{try{const e=await ce();for(const t of r)t.category!=="socket"&&e[t.packageName]&&(t.hasCodemod=!0)}catch{}},"markCodemodAvailability"),se=N(async(r,e)=>{let t=0;try{const n=(await ce())[e];if(!n)return{filesChanged:0,packageName:e};const a=n({}),i=await gr(r);for(const l of i){const m=z(l);if(m.includes(e))try{const y=await a.transform({file:{filename:l,source:m}});y!==m&&(me(l,y,"utf8"),t++)}catch(y){process.stderr.write(`warn: codemod transform failed for ${l}: ${y instanceof Error?y.message:String(y)}
|
|
11
|
+
`)}}}catch{}return{filesChanged:t,packageName:e}},"runCodemod"),gr=N(async r=>ue("**/*.{cjs,cts,js,jsx,mjs,mts,ts,tsx}",{absolute:!0,cwd:r,ignore:["**/.*/**","**/.*","**/node_modules/**","**/dist/**","**/coverage/**","**/.git/**","**/.next/**","**/.nuxt/**"]}),"collectSourceFiles"),yr=N(async({logger:r,options:e,visConfig:t,workspaceRoot:n})=>{if(!n)throw new Error("Could not determine workspace root. Run this command inside a monorepo.");const a=Ne(n),i=!!e.dryRun,l=!!e.prod,m=!!e.pin;h.info(`Detected ${a.name} v${a.version}.`);const y=V(P(n,"package.json"),l),g=ne(n),u=new Set(y);for(const s of g){const S=V(P(ge(n,s),"package.json"),l);for(const I of S)u.add(I)}g.length>0&&h.info(`Scanned ${String(g.length)} workspace package${g.length===1?"":"s"}.`),h.info(`Scanning dependencies...
|
|
12
|
+
`);const O=We(n,a.name),$=oe(u),k=ie(u,O,a,m),b=new Set($.map(s=>s.packageName)),v=k.filter(s=>!b.has(s.packageName)),f=[...$,...v];if(await ae(f),f.length===0){h.info("No optimizations found for your dependencies.");return}const x=!!process.stdout.isTTY&&!je,C=e.format==="json"||!!e.json;if(x&&!i&&!C){const s=new Ze(f),S=await $e(Ae.createElement(pr,{isDryRun:!1,store:s}),{alternateScreen:!0,exitOnCtrlC:!1,interactive:!0,patchConsole:!0}).waitUntilExit(),I=Array.isArray(S)?S:[];if(I.length===0){h.info("No optimizations selected.");return}const L=I.filter(w=>w.category!=="socket"&&w.hasCodemod),J=I.filter(w=>w.category!=="socket"&&!w.hasCodemod),U=I.filter(w=>w.category==="socket");if(L.length>0){h.info(`
|
|
13
|
+
Running ${String(L.length)} codemod${L.length===1?"":"s"}...
|
|
14
|
+
`);for(const w of L){const j=await se(n,w.packageName);j.filesChanged>0?h.success(` ${w.packageName}: ${String(j.filesChanged)} file${j.filesChanged===1?"":"s"} updated`):h.info(` ${w.packageName}: no files changed`)}}if(J.length>0){h.warn(`
|
|
15
|
+
${String(J.length)} selected replacement${J.length===1?"":"s"} require manual migration (no codemod available):`);for(const w of J)h.info(` ${w.packageName} → ${w.replacement}`);h.notice(" Replace usage in your source code, then remove from dependencies.")}if(U.length>0){const w=U.filter(G=>G.overrideSpec).map(G=>({original:G.packageName,spec:G.overrideSpec})),j=Xe(n,P(n,"package.json"),w,a,t?.editorconfig??!0);j.added.length>0&&h.success(`
|
|
16
|
+
Added ${String(j.added.length)} override${j.added.length===1?"":"s"}.`),j.updated.length>0&&h.success(`Updated ${String(j.updated.length)} override${j.updated.length===1?"":"s"}.`)}if(U.length>0&&!e.noInstall){h.info(`
|
|
17
|
+
Running ${a.name} install to update lockfile...`);const w=Oe(a,{dev:!1,filter:[],force:!1,frozenLockfile:!1,ignoreScripts:!1,lockfileOnly:!1,noOptional:!1,offline:!1,prod:!1,recursive:!1,silent:!1,workspaceRoot:!1},n,r);w!==0&&h.warn(`${a.name} install exited with code ${String(w)}. Run it manually.`)}h.info(""),h.success("Optimization complete.");return}if(C){process.stdout.write(`${JSON.stringify({e18e:$.map(s=>({category:s.category,hasCodemod:s.hasCodemod,packageName:s.packageName,replacement:s.replacement})),packageManager:a.name,socket:v.map(s=>({overrideSpec:s.overrideSpec,packageName:s.packageName,replacement:s.replacement})),total:f.length,workspaces:g.length},void 0,2)}
|
|
18
|
+
`);return}const A=$.filter(s=>s.category==="native"),D=$.filter(s=>s.category==="preferred"),R=$.filter(s=>s.category==="micro-utility");if(A.length>0){h.info(`Native replacements (${String(A.length)}):`);for(const s of A)h.info(` ${s.hasCodemod?"⚙":" "} ${s.packageName} → ${s.replacement}`)}if(D.length>0){h.info(`
|
|
19
|
+
Preferred alternatives (${String(D.length)}):`);for(const s of D)h.info(` ${s.hasCodemod?"⚙":" "} ${s.packageName} → ${s.replacement}`)}if(R.length>0){h.info(`
|
|
20
|
+
Micro-utilities (${String(R.length)}):`);for(const s of R)h.info(` ${s.hasCodemod?"⚙":" "} ${s.packageName} → ${s.replacement}`)}if(v.length>0){h.info(`
|
|
21
|
+
Socket.dev overrides (${String(v.length)}):`);for(const s of v)h.info(` ${s.packageName} → ${s.overrideSpec}`)}h.info(`
|
|
22
|
+
Total: ${String(f.length)} optimizations available (⚙ = codemod available).`),i&&h.notice("Run without --dry-run for interactive selection.")},"execute"),Pr=Object.defineProperty({__proto__:null,buildE18eEntries:oe,buildSocketEntries:ie,collectDepsFromPkgJson:V,default:yr,discoverWorkspacePackages:ne,markCodemodAvailability:ae,runCodemod:se},Symbol.toStringTag,{value:"Module"});export{ne as A,oe as F,ae as I,We as L,ie as T,se as U,Xe as a,Pr as h,V as x};
|