@savvy-web/lint-staged 0.3.2 → 0.4.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/376.js +237 -29
- package/README.md +6 -3
- package/index.d.ts +176 -26
- package/index.js +56 -114
- package/package.json +20 -3
- package/tsdoc-metadata.json +1 -1
package/376.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Command, Options } from "@effect/cli";
|
|
1
|
+
import { Args, Command, Options } from "@effect/cli";
|
|
2
2
|
import { NodeContext, NodeRuntime } from "@effect/platform-node";
|
|
3
3
|
import { Effect } from "effect";
|
|
4
4
|
import { isDeepStrictEqual } from "node:util";
|
|
@@ -7,19 +7,36 @@ import { applyEdits, modify, parse } from "jsonc-parser";
|
|
|
7
7
|
import { execSync } from "node:child_process";
|
|
8
8
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
9
9
|
import { dirname, isAbsolute, join, normalize, relative, resolve } from "node:path";
|
|
10
|
+
import { findProjectRoot, getWorkspaceInfos } from "workspace-tools";
|
|
10
11
|
import { cosmiconfigSync, defaultLoaders } from "cosmiconfig";
|
|
11
12
|
import parser from "@typescript-eslint/parser";
|
|
12
13
|
import { ESLint } from "eslint";
|
|
13
14
|
import eslint_plugin_tsdoc from "eslint-plugin-tsdoc";
|
|
14
|
-
import { getWorkspaceInfos } from "workspace-tools";
|
|
15
15
|
import typescript from "typescript";
|
|
16
|
+
import sort_package_json from "sort-package-json";
|
|
17
|
+
import { parse as external_yaml_parse, stringify } from "yaml";
|
|
18
|
+
import { format, resolveConfig } from "prettier";
|
|
19
|
+
import { lint } from "yaml-lint";
|
|
16
20
|
const VALID_COMMAND_PATTERN = /^[\w@/-]+$/;
|
|
17
21
|
function validateCommandName(name) {
|
|
18
22
|
if (!VALID_COMMAND_PATTERN.test(name)) throw new Error(`Invalid command name: "${name}". Only alphanumeric characters, hyphens, underscores, @ and / are allowed.`);
|
|
19
23
|
}
|
|
20
24
|
class Command_Command {
|
|
21
25
|
static cachedPackageManager = null;
|
|
22
|
-
static
|
|
26
|
+
static cachedRoot = null;
|
|
27
|
+
static findRoot(cwd = process.cwd()) {
|
|
28
|
+
if (null !== Command_Command.cachedRoot) return Command_Command.cachedRoot;
|
|
29
|
+
try {
|
|
30
|
+
const root = findProjectRoot(cwd);
|
|
31
|
+
if (root) {
|
|
32
|
+
Command_Command.cachedRoot = root;
|
|
33
|
+
return root;
|
|
34
|
+
}
|
|
35
|
+
} catch {}
|
|
36
|
+
Command_Command.cachedRoot = cwd;
|
|
37
|
+
return cwd;
|
|
38
|
+
}
|
|
39
|
+
static detectPackageManager(cwd = Command_Command.findRoot()) {
|
|
23
40
|
if (null !== Command_Command.cachedPackageManager) return Command_Command.cachedPackageManager;
|
|
24
41
|
const packageJsonPath = join(cwd, "package.json");
|
|
25
42
|
if (!existsSync(packageJsonPath)) {
|
|
@@ -54,7 +71,9 @@ class Command_Command {
|
|
|
54
71
|
];
|
|
55
72
|
case "bun":
|
|
56
73
|
return [
|
|
57
|
-
"
|
|
74
|
+
"bun",
|
|
75
|
+
"x",
|
|
76
|
+
"--no-install"
|
|
58
77
|
];
|
|
59
78
|
default:
|
|
60
79
|
return [
|
|
@@ -65,6 +84,7 @@ class Command_Command {
|
|
|
65
84
|
}
|
|
66
85
|
static clearCache() {
|
|
67
86
|
Command_Command.cachedPackageManager = null;
|
|
87
|
+
Command_Command.cachedRoot = null;
|
|
68
88
|
}
|
|
69
89
|
static isAvailable(command) {
|
|
70
90
|
validateCommandName(command);
|
|
@@ -111,6 +131,12 @@ class Command_Command {
|
|
|
111
131
|
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.`);
|
|
112
132
|
return result.command;
|
|
113
133
|
}
|
|
134
|
+
static findSavvyLint() {
|
|
135
|
+
const result = Command_Command.findTool("savvy-lint");
|
|
136
|
+
if (result.available && result.command) return result.command;
|
|
137
|
+
const root = Command_Command.findRoot();
|
|
138
|
+
return `node ${root}/dist/dev/bin/savvy-lint.js`;
|
|
139
|
+
}
|
|
114
140
|
static exec(command) {
|
|
115
141
|
return execSync(command, {
|
|
116
142
|
encoding: "utf-8"
|
|
@@ -190,6 +216,15 @@ const TOOL_CONFIGS = {
|
|
|
190
216
|
"prettier.config.js",
|
|
191
217
|
"package.json"
|
|
192
218
|
]
|
|
219
|
+
},
|
|
220
|
+
yamllint: {
|
|
221
|
+
moduleName: "yaml-lint",
|
|
222
|
+
libConfigFiles: [
|
|
223
|
+
".yaml-lint.json"
|
|
224
|
+
],
|
|
225
|
+
standardPlaces: [
|
|
226
|
+
".yaml-lint.json"
|
|
227
|
+
]
|
|
193
228
|
}
|
|
194
229
|
};
|
|
195
230
|
class ConfigSearch {
|
|
@@ -846,36 +881,39 @@ class TypeScript {
|
|
|
846
881
|
"__test__",
|
|
847
882
|
"__tests__"
|
|
848
883
|
];
|
|
849
|
-
static
|
|
850
|
-
|
|
851
|
-
if (
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
...pkg.devDependencies
|
|
884
|
+
static cachedCompilerResult = null;
|
|
885
|
+
static detectCompiler(_cwd) {
|
|
886
|
+
if (null !== TypeScript.cachedCompilerResult) return TypeScript.cachedCompilerResult.compiler;
|
|
887
|
+
const tsgo = Command_Command.findTool("tsgo");
|
|
888
|
+
if (tsgo.available) {
|
|
889
|
+
TypeScript.cachedCompilerResult = {
|
|
890
|
+
compiler: "tsgo",
|
|
891
|
+
tool: tsgo
|
|
858
892
|
};
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
893
|
+
return "tsgo";
|
|
894
|
+
}
|
|
895
|
+
const tsc = Command_Command.findTool("tsc");
|
|
896
|
+
if (tsc.available) {
|
|
897
|
+
TypeScript.cachedCompilerResult = {
|
|
898
|
+
compiler: "tsc",
|
|
899
|
+
tool: tsc
|
|
900
|
+
};
|
|
901
|
+
return "tsc";
|
|
902
|
+
}
|
|
862
903
|
}
|
|
863
904
|
static isAvailable() {
|
|
864
905
|
return void 0 !== TypeScript.detectCompiler();
|
|
865
906
|
}
|
|
866
907
|
static getDefaultTypecheckCommand() {
|
|
867
908
|
const compiler = TypeScript.detectCompiler();
|
|
868
|
-
if (!compiler) throw new Error("No TypeScript compiler found. Install 'typescript' or '@typescript/native-preview' as a dev dependency.");
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
compiler,
|
|
874
|
-
"--noEmit"
|
|
875
|
-
].join(" ");
|
|
909
|
+
if (!compiler || !TypeScript.cachedCompilerResult) throw new Error("No TypeScript compiler found. Install 'typescript' or '@typescript/native-preview' as a dev dependency.");
|
|
910
|
+
return `${TypeScript.cachedCompilerResult.tool.command} --noEmit`;
|
|
911
|
+
}
|
|
912
|
+
static clearCache() {
|
|
913
|
+
TypeScript.cachedCompilerResult = null;
|
|
876
914
|
}
|
|
877
915
|
static handler = TypeScript.create();
|
|
878
|
-
static isTsdocAvailable(cwd =
|
|
916
|
+
static isTsdocAvailable(cwd = Command_Command.findRoot()) {
|
|
879
917
|
const tsdocPath = join(cwd, "tsdoc.json");
|
|
880
918
|
return existsSync(tsdocPath);
|
|
881
919
|
}
|
|
@@ -888,7 +926,7 @@ class TypeScript {
|
|
|
888
926
|
];
|
|
889
927
|
const skipTsdoc = options.skipTsdoc ?? false;
|
|
890
928
|
const skipTypecheck = options.skipTypecheck ?? false;
|
|
891
|
-
const rootDir = options.rootDir ??
|
|
929
|
+
const rootDir = options.rootDir ?? Command_Command.findRoot();
|
|
892
930
|
let typecheckCommand;
|
|
893
931
|
const getTypecheckCommand = ()=>{
|
|
894
932
|
if (void 0 === typecheckCommand) typecheckCommand = options.typecheckCommand ?? TypeScript.getDefaultTypecheckCommand();
|
|
@@ -1515,16 +1553,186 @@ const checkCommand = Command.make("check", {
|
|
|
1515
1553
|
if (hasIssues) yield* Effect.log(`${check_WARNING} Some issues found. Run 'savvy-lint init' to fix.`);
|
|
1516
1554
|
else yield* Effect.log(`${check_CHECK_MARK} Lint-staged is configured correctly.`);
|
|
1517
1555
|
})).pipe(Command.withDescription("Check current lint-staged configuration and tool availability"));
|
|
1556
|
+
const DEFAULT_STRINGIFY_OPTIONS = {
|
|
1557
|
+
indent: 2,
|
|
1558
|
+
lineWidth: 0,
|
|
1559
|
+
singleQuote: false
|
|
1560
|
+
};
|
|
1561
|
+
class PnpmWorkspace {
|
|
1562
|
+
static glob = "pnpm-workspace.yaml";
|
|
1563
|
+
static defaultExcludes = [];
|
|
1564
|
+
static handler = PnpmWorkspace.create();
|
|
1565
|
+
static SORTABLE_ARRAY_KEYS = new Set([
|
|
1566
|
+
"packages",
|
|
1567
|
+
"onlyBuiltDependencies",
|
|
1568
|
+
"publicHoistPattern"
|
|
1569
|
+
]);
|
|
1570
|
+
static sortContent(content) {
|
|
1571
|
+
const result = {};
|
|
1572
|
+
const keys = Object.keys(content).sort((a, b)=>{
|
|
1573
|
+
if ("packages" === a) return -1;
|
|
1574
|
+
if ("packages" === b) return 1;
|
|
1575
|
+
return a.localeCompare(b);
|
|
1576
|
+
});
|
|
1577
|
+
for (const key of keys){
|
|
1578
|
+
const value = content[key];
|
|
1579
|
+
if (PnpmWorkspace.SORTABLE_ARRAY_KEYS.has(key) && Array.isArray(value)) result[key] = [
|
|
1580
|
+
...value
|
|
1581
|
+
].sort();
|
|
1582
|
+
else result[key] = value;
|
|
1583
|
+
}
|
|
1584
|
+
return result;
|
|
1585
|
+
}
|
|
1586
|
+
static fmtCommand() {
|
|
1587
|
+
return ()=>{
|
|
1588
|
+
if (!existsSync("pnpm-workspace.yaml")) return [];
|
|
1589
|
+
const cmd = Command_Command.findSavvyLint();
|
|
1590
|
+
return `${cmd} fmt pnpm-workspace`;
|
|
1591
|
+
};
|
|
1592
|
+
}
|
|
1593
|
+
static create(options = {}) {
|
|
1594
|
+
const skipSort = options.skipSort ?? false;
|
|
1595
|
+
const skipFormat = options.skipFormat ?? false;
|
|
1596
|
+
const skipLint = options.skipLint ?? false;
|
|
1597
|
+
return ()=>{
|
|
1598
|
+
const filepath = "pnpm-workspace.yaml";
|
|
1599
|
+
if (!existsSync(filepath)) return [];
|
|
1600
|
+
const content = readFileSync(filepath, "utf-8");
|
|
1601
|
+
let parsed;
|
|
1602
|
+
try {
|
|
1603
|
+
parsed = external_yaml_parse(content);
|
|
1604
|
+
} catch (error) {
|
|
1605
|
+
if (!skipLint) throw new Error(`Invalid YAML in ${filepath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1606
|
+
return [];
|
|
1607
|
+
}
|
|
1608
|
+
if (!skipSort) parsed = PnpmWorkspace.sortContent(parsed);
|
|
1609
|
+
if (!skipSort || !skipFormat) {
|
|
1610
|
+
const formatted = stringify(parsed, DEFAULT_STRINGIFY_OPTIONS);
|
|
1611
|
+
writeFileSync(filepath, formatted, "utf-8");
|
|
1612
|
+
}
|
|
1613
|
+
return [];
|
|
1614
|
+
};
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
class Yaml {
|
|
1618
|
+
static glob = "**/*.{yml,yaml}";
|
|
1619
|
+
static defaultExcludes = [
|
|
1620
|
+
"pnpm-lock.yaml",
|
|
1621
|
+
"pnpm-workspace.yaml"
|
|
1622
|
+
];
|
|
1623
|
+
static handler = Yaml.create();
|
|
1624
|
+
static findConfig() {
|
|
1625
|
+
const result = ConfigSearch.find("yamllint");
|
|
1626
|
+
return result.filepath;
|
|
1627
|
+
}
|
|
1628
|
+
static loadConfig(filepath) {
|
|
1629
|
+
try {
|
|
1630
|
+
const content = readFileSync(filepath, "utf-8");
|
|
1631
|
+
const config = JSON.parse(content);
|
|
1632
|
+
return config.schema;
|
|
1633
|
+
} catch {
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
static isAvailable() {
|
|
1638
|
+
return true;
|
|
1639
|
+
}
|
|
1640
|
+
static async formatFile(filepath) {
|
|
1641
|
+
const content = readFileSync(filepath, "utf-8");
|
|
1642
|
+
const prettierConfig = await resolveConfig(filepath);
|
|
1643
|
+
const formatted = await format(content, {
|
|
1644
|
+
...prettierConfig,
|
|
1645
|
+
filepath,
|
|
1646
|
+
parser: "yaml"
|
|
1647
|
+
});
|
|
1648
|
+
writeFileSync(filepath, formatted, "utf-8");
|
|
1649
|
+
}
|
|
1650
|
+
static async validateFile(filepath, schema) {
|
|
1651
|
+
const content = readFileSync(filepath, "utf-8");
|
|
1652
|
+
await lint(content, schema ? {
|
|
1653
|
+
schema: schema
|
|
1654
|
+
} : void 0);
|
|
1655
|
+
}
|
|
1656
|
+
static fmtCommand(options = {}) {
|
|
1657
|
+
const excludes = options.exclude ?? [
|
|
1658
|
+
...Yaml.defaultExcludes
|
|
1659
|
+
];
|
|
1660
|
+
return (filenames)=>{
|
|
1661
|
+
const filtered = Filter.exclude(filenames, excludes);
|
|
1662
|
+
if (0 === filtered.length) return [];
|
|
1663
|
+
const cmd = Command_Command.findSavvyLint();
|
|
1664
|
+
return `${cmd} fmt yaml ${Filter.shellEscape(filtered)}`;
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
static create(options = {}) {
|
|
1668
|
+
const excludes = options.exclude ?? [
|
|
1669
|
+
...Yaml.defaultExcludes
|
|
1670
|
+
];
|
|
1671
|
+
const skipFormat = options.skipFormat ?? false;
|
|
1672
|
+
const skipValidate = options.skipValidate ?? false;
|
|
1673
|
+
const configPath = options.config ?? Yaml.findConfig();
|
|
1674
|
+
const schema = configPath ? Yaml.loadConfig(configPath) : void 0;
|
|
1675
|
+
return async (filenames)=>{
|
|
1676
|
+
const filtered = Filter.exclude(filenames, excludes);
|
|
1677
|
+
if (0 === filtered.length) return [];
|
|
1678
|
+
if (!skipFormat) for (const filepath of filtered)await Yaml.formatFile(filepath);
|
|
1679
|
+
if (!skipValidate) for (const filepath of filtered)try {
|
|
1680
|
+
await Yaml.validateFile(filepath, schema);
|
|
1681
|
+
} catch (error) {
|
|
1682
|
+
throw new Error(`Invalid YAML in ${filepath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1683
|
+
}
|
|
1684
|
+
return [];
|
|
1685
|
+
};
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
const YAML_STRINGIFY_OPTIONS = {
|
|
1689
|
+
indent: 2,
|
|
1690
|
+
lineWidth: 0,
|
|
1691
|
+
singleQuote: false
|
|
1692
|
+
};
|
|
1693
|
+
const filesArg = Args.repeated(Args.file({
|
|
1694
|
+
name: "files",
|
|
1695
|
+
exists: "yes"
|
|
1696
|
+
}));
|
|
1697
|
+
const packageJsonCommand = Command.make("package-json", {
|
|
1698
|
+
files: filesArg
|
|
1699
|
+
}, ({ files })=>Effect.sync(()=>{
|
|
1700
|
+
for (const filepath of files){
|
|
1701
|
+
const content = readFileSync(filepath, "utf-8");
|
|
1702
|
+
const sorted = sort_package_json(content);
|
|
1703
|
+
if (sorted !== content) writeFileSync(filepath, sorted, "utf-8");
|
|
1704
|
+
}
|
|
1705
|
+
}));
|
|
1706
|
+
const pnpmWorkspaceCommand = Command.make("pnpm-workspace", {}, ()=>Effect.sync(()=>{
|
|
1707
|
+
const filepath = "pnpm-workspace.yaml";
|
|
1708
|
+
if (!existsSync(filepath)) return;
|
|
1709
|
+
const content = readFileSync(filepath, "utf-8");
|
|
1710
|
+
const parsed = external_yaml_parse(content);
|
|
1711
|
+
const sorted = PnpmWorkspace.sortContent(parsed);
|
|
1712
|
+
const formatted = stringify(sorted, YAML_STRINGIFY_OPTIONS);
|
|
1713
|
+
writeFileSync(filepath, formatted, "utf-8");
|
|
1714
|
+
}));
|
|
1715
|
+
const yamlCommand = Command.make("yaml", {
|
|
1716
|
+
files: filesArg
|
|
1717
|
+
}, ({ files })=>Effect.gen(function*() {
|
|
1718
|
+
for (const filepath of files)yield* Effect.promise(()=>Yaml.formatFile(filepath));
|
|
1719
|
+
}));
|
|
1720
|
+
const fmtCommand = Command.make("fmt").pipe(Command.withSubcommands([
|
|
1721
|
+
packageJsonCommand,
|
|
1722
|
+
pnpmWorkspaceCommand,
|
|
1723
|
+
yamlCommand
|
|
1724
|
+
]));
|
|
1518
1725
|
const rootCommand = Command.make("savvy-lint").pipe(Command.withSubcommands([
|
|
1519
1726
|
initCommand,
|
|
1520
|
-
checkCommand
|
|
1727
|
+
checkCommand,
|
|
1728
|
+
fmtCommand
|
|
1521
1729
|
]));
|
|
1522
1730
|
const cli = Command.run(rootCommand, {
|
|
1523
1731
|
name: "savvy-lint",
|
|
1524
|
-
version: "0.
|
|
1732
|
+
version: "0.4.0"
|
|
1525
1733
|
});
|
|
1526
1734
|
function runCli() {
|
|
1527
1735
|
const main = Effect.suspend(()=>cli(process.argv)).pipe(Effect.provide(NodeContext.layer));
|
|
1528
1736
|
NodeRuntime.runMain(main);
|
|
1529
1737
|
}
|
|
1530
|
-
export { Biome, Command_Command as Command, ConfigSearch, EntryExtractor, Filter, ImportGraph, Markdown, TsDocLinter, TsDocResolver, TypeScript, checkCommand,
|
|
1738
|
+
export { Biome, Command_Command as Command, ConfigSearch, EntryExtractor, Filter, ImportGraph, Markdown, PnpmWorkspace, TsDocLinter, TsDocResolver, TypeScript, Yaml, checkCommand, fmtCommand, initCommand, readFileSync, rootCommand, runCli, sort_package_json, writeFileSync };
|
package/README.md
CHANGED
|
@@ -76,27 +76,30 @@ export default {
|
|
|
76
76
|
| `PackageJson` | `**/package.json` | Sort and format with Biome |
|
|
77
77
|
| `Biome` | `*.{js,ts,jsx,tsx,json,jsonc}` | Format and lint |
|
|
78
78
|
| `Markdown` | `**/*.{md,mdx}` | Lint with markdownlint-cli2 |
|
|
79
|
-
| `Yaml` | `**/*.{yml,yaml}` | Format and validate |
|
|
79
|
+
| `Yaml` | `**/*.{yml,yaml}` | Format (Prettier) and validate (yaml-lint) |
|
|
80
80
|
| `PnpmWorkspace` | `pnpm-workspace.yaml` | Sort and format |
|
|
81
81
|
| `ShellScripts` | `**/*.sh` | Manage permissions |
|
|
82
82
|
| `TypeScript` | `*.{ts,cts,mts,tsx}` | TSDoc validation + typecheck |
|
|
83
83
|
|
|
84
84
|
## CLI
|
|
85
85
|
|
|
86
|
-
The `savvy-lint` CLI helps bootstrap and
|
|
86
|
+
The `savvy-lint` CLI helps bootstrap, validate, and format your setup:
|
|
87
87
|
|
|
88
88
|
```bash
|
|
89
89
|
savvy-lint init # Bootstrap hooks, config, and tooling
|
|
90
90
|
savvy-lint init --preset silk --force # Overwrite with silk preset
|
|
91
91
|
savvy-lint check # Validate current configuration
|
|
92
92
|
savvy-lint check --quiet # Warnings only (for postinstall)
|
|
93
|
+
savvy-lint fmt package-json # Sort package.json fields
|
|
94
|
+
savvy-lint fmt yaml # Format YAML files with Prettier
|
|
95
|
+
savvy-lint fmt pnpm-workspace # Sort and format pnpm-workspace.yaml
|
|
93
96
|
```
|
|
94
97
|
|
|
95
98
|
## Documentation
|
|
96
99
|
|
|
97
100
|
- [Handler Configuration](./docs/handlers.md) -- Detailed options for each handler
|
|
98
101
|
- [Configuration API](./docs/configuration.md) -- createConfig and Preset APIs
|
|
99
|
-
- [CLI Reference](./docs/cli.md) -- `savvy-lint init` and `
|
|
102
|
+
- [CLI Reference](./docs/cli.md) -- `savvy-lint init`, `check`, and `fmt`
|
|
100
103
|
- [Utilities](./docs/utilities.md) -- Command, Filter, and advanced utilities
|
|
101
104
|
- [Migration Guide](./docs/migration.md) -- Migrating from raw lint-staged configs
|
|
102
105
|
|
package/index.d.ts
CHANGED
|
@@ -24,7 +24,6 @@ import { FileSystem } from '@effect/platform';
|
|
|
24
24
|
import { FileSystem as FileSystem_2 } from '@effect/platform/FileSystem';
|
|
25
25
|
import { Option } from 'effect/Option';
|
|
26
26
|
import { PlatformError } from '@effect/platform/Error';
|
|
27
|
-
import { stringify } from 'yaml';
|
|
28
27
|
|
|
29
28
|
/**
|
|
30
29
|
* Base options shared by all handlers.
|
|
@@ -154,13 +153,37 @@ export declare const checkCommand: Command_2.Command<"check", FileSystem.FileSys
|
|
|
154
153
|
export declare class Command {
|
|
155
154
|
/** Cached package manager detection result */
|
|
156
155
|
private static cachedPackageManager;
|
|
156
|
+
/** Cached project root path */
|
|
157
|
+
private static cachedRoot;
|
|
158
|
+
/**
|
|
159
|
+
* Find the project root directory using workspace-tools.
|
|
160
|
+
*
|
|
161
|
+
* Uses `findProjectRoot()` from `workspace-tools` to locate the nearest
|
|
162
|
+
* directory containing a `package.json`. Falls back to the provided `cwd`
|
|
163
|
+
* (or `process.cwd()`) on error.
|
|
164
|
+
*
|
|
165
|
+
* @remarks
|
|
166
|
+
* This is more reliable than `process.cwd()` in environments like Husky
|
|
167
|
+
* hooks where the working directory may point to `.husky/` or another
|
|
168
|
+
* subdirectory.
|
|
169
|
+
*
|
|
170
|
+
* @param cwd - Starting directory for the search (defaults to `process.cwd()`)
|
|
171
|
+
* @returns The resolved project root path
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```typescript
|
|
175
|
+
* const root = Command.findRoot();
|
|
176
|
+
* console.log(root); // '/Users/me/my-project'
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
static findRoot(cwd?: string): string;
|
|
157
180
|
/**
|
|
158
181
|
* Detect the package manager from the root package.json's `packageManager` field.
|
|
159
182
|
*
|
|
160
183
|
* Parses the `packageManager` field (e.g., `pnpm\@9.0.0`) and extracts the manager name.
|
|
161
184
|
* Falls back to "npm" if no packageManager field is found.
|
|
162
185
|
*
|
|
163
|
-
* @param cwd - Directory to search for package.json (defaults to
|
|
186
|
+
* @param cwd - Directory to search for package.json (defaults to `Command.findRoot()`)
|
|
164
187
|
* @returns The detected package manager
|
|
165
188
|
*
|
|
166
189
|
* @example
|
|
@@ -181,12 +204,12 @@ export declare class Command {
|
|
|
181
204
|
* Command.getExecPrefix('pnpm'); // ['pnpm', 'exec']
|
|
182
205
|
* Command.getExecPrefix('npm'); // ['npx', '--no']
|
|
183
206
|
* Command.getExecPrefix('yarn'); // ['yarn', 'exec']
|
|
184
|
-
* Command.getExecPrefix('bun'); // ['
|
|
207
|
+
* Command.getExecPrefix('bun'); // ['bun', 'x', "--no-install"]
|
|
185
208
|
* ```
|
|
186
209
|
*/
|
|
187
210
|
static getExecPrefix(packageManager: PackageManager): string[];
|
|
188
211
|
/**
|
|
189
|
-
* Clear the cached package manager detection.
|
|
212
|
+
* Clear the cached package manager and project root detection.
|
|
190
213
|
* Useful for testing or when package.json changes.
|
|
191
214
|
*/
|
|
192
215
|
static clearCache(): void;
|
|
@@ -239,6 +262,17 @@ export declare class Command {
|
|
|
239
262
|
* ```
|
|
240
263
|
*/
|
|
241
264
|
static requireTool(tool: string, errorMessage?: string): string;
|
|
265
|
+
/**
|
|
266
|
+
* Find the savvy-lint CLI command.
|
|
267
|
+
*
|
|
268
|
+
* @remarks
|
|
269
|
+
* Searches for the `savvy-lint` binary via the standard tool search,
|
|
270
|
+
* then falls back to the dev build at `dist/dev/bin/savvy-lint.js`
|
|
271
|
+
* for dogfooding scenarios where the package's own bin isn't linked.
|
|
272
|
+
*
|
|
273
|
+
* @returns The command string to invoke savvy-lint
|
|
274
|
+
*/
|
|
275
|
+
static findSavvyLint(): string;
|
|
242
276
|
/**
|
|
243
277
|
* Execute a command and return its output.
|
|
244
278
|
*
|
|
@@ -294,7 +328,7 @@ export declare class ConfigSearch {
|
|
|
294
328
|
/**
|
|
295
329
|
* Find a configuration file for a known tool.
|
|
296
330
|
*
|
|
297
|
-
* Supported tools: 'markdownlint', 'biome', 'eslint', 'prettier'
|
|
331
|
+
* Supported tools: 'markdownlint', 'biome', 'eslint', 'prettier', 'yamllint'
|
|
298
332
|
*
|
|
299
333
|
* @param tool - The tool name
|
|
300
334
|
* @param options - Search options
|
|
@@ -308,7 +342,7 @@ export declare class ConfigSearch {
|
|
|
308
342
|
* }
|
|
309
343
|
* ```
|
|
310
344
|
*/
|
|
311
|
-
static find(tool: "markdownlint" | "biome" | "eslint" | "prettier", options?: ConfigSearchOptions): ConfigSearchResult;
|
|
345
|
+
static find(tool: "markdownlint" | "biome" | "eslint" | "prettier" | "yamllint", options?: ConfigSearchOptions): ConfigSearchResult;
|
|
312
346
|
/**
|
|
313
347
|
* Find a configuration file with custom search locations.
|
|
314
348
|
*
|
|
@@ -613,6 +647,15 @@ export declare class Filter {
|
|
|
613
647
|
static shellEscape(filenames: readonly string[]): string;
|
|
614
648
|
}
|
|
615
649
|
|
|
650
|
+
/** Parent fmt command with formatting subcommands. */
|
|
651
|
+
export declare const fmtCommand: Command_2.Command<"fmt", never, never, {
|
|
652
|
+
readonly subcommand: Option< {
|
|
653
|
+
readonly files: string[];
|
|
654
|
+
} | {} | {
|
|
655
|
+
readonly files: string[];
|
|
656
|
+
}>;
|
|
657
|
+
}>;
|
|
658
|
+
|
|
616
659
|
/**
|
|
617
660
|
* Abstract base class for lint-staged handlers.
|
|
618
661
|
*
|
|
@@ -813,9 +856,18 @@ export declare const initCommand: Command_2.Command<"init", FileSystem.FileSyste
|
|
|
813
856
|
|
|
814
857
|
/**
|
|
815
858
|
* A lint-staged configuration object.
|
|
816
|
-
* Maps glob patterns to handlers.
|
|
859
|
+
* Maps glob patterns to handlers, commands, or arrays of sequential steps.
|
|
860
|
+
*
|
|
861
|
+
* @remarks
|
|
862
|
+
* When a value is an array of functions/strings, lint-staged runs each element
|
|
863
|
+
* sequentially with proper staging between steps.
|
|
817
864
|
*/
|
|
818
|
-
export declare type LintStagedConfig = Record<string,
|
|
865
|
+
export declare type LintStagedConfig = Record<string, LintStagedEntry | LintStagedEntry[]>;
|
|
866
|
+
|
|
867
|
+
/**
|
|
868
|
+
* A single lint-staged command entry: a handler function, a string command, or an array of strings.
|
|
869
|
+
*/
|
|
870
|
+
export declare type LintStagedEntry = LintStagedHandler | string | string[];
|
|
819
871
|
|
|
820
872
|
/**
|
|
821
873
|
* A lint-staged handler function.
|
|
@@ -961,6 +1013,25 @@ export declare class PackageJson {
|
|
|
961
1013
|
* Pre-configured handler with default options.
|
|
962
1014
|
*/
|
|
963
1015
|
static readonly handler: LintStagedHandler;
|
|
1016
|
+
/**
|
|
1017
|
+
* Create a handler with custom options.
|
|
1018
|
+
*
|
|
1019
|
+
* @param options - Configuration options
|
|
1020
|
+
* @returns A lint-staged compatible handler function
|
|
1021
|
+
*/
|
|
1022
|
+
/**
|
|
1023
|
+
* Create a handler that returns a CLI command to sort package.json files.
|
|
1024
|
+
*
|
|
1025
|
+
* @remarks
|
|
1026
|
+
* Unlike {@link create}, this does not modify files in the handler function
|
|
1027
|
+
* body. Instead it returns a `savvy-lint fmt package-json` command so
|
|
1028
|
+
* lint-staged can detect the modification and auto-stage it.
|
|
1029
|
+
* Use this in lint-staged array syntax for sequential execution.
|
|
1030
|
+
*
|
|
1031
|
+
* @param options - Configuration options
|
|
1032
|
+
* @returns A lint-staged compatible handler function
|
|
1033
|
+
*/
|
|
1034
|
+
static fmtCommand(options?: PackageJsonOptions): LintStagedHandler;
|
|
964
1035
|
/**
|
|
965
1036
|
* Create a handler with custom options.
|
|
966
1037
|
*
|
|
@@ -979,6 +1050,11 @@ export declare interface PackageJsonOptions extends BaseHandlerOptions {
|
|
|
979
1050
|
* @defaultValue false
|
|
980
1051
|
*/
|
|
981
1052
|
skipSort?: boolean;
|
|
1053
|
+
/**
|
|
1054
|
+
* Skip Biome formatting (sort only).
|
|
1055
|
+
* @defaultValue false
|
|
1056
|
+
*/
|
|
1057
|
+
skipFormat?: boolean;
|
|
982
1058
|
/**
|
|
983
1059
|
* Path to Biome config file.
|
|
984
1060
|
*/
|
|
@@ -1068,6 +1144,18 @@ export declare class PnpmWorkspace {
|
|
|
1068
1144
|
* @returns Sorted content
|
|
1069
1145
|
*/
|
|
1070
1146
|
static sortContent(content: PnpmWorkspaceContent): PnpmWorkspaceContent;
|
|
1147
|
+
/**
|
|
1148
|
+
* Create a handler that returns a CLI command to sort/format pnpm-workspace.yaml.
|
|
1149
|
+
*
|
|
1150
|
+
* @remarks
|
|
1151
|
+
* Unlike {@link create}, this does not modify files in the handler function
|
|
1152
|
+
* body. Instead it returns a `savvy-lint fmt pnpm-workspace` command so
|
|
1153
|
+
* lint-staged can detect the modification and auto-stage it.
|
|
1154
|
+
* Use this in lint-staged array syntax for sequential execution.
|
|
1155
|
+
*
|
|
1156
|
+
* @returns A lint-staged compatible handler function
|
|
1157
|
+
*/
|
|
1158
|
+
static fmtCommand(): LintStagedHandler;
|
|
1071
1159
|
/**
|
|
1072
1160
|
* Create a handler with custom options.
|
|
1073
1161
|
*
|
|
@@ -1232,6 +1320,12 @@ export declare const rootCommand: Command_2.Command<"savvy-lint", FileSystem_2,
|
|
|
1232
1320
|
readonly preset: "minimal" | "silk" | "standard";
|
|
1233
1321
|
} | {
|
|
1234
1322
|
readonly quiet: boolean;
|
|
1323
|
+
} | {
|
|
1324
|
+
readonly subcommand: Option< {
|
|
1325
|
+
readonly files: string[];
|
|
1326
|
+
} | {} | {
|
|
1327
|
+
readonly files: string[];
|
|
1328
|
+
}>;
|
|
1235
1329
|
}>;
|
|
1236
1330
|
}>;
|
|
1237
1331
|
|
|
@@ -1589,17 +1683,24 @@ export declare class TypeScript {
|
|
|
1589
1683
|
* @defaultValue `['.test.', '.spec.', '__test__', '__tests__']`
|
|
1590
1684
|
*/
|
|
1591
1685
|
static readonly defaultTsdocExcludes: readonly [".test.", ".spec.", "__test__", "__tests__"];
|
|
1686
|
+
/** Cached compiler detection result */
|
|
1687
|
+
private static cachedCompilerResult;
|
|
1592
1688
|
/**
|
|
1593
|
-
* Detect which TypeScript compiler to use
|
|
1689
|
+
* Detect which TypeScript compiler to use.
|
|
1594
1690
|
*
|
|
1595
|
-
*
|
|
1596
|
-
* 1.
|
|
1597
|
-
* 2. `
|
|
1691
|
+
* Uses `Command.findTool()` to check for available compilers:
|
|
1692
|
+
* 1. `tsgo` (native TypeScript) — checked first
|
|
1693
|
+
* 2. `tsc` (standard TypeScript) — fallback
|
|
1598
1694
|
*
|
|
1599
|
-
* @
|
|
1600
|
-
*
|
|
1695
|
+
* @remarks
|
|
1696
|
+
* Unlike the previous implementation that parsed `package.json` dependencies,
|
|
1697
|
+
* this uses runtime tool detection which works correctly with pnpm catalogs,
|
|
1698
|
+
* peer dependencies, and hoisted/transitive deps.
|
|
1699
|
+
*
|
|
1700
|
+
* @param _cwd - Ignored (kept for backward compatibility)
|
|
1701
|
+
* @returns The compiler to use, or undefined if neither is available
|
|
1601
1702
|
*/
|
|
1602
|
-
static detectCompiler(
|
|
1703
|
+
static detectCompiler(_cwd?: string): TypeScriptCompiler | undefined;
|
|
1603
1704
|
/**
|
|
1604
1705
|
* Check if a TypeScript compiler is available.
|
|
1605
1706
|
*
|
|
@@ -1607,12 +1708,21 @@ export declare class TypeScript {
|
|
|
1607
1708
|
*/
|
|
1608
1709
|
static isAvailable(): boolean;
|
|
1609
1710
|
/**
|
|
1610
|
-
* Get the default type checking command for the detected
|
|
1711
|
+
* Get the default type checking command for the detected compiler.
|
|
1712
|
+
*
|
|
1713
|
+
* @remarks
|
|
1714
|
+
* Uses the cached `ToolSearchResult` from `detectCompiler()` to build
|
|
1715
|
+
* the command string, avoiding a separate package manager detection step.
|
|
1611
1716
|
*
|
|
1612
|
-
* @returns Command string like `pnpm exec tsgo --noEmit` or `
|
|
1613
|
-
* @throws Error if no TypeScript compiler is
|
|
1717
|
+
* @returns Command string like `pnpm exec tsgo --noEmit` or `tsgo --noEmit`
|
|
1718
|
+
* @throws Error if no TypeScript compiler is available
|
|
1614
1719
|
*/
|
|
1615
1720
|
static getDefaultTypecheckCommand(): string;
|
|
1721
|
+
/**
|
|
1722
|
+
* Clear the cached compiler detection result.
|
|
1723
|
+
* Useful for testing or when the environment changes.
|
|
1724
|
+
*/
|
|
1725
|
+
static clearCache(): void;
|
|
1616
1726
|
/**
|
|
1617
1727
|
* Pre-configured handler with default options.
|
|
1618
1728
|
* Auto-discovers workspaces for TSDoc linting.
|
|
@@ -1679,14 +1789,14 @@ export declare interface TypeScriptOptions extends BaseHandlerOptions {
|
|
|
1679
1789
|
/**
|
|
1680
1790
|
* Handler for YAML files.
|
|
1681
1791
|
*
|
|
1682
|
-
* Formats and validates
|
|
1792
|
+
* Formats with Prettier and validates with yaml-lint, both as bundled dependencies.
|
|
1683
1793
|
*
|
|
1684
1794
|
* @remarks
|
|
1685
1795
|
* Excludes pnpm-lock.yaml and pnpm-workspace.yaml by default.
|
|
1686
1796
|
* pnpm-workspace.yaml has its own dedicated handler.
|
|
1687
1797
|
*
|
|
1688
|
-
* Uses
|
|
1689
|
-
*
|
|
1798
|
+
* Uses Prettier for formatting and yaml-lint for validation.
|
|
1799
|
+
* Both are bundled dependencies (no CLI spawning required).
|
|
1690
1800
|
*
|
|
1691
1801
|
* @example
|
|
1692
1802
|
* ```typescript
|
|
@@ -1715,19 +1825,55 @@ export declare class Yaml {
|
|
|
1715
1825
|
*/
|
|
1716
1826
|
static readonly handler: LintStagedHandler;
|
|
1717
1827
|
/**
|
|
1718
|
-
*
|
|
1828
|
+
* Find the yaml-lint config file.
|
|
1829
|
+
*
|
|
1830
|
+
* Searches in order:
|
|
1831
|
+
* 1. `lib/configs/` directory
|
|
1832
|
+
* 2. Standard locations (repo root)
|
|
1833
|
+
*
|
|
1834
|
+
* @returns The config file path, or undefined if not found
|
|
1835
|
+
*/
|
|
1836
|
+
static findConfig(): string | undefined;
|
|
1837
|
+
/**
|
|
1838
|
+
* Load the yaml-lint schema from a config file.
|
|
1839
|
+
*
|
|
1840
|
+
* @param filepath - Path to the yaml-lint config file
|
|
1841
|
+
* @returns The schema string, or undefined if not found
|
|
1842
|
+
*/
|
|
1843
|
+
static loadConfig(filepath: string): string | undefined;
|
|
1844
|
+
/**
|
|
1845
|
+
* Check if yaml-lint is available.
|
|
1846
|
+
*
|
|
1847
|
+
* @returns Always `true` since yaml-lint is a bundled dependency
|
|
1848
|
+
*/
|
|
1849
|
+
static isAvailable(): boolean;
|
|
1850
|
+
/**
|
|
1851
|
+
* Format a YAML file in-place using Prettier.
|
|
1719
1852
|
*
|
|
1720
1853
|
* @param filepath - Path to the YAML file
|
|
1721
|
-
* @param options - Stringify options for the yaml package
|
|
1722
1854
|
*/
|
|
1723
|
-
static formatFile(filepath: string
|
|
1855
|
+
static formatFile(filepath: string): Promise<void>;
|
|
1724
1856
|
/**
|
|
1725
|
-
* Validate a YAML file.
|
|
1857
|
+
* Validate a YAML file using yaml-lint.
|
|
1726
1858
|
*
|
|
1727
1859
|
* @param filepath - Path to the YAML file
|
|
1860
|
+
* @param schema - The YAML schema to validate against
|
|
1728
1861
|
* @throws Error if the YAML is invalid
|
|
1729
1862
|
*/
|
|
1730
|
-
static validateFile(filepath: string): void
|
|
1863
|
+
static validateFile(filepath: string, schema?: string): Promise<void>;
|
|
1864
|
+
/**
|
|
1865
|
+
* Create a handler that returns a CLI command to format YAML files.
|
|
1866
|
+
*
|
|
1867
|
+
* @remarks
|
|
1868
|
+
* Unlike {@link create}, this does not modify files in the handler function
|
|
1869
|
+
* body. Instead it returns a `savvy-lint fmt yaml` command so lint-staged
|
|
1870
|
+
* can detect the modification and auto-stage it.
|
|
1871
|
+
* Use this in lint-staged array syntax for sequential execution.
|
|
1872
|
+
*
|
|
1873
|
+
* @param options - Configuration options
|
|
1874
|
+
* @returns A lint-staged compatible handler function
|
|
1875
|
+
*/
|
|
1876
|
+
static fmtCommand(options?: YamlOptions): LintStagedHandler;
|
|
1731
1877
|
/**
|
|
1732
1878
|
* Create a handler with custom options.
|
|
1733
1879
|
*
|
|
@@ -1741,6 +1887,10 @@ export declare class Yaml {
|
|
|
1741
1887
|
* Options for the Yaml handler.
|
|
1742
1888
|
*/
|
|
1743
1889
|
export declare interface YamlOptions extends BaseHandlerOptions {
|
|
1890
|
+
/**
|
|
1891
|
+
* Path to yaml-lint config file (.yaml-lint.json).
|
|
1892
|
+
*/
|
|
1893
|
+
config?: string;
|
|
1744
1894
|
/**
|
|
1745
1895
|
* Skip YAML formatting.
|
|
1746
1896
|
* @defaultValue false
|
package/index.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import sort_package_json from "
|
|
2
|
-
import { parse, stringify } from "yaml";
|
|
3
|
-
import { Biome, Markdown, readFileSync, TypeScript, writeFileSync, existsSync, Filter } from "./376.js";
|
|
1
|
+
import { Biome, Command as Command_Command, readFileSync, Yaml, TypeScript, sort_package_json, PnpmWorkspace, writeFileSync, Markdown, Filter } from "./376.js";
|
|
4
2
|
class PackageJson {
|
|
5
3
|
static glob = "**/package.json";
|
|
6
4
|
static defaultExcludes = [
|
|
@@ -8,11 +6,23 @@ class PackageJson {
|
|
|
8
6
|
"__fixtures__"
|
|
9
7
|
];
|
|
10
8
|
static handler = PackageJson.create();
|
|
9
|
+
static fmtCommand(options = {}) {
|
|
10
|
+
const excludes = options.exclude ?? [
|
|
11
|
+
...PackageJson.defaultExcludes
|
|
12
|
+
];
|
|
13
|
+
return (filenames)=>{
|
|
14
|
+
const filtered = Filter.exclude(filenames, excludes);
|
|
15
|
+
if (0 === filtered.length) return [];
|
|
16
|
+
const cmd = Command_Command.findSavvyLint();
|
|
17
|
+
return `${cmd} fmt package-json ${Filter.shellEscape(filtered)}`;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
11
20
|
static create(options = {}) {
|
|
12
21
|
const excludes = options.exclude ?? [
|
|
13
22
|
...PackageJson.defaultExcludes
|
|
14
23
|
];
|
|
15
24
|
const skipSort = options.skipSort ?? false;
|
|
25
|
+
const skipFormat = options.skipFormat ?? false;
|
|
16
26
|
return (filenames)=>{
|
|
17
27
|
const filtered = Filter.exclude(filenames, excludes);
|
|
18
28
|
if (0 === filtered.length) return [];
|
|
@@ -21,66 +31,13 @@ class PackageJson {
|
|
|
21
31
|
const sorted = sort_package_json(content);
|
|
22
32
|
if (sorted !== content) writeFileSync(filepath, sorted, "utf-8");
|
|
23
33
|
}
|
|
34
|
+
if (skipFormat) return [];
|
|
24
35
|
const files = Filter.shellEscape(filtered);
|
|
25
36
|
const biomeCmd = options.biomeConfig ? `biome check --write --max-diagnostics=none --config-path=${options.biomeConfig} ${files}` : `biome check --write --max-diagnostics=none ${files}`;
|
|
26
37
|
return biomeCmd;
|
|
27
38
|
};
|
|
28
39
|
}
|
|
29
40
|
}
|
|
30
|
-
const DEFAULT_STRINGIFY_OPTIONS = {
|
|
31
|
-
indent: 2,
|
|
32
|
-
lineWidth: 0,
|
|
33
|
-
singleQuote: false
|
|
34
|
-
};
|
|
35
|
-
class PnpmWorkspace {
|
|
36
|
-
static glob = "pnpm-workspace.yaml";
|
|
37
|
-
static defaultExcludes = [];
|
|
38
|
-
static handler = PnpmWorkspace.create();
|
|
39
|
-
static SORTABLE_ARRAY_KEYS = new Set([
|
|
40
|
-
"packages",
|
|
41
|
-
"onlyBuiltDependencies",
|
|
42
|
-
"publicHoistPattern"
|
|
43
|
-
]);
|
|
44
|
-
static sortContent(content) {
|
|
45
|
-
const result = {};
|
|
46
|
-
const keys = Object.keys(content).sort((a, b)=>{
|
|
47
|
-
if ("packages" === a) return -1;
|
|
48
|
-
if ("packages" === b) return 1;
|
|
49
|
-
return a.localeCompare(b);
|
|
50
|
-
});
|
|
51
|
-
for (const key of keys){
|
|
52
|
-
const value = content[key];
|
|
53
|
-
if (PnpmWorkspace.SORTABLE_ARRAY_KEYS.has(key) && Array.isArray(value)) result[key] = [
|
|
54
|
-
...value
|
|
55
|
-
].sort();
|
|
56
|
-
else result[key] = value;
|
|
57
|
-
}
|
|
58
|
-
return result;
|
|
59
|
-
}
|
|
60
|
-
static create(options = {}) {
|
|
61
|
-
const skipSort = options.skipSort ?? false;
|
|
62
|
-
const skipFormat = options.skipFormat ?? false;
|
|
63
|
-
const skipLint = options.skipLint ?? false;
|
|
64
|
-
return ()=>{
|
|
65
|
-
const filepath = "pnpm-workspace.yaml";
|
|
66
|
-
if (!existsSync(filepath)) return [];
|
|
67
|
-
const content = readFileSync(filepath, "utf-8");
|
|
68
|
-
let parsed;
|
|
69
|
-
try {
|
|
70
|
-
parsed = parse(content);
|
|
71
|
-
} catch (error) {
|
|
72
|
-
if (!skipLint) throw new Error(`Invalid YAML in ${filepath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
73
|
-
return [];
|
|
74
|
-
}
|
|
75
|
-
if (!skipSort) parsed = PnpmWorkspace.sortContent(parsed);
|
|
76
|
-
if (!skipSort || !skipFormat) {
|
|
77
|
-
const formatted = stringify(parsed, DEFAULT_STRINGIFY_OPTIONS);
|
|
78
|
-
writeFileSync(filepath, formatted, "utf-8");
|
|
79
|
-
}
|
|
80
|
-
return [];
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
41
|
class ShellScripts {
|
|
85
42
|
static glob = "**/*.sh";
|
|
86
43
|
static defaultExcludes = [
|
|
@@ -100,57 +57,27 @@ class ShellScripts {
|
|
|
100
57
|
};
|
|
101
58
|
}
|
|
102
59
|
}
|
|
103
|
-
const Yaml_DEFAULT_STRINGIFY_OPTIONS = {
|
|
104
|
-
indent: 2,
|
|
105
|
-
lineWidth: 0,
|
|
106
|
-
singleQuote: false
|
|
107
|
-
};
|
|
108
|
-
class Yaml {
|
|
109
|
-
static glob = "**/*.{yml,yaml}";
|
|
110
|
-
static defaultExcludes = [
|
|
111
|
-
"pnpm-lock.yaml",
|
|
112
|
-
"pnpm-workspace.yaml"
|
|
113
|
-
];
|
|
114
|
-
static handler = Yaml.create();
|
|
115
|
-
static formatFile(filepath, options) {
|
|
116
|
-
const content = readFileSync(filepath, "utf-8");
|
|
117
|
-
const parsed = parse(content);
|
|
118
|
-
const formatted = stringify(parsed, {
|
|
119
|
-
...Yaml_DEFAULT_STRINGIFY_OPTIONS,
|
|
120
|
-
...options
|
|
121
|
-
});
|
|
122
|
-
writeFileSync(filepath, formatted, "utf-8");
|
|
123
|
-
}
|
|
124
|
-
static validateFile(filepath) {
|
|
125
|
-
const content = readFileSync(filepath, "utf-8");
|
|
126
|
-
parse(content);
|
|
127
|
-
}
|
|
128
|
-
static create(options = {}) {
|
|
129
|
-
const excludes = options.exclude ?? [
|
|
130
|
-
...Yaml.defaultExcludes
|
|
131
|
-
];
|
|
132
|
-
const skipFormat = options.skipFormat ?? false;
|
|
133
|
-
const skipValidate = options.skipValidate ?? false;
|
|
134
|
-
return (filenames)=>{
|
|
135
|
-
const filtered = Filter.exclude(filenames, excludes);
|
|
136
|
-
if (0 === filtered.length) return [];
|
|
137
|
-
if (!skipFormat) for (const filepath of filtered)Yaml.formatFile(filepath);
|
|
138
|
-
if (!skipValidate) for (const filepath of filtered)try {
|
|
139
|
-
Yaml.validateFile(filepath);
|
|
140
|
-
} catch (error) {
|
|
141
|
-
throw new Error(`Invalid YAML in ${filepath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
142
|
-
}
|
|
143
|
-
return [];
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
60
|
function createConfig(options = {}) {
|
|
148
61
|
const config = {};
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
62
|
+
const pkgJsonEnabled = false !== options.packageJson;
|
|
63
|
+
const biomeEnabled = false !== options.biome;
|
|
64
|
+
if (pkgJsonEnabled && biomeEnabled) {
|
|
65
|
+
const pkgOpts = "object" == typeof options.packageJson ? options.packageJson : {};
|
|
66
|
+
const biomeOpts = "object" == typeof options.biome ? options.biome : {};
|
|
67
|
+
config[PackageJson.glob] = [
|
|
68
|
+
PackageJson.fmtCommand(pkgOpts),
|
|
69
|
+
Biome.create({
|
|
70
|
+
...biomeOpts,
|
|
71
|
+
exclude: [
|
|
72
|
+
...PackageJson.defaultExcludes
|
|
73
|
+
]
|
|
74
|
+
})
|
|
75
|
+
];
|
|
76
|
+
} else if (pkgJsonEnabled) {
|
|
77
|
+
const pkgOpts = "object" == typeof options.packageJson ? options.packageJson : {};
|
|
78
|
+
config[PackageJson.glob] = PackageJson.create(pkgOpts);
|
|
152
79
|
}
|
|
153
|
-
if (
|
|
80
|
+
if (biomeEnabled) {
|
|
154
81
|
const handlerOptions = "object" == typeof options.biome ? options.biome : {};
|
|
155
82
|
config[Biome.glob] = Biome.create(handlerOptions);
|
|
156
83
|
}
|
|
@@ -158,13 +85,28 @@ function createConfig(options = {}) {
|
|
|
158
85
|
const handlerOptions = "object" == typeof options.markdown ? options.markdown : {};
|
|
159
86
|
config[Markdown.glob] = Markdown.create(handlerOptions);
|
|
160
87
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
88
|
+
const pnpmEnabled = false !== options.pnpmWorkspace;
|
|
89
|
+
const yamlEnabled = false !== options.yaml;
|
|
90
|
+
if (pnpmEnabled && yamlEnabled) config[PnpmWorkspace.glob] = [
|
|
91
|
+
PnpmWorkspace.fmtCommand(),
|
|
92
|
+
Yaml.create({
|
|
93
|
+
exclude: [],
|
|
94
|
+
skipFormat: true
|
|
95
|
+
})
|
|
96
|
+
];
|
|
97
|
+
else if (pnpmEnabled) {
|
|
98
|
+
const pnpmOpts = "object" == typeof options.pnpmWorkspace ? options.pnpmWorkspace : {};
|
|
99
|
+
config[PnpmWorkspace.glob] = PnpmWorkspace.create(pnpmOpts);
|
|
100
|
+
}
|
|
101
|
+
if (yamlEnabled) {
|
|
102
|
+
const yamlOpts = "object" == typeof options.yaml ? options.yaml : {};
|
|
103
|
+
config[Yaml.glob] = [
|
|
104
|
+
Yaml.fmtCommand(yamlOpts),
|
|
105
|
+
Yaml.create({
|
|
106
|
+
...yamlOpts,
|
|
107
|
+
skipFormat: true
|
|
108
|
+
})
|
|
109
|
+
];
|
|
168
110
|
}
|
|
169
111
|
if (false !== options.shellScripts) {
|
|
170
112
|
const handlerOptions = "object" == typeof options.shellScripts ? options.shellScripts : {};
|
|
@@ -236,5 +178,5 @@ class Handler {
|
|
|
236
178
|
throw new Error("Handler.create() must be implemented by subclass");
|
|
237
179
|
}
|
|
238
180
|
}
|
|
239
|
-
export { Biome, Command, ConfigSearch, EntryExtractor, Filter, ImportGraph, Markdown, TsDocLinter, TsDocResolver, TypeScript, checkCommand, initCommand, rootCommand, runCli } from "./376.js";
|
|
240
|
-
export { Handler, PackageJson,
|
|
181
|
+
export { Biome, Command, ConfigSearch, EntryExtractor, Filter, ImportGraph, Markdown, PnpmWorkspace, TsDocLinter, TsDocResolver, TypeScript, Yaml, checkCommand, fmtCommand, initCommand, rootCommand, runCli } from "./376.js";
|
|
182
|
+
export { Handler, PackageJson, Preset, ShellScripts, createConfig };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@savvy-web/lint-staged",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Composable, configurable lint-staged handlers for pre-commit hooks. Provides reusable handlers for Biome, Markdown, YAML, TypeScript, and more.",
|
|
6
6
|
"keywords": [
|
|
@@ -49,21 +49,32 @@
|
|
|
49
49
|
"eslint": "^9.39.2",
|
|
50
50
|
"eslint-plugin-tsdoc": "^0.5.0",
|
|
51
51
|
"jsonc-parser": "^3.3.1",
|
|
52
|
+
"prettier": "^3.8.1",
|
|
52
53
|
"sort-package-json": "^3.6.1",
|
|
53
54
|
"workspace-tools": "^0.41.0",
|
|
54
|
-
"yaml": "^2.8.2"
|
|
55
|
+
"yaml": "^2.8.2",
|
|
56
|
+
"yaml-lint": "^1.7.0"
|
|
55
57
|
},
|
|
56
58
|
"peerDependencies": {
|
|
57
59
|
"@biomejs/biome": "2.3.14",
|
|
60
|
+
"@types/node": "^25.2.1",
|
|
61
|
+
"@typescript/native-preview": "7.0.0-dev.20260207.1",
|
|
58
62
|
"husky": "^9.1.7",
|
|
59
63
|
"lint-staged": "^16.2.7",
|
|
60
64
|
"markdownlint-cli2": "^0.20.0",
|
|
61
|
-
"markdownlint-cli2-formatter-codequality": "^0.0.7"
|
|
65
|
+
"markdownlint-cli2-formatter-codequality": "^0.0.7",
|
|
66
|
+
"typescript": "^5.9.3"
|
|
62
67
|
},
|
|
63
68
|
"peerDependenciesMeta": {
|
|
64
69
|
"@biomejs/biome": {
|
|
65
70
|
"optional": true
|
|
66
71
|
},
|
|
72
|
+
"@types/node": {
|
|
73
|
+
"optional": false
|
|
74
|
+
},
|
|
75
|
+
"@typescript/native-preview": {
|
|
76
|
+
"optional": false
|
|
77
|
+
},
|
|
67
78
|
"husky": {
|
|
68
79
|
"optional": false
|
|
69
80
|
},
|
|
@@ -75,8 +86,14 @@
|
|
|
75
86
|
},
|
|
76
87
|
"markdownlint-cli2-formatter-codequality": {
|
|
77
88
|
"optional": false
|
|
89
|
+
},
|
|
90
|
+
"typescript": {
|
|
91
|
+
"optional": false
|
|
78
92
|
}
|
|
79
93
|
},
|
|
94
|
+
"engines": {
|
|
95
|
+
"node": ">=24.0.0"
|
|
96
|
+
},
|
|
80
97
|
"scripts": {
|
|
81
98
|
"postinstall": "savvy-lint check --quiet || true"
|
|
82
99
|
},
|