@bensandee/tooling 0.7.0 → 0.7.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 (2) hide show
  1. package/dist/bin.mjs +122 -54
  2. package/package.json +2 -2
package/dist/bin.mjs CHANGED
@@ -64,6 +64,16 @@ function parseRenovateJson(raw) {
64
64
  return {};
65
65
  }
66
66
  }
67
+ const ChangesetConfigSchema = z.object({ commit: z.union([z.boolean(), z.string()]).optional() }).loose();
68
+ /** Parse a JSON string as a .changeset/config.json. Returns `undefined` on failure. */
69
+ function parseChangesetConfig(raw) {
70
+ try {
71
+ const result = ChangesetConfigSchema.safeParse(JSON.parse(raw));
72
+ return result.success ? result.data : void 0;
73
+ } catch {
74
+ return;
75
+ }
76
+ }
67
77
  /** Parse a JSON string as a package.json. Returns `undefined` on failure. */
68
78
  function parsePackageJson(raw) {
69
79
  try {
@@ -519,7 +529,6 @@ function addReleaseDeps(deps, config) {
519
529
  case "release-it":
520
530
  deps["release-it"] = "18.1.2";
521
531
  if (config.structure === "monorepo") deps["@release-it/bumper"] = "7.0.2";
522
- if (config.ci === "forgejo") deps["@bensandee/release-it-forgejo"] = "0.1.1";
523
532
  break;
524
533
  case "commit-and-tag-version":
525
534
  deps["commit-and-tag-version"] = "12.5.0";
@@ -582,6 +591,9 @@ async function generatePackageJson(ctx) {
582
591
  for (const [key, value] of Object.entries(devDeps)) if (!(key in existingDevDeps)) {
583
592
  existingDevDeps[key] = value;
584
593
  changes.push(`added devDependency: ${key}`);
594
+ } else if (key.startsWith("@bensandee/") && value === "latest" && existingDevDeps[key] !== "latest" && existingDevDeps[key] !== "workspace:*") {
595
+ existingDevDeps[key] = "latest";
596
+ changes.push(`updated devDependency: ${key} to latest`);
585
597
  }
586
598
  pkg.devDependencies = existingDevDeps;
587
599
  if (!pkg["engines"]) {
@@ -793,47 +805,81 @@ async function generateTsconfig(ctx) {
793
805
  const existing = ctx.read(filePath);
794
806
  if (ctx.config.structure === "monorepo") return [generateMonorepoRootTsconfig(ctx, existing), ...ctx.config.detectPackageTypes ? generateMonorepoPackageTsconfigs(ctx) : []];
795
807
  const extendsValue = `@bensandee/config/tsconfig/${ctx.config.projectType}`;
796
- const config = {
797
- extends: extendsValue,
798
- include: ["src"],
799
- exclude: ["node_modules", "dist"]
800
- };
801
- if (existing) {
802
- if (existing.includes("// @bensandee/tooling:ignore")) return [{
808
+ if (!existing) {
809
+ const config = {
810
+ extends: extendsValue,
811
+ include: ["src"],
812
+ exclude: ["node_modules", "dist"]
813
+ };
814
+ ctx.write(filePath, JSON.stringify(config, null, 2) + "\n");
815
+ return [{
803
816
  filePath,
804
- action: "skipped",
805
- description: "Ignored via tooling:ignore comment"
817
+ action: "created",
818
+ description: `Generated tsconfig.json with ${extendsValue}`
806
819
  }];
807
- const parsed = parseTsconfig(existing);
808
- const changes = [];
809
- if (!parsed.extends) {
810
- parsed.extends = extendsValue;
811
- changes.push(`added extends: ${extendsValue}`);
812
- }
813
- const existingInclude = parsed.include ?? [];
814
- for (const entry of config.include) if (!existingInclude.includes(entry)) {
815
- existingInclude.push(entry);
816
- changes.push(`added "${entry}" to include`);
817
- }
818
- parsed.include = existingInclude;
819
- if (changes.length === 0) return [{
820
+ }
821
+ if (existing.includes("// @bensandee/tooling:ignore")) return [{
822
+ filePath,
823
+ action: "skipped",
824
+ description: "Ignored via tooling:ignore comment"
825
+ }];
826
+ const parsed = parseTsconfig(existing);
827
+ if (isSolutionStyle(parsed)) {
828
+ const results = [{
820
829
  filePath,
821
830
  action: "skipped",
822
- description: "Already up to spec"
823
- }];
824
- ctx.write(filePath, JSON.stringify(parsed, null, 2) + "\n");
825
- return [{
826
- filePath,
827
- action: "updated",
828
- description: changes.join(", ")
831
+ description: "Solution-style tsconfig traversing references"
829
832
  }];
833
+ for (const ref of parsed.references ?? []) {
834
+ const refPath = resolveReferencePath(ref.path);
835
+ results.push(mergeSingleTsconfig(ctx, refPath, extendsValue));
836
+ }
837
+ return results;
838
+ }
839
+ return [mergeSingleTsconfig(ctx, filePath, extendsValue)];
840
+ }
841
+ function isSolutionStyle(parsed) {
842
+ return Array.isArray(parsed.references) && parsed.references.length > 0 && Array.isArray(parsed.files) && parsed.files.length === 0;
843
+ }
844
+ function resolveReferencePath(refPath) {
845
+ const resolved = refPath.endsWith(".json") ? refPath : path.join(refPath, "tsconfig.json");
846
+ return path.normalize(resolved);
847
+ }
848
+ function mergeSingleTsconfig(ctx, filePath, extendsValue) {
849
+ const existing = ctx.read(filePath);
850
+ if (!existing) return {
851
+ filePath,
852
+ action: "skipped",
853
+ description: "File not found"
854
+ };
855
+ if (existing.includes("// @bensandee/tooling:ignore")) return {
856
+ filePath,
857
+ action: "skipped",
858
+ description: "Ignored via tooling:ignore comment"
859
+ };
860
+ const parsed = parseTsconfig(existing);
861
+ const changes = [];
862
+ if (!parsed.extends) {
863
+ parsed.extends = extendsValue;
864
+ changes.push(`added extends: ${extendsValue}`);
830
865
  }
831
- ctx.write(filePath, JSON.stringify(config, null, 2) + "\n");
832
- return [{
866
+ const existingInclude = parsed.include ?? [];
867
+ if (!existingInclude.includes("src")) {
868
+ existingInclude.push("src");
869
+ changes.push("added \"src\" to include");
870
+ }
871
+ parsed.include = existingInclude;
872
+ if (changes.length === 0) return {
833
873
  filePath,
834
- action: "created",
835
- description: `Generated tsconfig.json with @bensandee/config/tsconfig/${ctx.config.projectType}`
836
- }];
874
+ action: "skipped",
875
+ description: "Already up to spec"
876
+ };
877
+ ctx.write(filePath, JSON.stringify(parsed, null, 2) + "\n");
878
+ return {
879
+ filePath,
880
+ action: "updated",
881
+ description: changes.join(", ")
882
+ };
837
883
  }
838
884
  function generateMonorepoRootTsconfig(ctx, existing) {
839
885
  const filePath = "tsconfig.json";
@@ -855,14 +901,10 @@ function generateMonorepoRootTsconfig(ctx, existing) {
855
901
  description: "Already has project references"
856
902
  };
857
903
  }
858
- ctx.write(filePath, JSON.stringify({
859
- files: [],
860
- references: []
861
- }, null, 2) + "\n");
862
904
  return {
863
905
  filePath,
864
- action: "created",
865
- description: "Generated monorepo root tsconfig.json with project references"
906
+ action: "skipped",
907
+ description: "No tsconfig.json found"
866
908
  };
867
909
  }
868
910
  function generateMonorepoPackageTsconfigs(ctx) {
@@ -884,6 +926,18 @@ function generateMonorepoPackageTsconfigs(ctx) {
884
926
  continue;
885
927
  }
886
928
  const parsed = parseTsconfig(existing);
929
+ if (isSolutionStyle(parsed)) {
930
+ results.push({
931
+ filePath,
932
+ action: "skipped",
933
+ description: "Solution-style tsconfig — traversing references"
934
+ });
935
+ for (const ref of parsed.references ?? []) {
936
+ const refPath = path.join(relDir, resolveReferencePath(ref.path));
937
+ results.push(mergeSingleTsconfig(ctx, refPath, extendsValue));
938
+ }
939
+ continue;
940
+ }
887
941
  const changes = [];
888
942
  if (parsed.extends !== extendsValue) {
889
943
  const prev = parsed.extends;
@@ -1542,7 +1596,6 @@ function buildConfig$2(ci, isMonorepo) {
1542
1596
  };
1543
1597
  if (ci === "github") config["github"] = { release: true };
1544
1598
  const plugins = {};
1545
- if (ci === "forgejo") plugins["@bensandee/release-it-forgejo"] = { release: true };
1546
1599
  if (isMonorepo) {
1547
1600
  config["npm"] = {
1548
1601
  publish: true,
@@ -2117,14 +2170,26 @@ async function runInit(config, options = {}) {
2117
2170
  p.log.info(`Migration prompt written to ${promptPath}`);
2118
2171
  p.log.info("Paste its contents into Claude Code to finish the migration.");
2119
2172
  }
2120
- const updateCmd = `pnpm update --latest ${getAddedDevDepNames(config).join(" ")}`;
2173
+ const bensandeeDeps = getAddedDevDepNames(config).filter((name) => name.startsWith("@bensandee/"));
2174
+ const hasLockfile = ctx.exists("pnpm-lock.yaml");
2175
+ if (bensandeeDeps.length > 0 && hasLockfile) {
2176
+ s.start("Updating @bensandee/* packages...");
2177
+ try {
2178
+ execSync(`pnpm update --latest ${bensandeeDeps.join(" ")}`, {
2179
+ cwd: config.targetDir,
2180
+ stdio: "ignore"
2181
+ });
2182
+ s.stop("Updated @bensandee/* packages");
2183
+ } catch (_error) {
2184
+ s.stop("Could not update @bensandee/* packages — run pnpm install first");
2185
+ }
2186
+ }
2121
2187
  p.note([
2122
2188
  "1. Run: pnpm install",
2123
- `2. Run: ${updateCmd}`,
2124
- "3. Run: pnpm typecheck",
2125
- "4. Run: pnpm build",
2126
- "5. Run: pnpm test",
2127
- ...options.noPrompt ? [] : ["6. Paste .tooling-migrate.md into Claude Code for cleanup"]
2189
+ "2. Run: pnpm typecheck",
2190
+ "3. Run: pnpm build",
2191
+ "4. Run: pnpm test",
2192
+ ...options.noPrompt ? [] : ["5. Paste .tooling-migrate.md into Claude Code for cleanup"]
2128
2193
  ].join("\n"), "Next steps");
2129
2194
  return results;
2130
2195
  }
@@ -2513,10 +2578,13 @@ async function runVersionMode(executor, config) {
2513
2578
  const changesetConfigPath = path.join(config.cwd, ".changeset", "config.json");
2514
2579
  const originalConfig = executor.readFile(changesetConfigPath);
2515
2580
  if (originalConfig) {
2516
- const parsed = JSON.parse(originalConfig);
2517
- if (parsed.commit) {
2518
- parsed.commit = false;
2519
- executor.writeFile(changesetConfigPath, JSON.stringify(parsed, null, 2) + "\n");
2581
+ const parsed = parseChangesetConfig(originalConfig);
2582
+ if (parsed?.commit) {
2583
+ const patched = {
2584
+ ...parsed,
2585
+ commit: false
2586
+ };
2587
+ executor.writeFile(changesetConfigPath, JSON.stringify(patched, null, 2) + "\n");
2520
2588
  debug(config, "Temporarily disabled changeset commit:true");
2521
2589
  }
2522
2590
  }
@@ -2903,7 +2971,7 @@ function mergeGitHub(dryRun) {
2903
2971
  const main = defineCommand({
2904
2972
  meta: {
2905
2973
  name: "tooling",
2906
- version: "0.7.0",
2974
+ version: "0.7.2",
2907
2975
  description: "Bootstrap and maintain standardized TypeScript project tooling"
2908
2976
  },
2909
2977
  subCommands: {
@@ -2916,7 +2984,7 @@ const main = defineCommand({
2916
2984
  "release:merge": releaseMergeCommand
2917
2985
  }
2918
2986
  });
2919
- console.log(`@bensandee/tooling v0.7.0`);
2987
+ console.log(`@bensandee/tooling v0.7.2`);
2920
2988
  runMain(main);
2921
2989
 
2922
2990
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bensandee/tooling",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
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.2"
36
+ "@bensandee/config": "0.6.3"
37
37
  },
38
38
  "scripts": {
39
39
  "build": "tsdown",