@bensandee/tooling 0.5.0 → 0.6.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.
Files changed (2) hide show
  1. package/dist/bin.mjs +92 -22
  2. package/package.json +2 -2
package/dist/bin.mjs CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { defineCommand, runMain } from "citty";
3
3
  import * as p from "@clack/prompts";
4
+ import { execSync } from "node:child_process";
4
5
  import path from "node:path";
5
6
  import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
6
7
  import { parse } from "jsonc-parser";
7
8
  import { z } from "zod";
8
- import { execSync } from "node:child_process";
9
9
 
10
10
  //#region src/types.ts
11
11
  /** Default CI platform when not explicitly chosen. */
@@ -41,7 +41,7 @@ const TsconfigSchema = z.object({
41
41
  include: z.array(z.string()).optional(),
42
42
  exclude: z.array(z.string()).optional(),
43
43
  files: z.array(z.string()).optional(),
44
- references: z.array(z.object({ path: z.string() })).optional(),
44
+ references: z.array(z.object({ path: z.string() }).passthrough()).optional(),
45
45
  compilerOptions: z.record(z.string(), z.unknown()).optional()
46
46
  }).loose();
47
47
  const RenovateSchema = z.object({
@@ -656,9 +656,16 @@ function generateMigratePrompt(results, config, detected) {
656
656
  sections.push("");
657
657
  for (const r of archived) sections.push(`- \`${r.filePath}\` → \`.tooling-archived/${r.filePath}\``);
658
658
  sections.push("");
659
- sections.push("1. Review the archived files for any custom configuration that should be preserved in the new files");
660
- sections.push("2. If the project previously used `husky` and `lint-staged`, remove them from `devDependencies`");
661
- sections.push("3. Delete the `.tooling-archived/` directory when migration is complete");
659
+ sections.push("For each archived file, **diff the old version against the new one** and look for features, categories, or modules that were enabled in the original but are missing from the replacement. Focus on broad capability gaps rather than individual rule strictness (in general, being stricter is fine). Examples of what to look for:");
660
+ sections.push("");
661
+ sections.push("- **Lint configs**: enabled plugin categories (e.g. `jsx-a11y`, `import`, `react`, `nextjs`), custom `plugins` or `overrides`, file-scoped rule blocks");
662
+ sections.push("- **TypeScript configs**: compiler features like `jsx`, `paths`, `baseUrl`, or `references` that affect build behavior");
663
+ sections.push("- **Other configs**: feature flags, custom presets, or integrations that go beyond the default template");
664
+ sections.push("");
665
+ sections.push("If the old config had capabilities the new one lacks, port them into the new file. Then:");
666
+ sections.push("");
667
+ sections.push("1. If the project previously used `husky` and `lint-staged`, remove them from `devDependencies`");
668
+ sections.push("2. Delete the `.tooling-archived/` directory when migration is complete");
662
669
  sections.push("");
663
670
  }
664
671
  const oxlintWasSkipped = results.find((r) => r.filePath === "oxlint.config.ts")?.action === "skipped";
@@ -1731,6 +1738,15 @@ function buildConfig(formatter) {
1731
1738
  ].join("\n");
1732
1739
  }
1733
1740
  const ARCHIVE_DIR = ".tooling-archived";
1741
+ /** Common client-side git hooks that husky may have configured. */
1742
+ const HUSKY_HOOK_NAMES = [
1743
+ "pre-commit",
1744
+ "commit-msg",
1745
+ "pre-push",
1746
+ "post-merge",
1747
+ "post-checkout",
1748
+ "prepare-commit-msg"
1749
+ ];
1734
1750
  /** All known lint-staged config file locations to archive. */
1735
1751
  const LINT_STAGED_CONFIG_PATHS = [
1736
1752
  "lint-staged.config.mjs",
@@ -1745,21 +1761,28 @@ const LINT_STAGED_CONFIG_PATHS = [
1745
1761
  ];
1746
1762
  /** All known lefthook config file locations, in priority order. */
1747
1763
  const LEFTHOOK_CONFIG_PATHS = ["lefthook.yml", ".lefthook.yml"];
1748
- async function generateLefthook(ctx) {
1749
- const filePath = "lefthook.yml";
1750
- const content = buildConfig(ctx.config.formatter);
1751
- const results = [];
1752
- const huskyPath = ".husky/pre-commit";
1753
- const existingHusky = ctx.read(huskyPath);
1754
- if (existingHusky !== void 0) {
1755
- ctx.write(`${ARCHIVE_DIR}/${huskyPath}`, existingHusky);
1756
- ctx.remove(huskyPath);
1757
- results.push({
1758
- filePath: huskyPath,
1759
- action: "archived",
1760
- description: `Moved to ${ARCHIVE_DIR}/${huskyPath}`
1761
- });
1764
+ /** Archive all husky hook files found in .husky/ */
1765
+ function archiveHuskyHooks(ctx, results) {
1766
+ let found = false;
1767
+ for (const hook of HUSKY_HOOK_NAMES) {
1768
+ const huskyPath = `.husky/${hook}`;
1769
+ const existing = ctx.read(huskyPath);
1770
+ if (existing !== void 0) {
1771
+ ctx.write(`${ARCHIVE_DIR}/${huskyPath}`, existing);
1772
+ ctx.remove(huskyPath);
1773
+ results.push({
1774
+ filePath: huskyPath,
1775
+ action: "archived",
1776
+ description: `Moved to ${ARCHIVE_DIR}/${huskyPath}`
1777
+ });
1778
+ found = true;
1779
+ }
1762
1780
  }
1781
+ return found;
1782
+ }
1783
+ /** Archive all lint-staged config files. */
1784
+ function archiveLintStagedConfigs(ctx, results) {
1785
+ let found = false;
1763
1786
  for (const lsPath of LINT_STAGED_CONFIG_PATHS) {
1764
1787
  const existing = ctx.read(lsPath);
1765
1788
  if (existing !== void 0) {
@@ -1770,8 +1793,47 @@ async function generateLefthook(ctx) {
1770
1793
  action: "archived",
1771
1794
  description: `Moved to ${ARCHIVE_DIR}/${lsPath}`
1772
1795
  });
1796
+ found = true;
1797
+ }
1798
+ }
1799
+ return found;
1800
+ }
1801
+ /** Remove husky/lint-staged from package.json devDependencies and fix prepare script. */
1802
+ function cleanPackageJson(ctx, results) {
1803
+ const raw = ctx.read("package.json");
1804
+ if (!raw) return;
1805
+ const pkg = parsePackageJson(raw);
1806
+ if (!pkg) return;
1807
+ const changes = [];
1808
+ if (pkg.devDependencies) {
1809
+ if ("husky" in pkg.devDependencies) {
1810
+ delete pkg.devDependencies["husky"];
1811
+ changes.push("removed devDependency: husky");
1812
+ }
1813
+ if ("lint-staged" in pkg.devDependencies) {
1814
+ delete pkg.devDependencies["lint-staged"];
1815
+ changes.push("removed devDependency: lint-staged");
1773
1816
  }
1774
1817
  }
1818
+ if (pkg.scripts?.["prepare"] && /\bhusky\b/.test(pkg.scripts["prepare"])) {
1819
+ pkg.scripts["prepare"] = "lefthook install";
1820
+ changes.push("replaced prepare script: husky → lefthook install");
1821
+ }
1822
+ if (changes.length === 0) return;
1823
+ ctx.write("package.json", JSON.stringify(pkg, null, 2) + "\n");
1824
+ results.push({
1825
+ filePath: "package.json",
1826
+ action: "updated",
1827
+ description: changes.join(", ")
1828
+ });
1829
+ }
1830
+ async function generateLefthook(ctx) {
1831
+ const filePath = "lefthook.yml";
1832
+ const content = buildConfig(ctx.config.formatter);
1833
+ const results = [];
1834
+ archiveHuskyHooks(ctx, results);
1835
+ archiveLintStagedConfigs(ctx, results);
1836
+ cleanPackageJson(ctx, results);
1775
1837
  const existingPath = LEFTHOOK_CONFIG_PATHS.find((p) => ctx.exists(p));
1776
1838
  if (existingPath === filePath) {
1777
1839
  const existing = ctx.read(filePath);
@@ -1892,6 +1954,12 @@ async function runInit(config, options = {}) {
1892
1954
  description: `Original saved to .tooling-archived/${rel}`
1893
1955
  });
1894
1956
  s.stop("Done!");
1957
+ if (results.some((r) => r.action === "archived" && r.filePath.startsWith(".husky/"))) try {
1958
+ execSync("git config --unset core.hooksPath", {
1959
+ cwd: config.targetDir,
1960
+ stdio: "ignore"
1961
+ });
1962
+ } catch (_error) {}
1895
1963
  const created = results.filter((r) => r.action === "created");
1896
1964
  const updated = results.filter((r) => r.action === "updated");
1897
1965
  const skipped = results.filter((r) => r.action === "skipped");
@@ -2630,10 +2698,10 @@ function mergeGitHub(dryRun) {
2630
2698
 
2631
2699
  //#endregion
2632
2700
  //#region src/bin.ts
2633
- runMain(defineCommand({
2701
+ const main = defineCommand({
2634
2702
  meta: {
2635
2703
  name: "tooling",
2636
- version: "0.1.0",
2704
+ version: "0.6.0",
2637
2705
  description: "Bootstrap and maintain standardized TypeScript project tooling"
2638
2706
  },
2639
2707
  subCommands: {
@@ -2644,7 +2712,9 @@ runMain(defineCommand({
2644
2712
  "release:create-forgejo-release": createForgejoReleaseCommand,
2645
2713
  "release:merge": releaseMergeCommand
2646
2714
  }
2647
- }));
2715
+ });
2716
+ console.log(`@bensandee/tooling v0.6.0`);
2717
+ runMain(main);
2648
2718
 
2649
2719
  //#endregion
2650
2720
  export { };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bensandee/tooling",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "CLI tool to bootstrap and maintain standardized TypeScript project tooling",
5
5
  "bin": {
6
6
  "tooling": "./dist/bin.mjs"
@@ -31,7 +31,7 @@
31
31
  "tsdown": "0.20.3",
32
32
  "typescript": "5.9.3",
33
33
  "vitest": "4.0.18",
34
- "@bensandee/config": "0.5.0"
34
+ "@bensandee/config": "0.6.0"
35
35
  },
36
36
  "scripts": {
37
37
  "build": "tsdown",