@bensandee/tooling 0.7.3 → 0.8.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 +113 -35
  2. package/package.json +2 -2
package/dist/bin.mjs CHANGED
@@ -160,6 +160,32 @@ function detectProjectType(targetDir) {
160
160
  if (pkg.exports || pkg.main || pkg.types || pkg.typings) return "library";
161
161
  return "default";
162
162
  }
163
+ const WEB_UI_DEPS = new Set([
164
+ "react",
165
+ "react-dom",
166
+ "vue",
167
+ "svelte",
168
+ "solid-js",
169
+ "next",
170
+ "nuxt",
171
+ "@angular/core",
172
+ "preact"
173
+ ]);
174
+ /** Check whether a package.json depends on a web UI framework. */
175
+ function packageHasWebUIDeps(pkg) {
176
+ const deps = {
177
+ ...pkg.dependencies,
178
+ ...pkg.devDependencies
179
+ };
180
+ for (const dep of WEB_UI_DEPS) if (dep in deps) return true;
181
+ return false;
182
+ }
183
+ /** Check whether a package.json in the given directory depends on a web UI framework. */
184
+ function hasWebUIDeps(targetDir) {
185
+ const pkg = readPackageJson(targetDir);
186
+ if (!pkg) return false;
187
+ return packageHasWebUIDeps(pkg);
188
+ }
163
189
  /** List packages in a monorepo's packages/ directory. */
164
190
  function getMonorepoPackages(targetDir) {
165
191
  const packagesDir = path.join(targetDir, "packages");
@@ -805,13 +831,12 @@ function generateMigratePrompt(results, config, detected) {
805
831
  async function generateTsconfig(ctx) {
806
832
  const filePath = "tsconfig.json";
807
833
  const existing = ctx.read(filePath);
808
- if (ctx.config.structure === "monorepo") return [generateMonorepoRootTsconfig(ctx, existing), ...ctx.config.detectPackageTypes ? generateMonorepoPackageTsconfigs(ctx) : []];
834
+ if (ctx.config.structure === "monorepo") return [generateMonorepoRootTsconfig(ctx), ...ctx.config.detectPackageTypes ? generateMonorepoPackageTsconfigs(ctx) : []];
809
835
  const extendsValue = `@bensandee/config/tsconfig/${ctx.config.projectType}`;
810
836
  if (!existing) {
811
837
  const config = {
812
838
  extends: extendsValue,
813
- ...ctx.exists("src") ? { include: ["src"] } : {},
814
- exclude: ["node_modules", "dist"]
839
+ ...ctx.exists("src") ? { include: ["src"] } : {}
815
840
  };
816
841
  ctx.write(filePath, JSON.stringify(config, null, 2) + "\n");
817
842
  return [{
@@ -885,31 +910,18 @@ function mergeSingleTsconfig(ctx, filePath, extendsValue) {
885
910
  description: changes.join(", ")
886
911
  };
887
912
  }
888
- function generateMonorepoRootTsconfig(ctx, existing) {
913
+ function generateMonorepoRootTsconfig(ctx) {
889
914
  const filePath = "tsconfig.json";
890
- if (existing) {
891
- const parsed = parseTsconfig(existing);
892
- if (!parsed.references) {
893
- parsed.files = [];
894
- parsed.references = [];
895
- ctx.write(filePath, JSON.stringify(parsed, null, 2) + "\n");
896
- return {
897
- filePath,
898
- action: "updated",
899
- description: "Added project references structure for monorepo"
900
- };
901
- }
902
- return {
903
- filePath,
904
- action: "skipped",
905
- description: "Already has project references"
906
- };
907
- }
908
- return {
915
+ if (!ctx.read(filePath)) return {
909
916
  filePath,
910
917
  action: "skipped",
911
918
  description: "No tsconfig.json found"
912
919
  };
920
+ return {
921
+ filePath,
922
+ action: "skipped",
923
+ description: "Root tsconfig left as-is"
924
+ };
913
925
  }
914
926
  function generateMonorepoPackageTsconfigs(ctx) {
915
927
  const packages = getMonorepoPackages(ctx.targetDir);
@@ -969,8 +981,7 @@ function generateMonorepoPackageTsconfigs(ctx) {
969
981
  } else {
970
982
  const config = {
971
983
  extends: extendsValue,
972
- ...ctx.exists(path.join(relDir, "src")) ? { include: ["src"] } : {},
973
- exclude: ["node_modules", "dist"]
984
+ ...ctx.exists(path.join(relDir, "src")) ? { include: ["src"] } : {}
974
985
  };
975
986
  ctx.write(filePath, JSON.stringify(config, null, 2) + "\n");
976
987
  results.push({
@@ -1472,7 +1483,9 @@ const ClaudeSettingsSchema = z.object({
1472
1483
  allow: [],
1473
1484
  deny: []
1474
1485
  }),
1475
- instructions: z.array(z.string()).default([])
1486
+ instructions: z.array(z.string()).default([]),
1487
+ enabledPlugins: z.record(z.string(), z.boolean()).default({}),
1488
+ extraKnownMarketplaces: z.record(z.string(), z.record(z.string(), z.unknown())).default({})
1476
1489
  });
1477
1490
  function parseClaudeSettings(raw) {
1478
1491
  try {
@@ -1536,11 +1549,21 @@ function buildSettings(ctx) {
1536
1549
  "Bash(test *)",
1537
1550
  "Bash([ *)",
1538
1551
  "Bash(find *)",
1552
+ "Bash(grep *)",
1539
1553
  "Bash(which *)",
1540
1554
  "Bash(node -e *)",
1541
1555
  "Bash(node -p *)"
1542
1556
  ];
1543
1557
  if (ctx.config.structure === "monorepo") allow.push(`Bash(${pm} --filter *)`, `Bash(${pm} -r *)`);
1558
+ const enabledPlugins = { "code-simplifier@claude-plugins-official": true };
1559
+ const extraKnownMarketplaces = {};
1560
+ if (ctx.config.structure === "monorepo" ? getMonorepoPackages(ctx.targetDir).some((p) => hasWebUIDeps(p.dir)) : ctx.packageJson ? packageHasWebUIDeps(ctx.packageJson) : false) {
1561
+ enabledPlugins["example-skills@anthropic-agent-skills"] = true;
1562
+ extraKnownMarketplaces["anthropic-agent-skills"] = { source: {
1563
+ source: "github",
1564
+ repo: "anthropics/skills"
1565
+ } };
1566
+ }
1544
1567
  return {
1545
1568
  permissions: {
1546
1569
  allow,
@@ -1548,17 +1571,68 @@ function buildSettings(ctx) {
1548
1571
  "Bash(npx *)",
1549
1572
  "Bash(git push *)",
1550
1573
  "Bash(git push)",
1551
- "Bash(git reset --hard *)",
1552
- "Bash(rm -rf *)"
1574
+ "Bash(git add *)",
1575
+ "Bash(git add)",
1576
+ "Bash(git commit *)",
1577
+ "Bash(git commit)",
1578
+ "Bash(git reset *)",
1579
+ "Bash(git merge *)",
1580
+ "Bash(git rebase *)",
1581
+ "Bash(git cherry-pick *)",
1582
+ "Bash(git checkout *)",
1583
+ "Bash(git switch *)",
1584
+ "Bash(git stash *)",
1585
+ "Bash(git tag *)",
1586
+ "Bash(git revert *)",
1587
+ "Bash(git clean *)",
1588
+ "Bash(git rm *)",
1589
+ "Bash(git mv *)",
1590
+ "Bash(rm -rf *)",
1591
+ "Bash(cat *.env)",
1592
+ "Bash(cat *.env.*)",
1593
+ "Bash(cat .env)",
1594
+ "Bash(cat .env.*)",
1595
+ "Bash(head *.env)",
1596
+ "Bash(head *.env.*)",
1597
+ "Bash(head .env)",
1598
+ "Bash(head .env.*)",
1599
+ "Bash(tail *.env)",
1600
+ "Bash(tail *.env.*)",
1601
+ "Bash(tail .env)",
1602
+ "Bash(tail .env.*)",
1603
+ "Bash(less *.env)",
1604
+ "Bash(less *.env.*)",
1605
+ "Bash(less .env)",
1606
+ "Bash(less .env.*)",
1607
+ "Bash(more *.env)",
1608
+ "Bash(more *.env.*)",
1609
+ "Bash(more .env)",
1610
+ "Bash(more .env.*)",
1611
+ "Bash(grep * .env)",
1612
+ "Bash(grep * .env.*)",
1613
+ "Read(.env)",
1614
+ "Read(.env.*)",
1615
+ "Read(*.env)",
1616
+ "Read(*.env.*)"
1553
1617
  ]
1554
1618
  },
1555
1619
  instructions: [
1556
1620
  "Use pnpm, not npm/yarn/npx. Run binaries with `pnpm exec`.",
1557
1621
  "No typecasts (as/any). Use zod schemas, type guards, or narrowing instead.",
1558
1622
  "Fix lint violations instead of suppressing them. Only add disable comments when suppression is genuinely the best option."
1559
- ]
1623
+ ],
1624
+ enabledPlugins,
1625
+ extraKnownMarketplaces
1560
1626
  };
1561
1627
  }
1628
+ /** Remove enabledPlugins/extraKnownMarketplaces when empty to keep the file clean. */
1629
+ function serializeSettings(settings) {
1630
+ const { enabledPlugins, extraKnownMarketplaces, ...rest } = settings;
1631
+ const out = { ...rest };
1632
+ if (Object.keys(enabledPlugins).length > 0) out["enabledPlugins"] = enabledPlugins;
1633
+ if (Object.keys(extraKnownMarketplaces).length > 0) out["extraKnownMarketplaces"] = extraKnownMarketplaces;
1634
+ return JSON.stringify(out, null, 2) + "\n";
1635
+ }
1562
1636
  async function generateClaudeSettings(ctx) {
1563
1637
  const filePath = ".claude/settings.json";
1564
1638
  const existing = ctx.read(filePath);
@@ -1573,7 +1647,10 @@ async function generateClaudeSettings(ctx) {
1573
1647
  const missingAllow = generated.permissions.allow.filter((rule) => !parsed.permissions.allow.includes(rule));
1574
1648
  const missingDeny = generated.permissions.deny.filter((rule) => !parsed.permissions.deny.includes(rule));
1575
1649
  const missingInstructions = generated.instructions.filter((inst) => !parsed.instructions.includes(inst));
1576
- if (missingAllow.length === 0 && missingDeny.length === 0 && missingInstructions.length === 0) return {
1650
+ const missingPlugins = Object.entries(generated.enabledPlugins).filter(([key]) => !(key in parsed.enabledPlugins));
1651
+ const missingMarketplaces = Object.entries(generated.extraKnownMarketplaces).filter(([key]) => !(key in parsed.extraKnownMarketplaces));
1652
+ const added = missingAllow.length + missingDeny.length + missingInstructions.length + missingPlugins.length + missingMarketplaces.length;
1653
+ if (added === 0) return {
1577
1654
  filePath,
1578
1655
  action: "skipped",
1579
1656
  description: "Already has all rules and instructions"
@@ -1581,15 +1658,16 @@ async function generateClaudeSettings(ctx) {
1581
1658
  parsed.permissions.allow = [...parsed.permissions.allow, ...missingAllow];
1582
1659
  parsed.permissions.deny = [...parsed.permissions.deny, ...missingDeny];
1583
1660
  parsed.instructions = [...parsed.instructions, ...missingInstructions];
1584
- const added = missingAllow.length + missingDeny.length + missingInstructions.length;
1585
- ctx.write(filePath, JSON.stringify(parsed, null, 2) + "\n");
1661
+ for (const [key, value] of missingPlugins) parsed.enabledPlugins[key] = value;
1662
+ for (const [key, value] of missingMarketplaces) parsed.extraKnownMarketplaces[key] = value;
1663
+ ctx.write(filePath, serializeSettings(parsed));
1586
1664
  return {
1587
1665
  filePath,
1588
1666
  action: "updated",
1589
1667
  description: `Added ${String(added)} rules/instructions`
1590
1668
  };
1591
1669
  }
1592
- ctx.write(filePath, JSON.stringify(generated, null, 2) + "\n");
1670
+ ctx.write(filePath, serializeSettings(generated));
1593
1671
  return {
1594
1672
  filePath,
1595
1673
  action: "created",
@@ -2985,7 +3063,7 @@ function mergeGitHub(dryRun) {
2985
3063
  const main = defineCommand({
2986
3064
  meta: {
2987
3065
  name: "tooling",
2988
- version: "0.7.3",
3066
+ version: "0.8.0",
2989
3067
  description: "Bootstrap and maintain standardized TypeScript project tooling"
2990
3068
  },
2991
3069
  subCommands: {
@@ -2998,7 +3076,7 @@ const main = defineCommand({
2998
3076
  "release:merge": releaseMergeCommand
2999
3077
  }
3000
3078
  });
3001
- console.log(`@bensandee/tooling v0.7.3`);
3079
+ console.log(`@bensandee/tooling v0.8.0`);
3002
3080
  runMain(main);
3003
3081
 
3004
3082
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bensandee/tooling",
3
- "version": "0.7.3",
3
+ "version": "0.8.0",
4
4
  "description": "CLI tool to bootstrap and maintain standardized TypeScript project tooling",
5
5
  "bin": {
6
6
  "tooling": "./dist/bin.mjs"
@@ -33,7 +33,7 @@
33
33
  "tsdown": "0.20.3",
34
34
  "typescript": "5.9.3",
35
35
  "vitest": "4.0.18",
36
- "@bensandee/config": "0.6.4"
36
+ "@bensandee/config": "0.6.5"
37
37
  },
38
38
  "scripts": {
39
39
  "build": "tsdown",