@savvy-web/lint-staged 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. package/376.js +15 -10
  2. package/README.md +2 -2
  3. package/index.d.ts +29 -11
  4. package/index.js +12 -18
  5. package/package.json +2 -2
package/376.js CHANGED
@@ -270,10 +270,14 @@ class Filter {
270
270
  if (options.exclude && options.exclude.length > 0) result = Filter.exclude(result, options.exclude);
271
271
  return result;
272
272
  }
273
+ static shellEscape(filenames) {
274
+ return filenames.map((f)=>`'${f.replace(/'/g, "'\\''")}'`).join(" ");
275
+ }
273
276
  }
274
277
  class Biome {
275
278
  static glob = "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}";
276
279
  static defaultExcludes = [
280
+ "package.json",
277
281
  "package-lock.json",
278
282
  "__fixtures__"
279
283
  ];
@@ -298,7 +302,7 @@ class Biome {
298
302
  const filtered = Filter.exclude(filenames, excludes);
299
303
  if (0 === filtered.length) return [];
300
304
  const biomeCmd = Command_Command.requireTool("biome", "Biome is not available. Install it globally (recommended) or add @biomejs/biome as a dev dependency.");
301
- const files = filtered.join(" ");
305
+ const files = Filter.shellEscape(filtered);
302
306
  const flags = options.flags ?? [];
303
307
  const configFlag = config ? `--config-path=${config}` : "";
304
308
  const cmd = [
@@ -336,7 +340,7 @@ class Markdown {
336
340
  const filtered = Filter.exclude(filenames, excludes);
337
341
  if (0 === filtered.length) return [];
338
342
  const mdlintCmd = Command_Command.requireTool("markdownlint-cli2", "markdownlint-cli2 is not available. Install it globally or add it as a dev dependency.");
339
- const files = filtered.join(" ");
343
+ const files = Filter.shellEscape(filtered);
340
344
  const fixFlag = noFix ? "" : "--fix";
341
345
  const configFlag = config ? `--config '${config}'` : "";
342
346
  const cmd = [
@@ -921,7 +925,7 @@ const CHECK_MARK = "\u2713";
921
925
  const WARNING = "\u26A0";
922
926
  const EXECUTABLE_MODE = 493;
923
927
  const HUSKY_HOOK_PATH = ".husky/pre-commit";
924
- const DEFAULT_CONFIG_PATH = "lib/configs/lint-staged.config.js";
928
+ const DEFAULT_CONFIG_PATH = "lib/configs/lint-staged.config.ts";
925
929
  const BEGIN_MARKER = "# --- BEGIN SAVVY-LINT MANAGED SECTION ---";
926
930
  const END_MARKER = "# --- END SAVVY-LINT MANAGED SECTION ---";
927
931
  function generateManagedContent(configPath) {
@@ -1001,12 +1005,13 @@ function updateManagedSection(existingContent, configPath) {
1001
1005
  }
1002
1006
  function generateConfigContent(preset) {
1003
1007
  return `/**
1004
- * @type {import('lint-staged').Configuration}
1008
+ * lint-staged configuration
1005
1009
  * Generated by savvy-lint init
1006
1010
  */
1011
+ import type { Configuration } from "lint-staged";
1007
1012
  import { Preset } from "@savvy-web/lint-staged";
1008
1013
 
1009
- export default Preset.${preset}();
1014
+ export default Preset.${preset}() satisfies Configuration;
1010
1015
  `;
1011
1016
  }
1012
1017
  const forceOption = Options.boolean("force").pipe(Options.withAlias("f"), Options.withDescription("Overwrite entire hook file (not just managed section)"), Options.withDefault(false));
@@ -1014,8 +1019,8 @@ const configOption = Options.text("config").pipe(Options.withAlias("c"), Options
1014
1019
  const presetOption = Options.choice("preset", [
1015
1020
  "minimal",
1016
1021
  "standard",
1017
- "full"
1018
- ]).pipe(Options.withAlias("p"), Options.withDescription("Preset to use: minimal, standard, or full"), Options.withDefault("full"));
1022
+ "silk"
1023
+ ]).pipe(Options.withAlias("p"), Options.withDescription("Preset to use: minimal, standard, or silk"), Options.withDefault("silk"));
1019
1024
  function makeExecutable(path) {
1020
1025
  return Effect.tryPromise(()=>import("node:fs/promises").then((fs)=>fs.chmod(path, EXECUTABLE_MODE)));
1021
1026
  }
@@ -1066,10 +1071,10 @@ const check_WARNING = "\u26A0";
1066
1071
  const BULLET = "\u2022";
1067
1072
  const check_HUSKY_HOOK_PATH = ".husky/pre-commit";
1068
1073
  const CONFIG_FILES = [
1074
+ "lint-staged.config.ts",
1069
1075
  "lint-staged.config.js",
1070
1076
  "lint-staged.config.mjs",
1071
1077
  "lint-staged.config.cjs",
1072
- "lint-staged.config.ts",
1073
1078
  ".lintstagedrc",
1074
1079
  ".lintstagedrc.json",
1075
1080
  ".lintstagedrc.yaml",
@@ -1079,8 +1084,8 @@ const CONFIG_FILES = [
1079
1084
  ".lintstagedrc.mjs"
1080
1085
  ];
1081
1086
  const CONFIG_SEARCH_PATHS = [
1082
- "lib/configs/lint-staged.config.js",
1083
1087
  "lib/configs/lint-staged.config.ts",
1088
+ "lib/configs/lint-staged.config.js",
1084
1089
  ...CONFIG_FILES
1085
1090
  ];
1086
1091
  function findConfigFile(fs) {
@@ -1189,7 +1194,7 @@ const rootCommand = Command.make("savvy-lint").pipe(Command.withSubcommands([
1189
1194
  ]));
1190
1195
  const cli = Command.run(rootCommand, {
1191
1196
  name: "savvy-lint",
1192
- version: "0.2.0"
1197
+ version: "0.2.2"
1193
1198
  });
1194
1199
  function runCli() {
1195
1200
  const main = Effect.suspend(()=>cli(process.argv)).pipe(Effect.provide(NodeContext.layer));
package/README.md CHANGED
@@ -59,7 +59,7 @@ export default {
59
59
  | ------ | -------- |
60
60
  | `minimal()` | PackageJson, Biome |
61
61
  | `standard()` | + Markdown, Yaml, PnpmWorkspace, ShellScripts |
62
- | `full()` | + TypeScript |
62
+ | `silk()` | + TypeScript |
63
63
 
64
64
  Extend any preset with options:
65
65
 
@@ -77,7 +77,7 @@ export default Preset.standard({
77
77
  | Handler | Files | Description |
78
78
  | ------- | ----- | ----------- |
79
79
  | `PackageJson` | `**/package.json` | Sort and format with Biome |
80
- | `Biome` | `*.{js,ts,jsx,tsx,json}` | Format and lint |
80
+ | `Biome` | `*.{js,ts,jsx,tsx,json,jsonc}` | Format and lint |
81
81
  | `Markdown` | `**/*.{md,mdx}` | Lint with markdownlint-cli2 |
82
82
  | `Yaml` | `**/*.{yml,yaml}` | Format and validate |
83
83
  | `PnpmWorkspace` | `pnpm-workspace.yaml` | Sort and format |
package/index.d.ts CHANGED
@@ -77,9 +77,10 @@ export declare class Biome {
77
77
  static readonly glob = "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}";
78
78
  /**
79
79
  * Default patterns to exclude from processing.
80
- * @defaultValue `['package-lock.json', '__fixtures__']`
80
+ * Excludes package.json since PackageJson handler processes those files.
81
+ * @defaultValue `['package.json', 'package-lock.json', '__fixtures__']`
81
82
  */
82
- static readonly defaultExcludes: readonly ["package-lock.json", "__fixtures__"];
83
+ static readonly defaultExcludes: readonly ["package.json", "package-lock.json", "__fixtures__"];
83
84
  /**
84
85
  * Pre-configured handler with default options.
85
86
  * Auto-discovers biome command and config file location.
@@ -534,7 +535,7 @@ export declare type ExportsField = string | Record<string, unknown> | null;
534
535
  *
535
536
  * const handler = (filenames: readonly string[]) => {
536
537
  * const filtered = Filter.exclude(filenames, ['dist/', '__fixtures__']);
537
- * return filtered.length > 0 ? `biome check ${filtered.join(' ')}` : [];
538
+ * return filtered.length > 0 ? `biome check ${Filter.shellEscape(filtered)}` : [];
538
539
  * };
539
540
  * ```
540
541
  */
@@ -593,6 +594,23 @@ export declare class Filter {
593
594
  include?: readonly string[];
594
595
  exclude?: readonly string[];
595
596
  }): string[];
597
+ /**
598
+ * Escape file paths for safe shell command construction.
599
+ *
600
+ * Wraps each path in single quotes and escapes any embedded single quotes.
601
+ * This prevents issues with paths containing spaces or special characters.
602
+ *
603
+ * @param filenames - Array of file paths
604
+ * @returns Space-separated string of shell-escaped paths
605
+ *
606
+ * @example
607
+ * ```typescript
608
+ * const files = ['/path/to/file.ts', '/path/with spaces/file.ts'];
609
+ * const escaped = Filter.shellEscape(files);
610
+ * // Result: "'/path/to/file.ts' '/path/with spaces/file.ts'"
611
+ * ```
612
+ */
613
+ static shellEscape(filenames: readonly string[]): string;
596
614
  }
597
615
 
598
616
  /**
@@ -788,7 +806,7 @@ export declare interface ImportGraphResult {
788
806
  export declare const initCommand: Command_2.Command<"init", FileSystem.FileSystem, Error | PlatformError, {
789
807
  readonly force: boolean;
790
808
  readonly config: string;
791
- readonly preset: "full" | "minimal" | "standard";
809
+ readonly preset: "minimal" | "silk" | "standard";
792
810
  }>;
793
811
 
794
812
  /**
@@ -1151,7 +1169,7 @@ export declare class Preset {
1151
1169
  */
1152
1170
  static standard(extend?: PresetExtendOptions): LintStagedConfig;
1153
1171
  /**
1154
- * Full preset: all handlers enabled.
1172
+ * Silk preset: all handlers enabled.
1155
1173
  *
1156
1174
  * Includes:
1157
1175
  * - PackageJson (sort + format)
@@ -1169,16 +1187,16 @@ export declare class Preset {
1169
1187
  * ```typescript
1170
1188
  * import { Preset } from '@savvy-web/lint-staged';
1171
1189
  *
1172
- * export default Preset.full({
1190
+ * export default Preset.silk({
1173
1191
  * typescript: { skipTypecheck: true },
1174
1192
  * });
1175
1193
  * ```
1176
1194
  */
1177
- static full(extend?: PresetExtendOptions): LintStagedConfig;
1195
+ static silk(extend?: PresetExtendOptions): LintStagedConfig;
1178
1196
  /**
1179
1197
  * Get a preset by name.
1180
1198
  *
1181
- * @param name - The preset name: 'minimal', 'standard', or 'full'
1199
+ * @param name - The preset name: 'minimal', 'standard', or 'silk'
1182
1200
  * @param extend - Options to customize or extend the preset
1183
1201
  * @returns A lint-staged configuration object
1184
1202
  *
@@ -1190,7 +1208,7 @@ export declare class Preset {
1190
1208
  * export default Preset.get(presetName);
1191
1209
  * ```
1192
1210
  */
1193
- static get(name: "minimal" | "standard" | "full", extend?: PresetExtendOptions): LintStagedConfig;
1211
+ static get(name: "minimal" | "standard" | "silk", extend?: PresetExtendOptions): LintStagedConfig;
1194
1212
  }
1195
1213
 
1196
1214
  /**
@@ -1202,14 +1220,14 @@ export declare type PresetExtendOptions = CreateConfigOptions;
1202
1220
  /**
1203
1221
  * Preset type for standard configurations.
1204
1222
  */
1205
- export declare type PresetType = "minimal" | "standard" | "full";
1223
+ export declare type PresetType = "minimal" | "standard" | "silk";
1206
1224
 
1207
1225
  /** Root command for the CLI with all subcommands. */
1208
1226
  export declare const rootCommand: Command_2.Command<"savvy-lint", FileSystem_2, Error | PlatformError, {
1209
1227
  readonly subcommand: Option< {
1210
1228
  readonly force: boolean;
1211
1229
  readonly config: string;
1212
- readonly preset: "full" | "minimal" | "standard";
1230
+ readonly preset: "minimal" | "silk" | "standard";
1213
1231
  } | {
1214
1232
  readonly quiet: boolean;
1215
1233
  }>;
package/index.js CHANGED
@@ -1,5 +1,6 @@
1
+ import sort_package_json from "sort-package-json";
1
2
  import { parse, stringify } from "yaml";
2
- import { Biome, Command as Command_Command, readFileSync, existsSync, TypeScript, writeFileSync, Markdown, Filter } from "./376.js";
3
+ import { Biome, Markdown, readFileSync, TypeScript, writeFileSync, existsSync, Filter } from "./376.js";
3
4
  class PackageJson {
4
5
  static glob = "**/package.json";
5
6
  static defaultExcludes = [
@@ -15,21 +16,14 @@ class PackageJson {
15
16
  return (filenames)=>{
16
17
  const filtered = Filter.exclude(filenames, excludes);
17
18
  if (0 === filtered.length) return [];
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);
19
+ if (!skipSort) for (const filepath of filtered){
20
+ const content = readFileSync(filepath, "utf-8");
21
+ const sorted = sort_package_json(content);
22
+ if (sorted !== content) writeFileSync(filepath, sorted, "utf-8");
29
23
  }
24
+ const files = Filter.shellEscape(filtered);
30
25
  const biomeCmd = options.biomeConfig ? `biome check --write --max-diagnostics=none --config-path=${options.biomeConfig} ${files}` : `biome check --write --max-diagnostics=none ${files}`;
31
- commands.push(biomeCmd);
32
- return commands.join(" && ");
26
+ return `${biomeCmd} && git add ${files}`;
33
27
  };
34
28
  }
35
29
  }
@@ -147,7 +141,7 @@ class Yaml {
147
141
  } catch (error) {
148
142
  throw new Error(`Invalid YAML in ${filepath}: ${error instanceof Error ? error.message : String(error)}`);
149
143
  }
150
- if (!skipFormat && filtered.length > 0) return `git add ${filtered.join(" ")}`;
144
+ if (!skipFormat && filtered.length > 0) return `git add ${Filter.shellEscape(filtered)}`;
151
145
  return [];
152
146
  };
153
147
  }
@@ -212,7 +206,7 @@ class Preset {
212
206
  if (void 0 !== extend.custom) options.custom = extend.custom;
213
207
  return createConfig(options);
214
208
  }
215
- static full(extend = {}) {
209
+ static silk(extend = {}) {
216
210
  const options = {
217
211
  packageJson: extend.packageJson ?? {},
218
212
  biome: extend.biome ?? {},
@@ -229,8 +223,8 @@ class Preset {
229
223
  switch(name){
230
224
  case "minimal":
231
225
  return Preset.minimal(extend);
232
- case "full":
233
- return Preset.full(extend);
226
+ case "silk":
227
+ return Preset.silk(extend);
234
228
  default:
235
229
  return Preset.standard(extend);
236
230
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@savvy-web/lint-staged",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
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": [
@@ -20,7 +20,7 @@
20
20
  },
21
21
  "repository": {
22
22
  "type": "git",
23
- "url": "https://github.com/savvy-web/lint-staged.git"
23
+ "url": "git+https://github.com/savvy-web/lint-staged.git"
24
24
  },
25
25
  "license": "MIT",
26
26
  "author": {