@savvy-web/lint-staged 0.2.2 → 0.3.1
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 +372 -45
- package/README.md +28 -19
- package/biome/silk.jsonc +126 -0
- package/index.d.ts +2 -0
- package/index.js +1 -3
- package/package.json +6 -17
- package/tsdoc-metadata.json +1 -1
package/376.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Command, Options } from "@effect/cli";
|
|
2
2
|
import { NodeContext, NodeRuntime } from "@effect/platform-node";
|
|
3
3
|
import { Effect } from "effect";
|
|
4
|
+
import { isDeepStrictEqual } from "node:util";
|
|
4
5
|
import { FileSystem } from "@effect/platform";
|
|
6
|
+
import { applyEdits, modify, parse } from "jsonc-parser";
|
|
5
7
|
import { execSync } from "node:child_process";
|
|
6
8
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
9
|
import { dirname, isAbsolute, join, normalize, relative, resolve } from "node:path";
|
|
@@ -921,17 +923,201 @@ class TypeScript {
|
|
|
921
923
|
};
|
|
922
924
|
}
|
|
923
925
|
}
|
|
926
|
+
const MARKDOWNLINT_TEMPLATE = {
|
|
927
|
+
$schema: "https://raw.githubusercontent.com/DavidAnson/markdownlint-cli2/v0.20.0/schema/markdownlint-cli2-config-schema.json",
|
|
928
|
+
globs: [
|
|
929
|
+
"**/*.{md,mdx}"
|
|
930
|
+
],
|
|
931
|
+
fix: true,
|
|
932
|
+
gitignore: true,
|
|
933
|
+
noBanner: true,
|
|
934
|
+
ignores: [
|
|
935
|
+
"**/node_modules",
|
|
936
|
+
"**/.cache",
|
|
937
|
+
"**/coverage",
|
|
938
|
+
"**/.coverage",
|
|
939
|
+
"**/dist",
|
|
940
|
+
"**/CHANGELOG.md",
|
|
941
|
+
"**/.claude/plans"
|
|
942
|
+
],
|
|
943
|
+
config: {
|
|
944
|
+
default: true,
|
|
945
|
+
MD001: true,
|
|
946
|
+
MD002: true,
|
|
947
|
+
MD003: true,
|
|
948
|
+
MD004: true,
|
|
949
|
+
MD005: true,
|
|
950
|
+
MD006: true,
|
|
951
|
+
MD007: true,
|
|
952
|
+
MD008: true,
|
|
953
|
+
MD009: true,
|
|
954
|
+
MD010: true,
|
|
955
|
+
MD011: true,
|
|
956
|
+
MD012: true,
|
|
957
|
+
MD013: false,
|
|
958
|
+
MD014: true,
|
|
959
|
+
MD015: true,
|
|
960
|
+
MD016: true,
|
|
961
|
+
MD017: true,
|
|
962
|
+
MD018: true,
|
|
963
|
+
MD019: true,
|
|
964
|
+
MD020: true,
|
|
965
|
+
MD021: true,
|
|
966
|
+
MD022: true,
|
|
967
|
+
MD023: true,
|
|
968
|
+
MD024: {
|
|
969
|
+
siblings_only: true
|
|
970
|
+
},
|
|
971
|
+
MD025: true,
|
|
972
|
+
MD026: true,
|
|
973
|
+
MD027: true,
|
|
974
|
+
MD028: true,
|
|
975
|
+
MD029: true,
|
|
976
|
+
MD030: true,
|
|
977
|
+
MD031: true,
|
|
978
|
+
MD032: true,
|
|
979
|
+
MD033: {
|
|
980
|
+
allowed_elements: [
|
|
981
|
+
"br",
|
|
982
|
+
"details",
|
|
983
|
+
"summary",
|
|
984
|
+
"img",
|
|
985
|
+
"sup",
|
|
986
|
+
"sub"
|
|
987
|
+
]
|
|
988
|
+
},
|
|
989
|
+
MD034: true,
|
|
990
|
+
MD035: true,
|
|
991
|
+
MD036: true,
|
|
992
|
+
MD037: true,
|
|
993
|
+
MD038: true,
|
|
994
|
+
MD039: true,
|
|
995
|
+
MD040: true,
|
|
996
|
+
MD041: true,
|
|
997
|
+
MD042: true,
|
|
998
|
+
MD043: false,
|
|
999
|
+
MD044: false,
|
|
1000
|
+
MD045: true,
|
|
1001
|
+
MD046: true,
|
|
1002
|
+
MD047: true,
|
|
1003
|
+
MD048: true,
|
|
1004
|
+
MD049: true,
|
|
1005
|
+
MD050: true,
|
|
1006
|
+
MD051: true,
|
|
1007
|
+
MD052: true,
|
|
1008
|
+
MD053: true,
|
|
1009
|
+
MD054: true,
|
|
1010
|
+
MD055: true,
|
|
1011
|
+
MD056: true,
|
|
1012
|
+
MD057: true,
|
|
1013
|
+
MD058: true,
|
|
1014
|
+
MD059: true,
|
|
1015
|
+
MD060: {
|
|
1016
|
+
style: "compact"
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
};
|
|
1020
|
+
const MARKDOWNLINT_SCHEMA = "https://raw.githubusercontent.com/DavidAnson/markdownlint-cli2/v0.20.0/schema/markdownlint-cli2-config-schema.json";
|
|
1021
|
+
const MARKDOWNLINT_CONFIG = {
|
|
1022
|
+
default: true,
|
|
1023
|
+
MD001: true,
|
|
1024
|
+
MD002: true,
|
|
1025
|
+
MD003: true,
|
|
1026
|
+
MD004: true,
|
|
1027
|
+
MD005: true,
|
|
1028
|
+
MD006: true,
|
|
1029
|
+
MD007: true,
|
|
1030
|
+
MD008: true,
|
|
1031
|
+
MD009: true,
|
|
1032
|
+
MD010: true,
|
|
1033
|
+
MD011: true,
|
|
1034
|
+
MD012: true,
|
|
1035
|
+
MD013: false,
|
|
1036
|
+
MD014: true,
|
|
1037
|
+
MD015: true,
|
|
1038
|
+
MD016: true,
|
|
1039
|
+
MD017: true,
|
|
1040
|
+
MD018: true,
|
|
1041
|
+
MD019: true,
|
|
1042
|
+
MD020: true,
|
|
1043
|
+
MD021: true,
|
|
1044
|
+
MD022: true,
|
|
1045
|
+
MD023: true,
|
|
1046
|
+
MD024: {
|
|
1047
|
+
siblings_only: true
|
|
1048
|
+
},
|
|
1049
|
+
MD025: true,
|
|
1050
|
+
MD026: true,
|
|
1051
|
+
MD027: true,
|
|
1052
|
+
MD028: true,
|
|
1053
|
+
MD029: true,
|
|
1054
|
+
MD030: true,
|
|
1055
|
+
MD031: true,
|
|
1056
|
+
MD032: true,
|
|
1057
|
+
MD033: {
|
|
1058
|
+
allowed_elements: [
|
|
1059
|
+
"br",
|
|
1060
|
+
"details",
|
|
1061
|
+
"summary",
|
|
1062
|
+
"img",
|
|
1063
|
+
"sup",
|
|
1064
|
+
"sub"
|
|
1065
|
+
]
|
|
1066
|
+
},
|
|
1067
|
+
MD034: true,
|
|
1068
|
+
MD035: true,
|
|
1069
|
+
MD036: true,
|
|
1070
|
+
MD037: true,
|
|
1071
|
+
MD038: true,
|
|
1072
|
+
MD039: true,
|
|
1073
|
+
MD040: true,
|
|
1074
|
+
MD041: true,
|
|
1075
|
+
MD042: true,
|
|
1076
|
+
MD043: false,
|
|
1077
|
+
MD044: false,
|
|
1078
|
+
MD045: true,
|
|
1079
|
+
MD046: true,
|
|
1080
|
+
MD047: true,
|
|
1081
|
+
MD048: true,
|
|
1082
|
+
MD049: true,
|
|
1083
|
+
MD050: true,
|
|
1084
|
+
MD051: true,
|
|
1085
|
+
MD052: true,
|
|
1086
|
+
MD053: true,
|
|
1087
|
+
MD054: true,
|
|
1088
|
+
MD055: true,
|
|
1089
|
+
MD056: true,
|
|
1090
|
+
MD057: true,
|
|
1091
|
+
MD058: true,
|
|
1092
|
+
MD059: true,
|
|
1093
|
+
MD060: {
|
|
1094
|
+
style: "compact"
|
|
1095
|
+
}
|
|
1096
|
+
};
|
|
924
1097
|
const CHECK_MARK = "\u2713";
|
|
925
1098
|
const WARNING = "\u26A0";
|
|
926
1099
|
const EXECUTABLE_MODE = 493;
|
|
927
1100
|
const HUSKY_HOOK_PATH = ".husky/pre-commit";
|
|
1101
|
+
const POST_CHECKOUT_HOOK_PATH = ".husky/post-checkout";
|
|
1102
|
+
const POST_MERGE_HOOK_PATH = ".husky/post-merge";
|
|
928
1103
|
const DEFAULT_CONFIG_PATH = "lib/configs/lint-staged.config.ts";
|
|
1104
|
+
const MARKDOWNLINT_CONFIG_PATH = "lib/configs/.markdownlint-cli2.jsonc";
|
|
1105
|
+
const JSONC_FORMAT = {
|
|
1106
|
+
tabSize: 1,
|
|
1107
|
+
insertSpaces: false
|
|
1108
|
+
};
|
|
929
1109
|
const BEGIN_MARKER = "# --- BEGIN SAVVY-LINT MANAGED SECTION ---";
|
|
930
1110
|
const END_MARKER = "# --- END SAVVY-LINT MANAGED SECTION ---";
|
|
1111
|
+
function presetIncludesShellScripts(preset) {
|
|
1112
|
+
return "minimal" !== preset;
|
|
1113
|
+
}
|
|
1114
|
+
function presetIncludesMarkdown(preset) {
|
|
1115
|
+
return "minimal" !== preset;
|
|
1116
|
+
}
|
|
931
1117
|
function generateManagedContent(configPath) {
|
|
932
1118
|
return `# DO NOT EDIT between these markers - managed by savvy-lint
|
|
933
1119
|
# Skip in CI environment
|
|
934
|
-
{ [ -n "$CI" ] || [ -n "$GITHUB_ACTIONS" ]; }
|
|
1120
|
+
if ! { [ -n "$CI" ] || [ -n "$GITHUB_ACTIONS" ]; }; then
|
|
935
1121
|
|
|
936
1122
|
# Get repo root directory
|
|
937
1123
|
ROOT=$(git rev-parse --show-toplevel)
|
|
@@ -959,26 +1145,36 @@ detect_pm() {
|
|
|
959
1145
|
fi
|
|
960
1146
|
}
|
|
961
1147
|
|
|
962
|
-
#
|
|
1148
|
+
# Run lint-staged with the detected package manager
|
|
963
1149
|
PM=$(detect_pm)
|
|
964
1150
|
case "$PM" in
|
|
965
|
-
pnpm)
|
|
966
|
-
yarn)
|
|
967
|
-
bun)
|
|
968
|
-
*)
|
|
1151
|
+
pnpm) pnpm exec lint-staged --config "$ROOT/${configPath}" ;;
|
|
1152
|
+
yarn) yarn exec lint-staged --config "$ROOT/${configPath}" ;;
|
|
1153
|
+
bun) bunx lint-staged --config "$ROOT/${configPath}" ;;
|
|
1154
|
+
*) npx --no -- lint-staged --config "$ROOT/${configPath}" ;;
|
|
969
1155
|
esac
|
|
970
1156
|
|
|
971
|
-
|
|
1157
|
+
fi`;
|
|
972
1158
|
}
|
|
973
|
-
function
|
|
974
|
-
return
|
|
975
|
-
|
|
976
|
-
# Custom hooks can go above or below the managed section
|
|
1159
|
+
function generateShellScriptsManagedContent() {
|
|
1160
|
+
return `# DO NOT EDIT between these markers - managed by savvy-lint
|
|
1161
|
+
if ! { [ -n "$CI" ] || [ -n "$GITHUB_ACTIONS" ]; }; then
|
|
977
1162
|
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1163
|
+
# Configure git to ignore executable bit changes
|
|
1164
|
+
# This ensures hook scripts can be made executable locally without git tracking the change
|
|
1165
|
+
git config core.fileMode false
|
|
1166
|
+
|
|
1167
|
+
# Ensure all shell scripts tracked by git are executable
|
|
1168
|
+
git ls-files -z '*.sh' | xargs -0 -r chmod +x 2>/dev/null || true
|
|
1169
|
+
|
|
1170
|
+
fi`;
|
|
1171
|
+
}
|
|
1172
|
+
function updateManagedSectionWithContent(existingContent, managedContent) {
|
|
1173
|
+
const { beforeSection, afterSection, found } = extractManagedSection(existingContent);
|
|
1174
|
+
const newManagedSection = `${BEGIN_MARKER}\n${managedContent}\n${END_MARKER}`;
|
|
1175
|
+
if (found) return `${beforeSection}${newManagedSection}${afterSection}`;
|
|
1176
|
+
const trimmedContent = existingContent.trimEnd();
|
|
1177
|
+
return `${trimmedContent}\n\n${newManagedSection}\n`;
|
|
982
1178
|
}
|
|
983
1179
|
function extractManagedSection(content) {
|
|
984
1180
|
const beginIndex = content.indexOf(BEGIN_MARKER);
|
|
@@ -996,24 +1192,73 @@ function extractManagedSection(content) {
|
|
|
996
1192
|
found: true
|
|
997
1193
|
};
|
|
998
1194
|
}
|
|
999
|
-
function
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1195
|
+
function generateFullHookContentFromManaged(comment, managedContent) {
|
|
1196
|
+
return `#!/usr/bin/env sh
|
|
1197
|
+
# ${comment}
|
|
1198
|
+
# Custom hooks can go above or below the managed section
|
|
1199
|
+
|
|
1200
|
+
${BEGIN_MARKER}
|
|
1201
|
+
${managedContent}
|
|
1202
|
+
${END_MARKER}
|
|
1203
|
+
`;
|
|
1005
1204
|
}
|
|
1006
1205
|
function generateConfigContent(preset) {
|
|
1007
1206
|
return `/**
|
|
1008
1207
|
* lint-staged configuration
|
|
1009
1208
|
* Generated by savvy-lint init
|
|
1010
1209
|
*/
|
|
1011
|
-
import type { Configuration } from "lint-staged";
|
|
1012
1210
|
import { Preset } from "@savvy-web/lint-staged";
|
|
1013
1211
|
|
|
1014
|
-
export default Preset.${preset}()
|
|
1212
|
+
export default Preset.${preset}();
|
|
1015
1213
|
`;
|
|
1016
1214
|
}
|
|
1215
|
+
function writeMarkdownlintConfig(fs, preset, force) {
|
|
1216
|
+
return Effect.gen(function*() {
|
|
1217
|
+
const configExists = yield* fs.exists(MARKDOWNLINT_CONFIG_PATH);
|
|
1218
|
+
const fullTemplate = JSON.stringify(MARKDOWNLINT_TEMPLATE, null, "\t");
|
|
1219
|
+
if (!configExists) {
|
|
1220
|
+
yield* fs.makeDirectory("lib/configs", {
|
|
1221
|
+
recursive: true
|
|
1222
|
+
});
|
|
1223
|
+
yield* fs.writeFileString(MARKDOWNLINT_CONFIG_PATH, `${fullTemplate}\n`);
|
|
1224
|
+
yield* Effect.log(`${CHECK_MARK} Created ${MARKDOWNLINT_CONFIG_PATH}`);
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
if ("silk" !== preset) return void (yield* Effect.log(`${CHECK_MARK} ${MARKDOWNLINT_CONFIG_PATH}: exists (not managed by ${preset} preset)`));
|
|
1228
|
+
if (force) {
|
|
1229
|
+
yield* fs.writeFileString(MARKDOWNLINT_CONFIG_PATH, `${fullTemplate}\n`);
|
|
1230
|
+
yield* Effect.log(`${CHECK_MARK} Replaced ${MARKDOWNLINT_CONFIG_PATH} (--force)`);
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
const existingText = yield* fs.readFileString(MARKDOWNLINT_CONFIG_PATH);
|
|
1234
|
+
const existingParsed = parse(existingText);
|
|
1235
|
+
let updatedText = existingText;
|
|
1236
|
+
let schemaUpdated = false;
|
|
1237
|
+
if (existingParsed.$schema !== MARKDOWNLINT_SCHEMA) {
|
|
1238
|
+
const edits = modify(updatedText, [
|
|
1239
|
+
"$schema"
|
|
1240
|
+
], MARKDOWNLINT_SCHEMA, {
|
|
1241
|
+
formattingOptions: JSONC_FORMAT
|
|
1242
|
+
});
|
|
1243
|
+
updatedText = applyEdits(updatedText, edits);
|
|
1244
|
+
schemaUpdated = true;
|
|
1245
|
+
}
|
|
1246
|
+
const existingConfig = existingParsed.config;
|
|
1247
|
+
const configMatches = void 0 !== existingConfig && isDeepStrictEqual(existingConfig, MARKDOWNLINT_CONFIG);
|
|
1248
|
+
if (!configMatches) {
|
|
1249
|
+
yield* Effect.log(`${WARNING} ${MARKDOWNLINT_CONFIG_PATH}: config rules differ from template (use --force to overwrite)`);
|
|
1250
|
+
if (schemaUpdated) {
|
|
1251
|
+
yield* fs.writeFileString(MARKDOWNLINT_CONFIG_PATH, updatedText);
|
|
1252
|
+
yield* Effect.log(`${CHECK_MARK} Updated $schema in ${MARKDOWNLINT_CONFIG_PATH}`);
|
|
1253
|
+
}
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
if (schemaUpdated) {
|
|
1257
|
+
yield* fs.writeFileString(MARKDOWNLINT_CONFIG_PATH, updatedText);
|
|
1258
|
+
yield* Effect.log(`${CHECK_MARK} Updated $schema in ${MARKDOWNLINT_CONFIG_PATH}`);
|
|
1259
|
+
} else yield* Effect.log(`${CHECK_MARK} ${MARKDOWNLINT_CONFIG_PATH}: up-to-date`);
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1017
1262
|
const forceOption = Options.boolean("force").pipe(Options.withAlias("f"), Options.withDescription("Overwrite entire hook file (not just managed section)"), Options.withDefault(false));
|
|
1018
1263
|
const configOption = Options.text("config").pipe(Options.withAlias("c"), Options.withDescription("Relative path for the lint-staged config file (from repo root)"), Options.withDefault(DEFAULT_CONFIG_PATH));
|
|
1019
1264
|
const presetOption = Options.choice("preset", [
|
|
@@ -1024,6 +1269,31 @@ const presetOption = Options.choice("preset", [
|
|
|
1024
1269
|
function makeExecutable(path) {
|
|
1025
1270
|
return Effect.tryPromise(()=>import("node:fs/promises").then((fs)=>fs.chmod(path, EXECUTABLE_MODE)));
|
|
1026
1271
|
}
|
|
1272
|
+
function writeHook(fs, hookPath, managedContent, comment, force) {
|
|
1273
|
+
return Effect.gen(function*() {
|
|
1274
|
+
const hookExists = yield* fs.exists(hookPath);
|
|
1275
|
+
if (hookExists && !force) {
|
|
1276
|
+
const existingContent = yield* fs.readFileString(hookPath);
|
|
1277
|
+
const { found } = extractManagedSection(existingContent);
|
|
1278
|
+
const updatedContent = updateManagedSectionWithContent(existingContent, managedContent);
|
|
1279
|
+
yield* fs.writeFileString(hookPath, updatedContent);
|
|
1280
|
+
yield* makeExecutable(hookPath);
|
|
1281
|
+
if (found) yield* Effect.log(`${CHECK_MARK} Updated managed section in ${hookPath}`);
|
|
1282
|
+
else yield* Effect.log(`${CHECK_MARK} Added managed section to ${hookPath}`);
|
|
1283
|
+
} else if (hookExists && force) {
|
|
1284
|
+
yield* fs.writeFileString(hookPath, generateFullHookContentFromManaged(comment, managedContent));
|
|
1285
|
+
yield* makeExecutable(hookPath);
|
|
1286
|
+
yield* Effect.log(`${CHECK_MARK} Replaced ${hookPath} (--force)`);
|
|
1287
|
+
} else {
|
|
1288
|
+
yield* fs.makeDirectory(".husky", {
|
|
1289
|
+
recursive: true
|
|
1290
|
+
});
|
|
1291
|
+
yield* fs.writeFileString(hookPath, generateFullHookContentFromManaged(comment, managedContent));
|
|
1292
|
+
yield* makeExecutable(hookPath);
|
|
1293
|
+
yield* Effect.log(`${CHECK_MARK} Created ${hookPath}`);
|
|
1294
|
+
}
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1027
1297
|
const initCommand = Command.make("init", {
|
|
1028
1298
|
force: forceOption,
|
|
1029
1299
|
config: configOption,
|
|
@@ -1032,27 +1302,13 @@ const initCommand = Command.make("init", {
|
|
|
1032
1302
|
const fs = yield* FileSystem.FileSystem;
|
|
1033
1303
|
if (config.startsWith("/")) yield* Effect.fail(new Error("Config path must be relative to repository root, not absolute"));
|
|
1034
1304
|
yield* Effect.log("Initializing lint-staged configuration...\n");
|
|
1035
|
-
|
|
1036
|
-
if (
|
|
1037
|
-
const
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
yield* fs.writeFileString(HUSKY_HOOK_PATH, updatedContent);
|
|
1041
|
-
yield* makeExecutable(HUSKY_HOOK_PATH);
|
|
1042
|
-
if (found) yield* Effect.log(`${CHECK_MARK} Updated managed section in ${HUSKY_HOOK_PATH}`);
|
|
1043
|
-
else yield* Effect.log(`${CHECK_MARK} Added managed section to ${HUSKY_HOOK_PATH}`);
|
|
1044
|
-
} else if (huskyExists && force) {
|
|
1045
|
-
yield* fs.writeFileString(HUSKY_HOOK_PATH, generateFullHookContent(config));
|
|
1046
|
-
yield* makeExecutable(HUSKY_HOOK_PATH);
|
|
1047
|
-
yield* Effect.log(`${CHECK_MARK} Replaced ${HUSKY_HOOK_PATH} (--force)`);
|
|
1048
|
-
} else {
|
|
1049
|
-
yield* fs.makeDirectory(".husky", {
|
|
1050
|
-
recursive: true
|
|
1051
|
-
});
|
|
1052
|
-
yield* fs.writeFileString(HUSKY_HOOK_PATH, generateFullHookContent(config));
|
|
1053
|
-
yield* makeExecutable(HUSKY_HOOK_PATH);
|
|
1054
|
-
yield* Effect.log(`${CHECK_MARK} Created ${HUSKY_HOOK_PATH}`);
|
|
1305
|
+
yield* writeHook(fs, HUSKY_HOOK_PATH, generateManagedContent(config), "Pre-commit hook with savvy-lint managed section", force);
|
|
1306
|
+
if (presetIncludesShellScripts(preset)) {
|
|
1307
|
+
const shellContent = generateShellScriptsManagedContent();
|
|
1308
|
+
yield* writeHook(fs, POST_CHECKOUT_HOOK_PATH, shellContent, "Post-checkout hook with savvy-lint managed section", force);
|
|
1309
|
+
yield* writeHook(fs, POST_MERGE_HOOK_PATH, shellContent, "Post-merge hook with savvy-lint managed section", force);
|
|
1055
1310
|
}
|
|
1311
|
+
if (presetIncludesMarkdown(preset)) yield* writeMarkdownlintConfig(fs, preset, force);
|
|
1056
1312
|
const configExists = yield* fs.exists(config);
|
|
1057
1313
|
if (configExists && !force) yield* Effect.log(`${WARNING} ${config} already exists (use --force to overwrite)`);
|
|
1058
1314
|
else {
|
|
@@ -1098,6 +1354,23 @@ function extractConfigPathFromManaged(managedContent) {
|
|
|
1098
1354
|
const match = managedContent.match(/lint-staged --config "\$ROOT\/([^"]+)"/);
|
|
1099
1355
|
return match ? match[1] : null;
|
|
1100
1356
|
}
|
|
1357
|
+
function checkHookManagedSection(hookContent, expectedManagedContent) {
|
|
1358
|
+
const { managedSection, found } = extractManagedSection(hookContent);
|
|
1359
|
+
if (!found) return {
|
|
1360
|
+
found: false,
|
|
1361
|
+
isUpToDate: false,
|
|
1362
|
+
needsUpdate: false
|
|
1363
|
+
};
|
|
1364
|
+
const expectedSection = `${BEGIN_MARKER}\n${expectedManagedContent}\n${END_MARKER}`;
|
|
1365
|
+
const normalizedExisting = managedSection.trim().replace(/\s+/g, " ");
|
|
1366
|
+
const normalizedExpected = expectedSection.trim().replace(/\s+/g, " ");
|
|
1367
|
+
const isUpToDate = normalizedExisting === normalizedExpected;
|
|
1368
|
+
return {
|
|
1369
|
+
found: true,
|
|
1370
|
+
isUpToDate,
|
|
1371
|
+
needsUpdate: !isUpToDate
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1101
1374
|
function checkManagedSectionStatus(existingManaged) {
|
|
1102
1375
|
const configPath = extractConfigPathFromManaged(existingManaged);
|
|
1103
1376
|
if (!configPath) return {
|
|
@@ -1115,6 +1388,18 @@ function checkManagedSectionStatus(existingManaged) {
|
|
|
1115
1388
|
needsUpdate: !isUpToDate
|
|
1116
1389
|
};
|
|
1117
1390
|
}
|
|
1391
|
+
function checkMarkdownlintConfig(content) {
|
|
1392
|
+
const parsed = parse(content);
|
|
1393
|
+
const schemaMatches = parsed.$schema === MARKDOWNLINT_SCHEMA;
|
|
1394
|
+
const existingConfig = parsed.config;
|
|
1395
|
+
const configMatches = void 0 !== existingConfig && isDeepStrictEqual(existingConfig, MARKDOWNLINT_CONFIG);
|
|
1396
|
+
return {
|
|
1397
|
+
exists: true,
|
|
1398
|
+
schemaMatches,
|
|
1399
|
+
configMatches,
|
|
1400
|
+
isUpToDate: schemaMatches && configMatches
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1118
1403
|
const quietOption = Options.boolean("quiet").pipe(Options.withAlias("q"), Options.withDescription("Only output warnings (for postinstall usage)"), Options.withDefault(false));
|
|
1119
1404
|
const checkCommand = Command.make("check", {
|
|
1120
1405
|
quiet: quietOption
|
|
@@ -1150,6 +1435,36 @@ const checkCommand = Command.make("check", {
|
|
|
1150
1435
|
}
|
|
1151
1436
|
} else warnings.push(`${check_WARNING} No husky pre-commit hook found.\n Run 'savvy-lint init' to create it.`);
|
|
1152
1437
|
if (!foundConfig) warnings.push(`${check_WARNING} No lint-staged config file found.\n Run 'savvy-lint init' to create one.`);
|
|
1438
|
+
const shellHookPaths = [
|
|
1439
|
+
POST_CHECKOUT_HOOK_PATH,
|
|
1440
|
+
POST_MERGE_HOOK_PATH
|
|
1441
|
+
];
|
|
1442
|
+
const shellHookStatuses = [];
|
|
1443
|
+
for (const hookPath of shellHookPaths){
|
|
1444
|
+
const hookExists = yield* fs.exists(hookPath);
|
|
1445
|
+
if (hookExists) {
|
|
1446
|
+
const hookContent = yield* fs.readFileString(hookPath);
|
|
1447
|
+
const status = checkHookManagedSection(hookContent, generateShellScriptsManagedContent());
|
|
1448
|
+
shellHookStatuses.push({
|
|
1449
|
+
path: hookPath,
|
|
1450
|
+
...status
|
|
1451
|
+
});
|
|
1452
|
+
if (status.found && status.needsUpdate) warnings.push(`${check_WARNING} Your ${hookPath} managed section is outdated.\n Run 'savvy-lint init' to update it (preserves your custom hooks).`);
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
const hasMarkdownlintConfig = yield* fs.exists(MARKDOWNLINT_CONFIG_PATH);
|
|
1456
|
+
let markdownlintStatus = {
|
|
1457
|
+
exists: false,
|
|
1458
|
+
schemaMatches: false,
|
|
1459
|
+
configMatches: false,
|
|
1460
|
+
isUpToDate: false
|
|
1461
|
+
};
|
|
1462
|
+
if (hasMarkdownlintConfig) {
|
|
1463
|
+
const mdContent = yield* fs.readFileString(MARKDOWNLINT_CONFIG_PATH);
|
|
1464
|
+
markdownlintStatus = checkMarkdownlintConfig(mdContent);
|
|
1465
|
+
if (!markdownlintStatus.schemaMatches) warnings.push(`${check_WARNING} ${MARKDOWNLINT_CONFIG_PATH}: $schema differs from template.\n Run 'savvy-lint init' to update it.`);
|
|
1466
|
+
if (!markdownlintStatus.configMatches) warnings.push(`${check_WARNING} ${MARKDOWNLINT_CONFIG_PATH}: config rules differ from template.\n Run 'savvy-lint init --force' to overwrite.`);
|
|
1467
|
+
}
|
|
1153
1468
|
if (quiet) {
|
|
1154
1469
|
if (warnings.length > 0) for (const warning of warnings)yield* Effect.log(warning);
|
|
1155
1470
|
return;
|
|
@@ -1162,6 +1477,8 @@ const checkCommand = Command.make("check", {
|
|
|
1162
1477
|
if (hasHuskyHook) if (managedStatus.found) if (managedStatus.isUpToDate) yield* Effect.log(`${check_CHECK_MARK} Managed section: up-to-date`);
|
|
1163
1478
|
else yield* Effect.log(`${check_WARNING} Managed section: outdated (run 'savvy-lint init' to update)`);
|
|
1164
1479
|
else yield* Effect.log(`${BULLET} Managed section: not found (run 'savvy-lint init' to add)`);
|
|
1480
|
+
for (const status of shellHookStatuses)if (status.found) if (status.isUpToDate) yield* Effect.log(`${check_CHECK_MARK} ${status.path}: up-to-date`);
|
|
1481
|
+
else yield* Effect.log(`${check_WARNING} ${status.path}: outdated (run 'savvy-lint init' to update)`);
|
|
1165
1482
|
yield* Effect.log("\nTool availability:");
|
|
1166
1483
|
const biomeAvailable = Biome.isAvailable();
|
|
1167
1484
|
const biomeConfig = Biome.findConfig();
|
|
@@ -1183,8 +1500,18 @@ const checkCommand = Command.make("check", {
|
|
|
1183
1500
|
const tsdocAvailable = TypeScript.isTsdocAvailable();
|
|
1184
1501
|
if (tsdocAvailable) yield* Effect.log(` ${check_CHECK_MARK} TSDoc (tsdoc.json found)`);
|
|
1185
1502
|
else yield* Effect.log(` ${BULLET} TSDoc: no tsdoc.json found`);
|
|
1503
|
+
if (hasMarkdownlintConfig) if (markdownlintStatus.isUpToDate) yield* Effect.log(` ${check_CHECK_MARK} ${MARKDOWNLINT_CONFIG_PATH}: up-to-date`);
|
|
1504
|
+
else {
|
|
1505
|
+
const issues = [];
|
|
1506
|
+
if (!markdownlintStatus.schemaMatches) issues.push("$schema");
|
|
1507
|
+
if (!markdownlintStatus.configMatches) issues.push("config");
|
|
1508
|
+
yield* Effect.log(` ${check_WARNING} ${MARKDOWNLINT_CONFIG_PATH}: ${issues.join(", ")} differ from template`);
|
|
1509
|
+
}
|
|
1510
|
+
else yield* Effect.log(` ${BULLET} ${MARKDOWNLINT_CONFIG_PATH}: not found`);
|
|
1186
1511
|
yield* Effect.log("");
|
|
1187
|
-
const
|
|
1512
|
+
const hasShellHookIssues = shellHookStatuses.some((s)=>s.found && s.needsUpdate);
|
|
1513
|
+
const hasMarkdownlintIssues = hasMarkdownlintConfig && !markdownlintStatus.isUpToDate;
|
|
1514
|
+
const hasIssues = !foundConfig || !hasHuskyHook || !managedStatus.found || managedStatus.needsUpdate || hasShellHookIssues || hasMarkdownlintIssues;
|
|
1188
1515
|
if (hasIssues) yield* Effect.log(`${check_WARNING} Some issues found. Run 'savvy-lint init' to fix.`);
|
|
1189
1516
|
else yield* Effect.log(`${check_CHECK_MARK} Lint-staged is configured correctly.`);
|
|
1190
1517
|
})).pipe(Command.withDescription("Check current lint-staged configuration and tool availability"));
|
|
@@ -1194,7 +1521,7 @@ const rootCommand = Command.make("savvy-lint").pipe(Command.withSubcommands([
|
|
|
1194
1521
|
]));
|
|
1195
1522
|
const cli = Command.run(rootCommand, {
|
|
1196
1523
|
name: "savvy-lint",
|
|
1197
|
-
version: "0.
|
|
1524
|
+
version: "0.3.1"
|
|
1198
1525
|
});
|
|
1199
1526
|
function runCli() {
|
|
1200
1527
|
const main = Effect.suspend(()=>cli(process.argv)).pipe(Effect.provide(NodeContext.layer));
|
package/README.md
CHANGED
|
@@ -2,17 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@savvy-web/lint-staged)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://nodejs.org/)
|
|
5
6
|
|
|
6
7
|
Composable, configurable lint-staged handlers for pre-commit hooks. Stop
|
|
7
|
-
duplicating lint-staged configs across projects
|
|
8
|
+
duplicating lint-staged configs across projects -- use reusable handlers with
|
|
8
9
|
sensible defaults and easy customization.
|
|
9
10
|
|
|
10
11
|
## Features
|
|
11
12
|
|
|
12
13
|
- Composable handlers for Biome, Markdown, YAML, TypeScript, and more
|
|
13
14
|
- Zero-config presets for instant setup
|
|
15
|
+
- CLI tool (`savvy-lint`) to bootstrap and validate your configuration
|
|
14
16
|
- Workspace-aware TSDoc validation for public APIs
|
|
15
|
-
-
|
|
17
|
+
- Shareable Biome configuration via `@savvy-web/lint-staged/biome/silk.jsonc`
|
|
16
18
|
- Static class API with excellent TypeScript and TSDoc support
|
|
17
19
|
|
|
18
20
|
## Installation
|
|
@@ -30,7 +32,13 @@ npm install -D markdownlint-cli2
|
|
|
30
32
|
|
|
31
33
|
## Quick Start
|
|
32
34
|
|
|
33
|
-
Use
|
|
35
|
+
Use the CLI to bootstrap your configuration:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx savvy-lint init --preset standard
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Or configure manually with a preset:
|
|
34
42
|
|
|
35
43
|
```typescript
|
|
36
44
|
// lint-staged.config.ts
|
|
@@ -61,17 +69,6 @@ export default {
|
|
|
61
69
|
| `standard()` | + Markdown, Yaml, PnpmWorkspace, ShellScripts |
|
|
62
70
|
| `silk()` | + TypeScript |
|
|
63
71
|
|
|
64
|
-
Extend any preset with options:
|
|
65
|
-
|
|
66
|
-
```typescript
|
|
67
|
-
import { Preset } from '@savvy-web/lint-staged';
|
|
68
|
-
|
|
69
|
-
export default Preset.standard({
|
|
70
|
-
biome: { exclude: ['vendor/'] },
|
|
71
|
-
typescript: {}, // Enable TypeScript in standard
|
|
72
|
-
});
|
|
73
|
-
```
|
|
74
|
-
|
|
75
72
|
## Available Handlers
|
|
76
73
|
|
|
77
74
|
| Handler | Files | Description |
|
|
@@ -82,14 +79,26 @@ export default Preset.standard({
|
|
|
82
79
|
| `Yaml` | `**/*.{yml,yaml}` | Format and validate |
|
|
83
80
|
| `PnpmWorkspace` | `pnpm-workspace.yaml` | Sort and format |
|
|
84
81
|
| `ShellScripts` | `**/*.sh` | Manage permissions |
|
|
85
|
-
| `TypeScript` | `*.{ts,tsx}` | TSDoc validation + typecheck |
|
|
82
|
+
| `TypeScript` | `*.{ts,cts,mts,tsx}` | TSDoc validation + typecheck |
|
|
83
|
+
|
|
84
|
+
## CLI
|
|
85
|
+
|
|
86
|
+
The `savvy-lint` CLI helps bootstrap and validate your setup:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
savvy-lint init # Bootstrap hooks, config, and tooling
|
|
90
|
+
savvy-lint init --preset silk --force # Overwrite with silk preset
|
|
91
|
+
savvy-lint check # Validate current configuration
|
|
92
|
+
savvy-lint check --quiet # Warnings only (for postinstall)
|
|
93
|
+
```
|
|
86
94
|
|
|
87
95
|
## Documentation
|
|
88
96
|
|
|
89
|
-
- [Handler Configuration](./docs/handlers.md)
|
|
90
|
-
- [
|
|
91
|
-
- [
|
|
92
|
-
- [
|
|
97
|
+
- [Handler Configuration](./docs/handlers.md) -- Detailed options for each handler
|
|
98
|
+
- [Configuration API](./docs/configuration.md) -- createConfig and Preset APIs
|
|
99
|
+
- [CLI Reference](./docs/cli.md) -- `savvy-lint init` and `savvy-lint check`
|
|
100
|
+
- [Utilities](./docs/utilities.md) -- Command, Filter, and advanced utilities
|
|
101
|
+
- [Migration Guide](./docs/migration.md) -- Migrating from raw lint-staged configs
|
|
93
102
|
|
|
94
103
|
## Contributing
|
|
95
104
|
|
package/biome/silk.jsonc
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/2.3.14/schema.json",
|
|
3
|
+
"assist": {
|
|
4
|
+
"actions": {
|
|
5
|
+
"source": {
|
|
6
|
+
"organizeImports": {
|
|
7
|
+
"level": "on",
|
|
8
|
+
"options": {
|
|
9
|
+
"identifierOrder": "lexicographic"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"enabled": true
|
|
15
|
+
},
|
|
16
|
+
"formatter": {
|
|
17
|
+
"enabled": true,
|
|
18
|
+
"formatWithErrors": true,
|
|
19
|
+
"indentStyle": "tab",
|
|
20
|
+
"indentWidth": 2,
|
|
21
|
+
"lineWidth": 120
|
|
22
|
+
},
|
|
23
|
+
"json": {
|
|
24
|
+
"assist": {
|
|
25
|
+
"enabled": true
|
|
26
|
+
},
|
|
27
|
+
"formatter": {
|
|
28
|
+
"enabled": true,
|
|
29
|
+
"expand": "auto"
|
|
30
|
+
},
|
|
31
|
+
"linter": {
|
|
32
|
+
"enabled": true
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"css": {
|
|
36
|
+
"parser": {
|
|
37
|
+
"cssModules": true
|
|
38
|
+
},
|
|
39
|
+
"linter": {
|
|
40
|
+
"enabled": true
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"linter": {
|
|
44
|
+
"domains": {
|
|
45
|
+
"project": "all"
|
|
46
|
+
},
|
|
47
|
+
"enabled": true,
|
|
48
|
+
"rules": {
|
|
49
|
+
"correctness": {
|
|
50
|
+
"noUnusedVariables": {
|
|
51
|
+
"level": "error",
|
|
52
|
+
"options": {
|
|
53
|
+
"ignoreRestSiblings": true
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"useImportExtensions": {
|
|
57
|
+
"level": "error",
|
|
58
|
+
"options": {
|
|
59
|
+
"forceJsExtensions": true
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"nursery": {
|
|
64
|
+
"noImportCycles": "error",
|
|
65
|
+
"useExplicitType": "off"
|
|
66
|
+
},
|
|
67
|
+
"recommended": true,
|
|
68
|
+
"style": {
|
|
69
|
+
"useConsistentTypeDefinitions": "error",
|
|
70
|
+
"useImportType": {
|
|
71
|
+
"level": "error",
|
|
72
|
+
"options": {
|
|
73
|
+
"style": "separatedType"
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"useNodejsImportProtocol": "error"
|
|
77
|
+
},
|
|
78
|
+
"suspicious": {
|
|
79
|
+
"noBiomeFirstException": "error",
|
|
80
|
+
"noDuplicateObjectKeys": "error",
|
|
81
|
+
"noQuickfixBiome": "error",
|
|
82
|
+
"useBiomeIgnoreFolder": "error"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"overrides": [
|
|
87
|
+
{
|
|
88
|
+
"includes": ["package.json"],
|
|
89
|
+
"json": {
|
|
90
|
+
"formatter": {
|
|
91
|
+
"expand": "auto"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"assist": {
|
|
97
|
+
"actions": {
|
|
98
|
+
"source": {
|
|
99
|
+
"useSortedKeys": "on"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
"includes": ["**/turbo.json", "**/tsconfig.json", "**/tsconfig.*.json"]
|
|
104
|
+
}
|
|
105
|
+
],
|
|
106
|
+
"root": true,
|
|
107
|
+
"vcs": {
|
|
108
|
+
"clientKind": "git",
|
|
109
|
+
"defaultBranch": "main",
|
|
110
|
+
"enabled": true,
|
|
111
|
+
"useIgnoreFile": true
|
|
112
|
+
},
|
|
113
|
+
"files": {
|
|
114
|
+
"includes": [
|
|
115
|
+
"**",
|
|
116
|
+
"!**/node_modules",
|
|
117
|
+
"!**/dist",
|
|
118
|
+
"!**/.turbo",
|
|
119
|
+
"!**/.git",
|
|
120
|
+
"!**/.rslib",
|
|
121
|
+
"!**/.vitest",
|
|
122
|
+
"!**/.coverage",
|
|
123
|
+
"!coverage"
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
}
|
package/index.d.ts
CHANGED
|
@@ -798,6 +798,8 @@ export declare interface ImportGraphResult {
|
|
|
798
798
|
* @remarks
|
|
799
799
|
* Creates the necessary configuration files for lint-staged:
|
|
800
800
|
* - `.husky/pre-commit` hook with managed section
|
|
801
|
+
* - `.husky/post-checkout` and `.husky/post-merge` hooks (when preset includes ShellScripts)
|
|
802
|
+
* - `.markdownlint-cli2.jsonc` config (when preset includes Markdown)
|
|
801
803
|
* - lint-staged config at the specified path
|
|
802
804
|
*
|
|
803
805
|
* The managed section feature allows users to add custom hooks above/below
|
package/index.js
CHANGED
|
@@ -23,7 +23,7 @@ class PackageJson {
|
|
|
23
23
|
}
|
|
24
24
|
const files = Filter.shellEscape(filtered);
|
|
25
25
|
const biomeCmd = options.biomeConfig ? `biome check --write --max-diagnostics=none --config-path=${options.biomeConfig} ${files}` : `biome check --write --max-diagnostics=none ${files}`;
|
|
26
|
-
return
|
|
26
|
+
return biomeCmd;
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
}
|
|
@@ -76,7 +76,6 @@ class PnpmWorkspace {
|
|
|
76
76
|
if (!skipSort || !skipFormat) {
|
|
77
77
|
const formatted = stringify(parsed, DEFAULT_STRINGIFY_OPTIONS);
|
|
78
78
|
writeFileSync(filepath, formatted, "utf-8");
|
|
79
|
-
return `git add ${filepath}`;
|
|
80
79
|
}
|
|
81
80
|
return [];
|
|
82
81
|
};
|
|
@@ -141,7 +140,6 @@ class Yaml {
|
|
|
141
140
|
} catch (error) {
|
|
142
141
|
throw new Error(`Invalid YAML in ${filepath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
143
142
|
}
|
|
144
|
-
if (!skipFormat && filtered.length > 0) return `git add ${Filter.shellEscape(filtered)}`;
|
|
145
143
|
return [];
|
|
146
144
|
};
|
|
147
145
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@savvy-web/lint-staged",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
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": [
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
".": {
|
|
34
34
|
"types": "./index.d.ts",
|
|
35
35
|
"import": "./index.js"
|
|
36
|
-
}
|
|
36
|
+
},
|
|
37
|
+
"./biome/silk.jsonc": "./biome/silk.jsonc"
|
|
37
38
|
},
|
|
38
39
|
"bin": {
|
|
39
40
|
"savvy-lint": "./bin/savvy-lint.js"
|
|
@@ -47,12 +48,13 @@
|
|
|
47
48
|
"effect": "^3.19.16",
|
|
48
49
|
"eslint": "^9.39.2",
|
|
49
50
|
"eslint-plugin-tsdoc": "^0.5.0",
|
|
51
|
+
"jsonc-parser": "^3.3.1",
|
|
50
52
|
"sort-package-json": "^3.6.1",
|
|
51
53
|
"workspace-tools": "^0.41.0",
|
|
52
54
|
"yaml": "^2.8.2"
|
|
53
55
|
},
|
|
54
56
|
"peerDependencies": {
|
|
55
|
-
"@biomejs/biome": "
|
|
57
|
+
"@biomejs/biome": "2.3.14",
|
|
56
58
|
"husky": "^9.1.7",
|
|
57
59
|
"lint-staged": "^16.2.7",
|
|
58
60
|
"markdownlint-cli2": "^0.20.0",
|
|
@@ -75,20 +77,6 @@
|
|
|
75
77
|
"optional": false
|
|
76
78
|
}
|
|
77
79
|
},
|
|
78
|
-
"devEngines": {
|
|
79
|
-
"packageManager": {
|
|
80
|
-
"name": "pnpm",
|
|
81
|
-
"version": "10.28.2",
|
|
82
|
-
"onFail": "ignore"
|
|
83
|
-
},
|
|
84
|
-
"runtime": [
|
|
85
|
-
{
|
|
86
|
-
"name": "node",
|
|
87
|
-
"version": "24.11.0",
|
|
88
|
-
"onFail": "ignore"
|
|
89
|
-
}
|
|
90
|
-
]
|
|
91
|
-
},
|
|
92
80
|
"scripts": {
|
|
93
81
|
"postinstall": "savvy-lint check --quiet || true"
|
|
94
82
|
},
|
|
@@ -100,6 +88,7 @@
|
|
|
100
88
|
"LICENSE",
|
|
101
89
|
"README.md",
|
|
102
90
|
"bin/savvy-lint.js",
|
|
91
|
+
"biome/silk.jsonc",
|
|
103
92
|
"index.d.ts",
|
|
104
93
|
"index.js",
|
|
105
94
|
"package.json",
|