@bensandee/tooling 0.2.0 → 0.3.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.
- package/dist/bin.mjs +118 -57
- package/package.json +2 -2
package/dist/bin.mjs
CHANGED
|
@@ -449,6 +449,7 @@ const STANDARD_SCRIPTS_MONOREPO = {
|
|
|
449
449
|
};
|
|
450
450
|
/** DevDeps that belong in every project (single repo) or per-package (monorepo). */
|
|
451
451
|
const PER_PACKAGE_DEV_DEPS = {
|
|
452
|
+
"@tsconfig/strictest": "2.0.8",
|
|
452
453
|
"@types/node": "25.3.2",
|
|
453
454
|
tsdown: "0.20.3",
|
|
454
455
|
typescript: "5.9.3",
|
|
@@ -1051,12 +1052,19 @@ const STANDARD_ENTRIES = [
|
|
|
1051
1052
|
"!.env.example",
|
|
1052
1053
|
".tooling-migrate.md"
|
|
1053
1054
|
];
|
|
1055
|
+
/** Normalize a gitignore entry for comparison: strip leading `/` and trailing `/`. */
|
|
1056
|
+
function normalizeEntry(entry) {
|
|
1057
|
+
let s = entry.trim();
|
|
1058
|
+
if (s.startsWith("/")) s = s.slice(1);
|
|
1059
|
+
if (s.endsWith("/")) s = s.slice(0, -1);
|
|
1060
|
+
return s;
|
|
1061
|
+
}
|
|
1054
1062
|
async function generateGitignore(ctx) {
|
|
1055
1063
|
const filePath = ".gitignore";
|
|
1056
1064
|
const existing = ctx.read(filePath);
|
|
1057
1065
|
if (existing) {
|
|
1058
|
-
const
|
|
1059
|
-
const missing = STANDARD_ENTRIES.filter((entry) => !
|
|
1066
|
+
const existingNormalized = new Set(existing.split("\n").map(normalizeEntry).filter((line) => line.length > 0));
|
|
1067
|
+
const missing = STANDARD_ENTRIES.filter((entry) => !existingNormalized.has(normalizeEntry(entry)));
|
|
1060
1068
|
if (missing.length === 0) return {
|
|
1061
1069
|
filePath,
|
|
1062
1070
|
action: "skipped",
|
|
@@ -1158,39 +1166,56 @@ const KNIP_CONFIG_MONOREPO = {
|
|
|
1158
1166
|
}
|
|
1159
1167
|
}
|
|
1160
1168
|
};
|
|
1169
|
+
/** All known knip config file locations, in priority order. */
|
|
1170
|
+
const KNIP_CONFIG_PATHS = [
|
|
1171
|
+
"knip.json",
|
|
1172
|
+
"knip.jsonc",
|
|
1173
|
+
"knip.ts",
|
|
1174
|
+
"knip.mts",
|
|
1175
|
+
"knip.config.ts",
|
|
1176
|
+
"knip.config.mts"
|
|
1177
|
+
];
|
|
1161
1178
|
async function generateKnip(ctx) {
|
|
1162
1179
|
const filePath = "knip.json";
|
|
1163
|
-
const existing = ctx.read(filePath);
|
|
1164
1180
|
const isMonorepo = ctx.config.structure === "monorepo";
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
const
|
|
1168
|
-
if (
|
|
1169
|
-
parsed
|
|
1170
|
-
changes
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
parsed.entry = KNIP_CONFIG_SINGLE.entry;
|
|
1175
|
-
changes.push("added entry patterns");
|
|
1181
|
+
const existingPath = KNIP_CONFIG_PATHS.find((p) => ctx.exists(p));
|
|
1182
|
+
if (existingPath === filePath) {
|
|
1183
|
+
const existing = ctx.read(filePath);
|
|
1184
|
+
if (existing) {
|
|
1185
|
+
const parsed = parseKnipJson(existing);
|
|
1186
|
+
const changes = [];
|
|
1187
|
+
if (isMonorepo && !parsed.workspaces) {
|
|
1188
|
+
parsed.workspaces = KNIP_CONFIG_MONOREPO.workspaces;
|
|
1189
|
+
changes.push("added monorepo workspaces config");
|
|
1176
1190
|
}
|
|
1177
|
-
if (!
|
|
1178
|
-
parsed.
|
|
1179
|
-
|
|
1191
|
+
if (!isMonorepo) {
|
|
1192
|
+
if (!parsed.entry) {
|
|
1193
|
+
parsed.entry = KNIP_CONFIG_SINGLE.entry;
|
|
1194
|
+
changes.push("added entry patterns");
|
|
1195
|
+
}
|
|
1196
|
+
if (!parsed.project) {
|
|
1197
|
+
parsed.project = KNIP_CONFIG_SINGLE.project;
|
|
1198
|
+
changes.push("added project patterns");
|
|
1199
|
+
}
|
|
1180
1200
|
}
|
|
1201
|
+
if (changes.length === 0) return {
|
|
1202
|
+
filePath,
|
|
1203
|
+
action: "skipped",
|
|
1204
|
+
description: "Already configured"
|
|
1205
|
+
};
|
|
1206
|
+
ctx.write(filePath, JSON.stringify(parsed, null, 2) + "\n");
|
|
1207
|
+
return {
|
|
1208
|
+
filePath,
|
|
1209
|
+
action: "updated",
|
|
1210
|
+
description: changes.join(", ")
|
|
1211
|
+
};
|
|
1181
1212
|
}
|
|
1182
|
-
if (changes.length === 0) return {
|
|
1183
|
-
filePath,
|
|
1184
|
-
action: "skipped",
|
|
1185
|
-
description: "Already configured"
|
|
1186
|
-
};
|
|
1187
|
-
ctx.write(filePath, JSON.stringify(parsed, null, 2) + "\n");
|
|
1188
|
-
return {
|
|
1189
|
-
filePath,
|
|
1190
|
-
action: "updated",
|
|
1191
|
-
description: changes.join(", ")
|
|
1192
|
-
};
|
|
1193
1213
|
}
|
|
1214
|
+
if (existingPath) return {
|
|
1215
|
+
filePath: existingPath,
|
|
1216
|
+
action: "skipped",
|
|
1217
|
+
description: `Existing config found at ${existingPath}`
|
|
1218
|
+
};
|
|
1194
1219
|
const config = isMonorepo ? KNIP_CONFIG_MONOREPO : KNIP_CONFIG_SINGLE;
|
|
1195
1220
|
ctx.write(filePath, JSON.stringify(config, null, 2) + "\n");
|
|
1196
1221
|
return {
|
|
@@ -1203,6 +1228,15 @@ async function generateKnip(ctx) {
|
|
|
1203
1228
|
//#endregion
|
|
1204
1229
|
//#region src/generators/renovate.ts
|
|
1205
1230
|
const SHARED_PRESET = "@bensandee/config";
|
|
1231
|
+
/** All known renovate config file locations, in priority order. */
|
|
1232
|
+
const RENOVATE_CONFIG_PATHS = [
|
|
1233
|
+
"renovate.json",
|
|
1234
|
+
"renovate.json5",
|
|
1235
|
+
".renovaterc",
|
|
1236
|
+
".renovaterc.json",
|
|
1237
|
+
".github/renovate.json",
|
|
1238
|
+
".github/renovate.json5"
|
|
1239
|
+
];
|
|
1206
1240
|
async function generateRenovate(ctx) {
|
|
1207
1241
|
const filePath = "renovate.json";
|
|
1208
1242
|
if (!ctx.config.setupRenovate) return {
|
|
@@ -1210,26 +1244,34 @@ async function generateRenovate(ctx) {
|
|
|
1210
1244
|
action: "skipped",
|
|
1211
1245
|
description: "Renovate not requested"
|
|
1212
1246
|
};
|
|
1213
|
-
const
|
|
1214
|
-
if (
|
|
1215
|
-
const
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
existingExtends.
|
|
1219
|
-
|
|
1220
|
-
|
|
1247
|
+
const existingPath = RENOVATE_CONFIG_PATHS.find((p) => ctx.exists(p));
|
|
1248
|
+
if (existingPath === filePath) {
|
|
1249
|
+
const existing = ctx.read(filePath);
|
|
1250
|
+
if (existing) {
|
|
1251
|
+
const parsed = parseRenovateJson(existing);
|
|
1252
|
+
const existingExtends = parsed.extends ?? [];
|
|
1253
|
+
if (!existingExtends.includes(SHARED_PRESET)) {
|
|
1254
|
+
existingExtends.unshift(SHARED_PRESET);
|
|
1255
|
+
parsed.extends = existingExtends;
|
|
1256
|
+
ctx.write(filePath, JSON.stringify(parsed, null, 2) + "\n");
|
|
1257
|
+
return {
|
|
1258
|
+
filePath,
|
|
1259
|
+
action: "updated",
|
|
1260
|
+
description: `Added extends: ${SHARED_PRESET}`
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1221
1263
|
return {
|
|
1222
1264
|
filePath,
|
|
1223
|
-
action: "
|
|
1224
|
-
description:
|
|
1265
|
+
action: "skipped",
|
|
1266
|
+
description: "Already extends shared config"
|
|
1225
1267
|
};
|
|
1226
1268
|
}
|
|
1227
|
-
return {
|
|
1228
|
-
filePath,
|
|
1229
|
-
action: "skipped",
|
|
1230
|
-
description: "Already extends shared config"
|
|
1231
|
-
};
|
|
1232
1269
|
}
|
|
1270
|
+
if (existingPath) return {
|
|
1271
|
+
filePath: existingPath,
|
|
1272
|
+
action: "skipped",
|
|
1273
|
+
description: `Existing config found at ${existingPath}`
|
|
1274
|
+
};
|
|
1233
1275
|
const config = {
|
|
1234
1276
|
$schema: "https://docs.renovatebot.com/renovate-schema.json",
|
|
1235
1277
|
extends: [SHARED_PRESET]
|
|
@@ -1673,28 +1715,47 @@ function buildConfig(formatter) {
|
|
|
1673
1715
|
return `export default {\n "*": "${formatter === "prettier" ? "prettier --write" : "oxfmt"}",\n};\n`;
|
|
1674
1716
|
}
|
|
1675
1717
|
const HUSKY_PRE_COMMIT = "pnpm exec lint-staged\n";
|
|
1718
|
+
/** All known lint-staged config file locations, in priority order. */
|
|
1719
|
+
const LINT_STAGED_CONFIG_PATHS = [
|
|
1720
|
+
"lint-staged.config.mjs",
|
|
1721
|
+
"lint-staged.config.js",
|
|
1722
|
+
"lint-staged.config.cjs",
|
|
1723
|
+
".lintstagedrc",
|
|
1724
|
+
".lintstagedrc.json",
|
|
1725
|
+
".lintstagedrc.yaml",
|
|
1726
|
+
".lintstagedrc.yml",
|
|
1727
|
+
".lintstagedrc.mjs",
|
|
1728
|
+
".lintstagedrc.cjs"
|
|
1729
|
+
];
|
|
1676
1730
|
async function generateLintStaged(ctx) {
|
|
1677
1731
|
const filePath = "lint-staged.config.mjs";
|
|
1678
1732
|
const huskyPath = ".husky/pre-commit";
|
|
1679
1733
|
const content = buildConfig(ctx.config.formatter);
|
|
1680
|
-
const existing = ctx.read(filePath);
|
|
1681
1734
|
if (ctx.read(huskyPath) !== HUSKY_PRE_COMMIT) ctx.write(huskyPath, HUSKY_PRE_COMMIT);
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1735
|
+
const existingPath = LINT_STAGED_CONFIG_PATHS.find((p) => ctx.exists(p));
|
|
1736
|
+
if (existingPath === filePath) {
|
|
1737
|
+
const existing = ctx.read(filePath);
|
|
1738
|
+
if (existing) {
|
|
1739
|
+
if (existing === content) return {
|
|
1740
|
+
filePath,
|
|
1741
|
+
action: "skipped",
|
|
1742
|
+
description: "Already configured"
|
|
1743
|
+
};
|
|
1744
|
+
if (await ctx.confirmOverwrite(filePath) === "skip") return {
|
|
1745
|
+
filePath,
|
|
1746
|
+
action: "skipped",
|
|
1747
|
+
description: "Existing lint-staged config preserved"
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
} else if (existingPath) return {
|
|
1751
|
+
filePath: existingPath,
|
|
1752
|
+
action: "skipped",
|
|
1753
|
+
description: `Existing config found at ${existingPath}`
|
|
1754
|
+
};
|
|
1694
1755
|
ctx.write(filePath, content);
|
|
1695
1756
|
return {
|
|
1696
1757
|
filePath,
|
|
1697
|
-
action:
|
|
1758
|
+
action: existingPath === filePath ? "updated" : "created",
|
|
1698
1759
|
description: "Generated lint-staged config and husky pre-commit hook"
|
|
1699
1760
|
};
|
|
1700
1761
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bensandee/tooling",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "CLI tool to bootstrap and maintain standardized TypeScript project tooling",
|
|
5
5
|
"bin": {
|
|
6
6
|
"tooling": "./dist/bin.mjs"
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"tsdown": "0.20.3",
|
|
36
36
|
"typescript": "5.9.3",
|
|
37
37
|
"vitest": "4.0.18",
|
|
38
|
-
"@bensandee/config": "0.
|
|
38
|
+
"@bensandee/config": "0.3.0"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "tsdown",
|