@savvy-web/lint-staged 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -1,377 +1,5 @@
1
- import { execSync } from "node:child_process";
2
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
3
- import { dirname, isAbsolute, join, normalize, relative, resolve } from "node:path";
4
- import { cosmiconfigSync, defaultLoaders } from "cosmiconfig";
5
- import sort_package_json from "sort-package-json";
6
1
  import { parse, stringify } from "yaml";
7
- import parser from "@typescript-eslint/parser";
8
- import { ESLint } from "eslint";
9
- import eslint_plugin_tsdoc from "eslint-plugin-tsdoc";
10
- import { getWorkspaces } from "workspace-tools";
11
- import typescript from "typescript";
12
- const VALID_COMMAND_PATTERN = /^[\w@/-]+$/;
13
- function validateCommandName(name) {
14
- if (!VALID_COMMAND_PATTERN.test(name)) throw new Error(`Invalid command name: "${name}". Only alphanumeric characters, hyphens, underscores, @ and / are allowed.`);
15
- }
16
- class Command {
17
- static cachedPackageManager = null;
18
- static detectPackageManager(cwd = process.cwd()) {
19
- if (null !== Command.cachedPackageManager) return Command.cachedPackageManager;
20
- const packageJsonPath = join(cwd, "package.json");
21
- if (!existsSync(packageJsonPath)) {
22
- Command.cachedPackageManager = "npm";
23
- return "npm";
24
- }
25
- try {
26
- const content = readFileSync(packageJsonPath, "utf-8");
27
- const pkg = JSON.parse(content);
28
- if (pkg.packageManager) {
29
- const match = pkg.packageManager.match(/^(npm|pnpm|yarn|bun)@/);
30
- if (match) {
31
- Command.cachedPackageManager = match[1];
32
- return Command.cachedPackageManager;
33
- }
34
- }
35
- } catch {}
36
- Command.cachedPackageManager = "npm";
37
- return "npm";
38
- }
39
- static getExecPrefix(packageManager) {
40
- switch(packageManager){
41
- case "pnpm":
42
- return [
43
- "pnpm",
44
- "exec"
45
- ];
46
- case "yarn":
47
- return [
48
- "yarn",
49
- "exec"
50
- ];
51
- case "bun":
52
- return [
53
- "bunx"
54
- ];
55
- default:
56
- return [
57
- "npx",
58
- "--no"
59
- ];
60
- }
61
- }
62
- static clearCache() {
63
- Command.cachedPackageManager = null;
64
- }
65
- static isAvailable(command) {
66
- validateCommandName(command);
67
- try {
68
- execSync(`command -v ${command}`, {
69
- stdio: "ignore"
70
- });
71
- return true;
72
- } catch {
73
- return false;
74
- }
75
- }
76
- static findTool(tool) {
77
- validateCommandName(tool);
78
- if (Command.isAvailable(tool)) return {
79
- available: true,
80
- command: tool,
81
- source: "global"
82
- };
83
- const pm = Command.detectPackageManager();
84
- const prefix = Command.getExecPrefix(pm);
85
- const execCmd = [
86
- ...prefix,
87
- tool
88
- ].join(" ");
89
- try {
90
- execSync(`${execCmd} --version`, {
91
- stdio: "ignore"
92
- });
93
- return {
94
- available: true,
95
- command: execCmd,
96
- source: pm
97
- };
98
- } catch {}
99
- return {
100
- available: false,
101
- command: void 0,
102
- source: void 0
103
- };
104
- }
105
- static requireTool(tool, errorMessage) {
106
- const result = Command.findTool(tool);
107
- if (!result.available || !result.command) throw new Error(errorMessage ?? `Required tool '${tool}' is not available. Install it globally or add it as a dev dependency.`);
108
- return result.command;
109
- }
110
- static exec(command) {
111
- return execSync(command, {
112
- encoding: "utf-8"
113
- }).trim();
114
- }
115
- static execSilent(command) {
116
- try {
117
- execSync(command, {
118
- stdio: "ignore"
119
- });
120
- return true;
121
- } catch {
122
- return false;
123
- }
124
- }
125
- }
126
- const TOOL_CONFIGS = {
127
- markdownlint: {
128
- moduleName: "markdownlint-cli2",
129
- libConfigFiles: [
130
- ".markdownlint-cli2.jsonc",
131
- ".markdownlint-cli2.json",
132
- ".markdownlint-cli2.yaml",
133
- ".markdownlint-cli2.cjs",
134
- ".markdownlint.jsonc",
135
- ".markdownlint.json",
136
- ".markdownlint.yaml"
137
- ],
138
- standardPlaces: [
139
- ".markdownlint-cli2.jsonc",
140
- ".markdownlint-cli2.json",
141
- ".markdownlint-cli2.yaml",
142
- ".markdownlint-cli2.cjs",
143
- ".markdownlint.jsonc",
144
- ".markdownlint.json",
145
- ".markdownlint.yaml"
146
- ]
147
- },
148
- biome: {
149
- moduleName: "biome",
150
- libConfigFiles: [
151
- "biome.jsonc",
152
- "biome.json"
153
- ],
154
- standardPlaces: [
155
- "biome.jsonc",
156
- "biome.json"
157
- ]
158
- },
159
- eslint: {
160
- moduleName: "eslint",
161
- libConfigFiles: [
162
- "eslint.config.ts",
163
- "eslint.config.js",
164
- "eslint.config.mjs"
165
- ],
166
- standardPlaces: [
167
- "eslint.config.ts",
168
- "eslint.config.js",
169
- "eslint.config.mjs"
170
- ]
171
- },
172
- prettier: {
173
- moduleName: "prettier",
174
- libConfigFiles: [
175
- ".prettierrc",
176
- ".prettierrc.json",
177
- ".prettierrc.yaml",
178
- ".prettierrc.js",
179
- "prettier.config.js"
180
- ],
181
- standardPlaces: [
182
- ".prettierrc",
183
- ".prettierrc.json",
184
- ".prettierrc.yaml",
185
- ".prettierrc.js",
186
- "prettier.config.js",
187
- "package.json"
188
- ]
189
- }
190
- };
191
- class ConfigSearch {
192
- static libConfigDir = "lib/configs";
193
- static find(tool, options = {}) {
194
- const config = TOOL_CONFIGS[tool];
195
- if (!config) return {
196
- filepath: void 0,
197
- found: false
198
- };
199
- return ConfigSearch.findFile(config.moduleName, {
200
- libConfigFiles: config.libConfigFiles,
201
- standardPlaces: config.standardPlaces,
202
- ...options
203
- });
204
- }
205
- static findFile(moduleName, options = {}) {
206
- const { searchFrom = process.cwd(), stopDir, libConfigFiles = [], standardPlaces = [] } = options;
207
- const loaders = {
208
- ".jsonc": defaultLoaders[".json"],
209
- ".yaml": defaultLoaders[".yaml"],
210
- ".yml": defaultLoaders[".yaml"]
211
- };
212
- const libConfigDir = join(searchFrom, ConfigSearch.libConfigDir);
213
- for (const file of libConfigFiles){
214
- const filepath = join(libConfigDir, file);
215
- if (existsSync(filepath)) return {
216
- filepath,
217
- found: true
218
- };
219
- }
220
- if (0 === standardPlaces.length) return {
221
- filepath: void 0,
222
- found: false
223
- };
224
- try {
225
- const explorer = cosmiconfigSync(moduleName, {
226
- searchPlaces: standardPlaces,
227
- loaders,
228
- stopDir
229
- });
230
- const result = explorer.search(searchFrom);
231
- if (result?.filepath) return {
232
- filepath: result.filepath,
233
- found: true
234
- };
235
- } catch {}
236
- return {
237
- filepath: void 0,
238
- found: false
239
- };
240
- }
241
- static exists(filepath) {
242
- return existsSync(filepath);
243
- }
244
- static resolve(filename, fallback) {
245
- const libPath = `${ConfigSearch.libConfigDir}/${filename}`;
246
- if (ConfigSearch.exists(libPath)) return libPath;
247
- return fallback;
248
- }
249
- }
250
- class Filter {
251
- static exclude(filenames, patterns) {
252
- if (0 === patterns.length) return [
253
- ...filenames
254
- ];
255
- return filenames.filter((file)=>!patterns.some((pattern)=>file.includes(pattern)));
256
- }
257
- static include(filenames, patterns) {
258
- if (0 === patterns.length) return [];
259
- return filenames.filter((file)=>patterns.some((pattern)=>file.includes(pattern)));
260
- }
261
- static apply(filenames, options) {
262
- let result = [
263
- ...filenames
264
- ];
265
- if (options.include && options.include.length > 0) result = Filter.include(result, options.include);
266
- if (options.exclude && options.exclude.length > 0) result = Filter.exclude(result, options.exclude);
267
- return result;
268
- }
269
- }
270
- class Biome {
271
- static glob = "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}";
272
- static defaultExcludes = [
273
- "package-lock.json",
274
- "__fixtures__"
275
- ];
276
- static handler = Biome.create();
277
- static findBiome() {
278
- const result = Command.findTool("biome");
279
- return result.command;
280
- }
281
- static isAvailable() {
282
- return Command.findTool("biome").available;
283
- }
284
- static findConfig() {
285
- const result = ConfigSearch.find("biome");
286
- return result.filepath;
287
- }
288
- static create(options = {}) {
289
- const excludes = options.exclude ?? [
290
- ...Biome.defaultExcludes
291
- ];
292
- const config = options.config ?? Biome.findConfig();
293
- return (filenames)=>{
294
- const filtered = Filter.exclude(filenames, excludes);
295
- if (0 === filtered.length) return [];
296
- const biomeCmd = Command.requireTool("biome", "Biome is not available. Install it globally (recommended) or add @biomejs/biome as a dev dependency.");
297
- const files = filtered.join(" ");
298
- const flags = options.flags ?? [];
299
- const configFlag = config ? `--config-path=${config}` : "";
300
- const cmd = [
301
- `${biomeCmd} check --write --no-errors-on-unmatched`,
302
- configFlag,
303
- ...flags,
304
- files
305
- ].filter(Boolean).join(" ");
306
- return cmd;
307
- };
308
- }
309
- }
310
- class DesignDocs {
311
- static glob = ".claude/design/**/*.md";
312
- static defaultExcludes = [
313
- "design.config.json"
314
- ];
315
- static defaultValidateScript = ".claude/skills/design-validate/scripts/validate-design-doc.sh";
316
- static defaultTimestampScript = ".claude/skills/design-update/scripts/update-timestamp.sh";
317
- static handler = DesignDocs.create();
318
- static create(options = {}) {
319
- const excludes = options.exclude ?? [
320
- ...DesignDocs.defaultExcludes
321
- ];
322
- const validateScript = options.validateScript ?? DesignDocs.defaultValidateScript;
323
- const timestampScript = options.timestampScript ?? DesignDocs.defaultTimestampScript;
324
- const skipTimestamp = options.skipTimestamp ?? false;
325
- return (filenames)=>{
326
- const filtered = Filter.exclude(filenames, excludes);
327
- if (0 === filtered.length) return [];
328
- const commands = [];
329
- for (const file of filtered){
330
- commands.push(`${validateScript} "${file}"`);
331
- if (!skipTimestamp) commands.push(`${timestampScript} "${file}"`);
332
- }
333
- return commands;
334
- };
335
- }
336
- }
337
- class Markdown {
338
- static glob = "**/*.{md,mdx}";
339
- static defaultExcludes = [];
340
- static handler = Markdown.create();
341
- static findMarkdownlint() {
342
- const result = Command.findTool("markdownlint-cli2");
343
- return result.command;
344
- }
345
- static isAvailable() {
346
- return Command.findTool("markdownlint-cli2").available;
347
- }
348
- static findConfig() {
349
- const result = ConfigSearch.find("markdownlint");
350
- return result.filepath;
351
- }
352
- static create(options = {}) {
353
- const excludes = options.exclude ?? [
354
- ...Markdown.defaultExcludes
355
- ];
356
- const noFix = options.noFix ?? false;
357
- const config = options.config ?? Markdown.findConfig();
358
- return (filenames)=>{
359
- const filtered = Filter.exclude(filenames, excludes);
360
- if (0 === filtered.length) return [];
361
- const mdlintCmd = Command.requireTool("markdownlint-cli2", "markdownlint-cli2 is not available. Install it globally or add it as a dev dependency.");
362
- const files = filtered.join(" ");
363
- const fixFlag = noFix ? "" : "--fix";
364
- const configFlag = config ? `--config '${config}'` : "";
365
- const cmd = [
366
- mdlintCmd,
367
- configFlag,
368
- fixFlag,
369
- files
370
- ].filter(Boolean).join(" ");
371
- return cmd;
372
- };
373
- }
374
- }
2
+ import { Biome, Command as Command_Command, readFileSync, existsSync, TypeScript, writeFileSync, Markdown, Filter } from "./376.js";
375
3
  class PackageJson {
376
4
  static glob = "**/package.json";
377
5
  static defaultExcludes = [
@@ -387,14 +15,21 @@ class PackageJson {
387
15
  return (filenames)=>{
388
16
  const filtered = Filter.exclude(filenames, excludes);
389
17
  if (0 === filtered.length) return [];
390
- if (!skipSort) for (const filepath of filtered){
391
- const content = readFileSync(filepath, "utf-8");
392
- const sorted = sort_package_json(content);
393
- writeFileSync(filepath, sorted, "utf-8");
394
- }
395
18
  const files = filtered.join(" ");
19
+ const commands = [];
20
+ if (!skipSort) {
21
+ const pm = Command_Command.detectPackageManager();
22
+ const prefix = Command_Command.getExecPrefix(pm);
23
+ const sortCmd = [
24
+ ...prefix,
25
+ "sort-package-json",
26
+ files
27
+ ].join(" ");
28
+ commands.push(sortCmd);
29
+ }
396
30
  const biomeCmd = options.biomeConfig ? `biome check --write --max-diagnostics=none --config-path=${options.biomeConfig} ${files}` : `biome check --write --max-diagnostics=none ${files}`;
397
- return biomeCmd;
31
+ commands.push(biomeCmd);
32
+ return commands.join(" && ");
398
33
  };
399
34
  }
400
35
  }
@@ -407,6 +42,11 @@ class PnpmWorkspace {
407
42
  static glob = "pnpm-workspace.yaml";
408
43
  static defaultExcludes = [];
409
44
  static handler = PnpmWorkspace.create();
45
+ static SORTABLE_ARRAY_KEYS = new Set([
46
+ "packages",
47
+ "onlyBuiltDependencies",
48
+ "publicHoistPattern"
49
+ ]);
410
50
  static sortContent(content) {
411
51
  const result = {};
412
52
  const keys = Object.keys(content).sort((a, b)=>{
@@ -416,13 +56,7 @@ class PnpmWorkspace {
416
56
  });
417
57
  for (const key of keys){
418
58
  const value = content[key];
419
- if ("packages" === key && Array.isArray(value)) result[key] = [
420
- ...value
421
- ].sort();
422
- else if ("onlyBuiltDependencies" === key && Array.isArray(value)) result[key] = [
423
- ...value
424
- ].sort();
425
- else if ("publicHoistPattern" === key && Array.isArray(value)) result[key] = [
59
+ if (PnpmWorkspace.SORTABLE_ARRAY_KEYS.has(key) && Array.isArray(value)) result[key] = [
426
60
  ...value
427
61
  ].sort();
428
62
  else result[key] = value;
@@ -448,6 +82,7 @@ class PnpmWorkspace {
448
82
  if (!skipSort || !skipFormat) {
449
83
  const formatted = stringify(parsed, DEFAULT_STRINGIFY_OPTIONS);
450
84
  writeFileSync(filepath, formatted, "utf-8");
85
+ return `git add ${filepath}`;
451
86
  }
452
87
  return [];
453
88
  };
@@ -472,578 +107,6 @@ class ShellScripts {
472
107
  };
473
108
  }
474
109
  }
475
- class TsDocLinter {
476
- eslint;
477
- constructor(options = {}){
478
- const ignorePatterns = options.ignorePatterns ?? [];
479
- const config = [
480
- {
481
- ignores: [
482
- "**/node_modules/**",
483
- "**/dist/**",
484
- "**/coverage/**",
485
- ...ignorePatterns
486
- ]
487
- },
488
- {
489
- files: [
490
- "**/*.ts",
491
- "**/*.tsx",
492
- "**/*.mts",
493
- "**/*.cts"
494
- ],
495
- languageOptions: {
496
- parser: parser
497
- },
498
- plugins: {
499
- tsdoc: eslint_plugin_tsdoc
500
- },
501
- rules: {
502
- "tsdoc/syntax": "error"
503
- }
504
- }
505
- ];
506
- this.eslint = new ESLint({
507
- overrideConfigFile: true,
508
- overrideConfig: config
509
- });
510
- }
511
- async lintFiles(filePaths) {
512
- if (0 === filePaths.length) return [];
513
- const results = await this.eslint.lintFiles(filePaths);
514
- return results.map((result)=>({
515
- filePath: result.filePath,
516
- errorCount: result.errorCount,
517
- warningCount: result.warningCount,
518
- messages: result.messages.map((msg)=>({
519
- line: msg.line,
520
- column: msg.column,
521
- severity: msg.severity,
522
- message: msg.message,
523
- ruleId: msg.ruleId
524
- }))
525
- }));
526
- }
527
- async lintFilesAndThrow(filePaths) {
528
- const results = await this.lintFiles(filePaths);
529
- const errors = [];
530
- for (const result of results)if (result.errorCount > 0) {
531
- for (const msg of result.messages)if (2 === msg.severity) errors.push(`${result.filePath}:${msg.line}:${msg.column} - ${msg.message}`);
532
- }
533
- if (errors.length > 0) throw new Error(`TSDoc validation failed:\n${errors.join("\n")}`);
534
- }
535
- static formatResults(results) {
536
- const lines = [];
537
- for (const result of results)if (0 !== result.errorCount || 0 !== result.warningCount) {
538
- lines.push(`\n${result.filePath}`);
539
- for (const msg of result.messages){
540
- const severity = 2 === msg.severity ? "error" : "warning";
541
- const rule = msg.ruleId ? ` (${msg.ruleId})` : "";
542
- lines.push(` ${msg.line}:${msg.column} ${severity} ${msg.message}${rule}`);
543
- }
544
- }
545
- const totalErrors = results.reduce((sum, r)=>sum + r.errorCount, 0);
546
- const totalWarnings = results.reduce((sum, r)=>sum + r.warningCount, 0);
547
- if (totalErrors > 0 || totalWarnings > 0) lines.push(`\n✖ ${totalErrors} error(s), ${totalWarnings} warning(s)`);
548
- return lines.join("\n");
549
- }
550
- static hasErrors(results) {
551
- return results.some((r)=>r.errorCount > 0);
552
- }
553
- }
554
- const TS_EXTENSIONS = [
555
- ".ts",
556
- ".tsx",
557
- ".mts",
558
- ".cts"
559
- ];
560
- class EntryExtractor {
561
- extract(packageJson) {
562
- const entries = {};
563
- const unresolved = [];
564
- const { exports } = packageJson;
565
- if (!exports) {
566
- const mainEntry = packageJson.module ?? packageJson.main;
567
- if (mainEntry && this.isTypeScriptFile(mainEntry)) entries["."] = mainEntry;
568
- else if (mainEntry) unresolved.push(".");
569
- return {
570
- entries,
571
- unresolved
572
- };
573
- }
574
- if ("string" == typeof exports) {
575
- if (this.isTypeScriptFile(exports)) entries["."] = exports;
576
- else unresolved.push(".");
577
- return {
578
- entries,
579
- unresolved
580
- };
581
- }
582
- this.extractFromObject(exports, entries, unresolved, ".");
583
- return {
584
- entries,
585
- unresolved
586
- };
587
- }
588
- extractFromObject(obj, entries, unresolved, currentPath) {
589
- for (const [key, value] of Object.entries(obj)){
590
- const exportPath = key.startsWith(".") ? key : currentPath;
591
- if ("string" == typeof value) {
592
- if (this.isTypeScriptFile(value)) entries[exportPath] = value;
593
- else if (key.startsWith(".")) unresolved.push(exportPath);
594
- } else if (value && "object" == typeof value && !Array.isArray(value)) {
595
- const nested = value;
596
- const tsPath = this.findTypeScriptCondition(nested);
597
- if (tsPath) entries[exportPath] = tsPath;
598
- else if (key.startsWith(".")) this.extractFromObject(nested, entries, unresolved, exportPath);
599
- else {
600
- const sourcePath = this.findSourceCondition(nested);
601
- if (sourcePath && this.isTypeScriptFile(sourcePath)) entries[exportPath] = sourcePath;
602
- }
603
- }
604
- }
605
- }
606
- findTypeScriptCondition(conditions) {
607
- const priorityKeys = [
608
- "source",
609
- "typescript",
610
- "development",
611
- "default"
612
- ];
613
- for (const key of priorityKeys){
614
- const value = conditions[key];
615
- if ("string" == typeof value && this.isTypeScriptFile(value)) return value;
616
- if (value && "object" == typeof value) {
617
- const nested = this.findTypeScriptCondition(value);
618
- if (nested) return nested;
619
- }
620
- }
621
- return null;
622
- }
623
- findSourceCondition(conditions) {
624
- const priorityKeys = [
625
- "source",
626
- "import",
627
- "require",
628
- "default"
629
- ];
630
- for (const key of priorityKeys){
631
- const value = conditions[key];
632
- if ("string" == typeof value) return value;
633
- if (value && "object" == typeof value) {
634
- const nested = this.findSourceCondition(value);
635
- if (nested) return nested;
636
- }
637
- }
638
- return null;
639
- }
640
- isTypeScriptFile(filePath) {
641
- return TS_EXTENSIONS.some((ext)=>filePath.endsWith(ext));
642
- }
643
- }
644
- class ImportGraph {
645
- options;
646
- program = null;
647
- compilerOptions = null;
648
- moduleResolutionCache = null;
649
- constructor(options){
650
- this.options = options;
651
- }
652
- traceFromEntries(entryPaths) {
653
- const errors = [];
654
- const visited = new Set();
655
- const entries = [];
656
- const initResult = this.initializeProgram();
657
- if (!initResult.success) return {
658
- files: [],
659
- entries: [],
660
- errors: [
661
- initResult.error
662
- ]
663
- };
664
- for (const entryPath of entryPaths){
665
- const absolutePath = this.resolveEntryPath(entryPath);
666
- if (!existsSync(absolutePath)) {
667
- errors.push({
668
- type: "entry_not_found",
669
- message: `Entry file not found: ${entryPath}`,
670
- path: absolutePath
671
- });
672
- continue;
673
- }
674
- entries.push(absolutePath);
675
- this.traceImports(absolutePath, visited, errors);
676
- }
677
- const files = Array.from(visited).filter((file)=>this.isSourceFile(file));
678
- return {
679
- files: files.sort(),
680
- entries,
681
- errors
682
- };
683
- }
684
- traceFromPackageExports(packageJsonPath) {
685
- const absolutePath = this.resolveEntryPath(packageJsonPath);
686
- let packageJson;
687
- try {
688
- if (!existsSync(absolutePath)) return {
689
- files: [],
690
- entries: [],
691
- errors: [
692
- {
693
- type: "package_json_not_found",
694
- message: `Failed to read package.json: File not found at ${absolutePath}`,
695
- path: absolutePath
696
- }
697
- ]
698
- };
699
- const content = readFileSync(absolutePath, "utf-8");
700
- packageJson = JSON.parse(content);
701
- } catch (error) {
702
- const message = error instanceof Error ? error.message : String(error);
703
- return {
704
- files: [],
705
- entries: [],
706
- errors: [
707
- {
708
- type: "package_json_parse_error",
709
- message: `Failed to parse package.json: ${message}`,
710
- path: absolutePath
711
- }
712
- ]
713
- };
714
- }
715
- const extractor = new EntryExtractor();
716
- const { entries } = extractor.extract(packageJson);
717
- const packageDir = dirname(absolutePath);
718
- const entryPaths = Object.values(entries).map((p)=>resolve(packageDir, p));
719
- return this.traceFromEntries(entryPaths);
720
- }
721
- initializeProgram() {
722
- if (this.program) return {
723
- success: true
724
- };
725
- const configPath = this.findTsConfig();
726
- if (!configPath) {
727
- this.compilerOptions = {
728
- moduleResolution: typescript.ModuleResolutionKind.NodeNext,
729
- module: typescript.ModuleKind.NodeNext,
730
- target: typescript.ScriptTarget.ESNext,
731
- strict: true
732
- };
733
- this.moduleResolutionCache = typescript.createModuleResolutionCache(this.options.rootDir, (fileName)=>fileName.toLowerCase(), this.compilerOptions);
734
- const host = typescript.createCompilerHost(this.compilerOptions, true);
735
- host.getCurrentDirectory = ()=>this.options.rootDir;
736
- this.program = typescript.createProgram([], this.compilerOptions, host);
737
- return {
738
- success: true
739
- };
740
- }
741
- const configFile = typescript.readConfigFile(configPath, (path)=>readFileSync(path, "utf-8"));
742
- if (configFile.error) {
743
- const message = typescript.flattenDiagnosticMessageText(configFile.error.messageText, "\n");
744
- return {
745
- success: false,
746
- error: {
747
- type: "tsconfig_read_error",
748
- message: `Failed to read tsconfig.json: ${message}`,
749
- path: configPath
750
- }
751
- };
752
- }
753
- const parsed = typescript.parseJsonConfigFileContent(configFile.config, typescript.sys, dirname(configPath));
754
- if (parsed.errors.length > 0) {
755
- const messages = parsed.errors.map((e)=>typescript.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
756
- return {
757
- success: false,
758
- error: {
759
- type: "tsconfig_parse_error",
760
- message: `Failed to parse tsconfig.json: ${messages}`,
761
- path: configPath
762
- }
763
- };
764
- }
765
- this.compilerOptions = parsed.options;
766
- this.moduleResolutionCache = typescript.createModuleResolutionCache(this.options.rootDir, (fileName)=>fileName.toLowerCase(), this.compilerOptions);
767
- const host = typescript.createCompilerHost(this.compilerOptions, true);
768
- host.getCurrentDirectory = ()=>this.options.rootDir;
769
- this.program = typescript.createProgram([], this.compilerOptions, host);
770
- return {
771
- success: true
772
- };
773
- }
774
- findTsConfig() {
775
- if (this.options.tsconfigPath) {
776
- const customPath = isAbsolute(this.options.tsconfigPath) ? this.options.tsconfigPath : resolve(this.options.rootDir, this.options.tsconfigPath);
777
- if (existsSync(customPath)) return customPath;
778
- return null;
779
- }
780
- const configPath = typescript.findConfigFile(this.options.rootDir, (path)=>existsSync(path));
781
- return configPath ?? null;
782
- }
783
- resolveEntryPath(entryPath) {
784
- if (isAbsolute(entryPath)) return normalize(entryPath);
785
- return normalize(resolve(this.options.rootDir, entryPath));
786
- }
787
- traceImports(filePath, visited, errors) {
788
- const normalizedPath = normalize(filePath);
789
- if (visited.has(normalizedPath)) return;
790
- if (this.isExternalModule(normalizedPath)) return;
791
- visited.add(normalizedPath);
792
- let content;
793
- try {
794
- content = readFileSync(normalizedPath, "utf-8");
795
- } catch {
796
- errors.push({
797
- type: "file_read_error",
798
- message: `Failed to read file: ${normalizedPath}`,
799
- path: normalizedPath
800
- });
801
- return;
802
- }
803
- const sourceFile = typescript.createSourceFile(normalizedPath, content, typescript.ScriptTarget.Latest, true);
804
- const imports = this.extractImports(sourceFile);
805
- for (const importPath of imports){
806
- const resolved = this.resolveImport(importPath, normalizedPath);
807
- if (resolved) this.traceImports(resolved, visited, errors);
808
- }
809
- }
810
- extractImports(sourceFile) {
811
- const imports = [];
812
- const visit = (node)=>{
813
- if (typescript.isImportDeclaration(node)) {
814
- const specifier = node.moduleSpecifier;
815
- if (typescript.isStringLiteral(specifier)) imports.push(specifier.text);
816
- } else if (typescript.isExportDeclaration(node)) {
817
- const specifier = node.moduleSpecifier;
818
- if (specifier && typescript.isStringLiteral(specifier)) imports.push(specifier.text);
819
- } else if (typescript.isCallExpression(node)) {
820
- const expression = node.expression;
821
- if (expression.kind === typescript.SyntaxKind.ImportKeyword && node.arguments.length > 0) {
822
- const arg = node.arguments[0];
823
- if (arg && typescript.isStringLiteral(arg)) imports.push(arg.text);
824
- }
825
- }
826
- typescript.forEachChild(node, visit);
827
- };
828
- visit(sourceFile);
829
- return imports;
830
- }
831
- resolveImport(specifier, fromFile) {
832
- if (!specifier.startsWith(".") && !specifier.startsWith("/")) {
833
- if (!this.compilerOptions?.paths || !Object.keys(this.compilerOptions.paths).length) return null;
834
- }
835
- if (!this.compilerOptions || !this.moduleResolutionCache) return null;
836
- const resolved = typescript.resolveModuleName(specifier, fromFile, this.compilerOptions, typescript.sys, this.moduleResolutionCache);
837
- if (resolved.resolvedModule) {
838
- const resolvedPath = resolved.resolvedModule.resolvedFileName;
839
- if (resolved.resolvedModule.isExternalLibraryImport) return null;
840
- if (resolvedPath.endsWith(".d.ts")) {
841
- const sourcePath = resolvedPath.replace(/\.d\.ts$/, ".ts");
842
- if (existsSync(sourcePath)) return sourcePath;
843
- return null;
844
- }
845
- return resolvedPath;
846
- }
847
- return null;
848
- }
849
- isExternalModule(filePath) {
850
- return filePath.includes("/node_modules/") || filePath.includes("\\node_modules\\");
851
- }
852
- isSourceFile(filePath) {
853
- if (!filePath.endsWith(".ts") && !filePath.endsWith(".tsx") && !filePath.endsWith(".mts") && !filePath.endsWith(".cts")) return false;
854
- if (filePath.endsWith(".d.ts") || filePath.endsWith(".d.mts") || filePath.endsWith(".d.cts")) return false;
855
- if (filePath.includes(".test.") || filePath.includes(".spec.")) return false;
856
- if (filePath.includes("/__test__/") || filePath.includes("\\__test__\\")) return false;
857
- if (filePath.includes("/__tests__/") || filePath.includes("\\__tests__\\")) return false;
858
- const excludePatterns = this.options.excludePatterns ?? [];
859
- for (const pattern of excludePatterns)if (filePath.includes(pattern)) return false;
860
- return true;
861
- }
862
- static fromEntries(entryPaths, options) {
863
- const graph = new ImportGraph(options);
864
- return graph.traceFromEntries(entryPaths);
865
- }
866
- static fromPackageExports(packageJsonPath, options) {
867
- const graph = new ImportGraph(options);
868
- return graph.traceFromPackageExports(packageJsonPath);
869
- }
870
- }
871
- class TsDocResolver {
872
- options;
873
- constructor(options){
874
- this.options = options;
875
- }
876
- resolve() {
877
- const { rootDir } = this.options;
878
- const workspaces = [];
879
- const repoTsdocPath = join(rootDir, "tsdoc.json");
880
- const repoTsdocConfig = existsSync(repoTsdocPath) ? repoTsdocPath : void 0;
881
- let workspaceInfos;
882
- let isMonorepo = false;
883
- try {
884
- workspaceInfos = getWorkspaces(rootDir);
885
- isMonorepo = workspaceInfos.length > 1;
886
- } catch {
887
- workspaceInfos = [];
888
- }
889
- if (0 === workspaceInfos.length) {
890
- const result = this.resolveWorkspace(rootDir, repoTsdocConfig);
891
- if (result) workspaces.push(result);
892
- } else for (const info of workspaceInfos){
893
- const workspacePath = info.path;
894
- const result = this.resolveWorkspace(workspacePath, repoTsdocConfig);
895
- if (result) workspaces.push(result);
896
- }
897
- return {
898
- workspaces,
899
- isMonorepo,
900
- repoTsdocConfig
901
- };
902
- }
903
- resolveWorkspace(workspacePath, repoTsdocConfig) {
904
- const packageJsonPath = join(workspacePath, "package.json");
905
- if (!existsSync(packageJsonPath)) return null;
906
- let packageJson;
907
- try {
908
- const content = readFileSync(packageJsonPath, "utf-8");
909
- packageJson = JSON.parse(content);
910
- } catch {
911
- return null;
912
- }
913
- const workspaceTsdocPath = join(workspacePath, "tsdoc.json");
914
- const workspaceTsdocConfig = existsSync(workspaceTsdocPath) ? workspaceTsdocPath : void 0;
915
- const tsdocConfigPath = workspaceTsdocConfig ?? repoTsdocConfig;
916
- if (!tsdocConfigPath) return null;
917
- if (!packageJson.exports) return null;
918
- const name = packageJson.name ?? relative(this.options.rootDir, workspacePath);
919
- const errors = [];
920
- const graph = new ImportGraph({
921
- rootDir: workspacePath,
922
- excludePatterns: this.options.excludePatterns
923
- });
924
- const result = graph.traceFromPackageExports(packageJsonPath);
925
- for (const error of result.errors)errors.push(error.message);
926
- return {
927
- name,
928
- path: workspacePath,
929
- tsdocConfigPath,
930
- files: result.files,
931
- errors
932
- };
933
- }
934
- filterStagedFiles(stagedFiles) {
935
- const result = this.resolve();
936
- const output = [];
937
- for (const workspace of result.workspaces){
938
- const workspaceFiles = new Set(workspace.files);
939
- const matchedFiles = stagedFiles.filter((f)=>workspaceFiles.has(f));
940
- if (matchedFiles.length > 0) output.push({
941
- files: matchedFiles,
942
- tsdocConfigPath: workspace.tsdocConfigPath
943
- });
944
- }
945
- return output;
946
- }
947
- needsLinting(filePath) {
948
- const result = this.resolve();
949
- for (const workspace of result.workspaces)if (workspace.files.includes(filePath)) return true;
950
- return false;
951
- }
952
- getTsDocConfig(filePath) {
953
- const result = this.resolve();
954
- for (const workspace of result.workspaces)if (workspace.files.includes(filePath)) return workspace.tsdocConfigPath;
955
- }
956
- findWorkspace(filePath) {
957
- const result = this.resolve();
958
- for (const workspace of result.workspaces)if (filePath.startsWith(workspace.path)) return workspace;
959
- }
960
- }
961
- class TypeScript {
962
- static glob = "*.{ts,cts,mts,tsx}";
963
- static defaultExcludes = [];
964
- static defaultTsdocExcludes = [
965
- ".test.",
966
- ".spec.",
967
- "__test__",
968
- "__tests__"
969
- ];
970
- static detectCompiler(cwd = process.cwd()) {
971
- const packageJsonPath = join(cwd, "package.json");
972
- if (!existsSync(packageJsonPath)) return;
973
- try {
974
- const content = readFileSync(packageJsonPath, "utf-8");
975
- const pkg = JSON.parse(content);
976
- const allDeps = {
977
- ...pkg.dependencies,
978
- ...pkg.devDependencies
979
- };
980
- if ("@typescript/native-preview" in allDeps) return "tsgo";
981
- if ("typescript" in allDeps) return "tsc";
982
- } catch {}
983
- }
984
- static isAvailable() {
985
- return void 0 !== TypeScript.detectCompiler();
986
- }
987
- static getDefaultTypecheckCommand() {
988
- const compiler = TypeScript.detectCompiler();
989
- if (!compiler) throw new Error("No TypeScript compiler found. Install 'typescript' or '@typescript/native-preview' as a dev dependency.");
990
- const pm = Command.detectPackageManager();
991
- const prefix = Command.getExecPrefix(pm);
992
- return [
993
- ...prefix,
994
- compiler,
995
- "--noEmit"
996
- ].join(" ");
997
- }
998
- static handler = TypeScript.create();
999
- static isTsdocAvailable(cwd = process.cwd()) {
1000
- const tsdocPath = join(cwd, "tsdoc.json");
1001
- return existsSync(tsdocPath);
1002
- }
1003
- static create(options = {}) {
1004
- const excludes = options.exclude ?? [
1005
- ...TypeScript.defaultExcludes
1006
- ];
1007
- const tsdocExcludes = options.excludeTsdoc ?? [
1008
- ...TypeScript.defaultTsdocExcludes
1009
- ];
1010
- const skipTsdoc = options.skipTsdoc ?? false;
1011
- const skipTypecheck = options.skipTypecheck ?? false;
1012
- const rootDir = options.rootDir ?? process.cwd();
1013
- let typecheckCommand;
1014
- const getTypecheckCommand = ()=>{
1015
- if (void 0 === typecheckCommand) typecheckCommand = options.typecheckCommand ?? TypeScript.getDefaultTypecheckCommand();
1016
- return typecheckCommand;
1017
- };
1018
- return async (filenames)=>{
1019
- const filtered = Filter.exclude(filenames, excludes);
1020
- if (0 === filtered.length) return [];
1021
- const commands = [];
1022
- if (!skipTsdoc) {
1023
- const resolver = new TsDocResolver({
1024
- rootDir,
1025
- excludePatterns: [
1026
- ...tsdocExcludes
1027
- ]
1028
- });
1029
- const absoluteFiles = filtered.map((f)=>isAbsolute(f) ? f : join(rootDir, f));
1030
- const tsdocGroups = resolver.filterStagedFiles(absoluteFiles);
1031
- for (const group of tsdocGroups)if (group.files.length > 0) {
1032
- const linter = new TsDocLinter({
1033
- ignorePatterns: tsdocExcludes.map((p)=>`**/*${p}*`)
1034
- });
1035
- const results = await linter.lintFiles(group.files);
1036
- if (TsDocLinter.hasErrors(results)) {
1037
- const output = TsDocLinter.formatResults(results);
1038
- throw new Error(`TSDoc validation failed:\n${output}`);
1039
- }
1040
- }
1041
- }
1042
- if (!skipTypecheck && filtered.length > 0) commands.push(getTypecheckCommand());
1043
- return commands;
1044
- };
1045
- }
1046
- }
1047
110
  const Yaml_DEFAULT_STRINGIFY_OPTIONS = {
1048
111
  indent: 2,
1049
112
  lineWidth: 0,
@@ -1084,6 +147,7 @@ class Yaml {
1084
147
  } catch (error) {
1085
148
  throw new Error(`Invalid YAML in ${filepath}: ${error instanceof Error ? error.message : String(error)}`);
1086
149
  }
150
+ if (!skipFormat && filtered.length > 0) return `git add ${filtered.join(" ")}`;
1087
151
  return [];
1088
152
  };
1089
153
  }
@@ -1118,52 +182,48 @@ function createConfig(options = {}) {
1118
182
  const handlerOptions = "object" == typeof options.typescript ? options.typescript : {};
1119
183
  config[TypeScript.glob] = TypeScript.create(handlerOptions);
1120
184
  }
1121
- if (true === options.designDocs || "object" == typeof options.designDocs) {
1122
- const handlerOptions = "object" == typeof options.designDocs ? options.designDocs : {};
1123
- config[DesignDocs.glob] = DesignDocs.create(handlerOptions);
1124
- }
1125
185
  if (options.custom) for (const [glob, handler] of Object.entries(options.custom))config[glob] = handler;
1126
186
  return config;
1127
187
  }
1128
188
  class Preset {
1129
189
  static minimal(extend = {}) {
1130
- return createConfig({
190
+ const options = {
1131
191
  packageJson: extend.packageJson ?? {},
1132
192
  biome: extend.biome ?? {},
1133
193
  markdown: extend.markdown ?? false,
1134
194
  yaml: extend.yaml ?? false,
1135
195
  pnpmWorkspace: extend.pnpmWorkspace ?? false,
1136
196
  shellScripts: extend.shellScripts ?? false,
1137
- typescript: extend.typescript ?? false,
1138
- designDocs: extend.designDocs ?? false,
1139
- custom: extend.custom
1140
- });
197
+ typescript: extend.typescript ?? false
198
+ };
199
+ if (void 0 !== extend.custom) options.custom = extend.custom;
200
+ return createConfig(options);
1141
201
  }
1142
202
  static standard(extend = {}) {
1143
- return createConfig({
203
+ const options = {
1144
204
  packageJson: extend.packageJson ?? {},
1145
205
  biome: extend.biome ?? {},
1146
206
  markdown: extend.markdown ?? {},
1147
207
  yaml: extend.yaml ?? {},
1148
208
  pnpmWorkspace: extend.pnpmWorkspace ?? {},
1149
209
  shellScripts: extend.shellScripts ?? {},
1150
- typescript: extend.typescript ?? false,
1151
- designDocs: extend.designDocs ?? false,
1152
- custom: extend.custom
1153
- });
210
+ typescript: extend.typescript ?? false
211
+ };
212
+ if (void 0 !== extend.custom) options.custom = extend.custom;
213
+ return createConfig(options);
1154
214
  }
1155
215
  static full(extend = {}) {
1156
- return createConfig({
216
+ const options = {
1157
217
  packageJson: extend.packageJson ?? {},
1158
218
  biome: extend.biome ?? {},
1159
219
  markdown: extend.markdown ?? {},
1160
220
  yaml: extend.yaml ?? {},
1161
221
  pnpmWorkspace: extend.pnpmWorkspace ?? {},
1162
222
  shellScripts: extend.shellScripts ?? {},
1163
- typescript: extend.typescript ?? {},
1164
- designDocs: extend.designDocs ?? true,
1165
- custom: extend.custom
1166
- });
223
+ typescript: extend.typescript ?? {}
224
+ };
225
+ if (void 0 !== extend.custom) options.custom = extend.custom;
226
+ return createConfig(options);
1167
227
  }
1168
228
  static get(name, extend = {}) {
1169
229
  switch(name){
@@ -1184,4 +244,5 @@ class Handler {
1184
244
  throw new Error("Handler.create() must be implemented by subclass");
1185
245
  }
1186
246
  }
1187
- export { Biome, Command, ConfigSearch, DesignDocs, EntryExtractor, Filter, Handler, ImportGraph, Markdown, PackageJson, PnpmWorkspace, Preset, ShellScripts, TsDocLinter, TsDocResolver, TypeScript, Yaml, createConfig };
247
+ export { Biome, Command, ConfigSearch, EntryExtractor, Filter, ImportGraph, Markdown, TsDocLinter, TsDocResolver, TypeScript, checkCommand, initCommand, rootCommand, runCli } from "./376.js";
248
+ export { Handler, PackageJson, PnpmWorkspace, Preset, ShellScripts, Yaml, createConfig };