@luxkit/cli 1.1.3 → 1.1.41

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/dist/index.js CHANGED
@@ -113,14 +113,25 @@ trim_trailing_whitespace = false
113
113
  "@stylistic/stylelint-plugin",
114
114
  "postcss-html",
115
115
  "postcss-scss",
116
- "cspell"
116
+ "cspell",
117
+ "husky",
118
+ "lint-staged"
117
119
  ]
118
120
  },
119
121
  scripts: {
120
122
  lint: 'eslint . --cache --cache-location node_modules/.cache/eslint && cspell --cache --cache-location node_modules/.cache/cspell --gitignore "src/**/*" && vue-tsc --noEmit && stylelint "src/**/*.{css,scss,vue}" --cache --cache-strategy content --cache-location node_modules/.cache/stylelint/',
121
123
  "lint:fix": 'eslint . --cache --cache-location node_modules/.cache/eslint --fix && stylelint "src/**/*.{css,scss,vue}" --fix --cache --cache-strategy content --cache-location node_modules/.cache/stylelint/',
122
- format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"'
123
- }
124
+ format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"',
125
+ "lint-staged": "lint-staged"
126
+ },
127
+ lintStaged: () => JSON.stringify(
128
+ {
129
+ "*.{ts,js,vue}": ["eslint --fix", "prettier --write"],
130
+ "*.{css,scss,vue}": ["stylelint --fix", "prettier --write"]
131
+ },
132
+ null,
133
+ 2
134
+ ) + "\n"
124
135
  };
125
136
 
126
137
  // src/presets/fmt/nest.ts
@@ -176,13 +187,21 @@ trim_trailing_whitespace = true
176
187
  trim_trailing_whitespace = false
177
188
  `,
178
189
  dependencies: {
179
- dev: ["prettier", "cspell"]
190
+ dev: ["prettier", "cspell", "husky", "lint-staged"]
180
191
  },
181
192
  scripts: {
182
193
  lint: 'eslint "{src,apps,libs,test}/**/*.ts" --cache --cache-location node_modules/.cache/eslint && cspell --cache --cache-location node_modules/.cache/cspell --gitignore "src/**/*" && tsc --noEmit',
183
194
  "lint:fix": 'eslint "{src,apps,libs,test}/**/*.ts" --cache --cache-location node_modules/.cache/eslint --fix',
184
- format: 'prettier --write "src/**/*.{ts,js,json}"'
185
- }
195
+ format: 'prettier --write "src/**/*.{ts,js,json}"',
196
+ "lint-staged": "lint-staged"
197
+ },
198
+ lintStaged: () => JSON.stringify(
199
+ {
200
+ "*.{ts,js}": ["eslint --fix", "prettier --write"]
201
+ },
202
+ null,
203
+ 2
204
+ ) + "\n"
186
205
  };
187
206
 
188
207
  // src/presets/fmt/node.ts
@@ -287,14 +306,24 @@ trim_trailing_whitespace = false
287
306
  "eslint-plugin-prettier",
288
307
  "eslint-config-prettier",
289
308
  "prettier",
290
- "cspell"
309
+ "cspell",
310
+ "husky",
311
+ "lint-staged"
291
312
  ]
292
313
  },
293
314
  scripts: {
294
315
  lint: 'eslint . --cache --cache-location node_modules/.cache/eslint && cspell --cache --cache-location node_modules/.cache/cspell --gitignore "src/**/*" && tsc --noEmit',
295
316
  "lint:fix": "eslint . --cache --cache-location node_modules/.cache/eslint --fix",
296
- format: 'prettier --write "src/**/*.{ts,js,json}"'
297
- }
317
+ format: 'prettier --write "src/**/*.{ts,js,json}"',
318
+ "lint-staged": "lint-staged"
319
+ },
320
+ lintStaged: () => JSON.stringify(
321
+ {
322
+ "*.{ts,js}": ["eslint --fix", "prettier --write"]
323
+ },
324
+ null,
325
+ 2
326
+ ) + "\n"
298
327
  };
299
328
 
300
329
  // src/presets/fmt/uniapp.ts
@@ -400,14 +429,25 @@ trim_trailing_whitespace = false
400
429
  "@stylistic/stylelint-plugin",
401
430
  "postcss-html",
402
431
  "postcss-scss",
403
- "cspell"
432
+ "cspell",
433
+ "husky",
434
+ "lint-staged"
404
435
  ]
405
436
  },
406
437
  scripts: {
407
438
  lint: 'eslint . --cache --cache-location node_modules/.cache/eslint && cspell --cache --cache-location node_modules/.cache/cspell --gitignore "src/**/*" && vue-tsc --noEmit && stylelint "src/**/*.{css,scss,vue}" --cache --cache-strategy content --cache-location node_modules/.cache/stylelint/',
408
439
  "lint:fix": 'eslint . --cache --cache-location node_modules/.cache/eslint --fix && stylelint "src/**/*.{css,scss,vue}" --fix --cache --cache-strategy content --cache-location node_modules/.cache/stylelint/',
409
- format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"'
410
- }
440
+ format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"',
441
+ "lint-staged": "lint-staged"
442
+ },
443
+ lintStaged: () => JSON.stringify(
444
+ {
445
+ "*.{ts,js,vue}": ["eslint --fix", "prettier --write"],
446
+ "*.{css,scss,vue}": ["stylelint --fix", "prettier --write"]
447
+ },
448
+ null,
449
+ 2
450
+ ) + "\n"
411
451
  };
412
452
 
413
453
  // src/presets/fmt/web-react.ts
@@ -520,14 +560,25 @@ trim_trailing_whitespace = false
520
560
  "stylelint-scss",
521
561
  "@stylistic/stylelint-plugin",
522
562
  "postcss-scss",
523
- "cspell"
563
+ "cspell",
564
+ "husky",
565
+ "lint-staged"
524
566
  ]
525
567
  },
526
568
  scripts: {
527
569
  lint: 'eslint . --cache --cache-location node_modules/.cache/eslint && cspell --cache --cache-location node_modules/.cache/cspell --gitignore "src/**/*" && tsc --noEmit && stylelint "src/**/*.{css,scss}" --cache --cache-strategy content --cache-location node_modules/.cache/stylelint/',
528
570
  "lint:fix": 'eslint . --cache --cache-location node_modules/.cache/eslint --fix && stylelint "src/**/*.{css,scss}" --fix --cache --cache-strategy content --cache-location node_modules/.cache/stylelint/',
529
- format: 'prettier --write "src/**/*.{ts,js,json,jsx,tsx,css,scss}"'
530
- }
571
+ format: 'prettier --write "src/**/*.{ts,js,json,jsx,tsx,css,scss}"',
572
+ "lint-staged": "lint-staged"
573
+ },
574
+ lintStaged: () => JSON.stringify(
575
+ {
576
+ "*.{ts,tsx,js,jsx}": ["eslint --fix", "prettier --write"],
577
+ "*.{css,scss}": ["stylelint --fix", "prettier --write"]
578
+ },
579
+ null,
580
+ 2
581
+ ) + "\n"
531
582
  };
532
583
 
533
584
  // src/presets/fmt/web-vue.ts
@@ -631,14 +682,25 @@ trim_trailing_whitespace = false
631
682
  "@stylistic/stylelint-plugin",
632
683
  "postcss-html",
633
684
  "postcss-scss",
634
- "cspell"
685
+ "cspell",
686
+ "husky",
687
+ "lint-staged"
635
688
  ]
636
689
  },
637
690
  scripts: {
638
691
  lint: 'eslint . --cache --cache-location node_modules/.cache/eslint && cspell --cache --cache-location node_modules/.cache/cspell --gitignore "src/**/*" && vue-tsc --noEmit && stylelint "src/**/*.{css,scss,vue}" --cache --cache-strategy content --cache-location node_modules/.cache/stylelint/',
639
692
  "lint:fix": 'eslint . --cache --cache-location node_modules/.cache/eslint --fix && stylelint "src/**/*.{css,scss,vue}" --fix --cache --cache-strategy content --cache-location node_modules/.cache/stylelint/',
640
- format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"'
641
- }
693
+ format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"',
694
+ "lint-staged": "lint-staged"
695
+ },
696
+ lintStaged: () => JSON.stringify(
697
+ {
698
+ "*.{ts,js,vue}": ["eslint --fix", "prettier --write"],
699
+ "*.{css,scss,vue}": ["stylelint --fix", "prettier --write"]
700
+ },
701
+ null,
702
+ 2
703
+ ) + "\n"
642
704
  };
643
705
 
644
706
  // src/presets/fmt/index.ts
@@ -776,7 +838,8 @@ var CONFIG_FILES = [
776
838
  { filename: "stylelint.config.mjs", getContent: (p) => p.stylelint?.() },
777
839
  { filename: ".stylelintignore", getContent: (p) => p.stylelintIgnore?.() },
778
840
  { filename: "cspell.json", getContent: (p) => p.cspell?.() },
779
- { filename: ".editorconfig", getContent: (p) => p.editorconfig?.() }
841
+ { filename: ".editorconfig", getContent: (p) => p.editorconfig?.() },
842
+ { filename: ".lintstagedrc.json", getContent: (p) => p.lintStaged?.() }
780
843
  ];
781
844
  function generateConfigFile(preset, filename, content, opts) {
782
845
  const filepath = path2.join(opts.cwd, filename);
@@ -799,6 +862,8 @@ function generateAllFmt(preset, opts) {
799
862
  for (const { filename, getContent } of CONFIG_FILES) {
800
863
  if (opts.noStylelint && filename.includes("stylelint")) continue;
801
864
  if (opts.noEditorconfig && filename === ".editorconfig") continue;
865
+ if (opts.noCspell && filename.includes("cspell")) continue;
866
+ if (opts.noLintStaged && filename === ".lintstagedrc.json") continue;
802
867
  const content = getContent(preset);
803
868
  if (content === void 0) continue;
804
869
  const action = generateConfigFile(preset, filename, content, opts);
@@ -991,10 +1056,13 @@ var CONFIG_GETTERS = [
991
1056
  { filename: "stylelint.config.mjs", getContent: (p) => p.stylelint?.() },
992
1057
  { filename: ".stylelintignore", getContent: (p) => p.stylelintIgnore?.() },
993
1058
  { filename: "cspell.json", getContent: (p) => p.cspell?.() },
994
- { filename: ".editorconfig", getContent: (p) => p.editorconfig?.() }
1059
+ { filename: ".editorconfig", getContent: (p) => p.editorconfig?.() },
1060
+ { filename: ".lintstagedrc.json", getContent: (p) => p.lintStaged?.() }
995
1061
  ];
996
1062
  var STYLELINT_FILES = /* @__PURE__ */ new Set(["stylelint.config.mjs", ".stylelintignore"]);
997
1063
  var EDITORCONFIG_FILE = ".editorconfig";
1064
+ var CSPELL_FILE = "cspell.json";
1065
+ var LINTSTAGED_FILE = ".lintstagedrc.json";
998
1066
  var STYLELINT_SETTINGS_PREFIXES = [
999
1067
  "stylelint.",
1000
1068
  "css.validate",
@@ -1011,6 +1079,8 @@ var STYLELINT_DEPS = /* @__PURE__ */ new Set([
1011
1079
  "postcss-scss"
1012
1080
  ]);
1013
1081
  var STYLELINT_EXTENSION = "stylelint.vscode-stylelint";
1082
+ var HUSKY_DEPS = /* @__PURE__ */ new Set(["husky"]);
1083
+ var LINTSTAGED_DEPS = /* @__PURE__ */ new Set(["lint-staged"]);
1014
1084
  function getLuxDir() {
1015
1085
  return process.env.LUX_HOME || path4.join(os.homedir(), ".lux");
1016
1086
  }
@@ -1129,6 +1199,8 @@ function applyLocalFmtPreset(cwd, presetName, opts) {
1129
1199
  for (const filename of entries) {
1130
1200
  if (opts.noStylelint && STYLELINT_FILES.has(filename)) continue;
1131
1201
  if (opts.noEditorconfig && filename === EDITORCONFIG_FILE) continue;
1202
+ if (opts.noCspell && filename === CSPELL_FILE) continue;
1203
+ if (opts.noLintStaged && filename === LINTSTAGED_FILE) continue;
1132
1204
  const destPath = path4.join(cwd, filename);
1133
1205
  const exists = fileExists(destPath);
1134
1206
  if (exists && !opts.force) {
@@ -1249,6 +1321,9 @@ function mergeTemplateIntoProject(templatePkg, projectPkg, pm, opts, result) {
1249
1321
  for (const [dep, version] of Object.entries(templatePkg.devDependencies)) {
1250
1322
  if (opts.noStylelint && STYLELINT_DEPS.has(dep)) continue;
1251
1323
  if (opts.noEditorconfig && dep.includes("editorconfig")) continue;
1324
+ if (opts.noCspell && dep === "cspell") continue;
1325
+ if (opts.noHusky && HUSKY_DEPS.has(dep)) continue;
1326
+ if (opts.noLintStaged && LINTSTAGED_DEPS.has(dep)) continue;
1252
1327
  if (existingDeps[dep] === void 0 && version !== "<latest>") {
1253
1328
  newDeps[dep] = version;
1254
1329
  }
@@ -1261,7 +1336,9 @@ function mergeTemplateIntoProject(templatePkg, projectPkg, pm, opts, result) {
1261
1336
  const filteredScripts = filterScripts(
1262
1337
  templatePkg.scripts,
1263
1338
  opts.noStylelint,
1264
- opts.noEditorconfig
1339
+ opts.noEditorconfig,
1340
+ opts.noCspell,
1341
+ opts.noLintStaged
1265
1342
  );
1266
1343
  for (const [key, value] of Object.entries(filteredScripts)) {
1267
1344
  const resolved = value.replace(/<pm>/g, prefix);
@@ -1299,15 +1376,21 @@ function filterStylelintSettings(settings) {
1299
1376
  }
1300
1377
  return filtered;
1301
1378
  }
1302
- function filterScripts(scripts, noStylelint, noEditorconfig) {
1379
+ function filterScripts(scripts, noStylelint, noEditorconfig, noCspell, noLintStaged = false) {
1303
1380
  const filtered = {};
1304
1381
  for (const [key, value] of Object.entries(scripts)) {
1305
1382
  if (noStylelint && key.includes("stylelint")) continue;
1306
1383
  if (noEditorconfig && key.includes("editorconfig")) continue;
1384
+ if (noCspell && key.includes("cspell")) continue;
1385
+ if (noLintStaged && key.includes("lint-staged")) continue;
1307
1386
  let resolved = value;
1308
1387
  if (noStylelint) {
1309
1388
  resolved = resolved.replace(/\s*&&\s*stylelint\s+"[^"]*".*/g, "");
1310
1389
  }
1390
+ if (noCspell) {
1391
+ resolved = resolved.replace(/\s*&&\s*cspell\s+[^&]*/g, "");
1392
+ resolved = resolved.replace(/(\S)&&/g, "$1 &&");
1393
+ }
1311
1394
  filtered[key] = resolved;
1312
1395
  }
1313
1396
  return filtered;
@@ -1322,9 +1405,15 @@ function detectPresetCapabilities(presetName) {
1322
1405
  const hasStylelintDep = pkg?.devDependencies ? Object.keys(pkg.devDependencies).some((d) => isNotStylelintDep(d) === false) : false;
1323
1406
  const hasEditorconfigFile = entries.includes(EDITORCONFIG_FILE);
1324
1407
  const hasEditorconfigDep = pkg?.devDependencies ? Object.keys(pkg.devDependencies).some((d) => !isNotEditorconfigDep(d)) : false;
1408
+ const hasCspellFile = entries.includes(CSPELL_FILE);
1409
+ const hasCspellDep = pkg?.devDependencies ? Object.keys(pkg.devDependencies).some((d) => d === "cspell") : false;
1410
+ const hasLintStagedFile = entries.includes(LINTSTAGED_FILE);
1411
+ const hasLintStagedDep = pkg?.devDependencies ? Object.keys(pkg.devDependencies).some((d) => isNotLintStagedDep(d) === false) : false;
1325
1412
  return {
1326
1413
  hasStylelint: hasStylelintFile || hasStylelintDep,
1327
- hasEditorconfig: hasEditorconfigFile || hasEditorconfigDep
1414
+ hasEditorconfig: hasEditorconfigFile || hasEditorconfigDep,
1415
+ hasCspell: hasCspellFile || hasCspellDep,
1416
+ hasLintStaged: hasLintStagedFile || hasLintStagedDep
1328
1417
  };
1329
1418
  }
1330
1419
  function isNotStylelintDep(dep) {
@@ -1335,6 +1424,9 @@ function isNotStylelintDep(dep) {
1335
1424
  function isNotEditorconfigDep(dep) {
1336
1425
  return !dep.includes("editorconfig");
1337
1426
  }
1427
+ function isNotLintStagedDep(dep) {
1428
+ return dep !== "lint-staged";
1429
+ }
1338
1430
  function resolveLocalDeps(deps) {
1339
1431
  const packages = [];
1340
1432
  for (const [name, version] of Object.entries(deps)) {
@@ -1356,9 +1448,18 @@ function isNotStylelintDep2(dep) {
1356
1448
  function isNotEditorconfigDep2(dep) {
1357
1449
  return !dep.includes("editorconfig");
1358
1450
  }
1451
+ function isNotCspellDep(dep) {
1452
+ return dep !== "cspell";
1453
+ }
1454
+ function isNotHuskyDep(dep) {
1455
+ return dep !== "husky";
1456
+ }
1457
+ function isNotLintStagedDep2(dep) {
1458
+ return dep !== "lint-staged";
1459
+ }
1359
1460
  function registerFmtCommand(program2) {
1360
1461
  const fmt = program2.command("fmt").description("Initialize formatting config with preset");
1361
- fmt.argument("<preset>").option("-F, --force", "Force overwrite existing files").option("--no-install", "Skip dependency installation").option("--dry-run", "Preview without writing files").option("--stylelint", "Include Stylelint config generation").option("--editorconfig", "Include EditorConfig config generation").option("--reset", "Reset local preset and re-materialize from built-in").action(
1462
+ fmt.argument("<preset>").option("-F, --force", "Force overwrite existing files").option("--no-install", "Skip dependency installation").option("--dry-run", "Preview without writing files").option("--stylelint", "Include Stylelint config generation").option("--editorconfig", "Include EditorConfig config generation").option("--cspell", "Include CSpell config generation").option("--husky", "Initialize husky for Git hooks").option("--lint-staged", "Set up lint-staged (implies --husky)").option("--reset", "Reset local preset and re-materialize from built-in").action(
1362
1463
  async (presetName, options) => {
1363
1464
  const builtinPreset = FMT_PRESETS.find((p) => p.name === presetName);
1364
1465
  const isBuiltin = builtinPreset !== void 0;
@@ -1424,12 +1525,27 @@ async function executeLocalPath(cwd, presetName, options) {
1424
1525
  "--editorconfig has no effect: this custom preset has no editorconfig config or dependencies"
1425
1526
  );
1426
1527
  }
1528
+ if (options.cspell && !caps.hasCspell) {
1529
+ logger.warn(
1530
+ "--cspell has no effect: this custom preset has no cspell config or dependencies"
1531
+ );
1532
+ }
1533
+ if (options.lintStaged && !caps.hasLintStaged) {
1534
+ logger.warn(
1535
+ "--lint-staged has no effect: this custom preset has no lint-staged config or dependencies"
1536
+ );
1537
+ }
1538
+ const noHusky = options.husky !== true && options.lintStaged !== true;
1539
+ const noLintStaged = options.lintStaged !== true;
1427
1540
  const opts = {
1428
1541
  cwd,
1429
1542
  force: options.force ?? false,
1430
1543
  dryRun: options.dryRun ?? false,
1431
1544
  noStylelint: options.stylelint !== true,
1432
- noEditorconfig: options.editorconfig !== true
1545
+ noEditorconfig: options.editorconfig !== true,
1546
+ noCspell: options.cspell !== true,
1547
+ noHusky,
1548
+ noLintStaged
1433
1549
  };
1434
1550
  let result;
1435
1551
  try {
@@ -1460,7 +1576,10 @@ async function executeLocalPath(cwd, presetName, options) {
1460
1576
  const depsToInstall = filterDeps(
1461
1577
  Object.keys(templatePkg.devDependencies),
1462
1578
  opts.noStylelint,
1463
- opts.noEditorconfig
1579
+ opts.noEditorconfig,
1580
+ opts.noCspell,
1581
+ opts.noHusky,
1582
+ opts.noLintStaged
1464
1583
  );
1465
1584
  const projectPkgPath = path5.join(cwd, "package.json");
1466
1585
  const projectPkg = readJson(projectPkgPath);
@@ -1470,6 +1589,9 @@ async function executeLocalPath(cwd, presetName, options) {
1470
1589
  if (missing.length === 0) return;
1471
1590
  if (opts.dryRun) {
1472
1591
  logger.log(`[dry-run] Would add to package.json: ${missing.join(", ")}`);
1592
+ if (!opts.noHusky) {
1593
+ await initHusky(cwd, pm, opts);
1594
+ }
1473
1595
  return;
1474
1596
  }
1475
1597
  if (options.install === false) {
@@ -1488,6 +1610,9 @@ async function executeLocalPath(cwd, presetName, options) {
1488
1610
  const message = error instanceof Error ? error.message : String(error);
1489
1611
  logger.warn(`Failed to fetch versions: ${message}. You can add dependencies manually.`);
1490
1612
  }
1613
+ if (!opts.noHusky) {
1614
+ await initHusky(cwd, pm, opts);
1615
+ }
1491
1616
  return;
1492
1617
  }
1493
1618
  try {
@@ -1503,15 +1628,23 @@ async function executeLocalPath(cwd, presetName, options) {
1503
1628
  const message = error instanceof Error ? error.message : String(error);
1504
1629
  logger.warn(`Dependency installation failed: ${message}. You can install manually.`);
1505
1630
  }
1631
+ if (!opts.noHusky) {
1632
+ await initHusky(cwd, pm, opts);
1633
+ }
1506
1634
  }
1507
1635
  async function executeBuiltinPath(cwd, presetName, preset, options) {
1508
1636
  const pm = fileExists(path5.join(cwd, "package.json")) ? detectPackageManager(cwd) : void 0;
1637
+ const noHusky = options.husky !== true && options.lintStaged !== true;
1638
+ const noLintStaged = options.lintStaged !== true;
1509
1639
  const opts = {
1510
1640
  cwd,
1511
1641
  force: options.force ?? false,
1512
1642
  dryRun: options.dryRun ?? false,
1513
1643
  noStylelint: options.stylelint !== true,
1514
1644
  noEditorconfig: options.editorconfig !== true,
1645
+ noCspell: options.cspell !== true,
1646
+ noHusky,
1647
+ noLintStaged,
1515
1648
  lockfile: pm ? getLockfileName(pm) : void 0
1516
1649
  };
1517
1650
  const result = generateAllFmt(preset, opts);
@@ -1528,15 +1661,27 @@ async function executeBuiltinPath(cwd, presetName, preset, options) {
1528
1661
  warnMissingPackageJson(preset, options.install !== false);
1529
1662
  return;
1530
1663
  }
1531
- const scripts = preset.scripts ? filterScripts(preset.scripts, opts.noStylelint, opts.noEditorconfig) : void 0;
1664
+ const scripts = preset.scripts ? filterScripts(
1665
+ preset.scripts,
1666
+ opts.noStylelint,
1667
+ opts.noEditorconfig,
1668
+ opts.noCspell,
1669
+ opts.noLintStaged
1670
+ ) : void 0;
1532
1671
  if (scripts) {
1533
1672
  await injectScripts(scripts, opts, pm);
1534
1673
  }
1535
1674
  if (!preset.dependencies?.dev) return;
1536
1675
  const devDeps = opts.noStylelint ? preset.dependencies.dev.filter(isNotStylelintDep2) : preset.dependencies.dev;
1537
- const finalDeps = opts.noEditorconfig ? devDeps.filter(isNotEditorconfigDep2) : devDeps;
1676
+ const noEditorconfigDeps = opts.noEditorconfig ? devDeps.filter(isNotEditorconfigDep2) : devDeps;
1677
+ const noCspellDeps = opts.noCspell ? noEditorconfigDeps.filter(isNotCspellDep) : noEditorconfigDeps;
1678
+ const noHuskyDeps = opts.noHusky ? noCspellDeps.filter(isNotHuskyDep) : noCspellDeps;
1679
+ const finalDeps = opts.noLintStaged ? noHuskyDeps.filter(isNotLintStagedDep2) : noHuskyDeps;
1538
1680
  if (opts.dryRun) {
1539
1681
  logger.log(`[dry-run] Would add to package.json: ${finalDeps.join(", ")}`);
1682
+ if (!opts.noHusky) {
1683
+ await initHusky(cwd, pm, opts);
1684
+ }
1540
1685
  return;
1541
1686
  }
1542
1687
  if (options.install === false) {
@@ -1551,6 +1696,9 @@ async function executeBuiltinPath(cwd, presetName, preset, options) {
1551
1696
  const message = error instanceof Error ? error.message : String(error);
1552
1697
  logger.warn(`Failed to fetch versions: ${message}. You can add dependencies manually.`);
1553
1698
  }
1699
+ if (!opts.noHusky) {
1700
+ await initHusky(cwd, pm, opts);
1701
+ }
1554
1702
  return;
1555
1703
  }
1556
1704
  try {
@@ -1561,11 +1709,17 @@ async function executeBuiltinPath(cwd, presetName, preset, options) {
1561
1709
  const message = error instanceof Error ? error.message : String(error);
1562
1710
  logger.warn(`Dependency installation failed: ${message}. You can install manually.`);
1563
1711
  }
1712
+ if (!opts.noHusky) {
1713
+ await initHusky(cwd, pm, opts);
1714
+ }
1564
1715
  }
1565
- function filterDeps(deps, noStylelint, noEditorconfig) {
1716
+ function filterDeps(deps, noStylelint, noEditorconfig, noCspell, noHusky, noLintStaged) {
1566
1717
  let filtered = deps;
1567
1718
  if (noStylelint) filtered = filtered.filter(isNotStylelintDep2);
1568
1719
  if (noEditorconfig) filtered = filtered.filter(isNotEditorconfigDep2);
1720
+ if (noCspell) filtered = filtered.filter(isNotCspellDep);
1721
+ if (noHusky) filtered = filtered.filter(isNotHuskyDep);
1722
+ if (noLintStaged) filtered = filtered.filter(isNotLintStagedDep2);
1569
1723
  return filtered;
1570
1724
  }
1571
1725
  function logGenerationResult(result, dryRun) {
@@ -1628,6 +1782,8 @@ function summarizeFiles(filenames) {
1628
1782
  else if (name.includes("stylelint")) categories.add("stylelint");
1629
1783
  else if (name.includes("cspell")) categories.add("cspell");
1630
1784
  else if (name.includes("editorconfig")) categories.add("editorconfig");
1785
+ else if (name.includes("husky")) categories.add("husky");
1786
+ else if (name.includes("lintstagedrc")) categories.add("lint-staged");
1631
1787
  }
1632
1788
  return [...categories].join(", ");
1633
1789
  }
@@ -1666,6 +1822,58 @@ async function injectScripts(scripts, opts, pm) {
1666
1822
  );
1667
1823
  }
1668
1824
  }
1825
+ async function initHusky(cwd, pm, opts) {
1826
+ const pkgPath = path5.join(cwd, "package.json");
1827
+ const pkg = readJson(pkgPath);
1828
+ if (!pkg) {
1829
+ logger.warn("package.json not found, skipping husky setup");
1830
+ return;
1831
+ }
1832
+ const prefix = getRunPrefix(pm);
1833
+ const isYarn = pm === "yarn";
1834
+ const initScriptName = isYarn ? "postinstall" : "prepare";
1835
+ const hookCommand = opts.noLintStaged ? `${prefix} lint` : `${prefix} lint-staged`;
1836
+ const huskyDir = path5.join(cwd, ".husky");
1837
+ const preCommitPath = path5.join(huskyDir, "pre-commit");
1838
+ if (opts.dryRun) {
1839
+ logger.log(`[dry-run] Would create .husky/pre-commit with: ${hookCommand}`);
1840
+ logger.log(`[dry-run] Would inject "${initScriptName}": "husky" script`);
1841
+ logger.log(`[dry-run] Would run ${prefix} ${initScriptName}`);
1842
+ return;
1843
+ }
1844
+ if (fileExists(preCommitPath) && !opts.force) {
1845
+ logger.log("Skipped .husky/pre-commit (already exists)");
1846
+ } else {
1847
+ ensureDir(huskyDir);
1848
+ writeFile(preCommitPath, `${hookCommand}
1849
+ `);
1850
+ fs3.chmodSync(preCommitPath, 493);
1851
+ }
1852
+ const scripts = pkg.scripts ?? {};
1853
+ if (scripts[initScriptName] !== void 0 && !opts.force) {
1854
+ logger.log(`Skipped script "${initScriptName}" (already exists)`);
1855
+ } else {
1856
+ scripts[initScriptName] = "husky";
1857
+ pkg.scripts = scripts;
1858
+ writeJson(pkgPath, pkg);
1859
+ logger.log(`Injected "${initScriptName}" script for husky`);
1860
+ }
1861
+ logger.log(`Running ${prefix} ${initScriptName} to initialize git hooks...`);
1862
+ try {
1863
+ const args = isYarn ? ["postinstall"] : ["run", initScriptName];
1864
+ const { exitCode } = await execFileNoThrow(pm, args, { cwd });
1865
+ if (exitCode === 0) {
1866
+ logger.success("Husky initialized successfully");
1867
+ } else {
1868
+ logger.warn(`Husky init script exited with code ${exitCode}`);
1869
+ }
1870
+ } catch (error) {
1871
+ const message = error instanceof Error ? error.message : String(error);
1872
+ logger.warn(
1873
+ `Husky init failed: ${message}. You can run "${prefix} ${initScriptName}" manually.`
1874
+ );
1875
+ }
1876
+ }
1669
1877
 
1670
1878
  // src/commands/init.ts
1671
1879
  import { select, isCancel, cancel, outro } from "@clack/prompts";
@@ -2524,7 +2732,10 @@ function materializeAllPresets() {
2524
2732
  force: false,
2525
2733
  dryRun: false,
2526
2734
  noStylelint: false,
2527
- noEditorconfig: false
2735
+ noEditorconfig: false,
2736
+ noCspell: false,
2737
+ noHusky: false,
2738
+ noLintStaged: false
2528
2739
  };
2529
2740
  for (const preset of FMT_PRESETS) {
2530
2741
  materializeFmtPreset(preset.name, preset, opts);
@@ -2786,7 +2997,10 @@ function executeVscodeLocalPath(cwd, presetName, options) {
2786
2997
  force: options.force ?? false,
2787
2998
  dryRun: options.dryRun ?? false,
2788
2999
  noStylelint: options.stylelint !== true,
2789
- noEditorconfig: false
3000
+ noEditorconfig: false,
3001
+ noCspell: false,
3002
+ noHusky: true,
3003
+ noLintStaged: true
2790
3004
  };
2791
3005
  const result = applyLocalVscodePreset(cwd, presetName, opts);
2792
3006
  const files = [...result.created, ...result.overwritten];
@@ -2806,7 +3020,10 @@ function executeVscodeBuiltinPath(cwd, presetName, preset, options) {
2806
3020
  force: options.force ?? false,
2807
3021
  dryRun: options.dryRun ?? false,
2808
3022
  noStylelint: options.stylelint !== true,
2809
- noEditorconfig: false
3023
+ noEditorconfig: false,
3024
+ noCspell: false,
3025
+ noHusky: true,
3026
+ noLintStaged: true
2810
3027
  };
2811
3028
  const result = generateAllVscode(preset, opts);
2812
3029
  const files = [...result.created, ...result.overwritten];